Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion desktop-app/prepare.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,19 @@ function downloadFile(url, destPath) {
async function prepareOfflineDependencies() {
console.log("\nStarting Offline Assets Preparation...");
let html = fs.readFileSync(path.join(ROOT_DIR, "index.html"), "utf-8");
const scriptJS = fs.readFileSync(path.join(ROOT_DIR, "script.js"), "utf-8");

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

while ((match = cdnRegex.exec(html)) !== null) {
const attr = match[1];
const url = match[2];
foundUrls.add(url);

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

// Also find all CDN URLs inside script.js and download them for offline use
const urlRegex = /https:\/\/(?:cdnjs\.cloudflare\.com|cdn\.jsdelivr\.net)\/[^'\s`"]+/g;
while ((match = urlRegex.exec(scriptJS)) !== null) {
const url = match[0];
if (foundUrls.has(url)) continue;
foundUrls.add(url);

const urlPath = new URL(url).pathname;
const filename = path.basename(urlPath);
const localDest = path.join(LIBS_DIR, filename);
downloads.push(downloadFile(url, localDest));
}

// Also download the relative fonts loaded by bootstrap-icons
const fontDir = path.join(LIBS_DIR, "fonts");
fs.mkdirSync(fontDir, { recursive: true });
Expand Down
149 changes: 89 additions & 60 deletions script.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,10 +209,17 @@ document.addEventListener("DOMContentLoaded", function () {
return Promise.resolve(window[dep.global]);
}

let url = dep.url;
if (typeof Neutralino !== 'undefined') {
const urlPath = new URL(url).pathname;
const filename = urlPath.substring(urlPath.lastIndexOf('/') + 1);
url = `/libs/${filename}`;
}

const promise = new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = dep.url;
if (dep.integrity) {
script.src = url;
if (dep.integrity && typeof Neutralino === 'undefined') {
script.integrity = dep.integrity;
script.crossOrigin = 'anonymous';
}
Expand All @@ -238,12 +245,18 @@ document.addEventListener("DOMContentLoaded", function () {

function loadStylesheet(id, url, integrity) {
if (document.getElementById(id)) return Promise.resolve();
let finalUrl = url;
if (typeof Neutralino !== 'undefined') {
const urlPath = new URL(url).pathname;
const filename = urlPath.substring(urlPath.lastIndexOf('/') + 1);
finalUrl = `/libs/${filename}`;
}
return new Promise((resolve, reject) => {
const link = document.createElement('link');
link.id = id;
link.rel = 'stylesheet';
link.href = url;
if (integrity) {
link.href = finalUrl;
if (integrity && typeof Neutralino === 'undefined') {
link.integrity = integrity;
link.crossOrigin = 'anonymous';
}
Expand All @@ -266,6 +279,7 @@ document.addEventListener("DOMContentLoaded", function () {
loadDependency('DOMPurify'),
loadDependency('hljs')
]).then(() => {
initializeMarked();
coreLibrariesLoaded = true;
return true;
});
Expand Down Expand Up @@ -446,7 +460,7 @@ document.addEventListener("DOMContentLoaded", function () {
let lineNumberMeasure = null;
let lineNumberUpdateFrame = null;

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

renderer.code = function (code, language) {
if (language === 'mermaid') {
const uniqueId = 'mermaid-diagram-' + Math.random().toString(36).substr(2, 9);
const escapedCode = code
.replace(/&/g, "&")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
return `<div class="mermaid-container"><div class="mermaid" id="${uniqueId}">${escapedCode}</div></div>`;
}

const validLanguage = hljs.getLanguage(language) ? language : "plaintext";
const highlightedCode = hljs.highlight(code, {
language: validLanguage,
}).value;
return `<pre><code class="hljs ${validLanguage}">${highlightedCode}</code></pre>`;
};
let markedInitialized = false;
function initializeMarked() {
if (markedInitialized) return;

marked.use({
extensions: [
blockMathExtension,
definitionListExtension,
superscriptExtension,
subscriptExtension,
highlightExtension,
],
hooks: {
preprocess(markdown) {
if (suppressFootnotePreprocess) {
return markdown;
}
resetExtendedMarkdownState();
// ✅ Replace escaped dollar signs before marked.js strips the backslash.
// This prevents MathJax from treating lone $ as a math delimiter.
const protectedMarkdown = markdown.replace(/\\\$/g, '&#36;');
return applyFootnotes(extractFootnoteDefinitions(protectedMarkdown));
renderer = new marked.Renderer();

renderer.code = function (code, language) {
if (language === 'mermaid') {
const uniqueId = 'mermaid-diagram-' + Math.random().toString(36).substr(2, 9);
const escapedCode = code
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
return `<div class="mermaid-container"><div class="mermaid" id="${uniqueId}">${escapedCode}</div></div>`;
}

const validLanguage = hljs.getLanguage(language) ? language : "plaintext";
const highlightedCode = hljs.highlight(code, {
language: validLanguage,
}).value;
return `<pre><code class="hljs ${validLanguage}">${highlightedCode}</code></pre>`;
};

marked.use({
extensions: [
blockMathExtension,
definitionListExtension,
superscriptExtension,
subscriptExtension,
highlightExtension,
],
hooks: {
preprocess(markdown) {
if (suppressFootnotePreprocess) {
return markdown;
}
resetExtendedMarkdownState();
// ✅ Replace escaped dollar signs before marked.js strips the backslash.
// This prevents MathJax from treating lone $ as a math delimiter.
const protectedMarkdown = markdown.replace(/\\\$/g, '&#36;');
return applyFootnotes(extractFootnoteDefinitions(protectedMarkdown));
},
},
},
});
});

marked.setOptions({
...markedOptions,
renderer: renderer,
});
marked.setOptions({
...markedOptions,
renderer: renderer,
});

markedInitialized = true;
}

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

try {
Promise.resolve(m.init(undefined, mermaidNodes))
.then(() => addMermaidToolbars())
.catch((e) => {
console.warn("Mermaid rendering failed:", e);
addMermaidToolbars();
});
} catch (e) {
m.run({
nodes: mermaidNodes,
suppressErrors: true
})
.then(() => addMermaidToolbars())
.catch((e) => {
console.warn("Mermaid rendering failed:", e);
}
addMermaidToolbars();
});
}).catch(err => {
console.warn("Failed to load Mermaid:", err);
});
Expand Down Expand Up @@ -2314,7 +2336,11 @@ This is a fully client-side application. Your content never leaves your browser
emojiLoadingPromise = Promise.all([
loadDependency('joypixels'),
loadStylesheet('joypixels-css', 'https://cdn.jsdelivr.net/npm/emoji-toolkit@9.0.1/extras/css/joypixels.min.css', 'sha384-4ok+tBQQdy5hcPT56tzcE11yQ2BkN0Py1uDE8ZOiXYstHOpUB61pJafm+NidByp4')
]).then(() => window.joypixels);
]).then(() => window.joypixels)
.catch(err => {
emojiLoadingPromise = null;
throw err;
});
return emojiLoadingPromise;
}

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

loadingPdfExportDependenciesPromise = Promise.all([
loadDependency('jspdf'),
loadDependency('html2canvas'),
loadDependency('pdfmake'),
loadDependency('vfs_fonts'),
loadDependency('html2pdf')
loadDependency('html2canvas')
]).then(() => {
pdfExportDependenciesLoaded = true;
return true;
}).catch(err => {
loadingPdfExportDependenciesPromise = null;
throw err;
});

return loadingPdfExportDependenciesPromise;
Expand Down Expand Up @@ -5677,7 +5703,10 @@ This is a fully client-side application. Your content never leaves your browser
function ensurePako() {
if (window.pako) return Promise.resolve(window.pako);
if (pakoLoadingPromise) return pakoLoadingPromise;
pakoLoadingPromise = loadDependency('pako');
pakoLoadingPromise = loadDependency('pako').catch(err => {
pakoLoadingPromise = null;
throw err;
});
return pakoLoadingPromise;
}

Expand Down
Loading