@ai-matrx/print 0.8.6 → 0.8.8
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 +35 -0
- package/dist/document.cjs +206 -4
- package/dist/document.cjs.map +1 -1
- package/dist/document.js +206 -4
- package/dist/document.js.map +1 -1
- package/dist/markdown.cjs +115 -7
- package/dist/markdown.cjs.map +1 -1
- package/dist/markdown.js +115 -7
- package/dist/markdown.js.map +1 -1
- package/dist/safe-html.cjs +231 -0
- package/dist/safe-html.cjs.map +1 -0
- package/dist/safe-html.d.cts +43 -0
- package/dist/safe-html.d.ts +43 -0
- package/dist/safe-html.js +210 -0
- package/dist/safe-html.js.map +1 -0
- package/package.json +15 -1
package/dist/markdown.js
CHANGED
|
@@ -23,6 +23,103 @@ function openPrintWindow(htmlDocument, filename = "print", windowFeatures = "wid
|
|
|
23
23
|
// src/markdown.ts
|
|
24
24
|
import { escapeHtml as escapeHtml2 } from "@ai-matrx/kit/html-escape";
|
|
25
25
|
|
|
26
|
+
// src/safe-html.ts
|
|
27
|
+
import { fromHtml } from "hast-util-from-html";
|
|
28
|
+
import { defaultSchema, sanitize } from "hast-util-sanitize";
|
|
29
|
+
import { toHtml } from "hast-util-to-html";
|
|
30
|
+
var STRIP_WITH_CONTENT = [
|
|
31
|
+
"script",
|
|
32
|
+
"style",
|
|
33
|
+
"noscript",
|
|
34
|
+
"template",
|
|
35
|
+
"iframe",
|
|
36
|
+
"object",
|
|
37
|
+
"embed",
|
|
38
|
+
"textarea",
|
|
39
|
+
"select",
|
|
40
|
+
"head",
|
|
41
|
+
"title"
|
|
42
|
+
];
|
|
43
|
+
var FORBIDDEN_TAGS = [
|
|
44
|
+
...STRIP_WITH_CONTENT,
|
|
45
|
+
"form",
|
|
46
|
+
"meta",
|
|
47
|
+
"base",
|
|
48
|
+
"link",
|
|
49
|
+
"frame",
|
|
50
|
+
"frameset",
|
|
51
|
+
"applet",
|
|
52
|
+
"button",
|
|
53
|
+
"svg",
|
|
54
|
+
"math"
|
|
55
|
+
];
|
|
56
|
+
var baseAttributes = defaultSchema.attributes ?? {};
|
|
57
|
+
var SAFE_HTML_SCHEMA = {
|
|
58
|
+
...defaultSchema,
|
|
59
|
+
tagNames: (defaultSchema.tagNames ?? []).filter((t) => !FORBIDDEN_TAGS.includes(t)),
|
|
60
|
+
strip: STRIP_WITH_CONTENT,
|
|
61
|
+
// Authored anchors keep their names; the GitHub `user-content-` rewrite
|
|
62
|
+
// would also break `<label for>` → `<input id>` pairs.
|
|
63
|
+
clobberPrefix: "",
|
|
64
|
+
clobber: []
|
|
65
|
+
};
|
|
66
|
+
var PRINT_CLASS = /^(?:(?:matrx|print|code|doc|language)-[\w-]*|inline-code|is-checked)$/;
|
|
67
|
+
function withPrintClasses(defs = []) {
|
|
68
|
+
let found = false;
|
|
69
|
+
const out = defs.map((d) => {
|
|
70
|
+
if (Array.isArray(d) && d[0] === "className") {
|
|
71
|
+
found = true;
|
|
72
|
+
return [...d, PRINT_CLASS];
|
|
73
|
+
}
|
|
74
|
+
return d;
|
|
75
|
+
});
|
|
76
|
+
return found ? out : [...out, ["className", PRINT_CLASS]];
|
|
77
|
+
}
|
|
78
|
+
var printAttributes = Object.fromEntries(
|
|
79
|
+
Object.entries(baseAttributes).map(([tag, defs]) => [tag, withPrintClasses(defs)])
|
|
80
|
+
);
|
|
81
|
+
var cellAlign = ["style", /^text-align:\s*(?:left|center|right);?$/];
|
|
82
|
+
var PRINT_HTML_SCHEMA = {
|
|
83
|
+
...SAFE_HTML_SCHEMA,
|
|
84
|
+
tagNames: [
|
|
85
|
+
.../* @__PURE__ */ new Set([...SAFE_HTML_SCHEMA.tagNames ?? [], "section", "figure", "figcaption", "label"])
|
|
86
|
+
],
|
|
87
|
+
attributes: {
|
|
88
|
+
...printAttributes,
|
|
89
|
+
"*": [
|
|
90
|
+
...baseAttributes["*"] ?? [],
|
|
91
|
+
["className", PRINT_CLASS],
|
|
92
|
+
"role",
|
|
93
|
+
"ariaLabel",
|
|
94
|
+
"ariaHidden",
|
|
95
|
+
"dataLabel",
|
|
96
|
+
"dataLanguage",
|
|
97
|
+
"dataArtifactType"
|
|
98
|
+
],
|
|
99
|
+
// Table-cell alignment is the one inline style the converter writes.
|
|
100
|
+
td: withPrintClasses([...baseAttributes["td"] ?? [], [...cellAlign]]),
|
|
101
|
+
th: withPrintClasses([...baseAttributes["th"] ?? [], [...cellAlign]]),
|
|
102
|
+
// The flashcard flip is a checkbox + label pair (GitHub forces `disabled`).
|
|
103
|
+
input: withPrintClasses([["type", "checkbox"], "checked"]),
|
|
104
|
+
label: withPrintClasses(["htmlFor"])
|
|
105
|
+
},
|
|
106
|
+
required: { input: { type: "checkbox" } },
|
|
107
|
+
protocols: {
|
|
108
|
+
...defaultSchema.protocols,
|
|
109
|
+
href: [...defaultSchema.protocols?.href ?? [], "tel"],
|
|
110
|
+
// Embedded images (`data:image/png;base64,…`) — an <img> never runs script.
|
|
111
|
+
src: [...defaultSchema.protocols?.src ?? [], "data"]
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
function sanitizeHtmlFragment(html, schema = SAFE_HTML_SCHEMA) {
|
|
115
|
+
if (!html) return "";
|
|
116
|
+
const tree = sanitize(fromHtml(html, { fragment: true }), schema);
|
|
117
|
+
return toHtml(tree, { characterReferences: { useNamedReferences: true } });
|
|
118
|
+
}
|
|
119
|
+
function sanitizePrintHtml(html) {
|
|
120
|
+
return sanitizeHtmlFragment(html, PRINT_HTML_SCHEMA);
|
|
121
|
+
}
|
|
122
|
+
|
|
26
123
|
// src/markdown-css.ts
|
|
27
124
|
var MARKDOWN_DOCUMENT_TOKENS_CSS = `:root {
|
|
28
125
|
--matrx-print-accent: #667eea;
|
|
@@ -1053,7 +1150,8 @@ function splitAtPageBreaks(markdown) {
|
|
|
1053
1150
|
// src/markdown.ts
|
|
1054
1151
|
function removeThinkingContent(content) {
|
|
1055
1152
|
if (!content) return "";
|
|
1056
|
-
|
|
1153
|
+
const hide = (_match, before, after) => before.length >= 2 || after.length >= 2 ? "\n\n" : "\n";
|
|
1154
|
+
let result = content.replace(/(\n*)<thinking>[\s\S]*?<\/thinking>(\n*)/gi, hide).replace(/(\n*)<think>[\s\S]*?<\/think>(\n*)/gi, hide).replace(/(\n*)<reasoning>[\s\S]*?<\/reasoning>(\n*)/gi, hide);
|
|
1057
1155
|
result = result.replace(/\n{3,}/g, "\n\n");
|
|
1058
1156
|
return result.trim();
|
|
1059
1157
|
}
|
|
@@ -1361,11 +1459,11 @@ function protectRegions(markdown, g, stash, ctx) {
|
|
|
1361
1459
|
text = next;
|
|
1362
1460
|
}
|
|
1363
1461
|
text = sanitizeRawTags(text);
|
|
1364
|
-
if (text !== before && !ctx.strippedAnnounced) {
|
|
1462
|
+
if ((text !== before || hasDisallowedTag(text)) && !ctx.strippedAnnounced) {
|
|
1365
1463
|
ctx.strippedAnnounced = true;
|
|
1366
1464
|
ctx.options.onNotice?.({
|
|
1367
1465
|
code: "html-stripped",
|
|
1368
|
-
message: "
|
|
1466
|
+
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.",
|
|
1369
1467
|
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.'
|
|
1370
1468
|
});
|
|
1371
1469
|
}
|
|
@@ -1377,6 +1475,14 @@ ${stash.block(flashcardsHtml(g, cards, ctx))}
|
|
|
1377
1475
|
});
|
|
1378
1476
|
return text;
|
|
1379
1477
|
}
|
|
1478
|
+
var ALLOWED_TAGS = new Set(PRINT_HTML_SCHEMA.tagNames ?? []);
|
|
1479
|
+
function hasDisallowedTag(text) {
|
|
1480
|
+
for (const m of text.matchAll(/<\/?([a-zA-Z][\w:-]*)/g)) {
|
|
1481
|
+
const tag = m[1].toLowerCase();
|
|
1482
|
+
if (tag !== "flashcards" && !ALLOWED_TAGS.has(tag)) return true;
|
|
1483
|
+
}
|
|
1484
|
+
return false;
|
|
1485
|
+
}
|
|
1380
1486
|
var URL_ATTRS = /* @__PURE__ */ new Set(["href", "src", "action", "formaction", "xlink:href", "poster", "background"]);
|
|
1381
1487
|
function decodeForSchemeCheck(value) {
|
|
1382
1488
|
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();
|
|
@@ -1431,7 +1537,7 @@ function splitTableRow(row) {
|
|
|
1431
1537
|
if (r.endsWith("|") && !r.endsWith("\\|")) r = r.slice(0, -1);
|
|
1432
1538
|
return r.split(/(?<!\\)\|/).map((c) => c.trim().replace(/\\\|/g, "|"));
|
|
1433
1539
|
}
|
|
1434
|
-
function
|
|
1540
|
+
function cellAlign2(separator) {
|
|
1435
1541
|
const s = separator.trim();
|
|
1436
1542
|
const left = s.startsWith(":");
|
|
1437
1543
|
const right = s.endsWith(":");
|
|
@@ -1445,7 +1551,7 @@ function convertTables(html, g) {
|
|
|
1445
1551
|
tableRegex,
|
|
1446
1552
|
(_m, headerRow, separatorRow, bodyRows) => {
|
|
1447
1553
|
const headers = splitTableRow(headerRow);
|
|
1448
|
-
const aligns = splitTableRow(separatorRow).map(
|
|
1554
|
+
const aligns = splitTableRow(separatorRow).map(cellAlign2);
|
|
1449
1555
|
const rows = bodyRows.trim().split("\n").filter((r) => r.trim()).map(splitTableRow);
|
|
1450
1556
|
let t = `<table${cls(g.table)}>
|
|
1451
1557
|
<thead${cls(g.thead)}>
|
|
@@ -1559,7 +1665,9 @@ function markdownToHtml(markdown, options = {}) {
|
|
|
1559
1665
|
return "";
|
|
1560
1666
|
}
|
|
1561
1667
|
const source = scrubTokens(options.includeThinking ? markdown : removeThinkingContent(markdown));
|
|
1562
|
-
return
|
|
1668
|
+
return sanitizePrintHtml(
|
|
1669
|
+
`<div class="matrx-content-container">${documentBody(source, newContext(options))}</div>`
|
|
1670
|
+
);
|
|
1563
1671
|
}
|
|
1564
1672
|
function documentBody(markdown, ctx) {
|
|
1565
1673
|
const g = DOCUMENT_GRAMMAR;
|
|
@@ -1629,7 +1737,7 @@ function markdownToArticleHtml(markdown, options = {}) {
|
|
|
1629
1737
|
return "";
|
|
1630
1738
|
}
|
|
1631
1739
|
const source = scrubTokens(options.includeThinking ? markdown : removeThinkingContent(markdown));
|
|
1632
|
-
return articleBody(source, newContext(options));
|
|
1740
|
+
return sanitizePrintHtml(articleBody(source, newContext(options)));
|
|
1633
1741
|
}
|
|
1634
1742
|
function articleBody(markdown, ctx) {
|
|
1635
1743
|
const g = ARTICLE_GRAMMAR;
|