Skip to content

Commit 1980474

Browse files
Merge pull request #198 from ThisIs-Developer/fix/markmap-nested-code-blocks
Replace Current Markmap Rendering with Official Markmap Rendering Engine
2 parents f2e0442 + e4004d8 commit 1980474

8 files changed

Lines changed: 972 additions & 196 deletions

File tree

desktop-app/prepare.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,21 @@ async function prepareOfflineDependencies() {
249249
dest: path.join(LIBS_DIR, "mermaid.min.js"),
250250
hash: null
251251
},
252+
{
253+
url: "https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js",
254+
dest: path.join(LIBS_DIR, "d3.min.js"),
255+
hash: null
256+
},
257+
{
258+
url: "https://cdn.jsdelivr.net/npm/markmap-lib@0.18.12/dist/browser/index.iife.js",
259+
dest: path.join(LIBS_DIR, "markmap-lib.iife.js"),
260+
hash: null
261+
},
262+
{
263+
url: "https://cdn.jsdelivr.net/npm/markmap-view@0.18.12/dist/browser/index.js",
264+
dest: path.join(LIBS_DIR, "markmap-view.js"),
265+
hash: null
266+
},
252267
{
253268
url: "https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.2.2/es5/tex-mml-chtml.min.js",
254269
dest: path.join(LIBS_DIR, "tex-mml-chtml.min.js"),

desktop-app/resources/index.html

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<head>
44
<meta charset="UTF-8">
55
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; base-uri 'self'; object-src 'none'; script-src 'self' https://cdnjs.cloudflare.com https://cdn.jsdelivr.net 'sha256-DgMFO4QE+qqf2xNgeNb5gMKG6BtiiQFniYj21c88yME='; worker-src 'self'; connect-src 'self' https://api.github.com https://raw.githubusercontent.com https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://kroki.io https://www.plantuml.com https://mermaid.ink https://paulrosen.github.io; img-src 'self' data: blob: https:; style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com https://cdn.jsdelivr.net; font-src 'self' data: https://cdnjs.cloudflare.com https://cdn.jsdelivr.net; media-src 'self' blob: data:; manifest-src 'self'; upgrade-insecure-requests">
67
<!-- PERF-015: Preload critical-path resources for faster discovery -->
78
<link rel="preload" href="/styles.css" as="style">
89
<link rel="preload" href="script.js" as="script">
@@ -13,13 +14,12 @@
1314
<!-- JSON-LD Structured Data Schema for Search Rich Snippets -->
1415

1516

16-
<title>Markdown Viewer</title>
17+
<title>Markdown Viewer - Secure Online Markdown Editor and Previewer</title>
1718
<link href="/assets/icon.jpg" rel="icon" type="image/jpg">
1819
<!-- Updated libraries to latest versions with Subresource Integrity (SRI) -->
1920
<link rel="stylesheet" href="/libs/bootstrap.min.css" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
2021
<link rel="stylesheet" href="/libs/github-markdown.min.css" integrity="sha384-hZuxRjC/Dsr4zEx1JlUhDQqkvqBPp2VLHsgXfnxPq1ULDy1eIdWCiux7nvO1RIZP" crossorigin="anonymous">
21-
<link rel="preload" href="/libs/bootstrap-icons.min.css" as="style" integrity="sha384-XGjxtQfXaH2tnPFa9x+ruJTuLE3Aa6LhHSWRr1XeTyhezb4abCG4ccI5AkVDxqC+" crossorigin="anonymous" onload="this.onload=null;this.rel='stylesheet'">
22-
<noscript><link rel="stylesheet" href="/libs/bootstrap-icons.min.css" integrity="sha384-XGjxtQfXaH2tnPFa9x+ruJTuLE3Aa6LhHSWRr1XeTyhezb4abCG4ccI5AkVDxqC+" crossorigin="anonymous"></noscript>
22+
<link rel="stylesheet" href="/libs/bootstrap-icons.min.css" integrity="sha384-XGjxtQfXaH2tnPFa9x+ruJTuLE3Aa6LhHSWRr1XeTyhezb4abCG4ccI5AkVDxqC+" crossorigin="anonymous">
2323
<link rel="stylesheet" href="/styles.css">
2424

2525
<!-- Loading order optimized - ensure libraries are loaded asynchronously using defer -->
@@ -973,7 +973,7 @@ <h3 class="modal-section-title">Open-source credits</h3>
973973
</div>
974974
</div>
975975

976-
<script type="text/markdown" id="default-markdown">---
976+
<textarea id="default-markdown" hidden aria-hidden="true">---
977977
title: Welcome to Markdown Viewer
978978
description: A GitHub-style Markdown renderer with live preview, math, diagrams, and export support.
979979
author: ThisIs-Developer
@@ -1140,12 +1140,10 @@ <h3 class="modal-section-title">Open-source credits</h3>
11401140
## 🛡️ Security Note
11411141

11421142
This is a fully client-side application. Your content never leaves your browser and stays secure on your device.
1143-
</script>
1143+
</textarea>
11441144

1145-
<script src="/libs/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
1146-
<script src="/js/neutralino.js"></script>
1147-
<script src="/js/main.js"></script>
1148-
<script src="/js/script.js"></script>
1145+
<script src="/libs/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous" defer></script>
1146+
<script src="script.js" defer></script>
11491147
<!-- Screen reader dynamic accessibility announcer -->
11501148
<div id="app-accessibility-announcer" class="visually-hidden" aria-live="polite" aria-atomic="true"></div>
11511149
</body>

desktop-app/resources/js/preview-worker.js

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -366,14 +366,17 @@ function configureMarked() {
366366
const krokiLanguages = {
367367
'vega-lite': ['vegalite', 'Vega-Lite'],
368368
vegalite: ['vegalite', 'Vega-Lite'],
369-
wavedrom: ['wavedrom', 'WaveDrom'],
370-
markmap: ['markmap', 'Markmap']
369+
wavedrom: ['wavedrom', 'WaveDrom']
371370
};
372371
if (krokiLanguages[language]) {
373372
const [engine, label] = krokiLanguages[language];
374373
const uniqueId = `${engine}-diagram-worker-${krokiIdCounter++}`;
375374
return renderDiagramShell(engine, 'kroki-container', 'kroki-diagram', uniqueId, code, label);
376375
}
376+
if (language === 'markmap') {
377+
const uniqueId = `markmap-diagram-worker-${krokiIdCounter++}`;
378+
return renderDiagramShell('markmap', 'markmap-container', 'markmap-diagram', uniqueId, code, 'Markmap');
379+
}
377380

378381
if (language === "math") {
379382
return `<div class="math-block">$$\n${code}\n$$</div>\n`;
@@ -400,6 +403,72 @@ function configureMarked() {
400403
return `<h${level} id="${id}">${text}</h${level}>`;
401404
};
402405

406+
function normalizeMarkmapFences(markdown) {
407+
const lines = String(markdown || '').split(/\r?\n/);
408+
const output = [];
409+
let index = 0;
410+
411+
while (index < lines.length) {
412+
const opening = lines[index].match(/^([ \t]{0,3})(`{3,}|~{3,})([ \t]*)(.*)$/);
413+
const info = opening ? opening[4].trim() : '';
414+
if (!opening || !/^markmap(?:\s|$)/i.test(info)) {
415+
output.push(lines[index]);
416+
index += 1;
417+
continue;
418+
}
419+
420+
const indent = opening[1];
421+
const fence = opening[2];
422+
const marker = fence[0];
423+
const content = [];
424+
let nestedFence = null;
425+
let maxInnerFenceLength = fence.length;
426+
let closeIndex = -1;
427+
428+
for (let scan = index + 1; scan < lines.length; scan += 1) {
429+
const line = lines[scan];
430+
const fenceMatch = line.match(/^[ \t]{0,3}(`{3,}|~{3,})([ \t]*.*)$/);
431+
if (fenceMatch) {
432+
const currentFence = fenceMatch[1];
433+
const currentMarker = currentFence[0];
434+
const tail = fenceMatch[2].trim();
435+
if (currentMarker === marker) {
436+
maxInnerFenceLength = Math.max(maxInnerFenceLength, currentFence.length);
437+
}
438+
439+
if (nestedFence) {
440+
if (currentMarker === nestedFence.marker && currentFence.length >= nestedFence.length && tail === '') {
441+
nestedFence = null;
442+
}
443+
} else if (currentMarker === marker && currentFence.length >= fence.length && tail === '') {
444+
closeIndex = scan;
445+
break;
446+
} else if (tail !== '') {
447+
nestedFence = {
448+
marker: currentMarker,
449+
length: currentFence.length
450+
};
451+
}
452+
}
453+
content.push(line);
454+
}
455+
456+
if (closeIndex === -1) {
457+
output.push(lines[index]);
458+
index += 1;
459+
continue;
460+
}
461+
462+
const normalizedFence = marker.repeat(maxInnerFenceLength + 1);
463+
output.push(`${indent}${normalizedFence}${opening[3]}${opening[4]}`);
464+
output.push(...content);
465+
output.push(`${indent}${normalizedFence}`);
466+
index = closeIndex + 1;
467+
}
468+
469+
return output.join('\n');
470+
}
471+
403472
marked.use({
404473
extensions: [
405474
blockMathExtension,
@@ -412,7 +481,8 @@ function configureMarked() {
412481
preprocess(markdown) {
413482
if (suppressFootnotePreprocess) return markdown;
414483
resetExtendedMarkdownState();
415-
const protectedMarkdown = markdown.replace(/\\\$/g, "&#36;");
484+
const normalizedMarkdown = normalizeMarkmapFences(markdown);
485+
const protectedMarkdown = normalizedMarkdown.replace(/\\\$/g, "&#36;");
416486
return applyFootnotes(extractFootnoteDefinitions(protectedMarkdown));
417487
},
418488
},
@@ -510,11 +580,12 @@ function splitMarkdownBlocks(markdown) {
510580
}
511581

512582
function renderSegmentedMarkdown(markdown, options) {
513-
if (!isSegmentedPreviewSafe(markdown)) {
583+
const normalizedMarkdown = normalizeMarkmapFences(markdown);
584+
if (!isSegmentedPreviewSafe(normalizedMarkdown)) {
514585
return { mode: "full-required", reason: "unsafe-markdown" };
515586
}
516587

517-
const blocks = splitMarkdownBlocks(markdown);
588+
const blocks = splitMarkdownBlocks(normalizedMarkdown);
518589
if (blocks.length < (options.minimumBlocks || 1)) {
519590
return { mode: "full-required", reason: "too-few-blocks" };
520591
}

0 commit comments

Comments
 (0)