diff --git a/frontend_nuxt/assets/global.css b/frontend_nuxt/assets/global.css index 4f7a3a895..659080bd9 100644 --- a/frontend_nuxt/assets/global.css +++ b/frontend_nuxt/assets/global.css @@ -139,6 +139,9 @@ body { margin-bottom: 0.8em; } +.info-content-text video { + max-width: 100%; +} .info-content-text { word-break: break-word; max-width: 100%; diff --git a/frontend_nuxt/package-lock.json b/frontend_nuxt/package-lock.json index e5f1cd8d8..aeae13297 100644 --- a/frontend_nuxt/package-lock.json +++ b/frontend_nuxt/package-lock.json @@ -15,6 +15,7 @@ "markdown-it": "^14.1.0", "nprogress": "^0.2.0", "nuxt": "latest", + "sanitize-html": "^2.17.0", "sockjs-client": "^1.6.1", "vditor": "^3.11.1", "vue-easy-lightbox": "^1.19.0", @@ -6923,6 +6924,25 @@ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "license": "ISC" }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -7260,6 +7280,15 @@ "node": ">=8" } }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-reference": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", @@ -8874,6 +8903,12 @@ "protocols": "^2.0.0" } }, + "node_modules/parse-srcset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", + "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==", + "license": "MIT" + }, "node_modules/parse-url": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-9.2.0.tgz", @@ -10018,6 +10053,32 @@ "node": ">=10" } }, + "node_modules/sanitize-html": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.17.0.tgz", + "integrity": "sha512-dLAADUSS8rBwhaevT12yCezvioCA+bmUTPH/u57xKPT8d++voeYE6HeluA/bPbQ15TwDBG2ii+QZIEmYx8VdxA==", + "license": "MIT", + "dependencies": { + "deepmerge": "^4.2.2", + "escape-string-regexp": "^4.0.0", + "htmlparser2": "^8.0.0", + "is-plain-object": "^5.0.0", + "parse-srcset": "^1.0.2", + "postcss": "^8.3.11" + } + }, + "node_modules/sanitize-html/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/sax": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", diff --git a/frontend_nuxt/package.json b/frontend_nuxt/package.json index a543255f0..7cb1e2032 100644 --- a/frontend_nuxt/package.json +++ b/frontend_nuxt/package.json @@ -9,20 +9,21 @@ "generate": "nuxt generate" }, "dependencies": { + "@stomp/stompjs": "^7.0.0", "cropperjs": "^1.6.2", "echarts": "^5.6.0", + "flatpickr": "^4.6.13", "highlight.js": "^11.11.1", "ldrs": "^1.0.0", "markdown-it": "^14.1.0", - "nuxt": "latest", "nprogress": "^0.2.0", + "nuxt": "latest", + "sanitize-html": "^2.17.0", + "sockjs-client": "^1.6.1", "vditor": "^3.11.1", "vue-easy-lightbox": "^1.19.0", "vue-echarts": "^7.0.3", - "vue-toastification": "^2.0.0-rc.5", - "flatpickr": "^4.6.13", "vue-flatpickr-component": "^12.0.0", - "@stomp/stompjs": "^7.0.0", - "sockjs-client": "^1.6.1" + "vue-toastification": "^2.0.0-rc.5" } } diff --git a/frontend_nuxt/utils/markdown.js b/frontend_nuxt/utils/markdown.js index 268deffd8..6de79772f 100644 --- a/frontend_nuxt/utils/markdown.js +++ b/frontend_nuxt/utils/markdown.js @@ -1,5 +1,11 @@ +// markdown.js import hljs from 'highlight.js/lib/common' +import MarkdownIt from 'markdown-it' +import sanitizeHtml from 'sanitize-html' +import { toast } from '../main' +import { tiebaEmoji } from './tiebaEmoji' +// 动态切换 hljs 主题(保持你原有逻辑) if (typeof window !== 'undefined') { const theme = document.documentElement.dataset.theme || @@ -10,10 +16,8 @@ if (typeof window !== 'undefined') { import('highlight.js/styles/atom-one-light.css') } } -import MarkdownIt from 'markdown-it' -import { toast } from '../main' -import { tiebaEmoji } from './tiebaEmoji' +/** @section 自定义插件:@mention */ function mentionPlugin(md) { const mentionReg = /^@\[([^\]]+)\]/ function mention(state, silent) { @@ -27,6 +31,7 @@ function mentionPlugin(md) { ['href', `/users/${match[1]}`], ['target', '_blank'], ['class', 'mention-link'], + ['rel', 'noopener noreferrer'], ] const text = state.push('text', '', 0) text.content = `@${match[1]}` @@ -38,6 +43,7 @@ function mentionPlugin(md) { md.inline.ruler.before('emphasis', 'mention', mention) } +/** @section 自定义插件:贴吧表情 :tieba123: */ function tiebaEmojiPlugin(md) { md.renderer.rules['tieba-emoji'] = (tokens, idx) => { const name = tokens[idx].content @@ -60,7 +66,7 @@ function tiebaEmojiPlugin(md) { }) } -// 链接在新窗口打开 +/** @section 链接外开 */ function linkPlugin(md) { const defaultRender = md.renderer.rules.link_open || @@ -74,7 +80,6 @@ function linkPlugin(md) { if (hrefIndex >= 0) { const href = token.attrs[hrefIndex][1] - // 如果是外部链接,添加 target="_blank" 和 rel="noopener noreferrer" if (href.startsWith('http://') || href.startsWith('https://')) { token.attrPush(['target', '_blank']) token.attrPush(['rel', 'noopener noreferrer']) @@ -85,8 +90,9 @@ function linkPlugin(md) { } } +/** @section MarkdownIt 实例:开启 HTML,但配合强净化 */ const md = new MarkdownIt({ - html: false, + html: true, // ⭐ 允许行内 HTML(为