sdocs 0.0.49 → 0.0.51

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,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.0.51] - 2026-07-05
11
+
12
+ ### Changed
13
+
14
+ - **The sdoc grammar colors `[PAGE]` bodies as markdown.** Headings, bold,
15
+ lists, and fences highlight in page prose (the body's block indent is
16
+ accounted for, so indented bodies don't read as code blocks). Applies
17
+ everywhere the grammar is used — the VS Code extension and ` ```sdoc `
18
+ fences on the site.
19
+
20
+ ### Fixed
21
+
22
+ - **Page expressions with string literals no longer break the page.** The
23
+ markdown pass HTML-escaped quotes (and `&&`, `<`) inside `{…}` expressions,
24
+ so `{@render colorBox("#ff0000")}` or `{format("x")}` in a `[PAGE]` body
25
+ failed to compile. Balanced `{…}` spans now pass through verbatim; prose
26
+ around them escapes exactly as before, and code fences and inline code stay
27
+ inert.
28
+
29
+ ### Added
30
+
31
+ - **`\{` escapes a literal brace in page prose.** Previously a lone brace
32
+ written in prose could break the page; a backslash-escaped brace now renders
33
+ as an inert literal `{`.
34
+
35
+ ## [0.0.50] - 2026-07-05
36
+
37
+ ### Fixed
38
+
39
+ - **The last block of a page no longer gets clipped.** Preview iframes size
40
+ themselves to the preview root's `scrollHeight`, but the first and last
41
+ child's margins collapsed through that root and weren't counted — so the
42
+ final block of `[PAGE]` content (a closing paragraph, a raw HTML block)
43
+ was cut off. The preview root is now a block formatting context
44
+ (`display: flow-root`), so the reported height includes everything.
45
+
10
46
  ## [0.0.49] - 2026-07-05
11
47
 
12
48
  ### Fixed
@@ -229,10 +229,11 @@
229
229
  "patterns": [
230
230
  {
231
231
  "begin": "(^|\\G)",
232
- "while": "(^|\\G)(?!\\s*\\[/PAGE\\]\\s*$)",
232
+ "while": "(^|\\G)(?!\\s*\\[/PAGE\\]\\s*$)(?:\\t|[ ]{1,4})?",
233
+ "contentName": "meta.embedded.block.markdown",
233
234
  "patterns": [
234
235
  {
235
- "include": "source.svelte"
236
+ "include": "text.html.markdown"
236
237
  }
237
238
  ]
238
239
  }
@@ -27,6 +27,55 @@ function escapeHtml(text) {
27
27
  .replace(/>/g, '&gt;')
28
28
  .replace(/"/g, '&quot;');
29
29
  }
30
+ /** marked-equivalent prose escaping (entities already present survive). */
31
+ function escapeProse(text) {
32
+ return text
33
+ .replace(/&(?!(?:#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g, '&amp;')
34
+ .replace(/</g, '&lt;')
35
+ .replace(/>/g, '&gt;')
36
+ .replace(/"/g, '&quot;')
37
+ .replace(/'/g, '&#39;');
38
+ }
39
+ /** Index of the `}` closing the `{` at `open`, or -1. Skips quoted strings. */
40
+ function findExpressionEnd(text, open) {
41
+ let depth = 0;
42
+ for (let i = open; i < text.length; i++) {
43
+ const ch = text[i];
44
+ if (ch === '"' || ch === "'" || ch === '`') {
45
+ for (i++; i < text.length && text[i] !== ch; i++) {
46
+ if (text[i] === '\\')
47
+ i++;
48
+ }
49
+ if (i >= text.length)
50
+ return -1;
51
+ }
52
+ else if (ch === '{')
53
+ depth++;
54
+ else if (ch === '}' && --depth === 0)
55
+ return i;
56
+ }
57
+ return -1;
58
+ }
59
+ /**
60
+ * Escape prose while passing balanced `{…}` spans through verbatim, so Svelte
61
+ * expressions survive the markdown pass — string literals, `&&`, comparisons.
62
+ * An unmatched `{` falls back to prose escaping, exactly as before.
63
+ */
64
+ function escapeTextKeepingExpressions(text) {
65
+ let out = '';
66
+ let from = 0;
67
+ for (let i = 0; i < text.length; i++) {
68
+ if (text[i] !== '{')
69
+ continue;
70
+ const end = findExpressionEnd(text, i);
71
+ if (end === -1)
72
+ break;
73
+ out += escapeProse(text.slice(from, i)) + text.slice(i, end + 1);
74
+ from = end + 1;
75
+ i = end;
76
+ }
77
+ return out + escapeProse(text.slice(from));
78
+ }
30
79
  function plainText(tokens) {
31
80
  let out = '';
32
81
  for (const token of tokens) {
@@ -97,6 +146,18 @@ export async function renderPageMarkdown(source) {
97
146
  // tags, {expressions} stay expressions.
98
147
  html(token) {
99
148
  return token.text;
149
+ },
150
+ // Prose escapes for HTML, but balanced {…} spans stay verbatim so
151
+ // expressions keep their quotes and operators. A backslash-escaped
152
+ // brace (`\{`) becomes an inert literal brace.
153
+ text(token) {
154
+ if (token.type === 'escape')
155
+ return escapeBraces(escapeProse(token.text));
156
+ if ('tokens' in token && token.tokens)
157
+ return this.parser.parseInline(token.tokens);
158
+ if ('escaped' in token && token.escaped)
159
+ return token.text;
160
+ return escapeTextKeepingExpressions(token.text);
100
161
  }
101
162
  }
102
163
  });
@@ -138,7 +138,10 @@ ${stateBroadcast}
138
138
 
139
139
  window.parent.postMessage({ type: 'sdocs:preview-ready' }, '*');
140
140
 
141
- // Report content height to parent for auto-sizing
141
+ // Report content height to parent for auto-sizing. The preview div is
142
+ // display: flow-root so child margins are contained — otherwise the
143
+ // first/last child's margins collapse through it, scrollHeight comes up
144
+ // short, and the iframe clips the final block of content.
142
145
  const ro = new ResizeObserver(() => {
143
146
  const height = document.getElementById('sdocs-preview')?.scrollHeight ?? 0;
144
147
  window.parent.postMessage({ type: 'sdocs:resize', height }, '*');
@@ -148,7 +151,7 @@ ${stateBroadcast}
148
151
  });
149
152
  </script>
150
153
 
151
- <div id="sdocs-preview">
154
+ <div id="sdocs-preview" style="display: flow-root">
152
155
  {#snippet SdocsPreview(args)}
153
156
  ${injectRootRef(snippetBody, componentName)}
154
157
  {/snippet}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sdocs",
3
- "version": "0.0.49",
3
+ "version": "0.0.51",
4
4
  "description": "A lightweight documentation tool for Svelte 5 components",
5
5
  "type": "module",
6
6
  "license": "MIT",