@aureuma/svelta 0.0.1 → 0.1.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 (83) hide show
  1. package/package.json +34 -2
  2. package/packages/blogkit/CHANGELOG.md +12 -0
  3. package/packages/blogkit/dist/index.d.ts +1 -1
  4. package/packages/blogkit/dist/server/blog.d.ts +68 -1
  5. package/packages/blogkit/dist/server/blog.js +248 -0
  6. package/packages/blogkit/dist/server/index.d.ts +1 -1
  7. package/packages/blogkit/dist/server/index.js +1 -1
  8. package/packages/blogkit/dist/types/blog.d.ts +10 -0
  9. package/.changeset/README.md +0 -8
  10. package/.changeset/config.json +0 -14
  11. package/.changeset/publish-blogkit.md +0 -5
  12. package/.github/workflows/release.yml +0 -65
  13. package/docs/mintlify-blog-study.md +0 -697
  14. package/packages/blogkit/package.json +0 -66
  15. package/packages/blogkit/src/lib/components/blog/Avatar.svelte +0 -15
  16. package/packages/blogkit/src/lib/components/blog/BackLink.svelte +0 -23
  17. package/packages/blogkit/src/lib/components/blog/BlogCard.svelte +0 -37
  18. package/packages/blogkit/src/lib/components/blog/BlogHeroCard.svelte +0 -36
  19. package/packages/blogkit/src/lib/components/blog/Container.svelte +0 -8
  20. package/packages/blogkit/src/lib/components/blog/ImageLightbox.svelte +0 -58
  21. package/packages/blogkit/src/lib/components/blog/MorePosts.svelte +0 -15
  22. package/packages/blogkit/src/lib/components/blog/ShareButtons.svelte +0 -113
  23. package/packages/blogkit/src/lib/components/blog/SummaryCard.svelte +0 -11
  24. package/packages/blogkit/src/lib/components/blog/TagTabs.svelte +0 -32
  25. package/packages/blogkit/src/lib/index.ts +0 -15
  26. package/packages/blogkit/src/lib/server/blog.ts +0 -264
  27. package/packages/blogkit/src/lib/server/index.ts +0 -2
  28. package/packages/blogkit/src/lib/theme/ThemeSwitcher.svelte +0 -34
  29. package/packages/blogkit/src/lib/theme/index.ts +0 -3
  30. package/packages/blogkit/src/lib/theme/store.ts +0 -64
  31. package/packages/blogkit/src/lib/types/blog.ts +0 -36
  32. package/packages/blogkit/svelte.config.js +0 -8
  33. package/packages/blogkit/tsconfig.json +0 -5
  34. package/playwright.config.ts +0 -24
  35. package/postcss.config.cjs +0 -6
  36. package/src/app.css +0 -146
  37. package/src/app.d.ts +0 -13
  38. package/src/app.html +0 -26
  39. package/src/content/blog/ai-summary-cards-with-frontmatter.md +0 -32
  40. package/src/content/blog/announcing-svelta-blog.md +0 -19
  41. package/src/content/blog/best-practices-ship-with-checklists.md +0 -26
  42. package/src/content/blog/building-a-mintlify-inspired-blog.md +0 -49
  43. package/src/content/blog/design-tokens-that-scale.md +0 -47
  44. package/src/content/blog/for-founders-why-speed-matters.md +0 -23
  45. package/src/content/blog/infinite-scroll-with-intersection-observer.md +0 -37
  46. package/src/content/blog/markdown-kitchen-sink.md +0 -101
  47. package/src/content/blog/markdown-pipeline-mdsvex-shiki.md +0 -39
  48. package/src/content/blog/rss-feeds-that-actually-work.md +0 -25
  49. package/src/content/blog/tag-tabs-and-mobile-fade-masks.md +0 -25
  50. package/src/lib/assets/favicon.svg +0 -1
  51. package/src/lib/components/site/SiteFooter.svelte +0 -24
  52. package/src/lib/components/site/SiteHeader.svelte +0 -36
  53. package/src/lib/content/authors.ts +0 -28
  54. package/src/lib/index.ts +0 -1
  55. package/src/lib/server/blog.ts +0 -22
  56. package/src/lib/server/rss.ts +0 -58
  57. package/src/lib/server/seo.ts +0 -31
  58. package/src/lib/stores/theme.ts +0 -10
  59. package/src/lib/types/blog.ts +0 -1
  60. package/src/routes/+layout.svelte +0 -31
  61. package/src/routes/+page.svelte +0 -44
  62. package/src/routes/blog/+page.server.ts +0 -28
  63. package/src/routes/blog/+page.svelte +0 -122
  64. package/src/routes/blog/[slug]/+page.server.ts +0 -39
  65. package/src/routes/blog/[slug]/+page.svelte +0 -118
  66. package/src/routes/blog/posts.json/+server.ts +0 -32
  67. package/src/routes/feed.xml/+server.ts +0 -21
  68. package/static/blog/authors/alex.svg +0 -13
  69. package/static/blog/authors/maria.svg +0 -13
  70. package/static/blog/authors/shawn.svg +0 -13
  71. package/static/blog/covers/ai-summary.svg +0 -38
  72. package/static/blog/covers/design-tokens.svg +0 -37
  73. package/static/blog/covers/infinite-scroll.svg +0 -38
  74. package/static/blog/covers/kitchen-sink.svg +0 -36
  75. package/static/blog/covers/markdown-pipeline.svg +0 -41
  76. package/static/blog/covers/mintlify-style.svg +0 -35
  77. package/static/blog/covers/rss.svg +0 -34
  78. package/static/robots.txt +0 -3
  79. package/svelte.config.js +0 -70
  80. package/tailwind.config.cjs +0 -133
  81. package/tests/blog.spec.ts +0 -63
  82. package/tsconfig.json +0 -21
  83. package/vite.config.ts +0 -14
@@ -1,26 +0,0 @@
1
- ---
2
- title: "Best Practices: Ship With Checklists"
3
- date: "2026-01-25"
4
- category: "Best practices"
5
- author: "maria"
6
- cover: "/blog/covers/rss.svg"
7
- tags:
8
- - "Quality"
9
- - "Process"
10
- excerpt: "If something matters, put it in a checklist. Then automate the boring parts."
11
- ---
12
-
13
- Checklists aren’t bureaucracy. They’re a compression format for your standards.
14
-
15
- ## A tiny publishing checklist
16
-
17
- - Title is clear and specific
18
- - Cover image is present and readable
19
- - Excerpt is 1–2 sentences
20
- - Code blocks are highlighted
21
- - A “more posts” section exists
22
-
23
- ## Bonus
24
-
25
- Use RSS to distribute without algorithms.
26
-
@@ -1,49 +0,0 @@
1
- ---
2
- title: "Building a Mintlify-Inspired Blog in SvelteKit"
3
- date: "2026-02-08"
4
- category: "Engineering"
5
- author: "shawn"
6
- cover: "/blog/covers/mintlify-style.svg"
7
- featured: true
8
- tags:
9
- - "SvelteKit"
10
- - "mdsvex"
11
- - "Tailwind"
12
- excerpt: "A practical blueprint for a Mintlify-inspired blog: proportions, theming, Markdown rendering, reading-time, share widgets, infinite scroll, and RSS."
13
- summaryAI: "We mirror Mintlify’s blog *system* (not its assets): fixed-width containers, hero+grid index, pill category tabs, a two-column post layout with a sticky author/share rail on desktop, Shiki-highlighted Markdown, reading-time labels, framed images, bottom recommendations, and an RSS feed. The implementation is SvelteKit-first: mdsvex for Markdown, Tailwind tokens for theming, and a JSON pagination endpoint for infinite scroll."
14
- ---
15
-
16
- This post documents the structure we’re building: a blog that *feels* like Mintlify’s in layout and typographic rhythm, while remaining our own code + assets.
17
-
18
- ## What “Mintlify-inspired” means
19
-
20
- It’s mainly about **placement and proportions**:
21
-
22
- - A large **hero card** on the index, followed by a tag bar and a two-column grid
23
- - Post pages that use a **narrow reading column** with a sticky right rail (desktop)
24
- - Small mono uppercase metadata: category + reading-time + date
25
-
26
- ## The content pipeline
27
-
28
- We use Markdown for content, but we want code blocks to look like “real UI”. That means Shiki.
29
-
30
- ```ts
31
- // src/lib/server/blog.ts
32
- import readingTime from "reading-time";
33
-
34
- const minutes = Math.max(1, Math.round(readingTime(markdown).minutes));
35
- ```
36
-
37
- ## A tiny checklist
38
-
39
- | Feature | Why it matters |
40
- | --- | --- |
41
- | Category pills | Fast scanning |
42
- | AI summary card | Better “skim” mode |
43
- | Sticky share rail | Share UX without interrupting reading |
44
- | RSS feed | Works in every reader |
45
-
46
- ## Next
47
-
48
- In the following posts we’ll break down each part (tabs, infinite scroll, Markdown, RSS) into its own implementation.
49
-
@@ -1,47 +0,0 @@
1
- ---
2
- title: "Design Tokens That Scale (Without Getting Mushy)"
3
- date: "2026-02-06"
4
- category: "Design"
5
- author: "maria"
6
- cover: "/blog/covers/design-tokens.svg"
7
- tags:
8
- - "Design Systems"
9
- - "Theming"
10
- excerpt: "A small, opinionated token set that keeps contrast crisp across light/dark, while preserving the Mintlify-like quiet UI."
11
- summaryAI: "Use a tiny set of semantic tokens: `background.main/soft`, `text.main/sub/muted`, `border.soft`, and `brand`. Map them to CSS variables on `html.light`/`html.dark`, then reference them via Tailwind colors. Keep borders extremely subtle and reserve the brand color for metadata accents."
12
- ---
13
-
14
- Mintlify’s blog looks “clean” because everything is token-driven and low-noise.
15
-
16
- ## Token strategy
17
-
18
- We intentionally keep the token set small:
19
-
20
- - `background.main`, `background.soft`
21
- - `text.main`, `text.sub`, `text.muted`
22
- - `border.soft`
23
- - `brand`
24
-
25
- ## Why “soft borders” matter
26
-
27
- When borders are too strong, the UI becomes a spreadsheet. The goal is to have frames that are visible *only when you’re looking for them*.
28
-
29
- ## Tailwind mapping
30
-
31
- We map CSS variables to Tailwind colors so components stay readable:
32
-
33
- ```js
34
- // tailwind.config.cjs
35
- colors: {
36
- background: {
37
- main: "rgb(var(--c-background-main) / <alpha-value>)",
38
- soft: "rgb(var(--c-background-soft) / <alpha-value>)",
39
- },
40
- }
41
- ```
42
-
43
- ## In-content images
44
-
45
- We frame every in-article image with a subtle 1px border and a 10px radius to match the overall “rounded media” identity:
46
-
47
- ![Framed example](/blog/covers/design-tokens.svg)
@@ -1,23 +0,0 @@
1
- ---
2
- title: "For Founders: Why Speed Matters More Than You Think"
3
- date: "2026-01-28"
4
- category: "For founders"
5
- author: "alex"
6
- cover: "/blog/covers/infinite-scroll.svg"
7
- tags:
8
- - "Product"
9
- - "Strategy"
10
- excerpt: "Shipping faster isn’t about heroics. It’s about removing friction everywhere: tooling, content, and communication."
11
- summaryAI: "Speed compounds when you remove tiny blockers: slow builds, unclear docs, non-skimmable posts, and missing distribution channels like RSS. A blog that’s easy to publish and easy to read reduces context-switch costs for your team and your audience."
12
- ---
13
-
14
- If you’re building a product, your blog isn’t “marketing”. It’s a leverage tool.
15
-
16
- ## The compounding effect
17
-
18
- Small improvements in clarity and publishing speed add up:
19
-
20
- - fewer repeated explanations
21
- - faster onboarding
22
- - better customer self-serve
23
-
@@ -1,37 +0,0 @@
1
- ---
2
- title: "Infinite Scroll With IntersectionObserver (Without Jank)"
3
- date: "2026-02-05"
4
- category: "Engineering"
5
- author: "alex"
6
- cover: "/blog/covers/infinite-scroll.svg"
7
- tags:
8
- - "Performance"
9
- - "Svelte"
10
- excerpt: "Mintlify-style content loading: a paginated JSON endpoint plus a sentinel at the bottom of the grid."
11
- summaryAI: "Render the first page on the server for SEO, then append pages on the client using an `IntersectionObserver` watching a sentinel div. The endpoint should accept `offset`, `limit`, and `category`, and it should exclude the hero post to avoid duplicates."
12
- ---
13
-
14
- The Mintlify blog index loads more cards as you scroll. There’s no “Load more” button.
15
-
16
- ## The shape of the API
17
-
18
- We keep it simple:
19
-
20
- ```http
21
- GET /blog/posts.json?offset=8&limit=8&category=engineering
22
- ```
23
-
24
- ## The sentinel pattern
25
-
26
- ```ts
27
- const io = new IntersectionObserver(([entry]) => {
28
- if (entry.isIntersecting) loadMore();
29
- });
30
-
31
- io.observe(sentinel);
32
- ```
33
-
34
- ## Optional: virtualization
35
-
36
- If you ever have thousands of posts, add virtualization. Until then, pagination is enough.
37
-
@@ -1,101 +0,0 @@
1
- ---
2
- title: "Markdown Kitchen Sink (Rendering QA)"
3
- date: "2026-02-08"
4
- category: "Engineering"
5
- author: "alex"
6
- cover: "/blog/covers/kitchen-sink.svg"
7
- tags:
8
- - "Markdown"
9
- - "QA"
10
- excerpt: "A deliberately dense post to validate typography, spacing, tables, lists, code blocks, images, and edge cases in our Markdown renderer."
11
- summaryAI: "This post exists to stress-test Markdown rendering: tables should scroll on mobile, code blocks should be readable in light/dark mode, headings should get anchors, and images should look framed and open in the lightbox."
12
- ---
13
-
14
- This post is intentionally packed. It is here to catch layout regressions before real content does.
15
-
16
- ## Headings and anchors
17
-
18
- If you hover headings on desktop, you should see a subtle `#` anchor indicator.
19
-
20
- ### A smaller heading
21
-
22
- Anchors should not shift layout, and scroll margins should land nicely below the sticky header.
23
-
24
- ## Lists (including nesting)
25
-
26
- - One
27
- - Two
28
- - Two.A
29
- - Two.B
30
- - Three
31
-
32
- 1. First
33
- 2. Second
34
- 1. Second.A
35
- 2. Second.B
36
- 3. Third
37
-
38
- Task list (GFM):
39
-
40
- - [x] Basic list spacing
41
- - [x] Nested list spacing
42
- - [ ] Task lists render correctly
43
-
44
- ## Blockquotes
45
-
46
- > A blockquote should feel like a callout: a soft left border, readable spacing, and no weird quote marks.
47
- >
48
- > Multiple paragraphs should keep the border and spacing intact.
49
-
50
- ## Inline formatting
51
-
52
- This has **bold**, *italic*, ~~strikethrough~~, and `inline code`.
53
-
54
- Long URL wrapping should not overflow:
55
- https://example.com/a/really/really/really/really/really/really/really/long/path?with=query&and=more
56
-
57
- ## Tables (GFM)
58
-
59
- | Column | Description | Notes |
60
- | --- | --- | --- |
61
- | Tags | Category pills on `/blog` | Scrollable on mobile |
62
- | RSS | `/feed.xml` | Auto-discoverable via `<link rel="alternate">` |
63
- | Share | X / LinkedIn / Facebook / Copy | Sticky rail on desktop |
64
-
65
- Tables should be horizontally scrollable on narrow screens.
66
-
67
- ## Code blocks (Shiki)
68
-
69
- ```ts
70
- type PostFrontmatter = {
71
- title: string;
72
- date: string; // YYYY-MM-DD
73
- category: string;
74
- author: string;
75
- cover: string;
76
- excerpt?: string;
77
- summaryAI?: string;
78
- tags?: string[];
79
- featured?: boolean;
80
- };
81
- ```
82
-
83
- ```svelte
84
- {#if post.summaryAI}
85
- <SummaryCard summary={post.summaryAI} />
86
- {/if}
87
- ```
88
-
89
- ## Images (frame + lightbox)
90
-
91
- Click the image to open the lightbox.
92
-
93
- ![RSS cover used as an in-article graphic](/blog/covers/rss.svg)
94
-
95
- ## Horizontal rule
96
-
97
- ---
98
-
99
- ## Final note
100
-
101
- If something looks wrong here, it will look wrong everywhere. Fix it here first.
@@ -1,39 +0,0 @@
1
- ---
2
- title: "A Markdown Pipeline That Looks Like Product UI"
3
- date: "2026-02-01"
4
- category: "Engineering"
5
- author: "alex"
6
- cover: "/blog/covers/markdown-pipeline.svg"
7
- tags:
8
- - "Markdown"
9
- - "Shiki"
10
- excerpt: "mdsvex gets us Markdown-in-Svelte; Shiki gets us code blocks that don’t look like blogspot."
11
- summaryAI: "Use mdsvex with `remark-gfm` (tables), `rehype-slug` + `rehype-autolink-headings` (deep links), and `@shikijs/rehype` (syntax highlighting with dual themes). Style `pre.shiki` to match Mintlify’s padding, radius, and line height."
12
- ---
13
-
14
- There are two failure modes for Markdown blogs:
15
-
16
- 1. Everything looks like a generic “prose” template.
17
- 2. Code blocks are unreadable in either light or dark mode.
18
-
19
- ## Tables (GFM)
20
-
21
- | Plugin | Why |
22
- | --- | --- |
23
- | `remark-gfm` | tables, strikethrough, task lists |
24
-
25
- ## Headings you can link to
26
-
27
- We add `id`s and autolink headings so every section is shareable.
28
-
29
- ## Shiki, dual theme
30
-
31
- Mintlify-style code blocks use different themes in light vs dark. We do the same.
32
-
33
- ```js
34
- // svelte.config.js
35
- rehypePlugins: [
36
- [rehypeShiki, { themes: { light: "one-light", dark: "github-dark-default" } }]
37
- ]
38
- ```
39
-
@@ -1,25 +0,0 @@
1
- ---
2
- title: "RSS Feeds That Actually Work"
3
- date: "2026-02-04"
4
- category: "Best practices"
5
- author: "shawn"
6
- cover: "/blog/covers/rss.svg"
7
- tags:
8
- - "RSS"
9
- - "SEO"
10
- excerpt: "Generate a real RSS 2.0 feed from your Markdown posts and expose it at /feed.xml."
11
- summaryAI: "Emit RSS 2.0 with an `atom:link rel=\"self\"`, include `title`, `description`, `link`, `guid`, `pubDate`, and `category` for each post, and set `content-type: application/rss+xml`. Keep descriptions short and safe (use the excerpt)."
12
- ---
13
-
14
- If you have a blog, you should have RSS. It’s a low-effort, high-trust feature.
15
-
16
- ## What we generate
17
-
18
- - RSS 2.0
19
- - `atom:link` self reference
20
- - Items with title, link, description, pubDate, and category
21
-
22
- ## A note on HTML
23
-
24
- RSS readers vary. Keep the description **plain text** (the excerpt) and let the post page carry the rich formatting.
25
-
@@ -1,25 +0,0 @@
1
- ---
2
- title: "Tag Tabs + Mobile Fade Masks"
3
- date: "2026-02-02"
4
- category: "Design"
5
- author: "maria"
6
- cover: "/blog/covers/design-tokens.svg"
7
- tags:
8
- - "Responsive"
9
- - "UI"
10
- excerpt: "A horizontally scrollable tag bar on mobile, with fade edges so it feels intentional instead of broken."
11
- summaryAI: "On small screens, render the tag row as `overflow-x-auto` with hidden scrollbar. Add a `mask-image` gradient to fade the left/right edges. On desktop, disable the mask and allow the pills to sit normally."
12
- ---
13
-
14
- Pill tabs are easy on desktop, but mobile needs a different treatment.
15
-
16
- ## The goal
17
-
18
- - Horizontal scroll
19
- - No visible scrollbar
20
- - Subtle fade at the edges
21
-
22
- ## CSS mask trick
23
-
24
- We apply a `mask-image` gradient to the scrolling container, then remove it on larger screens.
25
-
@@ -1 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>
@@ -1,24 +0,0 @@
1
- <script lang="ts">
2
- import { ThemeSwitcher } from '@aureuma/blogkit/theme';
3
- import { theme } from '$lib/stores/theme';
4
- </script>
5
-
6
- <footer class="border-t border-border-soft/10">
7
- <div class="mx-auto flex w-full max-w-5xl flex-col gap-6 px-6 py-10 md:flex-row md:items-center md:justify-between">
8
- <div class="text-xs font-mono uppercase tracking-[0.6px] text-text-muted">
9
- © {new Date().getFullYear()} svelta
10
- </div>
11
-
12
- <div class="flex items-center justify-between gap-6 md:justify-end">
13
- <a
14
- href="/feed.xml"
15
- class="inline-flex items-center gap-2 text-xs font-mono uppercase tracking-[0.6px] text-text-sub transition hover:text-text-main"
16
- >
17
- <span>RSS</span>
18
- <span class="inline-block size-1.5 rounded-full bg-brand" aria-hidden="true"></span>
19
- </a>
20
-
21
- <ThemeSwitcher controller={theme} />
22
- </div>
23
- </div>
24
- </footer>
@@ -1,36 +0,0 @@
1
- <script lang="ts">
2
- import { page } from '$app/stores';
3
-
4
- function isActive(path: string) {
5
- return $page.url.pathname === path || $page.url.pathname.startsWith(`${path}/`);
6
- }
7
- </script>
8
-
9
- <header
10
- class="sticky top-0 z-50 border-b border-border-soft/10 bg-background-main/75 backdrop-blur supports-[backdrop-filter]:bg-background-main/55"
11
- >
12
- <div class="mx-auto flex h-16 w-full max-w-5xl items-center justify-between px-6">
13
- <a href="/" class="group inline-flex items-center gap-2">
14
- <span class="text-sm font-semibold tracking-tight">svelta</span>
15
- <span
16
- class="rounded-full bg-brand/15 px-2 py-0.5 text-[11px] font-mono uppercase tracking-[0.6px] text-brand"
17
- >
18
- Blog
19
- </span>
20
- </a>
21
-
22
- <nav class="flex items-center gap-6 text-sm">
23
- <a
24
- href="/"
25
- class="transition hover:text-text-main {isActive('/') ? 'text-text-main' : 'text-text-sub'}"
26
- >Home</a
27
- >
28
- <a
29
- href="/blog"
30
- class="transition hover:text-text-main {isActive('/blog') ? 'text-text-main' : 'text-text-sub'}"
31
- >Blog</a
32
- >
33
- </nav>
34
- </div>
35
- </header>
36
-
@@ -1,28 +0,0 @@
1
- import type { BlogAuthor } from '$lib/types/blog';
2
-
3
- export const AUTHORS: Record<string, BlogAuthor> = {
4
- shawn: {
5
- id: 'shawn',
6
- name: 'Shawn',
7
- title: 'Builder at svelta',
8
- avatar: '/blog/authors/shawn.svg'
9
- },
10
- alex: {
11
- id: 'alex',
12
- name: 'Alex Kim',
13
- title: 'Product Engineer',
14
- avatar: '/blog/authors/alex.svg'
15
- },
16
- maria: {
17
- id: 'maria',
18
- name: 'Maria Santos',
19
- title: 'Design Lead',
20
- avatar: '/blog/authors/maria.svg'
21
- }
22
- };
23
-
24
- export function getAuthor(id: string): BlogAuthor {
25
- const author = AUTHORS[id];
26
- if (!author) throw new Error(`Unknown author id: ${id}`);
27
- return author;
28
- }
package/src/lib/index.ts DELETED
@@ -1 +0,0 @@
1
- // place files you want to import through the `$lib` alias in this folder.
@@ -1,22 +0,0 @@
1
- import { createBlog } from '@aureuma/blogkit/server';
2
- import { getAuthor } from '$lib/content/authors';
3
- import type { BlogPostFull } from '$lib/types/blog';
4
-
5
- type CompiledModule = { default: BlogPostFull['component'] };
6
-
7
- const compiledModules = import.meta.glob('/src/content/blog/*.md') as Record<
8
- string,
9
- () => Promise<CompiledModule>
10
- >;
11
- const rawModules = import.meta.glob('/src/content/blog/*.md', {
12
- query: '?raw',
13
- import: 'default'
14
- }) as Record<string, () => Promise<string>>;
15
-
16
- export const blog = createBlog({
17
- compiledModules,
18
- rawModules,
19
- getAuthor
20
- });
21
-
22
- export const { getAllPosts, getAllPostsFull, getPostBySlug, getCategories, pickHero } = blog;
@@ -1,58 +0,0 @@
1
- import type { BlogPost } from '$lib/types/blog';
2
-
3
- function escapeXml(input: string) {
4
- return input
5
- .replace(/&/g, '&amp;')
6
- .replace(/</g, '&lt;')
7
- .replace(/>/g, '&gt;')
8
- .replace(/"/g, '&quot;')
9
- .replace(/'/g, '&apos;');
10
- }
11
-
12
- function toPubDate(iso: string) {
13
- const d = /^\d{4}-\d{2}-\d{2}$/.test(iso) ? new Date(`${iso}T00:00:00Z`) : new Date(iso);
14
- return d.toUTCString();
15
- }
16
-
17
- export function buildRss(opts: {
18
- baseUrl: URL;
19
- posts: BlogPost[];
20
- siteTitle: string;
21
- description: string;
22
- maxItems?: number;
23
- }) {
24
- const siteLink = new URL('/blog', opts.baseUrl).toString();
25
- const selfLink = new URL('/feed.xml', opts.baseUrl).toString();
26
- const maxItems = Math.max(1, Math.min(200, opts.maxItems ?? 50));
27
-
28
- const items = opts.posts
29
- .slice(0, maxItems)
30
- .map((post) => {
31
- const link = new URL(`/blog/${post.slug}`, opts.baseUrl).toString();
32
- return [
33
- '<item>',
34
- `<title>${escapeXml(post.title)}</title>`,
35
- `<description>${escapeXml(post.excerpt)}</description>`,
36
- `<link>${escapeXml(link)}</link>`,
37
- `<guid isPermaLink="true">${escapeXml(link)}</guid>`,
38
- `<pubDate>${escapeXml(toPubDate(post.date))}</pubDate>`,
39
- `<category>${escapeXml(post.category.label)}</category>`,
40
- `<author>${escapeXml(post.author.name)}</author>`,
41
- '</item>'
42
- ].join('');
43
- })
44
- .join('');
45
-
46
- return `<?xml version="1.0" encoding="UTF-8"?>
47
- <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
48
- <channel>
49
- <title>${escapeXml(opts.siteTitle)}</title>
50
- <link>${escapeXml(siteLink)}</link>
51
- <atom:link href="${escapeXml(selfLink)}" rel="self" type="application/rss+xml" />
52
- <description>${escapeXml(opts.description)}</description>
53
- <language>en-us</language>
54
- ${items}
55
- </channel>
56
- </rss>`;
57
- }
58
-
@@ -1,31 +0,0 @@
1
- import type { BlogPost } from '$lib/types/blog';
2
-
3
- export type PostSeo = {
4
- title: string;
5
- description: string;
6
- canonicalUrl: string;
7
- og: {
8
- title: string;
9
- description: string;
10
- type: 'article';
11
- url: string;
12
- };
13
- };
14
-
15
- export function buildPostSeo(post: BlogPost, canonicalUrl: string): PostSeo {
16
- const title = `${post.title} | svelta Blog`;
17
- const description = post.excerpt;
18
-
19
- return {
20
- title,
21
- description,
22
- canonicalUrl,
23
- og: {
24
- title: post.title,
25
- description,
26
- type: 'article',
27
- url: canonicalUrl
28
- }
29
- };
30
- }
31
-
@@ -1,10 +0,0 @@
1
- import { createThemeController, type ThemeController, type ThemeMode } from '@aureuma/blogkit/theme';
2
-
3
- // Keep the storage key stable for this app (also used in `src/app.html`).
4
- export const theme: ThemeController = createThemeController({ storageKey: 'svelta-theme' });
5
-
6
- export const themeMode = theme.themeMode;
7
- export const initTheme = theme.initTheme;
8
- export const setThemeMode = theme.setThemeMode;
9
-
10
- export type { ThemeMode };
@@ -1 +0,0 @@
1
- export type { BlogAuthor, BlogCategory, BlogPost, BlogPostFull } from '@aureuma/blogkit';
@@ -1,31 +0,0 @@
1
- <script lang="ts">
2
- import favicon from '$lib/assets/favicon.svg';
3
- import '@fontsource/inter/400.css';
4
- import '@fontsource/inter/500.css';
5
- import '@fontsource/inter/600.css';
6
- import '@fontsource/geist-mono/400.css';
7
- import '@fontsource/geist-mono/500.css';
8
- import '../app.css';
9
- import SiteFooter from '$lib/components/site/SiteFooter.svelte';
10
- import SiteHeader from '$lib/components/site/SiteHeader.svelte';
11
- import { initTheme } from '$lib/stores/theme';
12
- import { onMount } from 'svelte';
13
-
14
- let { children } = $props();
15
-
16
- onMount(() => {
17
- const cleanup = initTheme();
18
- return cleanup;
19
- });
20
- </script>
21
-
22
- <svelte:head>
23
- <link rel="icon" href={favicon} />
24
- <link rel="alternate" type="application/rss+xml" title="svelta Blog" href="/feed.xml" />
25
- </svelte:head>
26
-
27
- <div class="min-h-dvh bg-background-main text-text-main">
28
- <SiteHeader />
29
- <main class="flex-1">{@render children()}</main>
30
- <SiteFooter />
31
- </div>