@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.
Files changed (71) hide show
  1. package/README.md +22 -2
  2. package/package.json +31 -19
  3. package/packages/{blogkit → core}/CHANGELOG.md +1 -1
  4. package/packages/core/README.md +11 -0
  5. package/packages/core/dist/appearance/AppearanceSwitcher.svelte +58 -0
  6. package/packages/core/dist/appearance/AppearanceSwitcher.svelte.d.ts +21 -0
  7. package/packages/core/dist/appearance/index.d.ts +3 -0
  8. package/packages/core/dist/appearance/index.js +3 -0
  9. package/packages/core/dist/appearance/palettes.d.ts +10 -0
  10. package/packages/core/dist/appearance/palettes.js +36 -0
  11. package/packages/core/dist/appearance/store.d.ts +20 -0
  12. package/packages/core/dist/appearance/store.js +87 -0
  13. package/packages/core/dist/components/blog/BackLink.svelte +26 -0
  14. package/packages/core/dist/components/blog/BlogCard.svelte +50 -0
  15. package/packages/core/dist/components/blog/BlogHeroCard.svelte +52 -0
  16. package/packages/core/dist/components/blog/Container.svelte +9 -0
  17. package/packages/{blogkit → core}/dist/components/blog/Container.svelte.d.ts +1 -1
  18. package/packages/{blogkit → core}/dist/components/blog/MorePosts.svelte +1 -1
  19. package/packages/core/dist/components/blog/ShareButtons.svelte +147 -0
  20. package/packages/{blogkit → core}/dist/components/blog/ShareButtons.svelte.d.ts +2 -0
  21. package/packages/core/dist/components/blog/TagTabs.svelte +85 -0
  22. package/packages/{blogkit → core}/dist/components/blog/TagTabs.svelte.d.ts +4 -2
  23. package/packages/core/dist/components/docs/DocsPager.svelte +28 -0
  24. package/packages/core/dist/components/docs/DocsPager.svelte.d.ts +22 -0
  25. package/packages/core/dist/components/docs/DocsSectionGrid.svelte +26 -0
  26. package/packages/core/dist/components/docs/DocsSectionGrid.svelte.d.ts +21 -0
  27. package/packages/core/dist/components/docs/DocsShell.svelte +19 -0
  28. package/packages/core/dist/components/docs/DocsShell.svelte.d.ts +31 -0
  29. package/packages/core/dist/components/docs/DocsSidebar.svelte +31 -0
  30. package/packages/core/dist/components/docs/DocsSidebar.svelte.d.ts +22 -0
  31. package/packages/core/dist/experience/index.d.ts +7 -0
  32. package/packages/core/dist/experience/index.js +43 -0
  33. package/packages/core/dist/index.d.ts +18 -0
  34. package/packages/{blogkit → core}/dist/index.js +5 -0
  35. package/packages/{blogkit → core}/dist/server/blog.d.ts +14 -1
  36. package/packages/{blogkit → core}/dist/server/blog.js +144 -14
  37. package/packages/core/dist/server/docs.d.ts +44 -0
  38. package/packages/core/dist/server/docs.js +193 -0
  39. package/packages/{blogkit → core}/dist/server/index.d.ts +1 -0
  40. package/packages/{blogkit → core}/dist/server/index.js +1 -0
  41. package/packages/{blogkit → core}/dist/types/blog.d.ts +14 -0
  42. package/packages/core/dist/types/docs.d.ts +28 -0
  43. package/packages/core/dist/types/docs.js +1 -0
  44. package/packages/core/dist/types/experience.d.ts +13 -0
  45. package/packages/core/dist/types/experience.js +1 -0
  46. package/packages/blogkit/README.md +0 -93
  47. package/packages/blogkit/dist/components/blog/BackLink.svelte +0 -23
  48. package/packages/blogkit/dist/components/blog/BlogCard.svelte +0 -37
  49. package/packages/blogkit/dist/components/blog/BlogHeroCard.svelte +0 -36
  50. package/packages/blogkit/dist/components/blog/Container.svelte +0 -8
  51. package/packages/blogkit/dist/components/blog/ShareButtons.svelte +0 -113
  52. package/packages/blogkit/dist/components/blog/TagTabs.svelte +0 -32
  53. package/packages/blogkit/dist/index.d.ts +0 -11
  54. package/packages/blogkit/dist/theme/ThemeSwitcher.svelte +0 -34
  55. package/packages/blogkit/dist/theme/ThemeSwitcher.svelte.d.ts +0 -21
  56. package/packages/blogkit/dist/theme/index.d.ts +0 -2
  57. package/packages/blogkit/dist/theme/index.js +0 -2
  58. package/packages/blogkit/dist/theme/store.d.ts +0 -12
  59. package/packages/blogkit/dist/theme/store.js +0 -50
  60. /package/packages/{blogkit → core}/LICENSE +0 -0
  61. /package/packages/{blogkit → core}/dist/components/blog/Avatar.svelte +0 -0
  62. /package/packages/{blogkit → core}/dist/components/blog/Avatar.svelte.d.ts +0 -0
  63. /package/packages/{blogkit → core}/dist/components/blog/BackLink.svelte.d.ts +0 -0
  64. /package/packages/{blogkit → core}/dist/components/blog/BlogCard.svelte.d.ts +0 -0
  65. /package/packages/{blogkit → core}/dist/components/blog/BlogHeroCard.svelte.d.ts +0 -0
  66. /package/packages/{blogkit → core}/dist/components/blog/ImageLightbox.svelte +0 -0
  67. /package/packages/{blogkit → core}/dist/components/blog/ImageLightbox.svelte.d.ts +0 -0
  68. /package/packages/{blogkit → core}/dist/components/blog/MorePosts.svelte.d.ts +0 -0
  69. /package/packages/{blogkit → core}/dist/components/blog/SummaryCard.svelte +0 -0
  70. /package/packages/{blogkit → core}/dist/components/blog/SummaryCard.svelte.d.ts +0 -0
  71. /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 (/^\\d{4}-\\d{2}-\\d{2}$/.test(date)) {
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: metadata.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
- return { getAllPosts, getAllPostsFull, getPostBySlug, getCategories, pickHero };
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: metadata.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 tagSet = new Set(current.tags.map((tag) => slugify(tag)));
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
- .map((post) => {
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';
@@ -1 +1,2 @@
1
1
  export { createBlog, createRawBlog, parseMarkdownAuthorMap, parseVivaAuthorFrontmatter, parseVivaAuthorProfiles, parseVivaBlogFrontmatter } from './blog';
2
+ export { createDocs } 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>