@effected/markdown 0.12.1 → 0.14.0

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/Markdown.js CHANGED
@@ -295,6 +295,13 @@ var Markdown = class Markdown {
295
295
  * rows describe a synthesized node, which is the case a test asserting on
296
296
  * generated markdown actually has.
297
297
  *
298
+ * **Text escaping is canonical unless a `Text` node opts out.** A `Text`
299
+ * carrying `escapeStyle: "literal"` is emitted verbatim apart from the
300
+ * escapes that protect block structure (a table cell's `|`, a newline in a
301
+ * single-line container, line-start block openers); its exact set is
302
+ * documented on {@link Text}. The opt-out is additive: a tree that never
303
+ * sets it serializes byte-identically to the canonical form.
304
+ *
298
305
  * **Representability wins over the table.** The canonical form never emits
299
306
  * text that would re-parse as something else, so a row yields where the two
300
307
  * conflict. The case that reaches a consumer is the indented code block: an
@@ -364,7 +371,9 @@ var Markdown = class Markdown {
364
371
  * admits spec mdast and strips everything outside it, so a `fenceChar`,
365
372
  * `headingStyle`, `markerChar` or `delimiter` placed on a plain mdast tree
366
373
  * before admission is silently dropped and the canonical default applies.
367
- * Set them on the nodes that come back.
374
+ * Set them on the nodes that come back. The exception is `Text`'s
375
+ * `escapeStyle`, an emitter instruction rather than a source spelling,
376
+ * which the boundary admits.
368
377
  *
369
378
  * @param root - The document tree to serialize.
370
379
  * @returns An `Effect` that succeeds with markdown source, or fails with
package/MarkdownNode.js CHANGED
@@ -145,11 +145,60 @@ const TableAlign = Schema.Literals([
145
145
  * Text — a run of literal characters, with entity references and backslash
146
146
  * escapes already resolved into `value`.
147
147
  *
148
+ * @remarks
149
+ * `escapeStyle` is an opt-in emitter extra, the one fidelity field the
150
+ * parser never sets: absent (or `"canonical"`), `Markdown.stringify` escapes
151
+ * the value canonically so it re-parses to the same text. `"literal"` is the
152
+ * caller vouching that `value` is already safe markdown source, so the
153
+ * emitter writes it verbatim and applies no escaping aimed at inline syntax:
154
+ * `~0.2.1`, `^1.0.0`, `some_pkg` and `@scope/pkg` come out as written, as do
155
+ * `*`, `_`, `[`, `]`, `~`, `&`, `\`, the backtick and autolink-shaped
156
+ * text.
157
+ *
158
+ * Escaping that defends the containing BLOCK'S structure still applies,
159
+ * because its absence would corrupt the document around the text rather
160
+ * than add formatting inside it:
161
+ *
162
+ * - in a table cell, every `|` not already backslash-escaped is written
163
+ * `\|` (a bare pipe splits the cell);
164
+ *
165
+ * - in any container, a value-final `\` is doubled when more content
166
+ * follows it (it would otherwise pair with that content's first
167
+ * character, escaping an inline-code backtick or freeing a cell's `\|`);
168
+ *
169
+ * - in a table cell or heading, a newline becomes a space (neither can
170
+ * hold a line break);
171
+ *
172
+ * - in a heading, a trailing `#` run that would read as the ATX closing
173
+ * sequence is escaped;
174
+ *
175
+ * - elsewhere, a newline that would form a blank line (ending the
176
+ * paragraph) becomes `
`, and at every line start leading whitespace
177
+ * becomes a character reference, and a line start that can open a block
178
+ * is escaped: a `-`, `+` or `*` bullet or an ordered-list marker followed
179
+ * by a space, tab or line end; one to six `#` followed by the same; `>`;
180
+ * three or more backticks or tildes; a thematic-break or setext run
181
+ * (`***`, `___`, `==`); a `---` or `+++` frontmatter fence; a line of
182
+ * only `-`, `:`, `|` and whitespace; and, conservatively, any `<`, `[`,
183
+ * `|` or `:`. Text that cannot open a block there, such as `~0.2.1`,
184
+ * `1.0.0`, `-x` or `#x`, is written as is;
185
+ *
186
+ * - in a tree carrying MDX nodes, `{` and `<` stay escaped, since MDX reads
187
+ * a stray one as a syntax error rather than as text.
188
+ *
189
+ * **A literal text whose value parses as markdown does not round-trip to the
190
+ * same value**: `*a*` emits verbatim and re-parses as emphasis. Keeping the
191
+ * value free of inline syntax is the caller's promise, not something the
192
+ * emitter checks. `Mdast.fromMdast` admits this field on a `text` node (it is
193
+ * the one emitter instruction a plain tree can carry in), and `Mdast.toMdast`
194
+ * projects it back out when present.
195
+ *
148
196
  * @public
149
197
  */
150
198
  var Text = class extends Schema.Class("Text")({
151
199
  type: Schema.tag("text"),
152
200
  value: Schema.String,
201
+ escapeStyle: Schema.optionalKey(Schema.Literals(["canonical", "literal"])),
153
202
  position: NodePosition
154
203
  }) {};
155
204
  /**
package/Mdast.js CHANGED
@@ -110,7 +110,12 @@ const projectNode = (node) => {
110
110
  children: projectChildren(node.children),
111
111
  position
112
112
  };
113
- case "text":
113
+ case "text": return {
114
+ type: "text",
115
+ value: node.value,
116
+ ...node.escapeStyle === void 0 ? {} : { escapeStyle: node.escapeStyle },
117
+ position
118
+ };
114
119
  case "html":
115
120
  case "inlineCode": return {
116
121
  type: node.type,
@@ -263,7 +268,7 @@ const admittedFields = {
263
268
  tableCell: [],
264
269
  break: [],
265
270
  thematicBreak: [],
266
- text: ["value"],
271
+ text: ["value", "escapeStyle"],
267
272
  html: ["value"],
268
273
  inlineCode: ["value"],
269
274
  heading: ["depth"],
@@ -434,6 +439,12 @@ var Mdast = class Mdast {
434
439
  * admits spec mdast and nothing else) but it is silent, which is why it is
435
440
  * called out here.
436
441
  *
442
+ * **One exception: `escapeStyle` on a `text` node is admitted.** It
443
+ * records no source spelling; it is the caller's instruction to the
444
+ * emitter (`"literal"` writes the value verbatim, see {@link Text}), so a
445
+ * plain tree built for `Markdown.stringify` can carry it straight in. A
446
+ * value outside `"canonical" | "literal"` fails the decode typed.
447
+ *
437
448
  * @param input - A plain mdast tree, typically a `root`.
438
449
  * @returns A `Result` succeeding with the decoded {@link Root}, or
439
450
  * failing with {@link MdastDecodeError} carrying the structured issue.
package/README.md CHANGED
@@ -248,7 +248,7 @@ Effect.runPromise(program).then(console.log);
248
248
 
249
249
  ## mdast interop
250
250
 
251
- The node classes already use mdast's type names and field shapes; `Mdast.toMdast` strips the fidelity fields this package adds — bullet characters, fence style, ATX-versus-setext spelling — and emits plain spec-valid mdast JSON that the remark ecosystem consumes directly, including `mdast-util-to-hast` if you want hast:
251
+ The node classes already use mdast's type names and field shapes; `Mdast.toMdast` strips the fidelity fields this package adds — bullet characters, fence style, ATX-versus-setext spelling — (keeping only a `Text` node's `escapeStyle` when set, see [Literal text](#literal-text)) and emits plain spec-valid mdast JSON that the remark ecosystem consumes directly, including `mdast-util-to-hast` if you want hast:
252
252
 
253
253
  ```ts
254
254
  import { Markdown, Mdast } from "@effected/markdown";
@@ -317,10 +317,41 @@ MDX nodes are part of the same commitment: the serialization choices listed unde
317
317
 
318
318
  The posture that makes this a non-issue — and the one to adopt if you build trees and assert on their bytes — is to set `fenceChar` on every `Code` node carrying neither a `lang` nor one already, in a post-decode walk that reaches nested nodes. The choice then leaves the emitter entirely and no output depends on a node's neighbours.
319
319
 
320
- Fidelity fields must be set on the **decoded** tree: `Mdast.fromMdast` admits spec mdast and strips everything outside it, so a `fenceChar` placed on a plain mdast tree before admission is silently dropped.
320
+ Fidelity fields must be set on the **decoded** tree: `Mdast.fromMdast` admits spec mdast and strips everything outside it, so a `fenceChar` placed on a plain mdast tree before admission is silently dropped. The one exception is `escapeStyle` on a `text` node, described next.
321
321
 
322
322
  A node that carries a fidelity field overrides the matching row — `headingStyle`, `markerChar`, `fenceChar`, `delimiter` — which is how a parsed document re-serializes in its author's spelling. The table describes a synthesized node, which is what a test asserting on generated markdown actually holds.
323
323
 
324
+ ### Literal text
325
+
326
+ Text escaping is canonical by default: `~0.2.1` in a table cell emits as `\~0.2.1`. A `Text` node carrying `escapeStyle: "literal"` opts out — the caller vouches that the value is already safe markdown, and the emitter writes it verbatim with none of the escaping aimed at inline syntax. That suits generated content such as a dependency table, where `~0.2.1`, `^1.0.0` and `@scope/pkg` should read as written:
327
+
328
+ ```ts
329
+ import { Markdown, Mdast } from "@effected/markdown";
330
+ import { Result } from "effect";
331
+
332
+ const cell = (value: string) => ({ type: "tableCell", children: [{ type: "text", value, escapeStyle: "literal" }] });
333
+ const tree = Mdast.fromMdastResult({
334
+ type: "root",
335
+ children: [{ type: "table", children: [{ type: "tableRow", children: [cell("Range"), cell("~0.2.1 | ^1.0.0")] }] }],
336
+ });
337
+ if (Result.isSuccess(tree)) {
338
+ console.log(Result.getOrThrow(Markdown.stringifyResult(tree.success)));
339
+ // | Range | ~0.2.1 \| ^1.0.0 |
340
+ // | --- | --- |
341
+ }
342
+ ```
343
+
344
+ Escapes that protect the surrounding **block** still apply, because dropping them would corrupt the document rather than add formatting:
345
+
346
+ - in a table cell, every `|` not already backslash-escaped becomes `\|`;
347
+ - in any container, a value-final `\` is doubled when more content follows it, so it cannot escape that content's first character;
348
+ - in a table cell or heading, a newline becomes a space;
349
+ - in a heading, a trailing `#` run that would read as the closing sequence is escaped;
350
+ - elsewhere, a newline that would form a blank line becomes `&#10;`, leading whitespace at a line start becomes a character reference, and a line start that can open a block is escaped — a `-`, `+` or `*` bullet or an ordered-list marker followed by a space, tab or line end, one to six `#` followed by the same, `>`, a run of three backticks or tildes, a thematic-break or setext run, a `---` or `+++` frontmatter fence, a line of only `-`, `:`, `|` and whitespace, and conservatively any `<`, `[`, `|` or `:` — while text that cannot, such as `~0.2.1`, `1.0.0`, `-x` or `#x`, is written as is;
351
+ - in a tree carrying MDX nodes, `{` and `<` stay escaped.
352
+
353
+ **A literal value that parses as markdown does not round-trip**: `*a*` emits verbatim and re-parses as emphasis. Keeping the value free of inline syntax is the caller's promise; the emitter does not check it. The parser never sets `escapeStyle`, and `Mdast.fromMdast` admits it on a `text` node — the one fidelity field that crosses that boundary, because it is an instruction to the emitter rather than a record of source spelling — so a plain mdast tree can carry it straight in. The opt-out is additive: a tree that never sets it serializes byte-identically to the canonical form above.
354
+
324
355
  To *normalize* an existing document to different choices, use `MarkdownFormat` with `MarkdownFormattingOptions`. That is the configurable surface; this one deliberately is not.
325
356
 
326
357
  ## Features
package/index.d.ts CHANGED
@@ -170,12 +170,61 @@ export type TableAlign = typeof TableAlign.Type;
170
170
  declare const Text_base: Schema.Class<Text, Schema.Struct<{
171
171
  readonly type: Schema.tag<"text">;
172
172
  readonly value: Schema.String;
173
+ readonly escapeStyle: Schema.optionalKey<Schema.Literals<readonly ["canonical", "literal"]>>;
173
174
  readonly position: Schema.withConstructorDefault<typeof Position>;
174
175
  }>, {}>;
175
176
  /**
176
177
  * Text — a run of literal characters, with entity references and backslash
177
178
  * escapes already resolved into `value`.
178
179
  *
180
+ * @remarks
181
+ * `escapeStyle` is an opt-in emitter extra, the one fidelity field the
182
+ * parser never sets: absent (or `"canonical"`), `Markdown.stringify` escapes
183
+ * the value canonically so it re-parses to the same text. `"literal"` is the
184
+ * caller vouching that `value` is already safe markdown source, so the
185
+ * emitter writes it verbatim and applies no escaping aimed at inline syntax:
186
+ * `~0.2.1`, `^1.0.0`, `some_pkg` and `@scope/pkg` come out as written, as do
187
+ * `*`, `_`, `[`, `]`, `~`, `&`, `\`, the backtick and autolink-shaped
188
+ * text.
189
+ *
190
+ * Escaping that defends the containing BLOCK'S structure still applies,
191
+ * because its absence would corrupt the document around the text rather
192
+ * than add formatting inside it:
193
+ *
194
+ * - in a table cell, every `|` not already backslash-escaped is written
195
+ * `\|` (a bare pipe splits the cell);
196
+ *
197
+ * - in any container, a value-final `\` is doubled when more content
198
+ * follows it (it would otherwise pair with that content's first
199
+ * character, escaping an inline-code backtick or freeing a cell's `\|`);
200
+ *
201
+ * - in a table cell or heading, a newline becomes a space (neither can
202
+ * hold a line break);
203
+ *
204
+ * - in a heading, a trailing `#` run that would read as the ATX closing
205
+ * sequence is escaped;
206
+ *
207
+ * - elsewhere, a newline that would form a blank line (ending the
208
+ * paragraph) becomes `&#10;`, and at every line start leading whitespace
209
+ * becomes a character reference, and a line start that can open a block
210
+ * is escaped: a `-`, `+` or `*` bullet or an ordered-list marker followed
211
+ * by a space, tab or line end; one to six `#` followed by the same; `>`;
212
+ * three or more backticks or tildes; a thematic-break or setext run
213
+ * (`***`, `___`, `==`); a `---` or `+++` frontmatter fence; a line of
214
+ * only `-`, `:`, `|` and whitespace; and, conservatively, any `<`, `[`,
215
+ * `|` or `:`. Text that cannot open a block there, such as `~0.2.1`,
216
+ * `1.0.0`, `-x` or `#x`, is written as is;
217
+ *
218
+ * - in a tree carrying MDX nodes, `{` and `<` stay escaped, since MDX reads
219
+ * a stray one as a syntax error rather than as text.
220
+ *
221
+ * **A literal text whose value parses as markdown does not round-trip to the
222
+ * same value**: `*a*` emits verbatim and re-parses as emphasis. Keeping the
223
+ * value free of inline syntax is the caller's promise, not something the
224
+ * emitter checks. `Mdast.fromMdast` admits this field on a `text` node (it is
225
+ * the one emitter instruction a plain tree can carry in), and `Mdast.toMdast`
226
+ * projects it back out when present.
227
+ *
179
228
  * @public
180
229
  */
181
230
  export declare class Text extends Text_base {}
@@ -1191,6 +1240,13 @@ export declare class Markdown {
1191
1240
  * rows describe a synthesized node, which is the case a test asserting on
1192
1241
  * generated markdown actually has.
1193
1242
  *
1243
+ * **Text escaping is canonical unless a `Text` node opts out.** A `Text`
1244
+ * carrying `escapeStyle: "literal"` is emitted verbatim apart from the
1245
+ * escapes that protect block structure (a table cell's `|`, a newline in a
1246
+ * single-line container, line-start block openers); its exact set is
1247
+ * documented on {@link Text}. The opt-out is additive: a tree that never
1248
+ * sets it serializes byte-identically to the canonical form.
1249
+ *
1194
1250
  * **Representability wins over the table.** The canonical form never emits
1195
1251
  * text that would re-parse as something else, so a row yields where the two
1196
1252
  * conflict. The case that reaches a consumer is the indented code block: an
@@ -1246,7 +1302,9 @@ export declare class Markdown {
1246
1302
  * admits spec mdast and strips everything outside it, so a `fenceChar`,
1247
1303
  * `headingStyle`, `markerChar` or `delimiter` placed on a plain mdast tree
1248
1304
  * before admission is silently dropped and the canonical default applies.
1249
- * Set them on the nodes that come back.
1305
+ * Set them on the nodes that come back. The exception is `Text`'s
1306
+ * `escapeStyle`, an emitter instruction rather than a source spelling,
1307
+ * which the boundary admits.
1250
1308
  *
1251
1309
  * @param root - The document tree to serialize.
1252
1310
  * @returns An `Effect` that succeeds with markdown source, or fails with
@@ -2778,6 +2836,12 @@ export declare class Mdast {
2778
2836
  * admits spec mdast and nothing else) but it is silent, which is why it is
2779
2837
  * called out here.
2780
2838
  *
2839
+ * **One exception: `escapeStyle` on a `text` node is admitted.** It
2840
+ * records no source spelling; it is the caller's instruction to the
2841
+ * emitter (`"literal"` writes the value verbatim, see {@link Text}), so a
2842
+ * plain tree built for `Markdown.stringify` can carry it straight in. A
2843
+ * value outside `"canonical" | "literal"` fails the decode typed.
2844
+ *
2781
2845
  * @param input - A plain mdast tree, typically a `root`.
2782
2846
  * @returns A `Result` succeeding with the decoded {@link Root}, or
2783
2847
  * failing with {@link MdastDecodeError} carrying the structured issue.
@@ -184,6 +184,132 @@ const escapeText = (value, context, atLineStart, mdx, followingText, nonTextFoll
184
184
  atLineStart: lineStart
185
185
  };
186
186
  };
187
+ /** Space or tab — the whitespace a list marker or ATX run must be followed by. */
188
+ const isSpaceOrTab = (char) => char === " " || char === " ";
189
+ /** Every character of `line` is `marker` or space/tab — a thematic-break or setext-shaped run. */
190
+ const isRunOf = (line, marker) => line.length > 0 && [...line].every((char) => char === marker || char === " " || char === " ");
191
+ /** Only the characters a GFM delimiter row is made of (re2c's `spacechar` included). */
192
+ const reDelimiterRowShaped = /^[-:| \t\v\f]+$/;
193
+ /**
194
+ * Whether a literal line starting with `line` can open a block, so its first
195
+ * character must be escaped. `line` is the text known to be on the line —
196
+ * the value up to its next newline, then the following text siblings — and
197
+ * `open` says the line may carry more than that (no newline ended it).
198
+ * Beyond the known text sits either the end of the line or content this
199
+ * function cannot see (a non-text sibling, a parent's closing delimiter), so
200
+ * a marker that needs "space, tab or end of line" after it is escaped when
201
+ * it reaches the end of `line`. Every test errs toward escaping: an extra
202
+ * backslash is inert, a missing one changes the document.
203
+ */
204
+ const literalLineOpensBlock = (line, open) => {
205
+ const char = line[0];
206
+ switch (char) {
207
+ case ">":
208
+ case "<":
209
+ case "[":
210
+ case "|":
211
+ case ":": return true;
212
+ case "#": {
213
+ const run = /^#{1,6}/.exec(line)?.[0] ?? "";
214
+ return run.length > 0 && (run.length === line.length || isSpaceOrTab(line[run.length]));
215
+ }
216
+ case "`":
217
+ case "~": return line.startsWith(char.repeat(3)) || open && [...line].every((each) => each === char);
218
+ case "=": return /^=+[ \t]*$/.test(line);
219
+ case "+":
220
+ case "-":
221
+ case "*":
222
+ case "_": {
223
+ const bullet = char !== "_" && (line.length === 1 || isSpaceOrTab(line[1]));
224
+ const thematic = char !== "+" && isRunOf(line, char) && (open || [...line].filter((each) => each === char).length >= 3);
225
+ const frontmatter = (char === "-" || char === "+") && line.startsWith(char.repeat(3));
226
+ const dashLine = char === "-" && reDelimiterRowShaped.test(line);
227
+ return bullet || thematic || frontmatter || dashLine;
228
+ }
229
+ default: return false;
230
+ }
231
+ };
232
+ /** ASCII punctuation: what a backslash escapes, so `\x` is one cell character. */
233
+ const reAsciiPunctuation = /[!-/:-@[-`{-~]/;
234
+ const literalText = (value, context, atLineStart, mdx, followingText, nonTextFollows) => {
235
+ let out = "";
236
+ let lineStart = atLineStart;
237
+ for (let index = 0; index < value.length; index += 1) {
238
+ const char = value[index];
239
+ if (char === "\n") {
240
+ const adjacent = value[index - 1] === "\n" || value[index + 1] === "\n";
241
+ if (context.singleLine) {
242
+ out += " ";
243
+ lineStart = false;
244
+ } else if (adjacent || lineStart) {
245
+ out += "&#10;";
246
+ lineStart = false;
247
+ } else {
248
+ out += "\n";
249
+ lineStart = true;
250
+ }
251
+ continue;
252
+ }
253
+ if (lineStart) {
254
+ lineStart = false;
255
+ if (char === " ") {
256
+ out += "&#9;";
257
+ continue;
258
+ }
259
+ if (char === " ") {
260
+ out += "&#32;";
261
+ continue;
262
+ }
263
+ const lineEnd = value.indexOf("\n", index);
264
+ let line = value.slice(index, lineEnd === -1 ? void 0 : lineEnd);
265
+ let open = false;
266
+ if (lineEnd === -1) {
267
+ const followingEnd = followingText.indexOf("\n");
268
+ line += followingText.slice(0, followingEnd === -1 ? void 0 : followingEnd);
269
+ open = followingEnd === -1;
270
+ }
271
+ const ordered = ORDERED_MARKER.exec(line);
272
+ if (ordered !== null) {
273
+ const after = line[ordered[0].length];
274
+ if (after === void 0 || isSpaceOrTab(after)) {
275
+ out += `${ordered[0].slice(0, -1)}\\${ordered[0].slice(-1)}`;
276
+ index += ordered[0].length - 1;
277
+ continue;
278
+ }
279
+ } else if (literalLineOpensBlock(line, open)) {
280
+ out += `\\${char}`;
281
+ continue;
282
+ }
283
+ }
284
+ if (char === "\\") {
285
+ const next = value[index + 1];
286
+ if (next !== void 0 && reAsciiPunctuation.test(next)) {
287
+ out += `\\${next}`;
288
+ index += 1;
289
+ continue;
290
+ }
291
+ out += next === void 0 && (followingText !== "" || nonTextFollows) ? "\\\\" : "\\";
292
+ continue;
293
+ }
294
+ if (char === "|" && context.inTable) {
295
+ out += "\\|";
296
+ continue;
297
+ }
298
+ if (mdx && (char === "{" || char === "<")) {
299
+ out += `\\${char}`;
300
+ continue;
301
+ }
302
+ if (context.inHeading && char === "#" && isClosingHashRun(value, index, followingText, nonTextFollows)) {
303
+ out += "\\#";
304
+ continue;
305
+ }
306
+ out += char;
307
+ }
308
+ return {
309
+ text: out,
310
+ atLineStart: lineStart
311
+ };
312
+ };
187
313
  /** Pick the effective emphasis marker, flipping away from a forbidden char. */
188
314
  const emphasisMarker = (fidelity, forbidden) => {
189
315
  const chosen = fidelity ?? "*";
@@ -260,7 +386,7 @@ const serializeInlines = (children, context, state, startsLine, junctionGuard) =
260
386
  followingText += children[next].value;
261
387
  next += 1;
262
388
  }
263
- const escaped = escapeText(child.value, context, atLineStart, state.mdx, followingText, next < children.length);
389
+ const escaped = (child.escapeStyle === "literal" ? literalText : escapeText)(child.value, context, atLineStart, state.mdx, followingText, next < children.length);
264
390
  out += escaped.text;
265
391
  atLineStart = escaped.atLineStart;
266
392
  break;
@@ -509,13 +635,30 @@ const alignCell = (align) => {
509
635
  default: return "---";
510
636
  }
511
637
  };
638
+ /**
639
+ * Escape every cell pipe the GFM cell splitter would read as a column
640
+ * boundary. The splitter consumes a backslash and the punctuation after it as
641
+ * one character, so `\\` is an escaped backslash: a pipe is escaped already
642
+ * iff the run of backslashes directly before it has odd length, and needs a
643
+ * `\` iff that run is even, zero included.
644
+ */
645
+ const escapeCellPipes = (content) => {
646
+ let out = "";
647
+ let backslashes = 0;
648
+ for (const char of content) {
649
+ if (char === "|" && backslashes % 2 === 0) out += "\\";
650
+ backslashes = char === "\\" ? backslashes + 1 : 0;
651
+ out += char;
652
+ }
653
+ return out;
654
+ };
512
655
  const serializeTable = (table, state) => {
513
656
  const columnCount = Math.max(1, ...table.children.map((row) => row.children.length));
514
657
  const rows = table.children.map((row) => {
515
658
  guard(state, row);
516
659
  const cells = row.children.map((cell) => {
517
660
  guard(state, cell);
518
- const content = serializeInlines(cell.children, CELL_CONTEXT, state, false).replace(/(?<!\\)\|/g, "\\|");
661
+ const content = escapeCellPipes(serializeInlines(cell.children, CELL_CONTEXT, state, false));
519
662
  unguard(state);
520
663
  return content;
521
664
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effected/markdown",
3
- "version": "0.12.1",
3
+ "version": "0.14.0",
4
4
  "private": false,
5
5
  "description": "CommonMark and GFM markdown parse, edit and transform schemas for Effect",
6
6
  "keywords": [
@@ -39,10 +39,10 @@
39
39
  "./package.json": "./package.json"
40
40
  },
41
41
  "peerDependencies": {
42
- "@effected/jsonc": "^0.13.0",
43
- "@effected/toml": "^0.9.0",
44
- "@effected/yaml": "^0.17.0",
45
- "effect": "4.0.0-rc.117"
42
+ "@effected/jsonc": "^0.14.0",
43
+ "@effected/toml": "^0.10.0",
44
+ "@effected/yaml": "^0.18.0",
45
+ "effect": "4.0.0-rc.118"
46
46
  },
47
47
  "peerDependenciesMeta": {
48
48
  "@effected/jsonc": {