@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,98 @@
|
|
|
1
|
+
export const DEFAULT_DOCS_PATTERN_CONFIG = {
|
|
2
|
+
kind: 'docs',
|
|
3
|
+
brandName: 'svelta',
|
|
4
|
+
productName: 'Documentation',
|
|
5
|
+
title: 'Structured docs with first-class markdown ergonomics.',
|
|
6
|
+
description: 'Ship Mintlify-style docs UX with sectioned navigation, command-palette search, right-rail table of contents, and content feedback loops.',
|
|
7
|
+
defaultSectionLabel: 'Guides',
|
|
8
|
+
sectionOrder: ['overview', 'getting-started', 'guides', 'api', 'reference'],
|
|
9
|
+
navigation: {
|
|
10
|
+
header: [
|
|
11
|
+
{ label: 'Docs', href: '/docs' },
|
|
12
|
+
{ label: 'Blog', href: '/blog' }
|
|
13
|
+
],
|
|
14
|
+
footer: [
|
|
15
|
+
{ label: 'Overview', href: '/docs/overview' },
|
|
16
|
+
{ label: 'Getting Started', href: '/docs/getting-started' }
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
search: {
|
|
20
|
+
enabled: true,
|
|
21
|
+
placeholder: 'Search documentation...',
|
|
22
|
+
shortcut: 'Ctrl K'
|
|
23
|
+
},
|
|
24
|
+
toc: {
|
|
25
|
+
enabled: true,
|
|
26
|
+
title: 'On This Page'
|
|
27
|
+
},
|
|
28
|
+
feedback: {
|
|
29
|
+
enabled: true,
|
|
30
|
+
prompt: 'Was this page helpful?'
|
|
31
|
+
},
|
|
32
|
+
editLinkTemplate: 'https://github.com/Aureuma/svelta/blob/main/src/content/docs/:slug.md'
|
|
33
|
+
};
|
|
34
|
+
export const DEFAULT_BLOG_PATTERN_CONFIG = {
|
|
35
|
+
kind: 'blog',
|
|
36
|
+
brandName: 'svelta',
|
|
37
|
+
title: 'Editorial publishing with modern feed ergonomics.',
|
|
38
|
+
description: 'Publish markdown-driven updates with taxonomy, progressive pagination, author attribution, and RSS delivery.',
|
|
39
|
+
pageSize: 8,
|
|
40
|
+
maxPageSize: 24,
|
|
41
|
+
infiniteScroll: true,
|
|
42
|
+
showRss: true,
|
|
43
|
+
navigation: {
|
|
44
|
+
header: [
|
|
45
|
+
{ label: 'Blog', href: '/blog' },
|
|
46
|
+
{ label: 'Tags', href: '/blog/tags' },
|
|
47
|
+
{ label: 'Archive', href: '/blog/archive' }
|
|
48
|
+
],
|
|
49
|
+
footer: [
|
|
50
|
+
{ label: 'All posts', href: '/blog' },
|
|
51
|
+
{ label: 'Authors', href: '/blog/authors' },
|
|
52
|
+
{ label: 'RSS', href: '/feed.xml' }
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
export function createDocsPatternConfig(overrides) {
|
|
57
|
+
return {
|
|
58
|
+
...DEFAULT_DOCS_PATTERN_CONFIG,
|
|
59
|
+
...overrides,
|
|
60
|
+
kind: 'docs',
|
|
61
|
+
navigation: {
|
|
62
|
+
...DEFAULT_DOCS_PATTERN_CONFIG.navigation,
|
|
63
|
+
...(overrides?.navigation ?? {})
|
|
64
|
+
},
|
|
65
|
+
search: {
|
|
66
|
+
...DEFAULT_DOCS_PATTERN_CONFIG.search,
|
|
67
|
+
...(overrides?.search ?? {})
|
|
68
|
+
},
|
|
69
|
+
toc: {
|
|
70
|
+
...DEFAULT_DOCS_PATTERN_CONFIG.toc,
|
|
71
|
+
...(overrides?.toc ?? {})
|
|
72
|
+
},
|
|
73
|
+
feedback: {
|
|
74
|
+
...DEFAULT_DOCS_PATTERN_CONFIG.feedback,
|
|
75
|
+
...(overrides?.feedback ?? {})
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
export function createBlogPatternConfig(overrides) {
|
|
80
|
+
return {
|
|
81
|
+
...DEFAULT_BLOG_PATTERN_CONFIG,
|
|
82
|
+
...overrides,
|
|
83
|
+
kind: 'blog',
|
|
84
|
+
navigation: {
|
|
85
|
+
...DEFAULT_BLOG_PATTERN_CONFIG.navigation,
|
|
86
|
+
...(overrides?.navigation ?? {})
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
export function createSveltaPatternConfig(overrides) {
|
|
91
|
+
return {
|
|
92
|
+
docs: createDocsPatternConfig(overrides?.docs),
|
|
93
|
+
blog: createBlogPatternConfig(overrides?.blog)
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
export function resolveDocsEditUrl(config, slug) {
|
|
97
|
+
return config.editLinkTemplate.replaceAll(':slug', slug);
|
|
98
|
+
}
|
|
@@ -8,4 +8,13 @@ export { default as MorePosts } from './components/blog/MorePosts.svelte';
|
|
|
8
8
|
export { default as ShareButtons } from './components/blog/ShareButtons.svelte';
|
|
9
9
|
export { default as SummaryCard } from './components/blog/SummaryCard.svelte';
|
|
10
10
|
export { default as TagTabs } from './components/blog/TagTabs.svelte';
|
|
11
|
-
export
|
|
11
|
+
export { default as DocsPager } from './components/docs/DocsPager.svelte';
|
|
12
|
+
export { default as DocsSectionGrid } from './components/docs/DocsSectionGrid.svelte';
|
|
13
|
+
export { default as DocsShell } from './components/docs/DocsShell.svelte';
|
|
14
|
+
export { default as DocsSidebar } from './components/docs/DocsSidebar.svelte';
|
|
15
|
+
export { createExperienceCatalog, parseExperienceKind } from './experience';
|
|
16
|
+
export { createBlogPatternConfig, createDocsPatternConfig, createSveltaPatternConfig, DEFAULT_BLOG_PATTERN_CONFIG, DEFAULT_DOCS_PATTERN_CONFIG, resolveDocsEditUrl } from './experience';
|
|
17
|
+
export type { BlogAuthor, BlogAuthorSummary, BlogArchiveGroup, BlogCategory, BlogPost, BlogPostFull, BlogPostWithContent, BlogTag, SharePlatform } from './types/blog';
|
|
18
|
+
export type { DocsPage, DocsPageFull, DocsPageWithContent, DocsSection, DocsSidebarSection } from './types/docs';
|
|
19
|
+
export type { ExperienceCatalog, ExperienceDefinition, ExperienceKind } from './types/experience';
|
|
20
|
+
export type { SveltaBlogPatternConfig, SveltaDocsPatternConfig, SveltaNavItem, SveltaPatternConfig } from './types/experience';
|
|
@@ -9,3 +9,9 @@ export { default as MorePosts } from './components/blog/MorePosts.svelte';
|
|
|
9
9
|
export { default as ShareButtons } from './components/blog/ShareButtons.svelte';
|
|
10
10
|
export { default as SummaryCard } from './components/blog/SummaryCard.svelte';
|
|
11
11
|
export { default as TagTabs } from './components/blog/TagTabs.svelte';
|
|
12
|
+
export { default as DocsPager } from './components/docs/DocsPager.svelte';
|
|
13
|
+
export { default as DocsSectionGrid } from './components/docs/DocsSectionGrid.svelte';
|
|
14
|
+
export { default as DocsShell } from './components/docs/DocsShell.svelte';
|
|
15
|
+
export { default as DocsSidebar } from './components/docs/DocsSidebar.svelte';
|
|
16
|
+
export { createExperienceCatalog, parseExperienceKind } from './experience';
|
|
17
|
+
export { createBlogPatternConfig, createDocsPatternConfig, createSveltaPatternConfig, DEFAULT_BLOG_PATTERN_CONFIG, DEFAULT_DOCS_PATTERN_CONFIG, resolveDocsEditUrl } from './experience';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BlogAuthor, BlogCategory, BlogPost, BlogPostFull, BlogPostWithContent, BlogTag } from '../types/blog';
|
|
1
|
+
import type { BlogAuthor, BlogAuthorSummary, BlogArchiveGroup, BlogCategory, BlogPost, BlogPostFull, BlogPostWithContent, BlogTag } from '../types/blog';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
declare const frontmatterSchema: z.ZodObject<{
|
|
4
4
|
title: z.ZodString;
|
|
@@ -27,6 +27,7 @@ export type BlogCreateConfig = {
|
|
|
27
27
|
rawModules: Record<string, () => Promise<string>>;
|
|
28
28
|
getAuthor: (id: string) => BlogAuthor;
|
|
29
29
|
categoryOrder?: string[];
|
|
30
|
+
allowMultipleTags?: boolean;
|
|
30
31
|
mapFrontmatter?: BlogFrontmatterAdapter;
|
|
31
32
|
};
|
|
32
33
|
export type MarkdownRenderer = (markdown: string) => string | Promise<string>;
|
|
@@ -34,6 +35,7 @@ export type RawBlogCreateConfig = {
|
|
|
34
35
|
rawModules: Record<string, () => Promise<string>>;
|
|
35
36
|
getAuthor: (id: string) => BlogAuthor;
|
|
36
37
|
categoryOrder?: string[];
|
|
38
|
+
allowMultipleTags?: boolean;
|
|
37
39
|
mapFrontmatter?: BlogFrontmatterAdapter;
|
|
38
40
|
renderMarkdown?: MarkdownRenderer;
|
|
39
41
|
};
|
|
@@ -83,6 +85,17 @@ export declare function createBlog(config: BlogCreateConfig): {
|
|
|
83
85
|
getPostBySlug: (slug: string) => Promise<BlogPostFull | null>;
|
|
84
86
|
getCategories: () => Promise<BlogCategory[]>;
|
|
85
87
|
pickHero: (posts?: BlogPost[]) => Promise<BlogPost>;
|
|
88
|
+
getAllTags: () => Promise<BlogTag[]>;
|
|
89
|
+
getPostsByTag: (tagSlug: string) => Promise<BlogPost[]>;
|
|
90
|
+
getPostsByAuthor: (authorId: string) => Promise<BlogPost[]>;
|
|
91
|
+
getAllAuthors: () => Promise<BlogAuthor[]>;
|
|
92
|
+
getAuthorSummaries: () => Promise<BlogAuthorSummary[]>;
|
|
93
|
+
getArchiveGroups: () => Promise<BlogArchiveGroup[]>;
|
|
94
|
+
getAdjacentPosts: (slug: string) => Promise<{
|
|
95
|
+
previous: BlogPost | null;
|
|
96
|
+
next: BlogPost | null;
|
|
97
|
+
}>;
|
|
98
|
+
getRelatedPosts: (slug: string, limit?: number) => Promise<BlogPost[]>;
|
|
86
99
|
};
|
|
87
100
|
export declare function parseVivaBlogFrontmatter(data: unknown): VivaBlogFrontmatter;
|
|
88
101
|
export declare function parseVivaAuthorFrontmatter(data: unknown): VivaAuthorFrontmatter;
|
|
@@ -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,64 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { DocsPage, DocsPageFull, DocsPageWithContent, 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 type DocsMarkdownRenderer = (markdown: string) => string | Promise<string>;
|
|
33
|
+
export type RawDocsCreateConfig = {
|
|
34
|
+
rawModules: Record<string, () => Promise<string>>;
|
|
35
|
+
defaultSectionLabel?: string;
|
|
36
|
+
sectionOrder?: string[];
|
|
37
|
+
mapFrontmatter?: DocsFrontmatterAdapter;
|
|
38
|
+
renderMarkdown?: DocsMarkdownRenderer;
|
|
39
|
+
};
|
|
40
|
+
export declare function createDocs(config: DocsCreateConfig): {
|
|
41
|
+
getAllPages: () => Promise<DocsPage[]>;
|
|
42
|
+
getAllPagesFull: () => Promise<DocsPageFull[]>;
|
|
43
|
+
getPageBySlug: (slug: string) => Promise<DocsPageFull | null>;
|
|
44
|
+
getSections: () => Promise<DocsSection[]>;
|
|
45
|
+
getSidebar: () => Promise<DocsSidebarSection[]>;
|
|
46
|
+
getAdjacentPages: (slug: string) => Promise<{
|
|
47
|
+
previous: DocsPage | null;
|
|
48
|
+
next: DocsPage | null;
|
|
49
|
+
}>;
|
|
50
|
+
pickLandingPage: () => Promise<DocsPage | null>;
|
|
51
|
+
};
|
|
52
|
+
export declare function createRawDocs(config: RawDocsCreateConfig): {
|
|
53
|
+
getAllPages: () => Promise<DocsPage[]>;
|
|
54
|
+
getAllPagesWithContent: () => Promise<DocsPageWithContent[]>;
|
|
55
|
+
getPageBySlug: (slug: string) => Promise<DocsPageWithContent | null>;
|
|
56
|
+
getSections: () => Promise<DocsSection[]>;
|
|
57
|
+
getSidebar: () => Promise<DocsSidebarSection[]>;
|
|
58
|
+
getAdjacentPages: (slug: string) => Promise<{
|
|
59
|
+
previous: DocsPage | null;
|
|
60
|
+
next: DocsPage | null;
|
|
61
|
+
}>;
|
|
62
|
+
pickLandingPage: () => Promise<DocsPage | null>;
|
|
63
|
+
};
|
|
64
|
+
export {};
|