@aureuma/svelta 0.1.1 → 0.4.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/README.md +66 -1
- package/package.json +21 -8
- package/packages/core/dist/appearance/AppearanceSwitcher.svelte +58 -0
- package/packages/core/dist/appearance/AppearanceSwitcher.svelte.d.ts +21 -0
- package/packages/core/dist/appearance/index.d.ts +3 -0
- package/packages/core/dist/appearance/index.js +3 -0
- package/packages/core/dist/appearance/palettes.d.ts +10 -0
- package/packages/core/dist/appearance/palettes.js +36 -0
- package/packages/core/dist/appearance/store.d.ts +20 -0
- package/packages/core/dist/appearance/store.js +87 -0
- package/packages/core/dist/components/blog/BackLink.svelte +7 -4
- package/packages/core/dist/components/blog/BlogCard.svelte +23 -10
- package/packages/core/dist/components/blog/BlogHeroCard.svelte +31 -15
- package/packages/core/dist/components/blog/Container.svelte +3 -2
- package/packages/core/dist/components/blog/Container.svelte.d.ts +1 -1
- package/packages/core/dist/components/blog/MorePosts.svelte +1 -1
- package/packages/core/dist/components/blog/ShareButtons.svelte +81 -47
- package/packages/core/dist/components/blog/ShareButtons.svelte.d.ts +2 -0
- package/packages/core/dist/components/blog/TagTabs.svelte +71 -18
- package/packages/core/dist/components/blog/TagTabs.svelte.d.ts +4 -2
- package/packages/core/dist/components/docs/DocsPager.svelte +28 -0
- package/packages/core/dist/{theme/ThemeSwitcher.svelte.d.ts → components/docs/DocsPager.svelte.d.ts} +6 -5
- package/packages/core/dist/components/docs/DocsSectionGrid.svelte +26 -0
- package/packages/core/dist/components/docs/DocsSectionGrid.svelte.d.ts +21 -0
- package/packages/core/dist/components/docs/DocsShell.svelte +19 -0
- package/packages/core/dist/components/docs/DocsShell.svelte.d.ts +31 -0
- package/packages/core/dist/components/docs/DocsSidebar.svelte +31 -0
- package/packages/core/dist/components/docs/DocsSidebar.svelte.d.ts +22 -0
- package/packages/core/dist/experience/index.d.ts +9 -0
- package/packages/core/dist/experience/index.js +45 -0
- package/packages/core/dist/experience/patterns.d.ts +26 -0
- package/packages/core/dist/experience/patterns.js +98 -0
- package/packages/core/dist/index.d.ts +10 -1
- package/packages/core/dist/index.js +6 -0
- package/packages/core/dist/server/blog.d.ts +14 -1
- package/packages/core/dist/server/blog.js +144 -14
- package/packages/core/dist/server/docs.d.ts +64 -0
- package/packages/core/dist/server/docs.js +309 -0
- package/packages/core/dist/server/index.d.ts +1 -0
- package/packages/core/dist/server/index.js +1 -0
- package/packages/core/dist/types/blog.d.ts +14 -0
- package/packages/core/dist/types/docs.d.ts +28 -0
- package/packages/core/dist/types/docs.js +1 -0
- package/packages/core/dist/types/experience.d.ts +63 -0
- package/packages/core/dist/types/experience.js +1 -0
- package/packages/core/dist/theme/ThemeSwitcher.svelte +0 -34
- package/packages/core/dist/theme/index.d.ts +0 -2
- package/packages/core/dist/theme/index.js +0 -2
- package/packages/core/dist/theme/store.d.ts +0 -12
- package/packages/core/dist/theme/store.js +0 -50
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { DEV } from 'esm-env';
|
|
2
|
+
import matter from 'gray-matter';
|
|
3
|
+
import { marked } from 'marked';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
const docsFrontmatterSchema = z.object({
|
|
6
|
+
title: z.string(),
|
|
7
|
+
navTitle: z.string().optional(),
|
|
8
|
+
description: z.string().optional(),
|
|
9
|
+
section: z.string().optional(),
|
|
10
|
+
sectionLabel: z.string().optional(),
|
|
11
|
+
order: z.number().optional(),
|
|
12
|
+
sectionOrder: z.number().optional(),
|
|
13
|
+
tags: z.array(z.string()).optional(),
|
|
14
|
+
updatedAt: z.string().optional(),
|
|
15
|
+
draft: z.boolean().optional()
|
|
16
|
+
});
|
|
17
|
+
function slugify(input) {
|
|
18
|
+
return input
|
|
19
|
+
.toLowerCase()
|
|
20
|
+
.trim()
|
|
21
|
+
.replace(/['"]/g, '')
|
|
22
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
23
|
+
.replace(/^-+|-+$/g, '');
|
|
24
|
+
}
|
|
25
|
+
const fmtLong = new Intl.DateTimeFormat('en-US', {
|
|
26
|
+
month: 'long',
|
|
27
|
+
day: 'numeric',
|
|
28
|
+
year: 'numeric',
|
|
29
|
+
timeZone: 'UTC'
|
|
30
|
+
});
|
|
31
|
+
function parseUpdatedAt(date) {
|
|
32
|
+
if (!date)
|
|
33
|
+
return { iso: undefined, long: undefined };
|
|
34
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
|
35
|
+
const d = new Date(`${date}T00:00:00Z`);
|
|
36
|
+
if (!Number.isNaN(d.getTime())) {
|
|
37
|
+
return { iso: date, long: fmtLong.format(d) };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const d = new Date(date);
|
|
41
|
+
if (Number.isNaN(d.getTime()))
|
|
42
|
+
return { iso: undefined, long: undefined };
|
|
43
|
+
return { iso: d.toISOString(), long: fmtLong.format(d) };
|
|
44
|
+
}
|
|
45
|
+
function sectionFromMeta(meta, defaultSectionLabel, sectionOrder) {
|
|
46
|
+
const label = (meta.sectionLabel || meta.section || defaultSectionLabel).trim();
|
|
47
|
+
const id = slugify(meta.section || label || defaultSectionLabel);
|
|
48
|
+
const configuredOrder = sectionOrder.indexOf(id);
|
|
49
|
+
const order = meta.sectionOrder ?? (configuredOrder === -1 ? 1000 : configuredOrder);
|
|
50
|
+
return { id, label, order };
|
|
51
|
+
}
|
|
52
|
+
export function createDocs(config) {
|
|
53
|
+
const defaultSectionLabel = config.defaultSectionLabel ?? 'Guides';
|
|
54
|
+
const sectionOrder = config.sectionOrder ?? ['overview', 'getting-started', 'guides', 'api', 'reference'];
|
|
55
|
+
let cachedMetaIndex = null;
|
|
56
|
+
let cachedFullIndex = null;
|
|
57
|
+
let cachedSlugToPath = null;
|
|
58
|
+
function getSlugToPath() {
|
|
59
|
+
if (!DEV && cachedSlugToPath)
|
|
60
|
+
return cachedSlugToPath;
|
|
61
|
+
const map = new Map();
|
|
62
|
+
for (const path of Object.keys(config.rawModules).sort()) {
|
|
63
|
+
const file = path.split('/').pop();
|
|
64
|
+
const slug = file?.replace(/\.md(?:\?.*)?$/, '');
|
|
65
|
+
if (!slug)
|
|
66
|
+
continue;
|
|
67
|
+
map.set(slug, path);
|
|
68
|
+
}
|
|
69
|
+
if (!DEV)
|
|
70
|
+
cachedSlugToPath = map;
|
|
71
|
+
return map;
|
|
72
|
+
}
|
|
73
|
+
async function buildMetaIndex() {
|
|
74
|
+
if (!DEV && cachedMetaIndex)
|
|
75
|
+
return cachedMetaIndex;
|
|
76
|
+
const pages = [];
|
|
77
|
+
const paths = Object.keys(config.rawModules).sort();
|
|
78
|
+
for (const path of paths) {
|
|
79
|
+
const file = path.split('/').pop();
|
|
80
|
+
const slug = file?.replace(/\.md(?:\?.*)?$/, '');
|
|
81
|
+
if (!slug)
|
|
82
|
+
continue;
|
|
83
|
+
const rawFn = config.rawModules[path];
|
|
84
|
+
if (!rawFn)
|
|
85
|
+
continue;
|
|
86
|
+
const raw = await rawFn();
|
|
87
|
+
const { data, content } = matter(raw);
|
|
88
|
+
const meta = config.mapFrontmatter
|
|
89
|
+
? config.mapFrontmatter({ data, content, slug, path })
|
|
90
|
+
: docsFrontmatterSchema.parse(data);
|
|
91
|
+
if (meta.draft)
|
|
92
|
+
continue;
|
|
93
|
+
const section = sectionFromMeta(meta, defaultSectionLabel, sectionOrder);
|
|
94
|
+
const updated = parseUpdatedAt(meta.updatedAt);
|
|
95
|
+
pages.push({
|
|
96
|
+
slug,
|
|
97
|
+
title: meta.title.trim(),
|
|
98
|
+
navTitle: (meta.navTitle || meta.title).trim(),
|
|
99
|
+
description: (meta.description || '').trim(),
|
|
100
|
+
section,
|
|
101
|
+
order: meta.order ?? 1000,
|
|
102
|
+
tags: meta.tags ?? [],
|
|
103
|
+
updatedAt: updated.iso,
|
|
104
|
+
updatedAtLong: updated.long
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
pages.sort((a, b) => {
|
|
108
|
+
if (a.section.order !== b.section.order)
|
|
109
|
+
return a.section.order - b.section.order;
|
|
110
|
+
if (a.section.id !== b.section.id)
|
|
111
|
+
return a.section.id.localeCompare(b.section.id);
|
|
112
|
+
if (a.order !== b.order)
|
|
113
|
+
return a.order - b.order;
|
|
114
|
+
return a.title.localeCompare(b.title);
|
|
115
|
+
});
|
|
116
|
+
if (!DEV)
|
|
117
|
+
cachedMetaIndex = pages;
|
|
118
|
+
return pages;
|
|
119
|
+
}
|
|
120
|
+
async function getAllPages() {
|
|
121
|
+
return buildMetaIndex();
|
|
122
|
+
}
|
|
123
|
+
async function getAllPagesFull() {
|
|
124
|
+
if (!DEV && cachedFullIndex)
|
|
125
|
+
return cachedFullIndex;
|
|
126
|
+
const meta = await buildMetaIndex();
|
|
127
|
+
const slugToPath = getSlugToPath();
|
|
128
|
+
const full = [];
|
|
129
|
+
for (const page of meta) {
|
|
130
|
+
const path = slugToPath.get(page.slug);
|
|
131
|
+
const compiledFn = path ? config.compiledModules[path] : undefined;
|
|
132
|
+
if (!compiledFn)
|
|
133
|
+
continue;
|
|
134
|
+
const compiled = await compiledFn();
|
|
135
|
+
full.push({ ...page, component: compiled.default });
|
|
136
|
+
}
|
|
137
|
+
if (!DEV)
|
|
138
|
+
cachedFullIndex = full;
|
|
139
|
+
return full;
|
|
140
|
+
}
|
|
141
|
+
async function getPageBySlug(slug) {
|
|
142
|
+
const page = (await getAllPages()).find((p) => p.slug === slug) ?? null;
|
|
143
|
+
if (!page)
|
|
144
|
+
return null;
|
|
145
|
+
const path = getSlugToPath().get(slug);
|
|
146
|
+
const compiledFn = path ? config.compiledModules[path] : undefined;
|
|
147
|
+
if (!compiledFn)
|
|
148
|
+
return null;
|
|
149
|
+
const compiled = await compiledFn();
|
|
150
|
+
return { ...page, component: compiled.default };
|
|
151
|
+
}
|
|
152
|
+
async function getSections() {
|
|
153
|
+
const pages = await getAllPages();
|
|
154
|
+
const map = new Map();
|
|
155
|
+
for (const page of pages) {
|
|
156
|
+
map.set(page.section.id, page.section);
|
|
157
|
+
}
|
|
158
|
+
return Array.from(map.values()).sort((a, b) => {
|
|
159
|
+
if (a.order !== b.order)
|
|
160
|
+
return a.order - b.order;
|
|
161
|
+
return a.label.localeCompare(b.label);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
async function getSidebar() {
|
|
165
|
+
const [sections, pages] = await Promise.all([getSections(), getAllPages()]);
|
|
166
|
+
return sections.map((section) => ({
|
|
167
|
+
...section,
|
|
168
|
+
pages: pages.filter((page) => page.section.id === section.id)
|
|
169
|
+
}));
|
|
170
|
+
}
|
|
171
|
+
async function getAdjacentPages(slug) {
|
|
172
|
+
const pages = await getAllPages();
|
|
173
|
+
const index = pages.findIndex((page) => page.slug === slug);
|
|
174
|
+
if (index === -1)
|
|
175
|
+
return { previous: null, next: null };
|
|
176
|
+
return {
|
|
177
|
+
previous: pages[index - 1] ?? null,
|
|
178
|
+
next: pages[index + 1] ?? null
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
async function pickLandingPage() {
|
|
182
|
+
const pages = await getAllPages();
|
|
183
|
+
return pages[0] ?? null;
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
getAllPages,
|
|
187
|
+
getAllPagesFull,
|
|
188
|
+
getPageBySlug,
|
|
189
|
+
getSections,
|
|
190
|
+
getSidebar,
|
|
191
|
+
getAdjacentPages,
|
|
192
|
+
pickLandingPage
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
const defaultRenderMarkdown = (markdown) => String(marked.parse(markdown));
|
|
196
|
+
export function createRawDocs(config) {
|
|
197
|
+
const defaultSectionLabel = config.defaultSectionLabel ?? 'Guides';
|
|
198
|
+
const sectionOrder = config.sectionOrder ?? ['overview', 'getting-started', 'guides', 'api', 'reference'];
|
|
199
|
+
const renderMarkdown = config.renderMarkdown ?? defaultRenderMarkdown;
|
|
200
|
+
let cachedContentIndex = null;
|
|
201
|
+
async function buildContentIndex() {
|
|
202
|
+
if (!DEV && cachedContentIndex)
|
|
203
|
+
return cachedContentIndex;
|
|
204
|
+
const pages = [];
|
|
205
|
+
const paths = Object.keys(config.rawModules).sort();
|
|
206
|
+
for (const path of paths) {
|
|
207
|
+
const file = path.split('/').pop();
|
|
208
|
+
const slug = file?.replace(/\.md(?:\?.*)?$/, '');
|
|
209
|
+
if (!slug)
|
|
210
|
+
continue;
|
|
211
|
+
const rawFn = config.rawModules[path];
|
|
212
|
+
if (!rawFn)
|
|
213
|
+
continue;
|
|
214
|
+
const raw = await rawFn();
|
|
215
|
+
const { data, content } = matter(raw);
|
|
216
|
+
const meta = config.mapFrontmatter
|
|
217
|
+
? config.mapFrontmatter({ data, content, slug, path })
|
|
218
|
+
: docsFrontmatterSchema.parse(data);
|
|
219
|
+
if (meta.draft)
|
|
220
|
+
continue;
|
|
221
|
+
const section = sectionFromMeta(meta, defaultSectionLabel, sectionOrder);
|
|
222
|
+
const updated = parseUpdatedAt(meta.updatedAt);
|
|
223
|
+
const html = await renderMarkdown(content);
|
|
224
|
+
pages.push({
|
|
225
|
+
slug,
|
|
226
|
+
title: meta.title.trim(),
|
|
227
|
+
navTitle: (meta.navTitle || meta.title).trim(),
|
|
228
|
+
description: (meta.description || '').trim(),
|
|
229
|
+
section,
|
|
230
|
+
order: meta.order ?? 1000,
|
|
231
|
+
tags: meta.tags ?? [],
|
|
232
|
+
updatedAt: updated.iso,
|
|
233
|
+
updatedAtLong: updated.long,
|
|
234
|
+
html,
|
|
235
|
+
raw: content,
|
|
236
|
+
frontmatter: typeof data === 'object' && data ? data : {}
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
pages.sort((a, b) => {
|
|
240
|
+
if (a.section.order !== b.section.order)
|
|
241
|
+
return a.section.order - b.section.order;
|
|
242
|
+
if (a.section.id !== b.section.id)
|
|
243
|
+
return a.section.id.localeCompare(b.section.id);
|
|
244
|
+
if (a.order !== b.order)
|
|
245
|
+
return a.order - b.order;
|
|
246
|
+
return a.title.localeCompare(b.title);
|
|
247
|
+
});
|
|
248
|
+
if (!DEV)
|
|
249
|
+
cachedContentIndex = pages;
|
|
250
|
+
return pages;
|
|
251
|
+
}
|
|
252
|
+
function stripContent(page) {
|
|
253
|
+
const { html: _html, raw: _raw, frontmatter: _frontmatter, ...meta } = page;
|
|
254
|
+
return meta;
|
|
255
|
+
}
|
|
256
|
+
async function getAllPages() {
|
|
257
|
+
const pages = await buildContentIndex();
|
|
258
|
+
return pages.map(stripContent);
|
|
259
|
+
}
|
|
260
|
+
async function getAllPagesWithContent() {
|
|
261
|
+
return buildContentIndex();
|
|
262
|
+
}
|
|
263
|
+
async function getPageBySlug(slug) {
|
|
264
|
+
const pages = await buildContentIndex();
|
|
265
|
+
return pages.find((page) => page.slug === slug) ?? null;
|
|
266
|
+
}
|
|
267
|
+
async function getSections() {
|
|
268
|
+
const pages = await getAllPages();
|
|
269
|
+
const map = new Map();
|
|
270
|
+
for (const page of pages) {
|
|
271
|
+
map.set(page.section.id, page.section);
|
|
272
|
+
}
|
|
273
|
+
return Array.from(map.values()).sort((a, b) => {
|
|
274
|
+
if (a.order !== b.order)
|
|
275
|
+
return a.order - b.order;
|
|
276
|
+
return a.label.localeCompare(b.label);
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
async function getSidebar() {
|
|
280
|
+
const [sections, pages] = await Promise.all([getSections(), getAllPages()]);
|
|
281
|
+
return sections.map((section) => ({
|
|
282
|
+
...section,
|
|
283
|
+
pages: pages.filter((page) => page.section.id === section.id)
|
|
284
|
+
}));
|
|
285
|
+
}
|
|
286
|
+
async function getAdjacentPages(slug) {
|
|
287
|
+
const pages = await getAllPages();
|
|
288
|
+
const index = pages.findIndex((page) => page.slug === slug);
|
|
289
|
+
if (index === -1)
|
|
290
|
+
return { previous: null, next: null };
|
|
291
|
+
return {
|
|
292
|
+
previous: pages[index - 1] ?? null,
|
|
293
|
+
next: pages[index + 1] ?? null
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
async function pickLandingPage() {
|
|
297
|
+
const pages = await getAllPages();
|
|
298
|
+
return pages[0] ?? null;
|
|
299
|
+
}
|
|
300
|
+
return {
|
|
301
|
+
getAllPages,
|
|
302
|
+
getAllPagesWithContent,
|
|
303
|
+
getPageBySlug,
|
|
304
|
+
getSections,
|
|
305
|
+
getSidebar,
|
|
306
|
+
getAdjacentPages,
|
|
307
|
+
pickLandingPage
|
|
308
|
+
};
|
|
309
|
+
}
|
|
@@ -1 +1,2 @@
|
|
|
1
1
|
export { createBlog, createRawBlog, parseMarkdownAuthorMap, parseVivaAuthorFrontmatter, parseVivaAuthorProfiles, parseVivaBlogFrontmatter, type BlogCreateConfig, type RawBlogCreateConfig, type MarkdownRenderer, type VivaAuthorFrontmatter, type VivaAuthorProfile, type VivaBlogFrontmatter, type VivaImageAsset, type VivaSeoFields } from './blog';
|
|
2
|
+
export { createDocs, createRawDocs, type DocsCreateConfig, type RawDocsCreateConfig, type DocsFrontmatter, type DocsFrontmatterAdapter, type DocsMarkdownRenderer } from './docs';
|
|
@@ -33,6 +33,20 @@ export type BlogTag = {
|
|
|
33
33
|
name: string;
|
|
34
34
|
slug: string;
|
|
35
35
|
};
|
|
36
|
+
export type SharePlatform = 'x' | 'linkedin' | 'facebook' | 'reddit' | 'hackernews' | 'copy';
|
|
37
|
+
export type BlogArchiveGroup = {
|
|
38
|
+
id: string;
|
|
39
|
+
year: number;
|
|
40
|
+
month: number;
|
|
41
|
+
label: string;
|
|
42
|
+
count: number;
|
|
43
|
+
posts: BlogPost[];
|
|
44
|
+
};
|
|
45
|
+
export type BlogAuthorSummary = {
|
|
46
|
+
author: BlogAuthor;
|
|
47
|
+
postCount: number;
|
|
48
|
+
latestPostDate: string;
|
|
49
|
+
};
|
|
36
50
|
export type BlogPostWithContent = BlogPost & {
|
|
37
51
|
html: string;
|
|
38
52
|
raw: string;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ComponentType } from 'svelte';
|
|
2
|
+
export type DocsSection = {
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
order: number;
|
|
6
|
+
};
|
|
7
|
+
export type DocsPage = {
|
|
8
|
+
slug: string;
|
|
9
|
+
title: string;
|
|
10
|
+
navTitle: string;
|
|
11
|
+
description: string;
|
|
12
|
+
section: DocsSection;
|
|
13
|
+
order: number;
|
|
14
|
+
tags: string[];
|
|
15
|
+
updatedAt?: string;
|
|
16
|
+
updatedAtLong?: string;
|
|
17
|
+
};
|
|
18
|
+
export type DocsPageFull = DocsPage & {
|
|
19
|
+
component: ComponentType;
|
|
20
|
+
};
|
|
21
|
+
export type DocsPageWithContent = DocsPage & {
|
|
22
|
+
html: string;
|
|
23
|
+
raw: string;
|
|
24
|
+
frontmatter: Record<string, unknown>;
|
|
25
|
+
};
|
|
26
|
+
export type DocsSidebarSection = DocsSection & {
|
|
27
|
+
pages: DocsPage[];
|
|
28
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export type ExperienceKind = 'docs' | 'blog';
|
|
2
|
+
export type ExperienceDefinition = {
|
|
3
|
+
kind: ExperienceKind;
|
|
4
|
+
label: string;
|
|
5
|
+
description: string;
|
|
6
|
+
href: string;
|
|
7
|
+
};
|
|
8
|
+
export type ExperienceCatalog = {
|
|
9
|
+
defaultKind: ExperienceKind;
|
|
10
|
+
items: ExperienceDefinition[];
|
|
11
|
+
byKind: Record<ExperienceKind, ExperienceDefinition>;
|
|
12
|
+
resolve: (kind?: string | null) => ExperienceDefinition;
|
|
13
|
+
};
|
|
14
|
+
export type SveltaNavItem = {
|
|
15
|
+
label: string;
|
|
16
|
+
href: string;
|
|
17
|
+
external?: boolean;
|
|
18
|
+
};
|
|
19
|
+
export type SveltaDocsPatternConfig = {
|
|
20
|
+
kind: 'docs';
|
|
21
|
+
brandName: string;
|
|
22
|
+
productName: string;
|
|
23
|
+
title: string;
|
|
24
|
+
description: string;
|
|
25
|
+
defaultSectionLabel: string;
|
|
26
|
+
sectionOrder: string[];
|
|
27
|
+
navigation: {
|
|
28
|
+
header: SveltaNavItem[];
|
|
29
|
+
footer: SveltaNavItem[];
|
|
30
|
+
};
|
|
31
|
+
search: {
|
|
32
|
+
enabled: boolean;
|
|
33
|
+
placeholder: string;
|
|
34
|
+
shortcut: string;
|
|
35
|
+
};
|
|
36
|
+
toc: {
|
|
37
|
+
enabled: boolean;
|
|
38
|
+
title: string;
|
|
39
|
+
};
|
|
40
|
+
feedback: {
|
|
41
|
+
enabled: boolean;
|
|
42
|
+
prompt: string;
|
|
43
|
+
};
|
|
44
|
+
editLinkTemplate: string;
|
|
45
|
+
};
|
|
46
|
+
export type SveltaBlogPatternConfig = {
|
|
47
|
+
kind: 'blog';
|
|
48
|
+
brandName: string;
|
|
49
|
+
title: string;
|
|
50
|
+
description: string;
|
|
51
|
+
pageSize: number;
|
|
52
|
+
maxPageSize: number;
|
|
53
|
+
infiniteScroll: boolean;
|
|
54
|
+
showRss: boolean;
|
|
55
|
+
navigation: {
|
|
56
|
+
header: SveltaNavItem[];
|
|
57
|
+
footer: SveltaNavItem[];
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
export type SveltaPatternConfig = {
|
|
61
|
+
docs: SveltaDocsPatternConfig;
|
|
62
|
+
blog: SveltaBlogPatternConfig;
|
|
63
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { ThemeController, ThemeMode } from './store';
|
|
3
|
-
|
|
4
|
-
export let controller: ThemeController;
|
|
5
|
-
|
|
6
|
-
const options: { id: ThemeMode; label: string }[] = [
|
|
7
|
-
{ id: 'system', label: 'System' },
|
|
8
|
-
{ id: 'light', label: 'Light' },
|
|
9
|
-
{ id: 'dark', label: 'Dark' }
|
|
10
|
-
];
|
|
11
|
-
|
|
12
|
-
// Store auto-subscriptions only work on identifiers, so alias it.
|
|
13
|
-
const themeMode = controller.themeMode;
|
|
14
|
-
$: currentMode = $themeMode;
|
|
15
|
-
</script>
|
|
16
|
-
|
|
17
|
-
<div class="flex items-center gap-2">
|
|
18
|
-
<span class="text-xs font-mono uppercase tracking-[0.6px] text-text-muted">Theme</span>
|
|
19
|
-
<div class="inline-flex rounded-full border border-border-soft/10 bg-background-soft p-1">
|
|
20
|
-
{#each options as opt (opt.id)}
|
|
21
|
-
<button
|
|
22
|
-
type="button"
|
|
23
|
-
class="rounded-full px-3 py-1 text-xs font-mono uppercase tracking-[0.6px] transition
|
|
24
|
-
hover:bg-background-main/60
|
|
25
|
-
{(currentMode === opt.id && 'bg-background-main shadow-sm') || 'text-text-sub'}"
|
|
26
|
-
onclick={() => controller.setThemeMode(opt.id)}
|
|
27
|
-
aria-pressed={currentMode === opt.id}
|
|
28
|
-
>
|
|
29
|
-
{opt.label}
|
|
30
|
-
</button>
|
|
31
|
-
{/each}
|
|
32
|
-
</div>
|
|
33
|
-
</div>
|
|
34
|
-
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { type Writable } from 'svelte/store';
|
|
2
|
-
export type ThemeMode = 'system' | 'light' | 'dark';
|
|
3
|
-
export type ThemeController = {
|
|
4
|
-
storageKey: string;
|
|
5
|
-
themeMode: Writable<ThemeMode>;
|
|
6
|
-
initTheme: () => void | (() => void);
|
|
7
|
-
setThemeMode: (mode: ThemeMode) => void;
|
|
8
|
-
};
|
|
9
|
-
export declare function createThemeController(opts?: {
|
|
10
|
-
storageKey?: string;
|
|
11
|
-
defaultMode?: ThemeMode;
|
|
12
|
-
}): ThemeController;
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { BROWSER } from 'esm-env';
|
|
2
|
-
import { writable } from 'svelte/store';
|
|
3
|
-
function resolve(mode) {
|
|
4
|
-
if (mode === 'light' || mode === 'dark')
|
|
5
|
-
return mode;
|
|
6
|
-
const prefersDark = window.matchMedia?.('(prefers-color-scheme: dark)')?.matches ?? false;
|
|
7
|
-
return prefersDark ? 'dark' : 'light';
|
|
8
|
-
}
|
|
9
|
-
function apply(mode) {
|
|
10
|
-
const resolved = resolve(mode);
|
|
11
|
-
document.documentElement.classList.remove('light', 'dark');
|
|
12
|
-
document.documentElement.classList.add(resolved);
|
|
13
|
-
document.documentElement.dataset.theme = mode;
|
|
14
|
-
}
|
|
15
|
-
export function createThemeController(opts) {
|
|
16
|
-
const storageKey = opts?.storageKey ?? 'svelta-theme';
|
|
17
|
-
const defaultMode = opts?.defaultMode ?? 'system';
|
|
18
|
-
const themeMode = writable(defaultMode);
|
|
19
|
-
function readStored() {
|
|
20
|
-
const v = localStorage.getItem(storageKey);
|
|
21
|
-
if (v === 'light' || v === 'dark' || v === 'system')
|
|
22
|
-
return v;
|
|
23
|
-
return defaultMode;
|
|
24
|
-
}
|
|
25
|
-
function initTheme() {
|
|
26
|
-
if (!BROWSER)
|
|
27
|
-
return;
|
|
28
|
-
const mode = readStored();
|
|
29
|
-
themeMode.set(mode);
|
|
30
|
-
apply(mode);
|
|
31
|
-
const mq = window.matchMedia?.('(prefers-color-scheme: dark)');
|
|
32
|
-
const onChange = () => {
|
|
33
|
-
let current = mode;
|
|
34
|
-
const unsub = themeMode.subscribe((v) => (current = v));
|
|
35
|
-
unsub();
|
|
36
|
-
if (current === 'system')
|
|
37
|
-
apply('system');
|
|
38
|
-
};
|
|
39
|
-
mq?.addEventListener?.('change', onChange);
|
|
40
|
-
return () => mq?.removeEventListener?.('change', onChange);
|
|
41
|
-
}
|
|
42
|
-
function setThemeMode(mode) {
|
|
43
|
-
themeMode.set(mode);
|
|
44
|
-
if (!BROWSER)
|
|
45
|
-
return;
|
|
46
|
-
localStorage.setItem(storageKey, mode);
|
|
47
|
-
apply(mode);
|
|
48
|
-
}
|
|
49
|
-
return { storageKey, themeMode, initTheme, setThemeMode };
|
|
50
|
-
}
|