@entropicwarrior/sdoc 0.1.9 → 0.1.10
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 +1 -1
- package/lexica/sdoc-authoring.sdoc +31 -2
- package/lexica/slide-authoring.sdoc +2 -0
- package/lexica/specification.sdoc +18 -3
- package/package.json +1 -1
- package/src/notion-renderer.js +20 -1
- package/src/sdoc.js +110 -5
- package/src/slide-renderer.js +13 -2
package/README.md
CHANGED
|
@@ -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,13 @@
|
|
|
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!\}\` | Warning marker (amber)
|
|
185
|
+
\`\{-text-\}\` | Negative marker (red)
|
|
186
|
+
\`\{~text~\}\` | Highlight (yellow)
|
|
178
187
|
}
|
|
179
188
|
|
|
180
189
|
Links: \`[Link text](https://example.com)\`
|
|
@@ -192,6 +201,8 @@
|
|
|
192
201
|
```
|
|
193
202
|
|
|
194
203
|
Autolinks: \`\<https://example.com\>\`
|
|
204
|
+
|
|
205
|
+
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
206
|
}
|
|
196
207
|
|
|
197
208
|
# References @references
|
|
@@ -252,6 +263,19 @@
|
|
|
252
263
|
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
264
|
}
|
|
254
265
|
|
|
266
|
+
# Math Blocks @math-blocks
|
|
267
|
+
{
|
|
268
|
+
Code blocks with the \`math\` language tag are rendered as display equations via KaTeX:
|
|
269
|
+
|
|
270
|
+
````
|
|
271
|
+
```math
|
|
272
|
+
\\int_{-\\infty}^{\\infty} e^{-x^2} \\, dx = \\sqrt{\\pi}
|
|
273
|
+
```
|
|
274
|
+
````
|
|
275
|
+
|
|
276
|
+
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.
|
|
277
|
+
}
|
|
278
|
+
|
|
255
279
|
# Blockquotes @blockquotes
|
|
256
280
|
{
|
|
257
281
|
```
|
|
@@ -268,6 +292,8 @@
|
|
|
268
292
|
@meta {
|
|
269
293
|
type: doc
|
|
270
294
|
|
|
295
|
+
sdoc-version: 0.1
|
|
296
|
+
|
|
271
297
|
company: Irreversible Inc.
|
|
272
298
|
|
|
273
299
|
confidential: true
|
|
@@ -305,6 +331,9 @@
|
|
|
305
331
|
\`uuid: ...\` — unique identifier.
|
|
306
332
|
|
|
307
333
|
\`tags: tag1, tag2\` — comma-separated tags.
|
|
334
|
+
|
|
335
|
+
\`sdoc-version: 0.1\` — SDOC format version. A parser warning is
|
|
336
|
+
emitted when this key is missing from \`@meta\`.
|
|
308
337
|
}
|
|
309
338
|
|
|
310
339
|
# About Scope @about-scope
|
|
@@ -324,9 +353,9 @@
|
|
|
324
353
|
{
|
|
325
354
|
Backslash escapes special characters: \`\\\\\` \`\\{\` \`\\}\` \`\\@\`
|
|
326
355
|
\`\\[\` \`\\]\` \`\\(\` \`\\)\` \`\\*\` \`\\~\` \`\\#\` \`\\!\` \`\\\<\`
|
|
327
|
-
\`\\\>\`
|
|
356
|
+
\`\\\>\` \`\\\$\` \`\\+\` \`\\=\` \`\\-\`
|
|
328
357
|
|
|
329
|
-
A line starting with \`\\#\` renders as a literal \`#\` (not a heading).
|
|
358
|
+
A line starting with \`\\#\` renders as a literal \`#\` (not a heading). Use \`\\\$\` to prevent a dollar sign from starting math mode.
|
|
330
359
|
}
|
|
331
360
|
}
|
|
332
361
|
|
|
@@ -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,25 @@ 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
|
+
- Warning marker: `{!text!}` (amber highlight)
|
|
458
|
+
- Negative marker: `{-text-}` (red highlight)
|
|
459
|
+
- Highlight: `{~text~}` (yellow highlight)
|
|
451
460
|
}
|
|
461
|
+
|
|
462
|
+
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
463
|
}
|
|
453
464
|
|
|
454
465
|
# Escaping @escaping
|
|
455
466
|
{
|
|
456
|
-
In normal text (including headings and paragraphs), a backslash escapes: `\\` `\{` `\}` `\@` `\[` `\]` `\(` `\)` `\*` `\~` `\#` `\!` `\<` `\>` and `` \` ``.
|
|
467
|
+
In normal text (including headings and paragraphs), a backslash escapes: `\\` `\{` `\}` `\@` `\[` `\]` `\(` `\)` `\*` `\~` `\#` `\!` `\<` `\>` `\$` `\+` `\=` `\-` and `` \` ``.
|
|
457
468
|
|
|
458
469
|
Escapes are processed before reference detection.
|
|
459
470
|
|
|
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 `>`).
|
|
471
|
+
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
472
|
}
|
|
462
473
|
|
|
463
474
|
# Code Blocks @code-blocks
|
|
@@ -474,6 +485,8 @@ Content of Section B.
|
|
|
474
485
|
- The opening and closing fences must be on their own lines
|
|
475
486
|
- Anything inside is treated as raw text (no parsing, no escapes)
|
|
476
487
|
- Optional language tag after the opening fence
|
|
488
|
+
- The special language tag `math` renders the block as a display equation via KaTeX (no copy button)
|
|
489
|
+
- The special language tag `mermaid` renders the block as an SVG diagram via the Mermaid library
|
|
477
490
|
}
|
|
478
491
|
|
|
479
492
|
# Include by Link @code-includes
|
|
@@ -570,13 +583,15 @@ Content of Section B.
|
|
|
570
583
|
date: 2026-02-09
|
|
571
584
|
version: 1.0
|
|
572
585
|
status: Draft
|
|
586
|
+
sdoc-version: 0.1
|
|
573
587
|
}
|
|
574
588
|
```
|
|
575
589
|
|
|
576
590
|
{[.]
|
|
577
591
|
- Key matching is case-insensitive
|
|
578
592
|
- 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`
|
|
593
|
+
- Well-known keys: `style`, `styleappend`/`style-append`, `header`, `footer`, `sdoc-version`
|
|
594
|
+
- `sdoc-version` identifies the SDOC format version the document targets (current: `0.1`). A parser warning is emitted when this key is missing.
|
|
580
595
|
- All other keys are stored as custom properties (e.g., `author`, `date`, `version`, `status`, `tags`)
|
|
581
596
|
- Sub-scope syntax takes precedence: if both `# Style { path }` and `style: path` exist, the sub-scope value wins
|
|
582
597
|
- Key:value and sub-scope syntax can be mixed freely in the same meta scope
|
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.10",
|
|
6
6
|
"publisher": "entropicwarrior",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"repository": {
|
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,21 @@ 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_warning":
|
|
143
|
+
case "mark_negative":
|
|
144
|
+
case "mark_highlight":
|
|
145
|
+
result.push(...flattenInlineNodes(node.children, ann, href));
|
|
146
|
+
break;
|
|
131
147
|
default:
|
|
132
148
|
break;
|
|
133
149
|
}
|
|
@@ -160,7 +176,7 @@ function extractImagesFromInline(text) {
|
|
|
160
176
|
}
|
|
161
177
|
|
|
162
178
|
const rt = enforceContentLimit(flattenInlineNodes(textNodes), RICH_TEXT_LIMIT);
|
|
163
|
-
const hasText = rt.some((r) => r.text.content.trim() !== "");
|
|
179
|
+
const hasText = rt.some((r) => (r.text ? r.text.content.trim() !== "" : r.type === "equation"));
|
|
164
180
|
|
|
165
181
|
const imageBlocks = [];
|
|
166
182
|
for (const img of images) {
|
|
@@ -258,6 +274,9 @@ function renderNode(node, depth, nestLevel) {
|
|
|
258
274
|
case "table":
|
|
259
275
|
return renderTable(node);
|
|
260
276
|
case "code":
|
|
277
|
+
if (node.lang === "math") {
|
|
278
|
+
return [{ type: "equation", equation: { expression: node.text || "" } }];
|
|
279
|
+
}
|
|
261
280
|
return renderCode(node);
|
|
262
281
|
case "blockquote":
|
|
263
282
|
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,25 @@ 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_warning";
|
|
1209
|
+
else if (mc === "-") mt = "mark_negative";
|
|
1210
|
+
else if (mc === "~") mt = "mark_highlight";
|
|
1211
|
+
if (mt) {
|
|
1212
|
+
const end = findUnescaped(text, i + 2, mc + "}");
|
|
1213
|
+
if (end !== -1) {
|
|
1214
|
+
flush();
|
|
1215
|
+
nodes.push({ type: mt, children: parseInline(text.slice(i + 2, end)) });
|
|
1216
|
+
i = end + 2;
|
|
1217
|
+
continue;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1157
1222
|
if (ch === "*") {
|
|
1158
1223
|
const end = findUnescaped(text, i + 1, "*");
|
|
1159
1224
|
if (end !== -1) {
|
|
@@ -1296,6 +1361,16 @@ function renderInlineNodes(nodes) {
|
|
|
1296
1361
|
return `<strong>${renderInlineNodes(node.children)}</strong>`;
|
|
1297
1362
|
case "strike":
|
|
1298
1363
|
return `<del>${renderInlineNodes(node.children)}</del>`;
|
|
1364
|
+
case "mark_positive":
|
|
1365
|
+
return `<span class="sdoc-mark sdoc-mark-positive">${renderInlineNodes(node.children)}</span>`;
|
|
1366
|
+
case "mark_neutral":
|
|
1367
|
+
return `<span class="sdoc-mark sdoc-mark-neutral">${renderInlineNodes(node.children)}</span>`;
|
|
1368
|
+
case "mark_warning":
|
|
1369
|
+
return `<span class="sdoc-mark sdoc-mark-warning">${renderInlineNodes(node.children)}</span>`;
|
|
1370
|
+
case "mark_negative":
|
|
1371
|
+
return `<span class="sdoc-mark sdoc-mark-negative">${renderInlineNodes(node.children)}</span>`;
|
|
1372
|
+
case "mark_highlight":
|
|
1373
|
+
return `<mark class="sdoc-mark sdoc-mark-highlight">${renderInlineNodes(node.children)}</mark>`;
|
|
1299
1374
|
case "link":
|
|
1300
1375
|
return `<a class="sdoc-link" href="${escapeAttr(node.href)}" target="_blank" rel="noopener noreferrer">${renderInlineNodes(
|
|
1301
1376
|
node.children
|
|
@@ -1309,6 +1384,10 @@ function renderInlineNodes(nodes) {
|
|
|
1309
1384
|
const imgStyle = imgParts.length ? ` style="${imgParts.join(";")}"` : "";
|
|
1310
1385
|
return `<img class="sdoc-image" src="${escapeAttr(node.src)}" alt="${escapeAttr(node.alt)}"${imgStyle} />`;
|
|
1311
1386
|
}
|
|
1387
|
+
case "math_inline":
|
|
1388
|
+
return `<span class="sdoc-math sdoc-math-inline">${renderKatex(node.value, false)}</span>`;
|
|
1389
|
+
case "math_display":
|
|
1390
|
+
return `<span class="sdoc-math sdoc-math-display">${renderKatex(node.value, true)}</span>`;
|
|
1312
1391
|
default:
|
|
1313
1392
|
return "";
|
|
1314
1393
|
}
|
|
@@ -1452,6 +1531,9 @@ function renderNode(node, depth) {
|
|
|
1452
1531
|
if (node.lang === "mermaid") {
|
|
1453
1532
|
return `<pre class="mermaid"${dl}>${escapeHtml(node.text)}</pre>`;
|
|
1454
1533
|
}
|
|
1534
|
+
if (node.lang === "math") {
|
|
1535
|
+
return `<div class="sdoc-math sdoc-math-block"${dl}>${renderKatex(node.text, true)}</div>`;
|
|
1536
|
+
}
|
|
1455
1537
|
const langClass = node.lang ? ` class="language-${escapeAttr(node.lang)}"` : "";
|
|
1456
1538
|
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
1539
|
}
|
|
@@ -1484,7 +1566,7 @@ function extractMeta(nodes) {
|
|
|
1484
1566
|
}
|
|
1485
1567
|
|
|
1486
1568
|
if (metaIndex === -1) {
|
|
1487
|
-
return { nodes, meta: {} };
|
|
1569
|
+
return { nodes, meta: {}, warnings: [] };
|
|
1488
1570
|
}
|
|
1489
1571
|
|
|
1490
1572
|
const metaNode = searchNodes[metaIndex];
|
|
@@ -1545,14 +1627,19 @@ function extractMeta(nodes) {
|
|
|
1545
1627
|
meta.company = meta.properties.company || null;
|
|
1546
1628
|
meta.confidential = meta.properties.confidential || null;
|
|
1547
1629
|
|
|
1630
|
+
const warnings = [];
|
|
1631
|
+
if (!meta.properties["sdoc-version"]) {
|
|
1632
|
+
warnings.push("Missing sdoc-version in @meta (current format version is " + SDOC_FORMAT_VERSION + ")");
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1548
1635
|
if (doc) {
|
|
1549
1636
|
// @meta was inside the document scope — strip it from children
|
|
1550
1637
|
const filteredChildren = doc.children.filter((_, index) => index !== metaIndex);
|
|
1551
1638
|
const stripped = { ...doc, children: filteredChildren };
|
|
1552
|
-
return { nodes: [stripped], meta };
|
|
1639
|
+
return { nodes: [stripped], meta, warnings };
|
|
1553
1640
|
}
|
|
1554
1641
|
const bodyNodes = nodes.filter((_, index) => index !== metaIndex);
|
|
1555
|
-
return { nodes: bodyNodes, meta };
|
|
1642
|
+
return { nodes: bodyNodes, meta, warnings };
|
|
1556
1643
|
}
|
|
1557
1644
|
|
|
1558
1645
|
function collectParagraphText(nodes) {
|
|
@@ -1834,6 +1921,17 @@ const DEFAULT_STYLE = `
|
|
|
1834
1921
|
padding: 0 0.2em;
|
|
1835
1922
|
}
|
|
1836
1923
|
|
|
1924
|
+
.sdoc-mark {
|
|
1925
|
+
border-radius: 3px;
|
|
1926
|
+
padding: 0.05em 0.3em;
|
|
1927
|
+
font-weight: 500;
|
|
1928
|
+
}
|
|
1929
|
+
.sdoc-mark-positive { background-color: rgba(34, 139, 34, 0.15); color: #166016; }
|
|
1930
|
+
.sdoc-mark-neutral { background-color: rgba(59, 130, 195, 0.15); color: #245d8a; }
|
|
1931
|
+
.sdoc-mark-warning { background-color: rgba(200, 150, 30, 0.18); color: #7a5f0e; }
|
|
1932
|
+
.sdoc-mark-negative { background-color: rgba(187, 50, 50, 0.15); color: #911e1e; }
|
|
1933
|
+
.sdoc-mark-highlight { background-color: rgba(255, 255, 0, 0.75); }
|
|
1934
|
+
|
|
1837
1935
|
.sdoc-image {
|
|
1838
1936
|
display: inline-block;
|
|
1839
1937
|
max-width: 100%;
|
|
@@ -1929,10 +2027,12 @@ const PRINT_STYLE = `
|
|
|
1929
2027
|
white-space: pre-wrap;
|
|
1930
2028
|
word-wrap: break-word;
|
|
1931
2029
|
}
|
|
2030
|
+
.sdoc-mark { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
|
1932
2031
|
}
|
|
1933
2032
|
`;
|
|
1934
2033
|
|
|
1935
2034
|
const MERMAID_CDN = "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js";
|
|
2035
|
+
const KATEX_CDN_CSS = "https://cdn.jsdelivr.net/npm/katex@0.16/dist/katex.min.css";
|
|
1936
2036
|
|
|
1937
2037
|
function hasMermaidBlocks(nodes) {
|
|
1938
2038
|
for (const node of nodes) {
|
|
@@ -1980,6 +2080,9 @@ function renderHtmlDocumentFromParsed(parsed, title, options = {}) {
|
|
|
1980
2080
|
const mermaidScript = hasMermaidBlocks(parsed.nodes)
|
|
1981
2081
|
? `\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
2082
|
: "";
|
|
2083
|
+
const katexCssTag = body.includes('class="katex"')
|
|
2084
|
+
? `\n<link rel="stylesheet" href="${KATEX_CDN_CSS}" />`
|
|
2085
|
+
: "";
|
|
1983
2086
|
|
|
1984
2087
|
return `<!DOCTYPE html>
|
|
1985
2088
|
<html lang="en">
|
|
@@ -1989,7 +2092,7 @@ function renderHtmlDocumentFromParsed(parsed, title, options = {}) {
|
|
|
1989
2092
|
<title>${escapeHtml(title)}</title>
|
|
1990
2093
|
<style>
|
|
1991
2094
|
${cssBase}${cssAppend}
|
|
1992
|
-
</style
|
|
2095
|
+
</style>${katexCssTag}
|
|
1993
2096
|
</head>
|
|
1994
2097
|
<body>
|
|
1995
2098
|
<div class="sdoc-shell">
|
|
@@ -2236,6 +2339,7 @@ async function resolveIncludes(nodes, resolverFn) {
|
|
|
2236
2339
|
}
|
|
2237
2340
|
|
|
2238
2341
|
module.exports = {
|
|
2342
|
+
SDOC_FORMAT_VERSION,
|
|
2239
2343
|
parseSdoc,
|
|
2240
2344
|
extractMeta,
|
|
2241
2345
|
resolveIncludes,
|
|
@@ -2251,6 +2355,7 @@ module.exports = {
|
|
|
2251
2355
|
extractAbout,
|
|
2252
2356
|
// Low-level helpers for custom renderers (e.g. slide-renderer)
|
|
2253
2357
|
parseInline,
|
|
2358
|
+
renderKatex,
|
|
2254
2359
|
escapeHtml,
|
|
2255
2360
|
escapeAttr
|
|
2256
2361
|
};
|
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}
|