A Markdown Editor That Lives in Your Browser, Desktop, and a Single URL.
Fast GitHub-style Markdown editing with live preview, diagrams, LaTeX, syntax highlighting, PDF export, and multi-tab support across web, desktop, and Docker.
Live Production Demo · Documentation Wiki · Issue Tracker · Releases
- About the Project
- Key Features
- System Architecture
- Under-the-Hood Subsystems Deep-Dive
- 1. Global State & Session Persistence
- 2. Document Tab & Session Lifecycle
- 3. Tab Overflow & Navigation
- 4. Responsive Pane Resizer & View Mode Layout Controller
- 5. Rich Text Editor History & Undo/Redo Engine
- 6. Dynamic Line-Number Gutter & Selection Highlighter
- 7. Web Worker Segmented Markdown Compilation & Sanitization
- 8. Throttled Bidirectional Scroll Synchronization
- 9. Interactive Mermaid Diagram & MathJax LaTeX Renderer
- 10. Draggable Find/Replace Search & Diff Preview Engine
- 11. Layout-Aware PDF Export & URL Sharing Subsystem
- Getting Started & Installation
- Usage Guide & Keyboard Shortcuts
- Project Directory Structure
- Built With (Technology Stack)
- Contributing & Code Quality
- Showcase & Community Projects
- License
- Contact & Support
Markdown Viewer is an advanced, fully client-side editing suite and previewer optimized for a professional documentation workflow. Running completely inside the browser, it renders GitHub-Flavored Markdown (GFM), math formulas, and architectural diagrams in real time.
Designed with privacy and performance at its core, the application performs all parsing in a background worker thread, employs incremental DOM patching to minimize browser repaints, and supports native offline capabilities via a Service Worker proxy. It is also packaged as a lightweight native desktop shell using the Neutralinojs framework.
-
🖊️ Decoupled Split-Screen Editing: Type Markdown in a customized text editor with a dynamic line-number gutter and see updates render instantly.
-
📐 LaTeX Math Notation: Full integration with MathJax for typesetting inline and block mathematical formulas.
-
📊 Interactive Mermaid Diagrams: Render flowcharts, Gantt charts, sequence diagrams, and more. Diagrams feature a toolbar for zooming, panning, copying, and exporting.
-
⚡ Off-Thread Parsing & Incremental Patching: Document parsing is offloaded to a background Web Worker. Rendered updates patch only changed DOM nodes to keep browser resources free.
-
📤 Professional Export Suite: Export documents as raw Markdown (
.md), standalone formatted HTML (.html) with inline styles, or high-resolution paginated PDF (.pdf). -
📥 Multi-Source File Import: Drag & drop local files or browse and download multiple Markdown files from a public GitHub repository URL.
-
🔗 URL-Encoded Compressed Sharing: Compress your document's text utilizing DEFLATE compression and encode it directly into a shareable URL hash. No server-side database required.
-
💾 Multi-Document Tab bar: Organize multiple files inside tab components featuring drag-and-drop reordering, title renaming, and local session persistence.
-
🔒 Privacy & Security Focus: No analytics, tracking beacons, or backend servers. HTML output is sanitized via DOMPurify to eliminate Cross-Site Scripting (XSS) threats.
Markdown Viewer is structured as a client-side single-page application (SPA). The diagram below outlines how the UI thread, background worker, service worker, browser cache, native desktop bridges, and third-party libraries interact.
graph TD
%% Client Interface Group
subgraph UI ["Client Interface (Main Thread)"]
HTML["index.html<br>(DOM Tree)"]
CSS["styles.css<br>(Custom Themes & Reset)"]
Script["script.js<br>(UI Orchestration)"]
Editor["Markdown Editor<br>(Textarea + Gutter)"]
Preview["Preview Pane<br>(Isolated Render Area)"]
Modal["Mermaid Modal<br>(Zoom & Drag-to-Pan)"]
end
%% Background Web Worker Group
subgraph Worker ["Web Worker (Background Thread)"]
PWorker["preview-worker.js<br>(Off-Thread Compiler)"]
MarkedLib["Marked.js<br>(GFM Parser)"]
HljsLib["Highlight.js<br>(Syntax Color)"]
end
%% Storage Group
subgraph Storage ["Local Storage & Network Proxy"]
LS["localStorage<br>(Tabs, Settings, Session)"]
Cache["Browser Cache<br>(Service Worker sw.js)"]
end
%% Third-Party Utilities
subgraph CDNs ["Third-Party CDN Libraries (Lazy Loaded)"]
MathJax["MathJax.js<br>(LaTeX Math)"]
Mermaid["Mermaid.js<br>(Diagrams)"]
PDF["jsPDF & html2canvas<br>(PDF Export Pipeline)"]
Pako["Pako.js<br>(zlib share encoder)"]
end
%% Native Desktop Layer
subgraph Desktop ["NeutralinoJS Desktop Shell"]
Neu["Neutralino.js Bridge<br>(Native File System APIs)"]
end
%% Interactions
Editor -- "1. Input Keystrokes" --> Script
Script -- "2. Size-Aware Debounced Text" --> PWorker
PWorker -- "3. Load Scripts" --> MarkedLib
PWorker -- "3. Load Scripts" --> HljsLib
PWorker -- "4. Returns Compiled HTML Blocks & Hashes" --> Script
Script -- "5. Incremental Patching (replaceWith)" --> Preview
Script -- "6. Debounced State Auto-Save" --> LS
%% Dynamic Loading triggers
Script -- "Lazy Load (Math strings detected)" --> MathJax
Script -- "Lazy Load (Mermaid block detected)" --> Mermaid
Script -- "Lazy Load (On PDF Export click)" --> PDF
Script -- "Lazy Load (On Share click)" --> Pako
%% Downstream Rendering outputs
MathJax -- "Inject Math formulas" --> Preview
Mermaid -- "Draw SVGs + Toolbars" --> Preview
Preview -- "Double click diagram" --> Modal
PDF -- "Capture sandboxed canvas" --> Script
%% Network Proxy Caching
Cache -. "Network-First (App Assets)" .-> HTML
Cache -. "Network-First (App Assets)" .-> Script
Cache -. "Network-First (App Assets)" .-> CSS
Cache -. "Cache-First (5.4MB bundles)" .-> CDNs
%% Desktop Logic
Script -- "Access OS API if wrapped" --> Neu
index.html: Establishes layout structures, floating panel anchors, and imports CSS files alongside core scripts using defer hooks. It keeps the default fallback markdown inside a<script type="text/markdown" id="default-markdown">element.script.js: Operates as the central controller on the main UI thread. It tracks active tab states, drives the split resizing loops, handles drag-and-drop file imports, coordinates communication with the preview Web Worker, manages the multi-pass PDF layout engine, and applies language mappings.styles.css: Configures variables for Light/Dark themes, handles layout spacing, aligns the line number gutter visually with the text editor area, and provides theme stylings for code fences.preview-worker.js: Operates on a background thread. It parses large text structures, calculates hashes for each section, compiles Markdown to HTML usingmarked.js, applies syntax highlighting viahighlight.js, and posts parsed output back to the main UI thread.sw.js: A Service Worker serving as a local network proxy. It intercepts requests to cache static files on the client's device, enabling the application to run offline.
Markdown Viewer employs custom-engineered client-side engines to deliver production-grade performance. Below is a detailed breakdown of the 11 core subsystems. For full source code listings and in-depth details of each implementation, please check the Features Wiki.
The global state manages application-wide preferences (such as theme, text direction, active tab, and split-pane ratio). It uses an in-memory shadowing cache to skip repeated parsing/serialization cycles over the synchronous localStorage block (preventing blocking disk I/O).
Theme switches write the theme attribute directly to the HTML document root to avoid visual flash or full-page layout reflows during loading. CSS transitions are strictly scoped to color properties to prevent costly layout recalculations:
Document files reside in an isolated document tab array structure. Tab dragging reorders tabs using the HTML5 Drag and Drop API, updating the underlying index array. Dropdown menus are positioned relative to the tab's bounding rectangle via getBoundingClientRect(). Keyboard accessibility mappings (ArrowRight, ArrowLeft, Home, End, Enter, Space) coordinate focus states inside the tab-list.
When open tabs exceed the horizontal viewport, the tab bar switches to an overflow state. Vertical mouse scroll wheel inputs are intercepted and translated to horizontal scroll coordinates to enable side-scrolling:
Overflow check checks the inequality scrollWidth > clientWidth to toggle the visibility of click-to-scroll navigation arrows.
The horizontal resizer calculates the percentage width of the editor relative to its parent container during window resizing:
The event loop tracks global resizing states on window mouse and touch move events, updating layout grid constraints via CSS properties.
To maintain separate command histories when navigating multiple documents, the history manager maintains tab-specific undo/redo stacks. Edits are batched to avoid bloated memory allocations; updates are pushed to the history stack only when transition boundaries occur, word borders (spaces) are typed, or keyboard idle time exceeds 300ms.
To keep line numbers in the gutter aligned with wrapped text in the transparent editor area, the gutter employs font-size wrap calculations:
DOM gutter paints are scheduled via requestAnimationFrame to prevent layout thrashing. A background overlay matches the text scroll coordinates to highlight find-and-replace queries.
Document parsing is offloaded to preview-worker.js on a background thread. Before offloading, the system runs safety checks to ensure the document contains no global definitions or complex footnotes. If safe, the worker tokenizes the text into blocks on double-newlines, calculates 32-bit FNV-1a hashes for each segment:
where
Proportional scrolling coordinates positions between the text editor and preview pane:
Scrolling feedback loops are prevented using state locks and decoupled animation schedules using requestAnimationFrame with a 50ms release timeout.
MathJax typesets equations asynchronously. A cleanup script strips MathJax's default assistive markup elements to prevent duplicate accessibility readings. Rendered SVG diagrams are manipulated in zoom modals using transform translation matrices:
Regular expression searches are parsed inside try-catch validation locks to avoid breaking runtime operations. The floating panel coordinates are clamped inside the window bounds:
A diff comparison engine computes modified line buffers to display a red/green visual preview before applying replacements.
PDF generation uses a multi-pass stabilization cascade loop (up to 10 iterations) to align layout components:
- Inline SVGs are converted to Base64 PNGs.
- Header elements near borders are shifted down via
.pdf-page-break-spacerto prevent orphan tags. - Tables are dynamically split and
<thead>elements are re-injected onto split tables. - Text element slicing is prevented by shifting lines downward past page cuts:
Documents are shared database-free via zlib DEFLATE compressed Base64 hashes.
Deploy the pre-compiled image hosted on the GitHub Container Registry (GHCR):
docker pull ghcr.io/thisis-developer/markdown-viewer:sha-15eafb0
docker run -d \
--name markdown-viewer \
-p 8080:80 \
--restart unless-stopped \
ghcr.io/thisis-developer/markdown-viewer:latestOpen http://localhost:8080 in your browser.
Clone the repository and spin up the container using Compose:
git clone https://github.com/ThisIs-Developer/Markdown-Viewer.git
cd Markdown-Viewer
docker compose up -dThe application will start on http://localhost:8080.
Because the code runs completely client-side, you can host the root directory using any static web server:
# Clone the repository
git clone https://github.com/ThisIs-Developer/Markdown-Viewer.git
cd Markdown-Viewer
# Open VSCode IDE
open index.html
and run on localhost http://127.0.0.1:5500 in your browser.
# OR Serve with Python (built-in, no dependencies)
python3 -m http.server 8080
# Serve with Node.js serve
npx serve . -p 8080Open http://localhost:8080.
Pre-built desktop binaries are available on the Releases page for Windows, Linux, and macOS.
To build the desktop application locally from source:
- Navigate to the
desktop-app/directory. - Run
npm installfollowed bynode setup-binaries.jsto download Neutralino binaries. - Synchronize files with
node prepare.js. - Compile using
npm run build(for Windows embedded) ornpm run build:portable.
For detailed desktop app settings, see the Desktop App Wiki.
- Write Markdown in the left editor pane.
- Toggle Split/Editor/Preview modes using the view controls in the top toolbar.
- Insert elements (tables, images, checklists, alerts) using the Markdown formatting toolbar.
- Save or export your files using the Export dropdown.
| Action | Windows / Linux | macOS |
|---|---|---|
| Export raw Markdown | Ctrl + S |
⌘ + S |
| Copy Rich HTML | Ctrl + C (with no text selected) |
⌘ + C (with no text selected) |
| Toggle Scroll Sync | Ctrl + Shift + S |
⌘ + Shift + S |
| Open a New Tab | Ctrl + T |
⌘ + T |
| Close the Active Tab | Ctrl + W |
⌘ + W |
| Open Find & Replace | Ctrl + F |
⌘ + F |
| Undo Last Edit | Ctrl + Z |
⌘ + Z |
| Redo Last Edit | Ctrl + Shift + Z / Ctrl + Y |
⌘ + Shift + Z / ⌘ + Y |
| Insert Code Block | Ctrl + Shift + C |
⌘ + Shift + C |
| Toggle Fullscreen Editor | F11 |
F11 |
| Insert 2-space Indent | Tab |
Tab |
| Outdent Line | Shift + Tab |
Shift + Tab |
Markdown-Viewer/
├── index.html # Core application DOM structure & CDN scripts
├── script.js # Main thread controller, state orchestrator, scroll sync
├── preview-worker.js # Background web worker for Markdown compilation
├── styles.css # Theme stylesheets, layout grids, print layouts
├── sw.js # Progressive Web App (PWA) offline Service Worker
├── Dockerfile # Production Nginx Docker configuration
├── docker-compose.yml # Port mappings and local Compose orchestrator
├── README.md # Main repository readme
├── LICENSE # Apache 2.0 license file
├── assets/ # Image assets, gifs, and screenshots
├── wiki/ # Markdown documentation pages for GitHub Wiki
└── desktop-app/ # Native Neutralinojs desktop configuration & binaries
├── package.json # Node packaging and scripts
├── neutralino.config.json # Neutralino runtime configuration
├── prepare.js # Synchronizes root web files with desktop workspace
└── resources/ # Copied workspace assets compiled into desktop app
| Library Name | Version | Role in App | Loading Method |
|---|---|---|---|
| Marked.js | 9.1.6 | Parses markdown content to HTML elements. | Defer (Upfront) |
| Highlight.js | 11.9.0 | Adds syntax highlighting to code sections. | Defer (Upfront) |
| DOMPurify | 3.0.9 | Sanitizes HTML outputs. | Defer (Upfront) |
| FileSaver.js | 2.0.5 | Manages file saving on the client side. | Defer (Upfront) |
| js-yaml | 4.1.0 | Parses YAML frontmatter headers. | Defer (Upfront) |
| Bootstrap | 5.3.2 | Provides component structures and modal panels. | Upfront Script |
| Mermaid.js | 11.15.0 | Renders diagrams and charts. | Lazy-loaded on diagram find |
| MathJax | 3.2.2 | Renders math formulas. | Lazy-loaded on math find |
| jsPDF | 2.5.1 | Generates PDF documents. | Lazy-loaded on PDF request |
| html2canvas | 1.4.1 | Captures HTML layouts as canvas objects. | Lazy-loaded on PDF request |
| pako.js | 2.1.0 | Compresses shared links. | Lazy-loaded on share request |
| JoyPixels | 9.0.1 | Renders emoji sets. | Lazy-loaded on emoji select |
We welcome community contributions! Please check our Contributing Guidelines Wiki before creating a pull request.
- Fork the repository and create a feature branch (
git checkout -b feature/your-feature). - Verify Code Style: Maintain a clean 2-space indentation style across HTML, CSS, and JS files. Ensure raw HTML structures are semantic. Avoid direct DOM queries inside processing workers.
- Conventional Commits: Write clear commit messages prefixed with
feat:,fix:,docs:,style:,refactor:,perf:, orchore:. - Testing: Test your revisions across Chrome, Firefox, Edge, and Safari viewports.
- Markdown Desk: A native macOS wrapper built using Tauri that adds native file-system handlers, menu bar integration, and auto-reload capabilities.
Thanks to everyone who has contributed to Markdown Viewer.
Markdown Viewer has grown from a lightweight Markdown parser into a full-featured, professional application with advanced rendering, workflow, and export capabilities. Compare the current version with the original version to see the progress in UI design, performance optimization, and feature depth.
This project is licensed under the Apache License 2.0. See the LICENSE file for the complete terms and conditions.
Developed and maintained by ThisIs-Developer.
- Bug Reports & Requests: Submit an Issue
- Documentation: Browse the Wiki







