sdocs 0.0.56 → 0.0.58
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 +58 -0
- package/dist/commands/build.js +2 -0
- package/dist/commands/dev.js +2 -0
- package/dist/explorer/Explorer.svelte +4 -1
- package/dist/explorer/views/PageView.svelte +285 -41
- package/dist/grammar/sdoc.tmLanguage.json +45 -0
- package/dist/language/config-schema.js +13 -1
- package/dist/language/page-islands.d.ts +3 -1
- package/dist/language/page-islands.js +15 -1
- package/dist/language/parser.d.ts +4 -0
- package/dist/language/parser.js +69 -20
- package/dist/language/projection.js +53 -0
- package/dist/language/scanner.d.ts +5 -3
- package/dist/language/scanner.js +90 -2
- package/dist/server/app-gen.js +5 -3
- package/dist/server/config.js +5 -1
- package/dist/server/doc-model.d.ts +8 -2
- package/dist/server/doc-model.js +22 -9
- package/dist/server/page-markdown.d.ts +3 -0
- package/dist/server/page-markdown.js +60 -3
- package/dist/server/snippet-compiler.d.ts +14 -0
- package/dist/server/snippet-compiler.js +30 -0
- package/dist/types.d.ts +19 -1
- package/dist/vite.js +62 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,64 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.0.58] - 2026-07-05
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Richer page markdown.** GitHub-style alerts (`> [!NOTE]`, `[!TIP]`,
|
|
15
|
+
`[!IMPORTANT]`, `[!WARNING]`, `[!CAUTION]`) render as tinted callouts;
|
|
16
|
+
task lists get proper checkboxes; table columns honor `:---:` alignment;
|
|
17
|
+
external links open in a new tab (internal ones stay in the app); images
|
|
18
|
+
render lazily and Svelte-safe.
|
|
19
|
+
- **`static` config option.** A folder of static assets served at the site
|
|
20
|
+
root in dev and copied into `dist/` by `sdocs build` — ``
|
|
21
|
+
in a page just works. Powers the standalone CLI flows; embedded apps keep
|
|
22
|
+
using the host's public directory.
|
|
23
|
+
- **Page column alignment.** `content.page.contentX` (config) and
|
|
24
|
+
`contentX` on `[PAGE]` align the content column `left`/`center`/`right`.
|
|
25
|
+
- **A body `#` heading takes over as the page title** — the header no longer
|
|
26
|
+
duplicates it; the entity `title` keeps naming the sidebar entry.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- **A page's `maxWidth` now bounds the content column together with its
|
|
31
|
+
table of contents**; when the toc is hidden the prose takes its space.
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- **Code fences can show component tags.** A `<Component />` line inside a
|
|
36
|
+
markdown fence, preceded by a blank line, was treated as a live Svelte
|
|
37
|
+
island and rendered (or crashed the page) instead of staying highlighted
|
|
38
|
+
code. Fences now shield island detection, matching the scanner and the
|
|
39
|
+
editor projection.
|
|
40
|
+
|
|
41
|
+
## [0.0.57] - 2026-07-05
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- **`[example]` blocks inside `[PAGE]`.** Pages can now stage real code in
|
|
46
|
+
the project's context, mid-prose: an example renders in place on an
|
|
47
|
+
isolated iframe stage that loads the configured `css`, with a collapsed
|
|
48
|
+
code panel — exactly like an example in `[DOCS]`. Stage attributes
|
|
49
|
+
(`maxWidth`, `padding`, `direction`, `gap`, `contentX`, `contentY`)
|
|
50
|
+
cascade from `content.docs`. Titles are required and unique per page;
|
|
51
|
+
markdown fences shield block syntax, so a fence may show `[example]`
|
|
52
|
+
without escaping.
|
|
53
|
+
|
|
54
|
+
### Changed
|
|
55
|
+
|
|
56
|
+
- **Page prose renders natively in the Explorer**, with the docs app's own
|
|
57
|
+
typography — no longer inside an iframe that loads the project `css`. The
|
|
58
|
+
boundary is now crisp: documentation is docs-styled, stages are
|
|
59
|
+
project-styled. Pages gain real markdown styling (headings, lists, tables,
|
|
60
|
+
code), direct table-of-contents scrolling, and native text flow. Svelte
|
|
61
|
+
islands still work and run in the docs context — showcase code belongs in
|
|
62
|
+
`[example]` blocks.
|
|
63
|
+
- **Embedded hosts pass `pageModules`.** `virtual:sdocs` now exports
|
|
64
|
+
`pageModules`; forward it to `<Explorer {docs} {cssNames} {pageModules}>`
|
|
65
|
+
so pages render their content. Hosts that don't pass it show page examples
|
|
66
|
+
and headers but no body.
|
|
67
|
+
|
|
10
68
|
## [0.0.56] - 2026-07-05
|
|
11
69
|
|
|
12
70
|
### Added
|
package/dist/commands/build.js
CHANGED
package/dist/commands/dev.js
CHANGED
|
@@ -37,6 +37,8 @@ export async function devCommand() {
|
|
|
37
37
|
const server = await createServer({
|
|
38
38
|
configFile: false,
|
|
39
39
|
root: sdocsDir,
|
|
40
|
+
// The project's static assets (config `static`), served at the site root
|
|
41
|
+
publicDir: config.static ?? false,
|
|
40
42
|
resolve: {
|
|
41
43
|
dedupe: svelteDedupe(cwd),
|
|
42
44
|
},
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
cssNames?: string[];
|
|
20
20
|
/** URL prefix for preview pages when the host app is served under a sub-path (e.g. SvelteKit's base). */
|
|
21
21
|
previewBase?: string;
|
|
22
|
+
/** Native page components from `virtual:sdocs`, keyed by contentKey. */
|
|
23
|
+
pageModules?: Record<string, () => Promise<{ default: unknown }>>;
|
|
22
24
|
sidebarConfig?: {
|
|
23
25
|
order?: Record<string, string[]>;
|
|
24
26
|
open?: string[];
|
|
@@ -31,6 +33,7 @@
|
|
|
31
33
|
icon = 'sdocs',
|
|
32
34
|
cssNames = [],
|
|
33
35
|
previewBase = '',
|
|
36
|
+
pageModules = {},
|
|
34
37
|
sidebarConfig
|
|
35
38
|
}: Props = $props();
|
|
36
39
|
|
|
@@ -90,7 +93,7 @@
|
|
|
90
93
|
<main class="sdocs-main" class:sdocs-main-fullscreen={sidebarHidden}>
|
|
91
94
|
{#if resolved}
|
|
92
95
|
{#if resolved.doc.kind === 'page'}
|
|
93
|
-
<PageView doc={resolved.doc} {activeStylesheet} />
|
|
96
|
+
<PageView doc={resolved.doc} {activeStylesheet} {pageModules} />
|
|
94
97
|
{:else if resolved.doc.kind === 'layout'}
|
|
95
98
|
<LayoutView doc={resolved.doc} {activeStylesheet} />
|
|
96
99
|
{:else}
|
|
@@ -1,73 +1,129 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import type { Component } from 'svelte';
|
|
2
3
|
import type { DocEntry } from '../../types.js';
|
|
4
|
+
import { Icon } from '../../ui/Icon/index.js';
|
|
5
|
+
import CollapsiblePanel from './CollapsiblePanel.svelte';
|
|
3
6
|
import PreviewFrame from './PreviewFrame.svelte';
|
|
4
7
|
import { displayTitle } from '../tree-builder.js';
|
|
5
8
|
|
|
6
9
|
interface Props {
|
|
7
10
|
doc: DocEntry;
|
|
8
11
|
activeStylesheet?: string;
|
|
12
|
+
/** Native page components from `virtual:sdocs`, keyed by contentKey. */
|
|
13
|
+
pageModules?: Record<string, () => Promise<{ default: unknown }>>;
|
|
9
14
|
}
|
|
10
15
|
|
|
11
|
-
let { doc, activeStylesheet }: Props = $props();
|
|
16
|
+
let { doc, activeStylesheet, pageModules = {} }: Props = $props();
|
|
12
17
|
|
|
13
18
|
const meta = $derived(doc.meta);
|
|
14
|
-
const contentSnippet = $derived(doc.content);
|
|
15
19
|
const toc = $derived(doc.toc ?? []);
|
|
16
20
|
|
|
21
|
+
// The page body compiles to its own component (prose + islands + example
|
|
22
|
+
// markers); it renders here, natively — sdocs styling, no iframe. Only the
|
|
23
|
+
// [example] stages load the project's css, each in its own PreviewFrame.
|
|
24
|
+
let PageComponent = $state<Component | null>(null);
|
|
25
|
+
let container: HTMLElement | undefined = $state();
|
|
26
|
+
|
|
27
|
+
$effect(() => {
|
|
28
|
+
const load = doc.contentKey ? pageModules[doc.contentKey] : undefined;
|
|
29
|
+
PageComponent = null;
|
|
30
|
+
if (!load) return;
|
|
31
|
+
let stale = false;
|
|
32
|
+
load().then((mod) => {
|
|
33
|
+
if (!stale) PageComponent = mod.default as Component;
|
|
34
|
+
});
|
|
35
|
+
return () => {
|
|
36
|
+
stale = true;
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
|
|
17
40
|
function scrollToHeading(id: string) {
|
|
18
|
-
|
|
19
|
-
if (iframe?.contentWindow) {
|
|
20
|
-
iframe.contentWindow.postMessage({ type: 'sdocs:scroll-to', id }, '*');
|
|
21
|
-
}
|
|
41
|
+
container?.querySelector(`#${CSS.escape(id)}`)?.scrollIntoView({ behavior: 'smooth' });
|
|
22
42
|
}
|
|
43
|
+
|
|
44
|
+
// contentX aligns the whole column (content + toc) inside the view.
|
|
45
|
+
const columnMargin = $derived(
|
|
46
|
+
doc.contentX === 'center' ? '0 auto' : doc.contentX === 'right' ? '0 0 0 auto' : undefined,
|
|
47
|
+
);
|
|
23
48
|
</script>
|
|
24
49
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
<div class="sdocs-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
50
|
+
{#snippet exampleFrame(index: number)}
|
|
51
|
+
{@const example = doc.examples?.[index]}
|
|
52
|
+
{#if example}
|
|
53
|
+
<div class="sdocs-page-example">
|
|
54
|
+
{#if example.name}
|
|
55
|
+
<h3 class="sdocs-example-title">
|
|
56
|
+
<Icon name="bookmark" --w="14px" --h="14px" --fill="var(--color-example-500)" />
|
|
57
|
+
{example.name}
|
|
58
|
+
</h3>
|
|
32
59
|
{/if}
|
|
60
|
+
<div class="sdocs-panels">
|
|
61
|
+
<div class="sdocs-preview-wrapper">
|
|
62
|
+
<PreviewFrame src={example.previewUrl ?? ''} {activeStylesheet} />
|
|
63
|
+
</div>
|
|
64
|
+
<CollapsiblePanel title="Code" defaultExpanded={false}>
|
|
65
|
+
<div class="sdocs-code-block">{@html example.highlightedHtml ?? ''}</div>
|
|
66
|
+
</CollapsiblePanel>
|
|
67
|
+
</div>
|
|
33
68
|
</div>
|
|
69
|
+
{/if}
|
|
70
|
+
{/snippet}
|
|
34
71
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
72
|
+
<div class="sdocs-page-view" style:padding={doc.padding}>
|
|
73
|
+
<!-- maxWidth constrains the whole column — content plus toc; contentX
|
|
74
|
+
places it. Without a toc the content takes the toc's space. -->
|
|
75
|
+
<div class="sdocs-page-inner" style:max-width={doc.maxWidth} style:margin={columnMargin}>
|
|
76
|
+
<div class="sdocs-page-main">
|
|
77
|
+
<!-- Header: a body `#` heading takes over as the page title -->
|
|
78
|
+
{#if !doc.bodyTitle}
|
|
79
|
+
<div class="sdocs-view-header">
|
|
80
|
+
<h1 class="sdocs-view-title">{displayTitle(meta.title)}</h1>
|
|
81
|
+
{#if meta.description}
|
|
82
|
+
<p class="sdocs-view-description">{meta.description}</p>
|
|
83
|
+
{/if}
|
|
84
|
+
</div>
|
|
85
|
+
{/if}
|
|
86
|
+
|
|
87
|
+
<!-- Content -->
|
|
88
|
+
<div class="sdocs-page-content" bind:this={container}>
|
|
89
|
+
{#if PageComponent}
|
|
90
|
+
<PageComponent __sdocsExample={exampleFrame} />
|
|
91
|
+
{/if}
|
|
39
92
|
</div>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<!-- Table of Contents -->
|
|
96
|
+
{#if toc.length > 0 && doc.showToc !== false}
|
|
97
|
+
<aside class="sdocs-toc">
|
|
98
|
+
<h3 class="sdocs-toc-title">On this page</h3>
|
|
99
|
+
<nav>
|
|
100
|
+
<ul class="sdocs-toc-list">
|
|
101
|
+
{#each toc as heading (heading.id)}
|
|
102
|
+
<li class="sdocs-toc-item" style:padding-left="{(heading.level - 2) * 12}px">
|
|
103
|
+
<button
|
|
104
|
+
class="sdocs-toc-link"
|
|
105
|
+
onclick={() => scrollToHeading(heading.id)}
|
|
106
|
+
>
|
|
107
|
+
{heading.text}
|
|
108
|
+
</button>
|
|
109
|
+
</li>
|
|
110
|
+
{/each}
|
|
111
|
+
</ul>
|
|
112
|
+
</nav>
|
|
113
|
+
</aside>
|
|
40
114
|
{/if}
|
|
41
115
|
</div>
|
|
42
|
-
|
|
43
|
-
<!-- Table of Contents -->
|
|
44
|
-
{#if toc.length > 0 && doc.showToc !== false}
|
|
45
|
-
<aside class="sdocs-toc">
|
|
46
|
-
<h3 class="sdocs-toc-title">On this page</h3>
|
|
47
|
-
<nav>
|
|
48
|
-
<ul class="sdocs-toc-list">
|
|
49
|
-
{#each toc as heading (heading.id)}
|
|
50
|
-
<li class="sdocs-toc-item" style:padding-left="{(heading.level - 2) * 12}px">
|
|
51
|
-
<button
|
|
52
|
-
class="sdocs-toc-link"
|
|
53
|
-
onclick={() => scrollToHeading(heading.id)}
|
|
54
|
-
>
|
|
55
|
-
{heading.text}
|
|
56
|
-
</button>
|
|
57
|
-
</li>
|
|
58
|
-
{/each}
|
|
59
|
-
</ul>
|
|
60
|
-
</nav>
|
|
61
|
-
</aside>
|
|
62
|
-
{/if}
|
|
63
116
|
</div>
|
|
64
117
|
|
|
65
118
|
<style>
|
|
66
119
|
.sdocs-page-view {
|
|
120
|
+
/* padding comes from the doc entry (config/entity cascade) */
|
|
121
|
+
font-family: var(--sans);
|
|
122
|
+
}
|
|
123
|
+
.sdocs-page-inner {
|
|
67
124
|
display: flex;
|
|
68
125
|
gap: 24px;
|
|
69
|
-
|
|
70
|
-
font-family: var(--sans);
|
|
126
|
+
/* max-width and margin (contentX) come from the doc entry */
|
|
71
127
|
}
|
|
72
128
|
.sdocs-page-main {
|
|
73
129
|
flex: 1;
|
|
@@ -87,9 +143,197 @@
|
|
|
87
143
|
color: var(--color-base-500);
|
|
88
144
|
margin: 6px 0 0;
|
|
89
145
|
}
|
|
146
|
+
|
|
147
|
+
/* ── Page prose: the docs app's own typography ── */
|
|
90
148
|
.sdocs-page-content {
|
|
91
|
-
|
|
149
|
+
font-size: 14px;
|
|
150
|
+
line-height: 1.7;
|
|
151
|
+
color: var(--color-base-800);
|
|
152
|
+
}
|
|
153
|
+
.sdocs-page-content :global(h1),
|
|
154
|
+
.sdocs-page-content :global(h2),
|
|
155
|
+
.sdocs-page-content :global(h3),
|
|
156
|
+
.sdocs-page-content :global(h4),
|
|
157
|
+
.sdocs-page-content :global(h5),
|
|
158
|
+
.sdocs-page-content :global(h6) {
|
|
159
|
+
color: var(--color-base-900);
|
|
160
|
+
font-weight: 650;
|
|
161
|
+
line-height: 1.3;
|
|
162
|
+
margin: 1.6em 0 0.5em;
|
|
163
|
+
scroll-margin-top: 16px;
|
|
164
|
+
}
|
|
165
|
+
.sdocs-page-content :global(h1) { font-size: 22px; }
|
|
166
|
+
.sdocs-page-content :global(h2) { font-size: 18px; }
|
|
167
|
+
.sdocs-page-content :global(h3) { font-size: 15px; }
|
|
168
|
+
.sdocs-page-content :global(h4),
|
|
169
|
+
.sdocs-page-content :global(h5),
|
|
170
|
+
.sdocs-page-content :global(h6) { font-size: 14px; }
|
|
171
|
+
.sdocs-page-content :global(h1:first-child),
|
|
172
|
+
.sdocs-page-content :global(h2:first-child),
|
|
173
|
+
.sdocs-page-content :global(h3:first-child) {
|
|
174
|
+
margin-top: 0;
|
|
175
|
+
}
|
|
176
|
+
.sdocs-page-content :global(p) {
|
|
177
|
+
margin: 0.7em 0;
|
|
178
|
+
}
|
|
179
|
+
.sdocs-page-content :global(a) {
|
|
180
|
+
color: var(--color-action-500, var(--color-base-900));
|
|
181
|
+
text-decoration: underline;
|
|
182
|
+
text-underline-offset: 2px;
|
|
183
|
+
}
|
|
184
|
+
.sdocs-page-content :global(ul),
|
|
185
|
+
.sdocs-page-content :global(ol) {
|
|
186
|
+
margin: 0.7em 0;
|
|
187
|
+
padding-left: 1.6em;
|
|
188
|
+
}
|
|
189
|
+
.sdocs-page-content :global(li) {
|
|
190
|
+
margin: 0.25em 0;
|
|
191
|
+
}
|
|
192
|
+
.sdocs-page-content :global(blockquote) {
|
|
193
|
+
margin: 0.9em 0;
|
|
194
|
+
padding: 2px 16px;
|
|
195
|
+
border-left: 3px solid var(--color-base-200);
|
|
196
|
+
color: var(--color-base-600);
|
|
197
|
+
}
|
|
198
|
+
.sdocs-page-content :global(hr) {
|
|
199
|
+
border: none;
|
|
200
|
+
border-top: 1px solid var(--color-base-200);
|
|
201
|
+
margin: 24px 0;
|
|
202
|
+
}
|
|
203
|
+
.sdocs-page-content :global(code) {
|
|
204
|
+
font-family: var(--mono);
|
|
205
|
+
font-size: 0.92em;
|
|
206
|
+
}
|
|
207
|
+
.sdocs-page-content :global(:not(pre) > code) {
|
|
208
|
+
background: var(--color-base-100);
|
|
209
|
+
border-radius: 4px;
|
|
210
|
+
padding: 1px 5px;
|
|
211
|
+
}
|
|
212
|
+
.sdocs-page-content :global(pre) {
|
|
213
|
+
margin: 0.9em 0;
|
|
214
|
+
padding: 12px;
|
|
215
|
+
border-radius: 6px;
|
|
216
|
+
overflow-x: auto;
|
|
217
|
+
font-size: 13px;
|
|
218
|
+
line-height: 1.5;
|
|
219
|
+
tab-size: 4;
|
|
220
|
+
}
|
|
221
|
+
.sdocs-page-content :global(table) {
|
|
222
|
+
border-collapse: collapse;
|
|
223
|
+
margin: 0.9em 0;
|
|
224
|
+
display: block;
|
|
225
|
+
max-width: 100%;
|
|
226
|
+
overflow-x: auto;
|
|
227
|
+
}
|
|
228
|
+
.sdocs-page-content :global(th),
|
|
229
|
+
.sdocs-page-content :global(td) {
|
|
230
|
+
border: 1px solid var(--color-base-200);
|
|
231
|
+
padding: 6px 12px;
|
|
232
|
+
text-align: left;
|
|
92
233
|
}
|
|
234
|
+
.sdocs-page-content :global(td[align='center']),
|
|
235
|
+
.sdocs-page-content :global(th[align='center']) {
|
|
236
|
+
text-align: center;
|
|
237
|
+
}
|
|
238
|
+
.sdocs-page-content :global(td[align='right']),
|
|
239
|
+
.sdocs-page-content :global(th[align='right']) {
|
|
240
|
+
text-align: right;
|
|
241
|
+
}
|
|
242
|
+
.sdocs-page-content :global(th) {
|
|
243
|
+
background: var(--color-base-50);
|
|
244
|
+
font-weight: 600;
|
|
245
|
+
}
|
|
246
|
+
.sdocs-page-content :global(img) {
|
|
247
|
+
max-width: 100%;
|
|
248
|
+
border-radius: 6px;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/* Task lists: GitHub-style checkboxes, no bullet */
|
|
252
|
+
.sdocs-page-content :global(li:has(> input[type='checkbox']:first-child)) {
|
|
253
|
+
list-style: none;
|
|
254
|
+
margin-left: -1.3em;
|
|
255
|
+
}
|
|
256
|
+
.sdocs-page-content :global(li > input[type='checkbox']) {
|
|
257
|
+
margin-right: 6px;
|
|
258
|
+
vertical-align: -2px;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/* GitHub-style alerts: > [!NOTE] / [!TIP] / [!IMPORTANT] / [!WARNING] / [!CAUTION] */
|
|
262
|
+
.sdocs-page-content :global(.sdocs-alert) {
|
|
263
|
+
margin: 0.9em 0;
|
|
264
|
+
padding: 8px 16px;
|
|
265
|
+
border-left: 3px solid var(--sdocs-alert-color);
|
|
266
|
+
border-radius: 0 6px 6px 0;
|
|
267
|
+
background: color-mix(in srgb, var(--sdocs-alert-color) 6%, transparent);
|
|
268
|
+
}
|
|
269
|
+
.sdocs-page-content :global(.sdocs-alert-label) {
|
|
270
|
+
font-size: 13px;
|
|
271
|
+
font-weight: 650;
|
|
272
|
+
color: var(--sdocs-alert-color);
|
|
273
|
+
margin: 0.25em 0 0;
|
|
274
|
+
}
|
|
275
|
+
.sdocs-page-content :global(.sdocs-alert-note) {
|
|
276
|
+
--sdocs-alert-color: var(--color-blue-500);
|
|
277
|
+
}
|
|
278
|
+
.sdocs-page-content :global(.sdocs-alert-tip) {
|
|
279
|
+
--sdocs-alert-color: var(--color-green-500);
|
|
280
|
+
}
|
|
281
|
+
.sdocs-page-content :global(.sdocs-alert-important) {
|
|
282
|
+
--sdocs-alert-color: var(--color-purple-500);
|
|
283
|
+
}
|
|
284
|
+
.sdocs-page-content :global(.sdocs-alert-warning) {
|
|
285
|
+
--sdocs-alert-color: var(--color-amber-500);
|
|
286
|
+
}
|
|
287
|
+
.sdocs-page-content :global(.sdocs-alert-caution) {
|
|
288
|
+
--sdocs-alert-color: var(--color-red-500);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/* ── Example stages in the page flow ── */
|
|
292
|
+
.sdocs-page-example {
|
|
293
|
+
display: flex;
|
|
294
|
+
flex-direction: column;
|
|
295
|
+
gap: 8px;
|
|
296
|
+
margin: 16px 0;
|
|
297
|
+
}
|
|
298
|
+
.sdocs-example-title {
|
|
299
|
+
display: flex;
|
|
300
|
+
align-items: center;
|
|
301
|
+
gap: 6px;
|
|
302
|
+
font-size: 15px;
|
|
303
|
+
font-weight: 600;
|
|
304
|
+
color: var(--color-base-800);
|
|
305
|
+
margin: 0;
|
|
306
|
+
}
|
|
307
|
+
.sdocs-panels {
|
|
308
|
+
display: flex;
|
|
309
|
+
flex-direction: column;
|
|
310
|
+
gap: 1px;
|
|
311
|
+
border: 1px solid var(--color-base-200);
|
|
312
|
+
background: var(--color-base-200);
|
|
313
|
+
border-radius: 8px;
|
|
314
|
+
overflow: hidden;
|
|
315
|
+
}
|
|
316
|
+
.sdocs-preview-wrapper {
|
|
317
|
+
/* stage padding lives inside the iframe (config/block cascade) */
|
|
318
|
+
background: var(--color-base-0);
|
|
319
|
+
}
|
|
320
|
+
.sdocs-code-block {
|
|
321
|
+
overflow-x: auto;
|
|
322
|
+
font-size: 13px;
|
|
323
|
+
line-height: 1.5;
|
|
324
|
+
tab-size: 4;
|
|
325
|
+
}
|
|
326
|
+
.sdocs-code-block :global(pre) {
|
|
327
|
+
margin: 0;
|
|
328
|
+
padding: 12px;
|
|
329
|
+
border-radius: 6px;
|
|
330
|
+
overflow-x: auto;
|
|
331
|
+
}
|
|
332
|
+
.sdocs-code-block :global(code) {
|
|
333
|
+
font-family: var(--mono);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/* ── Table of contents ── */
|
|
93
337
|
.sdocs-toc {
|
|
94
338
|
width: 200px;
|
|
95
339
|
flex-shrink: 0;
|
|
@@ -244,6 +244,9 @@
|
|
|
244
244
|
"while": "(^|\\G)(?!\\s*\\[/PAGE\\]\\s*$)(?:\\t|[ ]{1,4})?",
|
|
245
245
|
"contentName": "meta.embedded.block.markdown",
|
|
246
246
|
"patterns": [
|
|
247
|
+
{
|
|
248
|
+
"include": "#page-example"
|
|
249
|
+
},
|
|
247
250
|
{
|
|
248
251
|
"include": "#page-svelte-block"
|
|
249
252
|
},
|
|
@@ -570,6 +573,48 @@
|
|
|
570
573
|
"include": "source.ts"
|
|
571
574
|
}
|
|
572
575
|
]
|
|
576
|
+
},
|
|
577
|
+
"page-example": {
|
|
578
|
+
"name": "meta.block.example.sdoc",
|
|
579
|
+
"begin": "(^|\\G)\\s*(\\[)(example)\\b(.*)$",
|
|
580
|
+
"beginCaptures": {
|
|
581
|
+
"2": {
|
|
582
|
+
"name": "punctuation.definition.tag.begin.sdoc"
|
|
583
|
+
},
|
|
584
|
+
"3": {
|
|
585
|
+
"name": "entity.name.tag.sdoc"
|
|
586
|
+
},
|
|
587
|
+
"4": {
|
|
588
|
+
"patterns": [
|
|
589
|
+
{
|
|
590
|
+
"include": "#attributes"
|
|
591
|
+
}
|
|
592
|
+
]
|
|
593
|
+
}
|
|
594
|
+
},
|
|
595
|
+
"end": "(^|\\G)\\s*(\\[/)(example)(\\])\\s*$",
|
|
596
|
+
"endCaptures": {
|
|
597
|
+
"2": {
|
|
598
|
+
"name": "punctuation.definition.tag.begin.sdoc"
|
|
599
|
+
},
|
|
600
|
+
"3": {
|
|
601
|
+
"name": "entity.name.tag.sdoc"
|
|
602
|
+
},
|
|
603
|
+
"4": {
|
|
604
|
+
"name": "punctuation.definition.tag.end.sdoc"
|
|
605
|
+
}
|
|
606
|
+
},
|
|
607
|
+
"patterns": [
|
|
608
|
+
{
|
|
609
|
+
"begin": "(^|\\G)",
|
|
610
|
+
"while": "(^|\\G)(?!\\s*\\[/example\\]\\s*$)",
|
|
611
|
+
"patterns": [
|
|
612
|
+
{
|
|
613
|
+
"include": "source.svelte"
|
|
614
|
+
}
|
|
615
|
+
]
|
|
616
|
+
}
|
|
617
|
+
]
|
|
573
618
|
}
|
|
574
619
|
}
|
|
575
620
|
}
|
|
@@ -65,6 +65,11 @@ export const configSchema = {
|
|
|
65
65
|
doc: 'CSS loaded inside preview iframes — a single stylesheet path, or a map of named stylesheets to switch between.',
|
|
66
66
|
insert: ": '$0'",
|
|
67
67
|
},
|
|
68
|
+
static: {
|
|
69
|
+
detail: 'string',
|
|
70
|
+
doc: 'Folder of static assets served at the site root — images for pages, files for previews. Standalone CLI flows; embedded apps use the host\'s public directory.',
|
|
71
|
+
insert: ": './${0:static}'",
|
|
72
|
+
},
|
|
68
73
|
logo: {
|
|
69
74
|
detail: 'string',
|
|
70
75
|
doc: 'Sidebar logo text. Default: `sdocs`.',
|
|
@@ -101,7 +106,7 @@ export const configSchema = {
|
|
|
101
106
|
object: {
|
|
102
107
|
page: {
|
|
103
108
|
detail: 'object',
|
|
104
|
-
doc: '`[PAGE]` content. Defaults: `maxWidth` `1200px`, `padding` `32px`, `toc` `true`.',
|
|
109
|
+
doc: '`[PAGE]` content. Defaults: `maxWidth` `1200px`, `padding` `32px`, `toc` `true`, `contentX` `left`.',
|
|
105
110
|
insert: ': {\n\t$0\n}',
|
|
106
111
|
object: {
|
|
107
112
|
maxWidth,
|
|
@@ -112,6 +117,13 @@ export const configSchema = {
|
|
|
112
117
|
insert: ': ${0:true}',
|
|
113
118
|
values: ['true', 'false'],
|
|
114
119
|
},
|
|
120
|
+
contentX: {
|
|
121
|
+
detail: "'left' | 'center' | 'right'",
|
|
122
|
+
doc: 'Horizontal alignment of the page content column (with its toc). Default: `left`.',
|
|
123
|
+
insert: ": '${0:center}'",
|
|
124
|
+
values: ['left', 'center', 'right'],
|
|
125
|
+
quoted: true,
|
|
126
|
+
},
|
|
115
127
|
},
|
|
116
128
|
},
|
|
117
129
|
docs: {
|
|
@@ -19,5 +19,7 @@ export interface PageSegment {
|
|
|
19
19
|
/** Verbatim lines of the segment */
|
|
20
20
|
lines: string[];
|
|
21
21
|
}
|
|
22
|
-
/** Split a page body into prose and island segments.
|
|
22
|
+
/** Split a page body into prose and island segments. Lines inside markdown
|
|
23
|
+
* code fences are always prose — a component tag shown in a fence must stay
|
|
24
|
+
* displayed code, never a live island. */
|
|
23
25
|
export declare function segmentPageBody(body: string): PageSegment[];
|
|
@@ -80,7 +80,9 @@ function lineDepths(line) {
|
|
|
80
80
|
}
|
|
81
81
|
return { svelte, html };
|
|
82
82
|
}
|
|
83
|
-
/** Split a page body into prose and island segments.
|
|
83
|
+
/** Split a page body into prose and island segments. Lines inside markdown
|
|
84
|
+
* code fences are always prose — a component tag shown in a fence must stay
|
|
85
|
+
* displayed code, never a live island. */
|
|
84
86
|
export function segmentPageBody(body) {
|
|
85
87
|
const lines = body.split('\n');
|
|
86
88
|
const segments = [];
|
|
@@ -92,9 +94,21 @@ export function segmentPageBody(body) {
|
|
|
92
94
|
}
|
|
93
95
|
current.lines.push(line);
|
|
94
96
|
};
|
|
97
|
+
let inFence = false;
|
|
95
98
|
let i = 0;
|
|
96
99
|
while (i < lines.length) {
|
|
97
100
|
const line = lines[i];
|
|
101
|
+
if (/^\s*(`{3,}|~{3,})/.test(line)) {
|
|
102
|
+
inFence = !inFence;
|
|
103
|
+
push('prose', line);
|
|
104
|
+
i++;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (inFence) {
|
|
108
|
+
push('prose', line);
|
|
109
|
+
i++;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
98
112
|
const prevBlank = i === 0 || lines[i - 1].trim() === '';
|
|
99
113
|
if (prevBlank && line.trim() !== '' && startsIsland(line)) {
|
|
100
114
|
// Consume the island: until Svelte blocks and HTML tags balance out.
|
|
@@ -59,7 +59,11 @@ export interface PageEntity {
|
|
|
59
59
|
title: string;
|
|
60
60
|
slug: string;
|
|
61
61
|
sizing: Sizing;
|
|
62
|
+
/** Prose body with each [example] block replaced by a
|
|
63
|
+
* `{@render __sdocsExample?.(i)}` marker the page renderer resolves. */
|
|
62
64
|
body: string;
|
|
65
|
+
/** The page's [example] blocks, in marker order */
|
|
66
|
+
examples: ExampleBlock[];
|
|
63
67
|
bodySpan: Span;
|
|
64
68
|
openerSpan: Span;
|
|
65
69
|
span: Span;
|