@entropicwarrior/sdoc 0.1.9 → 0.1.11
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.
- package/README.md +67 -1
- package/lexica/sdoc-authoring.sdoc +32 -2
- package/lexica/slide-authoring.sdoc +2 -0
- package/lexica/specification.sdoc +20 -4
- package/package.json +29 -1
- package/src/notion-renderer.js +21 -1
- package/src/sdoc.js +114 -5
- package/src/slide-renderer.js +13 -2
package/README.md
CHANGED
|
@@ -77,6 +77,72 @@ Or use the **SDOC: Browse Documents** command from the VS Code Command Palette.
|
|
|
77
77
|
}
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
+
## Features
|
|
81
|
+
|
|
82
|
+
### Inline Formatting
|
|
83
|
+
|
|
84
|
+
`*emphasis*`, `**strong**`, `~~strikethrough~~`, `` `inline code` ``, `[links](url)`, and `<https://autolinks>`.
|
|
85
|
+
|
|
86
|
+
### Semantic Markers
|
|
87
|
+
|
|
88
|
+
Annotate text with semantic meaning that renders as colored highlights:
|
|
89
|
+
|
|
90
|
+
| Syntax | Meaning | Color |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| `{+text+}` | Positive | Green |
|
|
93
|
+
| `{=text=}` | Neutral | Blue |
|
|
94
|
+
| `{^text^}` | Caution | Amber |
|
|
95
|
+
| `{!text!}` | Warning | Orange |
|
|
96
|
+
| `{-text-}` | Negative | Red |
|
|
97
|
+
| `{~text~}` | Highlight | Yellow |
|
|
98
|
+
|
|
99
|
+
Markers nest with other inline formatting: `{+**all checks** passed+}`.
|
|
100
|
+
|
|
101
|
+
### Math
|
|
102
|
+
|
|
103
|
+
Inline math with `$x^2 + y^2$`, display math with `$$E = mc^2$$`, and multi-line equations with ` ```math ` code fences. Rendered via KaTeX.
|
|
104
|
+
|
|
105
|
+
### Code Blocks
|
|
106
|
+
|
|
107
|
+
Fenced with triple backticks, optional language tag for syntax highlighting. The `src:` directive includes external files inline:
|
|
108
|
+
|
|
109
|
+
````
|
|
110
|
+
```json src:./config.json lines:1-10
|
|
111
|
+
```
|
|
112
|
+
````
|
|
113
|
+
|
|
114
|
+
### Mermaid Diagrams
|
|
115
|
+
|
|
116
|
+
Code blocks tagged `mermaid` render as SVG diagrams — flowcharts, sequence diagrams, class diagrams, state diagrams, and more.
|
|
117
|
+
|
|
118
|
+
### Images
|
|
119
|
+
|
|
120
|
+
Markdown-style images with optional width and alignment:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+

|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Tables
|
|
127
|
+
|
|
128
|
+
Pipe-delimited tables with optional `borderless` and `headerless` flags.
|
|
129
|
+
|
|
130
|
+
### Lists
|
|
131
|
+
|
|
132
|
+
Bullet lists (`-`), numbered lists (`1.`), and task lists (`- [ ]` / `- [x]`). Items can have rich body content including nested lists, code blocks, and paragraphs.
|
|
133
|
+
|
|
134
|
+
### References
|
|
135
|
+
|
|
136
|
+
Tag any section with `@id` and cross-reference it anywhere with `@id` — renders as a clickable link.
|
|
137
|
+
|
|
138
|
+
### Slides
|
|
139
|
+
|
|
140
|
+
Turn any SDOC file into an HTML slide deck with themes, layouts (center, two-column), speaker notes, and PDF export.
|
|
141
|
+
|
|
142
|
+
### Custom Styling
|
|
143
|
+
|
|
144
|
+
Per-folder `sdoc.config.json` or per-file `@meta` scope for custom CSS, headers, footers, and confidentiality banners. Configs cascade from workspace root to file.
|
|
145
|
+
|
|
80
146
|
## Learning the Format
|
|
81
147
|
|
|
82
148
|
- `docs/guide/intro.sdoc` — what SDOC is and why
|
|
@@ -107,4 +173,4 @@ Release branches merge to both `main` and `develop`. No direct pushes to `main`
|
|
|
107
173
|
|
|
108
174
|
## License
|
|
109
175
|
|
|
110
|
-
[MIT](LICENSE) — 2026
|
|
176
|
+
[MIT](LICENSE) — 2026 @entropicwarrior
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
# Meta @meta
|
|
4
4
|
{
|
|
5
5
|
type: skill
|
|
6
|
+
|
|
7
|
+
sdoc-version: 0.1
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
# About @about
|
|
@@ -175,6 +177,14 @@
|
|
|
175
177
|
\`**text**\` | Strong
|
|
176
178
|
\`~~text~~\` | Strikethrough
|
|
177
179
|
\`\\\`code\\\`\` | Inline code
|
|
180
|
+
\`\$x^2\$\` | Inline math (KaTeX)
|
|
181
|
+
\`\$\$E = mc^2\$\$\` | Display math (centered)
|
|
182
|
+
\`\{+text+\}\` | Positive marker (green)
|
|
183
|
+
\`\{=text=\}\` | Neutral marker (blue)
|
|
184
|
+
\`\{^text^\}\` | Caution marker (amber)
|
|
185
|
+
\`\{!text!\}\` | Warning marker (orange)
|
|
186
|
+
\`\{-text-\}\` | Negative marker (red)
|
|
187
|
+
\`\{~text~\}\` | Highlight (yellow)
|
|
178
188
|
}
|
|
179
189
|
|
|
180
190
|
Links: \`[Link text](https://example.com)\`
|
|
@@ -192,6 +202,8 @@
|
|
|
192
202
|
```
|
|
193
203
|
|
|
194
204
|
Autolinks: \`\<https://example.com\>\`
|
|
205
|
+
|
|
206
|
+
Math: Use \`\$...\$\` for inline math and \`\$\$...\$\$\` for display math. Use \`\\\`\\\`\\\`math\` code fences for multi-line equations. A plain \`\$\` followed by a digit (e.g. \`\$100\`) does not trigger math mode.
|
|
195
207
|
}
|
|
196
208
|
|
|
197
209
|
# References @references
|
|
@@ -252,6 +264,19 @@
|
|
|
252
264
|
Mermaid supports flowcharts (\`graph\`), sequence diagrams (\`sequenceDiagram\`), class diagrams (\`classDiagram\`), state diagrams (\`stateDiagram-v2\`), and more. The Mermaid library is loaded from CDN only when a document contains mermaid blocks.
|
|
253
265
|
}
|
|
254
266
|
|
|
267
|
+
# Math Blocks @math-blocks
|
|
268
|
+
{
|
|
269
|
+
Code blocks with the \`math\` language tag are rendered as display equations via KaTeX:
|
|
270
|
+
|
|
271
|
+
````
|
|
272
|
+
```math
|
|
273
|
+
\\int_{-\\infty}^{\\infty} e^{-x^2} \\, dx = \\sqrt{\\pi}
|
|
274
|
+
```
|
|
275
|
+
````
|
|
276
|
+
|
|
277
|
+
Math blocks render as centered display equations with no copy button. The KaTeX CSS is loaded from CDN only when a document contains math content.
|
|
278
|
+
}
|
|
279
|
+
|
|
255
280
|
# Blockquotes @blockquotes
|
|
256
281
|
{
|
|
257
282
|
```
|
|
@@ -268,6 +293,8 @@
|
|
|
268
293
|
@meta {
|
|
269
294
|
type: doc
|
|
270
295
|
|
|
296
|
+
sdoc-version: 0.1
|
|
297
|
+
|
|
271
298
|
company: Irreversible Inc.
|
|
272
299
|
|
|
273
300
|
confidential: true
|
|
@@ -305,6 +332,9 @@
|
|
|
305
332
|
\`uuid: ...\` — unique identifier.
|
|
306
333
|
|
|
307
334
|
\`tags: tag1, tag2\` — comma-separated tags.
|
|
335
|
+
|
|
336
|
+
\`sdoc-version: 0.1\` — SDOC format version. A parser warning is
|
|
337
|
+
emitted when this key is missing from \`@meta\`.
|
|
308
338
|
}
|
|
309
339
|
|
|
310
340
|
# About Scope @about-scope
|
|
@@ -324,9 +354,9 @@
|
|
|
324
354
|
{
|
|
325
355
|
Backslash escapes special characters: \`\\\\\` \`\\{\` \`\\}\` \`\\@\`
|
|
326
356
|
\`\\[\` \`\\]\` \`\\(\` \`\\)\` \`\\*\` \`\\~\` \`\\#\` \`\\!\` \`\\\<\`
|
|
327
|
-
\`\\\>\`
|
|
357
|
+
\`\\\>\` \`\\\$\` \`\\+\` \`\\=\` \`\\-\` \`\\^\`
|
|
328
358
|
|
|
329
|
-
A line starting with \`\\#\` renders as a literal \`#\` (not a heading).
|
|
359
|
+
A line starting with \`\\#\` renders as a literal \`#\` (not a heading). Use \`\\\$\` to prevent a dollar sign from starting math mode.
|
|
330
360
|
}
|
|
331
361
|
}
|
|
332
362
|
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
# Meta @meta
|
|
4
4
|
{
|
|
5
5
|
type: doc
|
|
6
|
+
|
|
7
|
+
sdoc-version: 0.1
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
# About @about
|
|
@@ -448,16 +450,26 @@ Content of Section B.
|
|
|
448
450
|
- Strong: `**strong**`
|
|
449
451
|
- Strikethrough: `~~strike~~`
|
|
450
452
|
- Inline code: `` `code` ``
|
|
453
|
+
- Inline math: `$x^2$` (rendered via KaTeX)
|
|
454
|
+
- Display math: `$$E = mc^2$$` (centered block)
|
|
455
|
+
- Positive marker: `{+text+}` (green highlight)
|
|
456
|
+
- Neutral marker: `{=text=}` (blue highlight)
|
|
457
|
+
- Caution marker: `{^text^}` (amber highlight)
|
|
458
|
+
- Warning marker: `{!text!}` (orange highlight)
|
|
459
|
+
- Negative marker: `{-text-}` (red highlight)
|
|
460
|
+
- Highlight: `{~text~}` (yellow highlight)
|
|
451
461
|
}
|
|
462
|
+
|
|
463
|
+
Inline math requires non-whitespace immediately after the opening `$` and before the closing `$`. A plain `$` followed by a digit (e.g. `$100`) does not trigger math mode because there is no closing `$`.
|
|
452
464
|
}
|
|
453
465
|
|
|
454
466
|
# Escaping @escaping
|
|
455
467
|
{
|
|
456
|
-
In normal text (including headings and paragraphs), a backslash escapes: `\\` `\{` `\}` `\@` `\[` `\]` `\(` `\)` `\*` `\~` `\#` `\!` `\<` `\>` and `` \` ``.
|
|
468
|
+
In normal text (including headings and paragraphs), a backslash escapes: `\\` `\{` `\}` `\@` `\[` `\]` `\(` `\)` `\*` `\~` `\#` `\!` `\<` `\>` `\$` `\+` `\=` `\-` `\^` and `` \` ``.
|
|
457
469
|
|
|
458
470
|
Escapes are processed before reference detection.
|
|
459
471
|
|
|
460
|
-
If a line begins with `\#`, it is treated as a normal paragraph line (rendered with a literal `#`). If a line begins with `\>`, it is treated as a normal paragraph line (rendered with a literal `>`).
|
|
472
|
+
If a line begins with `\#`, it is treated as a normal paragraph line (rendered with a literal `#`). If a line begins with `\>`, it is treated as a normal paragraph line (rendered with a literal `>`). Use `\$` to prevent a dollar sign from starting math mode.
|
|
461
473
|
}
|
|
462
474
|
|
|
463
475
|
# Code Blocks @code-blocks
|
|
@@ -474,6 +486,8 @@ Content of Section B.
|
|
|
474
486
|
- The opening and closing fences must be on their own lines
|
|
475
487
|
- Anything inside is treated as raw text (no parsing, no escapes)
|
|
476
488
|
- Optional language tag after the opening fence
|
|
489
|
+
- The special language tag `math` renders the block as a display equation via KaTeX (no copy button)
|
|
490
|
+
- The special language tag `mermaid` renders the block as an SVG diagram via the Mermaid library
|
|
477
491
|
}
|
|
478
492
|
|
|
479
493
|
# Include by Link @code-includes
|
|
@@ -570,13 +584,15 @@ Content of Section B.
|
|
|
570
584
|
date: 2026-02-09
|
|
571
585
|
version: 1.0
|
|
572
586
|
status: Draft
|
|
587
|
+
sdoc-version: 0.1
|
|
573
588
|
}
|
|
574
589
|
```
|
|
575
590
|
|
|
576
591
|
{[.]
|
|
577
592
|
- Key matching is case-insensitive
|
|
578
593
|
- The pattern requires at least one space after the colon (`key: value`, not `key:value`)
|
|
579
|
-
- Well-known keys: `style`, `styleappend`/`style-append`, `header`, `footer`
|
|
594
|
+
- Well-known keys: `style`, `styleappend`/`style-append`, `header`, `footer`, `sdoc-version`
|
|
595
|
+
- `sdoc-version` identifies the SDOC format version the document targets (current: `0.1`). A parser warning is emitted when this key is missing.
|
|
580
596
|
- All other keys are stored as custom properties (e.g., `author`, `date`, `version`, `status`, `tags`)
|
|
581
597
|
- Sub-scope syntax takes precedence: if both `# Style { path }` and `style: path` exist, the sub-scope value wins
|
|
582
598
|
- Key:value and sub-scope syntax can be mixed freely in the same meta scope
|
|
@@ -773,7 +789,7 @@ Content of Section A.
|
|
|
773
789
|
- Comment syntax (if any)
|
|
774
790
|
- Duplicate ID resolution (error vs warning vs nearest-scope)
|
|
775
791
|
- Additional list types (checkboxes, alpha, roman)
|
|
776
|
-
- Additional inline formatting (underline
|
|
792
|
+
- Additional inline formatting (underline)
|
|
777
793
|
}
|
|
778
794
|
}
|
|
779
795
|
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@entropicwarrior/sdoc",
|
|
3
3
|
"displayName": "SDOC",
|
|
4
4
|
"description": "A plain-text documentation format with explicit brace scoping — deterministic parsing, AI-agent efficiency, and 10-50x token savings vs Markdown.",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.11",
|
|
6
6
|
"publisher": "entropicwarrior",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"repository": {
|
|
@@ -113,6 +113,34 @@
|
|
|
113
113
|
"configurationDefaults": {
|
|
114
114
|
"[sdoc]": {
|
|
115
115
|
"editor.wordWrap": "on"
|
|
116
|
+
},
|
|
117
|
+
"editor.tokenColorCustomizations": {
|
|
118
|
+
"textMateRules": [
|
|
119
|
+
{
|
|
120
|
+
"scope": "markup.quote.sdoc",
|
|
121
|
+
"settings": {
|
|
122
|
+
"foreground": "#3b82c3"
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"scope": "markup.changed.sdoc",
|
|
127
|
+
"settings": {
|
|
128
|
+
"foreground": "#b08a1a"
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"scope": "markup.keyword.warning.sdoc",
|
|
133
|
+
"settings": {
|
|
134
|
+
"foreground": "#d97706"
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"scope": "markup.other.highlight.sdoc",
|
|
139
|
+
"settings": {
|
|
140
|
+
"foreground": "#a68a00"
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
]
|
|
116
144
|
}
|
|
117
145
|
},
|
|
118
146
|
"languageModelTools": [
|
package/src/notion-renderer.js
CHANGED
|
@@ -76,6 +76,7 @@ function richText(content, href, annotations) {
|
|
|
76
76
|
function enforceContentLimit(richTexts, limit) {
|
|
77
77
|
const result = [];
|
|
78
78
|
for (const rt of richTexts) {
|
|
79
|
+
if (!rt.text) { result.push(rt); continue; }
|
|
79
80
|
const content = rt.text.content;
|
|
80
81
|
if (content.length <= limit) {
|
|
81
82
|
result.push(rt);
|
|
@@ -128,6 +129,22 @@ function flattenInlineNodes(nodes, annotations, href) {
|
|
|
128
129
|
case "image":
|
|
129
130
|
// Images are extracted at the block level; skip here.
|
|
130
131
|
break;
|
|
132
|
+
case "math_inline":
|
|
133
|
+
case "math_display":
|
|
134
|
+
result.push({
|
|
135
|
+
type: "equation",
|
|
136
|
+
equation: { expression: node.value },
|
|
137
|
+
annotations: ann
|
|
138
|
+
});
|
|
139
|
+
break;
|
|
140
|
+
case "mark_positive":
|
|
141
|
+
case "mark_neutral":
|
|
142
|
+
case "mark_caution":
|
|
143
|
+
case "mark_warning":
|
|
144
|
+
case "mark_negative":
|
|
145
|
+
case "mark_highlight":
|
|
146
|
+
result.push(...flattenInlineNodes(node.children, ann, href));
|
|
147
|
+
break;
|
|
131
148
|
default:
|
|
132
149
|
break;
|
|
133
150
|
}
|
|
@@ -160,7 +177,7 @@ function extractImagesFromInline(text) {
|
|
|
160
177
|
}
|
|
161
178
|
|
|
162
179
|
const rt = enforceContentLimit(flattenInlineNodes(textNodes), RICH_TEXT_LIMIT);
|
|
163
|
-
const hasText = rt.some((r) => r.text.content.trim() !== "");
|
|
180
|
+
const hasText = rt.some((r) => (r.text ? r.text.content.trim() !== "" : r.type === "equation"));
|
|
164
181
|
|
|
165
182
|
const imageBlocks = [];
|
|
166
183
|
for (const img of images) {
|
|
@@ -258,6 +275,9 @@ function renderNode(node, depth, nestLevel) {
|
|
|
258
275
|
case "table":
|
|
259
276
|
return renderTable(node);
|
|
260
277
|
case "code":
|
|
278
|
+
if (node.lang === "math") {
|
|
279
|
+
return [{ type: "equation", equation: { expression: node.text || "" } }];
|
|
280
|
+
}
|
|
261
281
|
return renderCode(node);
|
|
262
282
|
case "blockquote":
|
|
263
283
|
return renderBlockquote(node);
|
package/src/sdoc.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const SDOC_FORMAT_VERSION = "0.1";
|
|
2
|
+
|
|
1
3
|
const COMMAND_HEADING = "#";
|
|
2
4
|
const COMMAND_SCOPE_OPEN = "{";
|
|
3
5
|
const COMMAND_SCOPE_CLOSE = "}";
|
|
@@ -6,7 +8,29 @@ const COMMAND_LIST_NUMBER = "{[#]";
|
|
|
6
8
|
const COMMAND_TABLE = "{[table]";
|
|
7
9
|
const COMMAND_CODE_FENCE = "```";
|
|
8
10
|
|
|
9
|
-
const ESCAPABLE = new Set(["\\", "{", "}", "@", "[", "]", "(", ")", "*", "`", "#", "!", "~", "<", ">"]);
|
|
11
|
+
const ESCAPABLE = new Set(["\\", "{", "}", "@", "[", "]", "(", ")", "*", "`", "#", "!", "~", "<", ">", "$", "+", "=", "-", "^"]);
|
|
12
|
+
|
|
13
|
+
let _katex = null;
|
|
14
|
+
let _katexLoaded = false;
|
|
15
|
+
|
|
16
|
+
function getKatex() {
|
|
17
|
+
if (!_katexLoaded) {
|
|
18
|
+
_katexLoaded = true;
|
|
19
|
+
try {
|
|
20
|
+
_katex = require(require("path").join(__dirname, "..", "vendor", "katex.min.js"));
|
|
21
|
+
} catch { _katex = null; }
|
|
22
|
+
}
|
|
23
|
+
return _katex;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function renderKatex(latex, displayMode) {
|
|
27
|
+
const katex = getKatex();
|
|
28
|
+
if (katex) {
|
|
29
|
+
return katex.renderToString(latex, { displayMode, throwOnError: false });
|
|
30
|
+
}
|
|
31
|
+
const cls = displayMode ? "sdoc-math-fallback sdoc-math-display-fallback" : "sdoc-math-fallback";
|
|
32
|
+
return `<code class="${cls}">${escapeHtml(latex)}</code>`;
|
|
33
|
+
}
|
|
10
34
|
|
|
11
35
|
function isTableCommand(text) {
|
|
12
36
|
return /^\{\[table(?:\s+[^\]]*?)?\]$/.test(text);
|
|
@@ -1132,6 +1156,28 @@ function parseInline(text) {
|
|
|
1132
1156
|
}
|
|
1133
1157
|
}
|
|
1134
1158
|
|
|
1159
|
+
// Display math $$...$$ (must come before $ check)
|
|
1160
|
+
if (text.startsWith("$$", i)) {
|
|
1161
|
+
const end = findUnescaped(text, i + 2, "$$");
|
|
1162
|
+
if (end !== -1 && end > i + 2) {
|
|
1163
|
+
flush();
|
|
1164
|
+
nodes.push({ type: "math_display", value: text.slice(i + 2, end) });
|
|
1165
|
+
i = end + 2;
|
|
1166
|
+
continue;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
// Inline math $...$ (require non-whitespace after open and before close)
|
|
1171
|
+
if (ch === "$" && next && next !== " " && next !== "\t" && next !== "$") {
|
|
1172
|
+
const end = findUnescaped(text, i + 1, "$");
|
|
1173
|
+
if (end !== -1 && end > i + 1 && text[end - 1] !== " " && text[end - 1] !== "\t") {
|
|
1174
|
+
flush();
|
|
1175
|
+
nodes.push({ type: "math_inline", value: text.slice(i + 1, end) });
|
|
1176
|
+
i = end + 1;
|
|
1177
|
+
continue;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1135
1181
|
if (text.startsWith("**", i)) {
|
|
1136
1182
|
const end = findUnescaped(text, i + 2, "**");
|
|
1137
1183
|
if (end !== -1) {
|
|
@@ -1154,6 +1200,26 @@ function parseInline(text) {
|
|
|
1154
1200
|
}
|
|
1155
1201
|
}
|
|
1156
1202
|
|
|
1203
|
+
if (ch === "{") {
|
|
1204
|
+
const mc = next;
|
|
1205
|
+
let mt = null;
|
|
1206
|
+
if (mc === "+") mt = "mark_positive";
|
|
1207
|
+
else if (mc === "=") mt = "mark_neutral";
|
|
1208
|
+
else if (mc === "^") mt = "mark_caution";
|
|
1209
|
+
else if (mc === "!") mt = "mark_warning";
|
|
1210
|
+
else if (mc === "-") mt = "mark_negative";
|
|
1211
|
+
else if (mc === "~") mt = "mark_highlight";
|
|
1212
|
+
if (mt) {
|
|
1213
|
+
const end = findUnescaped(text, i + 2, mc + "}");
|
|
1214
|
+
if (end !== -1) {
|
|
1215
|
+
flush();
|
|
1216
|
+
nodes.push({ type: mt, children: parseInline(text.slice(i + 2, end)) });
|
|
1217
|
+
i = end + 2;
|
|
1218
|
+
continue;
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1157
1223
|
if (ch === "*") {
|
|
1158
1224
|
const end = findUnescaped(text, i + 1, "*");
|
|
1159
1225
|
if (end !== -1) {
|
|
@@ -1296,6 +1362,18 @@ function renderInlineNodes(nodes) {
|
|
|
1296
1362
|
return `<strong>${renderInlineNodes(node.children)}</strong>`;
|
|
1297
1363
|
case "strike":
|
|
1298
1364
|
return `<del>${renderInlineNodes(node.children)}</del>`;
|
|
1365
|
+
case "mark_positive":
|
|
1366
|
+
return `<span class="sdoc-mark sdoc-mark-positive">${renderInlineNodes(node.children)}</span>`;
|
|
1367
|
+
case "mark_neutral":
|
|
1368
|
+
return `<span class="sdoc-mark sdoc-mark-neutral">${renderInlineNodes(node.children)}</span>`;
|
|
1369
|
+
case "mark_caution":
|
|
1370
|
+
return `<span class="sdoc-mark sdoc-mark-caution">${renderInlineNodes(node.children)}</span>`;
|
|
1371
|
+
case "mark_warning":
|
|
1372
|
+
return `<span class="sdoc-mark sdoc-mark-warning">${renderInlineNodes(node.children)}</span>`;
|
|
1373
|
+
case "mark_negative":
|
|
1374
|
+
return `<span class="sdoc-mark sdoc-mark-negative">${renderInlineNodes(node.children)}</span>`;
|
|
1375
|
+
case "mark_highlight":
|
|
1376
|
+
return `<mark class="sdoc-mark sdoc-mark-highlight">${renderInlineNodes(node.children)}</mark>`;
|
|
1299
1377
|
case "link":
|
|
1300
1378
|
return `<a class="sdoc-link" href="${escapeAttr(node.href)}" target="_blank" rel="noopener noreferrer">${renderInlineNodes(
|
|
1301
1379
|
node.children
|
|
@@ -1309,6 +1387,10 @@ function renderInlineNodes(nodes) {
|
|
|
1309
1387
|
const imgStyle = imgParts.length ? ` style="${imgParts.join(";")}"` : "";
|
|
1310
1388
|
return `<img class="sdoc-image" src="${escapeAttr(node.src)}" alt="${escapeAttr(node.alt)}"${imgStyle} />`;
|
|
1311
1389
|
}
|
|
1390
|
+
case "math_inline":
|
|
1391
|
+
return `<span class="sdoc-math sdoc-math-inline">${renderKatex(node.value, false)}</span>`;
|
|
1392
|
+
case "math_display":
|
|
1393
|
+
return `<span class="sdoc-math sdoc-math-display">${renderKatex(node.value, true)}</span>`;
|
|
1312
1394
|
default:
|
|
1313
1395
|
return "";
|
|
1314
1396
|
}
|
|
@@ -1452,6 +1534,9 @@ function renderNode(node, depth) {
|
|
|
1452
1534
|
if (node.lang === "mermaid") {
|
|
1453
1535
|
return `<pre class="mermaid"${dl}>${escapeHtml(node.text)}</pre>`;
|
|
1454
1536
|
}
|
|
1537
|
+
if (node.lang === "math") {
|
|
1538
|
+
return `<div class="sdoc-math sdoc-math-block"${dl}>${renderKatex(node.text, true)}</div>`;
|
|
1539
|
+
}
|
|
1455
1540
|
const langClass = node.lang ? ` class="language-${escapeAttr(node.lang)}"` : "";
|
|
1456
1541
|
return `<div class="sdoc-code-wrap"${dl}><pre class="sdoc-code"><code${langClass}>${escapeHtml(node.text)}</code></pre><button class="sdoc-copy-btn" title="Copy code">\u29C9</button></div>`;
|
|
1457
1542
|
}
|
|
@@ -1484,7 +1569,7 @@ function extractMeta(nodes) {
|
|
|
1484
1569
|
}
|
|
1485
1570
|
|
|
1486
1571
|
if (metaIndex === -1) {
|
|
1487
|
-
return { nodes, meta: {} };
|
|
1572
|
+
return { nodes, meta: {}, warnings: [] };
|
|
1488
1573
|
}
|
|
1489
1574
|
|
|
1490
1575
|
const metaNode = searchNodes[metaIndex];
|
|
@@ -1545,14 +1630,19 @@ function extractMeta(nodes) {
|
|
|
1545
1630
|
meta.company = meta.properties.company || null;
|
|
1546
1631
|
meta.confidential = meta.properties.confidential || null;
|
|
1547
1632
|
|
|
1633
|
+
const warnings = [];
|
|
1634
|
+
if (!meta.properties["sdoc-version"]) {
|
|
1635
|
+
warnings.push("Missing sdoc-version in @meta (current format version is " + SDOC_FORMAT_VERSION + ")");
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1548
1638
|
if (doc) {
|
|
1549
1639
|
// @meta was inside the document scope — strip it from children
|
|
1550
1640
|
const filteredChildren = doc.children.filter((_, index) => index !== metaIndex);
|
|
1551
1641
|
const stripped = { ...doc, children: filteredChildren };
|
|
1552
|
-
return { nodes: [stripped], meta };
|
|
1642
|
+
return { nodes: [stripped], meta, warnings };
|
|
1553
1643
|
}
|
|
1554
1644
|
const bodyNodes = nodes.filter((_, index) => index !== metaIndex);
|
|
1555
|
-
return { nodes: bodyNodes, meta };
|
|
1645
|
+
return { nodes: bodyNodes, meta, warnings };
|
|
1556
1646
|
}
|
|
1557
1647
|
|
|
1558
1648
|
function collectParagraphText(nodes) {
|
|
@@ -1834,6 +1924,18 @@ const DEFAULT_STYLE = `
|
|
|
1834
1924
|
padding: 0 0.2em;
|
|
1835
1925
|
}
|
|
1836
1926
|
|
|
1927
|
+
.sdoc-mark {
|
|
1928
|
+
border-radius: 3px;
|
|
1929
|
+
padding: 0.05em 0.3em;
|
|
1930
|
+
font-weight: 500;
|
|
1931
|
+
}
|
|
1932
|
+
.sdoc-mark-positive { background-color: rgba(34, 139, 34, 0.15); color: #166016; }
|
|
1933
|
+
.sdoc-mark-neutral { background-color: rgba(59, 130, 195, 0.15); color: #245d8a; }
|
|
1934
|
+
.sdoc-mark-caution { background-color: rgba(200, 150, 30, 0.18); color: #7a5f0e; }
|
|
1935
|
+
.sdoc-mark-warning { background-color: rgba(255, 120, 0, 0.18); color: #b35400; }
|
|
1936
|
+
.sdoc-mark-negative { background-color: rgba(187, 50, 50, 0.15); color: #911e1e; }
|
|
1937
|
+
.sdoc-mark-highlight { background-color: rgba(255, 255, 0, 0.75); }
|
|
1938
|
+
|
|
1837
1939
|
.sdoc-image {
|
|
1838
1940
|
display: inline-block;
|
|
1839
1941
|
max-width: 100%;
|
|
@@ -1929,10 +2031,12 @@ const PRINT_STYLE = `
|
|
|
1929
2031
|
white-space: pre-wrap;
|
|
1930
2032
|
word-wrap: break-word;
|
|
1931
2033
|
}
|
|
2034
|
+
.sdoc-mark { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
|
1932
2035
|
}
|
|
1933
2036
|
`;
|
|
1934
2037
|
|
|
1935
2038
|
const MERMAID_CDN = "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js";
|
|
2039
|
+
const KATEX_CDN_CSS = "https://cdn.jsdelivr.net/npm/katex@0.16/dist/katex.min.css";
|
|
1936
2040
|
|
|
1937
2041
|
function hasMermaidBlocks(nodes) {
|
|
1938
2042
|
for (const node of nodes) {
|
|
@@ -1980,6 +2084,9 @@ function renderHtmlDocumentFromParsed(parsed, title, options = {}) {
|
|
|
1980
2084
|
const mermaidScript = hasMermaidBlocks(parsed.nodes)
|
|
1981
2085
|
? `\n<script src="${MERMAID_CDN}"></script>\n<script>mermaid.initialize({startOnLoad:true,theme:"neutral",themeCSS:".node rect, .node polygon, .node circle { rx: 4; ry: 4; }"});</script>`
|
|
1982
2086
|
: "";
|
|
2087
|
+
const katexCssTag = body.includes('class="katex"')
|
|
2088
|
+
? `\n<link rel="stylesheet" href="${KATEX_CDN_CSS}" />`
|
|
2089
|
+
: "";
|
|
1983
2090
|
|
|
1984
2091
|
return `<!DOCTYPE html>
|
|
1985
2092
|
<html lang="en">
|
|
@@ -1989,7 +2096,7 @@ function renderHtmlDocumentFromParsed(parsed, title, options = {}) {
|
|
|
1989
2096
|
<title>${escapeHtml(title)}</title>
|
|
1990
2097
|
<style>
|
|
1991
2098
|
${cssBase}${cssAppend}
|
|
1992
|
-
</style
|
|
2099
|
+
</style>${katexCssTag}
|
|
1993
2100
|
</head>
|
|
1994
2101
|
<body>
|
|
1995
2102
|
<div class="sdoc-shell">
|
|
@@ -2236,6 +2343,7 @@ async function resolveIncludes(nodes, resolverFn) {
|
|
|
2236
2343
|
}
|
|
2237
2344
|
|
|
2238
2345
|
module.exports = {
|
|
2346
|
+
SDOC_FORMAT_VERSION,
|
|
2239
2347
|
parseSdoc,
|
|
2240
2348
|
extractMeta,
|
|
2241
2349
|
resolveIncludes,
|
|
@@ -2251,6 +2359,7 @@ module.exports = {
|
|
|
2251
2359
|
extractAbout,
|
|
2252
2360
|
// Low-level helpers for custom renderers (e.g. slide-renderer)
|
|
2253
2361
|
parseInline,
|
|
2362
|
+
renderKatex,
|
|
2254
2363
|
escapeHtml,
|
|
2255
2364
|
escapeAttr
|
|
2256
2365
|
};
|
package/src/slide-renderer.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// const { nodes, meta } = extractMeta(parsed.nodes);
|
|
8
8
|
// const html = renderSlides(nodes, { meta, themeCss, themeJs });
|
|
9
9
|
|
|
10
|
-
const { parseInline, escapeHtml, escapeAttr } = require("./sdoc");
|
|
10
|
+
const { parseInline, renderKatex, escapeHtml, escapeAttr } = require("./sdoc");
|
|
11
11
|
|
|
12
12
|
// ---------------------------------------------------------------------------
|
|
13
13
|
// Inline rendering — produces clean HTML without sdoc-* classes
|
|
@@ -40,6 +40,10 @@ function renderInlineNodes(nodes) {
|
|
|
40
40
|
}
|
|
41
41
|
case "ref":
|
|
42
42
|
return `@${escapeHtml(node.id)}`;
|
|
43
|
+
case "math_inline":
|
|
44
|
+
return `<span class="sdoc-math sdoc-math-inline">${renderKatex(node.value, false)}</span>`;
|
|
45
|
+
case "math_display":
|
|
46
|
+
return `<span class="sdoc-math sdoc-math-display">${renderKatex(node.value, true)}</span>`;
|
|
43
47
|
default:
|
|
44
48
|
return "";
|
|
45
49
|
}
|
|
@@ -67,6 +71,9 @@ function renderNode(node) {
|
|
|
67
71
|
if (node.lang === "mermaid") {
|
|
68
72
|
return `<pre class="mermaid">${escapeHtml(node.text)}</pre>`;
|
|
69
73
|
}
|
|
74
|
+
if (node.lang === "math") {
|
|
75
|
+
return `<div class="sdoc-math sdoc-math-block">${renderKatex(node.text, true)}</div>`;
|
|
76
|
+
}
|
|
70
77
|
const langClass = node.lang ? ` class="language-${escapeAttr(node.lang)}"` : "";
|
|
71
78
|
return `<pre><code${langClass}>${escapeHtml(node.text)}</code></pre>`;
|
|
72
79
|
}
|
|
@@ -324,6 +331,10 @@ function renderSlides(nodes, options = {}) {
|
|
|
324
331
|
const mermaidTag = slidesHtml.includes('class="mermaid"')
|
|
325
332
|
? `\n<script src="${mermaidCdn}"></script>\n<script>mermaid.initialize({startOnLoad:true,theme:"neutral",themeCSS:".node rect, .node polygon, .node circle { rx: 4; ry: 4; }"});</script>`
|
|
326
333
|
: "";
|
|
334
|
+
const katexCssCdn = "https://cdn.jsdelivr.net/npm/katex@0.16/dist/katex.min.css";
|
|
335
|
+
const katexTag = slidesHtml.includes('class="katex"')
|
|
336
|
+
? `\n<link rel="stylesheet" href="${katexCssCdn}" />`
|
|
337
|
+
: "";
|
|
327
338
|
|
|
328
339
|
return `<!DOCTYPE html>
|
|
329
340
|
<html lang="en">
|
|
@@ -331,7 +342,7 @@ function renderSlides(nodes, options = {}) {
|
|
|
331
342
|
<meta charset="UTF-8">
|
|
332
343
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
333
344
|
<title>${escapeHtml(title)}</title>
|
|
334
|
-
${cssTag}
|
|
345
|
+
${cssTag}${katexTag}
|
|
335
346
|
</head>
|
|
336
347
|
<body>
|
|
337
348
|
${slidesHtml}
|