@autumnsgrove/groveengine 0.1.0 → 0.1.1
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/utils/markdown.d.ts +98 -76
- package/dist/utils/markdown.js +389 -579
- package/package.json +1 -1
package/dist/utils/markdown.d.ts
CHANGED
|
@@ -1,29 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Get the site configuration
|
|
3
|
-
* @returns {Object} Site configuration object
|
|
4
|
-
*/
|
|
5
|
-
export function getSiteConfig(): Object;
|
|
6
|
-
/**
|
|
7
|
-
* Get all markdown posts from the posts directory
|
|
8
|
-
* @returns {Array} Array of post objects with metadata and slug
|
|
9
|
-
*/
|
|
10
|
-
export function getAllPosts(): any[];
|
|
11
|
-
/**
|
|
12
|
-
* Get the latest (most recent) post with full content
|
|
13
|
-
* @returns {Object|null} The latest post object with content, or null if no posts exist
|
|
14
|
-
*/
|
|
15
|
-
export function getLatestPost(): Object | null;
|
|
16
|
-
/**
|
|
17
|
-
* Get all recipes from the recipes directory
|
|
18
|
-
* @returns {Array} Array of recipe objects with metadata and slug
|
|
19
|
-
*/
|
|
20
|
-
export function getAllRecipes(): any[];
|
|
21
|
-
/**
|
|
22
|
-
* Get a single post by slug
|
|
23
|
-
* @param {string} slug - The post slug
|
|
24
|
-
* @returns {Object|null} Post object with content and metadata
|
|
25
|
-
*/
|
|
26
|
-
export function getPostBySlug(slug: string): Object | null;
|
|
27
1
|
/**
|
|
28
2
|
* Extract headers from markdown content for table of contents
|
|
29
3
|
* @param {string} markdown - The raw markdown content
|
|
@@ -38,64 +12,112 @@ export function extractHeaders(markdown: string): any[];
|
|
|
38
12
|
*/
|
|
39
13
|
export function processAnchorTags(html: string): string;
|
|
40
14
|
/**
|
|
41
|
-
*
|
|
42
|
-
* @param {string}
|
|
43
|
-
* @returns {
|
|
44
|
-
*/
|
|
45
|
-
export function getRecipeGutterContent(slug: string): any[];
|
|
46
|
-
/**
|
|
47
|
-
* Get gutter content for a blog post by slug
|
|
48
|
-
* @param {string} slug - The post slug
|
|
49
|
-
* @returns {Array} Array of gutter items with content and position info
|
|
15
|
+
* Process Mermaid diagrams in markdown content
|
|
16
|
+
* @param {string} markdown - The markdown content
|
|
17
|
+
* @returns {string} Processed markdown with Mermaid diagrams
|
|
50
18
|
*/
|
|
51
|
-
export function
|
|
19
|
+
export function processMermaidDiagrams(markdown: string): string;
|
|
52
20
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* @returns {Array} Array of gutter items with content and position info
|
|
56
|
-
*/
|
|
57
|
-
export function getHomeGutterContent(slug: string): any[];
|
|
58
|
-
/**
|
|
59
|
-
* Get gutter content for the contact page
|
|
60
|
-
* @param {string} slug - The page slug (e.g., 'contact')
|
|
61
|
-
* @returns {Array} Array of gutter items with content and position info
|
|
62
|
-
*/
|
|
63
|
-
export function getContactGutterContent(slug: string): any[];
|
|
64
|
-
/**
|
|
65
|
-
* Get the home page content
|
|
66
|
-
* @returns {Object|null} Home page object with content, metadata, and galleries
|
|
21
|
+
* Render Mermaid diagrams in the DOM
|
|
22
|
+
* This should be called after the content is mounted
|
|
67
23
|
*/
|
|
68
|
-
export function
|
|
24
|
+
export function renderMermaidDiagrams(): Promise<void>;
|
|
69
25
|
/**
|
|
70
|
-
*
|
|
71
|
-
* @
|
|
26
|
+
* Parse markdown content and convert to HTML
|
|
27
|
+
* @param {string} markdownContent - The raw markdown content (may include frontmatter)
|
|
28
|
+
* @returns {Object} Object with data (frontmatter), content (HTML), headers, and raw markdown
|
|
72
29
|
*/
|
|
73
|
-
export function
|
|
30
|
+
export function parseMarkdownContent(markdownContent: string): Object;
|
|
74
31
|
/**
|
|
75
|
-
*
|
|
76
|
-
* @
|
|
32
|
+
* Parse markdown content with sanitization (for user-facing pages like home, about, contact)
|
|
33
|
+
* @param {string} markdownContent - The raw markdown content (may include frontmatter)
|
|
34
|
+
* @returns {Object} Object with data (frontmatter), content (sanitized HTML), headers
|
|
77
35
|
*/
|
|
78
|
-
export function
|
|
36
|
+
export function parseMarkdownContentSanitized(markdownContent: string): Object;
|
|
79
37
|
/**
|
|
80
|
-
* Get gutter content
|
|
81
|
-
*
|
|
38
|
+
* Get gutter content from provided modules
|
|
39
|
+
* This is a utility function that processes gutter manifests, markdown, and images
|
|
40
|
+
*
|
|
41
|
+
* @param {string} slug - The page/post slug
|
|
42
|
+
* @param {Object} manifestModules - The manifest modules (from import.meta.glob)
|
|
43
|
+
* @param {Object} markdownModules - The markdown modules (from import.meta.glob)
|
|
44
|
+
* @param {Object} imageModules - The image modules (from import.meta.glob)
|
|
82
45
|
* @returns {Array} Array of gutter items with content and position info
|
|
83
46
|
*/
|
|
84
|
-
export function
|
|
85
|
-
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* @
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
*
|
|
99
|
-
*
|
|
47
|
+
export function processGutterContent(slug: string, manifestModules: Object, markdownModules: Object, imageModules: Object): any[];
|
|
48
|
+
/**
|
|
49
|
+
* Process a list of markdown files into post/recipe objects
|
|
50
|
+
*
|
|
51
|
+
* @param {Object} modules - The modules from import.meta.glob (filepath -> content)
|
|
52
|
+
* @returns {Array} Array of post/content objects with metadata and slug
|
|
53
|
+
*/
|
|
54
|
+
export function processMarkdownModules(modules: Object): any[];
|
|
55
|
+
/**
|
|
56
|
+
* Get a single item by slug from modules
|
|
57
|
+
*
|
|
58
|
+
* @param {string} slug - The item slug
|
|
59
|
+
* @param {Object} modules - The modules from import.meta.glob (filepath -> content)
|
|
60
|
+
* @param {Object} options - Optional configuration
|
|
61
|
+
* @param {Object} options.gutterModules - Gutter modules { manifest, markdown, images }
|
|
62
|
+
* @param {Object} options.sidecarModules - Sidecar/metadata modules (for recipes)
|
|
63
|
+
* @returns {Object|null} Item object with content and metadata
|
|
64
|
+
*/
|
|
65
|
+
export function getItemBySlug(slug: string, modules: Object, options?: {
|
|
66
|
+
gutterModules: Object;
|
|
67
|
+
sidecarModules: Object;
|
|
68
|
+
}): Object | null;
|
|
69
|
+
/**
|
|
70
|
+
* Get a page (home, about, contact) by filename from modules
|
|
71
|
+
* Uses sanitization for security
|
|
72
|
+
*
|
|
73
|
+
* @param {string} filename - The filename to look for (e.g., "home.md", "about.md")
|
|
74
|
+
* @param {Object} modules - The modules from import.meta.glob (filepath -> content)
|
|
75
|
+
* @param {Object} options - Optional configuration
|
|
76
|
+
* @param {Object} options.gutterModules - Gutter modules { manifest, markdown, images }
|
|
77
|
+
* @param {string} options.slug - Override slug (defaults to filename without .md)
|
|
78
|
+
* @returns {Object|null} Page object with content and metadata
|
|
79
|
+
*/
|
|
80
|
+
export function getPageByFilename(filename: string, modules: Object, options?: {
|
|
81
|
+
gutterModules: Object;
|
|
82
|
+
slug: string;
|
|
83
|
+
}): Object | null;
|
|
84
|
+
/**
|
|
85
|
+
* Get site configuration from a config module
|
|
86
|
+
*
|
|
87
|
+
* @param {Object} configModule - The config module from import.meta.glob
|
|
88
|
+
* @returns {Object} Site configuration object
|
|
100
89
|
*/
|
|
101
|
-
export function
|
|
90
|
+
export function getSiteConfigFromModule(configModule: Object): Object;
|
|
91
|
+
/**
|
|
92
|
+
* Create a configured content loader with all functions bound to the provided modules
|
|
93
|
+
* This is the main factory function for creating a content loader in the consuming app
|
|
94
|
+
*
|
|
95
|
+
* @param {Object} config - Configuration object with all required modules
|
|
96
|
+
* @param {Object} config.posts - Post modules from import.meta.glob
|
|
97
|
+
* @param {Object} config.recipes - Recipe modules from import.meta.glob
|
|
98
|
+
* @param {Object} config.about - About page modules from import.meta.glob
|
|
99
|
+
* @param {Object} config.home - Home page modules from import.meta.glob
|
|
100
|
+
* @param {Object} config.contact - Contact page modules from import.meta.glob
|
|
101
|
+
* @param {Object} config.siteConfig - Site config module from import.meta.glob
|
|
102
|
+
* @param {Object} config.postGutter - Post gutter modules { manifest, markdown, images }
|
|
103
|
+
* @param {Object} config.recipeGutter - Recipe gutter modules { manifest, markdown, images }
|
|
104
|
+
* @param {Object} config.recipeMetadata - Recipe metadata modules from import.meta.glob
|
|
105
|
+
* @param {Object} config.aboutGutter - About gutter modules { manifest, markdown, images }
|
|
106
|
+
* @param {Object} config.homeGutter - Home gutter modules { manifest, markdown, images }
|
|
107
|
+
* @param {Object} config.contactGutter - Contact gutter modules { manifest, markdown, images }
|
|
108
|
+
* @returns {Object} Object with all content loader functions
|
|
109
|
+
*/
|
|
110
|
+
export function createContentLoader(config: {
|
|
111
|
+
posts: Object;
|
|
112
|
+
recipes: Object;
|
|
113
|
+
about: Object;
|
|
114
|
+
home: Object;
|
|
115
|
+
contact: Object;
|
|
116
|
+
siteConfig: Object;
|
|
117
|
+
postGutter: Object;
|
|
118
|
+
recipeGutter: Object;
|
|
119
|
+
recipeMetadata: Object;
|
|
120
|
+
aboutGutter: Object;
|
|
121
|
+
homeGutter: Object;
|
|
122
|
+
contactGutter: Object;
|
|
123
|
+
}): Object;
|
package/dist/utils/markdown.js
CHANGED
|
@@ -76,217 +76,6 @@ marked.setOptions({
|
|
|
76
76
|
breaks: false,
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
-
// Use Vite's import.meta.glob to load markdown files at build time
|
|
80
|
-
// This works in both dev and production (including Cloudflare Workers)
|
|
81
|
-
// Path is relative to project root - now using UserContent directory
|
|
82
|
-
|
|
83
|
-
// Posts - Using absolute path from project root for Cloudflare Pages compatibility
|
|
84
|
-
const modules = import.meta.glob("/UserContent/Posts/*.md", {
|
|
85
|
-
eager: true,
|
|
86
|
-
query: "?raw",
|
|
87
|
-
import: "default",
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// Recipes
|
|
91
|
-
const recipeModules = import.meta.glob("../../../UserContent/Recipes/*.md", {
|
|
92
|
-
eager: true,
|
|
93
|
-
query: "?raw",
|
|
94
|
-
import: "default",
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
// About
|
|
98
|
-
const aboutModules = import.meta.glob("../../../UserContent/About/*.md", {
|
|
99
|
-
eager: true,
|
|
100
|
-
query: "?raw",
|
|
101
|
-
import: "default",
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
// Home
|
|
105
|
-
const homeModules = import.meta.glob("../../../UserContent/Home/*.md", {
|
|
106
|
-
eager: true,
|
|
107
|
-
query: "?raw",
|
|
108
|
-
import: "default",
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
// Contact
|
|
112
|
-
const contactModules = import.meta.glob("../../../UserContent/Contact/*.md", {
|
|
113
|
-
eager: true,
|
|
114
|
-
query: "?raw",
|
|
115
|
-
import: "default",
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
// Site config
|
|
119
|
-
const siteConfigModule = import.meta.glob(
|
|
120
|
-
"../../../UserContent/site-config.json",
|
|
121
|
-
{
|
|
122
|
-
eager: true,
|
|
123
|
-
},
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Get the site configuration
|
|
128
|
-
* @returns {Object} Site configuration object
|
|
129
|
-
*/
|
|
130
|
-
export function getSiteConfig() {
|
|
131
|
-
const entry = Object.entries(siteConfigModule)[0];
|
|
132
|
-
if (entry) {
|
|
133
|
-
return entry[1].default || entry[1];
|
|
134
|
-
}
|
|
135
|
-
return {
|
|
136
|
-
owner: { name: "Admin", email: "" },
|
|
137
|
-
site: { title: "The Grove", description: "", copyright: "AutumnsGrove" },
|
|
138
|
-
social: {},
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Load recipe metadata JSON files (step icons, etc.)
|
|
143
|
-
const recipeMetadataModules = import.meta.glob(
|
|
144
|
-
"../../../UserContent/Recipes/*/gutter/recipe.json",
|
|
145
|
-
{
|
|
146
|
-
eager: true,
|
|
147
|
-
},
|
|
148
|
-
);
|
|
149
|
-
|
|
150
|
-
// Load gutter manifest files for blog posts
|
|
151
|
-
const gutterManifestModules = import.meta.glob(
|
|
152
|
-
"../../../UserContent/Posts/*/gutter/manifest.json",
|
|
153
|
-
{
|
|
154
|
-
eager: true,
|
|
155
|
-
},
|
|
156
|
-
);
|
|
157
|
-
|
|
158
|
-
// Load gutter markdown content files
|
|
159
|
-
const gutterMarkdownModules = import.meta.glob(
|
|
160
|
-
"../../../UserContent/Posts/*/gutter/*.md",
|
|
161
|
-
{
|
|
162
|
-
eager: true,
|
|
163
|
-
query: "?raw",
|
|
164
|
-
import: "default",
|
|
165
|
-
},
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
// Load gutter image files
|
|
169
|
-
const gutterImageModules = import.meta.glob(
|
|
170
|
-
"../../../UserContent/Posts/*/gutter/*.{jpg,jpeg,png,gif,webp}",
|
|
171
|
-
{
|
|
172
|
-
eager: true,
|
|
173
|
-
query: "?url",
|
|
174
|
-
import: "default",
|
|
175
|
-
},
|
|
176
|
-
);
|
|
177
|
-
|
|
178
|
-
// Load about page gutter manifest files
|
|
179
|
-
const aboutGutterManifestModules = import.meta.glob(
|
|
180
|
-
"../../../UserContent/About/*/gutter/manifest.json",
|
|
181
|
-
{
|
|
182
|
-
eager: true,
|
|
183
|
-
},
|
|
184
|
-
);
|
|
185
|
-
|
|
186
|
-
// Load about page gutter markdown content files
|
|
187
|
-
const aboutGutterMarkdownModules = import.meta.glob(
|
|
188
|
-
"../../../UserContent/About/*/gutter/*.md",
|
|
189
|
-
{
|
|
190
|
-
eager: true,
|
|
191
|
-
query: "?raw",
|
|
192
|
-
import: "default",
|
|
193
|
-
},
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
// Load about page gutter image files
|
|
197
|
-
const aboutGutterImageModules = import.meta.glob(
|
|
198
|
-
"../../../UserContent/About/*/gutter/*.{jpg,jpeg,png,gif,webp}",
|
|
199
|
-
{
|
|
200
|
-
eager: true,
|
|
201
|
-
query: "?url",
|
|
202
|
-
import: "default",
|
|
203
|
-
},
|
|
204
|
-
);
|
|
205
|
-
|
|
206
|
-
// Load recipe gutter manifest files
|
|
207
|
-
const recipeGutterManifestModules = import.meta.glob(
|
|
208
|
-
"../../../UserContent/Recipes/*/gutter/manifest.json",
|
|
209
|
-
{
|
|
210
|
-
eager: true,
|
|
211
|
-
},
|
|
212
|
-
);
|
|
213
|
-
|
|
214
|
-
// Load recipe gutter markdown content files
|
|
215
|
-
const recipeGutterMarkdownModules = import.meta.glob(
|
|
216
|
-
"../../../UserContent/Recipes/*/gutter/*.md",
|
|
217
|
-
{
|
|
218
|
-
eager: true,
|
|
219
|
-
query: "?raw",
|
|
220
|
-
import: "default",
|
|
221
|
-
},
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
// Load recipe gutter image files
|
|
225
|
-
const recipeGutterImageModules = import.meta.glob(
|
|
226
|
-
"../../../UserContent/Recipes/*/gutter/*.{jpg,jpeg,png,gif,webp}",
|
|
227
|
-
{
|
|
228
|
-
eager: true,
|
|
229
|
-
query: "?url",
|
|
230
|
-
import: "default",
|
|
231
|
-
},
|
|
232
|
-
);
|
|
233
|
-
|
|
234
|
-
// Load home page gutter manifest files
|
|
235
|
-
const homeGutterManifestModules = import.meta.glob(
|
|
236
|
-
"../../../UserContent/Home/*/gutter/manifest.json",
|
|
237
|
-
{
|
|
238
|
-
eager: true,
|
|
239
|
-
},
|
|
240
|
-
);
|
|
241
|
-
|
|
242
|
-
// Load home page gutter markdown content files
|
|
243
|
-
const homeGutterMarkdownModules = import.meta.glob(
|
|
244
|
-
"../../../UserContent/Home/*/gutter/*.md",
|
|
245
|
-
{
|
|
246
|
-
eager: true,
|
|
247
|
-
query: "?raw",
|
|
248
|
-
import: "default",
|
|
249
|
-
},
|
|
250
|
-
);
|
|
251
|
-
|
|
252
|
-
// Load home page gutter image files
|
|
253
|
-
const homeGutterImageModules = import.meta.glob(
|
|
254
|
-
"../../../UserContent/Home/*/gutter/*.{jpg,jpeg,png,gif,webp}",
|
|
255
|
-
{
|
|
256
|
-
eager: true,
|
|
257
|
-
query: "?url",
|
|
258
|
-
import: "default",
|
|
259
|
-
},
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
// Load contact page gutter manifest files
|
|
263
|
-
const contactGutterManifestModules = import.meta.glob(
|
|
264
|
-
"../../../UserContent/Contact/*/gutter/manifest.json",
|
|
265
|
-
{
|
|
266
|
-
eager: true,
|
|
267
|
-
},
|
|
268
|
-
);
|
|
269
|
-
|
|
270
|
-
// Load contact page gutter markdown content files
|
|
271
|
-
const contactGutterMarkdownModules = import.meta.glob(
|
|
272
|
-
"../../../UserContent/Contact/*/gutter/*.md",
|
|
273
|
-
{
|
|
274
|
-
eager: true,
|
|
275
|
-
query: "?raw",
|
|
276
|
-
import: "default",
|
|
277
|
-
},
|
|
278
|
-
);
|
|
279
|
-
|
|
280
|
-
// Load contact page gutter image files
|
|
281
|
-
const contactGutterImageModules = import.meta.glob(
|
|
282
|
-
"../../../UserContent/Contact/*/gutter/*.{jpg,jpeg,png,gif,webp}",
|
|
283
|
-
{
|
|
284
|
-
eager: true,
|
|
285
|
-
query: "?url",
|
|
286
|
-
import: "default",
|
|
287
|
-
},
|
|
288
|
-
);
|
|
289
|
-
|
|
290
79
|
/**
|
|
291
80
|
* Validates if a string is a valid URL
|
|
292
81
|
* @param {string} urlString - The string to validate as a URL
|
|
@@ -301,131 +90,6 @@ function isValidUrl(urlString) {
|
|
|
301
90
|
}
|
|
302
91
|
}
|
|
303
92
|
|
|
304
|
-
/**
|
|
305
|
-
* Get all markdown posts from the posts directory
|
|
306
|
-
* @returns {Array} Array of post objects with metadata and slug
|
|
307
|
-
*/
|
|
308
|
-
export function getAllPosts() {
|
|
309
|
-
try {
|
|
310
|
-
const posts = Object.entries(modules)
|
|
311
|
-
.map(([filepath, content]) => {
|
|
312
|
-
try {
|
|
313
|
-
// Extract slug from filepath: ../../../UserContent/Posts/example.md -> example
|
|
314
|
-
const slug = filepath.split("/").pop().replace(".md", "");
|
|
315
|
-
const { data } = matter(content);
|
|
316
|
-
|
|
317
|
-
return {
|
|
318
|
-
slug,
|
|
319
|
-
title: data.title || "Untitled",
|
|
320
|
-
date: data.date || new Date().toISOString(),
|
|
321
|
-
tags: data.tags || [],
|
|
322
|
-
description: data.description || "",
|
|
323
|
-
};
|
|
324
|
-
} catch (err) {
|
|
325
|
-
console.error(`Error processing post ${filepath}:`, err);
|
|
326
|
-
return null;
|
|
327
|
-
}
|
|
328
|
-
})
|
|
329
|
-
.filter(Boolean)
|
|
330
|
-
.sort((a, b) => new Date(b.date) - new Date(a.date));
|
|
331
|
-
|
|
332
|
-
return posts;
|
|
333
|
-
} catch (err) {
|
|
334
|
-
console.error("Error in getAllPosts:", err);
|
|
335
|
-
return [];
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
/**
|
|
340
|
-
* Get the latest (most recent) post with full content
|
|
341
|
-
* @returns {Object|null} The latest post object with content, or null if no posts exist
|
|
342
|
-
*/
|
|
343
|
-
export function getLatestPost() {
|
|
344
|
-
const posts = getAllPosts();
|
|
345
|
-
if (posts.length === 0) {
|
|
346
|
-
return null;
|
|
347
|
-
}
|
|
348
|
-
// Get the full post content for the most recent post
|
|
349
|
-
return getPostBySlug(posts[0].slug);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* Get all recipes from the recipes directory
|
|
354
|
-
* @returns {Array} Array of recipe objects with metadata and slug
|
|
355
|
-
*/
|
|
356
|
-
export function getAllRecipes() {
|
|
357
|
-
try {
|
|
358
|
-
const recipes = Object.entries(recipeModules)
|
|
359
|
-
.map(([filepath, content]) => {
|
|
360
|
-
try {
|
|
361
|
-
// Extract slug from filepath: ../../../UserContent/Recipes/example.md -> example
|
|
362
|
-
const slug = filepath.split("/").pop().replace(".md", "");
|
|
363
|
-
const { data } = matter(content);
|
|
364
|
-
|
|
365
|
-
return {
|
|
366
|
-
slug,
|
|
367
|
-
title: data.title || "Untitled Recipe",
|
|
368
|
-
date: data.date || new Date().toISOString(),
|
|
369
|
-
tags: data.tags || [],
|
|
370
|
-
description: data.description || "",
|
|
371
|
-
};
|
|
372
|
-
} catch (err) {
|
|
373
|
-
console.error(`Error processing recipe ${filepath}:`, err);
|
|
374
|
-
return null;
|
|
375
|
-
}
|
|
376
|
-
})
|
|
377
|
-
.filter(Boolean)
|
|
378
|
-
.sort((a, b) => new Date(b.date) - new Date(a.date));
|
|
379
|
-
|
|
380
|
-
return recipes;
|
|
381
|
-
} catch (err) {
|
|
382
|
-
console.error("Error in getAllRecipes:", err);
|
|
383
|
-
return [];
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* Get a single post by slug
|
|
389
|
-
* @param {string} slug - The post slug
|
|
390
|
-
* @returns {Object|null} Post object with content and metadata
|
|
391
|
-
*/
|
|
392
|
-
export function getPostBySlug(slug) {
|
|
393
|
-
// Find the matching module by slug
|
|
394
|
-
const entry = Object.entries(modules).find(([filepath]) => {
|
|
395
|
-
const fileSlug = filepath.split("/").pop().replace(".md", "");
|
|
396
|
-
return fileSlug === slug;
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
if (!entry) {
|
|
400
|
-
return null;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
const content = entry[1];
|
|
404
|
-
|
|
405
|
-
const { data, content: markdown } = matter(content);
|
|
406
|
-
let htmlContent = marked.parse(markdown);
|
|
407
|
-
|
|
408
|
-
// Process anchor tags in the HTML content
|
|
409
|
-
htmlContent = processAnchorTags(htmlContent);
|
|
410
|
-
|
|
411
|
-
// Extract headers for table of contents
|
|
412
|
-
const headers = extractHeaders(markdown);
|
|
413
|
-
|
|
414
|
-
// Get gutter content for this post
|
|
415
|
-
const gutterContent = getGutterContent(slug);
|
|
416
|
-
|
|
417
|
-
return {
|
|
418
|
-
slug,
|
|
419
|
-
title: data.title || "Untitled",
|
|
420
|
-
date: data.date || new Date().toISOString(),
|
|
421
|
-
tags: data.tags || [],
|
|
422
|
-
description: data.description || "",
|
|
423
|
-
content: htmlContent,
|
|
424
|
-
headers,
|
|
425
|
-
gutterContent,
|
|
426
|
-
};
|
|
427
|
-
}
|
|
428
|
-
|
|
429
93
|
/**
|
|
430
94
|
* Extract headers from markdown content for table of contents
|
|
431
95
|
* @param {string} markdown - The raw markdown content
|
|
@@ -479,14 +143,95 @@ export function processAnchorTags(html) {
|
|
|
479
143
|
}
|
|
480
144
|
|
|
481
145
|
/**
|
|
482
|
-
*
|
|
146
|
+
* Process Mermaid diagrams in markdown content
|
|
147
|
+
* @param {string} markdown - The markdown content
|
|
148
|
+
* @returns {string} Processed markdown with Mermaid diagrams
|
|
149
|
+
*/
|
|
150
|
+
export function processMermaidDiagrams(markdown) {
|
|
151
|
+
// Replace Mermaid code blocks with special divs that will be processed later
|
|
152
|
+
return markdown.replace(
|
|
153
|
+
/```mermaid\n([\s\S]*?)```/g,
|
|
154
|
+
(match, diagramCode) => {
|
|
155
|
+
const diagramId = "mermaid-" + Math.random().toString(36).substr(2, 9);
|
|
156
|
+
return `<div class="mermaid-container" id="${diagramId}" data-diagram="${encodeURIComponent(diagramCode.trim())}"></div>`;
|
|
157
|
+
},
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Render Mermaid diagrams in the DOM
|
|
163
|
+
* This should be called after the content is mounted
|
|
164
|
+
*/
|
|
165
|
+
export async function renderMermaidDiagrams() {
|
|
166
|
+
const containers = document.querySelectorAll(".mermaid-container");
|
|
167
|
+
|
|
168
|
+
for (const container of containers) {
|
|
169
|
+
try {
|
|
170
|
+
const diagramCode = decodeURIComponent(container.dataset.diagram);
|
|
171
|
+
const { svg } = await mermaid.render(container.id, diagramCode);
|
|
172
|
+
// Sanitize SVG output before injecting into DOM to prevent XSS
|
|
173
|
+
container.innerHTML = sanitizeSVG(svg);
|
|
174
|
+
} catch (error) {
|
|
175
|
+
console.error("Error rendering Mermaid diagram:", error);
|
|
176
|
+
container.innerHTML = '<p class="error">Error rendering diagram</p>';
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Parse markdown content and convert to HTML
|
|
183
|
+
* @param {string} markdownContent - The raw markdown content (may include frontmatter)
|
|
184
|
+
* @returns {Object} Object with data (frontmatter), content (HTML), headers, and raw markdown
|
|
185
|
+
*/
|
|
186
|
+
export function parseMarkdownContent(markdownContent) {
|
|
187
|
+
const { data, content: markdown } = matter(markdownContent);
|
|
188
|
+
|
|
189
|
+
// Process Mermaid diagrams in the content
|
|
190
|
+
const processedContent = processMermaidDiagrams(markdown);
|
|
191
|
+
let htmlContent = marked.parse(processedContent);
|
|
192
|
+
|
|
193
|
+
// Process anchor tags in the HTML content
|
|
194
|
+
htmlContent = processAnchorTags(htmlContent);
|
|
195
|
+
|
|
196
|
+
// Extract headers for table of contents
|
|
197
|
+
const headers = extractHeaders(markdown);
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
data,
|
|
201
|
+
content: htmlContent,
|
|
202
|
+
headers,
|
|
203
|
+
rawMarkdown: markdown,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Parse markdown content with sanitization (for user-facing pages like home, about, contact)
|
|
209
|
+
* @param {string} markdownContent - The raw markdown content (may include frontmatter)
|
|
210
|
+
* @returns {Object} Object with data (frontmatter), content (sanitized HTML), headers
|
|
211
|
+
*/
|
|
212
|
+
export function parseMarkdownContentSanitized(markdownContent) {
|
|
213
|
+
const { data, content: markdown } = matter(markdownContent);
|
|
214
|
+
const htmlContent = sanitizeMarkdown(marked.parse(markdown));
|
|
215
|
+
const headers = extractHeaders(markdown);
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
data,
|
|
219
|
+
content: htmlContent,
|
|
220
|
+
headers,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Get gutter content from provided modules
|
|
226
|
+
* This is a utility function that processes gutter manifests, markdown, and images
|
|
227
|
+
*
|
|
483
228
|
* @param {string} slug - The page/post slug
|
|
484
|
-
* @param {Object} manifestModules - The manifest modules
|
|
485
|
-
* @param {Object} markdownModules - The markdown modules
|
|
486
|
-
* @param {Object} imageModules - The image modules
|
|
229
|
+
* @param {Object} manifestModules - The manifest modules (from import.meta.glob)
|
|
230
|
+
* @param {Object} markdownModules - The markdown modules (from import.meta.glob)
|
|
231
|
+
* @param {Object} imageModules - The image modules (from import.meta.glob)
|
|
487
232
|
* @returns {Array} Array of gutter items with content and position info
|
|
488
233
|
*/
|
|
489
|
-
function
|
|
234
|
+
export function processGutterContent(
|
|
490
235
|
slug,
|
|
491
236
|
manifestModules,
|
|
492
237
|
markdownModules,
|
|
@@ -646,302 +391,367 @@ function getGutterContentFromModules(
|
|
|
646
391
|
}
|
|
647
392
|
|
|
648
393
|
/**
|
|
649
|
-
*
|
|
650
|
-
*
|
|
651
|
-
* @
|
|
394
|
+
* Process a list of markdown files into post/recipe objects
|
|
395
|
+
*
|
|
396
|
+
* @param {Object} modules - The modules from import.meta.glob (filepath -> content)
|
|
397
|
+
* @returns {Array} Array of post/content objects with metadata and slug
|
|
652
398
|
*/
|
|
653
|
-
export function
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
/**
|
|
663
|
-
* Get gutter content for a blog post by slug
|
|
664
|
-
* @param {string} slug - The post slug
|
|
665
|
-
* @returns {Array} Array of gutter items with content and position info
|
|
666
|
-
*/
|
|
667
|
-
export function getGutterContent(slug) {
|
|
668
|
-
return getGutterContentFromModules(
|
|
669
|
-
slug,
|
|
670
|
-
gutterManifestModules,
|
|
671
|
-
gutterMarkdownModules,
|
|
672
|
-
gutterImageModules,
|
|
673
|
-
);
|
|
674
|
-
}
|
|
399
|
+
export function processMarkdownModules(modules) {
|
|
400
|
+
try {
|
|
401
|
+
const items = Object.entries(modules)
|
|
402
|
+
.map(([filepath, content]) => {
|
|
403
|
+
try {
|
|
404
|
+
// Extract slug from filepath: /path/to/Posts/example.md -> example
|
|
405
|
+
const slug = filepath.split("/").pop().replace(".md", "");
|
|
406
|
+
const { data } = matter(content);
|
|
675
407
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
408
|
+
return {
|
|
409
|
+
slug,
|
|
410
|
+
title: data.title || "Untitled",
|
|
411
|
+
date: data.date || new Date().toISOString(),
|
|
412
|
+
tags: data.tags || [],
|
|
413
|
+
description: data.description || "",
|
|
414
|
+
};
|
|
415
|
+
} catch (err) {
|
|
416
|
+
console.error(`Error processing file ${filepath}:`, err);
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
})
|
|
420
|
+
.filter(Boolean)
|
|
421
|
+
.sort((a, b) => new Date(b.date) - new Date(a.date));
|
|
689
422
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
export function getContactGutterContent(slug) {
|
|
696
|
-
return getGutterContentFromModules(
|
|
697
|
-
slug,
|
|
698
|
-
contactGutterManifestModules,
|
|
699
|
-
contactGutterMarkdownModules,
|
|
700
|
-
contactGutterImageModules,
|
|
701
|
-
);
|
|
423
|
+
return items;
|
|
424
|
+
} catch (err) {
|
|
425
|
+
console.error("Error in processMarkdownModules:", err);
|
|
426
|
+
return [];
|
|
427
|
+
}
|
|
702
428
|
}
|
|
703
429
|
|
|
704
430
|
/**
|
|
705
|
-
* Get
|
|
706
|
-
*
|
|
431
|
+
* Get a single item by slug from modules
|
|
432
|
+
*
|
|
433
|
+
* @param {string} slug - The item slug
|
|
434
|
+
* @param {Object} modules - The modules from import.meta.glob (filepath -> content)
|
|
435
|
+
* @param {Object} options - Optional configuration
|
|
436
|
+
* @param {Object} options.gutterModules - Gutter modules { manifest, markdown, images }
|
|
437
|
+
* @param {Object} options.sidecarModules - Sidecar/metadata modules (for recipes)
|
|
438
|
+
* @returns {Object|null} Item object with content and metadata
|
|
707
439
|
*/
|
|
708
|
-
export function
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
const
|
|
712
|
-
|
|
713
|
-
|
|
440
|
+
export function getItemBySlug(slug, modules, options = {}) {
|
|
441
|
+
// Find the matching module by slug
|
|
442
|
+
const entry = Object.entries(modules).find(([filepath]) => {
|
|
443
|
+
const fileSlug = filepath.split("/").pop().replace(".md", "");
|
|
444
|
+
return fileSlug === slug;
|
|
445
|
+
});
|
|
714
446
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
447
|
+
if (!entry) {
|
|
448
|
+
return null;
|
|
449
|
+
}
|
|
718
450
|
|
|
719
|
-
|
|
451
|
+
const rawContent = entry[1];
|
|
452
|
+
const { data, content, headers } = parseMarkdownContent(rawContent);
|
|
720
453
|
|
|
721
|
-
|
|
722
|
-
|
|
454
|
+
// Build the result object
|
|
455
|
+
const result = {
|
|
456
|
+
slug,
|
|
457
|
+
title: data.title || "Untitled",
|
|
458
|
+
date: data.date || new Date().toISOString(),
|
|
459
|
+
tags: data.tags || [],
|
|
460
|
+
description: data.description || "",
|
|
461
|
+
content,
|
|
462
|
+
headers,
|
|
463
|
+
};
|
|
723
464
|
|
|
724
|
-
|
|
725
|
-
|
|
465
|
+
// Process gutter content if provided
|
|
466
|
+
if (options.gutterModules) {
|
|
467
|
+
const { manifest, markdown, images } = options.gutterModules;
|
|
468
|
+
result.gutterContent = processGutterContent(slug, manifest, markdown, images);
|
|
469
|
+
}
|
|
726
470
|
|
|
727
|
-
|
|
728
|
-
|
|
471
|
+
// Process sidecar/metadata if provided (for recipes)
|
|
472
|
+
if (options.sidecarModules) {
|
|
473
|
+
const sidecarEntry = Object.entries(options.sidecarModules).find(([filepath]) => {
|
|
474
|
+
const parts = filepath.split("/");
|
|
475
|
+
const folder = parts[parts.length - 3]; // Get the folder name
|
|
476
|
+
return folder === slug;
|
|
477
|
+
});
|
|
729
478
|
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
description: data.description || "",
|
|
734
|
-
hero: data.hero || null,
|
|
735
|
-
galleries: data.galleries || [],
|
|
736
|
-
content: htmlContent,
|
|
737
|
-
headers,
|
|
738
|
-
gutterContent,
|
|
739
|
-
};
|
|
740
|
-
} catch (err) {
|
|
741
|
-
console.error("Error in getHomePage:", err);
|
|
742
|
-
return null;
|
|
479
|
+
if (sidecarEntry) {
|
|
480
|
+
result.sidecar = sidecarEntry[1].default || sidecarEntry[1];
|
|
481
|
+
}
|
|
743
482
|
}
|
|
483
|
+
|
|
484
|
+
return result;
|
|
744
485
|
}
|
|
745
486
|
|
|
746
487
|
/**
|
|
747
|
-
* Get
|
|
748
|
-
*
|
|
488
|
+
* Get a page (home, about, contact) by filename from modules
|
|
489
|
+
* Uses sanitization for security
|
|
490
|
+
*
|
|
491
|
+
* @param {string} filename - The filename to look for (e.g., "home.md", "about.md")
|
|
492
|
+
* @param {Object} modules - The modules from import.meta.glob (filepath -> content)
|
|
493
|
+
* @param {Object} options - Optional configuration
|
|
494
|
+
* @param {Object} options.gutterModules - Gutter modules { manifest, markdown, images }
|
|
495
|
+
* @param {string} options.slug - Override slug (defaults to filename without .md)
|
|
496
|
+
* @returns {Object|null} Page object with content and metadata
|
|
749
497
|
*/
|
|
750
|
-
export function
|
|
498
|
+
export function getPageByFilename(filename, modules, options = {}) {
|
|
751
499
|
try {
|
|
752
|
-
// Find the
|
|
753
|
-
const entry = Object.entries(
|
|
754
|
-
return filepath.includes(
|
|
500
|
+
// Find the matching file
|
|
501
|
+
const entry = Object.entries(modules).find(([filepath]) => {
|
|
502
|
+
return filepath.includes(filename);
|
|
755
503
|
});
|
|
756
504
|
|
|
757
505
|
if (!entry) {
|
|
758
506
|
return null;
|
|
759
507
|
}
|
|
760
508
|
|
|
761
|
-
const
|
|
509
|
+
const rawContent = entry[1];
|
|
510
|
+
const { data, content, headers } = parseMarkdownContentSanitized(rawContent);
|
|
511
|
+
const slug = options.slug || filename.replace(".md", "");
|
|
762
512
|
|
|
763
|
-
|
|
764
|
-
const
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
const headers = extractHeaders(markdown);
|
|
768
|
-
|
|
769
|
-
// Get gutter content for the contact page
|
|
770
|
-
const gutterContent = getContactGutterContent("contact");
|
|
771
|
-
|
|
772
|
-
return {
|
|
773
|
-
slug: "contact",
|
|
774
|
-
title: data.title || "Contact",
|
|
513
|
+
// Build the result object
|
|
514
|
+
const result = {
|
|
515
|
+
slug,
|
|
516
|
+
title: data.title || slug.charAt(0).toUpperCase() + slug.slice(1),
|
|
775
517
|
description: data.description || "",
|
|
776
|
-
content
|
|
518
|
+
content,
|
|
777
519
|
headers,
|
|
778
|
-
gutterContent,
|
|
779
520
|
};
|
|
780
|
-
} catch (err) {
|
|
781
|
-
console.error("Error in getContactPage:", err);
|
|
782
|
-
return null;
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
521
|
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
export function getAboutPage() {
|
|
791
|
-
try {
|
|
792
|
-
// Find the about.md file
|
|
793
|
-
const entry = Object.entries(aboutModules).find(([filepath]) => {
|
|
794
|
-
return filepath.includes("about.md");
|
|
795
|
-
});
|
|
522
|
+
// Add optional fields from frontmatter
|
|
523
|
+
if (data.date) result.date = data.date;
|
|
524
|
+
if (data.hero) result.hero = data.hero;
|
|
525
|
+
if (data.galleries) result.galleries = data.galleries;
|
|
796
526
|
|
|
797
|
-
if
|
|
798
|
-
|
|
527
|
+
// Process gutter content if provided
|
|
528
|
+
if (options.gutterModules) {
|
|
529
|
+
const { manifest, markdown, images } = options.gutterModules;
|
|
530
|
+
result.gutterContent = processGutterContent(slug, manifest, markdown, images);
|
|
799
531
|
}
|
|
800
532
|
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
const { data, content: markdown } = matter(content);
|
|
804
|
-
const htmlContent = sanitizeMarkdown(marked.parse(markdown));
|
|
805
|
-
|
|
806
|
-
// Extract headers for table of contents
|
|
807
|
-
const headers = extractHeaders(markdown);
|
|
808
|
-
|
|
809
|
-
// Get gutter content for the about page
|
|
810
|
-
const gutterContent = getAboutGutterContent("about");
|
|
811
|
-
|
|
812
|
-
return {
|
|
813
|
-
slug: "about",
|
|
814
|
-
title: data.title || "About",
|
|
815
|
-
date: data.date || new Date().toISOString(),
|
|
816
|
-
description: data.description || "",
|
|
817
|
-
content: htmlContent,
|
|
818
|
-
headers,
|
|
819
|
-
gutterContent,
|
|
820
|
-
};
|
|
533
|
+
return result;
|
|
821
534
|
} catch (err) {
|
|
822
|
-
console.error(
|
|
535
|
+
console.error(`Error in getPageByFilename for ${filename}:`, err);
|
|
823
536
|
return null;
|
|
824
537
|
}
|
|
825
538
|
}
|
|
826
539
|
|
|
827
540
|
/**
|
|
828
|
-
* Get
|
|
829
|
-
*
|
|
830
|
-
* @
|
|
541
|
+
* Get site configuration from a config module
|
|
542
|
+
*
|
|
543
|
+
* @param {Object} configModule - The config module from import.meta.glob
|
|
544
|
+
* @returns {Object} Site configuration object
|
|
831
545
|
*/
|
|
832
|
-
export function
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
546
|
+
export function getSiteConfigFromModule(configModule) {
|
|
547
|
+
const entry = Object.entries(configModule)[0];
|
|
548
|
+
if (entry) {
|
|
549
|
+
return entry[1].default || entry[1];
|
|
550
|
+
}
|
|
551
|
+
return {
|
|
552
|
+
owner: { name: "Admin", email: "" },
|
|
553
|
+
site: { title: "The Grove", description: "", copyright: "AutumnsGrove" },
|
|
554
|
+
social: {},
|
|
555
|
+
};
|
|
839
556
|
}
|
|
840
557
|
|
|
841
558
|
/**
|
|
842
|
-
*
|
|
843
|
-
*
|
|
844
|
-
*
|
|
559
|
+
* Create a configured content loader with all functions bound to the provided modules
|
|
560
|
+
* This is the main factory function for creating a content loader in the consuming app
|
|
561
|
+
*
|
|
562
|
+
* @param {Object} config - Configuration object with all required modules
|
|
563
|
+
* @param {Object} config.posts - Post modules from import.meta.glob
|
|
564
|
+
* @param {Object} config.recipes - Recipe modules from import.meta.glob
|
|
565
|
+
* @param {Object} config.about - About page modules from import.meta.glob
|
|
566
|
+
* @param {Object} config.home - Home page modules from import.meta.glob
|
|
567
|
+
* @param {Object} config.contact - Contact page modules from import.meta.glob
|
|
568
|
+
* @param {Object} config.siteConfig - Site config module from import.meta.glob
|
|
569
|
+
* @param {Object} config.postGutter - Post gutter modules { manifest, markdown, images }
|
|
570
|
+
* @param {Object} config.recipeGutter - Recipe gutter modules { manifest, markdown, images }
|
|
571
|
+
* @param {Object} config.recipeMetadata - Recipe metadata modules from import.meta.glob
|
|
572
|
+
* @param {Object} config.aboutGutter - About gutter modules { manifest, markdown, images }
|
|
573
|
+
* @param {Object} config.homeGutter - Home gutter modules { manifest, markdown, images }
|
|
574
|
+
* @param {Object} config.contactGutter - Contact gutter modules { manifest, markdown, images }
|
|
575
|
+
* @returns {Object} Object with all content loader functions
|
|
845
576
|
*/
|
|
846
|
-
export function
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
577
|
+
export function createContentLoader(config) {
|
|
578
|
+
const {
|
|
579
|
+
posts = {},
|
|
580
|
+
recipes = {},
|
|
581
|
+
about = {},
|
|
582
|
+
home = {},
|
|
583
|
+
contact = {},
|
|
584
|
+
siteConfig = {},
|
|
585
|
+
postGutter = {},
|
|
586
|
+
recipeGutter = {},
|
|
587
|
+
recipeMetadata = {},
|
|
588
|
+
aboutGutter = {},
|
|
589
|
+
homeGutter = {},
|
|
590
|
+
contactGutter = {},
|
|
591
|
+
} = config;
|
|
855
592
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
593
|
+
return {
|
|
594
|
+
/**
|
|
595
|
+
* Get all posts with metadata
|
|
596
|
+
*/
|
|
597
|
+
getAllPosts() {
|
|
598
|
+
return processMarkdownModules(posts);
|
|
599
|
+
},
|
|
859
600
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
601
|
+
/**
|
|
602
|
+
* Get all recipes with metadata
|
|
603
|
+
*/
|
|
604
|
+
getAllRecipes() {
|
|
605
|
+
return processMarkdownModules(recipes);
|
|
606
|
+
},
|
|
863
607
|
|
|
864
|
-
/**
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
});
|
|
608
|
+
/**
|
|
609
|
+
* Get the latest (most recent) post with full content
|
|
610
|
+
*/
|
|
611
|
+
getLatestPost() {
|
|
612
|
+
const allPosts = processMarkdownModules(posts);
|
|
613
|
+
if (allPosts.length === 0) {
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
return this.getPostBySlug(allPosts[0].slug);
|
|
617
|
+
},
|
|
875
618
|
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
619
|
+
/**
|
|
620
|
+
* Get a single post by slug
|
|
621
|
+
*/
|
|
622
|
+
getPostBySlug(slug) {
|
|
623
|
+
return getItemBySlug(slug, posts, {
|
|
624
|
+
gutterModules: postGutter.manifest ? postGutter : undefined,
|
|
625
|
+
});
|
|
626
|
+
},
|
|
879
627
|
|
|
880
|
-
|
|
628
|
+
/**
|
|
629
|
+
* Get a single recipe by slug
|
|
630
|
+
*/
|
|
631
|
+
getRecipeBySlug(slug) {
|
|
632
|
+
return getItemBySlug(slug, recipes, {
|
|
633
|
+
gutterModules: recipeGutter.manifest ? recipeGutter : undefined,
|
|
634
|
+
sidecarModules: recipeMetadata,
|
|
635
|
+
});
|
|
636
|
+
},
|
|
881
637
|
|
|
882
|
-
|
|
638
|
+
/**
|
|
639
|
+
* Get the home page content
|
|
640
|
+
*/
|
|
641
|
+
getHomePage() {
|
|
642
|
+
return getPageByFilename("home.md", home, {
|
|
643
|
+
gutterModules: homeGutter.manifest ? homeGutter : undefined,
|
|
644
|
+
slug: "home",
|
|
645
|
+
});
|
|
646
|
+
},
|
|
883
647
|
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
648
|
+
/**
|
|
649
|
+
* Get the about page content
|
|
650
|
+
*/
|
|
651
|
+
getAboutPage() {
|
|
652
|
+
return getPageByFilename("about.md", about, {
|
|
653
|
+
gutterModules: aboutGutter.manifest ? aboutGutter : undefined,
|
|
654
|
+
slug: "about",
|
|
655
|
+
});
|
|
656
|
+
},
|
|
887
657
|
|
|
888
|
-
|
|
889
|
-
|
|
658
|
+
/**
|
|
659
|
+
* Get the contact page content
|
|
660
|
+
*/
|
|
661
|
+
getContactPage() {
|
|
662
|
+
return getPageByFilename("contact.md", contact, {
|
|
663
|
+
gutterModules: contactGutter.manifest ? contactGutter : undefined,
|
|
664
|
+
slug: "contact",
|
|
665
|
+
});
|
|
666
|
+
},
|
|
890
667
|
|
|
891
|
-
|
|
892
|
-
|
|
668
|
+
/**
|
|
669
|
+
* Get the site configuration
|
|
670
|
+
*/
|
|
671
|
+
getSiteConfig() {
|
|
672
|
+
return getSiteConfigFromModule(siteConfig);
|
|
673
|
+
},
|
|
893
674
|
|
|
894
|
-
|
|
895
|
-
|
|
675
|
+
/**
|
|
676
|
+
* Get gutter content for a post
|
|
677
|
+
*/
|
|
678
|
+
getGutterContent(slug) {
|
|
679
|
+
if (!postGutter.manifest) return [];
|
|
680
|
+
return processGutterContent(
|
|
681
|
+
slug,
|
|
682
|
+
postGutter.manifest,
|
|
683
|
+
postGutter.markdown || {},
|
|
684
|
+
postGutter.images || {},
|
|
685
|
+
);
|
|
686
|
+
},
|
|
896
687
|
|
|
897
|
-
|
|
898
|
-
|
|
688
|
+
/**
|
|
689
|
+
* Get gutter content for a recipe
|
|
690
|
+
*/
|
|
691
|
+
getRecipeGutterContent(slug) {
|
|
692
|
+
if (!recipeGutter.manifest) return [];
|
|
693
|
+
return processGutterContent(
|
|
694
|
+
slug,
|
|
695
|
+
recipeGutter.manifest,
|
|
696
|
+
recipeGutter.markdown || {},
|
|
697
|
+
recipeGutter.images || {},
|
|
698
|
+
);
|
|
699
|
+
},
|
|
899
700
|
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
}
|
|
701
|
+
/**
|
|
702
|
+
* Get gutter content for the home page
|
|
703
|
+
*/
|
|
704
|
+
getHomeGutterContent(slug) {
|
|
705
|
+
if (!homeGutter.manifest) return [];
|
|
706
|
+
return processGutterContent(
|
|
707
|
+
slug,
|
|
708
|
+
homeGutter.manifest,
|
|
709
|
+
homeGutter.markdown || {},
|
|
710
|
+
homeGutter.images || {},
|
|
711
|
+
);
|
|
712
|
+
},
|
|
912
713
|
|
|
913
|
-
/**
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
return `<div class="mermaid-container" id="${diagramId}" data-diagram="${encodeURIComponent(diagramCode.trim())}"></div>`;
|
|
714
|
+
/**
|
|
715
|
+
* Get gutter content for the about page
|
|
716
|
+
*/
|
|
717
|
+
getAboutGutterContent(slug) {
|
|
718
|
+
if (!aboutGutter.manifest) return [];
|
|
719
|
+
return processGutterContent(
|
|
720
|
+
slug,
|
|
721
|
+
aboutGutter.manifest,
|
|
722
|
+
aboutGutter.markdown || {},
|
|
723
|
+
aboutGutter.images || {},
|
|
724
|
+
);
|
|
925
725
|
},
|
|
926
|
-
);
|
|
927
|
-
}
|
|
928
726
|
|
|
929
|
-
/**
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
727
|
+
/**
|
|
728
|
+
* Get gutter content for the contact page
|
|
729
|
+
*/
|
|
730
|
+
getContactGutterContent(slug) {
|
|
731
|
+
if (!contactGutter.manifest) return [];
|
|
732
|
+
return processGutterContent(
|
|
733
|
+
slug,
|
|
734
|
+
contactGutter.manifest,
|
|
735
|
+
contactGutter.markdown || {},
|
|
736
|
+
contactGutter.images || {},
|
|
737
|
+
);
|
|
738
|
+
},
|
|
935
739
|
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
740
|
+
/**
|
|
741
|
+
* Get recipe sidecar/metadata by slug
|
|
742
|
+
*/
|
|
743
|
+
getRecipeSidecar(slug) {
|
|
744
|
+
const entry = Object.entries(recipeMetadata).find(([filepath]) => {
|
|
745
|
+
const parts = filepath.split("/");
|
|
746
|
+
const folder = parts[parts.length - 3];
|
|
747
|
+
return folder === slug;
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
if (!entry) {
|
|
751
|
+
return null;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
return entry[1].default || entry[1];
|
|
755
|
+
},
|
|
756
|
+
};
|
|
947
757
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@autumnsgrove/groveengine",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Multi-tenant blog engine for Grove Platform. Features gutter annotations, markdown editing, magic code auth, and Cloudflare Workers deployment.",
|
|
5
5
|
"author": "AutumnsGrove",
|
|
6
6
|
"license": "MIT",
|