From 66d269566bbc84de91008c3e6c3f09b17f3da2db Mon Sep 17 00:00:00 2001 From: Michael Wedl Date: Tue, 7 Jan 2025 08:50:49 +0100 Subject: [PATCH 1/4] Render MD preview in web worker --- .../src/components/Markdown/Preview.vue | 8 +++--- packages/frontend/src/composables/markdown.ts | 25 +++++++++++++++++++ .../frontend/src/workers/markdownWorker.ts | 10 ++++++++ packages/frontend/src/workers/regexWorker.ts | 5 ++-- 4 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 packages/frontend/src/workers/markdownWorker.ts diff --git a/packages/frontend/src/components/Markdown/Preview.vue b/packages/frontend/src/components/Markdown/Preview.vue index 6dea5578e..d388eabe0 100644 --- a/packages/frontend/src/components/Markdown/Preview.vue +++ b/packages/frontend/src/components/Markdown/Preview.vue @@ -8,9 +8,10 @@ diff --git a/packages/markdown/mdext/index.js b/packages/markdown/mdext/index.js index e736c90d5..40f29b727 100644 --- a/packages/markdown/mdext/index.js +++ b/packages/markdown/mdext/index.js @@ -9,7 +9,7 @@ import 'highlight.js/styles/default.css'; import { remarkFootnotes, remarkToRehypeHandlersFootnotes, remarkToRehypeHandersFootnotesPreview, rehypeFootnoteSeparator, rehypeFootnoteSeparatorPreview } from './footnotes'; import { remarkStrikethrough, remarkTaskListItem } from './gfm'; -import { rehypeConvertAttrsToStyle, rehypeLinkTargetBlank, rehypeRewriteImageSources, rehypeRewriteFileLinks, rehypeTemplates, rehypeRawFixSelfClosingTags, rehypeRawFixPassthroughStitches } from './rehypePlugins'; +import { rehypeConvertAttrsToStyle, rehypeLinkTargetBlank, rehypeRewriteFileUrls, rehypeTemplates, rehypeRawFixSelfClosingTags, rehypeRawFixPassthroughStitches } from './rehypePlugins'; import { remarkAttrs, remarkToRehypeAttrs } from './attrs'; import { remarkFigure, remarkToRehypeHandlersFigure } from './image'; import { remarkTables, remarkTableCaptions, remarkToRehypeHandlersTableCaptions, rehypeTableCaptions } from './tables'; @@ -84,11 +84,10 @@ export function formatMarkdown(text) { /** * Render markdown text to HTML - * @param {{text: string, preview?: boolean, rewriteFileSource?, referenceItems?: {id: string, href: string, label: string}[]}} options + * @param {{text: string, preview?: boolean, referenceItems?: {id: string, href?: string, label?: string}[], rewriteFileUrlMap?: Record, cacheBuster?: string}} options * @returns {string} */ -export function renderMarkdownToHtml({ text = '', preview = false, rewriteFileSource = undefined, referenceItems = undefined} = {}) { - // TODO: rewriteFileSource: dict instead of function, cache buster (optional) +export function renderMarkdownToHtml({ text = '', preview = false, referenceItems = undefined, rewriteFileUrlMap = undefined, cacheBuster = undefined} = {}) { let md = markdownParser() .use(remarkParse) .use(remarkRehype, { @@ -112,9 +111,10 @@ export function renderMarkdownToHtml({ text = '', preview = false, rewriteFileSo .use(rehypeConvertAttrsToStyle) .use(preview ? rehypeFootnoteSeparatorPreview : rehypeFootnoteSeparator) .use(preview ? rehypeReferenceLinkPreview : rehypeReferenceLink, { referenceItems }) - .use(rehypeRewriteImageSources, {rewriteImageSource: rewriteFileSource}) - .use(rehypeRewriteFileLinks, {rewriteFileUrl: rewriteFileSource}) - .use(rehypeLinkTargetBlank); + if (rewriteFileUrlMap) { + md = md.use(rehypeRewriteFileUrls, { rewriteFileUrlMap, cacheBuster }); + } + md = md.use(rehypeLinkTargetBlank) if (preview) { md = md.use(rehypeSanitize, rehypeSanitizeSchema); } diff --git a/packages/markdown/mdext/reference.js b/packages/markdown/mdext/reference.js index c3016b179..2a548f8a8 100644 --- a/packages/markdown/mdext/reference.js +++ b/packages/markdown/mdext/reference.js @@ -35,7 +35,6 @@ export function rehypeReferenceLinkPreview({ referenceItems = undefined }) { let refPreview = null; // Known reference target (e.g. other finding) - // TODO: refactor if (!refPreview && referenceItems) { refPreview = referenceItems.find(item => item.id === refId); } @@ -46,12 +45,12 @@ export function rehypeReferenceLinkPreview({ referenceItems = undefined }) { refTargets[refId].parent.tagName === 'figure' && refTargets[refId].parent.children.some(cn => cn.tagName === 'figcaption')) { refPreview = { - title: `[Figure #${refId}]`, + label: `[Figure #${refId}]`, }; } if (refTargets[refId].node.tagName === 'caption') { refPreview = { - title: `[Table #${refId}]`, + label: `[Table #${refId}]`, }; } } @@ -59,7 +58,7 @@ export function rehypeReferenceLinkPreview({ referenceItems = undefined }) { // Unknown reference target if (!refPreview) { refPreview = { - title: `[Reference to #${refId}]`, + label: `[Reference to #${refId}]`, }; } @@ -68,8 +67,8 @@ export function rehypeReferenceLinkPreview({ referenceItems = undefined }) { if (refPreview.href) { node.properties.href = refPreview.href; } - if (refPreview.title && node.children.length === 0) { - node.children.push({type: 'text', value: refPreview.title}); + if (refPreview.label && node.children.length === 0) { + node.children.push({type: 'text', value: refPreview.label}); } } } diff --git a/packages/markdown/mdext/rehypePlugins.js b/packages/markdown/mdext/rehypePlugins.js index a67efdbd6..285d79b95 100644 --- a/packages/markdown/mdext/rehypePlugins.js +++ b/packages/markdown/mdext/rehypePlugins.js @@ -67,19 +67,27 @@ export function removeClass(node, className) { } -export function rehypeRewriteImageSources({ rewriteImageSource }) { +/** + * Rewrite image source to handle image fetching from markdown. + * Images in markdown are referenced with a URL relative to the parent resource (e.g. "/images/name/image.png"). + */ +export function rehypeRewriteFileUrls({ rewriteFileUrlMap, cacheBuster }) { + function rewriteFileUrl(src) { + for (const [oldPrefix, newPrefix] of Object.entries(rewriteFileUrlMap)) { + if (src.startsWith(oldPrefix)) { + return newPrefix + src.slice(oldPrefix.length) + (cacheBuster ? '?c=' + cacheBuster : ''); + } + } + return src; + } + return tree => visit(tree, 'element', node => { - if (node.tagName === 'img' && node.properties.src && rewriteImageSource) { - node.properties.src = rewriteImageSource(node.properties.src); + if (node.tagName === 'img' && node.properties.src) { + node.properties.src = rewriteFileUrl(node.properties.src); node.properties.loading = 'lazy'; } - }); -} - -export function rehypeRewriteFileLinks({ rewriteFileUrl }) { - return tree => visit(tree, 'element', node => { - if (node.tagName === 'a' && node.properties?.href?.startsWith('/files/') && rewriteFileUrl) { + if (node.tagName === 'a' && node.properties.href) { node.properties.href = rewriteFileUrl(node.properties.href); node.properties.download = true; addClass(node, ['file-download-preview']); From 61486357246dfd0b969f296fd7c79dff06d41dfd Mon Sep 17 00:00:00 2001 From: Michael Wedl Date: Tue, 7 Jan 2025 13:23:26 +0100 Subject: [PATCH 4/4] Bugfixes --- packages/frontend/nuxt.config.ts | 3 +++ packages/frontend/src/components/Markdown/Preview.vue | 2 +- packages/frontend/src/composables/markdown.ts | 1 + .../frontend/src/pages/shared/[shareInfoId]/notes/[noteId].vue | 1 - packages/frontend/src/pages/templates/fromfinding.vue | 2 -- packages/frontend/test/e2e/global-setup.ts | 2 +- packages/markdown/mdext/index.js | 1 - packages/markdown/mdext/rehypePlugins.js | 2 +- packages/rendering/src/components/Markdown.vue | 1 + 9 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/frontend/nuxt.config.ts b/packages/frontend/nuxt.config.ts index ebec66158..0c4009fe3 100644 --- a/packages/frontend/nuxt.config.ts +++ b/packages/frontend/nuxt.config.ts @@ -44,6 +44,9 @@ export default defineNuxtConfig({ port: 3000, }, vite: { + resolve: { + conditions: ['module', 'worker', 'browser', 'development|production'], + }, optimizeDeps: { include: ['vuedraggable', 'monaco-editor', '@github/webauthn-json/browser-ponyfill'], }, diff --git a/packages/frontend/src/components/Markdown/Preview.vue b/packages/frontend/src/components/Markdown/Preview.vue index 1f41a6941..669487e97 100644 --- a/packages/frontend/src/components/Markdown/Preview.vue +++ b/packages/frontend/src/components/Markdown/Preview.vue @@ -10,8 +10,8 @@