@ai-matrx/print 0.8.7 → 0.8.9
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/CHANGELOG.md +51 -0
- package/dist/document.cjs +256 -8
- package/dist/document.cjs.map +1 -1
- package/dist/document.d.cts +10 -2
- package/dist/document.d.ts +10 -2
- package/dist/document.js +256 -8
- package/dist/document.js.map +1 -1
- package/dist/markdown-html.cjs +4207 -0
- package/dist/markdown-html.cjs.map +1 -0
- package/dist/markdown-html.d.cts +87 -0
- package/dist/markdown-html.d.ts +87 -0
- package/dist/markdown-html.js +4176 -0
- package/dist/markdown-html.js.map +1 -0
- package/dist/markdown.cjs +146 -36
- package/dist/markdown.cjs.map +1 -1
- package/dist/markdown.js +146 -36
- package/dist/markdown.js.map +1 -1
- package/dist/safe-html.cjs +299 -0
- package/dist/safe-html.cjs.map +1 -0
- package/dist/safe-html.d.cts +56 -0
- package/dist/safe-html.d.ts +56 -0
- package/dist/safe-html.js +278 -0
- package/dist/safe-html.js.map +1 -0
- package/package.json +27 -3
package/dist/markdown.cjs
CHANGED
|
@@ -67,6 +67,104 @@ function openPrintWindow(htmlDocument, filename = "print", windowFeatures = "wid
|
|
|
67
67
|
|
|
68
68
|
// src/markdown.ts
|
|
69
69
|
var import_html_escape2 = require("@ai-matrx/kit/html-escape");
|
|
70
|
+
var import_source = require("@ai-matrx/content-ir/source");
|
|
71
|
+
|
|
72
|
+
// src/safe-html.ts
|
|
73
|
+
var import_hast_util_from_html = require("hast-util-from-html");
|
|
74
|
+
var import_hast_util_sanitize = require("hast-util-sanitize");
|
|
75
|
+
var import_hast_util_to_html = require("hast-util-to-html");
|
|
76
|
+
var STRIP_WITH_CONTENT = [
|
|
77
|
+
"script",
|
|
78
|
+
"style",
|
|
79
|
+
"noscript",
|
|
80
|
+
"template",
|
|
81
|
+
"iframe",
|
|
82
|
+
"object",
|
|
83
|
+
"embed",
|
|
84
|
+
"textarea",
|
|
85
|
+
"select",
|
|
86
|
+
"head",
|
|
87
|
+
"title"
|
|
88
|
+
];
|
|
89
|
+
var FORBIDDEN_TAGS = [
|
|
90
|
+
...STRIP_WITH_CONTENT,
|
|
91
|
+
"form",
|
|
92
|
+
"meta",
|
|
93
|
+
"base",
|
|
94
|
+
"link",
|
|
95
|
+
"frame",
|
|
96
|
+
"frameset",
|
|
97
|
+
"applet",
|
|
98
|
+
"button",
|
|
99
|
+
"svg",
|
|
100
|
+
"math"
|
|
101
|
+
];
|
|
102
|
+
var baseAttributes = import_hast_util_sanitize.defaultSchema.attributes ?? {};
|
|
103
|
+
var SAFE_HTML_SCHEMA = {
|
|
104
|
+
...import_hast_util_sanitize.defaultSchema,
|
|
105
|
+
tagNames: (import_hast_util_sanitize.defaultSchema.tagNames ?? []).filter((t) => !FORBIDDEN_TAGS.includes(t)),
|
|
106
|
+
strip: STRIP_WITH_CONTENT,
|
|
107
|
+
// Authored anchors keep their names; the GitHub `user-content-` rewrite
|
|
108
|
+
// would also break `<label for>` → `<input id>` pairs.
|
|
109
|
+
clobberPrefix: "",
|
|
110
|
+
clobber: []
|
|
111
|
+
};
|
|
112
|
+
var PRINT_CLASS = /^(?:(?:matrx|print|code|doc|language)-[\w-]*|inline-code|is-checked)$/;
|
|
113
|
+
function withPrintClasses(defs = []) {
|
|
114
|
+
let found = false;
|
|
115
|
+
const out = defs.map((d) => {
|
|
116
|
+
if (Array.isArray(d) && d[0] === "className") {
|
|
117
|
+
found = true;
|
|
118
|
+
return [...d, PRINT_CLASS];
|
|
119
|
+
}
|
|
120
|
+
return d;
|
|
121
|
+
});
|
|
122
|
+
return found ? out : [...out, ["className", PRINT_CLASS]];
|
|
123
|
+
}
|
|
124
|
+
var printAttributes = Object.fromEntries(
|
|
125
|
+
Object.entries(baseAttributes).map(([tag, defs]) => [tag, withPrintClasses(defs)])
|
|
126
|
+
);
|
|
127
|
+
var cellAlign = ["style", /^text-align:\s*(?:left|center|right);?$/];
|
|
128
|
+
var PRINT_HTML_SCHEMA = {
|
|
129
|
+
...SAFE_HTML_SCHEMA,
|
|
130
|
+
tagNames: [
|
|
131
|
+
.../* @__PURE__ */ new Set([...SAFE_HTML_SCHEMA.tagNames ?? [], "section", "figure", "figcaption", "label"])
|
|
132
|
+
],
|
|
133
|
+
attributes: {
|
|
134
|
+
...printAttributes,
|
|
135
|
+
"*": [
|
|
136
|
+
...baseAttributes["*"] ?? [],
|
|
137
|
+
["className", PRINT_CLASS],
|
|
138
|
+
"role",
|
|
139
|
+
"ariaLabel",
|
|
140
|
+
"ariaHidden",
|
|
141
|
+
"dataLabel",
|
|
142
|
+
"dataLanguage",
|
|
143
|
+
"dataArtifactType"
|
|
144
|
+
],
|
|
145
|
+
// Table-cell alignment is the one inline style the converter writes.
|
|
146
|
+
td: withPrintClasses([...baseAttributes["td"] ?? [], [...cellAlign]]),
|
|
147
|
+
th: withPrintClasses([...baseAttributes["th"] ?? [], [...cellAlign]]),
|
|
148
|
+
// The flashcard flip is a checkbox + label pair (GitHub forces `disabled`).
|
|
149
|
+
input: withPrintClasses([["type", "checkbox"], "checked"]),
|
|
150
|
+
label: withPrintClasses(["htmlFor"])
|
|
151
|
+
},
|
|
152
|
+
required: { input: { type: "checkbox" } },
|
|
153
|
+
protocols: {
|
|
154
|
+
...import_hast_util_sanitize.defaultSchema.protocols,
|
|
155
|
+
href: [...import_hast_util_sanitize.defaultSchema.protocols?.href ?? [], "tel"],
|
|
156
|
+
// Embedded images (`data:image/png;base64,…`) — an <img> never runs script.
|
|
157
|
+
src: [...import_hast_util_sanitize.defaultSchema.protocols?.src ?? [], "data"]
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
function sanitizeHtmlFragment(html, schema = SAFE_HTML_SCHEMA) {
|
|
161
|
+
if (!html) return "";
|
|
162
|
+
const tree = (0, import_hast_util_sanitize.sanitize)((0, import_hast_util_from_html.fromHtml)(html, { fragment: true }), schema);
|
|
163
|
+
return (0, import_hast_util_to_html.toHtml)(tree, { characterReferences: { useNamedReferences: true } });
|
|
164
|
+
}
|
|
165
|
+
function sanitizePrintHtml(html) {
|
|
166
|
+
return sanitizeHtmlFragment(html, PRINT_HTML_SCHEMA);
|
|
167
|
+
}
|
|
70
168
|
|
|
71
169
|
// src/markdown-css.ts
|
|
72
170
|
var MARKDOWN_DOCUMENT_TOKENS_CSS = `:root {
|
|
@@ -1407,11 +1505,11 @@ function protectRegions(markdown, g, stash, ctx) {
|
|
|
1407
1505
|
text = next;
|
|
1408
1506
|
}
|
|
1409
1507
|
text = sanitizeRawTags(text);
|
|
1410
|
-
if (text !== before && !ctx.strippedAnnounced) {
|
|
1508
|
+
if ((text !== before || hasDisallowedTag(text)) && !ctx.strippedAnnounced) {
|
|
1411
1509
|
ctx.strippedAnnounced = true;
|
|
1412
1510
|
ctx.options.onNotice?.({
|
|
1413
1511
|
code: "html-stripped",
|
|
1414
|
-
message: "
|
|
1512
|
+
message: "Markup that is not on the safe list (a <script>, <style>, <iframe>, <form>, <object>/<embed>, <meta>, <link>, an on\u2026 event handler, inline style, or a javascript: URL) was removed from the rendered copy.",
|
|
1415
1513
|
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.'
|
|
1416
1514
|
});
|
|
1417
1515
|
}
|
|
@@ -1423,6 +1521,14 @@ ${stash.block(flashcardsHtml(g, cards, ctx))}
|
|
|
1423
1521
|
});
|
|
1424
1522
|
return text;
|
|
1425
1523
|
}
|
|
1524
|
+
var ALLOWED_TAGS = new Set(PRINT_HTML_SCHEMA.tagNames ?? []);
|
|
1525
|
+
function hasDisallowedTag(text) {
|
|
1526
|
+
for (const m of text.matchAll(/<\/?([a-zA-Z][\w:-]*)/g)) {
|
|
1527
|
+
const tag = m[1].toLowerCase();
|
|
1528
|
+
if (tag !== "flashcards" && !ALLOWED_TAGS.has(tag)) return true;
|
|
1529
|
+
}
|
|
1530
|
+
return false;
|
|
1531
|
+
}
|
|
1426
1532
|
var URL_ATTRS = /* @__PURE__ */ new Set(["href", "src", "action", "formaction", "xlink:href", "poster", "background"]);
|
|
1427
1533
|
function decodeForSchemeCheck(value) {
|
|
1428
1534
|
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(/:/gi, ":").replace(/[\u0000-\u0020]+/g, "").toLowerCase();
|
|
@@ -1471,13 +1577,7 @@ var LINK_RE = new RegExp(String.raw`\[([^\]]+)\]\(` + LINK_URL + String.raw`\)`,
|
|
|
1471
1577
|
function convertRules(html, g) {
|
|
1472
1578
|
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)}>`);
|
|
1473
1579
|
}
|
|
1474
|
-
function
|
|
1475
|
-
let r = row.trim();
|
|
1476
|
-
if (r.startsWith("|")) r = r.slice(1);
|
|
1477
|
-
if (r.endsWith("|") && !r.endsWith("\\|")) r = r.slice(0, -1);
|
|
1478
|
-
return r.split(/(?<!\\)\|/).map((c) => c.trim().replace(/\\\|/g, "|"));
|
|
1479
|
-
}
|
|
1480
|
-
function cellAlign(separator) {
|
|
1580
|
+
function cellAlign2(separator) {
|
|
1481
1581
|
const s = separator.trim();
|
|
1482
1582
|
const left = s.startsWith(":");
|
|
1483
1583
|
const right = s.endsWith(":");
|
|
@@ -1486,40 +1586,48 @@ function cellAlign(separator) {
|
|
|
1486
1586
|
return "";
|
|
1487
1587
|
}
|
|
1488
1588
|
function convertTables(html, g) {
|
|
1489
|
-
const
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
(
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1589
|
+
const lines = html.split("\n");
|
|
1590
|
+
const out = [];
|
|
1591
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
1592
|
+
if (!(0, import_source.tableStartsAt)(lines, i)) {
|
|
1593
|
+
out.push(lines[i] ?? "");
|
|
1594
|
+
continue;
|
|
1595
|
+
}
|
|
1596
|
+
const end = (0, import_source.findTableEnd)(lines, i);
|
|
1597
|
+
const cellsOf = (line) => (0, import_source.rowCells)(line).map(import_source.unescapeCellPipes);
|
|
1598
|
+
const headers = cellsOf(lines[i] ?? "");
|
|
1599
|
+
const aligns = (0, import_source.rowCells)(lines[i + 1] ?? "").map(cellAlign2);
|
|
1600
|
+
const rows = lines.slice(i + 2, end).map((line) => {
|
|
1601
|
+
const cells = cellsOf(line);
|
|
1602
|
+
return headers.map((_h, k) => cells[k] ?? "");
|
|
1603
|
+
});
|
|
1604
|
+
let t = `<table${cls(g.table)}>
|
|
1497
1605
|
<thead${cls(g.thead)}>
|
|
1498
1606
|
<tr${cls(g.tr)}>
|
|
1499
1607
|
`;
|
|
1500
|
-
|
|
1501
|
-
|
|
1608
|
+
headers.forEach((h, k) => {
|
|
1609
|
+
t += `<th${cls(g.th)}${aligns[k] ?? ""}>${h}</th>
|
|
1502
1610
|
`;
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1611
|
+
});
|
|
1612
|
+
t += "</tr>\n</thead>\n";
|
|
1613
|
+
if (rows.length > 0) {
|
|
1614
|
+
t += `<tbody${cls(g.tbody)}>
|
|
1507
1615
|
`;
|
|
1508
|
-
|
|
1509
|
-
|
|
1616
|
+
for (const row of rows) {
|
|
1617
|
+
t += `<tr${cls(g.tr)}>
|
|
1510
1618
|
`;
|
|
1511
|
-
|
|
1512
|
-
|
|
1619
|
+
row.forEach((c, k) => {
|
|
1620
|
+
t += `<td${cls(g.td)}${aligns[k] ?? ""}>${c}</td>
|
|
1513
1621
|
`;
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
}
|
|
1517
|
-
t += "</tbody>\n";
|
|
1622
|
+
});
|
|
1623
|
+
t += "</tr>\n";
|
|
1518
1624
|
}
|
|
1519
|
-
|
|
1520
|
-
`;
|
|
1625
|
+
t += "</tbody>\n";
|
|
1521
1626
|
}
|
|
1522
|
-
|
|
1627
|
+
out.push(`${t}</table>`);
|
|
1628
|
+
i = end - 1;
|
|
1629
|
+
}
|
|
1630
|
+
return out.join("\n");
|
|
1523
1631
|
}
|
|
1524
1632
|
function convertLists(html, g) {
|
|
1525
1633
|
const lines = html.split("\n");
|
|
@@ -1605,7 +1713,9 @@ function markdownToHtml(markdown, options = {}) {
|
|
|
1605
1713
|
return "";
|
|
1606
1714
|
}
|
|
1607
1715
|
const source = scrubTokens(options.includeThinking ? markdown : removeThinkingContent(markdown));
|
|
1608
|
-
return
|
|
1716
|
+
return sanitizePrintHtml(
|
|
1717
|
+
`<div class="matrx-content-container">${documentBody(source, newContext(options))}</div>`
|
|
1718
|
+
);
|
|
1609
1719
|
}
|
|
1610
1720
|
function documentBody(markdown, ctx) {
|
|
1611
1721
|
const g = DOCUMENT_GRAMMAR;
|
|
@@ -1675,7 +1785,7 @@ function markdownToArticleHtml(markdown, options = {}) {
|
|
|
1675
1785
|
return "";
|
|
1676
1786
|
}
|
|
1677
1787
|
const source = scrubTokens(options.includeThinking ? markdown : removeThinkingContent(markdown));
|
|
1678
|
-
return articleBody(source, newContext(options));
|
|
1788
|
+
return sanitizePrintHtml(articleBody(source, newContext(options)));
|
|
1679
1789
|
}
|
|
1680
1790
|
function articleBody(markdown, ctx) {
|
|
1681
1791
|
const g = ARTICLE_GRAMMAR;
|