@farm.js/core 0.1.24 → 0.1.26
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/agent-runtime.d.mts +2 -2
- package/dist/agent-runtime.d.ts +2 -2
- package/dist/api/route-manager.cjs +4 -4
- package/dist/api/route-manager.d.mts +2 -2
- package/dist/api/route-manager.d.ts +2 -2
- package/dist/api/route-manager.mjs +3 -3
- package/dist/api/runtime.cjs +3 -3
- package/dist/api/runtime.mjs +2 -2
- package/dist/api.cjs +7 -7
- package/dist/api.d.mts +3 -3
- package/dist/api.d.ts +3 -3
- package/dist/api.mjs +6 -6
- package/dist/{app-BZDWwwFa.d.mts → app-29vG5tmY.d.mts} +3 -3
- package/dist/{app-BV-7wV5q.d.ts → app-Cx71s_PT.d.ts} +3 -3
- package/dist/app-markdown.cjs +6 -4
- package/dist/app-markdown.d.mts +3 -3
- package/dist/app-markdown.d.ts +3 -3
- package/dist/app-markdown.mjs +5 -3
- package/dist/{chunk-ISTFC3MZ.cjs → chunk-2CT6XUNO.cjs} +99 -26
- package/dist/{chunk-G6GNNB44.cjs → chunk-3TQG7CGC.cjs} +201 -180
- package/dist/{chunk-KNPRCKXK.cjs → chunk-3TQWUUNP.cjs} +9 -7
- package/dist/{chunk-LYM7HY34.cjs → chunk-3WSWXPYM.cjs} +4 -4
- package/dist/{chunk-GZ3ZPTQV.mjs → chunk-52SH7TKB.mjs} +2 -2
- package/dist/{chunk-JIKGGIOL.mjs → chunk-6D242YGO.mjs} +1 -1
- package/dist/{chunk-MVUREHPN.mjs → chunk-74RZNRY6.mjs} +1 -1
- package/dist/{chunk-BD7FZ63M.cjs → chunk-ALIFMS4A.cjs} +1 -1
- package/dist/{chunk-PGQBL5DN.mjs → chunk-ARXCHQLX.mjs} +79 -2
- package/dist/{chunk-2QLBIPFT.cjs → chunk-B6ZCT36X.cjs} +4 -4
- package/dist/{chunk-IMBMOEWT.cjs → chunk-BD2U4ZL6.cjs} +8 -8
- package/dist/{chunk-2M435BUE.mjs → chunk-CEYUWDWA.mjs} +7 -5
- package/dist/{chunk-XI3GWPUS.cjs → chunk-CRQPYWLK.cjs} +14 -14
- package/dist/{chunk-47H7VENP.mjs → chunk-DU2CASV6.mjs} +1 -1
- package/dist/{chunk-QTBEAKCJ.cjs → chunk-H7CJRJ4L.cjs} +16 -16
- package/dist/chunk-J63ZH5OQ.cjs +62 -0
- package/dist/{chunk-N255VYFP.mjs → chunk-JM5GQL4H.mjs} +8 -3
- package/dist/{chunk-6VEHUKZ2.mjs → chunk-MM5D6VLP.mjs} +4 -20
- package/dist/chunk-MVL2RAJW.mjs +62 -0
- package/dist/{chunk-YA6LLTIJ.cjs → chunk-ND224OE2.cjs} +4 -20
- package/dist/{chunk-YGMK6ESJ.mjs → chunk-OFKG57MZ.mjs} +2 -2
- package/dist/chunk-OOIL3BWR.mjs +1229 -0
- package/dist/{chunk-BNSD3VDY.mjs → chunk-PPIW3MH6.mjs} +1 -1
- package/dist/{chunk-WNUBR4CA.mjs → chunk-QGG336NI.mjs} +1 -1
- package/dist/chunk-QIH2HD6H.cjs +1229 -0
- package/dist/{chunk-OYDRPBBV.cjs → chunk-QJ4D3UKJ.cjs} +80 -3
- package/dist/{chunk-LQIRVSBY.cjs → chunk-QLXEYIAW.cjs} +13 -13
- package/dist/{chunk-ARHWIOUZ.mjs → chunk-QPPQHB6X.mjs} +111 -40
- package/dist/{chunk-7BC6GAL7.mjs → chunk-RAI7NULA.mjs} +76 -3
- package/dist/{chunk-AH7HOAQ3.cjs → chunk-RCYINC4A.cjs} +9 -4
- package/dist/{chunk-SIQMTHCZ.cjs → chunk-SZ4LYNUX.cjs} +212 -141
- package/dist/{chunk-5UX4C5QI.mjs → chunk-TKDY6AJP.mjs} +1 -1
- package/dist/{chunk-YJJLXIIS.cjs → chunk-U74A3P2P.cjs} +7 -7
- package/dist/{chunk-6YSEHN46.mjs → chunk-UXQXGB5A.mjs} +36 -15
- package/dist/{chunk-72VJRKJD.mjs → chunk-VZWO3XVL.mjs} +2 -2
- package/dist/{chunk-I5YBCWQ6.cjs → chunk-W2HZKYES.cjs} +2 -2
- package/dist/{chunk-DNLQMFER.cjs → chunk-WFPRVVF2.cjs} +26 -19
- package/dist/{chunk-BDJNP7AQ.mjs → chunk-YMUH2BLR.mjs} +10 -3
- package/dist/{client-plugins-B_XzCegf.d.mts → client-plugins-B8RPJnot.d.mts} +129 -4
- package/dist/{client-plugins-CKsXQfXY.d.ts → client-plugins-bPBf4haK.d.ts} +129 -4
- package/dist/client-plugins.d.mts +2 -2
- package/dist/client-plugins.d.ts +2 -2
- package/dist/client.cjs +4 -4
- package/dist/client.d.mts +6 -6
- package/dist/client.d.ts +6 -6
- package/dist/client.mjs +3 -3
- package/dist/config.d.mts +2 -2
- package/dist/config.d.ts +2 -2
- package/dist/{devtools-OY4SNU3P.mjs → devtools-5D4ZDRPR.mjs} +5 -3
- package/dist/{devtools-UHRSZ7VA.cjs → devtools-QZJEUOJW.cjs} +6 -4
- package/dist/docs.cjs +8 -6
- package/dist/docs.mjs +7 -5
- package/dist/index.cjs +29 -23
- package/dist/index.d.mts +14 -14
- package/dist/index.d.ts +14 -14
- package/dist/index.mjs +28 -22
- package/dist/integrations.d.mts +2 -2
- package/dist/integrations.d.ts +2 -2
- package/dist/internal/app-markdown-runtime.cjs +5 -3
- package/dist/internal/app-markdown-runtime.d.mts +2 -2
- package/dist/internal/app-markdown-runtime.d.ts +2 -2
- package/dist/internal/app-markdown-runtime.mjs +4 -2
- package/dist/internal/build-runtime.cjs +18 -17
- package/dist/internal/build-runtime.d.mts +4 -4
- package/dist/internal/build-runtime.d.ts +4 -4
- package/dist/internal/build-runtime.mjs +17 -16
- package/dist/internal/cli-runtime.cjs +6 -5
- package/dist/internal/cli-runtime.mjs +5 -4
- package/dist/internal/client-error-overlay.cjs +4 -4
- package/dist/internal/client-error-overlay.mjs +1 -1
- package/dist/internal/compression-runtime.d.mts +2 -2
- package/dist/internal/compression-runtime.d.ts +2 -2
- package/dist/internal/config-runtime.cjs +6 -4
- package/dist/internal/config-runtime.d.mts +3 -3
- package/dist/internal/config-runtime.d.ts +3 -3
- package/dist/internal/config-runtime.mjs +5 -3
- package/dist/internal/infra-analytics-runtime.cjs +4 -3
- package/dist/internal/infra-analytics-runtime.mjs +2 -1
- package/dist/internal/product-telemetry-runtime.cjs +2 -2
- package/dist/internal/product-telemetry-runtime.mjs +1 -1
- package/dist/internal/production-runtime.cjs +12 -5
- package/dist/internal/production-runtime.d.mts +33 -9
- package/dist/internal/production-runtime.d.ts +33 -9
- package/dist/internal/production-runtime.mjs +11 -4
- package/dist/markdown.cjs +6 -2
- package/dist/markdown.d.mts +63 -1
- package/dist/markdown.d.ts +63 -1
- package/dist/markdown.mjs +5 -1
- package/dist/{metadata-B08w4EYV.d.mts → metadata-C3na4CBj.d.mts} +1 -1
- package/dist/{metadata-IQEblkZp.d.ts → metadata-CzBP4Mfp.d.ts} +1 -1
- package/dist/middleware.d.mts +4 -4
- package/dist/middleware.d.ts +4 -4
- package/dist/navigation.cjs +5 -5
- package/dist/navigation.d.mts +5 -5
- package/dist/navigation.d.ts +5 -5
- package/dist/navigation.mjs +4 -4
- package/dist/observability-BupkiN0m.d.mts +447 -0
- package/dist/observability-BupkiN0m.d.ts +447 -0
- package/dist/observability.d.mts +2 -417
- package/dist/observability.d.ts +2 -417
- package/dist/plugin.cjs +13 -12
- package/dist/plugin.d.mts +2 -2
- package/dist/plugin.d.ts +2 -2
- package/dist/plugin.mjs +12 -11
- package/dist/{production-runtime-DRSOkc-d.d.ts → production-runtime-CYg4wM4E.d.ts} +1 -1
- package/dist/{production-runtime-C3bGFEaM.d.mts → production-runtime-DP94hPGe.d.mts} +1 -1
- package/dist/query/client.cjs +6 -6
- package/dist/query/client.mjs +2 -2
- package/dist/{render-plan-CCFuMLNS.d.ts → render-plan-Bl6Fzg5z.d.ts} +1 -1
- package/dist/{render-plan-B6CKNl4e.d.mts → render-plan-CKAJg0jC.d.mts} +1 -1
- package/dist/renderer-client.cjs +5 -5
- package/dist/renderer-client.d.mts +4 -4
- package/dist/renderer-client.d.ts +4 -4
- package/dist/renderer-client.mjs +2 -2
- package/dist/{request-Dn6VURbE.d.ts → request-CR-Bp1Px.d.ts} +1 -1
- package/dist/{request-DgD1XsTx.d.mts → request-CrBCcPZZ.d.mts} +1 -1
- package/dist/request.d.mts +3 -3
- package/dist/request.d.ts +3 -3
- package/dist/{route-rendering-76A5I_kE.d.mts → route-rendering-C1NAHo-C.d.mts} +1 -1
- package/dist/{route-rendering-LAmQEdyG.d.ts → route-rendering-CZH5FegB.d.ts} +1 -1
- package/dist/{router-CnrRSa9d.d.mts → router-B57eDJ3u.d.mts} +1 -1
- package/dist/{router-BSOOGya2.d.ts → router-Cg_kD3Bd.d.ts} +1 -1
- package/dist/routes.d.mts +2 -2
- package/dist/routes.d.ts +2 -2
- package/dist/server-plugins.d.mts +3 -3
- package/dist/server-plugins.d.ts +3 -3
- package/dist/server.cjs +163 -106
- package/dist/server.d.mts +8 -8
- package/dist/server.d.ts +8 -8
- package/dist/server.mjs +144 -87
- package/dist/{spa-router-BMrSsG5b.d.ts → spa-router-BstQUO6Q.d.ts} +2 -2
- package/dist/{spa-router-DnYhWCvI.d.mts → spa-router-DTdbuinx.d.mts} +2 -2
- package/dist/testing.cjs +4 -4
- package/dist/testing.d.mts +2 -2
- package/dist/testing.d.ts +2 -2
- package/dist/testing.mjs +3 -3
- package/dist/{utils-ABkktwku.d.mts → utils-CS3ZLnEJ.d.mts} +1 -1
- package/dist/{utils-C3GguS-Q.d.ts → utils-D3rdsIvM.d.ts} +1 -1
- package/dist/version.cjs +2 -2
- package/dist/version.mjs +1 -1
- package/dist/vite.cjs +13 -12
- package/dist/vite.d.mts +2 -2
- package/dist/vite.d.ts +2 -2
- package/dist/vite.mjs +12 -11
- package/package.json +1 -1
- package/dist/chunk-BBVSJVBZ.cjs +0 -600
- package/dist/chunk-OFEY6KHO.mjs +0 -600
- package/dist/{chunk-XP777LRD.mjs → chunk-GDUYXFIS.mjs} +3 -3
- package/dist/{chunk-4PNUXIVU.cjs → chunk-YAOX43YI.cjs} +3 -3
|
@@ -0,0 +1,1229 @@
|
|
|
1
|
+
import {
|
|
2
|
+
emitFarmAgentRead
|
|
3
|
+
} from "./chunk-MVL2RAJW.mjs";
|
|
4
|
+
import {
|
|
5
|
+
__name
|
|
6
|
+
} from "./chunk-LPEX4YW6.mjs";
|
|
7
|
+
|
|
8
|
+
// src/markdown-view.ts
|
|
9
|
+
var FENCE = /^\s{0,3}(`{3,}|~{3,})/;
|
|
10
|
+
var ATX_HEADING = /^\s{0,3}(#{1,6})[ \t]+(.+?)[ \t]*#*[ \t]*$/;
|
|
11
|
+
function parseFarmMarkdownHeadings(markdown) {
|
|
12
|
+
const lines = markdown.split("\n");
|
|
13
|
+
const headings = [];
|
|
14
|
+
const used = /* @__PURE__ */ new Map();
|
|
15
|
+
let index = 0;
|
|
16
|
+
if (lines[0]?.trim() === "---") {
|
|
17
|
+
const close = lines.findIndex((line, lineIndex) => lineIndex > 0 && line.trim() === "---");
|
|
18
|
+
if (close > 0) index = close + 1;
|
|
19
|
+
}
|
|
20
|
+
let fence = null;
|
|
21
|
+
for (; index < lines.length; index++) {
|
|
22
|
+
const line = lines[index];
|
|
23
|
+
const fenceMatch = FENCE.exec(line);
|
|
24
|
+
if (fenceMatch) {
|
|
25
|
+
const marker = fenceMatch[1];
|
|
26
|
+
if (fence === null) fence = marker;
|
|
27
|
+
else if (marker[0] === fence[0] && marker.length >= fence.length) fence = null;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (fence !== null) continue;
|
|
31
|
+
const match = ATX_HEADING.exec(line);
|
|
32
|
+
if (!match) continue;
|
|
33
|
+
const text = match[2].trim();
|
|
34
|
+
const base = slugifyHeading(text) || "section";
|
|
35
|
+
const count = used.get(base) ?? 0;
|
|
36
|
+
used.set(base, count + 1);
|
|
37
|
+
headings.push({
|
|
38
|
+
id: count === 0 ? base : `${base}-${count}`,
|
|
39
|
+
level: match[1].length,
|
|
40
|
+
text,
|
|
41
|
+
start: index,
|
|
42
|
+
end: lines.length
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
const open = [];
|
|
46
|
+
for (const heading of headings) {
|
|
47
|
+
while (open.length > 0 && open[open.length - 1].level >= heading.level) {
|
|
48
|
+
open.pop().end = heading.start;
|
|
49
|
+
}
|
|
50
|
+
open.push(heading);
|
|
51
|
+
}
|
|
52
|
+
return headings;
|
|
53
|
+
}
|
|
54
|
+
__name(parseFarmMarkdownHeadings, "parseFarmMarkdownHeadings");
|
|
55
|
+
function slugifyHeading(text) {
|
|
56
|
+
return text.replace(/!?\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/[`*_~]/g, "").toLowerCase().replace(/[^\p{L}\p{N}\s-]/gu, "").trim().replace(/\s/g, "-");
|
|
57
|
+
}
|
|
58
|
+
__name(slugifyHeading, "slugifyHeading");
|
|
59
|
+
function estimateFarmMarkdownTokens(text) {
|
|
60
|
+
return Math.ceil(text.length / 4);
|
|
61
|
+
}
|
|
62
|
+
__name(estimateFarmMarkdownTokens, "estimateFarmMarkdownTokens");
|
|
63
|
+
var FARM_MARKDOWN_MIN_TOKEN_BUDGET = 100;
|
|
64
|
+
var FARM_MARKDOWN_VIEW_PARAMS = [
|
|
65
|
+
"outline",
|
|
66
|
+
"section",
|
|
67
|
+
"actions",
|
|
68
|
+
"max_tokens",
|
|
69
|
+
"cursor"
|
|
70
|
+
];
|
|
71
|
+
function selectFarmMarkdownView(markdown, searchParams, markdownPath) {
|
|
72
|
+
const wantsOutline = searchParams.has("outline");
|
|
73
|
+
const sectionId = searchParams.get("section");
|
|
74
|
+
const budget = searchParams.get("max_tokens");
|
|
75
|
+
if (!wantsOutline && sectionId === null && budget === null)
|
|
76
|
+
return { status: 200, body: markdown };
|
|
77
|
+
const viewUrl = /* @__PURE__ */ __name((view) => {
|
|
78
|
+
const query = new URLSearchParams(searchParams);
|
|
79
|
+
for (const name of FARM_MARKDOWN_VIEW_PARAMS) query.delete(name);
|
|
80
|
+
for (const [name, value] of Object.entries(view)) {
|
|
81
|
+
if (value !== null && value !== void 0) query.set(name, value);
|
|
82
|
+
}
|
|
83
|
+
const search = query.toString();
|
|
84
|
+
return search ? `${markdownPath}?${search}` : markdownPath;
|
|
85
|
+
}, "viewUrl");
|
|
86
|
+
const withinBudget = /* @__PURE__ */ __name((document) => budget === null ? { status: 200, body: document } : selectFarmMarkdownPart(
|
|
87
|
+
document,
|
|
88
|
+
budget,
|
|
89
|
+
searchParams.get("cursor"),
|
|
90
|
+
(cursor) => viewUrl({ section: sectionId, max_tokens: budget, cursor })
|
|
91
|
+
), "withinBudget");
|
|
92
|
+
if (!wantsOutline && sectionId === null) return withinBudget(markdown);
|
|
93
|
+
const lines = markdown.split("\n");
|
|
94
|
+
const headings = parseFarmMarkdownHeadings(markdown);
|
|
95
|
+
const sectionText = /* @__PURE__ */ __name((heading) => lines.slice(heading.start, heading.end).join("\n").trim(), "sectionText");
|
|
96
|
+
if (sectionId !== null) {
|
|
97
|
+
const heading = headings.find((candidate) => candidate.id === sectionId);
|
|
98
|
+
if (heading) return withinBudget(`${sectionText(heading)}
|
|
99
|
+
`);
|
|
100
|
+
const available = headings.map((candidate) => `- \`${candidate.id}\``).join("\n");
|
|
101
|
+
return {
|
|
102
|
+
status: 404,
|
|
103
|
+
body: `# Section not found
|
|
104
|
+
|
|
105
|
+
${markdownCodeSpan(sectionId)} is not a section of ${markdownPath}.
|
|
106
|
+
|
|
107
|
+
` + (available ? `Available sections:
|
|
108
|
+
|
|
109
|
+
${available}
|
|
110
|
+
` : "This page has no sections.\n")
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const minLevel = headings.reduce((min, heading) => Math.min(min, heading.level), 6);
|
|
114
|
+
const entries = headings.map((heading) => {
|
|
115
|
+
const indent = " ".repeat(heading.level - minLevel);
|
|
116
|
+
const tokens = estimateFarmMarkdownTokens(sectionText(heading));
|
|
117
|
+
return `${indent}- [${heading.text}](${viewUrl({ section: heading.id })}) ~${tokens} tokens`;
|
|
118
|
+
});
|
|
119
|
+
return {
|
|
120
|
+
status: 200,
|
|
121
|
+
body: `# Outline of ${markdownPath}
|
|
122
|
+
|
|
123
|
+
About ${estimateFarmMarkdownTokens(markdown)} tokens in total. Request one section with \`?section=<id>\`, or read in parts with \`?max_tokens=<n>\`.
|
|
124
|
+
|
|
125
|
+
` + (entries.length > 0 ? `${entries.join("\n")}
|
|
126
|
+
` : "This page has no headings.\n")
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
__name(selectFarmMarkdownView, "selectFarmMarkdownView");
|
|
130
|
+
function markdownCodeSpan(value) {
|
|
131
|
+
const text = value.replace(/[\p{Cc}\u2028\u2029]+/gu, " ");
|
|
132
|
+
const longestRun = (text.match(/`+/g) ?? []).reduce((max, run) => Math.max(max, run.length), 0);
|
|
133
|
+
const fence = "`".repeat(longestRun + 1);
|
|
134
|
+
const pad = text.startsWith("`") || text.endsWith("`") ? " " : "";
|
|
135
|
+
return `${fence}${pad}${text}${pad}${fence}`;
|
|
136
|
+
}
|
|
137
|
+
__name(markdownCodeSpan, "markdownCodeSpan");
|
|
138
|
+
function selectFarmPageActionsView(markdownPath, actions, headers) {
|
|
139
|
+
headers.set("Content-Type", "application/json; charset=utf-8");
|
|
140
|
+
const page = markdownPath === "/index.md" ? "/" : markdownPath.replace(/\.md$/, "");
|
|
141
|
+
return {
|
|
142
|
+
status: 200,
|
|
143
|
+
body: `${JSON.stringify({ page, markdown: markdownPath, actions }, null, 2)}
|
|
144
|
+
`
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
__name(selectFarmPageActionsView, "selectFarmPageActionsView");
|
|
148
|
+
function selectFarmMarkdownPart(document, rawBudget, rawCursor, partUrl) {
|
|
149
|
+
const budget = /^\d{1,9}$/.test(rawBudget) ? Number(rawBudget) : Number.NaN;
|
|
150
|
+
if (!(budget >= FARM_MARKDOWN_MIN_TOKEN_BUDGET)) {
|
|
151
|
+
return {
|
|
152
|
+
status: 400,
|
|
153
|
+
body: `# Invalid max_tokens
|
|
154
|
+
|
|
155
|
+
\`max_tokens\` must be a whole number of at least ${FARM_MARKDOWN_MIN_TOKEN_BUDGET}.
|
|
156
|
+
`
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
const lines = document.replace(/\n$/, "").split("\n");
|
|
160
|
+
const version = hashFarmMarkdown(document).slice(0, 8);
|
|
161
|
+
let start = 0;
|
|
162
|
+
if (rawCursor !== null) {
|
|
163
|
+
const match = /^(\d{1,9})\.([0-9a-z]+)$/.exec(rawCursor);
|
|
164
|
+
if (!match || Number(match[1]) <= 0 || Number(match[1]) >= lines.length) {
|
|
165
|
+
return {
|
|
166
|
+
status: 400,
|
|
167
|
+
body: `# Invalid cursor
|
|
168
|
+
|
|
169
|
+
Start again at ${partUrl()}.
|
|
170
|
+
`
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
if (match[2] !== version) {
|
|
174
|
+
return {
|
|
175
|
+
status: 409,
|
|
176
|
+
body: `# Page changed
|
|
177
|
+
|
|
178
|
+
The page changed after this cursor was issued. Start again at ${partUrl()}.
|
|
179
|
+
`
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
start = Number(match[1]);
|
|
183
|
+
}
|
|
184
|
+
const fences = readOpenFences(lines);
|
|
185
|
+
const reopen = fences[start];
|
|
186
|
+
const room = budget * 4 - 200;
|
|
187
|
+
let size = reopen ? reopen.opener.length + 1 : 0;
|
|
188
|
+
let end = start;
|
|
189
|
+
let lastBreak = -1;
|
|
190
|
+
while (end < lines.length) {
|
|
191
|
+
const length = lines[end].length + 1;
|
|
192
|
+
if (end > start && size + length > room) break;
|
|
193
|
+
size += length;
|
|
194
|
+
end++;
|
|
195
|
+
if (end < lines.length && isPartBreak(lines, fences, end)) lastBreak = end;
|
|
196
|
+
}
|
|
197
|
+
const head = reopen ? `${reopen.opener}
|
|
198
|
+
` : "";
|
|
199
|
+
if (end >= lines.length) {
|
|
200
|
+
return { status: 200, body: `${head}${lines.slice(start).join("\n")}
|
|
201
|
+
` };
|
|
202
|
+
}
|
|
203
|
+
if (lastBreak > start) end = lastBreak;
|
|
204
|
+
const open = fences[end];
|
|
205
|
+
const part = `${head}${lines.slice(start, end).join("\n").replace(/\n+$/, "")}${open ? `
|
|
206
|
+
${open.closer}` : ""}`;
|
|
207
|
+
const next = partUrl(`${end}.${version}`);
|
|
208
|
+
const remaining = estimateFarmMarkdownTokens(lines.slice(end).join("\n"));
|
|
209
|
+
return {
|
|
210
|
+
status: 200,
|
|
211
|
+
body: `${part}
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
Continue: ${next} (about ${remaining} more tokens)
|
|
216
|
+
`,
|
|
217
|
+
headers: { Link: `<${next}>; rel="next"` }
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
__name(selectFarmMarkdownPart, "selectFarmMarkdownPart");
|
|
221
|
+
function readOpenFences(lines) {
|
|
222
|
+
const open = [];
|
|
223
|
+
let fence;
|
|
224
|
+
let frontMatter = lines[0]?.trim() === "---";
|
|
225
|
+
for (let index = 0; index < lines.length; index++) {
|
|
226
|
+
open.push(frontMatter && index > 0 ? { opener: "", closer: "" } : fence);
|
|
227
|
+
const line = lines[index];
|
|
228
|
+
if (frontMatter) {
|
|
229
|
+
if (index > 0 && line.trim() === "---") frontMatter = false;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
const match = FENCE.exec(line);
|
|
233
|
+
if (!match) continue;
|
|
234
|
+
const marker = match[1];
|
|
235
|
+
if (!fence) fence = { opener: line.trim(), closer: marker, marker };
|
|
236
|
+
else if (marker[0] === fence.marker[0] && marker.length >= fence.marker.length)
|
|
237
|
+
fence = void 0;
|
|
238
|
+
}
|
|
239
|
+
return open;
|
|
240
|
+
}
|
|
241
|
+
__name(readOpenFences, "readOpenFences");
|
|
242
|
+
function isPartBreak(lines, fences, index) {
|
|
243
|
+
if (fences[index]) return false;
|
|
244
|
+
return lines[index - 1].trim() === "" || ATX_HEADING.test(lines[index]);
|
|
245
|
+
}
|
|
246
|
+
__name(isPartBreak, "isPartBreak");
|
|
247
|
+
function readMarkdownViewName(searchParams) {
|
|
248
|
+
if (searchParams.has("actions")) return "actions";
|
|
249
|
+
if (searchParams.has("outline")) return "outline";
|
|
250
|
+
if (searchParams.has("max_tokens")) return "parts";
|
|
251
|
+
return searchParams.has("section") ? "section" : void 0;
|
|
252
|
+
}
|
|
253
|
+
__name(readMarkdownViewName, "readMarkdownViewName");
|
|
254
|
+
function createFarmMarkdownETag(body) {
|
|
255
|
+
return `W/"${hashFarmMarkdown(body)}-${body.length.toString(36)}"`;
|
|
256
|
+
}
|
|
257
|
+
__name(createFarmMarkdownETag, "createFarmMarkdownETag");
|
|
258
|
+
function hashFarmMarkdown(body) {
|
|
259
|
+
let h1 = 3735928559;
|
|
260
|
+
let h2 = 1103547991;
|
|
261
|
+
for (let index = 0; index < body.length; index++) {
|
|
262
|
+
const code = body.charCodeAt(index);
|
|
263
|
+
h1 = Math.imul(h1 ^ code, 2654435761);
|
|
264
|
+
h2 = Math.imul(h2 ^ code, 1597334677);
|
|
265
|
+
}
|
|
266
|
+
h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
|
|
267
|
+
h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
|
|
268
|
+
return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36);
|
|
269
|
+
}
|
|
270
|
+
__name(hashFarmMarkdown, "hashFarmMarkdown");
|
|
271
|
+
function ifNoneMatchIncludes(header, etag) {
|
|
272
|
+
if (!header) return false;
|
|
273
|
+
const opaque = etag.replace(/^W\//, "");
|
|
274
|
+
return header.split(",").map((token) => token.trim()).some((token) => token === "*" || token.replace(/^W\//, "") === opaque);
|
|
275
|
+
}
|
|
276
|
+
__name(ifNoneMatchIncludes, "ifNoneMatchIncludes");
|
|
277
|
+
function createFarmMarkdownViewResponse(options) {
|
|
278
|
+
const url = options.requestUrl ?? new URL(options.request.url);
|
|
279
|
+
const view = url.searchParams.has("actions") ? selectFarmPageActionsView(options.markdownPath, options.actions ?? [], options.headers) : selectFarmMarkdownView(options.markdown, url.searchParams, options.markdownPath);
|
|
280
|
+
const status = view.status === 200 ? options.status : view.status;
|
|
281
|
+
emitFarmAgentRead(options.request, "markdown", status, readMarkdownViewName(url.searchParams));
|
|
282
|
+
for (const [name, value] of Object.entries(view.headers ?? {}))
|
|
283
|
+
options.headers.append(name, value);
|
|
284
|
+
const etag = createFarmMarkdownETag(view.body);
|
|
285
|
+
options.headers.set("ETag", etag);
|
|
286
|
+
if (status === 200 && ifNoneMatchIncludes(options.request.headers.get("if-none-match"), etag)) {
|
|
287
|
+
return new Response(null, { status: 304, headers: options.headers });
|
|
288
|
+
}
|
|
289
|
+
return new Response(options.request.method === "HEAD" ? null : view.body, {
|
|
290
|
+
status,
|
|
291
|
+
headers: options.headers
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
__name(createFarmMarkdownViewResponse, "createFarmMarkdownViewResponse");
|
|
295
|
+
|
|
296
|
+
// src/markdown.ts
|
|
297
|
+
var FARM_MARKDOWN_CONTENT_TYPE = "text/markdown; charset=utf-8";
|
|
298
|
+
function farmRequestWantsMarkdown(pathname, accept) {
|
|
299
|
+
return pathname.toLowerCase().endsWith(".md") || requestAcceptsMarkdown(accept);
|
|
300
|
+
}
|
|
301
|
+
__name(farmRequestWantsMarkdown, "farmRequestWantsMarkdown");
|
|
302
|
+
function createFarmMarkdownErrorBody(status, pathname, homeHref = "/") {
|
|
303
|
+
const heading = status === 404 ? "Page not found" : `Request failed (${status})`;
|
|
304
|
+
return `# ${heading}
|
|
305
|
+
|
|
306
|
+
No page is available at \`${pathname}\`. The URL may be incorrect, or the page has not been published.
|
|
307
|
+
|
|
308
|
+
Browse [the site homepage](${homeHref}) to find available pages.
|
|
309
|
+
`;
|
|
310
|
+
}
|
|
311
|
+
__name(createFarmMarkdownErrorBody, "createFarmMarkdownErrorBody");
|
|
312
|
+
function resolveMarkdownConfig(config) {
|
|
313
|
+
if (config === false) {
|
|
314
|
+
return {
|
|
315
|
+
enabled: false,
|
|
316
|
+
expose: [],
|
|
317
|
+
cache: false,
|
|
318
|
+
includeMetadata: true
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
if (config === true || config === void 0) {
|
|
322
|
+
return {
|
|
323
|
+
enabled: true,
|
|
324
|
+
expose: true,
|
|
325
|
+
cache: false,
|
|
326
|
+
includeMetadata: true
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
const exposeInput = config.expose ?? config.routes ?? true;
|
|
330
|
+
const expose = exposeInput === true ? true : Array.isArray(exposeInput) ? exposeInput.map(normalizeMarkdownRouteInput) : [];
|
|
331
|
+
return {
|
|
332
|
+
enabled: config.enabled !== false && (expose === true || expose.length > 0),
|
|
333
|
+
expose,
|
|
334
|
+
cache: config.cache ?? false,
|
|
335
|
+
includeMetadata: config.includeMetadata ?? true
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
__name(resolveMarkdownConfig, "resolveMarkdownConfig");
|
|
339
|
+
function resolveMarkdownMirrorTarget(config, pathname, options = {}) {
|
|
340
|
+
const hasMarkdownExtension = pathname.toLowerCase().endsWith(".md");
|
|
341
|
+
if (!config?.enabled || !hasMarkdownExtension && !requestAcceptsMarkdown(options.accept)) {
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
const targetPathname = normalizeMarkdownRoute(
|
|
345
|
+
hasMarkdownExtension ? pathname.slice(0, -".md".length) || "/" : pathname
|
|
346
|
+
);
|
|
347
|
+
const route = findExposedMarkdownRoute(config, targetPathname);
|
|
348
|
+
if (!route && config.expose !== true) {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
return {
|
|
352
|
+
pathname: targetPathname,
|
|
353
|
+
route
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
__name(resolveMarkdownMirrorTarget, "resolveMarkdownMirrorTarget");
|
|
357
|
+
async function createMarkdownMirrorResponse(options) {
|
|
358
|
+
if (options.request.method !== "GET" && options.request.method !== "HEAD") {
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
const requestUrl = options.requestUrl?.href === options.request.url ? options.requestUrl : new URL(options.request.url);
|
|
362
|
+
const hasMarkdownExtension = requestUrl.pathname.toLowerCase().endsWith(".md");
|
|
363
|
+
const target = resolveMarkdownMirrorTarget(options.config, requestUrl.pathname, {
|
|
364
|
+
accept: options.request.headers.get("accept")
|
|
365
|
+
});
|
|
366
|
+
if (!target) {
|
|
367
|
+
return null;
|
|
368
|
+
}
|
|
369
|
+
if (options.routeExists && !options.routeExists(target.pathname)) {
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
const pageUrl = new URL(options.request.url);
|
|
373
|
+
pageUrl.pathname = target.pathname;
|
|
374
|
+
for (const name of FARM_MARKDOWN_VIEW_PARAMS) pageUrl.searchParams.delete(name);
|
|
375
|
+
const headers = new Headers(options.request.headers);
|
|
376
|
+
headers.set("accept", "text/html");
|
|
377
|
+
const pageResponse = await options.renderPage(
|
|
378
|
+
new Request(pageUrl, {
|
|
379
|
+
method: "GET",
|
|
380
|
+
headers
|
|
381
|
+
})
|
|
382
|
+
);
|
|
383
|
+
const redirects = pageResponse.status >= 300 && pageResponse.status < 400 && pageResponse.headers.has("location");
|
|
384
|
+
if (redirects || !isHtmlResponse(pageResponse)) {
|
|
385
|
+
return createMarkdownAccessResponse(
|
|
386
|
+
pageResponse,
|
|
387
|
+
target.pathname,
|
|
388
|
+
pageUrl,
|
|
389
|
+
options.request.method === "HEAD"
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
const html = await pageResponse.text();
|
|
393
|
+
const markdownPath = target.pathname === "/" ? "/index.md" : `${target.pathname}.md`;
|
|
394
|
+
const actions = extractFarmPageActions(html, pageUrl);
|
|
395
|
+
const markdown = htmlToMarkdown(html, {
|
|
396
|
+
title: target.route?.title,
|
|
397
|
+
includeMetadata: options.config?.includeMetadata ?? true,
|
|
398
|
+
sourcePath: target.pathname,
|
|
399
|
+
actionsPath: actions.length > 0 ? `${markdownPath}?actions` : void 0
|
|
400
|
+
});
|
|
401
|
+
const headersOut = new Headers({
|
|
402
|
+
"Content-Type": "text/markdown; charset=utf-8",
|
|
403
|
+
"Content-Location": markdownPath,
|
|
404
|
+
"X-Farm-Markdown-Route": target.pathname
|
|
405
|
+
});
|
|
406
|
+
if (!hasMarkdownExtension) {
|
|
407
|
+
headersOut.set("Vary", "Accept");
|
|
408
|
+
}
|
|
409
|
+
for (const token of pageResponse.headers.get("vary")?.split(",") ?? []) {
|
|
410
|
+
const name = token.trim();
|
|
411
|
+
if (name && name.toLowerCase() !== "accept") appendHeaderToken(headersOut, "Vary", name);
|
|
412
|
+
}
|
|
413
|
+
const pageCacheControl = pageResponse.headers.get("cache-control");
|
|
414
|
+
const cache = target.route?.cache ?? options.config?.cache ?? false;
|
|
415
|
+
headersOut.set(
|
|
416
|
+
"Cache-Control",
|
|
417
|
+
pageCacheControl && isPrivateCacheControl(pageCacheControl) ? pageCacheControl : createMarkdownCacheHeader(cache)
|
|
418
|
+
);
|
|
419
|
+
const robots = [pageResponse.headers.get("x-robots-tag"), readHtmlMetaContent(html, "robots")].filter((value) => Boolean(value?.trim())).join(", ");
|
|
420
|
+
if (robots) headersOut.set("X-Robots-Tag", robots);
|
|
421
|
+
const canonical = readHtmlCanonicalHref(html);
|
|
422
|
+
if (canonical) {
|
|
423
|
+
try {
|
|
424
|
+
headersOut.append("Link", `<${new URL(canonical, pageUrl).href}>; rel="canonical"`);
|
|
425
|
+
} catch {
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return createFarmMarkdownViewResponse({
|
|
429
|
+
request: options.request,
|
|
430
|
+
requestUrl,
|
|
431
|
+
markdown,
|
|
432
|
+
status: pageResponse.status,
|
|
433
|
+
headers: headersOut,
|
|
434
|
+
markdownPath,
|
|
435
|
+
actions
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
__name(createMarkdownMirrorResponse, "createMarkdownMirrorResponse");
|
|
439
|
+
function createMarkdownAccessResponse(pageResponse, pathname, pageUrl, head) {
|
|
440
|
+
const location = pageResponse.headers.get("location");
|
|
441
|
+
const redirect = pageResponse.status >= 300 && pageResponse.status < 400 && location;
|
|
442
|
+
if (!redirect && pageResponse.status !== 401 && pageResponse.status !== 403) return null;
|
|
443
|
+
const headers = new Headers({
|
|
444
|
+
"Content-Type": "text/markdown; charset=utf-8",
|
|
445
|
+
"Cache-Control": "no-store",
|
|
446
|
+
"X-Farm-Markdown-Route": pathname
|
|
447
|
+
});
|
|
448
|
+
let body;
|
|
449
|
+
if (redirect) {
|
|
450
|
+
let target;
|
|
451
|
+
try {
|
|
452
|
+
target = new URL(location, pageUrl).href;
|
|
453
|
+
} catch {
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
headers.set("Location", location);
|
|
457
|
+
body = `# Redirected
|
|
458
|
+
|
|
459
|
+
\`${pathname}\` redirects to [${target}](${target}). The page may require signing in.
|
|
460
|
+
`;
|
|
461
|
+
} else {
|
|
462
|
+
const authenticate = pageResponse.headers.get("www-authenticate");
|
|
463
|
+
if (authenticate) headers.set("WWW-Authenticate", authenticate);
|
|
464
|
+
body = pageResponse.status === 401 ? `# Sign-in required
|
|
465
|
+
|
|
466
|
+
\`${pathname}\` requires signing in.
|
|
467
|
+
` : `# Access denied
|
|
468
|
+
|
|
469
|
+
This account cannot read \`${pathname}\`.
|
|
470
|
+
`;
|
|
471
|
+
}
|
|
472
|
+
return new Response(head ? null : body, { status: pageResponse.status, headers });
|
|
473
|
+
}
|
|
474
|
+
__name(createMarkdownAccessResponse, "createMarkdownAccessResponse");
|
|
475
|
+
function applyMarkdownNegotiationHeaders(response, options) {
|
|
476
|
+
if (!isHtmlResponse(response)) {
|
|
477
|
+
return response;
|
|
478
|
+
}
|
|
479
|
+
const alternatePath = getFarmMarkdownAlternatePath(options.config, options.pathname);
|
|
480
|
+
if (!alternatePath) {
|
|
481
|
+
return response;
|
|
482
|
+
}
|
|
483
|
+
const headers = new Headers(response.headers);
|
|
484
|
+
appendHeaderToken(headers, "Vary", "Accept");
|
|
485
|
+
headers.append("Link", `<${alternatePath}>; rel="alternate"; type="text/markdown"`);
|
|
486
|
+
return new Response(response.body, {
|
|
487
|
+
status: response.status,
|
|
488
|
+
statusText: response.statusText,
|
|
489
|
+
headers
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
__name(applyMarkdownNegotiationHeaders, "applyMarkdownNegotiationHeaders");
|
|
493
|
+
var CODE_BLOCK_MARK = "\0";
|
|
494
|
+
var CODE_BLOCK_PLACEHOLDER = /\u0000(\d+)\u0000/g;
|
|
495
|
+
var BLOCK_TAGS = /<\/?(ul|ol|main|section|article|header|footer|nav|aside|div|form|fieldset|legend|figure|figcaption|table|thead|tbody|tfoot|tr|dl|dt|dd|details|summary|address)\b[^>]*>/gi;
|
|
496
|
+
var BOX_TAGS = /<\/?(label|button|input|select|textarea|option|output|meter|progress|td|th)\b[^>]*>/gi;
|
|
497
|
+
function htmlToMarkdown(html, options = {}) {
|
|
498
|
+
const title = options.title ?? extractHtmlTitle(html);
|
|
499
|
+
let source = readHtmlContentRegion(html);
|
|
500
|
+
const codeBlocks = [];
|
|
501
|
+
const setAsideCodeBlock = /* @__PURE__ */ __name((code, attributes) => {
|
|
502
|
+
const content = decodeHtml(stripTags(code)).trim();
|
|
503
|
+
const longestRun = (content.match(/`+/g) ?? []).reduce(
|
|
504
|
+
(longest, run) => Math.max(longest, run.length),
|
|
505
|
+
0
|
|
506
|
+
);
|
|
507
|
+
const fence = "`".repeat(Math.max(3, longestRun + 1));
|
|
508
|
+
const language = readCodeLanguage(attributes) ?? "";
|
|
509
|
+
codeBlocks.push(`${fence}${language}
|
|
510
|
+
${content}
|
|
511
|
+
${fence}`);
|
|
512
|
+
return `
|
|
513
|
+
|
|
514
|
+
${CODE_BLOCK_MARK}${codeBlocks.length - 1}${CODE_BLOCK_MARK}
|
|
515
|
+
|
|
516
|
+
`;
|
|
517
|
+
}, "setAsideCodeBlock");
|
|
518
|
+
source = source.replace(
|
|
519
|
+
/<pre\b([^>]*)><code\b([^>]*)>([\s\S]*?)<\/code><\/pre>/gi,
|
|
520
|
+
(_, preAttributes, codeAttributes, code) => setAsideCodeBlock(code, [codeAttributes, preAttributes])
|
|
521
|
+
);
|
|
522
|
+
source = source.replace(
|
|
523
|
+
/<pre\b([^>]*)>([\s\S]*?)<\/pre>/gi,
|
|
524
|
+
(_, attributes, code) => setAsideCodeBlock(code, [attributes])
|
|
525
|
+
);
|
|
526
|
+
source = source.replace(
|
|
527
|
+
/<table\b[^>]*>([\s\S]*?)<\/table>/gi,
|
|
528
|
+
(table, content) => {
|
|
529
|
+
if (/<table\b/i.test(content) || content.includes(CODE_BLOCK_MARK)) return table;
|
|
530
|
+
const rows = toMarkdownTableRows(content);
|
|
531
|
+
if (!rows) return table;
|
|
532
|
+
codeBlocks.push(rows);
|
|
533
|
+
return `
|
|
534
|
+
|
|
535
|
+
${CODE_BLOCK_MARK}${codeBlocks.length - 1}${CODE_BLOCK_MARK}
|
|
536
|
+
|
|
537
|
+
`;
|
|
538
|
+
}
|
|
539
|
+
);
|
|
540
|
+
source = source.replace(/<h([1-6])\b[^>]*>([\s\S]*?)<\/h\1>/gi, (_, level, content) => {
|
|
541
|
+
return `
|
|
542
|
+
|
|
543
|
+
${"#".repeat(Number(level))} ${toInlineMarkdown(content).trim()}
|
|
544
|
+
|
|
545
|
+
`;
|
|
546
|
+
});
|
|
547
|
+
source = source.replace(/<p\b[^>]*>([\s\S]*?)<\/p>/gi, (_, content) => {
|
|
548
|
+
return `
|
|
549
|
+
|
|
550
|
+
${toInlineMarkdown(content).trim()}
|
|
551
|
+
|
|
552
|
+
`;
|
|
553
|
+
});
|
|
554
|
+
source = source.replace(/<blockquote\b[^>]*>([\s\S]*?)<\/blockquote>/gi, (_, content) => {
|
|
555
|
+
const quote = htmlToMarkdown(content, { includeMetadata: false }).split("\n").filter((line) => line.trim().length > 0).map((line) => {
|
|
556
|
+
const code = line.startsWith(CODE_BLOCK_MARK) && line.endsWith(CODE_BLOCK_MARK) ? codeBlocks[Number(line.slice(1, -1))] : void 0;
|
|
557
|
+
if (code === void 0) return `> ${escapeHtmlText(line)}`;
|
|
558
|
+
codeBlocks.push(
|
|
559
|
+
code.split("\n").map((codeLine) => codeLine ? `> ${codeLine}` : ">").join("\n")
|
|
560
|
+
);
|
|
561
|
+
return `${CODE_BLOCK_MARK}${codeBlocks.length - 1}${CODE_BLOCK_MARK}`;
|
|
562
|
+
}).join("\n");
|
|
563
|
+
return `
|
|
564
|
+
|
|
565
|
+
${quote}
|
|
566
|
+
|
|
567
|
+
`;
|
|
568
|
+
});
|
|
569
|
+
source = source.replace(
|
|
570
|
+
/<ol\b([^>]*)>([\s\S]*?)<\/ol>/gi,
|
|
571
|
+
(list, attributes, content) => {
|
|
572
|
+
if (/<[ou]l\b/i.test(content)) return list;
|
|
573
|
+
const start = Number.parseInt(readHtmlAttribute(attributes, "start") ?? "1", 10);
|
|
574
|
+
let number = Number.isFinite(start) ? start : 1;
|
|
575
|
+
const items = content.replace(/<li\b[^>]*>([\s\S]*?)<\/li>/gi, (_, item) => {
|
|
576
|
+
return `
|
|
577
|
+
${number++}. ${toInlineMarkdown(item).trim()}`;
|
|
578
|
+
});
|
|
579
|
+
return `
|
|
580
|
+
${items}
|
|
581
|
+
`;
|
|
582
|
+
}
|
|
583
|
+
);
|
|
584
|
+
source = source.replace(/<li\b[^>]*>([\s\S]*?)<\/li>/gi, (_, content) => {
|
|
585
|
+
return `
|
|
586
|
+
- ${toInlineMarkdown(content).trim()}`;
|
|
587
|
+
});
|
|
588
|
+
source = source.replace(BLOCK_TAGS, "\n").replace(BOX_TAGS, " ").replace(/<br\s*\/?>/gi, "\n").replace(/<hr\s*\/?>/gi, "\n\n---\n\n");
|
|
589
|
+
let markdown = decodeHtml(stripTags(source)).split("\n").map((line) => line.replace(/[ \t]+/g, " ").trim()).join("\n").replace(/\n{3,}/g, "\n\n").trim().replace(CODE_BLOCK_PLACEHOLDER, (match, index) => codeBlocks[Number(index)] ?? match);
|
|
590
|
+
if (options.includeMetadata !== false) {
|
|
591
|
+
const metadata = [];
|
|
592
|
+
if (title && !markdown.startsWith("# ")) {
|
|
593
|
+
metadata.push(`# ${title}`);
|
|
594
|
+
}
|
|
595
|
+
if (options.sourcePath) {
|
|
596
|
+
metadata.push(`Source: ${options.sourcePath}`);
|
|
597
|
+
}
|
|
598
|
+
if (options.actionsPath) {
|
|
599
|
+
metadata.push(`Actions: ${options.actionsPath}`);
|
|
600
|
+
}
|
|
601
|
+
if (metadata.length) {
|
|
602
|
+
markdown = `${metadata.join("\n\n")}${markdown ? `
|
|
603
|
+
|
|
604
|
+
${markdown}` : ""}`;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
return `${markdown}
|
|
608
|
+
`;
|
|
609
|
+
}
|
|
610
|
+
__name(htmlToMarkdown, "htmlToMarkdown");
|
|
611
|
+
function normalizeMarkdownRouteInput(input) {
|
|
612
|
+
if (typeof input === "string") {
|
|
613
|
+
return {
|
|
614
|
+
route: normalizeMarkdownRoute(input)
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
return {
|
|
618
|
+
...input,
|
|
619
|
+
route: normalizeMarkdownRoute(input.route)
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
__name(normalizeMarkdownRouteInput, "normalizeMarkdownRouteInput");
|
|
623
|
+
function normalizeMarkdownRoute(route) {
|
|
624
|
+
const withoutMarkdownExtension = route.toLowerCase().endsWith(".md") ? route.slice(0, -3) : route;
|
|
625
|
+
const withSlash = withoutMarkdownExtension.startsWith("/") ? withoutMarkdownExtension : `/${withoutMarkdownExtension}`;
|
|
626
|
+
const normalized = withSlash.replace(/\/+/g, "/").replace(/\/$/g, "");
|
|
627
|
+
return normalized === "" || normalized === "/index" ? "/" : normalized;
|
|
628
|
+
}
|
|
629
|
+
__name(normalizeMarkdownRoute, "normalizeMarkdownRoute");
|
|
630
|
+
function farmAcceptQuality(accept, mediaType, options = {}) {
|
|
631
|
+
if (!accept) return 0;
|
|
632
|
+
const target = mediaType.toLowerCase();
|
|
633
|
+
const [targetType] = target.split("/");
|
|
634
|
+
let best = 0;
|
|
635
|
+
for (const entry of accept.split(",")) {
|
|
636
|
+
const [candidate, ...parameters] = entry.trim().toLowerCase().split(";").map((part) => part.trim());
|
|
637
|
+
if (!candidate) continue;
|
|
638
|
+
const matches = candidate === target || options.wildcards === true && (candidate === "*/*" || candidate === `${targetType}/*`);
|
|
639
|
+
if (!matches) continue;
|
|
640
|
+
const quality = parameters.find((parameter) => parameter.startsWith("q="));
|
|
641
|
+
const value = quality === void 0 ? 1 : Number(quality.slice(2));
|
|
642
|
+
if (!Number.isFinite(value) || value <= 0) continue;
|
|
643
|
+
if (value > best) best = value;
|
|
644
|
+
}
|
|
645
|
+
return best;
|
|
646
|
+
}
|
|
647
|
+
__name(farmAcceptQuality, "farmAcceptQuality");
|
|
648
|
+
function requestAcceptsMarkdown(accept) {
|
|
649
|
+
if (!accept) {
|
|
650
|
+
return false;
|
|
651
|
+
}
|
|
652
|
+
return accept.split(",").some((entry) => {
|
|
653
|
+
const [mediaType, ...parameters] = entry.trim().toLowerCase().split(";").map((part) => part.trim());
|
|
654
|
+
if (mediaType !== "text/markdown") {
|
|
655
|
+
return false;
|
|
656
|
+
}
|
|
657
|
+
const quality = parameters.find((parameter) => parameter.startsWith("q="));
|
|
658
|
+
return quality === void 0 || Number(quality.slice(2)) > 0;
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
__name(requestAcceptsMarkdown, "requestAcceptsMarkdown");
|
|
662
|
+
function getMarkdownAlternatePath(pathname) {
|
|
663
|
+
return pathname === "/" ? "/index.md" : `${pathname}.md`;
|
|
664
|
+
}
|
|
665
|
+
__name(getMarkdownAlternatePath, "getMarkdownAlternatePath");
|
|
666
|
+
function getFarmMarkdownMirrorPath(pathname) {
|
|
667
|
+
return getMarkdownAlternatePath(pathname);
|
|
668
|
+
}
|
|
669
|
+
__name(getFarmMarkdownMirrorPath, "getFarmMarkdownMirrorPath");
|
|
670
|
+
function getFarmMarkdownAlternatePath(config, pathname) {
|
|
671
|
+
const target = resolveMarkdownMirrorTarget(config, pathname, { accept: "text/markdown" });
|
|
672
|
+
return target ? getMarkdownAlternatePath(target.pathname) : null;
|
|
673
|
+
}
|
|
674
|
+
__name(getFarmMarkdownAlternatePath, "getFarmMarkdownAlternatePath");
|
|
675
|
+
function isFarmMarkdownMirrorExposed(config, pathname) {
|
|
676
|
+
if (!config?.enabled) return false;
|
|
677
|
+
return config.expose === true || findExposedMarkdownRoute(config, pathname) !== null;
|
|
678
|
+
}
|
|
679
|
+
__name(isFarmMarkdownMirrorExposed, "isFarmMarkdownMirrorExposed");
|
|
680
|
+
function matchesFarmMarkdownRoutePattern(pattern, pathname) {
|
|
681
|
+
return routeMatches(pattern, pathname);
|
|
682
|
+
}
|
|
683
|
+
__name(matchesFarmMarkdownRoutePattern, "matchesFarmMarkdownRoutePattern");
|
|
684
|
+
function appendHeaderToken(headers, name, token) {
|
|
685
|
+
const current = headers.get(name);
|
|
686
|
+
if (!current) {
|
|
687
|
+
headers.set(name, token);
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
const tokens = current.split(",").map((value) => value.trim().toLowerCase());
|
|
691
|
+
if (!tokens.includes(token.toLowerCase())) {
|
|
692
|
+
headers.set(name, `${current}, ${token}`);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
__name(appendHeaderToken, "appendHeaderToken");
|
|
696
|
+
function findExposedMarkdownRoute(config, pathname) {
|
|
697
|
+
if (config.expose === true) {
|
|
698
|
+
return null;
|
|
699
|
+
}
|
|
700
|
+
return config.expose.find((route) => routeMatches(route.route, pathname)) ?? null;
|
|
701
|
+
}
|
|
702
|
+
__name(findExposedMarkdownRoute, "findExposedMarkdownRoute");
|
|
703
|
+
function routeMatches(pattern, pathname) {
|
|
704
|
+
if (pattern === pathname) {
|
|
705
|
+
return true;
|
|
706
|
+
}
|
|
707
|
+
let source = "";
|
|
708
|
+
pattern.split("/").forEach((segment, index) => {
|
|
709
|
+
if (/^\[\[\.\.\.[^\]]+\]\]$/.test(segment)) {
|
|
710
|
+
source += "(?:/.*)?";
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
const prefix = index === 0 ? "" : "/";
|
|
714
|
+
if (/^\[\.\.\.[^\]]+\]$/.test(segment)) {
|
|
715
|
+
source += `${prefix}.*`;
|
|
716
|
+
} else if (/^\[[^\]]+\]$/.test(segment)) {
|
|
717
|
+
source += `${prefix}[^/]+`;
|
|
718
|
+
} else {
|
|
719
|
+
source += prefix + segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
720
|
+
}
|
|
721
|
+
});
|
|
722
|
+
return new RegExp(`^${source}$`).test(pathname);
|
|
723
|
+
}
|
|
724
|
+
__name(routeMatches, "routeMatches");
|
|
725
|
+
function isHtmlResponse(response) {
|
|
726
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
727
|
+
return contentType.includes("text/html");
|
|
728
|
+
}
|
|
729
|
+
__name(isHtmlResponse, "isHtmlResponse");
|
|
730
|
+
function isPrivateCacheControl(value) {
|
|
731
|
+
return value.split(",").some((directive) => {
|
|
732
|
+
const name = directive.split("=", 1)[0].trim().toLowerCase();
|
|
733
|
+
return name === "private" || name === "no-store";
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
__name(isPrivateCacheControl, "isPrivateCacheControl");
|
|
737
|
+
function createMarkdownCacheHeader(cache) {
|
|
738
|
+
if (cache === false || cache <= 0) {
|
|
739
|
+
return "no-store";
|
|
740
|
+
}
|
|
741
|
+
return `public, max-age=${cache}, s-maxage=${cache}`;
|
|
742
|
+
}
|
|
743
|
+
__name(createMarkdownCacheHeader, "createMarkdownCacheHeader");
|
|
744
|
+
function extractHtmlTitle(html) {
|
|
745
|
+
const match = html.match(/<title\b[^>]*>([\s\S]*?)<\/title>/i);
|
|
746
|
+
return match ? decodeHtml(stripTags(match[1])).trim() : void 0;
|
|
747
|
+
}
|
|
748
|
+
__name(extractHtmlTitle, "extractHtmlTitle");
|
|
749
|
+
function readHtmlContentRegion(html) {
|
|
750
|
+
const streamed = readStreamedBoundaryInstructions(html);
|
|
751
|
+
return extractHtmlBody(
|
|
752
|
+
removeHiddenElements(
|
|
753
|
+
resolveStreamedBoundaries(
|
|
754
|
+
html.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<noscript\b[^>]*>[\s\S]*?<\/noscript>/gi, ""),
|
|
755
|
+
streamed
|
|
756
|
+
).replace(/<!--[\s\S]*?-->/g, "")
|
|
757
|
+
)
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
__name(readHtmlContentRegion, "readHtmlContentRegion");
|
|
761
|
+
function extractHtmlBody(html) {
|
|
762
|
+
const rootMatch = html.match(
|
|
763
|
+
/<div\b[^>]*id=["']root["'][^>]*>([\s\S]*?)<\/div>\s*(?:<script|<\/body>|$)/i
|
|
764
|
+
);
|
|
765
|
+
const bodyMatch = html.match(/<body\b[^>]*>([\s\S]*?)<\/body>/i);
|
|
766
|
+
const body = rootMatch ? rootMatch[1] : bodyMatch ? bodyMatch[1] : html;
|
|
767
|
+
const main = extractFirstElementContent(body, ["main"]);
|
|
768
|
+
if (main !== void 0) return main;
|
|
769
|
+
const articles = body.match(/<article\b/gi)?.length ?? 0;
|
|
770
|
+
return articles === 1 ? extractFirstElementContent(body, ["article"]) ?? body : body;
|
|
771
|
+
}
|
|
772
|
+
__name(extractHtmlBody, "extractHtmlBody");
|
|
773
|
+
function extractFirstElementContent(html, tagNames) {
|
|
774
|
+
for (const tagName of tagNames) {
|
|
775
|
+
const match = html.match(new RegExp(`<${tagName}\\b[^>]*>([\\s\\S]*?)<\\/${tagName}>`, "i"));
|
|
776
|
+
if (match) {
|
|
777
|
+
return match[1];
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
return void 0;
|
|
781
|
+
}
|
|
782
|
+
__name(extractFirstElementContent, "extractFirstElementContent");
|
|
783
|
+
function readHtmlMetaContent(html, name) {
|
|
784
|
+
for (const match of html.matchAll(/<meta\b([^>]*)>/gi)) {
|
|
785
|
+
if (readHtmlAttribute(match[1], "name")?.toLowerCase() === name) {
|
|
786
|
+
return readHtmlAttribute(match[1], "content");
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
return void 0;
|
|
790
|
+
}
|
|
791
|
+
__name(readHtmlMetaContent, "readHtmlMetaContent");
|
|
792
|
+
function readHtmlCanonicalHref(html) {
|
|
793
|
+
for (const match of html.matchAll(/<link\b([^>]*)>/gi)) {
|
|
794
|
+
const rel = readHtmlAttribute(match[1], "rel")?.toLowerCase().split(/\s+/);
|
|
795
|
+
if (rel?.includes("canonical")) return readHtmlAttribute(match[1], "href");
|
|
796
|
+
}
|
|
797
|
+
return void 0;
|
|
798
|
+
}
|
|
799
|
+
__name(readHtmlCanonicalHref, "readHtmlCanonicalHref");
|
|
800
|
+
var FORM_ENCTYPES = /* @__PURE__ */ new Set([
|
|
801
|
+
"application/x-www-form-urlencoded",
|
|
802
|
+
"multipart/form-data",
|
|
803
|
+
"text/plain"
|
|
804
|
+
]);
|
|
805
|
+
function extractFarmPageActions(html, pageUrl) {
|
|
806
|
+
if (!/<form\b/i.test(html)) return [];
|
|
807
|
+
const region = readHtmlContentRegion(html);
|
|
808
|
+
const labelsFor = /* @__PURE__ */ new Map();
|
|
809
|
+
for (const match of region.matchAll(/<label\b([^>]*)>([\s\S]*?)<\/label>/gi)) {
|
|
810
|
+
const target = readDecodedAttribute(match[1], "for");
|
|
811
|
+
if (target && !labelsFor.has(target)) labelsFor.set(target, readLabelText(match[2]));
|
|
812
|
+
}
|
|
813
|
+
const actions = [];
|
|
814
|
+
for (const form of region.matchAll(/<form\b([^>]*)>([\s\S]*?)<\/form>/gi)) {
|
|
815
|
+
const action = readPageForm(form[1], form[2], pageUrl, labelsFor);
|
|
816
|
+
if (action) actions.push(action);
|
|
817
|
+
}
|
|
818
|
+
return actions;
|
|
819
|
+
}
|
|
820
|
+
__name(extractFarmPageActions, "extractFarmPageActions");
|
|
821
|
+
function readPageForm(attributes, content, pageUrl, labelsFor) {
|
|
822
|
+
const method = readDecodedAttribute(attributes, "method")?.trim().toLowerCase();
|
|
823
|
+
if (method === "dialog") return void 0;
|
|
824
|
+
const target = resolveFormTarget(readDecodedAttribute(attributes, "action"), pageUrl);
|
|
825
|
+
const form = {
|
|
826
|
+
type: "form",
|
|
827
|
+
method: method === "post" ? "POST" : "GET",
|
|
828
|
+
fields: [],
|
|
829
|
+
submit: []
|
|
830
|
+
};
|
|
831
|
+
const name = readDecodedAttribute(attributes, "aria-label") ?? readDecodedAttribute(attributes, "name") ?? readDecodedAttribute(attributes, "id");
|
|
832
|
+
if (name?.trim()) form.name = name.trim();
|
|
833
|
+
if (target) form.action = target;
|
|
834
|
+
else form.requiresJavaScript = true;
|
|
835
|
+
if (form.method === "POST") {
|
|
836
|
+
const enctype = readDecodedAttribute(attributes, "enctype")?.trim().toLowerCase();
|
|
837
|
+
form.enctype = enctype && FORM_ENCTYPES.has(enctype) ? enctype : "application/x-www-form-urlencoded";
|
|
838
|
+
}
|
|
839
|
+
const wrappingLabels = [...content.matchAll(/<label\b[^>]*>([\s\S]*?)<\/label>/gi)].map(
|
|
840
|
+
(match) => ({
|
|
841
|
+
start: match.index,
|
|
842
|
+
end: match.index + match[0].length,
|
|
843
|
+
text: readLabelText(match[1])
|
|
844
|
+
})
|
|
845
|
+
);
|
|
846
|
+
const labelOf = /* @__PURE__ */ __name((controlAttributes, index) => {
|
|
847
|
+
const aria = readDecodedAttribute(controlAttributes, "aria-label")?.trim();
|
|
848
|
+
if (aria) return aria;
|
|
849
|
+
const id = readDecodedAttribute(controlAttributes, "id");
|
|
850
|
+
const forLabel = id ? labelsFor.get(id) : void 0;
|
|
851
|
+
if (forLabel) return forLabel;
|
|
852
|
+
const wrapping = wrappingLabels.find((label) => label.start < index && index < label.end);
|
|
853
|
+
return wrapping?.text || void 0;
|
|
854
|
+
}, "labelOf");
|
|
855
|
+
const groups = /* @__PURE__ */ new Map();
|
|
856
|
+
const controls = /<(input)\b([^>]*)>|<(select|textarea|button)\b([^>]*)>([\s\S]*?)<\/\3>/gi;
|
|
857
|
+
for (const match of content.matchAll(controls)) {
|
|
858
|
+
const tag = (match[1] ?? match[3]).toLowerCase();
|
|
859
|
+
const controlAttributes = match[2] ?? match[4];
|
|
860
|
+
const inner = match[5] ?? "";
|
|
861
|
+
if (hasHtmlAttribute(controlAttributes, "disabled")) continue;
|
|
862
|
+
const label = labelOf(controlAttributes, match.index);
|
|
863
|
+
const fieldName = readDecodedAttribute(controlAttributes, "name");
|
|
864
|
+
const typeAttribute = readDecodedAttribute(controlAttributes, "type")?.trim().toLowerCase();
|
|
865
|
+
const type = tag === "input" ? typeAttribute ?? "text" : tag === "button" ? typeAttribute ?? "submit" : tag;
|
|
866
|
+
if (tag === "button" && !["button", "reset"].includes(type) || tag === "input" && (type === "submit" || type === "image")) {
|
|
867
|
+
const submit = {
|
|
868
|
+
label: (tag === "button" ? readLabelText(inner) : "") || readDecodedAttribute(controlAttributes, "value") || readDecodedAttribute(controlAttributes, "alt") || label || "Submit"
|
|
869
|
+
};
|
|
870
|
+
if (fieldName) submit.name = fieldName;
|
|
871
|
+
const value = readDecodedAttribute(controlAttributes, "value");
|
|
872
|
+
if (fieldName && value !== void 0) submit.value = value;
|
|
873
|
+
const formAction = readDecodedAttribute(controlAttributes, "formaction");
|
|
874
|
+
if (formAction !== void 0) {
|
|
875
|
+
const resolved = resolveFormTarget(formAction, pageUrl);
|
|
876
|
+
if (resolved) submit.action = resolved;
|
|
877
|
+
}
|
|
878
|
+
const formMethod = readDecodedAttribute(controlAttributes, "formmethod")?.toLowerCase();
|
|
879
|
+
if (formMethod === "get" || formMethod === "post") submit.method = formMethod.toUpperCase();
|
|
880
|
+
form.submit.push(submit);
|
|
881
|
+
continue;
|
|
882
|
+
}
|
|
883
|
+
if (tag === "button" || type === "button" || type === "reset" || !fieldName) continue;
|
|
884
|
+
if (type === "hidden") {
|
|
885
|
+
const value = readDecodedAttribute(controlAttributes, "value") ?? "";
|
|
886
|
+
form.fields.push({ name: fieldName, type, value });
|
|
887
|
+
if (fieldName.startsWith("$ACTION_ID_")) {
|
|
888
|
+
form.serverAction ?? (form.serverAction = { id: fieldName.slice("$ACTION_ID_".length) });
|
|
889
|
+
} else if (/^\$ACTION_[^:]*:0$/.test(fieldName)) {
|
|
890
|
+
try {
|
|
891
|
+
const id = JSON.parse(value).id;
|
|
892
|
+
if (typeof id === "string") form.serverAction ?? (form.serverAction = { id });
|
|
893
|
+
} catch {
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
continue;
|
|
897
|
+
}
|
|
898
|
+
if (type === "radio" || type === "checkbox") {
|
|
899
|
+
const value = readDecodedAttribute(controlAttributes, "value") ?? "on";
|
|
900
|
+
const option = { value };
|
|
901
|
+
if (label) option.label = label;
|
|
902
|
+
if (hasHtmlAttribute(controlAttributes, "checked")) option.selected = true;
|
|
903
|
+
const group = groups.get(fieldName);
|
|
904
|
+
if (group) {
|
|
905
|
+
group.options.push(option);
|
|
906
|
+
if (hasHtmlAttribute(controlAttributes, "required")) group.required = true;
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
const field2 = { name: fieldName, type, options: [option] };
|
|
910
|
+
if (hasHtmlAttribute(controlAttributes, "required")) field2.required = true;
|
|
911
|
+
groups.set(fieldName, field2);
|
|
912
|
+
form.fields.push(field2);
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
915
|
+
const field = { name: fieldName, type };
|
|
916
|
+
if (label) field.label = label;
|
|
917
|
+
if (hasHtmlAttribute(controlAttributes, "required")) field.required = true;
|
|
918
|
+
if (hasHtmlAttribute(controlAttributes, "multiple")) field.multiple = true;
|
|
919
|
+
if (tag === "select") {
|
|
920
|
+
field.options = [...inner.matchAll(/<option\b([^>]*)>([\s\S]*?)(?=<\/option>|<option\b|$)/gi)].filter((option) => !hasHtmlAttribute(option[1], "disabled")).map((option) => {
|
|
921
|
+
const text = readLabelText(option[2]);
|
|
922
|
+
const entry = {
|
|
923
|
+
value: readDecodedAttribute(option[1], "value") ?? text
|
|
924
|
+
};
|
|
925
|
+
if (text && text !== entry.value) entry.label = text;
|
|
926
|
+
if (hasHtmlAttribute(option[1], "selected")) entry.selected = true;
|
|
927
|
+
return entry;
|
|
928
|
+
});
|
|
929
|
+
} else if (tag === "textarea") {
|
|
930
|
+
const value = decodeHtml(inner);
|
|
931
|
+
if (value) field.value = value;
|
|
932
|
+
} else {
|
|
933
|
+
const value = readDecodedAttribute(controlAttributes, "value");
|
|
934
|
+
if (value !== void 0 && type !== "password" && type !== "file") field.value = value;
|
|
935
|
+
}
|
|
936
|
+
for (const key of ["placeholder", "pattern", "min", "max", "step", "accept"]) {
|
|
937
|
+
const value = readDecodedAttribute(controlAttributes, key);
|
|
938
|
+
if (value !== void 0) field[key] = value;
|
|
939
|
+
}
|
|
940
|
+
for (const [attribute, key] of [
|
|
941
|
+
["minlength", "minLength"],
|
|
942
|
+
["maxlength", "maxLength"]
|
|
943
|
+
]) {
|
|
944
|
+
const value = Number.parseInt(readDecodedAttribute(controlAttributes, attribute) ?? "", 10);
|
|
945
|
+
if (Number.isFinite(value) && value >= 0) field[key] = value;
|
|
946
|
+
}
|
|
947
|
+
form.fields.push(field);
|
|
948
|
+
}
|
|
949
|
+
if (form.fields.length === 0 && form.submit.length === 0) return void 0;
|
|
950
|
+
if (form.submit.length === 0) form.submit.push({ label: "Submit" });
|
|
951
|
+
return form;
|
|
952
|
+
}
|
|
953
|
+
__name(readPageForm, "readPageForm");
|
|
954
|
+
function resolveFormTarget(action, pageUrl) {
|
|
955
|
+
let target;
|
|
956
|
+
try {
|
|
957
|
+
target = new URL(action?.trim() || pageUrl.href, pageUrl);
|
|
958
|
+
} catch {
|
|
959
|
+
return void 0;
|
|
960
|
+
}
|
|
961
|
+
if (target.protocol !== "http:" && target.protocol !== "https:") return void 0;
|
|
962
|
+
target.hash = "";
|
|
963
|
+
return target.origin === pageUrl.origin ? `${target.pathname}${target.search}` : target.href;
|
|
964
|
+
}
|
|
965
|
+
__name(resolveFormTarget, "resolveFormTarget");
|
|
966
|
+
function readLabelText(html) {
|
|
967
|
+
return decodeHtml(stripTags(html.replace(/<(select|textarea|button)\b[\s\S]*?<\/\1>/gi, " "))).replace(/\s+/g, " ").trim();
|
|
968
|
+
}
|
|
969
|
+
__name(readLabelText, "readLabelText");
|
|
970
|
+
function readDecodedAttribute(attributes, name) {
|
|
971
|
+
const value = readHtmlAttribute(attributes, name);
|
|
972
|
+
return value === void 0 ? void 0 : decodeHtml(value);
|
|
973
|
+
}
|
|
974
|
+
__name(readDecodedAttribute, "readDecodedAttribute");
|
|
975
|
+
function hasHtmlAttribute(attributes, name) {
|
|
976
|
+
return new RegExp(`(?:^|\\s)${name}(?=\\s|=|/|$)`, "i").test(attributes);
|
|
977
|
+
}
|
|
978
|
+
__name(hasHtmlAttribute, "hasHtmlAttribute");
|
|
979
|
+
function readHtmlAttribute(attributes, name) {
|
|
980
|
+
const match = new RegExp(
|
|
981
|
+
`(?:^|\\s)${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s"'=<>\`]+))`,
|
|
982
|
+
"i"
|
|
983
|
+
).exec(attributes);
|
|
984
|
+
return match ? match[1] ?? match[2] ?? match[3] : void 0;
|
|
985
|
+
}
|
|
986
|
+
__name(readHtmlAttribute, "readHtmlAttribute");
|
|
987
|
+
function readCodeLanguage(attributesList) {
|
|
988
|
+
for (const attributes of attributesList) {
|
|
989
|
+
const language = readHtmlAttribute(attributes, "data-language") ?? readHtmlAttribute(attributes, "data-lang") ?? readHtmlAttribute(attributes, "class")?.match(/(?:^|\s)lang(?:uage)?-(\S+)/)?.[1];
|
|
990
|
+
if (language && /^[\w#+.-]+$/.test(language)) return language;
|
|
991
|
+
}
|
|
992
|
+
return void 0;
|
|
993
|
+
}
|
|
994
|
+
__name(readCodeLanguage, "readCodeLanguage");
|
|
995
|
+
function toMarkdownTableRows(content) {
|
|
996
|
+
const rows = [];
|
|
997
|
+
for (const [, row] of content.matchAll(/<tr\b[^>]*>([\s\S]*?)<\/tr>/gi)) {
|
|
998
|
+
const cells = [];
|
|
999
|
+
for (const [, , attributes, cell] of row.matchAll(/<(td|th)\b([^>]*)>([\s\S]*?)<\/\1>/gi)) {
|
|
1000
|
+
cells.push(decodeHtml(toInlineMarkdown(cell)).trim().replace(/\|/g, "\\|"));
|
|
1001
|
+
const span = Number.parseInt(readHtmlAttribute(attributes, "colspan") ?? "1", 10);
|
|
1002
|
+
for (let extra = 1; extra < Math.min(span, 1e3); extra++) cells.push("");
|
|
1003
|
+
}
|
|
1004
|
+
if (cells.length) rows.push(cells);
|
|
1005
|
+
}
|
|
1006
|
+
if (!rows.length) return void 0;
|
|
1007
|
+
const columns = rows.reduce((widest, row) => Math.max(widest, row.length), 0);
|
|
1008
|
+
const line = /* @__PURE__ */ __name((cells) => `| ${Array.from({ length: columns }, (_, index) => cells[index] ?? "").join(" | ")} |`.replace(
|
|
1009
|
+
/ {2,}/g,
|
|
1010
|
+
" "
|
|
1011
|
+
), "line");
|
|
1012
|
+
return [line(rows[0]), line(Array(columns).fill("---")), ...rows.slice(1).map(line)].join("\n");
|
|
1013
|
+
}
|
|
1014
|
+
__name(toMarkdownTableRows, "toMarkdownTableRows");
|
|
1015
|
+
function toInlineMarkdown(html) {
|
|
1016
|
+
let source = html.replace(/<a\b[^>]*href=["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/gi, (_, href, text) => {
|
|
1017
|
+
const label = toInlineMarkdown(text).trim() || href;
|
|
1018
|
+
return `[${label}](${href})`;
|
|
1019
|
+
}).replace(/<img\b([^>]*)>/gi, (_, attributes) => {
|
|
1020
|
+
const src = readHtmlAttribute(attributes, "src");
|
|
1021
|
+
const alt = readHtmlAttribute(attributes, "alt") ?? "";
|
|
1022
|
+
if (!src) return alt;
|
|
1023
|
+
if (/^data:/i.test(src.trim())) return alt;
|
|
1024
|
+
return ``;
|
|
1025
|
+
}).replace(/<strong\b[^>]*>([\s\S]*?)<\/strong>/gi, (_, content) => {
|
|
1026
|
+
return `**${toInlineMarkdown(content).trim()}**`;
|
|
1027
|
+
}).replace(/<b\b[^>]*>([\s\S]*?)<\/b>/gi, (_, content) => {
|
|
1028
|
+
return `**${toInlineMarkdown(content).trim()}**`;
|
|
1029
|
+
}).replace(/<em\b[^>]*>([\s\S]*?)<\/em>/gi, (_, content) => {
|
|
1030
|
+
return `_${toInlineMarkdown(content).trim()}_`;
|
|
1031
|
+
}).replace(/<i\b[^>]*>([\s\S]*?)<\/i>/gi, (_, content) => {
|
|
1032
|
+
return `_${toInlineMarkdown(content).trim()}_`;
|
|
1033
|
+
}).replace(/<code\b[^>]*>([\s\S]*?)<\/code>/gi, (_, content) => {
|
|
1034
|
+
return `\`${stripTags(content).trim()}\``;
|
|
1035
|
+
}).replace(/<br\s*\/?>/gi, "\n").replace(BLOCK_TAGS, " ").replace(BOX_TAGS, " ");
|
|
1036
|
+
return stripTags(source).replace(/\s+/g, " ");
|
|
1037
|
+
}
|
|
1038
|
+
__name(toInlineMarkdown, "toInlineMarkdown");
|
|
1039
|
+
var VOID_TAGS = /* @__PURE__ */ new Set([
|
|
1040
|
+
"area",
|
|
1041
|
+
"base",
|
|
1042
|
+
"br",
|
|
1043
|
+
"col",
|
|
1044
|
+
"embed",
|
|
1045
|
+
"hr",
|
|
1046
|
+
"img",
|
|
1047
|
+
"input",
|
|
1048
|
+
"link",
|
|
1049
|
+
"meta",
|
|
1050
|
+
"source",
|
|
1051
|
+
"track",
|
|
1052
|
+
"wbr"
|
|
1053
|
+
]);
|
|
1054
|
+
function removeHiddenElements(html) {
|
|
1055
|
+
const openingTag = /<([a-zA-Z][\w:-]*)((?:[^>"']|"[^"]*"|'[^']*')*)>/g;
|
|
1056
|
+
let closingTags;
|
|
1057
|
+
let result = "";
|
|
1058
|
+
let cursor = 0;
|
|
1059
|
+
let match;
|
|
1060
|
+
while (match = openingTag.exec(html)) {
|
|
1061
|
+
const [tag, name, attributes] = match;
|
|
1062
|
+
if (!isHiddenElement(attributes)) continue;
|
|
1063
|
+
const start = match.index;
|
|
1064
|
+
let end = start + tag.length;
|
|
1065
|
+
if (!VOID_TAGS.has(name.toLowerCase()) && !isSelfClosingTag(tag)) {
|
|
1066
|
+
closingTags ?? (closingTags = matchClosingTags(html));
|
|
1067
|
+
const close = closingTags.get(start);
|
|
1068
|
+
if (close === void 0) continue;
|
|
1069
|
+
end = close;
|
|
1070
|
+
}
|
|
1071
|
+
result += html.slice(cursor, start);
|
|
1072
|
+
cursor = end;
|
|
1073
|
+
openingTag.lastIndex = end;
|
|
1074
|
+
}
|
|
1075
|
+
return result + html.slice(cursor);
|
|
1076
|
+
}
|
|
1077
|
+
__name(removeHiddenElements, "removeHiddenElements");
|
|
1078
|
+
function isHiddenElement(attributes) {
|
|
1079
|
+
const attribute = /([^\s"'=<>/]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
|
|
1080
|
+
let match;
|
|
1081
|
+
while (match = attribute.exec(attributes)) {
|
|
1082
|
+
const name = match[1].toLowerCase();
|
|
1083
|
+
const value = (match[2] ?? match[3] ?? match[4] ?? "").trim().toLowerCase();
|
|
1084
|
+
if (name === "aria-hidden" && value === "true") return true;
|
|
1085
|
+
if (name === "hidden" && value !== "until-found") return true;
|
|
1086
|
+
}
|
|
1087
|
+
return false;
|
|
1088
|
+
}
|
|
1089
|
+
__name(isHiddenElement, "isHiddenElement");
|
|
1090
|
+
function isSelfClosingTag(tag) {
|
|
1091
|
+
return /(?:^<[a-zA-Z][\w:-]*|[\s"'])\/>$/.test(tag);
|
|
1092
|
+
}
|
|
1093
|
+
__name(isSelfClosingTag, "isSelfClosingTag");
|
|
1094
|
+
function matchClosingTags(html) {
|
|
1095
|
+
const tags = /<(\/?)([a-zA-Z][\w:-]*)(?:[^>"']|"[^"]*"|'[^']*')*>/g;
|
|
1096
|
+
const open = /* @__PURE__ */ new Map();
|
|
1097
|
+
const closes = /* @__PURE__ */ new Map();
|
|
1098
|
+
let match;
|
|
1099
|
+
while (match = tags.exec(html)) {
|
|
1100
|
+
const name = match[2].toLowerCase();
|
|
1101
|
+
if (match[1]) {
|
|
1102
|
+
const start = open.get(name)?.pop();
|
|
1103
|
+
if (start !== void 0) closes.set(start, match.index + match[0].length);
|
|
1104
|
+
} else if (!VOID_TAGS.has(name) && !isSelfClosingTag(match[0])) {
|
|
1105
|
+
let starts = open.get(name);
|
|
1106
|
+
if (!starts) open.set(name, starts = []);
|
|
1107
|
+
starts.push(match.index);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
return closes;
|
|
1111
|
+
}
|
|
1112
|
+
__name(matchClosingTags, "matchClosingTags");
|
|
1113
|
+
function readStreamedBoundaryInstructions(html) {
|
|
1114
|
+
const boundaries = /* @__PURE__ */ new Map();
|
|
1115
|
+
const placeholders = /* @__PURE__ */ new Map();
|
|
1116
|
+
for (const [, script] of html.matchAll(/<script\b[^>]*>([\s\S]*?)<\/script>/gi)) {
|
|
1117
|
+
for (const [, kind, first, second] of script.matchAll(
|
|
1118
|
+
/\$R([CRS])\("([^"\\]+)","([^"\\]+)"/g
|
|
1119
|
+
)) {
|
|
1120
|
+
if (kind === "S") placeholders.set(second, first);
|
|
1121
|
+
else boundaries.set(first, second);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
return { boundaries, placeholders };
|
|
1125
|
+
}
|
|
1126
|
+
__name(readStreamedBoundaryInstructions, "readStreamedBoundaryInstructions");
|
|
1127
|
+
function resolveStreamedBoundaries(html, { boundaries, placeholders }) {
|
|
1128
|
+
if (boundaries.size === 0 && placeholders.size === 0) return html;
|
|
1129
|
+
const wanted = /* @__PURE__ */ new Set([...boundaries.values(), ...placeholders.values()]);
|
|
1130
|
+
const segments = /* @__PURE__ */ new Map();
|
|
1131
|
+
const closingTags = matchClosingTags(html);
|
|
1132
|
+
const openingTag = /<([a-zA-Z][\w:-]*)((?:[^>"']|"[^"]*"|'[^']*')*)>/g;
|
|
1133
|
+
let page = "";
|
|
1134
|
+
let cursor = 0;
|
|
1135
|
+
let match;
|
|
1136
|
+
while (match = openingTag.exec(html)) {
|
|
1137
|
+
const id = /\sid="([^"]*)"/.exec(match[2])?.[1];
|
|
1138
|
+
if (!id || !wanted.has(id) || segments.has(id)) continue;
|
|
1139
|
+
const close = closingTags.get(match.index);
|
|
1140
|
+
if (close === void 0) continue;
|
|
1141
|
+
const contentStart = match.index + match[0].length;
|
|
1142
|
+
segments.set(id, html.slice(contentStart, html.lastIndexOf("<", close - 1)));
|
|
1143
|
+
page += html.slice(cursor, match.index);
|
|
1144
|
+
cursor = close;
|
|
1145
|
+
openingTag.lastIndex = close;
|
|
1146
|
+
}
|
|
1147
|
+
page += html.slice(cursor);
|
|
1148
|
+
const used = /* @__PURE__ */ new Set();
|
|
1149
|
+
const expand = /* @__PURE__ */ __name((fragment) => {
|
|
1150
|
+
const template = /<template\b[^>]*\sid="([^"]+)"[^>]*>\s*<\/template>/gi;
|
|
1151
|
+
let output = "";
|
|
1152
|
+
let position = 0;
|
|
1153
|
+
let found;
|
|
1154
|
+
while (found = template.exec(fragment)) {
|
|
1155
|
+
const id = found[1];
|
|
1156
|
+
const segmentId = boundaries.get(id) ?? placeholders.get(id);
|
|
1157
|
+
const segment = segmentId === void 0 ? void 0 : segments.get(segmentId);
|
|
1158
|
+
if (segment === void 0 || used.has(segmentId)) continue;
|
|
1159
|
+
used.add(segmentId);
|
|
1160
|
+
output += fragment.slice(position, found.index) + expand(segment);
|
|
1161
|
+
position = found.index + found[0].length;
|
|
1162
|
+
if (boundaries.has(id)) {
|
|
1163
|
+
const marker = /<!--(\/?)(?:\$[?!~]?|&)-->/g;
|
|
1164
|
+
marker.lastIndex = position;
|
|
1165
|
+
let depth = 0;
|
|
1166
|
+
let end;
|
|
1167
|
+
while (end = marker.exec(fragment)) {
|
|
1168
|
+
if (!end[1]) depth += 1;
|
|
1169
|
+
else if (depth === 0) break;
|
|
1170
|
+
else depth -= 1;
|
|
1171
|
+
}
|
|
1172
|
+
if (end) position = end.index;
|
|
1173
|
+
}
|
|
1174
|
+
template.lastIndex = position;
|
|
1175
|
+
}
|
|
1176
|
+
return output + fragment.slice(position);
|
|
1177
|
+
}, "expand");
|
|
1178
|
+
return expand(page);
|
|
1179
|
+
}
|
|
1180
|
+
__name(resolveStreamedBoundaries, "resolveStreamedBoundaries");
|
|
1181
|
+
function stripTags(input) {
|
|
1182
|
+
return input.replace(/<[^>]+>/g, "");
|
|
1183
|
+
}
|
|
1184
|
+
__name(stripTags, "stripTags");
|
|
1185
|
+
function escapeHtmlText(input) {
|
|
1186
|
+
return input.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1187
|
+
}
|
|
1188
|
+
__name(escapeHtmlText, "escapeHtmlText");
|
|
1189
|
+
function decodeHtml(input) {
|
|
1190
|
+
return input.replace(/&(?:nbsp|amp|lt|gt|quot|#39|#(?:x|X)[0-9a-fA-F]+|#[0-9]+);/g, (entity) => {
|
|
1191
|
+
switch (entity) {
|
|
1192
|
+
case " ":
|
|
1193
|
+
return " ";
|
|
1194
|
+
case "&":
|
|
1195
|
+
return "&";
|
|
1196
|
+
case "<":
|
|
1197
|
+
return "<";
|
|
1198
|
+
case ">":
|
|
1199
|
+
return ">";
|
|
1200
|
+
case """:
|
|
1201
|
+
return '"';
|
|
1202
|
+
case "'":
|
|
1203
|
+
return "'";
|
|
1204
|
+
}
|
|
1205
|
+
const hexadecimal = entity[2] === "x" || entity[2] === "X";
|
|
1206
|
+
const codePoint = Number.parseInt(entity.slice(hexadecimal ? 3 : 2, -1), hexadecimal ? 16 : 10);
|
|
1207
|
+
return codePoint === 0 || codePoint > 1114111 || codePoint >= 55296 && codePoint <= 57343 ? "\uFFFD" : String.fromCodePoint(codePoint);
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
__name(decodeHtml, "decodeHtml");
|
|
1211
|
+
|
|
1212
|
+
export {
|
|
1213
|
+
createFarmMarkdownViewResponse,
|
|
1214
|
+
FARM_MARKDOWN_CONTENT_TYPE,
|
|
1215
|
+
farmRequestWantsMarkdown,
|
|
1216
|
+
createFarmMarkdownErrorBody,
|
|
1217
|
+
resolveMarkdownConfig,
|
|
1218
|
+
resolveMarkdownMirrorTarget,
|
|
1219
|
+
createMarkdownMirrorResponse,
|
|
1220
|
+
applyMarkdownNegotiationHeaders,
|
|
1221
|
+
htmlToMarkdown,
|
|
1222
|
+
farmAcceptQuality,
|
|
1223
|
+
requestAcceptsMarkdown,
|
|
1224
|
+
getFarmMarkdownMirrorPath,
|
|
1225
|
+
getFarmMarkdownAlternatePath,
|
|
1226
|
+
isFarmMarkdownMirrorExposed,
|
|
1227
|
+
matchesFarmMarkdownRoutePattern,
|
|
1228
|
+
extractFarmPageActions
|
|
1229
|
+
};
|