@riebeckite/plugin-related-posts 0.0.23 → 0.0.25

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.
@@ -1,7 +1,8 @@
1
1
  import type { RelatedPostsEntry, ResolvedRelatedPostsOptions } from "../src/types.js";
2
2
  export type RelatedPostsProps = {
3
- entries: readonly RelatedPostsEntry[];
4
- options: ResolvedRelatedPostsOptions;
3
+ entries?: readonly RelatedPostsEntry[];
4
+ options?: ResolvedRelatedPostsOptions;
5
5
  };
6
- declare const RelatedPosts: (props: Readonly<RelatedPostsProps>) => import("hono/jsx/jsx-dev-runtime").JSX.Element;
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
- if (props.entries.length === 0) return null;
5
- const { className, heading, headingText } = props.options;
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: props.entries.map((entry) => /* @__PURE__ */ jsx("li", { class: `${className}__item`, children: /* @__PURE__ */ jsx(
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 { jsx, jsxs } from "hono/jsx/jsx-runtime";
9
- var RelatedPosts = ((props) => {
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(related_posts_default({ entries, options }));
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.23",
3
+ "version": "0.0.25",
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.23"
25
+ "@riebeckite/core": "0.0.25",
26
+ "@riebeckite/ui": "0.0.25"
26
27
  },
27
28
  "devDependencies": {
28
29
  "tsx": "^4.22.4",
29
- "@riebeckite/test": "0.0.23"
30
+ "@riebeckite/test": "0.0.25"
30
31
  },
31
32
  "publishConfig": {
32
33
  "access": "public"