specra 0.2.57 → 0.2.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.
Files changed (2) hide show
  1. package/dist/mdx.js +104 -4
  2. package/package.json +1 -1
package/dist/mdx.js CHANGED
@@ -100,6 +100,100 @@ const PROP_NAME_MAP = {
100
100
  * processed by JSX/component preprocessing functions.
101
101
  * Matches 3+ backticks or tildes as fence markers.
102
102
  */
103
+ /**
104
+ * Private Use Area character used to mask `|` inside inline code spans
105
+ * so that remark-gfm's table tokenizer doesn't treat them as cell dividers.
106
+ *
107
+ * Why: GFM splits table cells on `|` BEFORE inline parsing recognizes
108
+ * backtick-delimited code spans. So a row like
109
+ * | `{{ name | upper }}` |
110
+ * gets split on the inner pipe, breaking the row. Escaping with `\|` is
111
+ * the GFM-spec workaround, but inside an inline code span the `\` renders
112
+ * literally — useless for users. We swap `|` for U+E000 before parsing
113
+ * and swap it back in the output; PUA chars pass through remark/rehype
114
+ * unchanged and are never HTML-escaped.
115
+ */
116
+ const PIPE_MARKER = '';
117
+ /**
118
+ * Mask `|` characters inside single-line markdown inline code spans by
119
+ * replacing them with `PIPE_MARKER`. Operates only on non-fenced segments.
120
+ * Idempotent: if no inline code contains `|`, output equals input.
121
+ */
122
+ function maskInlineCodePipes(markdown) {
123
+ return splitByCodeFences(markdown).map(({ text, isCode }) => {
124
+ if (isCode)
125
+ return text;
126
+ let result = '';
127
+ let i = 0;
128
+ while (i < text.length) {
129
+ if (text[i] !== '`') {
130
+ result += text[i];
131
+ i++;
132
+ continue;
133
+ }
134
+ // Count opening backtick run
135
+ let openLen = 0;
136
+ while (i + openLen < text.length && text[i + openLen] === '`')
137
+ openLen++;
138
+ // Find matching closing run of the same length on the same line.
139
+ // (Inline code spans inside table cells are always single-line.)
140
+ let j = i + openLen;
141
+ let closeIdx = -1;
142
+ while (j < text.length && text[j] !== '\n') {
143
+ if (text[j] === '`') {
144
+ let closeLen = 0;
145
+ while (j + closeLen < text.length && text[j + closeLen] === '`')
146
+ closeLen++;
147
+ if (closeLen === openLen) {
148
+ closeIdx = j;
149
+ break;
150
+ }
151
+ j += closeLen;
152
+ }
153
+ else {
154
+ j++;
155
+ }
156
+ }
157
+ if (closeIdx === -1) {
158
+ // Unmatched run — emit verbatim
159
+ result += text.slice(i, i + openLen);
160
+ i += openLen;
161
+ }
162
+ else {
163
+ const content = text.slice(i + openLen, closeIdx).replace(/\|/g, PIPE_MARKER);
164
+ result += text.slice(i, i + openLen) + content + text.slice(closeIdx, closeIdx + openLen);
165
+ i = closeIdx + openLen;
166
+ }
167
+ }
168
+ return result;
169
+ }).join('');
170
+ }
171
+ /**
172
+ * Restore `PIPE_MARKER` back to `|` in a string. No-op if the marker
173
+ * isn't present (the common case), so cheap to call blanket.
174
+ */
175
+ function restorePipeMarkers(s) {
176
+ return s.indexOf(PIPE_MARKER) === -1 ? s : s.split(PIPE_MARKER).join('|');
177
+ }
178
+ /**
179
+ * Walk an MdxNode tree and restore `PIPE_MARKER` to `|` in every string
180
+ * field that could carry the marker (html content, string props, children).
181
+ */
182
+ function restorePipeMarkersInNodes(nodes) {
183
+ for (const node of nodes) {
184
+ if (node.content)
185
+ node.content = restorePipeMarkers(node.content);
186
+ if (node.props) {
187
+ for (const key of Object.keys(node.props)) {
188
+ const v = node.props[key];
189
+ if (typeof v === 'string')
190
+ node.props[key] = restorePipeMarkers(v);
191
+ }
192
+ }
193
+ if (node.children && node.children.length > 0)
194
+ restorePipeMarkersInNodes(node.children);
195
+ }
196
+ }
103
197
  function splitByCodeFences(markdown) {
104
198
  const fencedCodeRegex = /(^|\n)((`{3,}|~{3,}).*\n[\s\S]*?\n\3\s*(?:\n|$))/g;
105
199
  const segments = [];
@@ -419,6 +513,7 @@ function resolveDeploymentBasePath() {
419
513
  }
420
514
  async function processMarkdownToHtml(markdown) {
421
515
  const basePath = resolveDeploymentBasePath();
516
+ const masked = maskInlineCodePipes(markdown);
422
517
  const processor = unified()
423
518
  .use(remarkParse)
424
519
  .use(remarkGfm)
@@ -432,8 +527,8 @@ async function processMarkdownToHtml(markdown) {
432
527
  }
433
528
  const result = await processor
434
529
  .use(rehypeStringify)
435
- .process(markdown);
436
- return String(result);
530
+ .process(masked);
531
+ return restorePipeMarkers(String(result));
437
532
  }
438
533
  /**
439
534
  * Convert hast properties to component props with correct casing.
@@ -1065,8 +1160,11 @@ function ensureComponentBlockIntegrity(markdown) {
1065
1160
  return markdown;
1066
1161
  }
1067
1162
  async function processMarkdownToMdxNodes(markdown) {
1163
+ // Mask pipes inside inline code spans so GFM tables containing
1164
+ // `{{ x | filter }}`-style code don't get their rows broken.
1165
+ const masked = maskInlineCodePipes(markdown);
1068
1166
  // Pre-process JSX expression attributes into HTML-safe string attributes
1069
- const preprocessed = preprocessJsxExpressions(markdown);
1167
+ const preprocessed = preprocessJsxExpressions(masked);
1070
1168
  // Dedent content inside component tags so that indented children
1071
1169
  // (e.g. <Tabs> inside <Step>) don't get treated as code blocks
1072
1170
  // by remark-parse (4+ spaces = indented code in CommonMark).
@@ -1089,7 +1187,9 @@ async function processMarkdownToMdxNodes(markdown) {
1089
1187
  const hast = await processor.run(mdast);
1090
1188
  // The hast root has children - process them into MdxNodes
1091
1189
  const children = hast.children || [];
1092
- return hastChildrenToMdxNodes(children);
1190
+ const nodes = await hastChildrenToMdxNodes(children);
1191
+ restorePipeMarkersInNodes(nodes);
1192
+ return nodes;
1093
1193
  }
1094
1194
  /**
1095
1195
  * Calculate reading time based on word count
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specra",
3
- "version": "0.2.57",
3
+ "version": "0.2.58",
4
4
  "description": "A modern documentation library for SvelteKit with built-in versioning, API reference generation, full-text search, and MDX support",
5
5
  "svelte": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",