@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.
- package/dist/components/admin/GutterManager.svelte +1 -2
- package/dist/components/admin/MarkdownEditor.svelte +1 -91
- package/dist/components/custom/ContentWithGutter.svelte +33 -7
- package/dist/components/custom/InternalsPostViewer.svelte +95 -0
- package/dist/components/custom/InternalsPostViewer.svelte.d.ts +13 -0
- package/dist/components/ui/index.d.ts +0 -12
- package/dist/components/ui/index.js +2 -13
- package/dist/components/ui/select/select-separator.svelte +2 -3
- package/dist/components/ui/select/select-separator.svelte.d.ts +1 -1
- package/dist/utils/markdown.d.ts +42 -11
- package/dist/utils/markdown.js +106 -48
- package/dist/utils/sanitize.js +3 -2
- package/package.json +2 -3
- package/dist/components/ui/Badge.svelte +0 -48
- package/dist/components/ui/Badge.svelte.d.ts +0 -26
- package/dist/components/ui/Button.svelte +0 -74
- package/dist/components/ui/Button.svelte.d.ts +0 -34
- package/dist/components/ui/Card.svelte +0 -102
- package/dist/components/ui/Card.svelte.d.ts +0 -46
- package/dist/components/ui/Input.svelte +0 -81
- package/dist/components/ui/Input.svelte.d.ts +0 -35
- package/dist/components/ui/Skeleton.svelte +0 -31
- package/dist/components/ui/Skeleton.svelte.d.ts +0 -26
- package/dist/components/ui/Textarea.svelte +0 -81
- package/dist/components/ui/Textarea.svelte.d.ts +0 -35
- package/dist/components/ui/badge/badge.svelte +0 -50
- package/dist/components/ui/badge/badge.svelte.d.ts +0 -60
- package/dist/components/ui/badge/index.d.ts +0 -2
- package/dist/components/ui/badge/index.js +0 -2
- package/dist/components/ui/button/button.svelte +0 -82
- package/dist/components/ui/button/button.svelte.d.ts +0 -132
- package/dist/components/ui/button/index.d.ts +0 -2
- package/dist/components/ui/button/index.js +0 -4
- package/dist/components/ui/card/card-content.svelte +0 -16
- package/dist/components/ui/card/card-content.svelte.d.ts +0 -5
- package/dist/components/ui/card/card-description.svelte +0 -16
- package/dist/components/ui/card/card-description.svelte.d.ts +0 -5
- package/dist/components/ui/card/card-footer.svelte +0 -16
- package/dist/components/ui/card/card-footer.svelte.d.ts +0 -5
- package/dist/components/ui/card/card-header.svelte +0 -16
- package/dist/components/ui/card/card-header.svelte.d.ts +0 -5
- package/dist/components/ui/card/card-title.svelte +0 -25
- package/dist/components/ui/card/card-title.svelte.d.ts +0 -8
- package/dist/components/ui/card/card.svelte +0 -20
- package/dist/components/ui/card/card.svelte.d.ts +0 -5
- package/dist/components/ui/card/index.d.ts +0 -7
- package/dist/components/ui/card/index.js +0 -9
- package/dist/components/ui/input/index.d.ts +0 -2
- package/dist/components/ui/input/index.js +0 -4
- package/dist/components/ui/input/input.svelte +0 -46
- package/dist/components/ui/input/input.svelte.d.ts +0 -13
- package/dist/components/ui/separator/index.d.ts +0 -2
- package/dist/components/ui/separator/index.js +0 -4
- package/dist/components/ui/separator/separator.svelte +0 -22
- package/dist/components/ui/separator/separator.svelte.d.ts +0 -4
- package/dist/components/ui/skeleton/index.d.ts +0 -2
- package/dist/components/ui/skeleton/index.js +0 -4
- package/dist/components/ui/skeleton/skeleton.svelte +0 -17
- package/dist/components/ui/skeleton/skeleton.svelte.d.ts +0 -5
- package/dist/components/ui/textarea/index.d.ts +0 -2
- package/dist/components/ui/textarea/index.js +0 -4
- package/dist/components/ui/textarea/textarea.svelte +0 -24
- 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
|
-
|
|
411
|
-
'
|
|
412
|
-
|
|
413
|
-
'
|
|
414
|
-
|
|
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
|
-
|
|
418
|
-
'
|
|
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
|
-
//
|
|
2
|
-
|
|
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
|
|
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
|
-
<
|
|
12
|
+
<SeparatorPrimitive.Root bind:ref class={cn("bg-muted -mx-1 my-1 h-px", className)} {...restProps} />
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
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;
|
package/dist/utils/markdown.d.ts
CHANGED
|
@@ -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;
|
package/dist/utils/markdown.js
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
import { marked } from "marked";
|
|
2
2
|
import matter from "gray-matter";
|
|
3
|
-
import
|
|
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
|
-
|
|
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
|
+
}
|
package/dist/utils/sanitize.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Centralized sanitization utilities for XSS prevention
|
|
3
|
-
* Uses
|
|
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 '
|
|
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.
|
|
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
|
}
|