@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,179 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* TableOfContents - Auto-generated table of contents from headings
|
|
4
|
+
*
|
|
5
|
+
* Extracts headings from HTML content and generates a navigable
|
|
6
|
+
* table of contents with smooth scrolling. Styled after Docusaurus TOC.
|
|
7
|
+
*/
|
|
8
|
+
import { onMount } from 'svelte';
|
|
9
|
+
import { cn } from '../utils.js';
|
|
10
|
+
import type { TocEntry } from '../types/docs.js';
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
/** HTML content to extract headings from */
|
|
14
|
+
html: string;
|
|
15
|
+
/** Maximum heading depth to include (default: 3) */
|
|
16
|
+
maxDepth?: number;
|
|
17
|
+
/** Custom class */
|
|
18
|
+
class?: string;
|
|
19
|
+
/** Title for the TOC section */
|
|
20
|
+
title?: string;
|
|
21
|
+
/** Make the TOC sticky */
|
|
22
|
+
sticky?: boolean;
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let {
|
|
27
|
+
html,
|
|
28
|
+
maxDepth = 3,
|
|
29
|
+
class: className,
|
|
30
|
+
title = 'On this page',
|
|
31
|
+
sticky = false,
|
|
32
|
+
...restProps
|
|
33
|
+
}: Props = $props();
|
|
34
|
+
|
|
35
|
+
let toc = $state<TocEntry[]>([]);
|
|
36
|
+
let activeId = $state<string | null>(null);
|
|
37
|
+
let ignoreObserver = false;
|
|
38
|
+
|
|
39
|
+
onMount(async () => {
|
|
40
|
+
const { extractToc } = await import('../parser/index.js');
|
|
41
|
+
toc = extractToc(html, maxDepth);
|
|
42
|
+
|
|
43
|
+
// Set up intersection observer to track active heading
|
|
44
|
+
// 100ms delay ensures DOM is fully rendered after markdown parsing completes
|
|
45
|
+
// This is necessary because markdown content is rendered asynchronously
|
|
46
|
+
setTimeout(() => {
|
|
47
|
+
const headings = document.querySelectorAll('h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]');
|
|
48
|
+
if (headings.length === 0) return;
|
|
49
|
+
|
|
50
|
+
// Set initial active heading based on scroll position
|
|
51
|
+
const firstVisibleHeading = Array.from(headings).find((heading) => {
|
|
52
|
+
const rect = heading.getBoundingClientRect();
|
|
53
|
+
return rect.top >= 0 && rect.top < window.innerHeight / 2;
|
|
54
|
+
});
|
|
55
|
+
if (firstVisibleHeading) {
|
|
56
|
+
activeId = firstVisibleHeading.id;
|
|
57
|
+
} else if (headings[0]) {
|
|
58
|
+
activeId = headings[0].id;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const observer = new IntersectionObserver(
|
|
62
|
+
(entries) => {
|
|
63
|
+
// Skip if we recently clicked a TOC link
|
|
64
|
+
if (ignoreObserver) return;
|
|
65
|
+
|
|
66
|
+
// Find the first intersecting entry
|
|
67
|
+
const intersecting = entries.filter((e) => e.isIntersecting);
|
|
68
|
+
if (intersecting.length > 0) {
|
|
69
|
+
// Sort by position and take the topmost
|
|
70
|
+
const topmost = intersecting.sort(
|
|
71
|
+
(a, b) => a.boundingClientRect.top - b.boundingClientRect.top
|
|
72
|
+
)[0];
|
|
73
|
+
activeId = topmost.target.id;
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
// rootMargin: top right bottom left
|
|
78
|
+
// -80px top: accounts for fixed header height (~80px)
|
|
79
|
+
// -70% bottom: heading becomes "active" when it enters the top 30% of viewport
|
|
80
|
+
// This creates a natural reading experience where the heading is highlighted
|
|
81
|
+
// before the user has scrolled past it
|
|
82
|
+
rootMargin: '-80px 0px -70% 0px',
|
|
83
|
+
threshold: 0,
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
headings.forEach((heading) => observer.observe(heading));
|
|
88
|
+
|
|
89
|
+
return () => {
|
|
90
|
+
headings.forEach((heading) => observer.unobserve(heading));
|
|
91
|
+
};
|
|
92
|
+
}, 100);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
function getIndentClass(level: number): string {
|
|
96
|
+
// More pronounced indentation for hierarchy
|
|
97
|
+
const indents: Record<number, string> = {
|
|
98
|
+
1: '',
|
|
99
|
+
2: '', // h2 is typically top-level in TOC (no indent)
|
|
100
|
+
3: 'pl-3', // h3 indented
|
|
101
|
+
4: 'pl-6', // h4 further indented
|
|
102
|
+
5: 'pl-9',
|
|
103
|
+
6: 'pl-12',
|
|
104
|
+
};
|
|
105
|
+
return indents[level] || '';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function getLevelStyles(level: number, isActive: boolean): string {
|
|
109
|
+
const baseStyles = 'block py-1 rounded-sm transition-colors';
|
|
110
|
+
|
|
111
|
+
if (isActive) {
|
|
112
|
+
return cn(baseStyles, 'text-primary font-medium');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Top-level items (h2) are more prominent
|
|
116
|
+
if (level <= 2) {
|
|
117
|
+
return cn(baseStyles, 'text-foreground/80 hover:text-foreground hover:bg-muted/50');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Deeper levels are more subdued
|
|
121
|
+
return cn(
|
|
122
|
+
baseStyles,
|
|
123
|
+
'text-muted-foreground text-[12px] hover:text-foreground hover:bg-muted/50'
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function handleClick(event: MouseEvent, id: string) {
|
|
128
|
+
event.preventDefault();
|
|
129
|
+
|
|
130
|
+
// Immediately update active state on click
|
|
131
|
+
activeId = id;
|
|
132
|
+
|
|
133
|
+
// Temporarily ignore intersection observer to prevent it from
|
|
134
|
+
// overwriting our selection during scroll animation
|
|
135
|
+
ignoreObserver = true;
|
|
136
|
+
setTimeout(() => {
|
|
137
|
+
ignoreObserver = false;
|
|
138
|
+
}, 1000); // Longer timeout to cover scroll animation
|
|
139
|
+
|
|
140
|
+
// Find the heading element and scroll to it with offset
|
|
141
|
+
const element = document.getElementById(id);
|
|
142
|
+
if (element) {
|
|
143
|
+
const headerOffset = 100; // Account for fixed header + some padding
|
|
144
|
+
const elementPosition = element.getBoundingClientRect().top;
|
|
145
|
+
const offsetPosition = elementPosition + window.scrollY - headerOffset;
|
|
146
|
+
|
|
147
|
+
window.scrollTo({
|
|
148
|
+
top: offsetPosition,
|
|
149
|
+
behavior: 'smooth',
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
</script>
|
|
154
|
+
|
|
155
|
+
{#if toc.length > 0}
|
|
156
|
+
<nav class={cn('text-sm', sticky && 'sticky top-4', className)} {...restProps}>
|
|
157
|
+
{#if title}
|
|
158
|
+
<h4 class="text-xs font-medium text-muted-foreground uppercase tracking-wider mb-2">
|
|
159
|
+
{title}
|
|
160
|
+
</h4>
|
|
161
|
+
{/if}
|
|
162
|
+
|
|
163
|
+
<ul class="space-y-0.5">
|
|
164
|
+
{#each toc as entry}
|
|
165
|
+
{@const isActive = activeId === entry.id}
|
|
166
|
+
<li class={getIndentClass(entry.level)}>
|
|
167
|
+
<a
|
|
168
|
+
href="#{entry.id}"
|
|
169
|
+
onclick={(e) => handleClick(e, entry.id)}
|
|
170
|
+
class={getLevelStyles(entry.level, isActive)}
|
|
171
|
+
aria-current={isActive ? 'true' : undefined}
|
|
172
|
+
>
|
|
173
|
+
{entry.text}
|
|
174
|
+
</a>
|
|
175
|
+
</li>
|
|
176
|
+
{/each}
|
|
177
|
+
</ul>
|
|
178
|
+
</nav>
|
|
179
|
+
{/if}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/** HTML content to extract headings from */
|
|
3
|
+
html: string;
|
|
4
|
+
/** Maximum heading depth to include (default: 3) */
|
|
5
|
+
maxDepth?: number;
|
|
6
|
+
/** Custom class */
|
|
7
|
+
class?: string;
|
|
8
|
+
/** Title for the TOC section */
|
|
9
|
+
title?: string;
|
|
10
|
+
/** Make the TOC sticky */
|
|
11
|
+
sticky?: boolean;
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
}
|
|
14
|
+
declare const TableOfContents: import("svelte").Component<Props, {}, "">;
|
|
15
|
+
type TableOfContents = ReturnType<typeof TableOfContents>;
|
|
16
|
+
export default TableOfContents;
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* TocPanel - Responsive table of contents panel
|
|
4
|
+
*
|
|
5
|
+
* Provides a mobile flyout panel and optional desktop sidebar for the
|
|
6
|
+
* TableOfContents component. Features a paper folder tab trigger on mobile
|
|
7
|
+
* that slides out a panel from the right side.
|
|
8
|
+
*/
|
|
9
|
+
import { onMount } from 'svelte';
|
|
10
|
+
import { fly, fade } from 'svelte/transition';
|
|
11
|
+
import { cn } from '../utils.js';
|
|
12
|
+
import TableOfContents from './TableOfContents.svelte';
|
|
13
|
+
|
|
14
|
+
interface Props {
|
|
15
|
+
/** HTML content to extract headings from */
|
|
16
|
+
html: string;
|
|
17
|
+
/** Maximum heading depth to include (default: 3) */
|
|
18
|
+
maxDepth?: number;
|
|
19
|
+
/** Breakpoint for desktop view (default: 'xl') */
|
|
20
|
+
breakpoint?: 'lg' | 'xl' | '2xl';
|
|
21
|
+
/** Show desktop sidebar (default: true) */
|
|
22
|
+
showDesktop?: boolean;
|
|
23
|
+
/** Custom class for mobile panel */
|
|
24
|
+
mobileClass?: string;
|
|
25
|
+
/** Custom class for desktop sidebar */
|
|
26
|
+
desktopClass?: string;
|
|
27
|
+
/** Title for the TOC section */
|
|
28
|
+
title?: string;
|
|
29
|
+
[key: string]: unknown;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let {
|
|
33
|
+
html,
|
|
34
|
+
maxDepth = 3,
|
|
35
|
+
breakpoint = 'lg',
|
|
36
|
+
showDesktop = true,
|
|
37
|
+
mobileClass,
|
|
38
|
+
desktopClass,
|
|
39
|
+
title = 'On this page',
|
|
40
|
+
...restProps
|
|
41
|
+
}: Props = $props();
|
|
42
|
+
|
|
43
|
+
let open = $state(false);
|
|
44
|
+
let closeButtonRef = $state<HTMLButtonElement | null>(null);
|
|
45
|
+
let previousActiveElement: HTMLElement | null = null;
|
|
46
|
+
|
|
47
|
+
function openPanel() {
|
|
48
|
+
// Store the currently focused element to restore focus when closing
|
|
49
|
+
previousActiveElement = document.activeElement as HTMLElement;
|
|
50
|
+
open = true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function close() {
|
|
54
|
+
open = false;
|
|
55
|
+
// Restore focus to the element that opened the panel
|
|
56
|
+
previousActiveElement?.focus();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
60
|
+
if (event.key === 'Escape' && open) {
|
|
61
|
+
event.preventDefault();
|
|
62
|
+
close();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Focus the close button when panel opens
|
|
67
|
+
$effect(() => {
|
|
68
|
+
if (open && closeButtonRef) {
|
|
69
|
+
// Small delay to ensure the panel is rendered
|
|
70
|
+
setTimeout(() => closeButtonRef?.focus(), 0);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
onMount(() => {
|
|
75
|
+
// Add global escape key listener
|
|
76
|
+
document.addEventListener('keydown', handleKeydown);
|
|
77
|
+
return () => document.removeEventListener('keydown', handleKeydown);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const breakpointHide = $derived(
|
|
81
|
+
{
|
|
82
|
+
lg: 'lg:hidden',
|
|
83
|
+
xl: 'xl:hidden',
|
|
84
|
+
'2xl': '2xl:hidden',
|
|
85
|
+
}[breakpoint]
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const breakpointShow = $derived(
|
|
89
|
+
{
|
|
90
|
+
lg: 'hidden lg:block',
|
|
91
|
+
xl: 'hidden xl:block',
|
|
92
|
+
'2xl': 'hidden 2xl:block',
|
|
93
|
+
}[breakpoint]
|
|
94
|
+
);
|
|
95
|
+
</script>
|
|
96
|
+
|
|
97
|
+
<!-- Mobile TOC Tab Trigger - Paper folder tab style -->
|
|
98
|
+
{#if html && !open}
|
|
99
|
+
<button
|
|
100
|
+
class={cn(breakpointHide, 'toc-tab-trigger')}
|
|
101
|
+
onclick={openPanel}
|
|
102
|
+
aria-label="Open table of contents"
|
|
103
|
+
>
|
|
104
|
+
<span class="toc-tab-text">
|
|
105
|
+
{title}
|
|
106
|
+
</span>
|
|
107
|
+
</button>
|
|
108
|
+
{/if}
|
|
109
|
+
|
|
110
|
+
<!-- Mobile TOC Flyout Panel -->
|
|
111
|
+
{#if html && open}
|
|
112
|
+
<!-- Backdrop -->
|
|
113
|
+
<button
|
|
114
|
+
class={cn(breakpointHide, 'toc-backdrop')}
|
|
115
|
+
onclick={close}
|
|
116
|
+
aria-label="Close table of contents"
|
|
117
|
+
transition:fade={{ duration: 200 }}
|
|
118
|
+
></button>
|
|
119
|
+
|
|
120
|
+
<!-- Panel -->
|
|
121
|
+
<div
|
|
122
|
+
class={cn(breakpointHide, 'toc-panel', mobileClass)}
|
|
123
|
+
transition:fly={{ x: 320, duration: 300 }}
|
|
124
|
+
{...restProps}
|
|
125
|
+
>
|
|
126
|
+
<div class="toc-panel-header">
|
|
127
|
+
<span class="text-sm font-medium text-foreground">{title}</span>
|
|
128
|
+
<button
|
|
129
|
+
bind:this={closeButtonRef}
|
|
130
|
+
class="toc-close-button"
|
|
131
|
+
onclick={close}
|
|
132
|
+
aria-label="Close table of contents"
|
|
133
|
+
>
|
|
134
|
+
<svg
|
|
135
|
+
class="h-5 w-5"
|
|
136
|
+
fill="none"
|
|
137
|
+
stroke="currentColor"
|
|
138
|
+
viewBox="0 0 24 24"
|
|
139
|
+
aria-hidden="true"
|
|
140
|
+
>
|
|
141
|
+
<path
|
|
142
|
+
stroke-linecap="round"
|
|
143
|
+
stroke-linejoin="round"
|
|
144
|
+
stroke-width="2"
|
|
145
|
+
d="M6 18L18 6M6 6l12 12"
|
|
146
|
+
/>
|
|
147
|
+
</svg>
|
|
148
|
+
</button>
|
|
149
|
+
</div>
|
|
150
|
+
<div class="p-5">
|
|
151
|
+
<TableOfContents {html} {maxDepth} title="" />
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
{/if}
|
|
155
|
+
|
|
156
|
+
<!-- Desktop TOC Sidebar -->
|
|
157
|
+
{#if html && showDesktop}
|
|
158
|
+
<aside
|
|
159
|
+
class={cn(
|
|
160
|
+
breakpointShow,
|
|
161
|
+
'fixed top-20 right-4 w-56 max-h-[calc(100vh-6rem)] overflow-y-auto z-30',
|
|
162
|
+
desktopClass
|
|
163
|
+
)}
|
|
164
|
+
>
|
|
165
|
+
<TableOfContents {html} {maxDepth} {title} />
|
|
166
|
+
</aside>
|
|
167
|
+
{/if}
|
|
168
|
+
|
|
169
|
+
<style>
|
|
170
|
+
/* Paper folder tab trigger - positioned on right edge */
|
|
171
|
+
.toc-tab-trigger {
|
|
172
|
+
position: fixed;
|
|
173
|
+
top: 25%;
|
|
174
|
+
right: 0;
|
|
175
|
+
transform: translateY(-50%);
|
|
176
|
+
z-index: 40;
|
|
177
|
+
/* Tab shape - taller for vertical text */
|
|
178
|
+
padding: 1rem 0.375rem;
|
|
179
|
+
/* Visual styling */
|
|
180
|
+
background-color: hsl(var(--card));
|
|
181
|
+
border: 1px solid hsl(var(--border));
|
|
182
|
+
border-right: none;
|
|
183
|
+
border-radius: 0.5rem 0 0 0.5rem;
|
|
184
|
+
box-shadow: -2px 0 8px -2px rgba(0, 0, 0, 0.1);
|
|
185
|
+
/* Hover effect */
|
|
186
|
+
transition:
|
|
187
|
+
background-color 0.15s ease,
|
|
188
|
+
padding-right 0.15s ease;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.toc-tab-trigger:hover {
|
|
192
|
+
background-color: hsl(var(--muted));
|
|
193
|
+
padding-right: 0.5rem;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/* Vertical text for folder tab */
|
|
197
|
+
.toc-tab-text {
|
|
198
|
+
display: block;
|
|
199
|
+
writing-mode: vertical-rl;
|
|
200
|
+
text-orientation: mixed;
|
|
201
|
+
transform: rotate(180deg);
|
|
202
|
+
font-size: 0.75rem;
|
|
203
|
+
font-weight: 500;
|
|
204
|
+
color: hsl(var(--muted-foreground));
|
|
205
|
+
white-space: nowrap;
|
|
206
|
+
letter-spacing: 0.025em;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/* Backdrop overlay */
|
|
210
|
+
.toc-backdrop {
|
|
211
|
+
position: fixed;
|
|
212
|
+
inset: 0;
|
|
213
|
+
background-color: rgba(0, 0, 0, 0.5);
|
|
214
|
+
z-index: 40;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/* Flyout panel */
|
|
218
|
+
.toc-panel {
|
|
219
|
+
position: fixed;
|
|
220
|
+
top: 0;
|
|
221
|
+
right: 0;
|
|
222
|
+
height: 100%;
|
|
223
|
+
width: 20rem;
|
|
224
|
+
max-width: 85vw;
|
|
225
|
+
background-color: hsl(var(--card));
|
|
226
|
+
border-left: 1px solid hsl(var(--border));
|
|
227
|
+
box-shadow: -4px 0 16px -4px rgba(0, 0, 0, 0.15);
|
|
228
|
+
z-index: 50;
|
|
229
|
+
overflow-y: auto;
|
|
230
|
+
overscroll-behavior: contain;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/* Panel header */
|
|
234
|
+
.toc-panel-header {
|
|
235
|
+
position: sticky;
|
|
236
|
+
top: 0;
|
|
237
|
+
display: flex;
|
|
238
|
+
align-items: center;
|
|
239
|
+
justify-content: space-between;
|
|
240
|
+
padding: 0.75rem 1rem;
|
|
241
|
+
background-color: hsl(var(--card));
|
|
242
|
+
border-bottom: 1px solid hsl(var(--border));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/* Close button */
|
|
246
|
+
.toc-close-button {
|
|
247
|
+
display: inline-flex;
|
|
248
|
+
align-items: center;
|
|
249
|
+
justify-content: center;
|
|
250
|
+
width: 2.25rem;
|
|
251
|
+
height: 2.25rem;
|
|
252
|
+
border-radius: 0.375rem;
|
|
253
|
+
color: hsl(var(--muted-foreground));
|
|
254
|
+
transition:
|
|
255
|
+
background-color 0.15s ease,
|
|
256
|
+
color 0.15s ease;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.toc-close-button:hover {
|
|
260
|
+
background-color: hsl(var(--muted));
|
|
261
|
+
color: hsl(var(--foreground));
|
|
262
|
+
}
|
|
263
|
+
</style>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/** HTML content to extract headings from */
|
|
3
|
+
html: string;
|
|
4
|
+
/** Maximum heading depth to include (default: 3) */
|
|
5
|
+
maxDepth?: number;
|
|
6
|
+
/** Breakpoint for desktop view (default: 'xl') */
|
|
7
|
+
breakpoint?: 'lg' | 'xl' | '2xl';
|
|
8
|
+
/** Show desktop sidebar (default: true) */
|
|
9
|
+
showDesktop?: boolean;
|
|
10
|
+
/** Custom class for mobile panel */
|
|
11
|
+
mobileClass?: string;
|
|
12
|
+
/** Custom class for desktop sidebar */
|
|
13
|
+
desktopClass?: string;
|
|
14
|
+
/** Title for the TOC section */
|
|
15
|
+
title?: string;
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
declare const TocPanel: import("svelte").Component<Props, {}, "">;
|
|
19
|
+
type TocPanel = ReturnType<typeof TocPanel>;
|
|
20
|
+
export default TocPanel;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type Highlighter } from 'shiki';
|
|
2
|
+
/**
|
|
3
|
+
* Get or create a singleton Shiki highlighter instance
|
|
4
|
+
*/
|
|
5
|
+
export declare function getHighlighter(): Promise<Highlighter>;
|
|
6
|
+
/**
|
|
7
|
+
* Highlight code with Shiki
|
|
8
|
+
*/
|
|
9
|
+
export declare function highlightCode(code: string, language: string, options?: {
|
|
10
|
+
theme?: string;
|
|
11
|
+
}): Promise<string>;
|
|
12
|
+
/**
|
|
13
|
+
* Escape HTML entities in a string
|
|
14
|
+
*/
|
|
15
|
+
export declare function escapeHtml(str: string): string;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { createHighlighter, } from 'shiki';
|
|
2
|
+
let highlighterPromise = null;
|
|
3
|
+
// Common languages to pre-load
|
|
4
|
+
const PRELOADED_LANGUAGES = [
|
|
5
|
+
'javascript',
|
|
6
|
+
'typescript',
|
|
7
|
+
'svelte',
|
|
8
|
+
'html',
|
|
9
|
+
'css',
|
|
10
|
+
'json',
|
|
11
|
+
'bash',
|
|
12
|
+
'markdown',
|
|
13
|
+
'yaml',
|
|
14
|
+
'python',
|
|
15
|
+
'sql',
|
|
16
|
+
];
|
|
17
|
+
const PRELOADED_THEMES = ['github-dark', 'github-light', 'one-dark-pro'];
|
|
18
|
+
/**
|
|
19
|
+
* Get or create a singleton Shiki highlighter instance
|
|
20
|
+
*/
|
|
21
|
+
export async function getHighlighter() {
|
|
22
|
+
if (!highlighterPromise) {
|
|
23
|
+
highlighterPromise = createHighlighter({
|
|
24
|
+
themes: PRELOADED_THEMES,
|
|
25
|
+
langs: PRELOADED_LANGUAGES,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return highlighterPromise;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Highlight code with Shiki
|
|
32
|
+
*/
|
|
33
|
+
export async function highlightCode(code, language, options = {}) {
|
|
34
|
+
const { theme = 'github-dark' } = options;
|
|
35
|
+
const highlighter = await getHighlighter();
|
|
36
|
+
// Load language if not already loaded
|
|
37
|
+
const loadedLangs = highlighter.getLoadedLanguages();
|
|
38
|
+
if (!loadedLangs.includes(language)) {
|
|
39
|
+
try {
|
|
40
|
+
await highlighter.loadLanguage(language);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
// Log warning for debugging - falling back to plain text
|
|
44
|
+
console.warn(`[docs] Failed to load language "${language}", falling back to text:`, err instanceof Error ? err.message : err);
|
|
45
|
+
language = 'text';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Load theme if not already loaded
|
|
49
|
+
const loadedThemes = highlighter.getLoadedThemes();
|
|
50
|
+
if (!loadedThemes.includes(theme)) {
|
|
51
|
+
try {
|
|
52
|
+
await highlighter.loadTheme(theme);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
// Log warning for debugging - falling back to github-dark
|
|
56
|
+
console.warn(`[docs] Failed to load theme "${theme}", falling back to github-dark:`, err instanceof Error ? err.message : err);
|
|
57
|
+
return highlighter.codeToHtml(code, {
|
|
58
|
+
lang: language,
|
|
59
|
+
theme: 'github-dark',
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return highlighter.codeToHtml(code, {
|
|
64
|
+
lang: language,
|
|
65
|
+
theme: theme,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Escape HTML entities in a string
|
|
70
|
+
*/
|
|
71
|
+
export function escapeHtml(str) {
|
|
72
|
+
return str
|
|
73
|
+
.replace(/&/g, '&')
|
|
74
|
+
.replace(/</g, '<')
|
|
75
|
+
.replace(/>/g, '>')
|
|
76
|
+
.replace(/"/g, '"')
|
|
77
|
+
.replace(/'/g, ''');
|
|
78
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Classic Theme Documentation Components
|
|
3
|
+
*
|
|
4
|
+
* A collection of Svelte 5 components for rendering markdown documentation
|
|
5
|
+
* with syntax highlighting, frontmatter support, and rich markdown extensions.
|
|
6
|
+
*
|
|
7
|
+
* Supported Markdown Extensions:
|
|
8
|
+
* - Admonitions/Callouts: :::note, :::tip, :::warning, :::important, :::caution
|
|
9
|
+
* - Footnotes: [^1] references and [^1]: definitions
|
|
10
|
+
* - Definition Lists: Term followed by : Definition
|
|
11
|
+
* - Mermaid Diagrams: ```mermaid code blocks (rendered client-side)
|
|
12
|
+
*
|
|
13
|
+
* @packageDocumentation
|
|
14
|
+
*/
|
|
15
|
+
export { default as MarkdownPage } from './components/MarkdownPage.svelte';
|
|
16
|
+
export { default as CodeBlock } from './components/CodeBlock.svelte';
|
|
17
|
+
export { default as DocsHub } from './components/DocsHub.svelte';
|
|
18
|
+
export { default as DocsCard } from './components/DocsCard.svelte';
|
|
19
|
+
export { default as TableOfContents } from './components/TableOfContents.svelte';
|
|
20
|
+
export { default as TocPanel } from './components/TocPanel.svelte';
|
|
21
|
+
export { default as MermaidDiagram } from './components/MermaidDiagram.svelte';
|
|
22
|
+
export { default as MermaidInit } from './components/MermaidInit.svelte';
|
|
23
|
+
export type { AuthorInfo, FrontmatterData, ParsedMarkdown, ParseOptions, MarkdownPageProps, } from './types/frontmatter.js';
|
|
24
|
+
export type { DocsItem, DocsSection, DocsHubConfig, DocsHubProps, DocsCardProps, TocEntry, TableOfContentsProps, } from './types/docs.js';
|
|
25
|
+
export { parseMarkdown, extractToc } from './parser/index.js';
|
|
26
|
+
export { highlightCode, getHighlighter, escapeHtml } from './highlighter/index.js';
|
|
27
|
+
export { cn } from './utils.js';
|
|
28
|
+
export { ADMONITION_TYPES, type AdmonitionType, admonitionExtension, footnoteExtension, definitionListExtension, mermaidExtension, getAllExtensions, resetFootnoteStore, renderFootnotes, } from './parser/extensions.js';
|
|
29
|
+
export { tv, type VariantProps } from 'tailwind-variants';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Classic Theme Documentation Components
|
|
3
|
+
*
|
|
4
|
+
* A collection of Svelte 5 components for rendering markdown documentation
|
|
5
|
+
* with syntax highlighting, frontmatter support, and rich markdown extensions.
|
|
6
|
+
*
|
|
7
|
+
* Supported Markdown Extensions:
|
|
8
|
+
* - Admonitions/Callouts: :::note, :::tip, :::warning, :::important, :::caution
|
|
9
|
+
* - Footnotes: [^1] references and [^1]: definitions
|
|
10
|
+
* - Definition Lists: Term followed by : Definition
|
|
11
|
+
* - Mermaid Diagrams: ```mermaid code blocks (rendered client-side)
|
|
12
|
+
*
|
|
13
|
+
* @packageDocumentation
|
|
14
|
+
*/
|
|
15
|
+
// Components
|
|
16
|
+
export { default as MarkdownPage } from './components/MarkdownPage.svelte';
|
|
17
|
+
export { default as CodeBlock } from './components/CodeBlock.svelte';
|
|
18
|
+
export { default as DocsHub } from './components/DocsHub.svelte';
|
|
19
|
+
export { default as DocsCard } from './components/DocsCard.svelte';
|
|
20
|
+
export { default as TableOfContents } from './components/TableOfContents.svelte';
|
|
21
|
+
export { default as TocPanel } from './components/TocPanel.svelte';
|
|
22
|
+
export { default as MermaidDiagram } from './components/MermaidDiagram.svelte';
|
|
23
|
+
export { default as MermaidInit } from './components/MermaidInit.svelte';
|
|
24
|
+
// Utilities
|
|
25
|
+
export { parseMarkdown, extractToc } from './parser/index.js';
|
|
26
|
+
export { highlightCode, getHighlighter, escapeHtml } from './highlighter/index.js';
|
|
27
|
+
export { cn } from './utils.js';
|
|
28
|
+
// Markdown Extensions
|
|
29
|
+
export { ADMONITION_TYPES, admonitionExtension, footnoteExtension, definitionListExtension, mermaidExtension, getAllExtensions, resetFootnoteStore, renderFootnotes, } from './parser/extensions.js';
|
|
30
|
+
// Re-export tailwind-variants types for consumer convenience
|
|
31
|
+
export { tv } from 'tailwind-variants';
|