@aureuma/svelta 0.1.0 → 0.2.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 +22 -2
- package/package.json +31 -19
- package/packages/{blogkit → core}/CHANGELOG.md +1 -1
- package/packages/core/README.md +11 -0
- 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 +26 -0
- package/packages/core/dist/components/blog/BlogCard.svelte +50 -0
- package/packages/core/dist/components/blog/BlogHeroCard.svelte +52 -0
- package/packages/core/dist/components/blog/Container.svelte +9 -0
- package/packages/{blogkit → core}/dist/components/blog/Container.svelte.d.ts +1 -1
- package/packages/{blogkit → core}/dist/components/blog/MorePosts.svelte +1 -1
- package/packages/core/dist/components/blog/ShareButtons.svelte +147 -0
- package/packages/{blogkit → core}/dist/components/blog/ShareButtons.svelte.d.ts +2 -0
- package/packages/core/dist/components/blog/TagTabs.svelte +85 -0
- package/packages/{blogkit → core}/dist/components/blog/TagTabs.svelte.d.ts +4 -2
- package/packages/core/dist/components/docs/DocsPager.svelte +28 -0
- package/packages/core/dist/components/docs/DocsPager.svelte.d.ts +22 -0
- 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 +7 -0
- package/packages/core/dist/experience/index.js +43 -0
- package/packages/core/dist/index.d.ts +18 -0
- package/packages/{blogkit → core}/dist/index.js +5 -0
- package/packages/{blogkit → core}/dist/server/blog.d.ts +14 -1
- package/packages/{blogkit → core}/dist/server/blog.js +144 -14
- package/packages/core/dist/server/docs.d.ts +44 -0
- package/packages/core/dist/server/docs.js +193 -0
- package/packages/{blogkit → core}/dist/server/index.d.ts +1 -0
- package/packages/{blogkit → core}/dist/server/index.js +1 -0
- package/packages/{blogkit → 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 +13 -0
- package/packages/core/dist/types/experience.js +1 -0
- package/packages/blogkit/README.md +0 -93
- package/packages/blogkit/dist/components/blog/BackLink.svelte +0 -23
- package/packages/blogkit/dist/components/blog/BlogCard.svelte +0 -37
- package/packages/blogkit/dist/components/blog/BlogHeroCard.svelte +0 -36
- package/packages/blogkit/dist/components/blog/Container.svelte +0 -8
- package/packages/blogkit/dist/components/blog/ShareButtons.svelte +0 -113
- package/packages/blogkit/dist/components/blog/TagTabs.svelte +0 -32
- package/packages/blogkit/dist/index.d.ts +0 -11
- package/packages/blogkit/dist/theme/ThemeSwitcher.svelte +0 -34
- package/packages/blogkit/dist/theme/ThemeSwitcher.svelte.d.ts +0 -21
- package/packages/blogkit/dist/theme/index.d.ts +0 -2
- package/packages/blogkit/dist/theme/index.js +0 -2
- package/packages/blogkit/dist/theme/store.d.ts +0 -12
- package/packages/blogkit/dist/theme/store.js +0 -50
- /package/packages/{blogkit → core}/LICENSE +0 -0
- /package/packages/{blogkit → core}/dist/components/blog/Avatar.svelte +0 -0
- /package/packages/{blogkit → core}/dist/components/blog/Avatar.svelte.d.ts +0 -0
- /package/packages/{blogkit → core}/dist/components/blog/BackLink.svelte.d.ts +0 -0
- /package/packages/{blogkit → core}/dist/components/blog/BlogCard.svelte.d.ts +0 -0
- /package/packages/{blogkit → core}/dist/components/blog/BlogHeroCard.svelte.d.ts +0 -0
- /package/packages/{blogkit → core}/dist/components/blog/ImageLightbox.svelte +0 -0
- /package/packages/{blogkit → core}/dist/components/blog/ImageLightbox.svelte.d.ts +0 -0
- /package/packages/{blogkit → core}/dist/components/blog/MorePosts.svelte.d.ts +0 -0
- /package/packages/{blogkit → core}/dist/components/blog/SummaryCard.svelte +0 -0
- /package/packages/{blogkit → core}/dist/components/blog/SummaryCard.svelte.d.ts +0 -0
- /package/packages/{blogkit → core}/dist/types/blog.js +0 -0
|
@@ -78,7 +78,7 @@ function normalizeCategory(label) {
|
|
|
78
78
|
}
|
|
79
79
|
function parseISODate(date) {
|
|
80
80
|
// Prefer stable UTC parsing for YYYY-MM-DD.
|
|
81
|
-
if (
|
|
81
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
|
82
82
|
const d = new Date(`${date}T00:00:00Z`);
|
|
83
83
|
if (!Number.isNaN(d.getTime()))
|
|
84
84
|
return d;
|
|
@@ -100,6 +100,11 @@ const fmtShort = new Intl.DateTimeFormat('en-US', {
|
|
|
100
100
|
year: 'numeric',
|
|
101
101
|
timeZone: 'UTC'
|
|
102
102
|
});
|
|
103
|
+
const fmtArchive = new Intl.DateTimeFormat('en-US', {
|
|
104
|
+
month: 'long',
|
|
105
|
+
year: 'numeric',
|
|
106
|
+
timeZone: 'UTC'
|
|
107
|
+
});
|
|
103
108
|
function stripForExcerpt(markdown) {
|
|
104
109
|
return (markdown
|
|
105
110
|
// remove fenced code blocks
|
|
@@ -133,8 +138,14 @@ function minutesToLabels(minutes) {
|
|
|
133
138
|
long: `${m} ${unit} read`
|
|
134
139
|
};
|
|
135
140
|
}
|
|
141
|
+
function normalizeTags(tags, categoryLabel, allowMultipleTags) {
|
|
142
|
+
const cleaned = (tags ?? []).map((tag) => tag.trim()).filter(Boolean);
|
|
143
|
+
const resolved = cleaned.length > 0 ? cleaned : [categoryLabel];
|
|
144
|
+
return allowMultipleTags ? resolved : [resolved[0]];
|
|
145
|
+
}
|
|
136
146
|
export function createBlog(config) {
|
|
137
147
|
const categoryOrder = config.categoryOrder ?? DEFAULT_CATEGORY_ORDER;
|
|
148
|
+
const allowMultipleTags = config.allowMultipleTags ?? false;
|
|
138
149
|
let cachedMetaIndex = null;
|
|
139
150
|
let cachedFullIndex = null;
|
|
140
151
|
let cachedSlugToPath = null;
|
|
@@ -178,13 +189,14 @@ export function createBlog(config) {
|
|
|
178
189
|
const dateObj = parseISODate(metadata.date);
|
|
179
190
|
const rt = minutesToLabels(readingTime(content).minutes);
|
|
180
191
|
const category = normalizeCategory(metadata.category);
|
|
192
|
+
const tags = normalizeTags(metadata.tags, category.label, allowMultipleTags);
|
|
181
193
|
const excerpt = metadata.excerpt?.trim() || excerptFromContent(content);
|
|
182
194
|
posts.push({
|
|
183
195
|
slug,
|
|
184
196
|
title: metadata.title.trim(),
|
|
185
197
|
excerpt,
|
|
186
198
|
category,
|
|
187
|
-
tags
|
|
199
|
+
tags,
|
|
188
200
|
author: config.getAuthor(metadata.author),
|
|
189
201
|
date: metadata.date,
|
|
190
202
|
dateLong: fmtLong.format(dateObj),
|
|
@@ -259,7 +271,121 @@ export function createBlog(config) {
|
|
|
259
271
|
const featured = list.filter((p) => p.featured);
|
|
260
272
|
return (featured[0] ?? list[0]);
|
|
261
273
|
}
|
|
262
|
-
|
|
274
|
+
async function getAllTags() {
|
|
275
|
+
const posts = await getAllPosts();
|
|
276
|
+
const map = new Map();
|
|
277
|
+
for (const post of posts) {
|
|
278
|
+
for (const tagName of post.tags) {
|
|
279
|
+
const tag = toBlogTag(tagName);
|
|
280
|
+
map.set(tag.slug, tag);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return Array.from(map.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
284
|
+
}
|
|
285
|
+
async function getPostsByTag(tagSlug) {
|
|
286
|
+
const posts = await getAllPosts();
|
|
287
|
+
return posts.filter((post) => post.tags.some((tagName) => slugify(tagName) === tagSlug));
|
|
288
|
+
}
|
|
289
|
+
async function getPostsByAuthor(authorId) {
|
|
290
|
+
const posts = await getAllPosts();
|
|
291
|
+
return posts.filter((post) => post.author.id === authorId);
|
|
292
|
+
}
|
|
293
|
+
async function getAllAuthors() {
|
|
294
|
+
const posts = await getAllPosts();
|
|
295
|
+
const map = new Map();
|
|
296
|
+
for (const post of posts)
|
|
297
|
+
map.set(post.author.id, post.author);
|
|
298
|
+
return Array.from(map.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
299
|
+
}
|
|
300
|
+
async function getAuthorSummaries() {
|
|
301
|
+
const posts = await getAllPosts();
|
|
302
|
+
const map = new Map();
|
|
303
|
+
for (const post of posts) {
|
|
304
|
+
const existing = map.get(post.author.id);
|
|
305
|
+
if (!existing) {
|
|
306
|
+
map.set(post.author.id, {
|
|
307
|
+
author: post.author,
|
|
308
|
+
postCount: 1,
|
|
309
|
+
latestPostDate: post.date
|
|
310
|
+
});
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
existing.postCount += 1;
|
|
314
|
+
if (parseISODate(post.date).getTime() > parseISODate(existing.latestPostDate).getTime()) {
|
|
315
|
+
existing.latestPostDate = post.date;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return Array.from(map.values()).sort((a, b) => b.postCount - a.postCount || a.author.name.localeCompare(b.author.name));
|
|
319
|
+
}
|
|
320
|
+
async function getArchiveGroups() {
|
|
321
|
+
const posts = await getAllPosts();
|
|
322
|
+
const map = new Map();
|
|
323
|
+
for (const post of posts) {
|
|
324
|
+
const date = parseISODate(post.date);
|
|
325
|
+
const year = date.getUTCFullYear();
|
|
326
|
+
const month = date.getUTCMonth() + 1;
|
|
327
|
+
const key = `${year}-${String(month).padStart(2, '0')}`;
|
|
328
|
+
const existing = map.get(key);
|
|
329
|
+
if (existing) {
|
|
330
|
+
existing.posts.push(post);
|
|
331
|
+
existing.count += 1;
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
map.set(key, {
|
|
335
|
+
id: key,
|
|
336
|
+
year,
|
|
337
|
+
month,
|
|
338
|
+
label: fmtArchive.format(date),
|
|
339
|
+
count: 1,
|
|
340
|
+
posts: [post]
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
return Array.from(map.values()).sort((a, b) => b.id.localeCompare(a.id));
|
|
344
|
+
}
|
|
345
|
+
async function getAdjacentPosts(slug) {
|
|
346
|
+
const posts = await getAllPosts();
|
|
347
|
+
const index = posts.findIndex((post) => post.slug === slug);
|
|
348
|
+
if (index === -1)
|
|
349
|
+
return { previous: null, next: null };
|
|
350
|
+
return {
|
|
351
|
+
previous: posts[index + 1] ?? null,
|
|
352
|
+
next: posts[index - 1] ?? null
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
async function getRelatedPosts(slug, limit = 3) {
|
|
356
|
+
const posts = await getAllPosts();
|
|
357
|
+
const current = posts.find((post) => post.slug === slug);
|
|
358
|
+
if (!current)
|
|
359
|
+
return [];
|
|
360
|
+
const primaryTag = current.tags[0];
|
|
361
|
+
if (primaryTag) {
|
|
362
|
+
const primaryTagSlug = slugify(primaryTag);
|
|
363
|
+
const sameTag = posts
|
|
364
|
+
.filter((post) => post.slug !== slug && post.tags.some((tagName) => slugify(tagName) === primaryTagSlug))
|
|
365
|
+
.sort((a, b) => parseISODate(b.date).getTime() - parseISODate(a.date).getTime())
|
|
366
|
+
.slice(0, limit);
|
|
367
|
+
if (sameTag.length > 0)
|
|
368
|
+
return sameTag;
|
|
369
|
+
}
|
|
370
|
+
return posts
|
|
371
|
+
.filter((post) => post.slug !== slug && post.category.slug === current.category.slug)
|
|
372
|
+
.slice(0, limit);
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
getAllPosts,
|
|
376
|
+
getAllPostsFull,
|
|
377
|
+
getPostBySlug,
|
|
378
|
+
getCategories,
|
|
379
|
+
pickHero,
|
|
380
|
+
getAllTags,
|
|
381
|
+
getPostsByTag,
|
|
382
|
+
getPostsByAuthor,
|
|
383
|
+
getAllAuthors,
|
|
384
|
+
getAuthorSummaries,
|
|
385
|
+
getArchiveGroups,
|
|
386
|
+
getAdjacentPosts,
|
|
387
|
+
getRelatedPosts
|
|
388
|
+
};
|
|
263
389
|
}
|
|
264
390
|
export function parseVivaBlogFrontmatter(data) {
|
|
265
391
|
return vivaBlogFrontmatterSchema.parse(data);
|
|
@@ -316,6 +442,7 @@ function toBlogTag(name) {
|
|
|
316
442
|
}
|
|
317
443
|
export function createRawBlog(config) {
|
|
318
444
|
const categoryOrder = config.categoryOrder ?? DEFAULT_CATEGORY_ORDER;
|
|
445
|
+
const allowMultipleTags = config.allowMultipleTags ?? false;
|
|
319
446
|
const renderMarkdown = config.renderMarkdown ?? defaultRenderMarkdown;
|
|
320
447
|
let cachedContentIndex = null;
|
|
321
448
|
async function buildContentIndex() {
|
|
@@ -341,6 +468,7 @@ export function createRawBlog(config) {
|
|
|
341
468
|
const dateObj = parseISODate(metadata.date);
|
|
342
469
|
const rt = minutesToLabels(readingTime(content).minutes);
|
|
343
470
|
const category = normalizeCategory(metadata.category);
|
|
471
|
+
const tags = normalizeTags(metadata.tags, category.label, allowMultipleTags);
|
|
344
472
|
const excerpt = metadata.excerpt?.trim() || excerptFromContent(content);
|
|
345
473
|
const rendered = await renderMarkdown(content);
|
|
346
474
|
posts.push({
|
|
@@ -348,7 +476,7 @@ export function createRawBlog(config) {
|
|
|
348
476
|
title: metadata.title.trim(),
|
|
349
477
|
excerpt,
|
|
350
478
|
category,
|
|
351
|
-
tags
|
|
479
|
+
tags,
|
|
352
480
|
author: config.getAuthor(metadata.author),
|
|
353
481
|
authorId: metadata.author,
|
|
354
482
|
date: metadata.date,
|
|
@@ -443,17 +571,19 @@ export function createRawBlog(config) {
|
|
|
443
571
|
const current = posts.find((post) => post.slug === slug);
|
|
444
572
|
if (!current)
|
|
445
573
|
return [];
|
|
446
|
-
const
|
|
574
|
+
const primaryTag = current.tags[0];
|
|
575
|
+
if (primaryTag) {
|
|
576
|
+
const primaryTagSlug = slugify(primaryTag);
|
|
577
|
+
const sameTag = posts
|
|
578
|
+
.filter((post) => post.slug !== slug && post.tags.some((tagName) => slugify(tagName) === primaryTagSlug))
|
|
579
|
+
.sort((a, b) => parseISODate(b.date).getTime() - parseISODate(a.date).getTime())
|
|
580
|
+
.slice(0, limit);
|
|
581
|
+
if (sameTag.length > 0)
|
|
582
|
+
return sameTag;
|
|
583
|
+
}
|
|
447
584
|
return posts
|
|
448
|
-
.filter((post) => post.slug !== slug)
|
|
449
|
-
.
|
|
450
|
-
const overlap = post.tags.filter((tag) => tagSet.has(slugify(tag))).length;
|
|
451
|
-
return { post, overlap };
|
|
452
|
-
})
|
|
453
|
-
.filter((entry) => entry.overlap > 0)
|
|
454
|
-
.sort((a, b) => b.overlap - a.overlap)
|
|
455
|
-
.slice(0, limit)
|
|
456
|
-
.map((entry) => entry.post);
|
|
585
|
+
.filter((post) => post.slug !== slug && post.category.slug === current.category.slug)
|
|
586
|
+
.slice(0, limit);
|
|
457
587
|
}
|
|
458
588
|
return {
|
|
459
589
|
getAllPosts,
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { DocsPage, DocsPageFull, DocsSidebarSection, DocsSection } from '../types/docs';
|
|
3
|
+
declare const docsFrontmatterSchema: z.ZodObject<{
|
|
4
|
+
title: z.ZodString;
|
|
5
|
+
navTitle: z.ZodOptional<z.ZodString>;
|
|
6
|
+
description: z.ZodOptional<z.ZodString>;
|
|
7
|
+
section: z.ZodOptional<z.ZodString>;
|
|
8
|
+
sectionLabel: z.ZodOptional<z.ZodString>;
|
|
9
|
+
order: z.ZodOptional<z.ZodNumber>;
|
|
10
|
+
sectionOrder: z.ZodOptional<z.ZodNumber>;
|
|
11
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
12
|
+
updatedAt: z.ZodOptional<z.ZodString>;
|
|
13
|
+
draft: z.ZodOptional<z.ZodBoolean>;
|
|
14
|
+
}, z.core.$strip>;
|
|
15
|
+
export type DocsFrontmatter = z.infer<typeof docsFrontmatterSchema>;
|
|
16
|
+
export type DocsFrontmatterAdapter = (args: {
|
|
17
|
+
data: unknown;
|
|
18
|
+
content: string;
|
|
19
|
+
slug: string;
|
|
20
|
+
path: string;
|
|
21
|
+
}) => DocsFrontmatter;
|
|
22
|
+
type CompiledModule = {
|
|
23
|
+
default: DocsPageFull['component'];
|
|
24
|
+
};
|
|
25
|
+
export type DocsCreateConfig = {
|
|
26
|
+
compiledModules: Record<string, () => Promise<CompiledModule>>;
|
|
27
|
+
rawModules: Record<string, () => Promise<string>>;
|
|
28
|
+
defaultSectionLabel?: string;
|
|
29
|
+
sectionOrder?: string[];
|
|
30
|
+
mapFrontmatter?: DocsFrontmatterAdapter;
|
|
31
|
+
};
|
|
32
|
+
export declare function createDocs(config: DocsCreateConfig): {
|
|
33
|
+
getAllPages: () => Promise<DocsPage[]>;
|
|
34
|
+
getAllPagesFull: () => Promise<DocsPageFull[]>;
|
|
35
|
+
getPageBySlug: (slug: string) => Promise<DocsPageFull | null>;
|
|
36
|
+
getSections: () => Promise<DocsSection[]>;
|
|
37
|
+
getSidebar: () => Promise<DocsSidebarSection[]>;
|
|
38
|
+
getAdjacentPages: (slug: string) => Promise<{
|
|
39
|
+
previous: DocsPage | null;
|
|
40
|
+
next: DocsPage | null;
|
|
41
|
+
}>;
|
|
42
|
+
pickLandingPage: () => Promise<DocsPage | null>;
|
|
43
|
+
};
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { DEV } from 'esm-env';
|
|
2
|
+
import matter from 'gray-matter';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
const docsFrontmatterSchema = z.object({
|
|
5
|
+
title: z.string(),
|
|
6
|
+
navTitle: z.string().optional(),
|
|
7
|
+
description: z.string().optional(),
|
|
8
|
+
section: z.string().optional(),
|
|
9
|
+
sectionLabel: z.string().optional(),
|
|
10
|
+
order: z.number().optional(),
|
|
11
|
+
sectionOrder: z.number().optional(),
|
|
12
|
+
tags: z.array(z.string()).optional(),
|
|
13
|
+
updatedAt: z.string().optional(),
|
|
14
|
+
draft: z.boolean().optional()
|
|
15
|
+
});
|
|
16
|
+
function slugify(input) {
|
|
17
|
+
return input
|
|
18
|
+
.toLowerCase()
|
|
19
|
+
.trim()
|
|
20
|
+
.replace(/['"]/g, '')
|
|
21
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
22
|
+
.replace(/^-+|-+$/g, '');
|
|
23
|
+
}
|
|
24
|
+
const fmtLong = new Intl.DateTimeFormat('en-US', {
|
|
25
|
+
month: 'long',
|
|
26
|
+
day: 'numeric',
|
|
27
|
+
year: 'numeric',
|
|
28
|
+
timeZone: 'UTC'
|
|
29
|
+
});
|
|
30
|
+
function parseUpdatedAt(date) {
|
|
31
|
+
if (!date)
|
|
32
|
+
return { iso: undefined, long: undefined };
|
|
33
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
|
34
|
+
const d = new Date(`${date}T00:00:00Z`);
|
|
35
|
+
if (!Number.isNaN(d.getTime())) {
|
|
36
|
+
return { iso: date, long: fmtLong.format(d) };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const d = new Date(date);
|
|
40
|
+
if (Number.isNaN(d.getTime()))
|
|
41
|
+
return { iso: undefined, long: undefined };
|
|
42
|
+
return { iso: d.toISOString(), long: fmtLong.format(d) };
|
|
43
|
+
}
|
|
44
|
+
function sectionFromMeta(meta, defaultSectionLabel, sectionOrder) {
|
|
45
|
+
const label = (meta.sectionLabel || meta.section || defaultSectionLabel).trim();
|
|
46
|
+
const id = slugify(meta.section || label || defaultSectionLabel);
|
|
47
|
+
const configuredOrder = sectionOrder.indexOf(id);
|
|
48
|
+
const order = meta.sectionOrder ?? (configuredOrder === -1 ? 1000 : configuredOrder);
|
|
49
|
+
return { id, label, order };
|
|
50
|
+
}
|
|
51
|
+
export function createDocs(config) {
|
|
52
|
+
const defaultSectionLabel = config.defaultSectionLabel ?? 'Guides';
|
|
53
|
+
const sectionOrder = config.sectionOrder ?? ['overview', 'getting-started', 'guides', 'api', 'reference'];
|
|
54
|
+
let cachedMetaIndex = null;
|
|
55
|
+
let cachedFullIndex = null;
|
|
56
|
+
let cachedSlugToPath = null;
|
|
57
|
+
function getSlugToPath() {
|
|
58
|
+
if (!DEV && cachedSlugToPath)
|
|
59
|
+
return cachedSlugToPath;
|
|
60
|
+
const map = new Map();
|
|
61
|
+
for (const path of Object.keys(config.rawModules).sort()) {
|
|
62
|
+
const file = path.split('/').pop();
|
|
63
|
+
const slug = file?.replace(/\.md(?:\?.*)?$/, '');
|
|
64
|
+
if (!slug)
|
|
65
|
+
continue;
|
|
66
|
+
map.set(slug, path);
|
|
67
|
+
}
|
|
68
|
+
if (!DEV)
|
|
69
|
+
cachedSlugToPath = map;
|
|
70
|
+
return map;
|
|
71
|
+
}
|
|
72
|
+
async function buildMetaIndex() {
|
|
73
|
+
if (!DEV && cachedMetaIndex)
|
|
74
|
+
return cachedMetaIndex;
|
|
75
|
+
const pages = [];
|
|
76
|
+
const paths = Object.keys(config.rawModules).sort();
|
|
77
|
+
for (const path of paths) {
|
|
78
|
+
const file = path.split('/').pop();
|
|
79
|
+
const slug = file?.replace(/\.md(?:\?.*)?$/, '');
|
|
80
|
+
if (!slug)
|
|
81
|
+
continue;
|
|
82
|
+
const rawFn = config.rawModules[path];
|
|
83
|
+
if (!rawFn)
|
|
84
|
+
continue;
|
|
85
|
+
const raw = await rawFn();
|
|
86
|
+
const { data, content } = matter(raw);
|
|
87
|
+
const meta = config.mapFrontmatter
|
|
88
|
+
? config.mapFrontmatter({ data, content, slug, path })
|
|
89
|
+
: docsFrontmatterSchema.parse(data);
|
|
90
|
+
if (meta.draft)
|
|
91
|
+
continue;
|
|
92
|
+
const section = sectionFromMeta(meta, defaultSectionLabel, sectionOrder);
|
|
93
|
+
const updated = parseUpdatedAt(meta.updatedAt);
|
|
94
|
+
pages.push({
|
|
95
|
+
slug,
|
|
96
|
+
title: meta.title.trim(),
|
|
97
|
+
navTitle: (meta.navTitle || meta.title).trim(),
|
|
98
|
+
description: (meta.description || '').trim(),
|
|
99
|
+
section,
|
|
100
|
+
order: meta.order ?? 1000,
|
|
101
|
+
tags: meta.tags ?? [],
|
|
102
|
+
updatedAt: updated.iso,
|
|
103
|
+
updatedAtLong: updated.long
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
pages.sort((a, b) => {
|
|
107
|
+
if (a.section.order !== b.section.order)
|
|
108
|
+
return a.section.order - b.section.order;
|
|
109
|
+
if (a.section.id !== b.section.id)
|
|
110
|
+
return a.section.id.localeCompare(b.section.id);
|
|
111
|
+
if (a.order !== b.order)
|
|
112
|
+
return a.order - b.order;
|
|
113
|
+
return a.title.localeCompare(b.title);
|
|
114
|
+
});
|
|
115
|
+
if (!DEV)
|
|
116
|
+
cachedMetaIndex = pages;
|
|
117
|
+
return pages;
|
|
118
|
+
}
|
|
119
|
+
async function getAllPages() {
|
|
120
|
+
return buildMetaIndex();
|
|
121
|
+
}
|
|
122
|
+
async function getAllPagesFull() {
|
|
123
|
+
if (!DEV && cachedFullIndex)
|
|
124
|
+
return cachedFullIndex;
|
|
125
|
+
const meta = await buildMetaIndex();
|
|
126
|
+
const slugToPath = getSlugToPath();
|
|
127
|
+
const full = [];
|
|
128
|
+
for (const page of meta) {
|
|
129
|
+
const path = slugToPath.get(page.slug);
|
|
130
|
+
const compiledFn = path ? config.compiledModules[path] : undefined;
|
|
131
|
+
if (!compiledFn)
|
|
132
|
+
continue;
|
|
133
|
+
const compiled = await compiledFn();
|
|
134
|
+
full.push({ ...page, component: compiled.default });
|
|
135
|
+
}
|
|
136
|
+
if (!DEV)
|
|
137
|
+
cachedFullIndex = full;
|
|
138
|
+
return full;
|
|
139
|
+
}
|
|
140
|
+
async function getPageBySlug(slug) {
|
|
141
|
+
const page = (await getAllPages()).find((p) => p.slug === slug) ?? null;
|
|
142
|
+
if (!page)
|
|
143
|
+
return null;
|
|
144
|
+
const path = getSlugToPath().get(slug);
|
|
145
|
+
const compiledFn = path ? config.compiledModules[path] : undefined;
|
|
146
|
+
if (!compiledFn)
|
|
147
|
+
return null;
|
|
148
|
+
const compiled = await compiledFn();
|
|
149
|
+
return { ...page, component: compiled.default };
|
|
150
|
+
}
|
|
151
|
+
async function getSections() {
|
|
152
|
+
const pages = await getAllPages();
|
|
153
|
+
const map = new Map();
|
|
154
|
+
for (const page of pages) {
|
|
155
|
+
map.set(page.section.id, page.section);
|
|
156
|
+
}
|
|
157
|
+
return Array.from(map.values()).sort((a, b) => {
|
|
158
|
+
if (a.order !== b.order)
|
|
159
|
+
return a.order - b.order;
|
|
160
|
+
return a.label.localeCompare(b.label);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
async function getSidebar() {
|
|
164
|
+
const [sections, pages] = await Promise.all([getSections(), getAllPages()]);
|
|
165
|
+
return sections.map((section) => ({
|
|
166
|
+
...section,
|
|
167
|
+
pages: pages.filter((page) => page.section.id === section.id)
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
170
|
+
async function getAdjacentPages(slug) {
|
|
171
|
+
const pages = await getAllPages();
|
|
172
|
+
const index = pages.findIndex((page) => page.slug === slug);
|
|
173
|
+
if (index === -1)
|
|
174
|
+
return { previous: null, next: null };
|
|
175
|
+
return {
|
|
176
|
+
previous: pages[index - 1] ?? null,
|
|
177
|
+
next: pages[index + 1] ?? null
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
async function pickLandingPage() {
|
|
181
|
+
const pages = await getAllPages();
|
|
182
|
+
return pages[0] ?? null;
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
getAllPages,
|
|
186
|
+
getAllPagesFull,
|
|
187
|
+
getPageBySlug,
|
|
188
|
+
getSections,
|
|
189
|
+
getSidebar,
|
|
190
|
+
getAdjacentPages,
|
|
191
|
+
pickLandingPage
|
|
192
|
+
};
|
|
193
|
+
}
|
|
@@ -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, type DocsCreateConfig, type DocsFrontmatter, type DocsFrontmatterAdapter } 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,13 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
# @aureuma/blogkit
|
|
2
|
-
|
|
3
|
-
A Mintlify-inspired blog UI + content pipeline helpers for SvelteKit + mdsvex.
|
|
4
|
-
|
|
5
|
-
This repo keeps `svelta` as a demo app, and exposes the reusable parts as a local workspace package under `packages/blogkit`.
|
|
6
|
-
|
|
7
|
-
## What you get
|
|
8
|
-
|
|
9
|
-
- Blog UI components: hero card, post cards, tag tabs, share buttons, summary card, more-posts grid, image lightbox.
|
|
10
|
-
- Server helpers: `createBlog()` to load Markdown posts (frontmatter, excerpt, reading-time, categories, hero).
|
|
11
|
-
- Theme helper: `createThemeController()` and a simple theme switcher component.
|
|
12
|
-
|
|
13
|
-
### Compatibility
|
|
14
|
-
|
|
15
|
-
- Component source is compatible with Svelte 4 and Svelte 5 (no runes required).
|
|
16
|
-
- If you already have an existing frontmatter schema, use `mapFrontmatter` to adapt it to BlogKit's expected fields.
|
|
17
|
-
|
|
18
|
-
## Use In Another Repo
|
|
19
|
-
|
|
20
|
-
### Install from npm
|
|
21
|
-
|
|
22
|
-
```sh
|
|
23
|
-
npm i @aureuma/blogkit
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
### Option A: mono-repo / workspaces (recommended)
|
|
27
|
-
|
|
28
|
-
1. Add this repo as a submodule (or subtree) inside your project:
|
|
29
|
-
|
|
30
|
-
```sh
|
|
31
|
-
git submodule add git@github.com:Aureuma/svelta.git packages/svelta
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
2. Point your workspace config at the package:
|
|
35
|
-
|
|
36
|
-
```json
|
|
37
|
-
{
|
|
38
|
-
"workspaces": ["packages/*", "packages/svelta/packages/*"]
|
|
39
|
-
}
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
3. Import and use:
|
|
43
|
-
|
|
44
|
-
```ts
|
|
45
|
-
import { BlogCard } from '@aureuma/blogkit';
|
|
46
|
-
import { createBlog } from '@aureuma/blogkit/server';
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
### Option B: publish to npm
|
|
50
|
-
|
|
51
|
-
1. Set `"private": false` in `packages/blogkit/package.json`.
|
|
52
|
-
2. From `packages/blogkit`, run `npm publish`.
|
|
53
|
-
|
|
54
|
-
## Integration Steps (SvelteKit)
|
|
55
|
-
|
|
56
|
-
1. mdsvex: configure Markdown compilation (`.md`) and Shiki/anchors (see this repo's `svelte.config.js`).
|
|
57
|
-
2. Content:
|
|
58
|
-
- posts: `src/content/blog/*.md` with YAML frontmatter
|
|
59
|
-
- assets: `static/blog/covers/*` and `static/blog/authors/*`
|
|
60
|
-
3. Blog loader glue:
|
|
61
|
-
|
|
62
|
-
```ts
|
|
63
|
-
// src/lib/server/blog.ts
|
|
64
|
-
import { createBlog } from '@aureuma/blogkit/server';
|
|
65
|
-
import { getAuthor } from '$lib/content/authors';
|
|
66
|
-
import type { BlogPostFull } from '@aureuma/blogkit';
|
|
67
|
-
|
|
68
|
-
type CompiledModule = { default: BlogPostFull['component'] };
|
|
69
|
-
|
|
70
|
-
const compiledModules = import.meta.glob('/src/content/blog/*.md') as Record<
|
|
71
|
-
string,
|
|
72
|
-
() => Promise<CompiledModule>
|
|
73
|
-
>;
|
|
74
|
-
const rawModules = import.meta.glob('/src/content/blog/*.md', {
|
|
75
|
-
query: '?raw',
|
|
76
|
-
import: 'default'
|
|
77
|
-
}) as Record<string, () => Promise<string>>;
|
|
78
|
-
|
|
79
|
-
export const blog = createBlog({ compiledModules, rawModules, getAuthor });
|
|
80
|
-
export const { getAllPosts, getPostBySlug, getCategories, pickHero } = blog;
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
4. Tailwind: ensure your `content` globs include the package so class names are not purged.
|
|
84
|
-
- If installed from npm: include `./node_modules/@aureuma/blogkit/dist/**/*`.
|
|
85
|
-
|
|
86
|
-
5. Routes: copy the blog routes from this repo:
|
|
87
|
-
- `src/routes/blog/*`
|
|
88
|
-
- `src/routes/feed.xml/*`
|
|
89
|
-
|
|
90
|
-
## Notes
|
|
91
|
-
|
|
92
|
-
- `createBlog()` builds the index from raw Markdown only (fast), and imports the compiled mdsvex component only for individual post pages.
|
|
93
|
-
- This package is intended for SvelteKit. UI uses Tailwind utility classes and expects your app to provide the CSS variables/tokens.
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
export let href: string = '/blog';
|
|
3
|
-
export let label: string = 'Back to blog';
|
|
4
|
-
</script>
|
|
5
|
-
|
|
6
|
-
<a
|
|
7
|
-
href={href}
|
|
8
|
-
class="inline-flex items-center gap-2 text-xs font-mono uppercase tracking-[0.6px] text-text-sub transition hover:text-text-main"
|
|
9
|
-
>
|
|
10
|
-
<svg
|
|
11
|
-
class="size-4"
|
|
12
|
-
viewBox="0 0 24 24"
|
|
13
|
-
fill="none"
|
|
14
|
-
stroke="currentColor"
|
|
15
|
-
stroke-width="2"
|
|
16
|
-
stroke-linecap="round"
|
|
17
|
-
stroke-linejoin="round"
|
|
18
|
-
aria-hidden="true"
|
|
19
|
-
>
|
|
20
|
-
<path d="M15 18l-6-6 6-6" />
|
|
21
|
-
</svg>
|
|
22
|
-
<span>{label}</span>
|
|
23
|
-
</a>
|