@vectojs/markdown 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -423,7 +423,259 @@ function createStreamController(host, options = {}) {
423
423
  }
424
424
 
425
425
  // src/Markdown.ts
426
- import { measureText, RichText, Stack, Table, Text, Image, UIComponent } from "@vectojs/ui";
426
+ import {
427
+ measureText as measureText2,
428
+ RichText,
429
+ Stack,
430
+ Table,
431
+ Text,
432
+ Image,
433
+ UIComponent as UIComponent2
434
+ } from "@vectojs/ui";
435
+
436
+ // src/blockAffordances.ts
437
+ import { Button, measureText, UIComponent } from "@vectojs/ui";
438
+ var LANGUAGE_EXTENSIONS = {
439
+ bash: "sh",
440
+ c: "c",
441
+ cpp: "cpp",
442
+ cs: "cs",
443
+ css: "css",
444
+ diff: "diff",
445
+ dockerfile: "dockerfile",
446
+ go: "go",
447
+ graphql: "graphql",
448
+ haskell: "hs",
449
+ html: "html",
450
+ java: "java",
451
+ javascript: "js",
452
+ js: "js",
453
+ json: "json",
454
+ jsonc: "jsonc",
455
+ jsx: "jsx",
456
+ kotlin: "kt",
457
+ latex: "tex",
458
+ lua: "lua",
459
+ make: "mk",
460
+ markdown: "md",
461
+ md: "md",
462
+ nix: "nix",
463
+ php: "php",
464
+ python: "py",
465
+ py: "py",
466
+ ruby: "rb",
467
+ rust: "rs",
468
+ rs: "rs",
469
+ scss: "scss",
470
+ sh: "sh",
471
+ shell: "sh",
472
+ sql: "sql",
473
+ svelte: "svelte",
474
+ swift: "swift",
475
+ tex: "tex",
476
+ toml: "toml",
477
+ ts: "ts",
478
+ tsx: "tsx",
479
+ typescript: "ts",
480
+ vue: "vue",
481
+ xml: "xml",
482
+ yaml: "yaml",
483
+ yml: "yaml",
484
+ zig: "zig",
485
+ zsh: "sh"
486
+ };
487
+ function extensionForLanguage(lang) {
488
+ const first = lang.trim().toLowerCase().split(/[\s:,{]/)[0] ?? "";
489
+ return LANGUAGE_EXTENSIONS[first] ?? "txt";
490
+ }
491
+ function mimeForLanguage(lang) {
492
+ const ext = extensionForLanguage(lang);
493
+ if (ext === "json" || ext === "jsonc") return "application/json";
494
+ if (ext === "html") return "text/html";
495
+ if (ext === "css") return "text/css";
496
+ if (ext === "xml" || ext === "svelte" || ext === "vue") return "text/plain";
497
+ return "text/plain";
498
+ }
499
+ function escapeCsvField(value) {
500
+ let needsQuoting = false;
501
+ let hasQuote = false;
502
+ for (const char of value) {
503
+ if (char === '"') {
504
+ hasQuote = true;
505
+ needsQuoting = true;
506
+ break;
507
+ }
508
+ if (char === "," || char === "\n" || char === "\r") needsQuoting = true;
509
+ }
510
+ if (!needsQuoting) return value;
511
+ return hasQuote ? `"${value.replace(/"/g, '""')}"` : `"${value}"`;
512
+ }
513
+ function escapeMarkdownTableCell(cell) {
514
+ let needsEscaping = false;
515
+ for (const char of cell) {
516
+ if (char === "\\" || char === "|") {
517
+ needsEscaping = true;
518
+ break;
519
+ }
520
+ }
521
+ if (!needsEscaping) return cell;
522
+ return cell.replace(/\\/g, "\\\\").replace(/\|/g, "\\|");
523
+ }
524
+ function tableToCsv(table) {
525
+ const lines = [table.headers.map(escapeCsvField).join(",")];
526
+ for (const row of table.rows) lines.push(row.map(escapeCsvField).join(","));
527
+ return `\uFEFF${lines.join("\r\n")}`;
528
+ }
529
+ function tableToMarkdown(table) {
530
+ const header = `| ${table.headers.map(escapeMarkdownTableCell).join(" | ")} |`;
531
+ const divider = `| ${table.headers.map((_cell, index) => {
532
+ switch (table.align[index]) {
533
+ case "left":
534
+ return ":---";
535
+ case "center":
536
+ return ":---:";
537
+ case "right":
538
+ return "---:";
539
+ default:
540
+ return "---";
541
+ }
542
+ }).join(" | ")} |`;
543
+ const body = table.rows.map(
544
+ (row) => `| ${table.headers.map((_cell, index) => escapeMarkdownTableCell(row[index] ?? "")).join(" | ")} |`
545
+ );
546
+ return [header, divider, ...body].join("\n");
547
+ }
548
+ function defaultWriteClipboard(text) {
549
+ const clipboard = globalThis.navigator?.clipboard;
550
+ clipboard?.writeText?.(text);
551
+ }
552
+ function defaultSaveFile(filename, content, mimeType) {
553
+ const doc = globalThis.document;
554
+ if (!doc?.body) return;
555
+ const blob = new Blob([content], { type: mimeType });
556
+ const url = URL.createObjectURL(blob);
557
+ const anchor = doc.createElement("a");
558
+ anchor.href = url;
559
+ anchor.download = filename;
560
+ doc.body.appendChild(anchor);
561
+ anchor.click();
562
+ doc.body.removeChild(anchor);
563
+ URL.revokeObjectURL(url);
564
+ }
565
+ var BlockAffordanceButton = class _BlockAffordanceButton extends Button {
566
+ constructor(label, successLabel, act, opts = {}) {
567
+ super(label, { ...opts, onClick: () => this.run() });
568
+ this.act = act;
569
+ this.restingLabel = label;
570
+ this.successLabel = successLabel;
571
+ this.width = Math.max(this.width, measureText(successLabel, this.font) + 24);
572
+ }
573
+ act;
574
+ /** How long the confirmation label stays up, in ms. */
575
+ static FEEDBACK_MS = 1600;
576
+ restingLabel;
577
+ successLabel;
578
+ feedbackTimer;
579
+ /**
580
+ * Runs the action, then shows the confirmation.
581
+ *
582
+ * The action runs first and a throw propagates: a clipboard write the browser
583
+ * rejected must not be reported as a success.
584
+ */
585
+ run() {
586
+ this.act();
587
+ this.setTransientLabel(this.successLabel);
588
+ if (this.feedbackTimer !== void 0) clearTimeout(this.feedbackTimer);
589
+ this.feedbackTimer = setTimeout(() => {
590
+ this.setTransientLabel(this.restingLabel);
591
+ this.feedbackTimer = void 0;
592
+ }, _BlockAffordanceButton.FEEDBACK_MS);
593
+ }
594
+ setTransientLabel(label) {
595
+ this.label = label;
596
+ this.textWidth = measureText(label, this.font);
597
+ this.scene?.markDirty();
598
+ }
599
+ /**
600
+ * The label a reader hears is the one they see, transient confirmation
601
+ * included, so an AT user gets the same feedback a sighted user does.
602
+ */
603
+ getA11yAttributes() {
604
+ return { ...super.getA11yAttributes(), label: this.label };
605
+ }
606
+ /** Clears the pending revert so a destroyed block leaves no timer behind. */
607
+ destroy() {
608
+ if (this.feedbackTimer !== void 0) {
609
+ clearTimeout(this.feedbackTimer);
610
+ this.feedbackTimer = void 0;
611
+ }
612
+ super.destroy();
613
+ }
614
+ };
615
+ var BlockWithAffordances = class _BlockWithAffordances extends UIComponent {
616
+ constructor(block, controls) {
617
+ super();
618
+ this.block = block;
619
+ this.controls = controls;
620
+ this.add(block);
621
+ for (const control of controls) this.add(control);
622
+ this.layoutAffordances();
623
+ }
624
+ block;
625
+ controls;
626
+ /** Gap between the block's edges and the controls, in px. */
627
+ static INSET = 8;
628
+ /** Gap between adjacent controls, in px. */
629
+ static GAP = 6;
630
+ /**
631
+ * Places the controls right-aligned along the block's top edge.
632
+ *
633
+ * Laid out right-to-left from the block's right edge so the first control in
634
+ * the list ends up leftmost, which keeps DOM order (and therefore tab order and
635
+ * the a11y reading order) matching the visual order.
636
+ */
637
+ layoutAffordances() {
638
+ this.width = this.block.width;
639
+ this.height = this.block.height;
640
+ let right = this.block.width - _BlockWithAffordances.INSET;
641
+ for (let i = this.controls.length - 1; i >= 0; i--) {
642
+ const control = this.controls[i];
643
+ control.x = right - control.width;
644
+ control.y = _BlockWithAffordances.INSET;
645
+ right = control.x - _BlockWithAffordances.GAP;
646
+ }
647
+ }
648
+ /**
649
+ * Re-places the controls after the block's own box changed.
650
+ *
651
+ * Called by the owner when a block is resized or its content grew; the controls
652
+ * are anchored to the right edge, so a width change moves them.
653
+ */
654
+ refreshAffordances() {
655
+ this.layoutAffordances();
656
+ this.scene?.markDirty();
657
+ }
658
+ /** The wrapper is a pass-through: its size is the block's size. */
659
+ getLayoutControlledProperties() {
660
+ return ["x", "y"];
661
+ }
662
+ /**
663
+ * Projected as a group so assistive technology reports one labelled region
664
+ * containing the block and its controls, rather than two unrelated siblings.
665
+ */
666
+ getA11yAttributes() {
667
+ return { role: "group", pointerEvents: "none" };
668
+ }
669
+ render() {
670
+ }
671
+ };
672
+ function tableContentOf(token) {
673
+ return {
674
+ headers: token.header.map((cell) => cell.text),
675
+ rows: token.rows.map((row) => row.map((cell) => cell.text)),
676
+ align: token.align
677
+ };
678
+ }
427
679
 
428
680
  // src/frontMatter.ts
429
681
  var OPEN_RE = /^---[ \t]*\r?\n/;
@@ -668,7 +920,59 @@ function isFenceClosed(raw) {
668
920
  return false;
669
921
  }
670
922
  function paragraphHasImage(token) {
671
- return token.tokens?.some((child) => child.type === "image") === true;
923
+ return containsImage(token.tokens);
924
+ }
925
+ function containsImage(tokens) {
926
+ if (!tokens) return false;
927
+ for (const token of tokens) {
928
+ if (token.type === "image") return true;
929
+ if (containsImage(token.tokens)) return true;
930
+ }
931
+ return false;
932
+ }
933
+ function imagesOf(tokens) {
934
+ const images = [];
935
+ for (const token of tokens ?? []) {
936
+ if (token.type === "image") {
937
+ images.push(token);
938
+ continue;
939
+ }
940
+ images.push(...imagesOf(token.tokens));
941
+ }
942
+ return images;
943
+ }
944
+ function stripImages(token) {
945
+ const children = token.tokens;
946
+ if (!children) return token;
947
+ const kept = [];
948
+ for (const child of children) {
949
+ if (child.type === "image") continue;
950
+ const grandchildren = child.tokens;
951
+ if (grandchildren && containsImage(grandchildren)) {
952
+ const stripped = stripImages(child);
953
+ const remaining = stripped.tokens;
954
+ if (remaining && remaining.length > 0) kept.push(stripped);
955
+ continue;
956
+ }
957
+ kept.push(child);
958
+ }
959
+ return { ...token, tokens: kept };
960
+ }
961
+ function liftNestedImages(tokens) {
962
+ const lifted = [];
963
+ for (const token of tokens) {
964
+ if (token.type === "image") {
965
+ lifted.push(token);
966
+ continue;
967
+ }
968
+ const children = token.tokens;
969
+ if (children && containsImage(children)) {
970
+ lifted.push(...liftNestedImages(children));
971
+ continue;
972
+ }
973
+ lifted.push(token);
974
+ }
975
+ return lifted;
672
976
  }
673
977
  function lastIndexOfImage(tokens) {
674
978
  for (let i = tokens.length - 1; i >= 0; i--) {
@@ -679,7 +983,7 @@ function lastIndexOfImage(tokens) {
679
983
  function expectedImageParagraphChildren(tokens) {
680
984
  let children = 0;
681
985
  let inTextRun = false;
682
- for (const token of tokens) {
986
+ for (const token of liftNestedImages(tokens)) {
683
987
  if (token.type === "image") {
684
988
  children++;
685
989
  inTextRun = false;
@@ -1115,7 +1419,7 @@ function highlightLine(line, lang, theme) {
1115
1419
  flush(theme.codeColor);
1116
1420
  return segments;
1117
1421
  }
1118
- var CodeBlock = class extends UIComponent {
1422
+ var CodeBlock = class extends UIComponent2 {
1119
1423
  lines;
1120
1424
  grid = null;
1121
1425
  /** Raw (unhighlighted) lines of the last build, for prefix reuse in buildLines. */
@@ -1248,7 +1552,7 @@ var CodeBlock = class extends UIComponent {
1248
1552
  this.height = this.pad * 2 + rawLines.length * this.lineH;
1249
1553
  }
1250
1554
  ensureGrid() {
1251
- const cellWidth = this.cellWidth || Math.max(1, measureText("M", this.codeFont));
1555
+ const cellWidth = this.cellWidth || Math.max(1, measureText2("M", this.codeFont));
1252
1556
  if (!this.grid || this.grid.source !== this.source || this.grid.font !== this.codeFont || this.grid.cellWidth !== cellWidth) {
1253
1557
  this.grid = prepareContentGrid(this.source, {
1254
1558
  font: this.codeFont,
@@ -1526,12 +1830,24 @@ function renderInlineToRichText(tokens, fallbackText, font, color, maxWidth, the
1526
1830
  onLinkClick
1527
1831
  });
1528
1832
  }
1529
- var Markdown = class _Markdown extends UIComponent {
1833
+ var Markdown = class _Markdown extends UIComponent2 {
1530
1834
  content;
1531
1835
  maxWidth;
1532
1836
  theme;
1533
1837
  onLinkClick;
1534
1838
  selectable;
1839
+ /**
1840
+ * Whether code blocks and tables carry copy / download controls.
1841
+ *
1842
+ * Read when a block entity is built, so it affects blocks rendered from here on
1843
+ * rather than retroactively; a document does not rebuild to gain or lose an
1844
+ * affordance.
1845
+ */
1846
+ blockAffordances;
1847
+ /** Clipboard writer used by the copy controls. */
1848
+ writeClipboard;
1849
+ /** File saver used by the download controls. */
1850
+ saveFile;
1535
1851
  activeBlockMetrics = null;
1536
1852
  /**
1537
1853
  * Called after a streamed append has re-laid-out the document.
@@ -1756,6 +2072,9 @@ var Markdown = class _Markdown extends UIComponent {
1756
2072
  this.onLinkClick = opts.onLinkClick;
1757
2073
  this.selectable = opts.selectable ?? true;
1758
2074
  this._userTiming = opts.userTiming ?? false;
2075
+ this.blockAffordances = opts.blockAffordances ?? false;
2076
+ this.writeClipboard = opts.writeClipboard ?? defaultWriteClipboard;
2077
+ this.saveFile = opts.saveFile ?? defaultSaveFile;
1759
2078
  this.content = new Stack({ direction: "vertical", gap: 16 });
1760
2079
  this.add(this.content);
1761
2080
  this.rawMarkdown = "";
@@ -2544,6 +2863,73 @@ var Markdown = class _Markdown extends UIComponent {
2544
2863
  * policy for a zero-dimension source is a separate decision from notifying
2545
2864
  * the scene, which is the actual defect here.
2546
2865
  */
2866
+ /**
2867
+ * Wraps a block in its copy / download controls, or returns it untouched.
2868
+ *
2869
+ * The controls are built lazily through `make` so a document with
2870
+ * `blockAffordances` off pays nothing — not the closures, not the measurement
2871
+ * `BlockAffordanceButton` does in its constructor.
2872
+ */
2873
+ withBlockAffordances(block, make) {
2874
+ if (!this.blockAffordances) return block;
2875
+ const controls = make();
2876
+ return controls.length > 0 ? new BlockWithAffordances(block, controls) : block;
2877
+ }
2878
+ /** Copy and download controls for one fenced code block. */
2879
+ codeBlockAffordances(source, lang) {
2880
+ const opts = this.affordanceButtonOptions();
2881
+ return [
2882
+ new BlockAffordanceButton("Copy code", "Copied", () => this.writeClipboard(source), opts),
2883
+ new BlockAffordanceButton(
2884
+ "Download code",
2885
+ "Saved",
2886
+ () => this.saveFile(`code.${extensionForLanguage(lang)}`, source, mimeForLanguage(lang)),
2887
+ opts
2888
+ )
2889
+ ];
2890
+ }
2891
+ /** Copy (as Markdown) and download (as CSV) controls for one table. */
2892
+ tableAffordances(tblToken) {
2893
+ const content = tableContentOf(tblToken);
2894
+ const opts = this.affordanceButtonOptions();
2895
+ return [
2896
+ // Markdown rather than CSV for the clipboard: the reader copied it out of a
2897
+ // Markdown document and the overwhelmingly likely destination is another
2898
+ // one. CSV is what the download is for, where a spreadsheet is the target.
2899
+ new BlockAffordanceButton(
2900
+ "Copy table",
2901
+ "Copied",
2902
+ () => this.writeClipboard(tableToMarkdown(content)),
2903
+ opts
2904
+ ),
2905
+ new BlockAffordanceButton(
2906
+ "Download table",
2907
+ "Saved",
2908
+ () => this.saveFile("table.csv", tableToCsv(content), "text/csv;charset=utf-8"),
2909
+ opts
2910
+ )
2911
+ ];
2912
+ }
2913
+ /**
2914
+ * Button styling for the affordances, derived from the document theme.
2915
+ *
2916
+ * Themed rather than hardcoded so a light-theme document does not get the dark
2917
+ * default palette. `focusColor` is set explicitly from the theme's accent
2918
+ * because `Button`'s default cyan is tuned for the dark palette and reads as
2919
+ * off-brand elsewhere — while a focus ring is the one affordance a keyboard
2920
+ * user cannot do without.
2921
+ */
2922
+ affordanceButtonOptions() {
2923
+ return {
2924
+ font: `600 12px ${this.theme.bodyFont}`,
2925
+ padding: 6,
2926
+ radius: 6,
2927
+ bg: this.theme.codeBgColor,
2928
+ hoverBg: this.theme.tableHeaderBgColor,
2929
+ color: this.theme.textColor,
2930
+ focusColor: this.theme.codeColor
2931
+ };
2932
+ }
2547
2933
  paragraphImage(imgToken, availableWidth) {
2548
2934
  const initialWidth = Math.min(800, availableWidth);
2549
2935
  const initialHeight = Math.round(initialWidth * 0.6);
@@ -2624,6 +3010,7 @@ var Markdown = class _Markdown extends UIComponent {
2624
3010
  itemIsInlineOnly(item) {
2625
3011
  const children = item.tokens;
2626
3012
  if (!children || children.length === 0) return true;
3013
+ if (containsImage(children)) return false;
2627
3014
  if (children.length === 1 && children[0].type === "paragraph") return true;
2628
3015
  return children.every((child) => _Markdown.INLINE_ITEM_TOKENS.has(child.type));
2629
3016
  }
@@ -2653,7 +3040,10 @@ var Markdown = class _Markdown extends UIComponent {
2653
3040
  const children = item.tokens ?? [];
2654
3041
  const stack = new Stack({ direction: "vertical", gap: 4 });
2655
3042
  const first = children[0];
2656
- const leadChildren = first && (first.type === "text" || first.type === "paragraph") ? [first] : [];
3043
+ const firstIsInline = Boolean(first) && (first.type === "text" || first.type === "paragraph");
3044
+ const leadHasImage = firstIsInline && containsImage(first.tokens);
3045
+ const leadChildren = firstIsInline ? [leadHasImage ? stripImages(first) : first] : [];
3046
+ const leadImages = leadHasImage ? imagesOf(first.tokens) : [];
2657
3047
  const leadToken = {
2658
3048
  ...token,
2659
3049
  items: token.items.map((it, i) => i === index ? { ...it, tokens: leadChildren } : it)
@@ -2666,6 +3056,13 @@ var Markdown = class _Markdown extends UIComponent {
2666
3056
  indentStart: indent,
2667
3057
  availableWidth: Math.max(1, availableWidth - indent)
2668
3058
  };
3059
+ for (const image of leadImages) {
3060
+ const el = this.paragraphImage(image, childMetrics.availableWidth);
3061
+ const wrapper = new MarkdownContainer();
3062
+ el.x = indent;
3063
+ wrapper.add(el);
3064
+ stack.add(wrapper);
3065
+ }
2669
3066
  for (let i = leadChildren.length; i < children.length; i++) {
2670
3067
  const el = this.renderTokenWithMetrics(children[i], childMetrics);
2671
3068
  if (!el) continue;
@@ -3483,7 +3880,7 @@ var Markdown = class _Markdown extends UIComponent {
3483
3880
  currentTokens = [];
3484
3881
  }
3485
3882
  };
3486
- for (const child of pToken.tokens) {
3883
+ for (const child of liftNestedImages(pToken.tokens)) {
3487
3884
  if (child.type === "image") {
3488
3885
  flushText();
3489
3886
  stack.add(this.paragraphImage(child, availableWidth));
@@ -3511,7 +3908,10 @@ var Markdown = class _Markdown extends UIComponent {
3511
3908
  const mathBlock = this.renderDisplayMath(codeToken.text, availableWidth);
3512
3909
  if (mathBlock) return mathBlock;
3513
3910
  }
3514
- return new CodeBlock(codeToken.text, lang, availableWidth, t, this.selectable);
3911
+ return this.withBlockAffordances(
3912
+ new CodeBlock(codeToken.text, lang, availableWidth, t, this.selectable),
3913
+ () => this.codeBlockAffordances(codeToken.text, lang)
3914
+ );
3515
3915
  }
3516
3916
  // ── Blockquotes ──────────────────────────────────────────────────
3517
3917
  case "blockquote": {
@@ -3567,21 +3967,24 @@ var Markdown = class _Markdown extends UIComponent {
3567
3967
  const rows = tblToken.rows.map(
3568
3968
  (row) => row.map((cell) => this.tableCellRichText(cell, false, t))
3569
3969
  );
3570
- return new Table({
3571
- headers,
3572
- rows,
3573
- // `| :--- | :---: | ---: |` already resolves to this on the token; it
3574
- // was previously discarded, so every column rendered left-aligned.
3575
- align: tblToken.align,
3576
- width: availableWidth,
3577
- textColor: t.textColor,
3578
- headerTextColor: t.headingColor,
3579
- font: `${t.fontSize - 2}px ${t.bodyFont}`,
3580
- borderColor: t.hrColor,
3581
- bg: t.tableBgColor,
3582
- headerBg: t.tableHeaderBgColor,
3583
- selectable: this.selectable
3584
- });
3970
+ return this.withBlockAffordances(
3971
+ new Table({
3972
+ headers,
3973
+ rows,
3974
+ // `| :--- | :---: | ---: |` already resolves to this on the token; it
3975
+ // was previously discarded, so every column rendered left-aligned.
3976
+ align: tblToken.align,
3977
+ width: availableWidth,
3978
+ textColor: t.textColor,
3979
+ headerTextColor: t.headingColor,
3980
+ font: `${t.fontSize - 2}px ${t.bodyFont}`,
3981
+ borderColor: t.hrColor,
3982
+ bg: t.tableBgColor,
3983
+ headerBg: t.tableHeaderBgColor,
3984
+ selectable: this.selectable
3985
+ }),
3986
+ () => this.tableAffordances(tblToken)
3987
+ );
3585
3988
  }
3586
3989
  // ── Horizontal rule ──────────────────────────────────────────────
3587
3990
  case "hr":
@@ -3616,13 +4019,22 @@ var Markdown = class _Markdown extends UIComponent {
3616
4019
  }
3617
4020
  };
3618
4021
  export {
4022
+ BlockAffordanceButton,
4023
+ BlockWithAffordances,
3619
4024
  CodeBlock,
3620
4025
  Markdown,
3621
4026
  MathBlock,
3622
4027
  codeAtlas,
3623
4028
  codeAtlasStats,
4029
+ escapeCsvField,
4030
+ escapeMarkdownTableCell,
4031
+ extensionForLanguage,
3624
4032
  isMathJaxReady,
4033
+ mimeForLanguage,
3625
4034
  parseFrontMatterFields,
3626
4035
  preloadMathJax,
3627
- scanFrontMatter
4036
+ scanFrontMatter,
4037
+ tableContentOf,
4038
+ tableToCsv,
4039
+ tableToMarkdown
3628
4040
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectojs/markdown",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -40,7 +40,7 @@
40
40
  "scripts": {
41
41
  "build": "(cd ../ui && bun run build) && node scripts/build-worker.js && tsup src/index.ts --format cjs,esm --clean && tsc -p tsconfig.build.json",
42
42
  "test": "vitest run",
43
- "test:e2e": "bun e2e/blockquote-layout.e2e.ts && bun e2e/stream-controller.e2e.ts && bun e2e/lazy-math.e2e.ts && bun e2e/paragraph-image-repaint.e2e.ts && bun e2e/selection-fidelity.e2e.ts && bun e2e/code-atlas-dpr.e2e.ts && bun e2e/set-max-width.e2e.ts"
43
+ "test:e2e": "bun e2e/blockquote-layout.e2e.ts && bun e2e/stream-controller.e2e.ts && bun e2e/lazy-math.e2e.ts && bun e2e/paragraph-image-repaint.e2e.ts && bun e2e/selection-fidelity.e2e.ts && bun e2e/code-atlas-dpr.e2e.ts && bun e2e/set-max-width.e2e.ts && bun e2e/block-affordances.e2e.ts"
44
44
  },
45
45
  "dependencies": {
46
46
  "marked": "^18.0.7",