formatferry 1.0.39 → 1.0.41
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/dist/index.js +354 -156
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -58011,6 +58011,153 @@ import DOMPurifyImport from "dompurify";
|
|
|
58011
58011
|
// ../../src/shared/constants.ts
|
|
58012
58012
|
var MAX_INTERMEDIATE_HTML_CHARS = 256 * 1024 * 1024;
|
|
58013
58013
|
|
|
58014
|
+
// ../../src/shared/linkedom-sanitizer.ts
|
|
58015
|
+
var FORBID_CONTENTS = /* @__PURE__ */ new Set([
|
|
58016
|
+
"annotation-xml",
|
|
58017
|
+
"audio",
|
|
58018
|
+
"colgroup",
|
|
58019
|
+
"desc",
|
|
58020
|
+
"foreignobject",
|
|
58021
|
+
"head",
|
|
58022
|
+
"iframe",
|
|
58023
|
+
"math",
|
|
58024
|
+
"mi",
|
|
58025
|
+
"mn",
|
|
58026
|
+
"mo",
|
|
58027
|
+
"ms",
|
|
58028
|
+
"mtext",
|
|
58029
|
+
"noembed",
|
|
58030
|
+
"noframes",
|
|
58031
|
+
"noscript",
|
|
58032
|
+
"plaintext",
|
|
58033
|
+
"script",
|
|
58034
|
+
"selectedcontent",
|
|
58035
|
+
"style",
|
|
58036
|
+
"svg",
|
|
58037
|
+
"template",
|
|
58038
|
+
"thead",
|
|
58039
|
+
"title",
|
|
58040
|
+
"video",
|
|
58041
|
+
"xmp"
|
|
58042
|
+
]);
|
|
58043
|
+
var BLOCK_UNWRAP_TAGS = /* @__PURE__ */ new Set([
|
|
58044
|
+
"address",
|
|
58045
|
+
"article",
|
|
58046
|
+
"aside",
|
|
58047
|
+
"center",
|
|
58048
|
+
"details",
|
|
58049
|
+
"dialog",
|
|
58050
|
+
"dir",
|
|
58051
|
+
"dd",
|
|
58052
|
+
"dl",
|
|
58053
|
+
"dt",
|
|
58054
|
+
"fieldset",
|
|
58055
|
+
"figcaption",
|
|
58056
|
+
"figure",
|
|
58057
|
+
"footer",
|
|
58058
|
+
"form",
|
|
58059
|
+
"header",
|
|
58060
|
+
"hgroup",
|
|
58061
|
+
"legend",
|
|
58062
|
+
"main",
|
|
58063
|
+
"menu",
|
|
58064
|
+
"nav",
|
|
58065
|
+
"search",
|
|
58066
|
+
"section",
|
|
58067
|
+
"summary"
|
|
58068
|
+
]);
|
|
58069
|
+
var DATA_URI_TAGS = /* @__PURE__ */ new Set(["audio", "video", "img", "source", "image", "track"]);
|
|
58070
|
+
var URI_SAFE_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
58071
|
+
"alt",
|
|
58072
|
+
"class",
|
|
58073
|
+
"for",
|
|
58074
|
+
"id",
|
|
58075
|
+
"label",
|
|
58076
|
+
"name",
|
|
58077
|
+
"pattern",
|
|
58078
|
+
"placeholder",
|
|
58079
|
+
"role",
|
|
58080
|
+
"summary",
|
|
58081
|
+
"title",
|
|
58082
|
+
"value",
|
|
58083
|
+
"style",
|
|
58084
|
+
"xmlns"
|
|
58085
|
+
]);
|
|
58086
|
+
var IS_ALLOWED_URI = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i;
|
|
58087
|
+
var ATTR_WHITESPACE = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g;
|
|
58088
|
+
var ELEMENT_NODE = 1;
|
|
58089
|
+
var COMMENT_NODE = 8;
|
|
58090
|
+
function toSet(list) {
|
|
58091
|
+
return new Set((list ?? []).map((s) => s.toLowerCase()));
|
|
58092
|
+
}
|
|
58093
|
+
function attrAllowed(name, allowed, wildcards) {
|
|
58094
|
+
return allowed.has(name) || wildcards.some((prefix) => name.startsWith(prefix));
|
|
58095
|
+
}
|
|
58096
|
+
function isSafeValue(tag, name, value) {
|
|
58097
|
+
if (URI_SAFE_ATTRIBUTES.has(name) || !value) return true;
|
|
58098
|
+
if (IS_ALLOWED_URI.test(value.replace(ATTR_WHITESPACE, ""))) return true;
|
|
58099
|
+
return (name === "src" || name === "href" || name === "xlink:href") && value.startsWith("data:") && DATA_URI_TAGS.has(tag);
|
|
58100
|
+
}
|
|
58101
|
+
function sanitizeWithLinkedom(html, config, parse) {
|
|
58102
|
+
if (!html) return "";
|
|
58103
|
+
const allowedTags = toSet(config.ALLOWED_TAGS);
|
|
58104
|
+
const forbidTags = toSet(config.FORBID_TAGS);
|
|
58105
|
+
const allowedAttrList = (config.ALLOWED_ATTR ?? []).map((s) => s.toLowerCase());
|
|
58106
|
+
const allowedAttrs = new Set(allowedAttrList.filter((a) => !a.endsWith("*")));
|
|
58107
|
+
const attrWildcards = allowedAttrList.filter((a) => a.endsWith("*")).map((a) => a.slice(0, -1));
|
|
58108
|
+
const forbidAttrs = toSet(config.FORBID_ATTR);
|
|
58109
|
+
const doc = parse(`<!DOCTYPE html><html><head></head><body>${html}</body></html>`);
|
|
58110
|
+
const body = doc.body;
|
|
58111
|
+
if (!body) return "";
|
|
58112
|
+
const toUnwrap = [];
|
|
58113
|
+
const hadChildren = /* @__PURE__ */ new Set();
|
|
58114
|
+
const stack = [body];
|
|
58115
|
+
while (stack.length > 0) {
|
|
58116
|
+
const parent = stack.pop();
|
|
58117
|
+
for (const child of Array.from(parent.childNodes)) {
|
|
58118
|
+
if (child.nodeType === COMMENT_NODE) {
|
|
58119
|
+
parent.removeChild(child);
|
|
58120
|
+
continue;
|
|
58121
|
+
}
|
|
58122
|
+
if (child.nodeType !== ELEMENT_NODE) continue;
|
|
58123
|
+
const el = child;
|
|
58124
|
+
const tag = el.nodeName.toLowerCase();
|
|
58125
|
+
if (!allowedTags.has(tag) || forbidTags.has(tag)) {
|
|
58126
|
+
if (FORBID_CONTENTS.has(tag)) {
|
|
58127
|
+
parent.removeChild(el);
|
|
58128
|
+
} else {
|
|
58129
|
+
toUnwrap.push(el);
|
|
58130
|
+
if (el.firstChild) hadChildren.add(el);
|
|
58131
|
+
stack.push(el);
|
|
58132
|
+
}
|
|
58133
|
+
continue;
|
|
58134
|
+
}
|
|
58135
|
+
for (const attr of Array.from(el.attributes)) {
|
|
58136
|
+
const name = attr.name.toLowerCase();
|
|
58137
|
+
if (name.startsWith("on") || forbidAttrs.has(name) || !attrAllowed(name, allowedAttrs, attrWildcards) || !isSafeValue(tag, name, attr.value)) {
|
|
58138
|
+
el.removeAttribute(attr.name);
|
|
58139
|
+
}
|
|
58140
|
+
}
|
|
58141
|
+
stack.push(el);
|
|
58142
|
+
}
|
|
58143
|
+
}
|
|
58144
|
+
const wrapBlocks = allowedTags.has("div") && !forbidTags.has("div");
|
|
58145
|
+
for (let i = toUnwrap.length - 1; i >= 0; i--) {
|
|
58146
|
+
const el = toUnwrap[i];
|
|
58147
|
+
const parent = el.parentNode;
|
|
58148
|
+
if (!parent) continue;
|
|
58149
|
+
if (wrapBlocks && hadChildren.has(el) && BLOCK_UNWRAP_TAGS.has(el.nodeName.toLowerCase())) {
|
|
58150
|
+
const div = doc.createElement("div");
|
|
58151
|
+
while (el.firstChild) div.appendChild(el.firstChild);
|
|
58152
|
+
parent.replaceChild(div, el);
|
|
58153
|
+
continue;
|
|
58154
|
+
}
|
|
58155
|
+
while (el.firstChild) parent.insertBefore(el.firstChild, el);
|
|
58156
|
+
parent.removeChild(el);
|
|
58157
|
+
}
|
|
58158
|
+
return body.innerHTML;
|
|
58159
|
+
}
|
|
58160
|
+
|
|
58014
58161
|
// ../../src/shared/input-sanitizer.ts
|
|
58015
58162
|
function resolveDOMPurify() {
|
|
58016
58163
|
const shimInstance = globalThis.__FF_DOMPurify;
|
|
@@ -58056,12 +58203,42 @@ var InputSanitizer = class {
|
|
|
58056
58203
|
if (!html || typeof html !== "string") {
|
|
58057
58204
|
return "";
|
|
58058
58205
|
}
|
|
58059
|
-
|
|
58060
|
-
|
|
58061
|
-
|
|
58062
|
-
|
|
58063
|
-
|
|
58064
|
-
|
|
58206
|
+
const purify = this.domEnv.DOMPurify;
|
|
58207
|
+
const hook = this.blockUnwrapHook();
|
|
58208
|
+
const useHook = hook && purify.addHook && purify.removeHook;
|
|
58209
|
+
if (useHook) purify.addHook("uponSanitizeElement", hook);
|
|
58210
|
+
try {
|
|
58211
|
+
return purify.sanitize(html, {
|
|
58212
|
+
ALLOWED_TAGS: this.purifyConfig.ALLOWED_TAGS,
|
|
58213
|
+
ALLOWED_ATTR: this.purifyConfig.ALLOWED_ATTR,
|
|
58214
|
+
FORBID_TAGS: this.purifyConfig.FORBID_TAGS,
|
|
58215
|
+
FORBID_ATTR: this.purifyConfig.FORBID_ATTR
|
|
58216
|
+
});
|
|
58217
|
+
} finally {
|
|
58218
|
+
if (useHook) purify.removeHook("uponSanitizeElement", hook);
|
|
58219
|
+
}
|
|
58220
|
+
}
|
|
58221
|
+
/**
|
|
58222
|
+
* DOMPurify unwraps a disallowed element by moving its children up, so
|
|
58223
|
+
* `<header>Head</header><nav>Nav</nav>` came out as "HeadNav". For a
|
|
58224
|
+
* block-level element, move its children into a <div> first: DOMPurify then
|
|
58225
|
+
* hoists that <div>, and Turndown gives the content its own paragraph. Same
|
|
58226
|
+
* rule as the linkedom sanitiser (BLOCK_UNWRAP_TAGS, #177).
|
|
58227
|
+
*/
|
|
58228
|
+
blockUnwrapHook() {
|
|
58229
|
+
const allowed = new Set((this.purifyConfig.ALLOWED_TAGS ?? []).map((t) => t.toLowerCase()));
|
|
58230
|
+
const forbidden = new Set((this.purifyConfig.FORBID_TAGS ?? []).map((t) => t.toLowerCase()));
|
|
58231
|
+
if (!allowed.has("div") || forbidden.has("div")) return void 0;
|
|
58232
|
+
return (node, data) => {
|
|
58233
|
+
const tag = data.tagName.toLowerCase();
|
|
58234
|
+
if (!BLOCK_UNWRAP_TAGS.has(tag) || allowed.has(tag) && !forbidden.has(tag)) return;
|
|
58235
|
+
const el = node;
|
|
58236
|
+
const doc = el.ownerDocument;
|
|
58237
|
+
if (!doc || !el.firstChild) return;
|
|
58238
|
+
const div = doc.createElement("div");
|
|
58239
|
+
while (el.firstChild) div.appendChild(el.firstChild);
|
|
58240
|
+
el.appendChild(div);
|
|
58241
|
+
};
|
|
58065
58242
|
}
|
|
58066
58243
|
/**
|
|
58067
58244
|
* Sanitize HTML content to prevent XSS attacks
|
|
@@ -58238,6 +58415,13 @@ var InputSanitizer = class {
|
|
|
58238
58415
|
"th",
|
|
58239
58416
|
"figure",
|
|
58240
58417
|
"figcaption",
|
|
58418
|
+
// Definition lists and disclosure widgets (#177): unwrapped, their
|
|
58419
|
+
// text ran together ("TermDefinition", "MoreHidden").
|
|
58420
|
+
"dl",
|
|
58421
|
+
"dt",
|
|
58422
|
+
"dd",
|
|
58423
|
+
"details",
|
|
58424
|
+
"summary",
|
|
58241
58425
|
"a",
|
|
58242
58426
|
"img",
|
|
58243
58427
|
"sup",
|
|
@@ -58942,6 +59126,12 @@ h${level}. ${trimmed}
|
|
|
58942
59126
|
}
|
|
58943
59127
|
|
|
58944
59128
|
// ../../src/shared/conversion-engine.ts
|
|
59129
|
+
var TableMarkdownLimitError = class extends Error {
|
|
59130
|
+
constructor() {
|
|
59131
|
+
super("Table Markdown exceeds the conversion limit");
|
|
59132
|
+
this.name = "TableMarkdownLimitError";
|
|
59133
|
+
}
|
|
59134
|
+
};
|
|
58945
59135
|
var ConversionEngine = class {
|
|
58946
59136
|
githubService;
|
|
58947
59137
|
commonmarkService;
|
|
@@ -58953,6 +59143,10 @@ var ConversionEngine = class {
|
|
|
58953
59143
|
confluenceService;
|
|
58954
59144
|
dom;
|
|
58955
59145
|
domSanitizer;
|
|
59146
|
+
/** Scoped to one synchronous Turndown pass; cloned cells do not change while table rules read them. */
|
|
59147
|
+
tableCellCache;
|
|
59148
|
+
/** Table Markdown characters this conversion may still emit (maxTableMarkdownChars). */
|
|
59149
|
+
tableMarkdownBudget = Number.POSITIVE_INFINITY;
|
|
58956
59150
|
constructor(options = {}) {
|
|
58957
59151
|
this.dom = options.dom;
|
|
58958
59152
|
this.initializeServices();
|
|
@@ -58967,7 +59161,7 @@ var ConversionEngine = class {
|
|
|
58967
59161
|
this.slackService,
|
|
58968
59162
|
this.confluenceService
|
|
58969
59163
|
]) {
|
|
58970
|
-
this.parseWithInjectedDom(service, this.dom);
|
|
59164
|
+
this.parseWithInjectedDom(service, this.dom, service === this.githubService);
|
|
58971
59165
|
}
|
|
58972
59166
|
}
|
|
58973
59167
|
}
|
|
@@ -58978,7 +59172,7 @@ var ConversionEngine = class {
|
|
|
58978
59172
|
* passes the element, which Turndown accepts in place of a string. Covers
|
|
58979
59173
|
* the nested `service.turndown(el.outerHTML)` calls in the list rules too.
|
|
58980
59174
|
*/
|
|
58981
|
-
parseWithInjectedDom(service, dom) {
|
|
59175
|
+
parseWithInjectedDom(service, dom, batchParagraphs) {
|
|
58982
59176
|
const turndown = service.turndown.bind(service);
|
|
58983
59177
|
service.turndown = (input) => {
|
|
58984
59178
|
if (typeof input !== "string") return turndown(input);
|
|
@@ -58989,9 +59183,64 @@ var ConversionEngine = class {
|
|
|
58989
59183
|
const root = doc.getElementById("turndown-root");
|
|
58990
59184
|
root?.normalize();
|
|
58991
59185
|
if (!root) throw new Error("Injected DOM did not produce the turndown root element");
|
|
58992
|
-
|
|
59186
|
+
if (batchParagraphs) {
|
|
59187
|
+
this.batchParagraphSiblings(root);
|
|
59188
|
+
this.batchTableRows(root);
|
|
59189
|
+
}
|
|
59190
|
+
return turndown(this.ownedDomInput(root));
|
|
58993
59191
|
};
|
|
58994
59192
|
}
|
|
59193
|
+
/** Turndown consumes a fresh private tree synchronously; caller-owned DOM never uses this adapter. */
|
|
59194
|
+
ownedDomInput(root) {
|
|
59195
|
+
return { nodeType: 1, cloneNode: () => root };
|
|
59196
|
+
}
|
|
59197
|
+
/**
|
|
59198
|
+
* GFM paragraphs and generic divs both contribute two separating newlines. Group long runs in the
|
|
59199
|
+
* private parsed DOM so Turndown joins a few hundred outputs at a time, instead of flattening a growing
|
|
59200
|
+
* string tens of thousands of times (#180). Keep DOM-sensitive table/list/quote/pre rules untouched.
|
|
59201
|
+
*/
|
|
59202
|
+
batchParagraphSiblings(root) {
|
|
59203
|
+
const containers = [root, ...Array.from(root.querySelectorAll("body,main,article,section,div"))];
|
|
59204
|
+
for (const container of containers) {
|
|
59205
|
+
if (container.childNodes.length < 512 || container.closest("table,ul,ol,pre,blockquote")) continue;
|
|
59206
|
+
const children = Array.from(container.childNodes);
|
|
59207
|
+
if (!children.every((child) => child.nodeType === 1 ? child.nodeName === "P" : child.nodeType === 8 || child.nodeType === 3 && !child.textContent?.trim())) continue;
|
|
59208
|
+
let group;
|
|
59209
|
+
let count = 0;
|
|
59210
|
+
for (const child of children) {
|
|
59211
|
+
if (!group) {
|
|
59212
|
+
group = container.ownerDocument.createElement("div");
|
|
59213
|
+
container.insertBefore(group, child);
|
|
59214
|
+
}
|
|
59215
|
+
group.appendChild(child);
|
|
59216
|
+
if (child.nodeType === 1 && ++count === 256) {
|
|
59217
|
+
group = void 0;
|
|
59218
|
+
count = 0;
|
|
59219
|
+
}
|
|
59220
|
+
}
|
|
59221
|
+
}
|
|
59222
|
+
}
|
|
59223
|
+
/** The table rule discards preliminary row Markdown and queries all descendant rows itself. */
|
|
59224
|
+
batchTableRows(root) {
|
|
59225
|
+
for (const container of Array.from(root.querySelectorAll("table,thead,tbody,tfoot"))) {
|
|
59226
|
+
if (container.childNodes.length < 512) continue;
|
|
59227
|
+
const children = Array.from(container.childNodes);
|
|
59228
|
+
if (!children.every((child) => child.nodeType === 1 ? child.nodeName === "TR" : child.nodeType === 8 || child.nodeType === 3 && !child.textContent?.trim())) continue;
|
|
59229
|
+
let group;
|
|
59230
|
+
let count = 0;
|
|
59231
|
+
for (const child of children) {
|
|
59232
|
+
if (!group) {
|
|
59233
|
+
group = container.ownerDocument.createElement("tbody");
|
|
59234
|
+
container.insertBefore(group, child);
|
|
59235
|
+
}
|
|
59236
|
+
group.appendChild(child);
|
|
59237
|
+
if (child.nodeType === 1 && ++count === 256) {
|
|
59238
|
+
group = void 0;
|
|
59239
|
+
count = 0;
|
|
59240
|
+
}
|
|
59241
|
+
}
|
|
59242
|
+
}
|
|
59243
|
+
}
|
|
58995
59244
|
initializeServices() {
|
|
58996
59245
|
this.githubService = new import_turndown2.default({
|
|
58997
59246
|
headingStyle: "atx",
|
|
@@ -59063,7 +59312,21 @@ var ConversionEngine = class {
|
|
|
59063
59312
|
this.rmarkdownService,
|
|
59064
59313
|
this.slackService,
|
|
59065
59314
|
this.confluenceService
|
|
59066
|
-
].forEach((service) =>
|
|
59315
|
+
].forEach((service) => {
|
|
59316
|
+
this.addGoogleDocsWrapperRule(service);
|
|
59317
|
+
this.addDisclosureBlockRule(service);
|
|
59318
|
+
});
|
|
59319
|
+
}
|
|
59320
|
+
/**
|
|
59321
|
+
* Turndown's block list has no DETAILS or SUMMARY, so it treated them as
|
|
59322
|
+
* inline and `<details><summary>More</summary>Hidden</details>` came out as
|
|
59323
|
+
* "MoreHidden". Give them block spacing like a <div> (#177).
|
|
59324
|
+
*/
|
|
59325
|
+
addDisclosureBlockRule(service) {
|
|
59326
|
+
service.addRule("disclosureBlock", {
|
|
59327
|
+
filter: ["details", "summary"],
|
|
59328
|
+
replacement: (content) => "\n\n" + content + "\n\n"
|
|
59329
|
+
});
|
|
59067
59330
|
}
|
|
59068
59331
|
/**
|
|
59069
59332
|
* Google Docs wraps every copied selection in
|
|
@@ -59627,10 +59890,22 @@ ${plainText}
|
|
|
59627
59890
|
if (rows.length === 0) return content;
|
|
59628
59891
|
let markdown = "\n";
|
|
59629
59892
|
let headerProcessed = false;
|
|
59893
|
+
let tableLength = 0;
|
|
59630
59894
|
rows.forEach((row, index) => {
|
|
59631
59895
|
const cells = Array.from(row.querySelectorAll("th, td"));
|
|
59632
|
-
const cellContents = cells.map((cell) =>
|
|
59896
|
+
const cellContents = cells.map((cell) => {
|
|
59897
|
+
let rendered = this.tableCellCache?.get(cell);
|
|
59898
|
+
if (rendered === void 0) {
|
|
59899
|
+
rendered = this.escapeTableCell(this.renderTableCell(cell));
|
|
59900
|
+
this.chargeTableMarkdown(rendered.length);
|
|
59901
|
+
this.tableCellCache?.set(cell, rendered);
|
|
59902
|
+
}
|
|
59903
|
+
return rendered;
|
|
59904
|
+
});
|
|
59633
59905
|
if (cellContents.length > 0) {
|
|
59906
|
+
tableLength += 1 + 3 * cellContents.length;
|
|
59907
|
+
for (const value of cellContents) tableLength += value.length;
|
|
59908
|
+
if (tableLength > this.tableMarkdownBudget) throw new TableMarkdownLimitError();
|
|
59634
59909
|
markdown += "| " + cellContents.join(" | ") + " |\n";
|
|
59635
59910
|
if (!headerProcessed && (row.querySelector("th") || index === 0)) {
|
|
59636
59911
|
const separator = cellContents.map(() => "---").join(" | ");
|
|
@@ -59643,6 +59918,11 @@ ${plainText}
|
|
|
59643
59918
|
}
|
|
59644
59919
|
});
|
|
59645
59920
|
}
|
|
59921
|
+
/** Deduct a rendered cell, which the per-conversion cache keeps alive until the conversion ends. */
|
|
59922
|
+
chargeTableMarkdown(chars) {
|
|
59923
|
+
this.tableMarkdownBudget -= chars;
|
|
59924
|
+
if (this.tableMarkdownBudget < 0) throw new TableMarkdownLimitError();
|
|
59925
|
+
}
|
|
59646
59926
|
renderTableCell(cell) {
|
|
59647
59927
|
const ELEMENT_NODE2 = 1;
|
|
59648
59928
|
const blocks = [];
|
|
@@ -59700,8 +59980,17 @@ ${plainText}
|
|
|
59700
59980
|
}
|
|
59701
59981
|
renderCodeSpan(content) {
|
|
59702
59982
|
if (!content.includes("`")) return `\`${content}\``;
|
|
59703
|
-
|
|
59704
|
-
|
|
59983
|
+
let longest = 0;
|
|
59984
|
+
let current = 0;
|
|
59985
|
+
for (let i = 0; i < content.length; i++) {
|
|
59986
|
+
if (content.charCodeAt(i) === 96) {
|
|
59987
|
+
current++;
|
|
59988
|
+
if (current > longest) longest = current;
|
|
59989
|
+
} else {
|
|
59990
|
+
current = 0;
|
|
59991
|
+
}
|
|
59992
|
+
}
|
|
59993
|
+
const delimiter = "`".repeat(longest + 1);
|
|
59705
59994
|
return `${delimiter} ${content} ${delimiter}`;
|
|
59706
59995
|
}
|
|
59707
59996
|
escapeMarkdownLinkText(text) {
|
|
@@ -59711,7 +60000,8 @@ ${plainText}
|
|
|
59711
60000
|
return href.replace(/</g, "%3C").replace(/>/g, "%3E");
|
|
59712
60001
|
}
|
|
59713
60002
|
escapeTableCell(content) {
|
|
59714
|
-
|
|
60003
|
+
const escaped = content.replace(/\|/g, "\\|");
|
|
60004
|
+
return escaped.includes("<br>") ? escaped.split("<br>").map((part) => part.trim()).join("<br>") : escaped.trim();
|
|
59715
60005
|
}
|
|
59716
60006
|
/** A space after a checkbox marker unless the label text already starts with one. */
|
|
59717
60007
|
checkboxSpacer(node) {
|
|
@@ -59766,6 +60056,12 @@ ${plainText}
|
|
|
59766
60056
|
});
|
|
59767
60057
|
}
|
|
59768
60058
|
addListRules(service) {
|
|
60059
|
+
if (this.dom && service === this.githubService) {
|
|
60060
|
+
service.addRule("discardPreliminaryListItems", {
|
|
60061
|
+
filter: (node) => node.nodeName === "LI" && (node.parentNode?.nodeName === "UL" || node.parentNode?.nodeName === "OL"),
|
|
60062
|
+
replacement: () => ""
|
|
60063
|
+
});
|
|
60064
|
+
}
|
|
59769
60065
|
service.addRule("enhancedList", {
|
|
59770
60066
|
filter: ["ul", "ol"],
|
|
59771
60067
|
replacement: (content, node) => {
|
|
@@ -59792,10 +60088,12 @@ ${plainText}
|
|
|
59792
60088
|
let markdown = "";
|
|
59793
60089
|
items.forEach((item, index) => {
|
|
59794
60090
|
const marker = isOrdered ? `${index + 1}.` : bullet;
|
|
59795
|
-
const indent = " ".repeat(marker.length + 1);
|
|
59796
60091
|
const body = this.processListItem(item, service);
|
|
59797
|
-
|
|
59798
|
-
|
|
60092
|
+
let indented = body;
|
|
60093
|
+
if (body.includes("\n")) {
|
|
60094
|
+
const indent = " ".repeat(marker.length + 1);
|
|
60095
|
+
indented = body.split("\n").map((line, i) => i === 0 || line === "" ? line : indent + line).join("\n");
|
|
60096
|
+
}
|
|
59799
60097
|
markdown += `${marker} ${indented}
|
|
59800
60098
|
`;
|
|
59801
60099
|
});
|
|
@@ -59808,13 +60106,21 @@ ${plainText}
|
|
|
59808
60106
|
* multiple paragraphs stay separated.
|
|
59809
60107
|
*/
|
|
59810
60108
|
processListItem(item, service) {
|
|
60109
|
+
if (this.dom && service === this.githubService && service.options.linkStyle !== "referenced") {
|
|
60110
|
+
const child = item.firstChild;
|
|
60111
|
+
if (child?.nodeType === 3 && child.nextSibling === null) {
|
|
60112
|
+
const text = child.textContent ?? "";
|
|
60113
|
+
if (!/\s/.test(text)) return service.escape(text).trim();
|
|
60114
|
+
}
|
|
60115
|
+
}
|
|
59811
60116
|
const parts = [];
|
|
59812
60117
|
const ELEMENT_NODE2 = 1;
|
|
59813
60118
|
const doc = item.ownerDocument;
|
|
59814
60119
|
let buffer = doc.createElement("div");
|
|
59815
60120
|
const flush = () => {
|
|
59816
60121
|
if (buffer.childNodes.length > 0) {
|
|
59817
|
-
const
|
|
60122
|
+
const input = this.dom && service === this.githubService ? this.ownedDomInput(buffer) : buffer;
|
|
60123
|
+
const md = service.turndown(input).trim();
|
|
59818
60124
|
if (md) parts.push(md);
|
|
59819
60125
|
}
|
|
59820
60126
|
buffer = doc.createElement("div");
|
|
@@ -59936,7 +60242,17 @@ ${plainText}
|
|
|
59936
60242
|
break;
|
|
59937
60243
|
}
|
|
59938
60244
|
const htmlForConversion = options.preserveTables ? sanitizedHtml : this.flattenTables(sanitizedHtml);
|
|
59939
|
-
|
|
60245
|
+
const previousCache = this.tableCellCache;
|
|
60246
|
+
const previousBudget = this.tableMarkdownBudget;
|
|
60247
|
+
this.tableCellCache = /* @__PURE__ */ new WeakMap();
|
|
60248
|
+
this.tableMarkdownBudget = options.maxTableMarkdownChars ?? Number.POSITIVE_INFINITY;
|
|
60249
|
+
let markdown;
|
|
60250
|
+
try {
|
|
60251
|
+
markdown = service.turndown(htmlForConversion);
|
|
60252
|
+
} finally {
|
|
60253
|
+
this.tableCellCache = previousCache;
|
|
60254
|
+
this.tableMarkdownBudget = previousBudget;
|
|
60255
|
+
}
|
|
59940
60256
|
markdown = this.applyFormatPostProcessing(markdown, options.format);
|
|
59941
60257
|
const endTime = performance.now();
|
|
59942
60258
|
const metadata = {
|
|
@@ -63696,118 +64012,6 @@ function buildCachedProvider(config, lastValidated) {
|
|
|
63696
64012
|
return new CachedEntitlementProvider(cachedState);
|
|
63697
64013
|
}
|
|
63698
64014
|
|
|
63699
|
-
// ../../src/shared/linkedom-sanitizer.ts
|
|
63700
|
-
var FORBID_CONTENTS = /* @__PURE__ */ new Set([
|
|
63701
|
-
"annotation-xml",
|
|
63702
|
-
"audio",
|
|
63703
|
-
"colgroup",
|
|
63704
|
-
"desc",
|
|
63705
|
-
"foreignobject",
|
|
63706
|
-
"head",
|
|
63707
|
-
"iframe",
|
|
63708
|
-
"math",
|
|
63709
|
-
"mi",
|
|
63710
|
-
"mn",
|
|
63711
|
-
"mo",
|
|
63712
|
-
"ms",
|
|
63713
|
-
"mtext",
|
|
63714
|
-
"noembed",
|
|
63715
|
-
"noframes",
|
|
63716
|
-
"noscript",
|
|
63717
|
-
"plaintext",
|
|
63718
|
-
"script",
|
|
63719
|
-
"selectedcontent",
|
|
63720
|
-
"style",
|
|
63721
|
-
"svg",
|
|
63722
|
-
"template",
|
|
63723
|
-
"thead",
|
|
63724
|
-
"title",
|
|
63725
|
-
"video",
|
|
63726
|
-
"xmp"
|
|
63727
|
-
]);
|
|
63728
|
-
var DATA_URI_TAGS = /* @__PURE__ */ new Set(["audio", "video", "img", "source", "image", "track"]);
|
|
63729
|
-
var URI_SAFE_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
63730
|
-
"alt",
|
|
63731
|
-
"class",
|
|
63732
|
-
"for",
|
|
63733
|
-
"id",
|
|
63734
|
-
"label",
|
|
63735
|
-
"name",
|
|
63736
|
-
"pattern",
|
|
63737
|
-
"placeholder",
|
|
63738
|
-
"role",
|
|
63739
|
-
"summary",
|
|
63740
|
-
"title",
|
|
63741
|
-
"value",
|
|
63742
|
-
"style",
|
|
63743
|
-
"xmlns"
|
|
63744
|
-
]);
|
|
63745
|
-
var IS_ALLOWED_URI = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i;
|
|
63746
|
-
var ATTR_WHITESPACE = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g;
|
|
63747
|
-
var ELEMENT_NODE = 1;
|
|
63748
|
-
var COMMENT_NODE = 8;
|
|
63749
|
-
function toSet(list) {
|
|
63750
|
-
return new Set((list ?? []).map((s) => s.toLowerCase()));
|
|
63751
|
-
}
|
|
63752
|
-
function attrAllowed(name, allowed, wildcards) {
|
|
63753
|
-
return allowed.has(name) || wildcards.some((prefix) => name.startsWith(prefix));
|
|
63754
|
-
}
|
|
63755
|
-
function isSafeValue(tag, name, value) {
|
|
63756
|
-
if (URI_SAFE_ATTRIBUTES.has(name) || !value) return true;
|
|
63757
|
-
if (IS_ALLOWED_URI.test(value.replace(ATTR_WHITESPACE, ""))) return true;
|
|
63758
|
-
return (name === "src" || name === "href" || name === "xlink:href") && value.startsWith("data:") && DATA_URI_TAGS.has(tag);
|
|
63759
|
-
}
|
|
63760
|
-
function sanitizeWithLinkedom(html, config, parse) {
|
|
63761
|
-
if (!html) return "";
|
|
63762
|
-
const allowedTags = toSet(config.ALLOWED_TAGS);
|
|
63763
|
-
const forbidTags = toSet(config.FORBID_TAGS);
|
|
63764
|
-
const allowedAttrList = (config.ALLOWED_ATTR ?? []).map((s) => s.toLowerCase());
|
|
63765
|
-
const allowedAttrs = new Set(allowedAttrList.filter((a) => !a.endsWith("*")));
|
|
63766
|
-
const attrWildcards = allowedAttrList.filter((a) => a.endsWith("*")).map((a) => a.slice(0, -1));
|
|
63767
|
-
const forbidAttrs = toSet(config.FORBID_ATTR);
|
|
63768
|
-
const doc = parse(`<!DOCTYPE html><html><head></head><body>${html}</body></html>`);
|
|
63769
|
-
const body = doc.body;
|
|
63770
|
-
if (!body) return "";
|
|
63771
|
-
const toUnwrap = [];
|
|
63772
|
-
const stack = [body];
|
|
63773
|
-
while (stack.length > 0) {
|
|
63774
|
-
const parent = stack.pop();
|
|
63775
|
-
for (const child of Array.from(parent.childNodes)) {
|
|
63776
|
-
if (child.nodeType === COMMENT_NODE) {
|
|
63777
|
-
parent.removeChild(child);
|
|
63778
|
-
continue;
|
|
63779
|
-
}
|
|
63780
|
-
if (child.nodeType !== ELEMENT_NODE) continue;
|
|
63781
|
-
const el = child;
|
|
63782
|
-
const tag = el.nodeName.toLowerCase();
|
|
63783
|
-
if (!allowedTags.has(tag) || forbidTags.has(tag)) {
|
|
63784
|
-
if (FORBID_CONTENTS.has(tag)) {
|
|
63785
|
-
parent.removeChild(el);
|
|
63786
|
-
} else {
|
|
63787
|
-
toUnwrap.push(el);
|
|
63788
|
-
stack.push(el);
|
|
63789
|
-
}
|
|
63790
|
-
continue;
|
|
63791
|
-
}
|
|
63792
|
-
for (const attr of Array.from(el.attributes)) {
|
|
63793
|
-
const name = attr.name.toLowerCase();
|
|
63794
|
-
if (name.startsWith("on") || forbidAttrs.has(name) || !attrAllowed(name, allowedAttrs, attrWildcards) || !isSafeValue(tag, name, attr.value)) {
|
|
63795
|
-
el.removeAttribute(attr.name);
|
|
63796
|
-
}
|
|
63797
|
-
}
|
|
63798
|
-
stack.push(el);
|
|
63799
|
-
}
|
|
63800
|
-
}
|
|
63801
|
-
for (let i = toUnwrap.length - 1; i >= 0; i--) {
|
|
63802
|
-
const el = toUnwrap[i];
|
|
63803
|
-
const parent = el.parentNode;
|
|
63804
|
-
if (!parent) continue;
|
|
63805
|
-
while (el.firstChild) parent.insertBefore(el.firstChild, el);
|
|
63806
|
-
parent.removeChild(el);
|
|
63807
|
-
}
|
|
63808
|
-
return body.innerHTML;
|
|
63809
|
-
}
|
|
63810
|
-
|
|
63811
64015
|
// ../../src/shared/token-estimator.ts
|
|
63812
64016
|
var CJK_REGEX3 = /[\u4e00-\u9fff\u3400-\u4dbf\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/g;
|
|
63813
64017
|
var MAX_INPUT_LENGTH = 1e6;
|
|
@@ -64272,7 +64476,7 @@ function readStdin() {
|
|
|
64272
64476
|
}
|
|
64273
64477
|
async function htmlToMarkdown(html, format, applyAtlassianNormalization = false) {
|
|
64274
64478
|
const processedHtml = applyAtlassianNormalization ? normalizeAtlassianHTML(html) : html;
|
|
64275
|
-
const engine = new ConversionEngine();
|
|
64479
|
+
const engine = new ConversionEngine({ dom: linkedomDomEnvironment() });
|
|
64276
64480
|
const result = await engine.convert(processedHtml, {
|
|
64277
64481
|
format,
|
|
64278
64482
|
preserveTables: true,
|
|
@@ -64286,12 +64490,11 @@ async function binaryToMarkdown(buffer, type, format) {
|
|
|
64286
64490
|
buffer.byteOffset,
|
|
64287
64491
|
buffer.byteOffset + buffer.byteLength
|
|
64288
64492
|
);
|
|
64289
|
-
const engine = new ConversionEngine();
|
|
64493
|
+
const engine = new ConversionEngine({ dom: linkedomDomEnvironment() });
|
|
64290
64494
|
switch (type) {
|
|
64291
64495
|
case "docx": {
|
|
64292
64496
|
const result = await docxAdapter.parseToHtml(buffer);
|
|
64293
|
-
const
|
|
64294
|
-
const converted = await engine.convert(sanitized, {
|
|
64497
|
+
const converted = await engine.convert(result.html, {
|
|
64295
64498
|
format,
|
|
64296
64499
|
preserveTables: true,
|
|
64297
64500
|
preserveLists: true,
|
|
@@ -64301,8 +64504,7 @@ async function binaryToMarkdown(buffer, type, format) {
|
|
|
64301
64504
|
}
|
|
64302
64505
|
case "pdf": {
|
|
64303
64506
|
const result = await pdfAdapter.parseToText(toArrayBuffer());
|
|
64304
|
-
const
|
|
64305
|
-
const converted = await engine.convert(sanitized, {
|
|
64507
|
+
const converted = await engine.convert(result.html, {
|
|
64306
64508
|
format,
|
|
64307
64509
|
preserveTables: true,
|
|
64308
64510
|
preserveLists: true,
|
|
@@ -64313,8 +64515,7 @@ async function binaryToMarkdown(buffer, type, format) {
|
|
|
64313
64515
|
case "xlsx":
|
|
64314
64516
|
case "csv": {
|
|
64315
64517
|
const result = await spreadsheetAdapter.parseToHtml(toArrayBuffer(), type, 0);
|
|
64316
|
-
const
|
|
64317
|
-
const converted = await engine.convert(sanitized, {
|
|
64518
|
+
const converted = await engine.convert(result.html, {
|
|
64318
64519
|
format,
|
|
64319
64520
|
preserveTables: true,
|
|
64320
64521
|
preserveLists: true,
|
|
@@ -64324,8 +64525,7 @@ async function binaryToMarkdown(buffer, type, format) {
|
|
|
64324
64525
|
}
|
|
64325
64526
|
case "json": {
|
|
64326
64527
|
const result = await dataAdapter.parseJsonToHtml(toArrayBuffer());
|
|
64327
|
-
const
|
|
64328
|
-
const converted = await engine.convert(sanitized, {
|
|
64528
|
+
const converted = await engine.convert(result.html, {
|
|
64329
64529
|
format,
|
|
64330
64530
|
preserveTables: true,
|
|
64331
64531
|
preserveLists: true,
|
|
@@ -64335,8 +64535,7 @@ async function binaryToMarkdown(buffer, type, format) {
|
|
|
64335
64535
|
}
|
|
64336
64536
|
case "xml": {
|
|
64337
64537
|
const result = await dataAdapter.parseXmlToHtml(toArrayBuffer());
|
|
64338
|
-
const
|
|
64339
|
-
const converted = await engine.convert(sanitized, {
|
|
64538
|
+
const converted = await engine.convert(result.html, {
|
|
64340
64539
|
format,
|
|
64341
64540
|
preserveTables: true,
|
|
64342
64541
|
preserveLists: true,
|
|
@@ -64346,8 +64545,7 @@ async function binaryToMarkdown(buffer, type, format) {
|
|
|
64346
64545
|
}
|
|
64347
64546
|
case "pptx": {
|
|
64348
64547
|
const result = await pptxAdapter.parseToHtml(toArrayBuffer());
|
|
64349
|
-
const
|
|
64350
|
-
const converted = await engine.convert(sanitized, {
|
|
64548
|
+
const converted = await engine.convert(result.html, {
|
|
64351
64549
|
format,
|
|
64352
64550
|
preserveTables: true,
|
|
64353
64551
|
preserveLists: true,
|
|
@@ -64359,7 +64557,7 @@ async function binaryToMarkdown(buffer, type, format) {
|
|
|
64359
64557
|
}
|
|
64360
64558
|
function linkedomDomEnvironment() {
|
|
64361
64559
|
const parseHTML2 = globalThis.__FF_parseHTML;
|
|
64362
|
-
if (!parseHTML2) throw new Error("The CLI shim must be loaded before converting
|
|
64560
|
+
if (!parseHTML2) throw new Error("The CLI shim must be loaded before converting");
|
|
64363
64561
|
const parseDocument = (html) => parseHTML2(html).document;
|
|
64364
64562
|
const { window: window3 } = parseHTML2("<!DOCTYPE html><html><head></head><body></body></html>");
|
|
64365
64563
|
return {
|
|
@@ -64567,9 +64765,9 @@ async function convertFile(filePath, format) {
|
|
|
64567
64765
|
const ext = extname2(filePath).toLowerCase();
|
|
64568
64766
|
if (!SUPPORTED_EXTENSIONS.has(ext)) throw new Error(`Unsupported file type: ${ext}`);
|
|
64569
64767
|
const buffer = readFileSync4(filePath);
|
|
64570
|
-
const engine = new ConversionEngine();
|
|
64768
|
+
const engine = new ConversionEngine({ dom: linkedomDomEnvironment() });
|
|
64571
64769
|
if (ext === ".html" || ext === ".htm") {
|
|
64572
|
-
const result = await engine.convert(
|
|
64770
|
+
const result = await engine.convert(buffer.toString("utf8"), {
|
|
64573
64771
|
format,
|
|
64574
64772
|
preserveTables: true,
|
|
64575
64773
|
preserveLists: true,
|
|
@@ -64580,32 +64778,32 @@ async function convertFile(filePath, format) {
|
|
|
64580
64778
|
const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
64581
64779
|
if (ext === ".docx") {
|
|
64582
64780
|
const r = await docxAdapter.parseToHtml(arrayBuffer);
|
|
64583
|
-
const c = await engine.convert(
|
|
64781
|
+
const c = await engine.convert(r.html, { format, preserveTables: true, preserveLists: true, customRules: [] });
|
|
64584
64782
|
return { markdown: c.markdown, warnings: r.warnings ?? [] };
|
|
64585
64783
|
}
|
|
64586
64784
|
if (ext === ".pdf") {
|
|
64587
64785
|
const r = await pdfAdapter.parseToText(arrayBuffer);
|
|
64588
|
-
const c = await engine.convert(
|
|
64786
|
+
const c = await engine.convert(r.html, { format, preserveTables: true, preserveLists: true, customRules: [] });
|
|
64589
64787
|
return { markdown: c.markdown, warnings: r.warnings ?? [] };
|
|
64590
64788
|
}
|
|
64591
64789
|
if (ext === ".xlsx" || ext === ".csv") {
|
|
64592
64790
|
const r = await spreadsheetAdapter.parseToHtml(arrayBuffer, ext.slice(1), 0);
|
|
64593
|
-
const c = await engine.convert(
|
|
64791
|
+
const c = await engine.convert(r.html, { format, preserveTables: true, preserveLists: true, customRules: [] });
|
|
64594
64792
|
return { markdown: c.markdown, warnings: r.warnings ?? [] };
|
|
64595
64793
|
}
|
|
64596
64794
|
if (ext === ".json") {
|
|
64597
64795
|
const r = await dataAdapter.parseJsonToHtml(arrayBuffer);
|
|
64598
|
-
const c = await engine.convert(
|
|
64796
|
+
const c = await engine.convert(r.html, { format, preserveTables: true, preserveLists: true, customRules: [] });
|
|
64599
64797
|
return { markdown: c.markdown, warnings: r.warnings ?? [] };
|
|
64600
64798
|
}
|
|
64601
64799
|
if (ext === ".xml") {
|
|
64602
64800
|
const r = await dataAdapter.parseXmlToHtml(arrayBuffer);
|
|
64603
|
-
const c = await engine.convert(
|
|
64801
|
+
const c = await engine.convert(r.html, { format, preserveTables: true, preserveLists: true, customRules: [] });
|
|
64604
64802
|
return { markdown: c.markdown, warnings: r.warnings ?? [] };
|
|
64605
64803
|
}
|
|
64606
64804
|
if (ext === ".pptx") {
|
|
64607
64805
|
const r = await pptxAdapter.parseToHtml(arrayBuffer);
|
|
64608
|
-
const c = await engine.convert(
|
|
64806
|
+
const c = await engine.convert(r.html, { format, preserveTables: true, preserveLists: true, customRules: [] });
|
|
64609
64807
|
return { markdown: c.markdown, warnings: r.warnings ?? [] };
|
|
64610
64808
|
}
|
|
64611
64809
|
throw new Error(`Unsupported file type: ${ext}`);
|
|
@@ -64909,8 +65107,8 @@ import { readFileSync as readFileSync5 } from "fs";
|
|
|
64909
65107
|
import { resolve, dirname as dirname3 } from "path";
|
|
64910
65108
|
import { fileURLToPath } from "url";
|
|
64911
65109
|
var getVersion = () => {
|
|
64912
|
-
if ("1.0.
|
|
64913
|
-
return "1.0.
|
|
65110
|
+
if ("1.0.41") {
|
|
65111
|
+
return "1.0.41";
|
|
64914
65112
|
}
|
|
64915
65113
|
try {
|
|
64916
65114
|
const __dirname2 = dirname3(fileURLToPath(import.meta.url));
|