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 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` — `![hero](/hero.png)`
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
@@ -19,6 +19,8 @@ export async function buildCommand() {
19
19
  await build({
20
20
  configFile: false,
21
21
  root: sdocsDir,
22
+ // The project's static assets (config `static`), copied into dist/
23
+ publicDir: config.static ?? false,
22
24
  resolve: {
23
25
  dedupe: svelteDedupe(cwd),
24
26
  },
@@ -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
- const iframe = document.querySelector('.sdocs-page-content .sdocs-iframe') as HTMLIFrameElement;
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
- <div class="sdocs-page-view">
26
- <div class="sdocs-page-main" style:max-width={doc.maxWidth}>
27
- <!-- Header -->
28
- <div class="sdocs-view-header">
29
- <h1 class="sdocs-view-title">{displayTitle(meta.title)}</h1>
30
- {#if meta.description}
31
- <p class="sdocs-view-description">{meta.description}</p>
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
- <!-- Content -->
36
- {#if contentSnippet}
37
- <div class="sdocs-page-content">
38
- <PreviewFrame src={contentSnippet.previewUrl} {activeStylesheet} />
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
- padding: 24px 32px;
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
- flex: 1;
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;