@eclipsa/content 0.0.0
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/.turbo/turbo-build.log +34 -0
- package/.turbo/turbo-test.log +13 -0
- package/.turbo/turbo-typecheck.log +1 -0
- package/dist/internal-h0upzIHm.mjs +644 -0
- package/dist/internal-h0upzIHm.mjs.map +1 -0
- package/dist/internal.d.mts +47 -0
- package/dist/internal.mjs +2 -0
- package/dist/mod-P8gKoDsz.d.mts +151 -0
- package/dist/mod.d.mts +2 -0
- package/dist/mod.mjs +34 -0
- package/dist/mod.mjs.map +1 -0
- package/dist/package.json +40 -0
- package/dist/types-rZ-wc23p.mjs +6 -0
- package/dist/types-rZ-wc23p.mjs.map +1 -0
- package/dist/virtual-runtime.d.ts +24 -0
- package/dist/vite.d.mts +7 -0
- package/dist/vite.mjs +195 -0
- package/dist/vite.mjs.map +1 -0
- package/highlight.ts +125 -0
- package/internal.test.ts +263 -0
- package/internal.ts +514 -0
- package/mod.ts +124 -0
- package/package.json +62 -0
- package/search.test.ts +56 -0
- package/search.ts +450 -0
- package/typecheck.ts +103 -0
- package/types.ts +172 -0
- package/virtual-runtime.d.ts +24 -0
- package/vite-config.test.ts +15 -0
- package/vite.config.ts +16 -0
- package/vite.test.ts +283 -0
- package/vite.ts +276 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
$ vp pack && bun ../../scripts/release/write-dist-package-json.ts
|
|
2
|
+
ℹ entry: mod.ts, vite.ts, internal.ts
|
|
3
|
+
ℹ tsconfig: ../../tsconfig.json
|
|
4
|
+
ℹ Build start
|
|
5
|
+
ℹ Cleaning 15 files
|
|
6
|
+
ℹ dist/vite.mjs 7.59 kB │ gzip: 2.38 kB
|
|
7
|
+
ℹ dist/mod.mjs 1.16 kB │ gzip: 0.56 kB
|
|
8
|
+
ℹ dist/internal.mjs 0.35 kB │ gzip: 0.18 kB
|
|
9
|
+
ℹ dist/internal-h0upzIHm.mjs.map 42.66 kB │ gzip: 11.12 kB
|
|
10
|
+
ℹ dist/internal-h0upzIHm.mjs 21.25 kB │ gzip: 6.36 kB
|
|
11
|
+
ℹ dist/vite.mjs.map 13.07 kB │ gzip: 3.98 kB
|
|
12
|
+
ℹ dist/mod.mjs.map 4.44 kB │ gzip: 1.36 kB
|
|
13
|
+
ℹ dist/types-rZ-wc23p.mjs.map 4.34 kB │ gzip: 1.19 kB
|
|
14
|
+
ℹ dist/types-rZ-wc23p.mjs 0.19 kB │ gzip: 0.17 kB
|
|
15
|
+
ℹ dist/internal.d.mts 1.67 kB │ gzip: 0.58 kB
|
|
16
|
+
ℹ dist/mod.d.mts 1.63 kB │ gzip: 0.51 kB
|
|
17
|
+
ℹ dist/vite.d.mts 0.18 kB │ gzip: 0.15 kB
|
|
18
|
+
ℹ dist/mod-P8gKoDsz.d.mts 6.45 kB │ gzip: 1.63 kB
|
|
19
|
+
ℹ 13 files, total: 104.99 kB
|
|
20
|
+
mod.ts (27:16) [33m[UNRESOLVED_IMPORT] Warning:[0m Could not resolve 'virtual:eclipsa-content:runtime' in mod.ts
|
|
21
|
+
[38;5;246m╭[0m[38;5;246m─[0m[38;5;246m[[0m mod.ts:27:17 [38;5;246m][0m
|
|
22
|
+
[38;5;246m│[0m
|
|
23
|
+
[38;5;246m27 │[0m [38;5;249m [0m[38;5;249m [0m[38;5;249mr[0m[38;5;249me[0m[38;5;249mt[0m[38;5;249mu[0m[38;5;249mr[0m[38;5;249mn[0m[38;5;249m [0m[38;5;249mi[0m[38;5;249mm[0m[38;5;249mp[0m[38;5;249mo[0m[38;5;249mr[0m[38;5;249mt[0m[38;5;249m([0m'virtual:eclipsa-content:runtime'[38;5;249m)[0m
|
|
24
|
+
[38;5;240m │[0m ────────────────┬────────────────
|
|
25
|
+
[38;5;240m │[0m ╰────────────────── Module not found, treating it as an external dependency
|
|
26
|
+
[38;5;246m────╯[0m
|
|
27
|
+
|
|
28
|
+
[33m[PLUGIN_TIMINGS] Warning:[0m Your build spent significant time in plugins. Here is a breakdown:
|
|
29
|
+
- tsdown:external (51%)
|
|
30
|
+
- rolldown-plugin-dts:generate (47%)
|
|
31
|
+
See https://rolldown.rs/options/checks#plugintimings for more details.
|
|
32
|
+
|
|
33
|
+
✔ Build complete in 3322ms
|
|
34
|
+
dist/package.json
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
$ vp test --run
|
|
2
|
+
RUN /home/nakasyou/eclipsa/packages/content
|
|
3
|
+
|
|
4
|
+
✓ search.test.ts (2 tests) 8ms
|
|
5
|
+
✓ vite-config.test.ts (1 test) 5ms
|
|
6
|
+
✓ vite.test.ts (6 tests) 95ms
|
|
7
|
+
✓ internal.test.ts (5 tests) 197ms
|
|
8
|
+
|
|
9
|
+
Test Files 4 passed (4)
|
|
10
|
+
Tests 14 passed (14)
|
|
11
|
+
Start at 17:36:38
|
|
12
|
+
Duration 734ms (transform 424ms, setup 0ms, import 1.17s, tests 304ms, environment 1ms)
|
|
13
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
$ bun x tsc -p ../../tsconfig.json --noEmit
|
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
import "./types-rZ-wc23p.mjs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import * as fs from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import fg from "fast-glob";
|
|
6
|
+
import YAML from "yaml";
|
|
7
|
+
import { createHighlighter } from "shiki";
|
|
8
|
+
//#region highlight.ts
|
|
9
|
+
const DEFAULT_THEME = "github-dark";
|
|
10
|
+
const CODE_BLOCK_RE = /<pre\b[^>]*>\s*<code\b([^>]*)>([\s\S]*?)<\/code>\s*<\/pre>/giu;
|
|
11
|
+
const CLASS_ATTR_RE = /\bclass=(['"])(.*?)\1/iu;
|
|
12
|
+
const HTML_ENTITY_RE = /&(?:#(\d+)|#x([\da-fA-F]+)|amp|lt|gt|quot|#39);/g;
|
|
13
|
+
const highlighterCache = /* @__PURE__ */ new Map();
|
|
14
|
+
const loadedLanguagesByTheme = /* @__PURE__ */ new Map();
|
|
15
|
+
const decodeHtmlEntities$1 = (value) => value.replace(HTML_ENTITY_RE, (entity, decimal, hex) => {
|
|
16
|
+
if (decimal) return String.fromCodePoint(Number(decimal));
|
|
17
|
+
if (hex) return String.fromCodePoint(Number.parseInt(hex, 16));
|
|
18
|
+
switch (entity) {
|
|
19
|
+
case "&": return "&";
|
|
20
|
+
case "<": return "<";
|
|
21
|
+
case ">": return ">";
|
|
22
|
+
case """: return "\"";
|
|
23
|
+
case "'": return "'";
|
|
24
|
+
default: return entity;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
const getLanguageFromCodeAttributes = (attributes) => {
|
|
28
|
+
const classAttr = CLASS_ATTR_RE.exec(attributes)?.[2];
|
|
29
|
+
if (!classAttr) return null;
|
|
30
|
+
for (const token of classAttr.split(/\s+/)) if (token.startsWith("language-")) return token.slice(9);
|
|
31
|
+
return null;
|
|
32
|
+
};
|
|
33
|
+
const resolveTheme = (options) => {
|
|
34
|
+
if (!options) return null;
|
|
35
|
+
return options === true ? DEFAULT_THEME : options.theme ?? DEFAULT_THEME;
|
|
36
|
+
};
|
|
37
|
+
const getHighlighter = (theme) => {
|
|
38
|
+
const cached = highlighterCache.get(theme);
|
|
39
|
+
if (cached) return cached;
|
|
40
|
+
const next = createHighlighter({
|
|
41
|
+
langs: [],
|
|
42
|
+
themes: [theme]
|
|
43
|
+
});
|
|
44
|
+
highlighterCache.set(theme, next);
|
|
45
|
+
loadedLanguagesByTheme.set(theme, /* @__PURE__ */ new Set());
|
|
46
|
+
return next;
|
|
47
|
+
};
|
|
48
|
+
const ensureLanguageLoaded = async (theme, language) => {
|
|
49
|
+
const loadedLanguages = loadedLanguagesByTheme.get(theme) ?? /* @__PURE__ */ new Set();
|
|
50
|
+
loadedLanguagesByTheme.set(theme, loadedLanguages);
|
|
51
|
+
if (loadedLanguages.has(language)) return;
|
|
52
|
+
await (await getHighlighter(theme)).loadLanguage(language);
|
|
53
|
+
loadedLanguages.add(language);
|
|
54
|
+
};
|
|
55
|
+
const highlightHtml = async (html, options) => {
|
|
56
|
+
const theme = resolveTheme(options);
|
|
57
|
+
if (!theme) return html;
|
|
58
|
+
const highlighter = await getHighlighter(theme);
|
|
59
|
+
let highlightedHtml = "";
|
|
60
|
+
let lastIndex = 0;
|
|
61
|
+
for (const match of html.matchAll(CODE_BLOCK_RE)) {
|
|
62
|
+
const index = match.index ?? 0;
|
|
63
|
+
const fullMatch = match[0];
|
|
64
|
+
const codeAttributes = match[1] ?? "";
|
|
65
|
+
const encodedCode = match[2] ?? "";
|
|
66
|
+
const language = getLanguageFromCodeAttributes(codeAttributes);
|
|
67
|
+
highlightedHtml += html.slice(lastIndex, index);
|
|
68
|
+
lastIndex = index + fullMatch.length;
|
|
69
|
+
if (!language) {
|
|
70
|
+
highlightedHtml += fullMatch;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
await ensureLanguageLoaded(theme, language);
|
|
75
|
+
highlightedHtml += highlighter.codeToHtml(decodeHtmlEntities$1(encodedCode), {
|
|
76
|
+
lang: language,
|
|
77
|
+
theme
|
|
78
|
+
});
|
|
79
|
+
} catch {
|
|
80
|
+
highlightedHtml += fullMatch;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (lastIndex === 0) return html;
|
|
84
|
+
highlightedHtml += html.slice(lastIndex);
|
|
85
|
+
return highlightedHtml;
|
|
86
|
+
};
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region search.ts
|
|
89
|
+
const DEFAULT_SEARCH_OPTIONS = {
|
|
90
|
+
enabled: true,
|
|
91
|
+
hotkey: "/",
|
|
92
|
+
limit: 10,
|
|
93
|
+
placeholder: "Search docs...",
|
|
94
|
+
prefix: true
|
|
95
|
+
};
|
|
96
|
+
const SEARCH_STOPWORDS = new Set([
|
|
97
|
+
"a",
|
|
98
|
+
"an",
|
|
99
|
+
"and",
|
|
100
|
+
"are",
|
|
101
|
+
"as",
|
|
102
|
+
"at",
|
|
103
|
+
"be",
|
|
104
|
+
"by",
|
|
105
|
+
"for",
|
|
106
|
+
"from",
|
|
107
|
+
"has",
|
|
108
|
+
"have",
|
|
109
|
+
"how",
|
|
110
|
+
"in",
|
|
111
|
+
"is",
|
|
112
|
+
"it",
|
|
113
|
+
"of",
|
|
114
|
+
"on",
|
|
115
|
+
"or",
|
|
116
|
+
"that",
|
|
117
|
+
"the",
|
|
118
|
+
"this",
|
|
119
|
+
"to",
|
|
120
|
+
"was",
|
|
121
|
+
"were",
|
|
122
|
+
"with"
|
|
123
|
+
]);
|
|
124
|
+
const isCjkChar = (char) => /[\u3400-\u4dbf\u4e00-\u9fff\u3040-\u30ff\uac00-\ud7af]/u.test(char);
|
|
125
|
+
const tokenizeValue = (text, query) => {
|
|
126
|
+
const tokens = [];
|
|
127
|
+
let current = "";
|
|
128
|
+
for (const char of text) {
|
|
129
|
+
if (isCjkChar(char)) {
|
|
130
|
+
if (current !== "") {
|
|
131
|
+
const token = current.toLowerCase();
|
|
132
|
+
if (query || token.length >= 2 && !SEARCH_STOPWORDS.has(token)) tokens.push(token);
|
|
133
|
+
current = "";
|
|
134
|
+
}
|
|
135
|
+
tokens.push(char);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (/[\p{L}\p{N}_]/u.test(char)) {
|
|
139
|
+
current += char;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (current !== "") {
|
|
143
|
+
const token = current.toLowerCase();
|
|
144
|
+
if (query || token.length >= 2 && !SEARCH_STOPWORDS.has(token)) tokens.push(token);
|
|
145
|
+
current = "";
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (current !== "") {
|
|
149
|
+
const token = current.toLowerCase();
|
|
150
|
+
if (query || token.length >= 2 && !SEARCH_STOPWORDS.has(token)) tokens.push(token);
|
|
151
|
+
}
|
|
152
|
+
return tokens;
|
|
153
|
+
};
|
|
154
|
+
const tokenizeIndex = (text) => tokenizeValue(text, false);
|
|
155
|
+
const addDocumentFieldTerms = (map, field, text) => {
|
|
156
|
+
for (const token of tokenizeIndex(text)) {
|
|
157
|
+
const existing = map.get(token);
|
|
158
|
+
if (existing) {
|
|
159
|
+
existing.tf += 1;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
map.set(token, {
|
|
163
|
+
field,
|
|
164
|
+
tf: 1
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
const resolveContentSearchOptions = (options) => {
|
|
169
|
+
if (options === false) return {
|
|
170
|
+
...DEFAULT_SEARCH_OPTIONS,
|
|
171
|
+
enabled: false
|
|
172
|
+
};
|
|
173
|
+
const normalized = typeof options === "object" ? options : {};
|
|
174
|
+
return {
|
|
175
|
+
enabled: normalized.enabled ?? true,
|
|
176
|
+
hotkey: normalized.hotkey ?? DEFAULT_SEARCH_OPTIONS.hotkey,
|
|
177
|
+
limit: normalized.limit ?? DEFAULT_SEARCH_OPTIONS.limit,
|
|
178
|
+
placeholder: normalized.placeholder ?? DEFAULT_SEARCH_OPTIONS.placeholder,
|
|
179
|
+
prefix: normalized.prefix ?? DEFAULT_SEARCH_OPTIONS.prefix
|
|
180
|
+
};
|
|
181
|
+
};
|
|
182
|
+
const buildContentSearchIndex = (documents, options) => {
|
|
183
|
+
const index = {};
|
|
184
|
+
const df = {};
|
|
185
|
+
let totalDocumentLength = 0;
|
|
186
|
+
documents.forEach((document, docIdx) => {
|
|
187
|
+
const docTerms = /* @__PURE__ */ new Map();
|
|
188
|
+
addDocumentFieldTerms(docTerms, "title", document.title);
|
|
189
|
+
for (const heading of document.headings) addDocumentFieldTerms(docTerms, "heading", heading);
|
|
190
|
+
addDocumentFieldTerms(docTerms, "body", document.body);
|
|
191
|
+
for (const code of document.code) addDocumentFieldTerms(docTerms, "code", code);
|
|
192
|
+
totalDocumentLength += tokenizeIndex(document.body).length;
|
|
193
|
+
for (const [term, posting] of docTerms) {
|
|
194
|
+
df[term] = (df[term] ?? 0) + 1;
|
|
195
|
+
const postings = index[term] ?? [];
|
|
196
|
+
postings.push({
|
|
197
|
+
docIdx,
|
|
198
|
+
field: posting.field,
|
|
199
|
+
tf: posting.tf
|
|
200
|
+
});
|
|
201
|
+
index[term] = postings;
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
return {
|
|
205
|
+
avgDl: documents.length === 0 ? 0 : totalDocumentLength / documents.length,
|
|
206
|
+
df,
|
|
207
|
+
docCount: documents.length,
|
|
208
|
+
documents,
|
|
209
|
+
index,
|
|
210
|
+
options
|
|
211
|
+
};
|
|
212
|
+
};
|
|
213
|
+
const generateContentSearchRuntimeModule = (assetPath, options) => `let searchIndexPromise = null
|
|
214
|
+
const searchOptions = ${JSON.stringify(options)}
|
|
215
|
+
|
|
216
|
+
const loadSearchIndex = async () => {
|
|
217
|
+
if (searchIndexPromise) {
|
|
218
|
+
return searchIndexPromise
|
|
219
|
+
}
|
|
220
|
+
searchIndexPromise = fetch(${JSON.stringify(assetPath)})
|
|
221
|
+
.then((response) => {
|
|
222
|
+
if (!response.ok) {
|
|
223
|
+
throw new Error('Failed to load search index.')
|
|
224
|
+
}
|
|
225
|
+
return response.json()
|
|
226
|
+
})
|
|
227
|
+
.catch(() => null)
|
|
228
|
+
return searchIndexPromise
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const isCjkChar = (char) => /[\\u3400-\\u4dbf\\u4e00-\\u9fff\\u3040-\\u30ff\\uac00-\\ud7af]/u.test(char)
|
|
232
|
+
|
|
233
|
+
const tokenizeQuery = (text) => {
|
|
234
|
+
const tokens = []
|
|
235
|
+
let current = ''
|
|
236
|
+
for (const char of text) {
|
|
237
|
+
if (isCjkChar(char)) {
|
|
238
|
+
if (current !== '') {
|
|
239
|
+
tokens.push(current.toLowerCase())
|
|
240
|
+
current = ''
|
|
241
|
+
}
|
|
242
|
+
tokens.push(char)
|
|
243
|
+
continue
|
|
244
|
+
}
|
|
245
|
+
if (/[\\p{L}\\p{N}_]/u.test(char)) {
|
|
246
|
+
current += char
|
|
247
|
+
continue
|
|
248
|
+
}
|
|
249
|
+
if (current !== '') {
|
|
250
|
+
tokens.push(current.toLowerCase())
|
|
251
|
+
current = ''
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (current !== '') {
|
|
255
|
+
tokens.push(current.toLowerCase())
|
|
256
|
+
}
|
|
257
|
+
return tokens
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const getFieldBoost = (field) => {
|
|
261
|
+
switch (field) {
|
|
262
|
+
case 'title':
|
|
263
|
+
return 10
|
|
264
|
+
case 'heading':
|
|
265
|
+
return 5
|
|
266
|
+
case 'code':
|
|
267
|
+
return 0.5
|
|
268
|
+
case 'body':
|
|
269
|
+
default:
|
|
270
|
+
return 1
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const getSnippet = (body, matches, maxLength = 150) => {
|
|
275
|
+
if (body === '') {
|
|
276
|
+
return ''
|
|
277
|
+
}
|
|
278
|
+
const lowerBody = body.toLowerCase()
|
|
279
|
+
let firstMatchIndex = -1
|
|
280
|
+
for (const match of matches) {
|
|
281
|
+
const index = lowerBody.indexOf(match.toLowerCase())
|
|
282
|
+
if (index !== -1 && (firstMatchIndex === -1 || index < firstMatchIndex)) {
|
|
283
|
+
firstMatchIndex = index
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const start = Math.max(0, firstMatchIndex - 50)
|
|
287
|
+
const end = Math.min(body.length, start + maxLength)
|
|
288
|
+
let snippet = body.slice(start, end).trim()
|
|
289
|
+
if (start > 0) {
|
|
290
|
+
snippet = '...' + snippet
|
|
291
|
+
}
|
|
292
|
+
if (end < body.length) {
|
|
293
|
+
snippet = snippet + '...'
|
|
294
|
+
}
|
|
295
|
+
return snippet
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export const search = async (
|
|
299
|
+
query,
|
|
300
|
+
options = {},
|
|
301
|
+
) => {
|
|
302
|
+
const searchIndex = await loadSearchIndex()
|
|
303
|
+
if (!searchIndex || query.trim() === '') {
|
|
304
|
+
return []
|
|
305
|
+
}
|
|
306
|
+
const tokens = tokenizeQuery(query)
|
|
307
|
+
if (tokens.length === 0) {
|
|
308
|
+
return []
|
|
309
|
+
}
|
|
310
|
+
const limit = options.limit ?? searchOptions.limit
|
|
311
|
+
const prefix = options.prefix ?? searchOptions.prefix
|
|
312
|
+
const docScores = new Map()
|
|
313
|
+
|
|
314
|
+
tokens.forEach((token, tokenIndex) => {
|
|
315
|
+
const isLastToken = tokenIndex === tokens.length - 1
|
|
316
|
+
const matchingTerms =
|
|
317
|
+
prefix && isLastToken && token.length >= 2
|
|
318
|
+
? Object.keys(searchIndex.index).filter((term) => term.startsWith(token))
|
|
319
|
+
: searchIndex.index[token]
|
|
320
|
+
? [token]
|
|
321
|
+
: []
|
|
322
|
+
|
|
323
|
+
for (const term of matchingTerms) {
|
|
324
|
+
const postings = searchIndex.index[term] ?? []
|
|
325
|
+
const df = searchIndex.df[term] ?? 1
|
|
326
|
+
const idf = Math.log((searchIndex.docCount - df + 0.5) / (df + 0.5) + 1)
|
|
327
|
+
|
|
328
|
+
for (const posting of postings) {
|
|
329
|
+
const document = searchIndex.documents[posting.docIdx]
|
|
330
|
+
if (!document) {
|
|
331
|
+
continue
|
|
332
|
+
}
|
|
333
|
+
const docLength = Math.max(1, document.body.split(/\\s+/u).filter(Boolean).length)
|
|
334
|
+
const score =
|
|
335
|
+
idf *
|
|
336
|
+
((posting.tf * (1.2 + 1)) /
|
|
337
|
+
(posting.tf + 1.2 * (1 - 0.75 + (0.75 * docLength) / Math.max(1, searchIndex.avgDl)))) *
|
|
338
|
+
getFieldBoost(posting.field)
|
|
339
|
+
|
|
340
|
+
const current = docScores.get(posting.docIdx) ?? {
|
|
341
|
+
matches: new Set(),
|
|
342
|
+
score: 0,
|
|
343
|
+
}
|
|
344
|
+
current.score += score
|
|
345
|
+
current.matches.add(term)
|
|
346
|
+
docScores.set(posting.docIdx, current)
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
return [...docScores.entries()]
|
|
352
|
+
.map(([docIdx, value]) => {
|
|
353
|
+
const document = searchIndex.documents[docIdx]
|
|
354
|
+
const matches = [...value.matches]
|
|
355
|
+
return {
|
|
356
|
+
collection: document.collection,
|
|
357
|
+
id: document.id,
|
|
358
|
+
matches,
|
|
359
|
+
score: value.score,
|
|
360
|
+
snippet: getSnippet(document.body, matches),
|
|
361
|
+
title: document.title,
|
|
362
|
+
url: document.url,
|
|
363
|
+
}
|
|
364
|
+
})
|
|
365
|
+
.sort((left, right) => right.score - left.score)
|
|
366
|
+
.slice(0, limit)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export { searchOptions }
|
|
370
|
+
export default { search, searchOptions }
|
|
371
|
+
`;
|
|
372
|
+
//#endregion
|
|
373
|
+
//#region internal.ts
|
|
374
|
+
const MARKDOWN_EXTENSION_RE = /\.md$/i;
|
|
375
|
+
const require = createRequire(import.meta.url);
|
|
376
|
+
let markdownTransform = null;
|
|
377
|
+
var ContentCollectionError = class extends Error {
|
|
378
|
+
constructor(message) {
|
|
379
|
+
super(message);
|
|
380
|
+
this.name = "ContentCollectionError";
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
const normalizeSlashes = (value) => value.replaceAll("\\", "/");
|
|
384
|
+
const formatIssuePath = (pathValue) => {
|
|
385
|
+
if (!pathValue || pathValue.length === 0) return "";
|
|
386
|
+
return pathValue.map((segment) => typeof segment === "object" && segment !== null && "key" in segment ? String(segment.key) : String(segment)).join(".");
|
|
387
|
+
};
|
|
388
|
+
const createSchemaError = (collection, filePath, issues) => {
|
|
389
|
+
return new ContentCollectionError(`Invalid frontmatter in collection "${collection}" for ${filePath}: ${issues.map((issue) => {
|
|
390
|
+
const issuePath = formatIssuePath(issue.path);
|
|
391
|
+
return issuePath === "" ? issue.message : `${issuePath}: ${issue.message}`;
|
|
392
|
+
}).join("; ")}`);
|
|
393
|
+
};
|
|
394
|
+
const parseFrontmatter = (source) => {
|
|
395
|
+
if (!source.startsWith("---")) return {
|
|
396
|
+
body: source,
|
|
397
|
+
data: {}
|
|
398
|
+
};
|
|
399
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/u.exec(source);
|
|
400
|
+
if (!match) return {
|
|
401
|
+
body: source,
|
|
402
|
+
data: {}
|
|
403
|
+
};
|
|
404
|
+
const raw = YAML.parse(match[1] ?? "");
|
|
405
|
+
if (raw == null) return {
|
|
406
|
+
body: source.slice(match[0].length),
|
|
407
|
+
data: {}
|
|
408
|
+
};
|
|
409
|
+
if (typeof raw !== "object" || Array.isArray(raw)) throw new ContentCollectionError("Markdown frontmatter must resolve to an object.");
|
|
410
|
+
return {
|
|
411
|
+
body: source.slice(match[0].length),
|
|
412
|
+
data: { ...raw }
|
|
413
|
+
};
|
|
414
|
+
};
|
|
415
|
+
const normalizeIdSegment = (segment) => segment.trim().replaceAll(/\s+/g, "-").replaceAll(/[^a-zA-Z0-9/_-]+/g, "-").replaceAll(/-+/g, "-").replaceAll(/^[-/]+|[-/]+$/g, "");
|
|
416
|
+
const normalizeEntryId = (value) => normalizeSlashes(value).split("/").map(normalizeIdSegment).filter(Boolean).join("/");
|
|
417
|
+
const toEntryIdFromRelativePath = (relativePath) => {
|
|
418
|
+
const segments = normalizeSlashes(relativePath).replace(MARKDOWN_EXTENSION_RE, "").split("/").filter(Boolean);
|
|
419
|
+
if (segments[segments.length - 1] === "index" && segments.length > 1) segments.pop();
|
|
420
|
+
return normalizeEntryId(segments.join("/")) || "index";
|
|
421
|
+
};
|
|
422
|
+
const validateData = async (collection, schema, filePath, data) => {
|
|
423
|
+
if (!schema) return data;
|
|
424
|
+
const result = await schema["~standard"].validate(data);
|
|
425
|
+
if ("issues" in result && result.issues !== void 0) throw createSchemaError(collection, filePath, result.issues);
|
|
426
|
+
return result.value;
|
|
427
|
+
};
|
|
428
|
+
const resolveGlobLoaderEntries = async (collection, loader, context) => {
|
|
429
|
+
const baseDir = path.resolve(path.dirname(context.configPath), loader.base);
|
|
430
|
+
const matches = await fg(loader.pattern, {
|
|
431
|
+
absolute: true,
|
|
432
|
+
cwd: baseDir,
|
|
433
|
+
onlyFiles: true
|
|
434
|
+
});
|
|
435
|
+
return Promise.all(matches.map(async (filePath) => {
|
|
436
|
+
const source = await fs.readFile(filePath, "utf8");
|
|
437
|
+
const relativePath = normalizeSlashes(path.relative(baseDir, filePath));
|
|
438
|
+
const parsed = parseFrontmatter(source);
|
|
439
|
+
const slug = typeof parsed.data.slug === "string" ? parsed.data.slug : void 0;
|
|
440
|
+
delete parsed.data.slug;
|
|
441
|
+
return {
|
|
442
|
+
body: parsed.body,
|
|
443
|
+
data: parsed.data,
|
|
444
|
+
filePath,
|
|
445
|
+
id: slug ? normalizeEntryId(slug) : toEntryIdFromRelativePath(relativePath)
|
|
446
|
+
};
|
|
447
|
+
}));
|
|
448
|
+
};
|
|
449
|
+
const resolveLoaderEntries = async (collection, loader, context) => {
|
|
450
|
+
if (loader.kind === "glob") return resolveGlobLoaderEntries(collection, loader, context);
|
|
451
|
+
return [...await loader.load(context)];
|
|
452
|
+
};
|
|
453
|
+
const normalizeResolvedEntry = async (collection, schema, entry, index) => {
|
|
454
|
+
const parsed = entry.data === void 0 ? parseFrontmatter(entry.body) : {
|
|
455
|
+
body: entry.body,
|
|
456
|
+
data: { ...entry.data }
|
|
457
|
+
};
|
|
458
|
+
const filePath = entry.filePath ?? `${collection}:${entry.id ?? index}`;
|
|
459
|
+
const slug = typeof parsed.data.slug === "string" ? parsed.data.slug : void 0;
|
|
460
|
+
delete parsed.data.slug;
|
|
461
|
+
const id = normalizeEntryId(entry.id ?? slug ?? `${collection}-${index}`) || `${collection}-${index}`;
|
|
462
|
+
return {
|
|
463
|
+
body: parsed.body,
|
|
464
|
+
collection,
|
|
465
|
+
data: await validateData(collection, schema, filePath, parsed.data),
|
|
466
|
+
filePath,
|
|
467
|
+
id
|
|
468
|
+
};
|
|
469
|
+
};
|
|
470
|
+
const isDefinedCollection = (value) => typeof value === "object" && value !== null && "__eclipsa_content_collection__" in value && value["__eclipsa_content_collection__"] === true;
|
|
471
|
+
const resolveCollections = async ({ collectionsModule, configPath, root }) => {
|
|
472
|
+
const byCollection = /* @__PURE__ */ new Map();
|
|
473
|
+
const entriesByCollection = /* @__PURE__ */ new Map();
|
|
474
|
+
const markdownByCollectionName = /* @__PURE__ */ new Map();
|
|
475
|
+
const searchByCollectionName = /* @__PURE__ */ new Map();
|
|
476
|
+
const definedCollections = Object.entries(collectionsModule).filter((entry) => isDefinedCollection(entry[1]));
|
|
477
|
+
for (const [collectionName, definition] of definedCollections) {
|
|
478
|
+
const context = {
|
|
479
|
+
collection: collectionName,
|
|
480
|
+
configPath,
|
|
481
|
+
root
|
|
482
|
+
};
|
|
483
|
+
const rawEntries = await resolveLoaderEntries(collectionName, definition.loader, context);
|
|
484
|
+
const resolvedEntries = await Promise.all(rawEntries.map((entry, index) => normalizeResolvedEntry(collectionName, definition.schema, entry, index)));
|
|
485
|
+
resolvedEntries.sort((left, right) => left.id.localeCompare(right.id));
|
|
486
|
+
const entriesById = /* @__PURE__ */ new Map();
|
|
487
|
+
for (const entry of resolvedEntries) {
|
|
488
|
+
if (entriesById.has(entry.id)) throw new ContentCollectionError(`Duplicate content id "${entry.id}" in collection "${collectionName}".`);
|
|
489
|
+
entriesById.set(entry.id, entry);
|
|
490
|
+
}
|
|
491
|
+
byCollection.set(definition, resolvedEntries);
|
|
492
|
+
entriesByCollection.set(definition, entriesById);
|
|
493
|
+
markdownByCollectionName.set(collectionName, definition.markdown);
|
|
494
|
+
searchByCollectionName.set(collectionName, resolveContentSearchOptions(definition.search));
|
|
495
|
+
}
|
|
496
|
+
return {
|
|
497
|
+
collections: byCollection,
|
|
498
|
+
markdownByCollectionName,
|
|
499
|
+
searchByCollectionName,
|
|
500
|
+
entriesByCollection
|
|
501
|
+
};
|
|
502
|
+
};
|
|
503
|
+
const createContentRenderer = (html) => (props = {}) => ({
|
|
504
|
+
isStatic: false,
|
|
505
|
+
props: {
|
|
506
|
+
...props,
|
|
507
|
+
dangerouslySetInnerHTML: html
|
|
508
|
+
},
|
|
509
|
+
type: props.as ?? "article"
|
|
510
|
+
});
|
|
511
|
+
const resolveOxContentNapiPath = () => {
|
|
512
|
+
const resolvePaths = [process.cwd(), path.join(process.cwd(), "node_modules", "@eclipsa", "content")];
|
|
513
|
+
try {
|
|
514
|
+
return require.resolve("@ox-content/napi", { paths: resolvePaths });
|
|
515
|
+
} catch {
|
|
516
|
+
return "@ox-content/napi";
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
const loadMarkdownTransform = async () => {
|
|
520
|
+
markdownTransform ??= require(resolveOxContentNapiPath()).transform;
|
|
521
|
+
return markdownTransform;
|
|
522
|
+
};
|
|
523
|
+
const decodeHtmlEntities = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll(""", "\"").replaceAll("'", "'").replaceAll(" ", " ");
|
|
524
|
+
const stripHtml = (html) => decodeHtmlEntities(html.replaceAll(/<style[\s\S]*?<\/style>/gu, " ").replaceAll(/<script[\s\S]*?<\/script>/gu, " ").replaceAll(/<[^>]+>/gu, " ").replaceAll(/\s+/gu, " ").trim());
|
|
525
|
+
const extractMarkdownCode = (source) => {
|
|
526
|
+
const codeBlocks = /* @__PURE__ */ new Set();
|
|
527
|
+
for (const match of source.matchAll(/```[\t ]*[^\n\r]*\r?\n([\s\S]*?)```/gu)) {
|
|
528
|
+
const code = match[1]?.trim();
|
|
529
|
+
if (code) codeBlocks.add(code);
|
|
530
|
+
}
|
|
531
|
+
for (const match of source.matchAll(/`([^`\n\r]+)`/gu)) {
|
|
532
|
+
const code = match[1]?.trim();
|
|
533
|
+
if (code) codeBlocks.add(code);
|
|
534
|
+
}
|
|
535
|
+
return [...codeBlocks];
|
|
536
|
+
};
|
|
537
|
+
const resolveSearchUrl = (base, entry) => {
|
|
538
|
+
return `${base === "" ? "/" : base.endsWith("/") ? base : `${base}/`}${entry.collection}/${entry.id}`.replaceAll(/\/+/g, "/");
|
|
539
|
+
};
|
|
540
|
+
const transformMarkdownEntry = async (entry) => {
|
|
541
|
+
const result = (await loadMarkdownTransform())(entry.body, {
|
|
542
|
+
autolinks: true,
|
|
543
|
+
footnotes: true,
|
|
544
|
+
gfm: true,
|
|
545
|
+
sourcePath: entry.filePath,
|
|
546
|
+
strikethrough: true,
|
|
547
|
+
tables: true,
|
|
548
|
+
taskLists: true,
|
|
549
|
+
tocMaxDepth: 6
|
|
550
|
+
});
|
|
551
|
+
if (result.errors.length > 0) throw new ContentCollectionError(`Failed to render markdown for ${entry.filePath}: ${result.errors.join("; ")}`);
|
|
552
|
+
return result;
|
|
553
|
+
};
|
|
554
|
+
const createSearchDocument = async (entry, base) => {
|
|
555
|
+
const result = await transformMarkdownEntry(entry);
|
|
556
|
+
const headings = result.toc.map((heading) => heading.text);
|
|
557
|
+
const title = typeof entry.data.title === "string" ? entry.data.title : result.toc.find((heading) => heading.depth === 1)?.text ?? entry.id;
|
|
558
|
+
return {
|
|
559
|
+
body: stripHtml(result.html),
|
|
560
|
+
code: extractMarkdownCode(entry.body),
|
|
561
|
+
collection: entry.collection,
|
|
562
|
+
headings,
|
|
563
|
+
id: entry.id,
|
|
564
|
+
title,
|
|
565
|
+
url: resolveSearchUrl(base, entry)
|
|
566
|
+
};
|
|
567
|
+
};
|
|
568
|
+
const renderMarkdown = async (entry, markdownOptions) => {
|
|
569
|
+
const result = await transformMarkdownEntry(entry);
|
|
570
|
+
const headings = result.toc.map((heading) => ({
|
|
571
|
+
depth: heading.depth,
|
|
572
|
+
slug: heading.slug,
|
|
573
|
+
text: heading.text
|
|
574
|
+
}));
|
|
575
|
+
const html = await highlightHtml(result.html, markdownOptions?.highlight);
|
|
576
|
+
return {
|
|
577
|
+
Content: createContentRenderer(html),
|
|
578
|
+
headings,
|
|
579
|
+
html
|
|
580
|
+
};
|
|
581
|
+
};
|
|
582
|
+
const createContentSearch = async ({ collectionsModule, configPath, root, base }) => {
|
|
583
|
+
const manifest = await resolveCollections({
|
|
584
|
+
collectionsModule,
|
|
585
|
+
configPath,
|
|
586
|
+
root
|
|
587
|
+
});
|
|
588
|
+
const documents = [];
|
|
589
|
+
let resolvedOptions = resolveContentSearchOptions(false);
|
|
590
|
+
for (const entries of manifest.collections.values()) {
|
|
591
|
+
const collectionName = entries[0]?.collection;
|
|
592
|
+
if (!collectionName) continue;
|
|
593
|
+
const searchOptions = manifest.searchByCollectionName.get(collectionName);
|
|
594
|
+
if (!searchOptions?.enabled) continue;
|
|
595
|
+
if (!resolvedOptions.enabled) resolvedOptions = searchOptions;
|
|
596
|
+
for (const entry of entries) documents.push(await createSearchDocument(entry, base));
|
|
597
|
+
}
|
|
598
|
+
return {
|
|
599
|
+
index: buildContentSearchIndex(documents, resolvedOptions),
|
|
600
|
+
options: resolvedOptions
|
|
601
|
+
};
|
|
602
|
+
};
|
|
603
|
+
const createContentRuntime = ({ collectionsModule, configPath, root }) => {
|
|
604
|
+
let manifestPromise = null;
|
|
605
|
+
const renderCache = /* @__PURE__ */ new Map();
|
|
606
|
+
const getManifest = () => {
|
|
607
|
+
manifestPromise ??= resolveCollections({
|
|
608
|
+
collectionsModule,
|
|
609
|
+
configPath,
|
|
610
|
+
root
|
|
611
|
+
});
|
|
612
|
+
return manifestPromise;
|
|
613
|
+
};
|
|
614
|
+
return {
|
|
615
|
+
async getCollection(collection, filter) {
|
|
616
|
+
const entries = (await getManifest()).collections.get(collection) ?? [];
|
|
617
|
+
if (!filter) return [...entries];
|
|
618
|
+
const filtered = [];
|
|
619
|
+
for (const entry of entries) if (await filter(entry)) filtered.push(entry);
|
|
620
|
+
return filtered;
|
|
621
|
+
},
|
|
622
|
+
async getEntries(entries) {
|
|
623
|
+
const manifest = await getManifest();
|
|
624
|
+
return entries.map((entry) => {
|
|
625
|
+
return manifest.entriesByCollection.get(entry.collection)?.get(entry.id);
|
|
626
|
+
});
|
|
627
|
+
},
|
|
628
|
+
async getEntry(collection, id) {
|
|
629
|
+
return (await getManifest()).entriesByCollection.get(collection)?.get(id);
|
|
630
|
+
},
|
|
631
|
+
async render(entry) {
|
|
632
|
+
const key = `${entry.collection}:${entry.id}`;
|
|
633
|
+
const cached = renderCache.get(key);
|
|
634
|
+
if (cached) return cached;
|
|
635
|
+
const rendered = await renderMarkdown(entry, (await getManifest()).markdownByCollectionName.get(entry.collection));
|
|
636
|
+
renderCache.set(key, rendered);
|
|
637
|
+
return rendered;
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
};
|
|
641
|
+
//#endregion
|
|
642
|
+
export { resolveCollections as a, resolveContentSearchOptions as c, parseFrontmatter as i, createContentRuntime as n, toEntryIdFromRelativePath as o, createContentSearch as r, generateContentSearchRuntimeModule as s, ContentCollectionError as t };
|
|
643
|
+
|
|
644
|
+
//# sourceMappingURL=internal-h0upzIHm.mjs.map
|