@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.
@@ -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 };
@@ -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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ubean/content",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "File-based content module for ubean (markdown/MDX/YAML/JSON)",
5
5
  "files": [
6
6
  "dist"