@riebeckite/plugin-recent-posts 0.0.19 → 0.0.20
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 +13 -19
- package/README_ja.md +6 -4
- package/dist/components/recent-posts.js +18 -25
- package/dist/index.js +34 -42
- package/dist/src/recent-posts.server.d.ts +13 -15
- package/package.json +2 -2
- package/style.css +12 -12
package/README.md
CHANGED
|
@@ -8,10 +8,12 @@ frontmatter date.
|
|
|
8
8
|
## Overview
|
|
9
9
|
|
|
10
10
|
`recentPosts()` provides a `RecentPosts` component that renders an ordered list
|
|
11
|
-
of recently posted articles. `getRecentPosts()`
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
of recently posted articles. `getRecentPosts()` reads
|
|
12
|
+
`manifest.discoverableEntries`, so `unlisted`, `draft`, and scheduled notes are
|
|
13
|
+
excluded. It derives a date from the frontmatter (`date` falling back to
|
|
14
|
+
`created`), sorts newest first, and truncates to `limit` items. Notes without a
|
|
15
|
+
parseable date are dropped. Whether and where to place the list is the site's
|
|
16
|
+
decision.
|
|
15
17
|
|
|
16
18
|
With no posts, the component renders nothing.
|
|
17
19
|
|
|
@@ -34,18 +36,9 @@ export default defineConfig({
|
|
|
34
36
|
|
|
35
37
|
```tsx
|
|
36
38
|
import RecentPosts, { getRecentPosts } from "@riebeckite/plugin-recent-posts";
|
|
37
|
-
import { config } from "../config";
|
|
38
39
|
import { content } from "../content";
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const posts = await content.getAllPosts();
|
|
42
|
-
const recentPosts = await getRecentPosts({
|
|
43
|
-
posts,
|
|
44
|
-
config,
|
|
45
|
-
getProcessedContent: (slug) => content.getProcessedContent(slug),
|
|
46
|
-
resolveTitle: getArticleTitle,
|
|
47
|
-
limit: 5,
|
|
48
|
-
});
|
|
40
|
+
|
|
41
|
+
const recentPosts = getRecentPosts({ manifest: await content.getManifest() });
|
|
49
42
|
|
|
50
43
|
// ...in your route
|
|
51
44
|
return <Article afterContent={<RecentPosts posts={recentPosts} />} />;
|
|
@@ -56,7 +49,7 @@ return <Article afterContent={<RecentPosts posts={recentPosts} />} />;
|
|
|
56
49
|
## Component
|
|
57
50
|
|
|
58
51
|
`RecentPosts({ posts })` renders a
|
|
59
|
-
`<section class="
|
|
52
|
+
`<section class="rr-recent-posts">` with a heading and an ordered
|
|
60
53
|
list. Each item links to the post and shows its date formatted for the
|
|
61
54
|
`en-US` locale. The `rr-recent-posts` root hook is the stable class themes may
|
|
62
55
|
target.
|
|
@@ -65,16 +58,17 @@ target.
|
|
|
65
58
|
|
|
66
59
|
| Option | Type | Default | Description |
|
|
67
60
|
| ------ | ---- | ------- | ----------- |
|
|
61
|
+
| `manifest` | `Pick<ContentManifest, "discoverableEntries">` | — | Source of candidate posts |
|
|
68
62
|
| `limit` | `number` | `5` | Maximum number of posts to return |
|
|
69
63
|
|
|
70
64
|
## Exports
|
|
71
65
|
|
|
72
66
|
- `recentPostsPlugin()` — plugin factory
|
|
73
67
|
- `RecentPosts` — list component (default export of `components/recent-posts.tsx`)
|
|
74
|
-
- `getRecentPosts({
|
|
75
|
-
|
|
68
|
+
- `getRecentPosts({ manifest, limit? })` — collects the latest discoverable
|
|
69
|
+
posts
|
|
76
70
|
- Type: `RecentPost` (`{ slug, permalink, title, postedAt }`)
|
|
77
71
|
|
|
78
72
|
## See also
|
|
79
73
|
|
|
80
|
-
- [Plugin guide](../../../docs/docs/reference/plugin-api.
|
|
74
|
+
- [Plugin guide](../../../docs/docs/reference/plugin-api.md)
|
package/README_ja.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
## できること
|
|
8
8
|
|
|
9
|
-
`getRecentPosts()`
|
|
9
|
+
`getRecentPosts()` は `manifest.discoverableEntries` を読み、unlisted・draft・予約公開のノートと `index` ノートを除き、`frontmatter.date`、なければ `created` を基準に新しい順へ並べます。日付を解釈できないノートは一覧から外れます。`RecentPosts` は結果が空なら何も描画しません。一覧を置くかどうか、どこへ置くかは Site 側で決めます。
|
|
10
10
|
|
|
11
11
|
## 設定と配置
|
|
12
12
|
|
|
@@ -21,23 +21,25 @@ export default defineConfig({ plugins: [recentPostsPlugin()] });
|
|
|
21
21
|
|
|
22
22
|
```tsx
|
|
23
23
|
import RecentPosts, { getRecentPosts } from "@riebeckite/plugin-recent-posts";
|
|
24
|
+
import { content } from "../content";
|
|
24
25
|
|
|
25
|
-
const posts =
|
|
26
|
+
const posts = getRecentPosts({ manifest: await content.getManifest() });
|
|
26
27
|
return <Article afterContent={<RecentPosts posts={posts} />} />;
|
|
27
28
|
```
|
|
28
29
|
|
|
29
30
|
| オプション | 既定値 | 内容 |
|
|
30
31
|
| --- | --- | --- |
|
|
32
|
+
| `manifest` | 必須 | 候補ノートの取得元(`discoverableEntries`) |
|
|
31
33
|
| `limit` | `5` | 返す記事数の上限 |
|
|
32
34
|
|
|
33
35
|
## 公開 API
|
|
34
36
|
|
|
35
37
|
- `recentPostsPlugin()` — プラグインファクトリ
|
|
36
38
|
- `RecentPosts` — 最新記事一覧コンポーネント
|
|
37
|
-
- `getRecentPosts({
|
|
39
|
+
- `getRecentPosts({ manifest, limit? })`
|
|
38
40
|
- `RecentPost` — `{ slug, permalink, title, postedAt }` の型
|
|
39
41
|
|
|
40
42
|
## 関連資料
|
|
41
43
|
|
|
42
|
-
- [プラグインシステム](../../../docs/docs/reference/plugin-api.md)
|
|
44
|
+
- [プラグインシステム](../../../docs/docs/reference/plugin-api.ja.md)
|
|
43
45
|
|
|
@@ -2,31 +2,24 @@
|
|
|
2
2
|
import { jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
3
3
|
function RecentPosts(props) {
|
|
4
4
|
if (props.posts.length === 0) return null;
|
|
5
|
-
return /* @__PURE__ */ jsxs(
|
|
6
|
-
"
|
|
7
|
-
{
|
|
8
|
-
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
),
|
|
24
|
-
/* @__PURE__ */ jsx("span", { class: "recent-posts__post-title", children: post.title })
|
|
25
|
-
] }) }, post.slug);
|
|
26
|
-
}) })
|
|
27
|
-
]
|
|
28
|
-
}
|
|
29
|
-
);
|
|
5
|
+
return /* @__PURE__ */ jsxs("section", { class: "rr-recent-posts", "aria-labelledby": "recent-posts-title", children: [
|
|
6
|
+
/* @__PURE__ */ jsx("div", { class: "rr-recent-posts__header", children: /* @__PURE__ */ jsx("h2", { class: "rr-recent-posts__title", id: "recent-posts-title", children: "Recent Posts" }) }),
|
|
7
|
+
/* @__PURE__ */ jsx("ol", { class: "rr-recent-posts__list", children: props.posts.map((post) => {
|
|
8
|
+
const formattedDate = formatPostedDate(post.postedAt);
|
|
9
|
+
return /* @__PURE__ */ jsx("li", { class: "rr-recent-posts__item", children: /* @__PURE__ */ jsxs("a", { class: "rr-recent-posts__link", href: post.permalink, children: [
|
|
10
|
+
/* @__PURE__ */ jsx(
|
|
11
|
+
"time",
|
|
12
|
+
{
|
|
13
|
+
class: "rr-recent-posts__date",
|
|
14
|
+
dateTime: formattedDate.isoDate,
|
|
15
|
+
title: formattedDate.fullDate,
|
|
16
|
+
children: formattedDate.displayDate
|
|
17
|
+
}
|
|
18
|
+
),
|
|
19
|
+
/* @__PURE__ */ jsx("span", { class: "rr-recent-posts__post-title", children: post.title })
|
|
20
|
+
] }) }, post.slug);
|
|
21
|
+
}) })
|
|
22
|
+
] });
|
|
30
23
|
}
|
|
31
24
|
function formatPostedDate(date) {
|
|
32
25
|
const isoDate = date.toISOString();
|
package/dist/index.js
CHANGED
|
@@ -5,31 +5,24 @@ import { definePlugin } from "@riebeckite/core";
|
|
|
5
5
|
import { jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
6
6
|
function RecentPosts(props) {
|
|
7
7
|
if (props.posts.length === 0) return null;
|
|
8
|
-
return /* @__PURE__ */ jsxs(
|
|
9
|
-
"
|
|
10
|
-
{
|
|
11
|
-
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
),
|
|
27
|
-
/* @__PURE__ */ jsx("span", { class: "recent-posts__post-title", children: post.title })
|
|
28
|
-
] }) }, post.slug);
|
|
29
|
-
}) })
|
|
30
|
-
]
|
|
31
|
-
}
|
|
32
|
-
);
|
|
8
|
+
return /* @__PURE__ */ jsxs("section", { class: "rr-recent-posts", "aria-labelledby": "recent-posts-title", children: [
|
|
9
|
+
/* @__PURE__ */ jsx("div", { class: "rr-recent-posts__header", children: /* @__PURE__ */ jsx("h2", { class: "rr-recent-posts__title", id: "recent-posts-title", children: "Recent Posts" }) }),
|
|
10
|
+
/* @__PURE__ */ jsx("ol", { class: "rr-recent-posts__list", children: props.posts.map((post) => {
|
|
11
|
+
const formattedDate = formatPostedDate(post.postedAt);
|
|
12
|
+
return /* @__PURE__ */ jsx("li", { class: "rr-recent-posts__item", children: /* @__PURE__ */ jsxs("a", { class: "rr-recent-posts__link", href: post.permalink, children: [
|
|
13
|
+
/* @__PURE__ */ jsx(
|
|
14
|
+
"time",
|
|
15
|
+
{
|
|
16
|
+
class: "rr-recent-posts__date",
|
|
17
|
+
dateTime: formattedDate.isoDate,
|
|
18
|
+
title: formattedDate.fullDate,
|
|
19
|
+
children: formattedDate.displayDate
|
|
20
|
+
}
|
|
21
|
+
),
|
|
22
|
+
/* @__PURE__ */ jsx("span", { class: "rr-recent-posts__post-title", children: post.title })
|
|
23
|
+
] }) }, post.slug);
|
|
24
|
+
}) })
|
|
25
|
+
] });
|
|
33
26
|
}
|
|
34
27
|
function formatPostedDate(date) {
|
|
35
28
|
const isoDate = date.toISOString();
|
|
@@ -46,24 +39,23 @@ function formatPostedDate(date) {
|
|
|
46
39
|
}
|
|
47
40
|
|
|
48
41
|
// src/recent-posts.server.ts
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
slug:
|
|
60
|
-
permalink:
|
|
61
|
-
title:
|
|
42
|
+
var DEFAULT_RECENT_POSTS_LIMIT = 5;
|
|
43
|
+
function getRecentPosts(args) {
|
|
44
|
+
const limit = Math.max(0, args.limit ?? DEFAULT_RECENT_POSTS_LIMIT);
|
|
45
|
+
return args.manifest.discoverableEntries.filter((entry) => entry.slug !== "index").flatMap((entry) => {
|
|
46
|
+
const postedAt = parseFrontmatterDate(
|
|
47
|
+
entry.frontmatter.date ?? entry.frontmatter.created
|
|
48
|
+
);
|
|
49
|
+
if (!postedAt) return [];
|
|
50
|
+
return [
|
|
51
|
+
{
|
|
52
|
+
slug: entry.slug,
|
|
53
|
+
permalink: entry.permalink,
|
|
54
|
+
title: entry.title,
|
|
62
55
|
postedAt
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
);
|
|
66
|
-
return recentPosts.filter((post) => post !== null).toSorted((a, b) => b.postedAt.getTime() - a.postedAt.getTime()).slice(0, limit);
|
|
56
|
+
}
|
|
57
|
+
];
|
|
58
|
+
}).toSorted((a, b) => b.postedAt.getTime() - a.postedAt.getTime()).slice(0, limit);
|
|
67
59
|
}
|
|
68
60
|
function parseFrontmatterDate(value) {
|
|
69
61
|
if (value instanceof Date) return value;
|
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ContentManifest } from "@riebeckite/core";
|
|
2
2
|
import type { RecentPost } from "./recent-posts.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
export declare const DEFAULT_RECENT_POSTS_LIMIT = 5;
|
|
4
|
+
/**
|
|
5
|
+
* Builds the recent-posts list from the manifest.
|
|
6
|
+
*
|
|
7
|
+
* Reads `manifest.discoverableEntries` on purpose: discovery UI must never
|
|
8
|
+
* surface `unlisted`, `draft`, or future-`publishAt` notes that are not
|
|
9
|
+
* discoverable, even though some of them are routable. The plugin owns the
|
|
10
|
+
* entry selection, the frontmatter date extraction, the sort, and the default
|
|
11
|
+
* title so a Site only decides whether and where to render the list.
|
|
12
|
+
*/
|
|
11
13
|
export declare function getRecentPosts(args: {
|
|
12
|
-
|
|
13
|
-
config: ResolvedRiebeckiteConfig;
|
|
14
|
-
getProcessedContent: (slug: string) => Promise<ProcessedPost>;
|
|
15
|
-
resolveTitle: TitleResolver;
|
|
14
|
+
manifest: Pick<ContentManifest, "discoverableEntries">;
|
|
16
15
|
limit?: number;
|
|
17
|
-
}):
|
|
18
|
-
export {};
|
|
16
|
+
}): RecentPost[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@riebeckite/plugin-recent-posts",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.20",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"hono": "^4.12.25",
|
|
25
|
-
"@riebeckite/core": "0.0.
|
|
25
|
+
"@riebeckite/core": "0.0.20"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"tsx": "^4.22.4"
|
package/style.css
CHANGED
|
@@ -1,35 +1,35 @@
|
|
|
1
1
|
@layer components {
|
|
2
|
-
.recent-posts {
|
|
2
|
+
.rr-recent-posts {
|
|
3
3
|
margin-top: 4rem;
|
|
4
4
|
padding-top: 1.5rem;
|
|
5
5
|
border-top: 1px solid color-mix(in srgb, currentColor 18%, transparent);
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
.recent-posts__header {
|
|
8
|
+
.rr-recent-posts__header {
|
|
9
9
|
margin-bottom: 1.25rem;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
.recent-posts__title {
|
|
12
|
+
.rr-recent-posts__title {
|
|
13
13
|
margin: 0;
|
|
14
14
|
font-size: clamp(1.5rem, 4vw, 2rem);
|
|
15
15
|
line-height: 1.2;
|
|
16
16
|
letter-spacing: -0.02em;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
.recent-posts__list {
|
|
19
|
+
.rr-recent-posts__list {
|
|
20
20
|
display: grid;
|
|
21
21
|
margin: 0;
|
|
22
22
|
padding: 0;
|
|
23
23
|
list-style: none;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
.recent-posts__item {
|
|
26
|
+
.rr-recent-posts__item {
|
|
27
27
|
margin: 0;
|
|
28
28
|
padding: 0;
|
|
29
29
|
border-bottom: 1px solid color-mix(in srgb, currentColor 14%, transparent);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
.recent-posts__link {
|
|
32
|
+
.rr-recent-posts__link {
|
|
33
33
|
display: grid;
|
|
34
34
|
gap: 0.375rem;
|
|
35
35
|
padding: 1rem 0;
|
|
@@ -40,30 +40,30 @@
|
|
|
40
40
|
padding-inline 160ms ease;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
.recent-posts__link:hover,
|
|
44
|
-
.recent-posts__link:focus-visible {
|
|
43
|
+
.rr-recent-posts__link:hover,
|
|
44
|
+
.rr-recent-posts__link:focus-visible {
|
|
45
45
|
color: var(--rb-color-accent);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
.recent-posts__link:focus-visible {
|
|
48
|
+
.rr-recent-posts__link:focus-visible {
|
|
49
49
|
outline: 2px solid var(--rb-color-accent);
|
|
50
50
|
outline-offset: 4px;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
.recent-posts__date {
|
|
53
|
+
.rr-recent-posts__date {
|
|
54
54
|
font-size: 0.75rem;
|
|
55
55
|
font-weight: 700;
|
|
56
56
|
letter-spacing: 0.08em;
|
|
57
57
|
color: color-mix(in srgb, currentColor 62%, transparent);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
.recent-posts__post-title {
|
|
60
|
+
.rr-recent-posts__post-title {
|
|
61
61
|
font-size: 1rem;
|
|
62
62
|
line-height: 1.5;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
@media (min-width: 40rem) {
|
|
66
|
-
.recent-posts__link {
|
|
66
|
+
.rr-recent-posts__link {
|
|
67
67
|
grid-template-columns: 8rem minmax(0, 1fr);
|
|
68
68
|
align-items: baseline;
|
|
69
69
|
gap: 1.5rem;
|