@riebeckite/plugin-related-posts 0.0.24 → 0.0.26
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/dist/components/related-posts.d.ts +4 -3
- package/dist/components/related-posts.js +115 -6
- package/dist/index.js +37 -19
- package/package.json +4 -3
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { RelatedPostsEntry, ResolvedRelatedPostsOptions } from "../src/types.js";
|
|
2
2
|
export type RelatedPostsProps = {
|
|
3
|
-
entries
|
|
4
|
-
options
|
|
3
|
+
entries?: readonly RelatedPostsEntry[];
|
|
4
|
+
options?: ResolvedRelatedPostsOptions;
|
|
5
5
|
};
|
|
6
|
-
declare const RelatedPosts: (props: Readonly<RelatedPostsProps>) => import("hono/
|
|
6
|
+
declare const RelatedPosts: (props: Readonly<RelatedPostsProps>) => Promise<import("hono/utils/html").HtmlEscapedString>;
|
|
7
|
+
export declare function renderRelatedPostsContent(entries: readonly RelatedPostsEntry[], options: ResolvedRelatedPostsOptions): import("hono/utils/html").HtmlEscapedString | Promise<import("hono/utils/html").HtmlEscapedString>;
|
|
7
8
|
export default RelatedPosts;
|
|
@@ -1,11 +1,116 @@
|
|
|
1
|
+
// components/related-posts.tsx
|
|
2
|
+
import { useArticleContext, useSiteContext } from "@riebeckite/ui";
|
|
3
|
+
import { html } from "hono/html";
|
|
4
|
+
|
|
5
|
+
// src/options.ts
|
|
6
|
+
var DEFAULT_RELATED_POSTS_LIMIT = 5;
|
|
7
|
+
var DEFAULT_RELATED_POSTS_MIN_SCORE = 1;
|
|
8
|
+
var DEFAULT_RELATED_POSTS_HEADING_TEXT = "Related";
|
|
9
|
+
var DEFAULT_RELATED_POSTS_CLASS_NAME = "rb-related-posts";
|
|
10
|
+
function resolveRelatedPostsOptions(options = {}) {
|
|
11
|
+
return {
|
|
12
|
+
limit: options.limit ?? DEFAULT_RELATED_POSTS_LIMIT,
|
|
13
|
+
minScore: options.minScore ?? DEFAULT_RELATED_POSTS_MIN_SCORE,
|
|
14
|
+
heading: options.heading ?? true,
|
|
15
|
+
headingText: options.headingText ?? DEFAULT_RELATED_POSTS_HEADING_TEXT,
|
|
16
|
+
className: normalizeClassName(options.className),
|
|
17
|
+
useTags: options.useTags ?? true,
|
|
18
|
+
useBacklinks: options.useBacklinks ?? true
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function normalizeClassName(className) {
|
|
22
|
+
if (typeof className !== "string") return DEFAULT_RELATED_POSTS_CLASS_NAME;
|
|
23
|
+
const trimmed = className.trim();
|
|
24
|
+
return trimmed === "" ? DEFAULT_RELATED_POSTS_CLASS_NAME : trimmed;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// src/related.ts
|
|
28
|
+
var DIRECT_LINK_WEIGHT = 3;
|
|
29
|
+
var SHARED_TAG_WEIGHT = 2;
|
|
30
|
+
var CO_CITATION_WEIGHT = 1;
|
|
31
|
+
var getRelatedPosts = ({ view, entry, options }) => {
|
|
32
|
+
const resolved = options ?? {
|
|
33
|
+
limit: 5,
|
|
34
|
+
minScore: 1,
|
|
35
|
+
useTags: true,
|
|
36
|
+
useBacklinks: true,
|
|
37
|
+
heading: true,
|
|
38
|
+
headingText: "Related posts",
|
|
39
|
+
className: "rr-related-posts"
|
|
40
|
+
};
|
|
41
|
+
const scores = /* @__PURE__ */ new Map();
|
|
42
|
+
const outgoing = new Set(
|
|
43
|
+
entry.links.flatMap(
|
|
44
|
+
(link) => link.kind === "note" && link.slug ? [link.slug] : []
|
|
45
|
+
)
|
|
46
|
+
);
|
|
47
|
+
if (resolved.useTags) {
|
|
48
|
+
for (const tag of entry.tags) {
|
|
49
|
+
for (const candidate of view.getDiscoverableEntriesByTag(tag)) {
|
|
50
|
+
if (candidate.slug !== entry.slug) {
|
|
51
|
+
addScore(scores, candidate.slug, SHARED_TAG_WEIGHT);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (resolved.useBacklinks) {
|
|
57
|
+
for (const candidate of [
|
|
58
|
+
...view.getDiscoverableOutgoing(entry.slug),
|
|
59
|
+
...view.getDiscoverableIncoming(entry.slug)
|
|
60
|
+
]) {
|
|
61
|
+
if (candidate.slug !== entry.slug) {
|
|
62
|
+
addScore(scores, candidate.slug, DIRECT_LINK_WEIGHT);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
for (const target of outgoing) {
|
|
67
|
+
for (const candidate of view.getDiscoverableIncoming(target)) {
|
|
68
|
+
if (candidate.slug !== entry.slug) {
|
|
69
|
+
addScore(scores, candidate.slug, CO_CITATION_WEIGHT);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return [...scores].flatMap(([slug, score]) => {
|
|
74
|
+
const candidate = view.findDiscoverableBySlug(slug);
|
|
75
|
+
return candidate && score >= resolved.minScore ? [
|
|
76
|
+
{
|
|
77
|
+
slug,
|
|
78
|
+
permalink: candidate.permalink,
|
|
79
|
+
title: candidate.title,
|
|
80
|
+
score
|
|
81
|
+
}
|
|
82
|
+
] : [];
|
|
83
|
+
}).sort(compareRelatedEntries).slice(0, resolved.limit);
|
|
84
|
+
};
|
|
85
|
+
function compareRelatedEntries(a, b) {
|
|
86
|
+
if (b.score !== a.score) return b.score - a.score;
|
|
87
|
+
const byTitle = a.title.localeCompare(b.title);
|
|
88
|
+
if (byTitle !== 0) return byTitle;
|
|
89
|
+
return a.slug.localeCompare(b.slug);
|
|
90
|
+
}
|
|
91
|
+
function addScore(scores, slug, weight) {
|
|
92
|
+
scores.set(slug, (scores.get(slug) ?? 0) + weight);
|
|
93
|
+
}
|
|
94
|
+
|
|
1
95
|
// components/related-posts.tsx
|
|
2
96
|
import { jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
3
|
-
var RelatedPosts = ((props) => {
|
|
4
|
-
|
|
5
|
-
const
|
|
97
|
+
var RelatedPosts = (async (props) => {
|
|
98
|
+
const site = useSiteContext();
|
|
99
|
+
const article = useArticleContext();
|
|
100
|
+
const options = props.options ?? resolveRelatedPostsOptions(findRelatedPostsOptions(site?.config.plugins));
|
|
101
|
+
const entries = props.entries ?? (site && article?.entry ? await getRelatedPosts({
|
|
102
|
+
view: site.view,
|
|
103
|
+
entry: article.entry,
|
|
104
|
+
options
|
|
105
|
+
}) : void 0);
|
|
106
|
+
return renderRelatedPostsContent(entries ?? [], options);
|
|
107
|
+
});
|
|
108
|
+
function renderRelatedPostsContent(entries, options) {
|
|
109
|
+
if (entries.length === 0) return html``;
|
|
110
|
+
const { className, heading, headingText } = options;
|
|
6
111
|
return /* @__PURE__ */ jsxs("nav", { class: className, "data-related-posts": "", children: [
|
|
7
112
|
heading ? /* @__PURE__ */ jsx("h2", { class: `${className}__heading`, children: headingText }) : null,
|
|
8
|
-
/* @__PURE__ */ jsx("ul", { children:
|
|
113
|
+
/* @__PURE__ */ jsx("ul", { children: entries.map((entry) => /* @__PURE__ */ jsx("li", { class: `${className}__item`, children: /* @__PURE__ */ jsx(
|
|
9
114
|
"a",
|
|
10
115
|
{
|
|
11
116
|
class: `${className}__link`,
|
|
@@ -15,8 +120,12 @@ var RelatedPosts = ((props) => {
|
|
|
15
120
|
}
|
|
16
121
|
) }, entry.slug)) })
|
|
17
122
|
] });
|
|
18
|
-
}
|
|
123
|
+
}
|
|
19
124
|
var related_posts_default = RelatedPosts;
|
|
125
|
+
function findRelatedPostsOptions(plugins) {
|
|
126
|
+
return plugins?.find((plugin) => plugin.name === "related-posts")?.options;
|
|
127
|
+
}
|
|
20
128
|
export {
|
|
21
|
-
related_posts_default as default
|
|
129
|
+
related_posts_default as default,
|
|
130
|
+
renderRelatedPostsContent
|
|
22
131
|
};
|
package/dist/index.js
CHANGED
|
@@ -5,24 +5,8 @@ import {
|
|
|
5
5
|
} from "@riebeckite/core";
|
|
6
6
|
|
|
7
7
|
// components/related-posts.tsx
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
if (props.entries.length === 0) return null;
|
|
11
|
-
const { className, heading, headingText } = props.options;
|
|
12
|
-
return /* @__PURE__ */ jsxs("nav", { class: className, "data-related-posts": "", children: [
|
|
13
|
-
heading ? /* @__PURE__ */ jsx("h2", { class: `${className}__heading`, children: headingText }) : null,
|
|
14
|
-
/* @__PURE__ */ jsx("ul", { children: props.entries.map((entry) => /* @__PURE__ */ jsx("li", { class: `${className}__item`, children: /* @__PURE__ */ jsx(
|
|
15
|
-
"a",
|
|
16
|
-
{
|
|
17
|
-
class: `${className}__link`,
|
|
18
|
-
href: entry.permalink,
|
|
19
|
-
"data-related-score": entry.score,
|
|
20
|
-
children: entry.title
|
|
21
|
-
}
|
|
22
|
-
) }, entry.slug)) })
|
|
23
|
-
] });
|
|
24
|
-
});
|
|
25
|
-
var related_posts_default = RelatedPosts;
|
|
8
|
+
import { useArticleContext, useSiteContext } from "@riebeckite/ui";
|
|
9
|
+
import { html } from "hono/html";
|
|
26
10
|
|
|
27
11
|
// src/options.ts
|
|
28
12
|
var DEFAULT_RELATED_POSTS_LIMIT = 5;
|
|
@@ -173,11 +157,45 @@ function addScore(scores, slug, weight) {
|
|
|
173
157
|
scores.set(slug, (scores.get(slug) ?? 0) + weight);
|
|
174
158
|
}
|
|
175
159
|
|
|
160
|
+
// components/related-posts.tsx
|
|
161
|
+
import { jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
162
|
+
var RelatedPosts = (async (props) => {
|
|
163
|
+
const site = useSiteContext();
|
|
164
|
+
const article = useArticleContext();
|
|
165
|
+
const options = props.options ?? resolveRelatedPostsOptions(findRelatedPostsOptions(site?.config.plugins));
|
|
166
|
+
const entries = props.entries ?? (site && article?.entry ? await getRelatedPosts({
|
|
167
|
+
view: site.view,
|
|
168
|
+
entry: article.entry,
|
|
169
|
+
options
|
|
170
|
+
}) : void 0);
|
|
171
|
+
return renderRelatedPostsContent(entries ?? [], options);
|
|
172
|
+
});
|
|
173
|
+
function renderRelatedPostsContent(entries, options) {
|
|
174
|
+
if (entries.length === 0) return html``;
|
|
175
|
+
const { className, heading, headingText } = options;
|
|
176
|
+
return /* @__PURE__ */ jsxs("nav", { class: className, "data-related-posts": "", children: [
|
|
177
|
+
heading ? /* @__PURE__ */ jsx("h2", { class: `${className}__heading`, children: headingText }) : null,
|
|
178
|
+
/* @__PURE__ */ jsx("ul", { children: entries.map((entry) => /* @__PURE__ */ jsx("li", { class: `${className}__item`, children: /* @__PURE__ */ jsx(
|
|
179
|
+
"a",
|
|
180
|
+
{
|
|
181
|
+
class: `${className}__link`,
|
|
182
|
+
href: entry.permalink,
|
|
183
|
+
"data-related-score": entry.score,
|
|
184
|
+
children: entry.title
|
|
185
|
+
}
|
|
186
|
+
) }, entry.slug)) })
|
|
187
|
+
] });
|
|
188
|
+
}
|
|
189
|
+
var related_posts_default = RelatedPosts;
|
|
190
|
+
function findRelatedPostsOptions(plugins) {
|
|
191
|
+
return plugins?.find((plugin) => plugin.name === "related-posts")?.options;
|
|
192
|
+
}
|
|
193
|
+
|
|
176
194
|
// src/render.ts
|
|
177
195
|
import { renderToString } from "hono/jsx/dom/server";
|
|
178
196
|
var RELATED_POSTS_ATTRIBUTE = "data-related-posts";
|
|
179
197
|
function renderRelatedPosts(entries, options) {
|
|
180
|
-
return renderToString(
|
|
198
|
+
return renderToString(renderRelatedPostsContent(entries, options));
|
|
181
199
|
}
|
|
182
200
|
|
|
183
201
|
// index.ts
|
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.26",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,11 +22,12 @@
|
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"hono": "^4.12.25",
|
|
25
|
-
"@riebeckite/core": "0.0.
|
|
25
|
+
"@riebeckite/core": "0.0.26",
|
|
26
|
+
"@riebeckite/ui": "0.0.26"
|
|
26
27
|
},
|
|
27
28
|
"devDependencies": {
|
|
28
29
|
"tsx": "^4.22.4",
|
|
29
|
-
"@riebeckite/test": "0.0.
|
|
30
|
+
"@riebeckite/test": "0.0.26"
|
|
30
31
|
},
|
|
31
32
|
"publishConfig": {
|
|
32
33
|
"access": "public"
|