@dogsbay/search 0.0.0-stage β 0.2.0-beta.117
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/README.md +78 -2
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +24 -0
- package/dist/cli.js.map +1 -0
- package/dist/detach.d.ts +10 -0
- package/dist/detach.js +7 -0
- package/dist/detach.js.map +1 -0
- package/dist/dogsbay-search.iife.js +2 -0
- package/dist/dogsbay-search.iife.js.map +1 -0
- package/dist/entry.d.ts +6 -0
- package/dist/entry.js +38 -0
- package/dist/entry.js.map +1 -0
- package/dist/format.d.ts +51 -0
- package/dist/format.js +29 -0
- package/dist/format.js.map +1 -0
- package/dist/html.d.ts +45 -0
- package/dist/html.js +351 -0
- package/dist/html.js.map +1 -0
- package/dist/index.d.ts +94 -0
- package/dist/index.js +208 -0
- package/dist/index.js.map +1 -0
- package/dist/query.d.ts +120 -0
- package/dist/query.js +334 -0
- package/dist/query.js.map +1 -0
- package/dist/site.d.ts +19 -0
- package/dist/site.js +61 -0
- package/dist/site.js.map +1 -0
- package/dist/text.d.ts +12 -0
- package/dist/text.js +53 -0
- package/dist/text.js.map +1 -0
- package/package.json +76 -3
package/dist/html.js
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
const VOID = /* @__PURE__ */ new Set(["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"]);
|
|
2
|
+
const SKIP = /* @__PURE__ */ new Set(["script", "style", "noscript", "template", "svg", "nav", "aside", "form", "button", "select", "pre", "textarea", "footer"]);
|
|
3
|
+
const BLOCK = /^(p|div|li|ul|ol|dl|dt|dd|h[1-6]|tr|td|th|table|section|article|main|header|blockquote|figure|figcaption|details|summary|br|hr)$/;
|
|
4
|
+
const CLOSES_P = /^(p|div|ul|ol|dl|h[1-6]|table|section|article|main|header|footer|nav|aside|blockquote|figure|details|pre|hr|form)$/;
|
|
5
|
+
function* tokens(html) {
|
|
6
|
+
const re = /<!--[\s\S]*?(?:-->|$)|<!\[CDATA\[[\s\S]*?(?:\]\]>|$)|<![^>]*>|<\?[^>]*>|<(script|style|template|textarea)\b((?:[^>"']|"[^"]*"|'[^']*')*)>([\s\S]*?)(?:<\/\1\s*>|$)|<(\/?)([a-zA-Z][\w:.-]*)((?:[^>"']|"[^"]*"|'[^']*')*)>|([^<]+)|</gi;
|
|
7
|
+
for (const m of html.matchAll(re)) {
|
|
8
|
+
const [whole, rawName, rawAttrs, , slash, name, attrs, text] = m;
|
|
9
|
+
if (text !== void 0) yield { kind: "text", text };
|
|
10
|
+
else if (whole === "<") yield { kind: "text", text: "<" };
|
|
11
|
+
else if (rawName) {
|
|
12
|
+
const n = rawName.toLowerCase();
|
|
13
|
+
yield { kind: "open", name: n, attrs: parseAttrs(rawAttrs ?? ""), selfClosing: false };
|
|
14
|
+
yield { kind: "close", name: n };
|
|
15
|
+
} else if (name) {
|
|
16
|
+
const n = name.toLowerCase();
|
|
17
|
+
if (slash) yield { kind: "close", name: n };
|
|
18
|
+
else yield { kind: "open", name: n, attrs: parseAttrs(attrs ?? ""), selfClosing: /(^|\s|["'])\/$/.test((attrs ?? "").trim()) };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function parseAttrs(s) {
|
|
23
|
+
const out = /* @__PURE__ */ new Map();
|
|
24
|
+
for (const m of s.matchAll(/([^\s"'>\/=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+)))?/g)) {
|
|
25
|
+
const name = m[1].toLowerCase();
|
|
26
|
+
if (!out.has(name)) out.set(name, decode(m[2] ?? m[3] ?? m[4] ?? ""));
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
function extractHtml(html, url, opts = {}) {
|
|
31
|
+
const all = [...tokens(html)];
|
|
32
|
+
let lang;
|
|
33
|
+
let titleTag = "";
|
|
34
|
+
const metas = /* @__PURE__ */ new Map();
|
|
35
|
+
let refresh = false;
|
|
36
|
+
let inTitle = false;
|
|
37
|
+
let bodyAt = all.length;
|
|
38
|
+
for (let i = 0; i < all.length; i++) {
|
|
39
|
+
const t = all[i];
|
|
40
|
+
if (t.kind === "open" && t.name === "body") {
|
|
41
|
+
bodyAt = i;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
if (t.kind === "open" && t.name === "html") lang = t.attrs.get("lang") || void 0;
|
|
45
|
+
else if (t.kind === "open" && t.name === "title") inTitle = true;
|
|
46
|
+
else if (t.kind === "close" && t.name === "title") inTitle = false;
|
|
47
|
+
else if (t.kind === "text" && inTitle) titleTag += t.text;
|
|
48
|
+
else if (t.kind === "open" && t.name === "meta") {
|
|
49
|
+
if ((t.attrs.get("http-equiv") ?? "").toLowerCase() === "refresh") refresh = true;
|
|
50
|
+
const name = (t.attrs.get("name") ?? "").toLowerCase();
|
|
51
|
+
if (name) metas.set(name, t.attrs.get("content") ?? "");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (refresh || /\bnoindex\b/i.test(metas.get("robots") ?? "")) return void 0;
|
|
55
|
+
const marker = all.some((t) => t.kind === "open" && t.attrs.has("data-search-body")) ? "data-search-body" : all.some((t) => t.kind === "open" && t.attrs.has("data-pagefind-body")) ? "data-pagefind-body" : void 0;
|
|
56
|
+
const has = (n) => all.some((t) => t.kind === "open" && t.name === n);
|
|
57
|
+
const rootTag = marker ? void 0 : has("main") ? "main" : has("article") ? "article" : "body";
|
|
58
|
+
const stack = [];
|
|
59
|
+
let skipDepth = 0;
|
|
60
|
+
let rootDepth = 0;
|
|
61
|
+
let rootSeen = false;
|
|
62
|
+
let heading;
|
|
63
|
+
let tagItem;
|
|
64
|
+
let tagListDepth = 0;
|
|
65
|
+
const body = [];
|
|
66
|
+
const headings = [];
|
|
67
|
+
const anchors = [];
|
|
68
|
+
const tags = [];
|
|
69
|
+
let h1;
|
|
70
|
+
const push = (o) => {
|
|
71
|
+
stack.push(o);
|
|
72
|
+
if (o.skip) skipDepth++;
|
|
73
|
+
if (o.root) rootDepth++;
|
|
74
|
+
if (o.heading) heading = o.heading;
|
|
75
|
+
if (o.tagList) tagListDepth++;
|
|
76
|
+
if (o.tagItem) tagItem = o.tagItem;
|
|
77
|
+
};
|
|
78
|
+
const pop = () => {
|
|
79
|
+
const o = stack.pop();
|
|
80
|
+
if (o.skip) skipDepth--;
|
|
81
|
+
if (o.root) rootDepth--;
|
|
82
|
+
if (o.tagList) tagListDepth--;
|
|
83
|
+
if (BLOCK.test(o.name)) (heading ? heading.text : body).push(" ");
|
|
84
|
+
if (o.permalink && /^\s*[#ΒΆπΒ§]?\s*$/u.test(o.permalink.heading.slice(o.permalink.from).join(""))) o.permalink.heading.length = o.permalink.from;
|
|
85
|
+
if (o.tagItem) {
|
|
86
|
+
const t = squash(o.tagItem.join("")).replace(/^#\s*/, "");
|
|
87
|
+
if (t) tags.push(t);
|
|
88
|
+
tagItem = void 0;
|
|
89
|
+
}
|
|
90
|
+
if (o.heading) {
|
|
91
|
+
heading = void 0;
|
|
92
|
+
const text = squash(o.heading.text.join("")).replace(/\s*[ΒΆπ]$/u, "");
|
|
93
|
+
if (!text) return;
|
|
94
|
+
if (o.heading.level === 1 && h1 === void 0) h1 = text;
|
|
95
|
+
else {
|
|
96
|
+
headings.push(text);
|
|
97
|
+
if (o.heading.level <= 3 && o.heading.id) anchors.push([text, o.heading.id]);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
const closeTo = (names, limit = Infinity) => {
|
|
102
|
+
for (let i = stack.length - 1, n = 0; i >= 0 && n < limit; i--, n++) {
|
|
103
|
+
if (names.test(stack[i].name)) {
|
|
104
|
+
while (stack.length > i) pop();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (/^(ul|ol|dl|table|body|main|article|section|div)$/.test(stack[i].name)) return;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
for (let i = bodyAt; i < all.length; i++) {
|
|
111
|
+
const t = all[i];
|
|
112
|
+
if (t.kind === "text") {
|
|
113
|
+
if (!rootDepth || skipDepth) continue;
|
|
114
|
+
const text = decode(t.text);
|
|
115
|
+
(heading ? heading.text : body).push(text);
|
|
116
|
+
tagItem?.push(text);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (t.kind === "close") {
|
|
120
|
+
let j = stack.length - 1;
|
|
121
|
+
while (j >= 0 && stack[j].name !== t.name) j--;
|
|
122
|
+
if (j >= 0) while (stack.length > j) pop();
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const { name, attrs } = t;
|
|
126
|
+
if (CLOSES_P.test(name)) closeTo(/^p$/, 64);
|
|
127
|
+
if (name === "li") closeTo(/^li$/);
|
|
128
|
+
else if (name === "dt" || name === "dd") closeTo(/^d[td]$/);
|
|
129
|
+
else if (name === "tr") closeTo(/^tr$/);
|
|
130
|
+
else if (name === "td" || name === "th") closeTo(/^t[dh]$/);
|
|
131
|
+
else if (name === "option") closeTo(/^option$/);
|
|
132
|
+
if (name === "img" && rootDepth && !skipDepth) {
|
|
133
|
+
const alt = attrs.get("alt");
|
|
134
|
+
if (alt) (heading ? heading.text : body).push(` ${alt} `);
|
|
135
|
+
}
|
|
136
|
+
if (VOID.has(name) || t.selfClosing) {
|
|
137
|
+
if ((name === "br" || name === "hr") && rootDepth) (heading ? heading.text : body).push(" ");
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const ignore = attrs.has("data-search-ignore") || attrs.has("data-pagefind-ignore") || attrs.get("aria-hidden") === "true";
|
|
141
|
+
const isRoot = !rootSeen && !skipDepth && !ignore && (marker ? attrs.has(marker) : name === rootTag);
|
|
142
|
+
if (isRoot) rootSeen = true;
|
|
143
|
+
const skip = (SKIP.has(name) || name === "header" && !rootDepth && !isRoot || ignore) && !isRoot;
|
|
144
|
+
const o = { name, root: isRoot, skip };
|
|
145
|
+
const level = /^h([1-6])$/.exec(name)?.[1];
|
|
146
|
+
if (level && !heading) o.heading = { level: Number(level), id: attrs.get("id") || void 0, text: [] };
|
|
147
|
+
if (name === "a" && heading && (attrs.get("href") ?? "").startsWith("#")) o.permalink = { heading: heading.text, from: heading.text.length };
|
|
148
|
+
if (attrs.has("data-tag-list")) o.tagList = true;
|
|
149
|
+
if (name === "li" && tagListDepth) o.tagItem = [];
|
|
150
|
+
if (BLOCK.test(name)) (heading ? heading.text : body).push(" ");
|
|
151
|
+
push(o);
|
|
152
|
+
}
|
|
153
|
+
while (stack.length) pop();
|
|
154
|
+
const bodyText = squash(body.join(""));
|
|
155
|
+
const title = h1 ?? squash(decode(titleTag));
|
|
156
|
+
if (!bodyText && !title) return void 0;
|
|
157
|
+
const keywords = (metas.get("keywords") ?? "").split(",").map((k) => k.trim()).filter(Boolean);
|
|
158
|
+
return {
|
|
159
|
+
doc: { title: title || url, url, section: opts.section ?? folderName(url), excerpt: excerpt(metas.get("description") || bodyText), anchors, tags },
|
|
160
|
+
parts: { title: title || url, headings, body: bodyText, keywords: [...keywords, ...tags] },
|
|
161
|
+
lang
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function squash(s) {
|
|
165
|
+
return s.replace(/\s+/g, " ").trim();
|
|
166
|
+
}
|
|
167
|
+
function excerpt(s) {
|
|
168
|
+
if (s.length <= 240) return s;
|
|
169
|
+
const cut = s.slice(0, 240);
|
|
170
|
+
return cut.slice(0, cut.lastIndexOf(" ") > 180 ? cut.lastIndexOf(" ") : 240) + "\u2026";
|
|
171
|
+
}
|
|
172
|
+
function folderName(url) {
|
|
173
|
+
const parts = url.split("/");
|
|
174
|
+
if (parts.length < 2) return "";
|
|
175
|
+
let f = parts[0];
|
|
176
|
+
try {
|
|
177
|
+
f = decodeURIComponent(f);
|
|
178
|
+
} catch {
|
|
179
|
+
}
|
|
180
|
+
f = f.replace(/[-_]+/g, " ");
|
|
181
|
+
return f.charAt(0).toUpperCase() + f.slice(1);
|
|
182
|
+
}
|
|
183
|
+
const NAMED = {
|
|
184
|
+
quot: 34,
|
|
185
|
+
amp: 38,
|
|
186
|
+
apos: 39,
|
|
187
|
+
lt: 60,
|
|
188
|
+
gt: 62,
|
|
189
|
+
nbsp: 160,
|
|
190
|
+
iexcl: 161,
|
|
191
|
+
cent: 162,
|
|
192
|
+
pound: 163,
|
|
193
|
+
curren: 164,
|
|
194
|
+
yen: 165,
|
|
195
|
+
brvbar: 166,
|
|
196
|
+
sect: 167,
|
|
197
|
+
uml: 168,
|
|
198
|
+
copy: 169,
|
|
199
|
+
ordf: 170,
|
|
200
|
+
laquo: 171,
|
|
201
|
+
not: 172,
|
|
202
|
+
shy: 173,
|
|
203
|
+
reg: 174,
|
|
204
|
+
macr: 175,
|
|
205
|
+
deg: 176,
|
|
206
|
+
plusmn: 177,
|
|
207
|
+
sup2: 178,
|
|
208
|
+
sup3: 179,
|
|
209
|
+
acute: 180,
|
|
210
|
+
micro: 181,
|
|
211
|
+
para: 182,
|
|
212
|
+
middot: 183,
|
|
213
|
+
cedil: 184,
|
|
214
|
+
sup1: 185,
|
|
215
|
+
ordm: 186,
|
|
216
|
+
raquo: 187,
|
|
217
|
+
frac14: 188,
|
|
218
|
+
frac12: 189,
|
|
219
|
+
frac34: 190,
|
|
220
|
+
iquest: 191,
|
|
221
|
+
Agrave: 192,
|
|
222
|
+
Aacute: 193,
|
|
223
|
+
Acirc: 194,
|
|
224
|
+
Atilde: 195,
|
|
225
|
+
Auml: 196,
|
|
226
|
+
Aring: 197,
|
|
227
|
+
AElig: 198,
|
|
228
|
+
Ccedil: 199,
|
|
229
|
+
Egrave: 200,
|
|
230
|
+
Eacute: 201,
|
|
231
|
+
Ecirc: 202,
|
|
232
|
+
Euml: 203,
|
|
233
|
+
Igrave: 204,
|
|
234
|
+
Iacute: 205,
|
|
235
|
+
Icirc: 206,
|
|
236
|
+
Iuml: 207,
|
|
237
|
+
ETH: 208,
|
|
238
|
+
Ntilde: 209,
|
|
239
|
+
Ograve: 210,
|
|
240
|
+
Oacute: 211,
|
|
241
|
+
Ocirc: 212,
|
|
242
|
+
Otilde: 213,
|
|
243
|
+
Ouml: 214,
|
|
244
|
+
times: 215,
|
|
245
|
+
Oslash: 216,
|
|
246
|
+
Ugrave: 217,
|
|
247
|
+
Uacute: 218,
|
|
248
|
+
Ucirc: 219,
|
|
249
|
+
Uuml: 220,
|
|
250
|
+
Yacute: 221,
|
|
251
|
+
THORN: 222,
|
|
252
|
+
szlig: 223,
|
|
253
|
+
agrave: 224,
|
|
254
|
+
aacute: 225,
|
|
255
|
+
acirc: 226,
|
|
256
|
+
atilde: 227,
|
|
257
|
+
auml: 228,
|
|
258
|
+
aring: 229,
|
|
259
|
+
aelig: 230,
|
|
260
|
+
ccedil: 231,
|
|
261
|
+
egrave: 232,
|
|
262
|
+
eacute: 233,
|
|
263
|
+
ecirc: 234,
|
|
264
|
+
euml: 235,
|
|
265
|
+
igrave: 236,
|
|
266
|
+
iacute: 237,
|
|
267
|
+
icirc: 238,
|
|
268
|
+
iuml: 239,
|
|
269
|
+
eth: 240,
|
|
270
|
+
ntilde: 241,
|
|
271
|
+
ograve: 242,
|
|
272
|
+
oacute: 243,
|
|
273
|
+
ocirc: 244,
|
|
274
|
+
otilde: 245,
|
|
275
|
+
ouml: 246,
|
|
276
|
+
divide: 247,
|
|
277
|
+
oslash: 248,
|
|
278
|
+
ugrave: 249,
|
|
279
|
+
uacute: 250,
|
|
280
|
+
ucirc: 251,
|
|
281
|
+
uuml: 252,
|
|
282
|
+
yacute: 253,
|
|
283
|
+
thorn: 254,
|
|
284
|
+
yuml: 255,
|
|
285
|
+
OElig: 338,
|
|
286
|
+
oelig: 339,
|
|
287
|
+
Scaron: 352,
|
|
288
|
+
scaron: 353,
|
|
289
|
+
Yuml: 376,
|
|
290
|
+
fnof: 402,
|
|
291
|
+
circ: 710,
|
|
292
|
+
tilde: 732,
|
|
293
|
+
ensp: 8194,
|
|
294
|
+
emsp: 8195,
|
|
295
|
+
thinsp: 8201,
|
|
296
|
+
zwnj: 8204,
|
|
297
|
+
zwj: 8205,
|
|
298
|
+
lrm: 8206,
|
|
299
|
+
rlm: 8207,
|
|
300
|
+
ndash: 8211,
|
|
301
|
+
mdash: 8212,
|
|
302
|
+
lsquo: 8216,
|
|
303
|
+
rsquo: 8217,
|
|
304
|
+
sbquo: 8218,
|
|
305
|
+
ldquo: 8220,
|
|
306
|
+
rdquo: 8221,
|
|
307
|
+
bdquo: 8222,
|
|
308
|
+
dagger: 8224,
|
|
309
|
+
Dagger: 8225,
|
|
310
|
+
bull: 8226,
|
|
311
|
+
hellip: 8230,
|
|
312
|
+
permil: 8240,
|
|
313
|
+
prime: 8242,
|
|
314
|
+
Prime: 8243,
|
|
315
|
+
lsaquo: 8249,
|
|
316
|
+
rsaquo: 8250,
|
|
317
|
+
euro: 8364,
|
|
318
|
+
trade: 8482,
|
|
319
|
+
larr: 8592,
|
|
320
|
+
uarr: 8593,
|
|
321
|
+
rarr: 8594,
|
|
322
|
+
darr: 8595,
|
|
323
|
+
harr: 8596,
|
|
324
|
+
rArr: 8658,
|
|
325
|
+
lArr: 8656,
|
|
326
|
+
hArr: 8660,
|
|
327
|
+
minus: 8722,
|
|
328
|
+
le: 8804,
|
|
329
|
+
ge: 8805,
|
|
330
|
+
ne: 8800,
|
|
331
|
+
asymp: 8776,
|
|
332
|
+
infin: 8734,
|
|
333
|
+
check: 10003,
|
|
334
|
+
hearts: 9829
|
|
335
|
+
};
|
|
336
|
+
function decode(s) {
|
|
337
|
+
if (!s.includes("&")) return s;
|
|
338
|
+
return s.replace(/&(#x[0-9a-f]+|#\d+|[a-z][a-z0-9]*);/gi, (m, e) => {
|
|
339
|
+
if (e[0] === "#") {
|
|
340
|
+
const n2 = e[1] === "x" || e[1] === "X" ? parseInt(e.slice(2), 16) : Number(e.slice(1));
|
|
341
|
+
return Number.isFinite(n2) && n2 > 0 && n2 < 1114112 ? String.fromCodePoint(n2) : m;
|
|
342
|
+
}
|
|
343
|
+
const n = NAMED[e] ?? NAMED[e.toLowerCase()];
|
|
344
|
+
return n === void 0 ? m : String.fromCodePoint(n);
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
export {
|
|
348
|
+
extractHtml,
|
|
349
|
+
folderName
|
|
350
|
+
};
|
|
351
|
+
//# sourceMappingURL=html.js.map
|
package/dist/html.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/html.ts"],"sourcesContent":["/**\n * Index built HTML, from any site generator: extract a page's title,\n * headings, body text, keywords and tags from its markup, for\n * SearchIndexBuilder.add(). A small tokenizer, not a DOM, so it runs in a\n * Worker and on large sites without a parser dependency.\n *\n * What is indexed:\n * root the element with a `data-search-body` attribute (or Pagefind's\n * `data-pagefind-body`), else the first <main>, else <article>,\n * else <body>; never one inside a skipped element\n * skipped script, style, template, noscript, svg, nav, aside, form,\n * button, select, textarea, pre (code blocks), footer, header\n * outside the root, aria-hidden content, and anything with\n * `data-search-ignore` or `data-pagefind-ignore`\n * title the first <h1> in the root, else <title>; later h1s are headings\n * headings h2βh6 (and later h1s) in the root; h2/h3 with an id are anchors\n * keywords <meta name=\"keywords\">, and tag lists (`data-tag-list`)\n * A page with a meta refresh or a robots `noindex`, or with neither a\n * title nor body text, is not indexed.\n */\nimport type { SearchDoc } from \"./index.js\";\n\nexport interface HtmlPage {\n doc: SearchDoc;\n parts: { title: string; headings: string[]; body: string; keywords: string[] };\n lang?: string;\n}\n\ntype Attrs = Map<string, string>;\ntype Token = { kind: \"open\"; name: string; attrs: Attrs; selfClosing: boolean } | { kind: \"close\"; name: string } | { kind: \"text\"; text: string };\n\nconst VOID = new Set([\"area\", \"base\", \"br\", \"col\", \"embed\", \"hr\", \"img\", \"input\", \"link\", \"meta\", \"param\", \"source\", \"track\", \"wbr\"]);\nconst SKIP = new Set([\"script\", \"style\", \"noscript\", \"template\", \"svg\", \"nav\", \"aside\", \"form\", \"button\", \"select\", \"pre\", \"textarea\", \"footer\"]);\nconst BLOCK = /^(p|div|li|ul|ol|dl|dt|dd|h[1-6]|tr|td|th|table|section|article|main|header|blockquote|figure|figcaption|details|summary|br|hr)$/;\n/** Elements that close an open <p> (the HTML parser's implied end tag). */\nconst CLOSES_P = /^(p|div|ul|ol|dl|h[1-6]|table|section|article|main|header|footer|nav|aside|blockquote|figure|details|pre|hr|form)$/;\n\n/**\n * Tokens of a document: tags with parsed attributes, and text. Comments,\n * CDATA, doctypes and the contents of script, style and template are left\n * out here, so nothing inside them is ever mistaken for markup.\n */\nfunction* tokens(html: string): Generator<Token> {\n const re = /<!--[\\s\\S]*?(?:-->|$)|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>|$)|<![^>]*>|<\\?[^>]*>|<(script|style|template|textarea)\\b((?:[^>\"']|\"[^\"]*\"|'[^']*')*)>([\\s\\S]*?)(?:<\\/\\1\\s*>|$)|<(\\/?)([a-zA-Z][\\w:.-]*)((?:[^>\"']|\"[^\"]*\"|'[^']*')*)>|([^<]+)|</gi;\n for (const m of html.matchAll(re)) {\n const [whole, rawName, rawAttrs, , slash, name, attrs, text] = m;\n if (text !== undefined) yield { kind: \"text\", text };\n else if (whole === \"<\") yield { kind: \"text\", text: \"<\" };\n else if (rawName) {\n // Kept as an element with no content, so skip rules and structure still apply.\n const n = rawName.toLowerCase();\n yield { kind: \"open\", name: n, attrs: parseAttrs(rawAttrs ?? \"\"), selfClosing: false };\n yield { kind: \"close\", name: n };\n } else if (name) {\n const n = name.toLowerCase();\n if (slash) yield { kind: \"close\", name: n };\n else yield { kind: \"open\", name: n, attrs: parseAttrs(attrs ?? \"\"), selfClosing: /(^|\\s|[\"'])\\/$/.test((attrs ?? \"\").trim()) };\n }\n }\n}\n\nfunction parseAttrs(s: string): Attrs {\n const out: Attrs = new Map();\n for (const m of s.matchAll(/([^\\s\"'>\\/=]+)(?:\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s\"'>]+)))?/g)) {\n const name = m[1].toLowerCase();\n if (!out.has(name)) out.set(name, decode(m[2] ?? m[3] ?? m[4] ?? \"\"));\n }\n return out;\n}\n\ninterface Open {\n name: string;\n skip: boolean;\n root: boolean;\n heading?: { level: number; id?: string; text: string[] };\n /** A heading's permalink (<a href=\"#β¦\">): its text is dropped when it is only a mark. */\n permalink?: { heading: string[]; from: number };\n tagList?: boolean;\n tagItem?: string[];\n}\n\n/**\n * Extract one page. `url` is the page's URL relative to the site (what\n * results link to); `section` defaults to its first folder.\n */\nexport function extractHtml(html: string, url: string, opts: { section?: string } = {}): HtmlPage | undefined {\n const all = [...tokens(html)];\n\n // Head: <title>, <meta>, <html lang>. Everything before <body>, by token, not by text search.\n let lang: string | undefined;\n let titleTag = \"\";\n const metas = new Map<string, string>();\n let refresh = false;\n let inTitle = false;\n let bodyAt = all.length;\n for (let i = 0; i < all.length; i++) {\n const t = all[i];\n if (t.kind === \"open\" && t.name === \"body\") {\n bodyAt = i;\n break;\n }\n if (t.kind === \"open\" && t.name === \"html\") lang = t.attrs.get(\"lang\") || undefined;\n else if (t.kind === \"open\" && t.name === \"title\") inTitle = true;\n else if (t.kind === \"close\" && t.name === \"title\") inTitle = false;\n else if (t.kind === \"text\" && inTitle) titleTag += t.text;\n else if (t.kind === \"open\" && t.name === \"meta\") {\n if ((t.attrs.get(\"http-equiv\") ?? \"\").toLowerCase() === \"refresh\") refresh = true;\n const name = (t.attrs.get(\"name\") ?? \"\").toLowerCase();\n if (name) metas.set(name, t.attrs.get(\"content\") ?? \"\");\n }\n }\n if (refresh || /\\bnoindex\\b/i.test(metas.get(\"robots\") ?? \"\")) return undefined;\n\n // The root: the marked element, else the first <main>, <article> or <body> that is not inside a skipped element.\n const marker = all.some((t) => t.kind === \"open\" && t.attrs.has(\"data-search-body\"))\n ? \"data-search-body\"\n : all.some((t) => t.kind === \"open\" && t.attrs.has(\"data-pagefind-body\"))\n ? \"data-pagefind-body\"\n : undefined;\n const has = (n: string) => all.some((t) => t.kind === \"open\" && t.name === n);\n const rootTag = marker ? undefined : has(\"main\") ? \"main\" : has(\"article\") ? \"article\" : \"body\";\n\n const stack: Open[] = [];\n let skipDepth = 0; // open skipped elements\n let rootDepth = 0; // open root elements (0 or 1)\n let rootSeen = false;\n let heading: Open[\"heading\"];\n let tagItem: string[] | undefined;\n let tagListDepth = 0;\n const body: string[] = [];\n const headings: string[] = [];\n const anchors: [string, string][] = [];\n const tags: string[] = [];\n let h1: string | undefined;\n\n const push = (o: Open) => {\n stack.push(o);\n if (o.skip) skipDepth++;\n if (o.root) rootDepth++;\n if (o.heading) heading = o.heading;\n if (o.tagList) tagListDepth++;\n if (o.tagItem) tagItem = o.tagItem;\n };\n const pop = () => {\n const o = stack.pop()!;\n if (o.skip) skipDepth--;\n if (o.root) rootDepth--;\n if (o.tagList) tagListDepth--;\n if (BLOCK.test(o.name)) (heading ? heading.text : body).push(\" \");\n if (o.permalink && /^\\s*[#ΒΆπΒ§]?\\s*$/u.test(o.permalink.heading.slice(o.permalink.from).join(\"\"))) o.permalink.heading.length = o.permalink.from;\n if (o.tagItem) {\n const t = squash(o.tagItem.join(\"\")).replace(/^#\\s*/, \"\");\n if (t) tags.push(t);\n tagItem = undefined;\n }\n if (o.heading) {\n heading = undefined;\n // A trailing ΒΆ or π is a permalink mark left as text; a trailing # is kept (it ends \"C#\").\n const text = squash(o.heading.text.join(\"\")).replace(/\\s*[ΒΆπ]$/u, \"\");\n if (!text) return;\n if (o.heading.level === 1 && h1 === undefined) h1 = text;\n else {\n headings.push(text);\n if (o.heading.level <= 3 && o.heading.id) anchors.push([text, o.heading.id]);\n }\n }\n };\n /** Close open elements down to and including the last one named `name`, if it is open (within `limit` elements). */\n const closeTo = (names: RegExp, limit = Infinity) => {\n for (let i = stack.length - 1, n = 0; i >= 0 && n < limit; i--, n++) {\n if (names.test(stack[i].name)) {\n while (stack.length > i) pop();\n return;\n }\n if (/^(ul|ol|dl|table|body|main|article|section|div)$/.test(stack[i].name)) return; // not across a container\n }\n };\n\n for (let i = bodyAt; i < all.length; i++) {\n const t = all[i];\n if (t.kind === \"text\") {\n if (!rootDepth || skipDepth) continue;\n const text = decode(t.text);\n (heading ? heading.text : body).push(text);\n tagItem?.push(text);\n continue;\n }\n if (t.kind === \"close\") {\n let j = stack.length - 1;\n while (j >= 0 && stack[j].name !== t.name) j--;\n if (j >= 0) while (stack.length > j) pop();\n continue;\n }\n const { name, attrs } = t;\n // Implied end tags, as the HTML parser applies them, so unclosed <p> and <li> do not nest without limit.\n if (CLOSES_P.test(name)) closeTo(/^p$/, 64);\n if (name === \"li\") closeTo(/^li$/);\n else if (name === \"dt\" || name === \"dd\") closeTo(/^d[td]$/);\n else if (name === \"tr\") closeTo(/^tr$/);\n else if (name === \"td\" || name === \"th\") closeTo(/^t[dh]$/);\n else if (name === \"option\") closeTo(/^option$/);\n\n if (name === \"img\" && rootDepth && !skipDepth) {\n const alt = attrs.get(\"alt\");\n if (alt) (heading ? heading.text : body).push(` ${alt} `);\n }\n if (VOID.has(name) || t.selfClosing) {\n if ((name === \"br\" || name === \"hr\") && rootDepth) (heading ? heading.text : body).push(\" \");\n continue;\n }\n const ignore = attrs.has(\"data-search-ignore\") || attrs.has(\"data-pagefind-ignore\") || attrs.get(\"aria-hidden\") === \"true\";\n const isRoot = !rootSeen && !skipDepth && !ignore && (marker ? attrs.has(marker) : name === rootTag);\n if (isRoot) rootSeen = true;\n // A <header> inside the root is the page's own header (it often holds the <h1>); outside, it is the site's.\n const skip = (SKIP.has(name) || (name === \"header\" && !rootDepth && !isRoot) || ignore) && !isRoot;\n const o: Open = { name, root: isRoot, skip };\n const level = /^h([1-6])$/.exec(name)?.[1];\n if (level && !heading) o.heading = { level: Number(level), id: attrs.get(\"id\") || undefined, text: [] };\n if (name === \"a\" && heading && (attrs.get(\"href\") ?? \"\").startsWith(\"#\")) o.permalink = { heading: heading.text, from: heading.text.length };\n if (attrs.has(\"data-tag-list\")) o.tagList = true;\n if (name === \"li\" && tagListDepth) o.tagItem = [];\n if (BLOCK.test(name)) (heading ? heading.text : body).push(\" \");\n push(o);\n }\n while (stack.length) pop();\n\n const bodyText = squash(body.join(\"\"));\n const title = h1 ?? squash(decode(titleTag));\n if (!bodyText && !title) return undefined;\n const keywords = (metas.get(\"keywords\") ?? \"\").split(\",\").map((k) => k.trim()).filter(Boolean);\n return {\n doc: { title: title || url, url, section: opts.section ?? folderName(url), excerpt: excerpt(metas.get(\"description\") || bodyText), anchors, tags },\n parts: { title: title || url, headings, body: bodyText, keywords: [...keywords, ...tags] },\n lang,\n };\n}\n\nfunction squash(s: string): string {\n return s.replace(/\\s+/g, \" \").trim();\n}\n\nfunction excerpt(s: string): string {\n if (s.length <= 240) return s;\n const cut = s.slice(0, 240);\n return cut.slice(0, cut.lastIndexOf(\" \") > 180 ? cut.lastIndexOf(\" \") : 240) + \"β¦\";\n}\n\n/** \"guide/install.html\" β \"Guide\"; top-level pages have no section. Works on encoded and raw paths. */\nexport function folderName(url: string): string {\n const parts = url.split(\"/\");\n if (parts.length < 2) return \"\";\n let f = parts[0];\n try {\n f = decodeURIComponent(f);\n } catch {\n /* a raw name with \"%\" */\n }\n f = f.replace(/[-_]+/g, \" \");\n return f.charAt(0).toUpperCase() + f.slice(1);\n}\n\n// Named entities: all of Latin-1 plus the common typographic and symbol ones. Numeric references are always decoded.\nconst NAMED: Record<string, number> = {\n quot: 34, amp: 38, apos: 39, lt: 60, gt: 62, nbsp: 160, iexcl: 161, cent: 162, pound: 163, curren: 164, yen: 165, brvbar: 166, sect: 167, uml: 168, copy: 169,\n ordf: 170, laquo: 171, not: 172, shy: 173, reg: 174, macr: 175, deg: 176, plusmn: 177, sup2: 178, sup3: 179, acute: 180, micro: 181, para: 182, middot: 183,\n cedil: 184, sup1: 185, ordm: 186, raquo: 187, frac14: 188, frac12: 189, frac34: 190, iquest: 191, Agrave: 192, Aacute: 193, Acirc: 194, Atilde: 195, Auml: 196,\n Aring: 197, AElig: 198, Ccedil: 199, Egrave: 200, Eacute: 201, Ecirc: 202, Euml: 203, Igrave: 204, Iacute: 205, Icirc: 206, Iuml: 207, ETH: 208, Ntilde: 209,\n Ograve: 210, Oacute: 211, Ocirc: 212, Otilde: 213, Ouml: 214, times: 215, Oslash: 216, Ugrave: 217, Uacute: 218, Ucirc: 219, Uuml: 220, Yacute: 221, THORN: 222,\n szlig: 223, agrave: 224, aacute: 225, acirc: 226, atilde: 227, auml: 228, aring: 229, aelig: 230, ccedil: 231, egrave: 232, eacute: 233, ecirc: 234, euml: 235,\n igrave: 236, iacute: 237, icirc: 238, iuml: 239, eth: 240, ntilde: 241, ograve: 242, oacute: 243, ocirc: 244, otilde: 245, ouml: 246, divide: 247, oslash: 248,\n ugrave: 249, uacute: 250, ucirc: 251, uuml: 252, yacute: 253, thorn: 254, yuml: 255, OElig: 338, oelig: 339, Scaron: 352, scaron: 353, Yuml: 376, fnof: 402,\n circ: 710, tilde: 732, ensp: 8194, emsp: 8195, thinsp: 8201, zwnj: 8204, zwj: 8205, lrm: 8206, rlm: 8207, ndash: 8211, mdash: 8212, lsquo: 8216, rsquo: 8217,\n sbquo: 8218, ldquo: 8220, rdquo: 8221, bdquo: 8222, dagger: 8224, Dagger: 8225, bull: 8226, hellip: 8230, permil: 8240, prime: 8242, Prime: 8243, lsaquo: 8249,\n rsaquo: 8250, euro: 8364, trade: 8482, larr: 8592, uarr: 8593, rarr: 8594, darr: 8595, harr: 8596, rArr: 8658, lArr: 8656, hArr: 8660, minus: 8722, le: 8804,\n ge: 8805, ne: 8800, asymp: 8776, infin: 8734, check: 10003, hearts: 9829,\n};\n\nfunction decode(s: string): string {\n if (!s.includes(\"&\")) return s;\n return s.replace(/&(#x[0-9a-f]+|#\\d+|[a-z][a-z0-9]*);/gi, (m, e: string) => {\n if (e[0] === \"#\") {\n const n = e[1] === \"x\" || e[1] === \"X\" ? parseInt(e.slice(2), 16) : Number(e.slice(1));\n return Number.isFinite(n) && n > 0 && n < 0x110000 ? String.fromCodePoint(n) : m;\n }\n const n = NAMED[e] ?? NAMED[e.toLowerCase()];\n return n === undefined ? m : String.fromCodePoint(n);\n });\n}\n"],"mappings":"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","names":["n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { TreeNode } from '@dogsbay/types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The indexer: documents in, index files out (see format.ts). Pure and
|
|
5
|
+
* Worker-safe: no Node built-ins, and memory for one page at a time plus
|
|
6
|
+
* the postings.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
interface SearchDoc {
|
|
10
|
+
title: string;
|
|
11
|
+
url: string;
|
|
12
|
+
section: string;
|
|
13
|
+
excerpt: string;
|
|
14
|
+
/** h2/h3 headings with their anchors, for results within the page. */
|
|
15
|
+
anchors?: [text: string, id: string][];
|
|
16
|
+
/** Tags, for filters (`tag:x`). */
|
|
17
|
+
tags?: string[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Ranking: each field is scored on its own and the fields are combined
|
|
21
|
+
* afterwards, BM25-style: term frequency saturates (k1), and a field's
|
|
22
|
+
* frequency is divided by its length relative to the corpus average (b),
|
|
23
|
+
* so a long page that mentions a word fifty times does not outrank a page
|
|
24
|
+
* with the word in its title. The inverse document frequency is applied at
|
|
25
|
+
* query time from the length of the term's posting list.
|
|
26
|
+
*/
|
|
27
|
+
declare const RANK: {
|
|
28
|
+
k1: number;
|
|
29
|
+
fields: {
|
|
30
|
+
title: {
|
|
31
|
+
weight: number;
|
|
32
|
+
b: number;
|
|
33
|
+
};
|
|
34
|
+
keywords: {
|
|
35
|
+
weight: number;
|
|
36
|
+
b: number;
|
|
37
|
+
};
|
|
38
|
+
headings: {
|
|
39
|
+
weight: number;
|
|
40
|
+
b: number;
|
|
41
|
+
};
|
|
42
|
+
body: {
|
|
43
|
+
weight: number;
|
|
44
|
+
b: number;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
declare class SearchIndexBuilder {
|
|
49
|
+
private opts;
|
|
50
|
+
constructor(opts?: {
|
|
51
|
+
lang?: string;
|
|
52
|
+
});
|
|
53
|
+
private docs;
|
|
54
|
+
/** Token counts per document and field (title, headings, body), for length normalisation. */
|
|
55
|
+
private lengths;
|
|
56
|
+
/**
|
|
57
|
+
* term β flat [doc, counts, doc, counts, β¦]. Documents arrive in order, so
|
|
58
|
+
* each page's counts are summed locally and appended once. Flat arrays
|
|
59
|
+
* cost a fraction of a Map per term, which matters inside a Worker.
|
|
60
|
+
*/
|
|
61
|
+
private postings;
|
|
62
|
+
add(doc: SearchDoc, parts: {
|
|
63
|
+
title: string;
|
|
64
|
+
headings: string[];
|
|
65
|
+
body: string;
|
|
66
|
+
keywords?: string[];
|
|
67
|
+
}): void;
|
|
68
|
+
/** The stored score of one posting: saturated, length-normalised field scores, weighted and summed. */
|
|
69
|
+
private scorer;
|
|
70
|
+
get size(): number;
|
|
71
|
+
/**
|
|
72
|
+
* Terms grouped into shards: by first character, and a group larger than
|
|
73
|
+
* SHARD_CHARS split on the next character (up to MAX_DEPTH), so a query
|
|
74
|
+
* loads small files on large sites. Terms no longer than a group's key stay
|
|
75
|
+
* in the group's own file.
|
|
76
|
+
*/
|
|
77
|
+
private shardTerms;
|
|
78
|
+
/** Emit the index files, keyed by path relative to the unit's `_dogsbay/search/` dir. */
|
|
79
|
+
emit(): Map<string, string>;
|
|
80
|
+
/**
|
|
81
|
+
* The index files one at a time: the document table, the fragments, then
|
|
82
|
+
* one shard per term prefix. Each shard is built, serialised and handed
|
|
83
|
+
* over before the next, and its postings are dropped, so writing the index
|
|
84
|
+
* needs memory for one shard rather than for every shard and its JSON.
|
|
85
|
+
*/
|
|
86
|
+
emitFiles(): Generator<[string, string]>;
|
|
87
|
+
}
|
|
88
|
+
/** Text for indexing: headings separated from body, code blocks skipped. */
|
|
89
|
+
declare function extractText(tree: TreeNode[]): {
|
|
90
|
+
headings: string[];
|
|
91
|
+
body: string;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export { RANK, type SearchDoc, SearchIndexBuilder, extractText };
|