svelte-docsmith 0.8.0 → 0.9.0
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/buildtime/changelog.d.ts +20 -0
- package/dist/buildtime/changelog.js +74 -0
- package/dist/buildtime/fence-meta.d.ts +20 -0
- package/dist/buildtime/fence-meta.js +48 -0
- package/dist/buildtime/markdown-layout.svelte.d.ts +3 -0
- package/dist/buildtime/preprocess.d.ts +13 -0
- package/dist/buildtime/preprocess.js +77 -17
- package/dist/buildtime/twoslash.d.ts +26 -0
- package/dist/buildtime/twoslash.js +40 -0
- package/dist/buildtime/vite/git.d.ts +7 -0
- package/dist/buildtime/vite/git.js +32 -0
- package/dist/buildtime/vite/index.d.ts +13 -0
- package/dist/buildtime/vite/index.js +16 -0
- package/dist/buildtime/vite/releases.d.ts +12 -0
- package/dist/buildtime/vite/releases.js +57 -0
- package/dist/components/changelog/changelog-entry.svelte +90 -0
- package/dist/components/changelog/changelog-entry.svelte.d.ts +7 -0
- package/dist/components/changelog/changelog.svelte +56 -0
- package/dist/components/changelog/changelog.svelte.d.ts +14 -0
- package/dist/components/docs/mermaid.svelte +213 -0
- package/dist/components/docs/mermaid.svelte.d.ts +6 -0
- package/dist/components/landing/action.svelte +50 -0
- package/dist/components/landing/action.svelte.d.ts +13 -0
- package/dist/components/landing/cta.svelte +52 -0
- package/dist/components/landing/cta.svelte.d.ts +13 -0
- package/dist/components/landing/feature-grid.svelte +51 -0
- package/dist/components/landing/feature-grid.svelte.d.ts +19 -0
- package/dist/components/landing/feature.svelte +33 -0
- package/dist/components/landing/feature.svelte.d.ts +11 -0
- package/dist/components/landing/hero.svelte +70 -0
- package/dist/components/landing/hero.svelte.d.ts +19 -0
- package/dist/components/layouts/docs-header.svelte +36 -3
- package/dist/components/markdown/pre.svelte +238 -8
- package/dist/components/markdown/pre.svelte.d.ts +6 -0
- package/dist/core/changelog.d.ts +31 -0
- package/dist/core/changelog.js +7 -0
- package/dist/fallbacks/changelog.d.ts +10 -0
- package/dist/fallbacks/changelog.js +3 -0
- package/dist/generate/feed.d.ts +35 -0
- package/dist/generate/feed.js +80 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +9 -0
- package/dist/theme.css +32 -0
- package/package.json +21 -2
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
title,
|
|
6
|
+
description,
|
|
7
|
+
eyebrow,
|
|
8
|
+
actions,
|
|
9
|
+
media
|
|
10
|
+
}: {
|
|
11
|
+
/** The headline. Pass a snippet instead of a string to mark part of it up. */
|
|
12
|
+
title: string | Snippet;
|
|
13
|
+
/** Supporting paragraph below the headline. */
|
|
14
|
+
description?: string | Snippet;
|
|
15
|
+
/** A pill, badge, or announcement link sitting above the headline. */
|
|
16
|
+
eyebrow?: Snippet;
|
|
17
|
+
/** Call-to-action buttons. */
|
|
18
|
+
actions?: Snippet;
|
|
19
|
+
/**
|
|
20
|
+
* The second column: a code sample, screenshot, or live demo. Without it the
|
|
21
|
+
* hero collapses to a single centred column.
|
|
22
|
+
*/
|
|
23
|
+
media?: Snippet;
|
|
24
|
+
} = $props();
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<!-- Renders either a plain string or a snippet, so simple pages stay simple. -->
|
|
28
|
+
{#snippet content(value: string | Snippet)}
|
|
29
|
+
{#if typeof value === 'string'}{value}{:else}{@render value()}{/if}
|
|
30
|
+
{/snippet}
|
|
31
|
+
|
|
32
|
+
<section
|
|
33
|
+
class="mx-auto grid max-w-7xl items-center gap-12 px-4 pt-16 pb-20 md:px-6 lg:gap-10 lg:px-8 lg:pt-24 lg:pb-28 {media
|
|
34
|
+
? 'lg:grid-cols-[1.05fr_1fr]'
|
|
35
|
+
: 'max-w-3xl text-center'}"
|
|
36
|
+
>
|
|
37
|
+
<div class="min-w-0">
|
|
38
|
+
{#if eyebrow}
|
|
39
|
+
{@render eyebrow()}
|
|
40
|
+
{/if}
|
|
41
|
+
|
|
42
|
+
<h1
|
|
43
|
+
class="text-5xl font-semibold tracking-[-0.03em] text-balance sm:text-6xl lg:text-[4.25rem] lg:leading-[1.02] {eyebrow
|
|
44
|
+
? 'mt-6'
|
|
45
|
+
: ''}"
|
|
46
|
+
>
|
|
47
|
+
{@render content(title)}
|
|
48
|
+
</h1>
|
|
49
|
+
|
|
50
|
+
{#if description}
|
|
51
|
+
<p
|
|
52
|
+
class="text-muted-foreground mt-6 max-w-xl text-lg leading-relaxed text-pretty {media
|
|
53
|
+
? ''
|
|
54
|
+
: 'mx-auto'}"
|
|
55
|
+
>
|
|
56
|
+
{@render content(description)}
|
|
57
|
+
</p>
|
|
58
|
+
{/if}
|
|
59
|
+
|
|
60
|
+
{#if actions}
|
|
61
|
+
<div class="mt-8 flex flex-wrap items-center gap-3 {media ? '' : 'justify-center'}">
|
|
62
|
+
{@render actions()}
|
|
63
|
+
</div>
|
|
64
|
+
{/if}
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
{#if media}
|
|
68
|
+
<div class="min-w-0">{@render media()}</div>
|
|
69
|
+
{/if}
|
|
70
|
+
</section>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
/** The headline. Pass a snippet instead of a string to mark part of it up. */
|
|
4
|
+
title: string | Snippet;
|
|
5
|
+
/** Supporting paragraph below the headline. */
|
|
6
|
+
description?: string | Snippet;
|
|
7
|
+
/** A pill, badge, or announcement link sitting above the headline. */
|
|
8
|
+
eyebrow?: Snippet;
|
|
9
|
+
/** Call-to-action buttons. */
|
|
10
|
+
actions?: Snippet;
|
|
11
|
+
/**
|
|
12
|
+
* The second column: a code sample, screenshot, or live demo. Without it the
|
|
13
|
+
* hero collapses to a single centred column.
|
|
14
|
+
*/
|
|
15
|
+
media?: Snippet;
|
|
16
|
+
};
|
|
17
|
+
declare const Hero: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
18
|
+
type Hero = ReturnType<typeof Hero>;
|
|
19
|
+
export default Hero;
|
|
@@ -4,10 +4,14 @@
|
|
|
4
4
|
import { Separator } from '../shadcn/separator/index.js';
|
|
5
5
|
import GithubIcon from '../icons/github.svelte';
|
|
6
6
|
import SearchTrigger from '../chrome/search-trigger.svelte';
|
|
7
|
+
import { useSearch } from '../../search/context.svelte.js';
|
|
7
8
|
import ThemeToggle from '../chrome/theme-toggle.svelte';
|
|
8
|
-
import type { DocsmithConfig } from '../../core/index.js';
|
|
9
|
+
import type { DocsmithConfig, DocsmithLink } from '../../core/index.js';
|
|
9
10
|
import BookOpenText from '@lucide/svelte/icons/book-open-text';
|
|
10
11
|
import type { Snippet } from 'svelte';
|
|
12
|
+
import { page } from '$app/state';
|
|
13
|
+
import { cn } from '../../utils/cn.js';
|
|
14
|
+
import { normalizePath } from '../../utils/normalize-path.js';
|
|
11
15
|
|
|
12
16
|
const {
|
|
13
17
|
config,
|
|
@@ -21,6 +25,26 @@
|
|
|
21
25
|
actions?: Snippet;
|
|
22
26
|
} = $props();
|
|
23
27
|
|
|
28
|
+
// Present only when the consumer passed a `search` loader to DocsShell.
|
|
29
|
+
const search = useSearch();
|
|
30
|
+
|
|
31
|
+
const current = $derived(normalizePath(page.url.pathname));
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* A header link is usually the entry point to a whole section rather than a
|
|
35
|
+
* page you sit on: "Docs" points at `/docs/introduction`, and should stay lit
|
|
36
|
+
* while the reader moves through `/docs/*`. So match the link's first segment,
|
|
37
|
+
* not the exact path. The root is exempt, since every path starts with `/`.
|
|
38
|
+
*/
|
|
39
|
+
function isActive(link: DocsmithLink): boolean {
|
|
40
|
+
if (link.external) return false;
|
|
41
|
+
const href = normalizePath(link.href);
|
|
42
|
+
if (href === current) return true;
|
|
43
|
+
if (href === '/') return false;
|
|
44
|
+
const section = '/' + href.split('/').filter(Boolean)[0];
|
|
45
|
+
return current === section || current.startsWith(section + '/');
|
|
46
|
+
}
|
|
47
|
+
|
|
24
48
|
let isScrolled = $state(false);
|
|
25
49
|
</script>
|
|
26
50
|
|
|
@@ -55,7 +79,11 @@
|
|
|
55
79
|
href={link.href}
|
|
56
80
|
target={link.external ? '_blank' : undefined}
|
|
57
81
|
rel={link.external ? 'noopener noreferrer' : undefined}
|
|
58
|
-
|
|
82
|
+
aria-current={isActive(link) ? 'page' : undefined}
|
|
83
|
+
class={cn(
|
|
84
|
+
'hover:text-foreground rounded-md px-3 py-1.5 text-sm font-medium transition-colors',
|
|
85
|
+
isActive(link) ? 'text-foreground bg-muted/60' : 'text-muted-foreground'
|
|
86
|
+
)}
|
|
59
87
|
>
|
|
60
88
|
{link.label}
|
|
61
89
|
</a>
|
|
@@ -76,7 +104,12 @@
|
|
|
76
104
|
|
|
77
105
|
<SearchTrigger />
|
|
78
106
|
|
|
79
|
-
|
|
107
|
+
<!-- The separator divides the search trigger from the action icons, so
|
|
108
|
+
it only earns its place when there is a trigger to divide from.
|
|
109
|
+
Without this a site that hasn't wired search shows a stray rule. -->
|
|
110
|
+
{#if search}
|
|
111
|
+
<Separator orientation="vertical" class="bg-border/40 mx-1 min-h-0 h-6" />
|
|
112
|
+
{/if}
|
|
80
113
|
|
|
81
114
|
<div class="flex items-center gap-0.5">
|
|
82
115
|
{#if actions}
|
|
@@ -6,18 +6,102 @@
|
|
|
6
6
|
|
|
7
7
|
const clipboard = useClipboard();
|
|
8
8
|
|
|
9
|
-
const {
|
|
9
|
+
const {
|
|
10
|
+
title,
|
|
11
|
+
lineNumbers = false,
|
|
12
|
+
startLine = 1,
|
|
13
|
+
children
|
|
14
|
+
}: {
|
|
15
|
+
/** Filename or caption, from the fence's `title=` metadata. */
|
|
16
|
+
title?: string;
|
|
17
|
+
/** Number the lines, from `showLineNumbers`. */
|
|
18
|
+
lineNumbers?: boolean;
|
|
19
|
+
/** First line's number, from `startLine=`. */
|
|
20
|
+
startLine?: number;
|
|
21
|
+
children: Snippet;
|
|
22
|
+
} = $props();
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Twoslash popups are absolutely positioned inside the code block, which is a
|
|
26
|
+
* scroll container, so anything reaching past its edges is clipped: a hover
|
|
27
|
+
* near the last line shows a sliver of the type and nothing else. CSS can't
|
|
28
|
+
* fix this, because a horizontally scrolling element must also clip
|
|
29
|
+
* vertically. So on hover the popup is promoted to fixed positioning, whose
|
|
30
|
+
* containing block is the viewport rather than the scroller.
|
|
31
|
+
*
|
|
32
|
+
* Without JavaScript the popup still opens on hover, clipped as before, so
|
|
33
|
+
* this only ever improves on the CSS-only behaviour.
|
|
34
|
+
*/
|
|
35
|
+
function unclipPopups(node: HTMLElement) {
|
|
36
|
+
const GAP = 6;
|
|
37
|
+
|
|
38
|
+
const place = (event: Event) => {
|
|
39
|
+
const target = event.target as HTMLElement | null;
|
|
40
|
+
const trigger = target?.closest?.('.twoslash-hover') as HTMLElement | null;
|
|
41
|
+
if (!trigger) return;
|
|
42
|
+
const popup = trigger.querySelector('.twoslash-popup-container') as HTMLElement | null;
|
|
43
|
+
if (!popup) return;
|
|
44
|
+
|
|
45
|
+
// Measure while visible but before committing a position, so the popup's
|
|
46
|
+
// own width decides whether it flips to stay on screen.
|
|
47
|
+
popup.style.position = 'fixed';
|
|
48
|
+
popup.style.left = '0px';
|
|
49
|
+
popup.style.top = '0px';
|
|
50
|
+
const rect = trigger.getBoundingClientRect();
|
|
51
|
+
const { width, height } = popup.getBoundingClientRect();
|
|
52
|
+
|
|
53
|
+
const left = Math.max(8, Math.min(rect.left, window.innerWidth - width - 8));
|
|
54
|
+
// Below the token unless that would run off the bottom, then above it.
|
|
55
|
+
const below = rect.bottom + GAP;
|
|
56
|
+
const top = below + height > window.innerHeight - 8 ? rect.top - height - GAP : below;
|
|
57
|
+
|
|
58
|
+
popup.style.left = `${left}px`;
|
|
59
|
+
popup.style.top = `${Math.max(8, top)}px`;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const reset = (event: Event) => {
|
|
63
|
+
const trigger = (event.target as HTMLElement | null)?.closest?.(
|
|
64
|
+
'.twoslash-hover'
|
|
65
|
+
) as HTMLElement | null;
|
|
66
|
+
const popup = trigger?.querySelector('.twoslash-popup-container') as HTMLElement | null;
|
|
67
|
+
if (popup) popup.removeAttribute('style');
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
node.addEventListener('mouseover', place);
|
|
71
|
+
node.addEventListener('mouseout', reset);
|
|
72
|
+
return {
|
|
73
|
+
destroy() {
|
|
74
|
+
node.removeEventListener('mouseover', place);
|
|
75
|
+
node.removeEventListener('mouseout', reset);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
10
79
|
</script>
|
|
11
80
|
|
|
12
|
-
<div class="rounded-md overflow-hidden bg-muted mt-2 shadow-md text-sm relative">
|
|
81
|
+
<div class="rounded-md overflow-hidden bg-muted mt-2 shadow-md text-sm relative" use:unclipPopups>
|
|
82
|
+
{#if title}
|
|
83
|
+
<!-- The filename bar doubles as the copy button's row, so the button stops
|
|
84
|
+
floating over the first line of code when a title is present. -->
|
|
85
|
+
<div class="docsmith-code-title">
|
|
86
|
+
<span class="docsmith-code-title-text">{title}</span>
|
|
87
|
+
<CopyButton copied={clipboard.copied} onclick={() => clipboard.copy()} class="bg-muted" />
|
|
88
|
+
</div>
|
|
89
|
+
{/if}
|
|
90
|
+
|
|
13
91
|
<ScrollArea orientation="both" class="relative">
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
92
|
+
{#if !title}
|
|
93
|
+
<CopyButton
|
|
94
|
+
copied={clipboard.copied}
|
|
95
|
+
onclick={() => clipboard.copy()}
|
|
96
|
+
class="bg-muted absolute top-2 right-2 z-10"
|
|
97
|
+
/>
|
|
98
|
+
{/if}
|
|
19
99
|
|
|
20
|
-
<pre
|
|
100
|
+
<pre
|
|
101
|
+
class="not-prose shiki max-h-[32rem] flex shrink"
|
|
102
|
+
class:docsmith-line-numbers={lineNumbers}
|
|
103
|
+
style={lineNumbers && startLine !== 1 ? `--docsmith-line-start: ${startLine - 1}` : undefined}
|
|
104
|
+
use:clipboard.readText>
|
|
21
105
|
{@render children()}
|
|
22
106
|
</pre>
|
|
23
107
|
</ScrollArea>
|
|
@@ -44,6 +128,64 @@
|
|
|
44
128
|
padding-inline: 1rem;
|
|
45
129
|
}
|
|
46
130
|
|
|
131
|
+
/* --- Filename bar --- */
|
|
132
|
+
.docsmith-code-title {
|
|
133
|
+
display: flex;
|
|
134
|
+
align-items: center;
|
|
135
|
+
justify-content: space-between;
|
|
136
|
+
gap: 0.75rem;
|
|
137
|
+
padding: 0.4rem 0.5rem 0.4rem 1rem;
|
|
138
|
+
border-bottom: 1px solid var(--border);
|
|
139
|
+
background: color-mix(in oklch, var(--muted) 60%, var(--background));
|
|
140
|
+
}
|
|
141
|
+
.docsmith-code-title-text {
|
|
142
|
+
font-family: var(--font-mono, ui-monospace, monospace);
|
|
143
|
+
font-size: 0.78rem;
|
|
144
|
+
color: var(--muted-foreground);
|
|
145
|
+
overflow-wrap: anywhere;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/* --- Line numbers ---
|
|
149
|
+
A CSS counter on Shiki's existing per-line spans, so numbering adds no DOM
|
|
150
|
+
and stays out of the text the copy button reads. `--docsmith-line-start`
|
|
151
|
+
offsets the counter for a snippet lifted out of a larger file. */
|
|
152
|
+
:global(pre.docsmith-line-numbers code) {
|
|
153
|
+
counter-reset: docsmith-line var(--docsmith-line-start, 0);
|
|
154
|
+
}
|
|
155
|
+
:global(pre.docsmith-line-numbers span.line) {
|
|
156
|
+
counter-increment: docsmith-line;
|
|
157
|
+
position: relative;
|
|
158
|
+
padding-inline-start: 3.2rem;
|
|
159
|
+
}
|
|
160
|
+
:global(pre.docsmith-line-numbers span.line::before) {
|
|
161
|
+
content: counter(docsmith-line);
|
|
162
|
+
position: absolute;
|
|
163
|
+
left: 0;
|
|
164
|
+
width: 2.4rem;
|
|
165
|
+
text-align: right;
|
|
166
|
+
color: color-mix(in oklch, var(--muted-foreground) 65%, transparent);
|
|
167
|
+
/* Numbers are decoration, not content: keep them out of selections and
|
|
168
|
+
off the clipboard. */
|
|
169
|
+
user-select: none;
|
|
170
|
+
}
|
|
171
|
+
/* The diff gutter glyph would collide with the number, so shift it over. */
|
|
172
|
+
:global(pre.docsmith-line-numbers span.line.diff::before) {
|
|
173
|
+
content: counter(docsmith-line);
|
|
174
|
+
}
|
|
175
|
+
:global(pre.docsmith-line-numbers span.line.diff::after) {
|
|
176
|
+
position: absolute;
|
|
177
|
+
left: 2.7rem;
|
|
178
|
+
font-weight: 600;
|
|
179
|
+
}
|
|
180
|
+
:global(pre.docsmith-line-numbers span.line.diff.add::after) {
|
|
181
|
+
content: '+';
|
|
182
|
+
color: oklch(0.6 0.15 150);
|
|
183
|
+
}
|
|
184
|
+
:global(pre.docsmith-line-numbers span.line.diff.remove::after) {
|
|
185
|
+
content: '−';
|
|
186
|
+
color: oklch(0.58 0.19 25);
|
|
187
|
+
}
|
|
188
|
+
|
|
47
189
|
/* Two tiers of annotation. EMPHASIS (highlight, word) tints with the theme's
|
|
48
190
|
--primary, so it reads as the brand pointing at a line and recolors with
|
|
49
191
|
the active theme. SEMANTIC (diff, error, warning) uses fixed OKLCH hues so
|
|
@@ -125,6 +267,94 @@
|
|
|
125
267
|
}
|
|
126
268
|
}
|
|
127
269
|
|
|
270
|
+
/* --- Twoslash hovers ---
|
|
271
|
+
Restyled from @shikijs/twoslash's rich theme onto our tokens so the popup
|
|
272
|
+
belongs to the site in both colour schemes. Underline marks a token as
|
|
273
|
+
inspectable; the popup is a hidden sibling revealed on hover. `unclipPopups`
|
|
274
|
+
then promotes it to fixed positioning so the code block's scrolling does not
|
|
275
|
+
clip it — see the comment there. */
|
|
276
|
+
:global(pre .twoslash-hover) {
|
|
277
|
+
position: relative;
|
|
278
|
+
border-bottom: 1px dotted color-mix(in oklch, var(--muted-foreground) 55%, transparent);
|
|
279
|
+
}
|
|
280
|
+
:global(pre .twoslash-popup-container) {
|
|
281
|
+
position: absolute;
|
|
282
|
+
z-index: 40;
|
|
283
|
+
display: none;
|
|
284
|
+
left: 0;
|
|
285
|
+
top: 1.6em;
|
|
286
|
+
/* Shrink to the type's own width so a short signature isn't a wide slab;
|
|
287
|
+
the script measures this before choosing a position. */
|
|
288
|
+
width: max-content;
|
|
289
|
+
max-width: min(32rem, 78vw);
|
|
290
|
+
padding: 0.5rem 0.7rem;
|
|
291
|
+
border: 1px solid var(--border);
|
|
292
|
+
border-radius: var(--radius, 0.5rem);
|
|
293
|
+
background: var(--popover, var(--card));
|
|
294
|
+
color: var(--popover-foreground, var(--foreground));
|
|
295
|
+
box-shadow: 0 8px 24px rgb(0 0 0 / 0.18);
|
|
296
|
+
white-space: pre-wrap;
|
|
297
|
+
text-align: left;
|
|
298
|
+
font-size: 0.85em;
|
|
299
|
+
line-height: 1.5;
|
|
300
|
+
}
|
|
301
|
+
:global(pre .twoslash-hover:hover .twoslash-popup-container) {
|
|
302
|
+
display: block;
|
|
303
|
+
}
|
|
304
|
+
/* The signature keeps Shiki's own colours and monospace. */
|
|
305
|
+
:global(pre .twoslash-popup-code) {
|
|
306
|
+
display: block;
|
|
307
|
+
font-family: var(--font-mono, ui-monospace, monospace);
|
|
308
|
+
white-space: pre-wrap;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/* Prose, by contrast, is prose. Inside a <pre> it would otherwise inherit
|
|
312
|
+
monospace and whatever token colour the hovered span happened to carry,
|
|
313
|
+
which reads as more code rather than an explanation of it. */
|
|
314
|
+
:global(pre .twoslash-popup-docs) {
|
|
315
|
+
/* The dark-theme swap repaints every span inside `.shiki` with
|
|
316
|
+
`--shiki-dark`, which these spans inherit from whichever token was
|
|
317
|
+
hovered — so docs took on the colour of the word above them. Redefining
|
|
318
|
+
the variable here resolves it to prose colours instead of fighting an
|
|
319
|
+
`!important` rule. */
|
|
320
|
+
--shiki-dark: var(--muted-foreground);
|
|
321
|
+
margin-top: 0.5rem;
|
|
322
|
+
padding-top: 0.5rem;
|
|
323
|
+
border-top: 1px solid var(--border);
|
|
324
|
+
font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif);
|
|
325
|
+
font-size: 0.92em;
|
|
326
|
+
line-height: 1.55;
|
|
327
|
+
color: var(--muted-foreground);
|
|
328
|
+
white-space: normal;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/* Each @param is its own line: the tags are inline spans, so without this
|
|
332
|
+
they run together into one paragraph. */
|
|
333
|
+
:global(pre .twoslash-popup-docs-tags) {
|
|
334
|
+
display: flex;
|
|
335
|
+
flex-direction: column;
|
|
336
|
+
gap: 0.35rem;
|
|
337
|
+
}
|
|
338
|
+
:global(pre .twoslash-popup-docs-tag) {
|
|
339
|
+
display: block;
|
|
340
|
+
}
|
|
341
|
+
:global(pre .twoslash-popup-docs-tag-value) {
|
|
342
|
+
--shiki-dark: var(--muted-foreground);
|
|
343
|
+
color: var(--muted-foreground);
|
|
344
|
+
}
|
|
345
|
+
/* And the tag name needs a gap, or it reads as "@paramcallbackfn". */
|
|
346
|
+
:global(pre .twoslash-popup-docs-tag-name) {
|
|
347
|
+
--shiki-dark: var(--primary);
|
|
348
|
+
margin-right: 0.4em;
|
|
349
|
+
font-family: var(--font-mono, ui-monospace, monospace);
|
|
350
|
+
font-size: 0.92em;
|
|
351
|
+
color: var(--primary);
|
|
352
|
+
}
|
|
353
|
+
/* A snippet that deliberately shows an error (// @errors:) marks the line. */
|
|
354
|
+
:global(pre .twoslash-error) {
|
|
355
|
+
background: color-mix(in oklch, oklch(0.62 0.2 25) 14%, transparent);
|
|
356
|
+
}
|
|
357
|
+
|
|
128
358
|
/* --- Word highlight (// [!code word:name]): a primary-tinted inline pill --- */
|
|
129
359
|
:global(pre .highlighted-word) {
|
|
130
360
|
border-radius: 0.3rem;
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
type $$ComponentProps = {
|
|
3
|
+
/** Filename or caption, from the fence's `title=` metadata. */
|
|
4
|
+
title?: string;
|
|
5
|
+
/** Number the lines, from `showLineNumbers`. */
|
|
6
|
+
lineNumbers?: boolean;
|
|
7
|
+
/** First line's number, from `startLine=`. */
|
|
8
|
+
startLine?: number;
|
|
3
9
|
children: Snippet;
|
|
4
10
|
};
|
|
5
11
|
declare const Pre: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The record types for the generated changelog index. The `docsmith()` Vite
|
|
3
|
+
* plugin parses your package's `CHANGELOG.md` at build time and emits these as
|
|
4
|
+
* the `svelte-docsmith/changelog` virtual module, so every release becomes a
|
|
5
|
+
* page and a feed entry without being written twice.
|
|
6
|
+
*/
|
|
7
|
+
/** One group of changes within a release, e.g. "Minor Changes". */
|
|
8
|
+
export type ChangelogGroup = {
|
|
9
|
+
/** The group heading as written, e.g. `Minor Changes` or `Patch Changes`. */
|
|
10
|
+
kind: string;
|
|
11
|
+
/**
|
|
12
|
+
* Each entry's body as HTML, rendered from markdown at build time with the
|
|
13
|
+
* changeset's commit hash removed. HTML rather than markdown so a consuming
|
|
14
|
+
* site needs no runtime renderer.
|
|
15
|
+
*/
|
|
16
|
+
items: string[];
|
|
17
|
+
};
|
|
18
|
+
/** One released version. */
|
|
19
|
+
export type ChangelogRelease = {
|
|
20
|
+
/** Version string as it appears in the changelog, e.g. `0.8.0`. */
|
|
21
|
+
version: string;
|
|
22
|
+
/** Release date (ISO) from the git history, when it can be determined. */
|
|
23
|
+
date?: string;
|
|
24
|
+
groups: ChangelogGroup[];
|
|
25
|
+
/**
|
|
26
|
+
* A hand-written page for this release, when `src/routes/changelog/<version>/`
|
|
27
|
+
* exists. The generated entry is the fallback, so a release that deserves a
|
|
28
|
+
* proper write-up can have one without the rest going undocumented.
|
|
29
|
+
*/
|
|
30
|
+
path?: string;
|
|
31
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The record types for the generated changelog index. The `docsmith()` Vite
|
|
3
|
+
* plugin parses your package's `CHANGELOG.md` at build time and emits these as
|
|
4
|
+
* the `svelte-docsmith/changelog` virtual module, so every release becomes a
|
|
5
|
+
* page and a feed entry without being written twice.
|
|
6
|
+
*/
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `svelte-docsmith/changelog` — the generated release index.
|
|
3
|
+
*
|
|
4
|
+
* At build time the `docsmith()` Vite plugin (in `vite.config.ts`) intercepts
|
|
5
|
+
* this import and replaces it with your package's `CHANGELOG.md`, parsed into
|
|
6
|
+
* releases and dated from git. This file is only the fallback that runs when
|
|
7
|
+
* the plugin is missing — see {@link missingPluginError}.
|
|
8
|
+
*/
|
|
9
|
+
import type { ChangelogRelease } from '../core/changelog.js';
|
|
10
|
+
export declare const releases: ChangelogRelease[];
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ChangelogRelease } from '../core/changelog.js';
|
|
2
|
+
/** Site details the feed needs beyond the releases themselves. */
|
|
3
|
+
export type FeedSite = {
|
|
4
|
+
/** Canonical origin, e.g. `https://docs.example.com`. */
|
|
5
|
+
url: string;
|
|
6
|
+
/** Feed title, usually the site title. */
|
|
7
|
+
title: string;
|
|
8
|
+
/** One-line feed description. */
|
|
9
|
+
description?: string;
|
|
10
|
+
/** Path the changelog lives at. Default: `/changelog`. */
|
|
11
|
+
path?: string;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Build an Atom feed body from parsed changelog releases. Framework-agnostic:
|
|
15
|
+
* wire it into a SvelteKit `src/routes/changelog.xml/+server.ts` over the
|
|
16
|
+
* generated `svelte-docsmith/changelog` index.
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* import { releases } from 'svelte-docsmith/changelog';
|
|
20
|
+
* import { generateFeed } from 'svelte-docsmith';
|
|
21
|
+
*
|
|
22
|
+
* export const prerender = true;
|
|
23
|
+
* export function GET() {
|
|
24
|
+
* const body = generateFeed(releases, {
|
|
25
|
+
* url: 'https://my-docs.dev',
|
|
26
|
+
* title: 'My Library'
|
|
27
|
+
* });
|
|
28
|
+
* return new Response(body, { headers: { 'content-type': 'application/atom+xml' } });
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* Atom rather than RSS because it requires an unambiguous per-entry id and
|
|
33
|
+
* update timestamp, which is exactly what a version and its release date give.
|
|
34
|
+
*/
|
|
35
|
+
export declare function generateFeed(releases: ChangelogRelease[], site: FeedSite): string;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const escapeXml = (s) => s.replace(/[&<>'"]/g, (c) => {
|
|
2
|
+
switch (c) {
|
|
3
|
+
case '&':
|
|
4
|
+
return '&';
|
|
5
|
+
case '<':
|
|
6
|
+
return '<';
|
|
7
|
+
case '>':
|
|
8
|
+
return '>';
|
|
9
|
+
case "'":
|
|
10
|
+
return ''';
|
|
11
|
+
default:
|
|
12
|
+
return '"';
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
/** A release's entries flattened to plain text, grouped by kind. */
|
|
16
|
+
function summarise(release) {
|
|
17
|
+
return release.groups
|
|
18
|
+
.map((group) => `${group.kind}\n\n${group.items.map((item) => `- ${item}`).join('\n\n')}`)
|
|
19
|
+
.join('\n\n');
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Build an Atom feed body from parsed changelog releases. Framework-agnostic:
|
|
23
|
+
* wire it into a SvelteKit `src/routes/changelog.xml/+server.ts` over the
|
|
24
|
+
* generated `svelte-docsmith/changelog` index.
|
|
25
|
+
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* import { releases } from 'svelte-docsmith/changelog';
|
|
28
|
+
* import { generateFeed } from 'svelte-docsmith';
|
|
29
|
+
*
|
|
30
|
+
* export const prerender = true;
|
|
31
|
+
* export function GET() {
|
|
32
|
+
* const body = generateFeed(releases, {
|
|
33
|
+
* url: 'https://my-docs.dev',
|
|
34
|
+
* title: 'My Library'
|
|
35
|
+
* });
|
|
36
|
+
* return new Response(body, { headers: { 'content-type': 'application/atom+xml' } });
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* Atom rather than RSS because it requires an unambiguous per-entry id and
|
|
41
|
+
* update timestamp, which is exactly what a version and its release date give.
|
|
42
|
+
*/
|
|
43
|
+
export function generateFeed(releases, site) {
|
|
44
|
+
const origin = site.url.replace(/\/$/, '');
|
|
45
|
+
const path = site.path ?? '/changelog';
|
|
46
|
+
const feedUrl = `${origin}${path}.xml`;
|
|
47
|
+
// A feed needs an updated timestamp; fall back to now when no release
|
|
48
|
+
// carries a date, so the document stays valid.
|
|
49
|
+
const updated = releases.find((r) => r.date)?.date ?? new Date().toISOString();
|
|
50
|
+
const entries = releases
|
|
51
|
+
.map((release) => {
|
|
52
|
+
const link = release.path
|
|
53
|
+
? `${origin}${release.path}`
|
|
54
|
+
: `${origin}${path}#${release.version}`;
|
|
55
|
+
return [
|
|
56
|
+
'\t<entry>',
|
|
57
|
+
`\t\t<title>${escapeXml(release.version)}</title>`,
|
|
58
|
+
`\t\t<id>${escapeXml(link)}</id>`,
|
|
59
|
+
`\t\t<link href="${escapeXml(link)}" />`,
|
|
60
|
+
`\t\t<updated>${escapeXml(release.date ?? updated)}</updated>`,
|
|
61
|
+
`\t\t<content type="text">${escapeXml(summarise(release))}</content>`,
|
|
62
|
+
'\t</entry>'
|
|
63
|
+
].join('\n');
|
|
64
|
+
})
|
|
65
|
+
.join('\n');
|
|
66
|
+
return [
|
|
67
|
+
'<?xml version="1.0" encoding="utf-8"?>',
|
|
68
|
+
'<feed xmlns="http://www.w3.org/2005/Atom">',
|
|
69
|
+
`\t<title>${escapeXml(site.title)}</title>`,
|
|
70
|
+
site.description ? `\t<subtitle>${escapeXml(site.description)}</subtitle>` : '',
|
|
71
|
+
`\t<id>${escapeXml(feedUrl)}</id>`,
|
|
72
|
+
`\t<link href="${escapeXml(origin + path)}" />`,
|
|
73
|
+
`\t<link rel="self" href="${escapeXml(feedUrl)}" />`,
|
|
74
|
+
`\t<updated>${escapeXml(updated)}</updated>`,
|
|
75
|
+
entries,
|
|
76
|
+
'</feed>'
|
|
77
|
+
]
|
|
78
|
+
.filter(Boolean)
|
|
79
|
+
.join('\n');
|
|
80
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -18,7 +18,15 @@ export { default as FileTree } from './components/docs/file-tree.svelte';
|
|
|
18
18
|
export { default as FileTreeItem } from './components/docs/file-tree-item.svelte';
|
|
19
19
|
export { default as PropsTable } from './components/docs/props-table.svelte';
|
|
20
20
|
export { default as Prop } from './components/docs/prop.svelte';
|
|
21
|
+
export { default as Hero } from './components/landing/hero.svelte';
|
|
22
|
+
export { default as FeatureGrid } from './components/landing/feature-grid.svelte';
|
|
23
|
+
export { default as Feature } from './components/landing/feature.svelte';
|
|
24
|
+
export { default as CTA } from './components/landing/cta.svelte';
|
|
25
|
+
export { default as Action } from './components/landing/action.svelte';
|
|
21
26
|
export { defineConfig, type DocsmithConfig, type DocsContentItem, type SearchDoc, type LlmsDoc } from './core/index.js';
|
|
22
27
|
export { createSearchEngine, type SearchEngine, type SearchResult } from './search/create-search.js';
|
|
23
28
|
export { generateSitemap, type SitemapEntry } from './generate/sitemap.js';
|
|
24
29
|
export { generateLlmsTxt, generateLlmsFullTxt, type LlmsSite } from './generate/llms.js';
|
|
30
|
+
export { default as Changelog } from './components/changelog/changelog.svelte';
|
|
31
|
+
export type { ChangelogRelease, ChangelogGroup } from './core/changelog.js';
|
|
32
|
+
export { generateFeed, type FeedSite } from './generate/feed.js';
|
package/dist/index.js
CHANGED
|
@@ -23,6 +23,12 @@ export { default as FileTree } from './components/docs/file-tree.svelte';
|
|
|
23
23
|
export { default as FileTreeItem } from './components/docs/file-tree-item.svelte';
|
|
24
24
|
export { default as PropsTable } from './components/docs/props-table.svelte';
|
|
25
25
|
export { default as Prop } from './components/docs/prop.svelte';
|
|
26
|
+
// Landing page sections (for the marketing page in front of your docs)
|
|
27
|
+
export { default as Hero } from './components/landing/hero.svelte';
|
|
28
|
+
export { default as FeatureGrid } from './components/landing/feature-grid.svelte';
|
|
29
|
+
export { default as Feature } from './components/landing/feature.svelte';
|
|
30
|
+
export { default as CTA } from './components/landing/cta.svelte';
|
|
31
|
+
export { default as Action } from './components/landing/action.svelte';
|
|
26
32
|
// Config
|
|
27
33
|
export { defineConfig } from './core/index.js';
|
|
28
34
|
// Search engine (framework-agnostic; for building a custom search UI over the
|
|
@@ -33,3 +39,6 @@ export { generateSitemap } from './generate/sitemap.js';
|
|
|
33
39
|
// llms.txt (framework-agnostic; wire into `llms.txt/+server.ts` and
|
|
34
40
|
// `llms-full.txt/+server.ts` over the generated `svelte-docsmith/llms` index)
|
|
35
41
|
export { generateLlmsTxt, generateLlmsFullTxt } from './generate/llms.js';
|
|
42
|
+
// Changelog (the generated `svelte-docsmith/changelog` index, plus its feed)
|
|
43
|
+
export { default as Changelog } from './components/changelog/changelog.svelte';
|
|
44
|
+
export { generateFeed } from './generate/feed.js';
|