# NanoMD - Architecture
> Back to [README](../README.md)
## Overview
NanoMD consists of three core components: **MDEditor** (editor), **MDParser** (parser), and **MDViewer** (previewer). Users type raw Markdown in the Editor, the Parser pipeline converts it to HTML, and the Viewer renders the result to the DOM.
```mermaid
graph LR
Input[User Input] --> Editor[MDEditor]
Editor -->|raw text| Parser[MDParser]
Parser -->|HTML| Viewer[MDViewer]
Editor -->|sync scroll| Viewer
Editor -->|export| Export[Markdown / HTML File]
```
## Core Components
### MDEditor
Markdown editor responsible for user input, cursor management, shortcut handling, and history tracking.
```mermaid
graph TB
Editor[MDEditor] --- Caret[editorCaret
Cursor Positioning]
Editor --- Selection[editorSelection
Selection Ops]
Editor --- History[editorHistory
Undo / Redo Stack]
Editor --- Keydown[editorKeydown
Shortcut Mapping]
Editor --- Panel[editorPanel
Toolbar]
Editor --- Tab[editorTab
Tab Indentation]
```
| Module | Responsibility |
|--------|---------------|
| `editorCaret` | Track and set cursor position (row index and offset) |
| `editorSelection` | Handle multi-line selection, cut, and paste range calculation |
| `editorHistory` | Maintain undo/redo dual stacks with delayed writes to merge consecutive inputs |
| `editorKeydown` | Map keyboard combos (`Cmd+B`, `Cmd+Z`, etc.) to editor methods |
| `editorPanel` | Render top toolbar buttons and bind corresponding actions |
| `editorTab` | Handle Tab key indentation and de-indentation logic |
### MDParser
Standalone Markdown-to-HTML parser with no DOM dependency, usable independently.
```mermaid
graph LR
Text[Markdown Text] --> Parse["parse()"]
Parse --> Trans[transToHTML]
Trans --> HTML[HTML String]
```
Parser internally calls `transToHTML()`, which sequentially executes each transform function in the parsing pipeline.
### MDViewer
Live previewer that receives HTML and renders it to the DOM. Includes a built-in vDOM diffing mechanism (currently paused in favor of full replacement for rendering correctness).
```mermaid
graph TB
Viewer[MDViewer]
Viewer -->|"timed trigger"| GetText["#get_text()"]
GetText -->|"Markdown"| Trans[transToHTML]
Trans -->|"HTML"| Render[replaceChildren]
Viewer --- Scroll["Sync Scrolling
(onwheel event forwarding)"]
Viewer --- Theme["Theme Switching
(light / dark / auto)"]
subgraph VDOM["vDOM (paused)"]
Diff["diff compare"]
Patch["patch apply"]
Diff --> Patch
end
```
## Parsing Pipeline
`transToHTML()` is the core of the entire parsing flow, calling the following transform functions in fixed order:
```mermaid
graph TB
Input["Raw Markdown"] --> Escape["Escape Pre-processing
(backslash sequences → placeholders)"]
Escape --> PreCode["setPreCode
Fenced Code Blocks"]
PreCode --> Code["setCode
Inline Code"]
Code --> Media["setMedia
Image / Video / Vimeo / YouTube"]
Media --> Link["setLink
Hyperlink / Email"]
Link --> Font["setFont
Bold / Italic / Strikethrough / Highlight / Sub-Superscript"]
Font --> Heading["setHeading
H1 – H6 Headings"]
Heading --> Hr["setHr
Horizontal Rule"]
Hr --> Table["setTable
Table"]
Table --> Blockquote["setBlockquote
Blockquote"]
Blockquote --> List["setList
Ordered / Unordered List / Checkbox"]
List --> TabCode["setTabCode
Tab-indented Code Block"]
TabCode --> Hashtag["setHashtag
Hashtag Link"]
Hashtag --> UUID["UUID Placeholder Restoration"]
UUID --> Cleanup["Escape Restoration
(placeholders → HTML entities)"]
Cleanup --> Output["HTML Output"]
```
### Pipeline Order and Design Rationale
| Order | Function | Rationale |
|-------|----------|-----------|
| 1 | `setPreCode` | Fenced code blocks must be isolated first to prevent inner syntax from being misinterpreted |
| 2 | `setCode` | Inline code must be isolated before font formatting |
| 3 | `setMedia` | Image syntax `![]()` resembles link syntax `[]()` and must match first |
| 4 | `setLink` | Hyperlinks and email links |
| 5 | `setFont` | Bold, italic, and other inline formatting |
| 6 | `setHeading` | Headings must precede lists (`#` may appear inside list items) |
| 7 | `setHr` | Horizontal rules `---` must precede tables to avoid conflicts |
| 8 | `setTable` | Table parsing |
| 9 | `setBlockquote` | Blockquotes (nestable) |
| 10 | `setList` | Ordered/unordered lists and checkboxes |
| 11 | `setTabCode` | Tab-indented code blocks processed last to avoid list conflicts |
| 12 | `setHashtag` | Hashtag links as the final text transform |
### UUID Placeholder Mechanism
During parsing, processed HTML fragments are replaced with UUID placeholders (`{{uuid32}}`) to prevent re-parsing by subsequent steps. After all transforms complete, placeholders are restored to actual HTML.
### Standard Mode
When `standard: true`, the extended versions of `setPreCode`, `setMedia`, `setLink`, `setFont`, `setBlockquote`, and `setHashtag` are skipped. Only standard Markdown equivalents (`setLinkStandard`, `setFontStandard`, `setBlockquoteStandard`) are used.
## Editor Event Flow
```mermaid
sequenceDiagram
participant U as User
participant E as MDEditor
participant H as editorHistory
participant V as MDViewer
participant T as transToHTML
U->>E: Keyboard input / Paste / Cut
E->>E: editorKeydown intercepts shortcuts
E->>E: editorCaret updates cursor position
E->>H: add(cursor, clearForward, delay)
H->>H: Delayed merge then push to undo stack
alt autosave enabled
E->>V: Trigger init()
V->>V: #get_text() reads editor text
V->>T: transToHTML(markdown)
T-->>V: HTML string
V->>V: replaceChildren(HTML)
end
U->>E: Cmd+Z
E->>H: undo()
H-->>E: Previous state (content + cursor)
E->>E: Restore editor content and cursor
E->>V: Trigger init()
```
## Virtual DOM Diffing
The vDOM module implements a lightweight diff algorithm that compares old and new node trees to produce minimal patch operations.
```mermaid
stateDiagram-v2
[*] --> Compare: Old/New vDOM input
Compare --> Create: Old node missing
Compare --> Remove: New node missing
Compare --> Replace: Different tag
Compare --> DiffProps: Same tag
DiffProps --> DiffChildren: Props diffed
DiffChildren --> Text: Both children are text
DiffChildren --> Append: Old child missing
DiffChildren --> RemoveChild: New child missing
DiffChildren --> Recurse: Recurse into children
Create --> [*]
Remove --> [*]
Replace --> [*]
Text --> [*]
Append --> [*]
RemoveChild --> [*]
Recurse --> Compare
```
### Patch Operation Types
| Type | Description |
|------|-------------|
| `create` | Add new node |
| `remove` | Remove node |
| `replace` | Replace node (different tag) |
| `prop` | Update or remove attribute |
| `text` | Update text content |
| `append` | Append child node |
> The vDOM diffing is currently paused in favor of `replaceChildren()` full replacement to ensure rendering correctness. A future version plans to rewrite the diff algorithm to reduce DOM operations.
## Theme System
```mermaid
graph LR
System["System Preference
prefers-color-scheme"] -->|auto| Mode{Mode Check}
User["User Specified
light / dark"] --> Mode
Mode -->|dark| Dark["data-mode='dark'"]
Mode -->|light| Light["data-mode=''"]
Dark --> CSS["SCSS Variable Switch"]
Light --> CSS
```
Editor and Viewer each maintain a `data-mode` attribute. SCSS uses `[data-mode="dark"]` selectors to switch color variables.
## Keyboard Shortcuts
| Combo | Action |
|-------|--------|
| `Cmd+B` | Bold |
| `Cmd+I` | Italic |
| `Cmd+Shift+X` | Strikethrough |
| `Cmd+U` | Underline |
| `Cmd+M` | Highlight marker |
| `Cmd+K` | Inline code |
| `Cmd+↑` | Superscript |
| `Cmd+↓` | Subscript |
| `Cmd+Z` | Undo |
| `Cmd+Shift+Z` | Redo |
| `Cmd+A` | Select all |
| `Cmd+S` | Save |
| `Cmd+]` | Indent |
| `Tab` | Insert indentation |
***
©️ 2024 [邱敬幃 Pardn Chiu](https://linkedin.com/in/pardnchiu)