@portabletext/markdown 1.4.6 → 1.5.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/README.md CHANGED
@@ -72,14 +72,14 @@ const markdown = portableTextToMarkdown([
72
72
  | Unordered lists | ✅ | ✅ |
73
73
  | Task lists | ✅\* | ✅\* |
74
74
  | Nested lists | ✅ | ✅ |
75
- | Code blocks | ✅ | ✅\* |
76
- | Horizontal rules | ✅ | ✅\* |
77
- | Images | ✅ | ✅\* |
78
- | Tables | ✅\* | ✅\* |
79
- | HTML blocks | ✅ | ✅\* |
80
- | Callouts | ✅\* | ✅\* |
75
+ | Code blocks | ✅ | |
76
+ | Horizontal rules | ✅ | |
77
+ | Images | ✅ | |
78
+ | Tables | | |
79
+ | HTML blocks | ✅ | |
80
+ | Callouts | | |
81
81
 
82
- \* Requires custom configuration (see usage below)
82
+ \* Requires a schema that declares a `task` list; the default schema does not (see [GFM task lists](#configuring-matchers) below)
83
83
 
84
84
  ## Usage
85
85
 
@@ -162,14 +162,14 @@ Out of the box, the library includes sensible defaults for both. Customize them
162
162
 
163
163
  The default schema includes the following definitions:
164
164
 
165
- | Type | Values |
166
- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
167
- | `styles` | `'normal'`, `'h1'`, `'h2'`, `'h3'`, `'h4'`, `'h5'`, `'h6'`, `'blockquote'` |
168
- | `lists` | `'number'`, `'bullet'` |
169
- | `decorators` | `'strong'`, `'em'`, `'code'`, `'strike-through'` |
170
- | `annotations` | `'link'` (fields: `'href'`, `'title'`) |
171
- | `blockObjects` | `'code'` (fields: `'language'`, `'code'`), `'image'` (fields: `'src'`, `'alt'`, `'title'`), `'horizontal-rule'`, `'html'` (fields: `'html'`), `'table'` (fields: `'headerRows'`, `'rows'`), `'callout'` (fields: `'tone'`, `'content'`) |
172
- | `inlineObjects` | `'image'` (fields: `'src'`, `'alt'`, `'title'`) |
165
+ | Type | Values |
166
+ | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
167
+ | `styles` | `'normal'`, `'h1'`, `'h2'`, `'h3'`, `'h4'`, `'h5'`, `'h6'`, `'blockquote'` |
168
+ | `lists` | `'number'`, `'bullet'` |
169
+ | `decorators` | `'strong'`, `'em'`, `'code'`, `'strike-through'` |
170
+ | `annotations` | `'link'` (fields: `'href'`, `'title'`) |
171
+ | `blockObjects` | `'code'` (fields: `'language'`, `'code'`), `'image'` (fields: `'src'`, `'alt'`, `'title'`), `'horizontal-rule'`, `'html'` (fields: `'html'`), `'table'` (the canonical nested shape, see [Default behavior](#default-behavior)), `'callout'` (fields: `'tone'`, `'content'`) |
172
+ | `inlineObjects` | `'image'` (fields: `'src'`, `'alt'`, `'title'`) |
173
173
 
174
174
  To use a custom Schema, import `compileSchema` and `defineSchema` from `@portabletext/schema`:
175
175
 
@@ -218,8 +218,38 @@ Matchers map Markdown concepts to Portable Text types defined in the Schema. Eac
218
218
  | | `image` | `![alt](src)` | `'image'` |
219
219
  | | `html` | HTML blocks | `'html'` |
220
220
  | | `callout` | `> [!NOTE]`, etc. | `'callout'` |
221
- | | `blockquote` | `>` blockquotes | `'blockquote'` |
222
- | | `list` | `- ` or `1. ` lists | `'list'` |
221
+ | | `table` | GFM pipe tables | `'table'` |
222
+ | | `blockquote`\* | `>` blockquotes | `'blockquote'` |
223
+ | | `list`\* | `- ` or `1. ` lists | `'list'` |
224
+
225
+ \* Opt-in, not registered by default: `blockquote` and `list` map onto structural container shapes, and the parser only produces those shapes when you register the matcher (see [Configuring matchers](#configuring-matchers)). Without them, blockquotes and lists parse to flat text blocks, which is the standard Portable Text shape for both, not a degraded fallback.
226
+
227
+ #### Default behavior
228
+
229
+ **Tables** (GFM pipe tables) convert by default, in the canonical shape `@portabletext/plugin-table` expects: a `table` block object (`headerRows`, `rows`), each row a `row` object (`cells`), each cell a `cell` object (`value`, an array of Portable Text blocks; a cell holding a single image becomes a standalone block-level `image` object instead of a text block wrapping it). `alignment` is a `@portabletext/markdown` extension field; `@portabletext/plugin-table` ignores it. This needs no configuration when the schema declares a `table` block object with a `rows` field (see `blockObjects` above). A schema whose `table` doesn't declare `rows`, or that doesn't declare `table` at all, produces no table object at all: the table's cell content flattens into top-level blocks, in reading order, and the table structure is discarded.
230
+
231
+ **Images** are handled based on context:
232
+
233
+ - Standalone images (a paragraph containing only an image) become block-level `'image'` objects
234
+ - Images mixed with text become inline `'image'` objects (if the schema includes `'image'` in `inlineObjects`)
235
+ - If neither is supported, falls back to plain text: `![alt](src)`
236
+
237
+ The default image matcher requires the schema type to have a `'src'` field. If your `'image'` type doesn't include this field, the matcher returns `undefined`.
238
+
239
+ **Code** is handled based on the Markdown syntax:
240
+
241
+ - Fenced code blocks (` ``` `) become `'code'` block objects with `language` and `code` fields
242
+ - Inline code (`` ` ``) applies the `'code'` decorator to a span
243
+
244
+ The default code block matcher requires the schema type to have a `'code'` field. If your `'code'` type doesn't include this field, the matcher returns `undefined`.
245
+
246
+ **Links** support optional titles using `[text](url "title")` syntax. The title is captured in the `'title'` field of the `'link'` annotation.
247
+
248
+ **Nested lists** are handled automatically. Each list item block includes a `level` property indicating its nesting depth (1 for top-level, 2 for nested, etc.).
249
+
250
+ **HTML blocks** (like `<div>...</div>`) become `'html'` block objects with the raw HTML in the `'html'` field. Inline HTML is controlled by the `html.inline` option.
251
+
252
+ **Callouts** use the `> [!TYPE]` syntax (GFM alerts) where `TYPE` is one of `NOTE`, `TIP`, `WARNING`, `CAUTION`, or `IMPORTANT`. They become `'callout'` block objects with a `'tone'` field (the lowercased type name) and a `'content'` field (an array of Portable Text blocks). When the schema doesn't include a `'callout'` block object, the content falls back to blockquote-styled blocks.
223
253
 
224
254
  #### Configuring matchers
225
255
 
@@ -246,7 +276,7 @@ markdownToPortableText(markdown, {
246
276
 
247
277
  > **Note:** Checking if the type exists in the schema isn't required, but it's good practice. Returning `undefined` gracefully skips unsupported types.
248
278
 
249
- **Table matcher:** Markdown tables are parsed but there's no default matcher. Provide one if your schema includes tables:
279
+ **Table matcher:** GFM pipe tables convert by default (see [Default behavior](#default-behavior)). Provide your own matcher to map onto a differently-shaped `table` type:
250
280
 
251
281
  ```ts
252
282
  markdownToPortableText(markdown, {
@@ -348,31 +378,6 @@ Matchers receive:
348
378
 
349
379
  Return `undefined` to skip the element (e.g., if the type isn't in the schema).
350
380
 
351
- #### Default behavior for images and code
352
-
353
- **Images** are handled based on context:
354
-
355
- - Standalone images (a paragraph containing only an image) become block-level `'image'` objects
356
- - Images mixed with text become inline `'image'` objects (if the schema includes `'image'` in `inlineObjects`)
357
- - If neither is supported, falls back to plain text: `![alt](src)`
358
-
359
- The default image matcher requires the schema type to have a `'src'` field. If your `'image'` type doesn't include this field, the matcher returns `undefined`.
360
-
361
- **Code** is handled based on the Markdown syntax:
362
-
363
- - Fenced code blocks (` ``` `) become `'code'` block objects with `language` and `code` fields
364
- - Inline code (`` ` ``) applies the `'code'` decorator to a span
365
-
366
- The default code block matcher requires the schema type to have a `'code'` field. If your `'code'` type doesn't include this field, the matcher returns `undefined`.
367
-
368
- **Links** support optional titles using `[text](url "title")` syntax. The title is captured in the `'title'` field of the `'link'` annotation.
369
-
370
- **Nested lists** are handled automatically. Each list item block includes a `level` property indicating its nesting depth (1 for top-level, 2 for nested, etc.).
371
-
372
- **HTML blocks** (like `<div>...</div>`) become `'html'` block objects with the raw HTML in the `'html'` field. Inline HTML is controlled by the `html.inline` option.
373
-
374
- **Callouts** use the `> [!TYPE]` syntax (GFM alerts) where `TYPE` is one of `NOTE`, `TIP`, `WARNING`, `CAUTION`, or `IMPORTANT`. They become `'callout'` block objects with a `'tone'` field (the lowercased type name) and a `'content'` field (an array of Portable Text blocks). When the schema doesn't include a `'callout'` block object, the content falls back to blockquote-styled blocks.
375
-
376
381
  #### Other options
377
382
 
378
383
  ```ts
@@ -449,34 +454,42 @@ The conversion is driven by **Renderers**: functions that render Portable Text e
449
454
 
450
455
  #### Default renderers
451
456
 
452
- | Group | Renderer | Renders | Output |
453
- | ------------------- | ---------------- | --------------------------------- | ------------------------ |
454
- | `block` | `normal` | Paragraphs | `{children}` |
455
- | | `h1`–`h6` | Headings | `# `–`###### ` |
456
- | | `blockquote` | Blockquotes | `> {children}` |
457
- | `marks` | `strong` | Bold text | `**{children}**` |
458
- | | `em` | Italic text | `_{children}_` |
459
- | | `code` | Inline code | `` `{children}` `` |
460
- | | `underline` | Underlined text | `<u>{children}</u>` |
461
- | | `strike-through` | Strikethrough | `~~{children}~~` |
462
- | | `link` | Links | `[{children}](url)` |
463
- | `listItem` | | List items (bullet, number, task) | `- `, `1. `, or `- [x] ` |
464
- | `hardBreak` | | Line breaks within blocks | ` \n` (two spaces) |
465
- | `blockSpacing` | | Spacing between blocks | `\n\n`, `\n`, `\n>\n` |
466
- | `unknownType` | | Unknown block types | JSON code block |
467
- | `unknownBlockStyle` | | Unknown block styles | `{children}` |
468
- | `unknownListItem` | | Unknown list item types | `- {children}` |
469
- | `unknownMark` | | Unknown marks | `{children}` |
457
+ | Group | Renderer | Renders | Output |
458
+ | ------------------- | ----------------- | --------------------------------- | ------------------------ |
459
+ | `types` | `callout` | `callout` block objects | `> [!TYPE]\n> content` |
460
+ | | `code` | `code` block objects | Fenced code block |
461
+ | | `horizontal-rule` | `horizontal-rule` block objects | `---` |
462
+ | | `html` | `html` block objects | Raw HTML |
463
+ | | `image` | `image` block/inline objects | `![alt](src "title")` |
464
+ | | `table` | `table` block objects | Markdown table |
465
+ | `block` | `normal` | Paragraphs | `{children}` |
466
+ | | `h1`–`h6` | Headings | `# `–`###### ` |
467
+ | | `blockquote` | Blockquotes | `> {children}` |
468
+ | `marks` | `strong` | Bold text | `**{children}**` |
469
+ | | `em` | Italic text | `_{children}_` |
470
+ | | `code` | Inline code | `` `{children}` `` |
471
+ | | `underline` | Underlined text | `<u>{children}</u>` |
472
+ | | `strike-through` | Strikethrough | `~~{children}~~` |
473
+ | | `link` | Links | `[{children}](url)` |
474
+ | `listItem` | | List items (bullet, number, task) | `- `, `1. `, or `- [x] ` |
475
+ | `hardBreak` | | Line breaks within blocks | ` \n` (two spaces) |
476
+ | `blockSpacing` | | Spacing between blocks | `\n\n`, `\n`, `\n>\n` |
477
+ | `unknownType` | | Unknown block types | JSON code block |
478
+ | `unknownBlockStyle` | | Unknown block styles | `{children}` |
479
+ | `unknownListItem` | | Unknown list item types | `- {children}` |
480
+ | `unknownMark` | | Unknown marks | `{children}` |
470
481
 
471
482
  Unknown types render as JSON code blocks by default; unknown styles, list items, and marks pass through their children.
472
483
 
484
+ The default type renderers are collision-safe: because the serializer dispatches on the `_type` name alone, `code`, `html`, `image`, `callout`, and `table` fall back to the `unknownType` renderer (a JSON code block) when a value doesn't match the shape their renderer expects (say, your own differently-shaped `code` type); `horizontal-rule` has no shape to check and always renders `---`. Register your own `types.<name>` renderer to override how any of them serialize, or to handle a same-named type of a different shape.
485
+
473
486
  > **Note:** The `underline` renderer is included for Portable Text that uses it, but there's no standard Markdown syntax for underline, so it renders as HTML.
474
487
 
475
488
  #### Configuring renderers
476
489
 
477
490
  Provide custom renderers to control how Portable Text renders to Markdown.
478
491
 
479
- **Custom type renderers:** Render custom block types (objects in the blocks array):
492
+ **Custom type renderers:** Render custom block types (objects in the blocks array). A custom renderer under a default's name (see [Default renderers](#default-renderers)) replaces that default:
480
493
 
481
494
  ```ts
482
495
  portableTextToMarkdown(blocks, {
@@ -500,31 +513,19 @@ portableTextToMarkdown(blocks, {
500
513
  })
501
514
  ```
502
515
 
503
- **Built-in type renderers:** The library exports default renderers for common block types:
516
+ **Built-in type renderers:** the library exports every built-in type renderer, in case you want to compose one into a different renderer or reuse it under a different type name. Two of them are exported but not registered by default: `DefaultBlockquoteObjectRenderer` and `DefaultListRenderer` render the structural container shapes (`types.blockquote`/`types.list` on the parser side) and stay opt-in, since the parser only produces those shapes when a matcher is registered for them:
504
517
 
505
518
  ```ts
506
519
  import {
507
520
  DefaultBlockquoteObjectRenderer,
508
- DefaultCalloutRenderer,
509
- DefaultCodeBlockRenderer,
510
- DefaultHorizontalRuleRenderer,
511
- DefaultHtmlRenderer,
512
- DefaultImageRenderer,
513
521
  DefaultListRenderer,
514
- DefaultTableRenderer,
515
522
  portableTextToMarkdown,
516
523
  } from '@portabletext/markdown'
517
524
 
518
525
  portableTextToMarkdown(blocks, {
519
526
  types: {
520
- 'blockquote': DefaultBlockquoteObjectRenderer,
521
- 'callout': DefaultCalloutRenderer,
522
- 'code': DefaultCodeBlockRenderer,
523
- 'horizontal-rule': DefaultHorizontalRuleRenderer,
524
- 'html': DefaultHtmlRenderer,
525
- 'image': DefaultImageRenderer,
526
- 'list': DefaultListRenderer,
527
- 'table': DefaultTableRenderer,
527
+ blockquote: DefaultBlockquoteObjectRenderer,
528
+ list: DefaultListRenderer,
528
529
  },
529
530
  })
530
531
  ```
package/dist/index.d.ts CHANGED
@@ -268,6 +268,14 @@ interface PortableTextRendererOptions<T> {
268
268
  * Index of a list item
269
269
  */
270
270
  listIndex?: number | undefined;
271
+ /**
272
+ * How deeply the list item should be indented, starting at 0.
273
+ *
274
+ * This is not the same as the block's `level`. A list can start deeper than
275
+ * level 1, and can skip levels, neither of which Markdown can express, so each
276
+ * jump to a deeper level counts as a single step of nesting.
277
+ */
278
+ listDepth?: number | undefined;
271
279
  /**
272
280
  * Whether or not this node is "inline" - ie as a child of a text block,
273
281
  * alongside text spans, or a block in and of itself.
@@ -446,13 +454,13 @@ declare const DefaultImageRenderer: PortableTextTypeRenderer<{
446
454
  /**
447
455
  * Renders a Portable Text table block-object back to Markdown.
448
456
  *
449
- * The PT `headerRows` field decides the header. `headerRows === 0` is an
450
- * explicit headerless table: GFM has no headerless form, so an empty header
451
- * row is emitted and every row goes in the body (that empty header reads back
452
- * as `headerRows: 0` via `markdownToPortableText`). Any other value
453
- * (`undefined` or `>= 1`) promotes `rows[0]` to the header. GFM allows exactly
454
- * one header row, so header rows beyond the first flatten into the body,
455
- * lossy, but the extra rows stay on the Portable Text side.
457
+ * The PT `headerRows` field decides the header. Missing `headerRows` and
458
+ * `headerRows === 0` both render headerless: GFM has no headerless form, so
459
+ * an empty header row is emitted and every row goes in the body (that empty
460
+ * header reads back as `headerRows: 0` via `markdownToPortableText`).
461
+ * `headerRows >= 1` promotes `rows[0]` to the header. GFM allows exactly one
462
+ * header row, so header rows beyond the first flatten into the body, lossy,
463
+ * but the extra rows stay on the Portable Text side.
456
464
  *
457
465
  * Asymmetric tables (rows of varying cell counts) are widened to match
458
466
  * the row with the most cells. Narrower rows are padded with empty cells
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../../node_modules/.pnpm/@portabletext+types@4.0.2/node_modules/@portabletext/types/dist/index.d.ts","../src/from-portable-text/renderers/block-spacing.ts","../src/from-portable-text/types.ts","../src/from-portable-text/portable-text-to-markdown.ts","../src/from-portable-text/renderers/hard-break.ts","../src/from-portable-text/renderers/list-item.ts","../src/from-portable-text/renderers/style.ts","../src/from-portable-text/renderers/marks.ts","../src/from-portable-text/renderers/type.ts","../src/to-portable-text/matchers.ts","../src/to-portable-text/markdown-to-portable-text.ts"],"x_google_ignoreList":[0],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../../node_modules/.pnpm/@portabletext+types@4.0.2/node_modules/@portabletext/types/dist/index.d.ts","../src/from-portable-text/renderers/block-spacing.ts","../src/from-portable-text/types.ts","../src/from-portable-text/portable-text-to-markdown.ts","../src/from-portable-text/renderers/hard-break.ts","../src/from-portable-text/renderers/list-item.ts","../src/from-portable-text/renderers/style.ts","../src/from-portable-text/renderers/marks.ts","../src/from-portable-text/renderers/type.ts","../src/to-portable-text/matchers.ts","../src/to-portable-text/markdown-to-portable-text.ts"],"x_google_ignoreList":[0],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { compileSchema, defineSchema, isSpan, isTextBlock } from "@portabletext/schema";
1
+ import { compileSchema, defineSchema, isSpan, isTextBlock, isTypedObject } from "@portabletext/schema";
2
2
  import { buildMarksTree, isPortableTextBlock, isPortableTextListItemBlock, isPortableTextToolkitSpan, isPortableTextToolkitTextNode, spanToPlainText } from "@portabletext/toolkit";
3
3
  import { alert } from "@mdit/plugin-alert";
4
4
  import markdownit from "markdown-it";
@@ -24,57 +24,74 @@ function randomKey(length) {
24
24
  }
25
25
  const schema = compileSchema(defineSchema({}));
26
26
  /**
27
- * Builds a map of list item `_key`s to their index.
27
+ * Builds a map of list item `_key`s to their index, and a map of list item
28
+ * `_key`s to the depth they should be rendered at.
29
+ *
30
+ * The depth is not the same as the block's `level`. A list can start at a level
31
+ * deeper than 1, and can skip levels, but Markdown has no way to express either:
32
+ * indentation is relative to the list item above, and indenting a first item by
33
+ * four spaces or more makes it a code block rather than a list. So each jump to a
34
+ * deeper level counts as a single step of nesting, however many levels it spans.
28
35
  *
29
36
  * Mutates the blocks in place by adding a `_key` if necessary.
30
37
  */
31
38
  function buildListIndexMap(blocks) {
32
- let levelIndexMaps = /* @__PURE__ */ new Map(), listIndexMap = /* @__PURE__ */ new Map(), previousListItem;
39
+ let levelIndexMaps = /* @__PURE__ */ new Map(), listIndexMap = /* @__PURE__ */ new Map(), listDepthMap = /* @__PURE__ */ new Map(), levelStack = [];
40
+ function depthOf(level) {
41
+ let deepest = levelStack.at(-1);
42
+ for (; deepest !== void 0 && deepest > level;) levelStack.pop(), deepest = levelStack.at(-1);
43
+ return deepest !== level && levelStack.push(level), levelStack.length - 1;
44
+ }
45
+ let previousListItem;
33
46
  for (let blockIndex = 0; blockIndex < blocks.length; blockIndex++) {
34
47
  let block = blocks.at(blockIndex);
35
48
  if (block === void 0) continue;
36
49
  if (block._key ||= defaultKeyGenerator(), !isTextBlock({ schema }, block)) {
37
- levelIndexMaps.clear(), previousListItem = void 0;
50
+ levelIndexMaps.clear(), previousListItem = void 0, levelStack = [];
38
51
  continue;
39
52
  }
40
53
  if (block.listItem === void 0 || block.level === void 0) {
41
- levelIndexMaps.clear(), previousListItem = void 0;
54
+ levelIndexMaps.clear(), previousListItem = void 0, levelStack = [];
42
55
  continue;
43
56
  }
44
- if (!previousListItem) {
57
+ let depth = depthOf(block.level);
58
+ if (listDepthMap.set(block._key, depth), !previousListItem) {
45
59
  let levelIndexMap = levelIndexMaps.get(block.listItem) ?? /* @__PURE__ */ new Map();
46
- levelIndexMap.set(block.level, 1), levelIndexMaps.set(block.listItem, levelIndexMap), listIndexMap.set(block._key, 1), previousListItem = {
60
+ levelIndexMap.set(depth, 1), levelIndexMaps.set(block.listItem, levelIndexMap), listIndexMap.set(block._key, 1), previousListItem = {
47
61
  listItem: block.listItem,
48
- level: block.level
62
+ depth
49
63
  };
50
64
  continue;
51
65
  }
52
- if (previousListItem.listItem === block.listItem && previousListItem.level < block.level) {
66
+ if (previousListItem.listItem === block.listItem && previousListItem.depth < depth) {
53
67
  let levelIndexMap = levelIndexMaps.get(block.listItem) ?? /* @__PURE__ */ new Map();
54
- levelIndexMap.set(block.level, 1), levelIndexMaps.set(block.listItem, levelIndexMap), listIndexMap.set(block._key, 1), previousListItem = {
68
+ levelIndexMap.set(depth, 1), levelIndexMaps.set(block.listItem, levelIndexMap), listIndexMap.set(block._key, 1), previousListItem = {
55
69
  listItem: block.listItem,
56
- level: block.level
70
+ depth
57
71
  };
58
72
  continue;
59
73
  }
60
74
  levelIndexMaps.forEach((levelIndexMap, listItem) => {
61
75
  if (listItem === block.listItem) return;
62
- let levelsToDelete = [];
63
- levelIndexMap.forEach((_, level) => {
64
- level >= block.level && levelsToDelete.push(level);
65
- }), levelsToDelete.forEach((level) => {
66
- levelIndexMap.delete(level);
76
+ let depthsToDelete = [];
77
+ levelIndexMap.forEach((_, existingDepth) => {
78
+ existingDepth >= depth && depthsToDelete.push(existingDepth);
79
+ }), depthsToDelete.forEach((depthToDelete) => {
80
+ levelIndexMap.delete(depthToDelete);
67
81
  });
68
82
  });
69
- let levelIndexMap = levelIndexMaps.get(block.listItem) ?? /* @__PURE__ */ new Map(), levelCounter = levelIndexMap.get(block.level) ?? 0;
70
- levelIndexMap.set(block.level, levelCounter + 1), levelIndexMaps.set(block.listItem, levelIndexMap), listIndexMap.set(block._key, levelCounter + 1), previousListItem = {
83
+ let levelIndexMap = levelIndexMaps.get(block.listItem) ?? /* @__PURE__ */ new Map(), levelCounter = levelIndexMap.get(depth) ?? 0;
84
+ levelIndexMap.set(depth, levelCounter + 1), levelIndexMaps.set(block.listItem, levelIndexMap), listIndexMap.set(block._key, levelCounter + 1), previousListItem = {
71
85
  listItem: block.listItem,
72
- level: block.level
86
+ depth
73
87
  };
74
88
  }
75
- return listIndexMap;
89
+ return {
90
+ listIndexMap,
91
+ listDepthMap
92
+ };
76
93
  }
77
- const createRenderNode = (renderers, listIndexMap) => {
94
+ const createRenderNode = (renderers, listIndexMap, listDepthMap) => {
78
95
  function renderNode(options) {
79
96
  let { node, index, isInline } = options;
80
97
  return isPortableTextListItemBlock(node) ? renderListItem(node, index) : isPortableTextToolkitSpan(node) ? renderSpan(node) : isPortableTextBlock(node) ? renderBlock(node, index, isInline) : isPortableTextToolkitTextNode(node) ? renderText(node) : renderCustomBlock(node, index, isInline);
@@ -99,6 +116,7 @@ const createRenderNode = (renderers, listIndexMap) => {
99
116
  value: node,
100
117
  index,
101
118
  listIndex: node._key ? listIndexMap.get(node._key) : void 0,
119
+ listDepth: node._key ? listDepthMap.get(node._key) : void 0,
102
120
  isInline: !1,
103
121
  renderNode,
104
122
  children
@@ -164,8 +182,8 @@ function serializeBlock(options) {
164
182
  /**
165
183
  * @public
166
184
  */
167
- const DefaultBlockSpacingRenderer = ({ current, next }) => isPortableTextListItemBlock(current) && isPortableTextListItemBlock(next) ? "\n" : isPortableTextBlock(current) && isPortableTextBlock(next) && current.style === "blockquote" && next.style === "blockquote" ? "\n>\n" : "\n\n", DefaultHardBreakRenderer = () => " \n", DefaultListItemRenderer = ({ children, value, listIndex }) => {
168
- let listStyle = value.listItem || "bullet", level = value.level || 1, indent = " ".repeat(level - 1);
185
+ const DefaultBlockSpacingRenderer = ({ current, next }) => isPortableTextListItemBlock(current) && isPortableTextListItemBlock(next) ? "\n" : isPortableTextBlock(current) && isPortableTextBlock(next) && current.style === "blockquote" && next.style === "blockquote" ? "\n>\n" : "\n\n", DefaultHardBreakRenderer = () => " \n", DefaultListItemRenderer = ({ children, value, listIndex, listDepth }) => {
186
+ let listStyle = value.listItem || "bullet", depth = listDepth ?? (value.level || 1) - 1, indent = " ".repeat(depth);
169
187
  return listStyle === "number" ? `${indent}${listIndex ?? 1}. ${children}` : listStyle === "task" ? `${indent}- ${"checked" in value && typeof value.checked == "boolean" && value.checked ? "[x]" : "[ ]"} ${children}` : `${indent}- ${children}`;
170
188
  }, DefaultUnknownListItemRenderer = ({ children }) => `- ${children}\n`;
171
189
  /**
@@ -237,11 +255,75 @@ function uriLooksSafe(uri) {
237
255
  /**
238
256
  * @public
239
257
  */
240
- const DefaultUnknownMarkRenderer = ({ children }) => children, DefaultNormalRenderer = ({ children }) => !children || children.trim() === "" ? "" : children, DefaultBlockquoteRenderer = ({ children }) => children ? children.split("\n").map((line) => `> ${line}`).join("\n") : ">", DefaultH1Renderer = ({ children }) => `# ${children}`, DefaultH2Renderer = ({ children }) => `## ${children}`, DefaultH3Renderer = ({ children }) => `### ${children}`, DefaultH4Renderer = ({ children }) => `#### ${children}`, DefaultH5Renderer = ({ children }) => `##### ${children}`, DefaultH6Renderer = ({ children }) => `###### ${children}`, DefaultUnknownStyleRenderer = ({ children }) => children ?? "", DefaultCodeBlockRenderer = ({ value }) => `\`\`\`${value.language ?? ""}\n${value.code}\n\`\`\``, DefaultHorizontalRuleRenderer = () => "---", DefaultHtmlRenderer = ({ value }) => value.html, DefaultImageRenderer = ({ value }) => {
241
- let alt = escapeImageAndLinkText(value.alt ?? ""), title = value.title ? ` "${escapeImageAndLinkTitle(value.title)}"` : "";
242
- return `![${alt}](${value.src}${title})`;
243
- }, DefaultTableRenderer = ({ value, renderNode }) => {
244
- let rows = value.rows, alignment = value.alignment, headerRow = rows.at(0);
258
+ const DefaultUnknownMarkRenderer = ({ children }) => children, DefaultNormalRenderer = ({ children }) => !children || children.trim() === "" ? "" : children, DefaultBlockquoteRenderer = ({ children }) => children ? children.split("\n").map((line) => `> ${line}`).join("\n") : ">", DefaultH1Renderer = ({ children }) => `# ${children}`, DefaultH2Renderer = ({ children }) => `## ${children}`, DefaultH3Renderer = ({ children }) => `### ${children}`, DefaultH4Renderer = ({ children }) => `#### ${children}`, DefaultH5Renderer = ({ children }) => `##### ${children}`, DefaultH6Renderer = ({ children }) => `###### ${children}`, DefaultUnknownStyleRenderer = ({ children }) => children ?? "", DefaultCodeBlockRenderer = (options) => isCodeShaped(options.value) ? `\`\`\`${normalizeLanguage(options.value.language)}\n${options.value.code}\n\`\`\`` : DefaultUnknownTypeRenderer(options);
259
+ function isCodeShaped(value) {
260
+ return typeof value?.code == "string";
261
+ }
262
+ /**
263
+ * A fence info string is everything after the opening fence on the same
264
+ * line, so a real `language` can never contain a newline, and the parser
265
+ * only ever produces a string. Junk in this optional field should not send
266
+ * an otherwise valid code block to the fenced-JSON path, so it is treated
267
+ * as absent instead of guarded.
268
+ */
269
+ function normalizeLanguage(language) {
270
+ return typeof language != "string" || language.includes("\n") ? "" : language;
271
+ }
272
+ /**
273
+ * @public
274
+ */
275
+ const DefaultHorizontalRuleRenderer = () => "---", DefaultHtmlRenderer = (options) => isHtmlShaped(options.value) ? options.value.html : DefaultUnknownTypeRenderer(options);
276
+ function isHtmlShaped(value) {
277
+ return typeof value?.html == "string";
278
+ }
279
+ /**
280
+ * @public
281
+ */
282
+ const DefaultImageRenderer = (options) => {
283
+ if (!isImageShaped(options.value)) return DefaultUnknownTypeRenderer(options);
284
+ let alt = escapeImageAndLinkText(options.value.alt ?? ""), title = options.value.title ? ` "${escapeImageAndLinkTitle(options.value.title)}"` : "";
285
+ return `![${alt}](${options.value.src}${title})`;
286
+ };
287
+ function isImageShaped(value) {
288
+ let image = value;
289
+ return typeof image?.src == "string" && (image.alt == null || typeof image.alt == "string") && (image.title == null || typeof image.title == "string");
290
+ }
291
+ /**
292
+ * A table is table-shaped when everything the renderer dereferences is
293
+ * there: `rows` an array of typed objects with a `cells` array, every cell
294
+ * a typed object whose `value` array holds typed objects (`renderNode`'s
295
+ * input contract). The predicate narrows to exactly what `renderTable`
296
+ * consumes, so the renderer needs no casts. A malformed `table` value
297
+ * (e.g. a consumer's differently-shaped `table` type) falls back to the
298
+ * fenced-JSON path instead of throwing.
299
+ */
300
+ function isTableShaped(value) {
301
+ let rows = value?.rows;
302
+ return Array.isArray(rows) && rows.every((row) => isTypedObject(row) && Array.isArray(row.cells) && row.cells.every((cell) => isTypedObject(cell) && Array.isArray(cell.value) && cell.value.every(isTypedObject)));
303
+ }
304
+ /**
305
+ * Renders a Portable Text table block-object back to Markdown.
306
+ *
307
+ * The PT `headerRows` field decides the header. Missing `headerRows` and
308
+ * `headerRows === 0` both render headerless: GFM has no headerless form, so
309
+ * an empty header row is emitted and every row goes in the body (that empty
310
+ * header reads back as `headerRows: 0` via `markdownToPortableText`).
311
+ * `headerRows >= 1` promotes `rows[0]` to the header. GFM allows exactly one
312
+ * header row, so header rows beyond the first flatten into the body, lossy,
313
+ * but the extra rows stay on the Portable Text side.
314
+ *
315
+ * Asymmetric tables (rows of varying cell counts) are widened to match
316
+ * the row with the most cells. Narrower rows are padded with empty cells
317
+ * so a GFM parser doesn't silently drop the extra cells in wider rows.
318
+ *
319
+ * @public
320
+ */
321
+ const DefaultTableRenderer = (options) => {
322
+ let { value, renderNode } = options;
323
+ return isTableShaped(value) ? renderTable(value, renderNode) : DefaultUnknownTypeRenderer(options);
324
+ };
325
+ function renderTable(value, renderNode) {
326
+ let rows = value.rows, alignment = Array.isArray(value.alignment) ? value.alignment : void 0, headerRow = rows.at(0);
245
327
  if (!headerRow) return "";
246
328
  let getCellText = (cellBlocks) => cellBlocks.map((block, index) => renderNode({
247
329
  node: block,
@@ -256,33 +338,55 @@ const DefaultUnknownMarkRenderer = ({ children }) => children, DefaultNormalRend
256
338
  let align = alignment?.at(index);
257
339
  return align === "left" ? " :--- " : align === "center" ? " :---: " : align === "right" ? " ---: " : " --- ";
258
340
  }).join("|")}|`;
259
- if (value.headerRows === 0) {
260
- lines.push(renderCells([])), lines.push(delimiter);
261
- for (let row of rows) lines.push(renderRow(row.cells));
262
- } else {
341
+ if ((Number(value.headerRows) || 0) >= 1) {
263
342
  lines.push(renderRow(headerRow.cells)), lines.push(delimiter);
264
343
  for (let i = 1; i < rows.length; i++) {
265
344
  let row = rows.at(i);
266
345
  row && lines.push(renderRow(row.cells));
267
346
  }
347
+ } else {
348
+ lines.push(renderCells([])), lines.push(delimiter);
349
+ for (let row of rows) lines.push(renderRow(row.cells));
268
350
  }
269
351
  return lines.join("\n");
270
- }, DefaultCalloutRenderer = ({ value, renderNode }) => {
271
- let prefixed = value.content.map((block, index) => renderNode({
272
- node: {
352
+ }
353
+ /**
354
+ * @public
355
+ */
356
+ const DefaultCalloutRenderer = (options) => {
357
+ if (!isCalloutShaped(options.value)) return DefaultUnknownTypeRenderer(options);
358
+ let { renderNode } = options, prefixed = options.value.content.map((block, index) => renderNode({
359
+ node: block._type === "block" ? {
273
360
  ...block,
274
361
  style: "normal"
275
- },
362
+ } : block,
276
363
  index,
277
364
  isInline: !1,
278
365
  renderNode
279
366
  })).join("\n\n").split("\n").map((line) => line === "" ? ">" : `> ${line}`).join("\n");
280
- return `> [!${value.tone.toUpperCase()}]\n${prefixed}`;
281
- }, DefaultBlockquoteObjectRenderer = ({ value, renderNode }) => value.content.map((block, index) => renderNode({
282
- node: {
367
+ return `> [!${options.value.tone.toUpperCase()}]\n${prefixed}`;
368
+ };
369
+ function isCalloutShaped(value) {
370
+ let callout = value;
371
+ return typeof callout?.tone == "string" && Array.isArray(callout.content) && callout.content.every(isTypedObject);
372
+ }
373
+ /**
374
+ * Renders a structural blockquote block-object (the `types.blockquote` shape
375
+ * produced by `markdownToPortableText` when a `types.blockquote` matcher is
376
+ * provided) back to Markdown. Each content block is rendered via the
377
+ * recursive renderer pipeline, joined with blank lines, and every line is
378
+ * prefixed with `> ` to form a Markdown blockquote.
379
+ *
380
+ * Distinct from `DefaultBlockquoteRenderer`, which renders flat-path text
381
+ * blocks with `style: 'blockquote'`.
382
+ *
383
+ * @public
384
+ */
385
+ const DefaultBlockquoteObjectRenderer = ({ value, renderNode }) => value.content.map((block, index) => renderNode({
386
+ node: block._type === "block" ? {
283
387
  ...block,
284
388
  style: "normal"
285
- },
389
+ } : block,
286
390
  index,
287
391
  isInline: !1,
288
392
  renderNode
@@ -314,7 +418,14 @@ const DefaultUnknownTypeRenderer = ({ value, isInline }) => {
314
418
  let json = `\`\`\`json\n${JSON.stringify(value, null, 2)}\n\`\`\``;
315
419
  return isInline ? `\n${json}\n` : json;
316
420
  }, defaultRenderers = {
317
- types: {},
421
+ types: {
422
+ callout: DefaultCalloutRenderer,
423
+ code: DefaultCodeBlockRenderer,
424
+ "horizontal-rule": DefaultHorizontalRuleRenderer,
425
+ html: DefaultHtmlRenderer,
426
+ image: DefaultImageRenderer,
427
+ table: DefaultTableRenderer
428
+ },
318
429
  block: {
319
430
  normal: DefaultNormalRenderer,
320
431
  blockquote: DefaultBlockquoteRenderer,
@@ -363,7 +474,7 @@ function portableTextToMarkdown(blocks, options = {}) {
363
474
  unknownBlockStyle: options.unknownBlockStyle ?? defaultRenderers.unknownBlockStyle,
364
475
  unknownListItem: options.unknownListItem ?? defaultRenderers.unknownListItem,
365
476
  unknownMark: options.unknownMark ?? defaultRenderers.unknownMark
366
- }, renderBlockSpacing = options.blockSpacing ?? DefaultBlockSpacingRenderer, listIndexMap = buildListIndexMap(blocks), renderNode = createRenderNode(renderers, listIndexMap);
477
+ }, renderBlockSpacing = options.blockSpacing ?? DefaultBlockSpacingRenderer, { listIndexMap, listDepthMap } = buildListIndexMap(blocks), renderNode = createRenderNode(renderers, listIndexMap, listDepthMap);
367
478
  return blocks.map((node, index) => {
368
479
  let renderedNode = renderNode({
369
480
  node,
@@ -435,7 +546,24 @@ const normalStyleDefinition = { name: "normal" }, h1StyleDefinition = { name: "h
435
546
  },
436
547
  {
437
548
  name: "rows",
438
- type: "array"
549
+ type: "array",
550
+ of: [{
551
+ type: "object",
552
+ name: "row",
553
+ fields: [{
554
+ name: "cells",
555
+ type: "array",
556
+ of: [{
557
+ type: "object",
558
+ name: "cell",
559
+ fields: [{
560
+ name: "value",
561
+ type: "array",
562
+ of: [{ type: "block" }, { type: "image" }]
563
+ }]
564
+ }]
565
+ }]
566
+ }]
439
567
  }
440
568
  ]
441
569
  }, defaultCalloutObjectDefinition = {
@@ -546,6 +674,13 @@ const codeBlockMatcher = ({ context, value, isInline }) => {
546
674
  isInline
547
675
  });
548
676
  if (imageObject && "src" in imageObject) return imageObject;
677
+ }, tableBlockMatcher = ({ context, value, isInline }) => {
678
+ let tableObject = buildObjectMatcher(defaultTableObjectDefinition)({
679
+ context,
680
+ value,
681
+ isInline
682
+ });
683
+ if (tableObject && "rows" in tableObject) return tableObject;
549
684
  }, defaultOptions = {
550
685
  schema: defaultSchema,
551
686
  keyGenerator: defaultKeyGenerator,
@@ -577,7 +712,8 @@ const codeBlockMatcher = ({ context, value, isInline }) => {
577
712
  horizontalRule: buildObjectMatcher(defaultHorizontalRuleObjectDefinition),
578
713
  html: buildObjectMatcher(defaultHtmlObjectDefinition),
579
714
  image: imageBlockMatcher,
580
- callout: buildObjectMatcher(defaultCalloutObjectDefinition)
715
+ callout: buildObjectMatcher(defaultCalloutObjectDefinition),
716
+ table: tableBlockMatcher
581
717
  }
582
718
  };
583
719
  /**