@riebeckite/plugin-garden-explorer 0.0.1
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/LICENSE +201 -0
- package/README.md +89 -0
- package/README_ja.md +69 -0
- package/dist/components/garden-explorer.d.ts +6 -0
- package/dist/components/garden-explorer.js +452 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +558 -0
- package/dist/src/garden-explorer.d.ts +32 -0
- package/dist/src/garden-explorer.server.d.ts +9 -0
- package/dist/src/search-notes.d.ts +2 -0
- package/package.json +62 -0
- package/style.css +292 -0
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
// components/garden-explorer.tsx
|
|
2
|
+
import { layoutRadialGraph } from "@riebeckite/core";
|
|
3
|
+
import { useEffect, useMemo, useState } from "hono/jsx";
|
|
4
|
+
|
|
5
|
+
// src/search-notes.ts
|
|
6
|
+
var FIELD_WEIGHTS = {
|
|
7
|
+
slug: 64,
|
|
8
|
+
title: 56,
|
|
9
|
+
tags: 44,
|
|
10
|
+
headings: 32,
|
|
11
|
+
body: 10
|
|
12
|
+
};
|
|
13
|
+
function searchGardenExplorerNotes(notes, query) {
|
|
14
|
+
const normalizedQuery = normalizeSearchQuery(query);
|
|
15
|
+
if (normalizedQuery.length === 0) return [];
|
|
16
|
+
return notes.map((note) => scoreNote(note, normalizedQuery)).filter(
|
|
17
|
+
(result) => result !== null
|
|
18
|
+
).sort(
|
|
19
|
+
(a, b) => b.score - a.score || a.note.title.localeCompare(b.note.title, "ja")
|
|
20
|
+
).map(({ note }) => note);
|
|
21
|
+
}
|
|
22
|
+
function normalizeSearchQuery(value) {
|
|
23
|
+
return normalizeSearchText(value).replace(/^#+/, "");
|
|
24
|
+
}
|
|
25
|
+
function normalizeSearchText(value) {
|
|
26
|
+
return value.toLocaleLowerCase().normalize("NFKC").replace(
|
|
27
|
+
/[ァ-ン]/g,
|
|
28
|
+
(char) => String.fromCharCode(char.charCodeAt(0) - 96)
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
function scoreNote(note, normalizedQuery) {
|
|
32
|
+
const matches = [
|
|
33
|
+
scoreField("title", note.title, normalizedQuery),
|
|
34
|
+
scoreField("slug", note.slug, normalizedQuery),
|
|
35
|
+
...note.tags.map((tag) => scoreField("tags", tag, normalizedQuery)),
|
|
36
|
+
...note.headings.map(
|
|
37
|
+
(heading) => scoreField("headings", heading, normalizedQuery)
|
|
38
|
+
),
|
|
39
|
+
scoreField("body", note.body, normalizedQuery)
|
|
40
|
+
].filter((match) => match.score > 0);
|
|
41
|
+
if (matches.length === 0) return null;
|
|
42
|
+
return {
|
|
43
|
+
note,
|
|
44
|
+
score: matches.reduce((total, match) => total + match.score, 0)
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function scoreField(field, value, normalizedQuery) {
|
|
48
|
+
const normalizedValue = normalizeSearchText(value);
|
|
49
|
+
const index = normalizedValue.indexOf(normalizedQuery);
|
|
50
|
+
const weight = FIELD_WEIGHTS[field];
|
|
51
|
+
if (normalizedValue === normalizedQuery) return { score: weight * 3 };
|
|
52
|
+
if (index !== -1) return { score: weight * (index === 0 ? 2 : 1) };
|
|
53
|
+
const fuzzyScore = scoreFuzzyMatch(normalizedValue, normalizedQuery);
|
|
54
|
+
return { score: fuzzyScore > 0 ? Math.round(weight * fuzzyScore) : 0 };
|
|
55
|
+
}
|
|
56
|
+
function scoreFuzzyMatch(value, query) {
|
|
57
|
+
if (query.length < 2) return 0;
|
|
58
|
+
let valueIndex = 0;
|
|
59
|
+
let matched = 0;
|
|
60
|
+
let gaps = 0;
|
|
61
|
+
for (const queryCharacter of query) {
|
|
62
|
+
const nextIndex = value.indexOf(queryCharacter, valueIndex);
|
|
63
|
+
if (nextIndex === -1) return 0;
|
|
64
|
+
gaps += nextIndex - valueIndex;
|
|
65
|
+
valueIndex = nextIndex + 1;
|
|
66
|
+
matched += 1;
|
|
67
|
+
}
|
|
68
|
+
const coverage = matched / Math.max(value.length, query.length);
|
|
69
|
+
const continuityPenalty = Math.min(gaps / Math.max(value.length, 1), 0.8);
|
|
70
|
+
return Math.max(0, coverage * (1 - continuityPenalty));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// components/garden-explorer.tsx
|
|
74
|
+
import { jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
75
|
+
var GRAPH_WIDTH = 900;
|
|
76
|
+
var GRAPH_HEIGHT = 620;
|
|
77
|
+
var MIN_SCALE = 0.6;
|
|
78
|
+
var MAX_SCALE = 2.4;
|
|
79
|
+
function GardenExplorer(props) {
|
|
80
|
+
const noteBySlug = useMemo(
|
|
81
|
+
() => new Map(props.data.notes.map((note) => [note.slug, note])),
|
|
82
|
+
[props.data.notes]
|
|
83
|
+
);
|
|
84
|
+
const [query, setQuery] = useState("");
|
|
85
|
+
const [selectedTag, setSelectedTag] = useState(null);
|
|
86
|
+
const [selectedFolder, setSelectedFolder] = useState(null);
|
|
87
|
+
const [selectedSlug, setSelectedSlug] = useState(
|
|
88
|
+
() => props.data.notes[0]?.slug ?? ""
|
|
89
|
+
);
|
|
90
|
+
const [mobilePanel, setMobilePanel] = useState("graph");
|
|
91
|
+
const [view, setView] = useState({ x: 0, y: 0, scale: 1 });
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
const params = new URLSearchParams(window.location.search);
|
|
94
|
+
const note = params.get("note");
|
|
95
|
+
const tag = params.get("tag");
|
|
96
|
+
const folder = params.get("folder");
|
|
97
|
+
if (note && noteBySlug.has(note)) setSelectedSlug(note);
|
|
98
|
+
if (tag) setSelectedTag(tag);
|
|
99
|
+
if (folder) setSelectedFolder(folder);
|
|
100
|
+
}, [noteBySlug]);
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
const params = new URLSearchParams();
|
|
103
|
+
if (selectedSlug) params.set("note", selectedSlug);
|
|
104
|
+
if (selectedTag) params.set("tag", selectedTag);
|
|
105
|
+
if (selectedFolder) params.set("folder", selectedFolder);
|
|
106
|
+
const next = params.toString() ? `/explore?${params}` : "/explore";
|
|
107
|
+
window.history.replaceState(null, "", next);
|
|
108
|
+
}, [selectedSlug, selectedTag, selectedFolder]);
|
|
109
|
+
const filteredNotes = useMemo(() => {
|
|
110
|
+
const base = props.data.notes.filter((note) => {
|
|
111
|
+
if (selectedTag && !note.tags.includes(selectedTag)) return false;
|
|
112
|
+
if (selectedFolder && note.folder !== selectedFolder) return false;
|
|
113
|
+
return true;
|
|
114
|
+
});
|
|
115
|
+
const searched = query ? searchGardenExplorerNotes(base, query) : base;
|
|
116
|
+
return searched.slice(0, 80);
|
|
117
|
+
}, [props.data.notes, query, selectedTag, selectedFolder]);
|
|
118
|
+
const visibleSlugSet = useMemo(
|
|
119
|
+
() => new Set(filteredNotes.map((note) => note.slug)),
|
|
120
|
+
[filteredNotes]
|
|
121
|
+
);
|
|
122
|
+
const visibleEdges = useMemo(
|
|
123
|
+
() => props.data.edges.filter(
|
|
124
|
+
(edge) => visibleSlugSet.has(edge.source) && visibleSlugSet.has(edge.target)
|
|
125
|
+
),
|
|
126
|
+
[props.data.edges, visibleSlugSet]
|
|
127
|
+
);
|
|
128
|
+
const graphNodes = useMemo(
|
|
129
|
+
() => layoutNodes(filteredNotes, selectedSlug),
|
|
130
|
+
[filteredNotes, selectedSlug]
|
|
131
|
+
);
|
|
132
|
+
const selectedNote = noteBySlug.get(selectedSlug) ?? filteredNotes[0] ?? null;
|
|
133
|
+
const relatedNotes = useMemo(
|
|
134
|
+
() => selectedNote ? getRelatedNotes(selectedNote, props.data.notes) : [],
|
|
135
|
+
[selectedNote, props.data.notes]
|
|
136
|
+
);
|
|
137
|
+
const selectNote = (slug) => {
|
|
138
|
+
setSelectedSlug(slug);
|
|
139
|
+
setMobilePanel("details");
|
|
140
|
+
};
|
|
141
|
+
const clearFilters = () => {
|
|
142
|
+
setQuery("");
|
|
143
|
+
setSelectedTag(null);
|
|
144
|
+
setSelectedFolder(null);
|
|
145
|
+
};
|
|
146
|
+
const zoom = (delta) => {
|
|
147
|
+
setView((current) => ({
|
|
148
|
+
...current,
|
|
149
|
+
scale: clamp(current.scale + delta, MIN_SCALE, MAX_SCALE)
|
|
150
|
+
}));
|
|
151
|
+
};
|
|
152
|
+
return /* @__PURE__ */ jsxs("div", { class: "garden-explorer rr-garden-explorer", "data-garden-explorer": true, children: [
|
|
153
|
+
/* @__PURE__ */ jsxs("fieldset", { class: "garden-explorer__mobile-tabs", children: [
|
|
154
|
+
/* @__PURE__ */ jsx("legend", { class: "sr-only", children: "Explorer panels" }),
|
|
155
|
+
["graph", "explorer", "details"].map((panel) => /* @__PURE__ */ jsx(
|
|
156
|
+
"button",
|
|
157
|
+
{
|
|
158
|
+
type: "button",
|
|
159
|
+
class: "garden-explorer__mobile-tab",
|
|
160
|
+
"aria-pressed": mobilePanel === panel,
|
|
161
|
+
onClick: () => setMobilePanel(panel),
|
|
162
|
+
children: panel
|
|
163
|
+
}
|
|
164
|
+
))
|
|
165
|
+
] }),
|
|
166
|
+
/* @__PURE__ */ jsxs("aside", { class: panelClass("explorer", mobilePanel), children: [
|
|
167
|
+
/* @__PURE__ */ jsxs("div", { class: "garden-explorer__section", children: [
|
|
168
|
+
/* @__PURE__ */ jsx("p", { class: "garden-explorer__eyebrow", children: "Explorer" }),
|
|
169
|
+
/* @__PURE__ */ jsxs("label", { class: "garden-explorer__search", children: [
|
|
170
|
+
/* @__PURE__ */ jsx("span", { class: "sr-only", children: "Search notes" }),
|
|
171
|
+
/* @__PURE__ */ jsx(
|
|
172
|
+
"input",
|
|
173
|
+
{
|
|
174
|
+
type: "search",
|
|
175
|
+
value: query,
|
|
176
|
+
placeholder: "Search notes...",
|
|
177
|
+
onInput: (event) => setQuery(event.currentTarget.value)
|
|
178
|
+
}
|
|
179
|
+
)
|
|
180
|
+
] }),
|
|
181
|
+
/* @__PURE__ */ jsx(
|
|
182
|
+
"button",
|
|
183
|
+
{
|
|
184
|
+
type: "button",
|
|
185
|
+
class: "garden-explorer__text-button",
|
|
186
|
+
onClick: clearFilters,
|
|
187
|
+
children: "Clear filters"
|
|
188
|
+
}
|
|
189
|
+
)
|
|
190
|
+
] }),
|
|
191
|
+
/* @__PURE__ */ jsx(
|
|
192
|
+
FilterList,
|
|
193
|
+
{
|
|
194
|
+
title: "Tags",
|
|
195
|
+
items: props.data.tags.map((tag) => ({
|
|
196
|
+
key: tag.name,
|
|
197
|
+
label: `#${tag.name}`,
|
|
198
|
+
count: tag.count
|
|
199
|
+
})),
|
|
200
|
+
selectedKey: selectedTag,
|
|
201
|
+
onSelect: (key) => setSelectedTag(key === selectedTag ? null : key)
|
|
202
|
+
}
|
|
203
|
+
),
|
|
204
|
+
/* @__PURE__ */ jsx(
|
|
205
|
+
FilterList,
|
|
206
|
+
{
|
|
207
|
+
title: "Folders",
|
|
208
|
+
items: props.data.folders.map((folder) => ({
|
|
209
|
+
key: folder.path,
|
|
210
|
+
label: folder.path,
|
|
211
|
+
count: folder.count
|
|
212
|
+
})),
|
|
213
|
+
selectedKey: selectedFolder,
|
|
214
|
+
onSelect: (key) => setSelectedFolder(key === selectedFolder ? null : key)
|
|
215
|
+
}
|
|
216
|
+
),
|
|
217
|
+
/* @__PURE__ */ jsxs("div", { class: "garden-explorer__section", children: [
|
|
218
|
+
/* @__PURE__ */ jsx("p", { class: "garden-explorer__eyebrow", children: "Notes" }),
|
|
219
|
+
/* @__PURE__ */ jsx(
|
|
220
|
+
NoteList,
|
|
221
|
+
{
|
|
222
|
+
notes: filteredNotes,
|
|
223
|
+
selectedSlug,
|
|
224
|
+
onSelect: selectNote
|
|
225
|
+
}
|
|
226
|
+
)
|
|
227
|
+
] })
|
|
228
|
+
] }),
|
|
229
|
+
/* @__PURE__ */ jsxs("section", { class: panelClass("graph", mobilePanel), "aria-label": "Note graph", children: [
|
|
230
|
+
/* @__PURE__ */ jsxs("div", { class: "garden-explorer__graph-toolbar", children: [
|
|
231
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
232
|
+
filteredNotes.length,
|
|
233
|
+
" notes"
|
|
234
|
+
] }),
|
|
235
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
236
|
+
/* @__PURE__ */ jsx(
|
|
237
|
+
"button",
|
|
238
|
+
{
|
|
239
|
+
type: "button",
|
|
240
|
+
onClick: () => zoom(-0.2),
|
|
241
|
+
"aria-label": "Zoom out",
|
|
242
|
+
children: "−"
|
|
243
|
+
}
|
|
244
|
+
),
|
|
245
|
+
/* @__PURE__ */ jsx(
|
|
246
|
+
"button",
|
|
247
|
+
{
|
|
248
|
+
type: "button",
|
|
249
|
+
onClick: () => zoom(0.2),
|
|
250
|
+
"aria-label": "Zoom in",
|
|
251
|
+
children: "+"
|
|
252
|
+
}
|
|
253
|
+
),
|
|
254
|
+
/* @__PURE__ */ jsx(
|
|
255
|
+
"button",
|
|
256
|
+
{
|
|
257
|
+
type: "button",
|
|
258
|
+
onClick: () => setView({ x: 0, y: 0, scale: 1 }),
|
|
259
|
+
children: "Reset"
|
|
260
|
+
}
|
|
261
|
+
)
|
|
262
|
+
] })
|
|
263
|
+
] }),
|
|
264
|
+
/* @__PURE__ */ jsx(
|
|
265
|
+
"svg",
|
|
266
|
+
{
|
|
267
|
+
class: "garden-graph",
|
|
268
|
+
viewBox: `0 0 ${GRAPH_WIDTH} ${GRAPH_HEIGHT}`,
|
|
269
|
+
role: "img",
|
|
270
|
+
"aria-label": "Interactive graph of notes and internal links",
|
|
271
|
+
onWheel: (event) => {
|
|
272
|
+
event.preventDefault();
|
|
273
|
+
zoom(event.deltaY > 0 ? -0.1 : 0.1);
|
|
274
|
+
},
|
|
275
|
+
onPointerMove: (event) => {
|
|
276
|
+
if (event.buttons !== 1) return;
|
|
277
|
+
setView((current) => ({
|
|
278
|
+
...current,
|
|
279
|
+
x: current.x + event.movementX / current.scale,
|
|
280
|
+
y: current.y + event.movementY / current.scale
|
|
281
|
+
}));
|
|
282
|
+
},
|
|
283
|
+
children: /* @__PURE__ */ jsxs("g", { transform: `translate(${view.x} ${view.y}) scale(${view.scale})`, children: [
|
|
284
|
+
visibleEdges.map((edge) => {
|
|
285
|
+
const source = graphNodes.get(edge.source);
|
|
286
|
+
const target = graphNodes.get(edge.target);
|
|
287
|
+
if (!source || !target) return null;
|
|
288
|
+
return /* @__PURE__ */ jsx(
|
|
289
|
+
"line",
|
|
290
|
+
{
|
|
291
|
+
class: "garden-graph__edge",
|
|
292
|
+
x1: source.x,
|
|
293
|
+
y1: source.y,
|
|
294
|
+
x2: target.x,
|
|
295
|
+
y2: target.y
|
|
296
|
+
},
|
|
297
|
+
`${edge.source}-${edge.target}`
|
|
298
|
+
);
|
|
299
|
+
}),
|
|
300
|
+
filteredNotes.map((note) => {
|
|
301
|
+
const node = graphNodes.get(note.slug);
|
|
302
|
+
if (!node) return null;
|
|
303
|
+
const selected = note.slug === selectedSlug;
|
|
304
|
+
return /* @__PURE__ */ jsx("g", { class: "garden-graph__node", children: /* @__PURE__ */ jsxs(
|
|
305
|
+
"a",
|
|
306
|
+
{
|
|
307
|
+
href: `/explore?note=${encodeURIComponent(note.slug)}`,
|
|
308
|
+
"aria-label": `Select ${note.title}`,
|
|
309
|
+
onClick: (event) => {
|
|
310
|
+
event.preventDefault();
|
|
311
|
+
selectNote(note.slug);
|
|
312
|
+
},
|
|
313
|
+
children: [
|
|
314
|
+
/* @__PURE__ */ jsx(
|
|
315
|
+
"circle",
|
|
316
|
+
{
|
|
317
|
+
cx: node.x,
|
|
318
|
+
cy: node.y,
|
|
319
|
+
r: selected ? 15 : node.radius,
|
|
320
|
+
"data-selected": selected ? "true" : "false"
|
|
321
|
+
}
|
|
322
|
+
),
|
|
323
|
+
/* @__PURE__ */ jsx("text", { x: node.x + 18, y: node.y + 5, children: note.title })
|
|
324
|
+
]
|
|
325
|
+
}
|
|
326
|
+
) }, note.slug);
|
|
327
|
+
})
|
|
328
|
+
] })
|
|
329
|
+
}
|
|
330
|
+
)
|
|
331
|
+
] }),
|
|
332
|
+
/* @__PURE__ */ jsx("aside", { class: panelClass("details", mobilePanel), children: selectedNote ? /* @__PURE__ */ jsx(
|
|
333
|
+
NoteDetails,
|
|
334
|
+
{
|
|
335
|
+
note: selectedNote,
|
|
336
|
+
noteBySlug,
|
|
337
|
+
relatedNotes,
|
|
338
|
+
onSelect: selectNote
|
|
339
|
+
}
|
|
340
|
+
) : /* @__PURE__ */ jsx("p", { class: "garden-explorer__empty", children: "Select a note to inspect links." }) })
|
|
341
|
+
] });
|
|
342
|
+
}
|
|
343
|
+
function FilterList(props) {
|
|
344
|
+
return /* @__PURE__ */ jsxs("div", { class: "garden-explorer__section", children: [
|
|
345
|
+
/* @__PURE__ */ jsx("p", { class: "garden-explorer__eyebrow", children: props.title }),
|
|
346
|
+
/* @__PURE__ */ jsx("div", { class: "garden-explorer__chips", children: props.items.slice(0, 28).map((item) => /* @__PURE__ */ jsxs(
|
|
347
|
+
"button",
|
|
348
|
+
{
|
|
349
|
+
type: "button",
|
|
350
|
+
class: "garden-explorer__chip",
|
|
351
|
+
"aria-pressed": props.selectedKey === item.key,
|
|
352
|
+
onClick: () => props.onSelect(item.key),
|
|
353
|
+
children: [
|
|
354
|
+
/* @__PURE__ */ jsx("span", { children: item.label }),
|
|
355
|
+
/* @__PURE__ */ jsx("span", { children: item.count })
|
|
356
|
+
]
|
|
357
|
+
},
|
|
358
|
+
item.key
|
|
359
|
+
)) })
|
|
360
|
+
] });
|
|
361
|
+
}
|
|
362
|
+
function NoteList(props) {
|
|
363
|
+
return /* @__PURE__ */ jsx("ul", { class: "garden-explorer__note-list", children: props.notes.map((note) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
364
|
+
"button",
|
|
365
|
+
{
|
|
366
|
+
type: "button",
|
|
367
|
+
"aria-pressed": props.selectedSlug === note.slug,
|
|
368
|
+
onClick: () => props.onSelect(note.slug),
|
|
369
|
+
children: [
|
|
370
|
+
/* @__PURE__ */ jsx("span", { children: note.title }),
|
|
371
|
+
/* @__PURE__ */ jsx("small", { children: note.folder })
|
|
372
|
+
]
|
|
373
|
+
}
|
|
374
|
+
) }, note.slug)) });
|
|
375
|
+
}
|
|
376
|
+
function NoteDetails(props) {
|
|
377
|
+
return /* @__PURE__ */ jsxs("div", { class: "garden-explorer__details", children: [
|
|
378
|
+
/* @__PURE__ */ jsx("p", { class: "garden-explorer__eyebrow", children: "Selected Note" }),
|
|
379
|
+
/* @__PURE__ */ jsx("h2", { children: /* @__PURE__ */ jsx("a", { href: props.note.permalink, children: props.note.title }) }),
|
|
380
|
+
/* @__PURE__ */ jsx("p", { class: "garden-explorer__path", children: props.note.slug }),
|
|
381
|
+
props.note.excerpt && /* @__PURE__ */ jsx("p", { children: props.note.excerpt }),
|
|
382
|
+
/* @__PURE__ */ jsx("div", { class: "garden-explorer__tag-row", children: props.note.tags.map((tag) => /* @__PURE__ */ jsxs("span", { children: [
|
|
383
|
+
"#",
|
|
384
|
+
tag
|
|
385
|
+
] }, tag)) }),
|
|
386
|
+
/* @__PURE__ */ jsx(
|
|
387
|
+
LinkedNoteSection,
|
|
388
|
+
{
|
|
389
|
+
title: "Outgoing Links",
|
|
390
|
+
slugs: props.note.outgoing,
|
|
391
|
+
noteBySlug: props.noteBySlug,
|
|
392
|
+
onSelect: props.onSelect
|
|
393
|
+
}
|
|
394
|
+
),
|
|
395
|
+
/* @__PURE__ */ jsx(
|
|
396
|
+
LinkedNoteSection,
|
|
397
|
+
{
|
|
398
|
+
title: "Backlinks",
|
|
399
|
+
slugs: props.note.backlinks,
|
|
400
|
+
noteBySlug: props.noteBySlug,
|
|
401
|
+
onSelect: props.onSelect
|
|
402
|
+
}
|
|
403
|
+
),
|
|
404
|
+
/* @__PURE__ */ jsxs("div", { class: "garden-explorer__section garden-explorer__section--flush", children: [
|
|
405
|
+
/* @__PURE__ */ jsx("p", { class: "garden-explorer__eyebrow", children: "Related Notes" }),
|
|
406
|
+
/* @__PURE__ */ jsx(
|
|
407
|
+
NoteList,
|
|
408
|
+
{
|
|
409
|
+
notes: props.relatedNotes,
|
|
410
|
+
selectedSlug: props.note.slug,
|
|
411
|
+
onSelect: props.onSelect
|
|
412
|
+
}
|
|
413
|
+
)
|
|
414
|
+
] })
|
|
415
|
+
] });
|
|
416
|
+
}
|
|
417
|
+
function LinkedNoteSection(props) {
|
|
418
|
+
const notes = props.slugs.map((slug) => props.noteBySlug.get(slug)).filter((note) => note !== void 0);
|
|
419
|
+
return /* @__PURE__ */ jsxs("div", { class: "garden-explorer__section garden-explorer__section--flush", children: [
|
|
420
|
+
/* @__PURE__ */ jsx("p", { class: "garden-explorer__eyebrow", children: props.title }),
|
|
421
|
+
notes.length > 0 ? /* @__PURE__ */ jsx(NoteList, { notes, selectedSlug: "", onSelect: props.onSelect }) : /* @__PURE__ */ jsx("p", { class: "garden-explorer__empty", children: "No notes." })
|
|
422
|
+
] });
|
|
423
|
+
}
|
|
424
|
+
function layoutNodes(notes, selectedSlug) {
|
|
425
|
+
return layoutRadialGraph(notes, {
|
|
426
|
+
width: GRAPH_WIDTH,
|
|
427
|
+
height: GRAPH_HEIGHT,
|
|
428
|
+
centerSlug: selectedSlug
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
function getRelatedNotes(selectedNote, notes) {
|
|
432
|
+
const directSlugs = /* @__PURE__ */ new Set([
|
|
433
|
+
...selectedNote.outgoing,
|
|
434
|
+
...selectedNote.backlinks
|
|
435
|
+
]);
|
|
436
|
+
const selectedTags = new Set(selectedNote.tags);
|
|
437
|
+
return notes.filter((note) => note.slug !== selectedNote.slug).map((note) => ({
|
|
438
|
+
note,
|
|
439
|
+
score: (directSlugs.has(note.slug) ? 8 : 0) + note.tags.filter((tag) => selectedTags.has(tag)).length * 3
|
|
440
|
+
})).filter((item) => item.score > 0).sort(
|
|
441
|
+
(a, b) => b.score - a.score || a.note.title.localeCompare(b.note.title, "ja")
|
|
442
|
+
).slice(0, 8).map((item) => item.note);
|
|
443
|
+
}
|
|
444
|
+
function panelClass(panel, activePanel) {
|
|
445
|
+
return `garden-explorer__panel garden-explorer__panel--${panel}${panel === activePanel ? " garden-explorer__panel--active" : ""}`;
|
|
446
|
+
}
|
|
447
|
+
function clamp(value, min, max) {
|
|
448
|
+
return Math.min(Math.max(value, min), max);
|
|
449
|
+
}
|
|
450
|
+
export {
|
|
451
|
+
GardenExplorer as default
|
|
452
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { default as GardenExplorer } from "./components/garden-explorer.js";
|
|
2
|
+
export type { GardenExplorerData, GardenExplorerEdge, GardenExplorerFolder, GardenExplorerNote, GardenExplorerTag, } from "./src/garden-explorer.js";
|
|
3
|
+
export { getGardenExplorerData } from "./src/garden-explorer.server.js";
|
|
4
|
+
export declare function gardenExplorerPlugin(): import("@riebeckite/core").RiebeckitePlugin<unknown>;
|