@entropicwarrior/sdoc 0.2.13 → 0.2.14
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/docs/reference/sdoc-authoring.sdoc +10 -5
- package/lexica/specification.sdoc +28 -2
- package/package.json +1 -1
- package/src/notion-renderer.js +49 -1
- package/src/sdoc.js +104 -12
- package/tools/build-doc.js +10 -4
|
@@ -554,7 +554,7 @@ Content of Section B.
|
|
|
554
554
|
{
|
|
555
555
|
The reserved \`@meta\` scope configures per-file settings and is not rendered in the document body. Use the bare \`@meta\` form (preferred) or the heading form:
|
|
556
556
|
|
|
557
|
-
```
|
|
557
|
+
```sdoc
|
|
558
558
|
@meta {
|
|
559
559
|
type: doc
|
|
560
560
|
|
|
@@ -604,15 +604,20 @@ Content of Section B.
|
|
|
604
604
|
|
|
605
605
|
# About Scope @about-scope
|
|
606
606
|
{
|
|
607
|
-
The reserved \`@about\` scope provides a discovery summary for the document. It is used by \`list_knowledge\` to describe the file
|
|
607
|
+
The reserved \`@about\` scope provides a discovery summary for the document. It is used by \`list_knowledge\` to describe the file and is also rendered in the live preview with a distinct meta-section style so readers can tell it apart from regular body content. Use the bare \`@about\` form (preferred) or the heading form:
|
|
608
608
|
|
|
609
|
-
```
|
|
609
|
+
```sdoc
|
|
610
610
|
@about {
|
|
611
|
-
How to write correct SDOC files.
|
|
611
|
+
How to write correct SDOC files.
|
|
612
|
+
Covers document structure, inline formatting, block types, and common mistakes.
|
|
612
613
|
}
|
|
613
614
|
```
|
|
614
615
|
|
|
615
|
-
The heading form \`# About @about { }\` also works but the bare form is preferred.
|
|
616
|
+
The heading form \`# About @about { }\` also works but the bare form is preferred. With the bare form, the renderer synthesizes an \`About\` heading so the meta-section is always labelled. The heading form lets you override that label — \`# Document Discovery @about { }\` renders with `Document Discovery` as the title.
|
|
617
|
+
|
|
618
|
+
**Hidden by default in HTML / PDF exports.** Exported files are usually sent to a recipient who has already decided to read the document, so the \`@about\` discovery summary is redundant in that context. To keep it in an export, pass the opt-in flag — \`--include-about\` (see [CLI and Tools Reference](./cli.sdoc)).
|
|
619
|
+
|
|
620
|
+
An empty or whitespace-only \`@about\` scope is never rendered, regardless of the include flag.
|
|
616
621
|
}
|
|
617
622
|
|
|
618
623
|
# Escaping @escaping
|
|
@@ -982,6 +982,29 @@ Content of Section B.
|
|
|
982
982
|
}
|
|
983
983
|
}
|
|
984
984
|
}
|
|
985
|
+
|
|
986
|
+
# About Scope @about-scope
|
|
987
|
+
{
|
|
988
|
+
A reserved \`@about\` scope holds a short discovery summary for the document — what it covers, who it is for, and when to reach for it. The bare form is preferred:
|
|
989
|
+
|
|
990
|
+
```sdoc
|
|
991
|
+
@about {
|
|
992
|
+
How to write correct SDOC files. Covers document structure,
|
|
993
|
+
inline formatting, block types, and common mistakes.
|
|
994
|
+
}
|
|
995
|
+
```
|
|
996
|
+
|
|
997
|
+
The heading form `# Title @about { }` is also accepted; the title given there overrides the default `About` label when the section is rendered.
|
|
998
|
+
|
|
999
|
+
{[.]
|
|
1000
|
+
- The `@about` id is reserved and must not be used for normal references
|
|
1001
|
+
- The scope is consumed by discovery tooling (e.g. `list_knowledge` and related agent-facing helpers) to describe a document without loading the full body
|
|
1002
|
+
- In the VS Code live preview, `@about` renders as a meta-section — distinct background, dashed border, subdued typography — so readers can tell it apart from regular body content
|
|
1003
|
+
- **HTML and PDF export hide `@about` by default**, on the principle that an exported file is normally sent to a recipient who has already been asked to read it, so the "should I read this?" framing is redundant. Renderers expose an `includeAbout` opt-in (`--include-about` for the CLI; `{ includeAbout: true }` for the VS Code export commands)
|
|
1004
|
+
- When rendered, a bare `@about { ... }` (no heading line) is given a synthesized `About` heading so the meta-section is always labelled. The heading form preserves any custom title the author wrote
|
|
1005
|
+
- An empty or whitespace-only `@about` scope is never rendered, regardless of the `includeAbout` setting — the meta-section box is suppressed entirely
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
985
1008
|
}
|
|
986
1009
|
|
|
987
1010
|
# Interactive Preview @interactive-preview
|
|
@@ -1146,9 +1169,12 @@ Content of Section A.
|
|
|
1146
1169
|
| "left" | "center" | "right" ;
|
|
1147
1170
|
percentage = digit { digit } [ "." digit { digit } ] "%" ;
|
|
1148
1171
|
pixels = digit { digit } "px" ;
|
|
1149
|
-
table_body = [ directive_row ]
|
|
1172
|
+
table_body = table_row [ directive_row ] { table_row }
|
|
1173
|
+
| directive_row { table_row } ;
|
|
1174
|
+
(* normal tables: header row then optional directive row;
|
|
1175
|
+
headerless tables: optional directive row first *)
|
|
1150
1176
|
directive_row = directive_cell { "|" directive_cell } ;
|
|
1151
|
-
directive_cell = [ align_char ] [
|
|
1177
|
+
directive_cell = [ align_char ] [ format_spec ] ;
|
|
1152
1178
|
align_char = "<" | ">" | "=" ;
|
|
1153
1179
|
format_spec = "$" [ "." digits ]
|
|
1154
1180
|
| "," [ "." digits ]
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@entropicwarrior/sdoc",
|
|
3
3
|
"displayName": "SDOC - Docs for Human/Agent Teams",
|
|
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.2.
|
|
5
|
+
"version": "0.2.14",
|
|
6
6
|
"publisher": "entropicwarrior-msenfin",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"repository": {
|
package/src/notion-renderer.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// const { nodes, meta } = extractMeta(parsed.nodes);
|
|
8
8
|
// const blocks = renderNotionBlocks(nodes);
|
|
9
9
|
|
|
10
|
-
const { parseInline } = require("./sdoc");
|
|
10
|
+
const { parseInline, isAboutEmpty } = require("./sdoc");
|
|
11
11
|
|
|
12
12
|
// ---------------------------------------------------------------------------
|
|
13
13
|
// Constants
|
|
@@ -293,6 +293,11 @@ function renderNode(node, depth, nestLevel) {
|
|
|
293
293
|
function renderScope(scope, depth, nestLevel) {
|
|
294
294
|
if (scope.scopeType === "comment") return [];
|
|
295
295
|
|
|
296
|
+
if (scope.id && scope.id.toLowerCase() === "about") {
|
|
297
|
+
if (isAboutEmpty(scope)) return [];
|
|
298
|
+
return renderAboutCallout(scope, depth, nestLevel);
|
|
299
|
+
}
|
|
300
|
+
|
|
296
301
|
const level = Math.min(3, Math.max(1, depth));
|
|
297
302
|
|
|
298
303
|
if (scope.hasHeading === false) {
|
|
@@ -310,6 +315,49 @@ function renderScope(scope, depth, nestLevel) {
|
|
|
310
315
|
return [headingBlock(level, scope.title, null), ...childBlocks];
|
|
311
316
|
}
|
|
312
317
|
|
|
318
|
+
// Render @about as a Notion callout so readers can tell it apart from body
|
|
319
|
+
// content. Paragraph children fold into the callout's rich_text (separated by
|
|
320
|
+
// newlines); anything else becomes a child block.
|
|
321
|
+
function renderAboutCallout(scope, depth, nestLevel) {
|
|
322
|
+
// Callout children sit one level deeper in the Notion block tree than the
|
|
323
|
+
// callout itself. Clamp to MAX_NEST so we never produce blocks past Notion's
|
|
324
|
+
// nesting limit.
|
|
325
|
+
const childDepth = depth + 1;
|
|
326
|
+
const childNestLevel = Math.min(nestLevel + 1, MAX_NEST);
|
|
327
|
+
const richTexts = [];
|
|
328
|
+
const childBlocks = [];
|
|
329
|
+
|
|
330
|
+
for (const child of scope.children || []) {
|
|
331
|
+
if (child.type === "paragraph") {
|
|
332
|
+
if (richTexts.length > 0) {
|
|
333
|
+
richTexts.push(richText("\n", null, defaultAnnotations()));
|
|
334
|
+
}
|
|
335
|
+
const { richText: rt, imageBlocks } = extractImagesFromInline(child.text);
|
|
336
|
+
richTexts.push(...rt);
|
|
337
|
+
childBlocks.push(...imageBlocks);
|
|
338
|
+
} else {
|
|
339
|
+
childBlocks.push(...renderNode(child, childDepth, childNestLevel));
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (richTexts.length === 0) {
|
|
344
|
+
richTexts.push(richText("", null, defaultAnnotations()));
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const callout = {
|
|
348
|
+
type: "callout",
|
|
349
|
+
callout: {
|
|
350
|
+
rich_text: enforceContentLimit(richTexts, RICH_TEXT_LIMIT),
|
|
351
|
+
icon: { type: "emoji", emoji: "ℹ️" },
|
|
352
|
+
color: "gray_background"
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
if (childBlocks.length > 0) {
|
|
356
|
+
callout.callout.children = childBlocks;
|
|
357
|
+
}
|
|
358
|
+
return [callout];
|
|
359
|
+
}
|
|
360
|
+
|
|
313
361
|
function renderParagraph(node) {
|
|
314
362
|
const { richText: rt, imageBlocks } = extractImagesFromInline(node.text);
|
|
315
363
|
const blocks = [];
|
package/src/sdoc.js
CHANGED
|
@@ -243,7 +243,8 @@ function detectImplicitRoot(cursor) {
|
|
|
243
243
|
if (nextTrimmed === COMMAND_SCOPE_OPEN ||
|
|
244
244
|
nextTrimmed === COMMAND_LIST_BULLET ||
|
|
245
245
|
nextTrimmed === COMMAND_LIST_NUMBER ||
|
|
246
|
-
isTableCommand(nextTrimmed)
|
|
246
|
+
isTableCommand(nextTrimmed) ||
|
|
247
|
+
isCitationsCommand(nextTrimmed)) {
|
|
247
248
|
isImplicit = false;
|
|
248
249
|
} else if (tryParseInlineBlock(nextTrimmed) !== null) {
|
|
249
250
|
isImplicit = false;
|
|
@@ -1817,8 +1818,17 @@ function renderInlineNodes(nodes) {
|
|
|
1817
1818
|
function renderScope(scope, depth, isTitleScope = false) {
|
|
1818
1819
|
// :comment scopes are not rendered
|
|
1819
1820
|
if (scope.scopeType === "comment") return "";
|
|
1820
|
-
|
|
1821
|
-
|
|
1821
|
+
|
|
1822
|
+
const isAbout = scope.id && scope.id.toLowerCase() === "about";
|
|
1823
|
+
// Skip empty/whitespace-only @about — no point rendering an empty meta box.
|
|
1824
|
+
if (isAbout && isAboutEmpty(scope)) return "";
|
|
1825
|
+
|
|
1826
|
+
// Bare `@about { ... }` (no heading) and heading-form with empty title both
|
|
1827
|
+
// get a default heading of "About" so the meta-section is always labelled.
|
|
1828
|
+
// A custom title (e.g. `# Document Discovery @about`) is preserved.
|
|
1829
|
+
if (isAbout && (!scope.hasHeading || !scope.title || !scope.title.trim())) {
|
|
1830
|
+
scope = { ...scope, hasHeading: true, title: "About" };
|
|
1831
|
+
}
|
|
1822
1832
|
|
|
1823
1833
|
const level = Math.min(6, Math.max(1, depth));
|
|
1824
1834
|
const children = scope.children.map((child) => renderNode(child, depth + 1)).join("\n");
|
|
@@ -1826,9 +1836,10 @@ function renderScope(scope, depth, isTitleScope = false) {
|
|
|
1826
1836
|
const dl = dataLineAttrs(scope);
|
|
1827
1837
|
const typeAttr = scope.scopeType ? ` data-scope-type="${escapeAttr(scope.scopeType)}"` : "";
|
|
1828
1838
|
const typeClass = scope.scopeType ? ` sdoc-scope-type-${scope.scopeType}` : "";
|
|
1839
|
+
const metaClass = isAbout ? " sdoc-meta-section" : "";
|
|
1829
1840
|
|
|
1830
1841
|
if (scope.hasHeading === false) {
|
|
1831
|
-
return `<section class="sdoc-scope sdoc-scope-noheading${rootClass}${typeClass}"${typeAttr}${dl}>${children}</section>`;
|
|
1842
|
+
return `<section class="sdoc-scope sdoc-scope-noheading${rootClass}${typeClass}${metaClass}"${typeAttr}${dl}>${children}</section>`;
|
|
1832
1843
|
}
|
|
1833
1844
|
|
|
1834
1845
|
const idAttr = scope.id ? ` id="${escapeAttr(scope.id)}"` : "";
|
|
@@ -1836,7 +1847,7 @@ function renderScope(scope, depth, isTitleScope = false) {
|
|
|
1836
1847
|
const toggle = hasChildren ? `<span class="sdoc-toggle"></span>` : "";
|
|
1837
1848
|
const heading = `<h${level}${idAttr} class="sdoc-heading sdoc-depth-${level}"${dl}>${toggle}${renderInline(scope.title)}</h${level}>`;
|
|
1838
1849
|
const childrenHtml = children ? `\n<div class="sdoc-scope-children">${children}</div>` : "";
|
|
1839
|
-
return `<section class="sdoc-scope${rootClass}${typeClass}"${typeAttr}>${heading}${childrenHtml}</section>`;
|
|
1850
|
+
return `<section class="sdoc-scope${rootClass}${typeClass}${metaClass}"${typeAttr}>${heading}${childrenHtml}</section>`;
|
|
1840
1851
|
}
|
|
1841
1852
|
|
|
1842
1853
|
function renderCitations(node) {
|
|
@@ -2940,6 +2951,43 @@ const DEFAULT_STYLE = `
|
|
|
2940
2951
|
display: none;
|
|
2941
2952
|
}
|
|
2942
2953
|
|
|
2954
|
+
/* Meta sections (@about) — rendered with a distinct, subdued style
|
|
2955
|
+
so readers can tell at a glance this is document metadata, not body content. */
|
|
2956
|
+
.sdoc-scope.sdoc-meta-section {
|
|
2957
|
+
position: relative;
|
|
2958
|
+
margin: 1.2rem 3rem 1.6rem 3rem;
|
|
2959
|
+
padding: 0.7rem 1rem 0.7rem 1rem;
|
|
2960
|
+
background: rgba(127, 120, 112, 0.06);
|
|
2961
|
+
border: 1px dashed var(--sdoc-border);
|
|
2962
|
+
border-left: 3px solid var(--sdoc-muted);
|
|
2963
|
+
border-radius: 6px;
|
|
2964
|
+
color: var(--sdoc-muted);
|
|
2965
|
+
font-size: 0.95em;
|
|
2966
|
+
}
|
|
2967
|
+
|
|
2968
|
+
.sdoc-meta-section .sdoc-heading {
|
|
2969
|
+
margin-top: 0.2rem;
|
|
2970
|
+
color: var(--sdoc-muted);
|
|
2971
|
+
font-weight: 600;
|
|
2972
|
+
border-bottom: none;
|
|
2973
|
+
}
|
|
2974
|
+
|
|
2975
|
+
.sdoc-meta-section .sdoc-paragraph {
|
|
2976
|
+
margin: 0.3rem 0;
|
|
2977
|
+
font-style: italic;
|
|
2978
|
+
}
|
|
2979
|
+
|
|
2980
|
+
.sdoc-meta-section .sdoc-scope-children > .sdoc-scope {
|
|
2981
|
+
padding-left: 0;
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
/* Place the collapse toggle in the gutter to the LEFT of the meta
|
|
2985
|
+
box (in the space created by margin-left), not on the colored
|
|
2986
|
+
left border. Default is left: -1.4em which lands on the border. */
|
|
2987
|
+
.sdoc-meta-section > .sdoc-heading > .sdoc-toggle {
|
|
2988
|
+
left: -2.6em;
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2943
2991
|
`;
|
|
2944
2992
|
|
|
2945
2993
|
const PRINT_STYLE = `
|
|
@@ -3077,15 +3125,21 @@ function renderBodyNodes(nodes) {
|
|
|
3077
3125
|
.join("\n");
|
|
3078
3126
|
}
|
|
3079
3127
|
|
|
3080
|
-
function renderHtmlBody(text) {
|
|
3128
|
+
function renderHtmlBody(text, options = {}) {
|
|
3081
3129
|
const parsed = parseSdoc(text);
|
|
3082
3130
|
const metaResult = extractMeta(parsed.nodes);
|
|
3083
3131
|
const savedOptions = _renderOptions;
|
|
3084
3132
|
_renderOptions = {};
|
|
3085
|
-
|
|
3133
|
+
// includeAbout defaults to false: HTML output is treated as "export-shape"
|
|
3134
|
+
// by default, hiding the discovery summary. The preview path in
|
|
3135
|
+
// extension.js opts in with `includeAbout: true` to keep the meta-section
|
|
3136
|
+
// visible during authoring.
|
|
3137
|
+
const includeAbout = options.includeAbout === true;
|
|
3138
|
+
const renderNodes = includeAbout ? metaResult.nodes : stripAboutScopes(metaResult.nodes);
|
|
3139
|
+
const citationData = buildCitationNumbering(renderNodes);
|
|
3086
3140
|
_citationNumbering = citationData.numbering;
|
|
3087
3141
|
_citationDefinitions = citationData.definitions;
|
|
3088
|
-
const result = renderBodyNodes(
|
|
3142
|
+
const result = renderBodyNodes(renderNodes);
|
|
3089
3143
|
_citationNumbering = new Map();
|
|
3090
3144
|
_citationDefinitions = new Map();
|
|
3091
3145
|
_renderOptions = savedOptions;
|
|
@@ -3094,10 +3148,16 @@ function renderHtmlBody(text) {
|
|
|
3094
3148
|
|
|
3095
3149
|
function renderHtmlDocumentFromParsed(parsed, title, options = {}) {
|
|
3096
3150
|
_renderOptions = options.renderOptions ?? {};
|
|
3097
|
-
|
|
3151
|
+
// includeAbout defaults to false: HTML/PDF output is hidden-by-default
|
|
3152
|
+
// because exported files are normally sent to a specific recipient who has
|
|
3153
|
+
// already been asked to read the doc, so the "should I read this?" framing
|
|
3154
|
+
// in @about adds noise. The live preview opts in with `includeAbout: true`.
|
|
3155
|
+
const includeAbout = options.includeAbout === true;
|
|
3156
|
+
const renderNodes = includeAbout ? parsed.nodes : stripAboutScopes(parsed.nodes);
|
|
3157
|
+
const citationData = buildCitationNumbering(renderNodes);
|
|
3098
3158
|
_citationNumbering = citationData.numbering;
|
|
3099
3159
|
_citationDefinitions = citationData.definitions;
|
|
3100
|
-
const body = renderBodyNodes(
|
|
3160
|
+
const body = renderBodyNodes(renderNodes);
|
|
3101
3161
|
_citationNumbering = new Map();
|
|
3102
3162
|
_citationDefinitions = new Map();
|
|
3103
3163
|
_renderOptions = {};
|
|
@@ -3125,13 +3185,13 @@ function renderHtmlDocumentFromParsed(parsed, title, options = {}) {
|
|
|
3125
3185
|
const mermaidInit = mermaidTheme === "auto"
|
|
3126
3186
|
? `var isDark=window.matchMedia("(prefers-color-scheme:dark)").matches;mermaid.initialize({startOnLoad:true,theme:isDark?"dark":"neutral",themeCSS:".node rect, .node polygon, .node circle { rx: 4; ry: 4; }"});`
|
|
3127
3187
|
: `mermaid.initialize({startOnLoad:true,theme:"${mermaidTheme}",themeCSS:".node rect, .node polygon, .node circle { rx: 4; ry: 4; }"});`;
|
|
3128
|
-
const mermaidScript = hasMermaidBlocks(
|
|
3188
|
+
const mermaidScript = hasMermaidBlocks(renderNodes)
|
|
3129
3189
|
? `\n<script src="${MERMAID_CDN}"></script>\n<script>${mermaidInit}</script>`
|
|
3130
3190
|
: "";
|
|
3131
3191
|
const katexCssTag = body.includes('class="katex"')
|
|
3132
3192
|
? `\n<link rel="stylesheet" href="${KATEX_CDN_CSS}" />`
|
|
3133
3193
|
: "";
|
|
3134
|
-
const hasHljs = hasHighlightableCodeBlocks(
|
|
3194
|
+
const hasHljs = hasHighlightableCodeBlocks(renderNodes);
|
|
3135
3195
|
// Highlight.js CSS is inlined (not a <link>) so it is extracted by parseDocHtml in the web viewer
|
|
3136
3196
|
// and applied inside shadow DOM. The @media query handles dark mode in browsers.
|
|
3137
3197
|
const hljsCssInline = hasHljs
|
|
@@ -3443,6 +3503,36 @@ function extractAbout(nodes) {
|
|
|
3443
3503
|
return null;
|
|
3444
3504
|
}
|
|
3445
3505
|
|
|
3506
|
+
// True when an @about scope has no meaningful content. Renderers use this to
|
|
3507
|
+
// skip emitting an empty meta-section / callout. Whitespace-only paragraphs
|
|
3508
|
+
// count as empty.
|
|
3509
|
+
function isAboutEmpty(scope) {
|
|
3510
|
+
if (!scope || !scope.children || scope.children.length === 0) return true;
|
|
3511
|
+
return scope.children.every(
|
|
3512
|
+
(child) => child.type === "paragraph" && (!child.text || child.text.trim() === "")
|
|
3513
|
+
);
|
|
3514
|
+
}
|
|
3515
|
+
|
|
3516
|
+
// Recursively remove @about scopes from an AST. Used by the HTML/PDF export
|
|
3517
|
+
// paths, which hide @about by default: when a doc is exported and sent to a
|
|
3518
|
+
// specific recipient, the "should I read this?" framing is moot — the sender
|
|
3519
|
+
// already decided the answer is yes. Pass-through for nodes without children.
|
|
3520
|
+
function stripAboutScopes(nodes) {
|
|
3521
|
+
if (!Array.isArray(nodes)) return nodes;
|
|
3522
|
+
const result = [];
|
|
3523
|
+
for (const node of nodes) {
|
|
3524
|
+
if (node && node.type === "scope" && node.id && node.id.toLowerCase() === "about") {
|
|
3525
|
+
continue;
|
|
3526
|
+
}
|
|
3527
|
+
if (node && node.type === "scope" && Array.isArray(node.children)) {
|
|
3528
|
+
result.push({ ...node, children: stripAboutScopes(node.children) });
|
|
3529
|
+
} else {
|
|
3530
|
+
result.push(node);
|
|
3531
|
+
}
|
|
3532
|
+
}
|
|
3533
|
+
return result;
|
|
3534
|
+
}
|
|
3535
|
+
|
|
3446
3536
|
function collectAllIds(nodes) {
|
|
3447
3537
|
const ids = new Set();
|
|
3448
3538
|
function walk(nodeList) {
|
|
@@ -3668,6 +3758,8 @@ module.exports = {
|
|
|
3668
3758
|
listSections,
|
|
3669
3759
|
extractSection,
|
|
3670
3760
|
extractAbout,
|
|
3761
|
+
isAboutEmpty,
|
|
3762
|
+
stripAboutScopes,
|
|
3671
3763
|
extractDataBlocks,
|
|
3672
3764
|
KNOWN_SCOPE_TYPES,
|
|
3673
3765
|
// Validation
|
package/tools/build-doc.js
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
// SDOC Document — CLI tool for HTML and PDF export
|
|
3
3
|
//
|
|
4
4
|
// Usage:
|
|
5
|
-
// node tools/build-doc.js input.sdoc [-o output] [--html]
|
|
5
|
+
// node tools/build-doc.js input.sdoc [-o output] [--html] [--include-about]
|
|
6
6
|
//
|
|
7
7
|
// Default output is PDF (requires Chrome/Chromium).
|
|
8
8
|
// Use --html for HTML-only output (no Chrome needed).
|
|
9
9
|
// If -o is omitted, writes to input.pdf (or input.html with --html).
|
|
10
|
+
// The @about scope is hidden by default; pass --include-about to keep it.
|
|
10
11
|
|
|
11
12
|
const fs = require("fs");
|
|
12
13
|
const path = require("path");
|
|
@@ -16,7 +17,7 @@ const { parseSdoc, extractMeta, resolveIncludes, renderHtmlDocumentFromParsed }
|
|
|
16
17
|
const CONFIG_FILENAME = "sdoc.config.json";
|
|
17
18
|
|
|
18
19
|
function usage() {
|
|
19
|
-
console.error("Usage: build-doc <input.sdoc> [-o output] [--html]");
|
|
20
|
+
console.error("Usage: build-doc <input.sdoc> [-o output] [--html] [--include-about]");
|
|
20
21
|
process.exit(1);
|
|
21
22
|
}
|
|
22
23
|
|
|
@@ -98,7 +99,8 @@ function resolveMetaStyles(meta, documentPath) {
|
|
|
98
99
|
return result;
|
|
99
100
|
}
|
|
100
101
|
|
|
101
|
-
async function buildHtml(filePath) {
|
|
102
|
+
async function buildHtml(filePath, options = {}) {
|
|
103
|
+
const includeAbout = options.includeAbout === true;
|
|
102
104
|
const resolvedPath = path.resolve(filePath);
|
|
103
105
|
const text = fs.readFileSync(resolvedPath, "utf8");
|
|
104
106
|
const parsed = parseSdoc(text);
|
|
@@ -141,6 +143,7 @@ async function buildHtml(filePath) {
|
|
|
141
143
|
config,
|
|
142
144
|
cssOverride: cssOverride || undefined,
|
|
143
145
|
cssAppend: cssAppendParts.join("\n") || undefined,
|
|
146
|
+
includeAbout,
|
|
144
147
|
}
|
|
145
148
|
);
|
|
146
149
|
}
|
|
@@ -150,12 +153,15 @@ async function main() {
|
|
|
150
153
|
let inputPath = null;
|
|
151
154
|
let outputPath = null;
|
|
152
155
|
let htmlMode = false;
|
|
156
|
+
let includeAbout = false;
|
|
153
157
|
|
|
154
158
|
for (let i = 0; i < args.length; i++) {
|
|
155
159
|
if (args[i] === "-o" && i + 1 < args.length) {
|
|
156
160
|
outputPath = args[++i];
|
|
157
161
|
} else if (args[i] === "--html") {
|
|
158
162
|
htmlMode = true;
|
|
163
|
+
} else if (args[i] === "--include-about") {
|
|
164
|
+
includeAbout = true;
|
|
159
165
|
} else if (args[i] === "--help" || args[i] === "-h") {
|
|
160
166
|
usage();
|
|
161
167
|
} else if (!inputPath) {
|
|
@@ -174,7 +180,7 @@ async function main() {
|
|
|
174
180
|
process.exit(1);
|
|
175
181
|
}
|
|
176
182
|
|
|
177
|
-
const html = await buildHtml(resolvedInput);
|
|
183
|
+
const html = await buildHtml(resolvedInput, { includeAbout });
|
|
178
184
|
|
|
179
185
|
if (htmlMode) {
|
|
180
186
|
if (!outputPath) {
|