@classic-homes/theme-docs 0.0.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/lib/components/CodeBlock.svelte +73 -0
- package/dist/lib/components/CodeBlock.svelte.d.ts +16 -0
- package/dist/lib/components/DocsCard.svelte +99 -0
- package/dist/lib/components/DocsCard.svelte.d.ts +11 -0
- package/dist/lib/components/DocsHub.svelte +83 -0
- package/dist/lib/components/DocsHub.svelte.d.ts +28 -0
- package/dist/lib/components/MarkdownPage.svelte +100 -0
- package/dist/lib/components/MarkdownPage.svelte.d.ts +33 -0
- package/dist/lib/components/MermaidDiagram.svelte +87 -0
- package/dist/lib/components/MermaidDiagram.svelte.d.ts +14 -0
- package/dist/lib/components/MermaidInit.svelte +100 -0
- package/dist/lib/components/MermaidInit.svelte.d.ts +9 -0
- package/dist/lib/components/TableOfContents.svelte +179 -0
- package/dist/lib/components/TableOfContents.svelte.d.ts +16 -0
- package/dist/lib/components/TocPanel.svelte +263 -0
- package/dist/lib/components/TocPanel.svelte.d.ts +20 -0
- package/dist/lib/highlighter/index.d.ts +15 -0
- package/dist/lib/highlighter/index.js +78 -0
- package/dist/lib/index.d.ts +29 -0
- package/dist/lib/index.js +31 -0
- package/dist/lib/parser/extensions.d.ts +75 -0
- package/dist/lib/parser/extensions.js +311 -0
- package/dist/lib/parser/index.d.ts +57 -0
- package/dist/lib/parser/index.js +150 -0
- package/dist/lib/styles/markdown.css +516 -0
- package/dist/lib/types/docs.d.ts +72 -0
- package/dist/lib/types/docs.js +4 -0
- package/dist/lib/types/frontmatter.d.ts +68 -0
- package/dist/lib/types/frontmatter.js +4 -0
- package/dist/lib/types/index.d.ts +5 -0
- package/dist/lib/types/index.js +4 -0
- package/dist/lib/utils.d.ts +6 -0
- package/dist/lib/utils.js +9 -0
- package/package.json +68 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* CodeBlock - Standalone syntax-highlighted code block
|
|
4
|
+
*
|
|
5
|
+
* For use outside of markdown content when you need
|
|
6
|
+
* to display code with highlighting directly.
|
|
7
|
+
*/
|
|
8
|
+
import { onMount } from 'svelte';
|
|
9
|
+
import { cn } from '../utils.js';
|
|
10
|
+
import { escapeHtml } from '../highlighter/index.js';
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
/** Code to highlight */
|
|
14
|
+
code: string;
|
|
15
|
+
/** Language for syntax highlighting */
|
|
16
|
+
language?: string;
|
|
17
|
+
/** Shiki theme */
|
|
18
|
+
theme?: string;
|
|
19
|
+
/** Custom class */
|
|
20
|
+
class?: string;
|
|
21
|
+
/** Optional filename to display */
|
|
22
|
+
filename?: string;
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let {
|
|
27
|
+
code,
|
|
28
|
+
language = 'text',
|
|
29
|
+
theme = 'github-dark',
|
|
30
|
+
class: className,
|
|
31
|
+
filename,
|
|
32
|
+
...restProps
|
|
33
|
+
}: Props = $props();
|
|
34
|
+
|
|
35
|
+
let highlighted = $state('');
|
|
36
|
+
let loading = $state(true);
|
|
37
|
+
|
|
38
|
+
onMount(async () => {
|
|
39
|
+
try {
|
|
40
|
+
const { highlightCode } = await import('../highlighter/index.js');
|
|
41
|
+
highlighted = await highlightCode(code, language, { theme });
|
|
42
|
+
loading = false;
|
|
43
|
+
} catch (err) {
|
|
44
|
+
// Log warning for debugging - falling back to plain code
|
|
45
|
+
console.warn(
|
|
46
|
+
'[docs] Failed to highlight code block, showing plain code:',
|
|
47
|
+
err instanceof Error ? err.message : err
|
|
48
|
+
);
|
|
49
|
+
highlighted = `<pre class="shiki bg-muted rounded-lg p-4 overflow-x-auto"><code>${escapeHtml(code)}</code></pre>`;
|
|
50
|
+
loading = false;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<div class={cn('code-block-wrapper', className)} {...restProps}>
|
|
56
|
+
{#if filename}
|
|
57
|
+
<div
|
|
58
|
+
class="code-block-filename bg-muted/50 px-4 py-2 text-sm text-muted-foreground border-b border-border rounded-t-lg font-mono"
|
|
59
|
+
>
|
|
60
|
+
{filename}
|
|
61
|
+
</div>
|
|
62
|
+
{/if}
|
|
63
|
+
|
|
64
|
+
{#if loading}
|
|
65
|
+
<div class="code-block-loading">
|
|
66
|
+
<pre class="bg-muted rounded-lg p-4 overflow-x-auto"><code class="text-sm font-mono"
|
|
67
|
+
>{code}</code
|
|
68
|
+
></pre>
|
|
69
|
+
</div>
|
|
70
|
+
{:else}
|
|
71
|
+
{@html highlighted}
|
|
72
|
+
{/if}
|
|
73
|
+
</div>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/** Code to highlight */
|
|
3
|
+
code: string;
|
|
4
|
+
/** Language for syntax highlighting */
|
|
5
|
+
language?: string;
|
|
6
|
+
/** Shiki theme */
|
|
7
|
+
theme?: string;
|
|
8
|
+
/** Custom class */
|
|
9
|
+
class?: string;
|
|
10
|
+
/** Optional filename to display */
|
|
11
|
+
filename?: string;
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
}
|
|
14
|
+
declare const CodeBlock: import("svelte").Component<Props, {}, "">;
|
|
15
|
+
type CodeBlock = ReturnType<typeof CodeBlock>;
|
|
16
|
+
export default CodeBlock;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* DocsCard - Individual documentation card for DocsHub
|
|
4
|
+
*
|
|
5
|
+
* Displays a single documentation item with title, description,
|
|
6
|
+
* optional icon, and optional badge.
|
|
7
|
+
*/
|
|
8
|
+
import { cn } from '../utils.js';
|
|
9
|
+
import { tv } from 'tailwind-variants';
|
|
10
|
+
import type { DocsItem } from '../types/docs.js';
|
|
11
|
+
|
|
12
|
+
const docsCardVariants = tv({
|
|
13
|
+
base: 'group relative flex flex-col rounded-lg border border-border bg-card p-6 transition-all hover:border-primary hover:shadow-md',
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
/** Documentation item to display */
|
|
18
|
+
item: DocsItem;
|
|
19
|
+
/** Custom class */
|
|
20
|
+
class?: string;
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let { item, class: className, ...restProps }: Props = $props();
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<a
|
|
28
|
+
href={item.href}
|
|
29
|
+
class={cn(docsCardVariants(), className)}
|
|
30
|
+
target={item.external ? '_blank' : undefined}
|
|
31
|
+
rel={item.external ? 'noopener noreferrer' : undefined}
|
|
32
|
+
aria-label={item.external ? `${item.title} (opens in new tab)` : undefined}
|
|
33
|
+
{...restProps}
|
|
34
|
+
>
|
|
35
|
+
{#if item.badge}
|
|
36
|
+
<span
|
|
37
|
+
class="absolute -top-2 -right-2 rounded-full px-2 py-0.5 text-xs font-medium {item.badge ===
|
|
38
|
+
'new'
|
|
39
|
+
? 'bg-primary text-primary-foreground'
|
|
40
|
+
: item.badge === 'updated'
|
|
41
|
+
? 'bg-blue-600 text-white'
|
|
42
|
+
: item.badge === 'deprecated'
|
|
43
|
+
? 'bg-destructive text-destructive-foreground'
|
|
44
|
+
: 'bg-muted text-foreground'}"
|
|
45
|
+
>
|
|
46
|
+
{item.badge}
|
|
47
|
+
</span>
|
|
48
|
+
{/if}
|
|
49
|
+
|
|
50
|
+
<div class="flex items-start gap-4">
|
|
51
|
+
{#if item.icon}
|
|
52
|
+
<div
|
|
53
|
+
class="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary"
|
|
54
|
+
>
|
|
55
|
+
<span class="text-lg">{item.icon}</span>
|
|
56
|
+
</div>
|
|
57
|
+
{/if}
|
|
58
|
+
|
|
59
|
+
<div class="flex-1 min-w-0">
|
|
60
|
+
<h3
|
|
61
|
+
class="font-semibold text-foreground group-hover:text-primary transition-colors flex items-center gap-2"
|
|
62
|
+
>
|
|
63
|
+
{item.title}
|
|
64
|
+
{#if item.external}
|
|
65
|
+
<svg
|
|
66
|
+
class="h-4 w-4 text-muted-foreground"
|
|
67
|
+
fill="none"
|
|
68
|
+
stroke="currentColor"
|
|
69
|
+
viewBox="0 0 24 24"
|
|
70
|
+
aria-hidden="true"
|
|
71
|
+
>
|
|
72
|
+
<path
|
|
73
|
+
stroke-linecap="round"
|
|
74
|
+
stroke-linejoin="round"
|
|
75
|
+
stroke-width="2"
|
|
76
|
+
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
|
77
|
+
/>
|
|
78
|
+
</svg>
|
|
79
|
+
{/if}
|
|
80
|
+
</h3>
|
|
81
|
+
|
|
82
|
+
{#if item.description}
|
|
83
|
+
<p class="mt-1 text-sm text-muted-foreground line-clamp-2">
|
|
84
|
+
{item.description}
|
|
85
|
+
</p>
|
|
86
|
+
{/if}
|
|
87
|
+
|
|
88
|
+
{#if item.tags && item.tags.length > 0}
|
|
89
|
+
<div class="mt-3 flex flex-wrap gap-1">
|
|
90
|
+
{#each item.tags as tag}
|
|
91
|
+
<span class="rounded bg-muted px-2 py-0.5 text-xs text-muted-foreground">
|
|
92
|
+
{tag}
|
|
93
|
+
</span>
|
|
94
|
+
{/each}
|
|
95
|
+
</div>
|
|
96
|
+
{/if}
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</a>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DocsItem } from '../types/docs.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
/** Documentation item to display */
|
|
4
|
+
item: DocsItem;
|
|
5
|
+
/** Custom class */
|
|
6
|
+
class?: string;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
}
|
|
9
|
+
declare const DocsCard: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type DocsCard = ReturnType<typeof DocsCard>;
|
|
11
|
+
export default DocsCard;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* DocsHub - Documentation navigation and index component
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Card-based layout for documentation entries
|
|
7
|
+
* - Category/section grouping
|
|
8
|
+
* - Responsive grid layout
|
|
9
|
+
*/
|
|
10
|
+
import type { Snippet } from 'svelte';
|
|
11
|
+
import { cn } from '../utils.js';
|
|
12
|
+
import { tv, type VariantProps } from 'tailwind-variants';
|
|
13
|
+
import DocsCard from './DocsCard.svelte';
|
|
14
|
+
import type { DocsHubConfig, DocsSection, DocsItem } from '../types/docs.js';
|
|
15
|
+
|
|
16
|
+
const docsHubVariants = tv({
|
|
17
|
+
slots: {
|
|
18
|
+
container: '',
|
|
19
|
+
sectionContainer: 'mb-8',
|
|
20
|
+
sectionTitle: 'text-2xl font-bold text-foreground mb-2',
|
|
21
|
+
sectionDescription: 'text-muted-foreground mb-6',
|
|
22
|
+
grid: 'grid gap-4',
|
|
23
|
+
},
|
|
24
|
+
variants: {
|
|
25
|
+
columns: {
|
|
26
|
+
1: { grid: 'grid-cols-1' },
|
|
27
|
+
2: { grid: 'grid-cols-1 md:grid-cols-2' },
|
|
28
|
+
3: { grid: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3' },
|
|
29
|
+
4: { grid: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-4' },
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
defaultVariants: {
|
|
33
|
+
columns: 3,
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
type DocsHubVariants = VariantProps<typeof docsHubVariants>;
|
|
38
|
+
|
|
39
|
+
interface Props {
|
|
40
|
+
/** Documentation configuration */
|
|
41
|
+
config: DocsHubConfig;
|
|
42
|
+
/** Number of columns in the grid */
|
|
43
|
+
columns?: DocsHubVariants['columns'];
|
|
44
|
+
/** Custom class for the container */
|
|
45
|
+
class?: string;
|
|
46
|
+
/** Optional custom card renderer */
|
|
47
|
+
card?: Snippet<[{ item: DocsItem; section: DocsSection }]>;
|
|
48
|
+
/** Optional header slot */
|
|
49
|
+
header?: Snippet;
|
|
50
|
+
[key: string]: unknown;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let { config, columns = 3, class: className, card, header, ...restProps }: Props = $props();
|
|
54
|
+
|
|
55
|
+
const styles = $derived(docsHubVariants({ columns }));
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<div class={cn(styles.container(), className)} {...restProps}>
|
|
59
|
+
{#if header}
|
|
60
|
+
{@render header()}
|
|
61
|
+
{/if}
|
|
62
|
+
|
|
63
|
+
{#each config.sections as section}
|
|
64
|
+
<div class={styles.sectionContainer()}>
|
|
65
|
+
{#if section.title}
|
|
66
|
+
<h2 class={styles.sectionTitle()}>{section.title}</h2>
|
|
67
|
+
{/if}
|
|
68
|
+
{#if section.description}
|
|
69
|
+
<p class={styles.sectionDescription()}>{section.description}</p>
|
|
70
|
+
{/if}
|
|
71
|
+
|
|
72
|
+
<div class={styles.grid()}>
|
|
73
|
+
{#each section.items as item}
|
|
74
|
+
{#if card}
|
|
75
|
+
{@render card({ item, section })}
|
|
76
|
+
{:else}
|
|
77
|
+
<DocsCard {item} />
|
|
78
|
+
{/if}
|
|
79
|
+
{/each}
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
{/each}
|
|
83
|
+
</div>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DocsHub - Documentation navigation and index component
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Card-based layout for documentation entries
|
|
6
|
+
* - Category/section grouping
|
|
7
|
+
* - Responsive grid layout
|
|
8
|
+
*/
|
|
9
|
+
import type { Snippet } from 'svelte';
|
|
10
|
+
import type { DocsHubConfig, DocsSection, DocsItem } from '../types/docs.js';
|
|
11
|
+
declare const DocsHub: import("svelte").Component<{
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
/** Documentation configuration */
|
|
14
|
+
config: DocsHubConfig;
|
|
15
|
+
/** Number of columns in the grid */
|
|
16
|
+
columns?: 1 | 2 | 3 | 4 | undefined;
|
|
17
|
+
/** Custom class for the container */
|
|
18
|
+
class?: string;
|
|
19
|
+
/** Optional custom card renderer */
|
|
20
|
+
card?: Snippet<[{
|
|
21
|
+
item: DocsItem;
|
|
22
|
+
section: DocsSection;
|
|
23
|
+
}]>;
|
|
24
|
+
/** Optional header slot */
|
|
25
|
+
header?: Snippet;
|
|
26
|
+
}, {}, "">;
|
|
27
|
+
type DocsHub = ReturnType<typeof DocsHub>;
|
|
28
|
+
export default DocsHub;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* MarkdownPage - Core markdown renderer with frontmatter and syntax highlighting
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - YAML frontmatter parsing with gray-matter
|
|
7
|
+
* - Shiki syntax highlighting for code blocks
|
|
8
|
+
* - Loading and error states
|
|
9
|
+
* - Configurable styling
|
|
10
|
+
* - Optional Table of Contents generation
|
|
11
|
+
*/
|
|
12
|
+
import type { Snippet } from 'svelte';
|
|
13
|
+
import { cn } from '../utils.js';
|
|
14
|
+
import type { FrontmatterData } from '../types/index.js';
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
/** Raw markdown content (with optional frontmatter) */
|
|
18
|
+
content: string;
|
|
19
|
+
/** Custom class for the content container */
|
|
20
|
+
class?: string;
|
|
21
|
+
/** Custom header snippet (receives frontmatter data) */
|
|
22
|
+
header?: Snippet<[FrontmatterData | null]>;
|
|
23
|
+
/** Custom error display snippet */
|
|
24
|
+
errorSlot?: Snippet<[string]>;
|
|
25
|
+
/** Shiki theme to use */
|
|
26
|
+
theme?: 'github-dark' | 'github-light' | 'one-dark-pro' | string;
|
|
27
|
+
/** Callback when content is parsed */
|
|
28
|
+
onParsed?: (data: { frontmatter: FrontmatterData | null; html: string }) => void;
|
|
29
|
+
[key: string]: unknown;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let {
|
|
33
|
+
content,
|
|
34
|
+
class: className,
|
|
35
|
+
header,
|
|
36
|
+
errorSlot,
|
|
37
|
+
theme = 'github-dark',
|
|
38
|
+
onParsed,
|
|
39
|
+
...restProps
|
|
40
|
+
}: Props = $props();
|
|
41
|
+
|
|
42
|
+
let htmlContent = $state('');
|
|
43
|
+
let frontmatter = $state<FrontmatterData | null>(null);
|
|
44
|
+
let loading = $state(true);
|
|
45
|
+
let error = $state('');
|
|
46
|
+
|
|
47
|
+
// Use $effect to react to content changes (enables proper navigation)
|
|
48
|
+
$effect(() => {
|
|
49
|
+
// Track content as a dependency
|
|
50
|
+
const currentContent = content;
|
|
51
|
+
|
|
52
|
+
// Reset state for new content
|
|
53
|
+
loading = true;
|
|
54
|
+
error = '';
|
|
55
|
+
|
|
56
|
+
(async () => {
|
|
57
|
+
try {
|
|
58
|
+
const { parseMarkdown } = await import('../parser/index.js');
|
|
59
|
+
const result = await parseMarkdown(currentContent, { theme });
|
|
60
|
+
|
|
61
|
+
frontmatter = result.frontmatter;
|
|
62
|
+
htmlContent = result.html;
|
|
63
|
+
loading = false;
|
|
64
|
+
|
|
65
|
+
onParsed?.({ frontmatter, html: htmlContent });
|
|
66
|
+
} catch (err: unknown) {
|
|
67
|
+
error = err instanceof Error ? err.message : 'Failed to parse markdown';
|
|
68
|
+
loading = false;
|
|
69
|
+
}
|
|
70
|
+
})();
|
|
71
|
+
});
|
|
72
|
+
</script>
|
|
73
|
+
|
|
74
|
+
{#if header && frontmatter}
|
|
75
|
+
{@render header(frontmatter)}
|
|
76
|
+
{/if}
|
|
77
|
+
|
|
78
|
+
{#if error}
|
|
79
|
+
{#if errorSlot}
|
|
80
|
+
{@render errorSlot(error)}
|
|
81
|
+
{:else}
|
|
82
|
+
<div class="rounded-lg border border-destructive bg-destructive/10 p-4 text-destructive">
|
|
83
|
+
{error}
|
|
84
|
+
</div>
|
|
85
|
+
{/if}
|
|
86
|
+
{/if}
|
|
87
|
+
|
|
88
|
+
{#if loading}
|
|
89
|
+
<div class="flex items-center justify-center py-12">
|
|
90
|
+
<div
|
|
91
|
+
class="h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent"
|
|
92
|
+
></div>
|
|
93
|
+
</div>
|
|
94
|
+
{/if}
|
|
95
|
+
|
|
96
|
+
{#if !loading && !error}
|
|
97
|
+
<div class={cn('markdown-content', className)} {...restProps}>
|
|
98
|
+
{@html htmlContent}
|
|
99
|
+
</div>
|
|
100
|
+
{/if}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MarkdownPage - Core markdown renderer with frontmatter and syntax highlighting
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - YAML frontmatter parsing with gray-matter
|
|
6
|
+
* - Shiki syntax highlighting for code blocks
|
|
7
|
+
* - Loading and error states
|
|
8
|
+
* - Configurable styling
|
|
9
|
+
* - Optional Table of Contents generation
|
|
10
|
+
*/
|
|
11
|
+
import type { Snippet } from 'svelte';
|
|
12
|
+
import type { FrontmatterData } from '../types/index.js';
|
|
13
|
+
interface Props {
|
|
14
|
+
/** Raw markdown content (with optional frontmatter) */
|
|
15
|
+
content: string;
|
|
16
|
+
/** Custom class for the content container */
|
|
17
|
+
class?: string;
|
|
18
|
+
/** Custom header snippet (receives frontmatter data) */
|
|
19
|
+
header?: Snippet<[FrontmatterData | null]>;
|
|
20
|
+
/** Custom error display snippet */
|
|
21
|
+
errorSlot?: Snippet<[string]>;
|
|
22
|
+
/** Shiki theme to use */
|
|
23
|
+
theme?: 'github-dark' | 'github-light' | 'one-dark-pro' | string;
|
|
24
|
+
/** Callback when content is parsed */
|
|
25
|
+
onParsed?: (data: {
|
|
26
|
+
frontmatter: FrontmatterData | null;
|
|
27
|
+
html: string;
|
|
28
|
+
}) => void;
|
|
29
|
+
[key: string]: unknown;
|
|
30
|
+
}
|
|
31
|
+
declare const MarkdownPage: import("svelte").Component<Props, {}, "">;
|
|
32
|
+
type MarkdownPage = ReturnType<typeof MarkdownPage>;
|
|
33
|
+
export default MarkdownPage;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* MermaidDiagram - Client-side Mermaid diagram renderer
|
|
4
|
+
*
|
|
5
|
+
* Renders Mermaid diagrams from code. Can be used standalone or
|
|
6
|
+
* automatically initialized on markdown-rendered mermaid blocks.
|
|
7
|
+
*
|
|
8
|
+
* Requires mermaid to be installed in the consuming application:
|
|
9
|
+
* npm install mermaid
|
|
10
|
+
*/
|
|
11
|
+
import { onMount } from 'svelte';
|
|
12
|
+
import { cn } from '../utils.js';
|
|
13
|
+
|
|
14
|
+
interface Props {
|
|
15
|
+
/** Mermaid diagram code */
|
|
16
|
+
code: string;
|
|
17
|
+
/** Custom class */
|
|
18
|
+
class?: string;
|
|
19
|
+
/** Unique ID for the diagram (auto-generated if not provided) */
|
|
20
|
+
id?: string;
|
|
21
|
+
/** Mermaid theme (default: 'default') */
|
|
22
|
+
theme?: 'default' | 'dark' | 'forest' | 'neutral';
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let {
|
|
27
|
+
code,
|
|
28
|
+
class: className,
|
|
29
|
+
id = `mermaid-${Math.random().toString(36).slice(2, 9)}`,
|
|
30
|
+
theme = 'default',
|
|
31
|
+
...restProps
|
|
32
|
+
}: Props = $props();
|
|
33
|
+
|
|
34
|
+
let containerRef = $state<HTMLDivElement | null>(null);
|
|
35
|
+
let rendered = $state(false);
|
|
36
|
+
let error = $state<string | null>(null);
|
|
37
|
+
|
|
38
|
+
onMount(async () => {
|
|
39
|
+
if (!containerRef) return;
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
// Dynamically import mermaid to avoid SSR issues
|
|
43
|
+
const mermaid = await import('mermaid');
|
|
44
|
+
|
|
45
|
+
// Initialize mermaid with theme
|
|
46
|
+
mermaid.default.initialize({
|
|
47
|
+
startOnLoad: false,
|
|
48
|
+
theme: theme,
|
|
49
|
+
securityLevel: 'strict',
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Render the diagram
|
|
53
|
+
const { svg } = await mermaid.default.render(id, code);
|
|
54
|
+
containerRef.innerHTML = svg;
|
|
55
|
+
rendered = true;
|
|
56
|
+
} catch (err) {
|
|
57
|
+
console.warn(
|
|
58
|
+
'[docs] Failed to render Mermaid diagram:',
|
|
59
|
+
err instanceof Error ? err.message : err
|
|
60
|
+
);
|
|
61
|
+
error = err instanceof Error ? err.message : 'Failed to render diagram';
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<div
|
|
67
|
+
bind:this={containerRef}
|
|
68
|
+
class={cn(
|
|
69
|
+
'mermaid-container',
|
|
70
|
+
!rendered && !error && 'mermaid-loading',
|
|
71
|
+
error && 'mermaid-error',
|
|
72
|
+
className
|
|
73
|
+
)}
|
|
74
|
+
{...restProps}
|
|
75
|
+
>
|
|
76
|
+
{#if !rendered && !error}
|
|
77
|
+
<!-- Show code as fallback while loading -->
|
|
78
|
+
<pre class="mermaid-fallback"><code>{code}</code></pre>
|
|
79
|
+
{/if}
|
|
80
|
+
{#if error}
|
|
81
|
+
<div class="mermaid-error-message">
|
|
82
|
+
<p class="mermaid-error-title">Failed to render diagram</p>
|
|
83
|
+
<pre class="mermaid-error-code"><code>{code}</code></pre>
|
|
84
|
+
<p class="mermaid-error-details">{error}</p>
|
|
85
|
+
</div>
|
|
86
|
+
{/if}
|
|
87
|
+
</div>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/** Mermaid diagram code */
|
|
3
|
+
code: string;
|
|
4
|
+
/** Custom class */
|
|
5
|
+
class?: string;
|
|
6
|
+
/** Unique ID for the diagram (auto-generated if not provided) */
|
|
7
|
+
id?: string;
|
|
8
|
+
/** Mermaid theme (default: 'default') */
|
|
9
|
+
theme?: 'default' | 'dark' | 'forest' | 'neutral';
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
declare const MermaidDiagram: import("svelte").Component<Props, {}, "">;
|
|
13
|
+
type MermaidDiagram = ReturnType<typeof MermaidDiagram>;
|
|
14
|
+
export default MermaidDiagram;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* MermaidInit - Auto-initialize all Mermaid diagrams on the page
|
|
4
|
+
*
|
|
5
|
+
* Place this component once in your layout or page to automatically
|
|
6
|
+
* render all mermaid diagrams from markdown content.
|
|
7
|
+
*
|
|
8
|
+
* Requires mermaid to be installed in the consuming application:
|
|
9
|
+
* npm install mermaid
|
|
10
|
+
*/
|
|
11
|
+
import { onMount } from 'svelte';
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
/** Mermaid theme (default: 'default') */
|
|
15
|
+
theme?: 'default' | 'dark' | 'forest' | 'neutral';
|
|
16
|
+
/** Whether to watch for new diagrams via MutationObserver */
|
|
17
|
+
watch?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let { theme = 'default', watch = false }: Props = $props();
|
|
21
|
+
|
|
22
|
+
let initialized = $state(false);
|
|
23
|
+
|
|
24
|
+
async function renderDiagrams() {
|
|
25
|
+
try {
|
|
26
|
+
const mermaid = await import('mermaid');
|
|
27
|
+
|
|
28
|
+
// Initialize mermaid
|
|
29
|
+
mermaid.default.initialize({
|
|
30
|
+
startOnLoad: false,
|
|
31
|
+
theme: theme,
|
|
32
|
+
securityLevel: 'strict',
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Find all mermaid diagram placeholders
|
|
36
|
+
const diagrams = document.querySelectorAll('.mermaid-diagram[data-mermaid]');
|
|
37
|
+
|
|
38
|
+
for (const diagram of diagrams) {
|
|
39
|
+
const code = diagram.getAttribute('data-mermaid');
|
|
40
|
+
if (!code || diagram.classList.contains('mermaid-rendered')) continue;
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const id = `mermaid-${Math.random().toString(36).slice(2, 9)}`;
|
|
44
|
+
const { svg } = await mermaid.default.render(id, code);
|
|
45
|
+
|
|
46
|
+
// Replace the placeholder with the rendered SVG
|
|
47
|
+
diagram.innerHTML = svg;
|
|
48
|
+
diagram.classList.add('mermaid-rendered');
|
|
49
|
+
diagram.removeAttribute('data-mermaid');
|
|
50
|
+
} catch (err) {
|
|
51
|
+
console.warn(
|
|
52
|
+
'[docs] Failed to render Mermaid diagram:',
|
|
53
|
+
err instanceof Error ? err.message : err
|
|
54
|
+
);
|
|
55
|
+
diagram.classList.add('mermaid-error');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
initialized = true;
|
|
60
|
+
} catch (err) {
|
|
61
|
+
// Mermaid not installed - that's okay, diagrams will show as code
|
|
62
|
+
console.warn('[docs] Mermaid library not available. Install with: npm install mermaid');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
onMount(() => {
|
|
67
|
+
renderDiagrams();
|
|
68
|
+
|
|
69
|
+
if (watch) {
|
|
70
|
+
// Watch for new diagrams being added to the DOM
|
|
71
|
+
const observer = new MutationObserver((mutations) => {
|
|
72
|
+
for (const mutation of mutations) {
|
|
73
|
+
if (mutation.type === 'childList') {
|
|
74
|
+
const hasNewDiagrams = Array.from(mutation.addedNodes).some(
|
|
75
|
+
(node) =>
|
|
76
|
+
node instanceof HTMLElement &&
|
|
77
|
+
(node.classList?.contains('mermaid-diagram') ||
|
|
78
|
+
node.querySelector?.('.mermaid-diagram[data-mermaid]'))
|
|
79
|
+
);
|
|
80
|
+
if (hasNewDiagrams) {
|
|
81
|
+
renderDiagrams();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
observer.observe(document.body, {
|
|
88
|
+
childList: true,
|
|
89
|
+
subtree: true,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
return () => observer.disconnect();
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
</script>
|
|
96
|
+
|
|
97
|
+
<!-- This component has no visual output, it just initializes mermaid -->
|
|
98
|
+
{#if !initialized}
|
|
99
|
+
<!-- Hidden slot for loading state if needed -->
|
|
100
|
+
{/if}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/** Mermaid theme (default: 'default') */
|
|
3
|
+
theme?: 'default' | 'dark' | 'forest' | 'neutral';
|
|
4
|
+
/** Whether to watch for new diagrams via MutationObserver */
|
|
5
|
+
watch?: boolean;
|
|
6
|
+
}
|
|
7
|
+
declare const MermaidInit: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type MermaidInit = ReturnType<typeof MermaidInit>;
|
|
9
|
+
export default MermaidInit;
|