@autumnsgrove/groveengine 0.1.1 → 0.3.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.
Files changed (63) hide show
  1. package/dist/components/admin/GutterManager.svelte +1 -2
  2. package/dist/components/admin/MarkdownEditor.svelte +1 -91
  3. package/dist/components/custom/ContentWithGutter.svelte +33 -7
  4. package/dist/components/custom/InternalsPostViewer.svelte +95 -0
  5. package/dist/components/custom/InternalsPostViewer.svelte.d.ts +13 -0
  6. package/dist/components/ui/index.d.ts +0 -12
  7. package/dist/components/ui/index.js +2 -13
  8. package/dist/components/ui/select/select-separator.svelte +2 -3
  9. package/dist/components/ui/select/select-separator.svelte.d.ts +1 -1
  10. package/dist/utils/markdown.d.ts +42 -11
  11. package/dist/utils/markdown.js +106 -48
  12. package/dist/utils/sanitize.js +3 -2
  13. package/package.json +2 -3
  14. package/dist/components/ui/Badge.svelte +0 -48
  15. package/dist/components/ui/Badge.svelte.d.ts +0 -26
  16. package/dist/components/ui/Button.svelte +0 -74
  17. package/dist/components/ui/Button.svelte.d.ts +0 -34
  18. package/dist/components/ui/Card.svelte +0 -102
  19. package/dist/components/ui/Card.svelte.d.ts +0 -46
  20. package/dist/components/ui/Input.svelte +0 -81
  21. package/dist/components/ui/Input.svelte.d.ts +0 -35
  22. package/dist/components/ui/Skeleton.svelte +0 -31
  23. package/dist/components/ui/Skeleton.svelte.d.ts +0 -26
  24. package/dist/components/ui/Textarea.svelte +0 -81
  25. package/dist/components/ui/Textarea.svelte.d.ts +0 -35
  26. package/dist/components/ui/badge/badge.svelte +0 -50
  27. package/dist/components/ui/badge/badge.svelte.d.ts +0 -60
  28. package/dist/components/ui/badge/index.d.ts +0 -2
  29. package/dist/components/ui/badge/index.js +0 -2
  30. package/dist/components/ui/button/button.svelte +0 -82
  31. package/dist/components/ui/button/button.svelte.d.ts +0 -132
  32. package/dist/components/ui/button/index.d.ts +0 -2
  33. package/dist/components/ui/button/index.js +0 -4
  34. package/dist/components/ui/card/card-content.svelte +0 -16
  35. package/dist/components/ui/card/card-content.svelte.d.ts +0 -5
  36. package/dist/components/ui/card/card-description.svelte +0 -16
  37. package/dist/components/ui/card/card-description.svelte.d.ts +0 -5
  38. package/dist/components/ui/card/card-footer.svelte +0 -16
  39. package/dist/components/ui/card/card-footer.svelte.d.ts +0 -5
  40. package/dist/components/ui/card/card-header.svelte +0 -16
  41. package/dist/components/ui/card/card-header.svelte.d.ts +0 -5
  42. package/dist/components/ui/card/card-title.svelte +0 -25
  43. package/dist/components/ui/card/card-title.svelte.d.ts +0 -8
  44. package/dist/components/ui/card/card.svelte +0 -20
  45. package/dist/components/ui/card/card.svelte.d.ts +0 -5
  46. package/dist/components/ui/card/index.d.ts +0 -7
  47. package/dist/components/ui/card/index.js +0 -9
  48. package/dist/components/ui/input/index.d.ts +0 -2
  49. package/dist/components/ui/input/index.js +0 -4
  50. package/dist/components/ui/input/input.svelte +0 -46
  51. package/dist/components/ui/input/input.svelte.d.ts +0 -13
  52. package/dist/components/ui/separator/index.d.ts +0 -2
  53. package/dist/components/ui/separator/index.js +0 -4
  54. package/dist/components/ui/separator/separator.svelte +0 -22
  55. package/dist/components/ui/separator/separator.svelte.d.ts +0 -4
  56. package/dist/components/ui/skeleton/index.d.ts +0 -2
  57. package/dist/components/ui/skeleton/index.js +0 -4
  58. package/dist/components/ui/skeleton/skeleton.svelte +0 -17
  59. package/dist/components/ui/skeleton/skeleton.svelte.d.ts +0 -5
  60. package/dist/components/ui/textarea/index.d.ts +0 -2
  61. package/dist/components/ui/textarea/index.js +0 -4
  62. package/dist/components/ui/textarea/textarea.svelte +0 -24
  63. package/dist/components/ui/textarea/textarea.svelte.d.ts +0 -6
@@ -1,8 +1,7 @@
1
1
  <script>
2
2
  import { marked } from "marked";
3
+ import { Input, Button } from '@groveengine/ui';
3
4
  import Dialog from "../ui/Dialog.svelte";
4
- import Input from "../ui/Input.svelte";
5
- import Button from "../ui/Button.svelte";
6
5
  import Select from "../ui/Select.svelte";
7
6
  import { toast } from "../ui/toast";
8
7
 
@@ -1,41 +1,10 @@
1
1
  <script>
2
2
  import { marked } from "marked";
3
- import mermaid from "mermaid";
4
3
  import { onMount, tick } from "svelte";
5
4
  import { sanitizeMarkdown } from "../../utils/sanitize.js";
6
5
  import "../../styles/content.css";
6
+ import { Button, Input } from '@groveengine/ui';
7
7
  import Dialog from "../ui/Dialog.svelte";
8
- import Button from "../ui/Button.svelte";
9
- import Input from "../ui/Input.svelte";
10
-
11
- // Initialize mermaid with grove-themed dark config
12
- mermaid.initialize({
13
- startOnLoad: false,
14
- theme: "dark",
15
- themeVariables: {
16
- primaryColor: "#2d5a2d",
17
- primaryTextColor: "#d4d4d4",
18
- primaryBorderColor: "#4a7c4a",
19
- lineColor: "#8bc48b",
20
- secondaryColor: "#1e3a1e",
21
- tertiaryColor: "#2a2a2a",
22
- background: "#1e1e1e",
23
- mainBkg: "#252526",
24
- secondBkg: "#1e1e1e",
25
- nodeBorder: "#4a7c4a",
26
- clusterBkg: "#1a2a1a",
27
- titleColor: "#8bc48b",
28
- edgeLabelBackground: "#252526",
29
- },
30
- flowchart: {
31
- curve: "basis",
32
- padding: 15,
33
- },
34
- sequence: {
35
- actorMargin: 50,
36
- boxMargin: 10,
37
- },
38
- });
39
8
 
40
9
  // Props
41
10
  let {
@@ -304,43 +273,9 @@
304
273
  );
305
274
  let charCount = $derived(content.length);
306
275
  let lineCount = $derived(content.split("\n").length);
307
- // Custom marked renderer for mermaid blocks
308
- const renderer = new marked.Renderer();
309
- const originalCodeRenderer = renderer.code.bind(renderer);
310
-
311
- renderer.code = function ({ text, lang }) {
312
- if (lang === "mermaid") {
313
- // Wrap mermaid code in a special container for rendering
314
- const id = `mermaid-${Math.random().toString(36).substr(2, 9)}`;
315
- return `<div class="mermaid-container"><pre class="mermaid" id="${id}">${text}</pre></div>`;
316
- }
317
- return originalCodeRenderer({ text, lang });
318
- };
319
-
320
- marked.use({ renderer });
321
276
 
322
277
  let previewHtml = $derived(content ? sanitizeMarkdown(marked.parse(content)) : "");
323
278
 
324
- // Render mermaid diagrams after preview updates
325
- async function renderMermaidDiagrams() {
326
- await tick();
327
- const mermaidElements = document.querySelectorAll(".preview-content .mermaid, .full-preview-scroll .mermaid");
328
- if (mermaidElements.length > 0) {
329
- try {
330
- await mermaid.run({ nodes: mermaidElements });
331
- } catch (e) {
332
- console.warn("Mermaid rendering error:", e);
333
- }
334
- }
335
- }
336
-
337
- // Trigger mermaid rendering when preview HTML changes
338
- $effect(() => {
339
- if (previewHtml && (showPreview || showFullPreview)) {
340
- renderMermaidDiagrams();
341
- }
342
- });
343
-
344
279
  // Reading time estimate (average 200 words per minute)
345
280
  let readingTime = $derived(() => {
346
281
  const minutes = Math.ceil(wordCount / 200);
@@ -534,7 +469,6 @@
534
469
  { id: "heading2", label: "Heading 2", insert: "## " },
535
470
  { id: "heading3", label: "Heading 3", insert: "### " },
536
471
  { id: "code", label: "Code Block", insert: "```\n\n```", cursorOffset: 4 },
537
- { id: "mermaid", label: "Mermaid Diagram", insert: "```mermaid\nflowchart TD\n A[Start] --> B[End]\n```", cursorOffset: 32 },
538
472
  { id: "quote", label: "Quote", insert: "> " },
539
473
  { id: "list", label: "Bullet List", insert: "- " },
540
474
  { id: "numbered", label: "Numbered List", insert: "1. " },
@@ -2620,30 +2554,6 @@
2620
2554
  color: #6a6a6a;
2621
2555
  font-family: "JetBrains Mono", monospace;
2622
2556
  }
2623
- /* Mermaid Diagram Styles */
2624
- :global(.mermaid-container) {
2625
- margin: 1.5rem 0;
2626
- padding: 1rem;
2627
- background: var(--light-bg-primary);
2628
- border: 1px solid var(--light-border-primary);
2629
- border-radius: 8px;
2630
- overflow-x: auto;
2631
- }
2632
- :global(.mermaid) {
2633
- display: flex;
2634
- justify-content: center;
2635
- }
2636
- :global(.mermaid svg) {
2637
- max-width: 100%;
2638
- height: auto;
2639
- }
2640
- /* Mermaid error styling */
2641
- :global(.mermaid-container .error) {
2642
- color: #e07030;
2643
- padding: 0.5rem;
2644
- font-family: monospace;
2645
- font-size: 0.85rem;
2646
- }
2647
2557
  /* Mode Transitions */
2648
2558
  .editor-container {
2649
2559
  transition: border-color 0.3s ease, box-shadow 0.3s ease;
@@ -406,16 +406,42 @@
406
406
  DOMPurify
407
407
  ? DOMPurify.sanitize(processedContent, {
408
408
  ALLOWED_TAGS: [
409
+ // Headings
409
410
  'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
410
- 'p', 'a', 'ul', 'ol', 'li', 'blockquote',
411
- 'code', 'pre', 'strong', 'em', 'img',
412
- 'table', 'thead', 'tbody', 'tr', 'th', 'td',
413
- 'br', 'hr', 'div', 'span', 'sup', 'sub',
414
- 'del', 'ins'
411
+ // Block elements
412
+ 'p', 'blockquote', 'pre', 'hr', 'br', 'div',
413
+ // Lists
414
+ 'ul', 'ol', 'li', 'dl', 'dt', 'dd',
415
+ // Inline elements
416
+ 'a', 'span', 'code', 'strong', 'em', 'b', 'i', 'u',
417
+ 'sup', 'sub', 'del', 'ins', 'mark', 'small', 'abbr',
418
+ 'kbd', 'samp', 'var', 'q', 's',
419
+ // Tables
420
+ 'table', 'thead', 'tbody', 'tfoot', 'tr', 'th', 'td', 'caption',
421
+ // Media
422
+ 'img', 'figure', 'figcaption', 'picture', 'source',
423
+ // Forms (for task lists)
424
+ 'input', 'label',
425
+ // Code block copy buttons
426
+ 'button', 'svg', 'path', 'rect', 'g', 'line', 'circle', 'polyline'
415
427
  ],
416
428
  ALLOWED_ATTR: [
417
- 'href', 'src', 'alt', 'title', 'class', 'id',
418
- 'data-anchor', 'data-language', 'data-line-numbers'
429
+ // Links and media
430
+ 'href', 'src', 'alt', 'title', 'target', 'rel',
431
+ // Styling and identification
432
+ 'class', 'id', 'style',
433
+ // Data attributes for custom functionality
434
+ 'data-anchor', 'data-language', 'data-line-numbers', 'data-code',
435
+ // Accessibility
436
+ 'aria-label', 'aria-hidden', 'role',
437
+ // Form elements (for task lists)
438
+ 'type', 'checked', 'disabled',
439
+ // SVG attributes
440
+ 'viewBox', 'fill', 'stroke', 'stroke-width', 'stroke-linecap',
441
+ 'stroke-linejoin', 'd', 'width', 'height', 'x', 'y', 'x1', 'y1',
442
+ 'x2', 'y2', 'r', 'cx', 'cy', 'points', 'xmlns',
443
+ // Tables
444
+ 'colspan', 'rowspan', 'scope'
419
445
  ],
420
446
  ALLOW_DATA_ATTR: true
421
447
  })
@@ -0,0 +1,95 @@
1
+ <script>
2
+ /**
3
+ * Simple component to display a featured blog post
4
+ * @prop {{ title: string; description?: string; slug: string; date?: string }} post - Post data
5
+ * @prop {string} [caption] - Optional caption text
6
+ */
7
+ let { post, caption = "" } = $props();
8
+
9
+ const formattedDate = $derived(post.date ? new Date(post.date).toLocaleDateString('en-US', {
10
+ year: 'numeric',
11
+ month: 'long',
12
+ day: 'numeric'
13
+ }) : null);
14
+ </script>
15
+
16
+ <article class="post-viewer">
17
+ {#if caption}
18
+ <span class="caption">{caption}</span>
19
+ {/if}
20
+ <a href="/blog/{post.slug}" class="post-link">
21
+ <h3 class="title">{post.title}</h3>
22
+ {#if post.description}
23
+ <p class="description">{post.description}</p>
24
+ {/if}
25
+ {#if formattedDate}
26
+ <time class="date">{formattedDate}</time>
27
+ {/if}
28
+ </a>
29
+ </article>
30
+
31
+ <style>
32
+ .post-viewer {
33
+ background: var(--color-bg-secondary);
34
+ border: 1px solid var(--color-border);
35
+ border-radius: var(--border-radius-standard);
36
+ padding: 1.5rem;
37
+ transition: all 0.3s ease;
38
+ }
39
+
40
+ .caption {
41
+ display: block;
42
+ font-size: 0.75rem;
43
+ text-transform: uppercase;
44
+ letter-spacing: 0.05em;
45
+ color: var(--color-primary);
46
+ margin-bottom: 0.5rem;
47
+ }
48
+
49
+ .post-link {
50
+ text-decoration: none;
51
+ color: inherit;
52
+ display: block;
53
+ }
54
+
55
+ .post-link:hover .title {
56
+ color: var(--color-primary);
57
+ }
58
+
59
+ .title {
60
+ font-size: 1.25rem;
61
+ margin: 0 0 0.5rem 0;
62
+ color: var(--color-text);
63
+ transition: color 0.2s ease;
64
+ }
65
+
66
+ .description {
67
+ margin: 0 0 0.75rem 0;
68
+ color: var(--color-text-muted);
69
+ font-size: 0.95rem;
70
+ line-height: 1.6;
71
+ }
72
+
73
+ .date {
74
+ display: block;
75
+ font-size: 0.875rem;
76
+ color: var(--color-text-subtle);
77
+ }
78
+
79
+ :global(.dark) .post-viewer {
80
+ background: var(--color-bg-tertiary-dark);
81
+ border-color: var(--color-border-dark);
82
+ }
83
+
84
+ :global(.dark) .title {
85
+ color: var(--color-text-dark);
86
+ }
87
+
88
+ :global(.dark) .description {
89
+ color: var(--color-text-subtle-dark);
90
+ }
91
+
92
+ :global(.dark) .date {
93
+ color: var(--color-text-subtle-dark);
94
+ }
95
+ </style>
@@ -0,0 +1,13 @@
1
+ export default InternalsPostViewer;
2
+ type InternalsPostViewer = {
3
+ $on?(type: string, callback: (e: any) => void): () => void;
4
+ $set?(props: Partial<$$ComponentProps>): void;
5
+ };
6
+ declare const InternalsPostViewer: import("svelte").Component<{
7
+ post: any;
8
+ caption?: string;
9
+ }, {}, "">;
10
+ type $$ComponentProps = {
11
+ post: any;
12
+ caption?: string;
13
+ };
@@ -1,26 +1,14 @@
1
- export { default as Button } from "./Button.svelte";
2
- export { default as Card } from "./Card.svelte";
3
- export { default as Badge } from "./Badge.svelte";
4
1
  export { default as Dialog } from "./Dialog.svelte";
5
- export { default as Input } from "./Input.svelte";
6
- export { default as Textarea } from "./Textarea.svelte";
7
2
  export { default as Select } from "./Select.svelte";
8
3
  export { default as Tabs } from "./Tabs.svelte";
9
4
  export { default as Accordion } from "./Accordion.svelte";
10
5
  export { default as Sheet } from "./Sheet.svelte";
11
6
  export { default as Toast } from "./Toast.svelte";
12
- export { default as Skeleton } from "./Skeleton.svelte";
13
7
  export { default as Table } from "./Table.svelte";
14
8
  export { toast } from "./toast";
15
9
  export { Root as DialogRoot, Trigger as DialogTrigger, Close as DialogClose, Portal as DialogPortal } from "./dialog";
16
10
  export { Root as SheetRoot, Trigger as SheetTrigger, Close as SheetClose, Portal as SheetPortal, Content as SheetContent, Header as SheetHeader, Footer as SheetFooter, Title as SheetTitle, Description as SheetDescription } from "./sheet";
17
11
  export { Root as TableRoot, Body as TableBody, Caption as TableCaption, Cell as TableCell, Footer as TableFooter, Head as TableHead, Header as TableHeader, Row as TableRow } from "./table";
18
- export { Root as SkeletonRoot, Skeleton as SkeletonComponent } from "./skeleton";
19
12
  export { Root as AccordionRoot } from "./accordion";
20
- export { Root as BadgeRoot } from "./badge";
21
- export { Root as ButtonRoot } from "./button";
22
- export { Root as CardRoot, Header as CardHeader, Title as CardTitle, Description as CardDescription, Content as CardContent, Footer as CardFooter } from "./card";
23
- export { Root as InputRoot } from "./input";
24
- export { Root as TextareaRoot } from "./textarea";
25
13
  export { Root as SelectRoot, Trigger as SelectTrigger, Content as SelectContent, Item as SelectItem, Group as SelectGroup, GroupHeading as SelectLabel, Separator as SelectSeparator, ScrollUpButton as SelectScrollUpButton, ScrollDownButton as SelectScrollDownButton } from "./select";
26
14
  export { Root as TabsRoot, List as TabsList, Trigger as TabsTrigger, Content as TabsContent } from "./tabs";
@@ -1,16 +1,11 @@
1
- // Wrappers
2
- export { default as Button } from "./Button.svelte";
3
- export { default as Card } from "./Card.svelte";
4
- export { default as Badge } from "./Badge.svelte";
1
+ // Admin-specific UI component wrappers
2
+ // Generic UI components (Button, Card, Badge, Input, Textarea, Skeleton) are now in @groveengine/ui
5
3
  export { default as Dialog } from "./Dialog.svelte";
6
- export { default as Input } from "./Input.svelte";
7
- export { default as Textarea } from "./Textarea.svelte";
8
4
  export { default as Select } from "./Select.svelte";
9
5
  export { default as Tabs } from "./Tabs.svelte";
10
6
  export { default as Accordion } from "./Accordion.svelte";
11
7
  export { default as Sheet } from "./Sheet.svelte";
12
8
  export { default as Toast } from "./Toast.svelte";
13
- export { default as Skeleton } from "./Skeleton.svelte";
14
9
  export { default as Table } from "./Table.svelte";
15
10
  // Toast utilities
16
11
  export { toast } from "./toast";
@@ -18,12 +13,6 @@ export { toast } from "./toast";
18
13
  export { Root as DialogRoot, Trigger as DialogTrigger, Close as DialogClose, Portal as DialogPortal } from "./dialog";
19
14
  export { Root as SheetRoot, Trigger as SheetTrigger, Close as SheetClose, Portal as SheetPortal, Content as SheetContent, Header as SheetHeader, Footer as SheetFooter, Title as SheetTitle, Description as SheetDescription } from "./sheet";
20
15
  export { Root as TableRoot, Body as TableBody, Caption as TableCaption, Cell as TableCell, Footer as TableFooter, Head as TableHead, Header as TableHeader, Row as TableRow } from "./table";
21
- export { Root as SkeletonRoot, Skeleton as SkeletonComponent } from "./skeleton";
22
16
  export { Root as AccordionRoot } from "./accordion";
23
- export { Root as BadgeRoot } from "./badge";
24
- export { Root as ButtonRoot } from "./button";
25
- export { Root as CardRoot, Header as CardHeader, Title as CardTitle, Description as CardDescription, Content as CardContent, Footer as CardFooter } from "./card";
26
- export { Root as InputRoot } from "./input";
27
- export { Root as TextareaRoot } from "./textarea";
28
17
  export { Root as SelectRoot, Trigger as SelectTrigger, Content as SelectContent, Item as SelectItem, Group as SelectGroup, GroupHeading as SelectLabel, Separator as SelectSeparator, ScrollUpButton as SelectScrollUpButton, ScrollDownButton as SelectScrollDownButton } from "./select";
29
18
  export { Root as TabsRoot, List as TabsList, Trigger as TabsTrigger, Content as TabsContent } from "./tabs";
@@ -1,6 +1,5 @@
1
1
  <script lang="ts">
2
- import type { Separator as SeparatorPrimitive } from "bits-ui";
3
- import { Separator } from "../separator/index.js";
2
+ import { Separator as SeparatorPrimitive } from "bits-ui";
4
3
  import { cn } from "../../../utils";
5
4
 
6
5
  let {
@@ -10,4 +9,4 @@
10
9
  }: SeparatorPrimitive.RootProps = $props();
11
10
  </script>
12
11
 
13
- <Separator bind:ref class={cn("bg-muted -mx-1 my-1 h-px", className)} {...restProps} />
12
+ <SeparatorPrimitive.Root bind:ref class={cn("bg-muted -mx-1 my-1 h-px", className)} {...restProps} />
@@ -1,4 +1,4 @@
1
- import type { Separator as SeparatorPrimitive } from "bits-ui";
1
+ import { Separator as SeparatorPrimitive } from "bits-ui";
2
2
  declare const SelectSeparator: import("svelte").Component<SeparatorPrimitive.RootProps, {}, "ref">;
3
3
  type SelectSeparator = ReturnType<typeof SelectSeparator>;
4
4
  export default SelectSeparator;
@@ -11,17 +11,6 @@ export function extractHeaders(markdown: string): any[];
11
11
  * @returns {string} HTML with anchor markers converted to spans
12
12
  */
13
13
  export function processAnchorTags(html: string): string;
14
- /**
15
- * Process Mermaid diagrams in markdown content
16
- * @param {string} markdown - The markdown content
17
- * @returns {string} Processed markdown with Mermaid diagrams
18
- */
19
- export function processMermaidDiagrams(markdown: string): string;
20
- /**
21
- * Render Mermaid diagrams in the DOM
22
- * This should be called after the content is mounted
23
- */
24
- export function renderMermaidDiagrams(): Promise<void>;
25
14
  /**
26
15
  * Parse markdown content and convert to HTML
27
16
  * @param {string} markdownContent - The raw markdown content (may include frontmatter)
@@ -121,3 +110,45 @@ export function createContentLoader(config: {
121
110
  homeGutter: Object;
122
111
  contactGutter: Object;
123
112
  }): Object;
113
+ /**
114
+ * Register a content loader for the site
115
+ * This should be called by the consuming site to provide access to content
116
+ * @param {Object} loader - Object with getAllPosts, getSiteConfig, getLatestPost functions
117
+ */
118
+ export function registerContentLoader(loader: Object): void;
119
+ /**
120
+ * Get all blog posts
121
+ * @returns {Array} Array of post objects
122
+ */
123
+ export function getAllPosts(): any[];
124
+ /**
125
+ * Get site configuration
126
+ * @returns {Object} Site config object
127
+ */
128
+ export function getSiteConfig(): Object;
129
+ /**
130
+ * Get the latest post
131
+ * @returns {Object|null} Latest post or null
132
+ */
133
+ export function getLatestPost(): Object | null;
134
+ /**
135
+ * Get home page content
136
+ * @returns {Object|null} Home page data or null
137
+ */
138
+ export function getHomePage(): Object | null;
139
+ /**
140
+ * Get a post by its slug
141
+ * @param {string} slug - The post slug
142
+ * @returns {Object|null} Post object or null
143
+ */
144
+ export function getPostBySlug(slug: string): Object | null;
145
+ /**
146
+ * Get about page content
147
+ * @returns {Object|null} About page data or null
148
+ */
149
+ export function getAboutPage(): Object | null;
150
+ /**
151
+ * Get contact page content
152
+ * @returns {Object|null} Contact page data or null
153
+ */
154
+ export function getContactPage(): Object | null;
@@ -1,14 +1,6 @@
1
1
  import { marked } from "marked";
2
2
  import matter from "gray-matter";
3
- import mermaid from "mermaid";
4
- import { sanitizeSVG, sanitizeMarkdown } from './sanitize.js';
5
-
6
- // Configure Mermaid
7
- mermaid.initialize({
8
- startOnLoad: false,
9
- theme: "default",
10
- securityLevel: "strict",
11
- });
3
+ import { sanitizeMarkdown } from './sanitize.js';
12
4
 
13
5
  // Configure marked renderer for GitHub-style code blocks
14
6
  const renderer = new marked.Renderer();
@@ -142,42 +134,6 @@ export function processAnchorTags(html) {
142
134
  );
143
135
  }
144
136
 
145
- /**
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
137
  /**
182
138
  * Parse markdown content and convert to HTML
183
139
  * @param {string} markdownContent - The raw markdown content (may include frontmatter)
@@ -186,9 +142,7 @@ export async function renderMermaidDiagrams() {
186
142
  export function parseMarkdownContent(markdownContent) {
187
143
  const { data, content: markdown } = matter(markdownContent);
188
144
 
189
- // Process Mermaid diagrams in the content
190
- const processedContent = processMermaidDiagrams(markdown);
191
- let htmlContent = marked.parse(processedContent);
145
+ let htmlContent = marked.parse(markdown);
192
146
 
193
147
  // Process anchor tags in the HTML content
194
148
  htmlContent = processAnchorTags(htmlContent);
@@ -755,3 +709,107 @@ export function createContentLoader(config) {
755
709
  },
756
710
  };
757
711
  }
712
+
713
+ /**
714
+ * Registry for site-specific content loaders
715
+ * Sites must register their content loaders using registerContentLoader()
716
+ */
717
+ let contentLoader = null;
718
+
719
+ /**
720
+ * Register a content loader for the site
721
+ * This should be called by the consuming site to provide access to content
722
+ * @param {Object} loader - Object with getAllPosts, getSiteConfig, getLatestPost functions
723
+ */
724
+ export function registerContentLoader(loader) {
725
+ contentLoader = loader;
726
+ }
727
+
728
+ /**
729
+ * Get all blog posts
730
+ * @returns {Array} Array of post objects
731
+ */
732
+ export function getAllPosts() {
733
+ if (!contentLoader || !contentLoader.getAllPosts) {
734
+ console.warn('getAllPosts: No content loader registered. Call registerContentLoader() in your site.');
735
+ return [];
736
+ }
737
+ return contentLoader.getAllPosts();
738
+ }
739
+
740
+ /**
741
+ * Get site configuration
742
+ * @returns {Object} Site config object
743
+ */
744
+ export function getSiteConfig() {
745
+ if (!contentLoader || !contentLoader.getSiteConfig) {
746
+ console.warn('getSiteConfig: No content loader registered. Call registerContentLoader() in your site.');
747
+ return {
748
+ owner: { name: "Admin", email: "" },
749
+ site: { title: "GroveEngine Site", description: "", copyright: "" },
750
+ social: {},
751
+ };
752
+ }
753
+ return contentLoader.getSiteConfig();
754
+ }
755
+
756
+ /**
757
+ * Get the latest post
758
+ * @returns {Object|null} Latest post or null
759
+ */
760
+ export function getLatestPost() {
761
+ if (!contentLoader || !contentLoader.getLatestPost) {
762
+ console.warn('getLatestPost: No content loader registered. Call registerContentLoader() in your site.');
763
+ return null;
764
+ }
765
+ return contentLoader.getLatestPost();
766
+ }
767
+
768
+ /**
769
+ * Get home page content
770
+ * @returns {Object|null} Home page data or null
771
+ */
772
+ export function getHomePage() {
773
+ if (!contentLoader || !contentLoader.getHomePage) {
774
+ console.warn('getHomePage: No content loader registered. Call registerContentLoader() in your site.');
775
+ return null;
776
+ }
777
+ return contentLoader.getHomePage();
778
+ }
779
+
780
+ /**
781
+ * Get a post by its slug
782
+ * @param {string} slug - The post slug
783
+ * @returns {Object|null} Post object or null
784
+ */
785
+ export function getPostBySlug(slug) {
786
+ if (!contentLoader || !contentLoader.getPostBySlug) {
787
+ console.warn('getPostBySlug: No content loader registered. Call registerContentLoader() in your site.');
788
+ return null;
789
+ }
790
+ return contentLoader.getPostBySlug(slug);
791
+ }
792
+
793
+ /**
794
+ * Get about page content
795
+ * @returns {Object|null} About page data or null
796
+ */
797
+ export function getAboutPage() {
798
+ if (!contentLoader || !contentLoader.getAboutPage) {
799
+ console.warn('getAboutPage: No content loader registered. Call registerContentLoader() in your site.');
800
+ return null;
801
+ }
802
+ return contentLoader.getAboutPage();
803
+ }
804
+
805
+ /**
806
+ * Get contact page content
807
+ * @returns {Object|null} Contact page data or null
808
+ */
809
+ export function getContactPage() {
810
+ if (!contentLoader || !contentLoader.getContactPage) {
811
+ console.warn('getContactPage: No content loader registered. Call registerContentLoader() in your site.');
812
+ return null;
813
+ }
814
+ return contentLoader.getContactPage();
815
+ }
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Centralized sanitization utilities for XSS prevention
3
- * Uses isomorphic-dompurify for both server-side and client-side sanitization
3
+ * Uses dompurify for client-side sanitization
4
+ * Note: For Cloudflare Workers, DOM APIs are available so we use client-side dompurify
4
5
  */
5
6
 
6
- import DOMPurify from 'isomorphic-dompurify';
7
+ import DOMPurify from 'dompurify';
7
8
 
8
9
  /**
9
10
  * Sanitize HTML content to prevent XSS attacks
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autumnsgrove/groveengine",
3
- "version": "0.1.1",
3
+ "version": "0.3.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",
@@ -114,15 +114,14 @@
114
114
  "vitest": "^4.0.14"
115
115
  },
116
116
  "dependencies": {
117
+ "@groveengine/ui": "^0.3.0",
117
118
  "@types/dompurify": "^3.0.5",
118
119
  "chart.js": "^4.5.1",
119
120
  "clsx": "^2.1.1",
120
121
  "dompurify": "^3.3.0",
121
122
  "gray-matter": "^4.0.3",
122
- "isomorphic-dompurify": "^2.33.0",
123
123
  "lucide-svelte": "^0.554.0",
124
124
  "marked": "^17.0.1",
125
- "mermaid": "^11.12.1",
126
125
  "sonner": "^2.0.7",
127
126
  "tailwind-merge": "^3.4.0"
128
127
  }