@ai-matrx/print 0.8.7 → 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 +25 -0
- package/dist/document.cjs +204 -3
- package/dist/document.cjs.map +1 -1
- package/dist/document.js +204 -3
- package/dist/document.js.map +1 -1
- package/dist/markdown.cjs +113 -6
- package/dist/markdown.cjs.map +1 -1
- package/dist/markdown.js +113 -6
- 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 +17 -3
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/safe-html.ts
|
|
21
|
+
var safe_html_exports = {};
|
|
22
|
+
__export(safe_html_exports, {
|
|
23
|
+
FORBIDDEN_TAGS: () => FORBIDDEN_TAGS,
|
|
24
|
+
MATHML_SCHEMA: () => MATHML_SCHEMA,
|
|
25
|
+
PRINT_HTML_SCHEMA: () => PRINT_HTML_SCHEMA,
|
|
26
|
+
SAFE_HTML_SCHEMA: () => SAFE_HTML_SCHEMA,
|
|
27
|
+
sanitizeHtmlFragment: () => sanitizeHtmlFragment,
|
|
28
|
+
sanitizeMathml: () => sanitizeMathml,
|
|
29
|
+
sanitizePrintHtml: () => sanitizePrintHtml
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(safe_html_exports);
|
|
32
|
+
var import_hast_util_from_html = require("hast-util-from-html");
|
|
33
|
+
var import_hast_util_sanitize = require("hast-util-sanitize");
|
|
34
|
+
var import_hast_util_to_html = require("hast-util-to-html");
|
|
35
|
+
var STRIP_WITH_CONTENT = [
|
|
36
|
+
"script",
|
|
37
|
+
"style",
|
|
38
|
+
"noscript",
|
|
39
|
+
"template",
|
|
40
|
+
"iframe",
|
|
41
|
+
"object",
|
|
42
|
+
"embed",
|
|
43
|
+
"textarea",
|
|
44
|
+
"select",
|
|
45
|
+
"head",
|
|
46
|
+
"title"
|
|
47
|
+
];
|
|
48
|
+
var FORBIDDEN_TAGS = [
|
|
49
|
+
...STRIP_WITH_CONTENT,
|
|
50
|
+
"form",
|
|
51
|
+
"meta",
|
|
52
|
+
"base",
|
|
53
|
+
"link",
|
|
54
|
+
"frame",
|
|
55
|
+
"frameset",
|
|
56
|
+
"applet",
|
|
57
|
+
"button",
|
|
58
|
+
"svg",
|
|
59
|
+
"math"
|
|
60
|
+
];
|
|
61
|
+
var baseAttributes = import_hast_util_sanitize.defaultSchema.attributes ?? {};
|
|
62
|
+
var SAFE_HTML_SCHEMA = {
|
|
63
|
+
...import_hast_util_sanitize.defaultSchema,
|
|
64
|
+
tagNames: (import_hast_util_sanitize.defaultSchema.tagNames ?? []).filter((t) => !FORBIDDEN_TAGS.includes(t)),
|
|
65
|
+
strip: STRIP_WITH_CONTENT,
|
|
66
|
+
// Authored anchors keep their names; the GitHub `user-content-` rewrite
|
|
67
|
+
// would also break `<label for>` → `<input id>` pairs.
|
|
68
|
+
clobberPrefix: "",
|
|
69
|
+
clobber: []
|
|
70
|
+
};
|
|
71
|
+
var PRINT_CLASS = /^(?:(?:matrx|print|code|doc|language)-[\w-]*|inline-code|is-checked)$/;
|
|
72
|
+
function withPrintClasses(defs = []) {
|
|
73
|
+
let found = false;
|
|
74
|
+
const out = defs.map((d) => {
|
|
75
|
+
if (Array.isArray(d) && d[0] === "className") {
|
|
76
|
+
found = true;
|
|
77
|
+
return [...d, PRINT_CLASS];
|
|
78
|
+
}
|
|
79
|
+
return d;
|
|
80
|
+
});
|
|
81
|
+
return found ? out : [...out, ["className", PRINT_CLASS]];
|
|
82
|
+
}
|
|
83
|
+
var printAttributes = Object.fromEntries(
|
|
84
|
+
Object.entries(baseAttributes).map(([tag, defs]) => [tag, withPrintClasses(defs)])
|
|
85
|
+
);
|
|
86
|
+
var cellAlign = ["style", /^text-align:\s*(?:left|center|right);?$/];
|
|
87
|
+
var PRINT_HTML_SCHEMA = {
|
|
88
|
+
...SAFE_HTML_SCHEMA,
|
|
89
|
+
tagNames: [
|
|
90
|
+
.../* @__PURE__ */ new Set([...SAFE_HTML_SCHEMA.tagNames ?? [], "section", "figure", "figcaption", "label"])
|
|
91
|
+
],
|
|
92
|
+
attributes: {
|
|
93
|
+
...printAttributes,
|
|
94
|
+
"*": [
|
|
95
|
+
...baseAttributes["*"] ?? [],
|
|
96
|
+
["className", PRINT_CLASS],
|
|
97
|
+
"role",
|
|
98
|
+
"ariaLabel",
|
|
99
|
+
"ariaHidden",
|
|
100
|
+
"dataLabel",
|
|
101
|
+
"dataLanguage",
|
|
102
|
+
"dataArtifactType"
|
|
103
|
+
],
|
|
104
|
+
// Table-cell alignment is the one inline style the converter writes.
|
|
105
|
+
td: withPrintClasses([...baseAttributes["td"] ?? [], [...cellAlign]]),
|
|
106
|
+
th: withPrintClasses([...baseAttributes["th"] ?? [], [...cellAlign]]),
|
|
107
|
+
// The flashcard flip is a checkbox + label pair (GitHub forces `disabled`).
|
|
108
|
+
input: withPrintClasses([["type", "checkbox"], "checked"]),
|
|
109
|
+
label: withPrintClasses(["htmlFor"])
|
|
110
|
+
},
|
|
111
|
+
required: { input: { type: "checkbox" } },
|
|
112
|
+
protocols: {
|
|
113
|
+
...import_hast_util_sanitize.defaultSchema.protocols,
|
|
114
|
+
href: [...import_hast_util_sanitize.defaultSchema.protocols?.href ?? [], "tel"],
|
|
115
|
+
// Embedded images (`data:image/png;base64,…`) — an <img> never runs script.
|
|
116
|
+
src: [...import_hast_util_sanitize.defaultSchema.protocols?.src ?? [], "data"]
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
function sanitizeHtmlFragment(html, schema = SAFE_HTML_SCHEMA) {
|
|
120
|
+
if (!html) return "";
|
|
121
|
+
const tree = (0, import_hast_util_sanitize.sanitize)((0, import_hast_util_from_html.fromHtml)(html, { fragment: true }), schema);
|
|
122
|
+
return (0, import_hast_util_to_html.toHtml)(tree, { characterReferences: { useNamedReferences: true } });
|
|
123
|
+
}
|
|
124
|
+
function sanitizePrintHtml(html) {
|
|
125
|
+
return sanitizeHtmlFragment(html, PRINT_HTML_SCHEMA);
|
|
126
|
+
}
|
|
127
|
+
var MATHML_TAGS = [
|
|
128
|
+
"math",
|
|
129
|
+
"maction",
|
|
130
|
+
"annotation",
|
|
131
|
+
"menclose",
|
|
132
|
+
"merror",
|
|
133
|
+
"mfrac",
|
|
134
|
+
"mglyph",
|
|
135
|
+
"mi",
|
|
136
|
+
"mlabeledtr",
|
|
137
|
+
"mmultiscripts",
|
|
138
|
+
"mn",
|
|
139
|
+
"mo",
|
|
140
|
+
"mover",
|
|
141
|
+
"mpadded",
|
|
142
|
+
"mphantom",
|
|
143
|
+
"mprescripts",
|
|
144
|
+
"none",
|
|
145
|
+
"mroot",
|
|
146
|
+
"mrow",
|
|
147
|
+
"ms",
|
|
148
|
+
"mspace",
|
|
149
|
+
"msqrt",
|
|
150
|
+
"mstyle",
|
|
151
|
+
"msub",
|
|
152
|
+
"msubsup",
|
|
153
|
+
"msup",
|
|
154
|
+
"mtable",
|
|
155
|
+
"mtd",
|
|
156
|
+
"mtext",
|
|
157
|
+
"mtr",
|
|
158
|
+
"munder",
|
|
159
|
+
"munderover",
|
|
160
|
+
"semantics"
|
|
161
|
+
];
|
|
162
|
+
var MATHML_ATTRIBUTES = [
|
|
163
|
+
"xmlns",
|
|
164
|
+
"display",
|
|
165
|
+
"displaystyle",
|
|
166
|
+
"mathvariant",
|
|
167
|
+
"mathsize",
|
|
168
|
+
"mathcolor",
|
|
169
|
+
"mathbackground",
|
|
170
|
+
"scriptlevel",
|
|
171
|
+
"dir",
|
|
172
|
+
"fence",
|
|
173
|
+
"form",
|
|
174
|
+
"largeop",
|
|
175
|
+
"lspace",
|
|
176
|
+
"rspace",
|
|
177
|
+
"movablelimits",
|
|
178
|
+
"separator",
|
|
179
|
+
"stretchy",
|
|
180
|
+
"symmetric",
|
|
181
|
+
"maxsize",
|
|
182
|
+
"minsize",
|
|
183
|
+
"accent",
|
|
184
|
+
"accentunder",
|
|
185
|
+
"align",
|
|
186
|
+
"columnalign",
|
|
187
|
+
"rowalign",
|
|
188
|
+
"columnspacing",
|
|
189
|
+
"rowspacing",
|
|
190
|
+
"columnlines",
|
|
191
|
+
"rowlines",
|
|
192
|
+
"frame",
|
|
193
|
+
"framespacing",
|
|
194
|
+
"equalrows",
|
|
195
|
+
"equalcolumns",
|
|
196
|
+
"linethickness",
|
|
197
|
+
"numalign",
|
|
198
|
+
"denomalign",
|
|
199
|
+
"bevelled",
|
|
200
|
+
"notation",
|
|
201
|
+
"width",
|
|
202
|
+
"height",
|
|
203
|
+
"depth",
|
|
204
|
+
"voffset",
|
|
205
|
+
"lquote",
|
|
206
|
+
"rquote",
|
|
207
|
+
"columnspan",
|
|
208
|
+
"rowspan",
|
|
209
|
+
"encoding",
|
|
210
|
+
"open",
|
|
211
|
+
"close",
|
|
212
|
+
"separators",
|
|
213
|
+
"actiontype",
|
|
214
|
+
"selection",
|
|
215
|
+
"mslinebreak",
|
|
216
|
+
"linebreak",
|
|
217
|
+
"indentalign",
|
|
218
|
+
"data*"
|
|
219
|
+
];
|
|
220
|
+
var MATHML_SCHEMA = {
|
|
221
|
+
tagNames: MATHML_TAGS,
|
|
222
|
+
attributes: { "*": MATHML_ATTRIBUTES },
|
|
223
|
+
strip: ["script", "style"],
|
|
224
|
+
clobberPrefix: "",
|
|
225
|
+
clobber: [],
|
|
226
|
+
protocols: {}
|
|
227
|
+
};
|
|
228
|
+
function sanitizeMathml(mathml) {
|
|
229
|
+
return sanitizeHtmlFragment(mathml, MATHML_SCHEMA);
|
|
230
|
+
}
|
|
231
|
+
//# sourceMappingURL=safe-html.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/safe-html.ts"],"sourcesContent":["/**\n * @ai-matrx/print/safe-html — THE allow-list every Markdown→HTML string this\n * package hands out goes through.\n *\n * Why an allow-list: the regex converter (`markdownToHtml` /\n * `markdownToArticleHtml`) lets author raw HTML through so a document can feed\n * WordPress. Its old guard was a block-list (script, style, on… handlers,\n * javascript: URLs) and everything nobody listed — `<iframe>`, `<form>`,\n * `<meta http-equiv=refresh>`, `<object>`/`<embed>`, `<base>`, `<link>`,\n * inline `style` — reached the clipboard, the downloaded page and the preview.\n * Now a tag or attribute survives only when it is listed here.\n *\n * Sync and DOM-free (parse5 via hast), so it runs in Node, workers, the\n * extension service worker and Tauri alike.\n *\n * Two schemas:\n * • `SAFE_HTML_SCHEMA` — GitHub's rendering rules (hast-util-sanitize's\n * default schema) with no id clobber-prefixing and the \"drop the element\n * AND its text\" list for elements whose text is not prose. For any host\n * that renders plain semantic HTML (email bodies, CMS drafts).\n * • `PRINT_HTML_SCHEMA` — the same, plus exactly the vocabulary print's own\n * converters emit: `matrx-*` / `print-*` / `code-*` / `doc-*` classes,\n * table-cell alignment, the flashcard checkbox + label, figures, and the\n * page-break separator's data/aria attributes.\n */\n\nimport { fromHtml } from \"hast-util-from-html\";\nimport { defaultSchema, sanitize, type Schema } from \"hast-util-sanitize\";\nimport { toHtml } from \"hast-util-to-html\";\nimport type { Root } from \"hast\";\n\ntype Attributes = NonNullable<Schema[\"attributes\"]>;\ntype AttributeList = Attributes[string];\n\n/** Elements removed together with their content (their text is not prose). */\nconst STRIP_WITH_CONTENT = [\n \"script\",\n \"style\",\n \"noscript\",\n \"template\",\n \"iframe\",\n \"object\",\n \"embed\",\n \"textarea\",\n \"select\",\n \"head\",\n \"title\",\n];\n\n/** Never allowed, whatever a schema extension adds. */\nexport const FORBIDDEN_TAGS: readonly string[] = [\n ...STRIP_WITH_CONTENT,\n \"form\",\n \"meta\",\n \"base\",\n \"link\",\n \"frame\",\n \"frameset\",\n \"applet\",\n \"button\",\n \"svg\",\n \"math\",\n];\n\nconst baseAttributes = defaultSchema.attributes ?? {};\n\nexport const SAFE_HTML_SCHEMA: Schema = {\n ...defaultSchema,\n tagNames: (defaultSchema.tagNames ?? []).filter((t) => !FORBIDDEN_TAGS.includes(t)),\n strip: STRIP_WITH_CONTENT,\n // Authored anchors keep their names; the GitHub `user-content-` rewrite\n // would also break `<label for>` → `<input id>` pairs.\n clobberPrefix: \"\",\n clobber: [],\n};\n\n/** Class tokens print's converters emit. Author classes outside these are dropped. */\nconst PRINT_CLASS = /^(?:(?:matrx|print|code|doc|language)-[\\w-]*|inline-code|is-checked)$/;\n\n/**\n * A tag's own `className` definition wins over `'*'` (and returns an empty\n * list rather than falling through), so the print class pattern is merged into\n * every definition that names `className`, and added where none does.\n */\nfunction withPrintClasses(defs: AttributeList = []): AttributeList {\n let found = false;\n const out = defs.map((d) => {\n if (Array.isArray(d) && d[0] === \"className\") {\n found = true;\n return [...d, PRINT_CLASS] as unknown as typeof d;\n }\n return d;\n });\n return found ? out : [...out, [\"className\", PRINT_CLASS]];\n}\n\nconst printAttributes: Attributes = Object.fromEntries(\n Object.entries(baseAttributes).map(([tag, defs]) => [tag, withPrintClasses(defs)]),\n);\n\nconst cellAlign = [\"style\", /^text-align:\\s*(?:left|center|right);?$/] as const;\n\nexport const PRINT_HTML_SCHEMA: Schema = {\n ...SAFE_HTML_SCHEMA,\n tagNames: [\n ...new Set([...(SAFE_HTML_SCHEMA.tagNames ?? []), \"section\", \"figure\", \"figcaption\", \"label\"]),\n ],\n attributes: {\n ...printAttributes,\n \"*\": [\n ...(baseAttributes[\"*\"] ?? []),\n [\"className\", PRINT_CLASS],\n \"role\",\n \"ariaLabel\",\n \"ariaHidden\",\n \"dataLabel\",\n \"dataLanguage\",\n \"dataArtifactType\",\n ],\n // Table-cell alignment is the one inline style the converter writes.\n td: withPrintClasses([...(baseAttributes[\"td\"] ?? []), [...cellAlign]]),\n th: withPrintClasses([...(baseAttributes[\"th\"] ?? []), [...cellAlign]]),\n // The flashcard flip is a checkbox + label pair (GitHub forces `disabled`).\n input: withPrintClasses([[\"type\", \"checkbox\"], \"checked\"]),\n label: withPrintClasses([\"htmlFor\"]),\n },\n required: { input: { type: \"checkbox\" } },\n protocols: {\n ...defaultSchema.protocols,\n href: [...(defaultSchema.protocols?.href ?? []), \"tel\"],\n // Embedded images (`data:image/png;base64,…`) — an <img> never runs script.\n src: [...(defaultSchema.protocols?.src ?? []), \"data\"],\n },\n};\n\n/** Sanitize an HTML fragment against an allow-list (default: `SAFE_HTML_SCHEMA`). */\nexport function sanitizeHtmlFragment(html: string, schema: Schema = SAFE_HTML_SCHEMA): string {\n if (!html) return \"\";\n const tree = sanitize(fromHtml(html, { fragment: true }), schema) as Root;\n return toHtml(tree, { characterReferences: { useNamedReferences: true } });\n}\n\n/** Sanitize the output of print's own converters (keeps the `.matrx-*` vocabulary). */\nexport function sanitizePrintHtml(html: string): string {\n return sanitizeHtmlFragment(html, PRINT_HTML_SCHEMA);\n}\n\nexport type { Schema as SafeHtmlSchema };\n\n/**\n * MathML from the TeX typesetter. TeX is author input, and MathJax's `html`,\n * `bbox` and `\\mmlToken` commands turn it into attributes (`\\href{javascript:…}`\n * → `href`, `\\style{position:fixed…}` / `\\bbox[position:fixed]` → `style`).\n * Only presentation MathML elements and their layout attributes survive.\n */\nconst MATHML_TAGS = [\n \"math\", \"maction\", \"annotation\", \"menclose\", \"merror\", \"mfrac\", \"mglyph\", \"mi\",\n \"mlabeledtr\", \"mmultiscripts\", \"mn\", \"mo\", \"mover\", \"mpadded\", \"mphantom\", \"mprescripts\", \"none\",\n \"mroot\", \"mrow\", \"ms\", \"mspace\", \"msqrt\", \"mstyle\", \"msub\", \"msubsup\", \"msup\", \"mtable\", \"mtd\",\n \"mtext\", \"mtr\", \"munder\", \"munderover\", \"semantics\",\n];\n\nconst MATHML_ATTRIBUTES = [\n \"xmlns\", \"display\", \"displaystyle\", \"mathvariant\", \"mathsize\", \"mathcolor\", \"mathbackground\",\n \"scriptlevel\", \"dir\", \"fence\", \"form\", \"largeop\", \"lspace\", \"rspace\", \"movablelimits\",\n \"separator\", \"stretchy\", \"symmetric\", \"maxsize\", \"minsize\", \"accent\", \"accentunder\", \"align\",\n \"columnalign\", \"rowalign\", \"columnspacing\", \"rowspacing\", \"columnlines\", \"rowlines\", \"frame\",\n \"framespacing\", \"equalrows\", \"equalcolumns\", \"linethickness\", \"numalign\", \"denomalign\",\n \"bevelled\", \"notation\", \"width\", \"height\", \"depth\", \"voffset\", \"lquote\", \"rquote\", \"columnspan\",\n \"rowspan\", \"encoding\", \"open\", \"close\", \"separators\", \"actiontype\", \"selection\", \"mslinebreak\",\n \"linebreak\", \"indentalign\", \"data*\",\n];\n\nexport const MATHML_SCHEMA: Schema = {\n tagNames: MATHML_TAGS,\n attributes: { \"*\": MATHML_ATTRIBUTES },\n strip: [\"script\", \"style\"],\n clobberPrefix: \"\",\n clobber: [],\n protocols: {},\n};\n\n/** Sanitize a typeset MathML string (keeps layout, drops links and styles). */\nexport function sanitizeMathml(mathml: string): string {\n return sanitizeHtmlFragment(mathml, MATHML_SCHEMA);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Schema } from 'hast-util-sanitize';
|
|
2
|
+
export { Schema as SafeHtmlSchema } from 'hast-util-sanitize';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @ai-matrx/print/safe-html — THE allow-list every Markdown→HTML string this
|
|
6
|
+
* package hands out goes through.
|
|
7
|
+
*
|
|
8
|
+
* Why an allow-list: the regex converter (`markdownToHtml` /
|
|
9
|
+
* `markdownToArticleHtml`) lets author raw HTML through so a document can feed
|
|
10
|
+
* WordPress. Its old guard was a block-list (script, style, on… handlers,
|
|
11
|
+
* javascript: URLs) and everything nobody listed — `<iframe>`, `<form>`,
|
|
12
|
+
* `<meta http-equiv=refresh>`, `<object>`/`<embed>`, `<base>`, `<link>`,
|
|
13
|
+
* inline `style` — reached the clipboard, the downloaded page and the preview.
|
|
14
|
+
* Now a tag or attribute survives only when it is listed here.
|
|
15
|
+
*
|
|
16
|
+
* Sync and DOM-free (parse5 via hast), so it runs in Node, workers, the
|
|
17
|
+
* extension service worker and Tauri alike.
|
|
18
|
+
*
|
|
19
|
+
* Two schemas:
|
|
20
|
+
* • `SAFE_HTML_SCHEMA` — GitHub's rendering rules (hast-util-sanitize's
|
|
21
|
+
* default schema) with no id clobber-prefixing and the "drop the element
|
|
22
|
+
* AND its text" list for elements whose text is not prose. For any host
|
|
23
|
+
* that renders plain semantic HTML (email bodies, CMS drafts).
|
|
24
|
+
* • `PRINT_HTML_SCHEMA` — the same, plus exactly the vocabulary print's own
|
|
25
|
+
* converters emit: `matrx-*` / `print-*` / `code-*` / `doc-*` classes,
|
|
26
|
+
* table-cell alignment, the flashcard checkbox + label, figures, and the
|
|
27
|
+
* page-break separator's data/aria attributes.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/** Never allowed, whatever a schema extension adds. */
|
|
31
|
+
declare const FORBIDDEN_TAGS: readonly string[];
|
|
32
|
+
declare const SAFE_HTML_SCHEMA: Schema;
|
|
33
|
+
declare const PRINT_HTML_SCHEMA: Schema;
|
|
34
|
+
/** Sanitize an HTML fragment against an allow-list (default: `SAFE_HTML_SCHEMA`). */
|
|
35
|
+
declare function sanitizeHtmlFragment(html: string, schema?: Schema): string;
|
|
36
|
+
/** Sanitize the output of print's own converters (keeps the `.matrx-*` vocabulary). */
|
|
37
|
+
declare function sanitizePrintHtml(html: string): string;
|
|
38
|
+
|
|
39
|
+
declare const MATHML_SCHEMA: Schema;
|
|
40
|
+
/** Sanitize a typeset MathML string (keeps layout, drops links and styles). */
|
|
41
|
+
declare function sanitizeMathml(mathml: string): string;
|
|
42
|
+
|
|
43
|
+
export { FORBIDDEN_TAGS, MATHML_SCHEMA, PRINT_HTML_SCHEMA, SAFE_HTML_SCHEMA, sanitizeHtmlFragment, sanitizeMathml, sanitizePrintHtml };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Schema } from 'hast-util-sanitize';
|
|
2
|
+
export { Schema as SafeHtmlSchema } from 'hast-util-sanitize';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @ai-matrx/print/safe-html — THE allow-list every Markdown→HTML string this
|
|
6
|
+
* package hands out goes through.
|
|
7
|
+
*
|
|
8
|
+
* Why an allow-list: the regex converter (`markdownToHtml` /
|
|
9
|
+
* `markdownToArticleHtml`) lets author raw HTML through so a document can feed
|
|
10
|
+
* WordPress. Its old guard was a block-list (script, style, on… handlers,
|
|
11
|
+
* javascript: URLs) and everything nobody listed — `<iframe>`, `<form>`,
|
|
12
|
+
* `<meta http-equiv=refresh>`, `<object>`/`<embed>`, `<base>`, `<link>`,
|
|
13
|
+
* inline `style` — reached the clipboard, the downloaded page and the preview.
|
|
14
|
+
* Now a tag or attribute survives only when it is listed here.
|
|
15
|
+
*
|
|
16
|
+
* Sync and DOM-free (parse5 via hast), so it runs in Node, workers, the
|
|
17
|
+
* extension service worker and Tauri alike.
|
|
18
|
+
*
|
|
19
|
+
* Two schemas:
|
|
20
|
+
* • `SAFE_HTML_SCHEMA` — GitHub's rendering rules (hast-util-sanitize's
|
|
21
|
+
* default schema) with no id clobber-prefixing and the "drop the element
|
|
22
|
+
* AND its text" list for elements whose text is not prose. For any host
|
|
23
|
+
* that renders plain semantic HTML (email bodies, CMS drafts).
|
|
24
|
+
* • `PRINT_HTML_SCHEMA` — the same, plus exactly the vocabulary print's own
|
|
25
|
+
* converters emit: `matrx-*` / `print-*` / `code-*` / `doc-*` classes,
|
|
26
|
+
* table-cell alignment, the flashcard checkbox + label, figures, and the
|
|
27
|
+
* page-break separator's data/aria attributes.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/** Never allowed, whatever a schema extension adds. */
|
|
31
|
+
declare const FORBIDDEN_TAGS: readonly string[];
|
|
32
|
+
declare const SAFE_HTML_SCHEMA: Schema;
|
|
33
|
+
declare const PRINT_HTML_SCHEMA: Schema;
|
|
34
|
+
/** Sanitize an HTML fragment against an allow-list (default: `SAFE_HTML_SCHEMA`). */
|
|
35
|
+
declare function sanitizeHtmlFragment(html: string, schema?: Schema): string;
|
|
36
|
+
/** Sanitize the output of print's own converters (keeps the `.matrx-*` vocabulary). */
|
|
37
|
+
declare function sanitizePrintHtml(html: string): string;
|
|
38
|
+
|
|
39
|
+
declare const MATHML_SCHEMA: Schema;
|
|
40
|
+
/** Sanitize a typeset MathML string (keeps layout, drops links and styles). */
|
|
41
|
+
declare function sanitizeMathml(mathml: string): string;
|
|
42
|
+
|
|
43
|
+
export { FORBIDDEN_TAGS, MATHML_SCHEMA, PRINT_HTML_SCHEMA, SAFE_HTML_SCHEMA, sanitizeHtmlFragment, sanitizeMathml, sanitizePrintHtml };
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// src/safe-html.ts
|
|
2
|
+
import { fromHtml } from "hast-util-from-html";
|
|
3
|
+
import { defaultSchema, sanitize } from "hast-util-sanitize";
|
|
4
|
+
import { toHtml } from "hast-util-to-html";
|
|
5
|
+
var STRIP_WITH_CONTENT = [
|
|
6
|
+
"script",
|
|
7
|
+
"style",
|
|
8
|
+
"noscript",
|
|
9
|
+
"template",
|
|
10
|
+
"iframe",
|
|
11
|
+
"object",
|
|
12
|
+
"embed",
|
|
13
|
+
"textarea",
|
|
14
|
+
"select",
|
|
15
|
+
"head",
|
|
16
|
+
"title"
|
|
17
|
+
];
|
|
18
|
+
var FORBIDDEN_TAGS = [
|
|
19
|
+
...STRIP_WITH_CONTENT,
|
|
20
|
+
"form",
|
|
21
|
+
"meta",
|
|
22
|
+
"base",
|
|
23
|
+
"link",
|
|
24
|
+
"frame",
|
|
25
|
+
"frameset",
|
|
26
|
+
"applet",
|
|
27
|
+
"button",
|
|
28
|
+
"svg",
|
|
29
|
+
"math"
|
|
30
|
+
];
|
|
31
|
+
var baseAttributes = defaultSchema.attributes ?? {};
|
|
32
|
+
var SAFE_HTML_SCHEMA = {
|
|
33
|
+
...defaultSchema,
|
|
34
|
+
tagNames: (defaultSchema.tagNames ?? []).filter((t) => !FORBIDDEN_TAGS.includes(t)),
|
|
35
|
+
strip: STRIP_WITH_CONTENT,
|
|
36
|
+
// Authored anchors keep their names; the GitHub `user-content-` rewrite
|
|
37
|
+
// would also break `<label for>` → `<input id>` pairs.
|
|
38
|
+
clobberPrefix: "",
|
|
39
|
+
clobber: []
|
|
40
|
+
};
|
|
41
|
+
var PRINT_CLASS = /^(?:(?:matrx|print|code|doc|language)-[\w-]*|inline-code|is-checked)$/;
|
|
42
|
+
function withPrintClasses(defs = []) {
|
|
43
|
+
let found = false;
|
|
44
|
+
const out = defs.map((d) => {
|
|
45
|
+
if (Array.isArray(d) && d[0] === "className") {
|
|
46
|
+
found = true;
|
|
47
|
+
return [...d, PRINT_CLASS];
|
|
48
|
+
}
|
|
49
|
+
return d;
|
|
50
|
+
});
|
|
51
|
+
return found ? out : [...out, ["className", PRINT_CLASS]];
|
|
52
|
+
}
|
|
53
|
+
var printAttributes = Object.fromEntries(
|
|
54
|
+
Object.entries(baseAttributes).map(([tag, defs]) => [tag, withPrintClasses(defs)])
|
|
55
|
+
);
|
|
56
|
+
var cellAlign = ["style", /^text-align:\s*(?:left|center|right);?$/];
|
|
57
|
+
var PRINT_HTML_SCHEMA = {
|
|
58
|
+
...SAFE_HTML_SCHEMA,
|
|
59
|
+
tagNames: [
|
|
60
|
+
.../* @__PURE__ */ new Set([...SAFE_HTML_SCHEMA.tagNames ?? [], "section", "figure", "figcaption", "label"])
|
|
61
|
+
],
|
|
62
|
+
attributes: {
|
|
63
|
+
...printAttributes,
|
|
64
|
+
"*": [
|
|
65
|
+
...baseAttributes["*"] ?? [],
|
|
66
|
+
["className", PRINT_CLASS],
|
|
67
|
+
"role",
|
|
68
|
+
"ariaLabel",
|
|
69
|
+
"ariaHidden",
|
|
70
|
+
"dataLabel",
|
|
71
|
+
"dataLanguage",
|
|
72
|
+
"dataArtifactType"
|
|
73
|
+
],
|
|
74
|
+
// Table-cell alignment is the one inline style the converter writes.
|
|
75
|
+
td: withPrintClasses([...baseAttributes["td"] ?? [], [...cellAlign]]),
|
|
76
|
+
th: withPrintClasses([...baseAttributes["th"] ?? [], [...cellAlign]]),
|
|
77
|
+
// The flashcard flip is a checkbox + label pair (GitHub forces `disabled`).
|
|
78
|
+
input: withPrintClasses([["type", "checkbox"], "checked"]),
|
|
79
|
+
label: withPrintClasses(["htmlFor"])
|
|
80
|
+
},
|
|
81
|
+
required: { input: { type: "checkbox" } },
|
|
82
|
+
protocols: {
|
|
83
|
+
...defaultSchema.protocols,
|
|
84
|
+
href: [...defaultSchema.protocols?.href ?? [], "tel"],
|
|
85
|
+
// Embedded images (`data:image/png;base64,…`) — an <img> never runs script.
|
|
86
|
+
src: [...defaultSchema.protocols?.src ?? [], "data"]
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
function sanitizeHtmlFragment(html, schema = SAFE_HTML_SCHEMA) {
|
|
90
|
+
if (!html) return "";
|
|
91
|
+
const tree = sanitize(fromHtml(html, { fragment: true }), schema);
|
|
92
|
+
return toHtml(tree, { characterReferences: { useNamedReferences: true } });
|
|
93
|
+
}
|
|
94
|
+
function sanitizePrintHtml(html) {
|
|
95
|
+
return sanitizeHtmlFragment(html, PRINT_HTML_SCHEMA);
|
|
96
|
+
}
|
|
97
|
+
var MATHML_TAGS = [
|
|
98
|
+
"math",
|
|
99
|
+
"maction",
|
|
100
|
+
"annotation",
|
|
101
|
+
"menclose",
|
|
102
|
+
"merror",
|
|
103
|
+
"mfrac",
|
|
104
|
+
"mglyph",
|
|
105
|
+
"mi",
|
|
106
|
+
"mlabeledtr",
|
|
107
|
+
"mmultiscripts",
|
|
108
|
+
"mn",
|
|
109
|
+
"mo",
|
|
110
|
+
"mover",
|
|
111
|
+
"mpadded",
|
|
112
|
+
"mphantom",
|
|
113
|
+
"mprescripts",
|
|
114
|
+
"none",
|
|
115
|
+
"mroot",
|
|
116
|
+
"mrow",
|
|
117
|
+
"ms",
|
|
118
|
+
"mspace",
|
|
119
|
+
"msqrt",
|
|
120
|
+
"mstyle",
|
|
121
|
+
"msub",
|
|
122
|
+
"msubsup",
|
|
123
|
+
"msup",
|
|
124
|
+
"mtable",
|
|
125
|
+
"mtd",
|
|
126
|
+
"mtext",
|
|
127
|
+
"mtr",
|
|
128
|
+
"munder",
|
|
129
|
+
"munderover",
|
|
130
|
+
"semantics"
|
|
131
|
+
];
|
|
132
|
+
var MATHML_ATTRIBUTES = [
|
|
133
|
+
"xmlns",
|
|
134
|
+
"display",
|
|
135
|
+
"displaystyle",
|
|
136
|
+
"mathvariant",
|
|
137
|
+
"mathsize",
|
|
138
|
+
"mathcolor",
|
|
139
|
+
"mathbackground",
|
|
140
|
+
"scriptlevel",
|
|
141
|
+
"dir",
|
|
142
|
+
"fence",
|
|
143
|
+
"form",
|
|
144
|
+
"largeop",
|
|
145
|
+
"lspace",
|
|
146
|
+
"rspace",
|
|
147
|
+
"movablelimits",
|
|
148
|
+
"separator",
|
|
149
|
+
"stretchy",
|
|
150
|
+
"symmetric",
|
|
151
|
+
"maxsize",
|
|
152
|
+
"minsize",
|
|
153
|
+
"accent",
|
|
154
|
+
"accentunder",
|
|
155
|
+
"align",
|
|
156
|
+
"columnalign",
|
|
157
|
+
"rowalign",
|
|
158
|
+
"columnspacing",
|
|
159
|
+
"rowspacing",
|
|
160
|
+
"columnlines",
|
|
161
|
+
"rowlines",
|
|
162
|
+
"frame",
|
|
163
|
+
"framespacing",
|
|
164
|
+
"equalrows",
|
|
165
|
+
"equalcolumns",
|
|
166
|
+
"linethickness",
|
|
167
|
+
"numalign",
|
|
168
|
+
"denomalign",
|
|
169
|
+
"bevelled",
|
|
170
|
+
"notation",
|
|
171
|
+
"width",
|
|
172
|
+
"height",
|
|
173
|
+
"depth",
|
|
174
|
+
"voffset",
|
|
175
|
+
"lquote",
|
|
176
|
+
"rquote",
|
|
177
|
+
"columnspan",
|
|
178
|
+
"rowspan",
|
|
179
|
+
"encoding",
|
|
180
|
+
"open",
|
|
181
|
+
"close",
|
|
182
|
+
"separators",
|
|
183
|
+
"actiontype",
|
|
184
|
+
"selection",
|
|
185
|
+
"mslinebreak",
|
|
186
|
+
"linebreak",
|
|
187
|
+
"indentalign",
|
|
188
|
+
"data*"
|
|
189
|
+
];
|
|
190
|
+
var MATHML_SCHEMA = {
|
|
191
|
+
tagNames: MATHML_TAGS,
|
|
192
|
+
attributes: { "*": MATHML_ATTRIBUTES },
|
|
193
|
+
strip: ["script", "style"],
|
|
194
|
+
clobberPrefix: "",
|
|
195
|
+
clobber: [],
|
|
196
|
+
protocols: {}
|
|
197
|
+
};
|
|
198
|
+
function sanitizeMathml(mathml) {
|
|
199
|
+
return sanitizeHtmlFragment(mathml, MATHML_SCHEMA);
|
|
200
|
+
}
|
|
201
|
+
export {
|
|
202
|
+
FORBIDDEN_TAGS,
|
|
203
|
+
MATHML_SCHEMA,
|
|
204
|
+
PRINT_HTML_SCHEMA,
|
|
205
|
+
SAFE_HTML_SCHEMA,
|
|
206
|
+
sanitizeHtmlFragment,
|
|
207
|
+
sanitizeMathml,
|
|
208
|
+
sanitizePrintHtml
|
|
209
|
+
};
|
|
210
|
+
//# sourceMappingURL=safe-html.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/safe-html.ts"],"sourcesContent":["/**\n * @ai-matrx/print/safe-html — THE allow-list every Markdown→HTML string this\n * package hands out goes through.\n *\n * Why an allow-list: the regex converter (`markdownToHtml` /\n * `markdownToArticleHtml`) lets author raw HTML through so a document can feed\n * WordPress. Its old guard was a block-list (script, style, on… handlers,\n * javascript: URLs) and everything nobody listed — `<iframe>`, `<form>`,\n * `<meta http-equiv=refresh>`, `<object>`/`<embed>`, `<base>`, `<link>`,\n * inline `style` — reached the clipboard, the downloaded page and the preview.\n * Now a tag or attribute survives only when it is listed here.\n *\n * Sync and DOM-free (parse5 via hast), so it runs in Node, workers, the\n * extension service worker and Tauri alike.\n *\n * Two schemas:\n * • `SAFE_HTML_SCHEMA` — GitHub's rendering rules (hast-util-sanitize's\n * default schema) with no id clobber-prefixing and the \"drop the element\n * AND its text\" list for elements whose text is not prose. For any host\n * that renders plain semantic HTML (email bodies, CMS drafts).\n * • `PRINT_HTML_SCHEMA` — the same, plus exactly the vocabulary print's own\n * converters emit: `matrx-*` / `print-*` / `code-*` / `doc-*` classes,\n * table-cell alignment, the flashcard checkbox + label, figures, and the\n * page-break separator's data/aria attributes.\n */\n\nimport { fromHtml } from \"hast-util-from-html\";\nimport { defaultSchema, sanitize, type Schema } from \"hast-util-sanitize\";\nimport { toHtml } from \"hast-util-to-html\";\nimport type { Root } from \"hast\";\n\ntype Attributes = NonNullable<Schema[\"attributes\"]>;\ntype AttributeList = Attributes[string];\n\n/** Elements removed together with their content (their text is not prose). */\nconst STRIP_WITH_CONTENT = [\n \"script\",\n \"style\",\n \"noscript\",\n \"template\",\n \"iframe\",\n \"object\",\n \"embed\",\n \"textarea\",\n \"select\",\n \"head\",\n \"title\",\n];\n\n/** Never allowed, whatever a schema extension adds. */\nexport const FORBIDDEN_TAGS: readonly string[] = [\n ...STRIP_WITH_CONTENT,\n \"form\",\n \"meta\",\n \"base\",\n \"link\",\n \"frame\",\n \"frameset\",\n \"applet\",\n \"button\",\n \"svg\",\n \"math\",\n];\n\nconst baseAttributes = defaultSchema.attributes ?? {};\n\nexport const SAFE_HTML_SCHEMA: Schema = {\n ...defaultSchema,\n tagNames: (defaultSchema.tagNames ?? []).filter((t) => !FORBIDDEN_TAGS.includes(t)),\n strip: STRIP_WITH_CONTENT,\n // Authored anchors keep their names; the GitHub `user-content-` rewrite\n // would also break `<label for>` → `<input id>` pairs.\n clobberPrefix: \"\",\n clobber: [],\n};\n\n/** Class tokens print's converters emit. Author classes outside these are dropped. */\nconst PRINT_CLASS = /^(?:(?:matrx|print|code|doc|language)-[\\w-]*|inline-code|is-checked)$/;\n\n/**\n * A tag's own `className` definition wins over `'*'` (and returns an empty\n * list rather than falling through), so the print class pattern is merged into\n * every definition that names `className`, and added where none does.\n */\nfunction withPrintClasses(defs: AttributeList = []): AttributeList {\n let found = false;\n const out = defs.map((d) => {\n if (Array.isArray(d) && d[0] === \"className\") {\n found = true;\n return [...d, PRINT_CLASS] as unknown as typeof d;\n }\n return d;\n });\n return found ? out : [...out, [\"className\", PRINT_CLASS]];\n}\n\nconst printAttributes: Attributes = Object.fromEntries(\n Object.entries(baseAttributes).map(([tag, defs]) => [tag, withPrintClasses(defs)]),\n);\n\nconst cellAlign = [\"style\", /^text-align:\\s*(?:left|center|right);?$/] as const;\n\nexport const PRINT_HTML_SCHEMA: Schema = {\n ...SAFE_HTML_SCHEMA,\n tagNames: [\n ...new Set([...(SAFE_HTML_SCHEMA.tagNames ?? []), \"section\", \"figure\", \"figcaption\", \"label\"]),\n ],\n attributes: {\n ...printAttributes,\n \"*\": [\n ...(baseAttributes[\"*\"] ?? []),\n [\"className\", PRINT_CLASS],\n \"role\",\n \"ariaLabel\",\n \"ariaHidden\",\n \"dataLabel\",\n \"dataLanguage\",\n \"dataArtifactType\",\n ],\n // Table-cell alignment is the one inline style the converter writes.\n td: withPrintClasses([...(baseAttributes[\"td\"] ?? []), [...cellAlign]]),\n th: withPrintClasses([...(baseAttributes[\"th\"] ?? []), [...cellAlign]]),\n // The flashcard flip is a checkbox + label pair (GitHub forces `disabled`).\n input: withPrintClasses([[\"type\", \"checkbox\"], \"checked\"]),\n label: withPrintClasses([\"htmlFor\"]),\n },\n required: { input: { type: \"checkbox\" } },\n protocols: {\n ...defaultSchema.protocols,\n href: [...(defaultSchema.protocols?.href ?? []), \"tel\"],\n // Embedded images (`data:image/png;base64,…`) — an <img> never runs script.\n src: [...(defaultSchema.protocols?.src ?? []), \"data\"],\n },\n};\n\n/** Sanitize an HTML fragment against an allow-list (default: `SAFE_HTML_SCHEMA`). */\nexport function sanitizeHtmlFragment(html: string, schema: Schema = SAFE_HTML_SCHEMA): string {\n if (!html) return \"\";\n const tree = sanitize(fromHtml(html, { fragment: true }), schema) as Root;\n return toHtml(tree, { characterReferences: { useNamedReferences: true } });\n}\n\n/** Sanitize the output of print's own converters (keeps the `.matrx-*` vocabulary). */\nexport function sanitizePrintHtml(html: string): string {\n return sanitizeHtmlFragment(html, PRINT_HTML_SCHEMA);\n}\n\nexport type { Schema as SafeHtmlSchema };\n\n/**\n * MathML from the TeX typesetter. TeX is author input, and MathJax's `html`,\n * `bbox` and `\\mmlToken` commands turn it into attributes (`\\href{javascript:…}`\n * → `href`, `\\style{position:fixed…}` / `\\bbox[position:fixed]` → `style`).\n * Only presentation MathML elements and their layout attributes survive.\n */\nconst MATHML_TAGS = [\n \"math\", \"maction\", \"annotation\", \"menclose\", \"merror\", \"mfrac\", \"mglyph\", \"mi\",\n \"mlabeledtr\", \"mmultiscripts\", \"mn\", \"mo\", \"mover\", \"mpadded\", \"mphantom\", \"mprescripts\", \"none\",\n \"mroot\", \"mrow\", \"ms\", \"mspace\", \"msqrt\", \"mstyle\", \"msub\", \"msubsup\", \"msup\", \"mtable\", \"mtd\",\n \"mtext\", \"mtr\", \"munder\", \"munderover\", \"semantics\",\n];\n\nconst MATHML_ATTRIBUTES = [\n \"xmlns\", \"display\", \"displaystyle\", \"mathvariant\", \"mathsize\", \"mathcolor\", \"mathbackground\",\n \"scriptlevel\", \"dir\", \"fence\", \"form\", \"largeop\", \"lspace\", \"rspace\", \"movablelimits\",\n \"separator\", \"stretchy\", \"symmetric\", \"maxsize\", \"minsize\", \"accent\", \"accentunder\", \"align\",\n \"columnalign\", \"rowalign\", \"columnspacing\", \"rowspacing\", \"columnlines\", \"rowlines\", \"frame\",\n \"framespacing\", \"equalrows\", \"equalcolumns\", \"linethickness\", \"numalign\", \"denomalign\",\n \"bevelled\", \"notation\", \"width\", \"height\", \"depth\", \"voffset\", \"lquote\", \"rquote\", \"columnspan\",\n \"rowspan\", \"encoding\", \"open\", \"close\", \"separators\", \"actiontype\", \"selection\", \"mslinebreak\",\n \"linebreak\", \"indentalign\", \"data*\",\n];\n\nexport const MATHML_SCHEMA: Schema = {\n tagNames: MATHML_TAGS,\n attributes: { \"*\": MATHML_ATTRIBUTES },\n strip: [\"script\", \"style\"],\n clobberPrefix: \"\",\n clobber: [],\n protocols: {},\n};\n\n/** Sanitize a typeset MathML string (keeps layout, drops links and styles). */\nexport function sanitizeMathml(mathml: string): string {\n return sanitizeHtmlFragment(mathml, MATHML_SCHEMA);\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-matrx/print",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.8",
|
|
4
4
|
"description": "The AI Matrx print system: the BlockPrinter contract and print-window composition, the flashcards deck printer (duplex-mirrored cut sheets, Avery 5388, study sheets), QR generation with spec quiet zones and e-commerce payload builders, label-sheet printing on Avery and roll stock with custom label formats, and a complete markdown pipeline: a shipped markdown converter, two default print stylesheets, print windows, and markdown/DOM to PDF, and print-grade documents (cover, TOC, captions, cross-references, citations, sections) exported to PDF, DOCX, EPUB and HTML from one parsed tree.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -215,6 +215,16 @@
|
|
|
215
215
|
"types": "./dist/document.d.cts",
|
|
216
216
|
"default": "./dist/document.cjs"
|
|
217
217
|
}
|
|
218
|
+
},
|
|
219
|
+
"./safe-html": {
|
|
220
|
+
"import": {
|
|
221
|
+
"types": "./dist/safe-html.d.ts",
|
|
222
|
+
"default": "./dist/safe-html.js"
|
|
223
|
+
},
|
|
224
|
+
"require": {
|
|
225
|
+
"types": "./dist/safe-html.d.cts",
|
|
226
|
+
"default": "./dist/safe-html.cjs"
|
|
227
|
+
}
|
|
218
228
|
}
|
|
219
229
|
},
|
|
220
230
|
"dependencies": {
|
|
@@ -224,6 +234,9 @@
|
|
|
224
234
|
"citeproc": "^2.4.63",
|
|
225
235
|
"docx": "^9.7.2",
|
|
226
236
|
"fontkit": "^2.0.4",
|
|
237
|
+
"hast-util-from-html": "^2.0.3",
|
|
238
|
+
"hast-util-sanitize": "^5.0.2",
|
|
239
|
+
"hast-util-to-html": "^9.0.5",
|
|
227
240
|
"html2canvas": "^1.4.1",
|
|
228
241
|
"hyphen": "^1.14.1",
|
|
229
242
|
"jspdf": "^4.2.1",
|
|
@@ -238,8 +251,8 @@
|
|
|
238
251
|
"tailwind-merge": "^3.7.0",
|
|
239
252
|
"vaul": "^1.1.2",
|
|
240
253
|
"yaml": "^2.9.1",
|
|
241
|
-
"@ai-matrx/
|
|
242
|
-
"@ai-matrx/
|
|
254
|
+
"@ai-matrx/content-ir": "latest",
|
|
255
|
+
"@ai-matrx/kit": "latest"
|
|
243
256
|
},
|
|
244
257
|
"peerDependencies": {
|
|
245
258
|
"react": ">=18.0.0",
|
|
@@ -259,6 +272,7 @@
|
|
|
259
272
|
"@fontsource/noto-sans-math": "^5.3.0",
|
|
260
273
|
"@testing-library/react": "^16.3.3",
|
|
261
274
|
"@types/fontkit": "^2.0.9",
|
|
275
|
+
"@types/hast": "^3.0.5",
|
|
262
276
|
"@types/hyphen": "^1.14.0",
|
|
263
277
|
"@types/mdast": "^4.0.4",
|
|
264
278
|
"@types/node": "^26.6.2",
|