Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
4 changes: 4 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ FROM nginx:alpine
# PERF-019: Only copy necessary web files (exclude .git, desktop-app, wiki, etc.)
COPY index.html /usr/share/nginx/html/
COPY workspace-storage.js /usr/share/nginx/html/
COPY visual-settings-adapters.js /usr/share/nginx/html/
COPY visual-settings-store.js /usr/share/nginx/html/
COPY script.js /usr/share/nginx/html/
COPY styles.css /usr/share/nginx/html/
COPY sw.js /usr/share/nginx/html/
Expand All @@ -12,6 +14,8 @@ COPY manifest.json /usr/share/nginx/html/
COPY robots.txt /usr/share/nginx/html/
COPY sitemap.xml /usr/share/nginx/html/
COPY assets/ /usr/share/nginx/html/assets/
COPY seo/locales.mjs /usr/share/nginx/html/seo/
COPY seo/welcome-content.mjs /usr/share/nginx/html/seo/

# Create a custom nginx configuration with compression and security
# PERF-020: Added gzip compression for text-based assets
Expand Down
37 changes: 32 additions & 5 deletions desktop-app/prepare.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ console.log("✓ Copied script.js → resources/js/script.js");
fs.copyFileSync(path.join(ROOT_DIR, "workspace-storage.js"), path.join(jsDest, "workspace-storage.js"));
console.log("✓ Copied workspace-storage.js → resources/js/workspace-storage.js");

if (fs.existsSync(path.join(ROOT_DIR, "visual-settings-store.js"))) {
fs.copyFileSync(path.join(ROOT_DIR, "visual-settings-store.js"), path.join(jsDest, "visual-settings-store.js"));
console.log("✓ Copied visual-settings-store.js → resources/js/visual-settings-store.js");
}
if (fs.existsSync(path.join(ROOT_DIR, "visual-settings-adapters.js"))) {
fs.copyFileSync(path.join(ROOT_DIR, "visual-settings-adapters.js"), path.join(jsDest, "visual-settings-adapters.js"));
console.log("✓ Copied visual-settings-adapters.js → resources/js/visual-settings-adapters.js");
}

fs.copyFileSync(path.join(ROOT_DIR, "preview-worker.js"), path.join(jsDest, "preview-worker.js"));
console.log("Copied preview-worker.js to resources/js/preview-worker.js");

Expand Down Expand Up @@ -262,9 +271,9 @@ async function prepareOfflineDependencies() {
hash: null
},
{
url: "https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.2.2/es5/tex-mml-chtml.min.js",
dest: path.join(LIBS_DIR, "tex-mml-chtml.min.js"),
hash: null
url: "https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.7/tex-mml-chtml.js",
dest: path.join(LIBS_DIR, "tex-mml-chtml.js"),
hash: "sha384-2f5bAKjuFIbbQ+0O9aSu5W1OyLA4q80QrDEvjXDwJexjU9VqoJhMgw19pYVpSJab"
},
{
url: "https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js",
Expand Down Expand Up @@ -305,6 +314,12 @@ async function prepareOfflineDependencies() {
// Wait for all downloads and cryptographic validations to finish
try {
await Promise.all(downloads);
// Keep backwards-compatible copy for tex-mml-chtml.min.js
const mathjaxDest = path.join(LIBS_DIR, "tex-mml-chtml.js");
const mathjaxMinDest = path.join(LIBS_DIR, "tex-mml-chtml.min.js");
if (fs.existsSync(mathjaxDest)) {
fs.copyFileSync(mathjaxDest, mathjaxMinDest);
}
console.log("✓ All offline libraries successfully downloaded and cryptographically validated.");
} catch (err) {
console.error("✗ Critical Security Error: Dependency integrity check failed!", err.message);
Expand All @@ -320,6 +335,8 @@ async function prepareOfflineDependencies() {
html = html.replace(/href="assets\//g, 'href="/assets/');
html = html.replace(/href="styles\.css"/g, 'href="/styles.css"');
html = html.replace(/href="workspace-storage\.js"/g, 'href="/js/workspace-storage.js"');
html = html.replace(/href="visual-settings-adapters\.js"/g, 'href="/js/visual-settings-adapters.js"');
html = html.replace(/href="visual-settings-store\.js"/g, 'href="/js/visual-settings-store.js"');
html = html.replace(/href="script\.js"/g, 'href="/js/script.js"');

// PERF-034: Strip web-specific SEO tags, canonical, hreflang, preconnect, manifest and JSON-LD structured data for desktop build
Expand All @@ -333,8 +350,18 @@ async function prepareOfflineDependencies() {

// Inject Neutralino script tags
html = html.replace(
/<script\s+src="workspace-storage\.js"[^>]*><\/script>\s*<script\s+src="script\.js"[^>]*><\/script>/i,
'<script src="/js/neutralino.js"></script>\n <script src="/js/main.js"></script>\n <script src="/js/workspace-storage.js"></script>\n <script src="/js/script.js"></script>',
/<script\s+src="workspace-storage\.js"[^>]*><\/script>(?:\s*<script\s+src="visual-settings-adapters\.js"[^>]*><\/script>)?(?:\s*<script\s+src="visual-settings-store\.js"[^>]*><\/script>)?\s*<script\s+src="script\.js"[^>]*><\/script>/i,
(match) => {
let tags = '<script src="/js/neutralino.js"></script>\n <script src="/js/main.js"></script>\n <script src="/js/workspace-storage.js"></script>';
if (match.includes('visual-settings-adapters.js')) {
tags += '\n <script src="/js/visual-settings-adapters.js"></script>';
}
if (match.includes('visual-settings-store.js')) {
tags += '\n <script src="/js/visual-settings-store.js"></script>';
}
tags += '\n <script src="/js/script.js"></script>';
return tags;
}
);

// Inject app-info element
Expand Down
91 changes: 91 additions & 0 deletions desktop-app/resources/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
<!-- PERF-015: Preload critical-path resources for faster discovery -->
<link rel="preload" href="/styles.css" as="style">
<link rel="preload" href="/js/workspace-storage.js" as="script">
<link rel="preload" href="/js/visual-settings-adapters.js" as="script">
<link rel="preload" href="/js/visual-settings-store.js" as="script">
<link rel="preload" href="/js/script.js" as="script">

<!-- PWA Web Manifest -->
Expand Down Expand Up @@ -121,6 +123,10 @@ <h1 class="h4 mb-0">Markdown Viewer<span class="visually-hidden"> - Online Markd
<i class="lucide lucide-info" aria-hidden="true"></i>
</button>

<button id="visual-settings-button" class="header-icon-command" type="button" title="Visual library settings" aria-label="Open visual library settings">
<i class="lucide lucide-layers-3" aria-hidden="true"></i>
</button>

<div class="dropdown settings-dropdown">
<button class="header-icon-command" type="button" id="workspaceSettingsDropdown" data-bs-toggle="dropdown" data-bs-auto-close="outside" aria-expanded="false" title="Workspace settings (Ctrl/Cmd+,)" aria-label="Open workspace settings" aria-keyshortcuts="Control+, Meta+,">
<i class="lucide lucide-settings" aria-hidden="true"></i>
Expand All @@ -132,6 +138,9 @@ <h1 class="h4 mb-0">Markdown Viewer<span class="visually-hidden"> - Online Markd
<button id="theme-toggle" class="settings-menu-item app-menu-item settings-menu-item--toggle" type="button" aria-pressed="false" title="Toggle appearance">
<i id="theme-toggle-icon" class="lucide lucide-sun" aria-hidden="true"></i><span class="app-menu-copy"><span class="app-menu-label">Appearance</span><span id="theme-toggle-description" class="app-menu-description">Switch to dark appearance</span></span><span class="settings-switch themed-settings-switch" aria-hidden="true"><span><i id="theme-switch-icon" class="lucide lucide-sun settings-switch-icon"></i></span></span>
</button>
<button id="menu-visual-settings-button" class="settings-menu-item app-menu-item" type="button" title="Visual library settings">
<i class="lucide lucide-layers-3" aria-hidden="true"></i><span class="app-menu-copy"><span class="app-menu-label">Visual Settings</span><span class="app-menu-description">Themes, fonts, and styles for libraries</span></span>
</button>
<div class="dropdown dropstart settings-language-dropdown">
<button class="settings-menu-item app-menu-item dropdown-toggle" type="button" id="languageDropdown" data-bs-toggle="dropdown" data-bs-auto-close="outside" aria-expanded="false" title="Switch language">
<i class="lucide lucide-languages" aria-hidden="true"></i><span class="app-menu-copy"><span id="current-lang-label" class="app-menu-label">English</span><span class="app-menu-description">Application language</span></span>
Expand Down Expand Up @@ -316,6 +325,9 @@ <h3 class="mobile-menu-group-label">Actions</h3>
<button id="mobile-private-mode-toggle" class="mobile-menu-item mobile-settings-item mobile-settings-toggle" type="button" aria-pressed="false" title="Turn private mode on">
<span class="mobile-menu-item-label"><i class="lucide lucide-hat-glasses" aria-hidden="true"></i><span>Private mode</span></span><span class="settings-switch themed-settings-switch" aria-hidden="true"><span></span></span>
</button>
<button id="mobile-visual-settings-button" class="mobile-menu-item mobile-settings-item" type="button" title="Visual library settings">
<span class="mobile-menu-item-label"><i class="lucide lucide-layers-3" aria-hidden="true"></i><span>Visual Settings</span></span>
</button>
<button id="mobile-storage-settings-button" class="mobile-menu-item mobile-settings-item" type="button" title="Storage and backup">
<span class="mobile-menu-item-label"><i class="lucide lucide-box" aria-hidden="true"></i><span>Storage and Backup</span></span>
</button>
Expand Down Expand Up @@ -559,6 +571,83 @@ <h2 id="document-sidebar-title">Explorer</h2>
</div>
<input id="storage-backup-file-input" type="file" accept=".zip,application/zip" hidden>

<!-- Visual Library Settings Modal -->
<div id="visual-settings-modal" class="reset-modal-overlay modal-overlay" role="dialog" aria-modal="true" aria-labelledby="visual-settings-title" aria-hidden="true" style="display:none;">
<div class="reset-modal-box reset-modal-box--wide app-modal-box modal-box visual-settings-box">
<div class="modal-header">
<p id="visual-settings-title" class="reset-modal-message">Visual Library Settings</p>
<button type="button" class="modal-close-btn" id="visual-settings-close-icon" aria-label="Close visual library settings">
<i class="lucide lucide-x" aria-hidden="true"></i>
</button>
</div>
<div class="modal-body visual-settings-body">
<p class="visual-setting-desc mb-2">Configure default themes, fonts, and rendering styles for preview visual libraries.</p>

<!-- highlight.js theme -->
<div class="visual-setting-group">
<label for="visual-setting-hljs-theme" class="visual-setting-label">
<i class="lucide lucide-code-2" aria-hidden="true"></i> Syntax Highlighting Theme (highlight.js)
</label>
<select id="visual-setting-hljs-theme" class="form-select visual-setting-select" aria-describedby="visual-setting-hljs-desc">
</select>
<p id="visual-setting-hljs-desc" class="visual-setting-desc">Color palette for highlighted code blocks in the preview.</p>
</div>

<!-- MathJax math font -->
<div class="visual-setting-group">
<label for="visual-setting-math-font" class="visual-setting-label">
<i class="lucide lucide-spline" aria-hidden="true"></i> Math Font (MathJax)
</label>
<select id="visual-setting-math-font" class="form-select visual-setting-select" aria-describedby="visual-setting-math-desc">
</select>
<p id="visual-setting-math-desc" class="visual-setting-desc">Font family used for rendering LaTeX mathematical notation.</p>
</div>

<!-- Mermaid theme -->
<div class="visual-setting-group">
<label for="visual-setting-mermaid-theme" class="visual-setting-label">
<i class="lucide lucide-workflow" aria-hidden="true"></i> Diagram Theme (Mermaid)
</label>
<select id="visual-setting-mermaid-theme" class="form-select visual-setting-select" aria-describedby="visual-setting-mermaid-theme-desc">
</select>
<p id="visual-setting-mermaid-theme-desc" class="visual-setting-desc">Color palette and appearance for Mermaid diagrams.</p>
</div>

<!-- Mermaid look -->
<div class="visual-setting-group">
<label for="visual-setting-mermaid-look" class="visual-setting-label">
<i class="lucide lucide-square-pen" aria-hidden="true"></i> Diagram Drawing Style (Mermaid Look)
</label>
<select id="visual-setting-mermaid-look" class="form-select visual-setting-select" aria-describedby="visual-setting-mermaid-look-desc">
</select>
<p id="visual-setting-mermaid-look-desc" class="visual-setting-desc">Geometric drawing style (Classic or Hand Drawn sketch).</p>
</div>

<!-- JoyPixels emoji skin tone -->
<div class="visual-setting-group">
<label for="visual-setting-emoji-skin-tone" class="visual-setting-label">
<i class="lucide lucide-smile" aria-hidden="true"></i> Emoji Skin Tone (JoyPixels)
</label>
<select id="visual-setting-emoji-skin-tone" class="form-select visual-setting-select" aria-describedby="visual-setting-emoji-skin-tone-desc">
</select>
<p id="visual-setting-emoji-skin-tone-desc" class="visual-setting-desc">Default Fitzpatrick skin tone for supported emoji shortcodes.</p>
</div>
</div>
<div class="reset-modal-actions visual-settings-actions">
<div class="visual-settings-actions-left">
<button type="button" class="reset-modal-btn reset-modal-cancel" id="visual-settings-reset-btn" title="Reset all visual settings to defaults">
<i class="lucide lucide-refresh-cw me-1" aria-hidden="true"></i> Reset to defaults
</button>
</div>
<div class="visual-settings-actions-right">
<button type="button" class="reset-modal-btn storage-primary-action" id="visual-settings-close-btn">
Close
</button>
</div>
</div>
</div>
</div>

<!-- Trash Modal -->
<div id="trash-modal" class="reset-modal-overlay modal-overlay" role="dialog" aria-modal="true" aria-labelledby="trash-modal-title" aria-describedby="trash-modal-description" aria-hidden="true" style="display:none;">
<div class="reset-modal-box reset-modal-box--wide app-modal-box modal-box trash-modal-box">
Expand Down Expand Up @@ -2214,6 +2303,8 @@ <h3>No comments yet</h3>
<script src="/js/neutralino.js"></script>
<script src="/js/main.js"></script>
<script src="/js/workspace-storage.js"></script>
<script src="/js/visual-settings-adapters.js"></script>
<script src="/js/visual-settings-store.js"></script>
<script src="/js/script.js"></script>
<!-- Screen reader dynamic accessibility announcer -->
<div id="app-accessibility-announcer" class="visually-hidden" aria-live="polite" aria-atomic="true"></div>
Expand Down
Loading
Loading