Skip to content

Commit 2edef6e

Browse files
fix: resolve post-PR#116 regressions in markdown rendering, mermaid, and offline desktop mode
BREAKING FIXES: - Fix ReferenceError: Defer marked.Renderer instantiation and marked.use() configuration into initializeMarked(), called only after core libraries (marked, hljs, DOMPurify) are lazy-loaded. Prevents synchronous access to undefined globals on page load. - Fix Mermaid v11 compatibility: Replace deprecated m.init() with m.run() API, matching Mermaid v11.6.0 shipped via CDN. - Add Neutralino offline routing: loadDependency() and loadStylesheet() now detect the Neutralino desktop runtime and redirect CDN URLs to local /libs/<filename> paths, skipping SRI integrity checks that would fail on local file:// or localhost origins. - Extend prepare.js: Scan script.js for CDN URLs in addition to index.html, ensuring all dynamically loaded dependencies are downloaded to resources/libs/ for offline desktop builds. - Add network resilience: ensureEmoji(), ensurePako(), and ensurePdfExportDependencies() now clear their cached promise on failure, allowing retry on subsequent calls. - Optimize PDF export: Remove unnecessary pdfmake/vfs_fonts/html2pdf from ensurePdfExportDependencies() since only jspdf + html2canvas are used by the active export pipeline.
1 parent a914d4b commit 2edef6e

2 files changed

Lines changed: 106 additions & 61 deletions

File tree

desktop-app/prepare.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,16 +71,19 @@ function downloadFile(url, destPath) {
7171
async function prepareOfflineDependencies() {
7272
console.log("\nStarting Offline Assets Preparation...");
7373
let html = fs.readFileSync(path.join(ROOT_DIR, "index.html"), "utf-8");
74+
const scriptJS = fs.readFileSync(path.join(ROOT_DIR, "script.js"), "utf-8");
7475

75-
// Find all CDN script and link tags
76+
// Find all CDN script and link tags in HTML
7677
const cdnRegex = /(href|src)="(https:\/\/(?:cdnjs\.cloudflare\.com|cdn\.jsdelivr\.net)\/[^"]+)"/g;
7778
let match;
7879
const downloads = [];
7980
const replacements = [];
81+
const foundUrls = new Set();
8082

8183
while ((match = cdnRegex.exec(html)) !== null) {
8284
const attr = match[1];
8385
const url = match[2];
86+
foundUrls.add(url);
8487

8588
// Determine local filename - sanitize package version tags or query strings
8689
const urlPath = new URL(url).pathname;
@@ -99,6 +102,19 @@ async function prepareOfflineDependencies() {
99102
});
100103
}
101104

105+
// Also find all CDN URLs inside script.js and download them for offline use
106+
const urlRegex = /https:\/\/(?:cdnjs\.cloudflare\.com|cdn\.jsdelivr\.net)\/[^'\s`"]+/g;
107+
while ((match = urlRegex.exec(scriptJS)) !== null) {
108+
const url = match[0];
109+
if (foundUrls.has(url)) continue;
110+
foundUrls.add(url);
111+
112+
const urlPath = new URL(url).pathname;
113+
const filename = path.basename(urlPath);
114+
const localDest = path.join(LIBS_DIR, filename);
115+
downloads.push(downloadFile(url, localDest));
116+
}
117+
102118
// Also download the relative fonts loaded by bootstrap-icons
103119
const fontDir = path.join(LIBS_DIR, "fonts");
104120
fs.mkdirSync(fontDir, { recursive: true });

script.js

Lines changed: 89 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -209,10 +209,17 @@ document.addEventListener("DOMContentLoaded", function () {
209209
return Promise.resolve(window[dep.global]);
210210
}
211211

212+
let url = dep.url;
213+
if (typeof Neutralino !== 'undefined') {
214+
const urlPath = new URL(url).pathname;
215+
const filename = urlPath.substring(urlPath.lastIndexOf('/') + 1);
216+
url = `/libs/${filename}`;
217+
}
218+
212219
const promise = new Promise((resolve, reject) => {
213220
const script = document.createElement('script');
214-
script.src = dep.url;
215-
if (dep.integrity) {
221+
script.src = url;
222+
if (dep.integrity && typeof Neutralino === 'undefined') {
216223
script.integrity = dep.integrity;
217224
script.crossOrigin = 'anonymous';
218225
}
@@ -238,12 +245,18 @@ document.addEventListener("DOMContentLoaded", function () {
238245

239246
function loadStylesheet(id, url, integrity) {
240247
if (document.getElementById(id)) return Promise.resolve();
248+
let finalUrl = url;
249+
if (typeof Neutralino !== 'undefined') {
250+
const urlPath = new URL(url).pathname;
251+
const filename = urlPath.substring(urlPath.lastIndexOf('/') + 1);
252+
finalUrl = `/libs/${filename}`;
253+
}
241254
return new Promise((resolve, reject) => {
242255
const link = document.createElement('link');
243256
link.id = id;
244257
link.rel = 'stylesheet';
245-
link.href = url;
246-
if (integrity) {
258+
link.href = finalUrl;
259+
if (integrity && typeof Neutralino === 'undefined') {
247260
link.integrity = integrity;
248261
link.crossOrigin = 'anonymous';
249262
}
@@ -266,6 +279,7 @@ document.addEventListener("DOMContentLoaded", function () {
266279
loadDependency('DOMPurify'),
267280
loadDependency('hljs')
268281
]).then(() => {
282+
initializeMarked();
269283
coreLibrariesLoaded = true;
270284
return true;
271285
});
@@ -446,7 +460,7 @@ document.addEventListener("DOMContentLoaded", function () {
446460
let lineNumberMeasure = null;
447461
let lineNumberUpdateFrame = null;
448462

449-
const renderer = new marked.Renderer();
463+
let renderer;
450464
const BLOCK_MATH_MARKER_PATTERN = /^\$\$/m;
451465
const BLOCK_MATH_PATTERN = /^\$\$[ \t]*\n?([\s\S]*?)\n?\$\$[ \t]*(?:\n|$)/;
452466
const DEFINITION_LIST_ITEM_PATTERN = /^:[ \t]+(.*)$/;
@@ -812,49 +826,58 @@ document.addEventListener("DOMContentLoaded", function () {
812826
},
813827
};
814828

815-
renderer.code = function (code, language) {
816-
if (language === 'mermaid') {
817-
const uniqueId = 'mermaid-diagram-' + Math.random().toString(36).substr(2, 9);
818-
const escapedCode = code
819-
.replace(/&/g, "&amp;")
820-
.replace(/</g, "&lt;")
821-
.replace(/>/g, "&gt;");
822-
return `<div class="mermaid-container"><div class="mermaid" id="${uniqueId}">${escapedCode}</div></div>`;
823-
}
824-
825-
const validLanguage = hljs.getLanguage(language) ? language : "plaintext";
826-
const highlightedCode = hljs.highlight(code, {
827-
language: validLanguage,
828-
}).value;
829-
return `<pre><code class="hljs ${validLanguage}">${highlightedCode}</code></pre>`;
830-
};
829+
let markedInitialized = false;
830+
function initializeMarked() {
831+
if (markedInitialized) return;
831832

832-
marked.use({
833-
extensions: [
834-
blockMathExtension,
835-
definitionListExtension,
836-
superscriptExtension,
837-
subscriptExtension,
838-
highlightExtension,
839-
],
840-
hooks: {
841-
preprocess(markdown) {
842-
if (suppressFootnotePreprocess) {
843-
return markdown;
844-
}
845-
resetExtendedMarkdownState();
846-
// ✅ Replace escaped dollar signs before marked.js strips the backslash.
847-
// This prevents MathJax from treating lone $ as a math delimiter.
848-
const protectedMarkdown = markdown.replace(/\\\$/g, '&#36;');
849-
return applyFootnotes(extractFootnoteDefinitions(protectedMarkdown));
833+
renderer = new marked.Renderer();
834+
835+
renderer.code = function (code, language) {
836+
if (language === 'mermaid') {
837+
const uniqueId = 'mermaid-diagram-' + Math.random().toString(36).substr(2, 9);
838+
const escapedCode = code
839+
.replace(/&/g, "&amp;")
840+
.replace(/</g, "&lt;")
841+
.replace(/>/g, "&gt;");
842+
return `<div class="mermaid-container"><div class="mermaid" id="${uniqueId}">${escapedCode}</div></div>`;
843+
}
844+
845+
const validLanguage = hljs.getLanguage(language) ? language : "plaintext";
846+
const highlightedCode = hljs.highlight(code, {
847+
language: validLanguage,
848+
}).value;
849+
return `<pre><code class="hljs ${validLanguage}">${highlightedCode}</code></pre>`;
850+
};
851+
852+
marked.use({
853+
extensions: [
854+
blockMathExtension,
855+
definitionListExtension,
856+
superscriptExtension,
857+
subscriptExtension,
858+
highlightExtension,
859+
],
860+
hooks: {
861+
preprocess(markdown) {
862+
if (suppressFootnotePreprocess) {
863+
return markdown;
864+
}
865+
resetExtendedMarkdownState();
866+
// ✅ Replace escaped dollar signs before marked.js strips the backslash.
867+
// This prevents MathJax from treating lone $ as a math delimiter.
868+
const protectedMarkdown = markdown.replace(/\\\$/g, '&#36;');
869+
return applyFootnotes(extractFootnoteDefinitions(protectedMarkdown));
870+
},
850871
},
851-
},
852-
});
872+
});
853873

854-
marked.setOptions({
855-
...markedOptions,
856-
renderer: renderer,
857-
});
874+
marked.setOptions({
875+
...markedOptions,
876+
renderer: renderer,
877+
});
878+
879+
markedInitialized = true;
880+
}
858881

859882
const GITHUB_ALERT_META = {
860883
note: {
@@ -1820,16 +1843,15 @@ This is a fully client-side application. Your content never leaves your browser
18201843
const mermaidTheme = currentTheme === "dark" ? "dark" : "default";
18211844
m.initialize({ theme: mermaidTheme });
18221845

1823-
try {
1824-
Promise.resolve(m.init(undefined, mermaidNodes))
1825-
.then(() => addMermaidToolbars())
1826-
.catch((e) => {
1827-
console.warn("Mermaid rendering failed:", e);
1828-
addMermaidToolbars();
1829-
});
1830-
} catch (e) {
1846+
m.run({
1847+
nodes: mermaidNodes,
1848+
suppressErrors: true
1849+
})
1850+
.then(() => addMermaidToolbars())
1851+
.catch((e) => {
18311852
console.warn("Mermaid rendering failed:", e);
1832-
}
1853+
addMermaidToolbars();
1854+
});
18331855
}).catch(err => {
18341856
console.warn("Failed to load Mermaid:", err);
18351857
});
@@ -2314,7 +2336,11 @@ This is a fully client-side application. Your content never leaves your browser
23142336
emojiLoadingPromise = Promise.all([
23152337
loadDependency('joypixels'),
23162338
loadStylesheet('joypixels-css', 'https://cdn.jsdelivr.net/npm/emoji-toolkit@9.0.1/extras/css/joypixels.min.css', 'sha384-4ok+tBQQdy5hcPT56tzcE11yQ2BkN0Py1uDE8ZOiXYstHOpUB61pJafm+NidByp4')
2317-
]).then(() => window.joypixels);
2339+
]).then(() => window.joypixels)
2340+
.catch(err => {
2341+
emojiLoadingPromise = null;
2342+
throw err;
2343+
});
23182344
return emojiLoadingPromise;
23192345
}
23202346

@@ -5368,13 +5394,13 @@ This is a fully client-side application. Your content never leaves your browser
53685394

53695395
loadingPdfExportDependenciesPromise = Promise.all([
53705396
loadDependency('jspdf'),
5371-
loadDependency('html2canvas'),
5372-
loadDependency('pdfmake'),
5373-
loadDependency('vfs_fonts'),
5374-
loadDependency('html2pdf')
5397+
loadDependency('html2canvas')
53755398
]).then(() => {
53765399
pdfExportDependenciesLoaded = true;
53775400
return true;
5401+
}).catch(err => {
5402+
loadingPdfExportDependenciesPromise = null;
5403+
throw err;
53785404
});
53795405

53805406
return loadingPdfExportDependenciesPromise;
@@ -5677,7 +5703,10 @@ This is a fully client-side application. Your content never leaves your browser
56775703
function ensurePako() {
56785704
if (window.pako) return Promise.resolve(window.pako);
56795705
if (pakoLoadingPromise) return pakoLoadingPromise;
5680-
pakoLoadingPromise = loadDependency('pako');
5706+
pakoLoadingPromise = loadDependency('pako').catch(err => {
5707+
pakoLoadingPromise = null;
5708+
throw err;
5709+
});
56815710
return pakoLoadingPromise;
56825711
}
56835712

0 commit comments

Comments
 (0)