@lapis-notes/workspace 0.1.0 → 0.2.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 (77) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/LICENSE.md +3 -3
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.js +3 -0
  5. package/dist/plugin-management/CommunityPluginsSettingsPage.svelte +152 -73
  6. package/dist/plugin-management/PluginBrowser.svelte +517 -0
  7. package/dist/plugin-management/PluginBrowser.svelte.d.ts +17 -0
  8. package/dist/plugin-management/PluginBrowserCard.svelte +230 -0
  9. package/dist/plugin-management/PluginBrowserCard.svelte.d.ts +9 -0
  10. package/dist/plugin-management/PluginBrowserDetail.svelte +1168 -0
  11. package/dist/plugin-management/PluginBrowserDetail.svelte.d.ts +20 -0
  12. package/dist/plugin-management/PluginBrowserDirectory.svelte +1069 -0
  13. package/dist/plugin-management/PluginBrowserDirectory.svelte.d.ts +21 -0
  14. package/dist/plugin-management/PluginBrowserLanding.svelte +341 -0
  15. package/dist/plugin-management/PluginBrowserLanding.svelte.d.ts +12 -0
  16. package/dist/plugin-management/PluginRegistryBadge.svelte +41 -0
  17. package/dist/plugin-management/PluginRegistryBadge.svelte.d.ts +8 -0
  18. package/dist/plugin-management/PluginRegistryContentState.svelte +80 -0
  19. package/dist/plugin-management/PluginRegistryContentState.svelte.d.ts +14 -0
  20. package/dist/plugin-management/PluginRegistryImage.svelte +64 -0
  21. package/dist/plugin-management/PluginRegistryImage.svelte.d.ts +9 -0
  22. package/dist/plugin-management/PluginRegistryMedia.svelte +223 -0
  23. package/dist/plugin-management/PluginRegistryMedia.svelte.d.ts +8 -0
  24. package/dist/plugin-management/PluginRegistryMediaViewer.svelte +155 -0
  25. package/dist/plugin-management/PluginRegistryMediaViewer.svelte.d.ts +13 -0
  26. package/dist/plugin-management/PluginRegistryRow.svelte +120 -0
  27. package/dist/plugin-management/PluginRegistryRow.svelte.d.ts +25 -0
  28. package/dist/plugin-management/PluginRegistrySettingsPage.svelte +1009 -512
  29. package/dist/plugin-management/PluginRegistryStatus.svelte +33 -0
  30. package/dist/plugin-management/PluginRegistryStatus.svelte.d.ts +8 -0
  31. package/dist/plugin-management/PluginRegistryToolbar.svelte +159 -0
  32. package/dist/plugin-management/PluginRegistryToolbar.svelte.d.ts +21 -0
  33. package/dist/plugin-management/community-presentation/CommunityChip.svelte +229 -0
  34. package/dist/plugin-management/community-presentation/CommunityChip.svelte.d.ts +4 -0
  35. package/dist/plugin-management/community-presentation/CommunityMemberAvatar.svelte +42 -0
  36. package/dist/plugin-management/community-presentation/CommunityMemberAvatar.svelte.d.ts +13 -0
  37. package/dist/plugin-management/community-presentation/RegistryMediaGallery.svelte +282 -0
  38. package/dist/plugin-management/community-presentation/RegistryMediaGallery.svelte.d.ts +8 -0
  39. package/dist/plugin-management/community-presentation/RegistryMediaViewer.css +179 -0
  40. package/dist/plugin-management/community-presentation/RegistryMediaViewer.svelte +241 -0
  41. package/dist/plugin-management/community-presentation/RegistryMediaViewer.svelte.d.ts +14 -0
  42. package/dist/plugin-management/community-presentation/RegistryPluginChangelog.svelte +216 -0
  43. package/dist/plugin-management/community-presentation/RegistryPluginChangelog.svelte.d.ts +8 -0
  44. package/dist/plugin-management/community-presentation/RegistryVerifiedImage.svelte +179 -0
  45. package/dist/plugin-management/community-presentation/RegistryVerifiedImage.svelte.d.ts +15 -0
  46. package/dist/plugin-management/community-presentation/avatar-model.d.ts +14 -0
  47. package/dist/plugin-management/community-presentation/avatar-model.js +21 -0
  48. package/dist/plugin-management/community-presentation/community-member-avatar.css +137 -0
  49. package/dist/plugin-management/community-presentation/community-typography.css +30 -0
  50. package/dist/plugin-management/community-presentation/model-adapter.d.ts +54 -0
  51. package/dist/plugin-management/community-presentation/model-adapter.js +90 -0
  52. package/dist/plugin-management/community-presentation/model.d.ts +9 -0
  53. package/dist/plugin-management/community-presentation/model.js +40 -0
  54. package/dist/plugin-management/community-presentation/props.d.ts +21 -0
  55. package/dist/plugin-management/community-presentation/props.js +1 -0
  56. package/dist/plugin-management/community-presentation/registry-filter-icons.d.ts +3 -0
  57. package/dist/plugin-management/community-presentation/registry-filter-icons.js +70 -0
  58. package/dist/plugin-management/community-presentation/registry-markdown.d.ts +1 -0
  59. package/dist/plugin-management/community-presentation/registry-markdown.js +57 -0
  60. package/dist/plugin-management/community-presentation/registry-plugin-icons.d.ts +2 -0
  61. package/dist/plugin-management/community-presentation/registry-plugin-icons.js +31 -0
  62. package/dist/plugin-management/plugin-browser-model.d.ts +43 -0
  63. package/dist/plugin-management/plugin-browser-model.js +95 -0
  64. package/dist/plugin-management/plugin-browser-navigation.d.ts +3 -0
  65. package/dist/plugin-management/plugin-browser-navigation.js +36 -0
  66. package/dist/plugin-management/plugin-download-stats.d.ts +1 -0
  67. package/dist/plugin-management/plugin-download-stats.js +15 -0
  68. package/dist/plugin-management/plugin-management.css +1088 -117
  69. package/dist/plugin-management/plugin-registry-controller.svelte.d.ts +29 -0
  70. package/dist/plugin-management/plugin-registry-controller.svelte.js +154 -0
  71. package/dist/plugin-management/plugin-registry-detail-session.svelte.d.ts +18 -0
  72. package/dist/plugin-management/plugin-registry-detail-session.svelte.js +106 -0
  73. package/dist/plugin-management/plugin-registry-detail.d.ts +17 -0
  74. package/dist/plugin-management/plugin-registry-detail.js +1 -0
  75. package/dist/plugin-management/plugins-view.svelte.d.ts +40 -0
  76. package/dist/plugin-management/plugins-view.svelte.js +105 -0
  77. package/package.json +5 -5
@@ -0,0 +1,517 @@
1
+ <script lang="ts">
2
+ import { onMount, tick, untrack } from "svelte";
3
+ import {
4
+ pluginDownloadCounts,
5
+ type App,
6
+ type PluginCatalogEntry,
7
+ } from "@lapis-notes/api";
8
+ import { AppShell, AppShellController } from "@lapismd/design-core/shell";
9
+ import { Button } from "@lapismd/design-core/shadcn/button";
10
+ import * as Tooltip from "@lapismd/design-core/shadcn/tooltip";
11
+ import ArrowLeft from "@lucide/svelte/icons/arrow-left";
12
+ import * as Breadcrumb from "@lapismd/design-core/shadcn/breadcrumb";
13
+ import {
14
+ communityDetail,
15
+ type RegistryInstallAction,
16
+ } from "./community-presentation/model-adapter";
17
+ import PluginReadmeRenderer from "./PluginReadmeRenderer.svelte";
18
+ import { getPluginRegistryController } from "./plugin-registry-controller.svelte";
19
+ import { PluginRegistryDetailSession } from "./plugin-registry-detail-session.svelte";
20
+ import { defaultDirectoryState } from "./plugin-browser-model";
21
+ import type {
22
+ PluginBrowserRoute,
23
+ PluginDirectoryState,
24
+ } from "./plugin-browser-model";
25
+ import { formatApproximateDownloadCount } from "./plugin-download-stats";
26
+ import PluginBrowserLanding from "./PluginBrowserLanding.svelte";
27
+ import PluginBrowserDirectory from "./PluginBrowserDirectory.svelte";
28
+ import PluginRegistryContentState from "./PluginRegistryContentState.svelte";
29
+ import PluginBrowserDetail from "./PluginBrowserDetail.svelte";
30
+ import "./community-presentation/community-typography.css";
31
+ export interface PluginBrowserNavigation {
32
+ route: PluginBrowserRoute;
33
+ directory: PluginDirectoryState;
34
+ returnPage: "landing" | "directory";
35
+ scrollTop: number;
36
+ }
37
+ let {
38
+ app,
39
+ navigation,
40
+ onstatechange,
41
+ }: {
42
+ app: App;
43
+ navigation: PluginBrowserNavigation;
44
+ onstatechange?: () => void;
45
+ } = $props();
46
+ const ownedApp = untrack(() => app);
47
+ const registry = getPluginRegistryController(ownedApp);
48
+ const session = new PluginRegistryDetailSession(ownedApp);
49
+ let contentRegion = $state<HTMLDivElement | null>(null);
50
+ const scroller = $derived(
51
+ contentRegion?.querySelector<HTMLElement>(
52
+ '[data-ui-part="scroll-area-viewport"]',
53
+ ),
54
+ );
55
+ $effect(() => {
56
+ const page = navigation.route.page;
57
+ const region = scroller;
58
+ const scrollTop = untrack(() => navigation.scrollTop);
59
+ if (region)
60
+ void tick().then(() => {
61
+ if (navigation.route.page === page)
62
+ region.scrollTop = page === "plugin" ? 0 : scrollTop;
63
+ });
64
+ });
65
+ let previousDetail = "";
66
+ const installedIds = $derived(
67
+ new Set(registry.installed.map((record) => record.pluginId)),
68
+ );
69
+ const bundledIds = $derived(
70
+ new Set(ownedApp.plugins.corePluginEntries.map((p) => p.manifest.id)),
71
+ );
72
+ const updateIds = $derived(
73
+ new Set(
74
+ registry.updates
75
+ .filter((update) => update.canUpdate && !bundledIds.has(update.id))
76
+ .map((update) => update.id),
77
+ ),
78
+ );
79
+ const availableIds = $derived(new Set([...installedIds, ...bundledIds]));
80
+ const selectedId = $derived(
81
+ navigation.route.page === "plugin" ? navigation.route.pluginId : null,
82
+ );
83
+ const selected = $derived(
84
+ registry.catalogEntries.find((p) => p.id === selectedId) ?? null,
85
+ );
86
+ const update = $derived(
87
+ registry.updates.find((p) => p.id === selected?.id) ?? null,
88
+ );
89
+ const shell = untrack(
90
+ () =>
91
+ new AppShellController({
92
+ rightClosed: true,
93
+ sidebarMinWidth: 48,
94
+ sidebarMaxWidth: 440,
95
+ }),
96
+ );
97
+ let selectedVersion = $state<string>();
98
+ const resolved = $derived(
99
+ selected && session.detail
100
+ ? communityDetail(selected, session.detail)
101
+ : null,
102
+ );
103
+ const related = $derived(
104
+ selected
105
+ ? registry.catalogEntries
106
+ .filter(
107
+ (entry) =>
108
+ entry.id !== selected.id &&
109
+ entry.categories.some((category) =>
110
+ selected.categories.includes(category),
111
+ ),
112
+ )
113
+ .slice(0, 3)
114
+ : [],
115
+ );
116
+ const installAction = $derived.by<RegistryInstallAction>(() => {
117
+ if (!selected) return { state: "disabled" };
118
+ const feedback =
119
+ registry.actionFeedback?.pluginId === selected.id
120
+ ? registry.actionFeedback
121
+ : null;
122
+ if (
123
+ registry.runningAction === `install:${selected.id}` ||
124
+ registry.runningAction === `update:${selected.id}`
125
+ )
126
+ return { state: "installing" };
127
+ if (feedback?.state === "error")
128
+ return { state: "rejected", reason: feedback.message };
129
+ if (bundledIds.has(selected.id))
130
+ return { state: "installed", label: "Bundled" };
131
+ if (update?.canUpdate) return { state: "available", label: "Update" };
132
+ if (installedIds.has(selected.id)) return { state: "installed" };
133
+ const disabled = reason(selected);
134
+ return disabled
135
+ ? { state: "disabled", reason: disabled }
136
+ : { state: "available" };
137
+ });
138
+ const downloads = (id: string) =>
139
+ registry.downloadStats?.periods["30d"].plugins[id]?.total ?? 0;
140
+ function reason(entry: PluginCatalogEntry): string | undefined {
141
+ if (entry.status && entry.status !== "active")
142
+ return `This plugin is ${entry.status}.`;
143
+ if (
144
+ !ownedApp.pluginDistribution
145
+ .search({ compatibleOnly: true })
146
+ .some((p) => p.id === entry.id)
147
+ )
148
+ return "This plugin is incompatible with this app.";
149
+ if (
150
+ !registry.registrySources.some(
151
+ (source) => source.source.enabled && source.installReady,
152
+ )
153
+ )
154
+ return "No enabled registry is ready to install plugins.";
155
+ const bundle =
156
+ session.entryId === entry.id
157
+ ? session.detail?.versions[entry.latestVersion]?.bundle
158
+ : undefined;
159
+ if (bundle && "pending" in bundle && bundle.pending === true)
160
+ return "The verified bundle is not ready.";
161
+ return undefined;
162
+ }
163
+ onMount(() => {
164
+ const release = registry.retain();
165
+ void registry.refresh(false);
166
+ return () => {
167
+ session.dispose();
168
+ release();
169
+ };
170
+ });
171
+ $effect(() => {
172
+ if (selected && previousDetail !== selected.id) {
173
+ previousDetail = selected.id;
174
+ selectedVersion = undefined;
175
+ void session.select(selected);
176
+ }
177
+ if (navigation.route.page !== "plugin") {
178
+ previousDetail = "";
179
+ session.dispose();
180
+ if (navigation.route.page === "landing" && scroller)
181
+ scroller.scrollTop = 0;
182
+ }
183
+ });
184
+ function persist() {
185
+ onstatechange?.();
186
+ }
187
+ function go(
188
+ page: "landing" | "directory",
189
+ category?: string,
190
+ platform?: string,
191
+ ) {
192
+ navigation.scrollTop = 0;
193
+ navigation.route = { page };
194
+ if (category)
195
+ navigation.directory = {
196
+ ...navigation.directory,
197
+ categories: [category],
198
+ };
199
+ if (platform)
200
+ navigation.directory = { ...navigation.directory, platforms: [platform] };
201
+ if (scroller) scroller.scrollTop = 0;
202
+ persist();
203
+ }
204
+ function backToPlugins() {
205
+ navigation.route = { page: navigation.returnPage };
206
+ persist();
207
+ }
208
+ function open(entry: PluginCatalogEntry) {
209
+ navigation.returnPage =
210
+ navigation.route.page === "directory" ? "directory" : "landing";
211
+ navigation.scrollTop = scroller?.scrollTop ?? 0;
212
+ navigation.route = { page: "plugin", pluginId: entry.id };
213
+ if (scroller) scroller.scrollTop = 0;
214
+ persist();
215
+ }
216
+ </script>
217
+
218
+ {#snippet downloadSummary()}
219
+ {@const counts =
220
+ selected && registry.downloadStats
221
+ ? pluginDownloadCounts(registry.downloadStats, selected.id)
222
+ : null}
223
+ {#if counts}
224
+ <dl>
225
+ <dt>Downloads (30d)</dt>
226
+ <dd>~{formatApproximateDownloadCount(counts.recent)}</dd>
227
+ <dt>Lifetime downloads</dt>
228
+ <dd>~{formatApproximateDownloadCount(counts.lifetime)}</dd>
229
+ </dl>
230
+ <p>
231
+ Tracked downloads since {new Date(
232
+ registry.downloadStats!.trackedSince + "T00:00:00Z",
233
+ ).toLocaleDateString("en-US", {
234
+ month: "short",
235
+ day: "numeric",
236
+ year: "numeric",
237
+ timeZone: "UTC",
238
+ })}. Approximate redirect requests.
239
+ </p>
240
+ {/if}
241
+ {/snippet}
242
+ {#snippet overviewContent()}
243
+ {#if session.overview.status === "loaded"}<PluginReadmeRenderer
244
+ app={ownedApp}
245
+ markdown={session.overview.markdown}
246
+ sourcePath={session.overview.url ?? ""}
247
+ />{:else if session.overview.status === "error"}<PluginRegistryContentState
248
+ kind="warning"
249
+ heading="Overview unavailable"
250
+ description={session.overview.message}
251
+ actionLabel="Retry"
252
+ onaction={() => void session.retry("overview")}
253
+ />{/if}
254
+ {/snippet}
255
+ {#snippet changelogContent()}
256
+ {#if session.changelog.status === "loaded"}<PluginReadmeRenderer
257
+ app={ownedApp}
258
+ markdown={session.changelog.markdown}
259
+ sourcePath={session.changelog.url ?? ""}
260
+ />{:else if session.changelog.status === "error"}<PluginRegistryContentState
261
+ kind="warning"
262
+ heading="Changelog unavailable"
263
+ description={session.changelog.message}
264
+ actionLabel="Retry"
265
+ onaction={() => void session.retry("changelog")}
266
+ />{/if}
267
+ {/snippet}
268
+ <div class="plugin-browser community-ui" data-ui-component="plugin-browser">
269
+ <AppShell.Root
270
+ controller={shell}
271
+ class="registry-shell"
272
+ style="--ui-shell-height:100%;"
273
+ >
274
+ <AppShell.Main>
275
+ {#if navigation.route.page === "directory" && !registry.initialLoading && registry.catalogEntries.length}
276
+ <PluginBrowserDirectory
277
+ {registry}
278
+ compact={shell.mobile.resolvedMode === "mobile"}
279
+ onhome={() => go("landing")}
280
+ filtersOpen={shell.mobile.stage === "left" &&
281
+ shell.mobile.activePanelId("left") === "registry-filters"}
282
+ ontogglefilters={(event) => {
283
+ if (shell.mobile.stage === "left") shell.mobile.showMain();
284
+ else
285
+ shell.mobile.show(
286
+ "left",
287
+ "registry-filters",
288
+ event.currentTarget as HTMLButtonElement,
289
+ );
290
+ }}
291
+ bind:contentRef={contentRegion}
292
+ directoryState={navigation.directory}
293
+ installedIds={availableIds}
294
+ {updateIds}
295
+ {downloads}
296
+ onopen={open}
297
+ onchange={(state) => {
298
+ navigation.directory = state;
299
+ persist();
300
+ }}
301
+ />
302
+ {:else}
303
+ <AppShell.Body
304
+ element="section"
305
+ layout="regions"
306
+ label="Registry pages"
307
+ >
308
+ <div class="registry-pages-main">
309
+ {#if navigation.route.page === "plugin"}
310
+ <div class="registry-detail-navigation">
311
+ <Tooltip.Provider>
312
+ <Tooltip.Root>
313
+ <Tooltip.Trigger>
314
+ {#snippet child({ props })}
315
+ <Button
316
+ {...props}
317
+ type="button"
318
+ variant="ghost"
319
+ size="icon-sm"
320
+ class="registry-detail-back"
321
+ aria-label="Back to plugins"
322
+ onclick={backToPlugins}
323
+ >
324
+ <ArrowLeft
325
+ data-icon="inline-start"
326
+ aria-hidden="true"
327
+ />
328
+ </Button>
329
+ {/snippet}
330
+ </Tooltip.Trigger>
331
+ <Tooltip.Content>Back to plugins</Tooltip.Content>
332
+ </Tooltip.Root>
333
+ </Tooltip.Provider>
334
+ <div
335
+ class="registry-detail-breadcrumbs"
336
+ data-ui-part="registry-detail-breadcrumbs"
337
+ >
338
+ <Breadcrumb.Root>
339
+ <Breadcrumb.List>
340
+ <Breadcrumb.Item>
341
+ <Breadcrumb.Link
342
+ href="#/registry"
343
+ onclick={(event) => {
344
+ event.preventDefault();
345
+ go("landing");
346
+ }}>Registry</Breadcrumb.Link
347
+ >
348
+ </Breadcrumb.Item>
349
+ <Breadcrumb.Separator />
350
+ <Breadcrumb.Item>
351
+ <Breadcrumb.Link
352
+ href="#/registry/plugins"
353
+ onclick={(event) => {
354
+ event.preventDefault();
355
+ backToPlugins();
356
+ }}>Plugins</Breadcrumb.Link
357
+ >
358
+ </Breadcrumb.Item>
359
+ <Breadcrumb.Separator />
360
+ <Breadcrumb.Item>
361
+ <Breadcrumb.Page
362
+ >{selected?.name ??
363
+ selectedId ??
364
+ "Plugin"}</Breadcrumb.Page
365
+ >
366
+ </Breadcrumb.Item>
367
+ </Breadcrumb.List>
368
+ </Breadcrumb.Root>
369
+ </div>
370
+ </div>
371
+ {/if}
372
+ <AppShell.Body.Content
373
+ bind:ref={contentRegion}
374
+ scrollbarVisibility="hover"
375
+ label="Registry content"
376
+ >
377
+ <div class="registry-browser">
378
+ {#if registry.initialLoading}<PluginRegistryContentState
379
+ kind="loading"
380
+ heading="Loading plugins"
381
+ description="Reading your verified registry…"
382
+ />
383
+ {:else if registry.lastError && !registry.catalogEntries.length}<PluginRegistryContentState
384
+ kind="warning"
385
+ heading="Plugins unavailable"
386
+ description={registry.lastError.message}
387
+ actionLabel="Retry"
388
+ onaction={() => void registry.refresh(true)}
389
+ />
390
+ {:else}
391
+ {#if registry.lastError}<p role="alert">
392
+ Refresh failed: {registry.lastError.message}
393
+ </p>{/if}
394
+ {#if !registry.catalogEntries.length}<PluginRegistryContentState
395
+ kind="empty"
396
+ heading="No plugins available"
397
+ description="Your enabled registries have no plugins."
398
+ actionLabel="Refresh"
399
+ onaction={() => void registry.refresh(true)}
400
+ />
401
+ {:else if navigation.route.page === "landing"}<PluginBrowserLanding
402
+ plugins={registry.catalogEntries}
403
+ {updateIds}
404
+ onOpenUpdates={() => {
405
+ navigation.directory = {
406
+ ...defaultDirectoryState(),
407
+ updatesOnly: true,
408
+ };
409
+ go("directory");
410
+ }}
411
+ onOpenDirectory={() => go("directory")}
412
+ onOpenPlugin={open}
413
+ onOpenCategory={(category) => go("directory", category)}
414
+ />
415
+ {:else if selected && resolved}<PluginBrowserDetail
416
+ {resolved}
417
+ downloadSummary={registry.downloadStats
418
+ ? downloadSummary
419
+ : undefined}
420
+ {related}
421
+ {installAction}
422
+ {selectedVersion}
423
+ currentVersion={selected.latestVersion}
424
+ overview={session.overview.status === "loaded" ||
425
+ session.overview.status === "error"
426
+ ? overviewContent
427
+ : undefined}
428
+ changelog={session.changelog.status === "loaded" ||
429
+ session.changelog.status === "error"
430
+ ? changelogContent
431
+ : undefined}
432
+ onInstall={(request) =>
433
+ update?.canUpdate
434
+ ? registry.update(update)
435
+ : registry.install(selected, request.version)}
436
+ onOpenPlugin={open}
437
+ onOpenCategory={(category) => go("directory", category)}
438
+ onOpenPlatform={(platform) =>
439
+ go("directory", undefined, platform)}
440
+ onSelectVersion={(version) => (selectedVersion = version)}
441
+ />
442
+ {:else if selected && session.loading}<PluginRegistryContentState
443
+ kind="loading"
444
+ heading="Loading plugin details"
445
+ description="Reading verified plugin metadata…"
446
+ />
447
+ {:else}<PluginRegistryContentState
448
+ kind="empty"
449
+ heading="Plugin unavailable"
450
+ description="This plugin is no longer in the enabled catalog."
451
+ actionLabel="Back to plugins"
452
+ onaction={() => go("directory")}
453
+ />{/if}
454
+ {/if}
455
+ </div>
456
+ </AppShell.Body.Content>
457
+ </div>
458
+ </AppShell.Body>
459
+ {/if}
460
+ </AppShell.Main>
461
+ </AppShell.Root>
462
+ </div>
463
+
464
+ <style>
465
+ .plugin-browser {
466
+ container: plugin-browser / inline-size;
467
+ min-width: 0;
468
+ height: 100%;
469
+ width: 100%;
470
+ min-height: 0;
471
+ background: var(--ui-workspace-view-background, var(--background));
472
+ color: var(--ui-workspace-view-foreground, var(--foreground));
473
+ }
474
+ :global(.registry-shell) {
475
+ height: 100%;
476
+ min-height: 0;
477
+ }
478
+ .registry-browser {
479
+ min-height: 100%;
480
+ color: var(--foreground);
481
+ background: var(--background);
482
+ }
483
+
484
+ .registry-pages-main {
485
+ display: flex;
486
+ flex: 1;
487
+ flex-direction: column;
488
+ min-width: 0;
489
+ min-height: 0;
490
+ }
491
+ .registry-detail-navigation {
492
+ display: flex;
493
+ align-items: center;
494
+ gap: 0.5rem;
495
+ padding: 0.5rem;
496
+ flex-shrink: 0;
497
+ }
498
+ .registry-detail-navigation :global(.registry-detail-back) {
499
+ border-radius: 50%;
500
+ flex-shrink: 0;
501
+ }
502
+ .registry-detail-breadcrumbs {
503
+ display: none;
504
+ width: min(100%, 70rem);
505
+ margin-inline: auto;
506
+ padding: clamp(1rem, 4cqi, 3rem);
507
+ padding-block-end: 0;
508
+ }
509
+
510
+ :global(
511
+ [data-ui-component="workspace-sidebar"]
512
+ [data-ui-part="sidebar-body"][data-content-kind="tab"]
513
+ )
514
+ .registry-detail-breadcrumbs {
515
+ display: block;
516
+ }
517
+ </style>
@@ -0,0 +1,17 @@
1
+ import { type App } from "@lapis-notes/api";
2
+ import type { PluginBrowserRoute, PluginDirectoryState } from "./plugin-browser-model";
3
+ import "./community-presentation/community-typography.css";
4
+ export interface PluginBrowserNavigation {
5
+ route: PluginBrowserRoute;
6
+ directory: PluginDirectoryState;
7
+ returnPage: "landing" | "directory";
8
+ scrollTop: number;
9
+ }
10
+ type $$ComponentProps = {
11
+ app: App;
12
+ navigation: PluginBrowserNavigation;
13
+ onstatechange?: () => void;
14
+ };
15
+ declare const PluginBrowser: import("svelte").Component<$$ComponentProps, {}, "">;
16
+ type PluginBrowser = ReturnType<typeof PluginBrowser>;
17
+ export default PluginBrowser;