ferst-core 0.7.2 → 0.7.4

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 (37) hide show
  1. package/components/BlockRenderer.astro +6 -0
  2. package/components/Pagination.astro +51 -0
  3. package/components/blocks/Heading.astro +47 -46
  4. package/components/blocks/Prose.astro +10 -2
  5. package/components/blocks/Quote.astro +40 -38
  6. package/components/recipes/Accordion.astro +91 -84
  7. package/components/recipes/Document.astro +152 -0
  8. package/components/recipes/FeatureCards.astro +112 -111
  9. package/components/recipes/Hero.astro +52 -4
  10. package/components/recipes/LinkRow.astro +91 -0
  11. package/components/recipes/MediaCards.astro +213 -212
  12. package/components/recipes/Notice.astro +90 -89
  13. package/components/recipes/Stats.astro +152 -145
  14. package/components/recipes/Table.astro +98 -97
  15. package/components/recipes/Tabs.astro +134 -133
  16. package/components/recipes/Tiles.astro +4 -2
  17. package/components/recipes/Timetable.astro +189 -0
  18. package/components/recipes/Video.astro +177 -157
  19. package/content/blocks.ts +65 -1
  20. package/content/cookies.ts +10 -0
  21. package/examples/primitives.ts +2 -0
  22. package/examples/recipes.ts +5 -0
  23. package/integration.mjs +83 -64
  24. package/layouts/Base.astro +481 -475
  25. package/lib/documents.ts +68 -62
  26. package/lib/embed-consent.ts +78 -73
  27. package/lib/inline-markdown.ts +6 -0
  28. package/lib/links.ts +12 -0
  29. package/lib/paginate.ts +23 -0
  30. package/lib/posts.ts +20 -2
  31. package/lib/timetable.ts +27 -0
  32. package/lib/video.ts +101 -75
  33. package/package.json +1 -1
  34. package/routes/cookie-policy.astro +27 -0
  35. package/routes/posts/index.astro +36 -31
  36. package/routes/posts/page/[page].astro +36 -0
  37. package/routes/posts/tag/[...tag].astro +41 -37
@@ -1,31 +1,36 @@
1
- ---
2
- /**
3
- * Posts index (`/posts`) — INJECTED by the ferst() integration (blog option).
4
- * The news / notices listing. Excludes any tag in postsSettings.excludeFromIndex
5
- * (e.g. a newsletter series that has its own tag page). Content, not code — a
6
- * client carries the post markdown + tag data; this route ships from core.
7
- */
8
- import Base from '../../layouts/Base.astro';
9
- import NewsList from '../../components/NewsList.astro';
10
- import PostsFilter from '../../components/PostsFilter.astro';
11
- import { getIndexPosts, getPostsSettings, getUsedTags } from '../../lib/posts';
12
-
13
- const [posts, settings, tags] = await Promise.all([getIndexPosts(), getPostsSettings(), getUsedTags()]);
14
- ---
15
-
16
- <Base title={settings.title} description={settings.intro || undefined}>
17
- <section class="posts">
18
- <header class="posts__head">
19
- <h1>{settings.title}</h1>
20
- {settings.intro && <p class="t-lead">{settings.intro}</p>}
21
- </header>
22
- <PostsFilter tags={tags} />
23
- <NewsList posts={posts} empty="No posts yet." />
24
- </section>
25
- </Base>
26
-
27
- <style>
28
- .posts { display: flex; flex-direction: column; gap: 2rem; }
29
- .posts__head { display: flex; flex-direction: column; gap: 0.5rem; }
30
- .posts__head .t-lead { color: var(--muted); }
31
- </style>
1
+ ---
2
+ /**
3
+ * Posts index (`/posts`) — INJECTED by the ferst() integration (blog option).
4
+ * The news / notices listing. Excludes any tag in postsSettings.excludeFromIndex
5
+ * (e.g. a newsletter series that has its own tag page). Content, not code — a
6
+ * client carries the post markdown + tag data; this route ships from core.
7
+ */
8
+ import Base from '../../layouts/Base.astro';
9
+ import NewsList from '../../components/NewsList.astro';
10
+ import PostsFilter from '../../components/PostsFilter.astro';
11
+ import Pagination from '../../components/Pagination.astro';
12
+ import { getIndexPosts, getPostsSettings, getUsedTags } from '../../lib/posts';
13
+ import { pageCount, pageSlice } from '../../lib/paginate';
14
+
15
+ const [all, settings, tags] = await Promise.all([getIndexPosts(), getPostsSettings(), getUsedTags()]);
16
+ const pages = pageCount(all.length, settings.perPage);
17
+ const posts = pageSlice(all, 1, settings.perPage);
18
+ ---
19
+
20
+ <Base title={settings.title} description={settings.intro || undefined}>
21
+ <section class="posts">
22
+ <header class="posts__head">
23
+ <h1>{settings.title}</h1>
24
+ {settings.intro && <p class="t-lead">{settings.intro}</p>}
25
+ </header>
26
+ <PostsFilter tags={tags} />
27
+ <NewsList posts={posts} empty="No posts yet." />
28
+ <Pagination base="/posts" page={1} pages={pages} />
29
+ </section>
30
+ </Base>
31
+
32
+ <style>
33
+ .posts { display: flex; flex-direction: column; gap: 2rem; }
34
+ .posts__head { display: flex; flex-direction: column; gap: 0.5rem; }
35
+ .posts__head .t-lead { color: var(--muted); }
36
+ </style>
@@ -0,0 +1,36 @@
1
+ ---
2
+ /**
3
+ * Posts index, page 2 onwards (`/posts/page/<n>`) — INJECTED by the ferst() integration
4
+ * (blog option). Page 1 is `/posts`. Pages are `postsSettings.perPage` posts long.
5
+ */
6
+ import Base from '../../../layouts/Base.astro';
7
+ import NewsList from '../../../components/NewsList.astro';
8
+ import PostsFilter from '../../../components/PostsFilter.astro';
9
+ import Pagination from '../../../components/Pagination.astro';
10
+ import { getIndexPageRoutes, getIndexPosts, getPostsSettings, getUsedTags } from '../../../lib/posts';
11
+ import { pageSlice } from '../../../lib/paginate';
12
+
13
+ export const getStaticPaths = getIndexPageRoutes;
14
+
15
+ const { page, pages } = Astro.props as { page: number; pages: number };
16
+ const [all, settings, tags] = await Promise.all([getIndexPosts(), getPostsSettings(), getUsedTags()]);
17
+ const posts = pageSlice(all, page, settings.perPage);
18
+ ---
19
+
20
+ <Base title={`${settings.title} — page ${page}`} description={settings.intro || undefined}>
21
+ <section class="posts">
22
+ <header class="posts__head">
23
+ <h1>{settings.title}</h1>
24
+ <p class="posts__page">Page {page} of {pages}</p>
25
+ </header>
26
+ <PostsFilter tags={tags} />
27
+ <NewsList posts={posts} />
28
+ <Pagination base="/posts" page={page} pages={pages} />
29
+ </section>
30
+ </Base>
31
+
32
+ <style>
33
+ .posts { display: flex; flex-direction: column; gap: 2rem; }
34
+ .posts__head { display: flex; flex-direction: column; gap: 0.5rem; }
35
+ .posts__page { margin: 0; color: var(--muted); }
36
+ </style>
@@ -1,37 +1,41 @@
1
- ---
2
- /**
3
- * A tag page (`/posts/tag/<slug>`) — INJECTED by the ferst() integration (blog
4
- * option). All posts carrying one tag. This is also how an excluded-from-index
5
- * series (e.g. a newsletter) gets its own page.
6
- */
7
- import Base from '../../../layouts/Base.astro';
8
- import NewsList from '../../../components/NewsList.astro';
9
- import PostsFilter from '../../../components/PostsFilter.astro';
10
- import { getTagRoutes, getPostsByTag, getUsedTags } from '../../../lib/posts';
11
-
12
- export const getStaticPaths = getTagRoutes;
13
-
14
- const { tag } = Astro.props;
15
- const [posts, tags] = await Promise.all([getPostsByTag(tag.slug), getUsedTags()]);
16
- ---
17
-
18
- <Base title={tag.label} description={tag.description || undefined}>
19
- <section class="posts">
20
- <header class="posts__head">
21
- <p class="posts__eyebrow">Tag</p>
22
- <h1>{tag.label}</h1>
23
- {tag.description && <p class="t-lead">{tag.description}</p>}
24
- </header>
25
- <PostsFilter tags={tags} active={tag.slug} />
26
- <NewsList posts={posts} />
27
- <a class="posts__back" href="/posts">&larr; All posts</a>
28
- </section>
29
- </Base>
30
-
31
- <style>
32
- .posts { display: flex; flex-direction: column; gap: 2rem; }
33
- .posts__head { display: flex; flex-direction: column; gap: 0.35rem; }
34
- .posts__eyebrow { margin: 0; font-family: var(--font-heading); font-size: 0.75rem; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; color: var(--gold); }
35
- .posts__head .t-lead { color: var(--muted); }
36
- .posts__back { font-weight: 600; color: var(--gold); }
37
- </style>
1
+ ---
2
+ /**
3
+ * A tag page (`/posts/tag/<slug>`) — INJECTED by the ferst() integration (blog
4
+ * option). All posts carrying one tag. This is also how an excluded-from-index
5
+ * series (e.g. a newsletter) gets its own page.
6
+ */
7
+ import Base from '../../../layouts/Base.astro';
8
+ import NewsList from '../../../components/NewsList.astro';
9
+ import PostsFilter from '../../../components/PostsFilter.astro';
10
+ import Pagination from '../../../components/Pagination.astro';
11
+ import { getTagRoutes, getPostsByTag, getPostsSettings, getUsedTags } from '../../../lib/posts';
12
+ import { pageSlice } from '../../../lib/paginate';
13
+
14
+ export const getStaticPaths = getTagRoutes;
15
+
16
+ const { tag, page = 1, pages = 1 } = Astro.props;
17
+ const [all, tags, settings] = await Promise.all([getPostsByTag(tag.slug), getUsedTags(), getPostsSettings()]);
18
+ const posts = pageSlice(all, page, settings.perPage);
19
+ ---
20
+
21
+ <Base title={page > 1 ? `${tag.label} — page ${page}` : tag.label} description={tag.description || undefined}>
22
+ <section class="posts">
23
+ <header class="posts__head">
24
+ <p class="posts__eyebrow">Tag</p>
25
+ <h1>{tag.label}</h1>
26
+ {tag.description && <p class="t-lead">{tag.description}</p>}
27
+ </header>
28
+ <PostsFilter tags={tags} active={tag.slug} />
29
+ <NewsList posts={posts} />
30
+ <Pagination base={`/posts/tag/${tag.slug}`} page={page} pages={pages} />
31
+ <a class="posts__back" href="/posts">&larr; All posts</a>
32
+ </section>
33
+ </Base>
34
+
35
+ <style>
36
+ .posts { display: flex; flex-direction: column; gap: 2rem; }
37
+ .posts__head { display: flex; flex-direction: column; gap: 0.35rem; }
38
+ .posts__eyebrow { margin: 0; font-family: var(--font-heading); font-size: 0.75rem; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; color: var(--gold); }
39
+ .posts__head .t-lead { color: var(--muted); }
40
+ .posts__back { font-weight: 600; color: var(--gold); }
41
+ </style>