@autumnsgrove/groveengine 0.3.0 → 0.3.2
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/MarkdownEditor.svelte +0 -89
- package/dist/components/custom/ContentWithGutter.svelte +33 -7
- package/dist/components/ui/Toast.svelte +1 -1
- package/dist/components/ui/toast.js +1 -1
- package/dist/utils/markdown.d.ts +0 -11
- package/dist/utils/markdown.js +2 -48
- package/dist/utils/sanitize.js +3 -2
- package/package.json +4 -6
|
@@ -1,41 +1,11 @@
|
|
|
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";
|
|
7
6
|
import { Button, Input } from '@groveengine/ui';
|
|
8
7
|
import Dialog from "../ui/Dialog.svelte";
|
|
9
8
|
|
|
10
|
-
// Initialize mermaid with grove-themed dark config
|
|
11
|
-
mermaid.initialize({
|
|
12
|
-
startOnLoad: false,
|
|
13
|
-
theme: "dark",
|
|
14
|
-
themeVariables: {
|
|
15
|
-
primaryColor: "#2d5a2d",
|
|
16
|
-
primaryTextColor: "#d4d4d4",
|
|
17
|
-
primaryBorderColor: "#4a7c4a",
|
|
18
|
-
lineColor: "#8bc48b",
|
|
19
|
-
secondaryColor: "#1e3a1e",
|
|
20
|
-
tertiaryColor: "#2a2a2a",
|
|
21
|
-
background: "#1e1e1e",
|
|
22
|
-
mainBkg: "#252526",
|
|
23
|
-
secondBkg: "#1e1e1e",
|
|
24
|
-
nodeBorder: "#4a7c4a",
|
|
25
|
-
clusterBkg: "#1a2a1a",
|
|
26
|
-
titleColor: "#8bc48b",
|
|
27
|
-
edgeLabelBackground: "#252526",
|
|
28
|
-
},
|
|
29
|
-
flowchart: {
|
|
30
|
-
curve: "basis",
|
|
31
|
-
padding: 15,
|
|
32
|
-
},
|
|
33
|
-
sequence: {
|
|
34
|
-
actorMargin: 50,
|
|
35
|
-
boxMargin: 10,
|
|
36
|
-
},
|
|
37
|
-
});
|
|
38
|
-
|
|
39
9
|
// Props
|
|
40
10
|
let {
|
|
41
11
|
content = $bindable(""),
|
|
@@ -303,43 +273,9 @@
|
|
|
303
273
|
);
|
|
304
274
|
let charCount = $derived(content.length);
|
|
305
275
|
let lineCount = $derived(content.split("\n").length);
|
|
306
|
-
// Custom marked renderer for mermaid blocks
|
|
307
|
-
const renderer = new marked.Renderer();
|
|
308
|
-
const originalCodeRenderer = renderer.code.bind(renderer);
|
|
309
|
-
|
|
310
|
-
renderer.code = function ({ text, lang }) {
|
|
311
|
-
if (lang === "mermaid") {
|
|
312
|
-
// Wrap mermaid code in a special container for rendering
|
|
313
|
-
const id = `mermaid-${Math.random().toString(36).substr(2, 9)}`;
|
|
314
|
-
return `<div class="mermaid-container"><pre class="mermaid" id="${id}">${text}</pre></div>`;
|
|
315
|
-
}
|
|
316
|
-
return originalCodeRenderer({ text, lang });
|
|
317
|
-
};
|
|
318
|
-
|
|
319
|
-
marked.use({ renderer });
|
|
320
276
|
|
|
321
277
|
let previewHtml = $derived(content ? sanitizeMarkdown(marked.parse(content)) : "");
|
|
322
278
|
|
|
323
|
-
// Render mermaid diagrams after preview updates
|
|
324
|
-
async function renderMermaidDiagrams() {
|
|
325
|
-
await tick();
|
|
326
|
-
const mermaidElements = document.querySelectorAll(".preview-content .mermaid, .full-preview-scroll .mermaid");
|
|
327
|
-
if (mermaidElements.length > 0) {
|
|
328
|
-
try {
|
|
329
|
-
await mermaid.run({ nodes: mermaidElements });
|
|
330
|
-
} catch (e) {
|
|
331
|
-
console.warn("Mermaid rendering error:", e);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
// Trigger mermaid rendering when preview HTML changes
|
|
337
|
-
$effect(() => {
|
|
338
|
-
if (previewHtml && (showPreview || showFullPreview)) {
|
|
339
|
-
renderMermaidDiagrams();
|
|
340
|
-
}
|
|
341
|
-
});
|
|
342
|
-
|
|
343
279
|
// Reading time estimate (average 200 words per minute)
|
|
344
280
|
let readingTime = $derived(() => {
|
|
345
281
|
const minutes = Math.ceil(wordCount / 200);
|
|
@@ -533,7 +469,6 @@
|
|
|
533
469
|
{ id: "heading2", label: "Heading 2", insert: "## " },
|
|
534
470
|
{ id: "heading3", label: "Heading 3", insert: "### " },
|
|
535
471
|
{ id: "code", label: "Code Block", insert: "```\n\n```", cursorOffset: 4 },
|
|
536
|
-
{ id: "mermaid", label: "Mermaid Diagram", insert: "```mermaid\nflowchart TD\n A[Start] --> B[End]\n```", cursorOffset: 32 },
|
|
537
472
|
{ id: "quote", label: "Quote", insert: "> " },
|
|
538
473
|
{ id: "list", label: "Bullet List", insert: "- " },
|
|
539
474
|
{ id: "numbered", label: "Numbered List", insert: "1. " },
|
|
@@ -2619,30 +2554,6 @@
|
|
|
2619
2554
|
color: #6a6a6a;
|
|
2620
2555
|
font-family: "JetBrains Mono", monospace;
|
|
2621
2556
|
}
|
|
2622
|
-
/* Mermaid Diagram Styles */
|
|
2623
|
-
:global(.mermaid-container) {
|
|
2624
|
-
margin: 1.5rem 0;
|
|
2625
|
-
padding: 1rem;
|
|
2626
|
-
background: var(--light-bg-primary);
|
|
2627
|
-
border: 1px solid var(--light-border-primary);
|
|
2628
|
-
border-radius: 8px;
|
|
2629
|
-
overflow-x: auto;
|
|
2630
|
-
}
|
|
2631
|
-
:global(.mermaid) {
|
|
2632
|
-
display: flex;
|
|
2633
|
-
justify-content: center;
|
|
2634
|
-
}
|
|
2635
|
-
:global(.mermaid svg) {
|
|
2636
|
-
max-width: 100%;
|
|
2637
|
-
height: auto;
|
|
2638
|
-
}
|
|
2639
|
-
/* Mermaid error styling */
|
|
2640
|
-
:global(.mermaid-container .error) {
|
|
2641
|
-
color: #e07030;
|
|
2642
|
-
padding: 0.5rem;
|
|
2643
|
-
font-family: monospace;
|
|
2644
|
-
font-size: 0.85rem;
|
|
2645
|
-
}
|
|
2646
2557
|
/* Mode Transitions */
|
|
2647
2558
|
.editor-container {
|
|
2648
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
|
})
|
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)
|
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);
|
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.
|
|
3
|
+
"version": "0.3.2",
|
|
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",
|
|
@@ -86,8 +86,8 @@
|
|
|
86
86
|
"test:watch": "vitest watch"
|
|
87
87
|
},
|
|
88
88
|
"peerDependencies": {
|
|
89
|
-
"svelte": "^5.0.0",
|
|
90
89
|
"@sveltejs/kit": "^2.0.0",
|
|
90
|
+
"svelte": "^5.0.0",
|
|
91
91
|
"tailwindcss": "^3.4.0"
|
|
92
92
|
},
|
|
93
93
|
"devDependencies": {
|
|
@@ -114,17 +114,15 @@
|
|
|
114
114
|
"vitest": "^4.0.14"
|
|
115
115
|
},
|
|
116
116
|
"dependencies": {
|
|
117
|
-
"@groveengine/ui": "
|
|
117
|
+
"@groveengine/ui": "workspace:*",
|
|
118
118
|
"@types/dompurify": "^3.0.5",
|
|
119
119
|
"chart.js": "^4.5.1",
|
|
120
120
|
"clsx": "^2.1.1",
|
|
121
121
|
"dompurify": "^3.3.0",
|
|
122
122
|
"gray-matter": "^4.0.3",
|
|
123
|
-
"isomorphic-dompurify": "^2.33.0",
|
|
124
123
|
"lucide-svelte": "^0.554.0",
|
|
125
124
|
"marked": "^17.0.1",
|
|
126
|
-
"
|
|
127
|
-
"sonner": "^2.0.7",
|
|
125
|
+
"svelte-sonner": "^1.0.7",
|
|
128
126
|
"tailwind-merge": "^3.4.0"
|
|
129
127
|
}
|
|
130
128
|
}
|