@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.js
CHANGED
|
@@ -22,6 +22,104 @@ function openPrintWindow(htmlDocument, filename = "print", windowFeatures = "wid
|
|
|
22
22
|
|
|
23
23
|
// src/markdown.ts
|
|
24
24
|
import { escapeHtml as escapeHtml2 } from "@ai-matrx/kit/html-escape";
|
|
25
|
+
import { findTableEnd, rowCells, tableStartsAt, unescapeCellPipes } from "@ai-matrx/content-ir/source";
|
|
26
|
+
|
|
27
|
+
// src/safe-html.ts
|
|
28
|
+
import { fromHtml } from "hast-util-from-html";
|
|
29
|
+
import { defaultSchema, sanitize } from "hast-util-sanitize";
|
|
30
|
+
import { toHtml } from "hast-util-to-html";
|
|
31
|
+
var STRIP_WITH_CONTENT = [
|
|
32
|
+
"script",
|
|
33
|
+
"style",
|
|
34
|
+
"noscript",
|
|
35
|
+
"template",
|
|
36
|
+
"iframe",
|
|
37
|
+
"object",
|
|
38
|
+
"embed",
|
|
39
|
+
"textarea",
|
|
40
|
+
"select",
|
|
41
|
+
"head",
|
|
42
|
+
"title"
|
|
43
|
+
];
|
|
44
|
+
var FORBIDDEN_TAGS = [
|
|
45
|
+
...STRIP_WITH_CONTENT,
|
|
46
|
+
"form",
|
|
47
|
+
"meta",
|
|
48
|
+
"base",
|
|
49
|
+
"link",
|
|
50
|
+
"frame",
|
|
51
|
+
"frameset",
|
|
52
|
+
"applet",
|
|
53
|
+
"button",
|
|
54
|
+
"svg",
|
|
55
|
+
"math"
|
|
56
|
+
];
|
|
57
|
+
var baseAttributes = defaultSchema.attributes ?? {};
|
|
58
|
+
var SAFE_HTML_SCHEMA = {
|
|
59
|
+
...defaultSchema,
|
|
60
|
+
tagNames: (defaultSchema.tagNames ?? []).filter((t) => !FORBIDDEN_TAGS.includes(t)),
|
|
61
|
+
strip: STRIP_WITH_CONTENT,
|
|
62
|
+
// Authored anchors keep their names; the GitHub `user-content-` rewrite
|
|
63
|
+
// would also break `<label for>` → `<input id>` pairs.
|
|
64
|
+
clobberPrefix: "",
|
|
65
|
+
clobber: []
|
|
66
|
+
};
|
|
67
|
+
var PRINT_CLASS = /^(?:(?:matrx|print|code|doc|language)-[\w-]*|inline-code|is-checked)$/;
|
|
68
|
+
function withPrintClasses(defs = []) {
|
|
69
|
+
let found = false;
|
|
70
|
+
const out = defs.map((d) => {
|
|
71
|
+
if (Array.isArray(d) && d[0] === "className") {
|
|
72
|
+
found = true;
|
|
73
|
+
return [...d, PRINT_CLASS];
|
|
74
|
+
}
|
|
75
|
+
return d;
|
|
76
|
+
});
|
|
77
|
+
return found ? out : [...out, ["className", PRINT_CLASS]];
|
|
78
|
+
}
|
|
79
|
+
var printAttributes = Object.fromEntries(
|
|
80
|
+
Object.entries(baseAttributes).map(([tag, defs]) => [tag, withPrintClasses(defs)])
|
|
81
|
+
);
|
|
82
|
+
var cellAlign = ["style", /^text-align:\s*(?:left|center|right);?$/];
|
|
83
|
+
var PRINT_HTML_SCHEMA = {
|
|
84
|
+
...SAFE_HTML_SCHEMA,
|
|
85
|
+
tagNames: [
|
|
86
|
+
.../* @__PURE__ */ new Set([...SAFE_HTML_SCHEMA.tagNames ?? [], "section", "figure", "figcaption", "label"])
|
|
87
|
+
],
|
|
88
|
+
attributes: {
|
|
89
|
+
...printAttributes,
|
|
90
|
+
"*": [
|
|
91
|
+
...baseAttributes["*"] ?? [],
|
|
92
|
+
["className", PRINT_CLASS],
|
|
93
|
+
"role",
|
|
94
|
+
"ariaLabel",
|
|
95
|
+
"ariaHidden",
|
|
96
|
+
"dataLabel",
|
|
97
|
+
"dataLanguage",
|
|
98
|
+
"dataArtifactType"
|
|
99
|
+
],
|
|
100
|
+
// Table-cell alignment is the one inline style the converter writes.
|
|
101
|
+
td: withPrintClasses([...baseAttributes["td"] ?? [], [...cellAlign]]),
|
|
102
|
+
th: withPrintClasses([...baseAttributes["th"] ?? [], [...cellAlign]]),
|
|
103
|
+
// The flashcard flip is a checkbox + label pair (GitHub forces `disabled`).
|
|
104
|
+
input: withPrintClasses([["type", "checkbox"], "checked"]),
|
|
105
|
+
label: withPrintClasses(["htmlFor"])
|
|
106
|
+
},
|
|
107
|
+
required: { input: { type: "checkbox" } },
|
|
108
|
+
protocols: {
|
|
109
|
+
...defaultSchema.protocols,
|
|
110
|
+
href: [...defaultSchema.protocols?.href ?? [], "tel"],
|
|
111
|
+
// Embedded images (`data:image/png;base64,…`) — an <img> never runs script.
|
|
112
|
+
src: [...defaultSchema.protocols?.src ?? [], "data"]
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
function sanitizeHtmlFragment(html, schema = SAFE_HTML_SCHEMA) {
|
|
116
|
+
if (!html) return "";
|
|
117
|
+
const tree = sanitize(fromHtml(html, { fragment: true }), schema);
|
|
118
|
+
return toHtml(tree, { characterReferences: { useNamedReferences: true } });
|
|
119
|
+
}
|
|
120
|
+
function sanitizePrintHtml(html) {
|
|
121
|
+
return sanitizeHtmlFragment(html, PRINT_HTML_SCHEMA);
|
|
122
|
+
}
|
|
25
123
|
|
|
26
124
|
// src/markdown-css.ts
|
|
27
125
|
var MARKDOWN_DOCUMENT_TOKENS_CSS = `:root {
|
|
@@ -1362,11 +1460,11 @@ function protectRegions(markdown, g, stash, ctx) {
|
|
|
1362
1460
|
text = next;
|
|
1363
1461
|
}
|
|
1364
1462
|
text = sanitizeRawTags(text);
|
|
1365
|
-
if (text !== before && !ctx.strippedAnnounced) {
|
|
1463
|
+
if ((text !== before || hasDisallowedTag(text)) && !ctx.strippedAnnounced) {
|
|
1366
1464
|
ctx.strippedAnnounced = true;
|
|
1367
1465
|
ctx.options.onNotice?.({
|
|
1368
1466
|
code: "html-stripped",
|
|
1369
|
-
message: "
|
|
1467
|
+
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.",
|
|
1370
1468
|
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.'
|
|
1371
1469
|
});
|
|
1372
1470
|
}
|
|
@@ -1378,6 +1476,14 @@ ${stash.block(flashcardsHtml(g, cards, ctx))}
|
|
|
1378
1476
|
});
|
|
1379
1477
|
return text;
|
|
1380
1478
|
}
|
|
1479
|
+
var ALLOWED_TAGS = new Set(PRINT_HTML_SCHEMA.tagNames ?? []);
|
|
1480
|
+
function hasDisallowedTag(text) {
|
|
1481
|
+
for (const m of text.matchAll(/<\/?([a-zA-Z][\w:-]*)/g)) {
|
|
1482
|
+
const tag = m[1].toLowerCase();
|
|
1483
|
+
if (tag !== "flashcards" && !ALLOWED_TAGS.has(tag)) return true;
|
|
1484
|
+
}
|
|
1485
|
+
return false;
|
|
1486
|
+
}
|
|
1381
1487
|
var URL_ATTRS = /* @__PURE__ */ new Set(["href", "src", "action", "formaction", "xlink:href", "poster", "background"]);
|
|
1382
1488
|
function decodeForSchemeCheck(value) {
|
|
1383
1489
|
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();
|
|
@@ -1426,13 +1532,7 @@ var LINK_RE = new RegExp(String.raw`\[([^\]]+)\]\(` + LINK_URL + String.raw`\)`,
|
|
|
1426
1532
|
function convertRules(html, g) {
|
|
1427
1533
|
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)}>`);
|
|
1428
1534
|
}
|
|
1429
|
-
function
|
|
1430
|
-
let r = row.trim();
|
|
1431
|
-
if (r.startsWith("|")) r = r.slice(1);
|
|
1432
|
-
if (r.endsWith("|") && !r.endsWith("\\|")) r = r.slice(0, -1);
|
|
1433
|
-
return r.split(/(?<!\\)\|/).map((c) => c.trim().replace(/\\\|/g, "|"));
|
|
1434
|
-
}
|
|
1435
|
-
function cellAlign(separator) {
|
|
1535
|
+
function cellAlign2(separator) {
|
|
1436
1536
|
const s = separator.trim();
|
|
1437
1537
|
const left = s.startsWith(":");
|
|
1438
1538
|
const right = s.endsWith(":");
|
|
@@ -1441,40 +1541,48 @@ function cellAlign(separator) {
|
|
|
1441
1541
|
return "";
|
|
1442
1542
|
}
|
|
1443
1543
|
function convertTables(html, g) {
|
|
1444
|
-
const
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
(
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1544
|
+
const lines = html.split("\n");
|
|
1545
|
+
const out = [];
|
|
1546
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
1547
|
+
if (!tableStartsAt(lines, i)) {
|
|
1548
|
+
out.push(lines[i] ?? "");
|
|
1549
|
+
continue;
|
|
1550
|
+
}
|
|
1551
|
+
const end = findTableEnd(lines, i);
|
|
1552
|
+
const cellsOf = (line) => rowCells(line).map(unescapeCellPipes);
|
|
1553
|
+
const headers = cellsOf(lines[i] ?? "");
|
|
1554
|
+
const aligns = rowCells(lines[i + 1] ?? "").map(cellAlign2);
|
|
1555
|
+
const rows = lines.slice(i + 2, end).map((line) => {
|
|
1556
|
+
const cells = cellsOf(line);
|
|
1557
|
+
return headers.map((_h, k) => cells[k] ?? "");
|
|
1558
|
+
});
|
|
1559
|
+
let t = `<table${cls(g.table)}>
|
|
1452
1560
|
<thead${cls(g.thead)}>
|
|
1453
1561
|
<tr${cls(g.tr)}>
|
|
1454
1562
|
`;
|
|
1455
|
-
|
|
1456
|
-
|
|
1563
|
+
headers.forEach((h, k) => {
|
|
1564
|
+
t += `<th${cls(g.th)}${aligns[k] ?? ""}>${h}</th>
|
|
1457
1565
|
`;
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1566
|
+
});
|
|
1567
|
+
t += "</tr>\n</thead>\n";
|
|
1568
|
+
if (rows.length > 0) {
|
|
1569
|
+
t += `<tbody${cls(g.tbody)}>
|
|
1462
1570
|
`;
|
|
1463
|
-
|
|
1464
|
-
|
|
1571
|
+
for (const row of rows) {
|
|
1572
|
+
t += `<tr${cls(g.tr)}>
|
|
1465
1573
|
`;
|
|
1466
|
-
|
|
1467
|
-
|
|
1574
|
+
row.forEach((c, k) => {
|
|
1575
|
+
t += `<td${cls(g.td)}${aligns[k] ?? ""}>${c}</td>
|
|
1468
1576
|
`;
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
}
|
|
1472
|
-
t += "</tbody>\n";
|
|
1577
|
+
});
|
|
1578
|
+
t += "</tr>\n";
|
|
1473
1579
|
}
|
|
1474
|
-
|
|
1475
|
-
`;
|
|
1580
|
+
t += "</tbody>\n";
|
|
1476
1581
|
}
|
|
1477
|
-
|
|
1582
|
+
out.push(`${t}</table>`);
|
|
1583
|
+
i = end - 1;
|
|
1584
|
+
}
|
|
1585
|
+
return out.join("\n");
|
|
1478
1586
|
}
|
|
1479
1587
|
function convertLists(html, g) {
|
|
1480
1588
|
const lines = html.split("\n");
|
|
@@ -1560,7 +1668,9 @@ function markdownToHtml(markdown, options = {}) {
|
|
|
1560
1668
|
return "";
|
|
1561
1669
|
}
|
|
1562
1670
|
const source = scrubTokens(options.includeThinking ? markdown : removeThinkingContent(markdown));
|
|
1563
|
-
return
|
|
1671
|
+
return sanitizePrintHtml(
|
|
1672
|
+
`<div class="matrx-content-container">${documentBody(source, newContext(options))}</div>`
|
|
1673
|
+
);
|
|
1564
1674
|
}
|
|
1565
1675
|
function documentBody(markdown, ctx) {
|
|
1566
1676
|
const g = DOCUMENT_GRAMMAR;
|
|
@@ -1630,7 +1740,7 @@ function markdownToArticleHtml(markdown, options = {}) {
|
|
|
1630
1740
|
return "";
|
|
1631
1741
|
}
|
|
1632
1742
|
const source = scrubTokens(options.includeThinking ? markdown : removeThinkingContent(markdown));
|
|
1633
|
-
return articleBody(source, newContext(options));
|
|
1743
|
+
return sanitizePrintHtml(articleBody(source, newContext(options)));
|
|
1634
1744
|
}
|
|
1635
1745
|
function articleBody(markdown, ctx) {
|
|
1636
1746
|
const g = ARTICLE_GRAMMAR;
|