nizel-kit 0.1.9 → 0.1.10

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.
@@ -64,8 +64,36 @@ var NizelKit = (() => {
64
64
  ]);
65
65
  var passthroughInlineTags = /* @__PURE__ */ new Set(["abbr", "bdi", "bdo", "cite", "data", "dfn", "kbd", "mark", "q", "samp", "small", "sub", "sup", "time", "u", "var"]);
66
66
  function htmlToMarkdown(html, options = {}) {
67
+ const resolved = {
68
+ unsupported: options.unsupported ?? "preserve",
69
+ plugins: options.plugins ?? []
70
+ };
71
+ const handlers = resolved.plugins.flatMap((plugin) => {
72
+ const handler = plugin?.htmlToMarkdown;
73
+ return handler ? Array.isArray(handler) ? handler : [handler] : [];
74
+ });
75
+ const ctx = { options: resolved, handlers, epilogue: [] };
67
76
  const root = parseHtml(html);
68
- return normalizeDocument(renderChildren(root.children, { unsupported: options.unsupported ?? "preserve" }, 0));
77
+ const body = normalizeDocument(renderChildren(root.children, ctx, 0));
78
+ return ctx.epilogue.length ? normalizeDocument(`${body}
79
+
80
+ ${ctx.epilogue.join("\n\n")}`) : body;
81
+ }
82
+ function buildHandlerContext(node, ctx, depth) {
83
+ return {
84
+ options: ctx.options,
85
+ inline: (target) => normalizeInline(renderChildren(childrenOf(target), ctx, depth)),
86
+ block: (target) => normalizeDocument(renderChildren(childrenOf(target), ctx, depth)),
87
+ text: (target) => textContent(target),
88
+ indent: (value, size) => indentContinuation(value, size),
89
+ epilogue: (value) => {
90
+ if (value)
91
+ ctx.epilogue.push(value);
92
+ }
93
+ };
94
+ }
95
+ function childrenOf(node) {
96
+ return node.type === "element" || node.type === "root" ? node.children : [];
69
97
  }
70
98
  function parseHtml(html) {
71
99
  const root = { type: "root", children: [] };
@@ -167,25 +195,31 @@ var NizelKit = (() => {
167
195
  function slugifyText(value) {
168
196
  return value.toLowerCase().replace(/[^\w\s-]/g, "").replace(/[\s_]+/g, "-").replace(/^-+|-+$/g, "");
169
197
  }
170
- function renderChildren(nodes, options, depth) {
171
- return nodes.map((node) => renderNode(node, options, depth)).join("");
198
+ function renderChildren(nodes, ctx, depth) {
199
+ return nodes.map((node) => renderNode(node, ctx, depth)).join("");
172
200
  }
173
- function renderNode(node, options, depth) {
201
+ function renderNode(node, ctx, depth) {
174
202
  if (node.type === "text")
175
203
  return collapseText(decodeEntities(node.value));
176
204
  if (node.type === "comment")
177
- return options.unsupported === "preserve" ? node.value : " ";
205
+ return ctx.options.unsupported === "preserve" ? node.value : " ";
178
206
  if (node.type === "root")
179
- return renderChildren(node.children, options, depth);
180
- const inline = () => normalizeInline(renderChildren(node.children, options, depth));
181
- const block = () => normalizeDocument(renderChildren(node.children, options, depth));
182
- if (options.unsupported === "preserve" && hasUnsupportedAttrs(node)) {
207
+ return renderChildren(node.children, ctx, depth);
208
+ const inline = () => normalizeInline(renderChildren(node.children, ctx, depth));
209
+ const block = () => normalizeDocument(renderChildren(node.children, ctx, depth));
210
+ for (const handler of ctx.handlers) {
211
+ const result = handler(node, buildHandlerContext(node, ctx, depth));
212
+ if (result !== void 0) {
213
+ return blockTags.has(node.tag) ? blockWrap(result) : result;
214
+ }
215
+ }
216
+ if (ctx.options.unsupported === "preserve" && hasUnsupportedAttrs(node)) {
183
217
  return blockTags.has(node.tag) ? blockWrap(node.raw) : node.raw;
184
218
  }
185
219
  if (/^h[1-6]$/.test(node.tag))
186
220
  return blockWrap(`${"#".repeat(Number(node.tag[1]))} ${inline()}`);
187
221
  if (node.tag === "p")
188
- return blockWrap(normalizeParagraph(renderChildren(node.children, options, depth)));
222
+ return blockWrap(normalizeParagraph(renderChildren(node.children, ctx, depth)));
189
223
  if (node.tag === "br")
190
224
  return " \n";
191
225
  if (node.tag === "hr")
@@ -207,13 +241,13 @@ var NizelKit = (() => {
207
241
  if (node.tag === "blockquote")
208
242
  return blockWrap(prefixLines(block(), "> "));
209
243
  if (node.tag === "ul" || node.tag === "ol")
210
- return blockWrap(renderList(node, options, depth));
244
+ return blockWrap(renderList(node, ctx, depth));
211
245
  if (node.tag === "li")
212
246
  return inline();
213
247
  if (node.tag === "table") {
214
- if (options.unsupported === "preserve" && tableHasUnsupportedStructure(node))
248
+ if (ctx.options.unsupported === "preserve" && tableHasUnsupportedStructure(node))
215
249
  return blockWrap(node.raw);
216
- return blockWrap(renderTable(node, options));
250
+ return blockWrap(renderTable(node, ctx));
217
251
  }
218
252
  if (node.tag === "thead" || node.tag === "tbody" || node.tag === "tr" || node.tag === "th" || node.tag === "td")
219
253
  return inline();
@@ -222,9 +256,9 @@ var NizelKit = (() => {
222
256
  if (node.tag === "span")
223
257
  return inline();
224
258
  if (passthroughInlineTags.has(node.tag) || blockTags.has(node.tag)) {
225
- return options.unsupported === "preserve" ? node.raw : block();
259
+ return ctx.options.unsupported === "preserve" ? node.raw : block();
226
260
  }
227
- return options.unsupported === "preserve" ? node.raw : inline();
261
+ return ctx.options.unsupported === "preserve" ? node.raw : inline();
228
262
  }
229
263
  function renderLink(node, text) {
230
264
  const href = node.attrs.href;
@@ -257,19 +291,19 @@ var NizelKit = (() => {
257
291
  ${code}
258
292
  ${fence}`;
259
293
  }
260
- function renderList(node, options, depth) {
294
+ function renderList(node, ctx, depth) {
261
295
  return node.children.filter((child) => child.type === "element" && child.tag === "li").map((item, index) => {
262
296
  const marker = node.tag === "ol" ? `${Number(node.attrs.start ?? 1) + index}. ` : "- ";
263
- const body = normalizeListItemBody(renderChildren(item.children, options, depth + 1));
297
+ const body = normalizeListItemBody(renderChildren(item.children, ctx, depth + 1));
264
298
  return indentContinuation(`${marker}${body}`, marker.length);
265
299
  }).join("\n");
266
300
  }
267
- function renderTable(node, options) {
301
+ function renderTable(node, ctx) {
268
302
  const rows = collectRows(node).map((row) => row.children.filter((cell) => cell.type === "element" && (cell.tag === "th" || cell.tag === "td"))).filter((row) => row.length > 0);
269
303
  if (rows.length === 0)
270
304
  return "";
271
305
  const width = Math.max(...rows.map((row) => row.length));
272
- const values = rows.map((row) => Array.from({ length: width }, (_, index) => normalizeInline(renderChildren(row[index]?.children ?? [], options, 0))));
306
+ const values = rows.map((row) => Array.from({ length: width }, (_, index) => normalizeInline(renderChildren(row[index]?.children ?? [], ctx, 0))));
273
307
  const header = values[0];
274
308
  const separator = Array.from({ length: width }, () => "---");
275
309
  const body = values.slice(1);
@@ -366,6 +400,20 @@ ${trimmed}
366
400
  }
367
401
  return longest;
368
402
  }
403
+ var htmlChildElements = (node) => node.type === "element" || node.type === "root" ? node.children.filter((child) => child.type === "element") : [];
404
+ var findHtmlElement = (node, predicate) => htmlChildElements(node).find(predicate);
405
+ var hasHtmlClass = (node, className) => node.type === "element" && (node.attrs.class ?? "").split(/\s+/).includes(className);
406
+ var findHtmlDescendant = (node, predicate) => {
407
+ for (const child of htmlChildElements(node)) {
408
+ if (predicate(child))
409
+ return child;
410
+ const nested = findHtmlDescendant(child, predicate);
411
+ if (nested)
412
+ return nested;
413
+ }
414
+ return void 0;
415
+ };
416
+ var htmlRoot = (children) => ({ type: "root", children });
369
417
 
370
418
  // ../nizel/dist/collect.js
371
419
  function collect(ast) {
@@ -4877,7 +4925,22 @@ ${body}
4877
4925
  afterParse(ast) {
4878
4926
  return replaceAbbreviations(ast, definitions);
4879
4927
  }
4880
- }
4928
+ },
4929
+ htmlToMarkdown: abbrToMarkdown()
4930
+ };
4931
+ };
4932
+ var abbrToMarkdown = () => {
4933
+ const seen = /* @__PURE__ */ new Set();
4934
+ return (node, ctx) => {
4935
+ if (node.type !== "element" || node.tag !== "abbr")
4936
+ return void 0;
4937
+ const term = ctx.text(node).trim();
4938
+ const title = node.attrs.title;
4939
+ if (title !== void 0 && term && !seen.has(term)) {
4940
+ seen.add(term);
4941
+ ctx.epilogue(`*[${term}]: ${title}`);
4942
+ }
4943
+ return term;
4881
4944
  };
4882
4945
  };
4883
4946
  var extractAbbreviations = (markdown) => {
@@ -4945,7 +5008,25 @@ ${body}
4945
5008
  blocks,
4946
5009
  hooks: {
4947
5010
  beforeParse: transformGitHubAlerts
4948
- }
5011
+ },
5012
+ htmlToMarkdown: alertToMarkdown(options)
5013
+ };
5014
+ };
5015
+ var alertToMarkdown = (options = {}) => {
5016
+ const rootClass = options.className ?? "alert";
5017
+ return (node, ctx) => {
5018
+ if (node.type !== "element" || node.tag !== "div")
5019
+ return void 0;
5020
+ const type = node.attrs["data-alert"];
5021
+ if (!type)
5022
+ return void 0;
5023
+ const titleEl = findHtmlElement(node, (el) => el.tag === "p" && hasHtmlClass(el, `${rootClass}__title`));
5024
+ const contentEl = findHtmlElement(node, (el) => hasHtmlClass(el, `${rootClass}__content`));
5025
+ const title = titleEl ? ctx.text(titleEl).trim() : "";
5026
+ const body = contentEl ? ctx.block(contentEl) : "";
5027
+ return `${title ? `::${type} ${title}` : `::${type}`}
5028
+ ${body}
5029
+ ::`;
4949
5030
  };
4950
5031
  };
4951
5032
  var transformGitHubAlerts = (markdown) => {
@@ -5030,9 +5111,21 @@ ${body}
5030
5111
  name: "autolink",
5031
5112
  options: {
5032
5113
  autolinks: resolveAutolinkPluginOptions(options)
5033
- }
5114
+ },
5115
+ htmlToMarkdown: autolinkToMarkdown()
5034
5116
  });
5035
5117
  };
5118
+ var autolinkToMarkdown = () => (node, ctx) => {
5119
+ if (node.type !== "element" || node.tag !== "a")
5120
+ return void 0;
5121
+ const href = node.attrs.href;
5122
+ if (!href)
5123
+ return void 0;
5124
+ const extras = Object.keys(node.attrs).filter((key) => key !== "href" && key !== "target" && key !== "rel");
5125
+ if (extras.length > 0)
5126
+ return void 0;
5127
+ return ctx.text(node).trim() === href ? href : void 0;
5128
+ };
5036
5129
 
5037
5130
  // ../plugin-code-copy/dist/index.js
5038
5131
  var codeCopyPlugin = (options = {}) => {
@@ -5055,9 +5148,34 @@ ${body}
5055
5148
  },
5056
5149
  hooks: {
5057
5150
  afterParse: wrapCodeBlocks
5058
- }
5151
+ },
5152
+ htmlToMarkdown: codeCopyToMarkdown()
5059
5153
  });
5060
5154
  };
5155
+ var codeCopyToMarkdown = () => (node, ctx) => {
5156
+ if (node.type !== "element" || node.tag !== "figure" || node.attrs["data-nizel-code-copy"] === void 0)
5157
+ return void 0;
5158
+ const source = findHtmlDescendant(node, (el) => el.tag === "textarea" && el.attrs["data-nizel-copy-source"] !== void 0);
5159
+ if (!source) {
5160
+ const body = htmlChildElements(node).filter((el) => !["figcaption", "textarea", "button"].includes(el.tag));
5161
+ return ctx.block(htmlRoot(body));
5162
+ }
5163
+ const code = ctx.text(source).replace(/\n$/, "");
5164
+ const pre = findHtmlDescendant(node, (el) => el.tag === "pre");
5165
+ const codeEl = pre ? findHtmlElement(pre, (el) => el.tag === "code") : void 0;
5166
+ const codeClass = codeEl?.attrs.class ?? "";
5167
+ const langClass = codeClass.match(/\blanguage-([A-Za-z0-9_-]+)/)?.[1];
5168
+ const isMermaid = findHtmlDescendant(node, (el) => el.tag === "div" && el.attrs.class === "mermaid") !== void 0;
5169
+ const lang = langClass ?? pre?.attrs["data-language"] ?? (isMermaid ? "mermaid" : "");
5170
+ const fence = fenceFor(code);
5171
+ return `${fence}${lang}
5172
+ ${code}
5173
+ ${fence}`;
5174
+ };
5175
+ var fenceFor = (code) => {
5176
+ const longest = Math.max(0, ...(code.match(/`+/g) ?? []).map((run) => run.length));
5177
+ return "`".repeat(Math.max(3, longest + 1));
5178
+ };
5061
5179
  var wrapCodeBlocks = (ast) => ({
5062
5180
  ...ast,
5063
5181
  children: ast.children.map(wrapCodeBlock)
@@ -5134,7 +5252,28 @@ ${body}
5134
5252
  afterParse(ast) {
5135
5253
  return appendBibliography(replaceCitationRefs(ast, citations), citations);
5136
5254
  }
5255
+ },
5256
+ htmlToMarkdown: citationsToMarkdown(options)
5257
+ };
5258
+ };
5259
+ var citationsToMarkdown = (options = {}) => {
5260
+ const className = options.className ?? "citations";
5261
+ return (node, ctx) => {
5262
+ if (node.type !== "element")
5263
+ return void 0;
5264
+ if (node.tag === "a" && hasHtmlClass(node, "citation")) {
5265
+ const match = /^#cite-(.+)$/.exec(node.attrs.href ?? "");
5266
+ return match ? `[@${match[1]}]` : void 0;
5267
+ }
5268
+ if (node.tag === "section" && hasHtmlClass(node, className)) {
5269
+ const list = findHtmlElement(node, (el) => el.tag === "ol");
5270
+ const items = list ? htmlChildElements(list).filter((el) => el.tag === "li") : [];
5271
+ return items.map((li) => {
5272
+ const id = /^cite-(.+)$/.exec(li.attrs.id ?? "")?.[1] ?? "";
5273
+ return `[@${id}]: ${ctx.block(htmlRoot(li.children)).trim()}`;
5274
+ }).join("\n");
5137
5275
  }
5276
+ return void 0;
5138
5277
  };
5139
5278
  };
5140
5279
  var extractCitations = (markdown) => {
@@ -5236,9 +5375,28 @@ ${body}
5236
5375
  beforeParse(markdown) {
5237
5376
  return transformDefinitionLists(markdown, options);
5238
5377
  }
5239
- }
5378
+ },
5379
+ htmlToMarkdown: deflistToMarkdown(options)
5240
5380
  };
5241
5381
  };
5382
+ var deflistToMarkdown = (options = {}) => (node, ctx) => {
5383
+ if (node.type !== "element" || node.tag !== "dl")
5384
+ return void 0;
5385
+ if (options.className) {
5386
+ if (node.attrs.class !== options.className)
5387
+ return void 0;
5388
+ } else if (node.attrs.class !== void 0) {
5389
+ return void 0;
5390
+ }
5391
+ const lines = [];
5392
+ for (const el of htmlChildElements(node)) {
5393
+ if (el.tag === "dt")
5394
+ lines.push(ctx.inline(el));
5395
+ else if (el.tag === "dd")
5396
+ lines.push(`: ${ctx.inline(el)}`);
5397
+ }
5398
+ return lines.join("\n");
5399
+ };
5242
5400
  var transformDefinitionLists = (markdown, _options = {}) => {
5243
5401
  const lines = markdown.split("\n");
5244
5402
  const result = [];
@@ -5325,8 +5483,23 @@ ${body}
5325
5483
  },
5326
5484
  hooks: {
5327
5485
  beforeParse: transformDetails
5328
- }
5486
+ },
5487
+ htmlToMarkdown: detailsToMarkdown(options)
5329
5488
  });
5489
+ var detailsToMarkdown = (options = {}) => {
5490
+ const className = options.className ?? "details";
5491
+ return (node, ctx) => {
5492
+ if (node.type !== "element" || node.tag !== "details" || node.attrs.class !== className)
5493
+ return void 0;
5494
+ const summaryEl = findHtmlElement(node, (el) => el.tag === "summary");
5495
+ const summary = summaryEl ? ctx.text(summaryEl).trim() : "Details";
5496
+ const body = ctx.block(htmlRoot(htmlChildElements(node).filter((el) => el.tag !== "summary"))).trim();
5497
+ return body ? `::details ${summary}
5498
+ ${body}
5499
+ ::` : `::details ${summary}
5500
+ ::`;
5501
+ };
5502
+ };
5330
5503
  var transformDetails = (markdown) => {
5331
5504
  return markdown.replace(/^:::details\s*(.*?)\s*$/gm, (_match, summary) => `::details ${summary || "Details"}`).replace(/^:::\s*$/gm, "::");
5332
5505
  };
@@ -5356,7 +5529,19 @@ ${body}
5356
5529
  afterParse(ast) {
5357
5530
  return transformDiagramCodes(ast, options);
5358
5531
  }
5359
- }
5532
+ },
5533
+ htmlToMarkdown: diagramsToMarkdown(options)
5534
+ };
5535
+ };
5536
+ var diagramsToMarkdown = (options = {}) => {
5537
+ const className = options.className ?? "mermaid";
5538
+ return (node, ctx) => {
5539
+ if (node.type !== "element" || node.tag !== "div" || node.attrs.class !== className)
5540
+ return void 0;
5541
+ const code = ctx.text(node).trim();
5542
+ return `\`\`\`mermaid
5543
+ ${code}
5544
+ \`\`\``;
5360
5545
  };
5361
5546
  };
5362
5547
  var transformDiagramCodes = (ast, options = {}) => ({
@@ -5501,9 +5686,45 @@ ${body}
5501
5686
  hooks: {
5502
5687
  beforeParse: transformFootnotes,
5503
5688
  afterParse: transformFootnoteRefs
5689
+ },
5690
+ htmlToMarkdown: footnotesToMarkdown(options)
5691
+ };
5692
+ };
5693
+ var footnotesToMarkdown = (options = {}) => {
5694
+ const className = options.className ?? "footnotes";
5695
+ return (node, ctx) => {
5696
+ if (node.type !== "element")
5697
+ return void 0;
5698
+ if (node.tag === "sup") {
5699
+ const anchor = findHtmlElement(node, (el) => el.tag === "a" && hasHtmlClass(el, "footnote-ref"));
5700
+ const match = anchor ? /^#fn-(.+)$/.exec(anchor.attrs.href ?? "") : void 0;
5701
+ return match ? `[^${match[1]}]` : void 0;
5702
+ }
5703
+ if (node.tag === "section" && hasHtmlClass(node, className)) {
5704
+ const list = findHtmlElement(node, (el) => el.tag === "ol");
5705
+ const items = list ? htmlChildElements(list).filter((el) => el.tag === "li") : [];
5706
+ return items.map((li) => {
5707
+ const id = /^fn-(.+)$/.exec(li.attrs.id ?? "")?.[1] ?? "";
5708
+ const body = ctx.block(htmlRoot(stripBackrefs(li.children, className))).trim();
5709
+ return `[^${id}]: ${body}`;
5710
+ }).join("\n");
5504
5711
  }
5712
+ return void 0;
5505
5713
  };
5506
5714
  };
5715
+ var stripBackrefs = (nodes, className) => {
5716
+ const result = [];
5717
+ for (const node of nodes) {
5718
+ if (node.type === "element") {
5719
+ if (hasHtmlClass(node, `${className}__backref`))
5720
+ continue;
5721
+ result.push({ ...node, children: stripBackrefs(node.children, className) });
5722
+ } else {
5723
+ result.push(node);
5724
+ }
5725
+ }
5726
+ return result;
5727
+ };
5507
5728
  var transformFootnotes = (markdown) => {
5508
5729
  const lines = markdown.split("\n");
5509
5730
  const body = [];
@@ -5649,9 +5870,46 @@ ${body}
5649
5870
  afterParse(ast) {
5650
5871
  return transformTaskLists(ast, mode);
5651
5872
  }
5652
- }
5873
+ },
5874
+ htmlToMarkdown: taskListToMarkdown()
5653
5875
  };
5654
5876
  };
5877
+ var CHECKBOX_ATTR = "data-nizel-task-checkbox";
5878
+ var isCheckbox = (node) => node.type === "element" && node.tag === "input" && node.attrs?.[CHECKBOX_ATTR] !== void 0;
5879
+ var taskListToMarkdown = () => (node, ctx) => {
5880
+ if (node.type !== "element" || node.tag !== "ul" && node.tag !== "ol")
5881
+ return void 0;
5882
+ const items = htmlChildElements(node).filter((el) => el.tag === "li");
5883
+ if (!items.some((item) => findHtmlDescendant(item, isCheckbox)))
5884
+ return void 0;
5885
+ const ordered = node.tag === "ol";
5886
+ const start = Number(node.attrs.start ?? 1);
5887
+ return items.map((item, index) => {
5888
+ const checkbox = findHtmlDescendant(item, isCheckbox);
5889
+ const body = ctx.block(htmlRoot(cleanTaskChildren(item.children))).trim();
5890
+ const baseMarker = ordered ? `${start + index}. ` : "- ";
5891
+ const marker = checkbox ? `${baseMarker}${checkbox.attrs.checked !== void 0 ? "[x] " : "[ ] "}` : baseMarker;
5892
+ return ctx.indent(`${marker}${body}`, marker.length);
5893
+ }).join("\n");
5894
+ };
5895
+ var cleanTaskChildren = (nodes) => {
5896
+ const result = [];
5897
+ for (const node of nodes) {
5898
+ if (node.type === "element") {
5899
+ if (isCheckbox(node))
5900
+ continue;
5901
+ const cleaned = { ...node, children: cleanTaskChildren(node.children) };
5902
+ if (hasHtmlClass(cleaned, "nizel-task-list__label") || hasHtmlClass(cleaned, "nizel-task-list__content")) {
5903
+ result.push(...cleaned.children);
5904
+ } else {
5905
+ result.push(cleaned);
5906
+ }
5907
+ } else {
5908
+ result.push(node);
5909
+ }
5910
+ }
5911
+ return result;
5912
+ };
5655
5913
  var transformTaskLists = (ast, mode = "view") => {
5656
5914
  return { ...ast, children: ast.children.map((node) => transformBlock5(node, mode)) };
5657
5915
  };
@@ -5733,8 +5991,17 @@ ${body}
5733
5991
  afterParse(ast) {
5734
5992
  return addHeadingAnchors(ast, options);
5735
5993
  }
5736
- }
5994
+ },
5995
+ htmlToMarkdown: headingAnchorsToMarkdown(options)
5737
5996
  });
5997
+ var headingAnchorsToMarkdown = (options = {}) => {
5998
+ const className = options.className ?? "heading-anchor";
5999
+ return (node) => {
6000
+ if (node.type !== "element" || node.tag !== "a" || !hasHtmlClass(node, className))
6001
+ return void 0;
6002
+ return "";
6003
+ };
6004
+ };
5738
6005
  var addHeadingAnchors = (ast, options = {}) => ({
5739
6006
  ...ast,
5740
6007
  children: ast.children.map((node) => {
@@ -5766,7 +6033,26 @@ ${body}
5766
6033
  afterParse(ast) {
5767
6034
  return transformInlineMath(ast, options);
5768
6035
  }
6036
+ },
6037
+ htmlToMarkdown: mathToMarkdown(options)
6038
+ };
6039
+ };
6040
+ var mathToMarkdown = (options = {}) => {
6041
+ const inlineTokens = (options.inlineClassName ?? "math math-inline").split(/\s+/);
6042
+ const blockTokens = (options.blockClassName ?? "math math-display").split(/\s+/);
6043
+ return (node, ctx) => {
6044
+ if (node.type !== "element")
6045
+ return void 0;
6046
+ const tokens = node.attrs.class ? node.attrs.class.split(/\s+/) : [];
6047
+ if (node.tag === "span" && inlineTokens.every((token) => tokens.includes(token))) {
6048
+ return `$${ctx.text(node)}$`;
6049
+ }
6050
+ if (node.tag === "div" && blockTokens.every((token) => tokens.includes(token))) {
6051
+ return `$$
6052
+ ${ctx.text(node).trim()}
6053
+ $$`;
5769
6054
  }
6055
+ return void 0;
5770
6056
  };
5771
6057
  };
5772
6058
  var transformBlockMath = (markdown) => {
@@ -5869,8 +6155,26 @@ ${body}
5869
6155
  afterRender(html) {
5870
6156
  return enhanceImages(html, options);
5871
6157
  }
5872
- }
6158
+ },
6159
+ htmlToMarkdown: mediaToMarkdown(options)
5873
6160
  });
6161
+ var mediaToMarkdown = (options = {}) => {
6162
+ const className = options.figureClassName ?? "media-figure";
6163
+ return (node, ctx) => {
6164
+ if (node.type !== "element" || node.tag !== "figure" || node.attrs.class !== className)
6165
+ return void 0;
6166
+ const img = findHtmlElement(node, (el) => el.tag === "img");
6167
+ if (!img)
6168
+ return void 0;
6169
+ const attrs = { src: img.attrs.src ?? "" };
6170
+ if (img.attrs.alt !== void 0)
6171
+ attrs.alt = img.attrs.alt;
6172
+ if (img.attrs.title !== void 0)
6173
+ attrs.title = img.attrs.title;
6174
+ const cleanImg = { type: "element", tag: "img", attrs, children: [], raw: "" };
6175
+ return ctx.inline({ type: "root", children: [cleanImg] });
6176
+ };
6177
+ };
5874
6178
  var enhanceImages = (html, options = {}) => {
5875
6179
  const lazy = options.lazy !== false;
5876
6180
  const responsive = options.responsive !== false;
@@ -5933,9 +6237,30 @@ ${body}
5933
6237
  }
5934
6238
  }
5935
6239
  }
5936
- }
6240
+ },
6241
+ htmlToMarkdown: shikiToMarkdown()
5937
6242
  });
5938
6243
  };
6244
+ var shikiToMarkdown = () => (node, ctx) => {
6245
+ if (node.type !== "element" || node.tag !== "pre")
6246
+ return void 0;
6247
+ const isShiki = hasHtmlClass(node, "shiki") || node.attrs.style !== void 0 || node.attrs["data-language"] !== void 0;
6248
+ if (!isShiki)
6249
+ return void 0;
6250
+ const codeEl = findHtmlElement(node, (el) => el.tag === "code");
6251
+ const codeClass = codeEl?.attrs.class ?? "";
6252
+ const langClass = codeClass.match(/\blanguage-([A-Za-z0-9_-]+)/)?.[1];
6253
+ const lang = node.attrs["data-language"] ?? langClass ?? "";
6254
+ const code = ctx.text(node).replace(/\n$/, "");
6255
+ const fence = fenceFor2(code);
6256
+ return `${fence}${lang}
6257
+ ${code}
6258
+ ${fence}`;
6259
+ };
6260
+ var fenceFor2 = (code) => {
6261
+ const longest = Math.max(0, ...(code.match(/`+/g) ?? []).map((run) => run.length));
6262
+ return "`".repeat(Math.max(3, longest + 1));
6263
+ };
5939
6264
  var fallback = (node, ctx) => {
5940
6265
  const language = node.lang ? ` class="language-${ctx.escape(node.lang)}"` : "";
5941
6266
  return `<pre><code${language}>${ctx.escape(node.code)}</code></pre>`;
@@ -5954,8 +6279,17 @@ ${body}
5954
6279
  afterParse(ast) {
5955
6280
  return fillTocBlocks(ast, options);
5956
6281
  }
5957
- }
6282
+ },
6283
+ htmlToMarkdown: tocToMarkdown(options)
5958
6284
  });
6285
+ var tocToMarkdown = (options = {}) => {
6286
+ const className = options.className ?? "toc";
6287
+ return (node) => {
6288
+ if (node.type !== "element" || node.tag !== "nav" || !hasHtmlClass(node, className))
6289
+ return void 0;
6290
+ return "[[toc]]";
6291
+ };
6292
+ };
5959
6293
  var fillTocBlocks = (ast, options = {}) => {
5960
6294
  const minDepth = options.minDepth ?? 2;
5961
6295
  const maxDepth = options.maxDepth ?? 6;
@@ -5989,7 +6323,24 @@ ${body}
5989
6323
  afterParse(ast) {
5990
6324
  return transformTypography(ast, options);
5991
6325
  }
5992
- }
6326
+ },
6327
+ htmlToMarkdown: typographyToMarkdown(options)
6328
+ };
6329
+ };
6330
+ var typographyToMarkdown = (options = {}) => {
6331
+ const mark = options.mark !== false;
6332
+ const subscript = options.subscript !== false;
6333
+ const superscript = options.superscript !== false;
6334
+ return (node, ctx) => {
6335
+ if (node.type !== "element" || Object.keys(node.attrs).length > 0)
6336
+ return void 0;
6337
+ if (node.tag === "mark" && mark)
6338
+ return `==${ctx.inline(node)}==`;
6339
+ if (node.tag === "sub" && subscript)
6340
+ return `~${ctx.inline(node)}~`;
6341
+ if (node.tag === "sup" && superscript)
6342
+ return `^${ctx.inline(node)}^`;
6343
+ return void 0;
5993
6344
  };
5994
6345
  };
5995
6346
  var transformTypography = (ast, options = {}) => {
package/dist/nizel-kit.js CHANGED
@@ -32,8 +32,36 @@ var blockTags = /* @__PURE__ */ new Set([
32
32
  ]);
33
33
  var passthroughInlineTags = /* @__PURE__ */ new Set(["abbr", "bdi", "bdo", "cite", "data", "dfn", "kbd", "mark", "q", "samp", "small", "sub", "sup", "time", "u", "var"]);
34
34
  function htmlToMarkdown(html, options = {}) {
35
+ const resolved = {
36
+ unsupported: options.unsupported ?? "preserve",
37
+ plugins: options.plugins ?? []
38
+ };
39
+ const handlers = resolved.plugins.flatMap((plugin) => {
40
+ const handler = plugin?.htmlToMarkdown;
41
+ return handler ? Array.isArray(handler) ? handler : [handler] : [];
42
+ });
43
+ const ctx = { options: resolved, handlers, epilogue: [] };
35
44
  const root = parseHtml(html);
36
- return normalizeDocument(renderChildren(root.children, { unsupported: options.unsupported ?? "preserve" }, 0));
45
+ const body = normalizeDocument(renderChildren(root.children, ctx, 0));
46
+ return ctx.epilogue.length ? normalizeDocument(`${body}
47
+
48
+ ${ctx.epilogue.join("\n\n")}`) : body;
49
+ }
50
+ function buildHandlerContext(node, ctx, depth) {
51
+ return {
52
+ options: ctx.options,
53
+ inline: (target) => normalizeInline(renderChildren(childrenOf(target), ctx, depth)),
54
+ block: (target) => normalizeDocument(renderChildren(childrenOf(target), ctx, depth)),
55
+ text: (target) => textContent(target),
56
+ indent: (value, size) => indentContinuation(value, size),
57
+ epilogue: (value) => {
58
+ if (value)
59
+ ctx.epilogue.push(value);
60
+ }
61
+ };
62
+ }
63
+ function childrenOf(node) {
64
+ return node.type === "element" || node.type === "root" ? node.children : [];
37
65
  }
38
66
  function parseHtml(html) {
39
67
  const root = { type: "root", children: [] };
@@ -135,25 +163,31 @@ function hasUnsupportedAttrs(node) {
135
163
  function slugifyText(value) {
136
164
  return value.toLowerCase().replace(/[^\w\s-]/g, "").replace(/[\s_]+/g, "-").replace(/^-+|-+$/g, "");
137
165
  }
138
- function renderChildren(nodes, options, depth) {
139
- return nodes.map((node) => renderNode(node, options, depth)).join("");
166
+ function renderChildren(nodes, ctx, depth) {
167
+ return nodes.map((node) => renderNode(node, ctx, depth)).join("");
140
168
  }
141
- function renderNode(node, options, depth) {
169
+ function renderNode(node, ctx, depth) {
142
170
  if (node.type === "text")
143
171
  return collapseText(decodeEntities(node.value));
144
172
  if (node.type === "comment")
145
- return options.unsupported === "preserve" ? node.value : " ";
173
+ return ctx.options.unsupported === "preserve" ? node.value : " ";
146
174
  if (node.type === "root")
147
- return renderChildren(node.children, options, depth);
148
- const inline = () => normalizeInline(renderChildren(node.children, options, depth));
149
- const block = () => normalizeDocument(renderChildren(node.children, options, depth));
150
- if (options.unsupported === "preserve" && hasUnsupportedAttrs(node)) {
175
+ return renderChildren(node.children, ctx, depth);
176
+ const inline = () => normalizeInline(renderChildren(node.children, ctx, depth));
177
+ const block = () => normalizeDocument(renderChildren(node.children, ctx, depth));
178
+ for (const handler of ctx.handlers) {
179
+ const result = handler(node, buildHandlerContext(node, ctx, depth));
180
+ if (result !== void 0) {
181
+ return blockTags.has(node.tag) ? blockWrap(result) : result;
182
+ }
183
+ }
184
+ if (ctx.options.unsupported === "preserve" && hasUnsupportedAttrs(node)) {
151
185
  return blockTags.has(node.tag) ? blockWrap(node.raw) : node.raw;
152
186
  }
153
187
  if (/^h[1-6]$/.test(node.tag))
154
188
  return blockWrap(`${"#".repeat(Number(node.tag[1]))} ${inline()}`);
155
189
  if (node.tag === "p")
156
- return blockWrap(normalizeParagraph(renderChildren(node.children, options, depth)));
190
+ return blockWrap(normalizeParagraph(renderChildren(node.children, ctx, depth)));
157
191
  if (node.tag === "br")
158
192
  return " \n";
159
193
  if (node.tag === "hr")
@@ -175,13 +209,13 @@ function renderNode(node, options, depth) {
175
209
  if (node.tag === "blockquote")
176
210
  return blockWrap(prefixLines(block(), "> "));
177
211
  if (node.tag === "ul" || node.tag === "ol")
178
- return blockWrap(renderList(node, options, depth));
212
+ return blockWrap(renderList(node, ctx, depth));
179
213
  if (node.tag === "li")
180
214
  return inline();
181
215
  if (node.tag === "table") {
182
- if (options.unsupported === "preserve" && tableHasUnsupportedStructure(node))
216
+ if (ctx.options.unsupported === "preserve" && tableHasUnsupportedStructure(node))
183
217
  return blockWrap(node.raw);
184
- return blockWrap(renderTable(node, options));
218
+ return blockWrap(renderTable(node, ctx));
185
219
  }
186
220
  if (node.tag === "thead" || node.tag === "tbody" || node.tag === "tr" || node.tag === "th" || node.tag === "td")
187
221
  return inline();
@@ -190,9 +224,9 @@ function renderNode(node, options, depth) {
190
224
  if (node.tag === "span")
191
225
  return inline();
192
226
  if (passthroughInlineTags.has(node.tag) || blockTags.has(node.tag)) {
193
- return options.unsupported === "preserve" ? node.raw : block();
227
+ return ctx.options.unsupported === "preserve" ? node.raw : block();
194
228
  }
195
- return options.unsupported === "preserve" ? node.raw : inline();
229
+ return ctx.options.unsupported === "preserve" ? node.raw : inline();
196
230
  }
197
231
  function renderLink(node, text) {
198
232
  const href = node.attrs.href;
@@ -225,19 +259,19 @@ function renderCodeBlock(node) {
225
259
  ${code}
226
260
  ${fence}`;
227
261
  }
228
- function renderList(node, options, depth) {
262
+ function renderList(node, ctx, depth) {
229
263
  return node.children.filter((child) => child.type === "element" && child.tag === "li").map((item, index) => {
230
264
  const marker = node.tag === "ol" ? `${Number(node.attrs.start ?? 1) + index}. ` : "- ";
231
- const body = normalizeListItemBody(renderChildren(item.children, options, depth + 1));
265
+ const body = normalizeListItemBody(renderChildren(item.children, ctx, depth + 1));
232
266
  return indentContinuation(`${marker}${body}`, marker.length);
233
267
  }).join("\n");
234
268
  }
235
- function renderTable(node, options) {
269
+ function renderTable(node, ctx) {
236
270
  const rows = collectRows(node).map((row) => row.children.filter((cell) => cell.type === "element" && (cell.tag === "th" || cell.tag === "td"))).filter((row) => row.length > 0);
237
271
  if (rows.length === 0)
238
272
  return "";
239
273
  const width = Math.max(...rows.map((row) => row.length));
240
- const values = rows.map((row) => Array.from({ length: width }, (_, index) => normalizeInline(renderChildren(row[index]?.children ?? [], options, 0))));
274
+ const values = rows.map((row) => Array.from({ length: width }, (_, index) => normalizeInline(renderChildren(row[index]?.children ?? [], ctx, 0))));
241
275
  const header = values[0];
242
276
  const separator = Array.from({ length: width }, () => "---");
243
277
  const body = values.slice(1);
@@ -334,6 +368,20 @@ function longestRun(value, char) {
334
368
  }
335
369
  return longest;
336
370
  }
371
+ var htmlChildElements = (node) => node.type === "element" || node.type === "root" ? node.children.filter((child) => child.type === "element") : [];
372
+ var findHtmlElement = (node, predicate) => htmlChildElements(node).find(predicate);
373
+ var hasHtmlClass = (node, className) => node.type === "element" && (node.attrs.class ?? "").split(/\s+/).includes(className);
374
+ var findHtmlDescendant = (node, predicate) => {
375
+ for (const child of htmlChildElements(node)) {
376
+ if (predicate(child))
377
+ return child;
378
+ const nested = findHtmlDescendant(child, predicate);
379
+ if (nested)
380
+ return nested;
381
+ }
382
+ return void 0;
383
+ };
384
+ var htmlRoot = (children) => ({ type: "root", children });
337
385
 
338
386
  // ../nizel/dist/collect.js
339
387
  function collect(ast) {
@@ -4845,7 +4893,22 @@ var abbrPlugin = (options = {}) => {
4845
4893
  afterParse(ast) {
4846
4894
  return replaceAbbreviations(ast, definitions);
4847
4895
  }
4848
- }
4896
+ },
4897
+ htmlToMarkdown: abbrToMarkdown()
4898
+ };
4899
+ };
4900
+ var abbrToMarkdown = () => {
4901
+ const seen = /* @__PURE__ */ new Set();
4902
+ return (node, ctx) => {
4903
+ if (node.type !== "element" || node.tag !== "abbr")
4904
+ return void 0;
4905
+ const term = ctx.text(node).trim();
4906
+ const title = node.attrs.title;
4907
+ if (title !== void 0 && term && !seen.has(term)) {
4908
+ seen.add(term);
4909
+ ctx.epilogue(`*[${term}]: ${title}`);
4910
+ }
4911
+ return term;
4849
4912
  };
4850
4913
  };
4851
4914
  var extractAbbreviations = (markdown) => {
@@ -4913,7 +4976,25 @@ var alertPlugin = (options = {}) => {
4913
4976
  blocks,
4914
4977
  hooks: {
4915
4978
  beforeParse: transformGitHubAlerts
4916
- }
4979
+ },
4980
+ htmlToMarkdown: alertToMarkdown(options)
4981
+ };
4982
+ };
4983
+ var alertToMarkdown = (options = {}) => {
4984
+ const rootClass = options.className ?? "alert";
4985
+ return (node, ctx) => {
4986
+ if (node.type !== "element" || node.tag !== "div")
4987
+ return void 0;
4988
+ const type = node.attrs["data-alert"];
4989
+ if (!type)
4990
+ return void 0;
4991
+ const titleEl = findHtmlElement(node, (el) => el.tag === "p" && hasHtmlClass(el, `${rootClass}__title`));
4992
+ const contentEl = findHtmlElement(node, (el) => hasHtmlClass(el, `${rootClass}__content`));
4993
+ const title = titleEl ? ctx.text(titleEl).trim() : "";
4994
+ const body = contentEl ? ctx.block(contentEl) : "";
4995
+ return `${title ? `::${type} ${title}` : `::${type}`}
4996
+ ${body}
4997
+ ::`;
4917
4998
  };
4918
4999
  };
4919
5000
  var transformGitHubAlerts = (markdown) => {
@@ -4998,9 +5079,21 @@ var autolinkPlugin = (options = {}) => {
4998
5079
  name: "autolink",
4999
5080
  options: {
5000
5081
  autolinks: resolveAutolinkPluginOptions(options)
5001
- }
5082
+ },
5083
+ htmlToMarkdown: autolinkToMarkdown()
5002
5084
  });
5003
5085
  };
5086
+ var autolinkToMarkdown = () => (node, ctx) => {
5087
+ if (node.type !== "element" || node.tag !== "a")
5088
+ return void 0;
5089
+ const href = node.attrs.href;
5090
+ if (!href)
5091
+ return void 0;
5092
+ const extras = Object.keys(node.attrs).filter((key) => key !== "href" && key !== "target" && key !== "rel");
5093
+ if (extras.length > 0)
5094
+ return void 0;
5095
+ return ctx.text(node).trim() === href ? href : void 0;
5096
+ };
5004
5097
 
5005
5098
  // ../plugin-code-copy/dist/index.js
5006
5099
  var codeCopyPlugin = (options = {}) => {
@@ -5023,9 +5116,34 @@ var codeCopyPlugin = (options = {}) => {
5023
5116
  },
5024
5117
  hooks: {
5025
5118
  afterParse: wrapCodeBlocks
5026
- }
5119
+ },
5120
+ htmlToMarkdown: codeCopyToMarkdown()
5027
5121
  });
5028
5122
  };
5123
+ var codeCopyToMarkdown = () => (node, ctx) => {
5124
+ if (node.type !== "element" || node.tag !== "figure" || node.attrs["data-nizel-code-copy"] === void 0)
5125
+ return void 0;
5126
+ const source = findHtmlDescendant(node, (el) => el.tag === "textarea" && el.attrs["data-nizel-copy-source"] !== void 0);
5127
+ if (!source) {
5128
+ const body = htmlChildElements(node).filter((el) => !["figcaption", "textarea", "button"].includes(el.tag));
5129
+ return ctx.block(htmlRoot(body));
5130
+ }
5131
+ const code = ctx.text(source).replace(/\n$/, "");
5132
+ const pre = findHtmlDescendant(node, (el) => el.tag === "pre");
5133
+ const codeEl = pre ? findHtmlElement(pre, (el) => el.tag === "code") : void 0;
5134
+ const codeClass = codeEl?.attrs.class ?? "";
5135
+ const langClass = codeClass.match(/\blanguage-([A-Za-z0-9_-]+)/)?.[1];
5136
+ const isMermaid = findHtmlDescendant(node, (el) => el.tag === "div" && el.attrs.class === "mermaid") !== void 0;
5137
+ const lang = langClass ?? pre?.attrs["data-language"] ?? (isMermaid ? "mermaid" : "");
5138
+ const fence = fenceFor(code);
5139
+ return `${fence}${lang}
5140
+ ${code}
5141
+ ${fence}`;
5142
+ };
5143
+ var fenceFor = (code) => {
5144
+ const longest = Math.max(0, ...(code.match(/`+/g) ?? []).map((run) => run.length));
5145
+ return "`".repeat(Math.max(3, longest + 1));
5146
+ };
5029
5147
  var wrapCodeBlocks = (ast) => ({
5030
5148
  ...ast,
5031
5149
  children: ast.children.map(wrapCodeBlock)
@@ -5102,7 +5220,28 @@ var citationsPlugin = (options = {}) => {
5102
5220
  afterParse(ast) {
5103
5221
  return appendBibliography(replaceCitationRefs(ast, citations), citations);
5104
5222
  }
5223
+ },
5224
+ htmlToMarkdown: citationsToMarkdown(options)
5225
+ };
5226
+ };
5227
+ var citationsToMarkdown = (options = {}) => {
5228
+ const className = options.className ?? "citations";
5229
+ return (node, ctx) => {
5230
+ if (node.type !== "element")
5231
+ return void 0;
5232
+ if (node.tag === "a" && hasHtmlClass(node, "citation")) {
5233
+ const match = /^#cite-(.+)$/.exec(node.attrs.href ?? "");
5234
+ return match ? `[@${match[1]}]` : void 0;
5235
+ }
5236
+ if (node.tag === "section" && hasHtmlClass(node, className)) {
5237
+ const list = findHtmlElement(node, (el) => el.tag === "ol");
5238
+ const items = list ? htmlChildElements(list).filter((el) => el.tag === "li") : [];
5239
+ return items.map((li) => {
5240
+ const id = /^cite-(.+)$/.exec(li.attrs.id ?? "")?.[1] ?? "";
5241
+ return `[@${id}]: ${ctx.block(htmlRoot(li.children)).trim()}`;
5242
+ }).join("\n");
5105
5243
  }
5244
+ return void 0;
5106
5245
  };
5107
5246
  };
5108
5247
  var extractCitations = (markdown) => {
@@ -5204,9 +5343,28 @@ var deflistPlugin = (options = {}) => {
5204
5343
  beforeParse(markdown) {
5205
5344
  return transformDefinitionLists(markdown, options);
5206
5345
  }
5207
- }
5346
+ },
5347
+ htmlToMarkdown: deflistToMarkdown(options)
5208
5348
  };
5209
5349
  };
5350
+ var deflistToMarkdown = (options = {}) => (node, ctx) => {
5351
+ if (node.type !== "element" || node.tag !== "dl")
5352
+ return void 0;
5353
+ if (options.className) {
5354
+ if (node.attrs.class !== options.className)
5355
+ return void 0;
5356
+ } else if (node.attrs.class !== void 0) {
5357
+ return void 0;
5358
+ }
5359
+ const lines = [];
5360
+ for (const el of htmlChildElements(node)) {
5361
+ if (el.tag === "dt")
5362
+ lines.push(ctx.inline(el));
5363
+ else if (el.tag === "dd")
5364
+ lines.push(`: ${ctx.inline(el)}`);
5365
+ }
5366
+ return lines.join("\n");
5367
+ };
5210
5368
  var transformDefinitionLists = (markdown, _options = {}) => {
5211
5369
  const lines = markdown.split("\n");
5212
5370
  const result = [];
@@ -5293,8 +5451,23 @@ var detailsPlugin = (options = {}) => ({
5293
5451
  },
5294
5452
  hooks: {
5295
5453
  beforeParse: transformDetails
5296
- }
5454
+ },
5455
+ htmlToMarkdown: detailsToMarkdown(options)
5297
5456
  });
5457
+ var detailsToMarkdown = (options = {}) => {
5458
+ const className = options.className ?? "details";
5459
+ return (node, ctx) => {
5460
+ if (node.type !== "element" || node.tag !== "details" || node.attrs.class !== className)
5461
+ return void 0;
5462
+ const summaryEl = findHtmlElement(node, (el) => el.tag === "summary");
5463
+ const summary = summaryEl ? ctx.text(summaryEl).trim() : "Details";
5464
+ const body = ctx.block(htmlRoot(htmlChildElements(node).filter((el) => el.tag !== "summary"))).trim();
5465
+ return body ? `::details ${summary}
5466
+ ${body}
5467
+ ::` : `::details ${summary}
5468
+ ::`;
5469
+ };
5470
+ };
5298
5471
  var transformDetails = (markdown) => {
5299
5472
  return markdown.replace(/^:::details\s*(.*?)\s*$/gm, (_match, summary) => `::details ${summary || "Details"}`).replace(/^:::\s*$/gm, "::");
5300
5473
  };
@@ -5324,7 +5497,19 @@ var diagramsPlugin = (options = {}) => {
5324
5497
  afterParse(ast) {
5325
5498
  return transformDiagramCodes(ast, options);
5326
5499
  }
5327
- }
5500
+ },
5501
+ htmlToMarkdown: diagramsToMarkdown(options)
5502
+ };
5503
+ };
5504
+ var diagramsToMarkdown = (options = {}) => {
5505
+ const className = options.className ?? "mermaid";
5506
+ return (node, ctx) => {
5507
+ if (node.type !== "element" || node.tag !== "div" || node.attrs.class !== className)
5508
+ return void 0;
5509
+ const code = ctx.text(node).trim();
5510
+ return `\`\`\`mermaid
5511
+ ${code}
5512
+ \`\`\``;
5328
5513
  };
5329
5514
  };
5330
5515
  var transformDiagramCodes = (ast, options = {}) => ({
@@ -5469,9 +5654,45 @@ var footnotesPlugin = (options = {}) => {
5469
5654
  hooks: {
5470
5655
  beforeParse: transformFootnotes,
5471
5656
  afterParse: transformFootnoteRefs
5657
+ },
5658
+ htmlToMarkdown: footnotesToMarkdown(options)
5659
+ };
5660
+ };
5661
+ var footnotesToMarkdown = (options = {}) => {
5662
+ const className = options.className ?? "footnotes";
5663
+ return (node, ctx) => {
5664
+ if (node.type !== "element")
5665
+ return void 0;
5666
+ if (node.tag === "sup") {
5667
+ const anchor = findHtmlElement(node, (el) => el.tag === "a" && hasHtmlClass(el, "footnote-ref"));
5668
+ const match = anchor ? /^#fn-(.+)$/.exec(anchor.attrs.href ?? "") : void 0;
5669
+ return match ? `[^${match[1]}]` : void 0;
5670
+ }
5671
+ if (node.tag === "section" && hasHtmlClass(node, className)) {
5672
+ const list = findHtmlElement(node, (el) => el.tag === "ol");
5673
+ const items = list ? htmlChildElements(list).filter((el) => el.tag === "li") : [];
5674
+ return items.map((li) => {
5675
+ const id = /^fn-(.+)$/.exec(li.attrs.id ?? "")?.[1] ?? "";
5676
+ const body = ctx.block(htmlRoot(stripBackrefs(li.children, className))).trim();
5677
+ return `[^${id}]: ${body}`;
5678
+ }).join("\n");
5472
5679
  }
5680
+ return void 0;
5473
5681
  };
5474
5682
  };
5683
+ var stripBackrefs = (nodes, className) => {
5684
+ const result = [];
5685
+ for (const node of nodes) {
5686
+ if (node.type === "element") {
5687
+ if (hasHtmlClass(node, `${className}__backref`))
5688
+ continue;
5689
+ result.push({ ...node, children: stripBackrefs(node.children, className) });
5690
+ } else {
5691
+ result.push(node);
5692
+ }
5693
+ }
5694
+ return result;
5695
+ };
5475
5696
  var transformFootnotes = (markdown) => {
5476
5697
  const lines = markdown.split("\n");
5477
5698
  const body = [];
@@ -5617,9 +5838,46 @@ var taskListPlugin = (options = {}) => {
5617
5838
  afterParse(ast) {
5618
5839
  return transformTaskLists(ast, mode);
5619
5840
  }
5620
- }
5841
+ },
5842
+ htmlToMarkdown: taskListToMarkdown()
5621
5843
  };
5622
5844
  };
5845
+ var CHECKBOX_ATTR = "data-nizel-task-checkbox";
5846
+ var isCheckbox = (node) => node.type === "element" && node.tag === "input" && node.attrs?.[CHECKBOX_ATTR] !== void 0;
5847
+ var taskListToMarkdown = () => (node, ctx) => {
5848
+ if (node.type !== "element" || node.tag !== "ul" && node.tag !== "ol")
5849
+ return void 0;
5850
+ const items = htmlChildElements(node).filter((el) => el.tag === "li");
5851
+ if (!items.some((item) => findHtmlDescendant(item, isCheckbox)))
5852
+ return void 0;
5853
+ const ordered = node.tag === "ol";
5854
+ const start = Number(node.attrs.start ?? 1);
5855
+ return items.map((item, index) => {
5856
+ const checkbox = findHtmlDescendant(item, isCheckbox);
5857
+ const body = ctx.block(htmlRoot(cleanTaskChildren(item.children))).trim();
5858
+ const baseMarker = ordered ? `${start + index}. ` : "- ";
5859
+ const marker = checkbox ? `${baseMarker}${checkbox.attrs.checked !== void 0 ? "[x] " : "[ ] "}` : baseMarker;
5860
+ return ctx.indent(`${marker}${body}`, marker.length);
5861
+ }).join("\n");
5862
+ };
5863
+ var cleanTaskChildren = (nodes) => {
5864
+ const result = [];
5865
+ for (const node of nodes) {
5866
+ if (node.type === "element") {
5867
+ if (isCheckbox(node))
5868
+ continue;
5869
+ const cleaned = { ...node, children: cleanTaskChildren(node.children) };
5870
+ if (hasHtmlClass(cleaned, "nizel-task-list__label") || hasHtmlClass(cleaned, "nizel-task-list__content")) {
5871
+ result.push(...cleaned.children);
5872
+ } else {
5873
+ result.push(cleaned);
5874
+ }
5875
+ } else {
5876
+ result.push(node);
5877
+ }
5878
+ }
5879
+ return result;
5880
+ };
5623
5881
  var transformTaskLists = (ast, mode = "view") => {
5624
5882
  return { ...ast, children: ast.children.map((node) => transformBlock5(node, mode)) };
5625
5883
  };
@@ -5701,8 +5959,17 @@ var headingAnchorsPlugin = (options = {}) => ({
5701
5959
  afterParse(ast) {
5702
5960
  return addHeadingAnchors(ast, options);
5703
5961
  }
5704
- }
5962
+ },
5963
+ htmlToMarkdown: headingAnchorsToMarkdown(options)
5705
5964
  });
5965
+ var headingAnchorsToMarkdown = (options = {}) => {
5966
+ const className = options.className ?? "heading-anchor";
5967
+ return (node) => {
5968
+ if (node.type !== "element" || node.tag !== "a" || !hasHtmlClass(node, className))
5969
+ return void 0;
5970
+ return "";
5971
+ };
5972
+ };
5706
5973
  var addHeadingAnchors = (ast, options = {}) => ({
5707
5974
  ...ast,
5708
5975
  children: ast.children.map((node) => {
@@ -5734,7 +6001,26 @@ var mathPlugin = (options = {}) => {
5734
6001
  afterParse(ast) {
5735
6002
  return transformInlineMath(ast, options);
5736
6003
  }
6004
+ },
6005
+ htmlToMarkdown: mathToMarkdown(options)
6006
+ };
6007
+ };
6008
+ var mathToMarkdown = (options = {}) => {
6009
+ const inlineTokens = (options.inlineClassName ?? "math math-inline").split(/\s+/);
6010
+ const blockTokens = (options.blockClassName ?? "math math-display").split(/\s+/);
6011
+ return (node, ctx) => {
6012
+ if (node.type !== "element")
6013
+ return void 0;
6014
+ const tokens = node.attrs.class ? node.attrs.class.split(/\s+/) : [];
6015
+ if (node.tag === "span" && inlineTokens.every((token) => tokens.includes(token))) {
6016
+ return `$${ctx.text(node)}$`;
6017
+ }
6018
+ if (node.tag === "div" && blockTokens.every((token) => tokens.includes(token))) {
6019
+ return `$$
6020
+ ${ctx.text(node).trim()}
6021
+ $$`;
5737
6022
  }
6023
+ return void 0;
5738
6024
  };
5739
6025
  };
5740
6026
  var transformBlockMath = (markdown) => {
@@ -5837,8 +6123,26 @@ var mediaPlugin = (options = {}) => ({
5837
6123
  afterRender(html) {
5838
6124
  return enhanceImages(html, options);
5839
6125
  }
5840
- }
6126
+ },
6127
+ htmlToMarkdown: mediaToMarkdown(options)
5841
6128
  });
6129
+ var mediaToMarkdown = (options = {}) => {
6130
+ const className = options.figureClassName ?? "media-figure";
6131
+ return (node, ctx) => {
6132
+ if (node.type !== "element" || node.tag !== "figure" || node.attrs.class !== className)
6133
+ return void 0;
6134
+ const img = findHtmlElement(node, (el) => el.tag === "img");
6135
+ if (!img)
6136
+ return void 0;
6137
+ const attrs = { src: img.attrs.src ?? "" };
6138
+ if (img.attrs.alt !== void 0)
6139
+ attrs.alt = img.attrs.alt;
6140
+ if (img.attrs.title !== void 0)
6141
+ attrs.title = img.attrs.title;
6142
+ const cleanImg = { type: "element", tag: "img", attrs, children: [], raw: "" };
6143
+ return ctx.inline({ type: "root", children: [cleanImg] });
6144
+ };
6145
+ };
5842
6146
  var enhanceImages = (html, options = {}) => {
5843
6147
  const lazy = options.lazy !== false;
5844
6148
  const responsive = options.responsive !== false;
@@ -5901,9 +6205,30 @@ var shikiPlugin = (options = {}) => {
5901
6205
  }
5902
6206
  }
5903
6207
  }
5904
- }
6208
+ },
6209
+ htmlToMarkdown: shikiToMarkdown()
5905
6210
  });
5906
6211
  };
6212
+ var shikiToMarkdown = () => (node, ctx) => {
6213
+ if (node.type !== "element" || node.tag !== "pre")
6214
+ return void 0;
6215
+ const isShiki = hasHtmlClass(node, "shiki") || node.attrs.style !== void 0 || node.attrs["data-language"] !== void 0;
6216
+ if (!isShiki)
6217
+ return void 0;
6218
+ const codeEl = findHtmlElement(node, (el) => el.tag === "code");
6219
+ const codeClass = codeEl?.attrs.class ?? "";
6220
+ const langClass = codeClass.match(/\blanguage-([A-Za-z0-9_-]+)/)?.[1];
6221
+ const lang = node.attrs["data-language"] ?? langClass ?? "";
6222
+ const code = ctx.text(node).replace(/\n$/, "");
6223
+ const fence = fenceFor2(code);
6224
+ return `${fence}${lang}
6225
+ ${code}
6226
+ ${fence}`;
6227
+ };
6228
+ var fenceFor2 = (code) => {
6229
+ const longest = Math.max(0, ...(code.match(/`+/g) ?? []).map((run) => run.length));
6230
+ return "`".repeat(Math.max(3, longest + 1));
6231
+ };
5907
6232
  var fallback = (node, ctx) => {
5908
6233
  const language = node.lang ? ` class="language-${ctx.escape(node.lang)}"` : "";
5909
6234
  return `<pre><code${language}>${ctx.escape(node.code)}</code></pre>`;
@@ -5922,8 +6247,17 @@ var tocPlugin = (options = {}) => ({
5922
6247
  afterParse(ast) {
5923
6248
  return fillTocBlocks(ast, options);
5924
6249
  }
5925
- }
6250
+ },
6251
+ htmlToMarkdown: tocToMarkdown(options)
5926
6252
  });
6253
+ var tocToMarkdown = (options = {}) => {
6254
+ const className = options.className ?? "toc";
6255
+ return (node) => {
6256
+ if (node.type !== "element" || node.tag !== "nav" || !hasHtmlClass(node, className))
6257
+ return void 0;
6258
+ return "[[toc]]";
6259
+ };
6260
+ };
5927
6261
  var fillTocBlocks = (ast, options = {}) => {
5928
6262
  const minDepth = options.minDepth ?? 2;
5929
6263
  const maxDepth = options.maxDepth ?? 6;
@@ -5957,7 +6291,24 @@ var typographyPlugin = (options = {}) => {
5957
6291
  afterParse(ast) {
5958
6292
  return transformTypography(ast, options);
5959
6293
  }
5960
- }
6294
+ },
6295
+ htmlToMarkdown: typographyToMarkdown(options)
6296
+ };
6297
+ };
6298
+ var typographyToMarkdown = (options = {}) => {
6299
+ const mark = options.mark !== false;
6300
+ const subscript = options.subscript !== false;
6301
+ const superscript = options.superscript !== false;
6302
+ return (node, ctx) => {
6303
+ if (node.type !== "element" || Object.keys(node.attrs).length > 0)
6304
+ return void 0;
6305
+ if (node.tag === "mark" && mark)
6306
+ return `==${ctx.inline(node)}==`;
6307
+ if (node.tag === "sub" && subscript)
6308
+ return `~${ctx.inline(node)}~`;
6309
+ if (node.tag === "sup" && superscript)
6310
+ return `^${ctx.inline(node)}^`;
6311
+ return void 0;
5961
6312
  };
5962
6313
  };
5963
6314
  var transformTypography = (ast, options = {}) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nizel-kit",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "Browser and native WebView integration kit for Nizel with official plugin registry.",
5
5
  "repository": {
6
6
  "type": "git",