ui: Linting & Formatting scripts (#26819)
This commit is contained in:
@@ -1,85 +1,85 @@
|
||||
<script lang="ts">
|
||||
import '$styles/katex-custom.scss';
|
||||
import {
|
||||
getCodeInfoFromTarget,
|
||||
getHastNodeId,
|
||||
getMdastNodeHash,
|
||||
isAppendMode
|
||||
} from './markdown-utils';
|
||||
import { rehypeEnhanceCodeBlocks } from './plugins/rehype/enhance-code-blocks';
|
||||
import { rehypeEnhanceLinks } from './plugins/rehype/enhance-links';
|
||||
import { rehypeEnhanceMermaidBlocks } from './plugins/rehype/enhance-mermaid-blocks';
|
||||
import { rehypeEnhanceSvgBlocks } from './plugins/rehype/enhance-svg-blocks';
|
||||
import { rehypeFileBadge } from './plugins/rehype/file-badge';
|
||||
import { rehypeMermaidPre } from './plugins/rehype/mermaid-pre';
|
||||
import { rehypeRtlSupport } from './plugins/rehype/rehype-rtl-support';
|
||||
import { rehypeResolveAttachmentImages } from './plugins/rehype/resolve-attachment-images';
|
||||
import { rehypeSvgPre } from './plugins/rehype/svg-pre';
|
||||
import { rehypeRestoreTableHtml } from './plugins/rehype/table-html-restorer';
|
||||
import { remarkLiteralHtml } from './plugins/remark/literal-html';
|
||||
import { browser } from '$app/environment';
|
||||
import {
|
||||
ActionIconCopyToClipboard,
|
||||
CodeBlockActions,
|
||||
DialogCodePreview,
|
||||
DialogMermaidPreview
|
||||
} from '$lib/components/app';
|
||||
import {
|
||||
BOOL_TRUE_STRING,
|
||||
CODE_BLOCK_HEADER_CLASS,
|
||||
DATA_ERROR_BOUND_ATTR,
|
||||
DATA_ERROR_HANDLED_ATTR,
|
||||
DIAGRAM_VIEW_MODE_ATTR,
|
||||
DIAGRAM_VIEW_RENDERED,
|
||||
DIAGRAM_VIEW_SOURCE,
|
||||
IMAGE_NOT_ERROR_BOUND_SELECTOR,
|
||||
MERMAID_BLOCK_CLASS,
|
||||
MERMAID_LANGUAGE,
|
||||
MERMAID_RENDERED_ATTR,
|
||||
MERMAID_SYNTAX_ATTR,
|
||||
MERMAID_WRAPPER_CLASS,
|
||||
SETTINGS_KEYS,
|
||||
SVG_BLOCK_CLASS,
|
||||
SVG_INLINE_SHADOW_STYLE,
|
||||
SVG_LANGUAGE,
|
||||
SVG_RENDERED_ATTR,
|
||||
SVG_SOURCE_ATTR,
|
||||
SVG_TAG_PREFIX,
|
||||
SVG_WRAPPER_CLASS,
|
||||
TOGGLE_SOURCE_BTN_CLASS,
|
||||
XML_LANGUAGE
|
||||
} from '$lib/constants';
|
||||
import { ColorMode, UrlProtocol } from '$lib/enums';
|
||||
import { FileTypeText } from '$lib/enums/files.enums';
|
||||
import { createAutoScrollController } from '$lib/hooks/use-auto-scroll.svelte';
|
||||
import { config } from '$lib/stores/settings.svelte';
|
||||
import type { DatabaseMessageExtra } from '$lib/types/database';
|
||||
import {
|
||||
copyCodeToClipboard,
|
||||
copyToClipboard,
|
||||
getImageErrorFallbackHtml,
|
||||
preprocessLaTeX,
|
||||
splitGluedClosingCodeFences
|
||||
} from '$lib/utils';
|
||||
import { detectIncompleteCodeBlock, highlightCode, type IncompleteCodeBlock } from '$lib/utils';
|
||||
import { sanitizeSvg } from '$lib/utils/sanitize-svg';
|
||||
import { mountSvgShadow } from '$lib/utils/svg-shadow';
|
||||
import type { Root as HastRoot, RootContent as HastRootContent } from 'hast';
|
||||
import githubLightCss from 'highlight.js/styles/github.css?inline';
|
||||
import githubDarkCss from 'highlight.js/styles/github-dark.css?inline';
|
||||
import { all as lowlightAll } from 'lowlight';
|
||||
import type { Root as MdastRoot } from 'mdast';
|
||||
import { mode } from 'mode-watcher';
|
||||
import rehypeHighlight from 'rehype-highlight';
|
||||
import rehypeKatex from 'rehype-katex';
|
||||
import rehypeStringify from 'rehype-stringify';
|
||||
import { remark } from 'remark';
|
||||
import remarkBreaks from 'remark-breaks';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import remarkMath from 'remark-math';
|
||||
import rehypeHighlight from 'rehype-highlight';
|
||||
import { all as lowlightAll } from 'lowlight';
|
||||
import remarkRehype from 'remark-rehype';
|
||||
import rehypeKatex from 'rehype-katex';
|
||||
import rehypeStringify from 'rehype-stringify';
|
||||
import type { Root as HastRoot, RootContent as HastRootContent } from 'hast';
|
||||
import type { Root as MdastRoot } from 'mdast';
|
||||
import { browser } from '$app/environment';
|
||||
import { onDestroy, tick } from 'svelte';
|
||||
import { SvelteMap } from 'svelte/reactivity';
|
||||
import { rehypeRestoreTableHtml } from './plugins/rehype/table-html-restorer';
|
||||
import { rehypeEnhanceLinks } from './plugins/rehype/enhance-links';
|
||||
import { rehypeFileBadge } from './plugins/rehype/file-badge';
|
||||
import { rehypeEnhanceCodeBlocks } from './plugins/rehype/enhance-code-blocks';
|
||||
import { rehypeEnhanceMermaidBlocks } from './plugins/rehype/enhance-mermaid-blocks';
|
||||
import { rehypeMermaidPre } from './plugins/rehype/mermaid-pre';
|
||||
import { rehypeSvgPre } from './plugins/rehype/svg-pre';
|
||||
import { rehypeEnhanceSvgBlocks } from './plugins/rehype/enhance-svg-blocks';
|
||||
import { rehypeResolveAttachmentImages } from './plugins/rehype/resolve-attachment-images';
|
||||
import { rehypeRtlSupport } from './plugins/rehype/rehype-rtl-support';
|
||||
import { remarkLiteralHtml } from './plugins/remark/literal-html';
|
||||
import {
|
||||
getHastNodeId,
|
||||
getMdastNodeHash,
|
||||
isAppendMode,
|
||||
getCodeInfoFromTarget
|
||||
} from './markdown-utils';
|
||||
import {
|
||||
preprocessLaTeX,
|
||||
getImageErrorFallbackHtml,
|
||||
copyCodeToClipboard,
|
||||
copyToClipboard,
|
||||
splitGluedClosingCodeFences
|
||||
} from '$lib/utils';
|
||||
import {
|
||||
IMAGE_NOT_ERROR_BOUND_SELECTOR,
|
||||
DATA_ERROR_BOUND_ATTR,
|
||||
DATA_ERROR_HANDLED_ATTR,
|
||||
BOOL_TRUE_STRING,
|
||||
SETTINGS_KEYS,
|
||||
CODE_BLOCK_HEADER_CLASS,
|
||||
MERMAID_WRAPPER_CLASS,
|
||||
MERMAID_BLOCK_CLASS,
|
||||
MERMAID_LANGUAGE,
|
||||
MERMAID_SYNTAX_ATTR,
|
||||
MERMAID_RENDERED_ATTR,
|
||||
SVG_WRAPPER_CLASS,
|
||||
SVG_BLOCK_CLASS,
|
||||
SVG_LANGUAGE,
|
||||
XML_LANGUAGE,
|
||||
SVG_TAG_PREFIX,
|
||||
SVG_SOURCE_ATTR,
|
||||
SVG_RENDERED_ATTR,
|
||||
SVG_INLINE_SHADOW_STYLE,
|
||||
TOGGLE_SOURCE_BTN_CLASS,
|
||||
DIAGRAM_VIEW_MODE_ATTR,
|
||||
DIAGRAM_VIEW_RENDERED,
|
||||
DIAGRAM_VIEW_SOURCE
|
||||
} from '$lib/constants';
|
||||
import { ColorMode, UrlProtocol } from '$lib/enums';
|
||||
import { FileTypeText } from '$lib/enums/files.enums';
|
||||
import { highlightCode, detectIncompleteCodeBlock, type IncompleteCodeBlock } from '$lib/utils';
|
||||
import { sanitizeSvg } from '$lib/utils/sanitize-svg';
|
||||
import { mountSvgShadow } from '$lib/utils/svg-shadow';
|
||||
import '$styles/katex-custom.scss';
|
||||
import githubDarkCss from 'highlight.js/styles/github-dark.css?inline';
|
||||
import githubLightCss from 'highlight.js/styles/github.css?inline';
|
||||
import { mode } from 'mode-watcher';
|
||||
import {
|
||||
CodeBlockActions,
|
||||
DialogCodePreview,
|
||||
DialogMermaidPreview,
|
||||
ActionIconCopyToClipboard
|
||||
} from '$lib/components/app';
|
||||
import { createAutoScrollController } from '$lib/hooks/use-auto-scroll.svelte';
|
||||
import type { DatabaseMessageExtra } from '$lib/types/database';
|
||||
import { config } from '$lib/stores/settings.svelte';
|
||||
|
||||
interface Props {
|
||||
attachments?: DatabaseMessageExtra[];
|
||||
@@ -94,7 +94,7 @@
|
||||
contentHash?: string;
|
||||
}
|
||||
|
||||
let { content, attachments, class: className = '', disableMath = false }: Props = $props();
|
||||
let { attachments, class: className = '', content, disableMath = false }: Props = $props();
|
||||
|
||||
let containerRef = $state<HTMLDivElement>();
|
||||
let renderedBlocks = $state<MarkdownBlock[]>([]);
|
||||
@@ -102,10 +102,14 @@
|
||||
let incompleteCodeBlock = $state<IncompleteCodeBlock | null>(null);
|
||||
const streamingSvgCode = $derived.by(() => {
|
||||
const block = incompleteCodeBlock;
|
||||
|
||||
if (!block) return null;
|
||||
|
||||
if (block.language === SVG_LANGUAGE) return block.code;
|
||||
|
||||
if (block.language === XML_LANGUAGE && block.code.trimStart().startsWith(SVG_TAG_PREFIX))
|
||||
return block.code;
|
||||
|
||||
return null;
|
||||
});
|
||||
const liveSvgHtml = $derived(streamingSvgCode !== null ? sanitizeSvg(streamingSvgCode) : '');
|
||||
@@ -171,8 +175,8 @@
|
||||
|
||||
return proc
|
||||
.use(rehypeHighlight, {
|
||||
languages: lowlightAll,
|
||||
aliases: { [FileTypeText.XML]: [FileTypeText.SVELTE, FileTypeText.VUE] }
|
||||
aliases: { [FileTypeText.XML]: [FileTypeText.SVELTE, FileTypeText.VUE] },
|
||||
languages: lowlightAll
|
||||
}) // Add syntax highlighting
|
||||
.use(rehypeRestoreTableHtml) // Restore limited HTML (e.g., <br>, <ul>) inside Markdown tables
|
||||
.use(rehypeEnhanceLinks) // Add target="_blank" to links
|
||||
@@ -214,6 +218,7 @@
|
||||
if (!browser) return;
|
||||
|
||||
const existingTheme = document.getElementById(themeStyleId);
|
||||
|
||||
existingTheme?.remove();
|
||||
}
|
||||
|
||||
@@ -226,9 +231,11 @@
|
||||
if (!browser) return;
|
||||
|
||||
const existingTheme = document.getElementById(themeStyleId);
|
||||
|
||||
existingTheme?.remove();
|
||||
|
||||
const style = document.createElement('style');
|
||||
|
||||
style.id = themeStyleId;
|
||||
style.textContent = isDark ? githubDarkCss : githubLightCss;
|
||||
|
||||
@@ -254,19 +261,19 @@
|
||||
index: number
|
||||
): Promise<{ html: string; hash: string }> {
|
||||
const hash = getMdastNodeHash(node, index);
|
||||
|
||||
const cached = transformCache.get(hash);
|
||||
|
||||
if (cached) {
|
||||
return { html: cached, hash };
|
||||
return { hash, html: cached };
|
||||
}
|
||||
|
||||
const singleNodeRoot = { type: 'root', children: [node] };
|
||||
const singleNodeRoot = { children: [node], type: 'root' };
|
||||
const transformedRoot = (await processorInstance.run(singleNodeRoot as MdastRoot)) as HastRoot;
|
||||
const html = processorInstance.stringify(transformedRoot);
|
||||
|
||||
transformCache.set(hash, html);
|
||||
|
||||
return { html, hash };
|
||||
return { hash, html };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -358,6 +365,7 @@
|
||||
unstableBlockHtml = '';
|
||||
incompleteCodeBlock = null;
|
||||
previousContent = '';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -374,7 +382,6 @@
|
||||
const ast = processorInstance.parse(normalizedPrefix) as MdastRoot;
|
||||
const mdastChildren = (ast as { children?: unknown[] }).children ?? [];
|
||||
const nextBlocks: MarkdownBlock[] = [];
|
||||
|
||||
// Check if we're in append mode for cache reuse
|
||||
const appendMode = isAppendMode(prefixMarkdown, previousContent);
|
||||
const previousBlockCount = appendMode ? renderedBlocks.length : 0;
|
||||
@@ -396,13 +403,13 @@
|
||||
}
|
||||
|
||||
// Transform this block (with caching)
|
||||
const { html, hash } = await transformMdastNode(processorInstance, child, index);
|
||||
const { hash, html } = await transformMdastNode(processorInstance, child, index);
|
||||
const id = getHastNodeId(
|
||||
{ position: (child as { position?: unknown }).position } as HastRootContent,
|
||||
index
|
||||
);
|
||||
|
||||
nextBlocks.push({ id, html, contentHash: hash });
|
||||
nextBlocks.push({ contentHash: hash, html, id });
|
||||
}
|
||||
|
||||
renderedBlocks = nextBlocks;
|
||||
@@ -426,7 +433,6 @@
|
||||
const mdastChildren = (ast as { children?: unknown[] }).children ?? [];
|
||||
const stableCount = Math.max(mdastChildren.length - 1, 0);
|
||||
const nextBlocks: MarkdownBlock[] = [];
|
||||
|
||||
// Check if we're in append mode for cache reuse
|
||||
const appendMode = isAppendMode(markdown, previousContent);
|
||||
const previousBlockCount = appendMode ? renderedBlocks.length : 0;
|
||||
@@ -438,6 +444,7 @@
|
||||
if (appendMode && index < previousBlockCount) {
|
||||
const prevBlock = renderedBlocks[index];
|
||||
const currentHash = getMdastNodeHash(child, index);
|
||||
|
||||
if (prevBlock?.contentHash === currentHash) {
|
||||
nextBlocks.push(prevBlock);
|
||||
|
||||
@@ -446,20 +453,20 @@
|
||||
}
|
||||
|
||||
// Transform this block (with caching)
|
||||
const { html, hash } = await transformMdastNode(processorInstance, child, index);
|
||||
const { hash, html } = await transformMdastNode(processorInstance, child, index);
|
||||
const id = getHastNodeId(
|
||||
{ position: (child as { position?: unknown }).position } as HastRootContent,
|
||||
index
|
||||
);
|
||||
|
||||
nextBlocks.push({ id, html, contentHash: hash });
|
||||
nextBlocks.push({ contentHash: hash, html, id });
|
||||
}
|
||||
|
||||
let unstableHtml = '';
|
||||
|
||||
if (mdastChildren.length > stableCount) {
|
||||
const unstableChild = mdastChildren[stableCount];
|
||||
const singleNodeRoot = { type: 'root', children: [unstableChild] };
|
||||
const singleNodeRoot = { children: [unstableChild], type: 'root' };
|
||||
const transformedRoot = (await processorInstance.run(
|
||||
singleNodeRoot as MdastRoot
|
||||
)) as HastRoot;
|
||||
@@ -520,21 +527,24 @@
|
||||
*/
|
||||
async function handleMermaidClick(event: MouseEvent) {
|
||||
const target = event.target as HTMLElement;
|
||||
|
||||
// Toggle a diagram block between its rendered view and its source view.
|
||||
// Shared by mermaid and svg, css drives the visibility from the wrapper mode.
|
||||
const toggleBtn = target.closest(`.${TOGGLE_SOURCE_BTN_CLASS}`);
|
||||
|
||||
if (toggleBtn) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const wrapper = toggleBtn.closest(`.${MERMAID_WRAPPER_CLASS}, .${SVG_WRAPPER_CLASS}`);
|
||||
|
||||
if (!wrapper) return;
|
||||
|
||||
const isSource = wrapper.getAttribute(DIAGRAM_VIEW_MODE_ATTR) === DIAGRAM_VIEW_SOURCE;
|
||||
const next = isSource ? DIAGRAM_VIEW_RENDERED : DIAGRAM_VIEW_SOURCE;
|
||||
|
||||
wrapper.setAttribute(DIAGRAM_VIEW_MODE_ATTR, next);
|
||||
toggleBtn.setAttribute('aria-pressed', String(!isSource));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -544,11 +554,13 @@
|
||||
|
||||
if (copyBtn || previewBtn) {
|
||||
const wrapper = target.closest(`.${MERMAID_WRAPPER_CLASS}`);
|
||||
|
||||
if (!wrapper) return;
|
||||
|
||||
const preElement = wrapper.querySelector<HTMLElement>(
|
||||
`pre.${MERMAID_BLOCK_CLASS}[${MERMAID_SYNTAX_ATTR}]`
|
||||
);
|
||||
|
||||
if (!preElement) return;
|
||||
|
||||
const mermaidSyntax = preElement.getAttribute(MERMAID_SYNTAX_ATTR) ?? '';
|
||||
@@ -561,6 +573,7 @@
|
||||
} catch (error) {
|
||||
console.error('Failed to copy mermaid syntax:', error);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -568,10 +581,13 @@
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const svg = preElement.querySelector('svg');
|
||||
|
||||
if (!svg) return;
|
||||
|
||||
mermaidPreviewSvgHtml = svg.outerHTML;
|
||||
svgPreviewLive = false;
|
||||
mermaidPreviewOpen = true;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -582,11 +598,13 @@
|
||||
|
||||
if (svgCopyBtn || svgPreviewBtn) {
|
||||
const wrapper = target.closest(`.${SVG_WRAPPER_CLASS}`);
|
||||
|
||||
if (!wrapper) return;
|
||||
|
||||
const preElement = wrapper.querySelector<HTMLElement>(
|
||||
`pre.${SVG_BLOCK_CLASS}[${SVG_SOURCE_ATTR}]`
|
||||
);
|
||||
|
||||
if (!preElement) return;
|
||||
|
||||
if (svgCopyBtn) {
|
||||
@@ -597,6 +615,7 @@
|
||||
} catch (error) {
|
||||
console.error('Failed to copy svg source:', error);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -606,6 +625,7 @@
|
||||
mermaidPreviewSvgHtml = sanitizeSvg(preElement.getAttribute(SVG_SOURCE_ATTR) ?? '');
|
||||
svgPreviewLive = false;
|
||||
mermaidPreviewOpen = true;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -618,23 +638,29 @@
|
||||
// Open preview when clicking the svg block itself. A final block carries its
|
||||
// source, a streaming block does not and is mirrored live into the dialog.
|
||||
const svgEl = target.closest(`.${SVG_BLOCK_CLASS}`);
|
||||
|
||||
if (svgEl) {
|
||||
const source = svgEl.getAttribute(SVG_SOURCE_ATTR);
|
||||
|
||||
if (source !== null) {
|
||||
mermaidPreviewSvgHtml = sanitizeSvg(source);
|
||||
svgPreviewLive = false;
|
||||
} else {
|
||||
svgPreviewLive = true;
|
||||
}
|
||||
|
||||
mermaidPreviewOpen = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, open preview when clicking on the mermaid diagram itself
|
||||
const mermaidEl = target.closest(`.${MERMAID_BLOCK_CLASS}`);
|
||||
|
||||
if (!mermaidEl) return;
|
||||
|
||||
const svg = mermaidEl.querySelector('svg');
|
||||
|
||||
if (!svg) return;
|
||||
|
||||
mermaidPreviewSvgHtml = svg.outerHTML;
|
||||
@@ -648,6 +674,7 @@
|
||||
*/
|
||||
function handleMermaidPreviewOpenChange(open: boolean) {
|
||||
mermaidPreviewOpen = open;
|
||||
|
||||
if (!open) {
|
||||
mermaidPreviewSvgHtml = '';
|
||||
svgPreviewLive = false;
|
||||
@@ -666,6 +693,7 @@
|
||||
const nodes = containerRef.querySelectorAll(
|
||||
`pre.${MERMAID_BLOCK_CLASS}:not([${MERMAID_RENDERED_ATTR}])`
|
||||
);
|
||||
|
||||
if (nodes.length === 0) return;
|
||||
|
||||
// Mark nodes immediately to prevent duplicate renders if called again during streaming.
|
||||
@@ -674,24 +702,23 @@
|
||||
|
||||
// Read mode before await so Svelte tracks it reactively.
|
||||
const isDark = mode.current === ColorMode.DARK;
|
||||
|
||||
// lazy load the mermaid dependecy only when needed to reduce bundle size.
|
||||
const { default: mermaid } = await import('mermaid');
|
||||
|
||||
mermaid.initialize({
|
||||
startOnLoad: false,
|
||||
theme: isDark ? 'dark' : 'default',
|
||||
securityLevel: 'strict',
|
||||
flowchart: {
|
||||
useMaxWidth: false,
|
||||
htmlLabels: true
|
||||
},
|
||||
sequence: {
|
||||
htmlLabels: true,
|
||||
useMaxWidth: false
|
||||
},
|
||||
gantt: {
|
||||
useMaxWidth: false
|
||||
}
|
||||
},
|
||||
securityLevel: 'strict',
|
||||
sequence: {
|
||||
useMaxWidth: false
|
||||
},
|
||||
startOnLoad: false,
|
||||
theme: isDark ? 'dark' : 'default'
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -714,6 +741,7 @@
|
||||
const nodes = containerRef.querySelectorAll<HTMLElement>(
|
||||
`pre.${SVG_BLOCK_CLASS}:not([${SVG_RENDERED_ATTR}])`
|
||||
);
|
||||
|
||||
if (nodes.length === 0) return;
|
||||
|
||||
nodes.forEach((node) => {
|
||||
@@ -725,6 +753,7 @@
|
||||
if (clean) {
|
||||
node.textContent = '';
|
||||
const host = document.createElement('div');
|
||||
|
||||
node.appendChild(host);
|
||||
mountSvgShadow(host, clean, SVG_INLINE_SHADOW_STYLE);
|
||||
}
|
||||
@@ -737,6 +766,7 @@
|
||||
*/
|
||||
function handleImageError(event: Event) {
|
||||
const img = event.target as HTMLImageElement;
|
||||
|
||||
if (!img || !img.src) return;
|
||||
|
||||
// Don't handle data URLs or already-handled images
|
||||
@@ -745,11 +775,13 @@
|
||||
img.dataset[DATA_ERROR_HANDLED_ATTR] === BOOL_TRUE_STRING
|
||||
)
|
||||
return;
|
||||
|
||||
img.dataset[DATA_ERROR_HANDLED_ATTR] = BOOL_TRUE_STRING;
|
||||
|
||||
const src = img.src;
|
||||
// Create fallback element
|
||||
const fallback = document.createElement('div');
|
||||
|
||||
fallback.className = 'image-load-error';
|
||||
fallback.innerHTML = getImageErrorFallbackHtml(src);
|
||||
|
||||
@@ -775,6 +807,7 @@
|
||||
try {
|
||||
while (pendingMarkdown !== null) {
|
||||
const nextMarkdown = pendingMarkdown;
|
||||
|
||||
pendingMarkdown = null;
|
||||
|
||||
await processMarkdown(nextMarkdown);
|
||||
|
||||
Reference in New Issue
Block a user