@mvarble/mesearch-ui 0.0.0-stage → 0.1.0

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.
Files changed (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +84 -2
  3. package/dist/ambient.d.ts +2 -0
  4. package/dist/components/Article.svelte +226 -0
  5. package/dist/components/Article.svelte.d.ts +16 -0
  6. package/dist/components/ArticleHeader.svelte +134 -0
  7. package/dist/components/ArticleHeader.svelte.d.ts +19 -0
  8. package/dist/components/Chips.svelte +22 -0
  9. package/dist/components/Chips.svelte.d.ts +8 -0
  10. package/dist/components/Graph.svelte +727 -0
  11. package/dist/components/Graph.svelte.d.ts +16 -0
  12. package/dist/components/GraphPanel.svelte +328 -0
  13. package/dist/components/GraphPanel.svelte.d.ts +17 -0
  14. package/dist/components/Icon.svelte +67 -0
  15. package/dist/components/Icon.svelte.d.ts +8 -0
  16. package/dist/components/Index.svelte +283 -0
  17. package/dist/components/Index.svelte.d.ts +9 -0
  18. package/dist/components/Pager.svelte +71 -0
  19. package/dist/components/Pager.svelte.d.ts +12 -0
  20. package/dist/components/Palette.svelte +333 -0
  21. package/dist/components/Palette.svelte.d.ts +9 -0
  22. package/dist/components/Parts.svelte +62 -0
  23. package/dist/components/Parts.svelte.d.ts +11 -0
  24. package/dist/components/Proof.svelte +39 -0
  25. package/dist/components/Proof.svelte.d.ts +8 -0
  26. package/dist/components/Rail.svelte +186 -0
  27. package/dist/components/Rail.svelte.d.ts +13 -0
  28. package/dist/components/References.svelte +94 -0
  29. package/dist/components/References.svelte.d.ts +8 -0
  30. package/dist/components/SequenceTrack.svelte +186 -0
  31. package/dist/components/SequenceTrack.svelte.d.ts +11 -0
  32. package/dist/components/Shell.svelte +82 -0
  33. package/dist/components/Shell.svelte.d.ts +14 -0
  34. package/dist/components/Statement.svelte +105 -0
  35. package/dist/components/Statement.svelte.d.ts +11 -0
  36. package/dist/components/ThemeToggle.svelte +19 -0
  37. package/dist/components/ThemeToggle.svelte.d.ts +3 -0
  38. package/dist/components/Toc.svelte +145 -0
  39. package/dist/components/Toc.svelte.d.ts +8 -0
  40. package/dist/fonts.d.ts +4 -0
  41. package/dist/fonts.js +6 -0
  42. package/dist/fuzzy.d.ts +12 -0
  43. package/dist/fuzzy.js +57 -0
  44. package/dist/graph-layout.d.ts +35 -0
  45. package/dist/graph-layout.js +98 -0
  46. package/dist/index.d.ts +24 -0
  47. package/dist/index.js +30 -0
  48. package/dist/reading.svelte.d.ts +3 -0
  49. package/dist/reading.svelte.js +4 -0
  50. package/dist/server.d.ts +26 -0
  51. package/dist/server.js +115 -0
  52. package/dist/settings.d.ts +10 -0
  53. package/dist/settings.js +28 -0
  54. package/dist/styles/base.css +136 -0
  55. package/dist/styles/index.css +6 -0
  56. package/dist/styles/prose.css +203 -0
  57. package/dist/styles/tokens.css +111 -0
  58. package/dist/theme.svelte.d.ts +12 -0
  59. package/dist/theme.svelte.js +66 -0
  60. package/dist/types.d.ts +84 -0
  61. package/dist/types.js +3 -0
  62. package/package.json +66 -3
@@ -0,0 +1,16 @@
1
+ import type { GraphLegend as Legend } from '../types.js';
2
+ export declare const MESEARCH_LEGEND: Legend;
3
+ import { type Component } from 'svelte';
4
+ import { type GraphTuning } from '../graph-layout.js';
5
+ import type { GraphLegend, GraphLink, GraphNode, IndexEntry } from '../types.js';
6
+ type $$ComponentProps = {
7
+ nodes: GraphNode[];
8
+ links: GraphLink[];
9
+ entries: Record<string, IndexEntry>;
10
+ descriptions?: Record<string, Component>;
11
+ tuning?: GraphTuning;
12
+ legend?: GraphLegend;
13
+ };
14
+ declare const Graph: Component<$$ComponentProps, {}, "">;
15
+ type Graph = ReturnType<typeof Graph>;
16
+ export default Graph;
@@ -0,0 +1,328 @@
1
+ <script lang="ts">
2
+ import type { Component } from 'svelte';
3
+ import { formatDate, isoDate, kindLabel } from '../settings.js';
4
+ import type { GraphLink, IndexEntry } from '../types.js';
5
+ import Icon from './Icon.svelte';
6
+
7
+ // A selected node's preview. Beside a wide graph it is a drawer that slides
8
+ // out of the graph's right edge, leaving the graph usable; under a narrow
9
+ // one it is a sheet that rises from the bottom of the screen, which a thumb
10
+ // can reach and dismiss with a downward swipe. Either way the reader stays
11
+ // on the map, can hop to neighbouring nodes, and opens the document only
12
+ // when they choose to.
13
+ let {
14
+ entry,
15
+ Description,
16
+ links,
17
+ entries,
18
+ onselect,
19
+ headings = { from: 'Builds on', to: 'Leads to', related: 'Related' },
20
+ }: {
21
+ entry: IndexEntry | undefined;
22
+ Description: Component | undefined;
23
+ links: GraphLink[];
24
+ entries: Record<string, IndexEntry>;
25
+ onselect: (key: string | null) => void;
26
+ headings?: { from: string; to: string; related: string };
27
+ } = $props();
28
+
29
+ let panel: HTMLElement | undefined = $state();
30
+ let dragFrom: number | null = null;
31
+ let dragBy = $state(0);
32
+
33
+ const neighbours = (pick: (link: GraphLink) => string | undefined) =>
34
+ links
35
+ .map(pick)
36
+ .filter((key): key is string => !!key && !!entries[key])
37
+ .map((key) => entries[key]!);
38
+
39
+ let buildsOn = $derived(
40
+ entry
41
+ ? neighbours((l) => (l.style == 'solid' && l.to == entry!.key ? l.from : undefined))
42
+ : [],
43
+ );
44
+ let leadsTo = $derived(
45
+ entry
46
+ ? neighbours((l) => (l.style == 'solid' && l.from == entry!.key ? l.to : undefined))
47
+ : [],
48
+ );
49
+ let related = $derived(
50
+ entry
51
+ ? neighbours((l) =>
52
+ l.style == 'dashed'
53
+ ? l.from == entry!.key
54
+ ? l.to
55
+ : l.to == entry!.key
56
+ ? l.from
57
+ : undefined
58
+ : undefined,
59
+ )
60
+ : [],
61
+ );
62
+
63
+ $effect(() => {
64
+ if (entry) panel?.focus({ preventScroll: true });
65
+ });
66
+
67
+ function onkeydown(event: KeyboardEvent) {
68
+ if (event.key == 'Escape') {
69
+ onselect(null);
70
+ event.stopPropagation();
71
+ }
72
+ }
73
+
74
+ // Swiping the sheet's handle down far enough dismisses it.
75
+ function onhandledown(event: PointerEvent) {
76
+ dragFrom = event.clientY;
77
+ (event.currentTarget as Element).setPointerCapture(event.pointerId);
78
+ }
79
+ function onhandlemove(event: PointerEvent) {
80
+ if (dragFrom !== null) dragBy = Math.max(0, event.clientY - dragFrom);
81
+ }
82
+ function onhandleup() {
83
+ if (dragBy > 90) onselect(null);
84
+ dragFrom = null;
85
+ dragBy = 0;
86
+ }
87
+ </script>
88
+
89
+ {#if entry}
90
+ <button
91
+ class="backdrop"
92
+ type="button"
93
+ aria-label="Close the preview"
94
+ onclick={() => onselect(null)}
95
+ ></button>
96
+ {/if}
97
+
98
+ <aside
99
+ bind:this={panel}
100
+ class="panel kind-{entry?.kind ?? 'concept'}"
101
+ class:open={entry}
102
+ style:--drag="{dragBy}px"
103
+ aria-label={entry ? `${kindLabel(entry.kind)}: ${entry.title}` : undefined}
104
+ aria-hidden={!entry}
105
+ tabindex="-1"
106
+ {onkeydown}
107
+ inert={!entry}
108
+ >
109
+ {#if entry}
110
+ <div
111
+ class="handle"
112
+ onpointerdown={onhandledown}
113
+ onpointermove={onhandlemove}
114
+ onpointerup={onhandleup}
115
+ onpointercancel={onhandleup}
116
+ aria-hidden="true"
117
+ ></div>
118
+ <header>
119
+ <p class="eyebrow kind">{kindLabel(entry.kind)}</p>
120
+ <button class="close" type="button" onclick={() => onselect(null)} aria-label="Close">
121
+ <Icon name="close" size={18} />
122
+ </button>
123
+ </header>
124
+ <h3 class="title">{@html entry.titleHtml}</h3>
125
+ <p class="meta">
126
+ Updated <time datetime={isoDate(entry.updated)}>{formatDate(entry.updated)}</time>
127
+ {#if entry.readingMinutes}· {entry.readingMinutes} min read{/if}
128
+ </p>
129
+ <div class="body prose">
130
+ {#if Description}
131
+ <Description />
132
+ {:else if entry.summaryHtml}
133
+ <p>{@html entry.summaryHtml}</p>
134
+ {/if}
135
+ </div>
136
+
137
+ {#each [[headings.from, buildsOn], [headings.to, leadsTo], [headings.related, related]] as const as [heading, list] (heading)}
138
+ {#if list.length}
139
+ <section>
140
+ <h4 class="eyebrow">{heading}</h4>
141
+ <ul class="chips">
142
+ {#each list as other (other.key)}
143
+ <li>
144
+ <button
145
+ type="button"
146
+ class="chip kind-{other.kind}"
147
+ onclick={() => onselect(other.key)}
148
+ >
149
+ {other.title}
150
+ </button>
151
+ </li>
152
+ {/each}
153
+ </ul>
154
+ </section>
155
+ {/if}
156
+ {/each}
157
+
158
+ <a class="read" href={entry.url}>
159
+ Read the {kindLabel(entry.kind).toLowerCase()}
160
+ <Icon name="arrow-right" size={18} />
161
+ </a>
162
+ {/if}
163
+ </aside>
164
+
165
+ <style>
166
+ .panel {
167
+ position: absolute;
168
+ z-index: 5;
169
+ top: 0;
170
+ right: 0;
171
+ bottom: 0;
172
+ width: min(23rem, 45%);
173
+ display: flex;
174
+ flex-direction: column;
175
+ gap: 0.9rem;
176
+ padding: 1.1rem 1.25rem 1.25rem;
177
+ background: var(--paper-raised);
178
+ border-left: 1px solid var(--rule);
179
+ box-shadow: var(--shadow);
180
+ overflow-y: auto;
181
+ outline: none;
182
+ transform: translateX(105%);
183
+ transition: transform 260ms cubic-bezier(0.2, 0.8, 0.2, 1);
184
+ font-family: var(--font-ui);
185
+ }
186
+
187
+ .panel.open {
188
+ transform: none;
189
+ }
190
+
191
+ .backdrop,
192
+ .handle {
193
+ display: none;
194
+ }
195
+
196
+ header {
197
+ display: flex;
198
+ align-items: center;
199
+ justify-content: space-between;
200
+ border-top: 3px solid var(--kind);
201
+ margin: -1.1rem -1.25rem 0;
202
+ padding: 0.8rem 1.25rem 0;
203
+ }
204
+
205
+ .kind {
206
+ color: var(--kind);
207
+ margin: 0;
208
+ }
209
+
210
+ .close {
211
+ display: grid;
212
+ place-items: center;
213
+ width: 2rem;
214
+ height: 2rem;
215
+ border: none;
216
+ border-radius: 8px;
217
+ background: transparent;
218
+ color: var(--muted);
219
+ cursor: pointer;
220
+ }
221
+
222
+ .close:hover {
223
+ background: var(--accent-soft);
224
+ color: var(--ink);
225
+ }
226
+
227
+ .title {
228
+ margin: 0;
229
+ font: 600 1.45rem / 1.2 var(--font-serif);
230
+ color: var(--ink);
231
+ text-wrap: balance;
232
+ }
233
+
234
+ .meta {
235
+ margin: -0.4rem 0 0;
236
+ font-size: 0.8rem;
237
+ color: var(--muted);
238
+ }
239
+
240
+ .body {
241
+ font: 400 0.98rem / 1.65 var(--font-serif);
242
+ color: var(--ink-soft);
243
+ }
244
+
245
+ .body :global(p:last-child) {
246
+ margin-bottom: 0;
247
+ }
248
+
249
+ section h4 {
250
+ margin: 0 0 0.45rem;
251
+ }
252
+
253
+ .chip {
254
+ font-size: 0.8rem;
255
+ }
256
+
257
+ .read {
258
+ margin-top: auto;
259
+ display: inline-flex;
260
+ align-items: center;
261
+ justify-content: center;
262
+ gap: 0.5rem;
263
+ padding: 0.7rem 1rem;
264
+ border-radius: 10px;
265
+ background: var(--kind);
266
+ color: var(--paper-raised);
267
+ font: 600 0.9rem / 1 var(--font-ui);
268
+ text-decoration: none;
269
+ transition: filter 120ms;
270
+ }
271
+
272
+ .read:hover {
273
+ filter: brightness(1.08);
274
+ }
275
+
276
+ /* A narrow screen: a sheet over the bottom of it. */
277
+ @media (max-width: 759px) {
278
+ .panel {
279
+ position: fixed;
280
+ z-index: 60;
281
+ top: auto;
282
+ left: 0;
283
+ right: 0;
284
+ bottom: 0;
285
+ width: auto;
286
+ max-height: 75vh;
287
+ border-left: none;
288
+ border-top: 1px solid var(--rule);
289
+ border-radius: 18px 18px 0 0;
290
+ box-shadow: var(--shadow-strong);
291
+ padding-bottom: max(1.25rem, env(safe-area-inset-bottom));
292
+ transform: translateY(105%);
293
+ }
294
+
295
+ .panel.open {
296
+ transform: translateY(var(--drag));
297
+ transition: transform 200ms ease;
298
+ }
299
+
300
+ header {
301
+ border-top: none;
302
+ }
303
+
304
+ .handle {
305
+ display: block;
306
+ align-self: center;
307
+ width: 2.75rem;
308
+ height: 5px;
309
+ margin: -0.35rem 0 -0.4rem;
310
+ padding: 0;
311
+ border-radius: 3px;
312
+ background: var(--rule-strong);
313
+ cursor: grab;
314
+ touch-action: none;
315
+ flex: none;
316
+ }
317
+
318
+ .backdrop {
319
+ display: block;
320
+ position: fixed;
321
+ z-index: 55;
322
+ inset: 0;
323
+ border: none;
324
+ background: rgba(20, 16, 10, 0.3);
325
+ cursor: default;
326
+ }
327
+ }
328
+ </style>
@@ -0,0 +1,17 @@
1
+ import type { Component } from 'svelte';
2
+ import type { GraphLink, IndexEntry } from '../types.js';
3
+ type $$ComponentProps = {
4
+ entry: IndexEntry | undefined;
5
+ Description: Component | undefined;
6
+ links: GraphLink[];
7
+ entries: Record<string, IndexEntry>;
8
+ onselect: (key: string | null) => void;
9
+ headings?: {
10
+ from: string;
11
+ to: string;
12
+ related: string;
13
+ };
14
+ };
15
+ declare const GraphPanel: Component<$$ComponentProps, {}, "">;
16
+ type GraphPanel = ReturnType<typeof GraphPanel>;
17
+ export default GraphPanel;
@@ -0,0 +1,67 @@
1
+ <script lang="ts">
2
+ // A handful of line icons, drawn on a 24-unit grid in the current colour.
3
+ import type { IconName } from '../types.js';
4
+
5
+ let { name, size = 20 }: { name: IconName; size?: number } = $props();
6
+ </script>
7
+
8
+ <svg
9
+ width={size}
10
+ height={size}
11
+ viewBox="0 0 24 24"
12
+ fill="none"
13
+ stroke="currentColor"
14
+ stroke-width="1.6"
15
+ stroke-linecap="round"
16
+ stroke-linejoin="round"
17
+ aria-hidden="true"
18
+ >
19
+ {#if name == 'search'}
20
+ <circle cx="11" cy="11" r="6.5" /><path d="m20 20-4.3-4.3" />
21
+ {:else if name == 'graph'}
22
+ <circle cx="6" cy="6" r="2.4" /><circle cx="18" cy="8" r="2.4" /><circle
23
+ cx="11"
24
+ cy="18"
25
+ r="2.4"
26
+ /><path d="M8.2 7 15.7 7.7M7.2 8.1l2.6 7.7M16.8 10l-4.4 6.2" />
27
+ {:else if name == 'list'}
28
+ <path d="M9 6h11M9 12h11M9 18h11" /><circle cx="4.5" cy="6" r="1" /><circle
29
+ cx="4.5"
30
+ cy="12"
31
+ r="1"
32
+ /><circle cx="4.5" cy="18" r="1" />
33
+ {:else if name == 'sun'}
34
+ <circle cx="12" cy="12" r="4" /><path
35
+ d="M12 2.5v2M12 19.5v2M2.5 12h2M19.5 12h2M5.3 5.3l1.4 1.4M17.3 17.3l1.4 1.4M5.3 18.7l1.4-1.4M17.3 6.7l1.4-1.4"
36
+ />
37
+ {:else if name == 'moon'}
38
+ <path d="M20 14.5A8.5 8.5 0 0 1 9.5 4a8.5 8.5 0 1 0 10.5 10.5Z" />
39
+ {:else if name == 'auto'}
40
+ <circle cx="12" cy="12" r="8.5" /><path
41
+ d="M12 3.5v17A8.5 8.5 0 0 0 12 3.5Z"
42
+ fill="currentColor"
43
+ />
44
+ {:else if name == 'plus'}
45
+ <path d="M12 5v14M5 12h14" />
46
+ {:else if name == 'minus'}
47
+ <path d="M5 12h14" />
48
+ {:else if name == 'reset'}
49
+ <path d="M4 4v5h5" /><path d="M5.5 15a7.5 7.5 0 1 0 1.3-8.3L4 9" />
50
+ {:else if name == 'close'}
51
+ <path d="M6 6l12 12M18 6 6 18" />
52
+ {:else if name == 'arrow-right'}
53
+ <path d="M5 12h14M13 6l6 6-6 6" />
54
+ {:else if name == 'arrow-left'}
55
+ <path d="M19 12H5M11 6l-6 6 6 6" />
56
+ {:else if name == 'home'}
57
+ <path d="M4 11 12 4l8 7M6 9.5V20h12V9.5" />
58
+ {:else if name == 'pen'}
59
+ <path d="M4 20h4L19.5 8.5a2.1 2.1 0 0 0-3-3L5 17v3Z" /><path d="m14.5 7.5 3 3" />
60
+ {:else if name == 'book'}
61
+ <path d="M4 5.5A2.5 2.5 0 0 1 6.5 3H20v15H6.5A2.5 2.5 0 0 0 4 20.5Z" /><path
62
+ d="M4 20.5A2.5 2.5 0 0 0 6.5 23H20v-5"
63
+ />
64
+ {:else if name == 'quote'}
65
+ <path d="M5 11h4v6H4v-5c0-3 1.5-5 4-6M15 11h4v6h-5v-5c0-3 1.5-5 4-6" />
66
+ {/if}
67
+ </svg>
@@ -0,0 +1,8 @@
1
+ import type { IconName } from '../types.js';
2
+ type $$ComponentProps = {
3
+ name: IconName;
4
+ size?: number;
5
+ };
6
+ declare const Icon: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type Icon = ReturnType<typeof Icon>;
8
+ export default Icon;