@ai-matrx/print 0.5.10 → 0.6.1

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/markdown.js CHANGED
@@ -753,6 +753,265 @@ var MARKDOWN_DOCUMENT_CSS = `/*
753
753
  }
754
754
  `;
755
755
  var MARKDOWN_ARTICLE_CSS = `*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}body{font-family:Georgia,'Times New Roman',serif;font-size:11pt;line-height:1.7;color:var(--matrx-print-fg, #1a1a1a);background:var(--matrx-print-bg, #fff);max-width:780px;margin:0 auto;padding:32px 40px}.print-actions{display:flex;gap:10px;margin-bottom:28px;padding-bottom:20px;border-bottom:2px solid var(--matrx-print-border, #e5e7eb)}.print-btn{display:inline-flex;align-items:center;gap:6px;padding:8px 20px;border-radius:8px;font-size:13px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;cursor:pointer;border:none;font-weight:600;transition:opacity .15s}.print-btn:hover{opacity:.85}.print-btn-primary{background:var(--matrx-print-accent, #2563eb);color:var(--matrx-print-bg, #fff)}.print-btn-secondary{background:var(--matrx-print-surface-sunken, #f3f4f6);color:var(--matrx-print-fg-muted, #374151);border:1px solid var(--matrx-print-border-muted, #d1d5db)}h1,h2,h3,h4,h5,h6{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','Helvetica Neue',sans-serif;font-weight:700;line-height:1.3;color:var(--matrx-print-heading, #111827);margin-top:1.6em;margin-bottom:.5em;page-break-after:avoid}h1{font-size:24pt;border-bottom:2px solid var(--matrx-print-border, #e5e7eb);padding-bottom:.3em;margin-top:0}h2{font-size:18pt;border-bottom:1px solid var(--matrx-print-border, #e5e7eb);padding-bottom:.2em}h3{font-size:14pt}p{margin-bottom:.9em;orphans:3;widows:3}a{color:var(--matrx-print-accent, #2563eb);text-decoration:underline;word-break:break-word}ul,ol{margin:.6em 0 .9em 0;padding-left:1.8em}li{margin-bottom:.3em;page-break-inside:avoid}blockquote{margin:1em 0;padding:.7em 1em .7em 1.2em;border-left:4px solid var(--matrx-print-quote-border, #6366f1);background:var(--matrx-print-quote-bg, #f5f3ff);border-radius:0 6px 6px 0;color:var(--matrx-print-fg-muted, #374151);font-style:italic;page-break-inside:avoid}.inline-code{font-family:'JetBrains Mono','Fira Code',monospace;font-size:9.5pt;background:var(--matrx-print-code-bg, #f1f5f9);color:var(--matrx-print-ink, #0f172a);padding:1px 5px;border-radius:4px;border:1px solid var(--matrx-print-line, #e2e8f0)}.code-block-wrapper{position:relative;margin:1em 0 1.2em;border-radius:8px;overflow:hidden;border:1px solid var(--matrx-print-line, #e2e8f0);page-break-inside:avoid}.code-lang{display:block;font-family:-apple-system,sans-serif;font-size:9pt;font-weight:600;color:var(--matrx-print-fg-subtle, #64748b);background:var(--matrx-print-surface, #f8fafc);padding:4px 12px;border-bottom:1px solid var(--matrx-print-line, #e2e8f0);text-transform:uppercase;letter-spacing:.05em}.code-block{margin:0;padding:14px 16px;background:var(--matrx-print-ink, #0f172a);color:var(--matrx-print-line, #e2e8f0);font-family:'JetBrains Mono',monospace;font-size:9pt;line-height:1.6;overflow-x:auto;white-space:pre;tab-size:2}.print-table{width:100%;border-collapse:collapse;margin:1em 0 1.2em;font-size:10pt;page-break-inside:avoid}.print-table th{background:var(--matrx-print-table-head-bg, #1e293b);color:var(--matrx-print-surface, #f8fafc);font-family:-apple-system,sans-serif;font-weight:600;font-size:9.5pt;text-align:left;padding:8px 12px;border:1px solid var(--matrx-print-table-head-border, #334155)}.print-table td{padding:7px 12px;border:1px solid var(--matrx-print-line, #e2e8f0);vertical-align:top}.print-table tr:nth-child(even) td{background:var(--matrx-print-surface, #f8fafc)}hr{border:none;border-top:2px solid var(--matrx-print-border, #e5e7eb);margin:1.5em 0}.print-figure{margin:1em 0 1.2em;page-break-inside:avoid}.print-image{display:block;max-width:100%;height:auto;margin:0 auto;border-radius:6px}.print-figure figcaption{margin-top:.4em;text-align:center;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;font-size:9.5pt;color:var(--matrx-print-fg-subtle, #64748b)}@media print{body{max-width:100%;padding:0;font-size:10.5pt}.print-actions{display:none!important}.code-block,.print-table th,blockquote{color-adjust:exact;-webkit-print-color-adjust:exact;print-color-adjust:exact}a[href]::after{content:" ("attr(href)")";font-size:9pt;color:var(--matrx-print-fg-subtle, #64748b)}a[href^="#"]::after,a[href^="javascript:"]::after{content:""}h1,h2,h3{page-break-after:avoid}pre,blockquote,table{page-break-inside:avoid}}`;
756
+ var MARKDOWN_SHARED_CSS = `
757
+ .matrx-page-break {
758
+ position: relative;
759
+ clear: both;
760
+ height: 0;
761
+ margin: 2.75rem 0;
762
+ border: 0;
763
+ border-top: 2px dashed var(--matrx-print-border-muted, #d1d5db);
764
+ }
765
+ .matrx-page-break::after {
766
+ content: attr(data-label);
767
+ position: absolute;
768
+ left: 50%;
769
+ top: 0;
770
+ transform: translate(-50%, -50%);
771
+ padding: 0 0.75rem;
772
+ background: var(--matrx-print-bg, #ffffff);
773
+ color: var(--matrx-print-fg-subtle, #6b7280);
774
+ font: 600 10px/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
775
+ letter-spacing: 0.08em;
776
+ text-transform: uppercase;
777
+ white-space: nowrap;
778
+ }
779
+
780
+ .matrx-artifact {
781
+ margin: 1.25em 0;
782
+ border: 1px solid var(--matrx-print-border, #e5e7eb);
783
+ border-radius: 8px;
784
+ overflow: hidden;
785
+ }
786
+ .matrx-artifact-header {
787
+ display: flex;
788
+ align-items: baseline;
789
+ gap: 0.6em;
790
+ padding: 0.45em 0.9em;
791
+ background: var(--matrx-print-surface, #f8fafc);
792
+ border-bottom: 1px solid var(--matrx-print-border, #e5e7eb);
793
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
794
+ }
795
+ .matrx-artifact-kind {
796
+ font-size: 0.72em;
797
+ font-weight: 700;
798
+ letter-spacing: 0.06em;
799
+ text-transform: uppercase;
800
+ color: var(--matrx-print-accent, #2563eb);
801
+ }
802
+ .matrx-artifact-title {
803
+ font-weight: 600;
804
+ color: var(--matrx-print-fg, #1a1a1a);
805
+ }
806
+ .matrx-artifact-body {
807
+ padding: 0.8em 0.9em;
808
+ }
809
+ .matrx-artifact-body > :first-child {
810
+ margin-top: 0;
811
+ }
812
+ .matrx-artifact-body > :last-child {
813
+ margin-bottom: 0;
814
+ }
815
+ /* The panel's kind chip already names these languages. */
816
+ .matrx-artifact:is([data-artifact-type="html"], [data-artifact-type="react"], [data-artifact-type="svg"], [data-artifact-type="mermaid"], [data-artifact-type="diff"], [data-artifact-type="iframe"]) .code-lang {
817
+ display: none;
818
+ }
819
+ .matrx-artifact-empty {
820
+ margin: 0;
821
+ font-style: italic;
822
+ color: var(--matrx-print-fg-subtle, #6b7280);
823
+ }
824
+
825
+ li.matrx-task-item {
826
+ list-style: none;
827
+ }
828
+ /* The box IS the marker \u2014 the document skin's custom bullet must not sit on it. */
829
+ :is(.matrx-content-container, .content) li.matrx-task-item::before {
830
+ content: none;
831
+ }
832
+ .matrx-task-box {
833
+ position: relative;
834
+ display: inline-block;
835
+ width: 0.9em;
836
+ height: 0.9em;
837
+ margin: 0 0.5em 0 -1.35em;
838
+ border: 1.5px solid var(--matrx-print-fg-muted, #4a4a4a);
839
+ border-radius: 3px;
840
+ vertical-align: -0.1em;
841
+ }
842
+ .matrx-task-box.is-checked::after {
843
+ content: "\\2713";
844
+ position: absolute;
845
+ inset: 0;
846
+ font-size: 0.8em;
847
+ font-weight: 700;
848
+ line-height: 1.1em;
849
+ text-align: center;
850
+ color: var(--matrx-print-fg, #1a1a1a);
851
+ }
852
+
853
+ :is(.matrx-content-container, .content) del {
854
+ color: var(--matrx-print-fg-muted, #4a4a4a);
855
+ text-decoration: line-through;
856
+ }
857
+ :is(.matrx-content-container, .content) hr.matrx-hr-accent {
858
+ height: 2px;
859
+ margin: 1.75em 0;
860
+ border: 0;
861
+ border-radius: 2px;
862
+ background: var(--matrx-print-accent, #2563eb);
863
+ opacity: 0.6;
864
+ }
865
+ :is(.matrx-content-container, .content) hr.matrx-hr-heavy {
866
+ height: 4px;
867
+ margin: 2.25em 0;
868
+ border: 0;
869
+ border-radius: 4px;
870
+ background: var(--matrx-print-accent, #2563eb);
871
+ }
872
+
873
+ .matrx-flashcards-print {
874
+ margin: 1em 0 1.2em;
875
+ }
876
+ .matrx-fc {
877
+ display: grid;
878
+ grid-template-columns: 1fr 1fr;
879
+ margin-bottom: 0.5em;
880
+ border: 1px solid var(--matrx-print-border, #e5e7eb);
881
+ border-radius: 6px;
882
+ }
883
+ .matrx-fc-front,
884
+ .matrx-fc-back {
885
+ padding: 0.6em 0.8em;
886
+ }
887
+ .matrx-fc-front {
888
+ font-weight: 600;
889
+ border-right: 1px dashed var(--matrx-print-border-muted, #d1d5db);
890
+ }
891
+ .matrx-fc-num {
892
+ display: inline-block;
893
+ min-width: 1.6em;
894
+ font-size: 0.85em;
895
+ color: var(--matrx-print-fg-subtle, #6b7280);
896
+ }
897
+
898
+ @media print {
899
+ .matrx-page-break {
900
+ margin: 0;
901
+ border: 0;
902
+ break-after: page;
903
+ page-break-after: always;
904
+ }
905
+ .matrx-page-break::after {
906
+ content: none;
907
+ }
908
+ .matrx-page-break + * {
909
+ margin-top: 0;
910
+ }
911
+
912
+ /* Scoped to the rendered markdown (.matrx-content-container = document
913
+ skin, .content = print window) so a host page that carries this sheet
914
+ \u2014 a WordPress theme \u2014 keeps its own print rules everywhere else. */
915
+
916
+ /* Long tables flow across pages: the header row repeats, rows never split. */
917
+ :is(.matrx-content-container, .content) thead {
918
+ display: table-header-group;
919
+ }
920
+ :is(.matrx-content-container, .content) :is(tr, img, figure),
921
+ .matrx-fc,
922
+ .matrx-task-item {
923
+ break-inside: avoid;
924
+ page-break-inside: avoid;
925
+ }
926
+ :is(.matrx-content-container, .content) :is(h1, h2, h3, h4, h5, h6),
927
+ .matrx-artifact-header {
928
+ break-after: avoid;
929
+ page-break-after: avoid;
930
+ break-inside: avoid;
931
+ }
932
+
933
+ /* Paper cannot scroll: long code lines wrap instead of being cut off. */
934
+ :is(.matrx-content-container, .content) :is(pre, pre code) {
935
+ white-space: pre-wrap !important;
936
+ overflow-wrap: anywhere;
937
+ overflow: visible !important;
938
+ }
939
+ .code-block-wrapper,
940
+ .matrx-artifact {
941
+ overflow: visible;
942
+ }
943
+ :is(.matrx-content-container, .content) img {
944
+ max-width: 100% !important;
945
+ }
946
+
947
+ .matrx-artifact-header,
948
+ .matrx-task-box {
949
+ -webkit-print-color-adjust: exact;
950
+ print-color-adjust: exact;
951
+ }
952
+
953
+ a.matrx-link[href^="http"]::after {
954
+ content: " (" attr(href) ")";
955
+ font-size: 0.85em;
956
+ color: var(--matrx-print-fg-subtle, #6b7280);
957
+ overflow-wrap: anywhere;
958
+ }
959
+ }
960
+ `;
961
+ var MARKDOWN_PAGE_CSS = `
962
+ @page {
963
+ margin: 0.6in 0.65in 0.7in;
964
+ @bottom-center {
965
+ content: counter(page) " / " counter(pages);
966
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
967
+ font-size: 8pt;
968
+ color: var(--matrx-print-fg-subtle, #6b7280);
969
+ }
970
+ }
971
+ `;
972
+ var MARKDOWN_PAGE_PLAIN_CSS = `
973
+ @page {
974
+ margin: 0.6in 0.65in 0.7in;
975
+ }
976
+ `;
977
+
978
+ // src/directives.ts
979
+ var PAGE_BREAK_MARKDOWN = "<!-- pagebreak -->";
980
+ var PAGE_BREAK_CLASS = "matrx-page-break";
981
+ var PAGE_BREAK_LABEL = "Page break";
982
+ var COMMENT_FORM = /^<!--\s*(?:page[\s_-]?break|new[\s_-]?page)\s*-->$/i;
983
+ var LATEX_FORM = /^\\(?:pagebreak|newpage|clearpage)\s*(?:\{\s*\})?$/i;
984
+ var DIV_FORM = /^<div\b([^>]*)>\s*<\/div>$|^<div\b([^>]*)\/>$/i;
985
+ var DIV_BREAK_ATTR = /(?:page-break-(?:after|before)\s*:\s*always|break-(?:after|before)\s*:\s*page|class\s*=\s*["'](?:[^"']*\s)?page-?break(?:\s[^"']*)?["'])/i;
986
+ function isPageBreakLine(line) {
987
+ const t = line.trim();
988
+ if (t.length < 8 || t.length > 200) return false;
989
+ if (COMMENT_FORM.test(t) || LATEX_FORM.test(t)) return true;
990
+ const div = DIV_FORM.exec(t);
991
+ if (!div) return false;
992
+ return DIV_BREAK_ATTR.test(div[1] ?? div[2] ?? "");
993
+ }
994
+ function splitAtPageBreaks(markdown) {
995
+ const lines = markdown.split(/\r?\n/);
996
+ const pieces = [];
997
+ let current = [];
998
+ let fence = null;
999
+ for (const line of lines) {
1000
+ const fenceMatch = /^\s*(`{3,}|~{3,})/.exec(line);
1001
+ if (fenceMatch) {
1002
+ const marker = fenceMatch[1];
1003
+ if (fence === null) fence = marker;
1004
+ else if (marker[0] === fence[0] && marker.length >= fence.length) fence = null;
1005
+ } else if (fence === null && isPageBreakLine(line)) {
1006
+ if (current.join("\n").trim()) pieces.push(current.join("\n"));
1007
+ current = [];
1008
+ continue;
1009
+ }
1010
+ current.push(line);
1011
+ }
1012
+ if (current.join("\n").trim() || pieces.length === 0) pieces.push(current.join("\n"));
1013
+ return pieces;
1014
+ }
756
1015
 
757
1016
  // src/markdown.ts
758
1017
  function removeThinkingContent(content) {
@@ -765,80 +1024,166 @@ function hasThinkingContent(content) {
765
1024
  if (!content) return false;
766
1025
  return /<thinking>|<think>|<reasoning>/i.test(content);
767
1026
  }
768
- function convertMarkdownTables(html) {
769
- const tableRegex = /^(\|.*\|)\s*\n(\|[-\s:|]*\|)\s*\n((?:\|.*\|\s*\n?)*)/gm;
770
- return html.replace(
771
- tableRegex,
772
- (_match, headerRow, _separatorRow, bodyRows) => {
773
- const headers = headerRow.split("|").map((cell) => cell.trim()).filter((cell) => cell !== "");
774
- const rows = bodyRows.trim().split("\n").filter((row) => row.trim()).map(
775
- (row) => row.split("|").map((cell) => cell.trim()).filter((cell) => cell !== "")
776
- );
777
- let tableHtml = '<table class="matrx-table">\n';
778
- if (headers.length > 0) {
779
- tableHtml += '<thead class="matrx-table-head">\n<tr class="matrx-table-row">\n';
780
- for (const header of headers) {
781
- tableHtml += `<th class="matrx-table-header">${header}</th>
782
- `;
783
- }
784
- tableHtml += "</tr>\n</thead>\n";
785
- }
786
- if (rows.length > 0) {
787
- tableHtml += '<tbody class="matrx-table-body">\n';
788
- for (const row of rows) {
789
- if (row.length === 0) continue;
790
- tableHtml += '<tr class="matrx-table-row">\n';
791
- for (const cell of row) {
792
- tableHtml += `<td class="matrx-table-cell">${cell}</td>
793
- `;
794
- }
795
- tableHtml += "</tr>\n";
796
- }
797
- tableHtml += "</tbody>\n";
798
- }
799
- return `${tableHtml}</table>
800
- `;
1027
+ var TOKEN_OPEN = "\uE000";
1028
+ var TOKEN_CLOSE = "\uE001";
1029
+ var BLOCK_TOKEN = (i) => `${TOKEN_OPEN}B${i}${TOKEN_CLOSE}`;
1030
+ var INLINE_TOKEN = (i) => `${TOKEN_OPEN}I${i}${TOKEN_CLOSE}`;
1031
+ var TOKEN_RE = /\uE000([BI])(\d+)\uE001/g;
1032
+ var PARAGRAPH_WRAPPED_BLOCK_RE = /<p[^>]*>\s*(\uE000B\d+\uE001)\s*<\/p>/g;
1033
+ var Stash = class {
1034
+ items = [];
1035
+ block(html) {
1036
+ return BLOCK_TOKEN(this.items.push(html) - 1);
1037
+ }
1038
+ inline(html) {
1039
+ return INLINE_TOKEN(this.items.push(html) - 1);
1040
+ }
1041
+ restore(html) {
1042
+ let out = html;
1043
+ for (let pass = 0; pass < 8 && out.includes(TOKEN_OPEN); pass++) {
1044
+ out = out.replace(PARAGRAPH_WRAPPED_BLOCK_RE, "$1").replace(TOKEN_RE, (_m, _k, i) => this.items[Number(i)] ?? "");
801
1045
  }
802
- );
1046
+ return out;
1047
+ }
1048
+ };
1049
+ var DOCUMENT_GRAMMAR = {
1050
+ skin: "document",
1051
+ table: "matrx-table",
1052
+ thead: "matrx-table-head",
1053
+ tbody: "matrx-table-body",
1054
+ tr: "matrx-table-row",
1055
+ th: "matrx-table-header",
1056
+ td: "matrx-table-cell",
1057
+ ul: "matrx-list matrx-bullet-list",
1058
+ ol: "matrx-list matrx-numbered-list",
1059
+ ulNested: "matrx-list matrx-bullet-list matrx-nested-list",
1060
+ olNested: "matrx-list matrx-numbered-list matrx-nested-list",
1061
+ li: "matrx-list-item",
1062
+ hr: "matrx-hr",
1063
+ del: "matrx-del",
1064
+ strong: "matrx-strong",
1065
+ em: "matrx-em",
1066
+ inlineCode: "matrx-inline-code"
1067
+ };
1068
+ var ARTICLE_GRAMMAR = {
1069
+ skin: "article",
1070
+ table: "print-table",
1071
+ thead: "",
1072
+ tbody: "",
1073
+ tr: "",
1074
+ th: "",
1075
+ td: "",
1076
+ ul: "",
1077
+ ol: "",
1078
+ ulNested: "",
1079
+ olNested: "",
1080
+ li: "",
1081
+ hr: "",
1082
+ del: "",
1083
+ strong: "",
1084
+ em: "",
1085
+ inlineCode: "inline-code"
1086
+ };
1087
+ var cls = (...names) => {
1088
+ const joined = names.filter(Boolean).join(" ");
1089
+ return joined ? ` class="${joined}"` : "";
1090
+ };
1091
+ var PAGE_BREAK_HTML = `<div class="${PAGE_BREAK_CLASS}" role="separator" aria-label="${PAGE_BREAK_LABEL}" data-label="${PAGE_BREAK_LABEL}"></div>`;
1092
+ function codeBlockHtml(g, code, language) {
1093
+ const body = escapeHtml2(code.replace(/\n$/, ""));
1094
+ const lang = language.trim();
1095
+ if (g.skin === "article") {
1096
+ const label = lang ? `<span class="code-lang">${escapeHtml2(lang)}</span>` : "";
1097
+ return `<div class="code-block-wrapper">${label}<pre class="code-block"><code>${body}</code></pre></div>`;
1098
+ }
1099
+ const dataLang = lang ? ` data-language="${escapeHtml2(lang)}"` : "";
1100
+ return `<pre class="matrx-code-block"${dataLang}><code class="matrx-code">${body}</code></pre>`;
1101
+ }
1102
+ var CODE_ARTIFACT_LANGUAGE = {
1103
+ html: "html",
1104
+ react: "tsx",
1105
+ svg: "svg",
1106
+ mermaid: "mermaid",
1107
+ diff: "diff",
1108
+ iframe: "html",
1109
+ code: ""
1110
+ };
1111
+ var ARTIFACT_TYPE_LABEL = {
1112
+ html: "HTML",
1113
+ svg: "SVG",
1114
+ react: "React",
1115
+ iframe: "Embed"
1116
+ };
1117
+ function parseTagAttributes(tag) {
1118
+ const attrs = {};
1119
+ for (const m of tag.matchAll(/([a-zA-Z_][\w-]*)\s*=\s*(?:"([^"]*)"|'([^']*)')/g)) {
1120
+ attrs[m[1].toLowerCase()] = m[2] ?? m[3] ?? "";
1121
+ }
1122
+ return attrs;
1123
+ }
1124
+ function artifactLabel(type) {
1125
+ if (!type) return "Artifact";
1126
+ return ARTIFACT_TYPE_LABEL[type] ?? type.replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
1127
+ }
1128
+ function looksLikeJson(body) {
1129
+ const t = body.trim();
1130
+ if (!/^[[{]/.test(t)) return false;
1131
+ try {
1132
+ JSON.parse(t);
1133
+ return true;
1134
+ } catch {
1135
+ return false;
1136
+ }
803
1137
  }
804
- function markdownToHtml(markdown, options = {}) {
805
- const notice = options.onNotice;
806
- if (!markdown) {
807
- notice?.({
808
- code: "empty-output",
809
- message: "markdownToHtml received an empty string.",
810
- remedy: "Check the content is loaded before converting; nothing was rendered."
811
- });
812
- return "";
1138
+ function artifactHtml(g, attrs, body, ctx) {
1139
+ const type = (attrs.type ?? "").toLowerCase();
1140
+ const title = attrs.title?.trim() ?? "";
1141
+ const header = `<div class="matrx-artifact-header"><span class="matrx-artifact-kind">${escapeHtml2(
1142
+ artifactLabel(type)
1143
+ )}</span>${title ? `<span class="matrx-artifact-title">${escapeHtml2(title)}</span>` : ""}</div>`;
1144
+ let inner;
1145
+ const trimmed = (body ?? "").replace(/^\n+|\n+$/g, "");
1146
+ if (body === null || !trimmed.trim()) {
1147
+ inner = `<p class="matrx-artifact-empty">Saved artifact \u2014 open it in AI Matrx to view its content.</p>`;
1148
+ } else if (/^\s*(`{3,}|~{3,})/.test(trimmed)) {
1149
+ inner = convertBody(g.skin, trimmed, ctx);
1150
+ } else if (type in CODE_ARTIFACT_LANGUAGE) {
1151
+ inner = codeBlockHtml(g, trimmed, CODE_ARTIFACT_LANGUAGE[type] || attrs.language || attrs.lang || "");
1152
+ } else if (looksLikeJson(trimmed)) {
1153
+ inner = codeBlockHtml(g, JSON.stringify(JSON.parse(trimmed), null, 2), "json");
1154
+ } else {
1155
+ inner = convertBody(g.skin, trimmed, ctx);
813
1156
  }
814
- const startWrapper = '<div class="matrx-content-container">';
815
- const endWrapper = "</div>";
816
- let html = options.includeThinking ? markdown : removeThinkingContent(markdown);
817
- const flashcardPlaceholders = [];
818
- let flashcardIndex = 0;
819
- let globalCardIndex = 0;
820
- html = html.replace(
821
- /<flashcards>([\s\S]*?)<\/flashcards>/gi,
822
- (_match, content) => {
823
- const cards = content.split(/\n*---\n*/g).map((card) => card.trim()).filter((card) => card.length > 0);
824
- if (cards.length === 0) return "";
825
- let flashcardsHtml = '<div class="matrx-flashcards-container">\n';
826
- for (const card of cards) {
827
- const frontMatch = card.match(/Front:\s*(.+?)(?=\n|$)/i);
828
- const backMatch = card.match(/Back:\s*([\s\S]+?)$/i);
829
- const front = frontMatch?.[1]?.trim();
830
- const back = backMatch?.[1]?.trim();
831
- if (!front || !back) {
832
- notice?.({
833
- code: "flashcard-malformed",
834
- message: `A flashcard was skipped: it is missing a ${front ? "Back:" : "Front:"} line.`,
835
- remedy: 'Every card inside <flashcards> needs both "Front: \u2026" and "Back: \u2026", separated from the next card by ---.'
836
- });
837
- continue;
838
- }
839
- globalCardIndex++;
840
- const cardId = `card${globalCardIndex}`;
841
- flashcardsHtml += ` <div class="matrx-flashcard">
1157
+ return `<section class="matrx-artifact" data-artifact-type="${escapeHtml2(type || "artifact")}">${header}<div class="matrx-artifact-body">${inner}</div></section>`;
1158
+ }
1159
+ function parseFlashcards(content, notice) {
1160
+ const cards = [];
1161
+ for (const card of content.split(/\n*---\n*/g).map((c) => c.trim()).filter(Boolean)) {
1162
+ const front = card.match(/Front:\s*(.+?)(?=\n|$)/i)?.[1]?.trim();
1163
+ const back = card.match(/Back:\s*([\s\S]+?)$/i)?.[1]?.trim();
1164
+ if (!front || !back) {
1165
+ notice?.({
1166
+ code: "flashcard-malformed",
1167
+ message: `A flashcard was skipped: it is missing a ${front ? "Back:" : "Front:"} line.`,
1168
+ remedy: 'Every card inside <flashcards> needs both "Front: \u2026" and "Back: \u2026", separated from the next card by ---.'
1169
+ });
1170
+ continue;
1171
+ }
1172
+ cards.push({ front, back });
1173
+ }
1174
+ return cards;
1175
+ }
1176
+ function flashcardsHtml(g, cards, ctx) {
1177
+ if (g.skin === "article") {
1178
+ return `<div class="matrx-flashcards-print">${cards.map(
1179
+ (c, i) => `<div class="matrx-fc"><div class="matrx-fc-front"><span class="matrx-fc-num">${i + 1}</span>${c.front}</div><div class="matrx-fc-back">${c.back}</div></div>`
1180
+ ).join("")}</div>`;
1181
+ }
1182
+ let out = '<div class="matrx-flashcards-container">\n';
1183
+ for (const { front, back } of cards) {
1184
+ ctx.cardIndex++;
1185
+ const cardId = `card${ctx.cardIndex}`;
1186
+ out += ` <div class="matrx-flashcard">
842
1187
  <input type="checkbox" class="matrx-flashcard-input" id="${cardId}">
843
1188
  <label for="${cardId}" class="matrx-flashcard-label"></label>
844
1189
  <div class="matrx-flashcard-inner">
@@ -858,154 +1203,318 @@ function markdownToHtml(markdown, options = {}) {
858
1203
  </div>
859
1204
 
860
1205
  `;
1206
+ }
1207
+ return `${out}</div>
1208
+ `;
1209
+ }
1210
+ var FENCE_OPEN = /^(\s*)(`{3,}|~{3,})\s*([^\s`]*)[^`]*$/;
1211
+ function protectRegions(markdown, g, stash, ctx) {
1212
+ const lines = markdown.split(/\r?\n/);
1213
+ const out = [];
1214
+ for (let i = 0; i < lines.length; i++) {
1215
+ const line = lines[i];
1216
+ const fence = FENCE_OPEN.exec(line);
1217
+ if (fence) {
1218
+ const marker = fence[2];
1219
+ const code = [];
1220
+ let j = i + 1;
1221
+ for (; j < lines.length; j++) {
1222
+ const close = /^\s*(`{3,}|~{3,})\s*$/.exec(lines[j]);
1223
+ if (close && close[1][0] === marker[0] && close[1].length >= marker.length) break;
1224
+ code.push(lines[j]);
861
1225
  }
862
- flashcardsHtml += "</div>\n";
863
- const placeholder = `\u03A9FLASHCARD\u03A9${flashcardIndex}\u03A9FLASHCARD\u03A9`;
864
- flashcardPlaceholders.push({ placeholder, html: flashcardsHtml });
865
- flashcardIndex++;
866
- return placeholder;
1226
+ const indent = fence[1].length;
1227
+ const dedented = code.map((l) => l.slice(Math.min(indent, l.length - l.trimStart().length)));
1228
+ out.push(stash.block(codeBlockHtml(g, dedented.join("\n"), fence[3] ?? "")));
1229
+ i = j;
1230
+ continue;
1231
+ }
1232
+ if (isPageBreakLine(line)) {
1233
+ out.push(stash.block(PAGE_BREAK_HTML));
1234
+ continue;
867
1235
  }
1236
+ const open = /^\s*<artifact\b((?:[^>"']|"[^"]*"|'[^']*')*?)(\/?)>(.*)$/i.exec(line);
1237
+ if (open) {
1238
+ const attrs = parseTagAttributes(open[1] ?? "");
1239
+ if (open[2] === "/") {
1240
+ out.push(stash.block(artifactHtml(g, attrs, null, ctx)));
1241
+ if (open[3]?.trim()) out.push(open[3]);
1242
+ continue;
1243
+ }
1244
+ const bodyLines = [];
1245
+ let rest = open[3] ?? "";
1246
+ let after = "";
1247
+ let j = i;
1248
+ let inFence = null;
1249
+ for (; ; ) {
1250
+ const closeAt = inFence ? -1 : rest.search(/<\/artifact\s*>/i);
1251
+ if (closeAt >= 0) {
1252
+ bodyLines.push(rest.slice(0, closeAt));
1253
+ after = rest.slice(closeAt).replace(/^<\/artifact\s*>/i, "");
1254
+ break;
1255
+ }
1256
+ bodyLines.push(rest);
1257
+ const f = /^\s*(`{3,}|~{3,})/.exec(rest);
1258
+ if (f) {
1259
+ const m = f[1];
1260
+ if (inFence === null) inFence = m;
1261
+ else if (m[0] === inFence[0] && m.length >= inFence.length) inFence = null;
1262
+ }
1263
+ j++;
1264
+ if (j >= lines.length) break;
1265
+ rest = lines[j];
1266
+ }
1267
+ const html = ctx.depth >= 3 ? codeBlockHtml(g, bodyLines.join("\n"), "") : artifactHtml(g, attrs, bodyLines.join("\n"), { ...ctx, depth: ctx.depth + 1 });
1268
+ out.push(stash.block(html));
1269
+ if (after.trim()) out.push(after);
1270
+ i = j;
1271
+ continue;
1272
+ }
1273
+ out.push(line);
1274
+ }
1275
+ let text = out.join("\n");
1276
+ text = text.replace(
1277
+ /`([^`\n]+)`/g,
1278
+ (_m, code) => stash.inline(`<code${cls(g.inlineCode)}>${escapeHtml2(code)}</code>`)
868
1279
  );
869
- html = html.replace(/^[-]{3,}$/gm, '<hr class="matrx-hr">');
870
- html = html.replace(/^[*]{3,}$/gm, '<hr class="matrx-hr">');
871
- const imagePlaceholders = [];
872
- let imageIndex = 0;
873
- html = html.replace(
874
- /!\[([^\]]*)\]\(([^)]+)\)/g,
875
- (_match, altText, url) => {
876
- const placeholder = `\u03A9IMAGE\u03A9${imageIndex}\u03A9IMAGE\u03A9`;
877
- imagePlaceholders.push({
878
- placeholder,
879
- html: `<img class="matrx-image" src="${url}" alt="${altText}" />`
880
- });
881
- imageIndex++;
882
- return placeholder;
1280
+ text = text.replace(/<!--[\s\S]*?-->/g, "");
1281
+ const before = text;
1282
+ for (let pass = 0; pass < 8; pass++) {
1283
+ const next = text.replace(/<(script|style|head)\b[^>]*>[\s\S]*?<\/\1\s*>/gi, "").replace(/<\/?(?:script|style|head|html|body)\b[^>]*>/gi, "").replace(/<!DOCTYPE[^>]*>/gi, "");
1284
+ if (next === text) break;
1285
+ text = next;
1286
+ }
1287
+ text = sanitizeRawTags(text);
1288
+ if (text !== before && !ctx.strippedAnnounced) {
1289
+ ctx.strippedAnnounced = true;
1290
+ ctx.options.onNotice?.({
1291
+ code: "html-stripped",
1292
+ message: "Unsafe or page-level markup (a <script>, <style>, <head>, an <html>/<body> shell, an on\u2026 event handler, or a javascript: URL) was removed from the rendered copy.",
1293
+ remedy: 'To show a full HTML page, put it in an ```html fence or an <artifact type="html"> block \u2014 it is printed as its source.'
1294
+ });
1295
+ }
1296
+ text = text.replace(/<flashcards>([\s\S]*?)<\/flashcards>/gi, (_m, content) => {
1297
+ const cards = parseFlashcards(content, ctx.options.onNotice);
1298
+ return cards.length ? `
1299
+ ${stash.block(flashcardsHtml(g, cards, ctx))}
1300
+ ` : "";
1301
+ });
1302
+ return text;
1303
+ }
1304
+ var URL_ATTRS = /* @__PURE__ */ new Set(["href", "src", "action", "formaction", "xlink:href", "poster", "background"]);
1305
+ function decodeForSchemeCheck(value) {
1306
+ return value.replace(/&#x([0-9a-f]+);?/gi, (_m, h) => String.fromCharCode(parseInt(h, 16))).replace(/&#(\d+);?/g, (_m, d) => String.fromCharCode(Number(d))).replace(/&colon;/gi, ":").replace(/[\u0000-\u0020]+/g, "").toLowerCase();
1307
+ }
1308
+ function sanitizeRawTags(text) {
1309
+ return text.replace(
1310
+ /<([a-zA-Z][\w:-]*)((?:[^>"']|"[^"]*"|'[^']*')*)>/g,
1311
+ (whole, tag, rawAttrs) => {
1312
+ if (!/[=\s]/.test(rawAttrs)) return whole;
1313
+ let changed = false;
1314
+ const attrs = rawAttrs.replace(
1315
+ /(\s+)([^\s"'=<>/]+)(\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+)))?/g,
1316
+ (m, _sp, name, _eq, dq, sq, bare) => {
1317
+ const lower = name.toLowerCase();
1318
+ if (lower.startsWith("on") || lower === "srcdoc") {
1319
+ changed = true;
1320
+ return "";
1321
+ }
1322
+ if (URL_ATTRS.has(lower)) {
1323
+ const value = decodeForSchemeCheck(dq ?? sq ?? bare ?? "");
1324
+ if (/^(?:javascript|vbscript):|^data:text\/html/.test(value)) {
1325
+ changed = true;
1326
+ return ` ${name}="#"`;
1327
+ }
1328
+ }
1329
+ return m;
1330
+ }
1331
+ );
1332
+ return changed ? `<${tag}${attrs}>` : whole;
883
1333
  }
884
1334
  );
885
- const linkPlaceholders = [];
886
- let linkIndex = 0;
887
- html = html.replace(
888
- /\[([^\]]+)\]\(([^)]+)\)/g,
889
- (_match, linkText, url) => {
890
- const placeholder = `\u03A9LINK\u03A9${linkIndex}\u03A9LINK\u03A9`;
891
- linkPlaceholders.push({
892
- placeholder,
893
- html: `<a class="matrx-link" href="${url}">${linkText}</a>`
1335
+ }
1336
+ function safeUrl(url) {
1337
+ const u = url.trim();
1338
+ if (/^(?:javascript|vbscript):|^data:text\/html/.test(decodeForSchemeCheck(u))) return "#";
1339
+ return u.replace(/"/g, "%22");
1340
+ }
1341
+ function convertEmphasis(html, g) {
1342
+ const strong = `<strong${cls(g.strong)}>`;
1343
+ const em = `<em${cls(g.em)}>`;
1344
+ return html.replace(/\*\*\*(?!\s)(.+?)(?<!\s)\*\*\*/g, `${strong}${em}$1</em></strong>`).replace(/\*\*(?!\s)(.+?)(?<!\s)\*\*/g, `${strong}$1</strong>`).replace(/(?<![\w*])\*(?![\s*])(.+?)(?<![\s*])\*(?![\w*])/g, `${em}$1</em>`).replace(/(?<![\w_])___(?!\s)(.+?)(?<!\s)___(?![\w_])/g, `${strong}${em}$1</em></strong>`).replace(/(?<![\w_])__(?!\s)(.+?)(?<!\s)__(?![\w_])/g, `${strong}$1</strong>`).replace(/(?<![\w_])_(?![\s_])(.+?)(?<![\s_])_(?![\w_])/g, `${em}$1</em>`).replace(/~~(?!\s)(.+?)(?<!\s)~~/g, `<del${cls(g.del)}>$1</del>`);
1345
+ }
1346
+ var LINK_URL = String.raw`((?:[^()\s]|\([^()\s]*\))+)(?:\s+"[^"]*")?`;
1347
+ var IMAGE_RE = new RegExp(String.raw`!\[([^\]]*)\]\(` + LINK_URL + String.raw`\)`, "g");
1348
+ var LINK_RE = new RegExp(String.raw`\[([^\]]+)\]\(` + LINK_URL + String.raw`\)`, "g");
1349
+ function convertRules(html, g) {
1350
+ return html.replace(/^\s{0,3}#\s*={3,}\s*$/gm, `<hr${cls(g.hr, "matrx-hr-heavy")}>`).replace(/^\s{0,3}\*(?:\s*\*){2,}\s*$/gm, `<hr${cls(g.hr, "matrx-hr-accent")}>`).replace(/^\s{0,3}(?:-\s*){3,}$/gm, `<hr${cls(g.hr)}>`).replace(/^\s{0,3}(?:_\s*){3,}$/gm, `<hr${cls(g.hr)}>`);
1351
+ }
1352
+ function splitTableRow(row) {
1353
+ let r = row.trim();
1354
+ if (r.startsWith("|")) r = r.slice(1);
1355
+ if (r.endsWith("|") && !r.endsWith("\\|")) r = r.slice(0, -1);
1356
+ return r.split(/(?<!\\)\|/).map((c) => c.trim().replace(/\\\|/g, "|"));
1357
+ }
1358
+ function cellAlign(separator) {
1359
+ const s = separator.trim();
1360
+ const left = s.startsWith(":");
1361
+ const right = s.endsWith(":");
1362
+ if (left && right) return ' style="text-align:center"';
1363
+ if (right) return ' style="text-align:right"';
1364
+ return "";
1365
+ }
1366
+ function convertTables(html, g) {
1367
+ const tableRegex = /^(\|.*\|)\s*\n(\|[-\s:|]*\|)\s*\n((?:\|.*\|\s*\n?)*)/gm;
1368
+ return html.replace(
1369
+ tableRegex,
1370
+ (_m, headerRow, separatorRow, bodyRows) => {
1371
+ const headers = splitTableRow(headerRow);
1372
+ const aligns = splitTableRow(separatorRow).map(cellAlign);
1373
+ const rows = bodyRows.trim().split("\n").filter((r) => r.trim()).map(splitTableRow);
1374
+ let t = `<table${cls(g.table)}>
1375
+ <thead${cls(g.thead)}>
1376
+ <tr${cls(g.tr)}>
1377
+ `;
1378
+ headers.forEach((h, i) => {
1379
+ t += `<th${cls(g.th)}${aligns[i] ?? ""}>${h}</th>
1380
+ `;
894
1381
  });
895
- linkIndex++;
896
- return placeholder;
1382
+ t += "</tr>\n</thead>\n";
1383
+ if (rows.length > 0) {
1384
+ t += `<tbody${cls(g.tbody)}>
1385
+ `;
1386
+ for (const row of rows) {
1387
+ t += `<tr${cls(g.tr)}>
1388
+ `;
1389
+ row.forEach((c, i) => {
1390
+ t += `<td${cls(g.td)}${aligns[i] ?? ""}>${c}</td>
1391
+ `;
1392
+ });
1393
+ t += "</tr>\n";
1394
+ }
1395
+ t += "</tbody>\n";
1396
+ }
1397
+ return `${t}</table>
1398
+ `;
897
1399
  }
898
1400
  );
899
- html = html.replace(/^# (.+)$/gm, '<h1 class="matrx-h1">$1</h1>').replace(/^## (.+)$/gm, '<h2 class="matrx-h2">$1</h2>').replace(/^### (.+)$/gm, '<h3 class="matrx-h3">$1</h3>').replace(/^#### (.+)$/gm, '<h4 class="matrx-h4">$1</h4>').replace(/^##### (.+)$/gm, '<h5 class="matrx-h5">$1</h5>').replace(/^###### (.+)$/gm, '<h6 class="matrx-h6">$1</h6>');
900
- html = html.replace(/\*\*(.+?)\*\*/g, '<strong class="matrx-strong">$1</strong>').replace(/\*(.+?)\*/g, '<em class="matrx-em">$1</em>').replace(/__(.+?)__/g, '<strong class="matrx-strong">$1</strong>').replace(/_(.+?)_/g, '<em class="matrx-em">$1</em>');
1401
+ }
1402
+ function convertLists(html, g) {
901
1403
  const lines = html.split("\n");
902
- let processedHtml = "";
1404
+ let processed = "";
903
1405
  const listStack = [];
904
1406
  let lastListItem = "";
1407
+ const li = `<li${cls(g.li)}>`;
905
1408
  const top = () => listStack[listStack.length - 1];
906
- const closeDeeperThan = (indent) => {
907
- while (listStack.length > 0 && top().indent > indent) {
908
- if (lastListItem) {
909
- processedHtml += "</li>\n";
910
- lastListItem = "";
911
- }
912
- const closing = listStack.pop();
913
- if (!closing) break;
914
- processedHtml += closing.type === "ol" ? "</ol>\n" : "</ul>\n";
915
- if (listStack.length > 0) processedHtml += "</li>\n";
1409
+ const openTag = (type, nested) => type === "ol" ? `<ol${cls(nested ? g.olNested : g.ol)}>
1410
+ ` : `<ul${cls(nested ? g.ulNested : g.ul)}>
1411
+ `;
1412
+ const popOne = () => {
1413
+ if (lastListItem) {
1414
+ processed += "</li>\n";
1415
+ lastListItem = "";
916
1416
  }
1417
+ const closing = listStack.pop();
1418
+ if (!closing) return;
1419
+ processed += closing.type === "ol" ? "</ol>\n" : "</ul>\n";
1420
+ if (listStack.length > 0) processed += "</li>\n";
1421
+ };
1422
+ const closeDeeperThan = (indent) => {
1423
+ while (listStack.length > 0 && top().indent > indent) popOne();
917
1424
  };
918
1425
  for (const line of lines) {
919
- const numberedMatch = line.match(/^(\s*)(\d+)\.\s+(.+)$/);
920
- if (numberedMatch) {
921
- const indent = (numberedMatch[1] ?? "").length;
922
- const content = numberedMatch[3] ?? "";
1426
+ const numbered = line.match(/^(\s*)(\d+)[.)]\s+(.+)$/);
1427
+ const bullet = numbered ? null : line.match(/^(\s*)[*\-+]\s+(.+)$/);
1428
+ if (numbered || bullet) {
1429
+ const type = numbered ? "ol" : "ul";
1430
+ const indent = (numbered ?? bullet)[1]?.length ?? 0;
1431
+ const content = (numbered ? numbered[3] : bullet[2]) ?? "";
923
1432
  const current = top();
924
1433
  if (current && indent > current.indent) {
925
- processedHtml += '\n<ol class="matrx-list matrx-numbered-list matrx-nested-list">\n';
926
- listStack.push({ type: "ol", indent });
927
- processedHtml += `<li class="matrx-list-item">${content}`;
928
- lastListItem = "ol";
1434
+ processed += `
1435
+ ${openTag(type, true)}`;
1436
+ listStack.push({ type, indent });
929
1437
  } else {
930
1438
  closeDeeperThan(indent);
931
- if (lastListItem && top()?.indent === indent) processedHtml += "</li>\n";
1439
+ if (lastListItem && top()?.indent === indent) processed += "</li>\n";
932
1440
  const now = top();
933
- if (!now || now.indent !== indent || now.type !== "ol") {
934
- processedHtml += '<ol class="matrx-list matrx-numbered-list">\n';
935
- listStack.push({ type: "ol", indent });
1441
+ if (now && now.indent === indent && now.type !== type) {
1442
+ const closing = listStack.pop();
1443
+ processed += closing.type === "ol" ? "</ol>\n" : "</ul>\n";
936
1444
  }
937
- processedHtml += `<li class="matrx-list-item">${content}`;
938
- lastListItem = "ol";
939
- }
940
- continue;
941
- }
942
- const bulletMatch = line.match(/^(\s*)[*\-+]\s+(.+)$/);
943
- if (bulletMatch) {
944
- const indent = (bulletMatch[1] ?? "").length;
945
- const content = bulletMatch[2] ?? "";
946
- const current = top();
947
- if (current && indent > current.indent) {
948
- processedHtml += '\n<ul class="matrx-list matrx-bullet-list matrx-nested-list">\n';
949
- listStack.push({ type: "ul", indent });
950
- processedHtml += `<li class="matrx-list-item">${content}`;
951
- lastListItem = "ul";
952
- } else {
953
- closeDeeperThan(indent);
954
- if (lastListItem && top()?.indent === indent) processedHtml += "</li>\n";
955
- const now = top();
956
- if (!now || now.indent !== indent || now.type !== "ul") {
957
- processedHtml += '<ul class="matrx-list matrx-bullet-list">\n';
958
- listStack.push({ type: "ul", indent });
1445
+ const after = top();
1446
+ if (!after || after.indent !== indent || after.type !== type) {
1447
+ processed += openTag(type, listStack.length > 0);
1448
+ listStack.push({ type, indent });
959
1449
  }
960
- processedHtml += `<li class="matrx-list-item">${content}`;
961
- lastListItem = "ul";
962
1450
  }
1451
+ processed += `${li}${content}`;
1452
+ lastListItem = type;
963
1453
  continue;
964
1454
  }
965
1455
  if (line.trim() !== "") {
966
- while (listStack.length > 0) {
967
- if (lastListItem) {
968
- processedHtml += "</li>\n";
969
- lastListItem = "";
970
- }
971
- const closing = listStack.pop();
972
- if (!closing) break;
973
- processedHtml += closing.type === "ol" ? "</ol>\n" : "</ul>\n";
974
- if (listStack.length > 0) processedHtml += "</li>\n";
975
- }
1456
+ while (listStack.length > 0) popOne();
976
1457
  }
977
- processedHtml += `${line}
1458
+ processed += `${line}
978
1459
  `;
979
1460
  }
980
- while (listStack.length > 0) {
981
- if (lastListItem) {
982
- processedHtml += "</li>\n";
983
- lastListItem = "";
984
- }
985
- const closing = listStack.pop();
986
- if (!closing) break;
987
- processedHtml += closing.type === "ol" ? "</ol>\n" : "</ul>\n";
988
- if (listStack.length > 0) processedHtml += "</li>\n";
1461
+ while (listStack.length > 0) popOne();
1462
+ return processed.replace(
1463
+ /<li( class="([^"]*)")?>\s*\[([ xX])\]\s+/g,
1464
+ (_m, _c, classes, mark) => `<li${cls(classes ?? "", "matrx-task-item")}><span class="matrx-task-box${mark === " " ? "" : " is-checked"}" aria-hidden="true"></span>`
1465
+ );
1466
+ }
1467
+ function convertBody(skin, markdown, ctx) {
1468
+ return skin === "article" ? articleBody(markdown, ctx) : documentBody(markdown, ctx);
1469
+ }
1470
+ function scrubTokens(markdown) {
1471
+ return markdown.replace(/[\uE000\uE001]/g, "");
1472
+ }
1473
+ function newContext(options) {
1474
+ return { options, depth: 0, cardIndex: 0, strippedAnnounced: false };
1475
+ }
1476
+ function markdownToHtml(markdown, options = {}) {
1477
+ if (!markdown) {
1478
+ options.onNotice?.({
1479
+ code: "empty-output",
1480
+ message: "markdownToHtml received an empty string.",
1481
+ remedy: "Check the content is loaded before converting; nothing was rendered."
1482
+ });
1483
+ return "";
989
1484
  }
990
- html = processedHtml;
991
- html = html.replace(/^> (.+)$/gm, '<blockquote class="matrx-blockquote">$1</blockquote>');
1485
+ const source = scrubTokens(options.includeThinking ? markdown : removeThinkingContent(markdown));
1486
+ return `<div class="matrx-content-container">${documentBody(source, newContext(options))}</div>`;
1487
+ }
1488
+ function documentBody(markdown, ctx) {
1489
+ const g = DOCUMENT_GRAMMAR;
1490
+ const stash = new Stash();
1491
+ let html = protectRegions(markdown, g, stash, ctx);
1492
+ html = convertRules(html, g);
992
1493
  html = html.replace(
993
- /```([^`]+)```/gs,
994
- '<pre class="matrx-code-block"><code class="matrx-code">$1</code></pre>'
1494
+ IMAGE_RE,
1495
+ (_m, alt, url) => stash.block(`<img class="matrx-image" src="${safeUrl(url)}" alt="${escapeHtml2(alt)}" />`)
995
1496
  );
996
- html = html.replace(/`([^`]+)`/g, '<code class="matrx-inline-code">$1</code>');
997
- html = convertMarkdownTables(html);
998
- html = html.replace(/<table>/g, '<table class="matrx-table">').replace(/<thead>/g, '<thead class="matrx-table-head">').replace(/<tbody>/g, '<tbody class="matrx-table-body">').replace(/<tr>/g, '<tr class="matrx-table-row">').replace(/<th>/g, '<th class="matrx-table-header">').replace(/<td>/g, '<td class="matrx-table-cell">');
999
- html = html.replace(/<img([^>]*)>/g, '<img class="matrx-image"$1>');
1000
- html = html.replace(/<div(?!\s+class="matrx-)([^>]*)>/g, '<div class="matrx-div"$1>');
1001
- html = html.replace(/<span([^>]*)>/g, '<span class="matrx-span"$1>');
1002
- html = html.replace(/^([^<\n].+)$/gm, '<p class="matrx-paragraph">$1</p>');
1003
1497
  html = html.replace(
1004
- /<\/p>\s*<p class="matrx-paragraph">/g,
1005
- '</p><p class="matrx-paragraph">'
1498
+ LINK_RE,
1499
+ (_m, text, url) => stash.inline(`<a class="matrx-link" href="${safeUrl(url)}">${convertEmphasis(text, g)}</a>`)
1006
1500
  );
1501
+ html = html.replace(/^# (.+)$/gm, '<h1 class="matrx-h1">$1</h1>').replace(/^## (.+)$/gm, '<h2 class="matrx-h2">$1</h2>').replace(/^### (.+)$/gm, '<h3 class="matrx-h3">$1</h3>').replace(/^#### (.+)$/gm, '<h4 class="matrx-h4">$1</h4>').replace(/^##### (.+)$/gm, '<h5 class="matrx-h5">$1</h5>').replace(/^###### (.+)$/gm, '<h6 class="matrx-h6">$1</h6>');
1502
+ html = convertLists(html, g);
1503
+ html = convertEmphasis(html, g);
1504
+ html = html.replace(/^> ?(.+)$/gm, '<blockquote class="matrx-blockquote">$1</blockquote>');
1505
+ html = convertTables(html, g);
1506
+ html = html.replace(/<table>/g, '<table class="matrx-table">').replace(/<thead>/g, '<thead class="matrx-table-head">').replace(/<tbody>/g, '<tbody class="matrx-table-body">').replace(/<tr>/g, '<tr class="matrx-table-row">').replace(/<th>/g, '<th class="matrx-table-header">').replace(/<td>/g, '<td class="matrx-table-cell">');
1507
+ html = html.replace(/<img(?![^>]*class=)([^>]*)>/g, '<img class="matrx-image"$1>');
1508
+ html = html.replace(/<div\b([^>]*)>/g, (m, attrs) => {
1509
+ if (/\bclass="matrx-/.test(attrs)) return m;
1510
+ if (/\bclass="/.test(attrs)) return `<div${attrs.replace(/\bclass="/, 'class="matrx-div ')}>`;
1511
+ return `<div class="matrx-div"${attrs}>`;
1512
+ });
1513
+ html = html.replace(/<span(?![^>]*class=)([^>]*)>/g, '<span class="matrx-span"$1>');
1514
+ html = html.replace(/^([^<\n].+)$/gm, '<p class="matrx-paragraph">$1</p>');
1515
+ html = html.replace(/<\/p>\s*<p class="matrx-paragraph">/g, '</p><p class="matrx-paragraph">');
1007
1516
  html = html.replace(
1008
- /(<h1 class="matrx-h1">[\s\S]*?<\/h1>\s*)<p class="matrx-paragraph">([\s\S]*?)<\/p>/g,
1517
+ /(<h1 class="matrx-h1">[\s\S]*?<\/h1>\s*)<p class="matrx-paragraph">((?:(?!\uE000B)[\s\S])*?)<\/p>/g,
1009
1518
  '$1<p class="matrx-intro">$2</p>'
1010
1519
  );
1011
1520
  html = html.replace(
@@ -1032,42 +1541,7 @@ function markdownToHtml(markdown, options = {}) {
1032
1541
  /(<div class="matrx-faq-answer">)<p class="matrx-paragraph">([\s\S]*?)<\/p>(<\/div>)/g,
1033
1542
  "$1$2$3"
1034
1543
  );
1035
- for (const { placeholder, html: imageHtml } of imagePlaceholders) {
1036
- if (html.includes(placeholder)) html = html.replaceAll(placeholder, imageHtml);
1037
- }
1038
- html = html.replace(/<p class="matrx-paragraph">\s*(<img[^>]*>)\s*<\/p>/g, "$1");
1039
- for (const { placeholder, html: linkHtml } of linkPlaceholders) {
1040
- if (html.includes(placeholder)) html = html.replaceAll(placeholder, linkHtml);
1041
- }
1042
- for (const { placeholder, html: cardHtml } of flashcardPlaceholders) {
1043
- if (html.includes(placeholder)) html = html.replaceAll(placeholder, cardHtml);
1044
- }
1045
- return startWrapper + html + endWrapper;
1046
- }
1047
- function convertArticleTables(html) {
1048
- const tableRegex = /^(\|.*\|)\s*\n(\|[-\s:|]*\|)\s*\n((?:\|.*\|\s*\n?)*)/gm;
1049
- return html.replace(
1050
- tableRegex,
1051
- (_match, headerRow, _sep, bodyRows) => {
1052
- const headers = headerRow.split("|").map((c) => c.trim()).filter((c) => c !== "");
1053
- const rows = bodyRows.trim().split("\n").filter((r) => r.trim()).map(
1054
- (r) => r.split("|").map((c) => c.trim()).filter((c) => c !== "")
1055
- );
1056
- let t = '<table class="print-table">\n';
1057
- if (headers.length) {
1058
- t += `<thead><tr>${headers.map((h) => `<th>${h}</th>`).join("")}</tr></thead>
1059
- `;
1060
- }
1061
- if (rows.length) {
1062
- t += `<tbody>${rows.map(
1063
- (r) => r.length ? `<tr>${r.map((c) => `<td>${c}</td>`).join("")}</tr>` : ""
1064
- ).join("\n")}</tbody>
1065
- `;
1066
- }
1067
- return `${t}</table>
1068
- `;
1069
- }
1070
- );
1544
+ return stash.restore(html);
1071
1545
  }
1072
1546
  function markdownToArticleHtml(markdown, options = {}) {
1073
1547
  if (!markdown) {
@@ -1078,117 +1552,38 @@ function markdownToArticleHtml(markdown, options = {}) {
1078
1552
  });
1079
1553
  return "";
1080
1554
  }
1081
- let html = options.includeThinking ? markdown : removeThinkingContent(markdown);
1082
- html = convertArticleTables(html);
1083
- const codeBlockPlaceholders = [];
1084
- let codeIdx = 0;
1555
+ const source = scrubTokens(options.includeThinking ? markdown : removeThinkingContent(markdown));
1556
+ return articleBody(source, newContext(options));
1557
+ }
1558
+ function articleBody(markdown, ctx) {
1559
+ const g = ARTICLE_GRAMMAR;
1560
+ const stash = new Stash();
1561
+ let html = protectRegions(markdown, g, stash, ctx);
1562
+ html = convertTables(html, g);
1563
+ html = convertRules(html, g);
1085
1564
  html = html.replace(
1086
- /```(\w*)\n?([\s\S]*?)```/g,
1087
- (_m, lang, code) => {
1088
- const ph = `\u03A9CODEBLOCK\u03A9${codeIdx}\u03A9CODEBLOCK\u03A9`;
1089
- const langLabel = lang ? `<span class="code-lang">${lang}</span>` : "";
1090
- codeBlockPlaceholders.push({
1091
- placeholder: ph,
1092
- html: `<div class="code-block-wrapper">${langLabel}<pre class="code-block"><code>${escapeHtml2(
1093
- code.trim()
1094
- )}</code></pre></div>`
1095
- });
1096
- codeIdx++;
1097
- return ph;
1098
- }
1099
- );
1100
- const imagePlaceholders = [];
1101
- let imageIdx = 0;
1102
- html = html.replace(
1103
- /!\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g,
1104
- (_m, alt, url) => {
1105
- const ph = `\u03A9IMAGE\u03A9${imageIdx}\u03A9IMAGE\u03A9`;
1106
- imagePlaceholders.push({
1107
- placeholder: ph,
1108
- html: `<figure class="print-figure"><img class="print-image" src="${url}" alt="${escapeHtml2(alt)}" />${alt ? `<figcaption>${escapeHtml2(alt)}</figcaption>` : ""}</figure>`
1109
- });
1110
- imageIdx++;
1111
- return ph;
1112
- }
1565
+ IMAGE_RE,
1566
+ (_m, alt, url) => stash.block(
1567
+ `<figure class="print-figure"><img class="print-image" src="${safeUrl(url)}" alt="${escapeHtml2(alt)}" />${alt ? `<figcaption>${escapeHtml2(alt)}</figcaption>` : ""}</figure>`
1568
+ )
1113
1569
  );
1114
- const linkPlaceholders = [];
1115
- let linkIdx = 0;
1116
1570
  html = html.replace(
1117
- /\[([^\]]+)\]\(([^)]+)\)/g,
1118
- (_m, text, url) => {
1119
- const ph = `\u03A9LINK\u03A9${linkIdx}\u03A9LINK\u03A9`;
1120
- linkPlaceholders.push({ placeholder: ph, html: `<a href="${url}">${text}</a>` });
1121
- linkIdx++;
1122
- return ph;
1123
- }
1571
+ LINK_RE,
1572
+ (_m, text, url) => stash.inline(`<a href="${safeUrl(url)}">${convertEmphasis(text, g)}</a>`)
1124
1573
  );
1125
1574
  html = html.replace(/^#{6}\s+(.+)$/gm, "<h6>$1</h6>").replace(/^#{5}\s+(.+)$/gm, "<h5>$1</h5>").replace(/^#{4}\s+(.+)$/gm, "<h4>$1</h4>").replace(/^#{3}\s+(.+)$/gm, "<h3>$1</h3>").replace(/^#{2}\s+(.+)$/gm, "<h2>$1</h2>").replace(/^#{1}\s+(.+)$/gm, "<h1>$1</h1>");
1126
- html = html.replace(/^> (.+)$/gm, "<blockquote>$1</blockquote>");
1127
- html = html.replace(/^[-*_]{3,}$/gm, "<hr>");
1128
- html = html.replace(/\*\*\*(.+?)\*\*\*/g, "<strong><em>$1</em></strong>").replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>").replace(/\*(.+?)\*/g, "<em>$1</em>").replace(/___(.+?)___/g, "<strong><em>$1</em></strong>").replace(/__(.+?)__/g, "<strong>$1</strong>").replace(/_([^_\s][^_]*)_/g, "<em>$1</em>");
1129
- html = html.replace(/`([^`]+)`/g, '<code class="inline-code">$1</code>');
1130
- const lines = html.split("\n");
1131
- const result = [];
1132
- let inUl = false;
1133
- let inOl = false;
1134
- for (const line of lines) {
1135
- const ulMatch = line.match(/^(\s*)[-*+]\s+(.+)$/);
1136
- const olMatch = line.match(/^(\s*)\d+\.\s+(.+)$/);
1137
- if (ulMatch) {
1138
- if (inOl) {
1139
- result.push("</ol>");
1140
- inOl = false;
1141
- }
1142
- if (!inUl) {
1143
- result.push("<ul>");
1144
- inUl = true;
1145
- }
1146
- result.push(`<li>${ulMatch[2] ?? ""}</li>`);
1147
- } else if (olMatch) {
1148
- if (inUl) {
1149
- result.push("</ul>");
1150
- inUl = false;
1151
- }
1152
- if (!inOl) {
1153
- result.push("<ol>");
1154
- inOl = true;
1155
- }
1156
- result.push(`<li>${olMatch[2] ?? ""}</li>`);
1157
- } else {
1158
- if (inUl) {
1159
- result.push("</ul>");
1160
- inUl = false;
1161
- }
1162
- if (inOl) {
1163
- result.push("</ol>");
1164
- inOl = false;
1165
- }
1166
- result.push(line);
1167
- }
1168
- }
1169
- if (inUl) result.push("</ul>");
1170
- if (inOl) result.push("</ol>");
1171
- html = result.join("\n");
1575
+ html = html.replace(/^> ?(.+)$/gm, "<blockquote>$1</blockquote>");
1576
+ html = convertLists(html, g);
1577
+ html = convertEmphasis(html, g);
1172
1578
  html = html.split("\n").map((line) => {
1173
1579
  const t = line.trim();
1174
1580
  if (!t) return "";
1175
- if (/^<\/?(h[1-6]|ul|ol|li|blockquote|pre|hr|table|thead|tbody|tr|th|td|div|figure)/.test(t) || /^Ω(CODEBLOCK|IMAGE)Ω/.test(t)) {
1581
+ if (/^<\/?(h[1-6]|ul|ol|li|blockquote|pre|hr|table|thead|tbody|tr|th|td|div|figure|section)/.test(t) || /^\uE000B\d+\uE001$/.test(t)) {
1176
1582
  return line;
1177
1583
  }
1178
1584
  return `<p>${t}</p>`;
1179
1585
  }).join("\n");
1180
- for (const { placeholder, html: ph } of codeBlockPlaceholders) {
1181
- html = html.replace(`<p>${placeholder}</p>`, ph);
1182
- html = html.replace(placeholder, ph);
1183
- }
1184
- for (const { placeholder, html: ph } of imagePlaceholders) {
1185
- html = html.replaceAll(`<p>${placeholder}</p>`, ph);
1186
- html = html.replaceAll(placeholder, ph);
1187
- }
1188
- for (const { placeholder, html: ph } of linkPlaceholders) {
1189
- html = html.replaceAll(placeholder, ph);
1190
- }
1191
- return html;
1586
+ return stash.restore(html).replace(/\n{3,}/g, "\n\n").trim();
1192
1587
  }
1193
1588
  var PRINT_ACTIONS_CSS = `
1194
1589
  .print-actions { display: flex; gap: 10px; margin: 0 auto 28px; max-width: 800px; padding: 0 2rem 20px; border-bottom: 2px solid var(--matrx-print-border, #e1e1e1); }
@@ -1202,7 +1597,8 @@ function getMarkdownStylesheet(options = {}) {
1202
1597
  const base = skin === "article" ? `${MARKDOWN_ARTICLE_TOKENS_CSS}
1203
1598
  ${MARKDOWN_ARTICLE_CSS}` : `${MARKDOWN_DOCUMENT_TOKENS_CSS}
1204
1599
  ${MARKDOWN_DOCUMENT_CSS}`;
1205
- return [base, options.tokens ?? "", options.extraCss ?? ""].filter((part) => part.trim().length > 0).join("\n");
1600
+ const page = options.pageNumbers === false ? MARKDOWN_PAGE_PLAIN_CSS : MARKDOWN_PAGE_CSS;
1601
+ return [base, MARKDOWN_SHARED_CSS, page, options.tokens ?? "", options.extraCss ?? ""].filter((part) => part.trim().length > 0).join("\n");
1206
1602
  }
1207
1603
  function markdownToHtmlForSkin(markdown, skin, options = {}) {
1208
1604
  return skin === "article" ? markdownToArticleHtml(markdown, options) : markdownToHtml(markdown, options);
@@ -1243,14 +1639,20 @@ export {
1243
1639
  MARKDOWN_ARTICLE_TOKENS_CSS,
1244
1640
  MARKDOWN_DOCUMENT_CSS,
1245
1641
  MARKDOWN_DOCUMENT_TOKENS_CSS,
1642
+ MARKDOWN_PAGE_CSS,
1643
+ MARKDOWN_SHARED_CSS,
1644
+ PAGE_BREAK_CLASS,
1645
+ PAGE_BREAK_MARKDOWN,
1246
1646
  PRINT_ACTIONS_CSS,
1247
1647
  getMarkdownStylesheet,
1248
1648
  hasThinkingContent,
1649
+ isPageBreakLine,
1249
1650
  markdownToArticleHtml,
1250
1651
  markdownToHtml,
1251
1652
  markdownToHtmlForSkin,
1252
1653
  printMarkdown,
1253
1654
  removeThinkingContent,
1254
- renderMarkdownDocument
1655
+ renderMarkdownDocument,
1656
+ splitAtPageBreaks
1255
1657
  };
1256
1658
  //# sourceMappingURL=markdown.js.map