@zenithbuild/plugins 0.3.5 → 0.3.6
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/content/hooks/useZenOrder.ts +67 -79
- package/content/index.ts +5 -0
- package/content/loader.ts +20 -1
- package/content/markdown.ts +20 -2
- package/content/query.ts +66 -0
- package/package.json +2 -2
|
@@ -172,90 +172,78 @@ export function createZenOrder(rawSections: any[]): ZenOrderReturn {
|
|
|
172
172
|
// Process and sort sections
|
|
173
173
|
const sections = processRawSections(rawSections);
|
|
174
174
|
|
|
175
|
-
//
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
175
|
+
// React to selection changes by updating the returned object's properties
|
|
176
|
+
const actions: ZenOrderActions = {
|
|
177
|
+
selectSection: (section: Section): void => {
|
|
178
|
+
instance.selectedSection = section;
|
|
179
|
+
instance.selectedDoc = section.items[0] || null;
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
selectDoc: (doc: DocItem): void => {
|
|
183
|
+
instance.selectedDoc = doc;
|
|
184
|
+
const docSection = sections.find(s => s.slug === doc.sectionSlug);
|
|
185
|
+
if (docSection) {
|
|
186
|
+
instance.selectedSection = docSection;
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
getSectionBySlug: (sectionSlug: string): Section | null => {
|
|
191
|
+
return sections.find(s => s.slug === sectionSlug) || null;
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
getDocBySlug: (sectionSlug: string, docSlug: string): DocItem | null => {
|
|
195
|
+
const section = sections.find(s => s.slug === sectionSlug);
|
|
196
|
+
if (!section) return null;
|
|
197
|
+
return section.items.find(d => d.slug === docSlug) || null;
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
getNextDoc: (currentDoc: DocItem): DocItem | null => {
|
|
201
|
+
const currentSection = sections.find(s => s.slug === currentDoc.sectionSlug);
|
|
202
|
+
if (!currentSection) return null;
|
|
203
|
+
|
|
204
|
+
const currentIndex = currentSection.items.findIndex(d => d.slug === currentDoc.slug);
|
|
205
|
+
|
|
206
|
+
if (currentIndex < currentSection.items.length - 1) {
|
|
207
|
+
return currentSection.items[currentIndex + 1];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const sectionIndex = sections.findIndex(s => s.slug === currentSection.slug);
|
|
211
|
+
if (sectionIndex < sections.length - 1) {
|
|
212
|
+
const nextSection = sections[sectionIndex + 1];
|
|
213
|
+
return nextSection.items[0] || null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return null;
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
getPrevDoc: (currentDoc: DocItem): DocItem | null => {
|
|
220
|
+
const currentSection = sections.find(s => s.slug === currentDoc.sectionSlug);
|
|
221
|
+
if (!currentSection) return null;
|
|
222
|
+
|
|
223
|
+
const currentIndex = currentSection.items.findIndex(d => d.slug === currentDoc.slug);
|
|
224
|
+
|
|
225
|
+
if (currentIndex > 0) {
|
|
226
|
+
return currentSection.items[currentIndex - 1];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const sectionIndex = sections.findIndex(s => s.slug === currentSection.slug);
|
|
230
|
+
if (sectionIndex > 0) {
|
|
231
|
+
const prevSection = sections[sectionIndex - 1];
|
|
232
|
+
return prevSection.items[prevSection.items.length - 1] || null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return null;
|
|
194
236
|
}
|
|
195
237
|
};
|
|
196
238
|
|
|
197
|
-
const
|
|
198
|
-
return sections.find(s => s.slug === sectionSlug) || null;
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
const getDocBySlug = (sectionSlug: string, docSlug: string): DocItem | null => {
|
|
202
|
-
const section = getSectionBySlug(sectionSlug);
|
|
203
|
-
if (!section) return null;
|
|
204
|
-
return section.items.find(d => d.slug === docSlug) || null;
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
const getNextDoc = (currentDoc: DocItem): DocItem | null => {
|
|
208
|
-
const currentSection = sections.find(s => s.slug === currentDoc.sectionSlug);
|
|
209
|
-
if (!currentSection) return null;
|
|
210
|
-
|
|
211
|
-
const currentIndex = currentSection.items.findIndex(d => d.slug === currentDoc.slug);
|
|
212
|
-
|
|
213
|
-
// Try next doc in same section
|
|
214
|
-
if (currentIndex < currentSection.items.length - 1) {
|
|
215
|
-
return currentSection.items[currentIndex + 1];
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Try first doc of next section
|
|
219
|
-
const sectionIndex = sections.findIndex(s => s.slug === currentSection.slug);
|
|
220
|
-
if (sectionIndex < sections.length - 1) {
|
|
221
|
-
const nextSection = sections[sectionIndex + 1];
|
|
222
|
-
return nextSection.items[0] || null;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
return null;
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
const getPrevDoc = (currentDoc: DocItem): DocItem | null => {
|
|
229
|
-
const currentSection = sections.find(s => s.slug === currentDoc.sectionSlug);
|
|
230
|
-
if (!currentSection) return null;
|
|
231
|
-
|
|
232
|
-
const currentIndex = currentSection.items.findIndex(d => d.slug === currentDoc.slug);
|
|
233
|
-
|
|
234
|
-
// Try previous doc in same section
|
|
235
|
-
if (currentIndex > 0) {
|
|
236
|
-
return currentSection.items[currentIndex - 1];
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Try last doc of previous section
|
|
240
|
-
const sectionIndex = sections.findIndex(s => s.slug === currentSection.slug);
|
|
241
|
-
if (sectionIndex > 0) {
|
|
242
|
-
const prevSection = sections[sectionIndex - 1];
|
|
243
|
-
return prevSection.items[prevSection.items.length - 1] || null;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
return null;
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
return {
|
|
250
|
-
...state,
|
|
239
|
+
const instance: ZenOrderReturn = {
|
|
251
240
|
sections,
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
getPrevDoc,
|
|
256
|
-
getDocBySlug,
|
|
257
|
-
getSectionBySlug
|
|
241
|
+
selectedSection: sections[0] || null,
|
|
242
|
+
selectedDoc: sections[0]?.items[0] || null,
|
|
243
|
+
...actions
|
|
258
244
|
};
|
|
245
|
+
|
|
246
|
+
return instance;
|
|
259
247
|
}
|
|
260
248
|
|
|
261
249
|
/**
|
package/content/index.ts
CHANGED
|
@@ -107,11 +107,14 @@ export default function content(options: ContentPluginOptions = {}): ZenithPlugi
|
|
|
107
107
|
name: 'zenith-content',
|
|
108
108
|
config: options,
|
|
109
109
|
setup(ctx: PluginContext) {
|
|
110
|
+
console.log('[zenith:content] setup() starting...');
|
|
110
111
|
let collections: Record<string, ContentItem[]> = {};
|
|
111
112
|
|
|
112
113
|
if (options.sources && Object.keys(options.sources).length > 0) {
|
|
113
114
|
// Use new sources configuration
|
|
115
|
+
console.log('[zenith:content] Loading from sources...');
|
|
114
116
|
collections = loadFromSources(options.sources, ctx.projectRoot);
|
|
117
|
+
console.log('[zenith:content] loadFromSources completed');
|
|
115
118
|
} else if (options.contentDir) {
|
|
116
119
|
// Legacy: single content directory
|
|
117
120
|
const contentPath = path.resolve(ctx.projectRoot, options.contentDir);
|
|
@@ -131,10 +134,12 @@ export default function content(options: ContentPluginOptions = {}): ZenithPlugi
|
|
|
131
134
|
|
|
132
135
|
// Pass to runtime using generic namespaced data store
|
|
133
136
|
const allItems = Object.values(collections).flat();
|
|
137
|
+
console.log('[zenith:content] Setting plugin data, items:', allItems.length);
|
|
134
138
|
ctx.setPluginData('content', allItems);
|
|
135
139
|
|
|
136
140
|
// Update legacy storage
|
|
137
141
|
allContent = allItems;
|
|
142
|
+
console.log('[zenith:content] setup() completed');
|
|
138
143
|
},
|
|
139
144
|
|
|
140
145
|
/**
|
package/content/loader.ts
CHANGED
|
@@ -4,15 +4,19 @@ import type { ContentItem, ContentSourceConfig } from './types';
|
|
|
4
4
|
import { compileMarkdown, vnodesToHtml } from './markdown';
|
|
5
5
|
|
|
6
6
|
export function loadContent(contentDir: string): ContentItem[] {
|
|
7
|
+
console.log(`[zenith:content:loadContent] Starting for: ${contentDir}`);
|
|
7
8
|
if (!fs.existsSync(contentDir)) {
|
|
8
9
|
console.warn(`Content directory ${contentDir} does not exist.`);
|
|
9
10
|
return [];
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
const items: ContentItem[] = [];
|
|
14
|
+
console.log('[zenith:content:loadContent] Getting all files...');
|
|
13
15
|
const files = getAllFiles(contentDir);
|
|
16
|
+
console.log(`[zenith:content:loadContent] Found ${files.length} files`);
|
|
14
17
|
|
|
15
18
|
for (const filePath of files) {
|
|
19
|
+
console.log(`[zenith:content:loadContent] Processing file: ${path.basename(filePath)}`);
|
|
16
20
|
const ext = path.extname(filePath).toLowerCase();
|
|
17
21
|
const relativePath = path.relative(contentDir, filePath);
|
|
18
22
|
const collection = relativePath.split(path.sep)[0];
|
|
@@ -40,10 +44,14 @@ export function loadContent(contentDir: string): ContentItem[] {
|
|
|
40
44
|
console.error(`Error parsing JSON file ${filePath}:`, e);
|
|
41
45
|
}
|
|
42
46
|
} else if (ext === '.md' || ext === '.mdx') {
|
|
47
|
+
console.log(`[zenith:content:loadContent] Parsing markdown: ${path.basename(filePath)}`);
|
|
43
48
|
const { metadata, content: markdownBody } = parseMarkdown(rawContent);
|
|
49
|
+
console.log(`[zenith:content:loadContent] Compiling markdown...`);
|
|
44
50
|
// Compile markdown to VNodes then to HTML for rendering
|
|
45
51
|
const vnodes = compileMarkdown(markdownBody);
|
|
52
|
+
console.log(`[zenith:content:loadContent] Converting vnodes to HTML...`);
|
|
46
53
|
const compiledContent = vnodesToHtml(vnodes);
|
|
54
|
+
console.log(`[zenith:content:loadContent] Done with: ${path.basename(filePath)}`);
|
|
47
55
|
items.push({
|
|
48
56
|
id,
|
|
49
57
|
slug,
|
|
@@ -53,7 +61,7 @@ export function loadContent(contentDir: string): ContentItem[] {
|
|
|
53
61
|
});
|
|
54
62
|
}
|
|
55
63
|
}
|
|
56
|
-
|
|
64
|
+
console.log(`[zenith:content:loadContent] Finished, returning ${items.length} items`);
|
|
57
65
|
return items;
|
|
58
66
|
}
|
|
59
67
|
|
|
@@ -65,27 +73,34 @@ export function loadFromSources(
|
|
|
65
73
|
sources: Record<string, ContentSourceConfig>,
|
|
66
74
|
projectRoot: string
|
|
67
75
|
): Record<string, ContentItem[]> {
|
|
76
|
+
console.log('[zenith:content:loader] loadFromSources called, sources:', Object.keys(sources));
|
|
68
77
|
const collections: Record<string, ContentItem[]> = {};
|
|
69
78
|
|
|
70
79
|
for (const [collectionName, config] of Object.entries(sources)) {
|
|
80
|
+
console.log(`[zenith:content:loader] Processing collection: ${collectionName}`);
|
|
71
81
|
const rootPath = path.resolve(projectRoot, config.root);
|
|
82
|
+
console.log(`[zenith:content:loader] Root path: ${rootPath}`);
|
|
72
83
|
|
|
73
84
|
if (!fs.existsSync(rootPath)) {
|
|
74
85
|
console.warn(`[zenith:content] Source root "${rootPath}" does not exist for collection "${collectionName}"`);
|
|
75
86
|
continue;
|
|
76
87
|
}
|
|
88
|
+
console.log('[zenith:content:loader] Root path exists, scanning folders...');
|
|
77
89
|
|
|
78
90
|
// Get folders to scan
|
|
79
91
|
let foldersToScan: string[] = [];
|
|
80
92
|
|
|
81
93
|
if (config.include && config.include.length > 0) {
|
|
82
94
|
// Explicit include list
|
|
95
|
+
console.log('[zenith:content:loader] Using explicit include list:', config.include);
|
|
83
96
|
foldersToScan = config.include;
|
|
84
97
|
} else {
|
|
85
98
|
// Scan all subdirectories if no include specified
|
|
86
99
|
try {
|
|
100
|
+
console.log('[zenith:content:loader] Reading subdirectories...');
|
|
87
101
|
foldersToScan = fs.readdirSync(rootPath)
|
|
88
102
|
.filter(f => fs.statSync(path.join(rootPath, f)).isDirectory());
|
|
103
|
+
console.log('[zenith:content:loader] Found subdirectories:', foldersToScan);
|
|
89
104
|
} catch {
|
|
90
105
|
// If root is itself the content folder
|
|
91
106
|
foldersToScan = ['.'];
|
|
@@ -95,10 +110,12 @@ export function loadFromSources(
|
|
|
95
110
|
// Apply excludes
|
|
96
111
|
const exclude = config.exclude || [];
|
|
97
112
|
foldersToScan = foldersToScan.filter(f => !exclude.includes(f));
|
|
113
|
+
console.log('[zenith:content:loader] Folders after exclude filter:', foldersToScan);
|
|
98
114
|
|
|
99
115
|
// Load content from each folder
|
|
100
116
|
const items: ContentItem[] = [];
|
|
101
117
|
for (const folder of foldersToScan) {
|
|
118
|
+
console.log(`[zenith:content:loader] Processing folder: ${folder}`);
|
|
102
119
|
const folderPath = folder === '.' ? rootPath : path.join(rootPath, folder);
|
|
103
120
|
|
|
104
121
|
if (!fs.existsSync(folderPath)) {
|
|
@@ -106,7 +123,9 @@ export function loadFromSources(
|
|
|
106
123
|
continue;
|
|
107
124
|
}
|
|
108
125
|
|
|
126
|
+
console.log(`[zenith:content:loader] Calling loadContent for: ${folderPath}`);
|
|
109
127
|
const folderItems = loadContent(folderPath);
|
|
128
|
+
console.log(`[zenith:content:loader] loadContent returned ${folderItems.length} items`);
|
|
110
129
|
|
|
111
130
|
// Override collection name to match the configured name
|
|
112
131
|
items.push(...folderItems.map(item => ({
|
package/content/markdown.ts
CHANGED
|
@@ -109,8 +109,16 @@ export function compileMarkdown(markdown: string): VNode[] {
|
|
|
109
109
|
const line = lines[i];
|
|
110
110
|
const trimmed = line.trim();
|
|
111
111
|
|
|
112
|
-
//
|
|
113
|
-
if (trimmed
|
|
112
|
+
// Raw HTML: if a line starts with < and ends with > (simplified detection)
|
|
113
|
+
if (trimmed.startsWith('<') && trimmed.endsWith('>')) {
|
|
114
|
+
// We'll treat this as a raw HTML block.
|
|
115
|
+
// VNode doesn't have a 'raw' type, so we'll use a special tag 'div'
|
|
116
|
+
// but we need a way to signal it shouldn't be escaped.
|
|
117
|
+
// Since our system uses innerHTML for the final injection,
|
|
118
|
+
// we can just put the raw HTML in the children for now?
|
|
119
|
+
// Wait, vnodesToHtml will escape children if they are strings.
|
|
120
|
+
// Let's use a special tag 'zen-raw-html' that vnodesToHtml handles.
|
|
121
|
+
nodes.push(h('zen-raw-html', { html: trimmed }, []));
|
|
114
122
|
i++;
|
|
115
123
|
continue;
|
|
116
124
|
}
|
|
@@ -185,6 +193,12 @@ export function compileMarkdown(markdown: string): VNode[] {
|
|
|
185
193
|
continue;
|
|
186
194
|
}
|
|
187
195
|
|
|
196
|
+
// Skip empty lines explicitly to prevent infinite loop
|
|
197
|
+
if (trimmed === '') {
|
|
198
|
+
i++;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
|
|
188
202
|
// Paragraph: collect consecutive non-empty lines
|
|
189
203
|
const paraLines: string[] = [];
|
|
190
204
|
while (i < lines.length && lines[i].trim() !== '' &&
|
|
@@ -219,6 +233,10 @@ export function vnodesToHtml(nodes: VNode[]): string {
|
|
|
219
233
|
|
|
220
234
|
const { tag, props, children } = node;
|
|
221
235
|
|
|
236
|
+
if (tag === 'zen-raw-html') {
|
|
237
|
+
return props.html || '';
|
|
238
|
+
}
|
|
239
|
+
|
|
222
240
|
// Self-closing tags
|
|
223
241
|
if (tag === 'hr' || tag === 'br') {
|
|
224
242
|
const attrs = Object.entries(props)
|
package/content/query.ts
CHANGED
|
@@ -97,4 +97,70 @@ export class ZenCollection {
|
|
|
97
97
|
const results = await this.get();
|
|
98
98
|
return results.length;
|
|
99
99
|
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Group items by folder (first segment of slug).
|
|
103
|
+
* Returns an object with a groupByFolder method that can be chained.
|
|
104
|
+
*/
|
|
105
|
+
groupByFolder(): { get: () => Array<{ id: string; title: string; items: ContentItem[] }> } {
|
|
106
|
+
const self = this;
|
|
107
|
+
return {
|
|
108
|
+
get(): Array<{ id: string; title: string; items: ContentItem[] }> {
|
|
109
|
+
let results = [...self.collectionItems];
|
|
110
|
+
|
|
111
|
+
// 1. Filter
|
|
112
|
+
for (const filter of self.filters) {
|
|
113
|
+
results = results.filter(filter);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 2. Sort
|
|
117
|
+
if (self.sortField) {
|
|
118
|
+
results.sort((a, b) => {
|
|
119
|
+
const valA = a[self.sortField!];
|
|
120
|
+
const valB = b[self.sortField!];
|
|
121
|
+
|
|
122
|
+
if (valA < valB) return self.sortOrder === 'asc' ? -1 : 1;
|
|
123
|
+
if (valA > valB) return self.sortOrder === 'asc' ? 1 : -1;
|
|
124
|
+
return 0;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// 3. Limit
|
|
129
|
+
if (self.limitCount !== null) {
|
|
130
|
+
results = results.slice(0, self.limitCount);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Group by folder (first segment of slug)
|
|
134
|
+
const groups = new Map<string, ContentItem[]>();
|
|
135
|
+
for (const item of results) {
|
|
136
|
+
const slug = String(item.slug || item.id || '');
|
|
137
|
+
const parts = slug.split('/');
|
|
138
|
+
const folder = parts.length > 1 ? parts[0] : 'default';
|
|
139
|
+
|
|
140
|
+
if (!groups.has(folder)) {
|
|
141
|
+
groups.set(folder, []);
|
|
142
|
+
}
|
|
143
|
+
groups.get(folder)!.push(item);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Convert to array of sections
|
|
147
|
+
const sections: Array<{ id: string; title: string; items: ContentItem[] }> = [];
|
|
148
|
+
for (const [folder, items] of groups) {
|
|
149
|
+
// Generate title from folder name
|
|
150
|
+
const title = folder
|
|
151
|
+
.split('-')
|
|
152
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
153
|
+
.join(' ');
|
|
154
|
+
|
|
155
|
+
sections.push({
|
|
156
|
+
id: folder,
|
|
157
|
+
title,
|
|
158
|
+
items
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return sections;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
100
166
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zenithbuild/plugins",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"description": "Plugin system for Zenith framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "content/index.ts",
|
|
@@ -33,4 +33,4 @@
|
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@types/node": "^25.0.6"
|
|
35
35
|
}
|
|
36
|
-
}
|
|
36
|
+
}
|