@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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.8
|
|
4
|
+
|
|
5
|
+
- **Security: every markdown→HTML path is allow-list sanitized.** The regex
|
|
6
|
+
converter (`markdownToHtml`, `markdownToArticleHtml`, and through them
|
|
7
|
+
`renderMarkdownDocument`, `markdownToHtmlForSkin`, `markdownToPdfBlob`) used a
|
|
8
|
+
block-list and let `<iframe>`, `<form>`, `<meta http-equiv=refresh>`,
|
|
9
|
+
`<object>`/`<embed>`, `<base>`, `<link>`, `<svg>`, `<textarea>`/`<select>` and
|
|
10
|
+
inline `style` through. Output now goes through `PRINT_HTML_SCHEMA`
|
|
11
|
+
(hast-util-sanitize's GitHub schema plus the converter's own `.matrx-*` /
|
|
12
|
+
`print-*` / `code-*` classes, table-cell `text-align`, the flashcard
|
|
13
|
+
checkbox + label, figures, page-break data/aria attributes). Sync, DOM-free.
|
|
14
|
+
- **Security (`/document`):** TeX `\href`, `\style`, `\class`, `\cssId`,
|
|
15
|
+
`\bbox[css]` and `\mmlToken[attrs]` no longer write `href`/`style` into the
|
|
16
|
+
exported MathML (`MATHML_SCHEMA`); a front-matter `header`/`footer` can no
|
|
17
|
+
longer close the document's `<style>` element.
|
|
18
|
+
- **New subpath `/safe-html`:** `sanitizeHtmlFragment`, `SAFE_HTML_SCHEMA`
|
|
19
|
+
(plain GitHub allow-list for email/CMS), `PRINT_HTML_SCHEMA`,
|
|
20
|
+
`sanitizePrintHtml`, `MATHML_SCHEMA`, `sanitizeMathml`.
|
|
21
|
+
- **Visible differences:** author raw-HTML classes outside the print
|
|
22
|
+
vocabulary are dropped (`<div class="note">` → `<div class="matrx-div">`);
|
|
23
|
+
author inline styles are dropped; HTML serialization is normalized (`<img …>`
|
|
24
|
+
not `<img … />`, text `>` and `"` literal instead of `>`/`"`) —
|
|
25
|
+
same rendered page.
|
|
26
|
+
- Consumer action: none — refresh to latest.
|
|
27
|
+
|
|
28
|
+
## 0.8.7
|
|
29
|
+
|
|
30
|
+
- **Fixed: hidden reasoning merged two paragraphs.** `removeThinkingContent`
|
|
31
|
+
replaced every `<thinking>` / `<think>` / `<reasoning>` section with one line
|
|
32
|
+
break, so a section standing between two paragraphs (blank lines around it)
|
|
33
|
+
joined them into one on screen. A section with a blank line on either side
|
|
34
|
+
now leaves a paragraph break; one inside a paragraph still leaves a line
|
|
35
|
+
break (verify-RC-B5 round 3 F6).
|
|
36
|
+
- Consumer action: none — refresh to latest.
|
|
37
|
+
|
|
3
38
|
## 0.8.6
|
|
4
39
|
|
|
5
40
|
Automatic changed-only republish (docs/metadata drift since the last tag — see
|
package/dist/document.cjs
CHANGED
|
@@ -170,6 +170,206 @@ function contentColumnWidths(b, n, avail, measure, pad = 10) {
|
|
|
170
170
|
|
|
171
171
|
// src/document/math.ts
|
|
172
172
|
var import_source = require("@ai-matrx/content-ir/source");
|
|
173
|
+
|
|
174
|
+
// src/safe-html.ts
|
|
175
|
+
var import_hast_util_from_html = require("hast-util-from-html");
|
|
176
|
+
var import_hast_util_sanitize = require("hast-util-sanitize");
|
|
177
|
+
var import_hast_util_to_html = require("hast-util-to-html");
|
|
178
|
+
var STRIP_WITH_CONTENT = [
|
|
179
|
+
"script",
|
|
180
|
+
"style",
|
|
181
|
+
"noscript",
|
|
182
|
+
"template",
|
|
183
|
+
"iframe",
|
|
184
|
+
"object",
|
|
185
|
+
"embed",
|
|
186
|
+
"textarea",
|
|
187
|
+
"select",
|
|
188
|
+
"head",
|
|
189
|
+
"title"
|
|
190
|
+
];
|
|
191
|
+
var FORBIDDEN_TAGS = [
|
|
192
|
+
...STRIP_WITH_CONTENT,
|
|
193
|
+
"form",
|
|
194
|
+
"meta",
|
|
195
|
+
"base",
|
|
196
|
+
"link",
|
|
197
|
+
"frame",
|
|
198
|
+
"frameset",
|
|
199
|
+
"applet",
|
|
200
|
+
"button",
|
|
201
|
+
"svg",
|
|
202
|
+
"math"
|
|
203
|
+
];
|
|
204
|
+
var baseAttributes = import_hast_util_sanitize.defaultSchema.attributes ?? {};
|
|
205
|
+
var SAFE_HTML_SCHEMA = {
|
|
206
|
+
...import_hast_util_sanitize.defaultSchema,
|
|
207
|
+
tagNames: (import_hast_util_sanitize.defaultSchema.tagNames ?? []).filter((t) => !FORBIDDEN_TAGS.includes(t)),
|
|
208
|
+
strip: STRIP_WITH_CONTENT,
|
|
209
|
+
// Authored anchors keep their names; the GitHub `user-content-` rewrite
|
|
210
|
+
// would also break `<label for>` → `<input id>` pairs.
|
|
211
|
+
clobberPrefix: "",
|
|
212
|
+
clobber: []
|
|
213
|
+
};
|
|
214
|
+
var PRINT_CLASS = /^(?:(?:matrx|print|code|doc|language)-[\w-]*|inline-code|is-checked)$/;
|
|
215
|
+
function withPrintClasses(defs = []) {
|
|
216
|
+
let found = false;
|
|
217
|
+
const out = defs.map((d) => {
|
|
218
|
+
if (Array.isArray(d) && d[0] === "className") {
|
|
219
|
+
found = true;
|
|
220
|
+
return [...d, PRINT_CLASS];
|
|
221
|
+
}
|
|
222
|
+
return d;
|
|
223
|
+
});
|
|
224
|
+
return found ? out : [...out, ["className", PRINT_CLASS]];
|
|
225
|
+
}
|
|
226
|
+
var printAttributes = Object.fromEntries(
|
|
227
|
+
Object.entries(baseAttributes).map(([tag, defs]) => [tag, withPrintClasses(defs)])
|
|
228
|
+
);
|
|
229
|
+
var cellAlign = ["style", /^text-align:\s*(?:left|center|right);?$/];
|
|
230
|
+
var PRINT_HTML_SCHEMA = {
|
|
231
|
+
...SAFE_HTML_SCHEMA,
|
|
232
|
+
tagNames: [
|
|
233
|
+
.../* @__PURE__ */ new Set([...SAFE_HTML_SCHEMA.tagNames ?? [], "section", "figure", "figcaption", "label"])
|
|
234
|
+
],
|
|
235
|
+
attributes: {
|
|
236
|
+
...printAttributes,
|
|
237
|
+
"*": [
|
|
238
|
+
...baseAttributes["*"] ?? [],
|
|
239
|
+
["className", PRINT_CLASS],
|
|
240
|
+
"role",
|
|
241
|
+
"ariaLabel",
|
|
242
|
+
"ariaHidden",
|
|
243
|
+
"dataLabel",
|
|
244
|
+
"dataLanguage",
|
|
245
|
+
"dataArtifactType"
|
|
246
|
+
],
|
|
247
|
+
// Table-cell alignment is the one inline style the converter writes.
|
|
248
|
+
td: withPrintClasses([...baseAttributes["td"] ?? [], [...cellAlign]]),
|
|
249
|
+
th: withPrintClasses([...baseAttributes["th"] ?? [], [...cellAlign]]),
|
|
250
|
+
// The flashcard flip is a checkbox + label pair (GitHub forces `disabled`).
|
|
251
|
+
input: withPrintClasses([["type", "checkbox"], "checked"]),
|
|
252
|
+
label: withPrintClasses(["htmlFor"])
|
|
253
|
+
},
|
|
254
|
+
required: { input: { type: "checkbox" } },
|
|
255
|
+
protocols: {
|
|
256
|
+
...import_hast_util_sanitize.defaultSchema.protocols,
|
|
257
|
+
href: [...import_hast_util_sanitize.defaultSchema.protocols?.href ?? [], "tel"],
|
|
258
|
+
// Embedded images (`data:image/png;base64,…`) — an <img> never runs script.
|
|
259
|
+
src: [...import_hast_util_sanitize.defaultSchema.protocols?.src ?? [], "data"]
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
function sanitizeHtmlFragment(html, schema = SAFE_HTML_SCHEMA) {
|
|
263
|
+
if (!html) return "";
|
|
264
|
+
const tree = (0, import_hast_util_sanitize.sanitize)((0, import_hast_util_from_html.fromHtml)(html, { fragment: true }), schema);
|
|
265
|
+
return (0, import_hast_util_to_html.toHtml)(tree, { characterReferences: { useNamedReferences: true } });
|
|
266
|
+
}
|
|
267
|
+
var MATHML_TAGS = [
|
|
268
|
+
"math",
|
|
269
|
+
"maction",
|
|
270
|
+
"annotation",
|
|
271
|
+
"menclose",
|
|
272
|
+
"merror",
|
|
273
|
+
"mfrac",
|
|
274
|
+
"mglyph",
|
|
275
|
+
"mi",
|
|
276
|
+
"mlabeledtr",
|
|
277
|
+
"mmultiscripts",
|
|
278
|
+
"mn",
|
|
279
|
+
"mo",
|
|
280
|
+
"mover",
|
|
281
|
+
"mpadded",
|
|
282
|
+
"mphantom",
|
|
283
|
+
"mprescripts",
|
|
284
|
+
"none",
|
|
285
|
+
"mroot",
|
|
286
|
+
"mrow",
|
|
287
|
+
"ms",
|
|
288
|
+
"mspace",
|
|
289
|
+
"msqrt",
|
|
290
|
+
"mstyle",
|
|
291
|
+
"msub",
|
|
292
|
+
"msubsup",
|
|
293
|
+
"msup",
|
|
294
|
+
"mtable",
|
|
295
|
+
"mtd",
|
|
296
|
+
"mtext",
|
|
297
|
+
"mtr",
|
|
298
|
+
"munder",
|
|
299
|
+
"munderover",
|
|
300
|
+
"semantics"
|
|
301
|
+
];
|
|
302
|
+
var MATHML_ATTRIBUTES = [
|
|
303
|
+
"xmlns",
|
|
304
|
+
"display",
|
|
305
|
+
"displaystyle",
|
|
306
|
+
"mathvariant",
|
|
307
|
+
"mathsize",
|
|
308
|
+
"mathcolor",
|
|
309
|
+
"mathbackground",
|
|
310
|
+
"scriptlevel",
|
|
311
|
+
"dir",
|
|
312
|
+
"fence",
|
|
313
|
+
"form",
|
|
314
|
+
"largeop",
|
|
315
|
+
"lspace",
|
|
316
|
+
"rspace",
|
|
317
|
+
"movablelimits",
|
|
318
|
+
"separator",
|
|
319
|
+
"stretchy",
|
|
320
|
+
"symmetric",
|
|
321
|
+
"maxsize",
|
|
322
|
+
"minsize",
|
|
323
|
+
"accent",
|
|
324
|
+
"accentunder",
|
|
325
|
+
"align",
|
|
326
|
+
"columnalign",
|
|
327
|
+
"rowalign",
|
|
328
|
+
"columnspacing",
|
|
329
|
+
"rowspacing",
|
|
330
|
+
"columnlines",
|
|
331
|
+
"rowlines",
|
|
332
|
+
"frame",
|
|
333
|
+
"framespacing",
|
|
334
|
+
"equalrows",
|
|
335
|
+
"equalcolumns",
|
|
336
|
+
"linethickness",
|
|
337
|
+
"numalign",
|
|
338
|
+
"denomalign",
|
|
339
|
+
"bevelled",
|
|
340
|
+
"notation",
|
|
341
|
+
"width",
|
|
342
|
+
"height",
|
|
343
|
+
"depth",
|
|
344
|
+
"voffset",
|
|
345
|
+
"lquote",
|
|
346
|
+
"rquote",
|
|
347
|
+
"columnspan",
|
|
348
|
+
"rowspan",
|
|
349
|
+
"encoding",
|
|
350
|
+
"open",
|
|
351
|
+
"close",
|
|
352
|
+
"separators",
|
|
353
|
+
"actiontype",
|
|
354
|
+
"selection",
|
|
355
|
+
"mslinebreak",
|
|
356
|
+
"linebreak",
|
|
357
|
+
"indentalign",
|
|
358
|
+
"data*"
|
|
359
|
+
];
|
|
360
|
+
var MATHML_SCHEMA = {
|
|
361
|
+
tagNames: MATHML_TAGS,
|
|
362
|
+
attributes: { "*": MATHML_ATTRIBUTES },
|
|
363
|
+
strip: ["script", "style"],
|
|
364
|
+
clobberPrefix: "",
|
|
365
|
+
clobber: [],
|
|
366
|
+
protocols: {}
|
|
367
|
+
};
|
|
368
|
+
function sanitizeMathml(mathml) {
|
|
369
|
+
return sanitizeHtmlFragment(mathml, MATHML_SCHEMA);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// src/document/math.ts
|
|
173
373
|
var MATH_OPEN = "\uE100";
|
|
174
374
|
var MATH_CLOSE = "\uE101";
|
|
175
375
|
var MATH_TOKEN_RE = /\uE100(\d+)\uE101/g;
|
|
@@ -236,10 +436,10 @@ async function typesetMathSpans(spans) {
|
|
|
236
436
|
const mathml = mj.mml(s.tex, s.display);
|
|
237
437
|
const err = /<merror[\s\S]*?<mtext[^>]*>([\s\S]*?)<\/mtext>/.exec(mathml);
|
|
238
438
|
if (err) {
|
|
239
|
-
out.set(key, { mathml, error: decodeXml(err[1]) });
|
|
439
|
+
out.set(key, { mathml: sanitizeMathml(mathml), error: decodeXml(err[1]) });
|
|
240
440
|
continue;
|
|
241
441
|
}
|
|
242
|
-
const entry = { mathml: mathml.replace(/\n\s*/g, "") };
|
|
442
|
+
const entry = { mathml: sanitizeMathml(mathml.replace(/\n\s*/g, "")) };
|
|
243
443
|
if (s.display) {
|
|
244
444
|
const svg = mj.svg(s.tex, true);
|
|
245
445
|
entry.svg = svg;
|
|
@@ -3437,7 +3637,7 @@ function coverHtml(s, x) {
|
|
|
3437
3637
|
s.title ?? ""
|
|
3438
3638
|
)}</h1>${s.subtitle ? `<p class="doc-cover-subtitle">${(0, import_html_escape.escapeHtml)(s.subtitle)}</p>` : ""}${s.author ? `<p class="doc-cover-author">${(0, import_html_escape.escapeHtml)(s.author)}</p>` : ""}${s.date ? `<p class="doc-cover-date">${(0, import_html_escape.escapeHtml)(s.date)}</p>` : ""}</section>`;
|
|
3439
3639
|
}
|
|
3440
|
-
var cssString = (s) => `"${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, " ")}"`;
|
|
3640
|
+
var cssString = (s) => `"${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, " ").replace(/</g, "\\3C ").replace(/>/g, "\\3E ")}"`;
|
|
3441
3641
|
function marginContent(template, s) {
|
|
3442
3642
|
if (!template) return "none";
|
|
3443
3643
|
return templateParts(template, s).map((p) => "field" in p ? `counter(${p.field})` : cssString(p.text)).join(" ");
|
|
@@ -4897,7 +5097,8 @@ var import_micromark_extension_gfm = require("micromark-extension-gfm");
|
|
|
4897
5097
|
var import_html_escape3 = require("@ai-matrx/kit/html-escape");
|
|
4898
5098
|
function removeThinkingContent(content) {
|
|
4899
5099
|
if (!content) return "";
|
|
4900
|
-
|
|
5100
|
+
const hide = (_match, before, after) => before.length >= 2 || after.length >= 2 ? "\n\n" : "\n";
|
|
5101
|
+
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);
|
|
4901
5102
|
result = result.replace(/\n{3,}/g, "\n\n");
|
|
4902
5103
|
return result.trim();
|
|
4903
5104
|
}
|
|
@@ -4966,6 +5167,7 @@ function looksLikeJson(body) {
|
|
|
4966
5167
|
return false;
|
|
4967
5168
|
}
|
|
4968
5169
|
}
|
|
5170
|
+
var ALLOWED_TAGS = new Set(PRINT_HTML_SCHEMA.tagNames ?? []);
|
|
4969
5171
|
var LINK_URL = String.raw`((?:[^()\s]|\([^()\s]*\))+)(?:\s+"[^"]*")?`;
|
|
4970
5172
|
var IMAGE_RE = new RegExp(String.raw`!\[([^\]]*)\]\(` + LINK_URL + String.raw`\)`, "g");
|
|
4971
5173
|
var LINK_RE = new RegExp(String.raw`\[([^\]]+)\]\(` + LINK_URL + String.raw`\)`, "g");
|