markdown-codec 4.0.15 → 4.1.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.
Files changed (62) hide show
  1. package/README.md +13 -12
  2. package/dist/block/block.d.cts +3 -3
  3. package/dist/block/block.d.ts +3 -3
  4. package/dist/block/definitions.d.cts +2 -2
  5. package/dist/block/definitions.d.ts +2 -2
  6. package/dist/codec.d.cts +20 -0
  7. package/dist/codec.d.ts +20 -0
  8. package/dist/diagnostics/diagnostics.cjs +12 -4
  9. package/dist/diagnostics/diagnostics.d.cts +2 -2
  10. package/dist/diagnostics/diagnostics.d.ts +2 -2
  11. package/dist/diagnostics/diagnostics.js +12 -5
  12. package/dist/{diagnostics-BWK1iGy7.d.cts → diagnostics-CUMLtGHJ.d.cts} +7 -5
  13. package/dist/{diagnostics-BWK1iGy7.d.ts → diagnostics-CUMLtGHJ.d.ts} +7 -5
  14. package/dist/emit/emit.cjs +93 -27
  15. package/dist/emit/emit.js +96 -30
  16. package/dist/emit/inline.cjs +22 -14
  17. package/dist/emit/inline.d.cts +7 -5
  18. package/dist/emit/inline.d.ts +7 -5
  19. package/dist/emit/inline.js +21 -15
  20. package/dist/emit/table.cjs +1 -1
  21. package/dist/emit/table.d.cts +1 -1
  22. package/dist/emit/table.d.ts +1 -1
  23. package/dist/emit/table.js +1 -1
  24. package/dist/index.cjs +1 -0
  25. package/dist/index.d.cts +2 -2
  26. package/dist/index.d.ts +2 -2
  27. package/dist/index.js +2 -2
  28. package/dist/inline/inline.d.cts +1 -1
  29. package/dist/inline/inline.d.ts +1 -1
  30. package/dist/inline/link.d.cts +17 -1
  31. package/dist/inline/link.d.ts +17 -1
  32. package/dist/{inline-CXVQWQnW.d.cts → inline-CoS1JzxI.d.cts} +1 -1
  33. package/dist/{inline-B_V7bs5j.d.ts → inline-OGXVE6hB.d.ts} +1 -1
  34. package/dist/lower/front-matter.cjs +7 -4
  35. package/dist/lower/front-matter.d.cts +2 -1
  36. package/dist/lower/front-matter.d.ts +2 -1
  37. package/dist/lower/front-matter.js +7 -4
  38. package/dist/lower/inline.cjs +73 -29
  39. package/dist/lower/inline.d.cts +7 -10
  40. package/dist/lower/inline.d.ts +7 -10
  41. package/dist/lower/inline.js +74 -29
  42. package/dist/lower/lower.cjs +140 -71
  43. package/dist/lower/lower.d.cts +8 -1
  44. package/dist/lower/lower.d.ts +8 -1
  45. package/dist/lower/lower.js +142 -74
  46. package/dist/lower/table.cjs +3 -2
  47. package/dist/lower/table.js +3 -2
  48. package/dist/options/options.d.cts +1 -1
  49. package/dist/options/options.d.ts +1 -1
  50. package/dist/read.cjs +45 -9
  51. package/dist/read.d.cts +1 -1
  52. package/dist/read.d.ts +1 -1
  53. package/dist/read.js +46 -10
  54. package/dist/shared/list-id.cjs +6 -0
  55. package/dist/shared/list-id.d.cts +2 -1
  56. package/dist/shared/list-id.d.ts +2 -1
  57. package/dist/shared/list-id.js +6 -1
  58. package/dist/write.cjs +31 -4
  59. package/dist/write.js +31 -4
  60. package/package.json +2 -2
  61. package/dist/link-Dv4kxVjk.d.cts +0 -18
  62. package/dist/link-Dv4kxVjk.d.ts +0 -18
@@ -10,6 +10,9 @@ const require_emit_image = require("./image.cjs");
10
10
  const require_emit_table = require("./table.cjs");
11
11
  let document_schema_js = require("document-schema.js");
12
12
  //#region src/emit/emit.ts
13
+ function isDataUri(destination) {
14
+ return destination.startsWith("data:");
15
+ }
13
16
  const MAX_SETEXT_LEVEL = 2;
14
17
  const SETEXT_LEVEL_1_CHAR = "=";
15
18
  const SETEXT_LEVEL_2_CHAR = "-";
@@ -52,9 +55,12 @@ function renderParagraphBody(paragraph, context) {
52
55
  if (paragraph.styleId === "CodeBlock") {
53
56
  const literal = paragraph.runs.map((run) => run.text).join("");
54
57
  const fence = codeFenceFor(literal, context.codeFenceChar);
55
- return literal.length === 0 ? `${fence}\n${fence}` : `${fence}\n${literal}\n${fence}`;
58
+ const remainder = paragraph.source?.format === "markdown" ? paragraph.source.xml : void 0;
59
+ const info = [paragraph.codeLanguage, remainder].filter((part) => part !== void 0 && part.length > 0).join(" ");
60
+ const opening = info.length > 0 ? `${fence} ${info}` : fence;
61
+ return literal.length === 0 ? `${opening}\n${fence}` : `${opening}\n${literal}\n${fence}`;
56
62
  }
57
- if (paragraph.styleId === "HTMLPreformatted") return paragraph.runs.map((run) => run.text).join("");
63
+ if (paragraph.styleId === "HTMLPreformatted") return paragraph.source?.format === "markdown" ? paragraph.source.xml : paragraph.runs.map((run) => run.text).join("");
58
64
  if (paragraph.styleId === "MathBlock") return `$$\n${paragraph.runs.map((run) => run.text).join("")}\n$$`;
59
65
  const headingLevel = paragraph.styleId === void 0 ? void 0 : require_shared_style_constants.parseHeadingStyleId(paragraph.styleId);
60
66
  if (headingLevel !== void 0) {
@@ -64,15 +70,15 @@ function renderParagraphBody(paragraph, context) {
64
70
  severity: "info",
65
71
  message: `heading level ${String(headingLevel)} exceeds ATX's own six-"#" ceiling and is clamped to ${String(level)}`
66
72
  });
67
- const text = require_emit_inline.emitRuns(paragraph.runs, context);
73
+ const text = require_emit_inline.emitRuns(paragraph.runs, context, paragraph.constructs);
68
74
  if (context.headingStyle === "setext" && level <= MAX_SETEXT_LEVEL) return renderSetextHeading(level, text);
69
75
  return `${"#".repeat(level)} ${text}`;
70
76
  }
71
- return require_emit_inline.emitRuns(paragraph.runs, context);
77
+ return require_emit_inline.emitRuns(paragraph.runs, context, paragraph.constructs);
72
78
  }
73
79
  function renderParagraph(paragraph, context) {
74
80
  const body = renderParagraphBody(paragraph, context);
75
- const depth = quoteDepthOf(paragraph);
81
+ const depth = context.divisionDepth > 0 ? 0 : quoteDepthOf(paragraph);
76
82
  if (depth === 0) return body;
77
83
  if (!isQuotableStyle(paragraph.styleId)) {
78
84
  context.sink({
@@ -90,8 +96,13 @@ function renderTopLevelBlock(block, context) {
90
96
  case "paragraph": return renderParagraph(block, context);
91
97
  case "table": return require_emit_table.emitTable(block, context);
92
98
  case "image": return require_emit_image.emitImage(block, context.embedImages);
93
- case "pageBreak":
94
- case "embeddedObject": return "";
99
+ case "embeddedObject":
100
+ if (block.objectKind === "formula" && block.document.kind === "formula" && block.document.formula.presentation !== void 0) {
101
+ const latex = block.document.formula.presentation.latex;
102
+ return latex.length === 0 ? "$$\n$$" : `$$\n${latex}\n$$`;
103
+ }
104
+ return "";
105
+ case "pageBreak": return "";
95
106
  }
96
107
  }
97
108
  function listInfoFor(numId, context) {
@@ -117,16 +128,9 @@ function listInfoFor(numId, context) {
117
128
  }
118
129
  return info;
119
130
  }
120
- function checkboxPrefixFor(item) {
121
- const first = item.runs[0];
122
- if (first === void 0) return;
123
- if (first.text.startsWith(`☒ `)) return "[x] ";
124
- if (first.text.startsWith(`☐ `)) return "[ ] ";
125
- }
126
- function stripCheckboxRun(item, checkboxPrefix) {
127
- if (checkboxPrefix === void 0) return item;
131
+ function stripCheckboxRun(item) {
128
132
  const first = item.runs[0];
129
- const glyphPrefix = checkboxPrefix === "[x] " ? `☒ ` : `☐ `;
133
+ const glyphPrefix = first?.text.startsWith(`☒ `) ? `☒ ` : `☐ `;
130
134
  if (!first?.text.startsWith(glyphPrefix)) return item;
131
135
  const strippedText = first.text.slice(glyphPrefix.length);
132
136
  const runs = strippedText.length === 0 ? item.runs.slice(1) : [{
@@ -138,8 +142,30 @@ function stripCheckboxRun(item, checkboxPrefix) {
138
142
  runs
139
143
  };
140
144
  }
141
- function renderListItemMarker(numId, info, item, context) {
142
- const checkboxText = (info?.task === true ? checkboxPrefixFor(item) : void 0) ?? "";
145
+ function firstBlockCheckbox(first, taskNumId) {
146
+ if (first.list?.checked !== void 0) return {
147
+ checkboxText: first.list.checked ? "[x] " : "[ ] ",
148
+ stripGlyph: false
149
+ };
150
+ if (!taskNumId) return {
151
+ checkboxText: "",
152
+ stripGlyph: false
153
+ };
154
+ const leading = first.runs[0]?.text ?? "";
155
+ if (leading.startsWith(`☒ `)) return {
156
+ checkboxText: "[x] ",
157
+ stripGlyph: true
158
+ };
159
+ if (leading.startsWith(`☐ `)) return {
160
+ checkboxText: "[ ] ",
161
+ stripGlyph: true
162
+ };
163
+ return {
164
+ checkboxText: "",
165
+ stripGlyph: false
166
+ };
167
+ }
168
+ function renderListItemMarker(numId, info, checkboxText, context) {
143
169
  if (info?.type === "ordered" && numId !== void 0) {
144
170
  const next = context.orderedCounters.get(numId) ?? info.start ?? 1;
145
171
  context.orderedCounters.set(numId, next + 1);
@@ -161,17 +187,29 @@ function renderListRegion(items, context) {
161
187
  while (index < items.length) {
162
188
  const item = items[index];
163
189
  if (item?.list === void 0) break;
164
- const { numId, level } = item.list;
190
+ const { numId, level, itemId } = item.list;
165
191
  const info = listInfoFor(numId, context);
166
- let lookahead = index + 1;
192
+ let ownEnd = index + 1;
193
+ if (itemId !== void 0) while (ownEnd < items.length) {
194
+ const candidate = items[ownEnd];
195
+ if (candidate?.list?.level !== level || candidate.list?.itemId !== itemId) break;
196
+ ownEnd += 1;
197
+ }
198
+ const ownBlocks = items.slice(index, ownEnd);
199
+ let lookahead = ownEnd;
167
200
  while (lookahead < items.length && (items[lookahead]?.list?.level ?? -1) > level) lookahead += 1;
168
- const nestedItems = items.slice(index + 1, lookahead);
169
- const checkboxPrefix = info?.task === true ? checkboxPrefixFor(item) : void 0;
170
- const marker = renderListItemMarker(numId, info, item, context);
171
- const bodyLines = renderParagraphBody(stripCheckboxRun(item, checkboxPrefix), context).split("\n");
201
+ const nestedItems = items.slice(ownEnd, lookahead);
202
+ const first = ownBlocks[0];
203
+ if (first === void 0) break;
204
+ const { checkboxText, stripGlyph } = firstBlockCheckbox(first, info?.task === true);
205
+ const marker = renderListItemMarker(numId, info, checkboxText, context);
172
206
  const indent = " ".repeat(marker.bareLength);
173
- const [firstLine = "", ...restLines] = bodyLines;
207
+ const [firstLine = "", ...restLines] = renderParagraphBody(stripGlyph ? stripCheckboxRun(first) : first, context).split("\n");
174
208
  let text = [`${marker.full}${firstLine}`, ...restLines.map((line) => `${indent}${line}`)].join("\n");
209
+ for (const extra of ownBlocks.slice(1)) {
210
+ const rendered = renderParagraphBody(extra, context).split("\n").map((line) => line.length === 0 ? line : `${indent}${line}`).join("\n");
211
+ text += `\n\n${rendered}`;
212
+ }
175
213
  if (nestedItems.length > 0) {
176
214
  const nested = renderListRegion(nestedItems, context).split("\n").map((line) => line.length === 0 ? line : `${indent}${line}`).join("\n");
177
215
  text += `\n${nested}`;
@@ -233,9 +271,9 @@ function renderFootnoteDefinition(name, body) {
233
271
  return [`${marker} ${firstLine}`, ...restLines.map((line) => line.length === 0 ? line : `${indent}${line}`)].join("\n");
234
272
  }
235
273
  function renderConstruct(item, context) {
236
- const body = renderItems(item.children, context);
237
274
  const { descriptor } = item;
238
275
  if (descriptor.kind === "anchor" && descriptor.anchorType === "footnote") {
276
+ const body = renderItems(item.children, context);
239
277
  if (require_inline_footnote.isValidFootnoteLabel(descriptor.name)) return renderFootnoteDefinition(descriptor.name, body);
240
278
  context.sink({
241
279
  code: require_diagnostics_diagnostics.MarkdownDiagnosticCodes.CONSTRUCT_UNREPRESENTED,
@@ -244,6 +282,23 @@ function renderConstruct(item, context) {
244
282
  });
245
283
  return body;
246
284
  }
285
+ if (descriptor.kind === "division") {
286
+ if (item.children.every((child) => {
287
+ if (isConstructItem(child)) return true;
288
+ return child.block.kind !== "paragraph" || child.block.indentLeftPt !== void 0 && child.block.indentLeftPt >= 36;
289
+ })) {
290
+ context.divisionDepth += 1;
291
+ const body = renderItems(item.children, context);
292
+ context.divisionDepth -= 1;
293
+ return body.split("\n").map((line) => line.length === 0 ? ">" : `> ${line}`).join("\n");
294
+ }
295
+ }
296
+ if (descriptor.kind === "link" && descriptor.target.kind === "external") {
297
+ const onlyChild = item.children.length === 1 && !isConstructItem(item.children[0]) ? item.children[0].block : void 0;
298
+ if (onlyChild?.kind === "image" && !isDataUri(descriptor.target.uri)) return `![${require_emit_inline.escapeMarkdownText(onlyChild.altText ?? "")}](${require_emit_inline.escapeLinkDestination(descriptor.target.uri)}${descriptor.title === void 0 ? "" : ` "${require_emit_inline.renderLinkTitle(descriptor.title)}"`})`;
299
+ if (onlyChild?.kind === "image" && isDataUri(descriptor.target.uri) && !context.embedImages) return require_emit_image.emitImage(onlyChild, false);
300
+ }
301
+ const body = renderItems(item.children, context);
247
302
  const detail = descriptor.kind === "anchor" ? `${descriptor.kind} (${descriptor.anchorType})` : descriptor.kind;
248
303
  context.sink({
249
304
  code: require_diagnostics_diagnostics.MarkdownDiagnosticCodes.CONSTRUCT_UNREPRESENTED,
@@ -284,8 +339,18 @@ function renderItems(items, context) {
284
339
  function emitBlocks(blocks, context) {
285
340
  const imbalance = (0, document_schema_js.findConstructMarkerImbalance)(blocks);
286
341
  if (imbalance !== void 0) throw new require_diagnostics_diagnostics.MarkdownUnbalancedConstructMarkersError(imbalance.kind, imbalance.index);
342
+ validateRunConstructExtents(blocks);
287
343
  return renderItems(groupConstructItems(blocks, 0).items, context);
288
344
  }
345
+ function validateRunConstructExtents(blocks) {
346
+ for (const block of blocks) {
347
+ if (block.kind === "paragraph" && block.constructs !== void 0) {
348
+ const fault = (0, document_schema_js.findRunConstructFault)(block);
349
+ if (fault !== void 0) throw new require_diagnostics_diagnostics.MarkdownInvalidRunConstructExtentError(fault.kind, fault.index);
350
+ }
351
+ if (block.kind === "table") for (const row of block.rows) for (const cell of row.cells) validateRunConstructExtents(cell.blocks);
352
+ }
353
+ }
289
354
  function emitMarkdown(document, options = {}) {
290
355
  if (document.kind !== "wordprocessing") throw new require_diagnostics_diagnostics.MarkdownUnsupportedDocumentKindError(document.kind);
291
356
  const context = {
@@ -299,7 +364,8 @@ function emitMarkdown(document, options = {}) {
299
364
  embedImages: options.images ?? true,
300
365
  orderedCounters: /* @__PURE__ */ new Map(),
301
366
  reportedFallbackNumIds: /* @__PURE__ */ new Set(),
302
- reportedAbsentNumIdFallback: false
367
+ reportedAbsentNumIdFallback: false,
368
+ divisionDepth: 0
303
369
  };
304
370
  const body = document.sections.map((section) => emitBlocks(section.blocks, context)).join("\n\n");
305
371
  const frontMatter = options.frontMatter === true ? require_emit_front_matter.emitFrontMatter(document.metadata) : void 0;
package/dist/emit/emit.js CHANGED
@@ -1,14 +1,17 @@
1
1
  import { isValidFootnoteLabel } from "../inline/footnote.js";
2
2
  import "../defaults/defaults.js";
3
- import { MarkdownDiagnosticCodes, MarkdownUnbalancedConstructMarkersError, MarkdownUnsupportedDocumentKindError, NOOP_MARKDOWN_DIAGNOSTIC_SINK } from "../diagnostics/diagnostics.js";
3
+ import { MarkdownDiagnosticCodes, MarkdownInvalidRunConstructExtentError, MarkdownUnbalancedConstructMarkersError, MarkdownUnsupportedDocumentKindError, NOOP_MARKDOWN_DIAGNOSTIC_SINK } from "../diagnostics/diagnostics.js";
4
4
  import { parseListNumId } from "../shared/list-id.js";
5
5
  import { CODE_BLOCK_STYLE_ID, HORIZONTAL_RULE_STYLE_ID, HTML_PREFORMATTED_STYLE_ID, MATH_BLOCK_STYLE_ID, QUOTE_STYLE_ID, parseHeadingStyleId } from "../shared/style-constants.js";
6
6
  import { emitFrontMatter } from "./front-matter.js";
7
- import { emitRuns } from "./inline.js";
7
+ import { emitRuns, escapeLinkDestination, escapeMarkdownText, renderLinkTitle } from "./inline.js";
8
8
  import { emitImage } from "./image.js";
9
9
  import { emitTable } from "./table.js";
10
- import { clampHeadingLevel, findConstructMarkerImbalance } from "document-schema.js";
10
+ import { clampHeadingLevel, findConstructMarkerImbalance, findRunConstructFault } from "document-schema.js";
11
11
  //#region src/emit/emit.ts
12
+ function isDataUri(destination) {
13
+ return destination.startsWith("data:");
14
+ }
12
15
  const MAX_SETEXT_LEVEL = 2;
13
16
  const SETEXT_LEVEL_1_CHAR = "=";
14
17
  const SETEXT_LEVEL_2_CHAR = "-";
@@ -51,9 +54,12 @@ function renderParagraphBody(paragraph, context) {
51
54
  if (paragraph.styleId === "CodeBlock") {
52
55
  const literal = paragraph.runs.map((run) => run.text).join("");
53
56
  const fence = codeFenceFor(literal, context.codeFenceChar);
54
- return literal.length === 0 ? `${fence}\n${fence}` : `${fence}\n${literal}\n${fence}`;
57
+ const remainder = paragraph.source?.format === "markdown" ? paragraph.source.xml : void 0;
58
+ const info = [paragraph.codeLanguage, remainder].filter((part) => part !== void 0 && part.length > 0).join(" ");
59
+ const opening = info.length > 0 ? `${fence} ${info}` : fence;
60
+ return literal.length === 0 ? `${opening}\n${fence}` : `${opening}\n${literal}\n${fence}`;
55
61
  }
56
- if (paragraph.styleId === "HTMLPreformatted") return paragraph.runs.map((run) => run.text).join("");
62
+ if (paragraph.styleId === "HTMLPreformatted") return paragraph.source?.format === "markdown" ? paragraph.source.xml : paragraph.runs.map((run) => run.text).join("");
57
63
  if (paragraph.styleId === "MathBlock") return `$$\n${paragraph.runs.map((run) => run.text).join("")}\n$$`;
58
64
  const headingLevel = paragraph.styleId === void 0 ? void 0 : parseHeadingStyleId(paragraph.styleId);
59
65
  if (headingLevel !== void 0) {
@@ -63,15 +69,15 @@ function renderParagraphBody(paragraph, context) {
63
69
  severity: "info",
64
70
  message: `heading level ${String(headingLevel)} exceeds ATX's own six-"#" ceiling and is clamped to ${String(level)}`
65
71
  });
66
- const text = emitRuns(paragraph.runs, context);
72
+ const text = emitRuns(paragraph.runs, context, paragraph.constructs);
67
73
  if (context.headingStyle === "setext" && level <= MAX_SETEXT_LEVEL) return renderSetextHeading(level, text);
68
74
  return `${"#".repeat(level)} ${text}`;
69
75
  }
70
- return emitRuns(paragraph.runs, context);
76
+ return emitRuns(paragraph.runs, context, paragraph.constructs);
71
77
  }
72
78
  function renderParagraph(paragraph, context) {
73
79
  const body = renderParagraphBody(paragraph, context);
74
- const depth = quoteDepthOf(paragraph);
80
+ const depth = context.divisionDepth > 0 ? 0 : quoteDepthOf(paragraph);
75
81
  if (depth === 0) return body;
76
82
  if (!isQuotableStyle(paragraph.styleId)) {
77
83
  context.sink({
@@ -89,8 +95,13 @@ function renderTopLevelBlock(block, context) {
89
95
  case "paragraph": return renderParagraph(block, context);
90
96
  case "table": return emitTable(block, context);
91
97
  case "image": return emitImage(block, context.embedImages);
92
- case "pageBreak":
93
- case "embeddedObject": return "";
98
+ case "embeddedObject":
99
+ if (block.objectKind === "formula" && block.document.kind === "formula" && block.document.formula.presentation !== void 0) {
100
+ const latex = block.document.formula.presentation.latex;
101
+ return latex.length === 0 ? "$$\n$$" : `$$\n${latex}\n$$`;
102
+ }
103
+ return "";
104
+ case "pageBreak": return "";
94
105
  }
95
106
  }
96
107
  function listInfoFor(numId, context) {
@@ -116,16 +127,9 @@ function listInfoFor(numId, context) {
116
127
  }
117
128
  return info;
118
129
  }
119
- function checkboxPrefixFor(item) {
120
- const first = item.runs[0];
121
- if (first === void 0) return;
122
- if (first.text.startsWith(`☒ `)) return "[x] ";
123
- if (first.text.startsWith(`☐ `)) return "[ ] ";
124
- }
125
- function stripCheckboxRun(item, checkboxPrefix) {
126
- if (checkboxPrefix === void 0) return item;
130
+ function stripCheckboxRun(item) {
127
131
  const first = item.runs[0];
128
- const glyphPrefix = checkboxPrefix === "[x] " ? `☒ ` : `☐ `;
132
+ const glyphPrefix = first?.text.startsWith(`☒ `) ? `☒ ` : `☐ `;
129
133
  if (!first?.text.startsWith(glyphPrefix)) return item;
130
134
  const strippedText = first.text.slice(glyphPrefix.length);
131
135
  const runs = strippedText.length === 0 ? item.runs.slice(1) : [{
@@ -137,8 +141,30 @@ function stripCheckboxRun(item, checkboxPrefix) {
137
141
  runs
138
142
  };
139
143
  }
140
- function renderListItemMarker(numId, info, item, context) {
141
- const checkboxText = (info?.task === true ? checkboxPrefixFor(item) : void 0) ?? "";
144
+ function firstBlockCheckbox(first, taskNumId) {
145
+ if (first.list?.checked !== void 0) return {
146
+ checkboxText: first.list.checked ? "[x] " : "[ ] ",
147
+ stripGlyph: false
148
+ };
149
+ if (!taskNumId) return {
150
+ checkboxText: "",
151
+ stripGlyph: false
152
+ };
153
+ const leading = first.runs[0]?.text ?? "";
154
+ if (leading.startsWith(`☒ `)) return {
155
+ checkboxText: "[x] ",
156
+ stripGlyph: true
157
+ };
158
+ if (leading.startsWith(`☐ `)) return {
159
+ checkboxText: "[ ] ",
160
+ stripGlyph: true
161
+ };
162
+ return {
163
+ checkboxText: "",
164
+ stripGlyph: false
165
+ };
166
+ }
167
+ function renderListItemMarker(numId, info, checkboxText, context) {
142
168
  if (info?.type === "ordered" && numId !== void 0) {
143
169
  const next = context.orderedCounters.get(numId) ?? info.start ?? 1;
144
170
  context.orderedCounters.set(numId, next + 1);
@@ -160,17 +186,29 @@ function renderListRegion(items, context) {
160
186
  while (index < items.length) {
161
187
  const item = items[index];
162
188
  if (item?.list === void 0) break;
163
- const { numId, level } = item.list;
189
+ const { numId, level, itemId } = item.list;
164
190
  const info = listInfoFor(numId, context);
165
- let lookahead = index + 1;
191
+ let ownEnd = index + 1;
192
+ if (itemId !== void 0) while (ownEnd < items.length) {
193
+ const candidate = items[ownEnd];
194
+ if (candidate?.list?.level !== level || candidate.list?.itemId !== itemId) break;
195
+ ownEnd += 1;
196
+ }
197
+ const ownBlocks = items.slice(index, ownEnd);
198
+ let lookahead = ownEnd;
166
199
  while (lookahead < items.length && (items[lookahead]?.list?.level ?? -1) > level) lookahead += 1;
167
- const nestedItems = items.slice(index + 1, lookahead);
168
- const checkboxPrefix = info?.task === true ? checkboxPrefixFor(item) : void 0;
169
- const marker = renderListItemMarker(numId, info, item, context);
170
- const bodyLines = renderParagraphBody(stripCheckboxRun(item, checkboxPrefix), context).split("\n");
200
+ const nestedItems = items.slice(ownEnd, lookahead);
201
+ const first = ownBlocks[0];
202
+ if (first === void 0) break;
203
+ const { checkboxText, stripGlyph } = firstBlockCheckbox(first, info?.task === true);
204
+ const marker = renderListItemMarker(numId, info, checkboxText, context);
171
205
  const indent = " ".repeat(marker.bareLength);
172
- const [firstLine = "", ...restLines] = bodyLines;
206
+ const [firstLine = "", ...restLines] = renderParagraphBody(stripGlyph ? stripCheckboxRun(first) : first, context).split("\n");
173
207
  let text = [`${marker.full}${firstLine}`, ...restLines.map((line) => `${indent}${line}`)].join("\n");
208
+ for (const extra of ownBlocks.slice(1)) {
209
+ const rendered = renderParagraphBody(extra, context).split("\n").map((line) => line.length === 0 ? line : `${indent}${line}`).join("\n");
210
+ text += `\n\n${rendered}`;
211
+ }
174
212
  if (nestedItems.length > 0) {
175
213
  const nested = renderListRegion(nestedItems, context).split("\n").map((line) => line.length === 0 ? line : `${indent}${line}`).join("\n");
176
214
  text += `\n${nested}`;
@@ -232,9 +270,9 @@ function renderFootnoteDefinition(name, body) {
232
270
  return [`${marker} ${firstLine}`, ...restLines.map((line) => line.length === 0 ? line : `${indent}${line}`)].join("\n");
233
271
  }
234
272
  function renderConstruct(item, context) {
235
- const body = renderItems(item.children, context);
236
273
  const { descriptor } = item;
237
274
  if (descriptor.kind === "anchor" && descriptor.anchorType === "footnote") {
275
+ const body = renderItems(item.children, context);
238
276
  if (isValidFootnoteLabel(descriptor.name)) return renderFootnoteDefinition(descriptor.name, body);
239
277
  context.sink({
240
278
  code: MarkdownDiagnosticCodes.CONSTRUCT_UNREPRESENTED,
@@ -243,6 +281,23 @@ function renderConstruct(item, context) {
243
281
  });
244
282
  return body;
245
283
  }
284
+ if (descriptor.kind === "division") {
285
+ if (item.children.every((child) => {
286
+ if (isConstructItem(child)) return true;
287
+ return child.block.kind !== "paragraph" || child.block.indentLeftPt !== void 0 && child.block.indentLeftPt >= 36;
288
+ })) {
289
+ context.divisionDepth += 1;
290
+ const body = renderItems(item.children, context);
291
+ context.divisionDepth -= 1;
292
+ return body.split("\n").map((line) => line.length === 0 ? ">" : `> ${line}`).join("\n");
293
+ }
294
+ }
295
+ if (descriptor.kind === "link" && descriptor.target.kind === "external") {
296
+ const onlyChild = item.children.length === 1 && !isConstructItem(item.children[0]) ? item.children[0].block : void 0;
297
+ if (onlyChild?.kind === "image" && !isDataUri(descriptor.target.uri)) return `![${escapeMarkdownText(onlyChild.altText ?? "")}](${escapeLinkDestination(descriptor.target.uri)}${descriptor.title === void 0 ? "" : ` "${renderLinkTitle(descriptor.title)}"`})`;
298
+ if (onlyChild?.kind === "image" && isDataUri(descriptor.target.uri) && !context.embedImages) return emitImage(onlyChild, false);
299
+ }
300
+ const body = renderItems(item.children, context);
246
301
  const detail = descriptor.kind === "anchor" ? `${descriptor.kind} (${descriptor.anchorType})` : descriptor.kind;
247
302
  context.sink({
248
303
  code: MarkdownDiagnosticCodes.CONSTRUCT_UNREPRESENTED,
@@ -283,8 +338,18 @@ function renderItems(items, context) {
283
338
  function emitBlocks(blocks, context) {
284
339
  const imbalance = findConstructMarkerImbalance(blocks);
285
340
  if (imbalance !== void 0) throw new MarkdownUnbalancedConstructMarkersError(imbalance.kind, imbalance.index);
341
+ validateRunConstructExtents(blocks);
286
342
  return renderItems(groupConstructItems(blocks, 0).items, context);
287
343
  }
344
+ function validateRunConstructExtents(blocks) {
345
+ for (const block of blocks) {
346
+ if (block.kind === "paragraph" && block.constructs !== void 0) {
347
+ const fault = findRunConstructFault(block);
348
+ if (fault !== void 0) throw new MarkdownInvalidRunConstructExtentError(fault.kind, fault.index);
349
+ }
350
+ if (block.kind === "table") for (const row of block.rows) for (const cell of row.cells) validateRunConstructExtents(cell.blocks);
351
+ }
352
+ }
288
353
  function emitMarkdown(document, options = {}) {
289
354
  if (document.kind !== "wordprocessing") throw new MarkdownUnsupportedDocumentKindError(document.kind);
290
355
  const context = {
@@ -298,7 +363,8 @@ function emitMarkdown(document, options = {}) {
298
363
  embedImages: options.images ?? true,
299
364
  orderedCounters: /* @__PURE__ */ new Map(),
300
365
  reportedFallbackNumIds: /* @__PURE__ */ new Set(),
301
- reportedAbsentNumIdFallback: false
366
+ reportedAbsentNumIdFallback: false,
367
+ divisionDepth: 0
302
368
  };
303
369
  const body = document.sections.map((section) => emitBlocks(section.blocks, context)).join("\n\n");
304
370
  const frontMatter = options.frontMatter === true ? emitFrontMatter(document.metadata) : void 0;
@@ -1,6 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_diagnostics_diagnostics = require("../diagnostics/diagnostics.cjs");
3
- const require_html_html = require("../html/html.cjs");
4
3
  require("../shared/style-constants.cjs");
5
4
  //#region src/emit/inline.ts
6
5
  const ESCAPE_CHARS = /* @__PURE__ */ new Set([
@@ -40,14 +39,6 @@ function escapeMarkdownText(text) {
40
39
  let index = 0;
41
40
  while (index < text.length) {
42
41
  const char = text.charAt(index);
43
- if (char === "<") {
44
- const tag = require_html_html.matchHtmlTag(text, index);
45
- if (tag !== void 0) {
46
- out += tag;
47
- index += tag.length;
48
- continue;
49
- }
50
- }
51
42
  if (char === "\n") {
52
43
  out += "\\\n";
53
44
  index += 1;
@@ -77,6 +68,7 @@ function renderCodeSpan(text) {
77
68
  return risksFenceCollision || wouldBeStrippedOnReparse ? `${fence} ${text} ${fence}` : `${fence}${text}${fence}`;
78
69
  }
79
70
  function renderLeaf(run, context) {
71
+ if (run.source?.format === "markdown") return run.source.xml;
80
72
  if (run.fontFamily === "Courier New") {
81
73
  context.sink({
82
74
  code: require_diagnostics_diagnostics.MarkdownDiagnosticCodes.CODE_SPAN_AS_MONOSPACE_RUN,
@@ -143,7 +135,20 @@ function escapeLinkDestination(destination) {
143
135
  if (!/[\s()]/.test(destination)) return destination;
144
136
  return `<${destination.replace(/[<>]/g, (char) => `\\${char}`)}>`;
145
137
  }
146
- function emitRuns(runs, context) {
138
+ function renderLinkTitle(title) {
139
+ return title.replace(/[\n\r]+/g, " ").replace(/["\\]/g, (char) => `\\${char}`);
140
+ }
141
+ function linkTitleCovering(constructs, start, end) {
142
+ let tightest;
143
+ for (const extent of constructs ?? []) {
144
+ if (extent.descriptor.kind !== "link" || extent.descriptor.title === void 0) continue;
145
+ if (extent.startRun > start || extent.endRun < end) continue;
146
+ const current = tightest;
147
+ if (current === void 0 || extent.startRun > current.startRun || extent.startRun === current.startRun && extent.endRun < current.endRun) tightest = extent;
148
+ }
149
+ return tightest?.descriptor.kind === "link" ? tightest.descriptor.title : void 0;
150
+ }
151
+ function emitRuns(runs, context, constructs = void 0) {
147
152
  let out = "";
148
153
  let index = 0;
149
154
  while (index < runs.length) {
@@ -165,19 +170,22 @@ function emitRuns(runs, context) {
165
170
  severity: "info",
166
171
  message: `${String(group.length)} adjacent runs share the hyperlink "${hyperlink}"; markdown has no way to place two link boundaries back to back, so they render as one link spanning their combined text`
167
172
  });
168
- if (group.length === 1 && isPlainAutolink(group[0])) out += `<${group[0].text}>`;
173
+ const title = linkTitleCovering(constructs, index, groupEnd);
174
+ if (title === void 0 && group.length === 1 && isPlainAutolink(group[0])) out += `<${group[0].text}>`;
169
175
  else {
170
176
  const linkText = renderNestedStyles(group, 0, context);
171
- out += `[${linkText}](${escapeLinkDestination(hyperlink)})`;
177
+ out += `[${linkText}](${escapeLinkDestination(hyperlink)}${title === void 0 ? "" : ` "${renderLinkTitle(title)}"`})`;
172
178
  }
173
179
  index = groupEnd;
174
180
  }
175
181
  return out;
176
182
  }
177
- function emitRunsSingleLine(runs, context) {
178
- return emitRuns(runs, context).replace(/\\\n/g, " ");
183
+ function emitRunsSingleLine(runs, context, constructs = void 0) {
184
+ return emitRuns(runs, context, constructs).replace(/\\\n/g, " ");
179
185
  }
180
186
  //#endregion
181
187
  exports.emitRuns = emitRuns;
182
188
  exports.emitRunsSingleLine = emitRunsSingleLine;
189
+ exports.escapeLinkDestination = escapeLinkDestination;
183
190
  exports.escapeMarkdownText = escapeMarkdownText;
191
+ exports.renderLinkTitle = renderLinkTitle;
@@ -1,12 +1,14 @@
1
- import { i as MarkdownDiagnosticSink } from "../diagnostics-BWK1iGy7.cjs";
2
- import { ContentRun } from "document-schema.js";
1
+ import { i as MarkdownDiagnosticSink } from "../diagnostics-CUMLtGHJ.cjs";
2
+ import { ContentRun, RunConstructExtent } from "document-schema.js";
3
3
  //#region src/emit/inline.d.ts
4
4
  interface InlineEmitContext {
5
5
  readonly sink: MarkdownDiagnosticSink;
6
6
  readonly emphasisMarker: string;
7
7
  }
8
8
  declare function escapeMarkdownText(text: string): string;
9
- declare function emitRuns(runs: readonly ContentRun[], context: InlineEmitContext): string;
10
- declare function emitRunsSingleLine(runs: readonly ContentRun[], context: InlineEmitContext): string;
9
+ declare function escapeLinkDestination(destination: string): string;
10
+ declare function renderLinkTitle(title: string): string;
11
+ declare function emitRuns(runs: readonly ContentRun[], context: InlineEmitContext, constructs?: readonly RunConstructExtent[] | undefined): string;
12
+ declare function emitRunsSingleLine(runs: readonly ContentRun[], context: InlineEmitContext, constructs?: readonly RunConstructExtent[] | undefined): string;
11
13
  //#endregion
12
- export { InlineEmitContext, emitRuns, emitRunsSingleLine, escapeMarkdownText };
14
+ export { InlineEmitContext, emitRuns, emitRunsSingleLine, escapeLinkDestination, escapeMarkdownText, renderLinkTitle };
@@ -1,12 +1,14 @@
1
- import { i as MarkdownDiagnosticSink } from "../diagnostics-BWK1iGy7.js";
2
- import { ContentRun } from "document-schema.js";
1
+ import { i as MarkdownDiagnosticSink } from "../diagnostics-CUMLtGHJ.js";
2
+ import { ContentRun, RunConstructExtent } from "document-schema.js";
3
3
  //#region src/emit/inline.d.ts
4
4
  interface InlineEmitContext {
5
5
  readonly sink: MarkdownDiagnosticSink;
6
6
  readonly emphasisMarker: string;
7
7
  }
8
8
  declare function escapeMarkdownText(text: string): string;
9
- declare function emitRuns(runs: readonly ContentRun[], context: InlineEmitContext): string;
10
- declare function emitRunsSingleLine(runs: readonly ContentRun[], context: InlineEmitContext): string;
9
+ declare function escapeLinkDestination(destination: string): string;
10
+ declare function renderLinkTitle(title: string): string;
11
+ declare function emitRuns(runs: readonly ContentRun[], context: InlineEmitContext, constructs?: readonly RunConstructExtent[] | undefined): string;
12
+ declare function emitRunsSingleLine(runs: readonly ContentRun[], context: InlineEmitContext, constructs?: readonly RunConstructExtent[] | undefined): string;
11
13
  //#endregion
12
- export { InlineEmitContext, emitRuns, emitRunsSingleLine, escapeMarkdownText };
14
+ export { InlineEmitContext, emitRuns, emitRunsSingleLine, escapeLinkDestination, escapeMarkdownText, renderLinkTitle };
@@ -1,5 +1,4 @@
1
1
  import { MarkdownDiagnosticCodes } from "../diagnostics/diagnostics.js";
2
- import { matchHtmlTag } from "../html/html.js";
3
2
  import "../shared/style-constants.js";
4
3
  //#region src/emit/inline.ts
5
4
  const ESCAPE_CHARS = /* @__PURE__ */ new Set([
@@ -39,14 +38,6 @@ function escapeMarkdownText(text) {
39
38
  let index = 0;
40
39
  while (index < text.length) {
41
40
  const char = text.charAt(index);
42
- if (char === "<") {
43
- const tag = matchHtmlTag(text, index);
44
- if (tag !== void 0) {
45
- out += tag;
46
- index += tag.length;
47
- continue;
48
- }
49
- }
50
41
  if (char === "\n") {
51
42
  out += "\\\n";
52
43
  index += 1;
@@ -76,6 +67,7 @@ function renderCodeSpan(text) {
76
67
  return risksFenceCollision || wouldBeStrippedOnReparse ? `${fence} ${text} ${fence}` : `${fence}${text}${fence}`;
77
68
  }
78
69
  function renderLeaf(run, context) {
70
+ if (run.source?.format === "markdown") return run.source.xml;
79
71
  if (run.fontFamily === "Courier New") {
80
72
  context.sink({
81
73
  code: MarkdownDiagnosticCodes.CODE_SPAN_AS_MONOSPACE_RUN,
@@ -142,7 +134,20 @@ function escapeLinkDestination(destination) {
142
134
  if (!/[\s()]/.test(destination)) return destination;
143
135
  return `<${destination.replace(/[<>]/g, (char) => `\\${char}`)}>`;
144
136
  }
145
- function emitRuns(runs, context) {
137
+ function renderLinkTitle(title) {
138
+ return title.replace(/[\n\r]+/g, " ").replace(/["\\]/g, (char) => `\\${char}`);
139
+ }
140
+ function linkTitleCovering(constructs, start, end) {
141
+ let tightest;
142
+ for (const extent of constructs ?? []) {
143
+ if (extent.descriptor.kind !== "link" || extent.descriptor.title === void 0) continue;
144
+ if (extent.startRun > start || extent.endRun < end) continue;
145
+ const current = tightest;
146
+ if (current === void 0 || extent.startRun > current.startRun || extent.startRun === current.startRun && extent.endRun < current.endRun) tightest = extent;
147
+ }
148
+ return tightest?.descriptor.kind === "link" ? tightest.descriptor.title : void 0;
149
+ }
150
+ function emitRuns(runs, context, constructs = void 0) {
146
151
  let out = "";
147
152
  let index = 0;
148
153
  while (index < runs.length) {
@@ -164,17 +169,18 @@ function emitRuns(runs, context) {
164
169
  severity: "info",
165
170
  message: `${String(group.length)} adjacent runs share the hyperlink "${hyperlink}"; markdown has no way to place two link boundaries back to back, so they render as one link spanning their combined text`
166
171
  });
167
- if (group.length === 1 && isPlainAutolink(group[0])) out += `<${group[0].text}>`;
172
+ const title = linkTitleCovering(constructs, index, groupEnd);
173
+ if (title === void 0 && group.length === 1 && isPlainAutolink(group[0])) out += `<${group[0].text}>`;
168
174
  else {
169
175
  const linkText = renderNestedStyles(group, 0, context);
170
- out += `[${linkText}](${escapeLinkDestination(hyperlink)})`;
176
+ out += `[${linkText}](${escapeLinkDestination(hyperlink)}${title === void 0 ? "" : ` "${renderLinkTitle(title)}"`})`;
171
177
  }
172
178
  index = groupEnd;
173
179
  }
174
180
  return out;
175
181
  }
176
- function emitRunsSingleLine(runs, context) {
177
- return emitRuns(runs, context).replace(/\\\n/g, " ");
182
+ function emitRunsSingleLine(runs, context, constructs = void 0) {
183
+ return emitRuns(runs, context, constructs).replace(/\\\n/g, " ");
178
184
  }
179
185
  //#endregion
180
- export { emitRuns, emitRunsSingleLine, escapeMarkdownText };
186
+ export { emitRuns, emitRunsSingleLine, escapeLinkDestination, escapeMarkdownText, renderLinkTitle };
@@ -54,7 +54,7 @@ function renderCellText(cell, context) {
54
54
  });
55
55
  continue;
56
56
  }
57
- const text = require_emit_inline.emitRunsSingleLine(block.runs, context);
57
+ const text = require_emit_inline.emitRunsSingleLine(block.runs, context, block.constructs);
58
58
  if (text.length > 0) parts.push(text);
59
59
  }
60
60
  return escapeUnescapedPipes(parts.join(" "));
@@ -1,4 +1,4 @@
1
- import { i as MarkdownDiagnosticSink } from "../diagnostics-BWK1iGy7.cjs";
1
+ import { i as MarkdownDiagnosticSink } from "../diagnostics-CUMLtGHJ.cjs";
2
2
  import { InlineEmitContext } from "./inline.cjs";
3
3
  import { ContentTable } from "document-schema.js";
4
4
  //#region src/emit/table.d.ts
@@ -1,4 +1,4 @@
1
- import { i as MarkdownDiagnosticSink } from "../diagnostics-BWK1iGy7.js";
1
+ import { i as MarkdownDiagnosticSink } from "../diagnostics-CUMLtGHJ.js";
2
2
  import { InlineEmitContext } from "./inline.js";
3
3
  import { ContentTable } from "document-schema.js";
4
4
  //#region src/emit/table.d.ts