@ubean/content 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/runtime-C8qrycGf.js +558 -0
- package/dist/runtime-zD72Z3v3.d.ts +195 -0
- package/dist/runtime.d.ts +2 -0
- package/dist/runtime.js +2 -0
- package/dist/vite.d.ts +25 -0
- package/dist/vite.js +111 -0
- package/package.json +1 -1
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import ubeanContentPlugin, { UbeanContentOptions } from "./vite.js";
|
|
2
|
+
import { A as ContentQueryBuilder, C as pathToTitle, D as ContentFieldSchema, E as ContentDocument, F as MarkdownNode, I as ParsedContentMeta, M as ContentSourceConfig, N as ContentTocItem, O as ContentModuleOptions, P as ContentType, S as parseMarkdown, T as ContentCollection, _ as getExtension, a as getContentItem, b as parseContent, c as queryCollection, d as createContentCollection, f as createQueryBuilder, g as getDirname, h as getBasename, i as getCollection, j as ContentSchema, k as ContentNavigationItem, l as registerContent, m as generateId, n as defineCollection, o as listCollections, p as defineContentCollection, r as fetchContentNavigation, s as parseContentFile, t as configureContentRuntime, u as buildNavigation, v as getStem, w as ContentBody, x as parseFrontmatter, y as normalizePath } from "./runtime-zD72Z3v3.js";
|
|
3
|
+
export { type ContentBody, type ContentCollection, type ContentDocument, type ContentFieldSchema, type ContentModuleOptions, type ContentNavigationItem, type ContentQueryBuilder, type ContentSchema, type ContentSourceConfig, type ContentTocItem, type ContentType, type MarkdownNode, type ParsedContentMeta, type UbeanContentOptions, buildNavigation, configureContentRuntime, createContentCollection, createQueryBuilder, defineCollection, defineContentCollection, fetchContentNavigation, fetchContentNavigation as fetchNavigation, generateId, getBasename, getCollection, getContentItem, getDirname, getExtension, getStem, listCollections, normalizePath, parseContent, parseContentFile, parseFrontmatter, parseMarkdown, pathToTitle, queryCollection, queryCollection as queryContent, registerContent, ubeanContentPlugin };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { C as pathToTitle, S as parseMarkdown, _ as getExtension, a as getContentItem, b as parseContent, c as queryCollection, d as createContentCollection, f as createQueryBuilder, g as getDirname, h as getBasename, i as getCollection, l as registerContent, m as generateId, n as defineCollection, o as listCollections, p as defineContentCollection, r as fetchContentNavigation, s as parseContentFile, t as configureContentRuntime, u as buildNavigation, v as getStem, x as parseFrontmatter, y as normalizePath } from "./runtime-C8qrycGf.js";
|
|
2
|
+
import ubeanContentPlugin from "./vite.js";
|
|
3
|
+
export { buildNavigation, configureContentRuntime, createContentCollection, createQueryBuilder, defineCollection, defineContentCollection, fetchContentNavigation, fetchContentNavigation as fetchNavigation, generateId, getBasename, getCollection, getContentItem, getDirname, getExtension, getStem, listCollections, normalizePath, parseContent, parseContentFile, parseFrontmatter, parseMarkdown, pathToTitle, queryCollection, queryCollection as queryContent, registerContent, ubeanContentPlugin };
|
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
import { kebabCase } from "scule";
|
|
2
|
+
//#region src/core.ts
|
|
3
|
+
function generateId(path, extension) {
|
|
4
|
+
const cleanPath = path.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
5
|
+
if (extension) {
|
|
6
|
+
const ext = extension.startsWith(".") ? extension : `.${extension}`;
|
|
7
|
+
return `content:${cleanPath.replace(new RegExp(`${ext}$`), "")}`;
|
|
8
|
+
}
|
|
9
|
+
return `content:${cleanPath.replace(/\.[^.]+$/, "")}`;
|
|
10
|
+
}
|
|
11
|
+
function getDirname(path) {
|
|
12
|
+
const parts = path.split("/");
|
|
13
|
+
parts.pop();
|
|
14
|
+
return parts.join("/") || "/";
|
|
15
|
+
}
|
|
16
|
+
function getBasename(path) {
|
|
17
|
+
const parts = path.split("/");
|
|
18
|
+
return parts[parts.length - 1];
|
|
19
|
+
}
|
|
20
|
+
function getExtension(filename) {
|
|
21
|
+
const match = filename.match(/\.([^.]+)$/);
|
|
22
|
+
return match ? match[1].toLowerCase() : "";
|
|
23
|
+
}
|
|
24
|
+
function getStem(filename) {
|
|
25
|
+
return filename.replace(/\.[^.]+$/, "");
|
|
26
|
+
}
|
|
27
|
+
function normalizePath(path) {
|
|
28
|
+
return `/${path.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/+$/, "").replace(/\/+/g, "/")}`;
|
|
29
|
+
}
|
|
30
|
+
function pathToTitle(path) {
|
|
31
|
+
const stem = getStem(getBasename(path));
|
|
32
|
+
if (stem === "index") return kebabCase(getBasename(getDirname(path)) || "home").split("-").map(capitalize).join(" ");
|
|
33
|
+
return kebabCase(stem).split("-").map(capitalize).join(" ");
|
|
34
|
+
}
|
|
35
|
+
function capitalize(str) {
|
|
36
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
37
|
+
}
|
|
38
|
+
function parseFrontmatter(content) {
|
|
39
|
+
const data = {};
|
|
40
|
+
const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/);
|
|
41
|
+
if (!fmMatch) return {
|
|
42
|
+
data,
|
|
43
|
+
content
|
|
44
|
+
};
|
|
45
|
+
const [, frontmatterStr, body] = fmMatch;
|
|
46
|
+
const lines = frontmatterStr.split("\n");
|
|
47
|
+
let currentKey = "";
|
|
48
|
+
let isArray = false;
|
|
49
|
+
for (const line of lines) {
|
|
50
|
+
const arrayItemMatch = line.match(/^\s*-\s*(.+)$/);
|
|
51
|
+
if (arrayItemMatch && currentKey && isArray) {
|
|
52
|
+
if (!Array.isArray(data[currentKey])) data[currentKey] = [];
|
|
53
|
+
data[currentKey].push(parseValue(arrayItemMatch[1].trim()));
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const kvMatch = line.match(/^([\w.-]+):\s*(.*)$/);
|
|
57
|
+
if (kvMatch) {
|
|
58
|
+
const [, key, value] = kvMatch;
|
|
59
|
+
currentKey = key;
|
|
60
|
+
if (value.trim() === "") {
|
|
61
|
+
isArray = true;
|
|
62
|
+
data[key] = [];
|
|
63
|
+
} else if (value.trim() === "[]") {
|
|
64
|
+
isArray = false;
|
|
65
|
+
data[key] = [];
|
|
66
|
+
} else if (value.trim() === "{}") {
|
|
67
|
+
isArray = false;
|
|
68
|
+
data[key] = {};
|
|
69
|
+
} else {
|
|
70
|
+
isArray = false;
|
|
71
|
+
data[key] = parseValue(value.trim());
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
data,
|
|
77
|
+
content: body
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function parseValue(value) {
|
|
81
|
+
if (value === "true") return true;
|
|
82
|
+
if (value === "false") return false;
|
|
83
|
+
if (value === "null") return null;
|
|
84
|
+
if (value === "undefined") return void 0;
|
|
85
|
+
if (/^-?\d+$/.test(value)) return parseInt(value, 10);
|
|
86
|
+
if (/^-?\d+\.\d+$/.test(value)) return parseFloat(value);
|
|
87
|
+
if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
function parseMarkdown(content) {
|
|
91
|
+
const children = [];
|
|
92
|
+
const lines = content.split("\n");
|
|
93
|
+
const toc = [];
|
|
94
|
+
let excerpt = "";
|
|
95
|
+
let inCodeBlock = false;
|
|
96
|
+
let codeContent = "";
|
|
97
|
+
let paragraphLines = [];
|
|
98
|
+
function flushParagraph() {
|
|
99
|
+
if (paragraphLines.length > 0) {
|
|
100
|
+
const text = paragraphLines.join(" ").trim();
|
|
101
|
+
if (text) children.push({
|
|
102
|
+
type: "element",
|
|
103
|
+
tag: "p",
|
|
104
|
+
children: [{
|
|
105
|
+
type: "text",
|
|
106
|
+
value: text
|
|
107
|
+
}]
|
|
108
|
+
});
|
|
109
|
+
paragraphLines = [];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
let foundExcerptSeparator = false;
|
|
113
|
+
let headingStack = [toc];
|
|
114
|
+
let headingDepths = [0];
|
|
115
|
+
for (let i = 0; i < lines.length; i++) {
|
|
116
|
+
const line = lines[i];
|
|
117
|
+
if (line.startsWith("```")) {
|
|
118
|
+
flushParagraph();
|
|
119
|
+
if (inCodeBlock) {
|
|
120
|
+
children.push({
|
|
121
|
+
type: "element",
|
|
122
|
+
tag: "pre",
|
|
123
|
+
children: [{
|
|
124
|
+
type: "element",
|
|
125
|
+
tag: "code",
|
|
126
|
+
children: [{
|
|
127
|
+
type: "text",
|
|
128
|
+
value: codeContent
|
|
129
|
+
}]
|
|
130
|
+
}]
|
|
131
|
+
});
|
|
132
|
+
inCodeBlock = false;
|
|
133
|
+
codeContent = "";
|
|
134
|
+
} else {
|
|
135
|
+
inCodeBlock = true;
|
|
136
|
+
codeContent = "";
|
|
137
|
+
}
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (inCodeBlock) {
|
|
141
|
+
codeContent += (codeContent ? "\n" : "") + line;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (line.trim() === "<!-- more -->") {
|
|
145
|
+
foundExcerptSeparator = true;
|
|
146
|
+
if (paragraphLines.length > 0) excerpt = paragraphLines.join(" ").trim();
|
|
147
|
+
else excerpt = [...children].reverse().find((c) => c.tag === "p" && c.children?.[0]?.value)?.children?.[0]?.value || "";
|
|
148
|
+
flushParagraph();
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
|
|
152
|
+
if (headingMatch) {
|
|
153
|
+
flushParagraph();
|
|
154
|
+
const depth = headingMatch[1].length;
|
|
155
|
+
const text = headingMatch[2].replace(/[#*_`~[\]]/g, "").trim();
|
|
156
|
+
const id = kebabCase(text);
|
|
157
|
+
while (headingDepths[headingDepths.length - 1] >= depth) {
|
|
158
|
+
headingDepths.pop();
|
|
159
|
+
headingStack.pop();
|
|
160
|
+
}
|
|
161
|
+
const item = {
|
|
162
|
+
id,
|
|
163
|
+
depth,
|
|
164
|
+
text,
|
|
165
|
+
children: []
|
|
166
|
+
};
|
|
167
|
+
headingStack[headingStack.length - 1].push(item);
|
|
168
|
+
headingStack.push(item.children);
|
|
169
|
+
headingDepths.push(depth);
|
|
170
|
+
children.push({
|
|
171
|
+
type: "element",
|
|
172
|
+
tag: `h${depth}`,
|
|
173
|
+
props: { id },
|
|
174
|
+
children: [{
|
|
175
|
+
type: "text",
|
|
176
|
+
value: text
|
|
177
|
+
}]
|
|
178
|
+
});
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const listMatch = line.match(/^(\s*)[-*]\s+(.+)$/);
|
|
182
|
+
if (listMatch) {
|
|
183
|
+
flushParagraph();
|
|
184
|
+
const text = parseInlineMarkdown(listMatch[2].trim());
|
|
185
|
+
children.push({
|
|
186
|
+
type: "element",
|
|
187
|
+
tag: "li",
|
|
188
|
+
children: [{
|
|
189
|
+
type: "text",
|
|
190
|
+
value: text
|
|
191
|
+
}]
|
|
192
|
+
});
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
const orderedListMatch = line.match(/^(\s*)\d+\.\s+(.+)$/);
|
|
196
|
+
if (orderedListMatch) {
|
|
197
|
+
flushParagraph();
|
|
198
|
+
const text = parseInlineMarkdown(orderedListMatch[2].trim());
|
|
199
|
+
children.push({
|
|
200
|
+
type: "element",
|
|
201
|
+
tag: "li",
|
|
202
|
+
children: [{
|
|
203
|
+
type: "text",
|
|
204
|
+
value: text
|
|
205
|
+
}]
|
|
206
|
+
});
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (line.match(/^[-*_]{3,}$/)) {
|
|
210
|
+
flushParagraph();
|
|
211
|
+
children.push({
|
|
212
|
+
type: "element",
|
|
213
|
+
tag: "hr"
|
|
214
|
+
});
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
const blockquoteMatch = line.match(/^>\s*(.*)$/);
|
|
218
|
+
if (blockquoteMatch) {
|
|
219
|
+
flushParagraph();
|
|
220
|
+
children.push({
|
|
221
|
+
type: "element",
|
|
222
|
+
tag: "blockquote",
|
|
223
|
+
children: [{
|
|
224
|
+
type: "text",
|
|
225
|
+
value: blockquoteMatch[1].trim()
|
|
226
|
+
}]
|
|
227
|
+
});
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (line.trim() === "") {
|
|
231
|
+
flushParagraph();
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
paragraphLines.push(line);
|
|
235
|
+
}
|
|
236
|
+
flushParagraph();
|
|
237
|
+
if (!foundExcerptSeparator && paragraphLines.length === 0 && children.length > 0) {
|
|
238
|
+
const firstP = children.find((c) => c.tag === "p" && c.children?.[0]?.value);
|
|
239
|
+
if (firstP?.children?.[0]?.value) excerpt = firstP.children[0].value.slice(0, 200);
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
type: "root",
|
|
243
|
+
children,
|
|
244
|
+
toc,
|
|
245
|
+
excerpt: excerpt || void 0
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function parseInlineMarkdown(text) {
|
|
249
|
+
return text.replace(/\*\*(.+?)\*\*/g, "$1").replace(/\*(.+?)\*/g, "$1").replace(/`(.+?)`/g, "$1").replace(/\[(.+?)\]\(.+?\)/g, "$1");
|
|
250
|
+
}
|
|
251
|
+
function createQueryBuilder(documents) {
|
|
252
|
+
let result = [...documents];
|
|
253
|
+
const whereClauses = [];
|
|
254
|
+
let sortFields = [];
|
|
255
|
+
let limitCount = null;
|
|
256
|
+
let skipCount = 0;
|
|
257
|
+
let selectedFields = null;
|
|
258
|
+
let excludedFields = null;
|
|
259
|
+
const builder = {
|
|
260
|
+
where(fieldOrQuery, operator, value) {
|
|
261
|
+
if (typeof fieldOrQuery === "object") for (const [key, val] of Object.entries(fieldOrQuery)) whereClauses.push((doc) => getNestedValue(doc, key) === val);
|
|
262
|
+
else {
|
|
263
|
+
const field = fieldOrQuery;
|
|
264
|
+
let op = operator;
|
|
265
|
+
let val = value;
|
|
266
|
+
if (value === void 0 && operator !== void 0) {
|
|
267
|
+
op = "==";
|
|
268
|
+
val = operator;
|
|
269
|
+
}
|
|
270
|
+
whereClauses.push((doc) => {
|
|
271
|
+
const docValue = getNestedValue(doc, field);
|
|
272
|
+
switch (op) {
|
|
273
|
+
case "=":
|
|
274
|
+
case "==":
|
|
275
|
+
case void 0: return docValue === val;
|
|
276
|
+
case "!=": return docValue !== val;
|
|
277
|
+
case ">": return docValue > val;
|
|
278
|
+
case ">=": return docValue >= val;
|
|
279
|
+
case "<": return docValue < val;
|
|
280
|
+
case "<=": return docValue <= val;
|
|
281
|
+
case "contains": return String(docValue).includes(val);
|
|
282
|
+
case "in": return Array.isArray(val) && val.includes(docValue);
|
|
283
|
+
case "exists": return val ? docValue !== void 0 : docValue === void 0;
|
|
284
|
+
default: return docValue === val;
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
return builder;
|
|
289
|
+
},
|
|
290
|
+
sort(field, direction = "asc") {
|
|
291
|
+
sortFields.push({
|
|
292
|
+
field,
|
|
293
|
+
direction
|
|
294
|
+
});
|
|
295
|
+
return builder;
|
|
296
|
+
},
|
|
297
|
+
limit(count) {
|
|
298
|
+
limitCount = count;
|
|
299
|
+
return builder;
|
|
300
|
+
},
|
|
301
|
+
skip(count) {
|
|
302
|
+
skipCount = count;
|
|
303
|
+
return builder;
|
|
304
|
+
},
|
|
305
|
+
only(fields) {
|
|
306
|
+
selectedFields = fields;
|
|
307
|
+
return builder;
|
|
308
|
+
},
|
|
309
|
+
without(fields) {
|
|
310
|
+
excludedFields = fields;
|
|
311
|
+
return builder;
|
|
312
|
+
},
|
|
313
|
+
async find() {
|
|
314
|
+
applyWhere();
|
|
315
|
+
applySort();
|
|
316
|
+
applySkipLimit();
|
|
317
|
+
return applyFieldSelection(result);
|
|
318
|
+
},
|
|
319
|
+
async findOne() {
|
|
320
|
+
limitCount = 1;
|
|
321
|
+
return (await builder.find())[0] || null;
|
|
322
|
+
},
|
|
323
|
+
async findSurround(path, options = {}) {
|
|
324
|
+
const before = options.before ?? 1;
|
|
325
|
+
const after = options.after ?? 1;
|
|
326
|
+
applyWhere();
|
|
327
|
+
applySort();
|
|
328
|
+
const index = result.findIndex((doc) => doc._path === path);
|
|
329
|
+
if (index === -1) return [];
|
|
330
|
+
const start = Math.max(0, index - before);
|
|
331
|
+
const end = Math.min(result.length, index + after + 1);
|
|
332
|
+
return applyFieldSelection(result.slice(start, end).filter((_, i) => i !== before));
|
|
333
|
+
},
|
|
334
|
+
async count() {
|
|
335
|
+
applyWhere();
|
|
336
|
+
return result.length;
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
function applyWhere() {
|
|
340
|
+
result = result.filter((doc) => whereClauses.every((clause) => clause(doc)));
|
|
341
|
+
}
|
|
342
|
+
function applySort() {
|
|
343
|
+
if (sortFields.length === 0) sortFields = [{
|
|
344
|
+
field: "_path",
|
|
345
|
+
direction: "asc"
|
|
346
|
+
}];
|
|
347
|
+
result.sort((a, b) => {
|
|
348
|
+
for (const { field, direction } of sortFields) {
|
|
349
|
+
const aVal = getNestedValue(a, field);
|
|
350
|
+
const bVal = getNestedValue(b, field);
|
|
351
|
+
if (aVal === bVal) continue;
|
|
352
|
+
const cmp = aVal < bVal ? -1 : 1;
|
|
353
|
+
return direction === "desc" ? -cmp : cmp;
|
|
354
|
+
}
|
|
355
|
+
return 0;
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
function applySkipLimit() {
|
|
359
|
+
if (skipCount > 0) result = result.slice(skipCount);
|
|
360
|
+
if (limitCount !== null) result = result.slice(0, limitCount);
|
|
361
|
+
}
|
|
362
|
+
function applyFieldSelection(docs) {
|
|
363
|
+
if (!selectedFields && !excludedFields) return docs;
|
|
364
|
+
return docs.map((doc) => {
|
|
365
|
+
const newDoc = { ...doc };
|
|
366
|
+
if (excludedFields) for (const field of excludedFields) delete newDoc[field];
|
|
367
|
+
if (selectedFields) {
|
|
368
|
+
const kept = {};
|
|
369
|
+
for (const field of selectedFields) kept[field] = doc[field];
|
|
370
|
+
kept._id = doc._id;
|
|
371
|
+
kept._path = doc._path;
|
|
372
|
+
return kept;
|
|
373
|
+
}
|
|
374
|
+
return newDoc;
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
return builder;
|
|
378
|
+
}
|
|
379
|
+
function getNestedValue(obj, path) {
|
|
380
|
+
return path.split(".").reduce((o, key) => o?.[key], obj);
|
|
381
|
+
}
|
|
382
|
+
function buildNavigation(documents, _options = {}) {
|
|
383
|
+
const tree = [];
|
|
384
|
+
const map = /* @__PURE__ */ new Map();
|
|
385
|
+
const docs = documents.filter((d) => !d._draft && !d._partial && d.navigation !== false).sort((a, b) => a._path.localeCompare(b._path));
|
|
386
|
+
for (const doc of docs) {
|
|
387
|
+
const item = {
|
|
388
|
+
title: doc.title || pathToTitle(doc._file),
|
|
389
|
+
path: doc._path,
|
|
390
|
+
id: doc._id,
|
|
391
|
+
draft: doc._draft
|
|
392
|
+
};
|
|
393
|
+
map.set(doc._path, item);
|
|
394
|
+
}
|
|
395
|
+
for (const doc of docs) {
|
|
396
|
+
const item = map.get(doc._path);
|
|
397
|
+
if (doc._path === "/") {
|
|
398
|
+
tree.unshift(item);
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
const parentPath = doc._dir;
|
|
402
|
+
if (parentPath === "/") {
|
|
403
|
+
tree.push(item);
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
const parent = map.get(parentPath);
|
|
407
|
+
if (parent) {
|
|
408
|
+
if (!parent.children) parent.children = [];
|
|
409
|
+
parent.children.push(item);
|
|
410
|
+
} else tree.push(item);
|
|
411
|
+
}
|
|
412
|
+
return tree;
|
|
413
|
+
}
|
|
414
|
+
function parseContent(raw, filePath, options = {}) {
|
|
415
|
+
const extension = getExtension(filePath);
|
|
416
|
+
const type = options.type || extensionToType(extension);
|
|
417
|
+
const stem = getStem(getBasename(filePath));
|
|
418
|
+
const isDraft = stem.startsWith(".") || filePath.includes("/.") || filePath.includes("_draft");
|
|
419
|
+
const isPartial = stem.startsWith("_") || filePath.includes("_partial");
|
|
420
|
+
let meta = {
|
|
421
|
+
_id: generateId(filePath, extension),
|
|
422
|
+
_path: normalizePath(`${getDirname(filePath)}/${stem === "index" ? "" : stem}`),
|
|
423
|
+
_file: getBasename(filePath),
|
|
424
|
+
_dir: normalizePath(getDirname(filePath)),
|
|
425
|
+
_draft: isDraft,
|
|
426
|
+
_partial: isPartial,
|
|
427
|
+
_type: type,
|
|
428
|
+
_extension: extension,
|
|
429
|
+
_empty: raw.trim().length === 0
|
|
430
|
+
};
|
|
431
|
+
let body;
|
|
432
|
+
let extraMeta = {};
|
|
433
|
+
if (type === "markdown" || type === "mdx") {
|
|
434
|
+
const { data, content } = parseFrontmatter(raw);
|
|
435
|
+
extraMeta = data;
|
|
436
|
+
body = parseMarkdown(content);
|
|
437
|
+
if (body.excerpt) extraMeta.description = extraMeta.description || body.excerpt;
|
|
438
|
+
} else if (type === "json") try {
|
|
439
|
+
extraMeta = JSON.parse(raw);
|
|
440
|
+
} catch {
|
|
441
|
+
extraMeta = {};
|
|
442
|
+
}
|
|
443
|
+
else if (type === "yaml") extraMeta = parseSimpleYaml(raw);
|
|
444
|
+
return {
|
|
445
|
+
...meta,
|
|
446
|
+
...extraMeta,
|
|
447
|
+
title: extraMeta.title || (meta._path === "/" ? "Home" : pathToTitle(filePath)),
|
|
448
|
+
body,
|
|
449
|
+
_draft: extraMeta.draft || meta._draft,
|
|
450
|
+
_partial: extraMeta.partial || meta._partial
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
function extensionToType(ext) {
|
|
454
|
+
return {
|
|
455
|
+
md: "markdown",
|
|
456
|
+
mdx: "mdx",
|
|
457
|
+
json: "json",
|
|
458
|
+
yaml: "yaml",
|
|
459
|
+
yml: "yaml",
|
|
460
|
+
csv: "csv",
|
|
461
|
+
html: "html",
|
|
462
|
+
htm: "html"
|
|
463
|
+
}[ext] || "markdown";
|
|
464
|
+
}
|
|
465
|
+
function parseSimpleYaml(content) {
|
|
466
|
+
const result = {};
|
|
467
|
+
const lines = content.split("\n");
|
|
468
|
+
for (const line of lines) {
|
|
469
|
+
const kvMatch = line.match(/^([\w.-]+):\s*(.*)$/);
|
|
470
|
+
if (kvMatch) {
|
|
471
|
+
const [, key, value] = kvMatch;
|
|
472
|
+
result[key] = parseValue(value.trim() || "");
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return result;
|
|
476
|
+
}
|
|
477
|
+
function defineContentCollection(collection) {
|
|
478
|
+
const docs = collection.documents || [];
|
|
479
|
+
return {
|
|
480
|
+
name: collection.name,
|
|
481
|
+
source: collection.source,
|
|
482
|
+
type: collection.type,
|
|
483
|
+
schema: collection.schema,
|
|
484
|
+
documents: docs,
|
|
485
|
+
list: async () => docs,
|
|
486
|
+
getItem: async (path) => docs.find((d) => d._path === path) || null,
|
|
487
|
+
query: () => createQueryBuilder(docs)
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
function createContentCollection(name, source, documents = []) {
|
|
491
|
+
return defineContentCollection({
|
|
492
|
+
name,
|
|
493
|
+
source,
|
|
494
|
+
documents
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
//#endregion
|
|
498
|
+
//#region src/runtime.ts
|
|
499
|
+
let collections = /* @__PURE__ */ new Map();
|
|
500
|
+
let navigationCache = null;
|
|
501
|
+
function configureContentRuntime(_options = {}) {
|
|
502
|
+
collections = /* @__PURE__ */ new Map();
|
|
503
|
+
navigationCache = null;
|
|
504
|
+
}
|
|
505
|
+
function defineCollection(config) {
|
|
506
|
+
const collection = createContentCollection(config.name, config.source);
|
|
507
|
+
collections.set(config.name, collection);
|
|
508
|
+
navigationCache = null;
|
|
509
|
+
return collection;
|
|
510
|
+
}
|
|
511
|
+
function getCollection(name) {
|
|
512
|
+
return collections.get(name);
|
|
513
|
+
}
|
|
514
|
+
function listCollections() {
|
|
515
|
+
return Array.from(collections.keys());
|
|
516
|
+
}
|
|
517
|
+
async function queryCollection(name) {
|
|
518
|
+
const collection = collections.get(name);
|
|
519
|
+
if (!collection) throw new Error(`Collection "${name}" not found. Available collections: ${listCollections().join(", ")}`);
|
|
520
|
+
return createQueryBuilder(await collection.list());
|
|
521
|
+
}
|
|
522
|
+
async function getContentItem(collection, path) {
|
|
523
|
+
const col = collections.get(collection);
|
|
524
|
+
if (!col) return null;
|
|
525
|
+
return col.getItem(path);
|
|
526
|
+
}
|
|
527
|
+
async function fetchContentNavigation(collectionName) {
|
|
528
|
+
if (navigationCache && !collectionName) return navigationCache;
|
|
529
|
+
let allDocs = [];
|
|
530
|
+
if (collectionName) {
|
|
531
|
+
const col = collections.get(collectionName);
|
|
532
|
+
if (col) allDocs = await col.list();
|
|
533
|
+
} else for (const col of collections.values()) {
|
|
534
|
+
const docs = await col.list();
|
|
535
|
+
allDocs.push(...docs);
|
|
536
|
+
}
|
|
537
|
+
const nav = buildNavigation(allDocs);
|
|
538
|
+
if (!collectionName) navigationCache = nav;
|
|
539
|
+
return nav;
|
|
540
|
+
}
|
|
541
|
+
function registerContent(collectionName, documents) {
|
|
542
|
+
let collection = collections.get(collectionName);
|
|
543
|
+
if (!collection) {
|
|
544
|
+
collection = defineContentCollection({
|
|
545
|
+
name: collectionName,
|
|
546
|
+
source: collectionName,
|
|
547
|
+
documents
|
|
548
|
+
});
|
|
549
|
+
collections.set(collectionName, collection);
|
|
550
|
+
} else collection.documents.push(...documents);
|
|
551
|
+
navigationCache = null;
|
|
552
|
+
return collection;
|
|
553
|
+
}
|
|
554
|
+
function parseContentFile(raw, filePath, options) {
|
|
555
|
+
return parseContent(raw, filePath, options);
|
|
556
|
+
}
|
|
557
|
+
//#endregion
|
|
558
|
+
export { pathToTitle as C, parseMarkdown as S, getExtension as _, getContentItem as a, parseContent as b, queryCollection as c, createContentCollection as d, createQueryBuilder as f, getDirname as g, getBasename as h, getCollection as i, registerContent as l, generateId as m, defineCollection as n, listCollections as o, defineContentCollection as p, fetchContentNavigation as r, parseContentFile as s, configureContentRuntime as t, buildNavigation as u, getStem as v, parseFrontmatter as x, normalizePath as y };
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
type ContentType = 'markdown' | 'mdx' | 'json' | 'yaml' | 'csv' | 'html';
|
|
3
|
+
interface ContentDocument {
|
|
4
|
+
_id: string;
|
|
5
|
+
_path: string;
|
|
6
|
+
_dir: string;
|
|
7
|
+
_file: string;
|
|
8
|
+
_type: ContentType;
|
|
9
|
+
_extension: string;
|
|
10
|
+
_draft: boolean;
|
|
11
|
+
_partial: boolean;
|
|
12
|
+
_empty: boolean;
|
|
13
|
+
title?: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
date?: string;
|
|
16
|
+
draft?: boolean;
|
|
17
|
+
partial?: boolean;
|
|
18
|
+
navigation?: boolean | {
|
|
19
|
+
title?: string;
|
|
20
|
+
order?: number;
|
|
21
|
+
};
|
|
22
|
+
body?: ContentBody;
|
|
23
|
+
[key: string]: any;
|
|
24
|
+
}
|
|
25
|
+
interface ContentBody {
|
|
26
|
+
type: 'root';
|
|
27
|
+
children: MarkdownNode[];
|
|
28
|
+
toc?: ContentTocItem[];
|
|
29
|
+
excerpt?: string;
|
|
30
|
+
}
|
|
31
|
+
interface MarkdownNode {
|
|
32
|
+
type: string;
|
|
33
|
+
tag?: string;
|
|
34
|
+
value?: string;
|
|
35
|
+
props?: Record<string, any>;
|
|
36
|
+
children?: MarkdownNode[];
|
|
37
|
+
}
|
|
38
|
+
interface ContentTocItem {
|
|
39
|
+
id: string;
|
|
40
|
+
depth: number;
|
|
41
|
+
text: string;
|
|
42
|
+
children: ContentTocItem[];
|
|
43
|
+
}
|
|
44
|
+
interface ContentCollection {
|
|
45
|
+
name: string;
|
|
46
|
+
source: string;
|
|
47
|
+
type?: ContentType;
|
|
48
|
+
schema?: ContentSchema;
|
|
49
|
+
documents: ContentDocument[];
|
|
50
|
+
list: () => Promise<ContentDocument[]>;
|
|
51
|
+
getItem: (path: string) => Promise<ContentDocument | null>;
|
|
52
|
+
query: () => ContentQueryBuilder;
|
|
53
|
+
}
|
|
54
|
+
interface ContentFieldSchema {
|
|
55
|
+
type: 'string' | 'number' | 'boolean' | 'date' | 'array' | 'object' | 'markdown' | 'json';
|
|
56
|
+
required?: boolean;
|
|
57
|
+
default?: any;
|
|
58
|
+
items?: ContentFieldSchema;
|
|
59
|
+
properties?: Record<string, ContentFieldSchema>;
|
|
60
|
+
description?: string;
|
|
61
|
+
enum?: any[];
|
|
62
|
+
format?: string;
|
|
63
|
+
}
|
|
64
|
+
interface ContentSchema {
|
|
65
|
+
title?: string;
|
|
66
|
+
description?: string;
|
|
67
|
+
type: 'object';
|
|
68
|
+
properties: Record<string, ContentFieldSchema>;
|
|
69
|
+
required?: string[];
|
|
70
|
+
}
|
|
71
|
+
interface ContentQueryBuilder {
|
|
72
|
+
where(field: string, operator: string, value: any): ContentQueryBuilder;
|
|
73
|
+
where(query: Record<string, any>): ContentQueryBuilder;
|
|
74
|
+
sort(field: string, direction?: 'asc' | 'desc'): ContentQueryBuilder;
|
|
75
|
+
limit(count: number): ContentQueryBuilder;
|
|
76
|
+
skip(count: number): ContentQueryBuilder;
|
|
77
|
+
only(fields: string[]): ContentQueryBuilder;
|
|
78
|
+
without(fields: string[]): ContentQueryBuilder;
|
|
79
|
+
find(): Promise<ContentDocument[]>;
|
|
80
|
+
findOne(): Promise<ContentDocument | null>;
|
|
81
|
+
findSurround(path: string, options?: {
|
|
82
|
+
before?: number;
|
|
83
|
+
after?: number;
|
|
84
|
+
}): Promise<ContentDocument[]>;
|
|
85
|
+
count(): Promise<number>;
|
|
86
|
+
}
|
|
87
|
+
interface ContentModuleOptions {
|
|
88
|
+
sources: Record<string, ContentSourceConfig>;
|
|
89
|
+
defaultSource: string;
|
|
90
|
+
markdown: MarkdownOptions;
|
|
91
|
+
highlight: HighlightOptions;
|
|
92
|
+
navigation: boolean;
|
|
93
|
+
experimental: {
|
|
94
|
+
advancedSyntax: boolean;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
interface ContentSourceConfig {
|
|
98
|
+
driver: 'fs' | 'github' | 'http' | 'custom';
|
|
99
|
+
base?: string;
|
|
100
|
+
dirname?: string;
|
|
101
|
+
prefix?: string;
|
|
102
|
+
driverOptions?: Record<string, any>;
|
|
103
|
+
}
|
|
104
|
+
interface MarkdownOptions {
|
|
105
|
+
toc: {
|
|
106
|
+
depth: number;
|
|
107
|
+
searchDepth: number;
|
|
108
|
+
};
|
|
109
|
+
anchorLinks: boolean;
|
|
110
|
+
externalLinks: boolean;
|
|
111
|
+
tables: boolean;
|
|
112
|
+
footnotes: boolean;
|
|
113
|
+
mdc: boolean;
|
|
114
|
+
remarkPlugins: any[];
|
|
115
|
+
rehypePlugins: any[];
|
|
116
|
+
}
|
|
117
|
+
interface HighlightOptions {
|
|
118
|
+
theme: string | Record<string, string>;
|
|
119
|
+
preload: string[];
|
|
120
|
+
langs: string[];
|
|
121
|
+
wrapperStyle: boolean;
|
|
122
|
+
}
|
|
123
|
+
interface ParsedContentMeta {
|
|
124
|
+
_id: string;
|
|
125
|
+
_path: string;
|
|
126
|
+
_file: string;
|
|
127
|
+
_dir: string;
|
|
128
|
+
_draft: boolean;
|
|
129
|
+
_partial: boolean;
|
|
130
|
+
_type: ContentType;
|
|
131
|
+
_extension: string;
|
|
132
|
+
_empty: boolean;
|
|
133
|
+
title?: string;
|
|
134
|
+
description?: string;
|
|
135
|
+
date?: string;
|
|
136
|
+
draft?: boolean;
|
|
137
|
+
partial?: boolean;
|
|
138
|
+
navigation?: boolean;
|
|
139
|
+
}
|
|
140
|
+
interface ContentNavigationItem {
|
|
141
|
+
title: string;
|
|
142
|
+
path: string;
|
|
143
|
+
id: string;
|
|
144
|
+
draft?: boolean;
|
|
145
|
+
children?: ContentNavigationItem[];
|
|
146
|
+
}
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/core.d.ts
|
|
149
|
+
declare function generateId(path: string, extension?: string): string;
|
|
150
|
+
declare function getDirname(path: string): string;
|
|
151
|
+
declare function getBasename(path: string): string;
|
|
152
|
+
declare function getExtension(filename: string): string;
|
|
153
|
+
declare function getStem(filename: string): string;
|
|
154
|
+
declare function normalizePath(path: string): string;
|
|
155
|
+
declare function pathToTitle(path: string): string;
|
|
156
|
+
declare function parseFrontmatter(content: string): {
|
|
157
|
+
data: Record<string, any>;
|
|
158
|
+
content: string;
|
|
159
|
+
};
|
|
160
|
+
declare function parseMarkdown(content: string): ContentBody;
|
|
161
|
+
declare function createQueryBuilder(documents: ContentDocument[]): ContentQueryBuilder;
|
|
162
|
+
declare function buildNavigation(documents: ContentDocument[], _options?: {
|
|
163
|
+
fields?: string[];
|
|
164
|
+
}): ContentNavigationItem[];
|
|
165
|
+
declare function parseContent(raw: string, filePath: string, options?: {
|
|
166
|
+
type?: string;
|
|
167
|
+
}): ContentDocument;
|
|
168
|
+
declare function defineContentCollection(collection: {
|
|
169
|
+
name: string;
|
|
170
|
+
source: string;
|
|
171
|
+
type?: any;
|
|
172
|
+
schema?: ContentSchema;
|
|
173
|
+
documents?: ContentDocument[];
|
|
174
|
+
}): ContentCollection;
|
|
175
|
+
declare function createContentCollection(name: string, source: string, documents?: ContentDocument[]): ContentCollection;
|
|
176
|
+
//#endregion
|
|
177
|
+
//#region src/runtime.d.ts
|
|
178
|
+
declare function configureContentRuntime(_options?: Partial<ContentModuleOptions>): void;
|
|
179
|
+
declare function defineCollection(config: {
|
|
180
|
+
name: string;
|
|
181
|
+
source: string;
|
|
182
|
+
type?: any;
|
|
183
|
+
schema?: ContentSchema;
|
|
184
|
+
}): ContentCollection;
|
|
185
|
+
declare function getCollection(name: string): ContentCollection | undefined;
|
|
186
|
+
declare function listCollections(): string[];
|
|
187
|
+
declare function queryCollection(name: string): Promise<ContentQueryBuilder>;
|
|
188
|
+
declare function getContentItem(collection: string, path: string): Promise<ContentDocument | null>;
|
|
189
|
+
declare function fetchContentNavigation(collectionName?: string): Promise<ContentNavigationItem[]>;
|
|
190
|
+
declare function registerContent(collectionName: string, documents: ContentDocument[]): ContentCollection;
|
|
191
|
+
declare function parseContentFile(raw: string, filePath: string, options?: {
|
|
192
|
+
type?: string;
|
|
193
|
+
}): ContentDocument;
|
|
194
|
+
//#endregion
|
|
195
|
+
export { ContentQueryBuilder as A, pathToTitle as C, ContentFieldSchema as D, ContentDocument as E, MarkdownNode as F, ParsedContentMeta as I, ContentSourceConfig as M, ContentTocItem as N, ContentModuleOptions as O, ContentType as P, parseMarkdown as S, ContentCollection as T, getExtension as _, getContentItem as a, parseContent as b, queryCollection as c, createContentCollection as d, createQueryBuilder as f, getDirname as g, getBasename as h, getCollection as i, ContentSchema as j, ContentNavigationItem as k, registerContent as l, generateId as m, defineCollection as n, listCollections as o, defineContentCollection as p, fetchContentNavigation as r, parseContentFile as s, configureContentRuntime as t, buildNavigation as u, getStem as v, ContentBody as w, parseFrontmatter as x, normalizePath as y };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { A as ContentQueryBuilder, C as pathToTitle, E as ContentDocument, F as MarkdownNode, M as ContentSourceConfig, N as ContentTocItem, O as ContentModuleOptions, P as ContentType, S as parseMarkdown, T as ContentCollection, a as getContentItem, b as parseContent, c as queryCollection, d as createContentCollection, f as createQueryBuilder, i as getCollection, j as ContentSchema, k as ContentNavigationItem, l as registerContent, m as generateId, n as defineCollection, o as listCollections, p as defineContentCollection, r as fetchContentNavigation, s as parseContentFile, t as configureContentRuntime, u as buildNavigation, w as ContentBody, x as parseFrontmatter, y as normalizePath } from "./runtime-zD72Z3v3.js";
|
|
2
|
+
export { type ContentBody, type ContentCollection, type ContentDocument, type ContentModuleOptions, type ContentNavigationItem, type ContentQueryBuilder, type ContentSchema, type ContentSourceConfig, type ContentTocItem, type ContentType, type MarkdownNode, buildNavigation, configureContentRuntime, createContentCollection, createQueryBuilder, defineCollection, defineContentCollection, fetchContentNavigation, generateId, getCollection, getContentItem, listCollections, normalizePath, parseContent, parseContentFile, parseFrontmatter, parseMarkdown, pathToTitle, queryCollection, registerContent };
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { C as pathToTitle, S as parseMarkdown, a as getContentItem, b as parseContent, c as queryCollection, d as createContentCollection, f as createQueryBuilder, i as getCollection, l as registerContent, m as generateId, n as defineCollection, o as listCollections, p as defineContentCollection, r as fetchContentNavigation, s as parseContentFile, t as configureContentRuntime, u as buildNavigation, x as parseFrontmatter, y as normalizePath } from "./runtime-C8qrycGf.js";
|
|
2
|
+
export { buildNavigation, configureContentRuntime, createContentCollection, createQueryBuilder, defineCollection, defineContentCollection, fetchContentNavigation, generateId, getCollection, getContentItem, listCollections, normalizePath, parseContent, parseContentFile, parseFrontmatter, parseMarkdown, pathToTitle, queryCollection, registerContent };
|
package/dist/vite.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Plugin } from "vite";
|
|
2
|
+
//#region src/vite.d.ts
|
|
3
|
+
interface UbeanContentOptions {
|
|
4
|
+
sources?: Record<string, {
|
|
5
|
+
dir: string;
|
|
6
|
+
prefix?: string;
|
|
7
|
+
type?: string;
|
|
8
|
+
}>;
|
|
9
|
+
defaultDir?: string;
|
|
10
|
+
ignores?: string[];
|
|
11
|
+
markdown?: {
|
|
12
|
+
toc?: {
|
|
13
|
+
depth?: number;
|
|
14
|
+
searchDepth?: number;
|
|
15
|
+
};
|
|
16
|
+
anchorLinks?: boolean;
|
|
17
|
+
};
|
|
18
|
+
navigation?: boolean;
|
|
19
|
+
experimental?: {
|
|
20
|
+
watch?: boolean;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
declare function ubeanContentPlugin(userOptions?: UbeanContentOptions): Plugin;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { UbeanContentOptions, ubeanContentPlugin as default, ubeanContentPlugin };
|
package/dist/vite.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { l as registerContent, s as parseContentFile, t as configureContentRuntime } from "./runtime-C8qrycGf.js";
|
|
2
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
3
|
+
import { defu } from "defu";
|
|
4
|
+
import { join, relative, resolve } from "pathe";
|
|
5
|
+
//#region src/vite.ts
|
|
6
|
+
const defaultOptions = {
|
|
7
|
+
sources: { content: { dir: "content" } },
|
|
8
|
+
defaultDir: "content",
|
|
9
|
+
ignores: [
|
|
10
|
+
"draft",
|
|
11
|
+
"partial",
|
|
12
|
+
"."
|
|
13
|
+
],
|
|
14
|
+
navigation: true,
|
|
15
|
+
experimental: { watch: true }
|
|
16
|
+
};
|
|
17
|
+
const VIRTUAL_CONTENT = "virtual:ubean-content";
|
|
18
|
+
const RESOLVED_VIRTUAL_CONTENT = `\0${VIRTUAL_CONTENT}`;
|
|
19
|
+
function ubeanContentPlugin(userOptions = {}) {
|
|
20
|
+
const options = defu(userOptions, defaultOptions);
|
|
21
|
+
let rootDir;
|
|
22
|
+
let loadedDocuments = {};
|
|
23
|
+
function walkDir(dir, baseDir, files = []) {
|
|
24
|
+
if (!existsSync(dir)) return files;
|
|
25
|
+
const entries = readdirSync(dir);
|
|
26
|
+
for (const entry of entries) {
|
|
27
|
+
const fullPath = join(dir, entry);
|
|
28
|
+
if (statSync(fullPath).isDirectory()) {
|
|
29
|
+
if (entry === "node_modules" || entry.startsWith(".")) continue;
|
|
30
|
+
walkDir(fullPath, baseDir, files);
|
|
31
|
+
} else if (/\.(md|mdx|json|ya?ml)$/.test(entry)) files.push(relative(baseDir, fullPath));
|
|
32
|
+
}
|
|
33
|
+
return files;
|
|
34
|
+
}
|
|
35
|
+
function scanContent() {
|
|
36
|
+
loadedDocuments = {};
|
|
37
|
+
configureContentRuntime();
|
|
38
|
+
for (const [name, sourceConfig] of Object.entries(options.sources || {})) {
|
|
39
|
+
const contentDir = resolve(rootDir, sourceConfig.dir || options.defaultDir);
|
|
40
|
+
if (!existsSync(contentDir)) continue;
|
|
41
|
+
const files = walkDir(contentDir, contentDir);
|
|
42
|
+
const documents = [];
|
|
43
|
+
for (const file of files) {
|
|
44
|
+
const fullPath = join(contentDir, file);
|
|
45
|
+
try {
|
|
46
|
+
const parsed = parseContentFile(readFileSync(fullPath, "utf-8"), file, { type: sourceConfig.type });
|
|
47
|
+
if (sourceConfig.prefix) parsed._path = sourceConfig.prefix + parsed._path;
|
|
48
|
+
documents.push(parsed);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
console.warn(`[ubean-content] Failed to parse ${file}:`, err);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
loadedDocuments[name] = documents;
|
|
54
|
+
registerContent(name, documents);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
name: "ubean:content",
|
|
59
|
+
enforce: "pre",
|
|
60
|
+
configResolved(resolvedConfig) {
|
|
61
|
+
rootDir = resolvedConfig.root;
|
|
62
|
+
scanContent();
|
|
63
|
+
},
|
|
64
|
+
resolveId(id) {
|
|
65
|
+
if (id === VIRTUAL_CONTENT || id.startsWith(`${VIRTUAL_CONTENT}/`)) return `\0${id}`;
|
|
66
|
+
return null;
|
|
67
|
+
},
|
|
68
|
+
load(id) {
|
|
69
|
+
if (id === RESOLVED_VIRTUAL_CONTENT) return `${Object.entries(loadedDocuments).map(([name, docs]) => `export const ${name} = ${JSON.stringify(docs)};`).join("\n")}
|
|
70
|
+
|
|
71
|
+
export const collections = {
|
|
72
|
+
${Object.keys(loadedDocuments).map((n) => ` ${n}: ${n}`).join(",\n")}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export function getCollection(name) {
|
|
76
|
+
return collections[name] || [];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export default collections;
|
|
80
|
+
`;
|
|
81
|
+
if (id.startsWith(`${RESOLVED_VIRTUAL_CONTENT}/`)) {
|
|
82
|
+
const collectionName = id.slice(RESOLVED_VIRTUAL_CONTENT.length + 1);
|
|
83
|
+
const docs = loadedDocuments[collectionName] || [];
|
|
84
|
+
return `export default ${JSON.stringify(docs)};`;
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
},
|
|
88
|
+
configureServer(server) {
|
|
89
|
+
if (options.experimental?.watch) {
|
|
90
|
+
const watchPatterns = Object.values(options.sources || {}).map((s) => {
|
|
91
|
+
return join(s.dir || options.defaultDir, "**/*.{md,mdx,json,yaml,yml}");
|
|
92
|
+
});
|
|
93
|
+
server.watcher.add(watchPatterns);
|
|
94
|
+
server.watcher.on("change", () => {
|
|
95
|
+
scanContent();
|
|
96
|
+
server.ws.send({ type: "full-reload" });
|
|
97
|
+
});
|
|
98
|
+
server.watcher.on("add", () => {
|
|
99
|
+
scanContent();
|
|
100
|
+
server.ws.send({ type: "full-reload" });
|
|
101
|
+
});
|
|
102
|
+
server.watcher.on("unlink", () => {
|
|
103
|
+
scanContent();
|
|
104
|
+
server.ws.send({ type: "full-reload" });
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
//#endregion
|
|
111
|
+
export { ubeanContentPlugin as default, ubeanContentPlugin };
|