@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.
@@ -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) [UNRESOLVED_IMPORT] Warning: Could not resolve 'virtual:eclipsa-content:runtime' in mod.ts
21
+ ╭─[ mod.ts:27:17 ]
22
+ │
23
+ 27 │   return import('virtual:eclipsa-content:runtime')
24
+  │ ────────────────┬────────────────
25
+  │ ╰────────────────── Module not found, treating it as an external dependency
26
+ ────╯
27
+
28
+ [PLUGIN_TIMINGS] Warning: 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 "&amp;": return "&";
20
+ case "&lt;": return "<";
21
+ case "&gt;": return ">";
22
+ case "&quot;": return "\"";
23
+ case "&#39;": 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("&amp;", "&").replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&quot;", "\"").replaceAll("&#39;", "'").replaceAll("&nbsp;", " ");
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