diff --git a/package-lock.json b/package-lock.json index 7e44138..8945ef7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,10 @@ { - "name": "markdown_it", + "name": "markdown-it", "lockfileVersion": 3, "requires": true, "packages": { "": { "dependencies": { - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", "dompurify": "^3.1.5", "highlight.js": "^11.9.0", "immer": "^10.1.1", @@ -24,6 +22,8 @@ "@babel/preset-react": "^7.24.7", "@babel/preset-typescript": "^7.24.7", "@types/markdown-it": "^14.1.1", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", "@types/throttle-debounce": "^5.0.2", "babel-loader": "^9.1.3", "webpack": "^5.92.1", @@ -2266,7 +2266,8 @@ "node_modules/@types/prop-types": { "version": "15.7.12", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", - "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" + "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==", + "devOptional": true }, "node_modules/@types/qs": { "version": "6.9.15", @@ -2284,6 +2285,7 @@ "version": "18.3.3", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", + "devOptional": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -2293,6 +2295,7 @@ "version": "18.3.0", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", + "dev": true, "dependencies": { "@types/react": "*" } @@ -3285,7 +3288,8 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "devOptional": true }, "node_modules/debug": { "version": "2.6.9", diff --git a/src/components/code_block.jsx b/src/components/code_block.jsx index 8036c83..2a5f35b 100644 --- a/src/components/code_block.jsx +++ b/src/components/code_block.jsx @@ -100,8 +100,12 @@ export function changeDirectionToColumnWhenLargerHeight() { var msgBlocks = document.querySelectorAll('.mix-message__inner'); Array.from(msgBlocks).forEach(function (block) { var height = block.offsetHeight; - if (height > 30) { + mditLogger('debug', 'Detected messagebox height:', height); + if (height > 35) { block.style.flexDirection = 'column'; } + else { + block.style.flexDirection = 'row'; + } }); } \ No newline at end of file diff --git a/src/render/msgpiece_processor.ts b/src/render/msgpiece_processor.ts deleted file mode 100644 index a698fb0..0000000 --- a/src/render/msgpiece_processor.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { useSettingsStore } from '@/states/settings'; -import { escapeHtml, purifyHtml, unescapeHtml } from '@/utils/htmlProc'; -import { mditLogger } from '@/utils/logger'; - -type ReplaceFunc = (parentElement: HTMLElement, id: string) => any; - -const TEXT_ELEMENT_MATCHER = 'text-element'; -const IMG_ELEMENT_MATCHER = 'pic-element'; - -/** - * Data type used by renderer to determine how to render and replace an element. - */ -export interface MsgProcessInfo { - mark: string; - replace?: ReplaceFunc; - id?: string; -} - -/** - * Function type that used to process children elements inside QQNT message box. - */ -type FragmentProcessFunc = (element: HTMLElement, index: number) => MsgProcessInfo | undefined; - -function textElementProcessor(element: Element): MsgProcessInfo | undefined { - // return undefine if type not match - if (!(element.tagName == 'SPAN') || !element.classList.contains(TEXT_ELEMENT_MATCHER) || element.querySelector('.text-element--at')) { - return undefined; - } - - mditLogger('debug', 'ElementMatch', 'Source', element); - mditLogger('debug', 'Element', 'Match', 'spanTextProcessor'); - - // text processor - let settings = useSettingsStore.getState(); - function entityProcesor(x: string) { - if (settings.unescapeAllHtmlEntites == true) { - return unescapeHtml(x); - } - if (settings.unescapeGtInText == true) { - return x.replaceAll('>', '>'); - } - return x; - } - - return { - mark: Array.from(element.getElementsByTagName("span")) - .map((element) => element.innerHTML) - .reduce((acc, x) => acc + entityProcesor(x), ''), - }; -} - -/** - * This fucked up everything. - */ -const picElementProcessor = replaceFuncGenerator({ filter: (e) => e.classList.contains(IMG_ELEMENT_MATCHER), placeholder: (id) => (` `) }); - -const spanReplaceProcessor = replaceFuncGenerator({ - filter: (e) => ( - e.tagName == 'SPAN' || // deal with span - (e.tagName == 'DIV' && (e.classList?.contains('reply-element') ?? false)) // deal with reply element - ) -}); - - -interface replaceFuncGeneratorProps { - /** - * The generated processort with only deal with elements which this filter returns `true`. - */ - filter: (element: HTMLElement) => boolean, - /** - * Custom function to generate placeholder text based on id. - */ - placeholder?: (id: string) => string, - /** - * Custom replace function. Use defaul one if `undefined`. - */ - replace?: (parent: HTMLElement, id: string) => any, -} - -function replaceFuncGenerator( - props: replaceFuncGeneratorProps, -): FragmentProcessFunc { - let { - filter, - placeholder, - } = props; - placeholder ??= (id) => (``); - - return function (element: HTMLElement, index: number) { - if (!filter(element)) { - return undefined; - } - - let id = `placeholder-${index}`; - let replace = props.replace; - - replace ??= (parent: HTMLElement, id: string) => { - try { - // here oldNode may be `undefined` or `null`. - // Plugin will broke without this try catch block. - mditLogger('debug', 'Try replace oldNode with element:', element); - mditLogger('debug', 'Search placeholder with id', id); - - const oldNode = parent.querySelector(`#${id}`); - mditLogger('debug', 'Old node found', oldNode); - - oldNode.replaceWith(element); - mditLogger('debug', 'Replace success:', element); - } catch (e) { - mditLogger('error', 'Replace failed on element:', element, e); - } - }; - - return { - mark: placeholder(id), - id: id, - replace: replace, - } - } -} - -/** - * Triggered from begin to end, preemptive. - */ -export const processorList: FragmentProcessFunc[] = [ - picElementProcessor, - textElementProcessor, - spanReplaceProcessor, -]; \ No newline at end of file diff --git a/src/render/msgpiece_processor.tsx b/src/render/msgpiece_processor.tsx new file mode 100644 index 0000000..69e1320 --- /dev/null +++ b/src/render/msgpiece_processor.tsx @@ -0,0 +1,249 @@ +// markdown +import React from 'react'; +import markdownIt from 'markdown-it'; +import { renderToString } from 'react-dom/server'; +const hljs = require('highlight.js'); +import katex from '@/lib/markdown-it-katex'; + +// Components +import { HighLightedCodeBlock, renderInlineCodeBlockString } from '@/components/code_block'; + +// Settings +import { useSettingsStore } from '@/states/settings'; + +// Utils +import { escapeHtml, purifyHtml, unescapeHtml } from '@/utils/htmlProc'; +import { mditLogger } from '@/utils/logger'; + + + +type ReplaceFunc = (parentElement: HTMLElement, id: string) => any; + +const TEXT_ELEMENT_MATCHER = 'text-element'; +const IMG_ELEMENT_MATCHER = 'pic-element'; + +const HANDLED_BY_FRAG_PROC_PREFIX = 'markdown-it-handled-as-' + +/** + * Data type used by renderer to determine how to render and replace an element. + */ +export interface MsgProcessInfo { + mark: string; + replace?: ReplaceFunc; + id?: string; +} + +// declare const LiteLoader: LiteLoaderInterFace; +// const markdownRenderedClassName = 'markdown-rendered'; +// const markdownIgnoredPieceClassName = 'mdit-ignored'; +let markdownItIns: markdownIt | undefined = undefined; + +/** + * Function that generates a MarkdownIt instance based on user settings. + */ +function getMarkdownIns() { + const settings = useSettingsStore.getState(); + if (markdownItIns !== undefined) { + return markdownItIns; + } + mditLogger('info', 'Generating new markdown-it renderer...'); + let localMarkdownItIns = markdownIt({ + html: true, // 在源码中启用 HTML 标签 + xhtmlOut: true, // 使用 '/' 来闭合单标签 (比如
)。 + // 这个选项只对完全的 CommonMark 模式兼容。 + breaks: true, // 转换段落里的 '\n' 到
。 + langPrefix: "language-", // 给围栏代码块的 CSS 语言前缀。对于额外的高亮代码非常有用。 + linkify: settings.linkify, // 将类似 URL 的文本自动转换为链接。 + + // 启用一些语言中立的替换 + 引号美化 + typographer: settings.typographer, + + // 双 + 单引号替换对,当 typographer 启用时。 + // 或者智能引号等,可以是 String 或 Array。 + // + // 比方说,你可以支持 '«»„“' 给俄罗斯人使用, '„“‚‘' 给德国人使用。 + // 还有 ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] 给法国人使用(包括 nbsp)。 + quotes: "“”‘’", + + // custom highlight UI renderer for markdown it. + highlight: function (str, lang) { + return (renderToString()); + }, + }).use(katex); + localMarkdownItIns.renderer.rules.code_inline = renderInlineCodeBlockString; + markdownItIns = localMarkdownItIns; + return localMarkdownItIns; +} + + + +/** + * Function type that used to process children elements inside QQNT message box. + */ +type FragmentProcessFunc = (parent: HTMLElement, element: HTMLElement, index: number) => FragmentProcessFuncRetType | undefined; + +interface FragmentProcessFuncRetType { + original: HTMLElement; + rendered: HTMLElement; +} + +/** + * Message fragment processor that deal with all text span in messages. + * @param element + * @returns + */ +const textElementProcessor: FragmentProcessFunc = (parent, element, index) => { + // text processor + let settings = useSettingsStore.getState(); + + // generate rendered HTML processor based on user config. + function renderedHtmlPostProcessor(x: string) { + // text processor + if ((settings.forceEnableHtmlPurify() ?? settings.enableHtmlPurify) === true) { + mditLogger('debug', `Purify`, 'Input:', `${x}`); + return purifyHtml(x); + } + + return x; + } + + // filter to only process pure text messages fragments + if (!(element.tagName == 'SPAN') + || !element.classList.contains(TEXT_ELEMENT_MATCHER) + || element.querySelector('.text-element--at')) { + return undefined; + } + + mditLogger('debug', 'ElementMatch', 'Source', element); + mditLogger('debug', 'Element', 'Match', 'spanTextProcessor'); + + + // entity processor + // determine the HTML enetity escape behaviour based on user settings + function entityProcesor(x: string) { + if (settings.unescapeAllHtmlEntites == true) { + return unescapeHtml(x); + } + if (settings.unescapeGtInText == true) { + return x.replaceAll('>', '>'); + } + return x; + } + + // get all text in this text span + let originalText = Array.from(element.getElementsByTagName("span")) + .map((element) => element.innerHTML) + .reduce((acc, x) => acc + entityProcesor(x), ''); + + // render + let renderedTextElement = element; + renderedTextElement.innerHTML = ( + // first use markdownit to render the html text + // then passed to post processor (post processor also accept text) + renderedHtmlPostProcessor(getMarkdownIns().render(originalText)) + ); + + return { + original: element, + rendered: renderedTextElement, + }; +} + +/** + * This fucked up everything. + */ +// const picElementProcessor = fragProcessFuncGenerator({ filter: (e) => e.classList.contains(IMG_ELEMENT_MATCHER), placeholder: (id) => (` `) }); + +// const spanReplaceProcessor = fragProcessFuncGenerator({ +// filter: (e) => ( +// e.tagName == 'SPAN' || // deal with span +// (e.tagName == 'DIV' && (e.classList?.contains('reply-element') ?? false)) // deal with reply element +// ) +// }); + + +interface FragProcessFuncGeneratorProps { + /** + * The generated processort with only deal with elements which this filter returns `true`. + */ + filter: (element: HTMLElement) => boolean, + /** + * Custom function to generate placeholder text based on id. + */ + placeholder?: (id: string) => string, + /** + * Custom replace function. Use default one if `undefined`. + * + * This function is in charge of replace the old element in the DOM to the new element + * generated by a markdown renderer. + */ + replace?: (parent: HTMLElement, id: string, newElemet: HTMLElement) => any, +} + +/** + * A function generator to quickly generate simple replacer for some certain message span. + * + * Checkout `FragProcessFuncGeneratorProps` for more info. + */ +// function fragProcessFuncGenerator( +// props: FragProcessFuncGeneratorProps, +// ): FragmentProcessFunc { + +// let { +// filter, +// placeholder, +// } = props; +// placeholder ??= (id) => (``); + +// // This is the generated Span Replacer function +// return function (element: HTMLElement, index: number) { +// // element not required the filter condition, do not process +// if (!filter(element)) { +// return undefined; +// } + +// // generate the placeholder for this element +// let id = `placeholder-${index}`; + +// // using a default replace function is not provided +// let replace = props.replace; +// replace ??= (parent: HTMLElement, id: string, newElemet: HTMLElement) => { +// const oldNode = parent.querySelector(`#${id}`); +// mditLogger('debug', 'Old node found', oldNode); +// oldNode.replaceWith(newElemet); +// }; + +// // wrap replace function with logs +// function replaceWithLog(parent: HTMLElement, id: string) { +// try { +// // here oldNode may be `undefined` or `null`. +// // Plugin will broke without this try catch block. +// mditLogger('debug', 'Try replace oldNode with element:', element); +// mditLogger('debug', 'Search placeholder with id', id); + +// // call the specified replace function +// replace(parent, id, element); + +// mditLogger('debug', 'Replace success:', element); +// } catch (e) { +// mditLogger('error', 'Replace failed on element:', element, e); +// } +// } + +// return { +// mark: placeholder(id), +// id: id, +// replace: replaceWithLog, +// } +// } +// } + +/** + * Triggered from begin to end, preemptive. + */ +export const processorList: FragmentProcessFunc[] = [ + // picElementProcessor, + textElementProcessor, + // spanReplaceProcessor, +]; \ No newline at end of file diff --git a/src/renderer.tsx b/src/renderer.tsx index 022f4ae..8aaa7bc 100644 --- a/src/renderer.tsx +++ b/src/renderer.tsx @@ -1,16 +1,14 @@ // 运行在 Electron 渲染进程 下的页面脚本 -const {createRoot} = require("react-dom/client"); +const { createRoot } = require("react-dom/client"); import React from 'react'; -import {renderToString} from 'react-dom/server'; + (React as any).createRoot = createRoot; -import {SettingPage} from "./components/setting_page"; +import { SettingPage } from "./components/setting_page"; const hljs = require('highlight.js'); import markdownIt from 'markdown-it'; -import katex from '@/lib/markdown-it-katex'; -import {escapeHtml, purifyHtml, unescapeHtml} from '@/utils/htmlProc'; // Components import { @@ -20,18 +18,18 @@ import { addOnClickHandleForLatexBlock, changeDirectionToColumnWhenLargerHeight } from './components/code_block'; -import {ShowOriginalContentButton, addShowOriginButtonToMarkdownBody} from '@/components/show_origin'; +import { ShowOriginalContentButton, addShowOriginButtonToMarkdownBody } from '@/components/show_origin'; // States -import {useSettingsStore} from '@/states/settings'; +import { useSettingsStore } from '@/states/settings'; // Utils -import {debounce} from 'throttle-debounce'; -import {mditLogger, elementDebugLogger} from './utils/logger'; -import {processorList} from '@/render/msgpiece_processor'; +import { debounce } from 'throttle-debounce'; +import { mditLogger, elementDebugLogger } from './utils/logger'; +import { MsgProcessInfo, processorList } from '@/render/msgpiece_processor'; // Types -import {LiteLoaderInterFace} from '@/utils/liteloader_type'; +import { LiteLoaderInterFace } from '@/utils/liteloader_type'; declare const LiteLoader: LiteLoaderInterFace; const markdownRenderedClassName = 'markdown-rendered'; @@ -40,46 +38,10 @@ let markdownItIns: markdownIt | undefined = undefined; onLoad(); -/** - * Function that generates a MarkdownIt instance based on user settings. - */ -function generateMarkdownIns() { - const settings = useSettingsStore.getState(); - if (markdownItIns !== undefined) { - return markdownItIns; - } - mditLogger('info', 'Generating new markdown-it renderer...'); - let localMarkdownItIns = markdownIt({ - html: true, // 在源码中启用 HTML 标签 - xhtmlOut: true, // 使用 '/' 来闭合单标签 (比如
)。 - // 这个选项只对完全的 CommonMark 模式兼容。 - breaks: true, // 转换段落里的 '\n' 到
。 - langPrefix: "language-", // 给围栏代码块的 CSS 语言前缀。对于额外的高亮代码非常有用。 - linkify: settings.linkify, // 将类似 URL 的文本自动转换为链接。 - - // 启用一些语言中立的替换 + 引号美化 - typographer: settings.typographer, - - // 双 + 单引号替换对,当 typographer 启用时。 - // 或者智能引号等,可以是 String 或 Array。 - // - // 比方说,你可以支持 '«»„“' 给俄罗斯人使用, '„“‚‘' 给德国人使用。 - // 还有 ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] 给法国人使用(包括 nbsp)。 - quotes: "“”‘’", - - // custom highlight UI renderer for markdown it. - highlight: function (str, lang) { - return (renderToString()); - }, - }).use(katex); - localMarkdownItIns.renderer.rules.code_inline = renderInlineCodeBlockString; - markdownItIns = localMarkdownItIns; - return localMarkdownItIns; -} -const debouncedRender = debounce(50, render, {atBegin: false},); + +const debouncedRender = debounce(50, render, { atBegin: false },); /** * Root markdown render function. @@ -95,10 +57,10 @@ function render() { const elements = document.querySelectorAll(".message-content"); let newlyFoundMsgList = Array.from(elements) - // 跳过已渲染的消息 - .filter((messageBox) => (!messageBox.classList.contains(markdownRenderedClassName))) - // 跳过空消息 - .filter((messageBox) => messageBox.childNodes.length > 0); + // 跳过已渲染的消息 + .filter((messageBox) => (!messageBox.classList.contains(markdownRenderedClassName))) + // 跳过空消息 + .filter((messageBox) => messageBox.childNodes.length > 0); mditLogger('debug', 'Newly found message count:', newlyFoundMsgList.length); @@ -135,86 +97,100 @@ function handleExternalLink(markdownBody: HTMLElement) { async function renderSingleMsgBox(messageBox: HTMLElement) { const settings = useSettingsStore.getState(); - // generate rendered HTML processor based on user config. - function renderedHtmlProcessor(x: string) { - if ((settings.forceEnableHtmlPurify() ?? settings.enableHtmlPurify) === true) { - mditLogger('debug', `Purify`, 'Input:', `${x}`); - return purifyHtml(x); - } - return x; - } - // For more info about Rendered class mark, // checkout: docs/dev/msg_rendering_process.md // skip rendered message if (messageBox.classList.contains(markdownRenderedClassName)) { return; } + // mark the current message as rendered messageBox.classList.add(markdownRenderedClassName); // original innerHTML for message box. + // This is captured and used by "Show Original" feature. let msgBoxOriginalInnerHTML = messageBox.innerHTML; // Get all children of message box. Return if length is zero. - const spanElem = Array.from(messageBox.children); - mditLogger('debug', 'renderSingleMsgBox', 'SpanElem:', spanElem); - if (spanElem.length == 0) return; + const originalSpanList = Array.from(messageBox.children); + mditLogger('debug', 'renderSingleMsgBox', 'originalSpanList:', originalSpanList); + if (originalSpanList.length == 0) return; // used as pivot when we're inserting rendered elements later. - const posBase = document.createElement('span') - spanElem[0].before(posBase); + // const posBase = document.createElement('span') + // originalSpanList[0].before(posBase); // Here using entityProcess which may finally call DOMParser().parseFromString(input, "text/html"); // This may introduce XSS attack vulnerability, however, we will use DOMPurify to prevent all // dangerous HTML elements when rendering markdown. - const markPieces = spanElem.map((msgPiece, index) => { - mditLogger('debug', 'PieceProcessor', 'index:', index); - mditLogger('debug', 'PieceProcessor', 'Original Piece:', msgPiece); - let retInfo = undefined; + + + // use fragment processors to deal with the span in messages one by one + // finally, we will get a list of rendered span + const renderedSpanInfo = originalSpanList.map((msgSpan, index) => { + mditLogger('debug', 'PieceProcessor', 'Original Piece:', msgSpan); // Try to apply piece processor in order. Stop once a processor could process current msgPiece - processorList.some(function (procFunc) { - let funcRet = procFunc((msgPiece as HTMLElement), index); - retInfo = funcRet; + for (let processor of processorList) { + // try get the return value of the processor + let renderedSpan = processor(messageBox, (msgSpan as HTMLElement), index); + // if processor returned a non-undefined value, use the new element + if (renderedSpan !== undefined) { + return renderedSpan; + } + } - return retInfo !== undefined; - }); + // here means no any frag processor could handle this msgSpan, just return itself, + // in other word, keep it's original looks. + return { original: msgSpan, rendered: msgSpan }; // if undefined, this element should be ignored and not be removed in later process. - if (retInfo === undefined) { - msgPiece.classList.add(markdownIgnoredPieceClassName); - } - mditLogger('debug', 'PieceProcessor', 'Piece processor return:', retInfo); - return retInfo; + // if (retInfo === undefined) { + // msgPiece.classList.add(markdownIgnoredPieceClassName); + // } + // mditLogger('debug', 'PieceProcessor', 'Piece processor return:', retInfo); + // return retInfo; }); + mditLogger('debug', 'RenderedList generated, start replacing messagebox children...'); + + // replace the children based on rendered info + for (let renderedInfo of renderedSpanInfo) { + mditLogger('debug', 'Try to replace:', renderedInfo); + let originalIsChildren = originalSpanList.some((e) => e === renderedInfo.original); + // mditLogger('debug', 'Original element in messageBox:', originalIsChildren); + messageBox.replaceChild(renderedInfo.rendered, renderedInfo.original); + } + + // 渲染 markdown - const marks = markPieces.filter(p => p !== undefined).map((p) => p.mark).reduce((acc, p) => acc + p, ""); - mditLogger('debug', 'MarkdownRender Input:', marks); - let renderedHtml = renderedHtmlProcessor(await generateMarkdownIns().render(marks)); - mditLogger('debug', 'MarkdownRender Output:', renderedHtml) + // const marks = markPieces.filter(p => p !== undefined).map((p) => p.mark).reduce((acc, p) => acc + p, ""); + // mditLogger('debug', 'MarkdownRender Input:', marks); + // let renderedHtml = renderedHtmlProcessor(await generateMarkdownIns().render(marks)); + // mditLogger('debug', 'MarkdownRender Output:', renderedHtml); // 移除旧元素 - spanElem - .filter((e) => messageBox.hasChildNodes()) - .forEach((e) => { - // do not remove formerly ignored elements - if (e.classList.contains(markdownIgnoredPieceClassName)) { - mditLogger('debug', 'Remove Ignore Triggered:', e); - return; - } - messageBox.removeChild(e); - }); - - // 将原有元素替换回内容 - const markdownBody = document.createElement('div'); - // some themes rely on this class to render - markdownBody.innerHTML = `
${renderedHtml}
`; - markPieces.filter((p) => (p?.replace !== undefined)) - .forEach((p) => { - p.replace(markdownBody, p.id); - }); + // originalSpanList + // .filter((e) => messageBox.hasChildNodes()) + // .forEach((e) => { + // // do not remove formerly ignored elements + // if (e.classList.contains(markdownIgnoredPieceClassName)) { + // mditLogger('debug', 'Remove Ignore Triggered:', e); + // return; + // } + // messageBox.removeChild(e); + // }); + + // // 将原有元素替换回内容 + // const markdownBody = document.createElement('div'); + // // some themes rely on this class to render + // markdownBody.innerHTML = `
${renderedHtml}
`; + // markPieces.filter((p) => (p?.replace !== undefined)) + // .forEach((p) => { + // p.replace(markdownBody, p.id); + // }); + + let markdownBody = messageBox; // Handle click of Copy Code Button addOnClickHandleForCopyButton(markdownBody); @@ -227,13 +203,6 @@ async function renderSingleMsgBox(messageBox: HTMLElement) { // Add ShowOriginalContent button for this message. addShowOriginButtonToMarkdownBody(markdownBody, messageBox, msgBoxOriginalInnerHTML); - - // 放回内容 - Array.from(markdownBody.childNodes) - .forEach((elem) => { - posBase.before(elem) - }) - messageBox.removeChild(posBase); } function _onLoad() { @@ -268,7 +237,7 @@ function _onLoad() { } } }); - observer.observe(document.body, {childList: true, subtree: true}); + observer.observe(document.body, { childList: true, subtree: true }); } /** diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 45a3e2c..826c004 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -17,6 +17,11 @@ type LoggerFuncKey = { [K in keyof Console]: Console[K] extends (args: any) => any ? K : never; }[keyof Console]; +/** + * String literal unions of the supported logger function type. + * + * `DistributiveFilter` will filtered out all options that actually not a valid function of console. + */ export type SupportedLoggerFuncKey = DistributiveFilter; function outputToConsoleSettingEnabled() { @@ -43,9 +48,16 @@ const defaultMditLoggerOptions: MditLoggerOptions = { fileOutput: true, } +/** + * Logger function generator. Could generate log functions based on some configurations to + * fit different use cases. + * @param options `MditLoggerOptions` + */ export function mditLoggerGenerator(options: MditLoggerOptions = defaultMditLoggerOptions): (consoleFunction: SupportedLoggerFuncKey, ...params: any[]) => (undefined) { return function (consoleFunction: SupportedLoggerFuncKey, ...params: any[]) { + + // if user enabled console output in settings, and the console output config of this logger is on if (outputToConsoleSettingEnabled() && options.consoleOutput) { console[consoleFunction]( '%c [MarkdownIt] ',