sdocs 0.0.40 → 0.0.42
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/CHANGELOG.md +40 -0
- package/dist/explorer/tree-builder.js +2 -4
- package/dist/explorer/views/ComponentView.svelte +85 -29
- package/dist/explorer/views/LayoutView.svelte +1 -1
- package/dist/explorer/views/PageView.svelte +1 -1
- package/dist/grammar/sdoc.tmLanguage.json +245 -0
- package/dist/index.d.ts +1 -1
- package/dist/language/index.d.ts +2 -0
- package/dist/language/index.js +2 -0
- package/dist/language/parser.d.ts +82 -0
- package/dist/language/parser.js +293 -0
- package/dist/language/parser.test.d.ts +1 -0
- package/dist/language/parser.test.js +158 -0
- package/dist/language/scanner.d.ts +82 -0
- package/dist/language/scanner.js +529 -0
- package/dist/language/scanner.test.d.ts +1 -0
- package/dist/language/scanner.test.js +146 -0
- package/dist/server/app-gen.js +18 -21
- package/dist/server/discovery.d.ts +0 -2
- package/dist/server/discovery.js +0 -9
- package/dist/server/doc-model.d.ts +26 -0
- package/dist/server/doc-model.js +58 -0
- package/dist/server/page-markdown.d.ts +15 -0
- package/dist/server/page-markdown.js +104 -0
- package/dist/server/snippet-compiler.d.ts +18 -18
- package/dist/server/snippet-compiler.js +32 -29
- package/dist/types.d.ts +35 -19
- package/dist/vite.js +168 -99
- package/package.json +7 -1
- package/dist/server/meta-parser.d.ts +0 -11
- package/dist/server/meta-parser.js +0 -109
- package/dist/server/snippet-extractor.d.ts +0 -11
- package/dist/server/snippet-extractor.js +0 -83
- package/dist/server/toc-extractor.d.ts +0 -3
- package/dist/server/toc-extractor.js +0 -23
package/dist/server/app-gen.js
CHANGED
|
@@ -3,9 +3,10 @@ import { existsSync } from 'node:fs';
|
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import { dirname, resolve, join } from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import { discoverDocFiles
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import { discoverDocFiles } from './discovery.js';
|
|
7
|
+
import { parseSdoc } from '../language/index.js';
|
|
8
|
+
import { planEntitySnippets } from './doc-model.js';
|
|
9
|
+
import { encodeEntityId, setDocPathRoot } from './snippet-compiler.js';
|
|
9
10
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
11
|
/** Source Explorer directory in the installed package (dist/explorer, next to dist/server) */
|
|
11
12
|
function getExplorerSourceDir() {
|
|
@@ -141,23 +142,19 @@ function generatePreviewHtml(iframeVirtualId, css) {
|
|
|
141
142
|
</body>
|
|
142
143
|
</html>`;
|
|
143
144
|
}
|
|
144
|
-
/** Discover doc files and
|
|
145
|
+
/** Discover doc files and plan snippet slugs per entity (lightweight, no highlighting) */
|
|
145
146
|
async function discoverSnippets(config, cwd) {
|
|
146
147
|
const files = await discoverDocFiles(config.include, cwd);
|
|
147
148
|
const results = [];
|
|
148
149
|
for (const filePath of files) {
|
|
149
150
|
const source = await readFile(filePath, 'utf-8');
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
results.push({
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if (!hasDefaultSnippet(snippets)) {
|
|
158
|
-
names.unshift('Default');
|
|
159
|
-
}
|
|
160
|
-
results.push({ filePath, snippetNames: names });
|
|
151
|
+
const doc = parseSdoc(source);
|
|
152
|
+
for (const entity of doc.entities) {
|
|
153
|
+
results.push({
|
|
154
|
+
filePath,
|
|
155
|
+
entitySlug: entity.slug,
|
|
156
|
+
snippetSlugs: planEntitySnippets(entity).map((s) => s.slug),
|
|
157
|
+
});
|
|
161
158
|
}
|
|
162
159
|
}
|
|
163
160
|
return results;
|
|
@@ -186,15 +183,15 @@ export async function generateBuildFiles(config, cwd) {
|
|
|
186
183
|
};
|
|
187
184
|
// Discover snippets and generate preview HTML pages
|
|
188
185
|
const docSnippets = await discoverSnippets(config, cwd);
|
|
189
|
-
for (const { filePath,
|
|
190
|
-
const encoded =
|
|
191
|
-
for (const
|
|
192
|
-
const iframeId = `/@sdocs/iframe/${encoded}/${
|
|
186
|
+
for (const { filePath, entitySlug, snippetSlugs } of docSnippets) {
|
|
187
|
+
const encoded = encodeEntityId(filePath, entitySlug);
|
|
188
|
+
for (const snippetSlug of snippetSlugs) {
|
|
189
|
+
const iframeId = `/@sdocs/iframe/${encoded}/${snippetSlug}.svelte`;
|
|
193
190
|
const previewDir = resolve(sdocsDir, 'previews', encoded);
|
|
194
191
|
await mkdir(previewDir, { recursive: true });
|
|
195
|
-
const previewPath = resolve(previewDir, `${
|
|
192
|
+
const previewPath = resolve(previewDir, `${snippetSlug}.html`);
|
|
196
193
|
await writeFile(previewPath, generatePreviewHtml(iframeId, config.css));
|
|
197
|
-
const inputKey = `preview-${encoded}-${
|
|
194
|
+
const inputKey = `preview-${encoded}-${snippetSlug}`;
|
|
198
195
|
inputs[inputKey] = previewPath;
|
|
199
196
|
}
|
|
200
197
|
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
/** Discover all .sdoc files matching the include patterns */
|
|
2
2
|
export declare function discoverDocFiles(include: string[], root: string): Promise<string[]>;
|
|
3
|
-
/** Determine the sdoc kind from the file path */
|
|
4
|
-
export declare function getSdocKind(filePath: string): 'component' | 'page' | 'layout';
|
|
5
3
|
/** Get the absolute path for a relative import */
|
|
6
4
|
export declare function resolveImportPath(importPath: string, fromFile: string): string;
|
package/dist/server/discovery.js
CHANGED
|
@@ -8,15 +8,6 @@ export async function discoverDocFiles(include, root) {
|
|
|
8
8
|
});
|
|
9
9
|
return files.sort();
|
|
10
10
|
}
|
|
11
|
-
/** Determine the sdoc kind from the file path */
|
|
12
|
-
export function getSdocKind(filePath) {
|
|
13
|
-
const name = filePath.split('/').pop() ?? '';
|
|
14
|
-
if (name.includes('.page.'))
|
|
15
|
-
return 'page';
|
|
16
|
-
if (name.includes('.layout.'))
|
|
17
|
-
return 'layout';
|
|
18
|
-
return 'component';
|
|
19
|
-
}
|
|
20
11
|
/** Get the absolute path for a relative import */
|
|
21
12
|
export function resolveImportPath(importPath, fromFile) {
|
|
22
13
|
const dir = fromFile.substring(0, fromFile.lastIndexOf('/'));
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared entity → snippet planning. Both the Vite plugin (URL generation)
|
|
3
|
+
* and the CLI app-gen (pre-computed rollup inputs) derive snippet slugs
|
|
4
|
+
* from this module, so build inputs always byte-match plugin output.
|
|
5
|
+
*/
|
|
6
|
+
import { type SdocEntity } from '../language/index.js';
|
|
7
|
+
export type SnippetRole = 'preview' | 'example' | 'content';
|
|
8
|
+
export interface PlannedSnippet {
|
|
9
|
+
name: string;
|
|
10
|
+
slug: string;
|
|
11
|
+
role: SnippetRole;
|
|
12
|
+
body: string;
|
|
13
|
+
}
|
|
14
|
+
/** URL-safe slug for an example snippet. The 'x-' prefix keeps example
|
|
15
|
+
* slugs disjoint from preview slugs and 'content'. */
|
|
16
|
+
export declare function exampleSlug(title: string): string;
|
|
17
|
+
/** URL-safe slug for a preview snippet (from its tab label). */
|
|
18
|
+
export declare function previewSlug(label: string): string;
|
|
19
|
+
/** The snippets one entity produces, in order: previews, then examples,
|
|
20
|
+
* or the single 'content' body for PAGE/LAYOUT. */
|
|
21
|
+
export declare function planEntitySnippets(entity: SdocEntity): PlannedSnippet[];
|
|
22
|
+
/** Extract import statements from the file-level script content. */
|
|
23
|
+
export declare function extractImports(scriptContent: string): string[];
|
|
24
|
+
/** Resolve a preview's component identifier to an absolute .svelte path via
|
|
25
|
+
* the file's imports. Returns null when the identifier isn't imported. */
|
|
26
|
+
export declare function resolveComponentImport(componentName: string, imports: string[], docFilePath: string): string | null;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared entity → snippet planning. Both the Vite plugin (URL generation)
|
|
3
|
+
* and the CLI app-gen (pre-computed rollup inputs) derive snippet slugs
|
|
4
|
+
* from this module, so build inputs always byte-match plugin output.
|
|
5
|
+
*/
|
|
6
|
+
import { resolve, dirname } from 'node:path';
|
|
7
|
+
import { slugifyTitle } from '../language/index.js';
|
|
8
|
+
/** URL-safe slug for an example snippet. The 'x-' prefix keeps example
|
|
9
|
+
* slugs disjoint from preview slugs and 'content'. */
|
|
10
|
+
export function exampleSlug(title) {
|
|
11
|
+
return 'x-' + slugifyTitle(title);
|
|
12
|
+
}
|
|
13
|
+
/** URL-safe slug for a preview snippet (from its tab label). */
|
|
14
|
+
export function previewSlug(label) {
|
|
15
|
+
return slugifyTitle(label);
|
|
16
|
+
}
|
|
17
|
+
/** The snippets one entity produces, in order: previews, then examples,
|
|
18
|
+
* or the single 'content' body for PAGE/LAYOUT. */
|
|
19
|
+
export function planEntitySnippets(entity) {
|
|
20
|
+
if (entity.kind === 'DOCS') {
|
|
21
|
+
return [
|
|
22
|
+
...entity.previews.map((p) => ({
|
|
23
|
+
name: p.label,
|
|
24
|
+
slug: previewSlug(p.label),
|
|
25
|
+
role: 'preview',
|
|
26
|
+
body: p.body,
|
|
27
|
+
})),
|
|
28
|
+
...entity.examples.map((e) => ({
|
|
29
|
+
name: e.title,
|
|
30
|
+
slug: exampleSlug(e.title),
|
|
31
|
+
role: 'example',
|
|
32
|
+
body: e.body,
|
|
33
|
+
})),
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
return [{ name: 'Content', slug: 'content', role: 'content', body: entity.body }];
|
|
37
|
+
}
|
|
38
|
+
/** Extract import statements from the file-level script content. */
|
|
39
|
+
export function extractImports(scriptContent) {
|
|
40
|
+
const imports = [];
|
|
41
|
+
const regex = /^\s*import\s+.+$/gm;
|
|
42
|
+
let match;
|
|
43
|
+
while ((match = regex.exec(scriptContent)) !== null) {
|
|
44
|
+
imports.push(match[0].trim());
|
|
45
|
+
}
|
|
46
|
+
return imports;
|
|
47
|
+
}
|
|
48
|
+
/** Resolve a preview's component identifier to an absolute .svelte path via
|
|
49
|
+
* the file's imports. Returns null when the identifier isn't imported. */
|
|
50
|
+
export function resolveComponentImport(componentName, imports, docFilePath) {
|
|
51
|
+
for (const imp of imports) {
|
|
52
|
+
const match = imp.match(new RegExp(`import\\s+${componentName}\\s+from\\s+['"](.+?)['"]`));
|
|
53
|
+
if (match) {
|
|
54
|
+
return resolve(dirname(docFilePath), match[1]);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [PAGE] body transform: markdown-first with Svelte conveniences.
|
|
3
|
+
*
|
|
4
|
+
* The body is CommonMark/GFM; prose passes through untouched so
|
|
5
|
+
* `{expression}` interpolation and `<Component />` islands compile in the
|
|
6
|
+
* generated Svelte preview. Code fences and inline code are INERT: they are
|
|
7
|
+
* highlighted (fences) or wrapped (inline) at transform time with `{` and
|
|
8
|
+
* `}` escaped, so nothing inside code ever interpolates.
|
|
9
|
+
*/
|
|
10
|
+
import type { TocHeading } from '../types.js';
|
|
11
|
+
export interface RenderedPage {
|
|
12
|
+
html: string;
|
|
13
|
+
toc: TocHeading[];
|
|
14
|
+
}
|
|
15
|
+
export declare function renderPageMarkdown(source: string): Promise<RenderedPage>;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [PAGE] body transform: markdown-first with Svelte conveniences.
|
|
3
|
+
*
|
|
4
|
+
* The body is CommonMark/GFM; prose passes through untouched so
|
|
5
|
+
* `{expression}` interpolation and `<Component />` islands compile in the
|
|
6
|
+
* generated Svelte preview. Code fences and inline code are INERT: they are
|
|
7
|
+
* highlighted (fences) or wrapped (inline) at transform time with `{` and
|
|
8
|
+
* `}` escaped, so nothing inside code ever interpolates.
|
|
9
|
+
*/
|
|
10
|
+
import { Marked } from 'marked';
|
|
11
|
+
import { highlight } from './highlighter.js';
|
|
12
|
+
function slugify(text) {
|
|
13
|
+
return (text
|
|
14
|
+
.toLowerCase()
|
|
15
|
+
.replace(/[^\w\s-]/g, '')
|
|
16
|
+
.trim()
|
|
17
|
+
.replace(/[\s_]+/g, '-') || 'section');
|
|
18
|
+
}
|
|
19
|
+
/** Make markup Svelte-inert: braces never interpolate. */
|
|
20
|
+
function escapeBraces(html) {
|
|
21
|
+
return html.replace(/\{/g, '{').replace(/\}/g, '}');
|
|
22
|
+
}
|
|
23
|
+
function escapeHtml(text) {
|
|
24
|
+
return text
|
|
25
|
+
.replace(/&/g, '&')
|
|
26
|
+
.replace(/</g, '<')
|
|
27
|
+
.replace(/>/g, '>')
|
|
28
|
+
.replace(/"/g, '"');
|
|
29
|
+
}
|
|
30
|
+
function plainText(tokens) {
|
|
31
|
+
let out = '';
|
|
32
|
+
for (const token of tokens) {
|
|
33
|
+
if ('tokens' in token && token.tokens?.length)
|
|
34
|
+
out += plainText(token.tokens);
|
|
35
|
+
else if ('text' in token)
|
|
36
|
+
out += token.text;
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
export async function renderPageMarkdown(source) {
|
|
41
|
+
const toc = [];
|
|
42
|
+
const usedIds = new Set();
|
|
43
|
+
const headingIds = new WeakMap();
|
|
44
|
+
const fenceHtml = new WeakMap();
|
|
45
|
+
const marked = new Marked({ gfm: true });
|
|
46
|
+
const tokens = marked.lexer(source);
|
|
47
|
+
const walk = (list) => {
|
|
48
|
+
for (const token of list) {
|
|
49
|
+
if (token.type === 'heading') {
|
|
50
|
+
const text = plainText(token.tokens ?? []);
|
|
51
|
+
const baseId = slugify(text);
|
|
52
|
+
let id = baseId;
|
|
53
|
+
for (let n = 2; usedIds.has(id); n++)
|
|
54
|
+
id = `${baseId}-${n}`;
|
|
55
|
+
usedIds.add(id);
|
|
56
|
+
headingIds.set(token, id);
|
|
57
|
+
if (token.depth >= 2 && token.depth <= 4) {
|
|
58
|
+
toc.push({ text, level: token.depth, id });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if ('tokens' in token && token.tokens)
|
|
62
|
+
walk(token.tokens);
|
|
63
|
+
if ('items' in token && token.items)
|
|
64
|
+
walk(token.items);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
walk(tokens);
|
|
68
|
+
// Highlight fences up front (the renderer hooks below must be sync)
|
|
69
|
+
for (const token of tokens) {
|
|
70
|
+
if (token.type === 'code') {
|
|
71
|
+
const lang = (token.lang ?? '').trim().split(/\s+/)[0] || 'text';
|
|
72
|
+
try {
|
|
73
|
+
fenceHtml.set(token, escapeBraces(await highlight(token.text, lang)));
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
fenceHtml.set(token, `<pre><code>${escapeBraces(escapeHtml(token.text))}</code></pre>`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
marked.use({
|
|
81
|
+
renderer: {
|
|
82
|
+
heading(token) {
|
|
83
|
+
const html = this.parser.parseInline(token.tokens);
|
|
84
|
+
const id = headingIds.get(token);
|
|
85
|
+
return id
|
|
86
|
+
? `<h${token.depth} id="${id}">${html}</h${token.depth}>\n`
|
|
87
|
+
: `<h${token.depth}>${html}</h${token.depth}>\n`;
|
|
88
|
+
},
|
|
89
|
+
code(token) {
|
|
90
|
+
return (fenceHtml.get(token) ??
|
|
91
|
+
`<pre><code>${escapeBraces(escapeHtml(token.text))}</code></pre>\n`);
|
|
92
|
+
},
|
|
93
|
+
codespan(token) {
|
|
94
|
+
return `<code>${escapeBraces(escapeHtml(token.text))}</code>`;
|
|
95
|
+
},
|
|
96
|
+
// Prose passes through as-is: component islands stay component
|
|
97
|
+
// tags, {expressions} stay expressions.
|
|
98
|
+
html(token) {
|
|
99
|
+
return token.text;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
return { html: marked.parser(tokens), toc };
|
|
104
|
+
}
|
|
@@ -4,8 +4,12 @@ export declare function base64urlEncode(str: string): string;
|
|
|
4
4
|
export declare function base64urlDecode(str: string): string;
|
|
5
5
|
/** Set the root that doc paths are encoded against (the Vite root / staging dir) */
|
|
6
6
|
export declare function setDocPathRoot(root: string): void;
|
|
7
|
-
/** Encode a doc file
|
|
8
|
-
|
|
7
|
+
/** Encode a doc entity (file + entity slug) for URLs and emitted file names.
|
|
8
|
+
* The slug rides inside the token as a '#' fragment so every URL shape keeps
|
|
9
|
+
* exactly one token path segment. */
|
|
10
|
+
export declare function encodeEntityId(filePath: string, entitySlug: string): string;
|
|
11
|
+
/** The docEntries key for one entity of one file */
|
|
12
|
+
export declare function entityKey(filePath: string, entitySlug: string): string;
|
|
9
13
|
/** Resolve relative imports to absolute paths for use in virtual components */
|
|
10
14
|
export declare function resolveImportsToAbsolute(imports: string[], docFilePath: string): string[];
|
|
11
15
|
/** Generate a virtual Svelte iframe wrapper component for a snippet.
|
|
@@ -23,26 +27,22 @@ export interface StaticCssLink {
|
|
|
23
27
|
}
|
|
24
28
|
/** Generate the HTML page for a preview emitted into a host app's build. */
|
|
25
29
|
export declare function generateStaticPreviewHtml(scriptSrc: string, cssLinks: StaticCssLink[]): string;
|
|
30
|
+
export interface ParsedSnippetId {
|
|
31
|
+
docFilePath: string;
|
|
32
|
+
entitySlug: string;
|
|
33
|
+
snippetSlug: string;
|
|
34
|
+
}
|
|
26
35
|
/** Build the virtual module ID for an iframe wrapper component */
|
|
27
|
-
export declare function iframeVirtualId(docFilePath: string,
|
|
36
|
+
export declare function iframeVirtualId(docFilePath: string, entitySlug: string, snippetSlug: string): string;
|
|
28
37
|
/** Build the preview URL for an iframe HTML page (dev mode) */
|
|
29
|
-
export declare function previewUrl(docFilePath: string,
|
|
38
|
+
export declare function previewUrl(docFilePath: string, entitySlug: string, snippetSlug: string): string;
|
|
30
39
|
/** Build the preview URL for static build output */
|
|
31
|
-
export declare function buildPreviewUrl(docFilePath: string,
|
|
40
|
+
export declare function buildPreviewUrl(docFilePath: string, entitySlug: string, snippetSlug: string): string;
|
|
32
41
|
/** Virtual module ID for a preview's mount script (embedded production builds) */
|
|
33
|
-
export declare function mountVirtualId(docFilePath: string,
|
|
42
|
+
export declare function mountVirtualId(docFilePath: string, entitySlug: string, snippetSlug: string): string;
|
|
34
43
|
/** Parse a mount virtual ID back into its parts */
|
|
35
|
-
export declare function parseMountId(id: string):
|
|
36
|
-
docFilePath: string;
|
|
37
|
-
snippetName: string;
|
|
38
|
-
} | null;
|
|
44
|
+
export declare function parseMountId(id: string): ParsedSnippetId | null;
|
|
39
45
|
/** Parse an iframe virtual ID back into its parts */
|
|
40
|
-
export declare function parseIframeId(id: string):
|
|
41
|
-
docFilePath: string;
|
|
42
|
-
snippetName: string;
|
|
43
|
-
} | null;
|
|
46
|
+
export declare function parseIframeId(id: string): ParsedSnippetId | null;
|
|
44
47
|
/** Parse a preview URL back into its parts */
|
|
45
|
-
export declare function parsePreviewUrl(url: string):
|
|
46
|
-
docFilePath: string;
|
|
47
|
-
snippetName: string;
|
|
48
|
-
} | null;
|
|
48
|
+
export declare function parsePreviewUrl(url: string): ParsedSnippetId | null;
|
|
@@ -15,13 +15,25 @@ let docPathRoot = process.cwd();
|
|
|
15
15
|
export function setDocPathRoot(root) {
|
|
16
16
|
docPathRoot = root;
|
|
17
17
|
}
|
|
18
|
-
/** Encode a doc file
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
/** Encode a doc entity (file + entity slug) for URLs and emitted file names.
|
|
19
|
+
* The slug rides inside the token as a '#' fragment so every URL shape keeps
|
|
20
|
+
* exactly one token path segment. */
|
|
21
|
+
export function encodeEntityId(filePath, entitySlug) {
|
|
22
|
+
return base64urlEncode(relative(docPathRoot, filePath).split(sep).join('/') + '#' + entitySlug);
|
|
21
23
|
}
|
|
22
|
-
/** Decode an encoded
|
|
23
|
-
function
|
|
24
|
-
|
|
24
|
+
/** Decode an encoded entity id back to an absolute path + entity slug */
|
|
25
|
+
function decodeEntityId(encoded) {
|
|
26
|
+
const decoded = base64urlDecode(encoded);
|
|
27
|
+
const hash = decoded.lastIndexOf('#');
|
|
28
|
+
const relPath = hash === -1 ? decoded : decoded.slice(0, hash);
|
|
29
|
+
return {
|
|
30
|
+
docFilePath: resolve(docPathRoot, relPath),
|
|
31
|
+
entitySlug: hash === -1 ? '' : decoded.slice(hash + 1),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** The docEntries key for one entity of one file */
|
|
35
|
+
export function entityKey(filePath, entitySlug) {
|
|
36
|
+
return `${filePath}#${entitySlug}`;
|
|
25
37
|
}
|
|
26
38
|
/** Resolve relative imports to absolute paths for use in virtual components */
|
|
27
39
|
export function resolveImportsToAbsolute(imports, docFilePath) {
|
|
@@ -206,48 +218,39 @@ export function generateStaticPreviewHtml(scriptSrc, cssLinks) {
|
|
|
206
218
|
return previewHtmlShell(links, `<script type="module" src="${scriptSrc}"></script>`);
|
|
207
219
|
}
|
|
208
220
|
/** Build the virtual module ID for an iframe wrapper component */
|
|
209
|
-
export function iframeVirtualId(docFilePath,
|
|
210
|
-
return `/@sdocs/iframe/${
|
|
221
|
+
export function iframeVirtualId(docFilePath, entitySlug, snippetSlug) {
|
|
222
|
+
return `/@sdocs/iframe/${encodeEntityId(docFilePath, entitySlug)}/${snippetSlug}.svelte`;
|
|
211
223
|
}
|
|
212
224
|
/** Build the preview URL for an iframe HTML page (dev mode) */
|
|
213
|
-
export function previewUrl(docFilePath,
|
|
214
|
-
return `/@sdocs/preview/${
|
|
225
|
+
export function previewUrl(docFilePath, entitySlug, snippetSlug) {
|
|
226
|
+
return `/@sdocs/preview/${encodeEntityId(docFilePath, entitySlug)}/${snippetSlug}`;
|
|
215
227
|
}
|
|
216
228
|
/** Build the preview URL for static build output */
|
|
217
|
-
export function buildPreviewUrl(docFilePath,
|
|
218
|
-
return `/previews/${
|
|
229
|
+
export function buildPreviewUrl(docFilePath, entitySlug, snippetSlug) {
|
|
230
|
+
return `/previews/${encodeEntityId(docFilePath, entitySlug)}/${snippetSlug}.html`;
|
|
219
231
|
}
|
|
220
232
|
/** Virtual module ID for a preview's mount script (embedded production builds) */
|
|
221
|
-
export function mountVirtualId(docFilePath,
|
|
222
|
-
return `/@sdocs/mount/${
|
|
233
|
+
export function mountVirtualId(docFilePath, entitySlug, snippetSlug) {
|
|
234
|
+
return `/@sdocs/mount/${encodeEntityId(docFilePath, entitySlug)}/${snippetSlug}.js`;
|
|
223
235
|
}
|
|
224
236
|
/** Parse a mount virtual ID back into its parts */
|
|
225
237
|
export function parseMountId(id) {
|
|
226
|
-
const match = id.match(/^\/@sdocs\/mount\/([^/]+)\/(\w+)\.js$/);
|
|
238
|
+
const match = id.match(/^\/@sdocs\/mount\/([^/]+)\/([\w-]+)\.js$/);
|
|
227
239
|
if (!match)
|
|
228
240
|
return null;
|
|
229
|
-
return {
|
|
230
|
-
docFilePath: decodeDocPath(match[1]),
|
|
231
|
-
snippetName: match[2],
|
|
232
|
-
};
|
|
241
|
+
return { ...decodeEntityId(match[1]), snippetSlug: match[2] };
|
|
233
242
|
}
|
|
234
243
|
/** Parse an iframe virtual ID back into its parts */
|
|
235
244
|
export function parseIframeId(id) {
|
|
236
|
-
const match = id.match(/^\/@sdocs\/iframe\/([^/]+)\/(\w+)\.svelte$/);
|
|
245
|
+
const match = id.match(/^\/@sdocs\/iframe\/([^/]+)\/([\w-]+)\.svelte$/);
|
|
237
246
|
if (!match)
|
|
238
247
|
return null;
|
|
239
|
-
return {
|
|
240
|
-
docFilePath: decodeDocPath(match[1]),
|
|
241
|
-
snippetName: match[2],
|
|
242
|
-
};
|
|
248
|
+
return { ...decodeEntityId(match[1]), snippetSlug: match[2] };
|
|
243
249
|
}
|
|
244
250
|
/** Parse a preview URL back into its parts */
|
|
245
251
|
export function parsePreviewUrl(url) {
|
|
246
|
-
const match = url.match(/^\/@sdocs\/preview\/([^/]+)\/(\w+)$/);
|
|
252
|
+
const match = url.match(/^\/@sdocs\/preview\/([^/]+)\/([\w-]+)$/);
|
|
247
253
|
if (!match)
|
|
248
254
|
return null;
|
|
249
|
-
return {
|
|
250
|
-
docFilePath: decodeDocPath(match[1]),
|
|
251
|
-
snippetName: match[2],
|
|
252
|
-
};
|
|
255
|
+
return { ...decodeEntityId(match[1]), snippetSlug: match[2] };
|
|
253
256
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -33,18 +33,12 @@ export interface ResolvedSdocsConfig {
|
|
|
33
33
|
open: string[];
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
|
-
/**
|
|
36
|
+
/** Entity metadata from a [DOCS]/[PAGE]/[LAYOUT] opener */
|
|
37
37
|
export interface SdocMeta {
|
|
38
|
-
/** The Svelte component being documented */
|
|
39
|
-
component?: unknown;
|
|
40
38
|
/** Sidebar path (e.g. 'Demo / Button') */
|
|
41
39
|
title: string;
|
|
42
40
|
/** Short description */
|
|
43
41
|
description?: string;
|
|
44
|
-
/** Default prop values */
|
|
45
|
-
args?: Record<string, unknown>;
|
|
46
|
-
/** Preview settings (padding, background, etc.) */
|
|
47
|
-
settings?: Record<string, unknown>;
|
|
48
42
|
}
|
|
49
43
|
/** A parsed prop */
|
|
50
44
|
export interface ParsedProp {
|
|
@@ -82,36 +76,58 @@ export interface ComponentData {
|
|
|
82
76
|
state: ParsedState[];
|
|
83
77
|
cssProps: ParsedCssProp[];
|
|
84
78
|
}
|
|
85
|
-
/**
|
|
79
|
+
/** A renderable snippet of an entity: a preview, an example, or the body */
|
|
86
80
|
export interface ExtractedSnippet {
|
|
81
|
+
/** Display name: preview label / example title / 'Content' */
|
|
87
82
|
name: string;
|
|
83
|
+
/** URL-safe id, unique within the entity */
|
|
84
|
+
slug: string;
|
|
85
|
+
role: 'preview' | 'example' | 'content';
|
|
88
86
|
body: string;
|
|
89
87
|
highlightedHtml?: string;
|
|
90
88
|
/** Preview URL for iframe (added by virtual module) */
|
|
91
89
|
previewUrl?: string;
|
|
92
90
|
}
|
|
91
|
+
/** One [preview] of a DOCS entity: a live showcase of one component */
|
|
92
|
+
export interface PreviewEntry {
|
|
93
|
+
/** Tab label (title override or the component name) */
|
|
94
|
+
label: string;
|
|
95
|
+
/** The previewed component's identifier in the file's script */
|
|
96
|
+
componentName: string | null;
|
|
97
|
+
/** Absolute path to the previewed component */
|
|
98
|
+
componentPath: string | null;
|
|
99
|
+
/** Parsed component data (props, methods, state, CSS props) */
|
|
100
|
+
componentData: ComponentData | null;
|
|
101
|
+
/** Highlighted component source HTML */
|
|
102
|
+
highlightedSource: string | null;
|
|
103
|
+
/** Control defaults for this preview */
|
|
104
|
+
args: Record<string, unknown>;
|
|
105
|
+
snippet: ExtractedSnippet;
|
|
106
|
+
}
|
|
93
107
|
/** A table of contents heading (for pages) */
|
|
94
108
|
export interface TocHeading {
|
|
95
109
|
text: string;
|
|
96
110
|
level: number;
|
|
97
111
|
id: string;
|
|
98
112
|
}
|
|
99
|
-
/** A complete doc entry (one .sdoc file) */
|
|
113
|
+
/** A complete doc entry (one entity of one .sdoc file) */
|
|
100
114
|
export interface DocEntry {
|
|
101
|
-
/** Doc kind */
|
|
115
|
+
/** Doc kind: DOCS / PAGE / LAYOUT */
|
|
102
116
|
kind: 'component' | 'page' | 'layout';
|
|
103
117
|
/** Absolute path to the .sdoc file */
|
|
104
118
|
filePath: string;
|
|
105
|
-
/**
|
|
106
|
-
|
|
107
|
-
/**
|
|
119
|
+
/** URL-safe entity id, unique within the file */
|
|
120
|
+
entitySlug: string;
|
|
121
|
+
/** Entity metadata (title drives the sidebar) */
|
|
108
122
|
meta: SdocMeta;
|
|
109
|
-
/**
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
|
|
123
|
+
/** Live previews (component kind; empty otherwise) */
|
|
124
|
+
previews: PreviewEntry[];
|
|
125
|
+
/** Frozen examples (component kind; empty otherwise) */
|
|
126
|
+
examples: ExtractedSnippet[];
|
|
127
|
+
/** The rendered body (page/layout kind; null otherwise) */
|
|
128
|
+
content: ExtractedSnippet | null;
|
|
115
129
|
/** Table of contents headings (pages only) */
|
|
116
130
|
toc?: TocHeading[];
|
|
131
|
+
/** Stage padding (layouts only) */
|
|
132
|
+
padding?: string | null;
|
|
117
133
|
}
|