orz-markdown 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,182 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Document</title>
7
+
8
+ <!-- Google Fonts preconnect hints (needed when using a theme with Google Fonts) -->
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+
12
+ <!-- KaTeX CSS — required for all math rendering ($...$ and $$...$$) -->
13
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.35/dist/katex.min.css">
14
+
15
+ <!-- Highlight.js — pick a theme that matches your color scheme.
16
+ Light themes: github.min.css, stackoverflow-light.min.css
17
+ Dark themes: atom-one-dark.min.css, github-dark.min.css -->
18
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css">
19
+
20
+ <!-- Parser stylesheet — choose one of the bundled themes or link your own.
21
+ Bundled themes (each imports common.css automatically):
22
+ dark-elegant-1.css dark-elegant-2.css
23
+ light-neat-1.css light-neat-2.css
24
+ beige-decent-1.css beige-decent-2.css
25
+ light-academic-1.css light-academic-2.css
26
+ light-playful-1.css light-playful-2.css
27
+ Minimal no-frills stylesheet (feature support only): minimal.css -->
28
+ <link rel="stylesheet" href="./minimal.css">
29
+ </head>
30
+ <body>
31
+
32
+ <!-- All rendered HTML from md.render() goes inside this article element.
33
+ The .markdown-body class is required — all theme styles are scoped under it. -->
34
+ <article class="markdown-body">
35
+ <!-- INSERT RENDERED HTML HERE -->
36
+ </article>
37
+
38
+ <!-- =====================================================================
39
+ Required client-side libraries and runtime scripts.
40
+ These must appear after the body content so the DOM is ready.
41
+ ===================================================================== -->
42
+
43
+ <!-- Highlight.js — syntax-highlights fenced code blocks -->
44
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
45
+ <script>if (typeof hljs !== 'undefined') hljs.highlightAll();</script>
46
+
47
+ <!-- Mermaid.js — renders {{mermaid}} / {{mm}} diagram blocks client-side.
48
+ The .mermaid div contains the raw Mermaid DSL emitted by the parser. -->
49
+ <script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
50
+ <script>if (typeof mermaid !== 'undefined') mermaid.initialize({ startOnLoad: true });</script>
51
+
52
+ <!-- SmilesDrawer — renders {{smiles}} / {{sm}} chemical structure canvases.
53
+ Draws into canvas[data-smiles] elements emitted by the parser. -->
54
+ <script src="https://unpkg.com/smiles-drawer@1.0.10/dist/smiles-drawer.min.js"></script>
55
+ <script>
56
+ if (typeof SmilesDrawer !== 'undefined') {
57
+ document.querySelectorAll('canvas[data-smiles]').forEach(function (canvas, i) {
58
+ if (!canvas.id) canvas.id = 'smiles-canvas-' + i;
59
+ var drawer = new SmilesDrawer.Drawer({ width: canvas.width || 250, height: canvas.height || 180 });
60
+ SmilesDrawer.parse(canvas.getAttribute('data-smiles'), function (tree) {
61
+ drawer.draw(tree, canvas, 'light', false);
62
+ });
63
+ });
64
+ }
65
+ </script>
66
+
67
+ <!-- Tabs component — builds the .tabs-bar and activates .tab panels.
68
+ Must run after the DOM is painted so .tabs elements are found. -->
69
+ <script>
70
+ (function () {
71
+ document.querySelectorAll('.tabs').forEach(function (tabs) {
72
+ tabs.setAttribute('data-js', '1');
73
+ var panels = tabs.querySelectorAll(':scope > .tab');
74
+ if (!panels.length) return;
75
+
76
+ var bar = document.createElement('div');
77
+ bar.className = 'tabs-bar';
78
+ panels.forEach(function (panel, i) {
79
+ var label = panel.getAttribute('data-label') || 'Tab ' + (i + 1);
80
+ var btn = document.createElement('button');
81
+ btn.className = 'tabs-bar-btn' + (i === 0 ? ' active' : '');
82
+ btn.textContent = label;
83
+ btn.addEventListener('click', function () {
84
+ tabs.querySelectorAll('.tabs-bar-btn').forEach(function (b) { b.classList.remove('active'); });
85
+ panels.forEach(function (p) { p.classList.remove('active'); });
86
+ btn.classList.add('active');
87
+ panel.classList.add('active');
88
+ });
89
+ bar.appendChild(btn);
90
+ });
91
+ tabs.insertBefore(bar, tabs.firstChild);
92
+ panels[0].classList.add('active');
93
+ });
94
+ })();
95
+ </script>
96
+
97
+ <!-- QR code runtime — click to expand {{qrcode}} / {{qr}} overlays.
98
+ Provides window.OrzMarkdownRuntime.init(root) for programmatic use. -->
99
+ <script>
100
+ (function (global) {
101
+ var expandedQr = null;
102
+ var expandedSourceQr = null;
103
+
104
+ function collapseQr() {
105
+ if (!expandedQr) return;
106
+ if (expandedSourceQr) {
107
+ expandedSourceQr.classList.remove('is-expanded');
108
+ expandedSourceQr.setAttribute('aria-expanded', 'false');
109
+ }
110
+ if (expandedQr.parentNode) expandedQr.parentNode.removeChild(expandedQr);
111
+ expandedQr = null;
112
+ expandedSourceQr = null;
113
+ if (global.document && global.document.body) global.document.body.style.overflow = '';
114
+ }
115
+
116
+ function toggleQr(node) {
117
+ if (expandedSourceQr === node) { collapseQr(); return; }
118
+ if (expandedQr) collapseQr();
119
+ expandedSourceQr = node;
120
+ node.classList.add('is-expanded');
121
+ node.setAttribute('aria-expanded', 'true');
122
+ expandedQr = node.cloneNode(true);
123
+ expandedQr.classList.add('qrcode-overlay', 'is-expanded');
124
+ expandedQr.setAttribute('aria-hidden', 'true');
125
+ expandedQr.removeAttribute('aria-expanded');
126
+ expandedQr.removeAttribute('tabindex');
127
+ expandedQr.removeAttribute('role');
128
+ expandedQr.addEventListener('click', function (event) {
129
+ event.stopPropagation();
130
+ collapseQr();
131
+ });
132
+ global.document.body.appendChild(expandedQr);
133
+ if (global.document && global.document.body) global.document.body.style.overflow = 'hidden';
134
+ }
135
+
136
+ function initQrCodes(root) {
137
+ if (!root || typeof root.querySelectorAll !== 'function') return;
138
+ Array.prototype.slice.call(root.querySelectorAll('.qrcode')).forEach(function (node) {
139
+ if (node.getAttribute('data-qr-ready') === '1') return;
140
+ node.setAttribute('data-qr-ready', '1');
141
+ node.setAttribute('tabindex', '0');
142
+ node.setAttribute('role', 'button');
143
+ node.setAttribute('aria-expanded', 'false');
144
+ node.addEventListener('click', function (event) {
145
+ event.stopPropagation();
146
+ toggleQr(node);
147
+ });
148
+ node.addEventListener('keydown', function (event) {
149
+ if (event.key === 'Enter' || event.key === ' ') {
150
+ event.preventDefault();
151
+ toggleQr(node);
152
+ }
153
+ });
154
+ });
155
+ }
156
+
157
+ function bindGlobalHandlers() {
158
+ global.document.addEventListener('click', function () { collapseQr(); });
159
+ global.document.addEventListener('keydown', function (event) {
160
+ if (event.key === 'Escape') collapseQr();
161
+ });
162
+ }
163
+
164
+ function init(doc) {
165
+ initQrCodes(doc);
166
+ bindGlobalHandlers();
167
+ }
168
+
169
+ global.OrzMarkdownRuntime = { init: init, initQrCodes: initQrCodes };
170
+
171
+ if (global.document) {
172
+ if (global.document.readyState === 'loading') {
173
+ global.document.addEventListener('DOMContentLoaded', function () { init(global.document); }, { once: true });
174
+ } else {
175
+ init(global.document);
176
+ }
177
+ }
178
+ })(typeof globalThis !== 'undefined' ? globalThis : window);
179
+ </script>
180
+
181
+ </body>
182
+ </html>
@@ -0,0 +1,51 @@
1
+ # Stable Block IDs — Preservation Rules
2
+
3
+ Host applications built on orz-markdown attach stable block identifiers to headings so that sections keep their identity across edits, AI rewrites, and document versions. This file is the contract an AI editor MUST follow whenever a document uses these IDs. Violating it silently breaks cross-references, progress tracking, and anything else the host keys off block identity.
4
+
5
+ ---
6
+
7
+ ## What a block ID looks like
8
+
9
+ A block ID is `blk-` followed by 8 or more lowercase base36 characters (`a–z`, `0–9`), attached with the `attrs` plugin immediately after a heading:
10
+
11
+ ```markdown
12
+ ## Acid-Base Theory{{attrs[#blk-abc12345]}}
13
+ ```
14
+
15
+ Canonical form: the `{{attrs[...]}}` marker comes **directly after the heading text with no space before it**. (A space before the marker also parses, but the no-space form is canonical — emit it when writing new headings.)
16
+
17
+ Renders as:
18
+
19
+ ```html
20
+ <h2 id="blk-abc12345" tabindex="-1">Acid-Base Theory</h2>
21
+ ```
22
+
23
+ The ID overrides the auto-generated `markdown-it-anchor` slug, so the anchor stays fixed no matter what the heading says.
24
+
25
+ ---
26
+
27
+ ## The rules
28
+
29
+ 1. **IDs are immutable.** When editing or rewriting a section, NEVER change, remove, regenerate, or "fix" its `{{attrs[#...]}}` marker. Preserve it character-for-character — even when you rewrite the heading text completely.
30
+
31
+ ```markdown
32
+ Before: ## Acid-Base Theory{{attrs[#blk-abc12345]}}
33
+ After: ## Brønsted–Lowry Acids and Bases{{attrs[#blk-abc12345]}} ← same ID
34
+ ```
35
+
36
+ 2. **Never reuse an ID from a deleted block** for new content. A deleted block's identity dies with it.
37
+
38
+ 3. **Replacing is not editing.** When a section is deliberately replaced with new content (rather than edited), the host application mints the new ID. Do not invent one yourself unless explicitly asked.
39
+
40
+ 4. **Copies do not inherit IDs.** When copying a section into another document, do not carry the ID along verbatim if the host supplies a new one. An ID must never appear twice within one document.
41
+
42
+ 5. **Never convert to or from Pandoc-style `{#id}` syntax.** orz-markdown does not support it: `## Title {#blk-x}` renders the braces as literal heading text and corrupts the anchor slug. The only supported form is `{{attrs[#...]}}` (or `{{attrs[id="..."]}}`).
43
+
44
+ ---
45
+
46
+ ## Quick checklist before saving an edit
47
+
48
+ - Every heading that had a `{{attrs[#blk-...]}}` marker before your edit still has the **same** marker.
49
+ - No new ID was invented, regenerated, or copied from elsewhere.
50
+ - No ID appears more than once in the document.
51
+ - No Pandoc `{#id}` syntax anywhere.
@@ -0,0 +1,218 @@
1
+ # CSS Class Reference — orz-markdown
2
+
3
+ Every CSS class and structural HTML element emitted by the parser. Use this as the definitive spec when writing or auditing a theme. All selectors should be scoped under `.markdown-body` in theme files.
4
+
5
+ ---
6
+
7
+ ## Article wrapper
8
+
9
+ | Selector | Source | Notes |
10
+ |---|---|---|
11
+ | `.markdown-body` | `<article>` wrapping all rendered content | Every theme scopes its styles here |
12
+
13
+ ---
14
+
15
+ ## Standard markdown elements
16
+
17
+ No plugin-specific classes. Style by tag name scoped under `.markdown-body`.
18
+
19
+ | Element | Notes |
20
+ |---|---|
21
+ | `h1` – `h6` | Headings. `markdown-it-anchor` adds `id` and injects `<a class="header-anchor">` inside. |
22
+ | `p` | Paragraph. |
23
+ | `a` | Hyperlink. |
24
+ | `strong`, `em` | Bold / italic. |
25
+ | `s`, `del` | Strikethrough. |
26
+ | `mark` | Highlighted text (`==text==` via `markdown-it-mark`). |
27
+ | `ins` | Inserted text (`++text++` via `markdown-it-ins`). |
28
+ | `sub`, `sup` | Subscript / superscript (`~sub~` / `^sup^`). |
29
+ | `code` | Inline code. |
30
+ | `pre > code` | Fenced code block. Highlight.js adds token classes when active. |
31
+ | `blockquote` | Block quote. |
32
+ | `ul`, `ol`, `li` | Lists. |
33
+ | `table`, `thead`, `tbody`, `tr`, `th`, `td` | GFM table. |
34
+ | `hr` | Horizontal rule. |
35
+ | `img` | Image. `markdown-it-imsize` adds `width`/`height` attributes when `=WxH` syntax used. |
36
+
37
+ ---
38
+
39
+ ## markdown-it-anchor
40
+
41
+ | Class | Element | Description |
42
+ |---|---|---|
43
+ | `.header-anchor` | `<a>` | Permalink prepended inside each heading. Hide by default, show on heading hover. |
44
+
45
+ ---
46
+
47
+ ## markdown-it-task-lists
48
+
49
+ | Class | Element | Description |
50
+ |---|---|---|
51
+ | `.task-list-item` | `<li>` | List item beginning with `[ ]` or `[x]`. Remove default list-style. |
52
+ | `.task-list-item-checkbox` | `<input type="checkbox">` | Checkbox inside the task item. Usually `pointer-events: none`. |
53
+
54
+ ---
55
+
56
+ ## markdown-it-footnote
57
+
58
+ | Class | Element | Description |
59
+ |---|---|---|
60
+ | `.footnote-ref` | `<sup><a>` | Inline superscript reference (`[^1]`). |
61
+ | `.footnotes` | `<section>` | Container for all footnotes at page bottom. |
62
+ | `.footnotes-sep` | `<hr>` | Separator between body and footnotes section. |
63
+ | `.footnote-backref` | `<a>` | Back-link from definition to reference. |
64
+
65
+ ---
66
+
67
+ ## KaTeX
68
+
69
+ | Class | Element | Description |
70
+ |---|---|---|
71
+ | `.katex` | `<span>` | Wrapper for every inline math expression (`$...$`). |
72
+ | `.katex-display` | `<span>` | Wrapper for display block math (`$$...$$`). Add overflow-x for wide equations. |
73
+ | `.katex-error` | `<span>` | Shown when KaTeX cannot parse the expression. Style as a visible error (red, monospace). |
74
+
75
+ ---
76
+
77
+ ## Semantic callouts (`markdown-it-container`)
78
+
79
+ | Class | Element | Description |
80
+ |---|---|---|
81
+ | `.success` | `<div>` | Green success callout. `:::success ... :::` |
82
+ | `.info` | `<div>` | Blue info callout. `:::info ... :::` |
83
+ | `.warning` | `<div>` | Amber warning callout. `:::warning ... :::` |
84
+ | `.danger` | `<div>` | Red danger callout. `:::danger ... :::` |
85
+
86
+ Each has `> *:last-child { margin-bottom: 0 }` as a best practice.
87
+
88
+ ---
89
+
90
+ ## Layout containers
91
+
92
+ | Class | Element | Description |
93
+ |---|---|---|
94
+ | `.left` | `<div>` | Float-left wrapper. `:::left ... :::` Needs mobile unfloat breakpoint. |
95
+ | `.right` | `<div>` | Right-aligned wrapper. `:::right ... :::` |
96
+ | `.center` | `<div>` | Centred wrapper. `:::center ... :::` |
97
+
98
+ ---
99
+
100
+ ## Spoiler
101
+
102
+ | Selector | Element | Description |
103
+ |---|---|---|
104
+ | `details.spoil` | `<details>` | Collapsible spoiler block. `:::spoil My Title ... :::` |
105
+ | `details.spoil > summary` | `<summary>` | Toggle label. Hide `::webkit-details-marker`. Provide open/close marker via `::before`. |
106
+ | `details.spoil[open] > summary` | `<summary>` | Style when open. Update `::before` content. |
107
+ | `details.spoil > :not(summary)` | any | Body content area. Apply padding here. |
108
+
109
+ ---
110
+
111
+ ## Tabs
112
+
113
+ | Selector | Element | Description |
114
+ |---|---|---|
115
+ | `.tabs` | `<div>` | Outer wrapper. `:::: tabs ... ::::` |
116
+ | `.tabs[data-js]` | `<div>` | Added by JS after initialisation. Use `.tabs:not([data-js])` for no-JS fallback. |
117
+ | `.tabs-bar` | `<div>` | Tab button bar injected by JS. |
118
+ | `.tabs-bar-btn` | `<button>` | Individual tab button. |
119
+ | `.tabs-bar-btn.active` | `<button>` | Currently selected tab button. |
120
+ | `.tab` | `<div>` | Individual tab panel. **Must be `display: none` by default.** |
121
+ | `.tab.active` | `<div>` | Currently visible panel. **Must be `display: block`.** |
122
+ | `.tabs:not([data-js]) .tab` | `<div>` | No-JS fallback: show all panels. **Must be `display: block`.** |
123
+
124
+ ---
125
+
126
+ ## Columns
127
+
128
+ | Class | Element | Description |
129
+ |---|---|---|
130
+ | `.cols` | `<div>` | CSS grid wrapper. `:::cols ... :::` Use `grid-template-columns: repeat(auto-fit, minmax(..., 1fr))`. |
131
+ | `.col` | `<div>` | Individual column cell. `:::col ... :::` |
132
+
133
+ ---
134
+
135
+ ## Custom plugins
136
+
137
+ ### YouTube embed
138
+
139
+ | Class | Element | Description |
140
+ |---|---|---|
141
+ | `.youtube-embed` | `<div>` | 16:9 responsive wrapper. Use padding-bottom trick (`height:0; padding-bottom:56.25%`). |
142
+
143
+ ```css
144
+ div.youtube-embed { position: relative; height: 0; padding-bottom: 56.25%; overflow: hidden; }
145
+ div.youtube-embed iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
146
+ ```
147
+
148
+ ### Mermaid diagram
149
+
150
+ | Class | Element | Description |
151
+ |---|---|---|
152
+ | `.mermaid` | `<div>` | Contains raw Mermaid DSL. Mermaid.js replaces it with `<svg>` client-side. |
153
+
154
+ ```css
155
+ div.mermaid { overflow-x: auto; text-align: center; }
156
+ div.mermaid svg { max-width: 100%; height: auto; }
157
+ ```
158
+
159
+ ### SMILES chemical structure
160
+
161
+ | Class | Element | Description |
162
+ |---|---|---|
163
+ | `.smiles-render` | `<div>` | Wrapper around the canvas. Centered by `common.css` structural rules. |
164
+ | `canvas[data-smiles]` | `<canvas>` | Drawing target. Width/height set as HTML attributes. SmilesDrawer draws here client-side. |
165
+
166
+ The structural flex centering is already in `common.css`. Theme only needs background/border on `.smiles-render` (usually `background: transparent; border: none`).
167
+
168
+ ### QR code
169
+
170
+ | Class | Element | Description |
171
+ |---|---|---|
172
+ | `.qrcode` | `<span>` | Inline wrapper around the SVG. **Must have `background: #fff; padding: 4px`** so the black SVG is readable on any background. |
173
+ | `.qrcode__icon` | `<span>` | Expand icon shown on hover. Structural rules in `common.css`. |
174
+ | `.qrcode.is-expanded` | `<span>` | Added while expanded. `common.css` hides the source with `visibility: hidden`. |
175
+ | `.qrcode-overlay` | `<span>` (cloned) | Fixed-position fullscreen overlay. All geometry handled by `common.css`. Theme can adjust `background`. |
176
+
177
+ ```css
178
+ span.qrcode { background: #fff; padding: 4px; /* or 6px */ vertical-align: middle; }
179
+ ```
180
+
181
+ ### Span (inline color / badge)
182
+
183
+ | Selector | Description |
184
+ |---|---|
185
+ | `span.red` | Color only — set `color: var(--span-red)` |
186
+ | `span.yellow` | Color only — set `color: var(--span-yellow)` |
187
+ | `span.green` | Color only — set `color: var(--span-green)` |
188
+ | `span.blue` | Color only — set `color: var(--span-blue)` |
189
+ | `span.success` | Badge — background, text color, border matching success palette |
190
+ | `span.info` | Badge — background, text color, border matching info palette |
191
+ | `span.warning` | Badge — background, text color, border matching warning palette |
192
+ | `span.danger` | Badge — background, text color, border matching danger palette |
193
+
194
+ Note: `span.success/info/warning/danger` also have `display: inline-flex; align-items: center; line-height: 1; white-space: nowrap` set by `common.css`. Add `padding`, `border-radius`, `font-size`, and colors in the theme.
195
+
196
+ ### Table of Contents
197
+
198
+ | Class | Element | Description |
199
+ |---|---|---|
200
+ | `.toc-wrapper` | `<div>` | Outer wrapper (for positioning). |
201
+ | `nav.toc` / `.toc-list` | `<nav>` | The `<ul>` tree of heading links. Reset list bullets in TOC context. |
202
+
203
+ ```css
204
+ nav.toc, .toc-list {
205
+ padding: ...;
206
+ border: 1px solid var(--border);
207
+ }
208
+ nav.toc ul, .toc-list ul { margin: 0; padding-left: 1.1rem; }
209
+ nav.toc li, .toc-list li { list-style: none; margin-bottom: 0.25rem; }
210
+ nav.toc a, .toc-list a { text-decoration: none; color: var(--link); }
211
+ ```
212
+
213
+ ### Test elements (development only)
214
+
215
+ | Class | Element | Description |
216
+ |---|---|---|
217
+ | `.test-block` | `<div>` | Block debug output. Style as a visually distinct dev-only box. |
218
+ | `.test-inline` | `<span>` | Inline debug output. |