@riebeckite/plugin-related-posts 0.0.22 → 0.0.23
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 +15 -11
- package/README_ja.md +13 -10
- package/dist/components/related-posts.d.ts +3 -3
- package/dist/components/related-posts.js +4 -3
- package/dist/index.d.ts +2 -1
- package/dist/index.js +67 -31
- package/dist/src/related.d.ts +6 -1
- package/dist/src/types.d.ts +0 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
Build-time "related notes" navigation. For every published entry, the plugin
|
|
6
6
|
ranks the other entries in the content manifest and contributes a related-posts
|
|
7
|
-
section
|
|
7
|
+
section. The Site places the `<RelatedPosts />` component itself, normally once
|
|
8
|
+
after article content, to display the navigation. No client-side JavaScript is
|
|
9
|
+
required.
|
|
8
10
|
|
|
9
11
|
[日本語](./README_ja.md)
|
|
10
12
|
|
|
@@ -23,9 +25,9 @@ Candidates are sorted by score (descending), then by title, then by slug, and
|
|
|
23
25
|
clamped to `limit`. Entries that score below `minScore` are dropped. When no
|
|
24
26
|
candidate qualifies, the entry's HTML is left untouched.
|
|
25
27
|
|
|
26
|
-
The
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
The Site places the `<RelatedPosts entries={...} options={...} />` component
|
|
29
|
+
itself, normally once after article content. The component renders nothing when
|
|
30
|
+
no related entries qualify.
|
|
29
31
|
|
|
30
32
|
## Quick Start
|
|
31
33
|
|
|
@@ -41,9 +43,10 @@ export default defineConfig({
|
|
|
41
43
|
|
|
42
44
|
## Advanced customization
|
|
43
45
|
|
|
44
|
-
The Site can place the public server-rendered
|
|
45
|
-
`render: false` first to prevent the automatic `article.footer`
|
|
46
|
-
ranking and publication filtering remain available through the
|
|
46
|
+
The Site can place the public server-rendered `<RelatedPosts />` component
|
|
47
|
+
anywhere. Set `render: false` first to prevent the automatic `article.footer`
|
|
48
|
+
contribution; ranking and publication filtering remain available through the
|
|
49
|
+
public helpers.
|
|
47
50
|
|
|
48
51
|
```tsx
|
|
49
52
|
import {
|
|
@@ -52,10 +55,10 @@ import {
|
|
|
52
55
|
resolveRelatedPostsOptions,
|
|
53
56
|
} from "@riebeckite/plugin-related-posts";
|
|
54
57
|
|
|
55
|
-
const options = resolveRelatedPostsOptions({
|
|
58
|
+
const options = resolveRelatedPostsOptions({});
|
|
56
59
|
const entries = buildRelatedPosts({ manifest, entry, options, config });
|
|
57
60
|
|
|
58
|
-
return <
|
|
61
|
+
return <nav rb-related-posts><RelatedPosts entries={entries} options={options} /></nav>;
|
|
59
62
|
```
|
|
60
63
|
|
|
61
64
|
The official Starter already renders `article.footer` once. Remove the manual
|
|
@@ -66,7 +69,6 @@ rendering for the same article.
|
|
|
66
69
|
|
|
67
70
|
| Option | Type | Default | Description |
|
|
68
71
|
| ------ | ---- | ------- | ----------- |
|
|
69
|
-
| `render` | `boolean` | `true` | Append generated navigation to `article.footer`; set `false` for manual placement |
|
|
70
72
|
| `limit` | `number` | `5` | Maximum number of related entries |
|
|
71
73
|
| `minScore` | `number` | `1` | Minimum score required to be listed |
|
|
72
74
|
| `heading` | `boolean` | `true` | Render the `<h2>` heading |
|
|
@@ -85,7 +87,9 @@ relatedPosts({
|
|
|
85
87
|
|
|
86
88
|
## Display
|
|
87
89
|
|
|
88
|
-
Related entries are rendered in the `article.footer`
|
|
90
|
+
Related entries are rendered in the `article.footer` area on eligible pages.
|
|
91
|
+
This site renders the live navigation below the article when matching public
|
|
92
|
+
entries exist.
|
|
89
93
|
|
|
90
94
|
## Style
|
|
91
95
|
|
package/README_ja.md
CHANGED
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
ビルド時に「関連ノート」ナビゲーションを生成するプラグインです。公開対象の
|
|
6
6
|
各エントリについて、コンテンツマニフェスト上の他のエントリをスコア順に並べ、
|
|
7
|
-
|
|
8
|
-
不要です。
|
|
7
|
+
関連セクションを表示します。クライアント側 JavaScript は不要です。
|
|
9
8
|
|
|
10
9
|
[English](./README.md)
|
|
11
10
|
|
|
@@ -24,9 +23,9 @@
|
|
|
24
23
|
`limit` 件に絞ります。`minScore` 未満の候補は除外します。条件を満たす候補が
|
|
25
24
|
1 件もない場合、そのエントリの HTML は変更しません。
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
Site は `<RelatedPosts entries={...} options={...} />` コンポーネントを
|
|
27
|
+
記事の後などに配置します。条件を満たす関連エントリがない場合、コンポーネントは
|
|
28
|
+
何も描画しません。
|
|
30
29
|
|
|
31
30
|
## クイックスタート
|
|
32
31
|
|
|
@@ -42,14 +41,17 @@ export default defineConfig({
|
|
|
42
41
|
|
|
43
42
|
## 高度なカスタマイズ
|
|
44
43
|
|
|
45
|
-
Site のレイアウト内で任意の位置に配置できるサーバー描画の Hono JSX Component
|
|
44
|
+
Site のレイアウト内で任意の位置に配置できるサーバー描画の Hono JSX Component
|
|
45
|
+
を公開しています。`render: false` で自動の `article.footer` Slot 追加を止め、
|
|
46
|
+
既存の helper で entries を計算して解決済み options を渡してください。採点と公開境界のフィルタリングは維持されます。
|
|
46
47
|
|
|
47
48
|
```tsx
|
|
48
49
|
import { buildRelatedPosts, RelatedPosts, resolveRelatedPostsOptions } from "@riebeckite/plugin-related-posts";
|
|
49
50
|
|
|
50
|
-
const options = resolveRelatedPostsOptions({
|
|
51
|
+
const options = resolveRelatedPostsOptions({});
|
|
51
52
|
const entries = buildRelatedPosts({ manifest, entry, options, config });
|
|
52
|
-
|
|
53
|
+
|
|
54
|
+
<nav rb-related-posts><RelatedPosts entries={entries} options={options} /></nav>;
|
|
53
55
|
```
|
|
54
56
|
|
|
55
57
|
公式 Starter は `article.footer` を一度描画します。手動配置を消すにはコンポーネントを消します。自動描画を有効にしたまま同じ記事へ手動描画しないでください。
|
|
@@ -58,7 +60,6 @@ return <aside><RelatedPosts entries={entries} options={options} /></aside>;
|
|
|
58
60
|
|
|
59
61
|
| オプション | 型 | 既定値 | 説明 |
|
|
60
62
|
| ---------- | -- | ------ | ---- |
|
|
61
|
-
| `render` | `boolean` | `true` | `article.footer` への自動追加。手動配置では `false` |
|
|
62
63
|
| `limit` | `number` | `5` | 表示する関連エントリの最大件数 |
|
|
63
64
|
| `minScore` | `number` | `1` | 表示に必要な最小スコア |
|
|
64
65
|
| `heading` | `boolean` | `true` | `<h2>` 見出しを出力する |
|
|
@@ -77,7 +78,9 @@ relatedPosts({
|
|
|
77
78
|
|
|
78
79
|
## 表示
|
|
79
80
|
|
|
80
|
-
関連エントリは対象ページの `article.footer`
|
|
81
|
+
関連エントリは対象ページの `article.footer` area に描画されます。この
|
|
82
|
+
サイトでは、条件に合う公開エントリがあれば記事下に実際のナビゲーションを
|
|
83
|
+
表示します。
|
|
81
84
|
|
|
82
85
|
## スタイル
|
|
83
86
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { RelatedPostsEntry, ResolvedRelatedPostsOptions } from "../src/types.js";
|
|
2
|
-
type
|
|
2
|
+
export type RelatedPostsProps = {
|
|
3
3
|
entries: readonly RelatedPostsEntry[];
|
|
4
4
|
options: ResolvedRelatedPostsOptions;
|
|
5
5
|
};
|
|
6
|
-
|
|
7
|
-
export
|
|
6
|
+
declare const RelatedPosts: (props: Readonly<RelatedPostsProps>) => import("hono/jsx/jsx-dev-runtime").JSX.Element;
|
|
7
|
+
export default RelatedPosts;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// components/related-posts.tsx
|
|
2
2
|
import { jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
3
|
-
|
|
3
|
+
var RelatedPosts = ((props) => {
|
|
4
4
|
if (props.entries.length === 0) return null;
|
|
5
5
|
const { className, heading, headingText } = props.options;
|
|
6
6
|
return /* @__PURE__ */ jsxs("nav", { class: className, "data-related-posts": "", children: [
|
|
@@ -15,7 +15,8 @@ function RelatedPosts(props) {
|
|
|
15
15
|
}
|
|
16
16
|
) }, entry.slug)) })
|
|
17
17
|
] });
|
|
18
|
-
}
|
|
18
|
+
});
|
|
19
|
+
var related_posts_default = RelatedPosts;
|
|
19
20
|
export {
|
|
20
|
-
|
|
21
|
+
related_posts_default as default
|
|
21
22
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { RelatedPostsOptions } from "./src/types.js";
|
|
2
|
+
export type { RelatedPostsProps } from "./components/related-posts.js";
|
|
2
3
|
export { default as RelatedPosts } from "./components/related-posts.js";
|
|
3
4
|
export { resolveRelatedPostsOptions } from "./src/options.js";
|
|
4
|
-
export { buildRelatedPosts, CO_CITATION_WEIGHT, DIRECT_LINK_WEIGHT, isEligibleRelatedEntry, SHARED_TAG_WEIGHT, } from "./src/related.js";
|
|
5
|
+
export { buildRelatedPosts, CO_CITATION_WEIGHT, DIRECT_LINK_WEIGHT, getRelatedPosts, isEligibleRelatedEntry, SHARED_TAG_WEIGHT, } from "./src/related.js";
|
|
5
6
|
export { RELATED_POSTS_ATTRIBUTE, renderRelatedPosts, } from "./src/render.js";
|
|
6
7
|
export type { RelatedPostsEntry, RelatedPostsOptions, ResolvedRelatedPostsOptions, } from "./src/types.js";
|
|
7
8
|
export declare const RELATED_POSTS_PLUGIN_NAME = "related-posts";
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
// index.ts
|
|
2
2
|
import {
|
|
3
|
-
appendContentBodySlot,
|
|
4
3
|
createStyleAsset,
|
|
5
4
|
definePlugin
|
|
6
5
|
} from "@riebeckite/core";
|
|
7
|
-
import { renderToString as renderToString2 } from "hono/jsx/dom/server";
|
|
8
6
|
|
|
9
7
|
// components/related-posts.tsx
|
|
10
8
|
import { jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
11
|
-
|
|
9
|
+
var RelatedPosts = ((props) => {
|
|
12
10
|
if (props.entries.length === 0) return null;
|
|
13
11
|
const { className, heading, headingText } = props.options;
|
|
14
12
|
return /* @__PURE__ */ jsxs("nav", { class: className, "data-related-posts": "", children: [
|
|
@@ -23,7 +21,8 @@ function RelatedPosts(props) {
|
|
|
23
21
|
}
|
|
24
22
|
) }, entry.slug)) })
|
|
25
23
|
] });
|
|
26
|
-
}
|
|
24
|
+
});
|
|
25
|
+
var related_posts_default = RelatedPosts;
|
|
27
26
|
|
|
28
27
|
// src/options.ts
|
|
29
28
|
var DEFAULT_RELATED_POSTS_LIMIT = 5;
|
|
@@ -95,6 +94,60 @@ function buildRelatedPosts(args) {
|
|
|
95
94
|
candidates.sort(compareRelatedEntries);
|
|
96
95
|
return candidates.slice(0, options.limit);
|
|
97
96
|
}
|
|
97
|
+
var getRelatedPosts = ({ view, entry, options }) => {
|
|
98
|
+
const resolved = options ?? {
|
|
99
|
+
limit: 5,
|
|
100
|
+
minScore: 1,
|
|
101
|
+
useTags: true,
|
|
102
|
+
useBacklinks: true,
|
|
103
|
+
heading: true,
|
|
104
|
+
headingText: "Related posts",
|
|
105
|
+
className: "rr-related-posts"
|
|
106
|
+
};
|
|
107
|
+
const scores = /* @__PURE__ */ new Map();
|
|
108
|
+
const outgoing = new Set(
|
|
109
|
+
entry.links.flatMap(
|
|
110
|
+
(link) => link.kind === "note" && link.slug ? [link.slug] : []
|
|
111
|
+
)
|
|
112
|
+
);
|
|
113
|
+
if (resolved.useTags) {
|
|
114
|
+
for (const tag of entry.tags) {
|
|
115
|
+
for (const candidate of view.getDiscoverableEntriesByTag(tag)) {
|
|
116
|
+
if (candidate.slug !== entry.slug) {
|
|
117
|
+
addScore(scores, candidate.slug, SHARED_TAG_WEIGHT);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (resolved.useBacklinks) {
|
|
123
|
+
for (const candidate of [
|
|
124
|
+
...view.getDiscoverableOutgoing(entry.slug),
|
|
125
|
+
...view.getDiscoverableIncoming(entry.slug)
|
|
126
|
+
]) {
|
|
127
|
+
if (candidate.slug !== entry.slug) {
|
|
128
|
+
addScore(scores, candidate.slug, DIRECT_LINK_WEIGHT);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
for (const target of outgoing) {
|
|
133
|
+
for (const candidate of view.getDiscoverableIncoming(target)) {
|
|
134
|
+
if (candidate.slug !== entry.slug) {
|
|
135
|
+
addScore(scores, candidate.slug, CO_CITATION_WEIGHT);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return [...scores].flatMap(([slug, score]) => {
|
|
140
|
+
const candidate = view.findDiscoverableBySlug(slug);
|
|
141
|
+
return candidate && score >= resolved.minScore ? [
|
|
142
|
+
{
|
|
143
|
+
slug,
|
|
144
|
+
permalink: candidate.permalink,
|
|
145
|
+
title: candidate.title,
|
|
146
|
+
score
|
|
147
|
+
}
|
|
148
|
+
] : [];
|
|
149
|
+
}).sort(compareRelatedEntries).slice(0, resolved.limit);
|
|
150
|
+
};
|
|
98
151
|
function isEligibleRelatedEntry(entry, manifest, _config) {
|
|
99
152
|
if (manifest.redirects.has(entry.permalink)) return false;
|
|
100
153
|
return manifest.discoverableEntries.some(
|
|
@@ -124,13 +177,12 @@ function addScore(scores, slug, weight) {
|
|
|
124
177
|
import { renderToString } from "hono/jsx/dom/server";
|
|
125
178
|
var RELATED_POSTS_ATTRIBUTE = "data-related-posts";
|
|
126
179
|
function renderRelatedPosts(entries, options) {
|
|
127
|
-
return renderToString(
|
|
180
|
+
return renderToString(related_posts_default({ entries, options }));
|
|
128
181
|
}
|
|
129
182
|
|
|
130
183
|
// index.ts
|
|
131
184
|
var RELATED_POSTS_PLUGIN_NAME = "related-posts";
|
|
132
185
|
function relatedPosts(options = {}) {
|
|
133
|
-
const resolved = resolveRelatedPostsOptions(options);
|
|
134
186
|
return definePlugin({
|
|
135
187
|
name: RELATED_POSTS_PLUGIN_NAME,
|
|
136
188
|
order: 100,
|
|
@@ -138,38 +190,21 @@ function relatedPosts(options = {}) {
|
|
|
138
190
|
version: "related-posts-v1",
|
|
139
191
|
dependencyMode: "none"
|
|
140
192
|
},
|
|
141
|
-
|
|
193
|
+
contentOutputDependencies: [{ type: "global" }],
|
|
142
194
|
options,
|
|
143
195
|
validateOptions: validateRelatedPostsOptions,
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
manifest,
|
|
151
|
-
entry,
|
|
152
|
-
options: resolved,
|
|
153
|
-
config: context.config
|
|
154
|
-
});
|
|
155
|
-
if (related.length === 0) continue;
|
|
156
|
-
appendContentBodySlot(
|
|
157
|
-
entry,
|
|
158
|
-
"article.footer",
|
|
159
|
-
renderToString2(RelatedPosts({ entries: related, options: resolved }))
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
},
|
|
163
|
-
assets: [createStyleAsset(RELATED_POSTS_PLUGIN_NAME)]
|
|
196
|
+
assets: [
|
|
197
|
+
createStyleAsset(
|
|
198
|
+
RELATED_POSTS_PLUGIN_NAME,
|
|
199
|
+
"@riebeckite/plugin-related-posts/style.css"
|
|
200
|
+
)
|
|
201
|
+
]
|
|
164
202
|
});
|
|
165
203
|
}
|
|
166
204
|
var relatedPostsPlugin = relatedPosts;
|
|
167
205
|
function validateRelatedPostsOptions(options) {
|
|
168
206
|
if (!options) return [];
|
|
169
207
|
const issues = [];
|
|
170
|
-
if (options.render !== void 0 && typeof options.render !== "boolean") {
|
|
171
|
-
issues.push({ path: "render", message: "Expected a boolean." });
|
|
172
|
-
}
|
|
173
208
|
if (options.limit !== void 0 && (!Number.isInteger(options.limit) || options.limit < 0)) {
|
|
174
209
|
issues.push({ path: "limit", message: "Expected a non-negative integer." });
|
|
175
210
|
}
|
|
@@ -204,9 +239,10 @@ export {
|
|
|
204
239
|
DIRECT_LINK_WEIGHT,
|
|
205
240
|
RELATED_POSTS_ATTRIBUTE,
|
|
206
241
|
RELATED_POSTS_PLUGIN_NAME,
|
|
207
|
-
RelatedPosts,
|
|
242
|
+
related_posts_default as RelatedPosts,
|
|
208
243
|
SHARED_TAG_WEIGHT,
|
|
209
244
|
buildRelatedPosts,
|
|
245
|
+
getRelatedPosts,
|
|
210
246
|
isEligibleRelatedEntry,
|
|
211
247
|
relatedPosts,
|
|
212
248
|
relatedPostsPlugin,
|
package/dist/src/related.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ContentManifest, ContentManifestEntry, ResolvedRiebeckiteConfig } from "@riebeckite/core";
|
|
1
|
+
import type { ContentManifest, ContentManifestEntry, PluginDataHelper, PublicContentEntry, PublicContentView, ResolvedRiebeckiteConfig } from "@riebeckite/core";
|
|
2
2
|
import type { RelatedPostsEntry, ResolvedRelatedPostsOptions } from "./types.js";
|
|
3
3
|
/**
|
|
4
4
|
* Weights are deterministic constants: the same manifest always produces the
|
|
@@ -23,6 +23,11 @@ export type BuildRelatedPostsArgs = {
|
|
|
23
23
|
* and the Core-resolved publishing view.
|
|
24
24
|
*/
|
|
25
25
|
export declare function buildRelatedPosts(args: BuildRelatedPostsArgs): RelatedPostsEntry[];
|
|
26
|
+
export declare const getRelatedPosts: PluginDataHelper<{
|
|
27
|
+
view: PublicContentView;
|
|
28
|
+
entry: PublicContentEntry;
|
|
29
|
+
options?: ResolvedRelatedPostsOptions;
|
|
30
|
+
}, readonly RelatedPostsEntry[]>;
|
|
26
31
|
/**
|
|
27
32
|
* An entry may receive a related-posts section (and be offered as a related
|
|
28
33
|
* candidate) only when it is discoverable and its canonical permalink is not
|
package/dist/src/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@riebeckite/plugin-related-posts",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.23",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,11 +22,11 @@
|
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"hono": "^4.12.25",
|
|
25
|
-
"@riebeckite/core": "0.0.
|
|
25
|
+
"@riebeckite/core": "0.0.23"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"tsx": "^4.22.4",
|
|
29
|
-
"@riebeckite/test": "0.0.
|
|
29
|
+
"@riebeckite/test": "0.0.23"
|
|
30
30
|
},
|
|
31
31
|
"publishConfig": {
|
|
32
32
|
"access": "public"
|