diff --git a/assets/workers/example.worker.js b/assets/workers/example.worker.js new file mode 100644 index 0000000..ce814f4 --- /dev/null +++ b/assets/workers/example.worker.js @@ -0,0 +1,9 @@ +addEventListener('message', (e) => { + const result = heavyTask(e.data) + postMessage(result) +}) +postMessage('Hello World!ABC') +function heavyTask(data) { + // 执行耗时计算 + return data * 2 +} diff --git a/components/Home/Blog/Marked.vue b/components/Home/Blog/Marked.vue index 12fb902..ddd95fb 100644 --- a/components/Home/Blog/Marked.vue +++ b/components/Home/Blog/Marked.vue @@ -3,13 +3,14 @@ import {Marked} from 'marked'; // 默认导入所有语言 import hljs from 'highlight.js'; import 'highlight.js/styles/atom-one-light.css'; -// 注册语言 -// import hljs from 'highlight.js/lib/core'; -// import javascript from 'highlight.js/lib/languages/javascript'; -// hljs.registerLanguage('javascript', javascript); +import DOMPurify from 'dompurify'; +import markedFootnote from 'marked-footnote' +import markedCodeFormat from 'marked-code-format' +import { markedEmoji } from 'marked-emoji'; const marked = new Marked(); + // 自定义行号注入函数 const injectLineNumbers = (highlightedCode: string) => { const lines = highlightedCode.split('\n') @@ -25,24 +26,93 @@ marked.use({ async: true, pedantic: false, gfm: true, - silent: true, + silent: false, + breaks: false, renderer: { code: ({text, lang}) => { - console.log(text); + // console.log(text); const validLang = hljs.getLanguage(lang) ? lang : 'plaintext' const highlighted = hljs.highlight(text, {language: lang}).value - console.log(highlighted) + // console.log(highlighted) const withLineNumbers = injectLineNumbers(highlighted) return `
${withLineNumbers}
`; } } }) +// 支持脚注[^1] 会影响原始文本 +marked.use(markedFootnote()) +marked.use(markedCodeFormat({ + /* Prettier options */ + })) + +const markedEmojiOptions = { + emojis: { + "heart": "❤️", + "tada": "🎉" + }, + renderer: (token) => { + console.log(token) + return token.emoji + } +}; +marked.use(markedEmoji(markedEmojiOptions)) const content = ref(` # Hello Vue 3! +AAA +AAA + +BBB +B + +Hello :smile: + +- [ ] xsxs + +I :heart: marked! :tada: + +## 脚注 + +这是标记[^1] + +[^1]: This is a footnote content. + +Here is a simple footnote[^1]. With some additional[^3] text after it[^@#$%] and without disrupting the blocks[^3]. + +[^2]: The first paragraph of the definition2. + +[^3]: The first paragraph of the definition3. + +[^4]: ABC + **Markdown 内容示例:** +1. 潇洒些 +2. 下洒下阿萨[^4] + +3. 想啊伤心啊伤心啊 + 1. 下洒下阿萨 + 2. 下洒下阿萨 + a. sxaas + b. xsaxa + c. xsaxa + - xasx + - cascade + +1. 潇洒些 +2. 下洒下阿萨 +3. 想啊伤心啊伤心啊 + 1. 下洒下阿萨 + 2. 下洒下阿萨 + a. sxaas + b. xsaxa + c. xsaxa + - xasx + - cascade + +> 引用 + - 列表项 - 另一项 @@ -63,19 +133,95 @@ function greet() { | sanitizer\t | function | \t自定义 HTML 过滤函数 | \t- | | silent\t | boolean | \t静默模式:忽略解析错误(如未闭合的代码块) | \tfalse | `); +// 使用编译 +async function make(contents: string){ + // 过滤不能识别,难识别的字符,空字符 + let content = contents.replace(/^[\u200B\u200C\u200D\u200E\u200F\uFEFF]/,""); + // 编译 + content = await marked.parse(content) + // 加强安全,防止xss攻击 + content = DOMPurify.sanitize(content); + return content +} -const compiledMarkdown = ref(await marked.parse(content.value)); +const compiledMarkdown = ref(await make(content.value)); + +const editableDiv = ref(null); // 绑定可编辑的 div +const rawText = ref(""); // 存储纯文本内容 + +// 实时监听输入事件 +const handleInput = async (event) => { + if (editableDiv.value) { + // 直接获取去标签后的纯文本 + rawText.value = editableDiv.value.innerText.trim().replace(/  /g, ' '); + console.log(rawText.value) + compiledMarkdown.value = await make(rawText.value); + } +}; + +// 手动获取内容(通过按钮触发) +const getContent = () => { + if (editableDiv.value) { + const text = editableDiv.value.innerText.trim(); + console.log("手动获取:", text); + } +}; + +// 处理粘贴内容(自动去除粘贴的 HTML 标签) +const handlePaste = (event) => { + event.preventDefault(); // 阻止默认粘贴行为 + const text = event.clipboardData.getData("text/plain"); // 获取纯文本 + document.execCommand("insertText", false, text); // 插入纯文本 +}; +onMounted(async () => { + + const worker = new Worker(new URL("~/assets/workers/example.worker.js", import.meta.url)); + worker.postMessage("Hello Worker"); + + worker.onmessage = (e) => { + console.log(e) + worker.terminate(); + }; +}) \ No newline at end of file + diff --git a/components/Ni/Markdown.vue b/components/Ni/Markdown.vue new file mode 100644 index 0000000..e4b2faf --- /dev/null +++ b/components/Ni/Markdown.vue @@ -0,0 +1,222 @@ + + + + + diff --git a/components/Ni/Markdown.worker.js b/components/Ni/Markdown.worker.js new file mode 100644 index 0000000..3f134e1 --- /dev/null +++ b/components/Ni/Markdown.worker.js @@ -0,0 +1,201 @@ +import {Marked} from 'marked'; +import hljs from 'highlight.js'; +import markedFootnote from 'marked-footnote' +import markedCodeFormat from 'marked-code-format' +import { markedEmoji } from 'marked-emoji'; + +postMessage({ + type: 'init', + data: 'Hello World!' +}) + + +// 默认编辑器配置 +const defaoltMarkOption = { + async: true, // 同步编译 + pedantic: false, + gfm: true, + silent: true, // 输出错误 + breaks: false, // 将回车编程
+} + +// 自定义行号注入函数 +const injectLineNumbers = (highlightedCode) => { + const lines = highlightedCode.split('\n') + + // 移除最后一行空行(常见于代码块末尾的换行) + if (lines[lines.length - 1] === '') lines.pop() + + // 为每行添加行号容器 + return lines.map((line, index) => `
${index + 1}${line}
`).join('') +} + +// 编辑器render配置 +const editorRenderOption = { + space: (...args) => { + console.log(args, 'space') + return `
space
` + }, + code: (...args) => { + console.log(args, 'code') + return `
xscode
` + }, + blockquote: (...args) => { + console.log(args, 'blockquote') + return `
blockquote
` + }, + html: (...args) => { + console.log(args, 'html') + return `
html
` + }, + heading: (...args) => { + console.log(args[0], 'heading') + return `${args[0].raw}` + }, + hr: (...args) => { + console.log(args, 'hr') + return `
hr
` + }, + list: (...args) => { + console.log(args, 'list') + return `
list
` + }, + listitem: (...args) => { + console.log(args, 'listitem') + return `
listitem
` + }, + checkbox: (...args) => { + console.log(args, 'checkbox') + return `
checkbox
` + }, + paragraph: (...args) => { + console.log(args, 'paragraph') + return `
${args[0].raw}
` + }, + table: (...args) => { + console.log(args, 'table') + return `
table
` + }, + tablerow: (...args) => { + console.log(args, 'tablerow') + return `
tablerow
` + }, + tablecell: (...args) => { + console.log(args, 'tablecell') + return `
tablecell
` + }, + strong: (...args) => { + console.log(args, 'strong') + return `
strong
` + }, + em: (...args) => { + console.log(args, 'em') + return `
em
` + }, + codespan: (...args) => { + console.log(args, 'codespan') + return `
codespan
` + }, + br: (...args) => { + console.log(args, 'br') + return `
br
` + }, + del: (...args) => { + console.log(args, 'del') + return `
del
` + }, + link: (...args) => { + console.log(args, 'link') + return `
link
` + }, + image: (...args) => { + console.log(args, 'image') + return `
image
` + }, + text: (...args) => { + console.log(args, 'text') + return `
text
` + }, +} +// 显示器render配置 +const viewerRenderOption = { + code: ({text, lang}) => { + // console.log(text); + const validLang = hljs.getLanguage(lang) ? lang : 'plaintext' + const highlighted = hljs.highlight(text, {language: lang}).value + // console.log(highlighted) + const withLineNumbers = injectLineNumbers(highlighted) + return `
${withLineNumbers}
`; + } +} + + +// 编辑器Marked编译器 +const editorMarked = new Marked({ + ...defaoltMarkOption, + renderer: editorRenderOption +}); +// 显示器Marked编译器 +const viewerMarked = new Marked({ + ...defaoltMarkOption, + renderer: viewerRenderOption +}); + + +// 编辑器插件 +editorMarked.use(markedCodeFormat()).use() + +// 显示器插件 +// 自定义表情快速插入 +const markedEmojiOptions = { + emojis: { + "heart": "❤️", + "tada": "🎉" + }, + renderer: (token) => { + console.log(token) + return token.emoji + } +}; +// 支持脚注[^1] 会影响原始文本 +viewerMarked.use(markedFootnote()).use(markedCodeFormat()).use(markedEmoji(markedEmojiOptions)) + +addEventListener('message', async (e) => { + console.log(e.data) + switch (e.data.type) { + case 'render': { + // const editContent = await makeEditContent(e.data.content) + const viewContent = await makeViewContent(e.data.content) + postMessage({ + type: 'render', + content: { + viewContent, + // editContent, + }, + }) + break; + } + } +}) + + + +// 函数 + +// 使用编译 +async function makeEditContent(contents){ + // 去除html innerText两端的空格,将 转化为空格 + let content = contents.trim().replace(/  /g, ' '); + // 过滤不能识别,难识别的字符,空字符 + content = content.replace(/^[\u200B\u200C\u200D\u200E\u200F\uFEFF]/,""); + // 编译 + content = await editorMarked.parse(content) + return content +} +async function makeViewContent(contents){ + // 过滤不能识别,难识别的字符,空字符 + let content = contents.replace(/^[\u200B\u200C\u200D\u200E\u200F\uFEFF]/,""); + // 编译 + content = await viewerMarked.parse(content) + return content +} diff --git a/nuxt.config.ts b/nuxt.config.ts index 4609fe7..3e39a5f 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -13,6 +13,10 @@ export default defineNuxtConfig({ esbuildOptions: { target: 'esnext' } + }, + + worker: { + format: 'es' } }, compatibilityDate: '2024-11-01', diff --git a/package-lock.json b/package-lock.json index ad4f78e..554b80c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,9 @@ "highlight.js": "^11.11.1", "jsonwebtoken": "^9.0.2", "marked": "^15.0.11", + "marked-code-format": "^1.1.6", + "marked-emoji": "^2.0.0", + "marked-footnote": "^1.2.4", "mysql2": "^3.14.0", "nuxt": "^3.16.2", "redis": "^4.7.0", @@ -6157,6 +6160,15 @@ "node": ">= 4.0.0" } }, + "node_modules/attributes-parser": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/attributes-parser/-/attributes-parser-2.2.3.tgz", + "integrity": "sha512-zjOUWt95la8AdUO+kP1GBOonWrV5jy9NjJP+z9tva/DSA6FIzGKcN/gk3tdqQf/pOeB8dkyd3FCPrjhELMmrkg==", + "license": "MIT", + "dependencies": { + "json-loose": "^1.2.4" + } + }, "node_modules/autoprefixer": { "version": "10.4.21", "resolved": "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.21.tgz", @@ -7710,7 +7722,7 @@ }, "node_modules/dompurify": { "version": "3.2.5", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.5.tgz", + "resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-3.2.5.tgz", "integrity": "sha512-mLPd29uoRe9HpvwP2TxClGQBzGXeEC/we/q+bFlmPPmj2p2Ugl3r6ATu/UU1v77DXNcehiBg9zsr1dREyA/dJQ==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { @@ -10405,6 +10417,15 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "license": "MIT" }, + "node_modules/json-loose": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/json-loose/-/json-loose-1.2.4.tgz", + "integrity": "sha512-lwMWNC5pvVI33rhYWmAsmtICWE2IH7euDY/iIPeMFE5AuzAifYgqQrjqSMzwbrFV6MWPs41XD+CajElHI4cZMQ==", + "license": "MIT", + "dependencies": { + "moo": "^0.5.2" + } + }, "node_modules/json-schema-to-typescript-lite": { "version": "14.1.0", "resolved": "https://registry.npmmirror.com/json-schema-to-typescript-lite/-/json-schema-to-typescript-lite-14.1.0.tgz", @@ -11403,6 +11424,37 @@ "node": ">= 18" } }, + "node_modules/marked-code-format": { + "version": "1.1.6", + "resolved": "https://registry.npmmirror.com/marked-code-format/-/marked-code-format-1.1.6.tgz", + "integrity": "sha512-DSzefm4DvOOGNdtCx+WekoLyXd8rwcEROSzyydnbJ3rQ2vuE2dKW8cpJui/ulUUHMvyrF9TCq351Fqjd81ocEQ==", + "license": "MIT", + "dependencies": { + "attributes-parser": "^2.2.3" + }, + "peerDependencies": { + "marked": ">=7.0.0", + "prettier": ">=3.0.0" + } + }, + "node_modules/marked-emoji": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/marked-emoji/-/marked-emoji-2.0.0.tgz", + "integrity": "sha512-oTZ8fqbdVDHFQnqCE1tg4ND7zEd7cUVNHliR9Ldu4eys0J86uz/5Uksjd2mt5xcX16OOScDEr3MmPjajI/ZDHA==", + "license": "MIT", + "peerDependencies": { + "marked": ">=4 <16" + } + }, + "node_modules/marked-footnote": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/marked-footnote/-/marked-footnote-1.2.4.tgz", + "integrity": "sha512-DB2Kl+wFh6YwZd70qABMY6WUkG1UuyqoNTFoDfGyG79Pz24neYtLBkB+45a7o72V7gkfvbC3CGzIYFobxfMT1Q==", + "license": "MIT", + "peerDependencies": { + "marked": ">=7.0.0" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -11670,6 +11722,12 @@ "node": ">=14" } }, + "node_modules/moo": { + "version": "0.5.2", + "resolved": "https://registry.npmmirror.com/moo/-/moo-0.5.2.tgz", + "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==", + "license": "BSD-3-Clause" + }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmmirror.com/mrmime/-/mrmime-2.0.1.tgz", @@ -13539,6 +13597,22 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmmirror.com/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "license": "MIT", + "peer": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-bytes": { "version": "6.1.1", "resolved": "https://registry.npmmirror.com/pretty-bytes/-/pretty-bytes-6.1.1.tgz", diff --git a/package.json b/package.json index 5c6d5e8..bb1a21d 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,9 @@ "highlight.js": "^11.11.1", "jsonwebtoken": "^9.0.2", "marked": "^15.0.11", + "marked-code-format": "^1.1.6", + "marked-emoji": "^2.0.0", + "marked-footnote": "^1.2.4", "mysql2": "^3.14.0", "nuxt": "^3.16.2", "redis": "^4.7.0", diff --git a/pages/home/blog.vue b/pages/home/blog.vue index 4487f6e..63ed563 100644 --- a/pages/home/blog.vue +++ b/pages/home/blog.vue @@ -66,7 +66,6 @@ const formatBlogMenuListToPidMap = (blogMenuList: Array) => { const id = menuItem.id; if(pidObj[id]){ menuItem.childrenLength = pidObj[id].length - consola.info(menuItem.name, menuItem.childrenLength) } idMap.set(id, menuItem) } @@ -271,6 +270,7 @@ onMounted(() => { overflow: auto; flex: 1; display: flex; + flex-direction: column; & > header{ position: relative; flex-shrink: 0; diff --git a/pages/home/blog/[blogId].vue b/pages/home/blog/[blogId].vue index 68654e4..cb2c7a6 100644 --- a/pages/home/blog/[blogId].vue +++ b/pages/home/blog/[blogId].vue @@ -1,10 +1,19 @@ @@ -12,5 +21,6 @@ .BlogEntity{ position: relative; height: 100%; + overflow: auto; } diff --git a/测试输入.html b/测试输入.html new file mode 100644 index 0000000..cde8c8d --- /dev/null +++ b/测试输入.html @@ -0,0 +1,23 @@ + + + + + Title + + + +
+ +