@lapis-notes/bookmarks 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.
@@ -0,0 +1,654 @@
1
+ <script lang="ts">
2
+ import { Menu, type App } from "@lapis-notes/api";
3
+ import { SearchFilterBar } from "@lapismd/design-core/filter";
4
+ import { Button } from "@lapismd/design-core/shadcn/button";
5
+ import { Input } from "@lapismd/design-core/shadcn/input";
6
+ import { ScrollArea } from "@lapismd/design-core/shadcn/scroll-area";
7
+ import * as Tooltip from "@lapismd/design-core/shadcn/tooltip";
8
+ import { WorkspaceIcon } from "@lapismd/design-core/workspace/icon";
9
+ import BookmarkPlusIcon from "@lucide/svelte/icons/bookmark-plus";
10
+ import ChevronsDownUpIcon from "@lucide/svelte/icons/chevrons-down-up";
11
+ import ChevronsUpDownIcon from "@lucide/svelte/icons/chevrons-up-down";
12
+ import ChevronRightIcon from "@lucide/svelte/icons/chevron-right";
13
+ import FolderPlusIcon from "@lucide/svelte/icons/folder-plus";
14
+ import SearchIcon from "@lucide/svelte/icons/search";
15
+ import { createSubscriber, SvelteSet } from "svelte/reactivity";
16
+ import { activateBookmark } from "./activate-bookmark";
17
+ import { filterBookmarkItems } from "./bookmarks-filter";
18
+ import {
19
+ bookmarkIcon,
20
+ bookmarkLabel,
21
+ cloneBookmarkItems,
22
+ isGroupBookmark,
23
+ type BookmarkItem,
24
+ } from "./bookmarks-schema";
25
+ import type { BookmarksStore } from "./bookmarks-store";
26
+
27
+ let {
28
+ app,
29
+ store,
30
+ onBookmarkActive,
31
+ onNewGroup,
32
+ }: {
33
+ app: App;
34
+ store: BookmarksStore;
35
+ onBookmarkActive: () => void;
36
+ onNewGroup: (parentCtime: number | null) => Promise<{ ctime: number }>;
37
+ } = $props();
38
+
39
+ let query = $state("");
40
+ let showFilter = $state(false);
41
+ let selectedCtime = $state<number | null>(null);
42
+ let renamingCtime = $state<number | null>(null);
43
+ let renameValue = $state("");
44
+ let draggingCtime = $state<number | null>(null);
45
+ let dropTarget = $state<string | null>(null);
46
+ const expanded = new SvelteSet<number>();
47
+ const watchStore = createSubscriber((update) => store.subscribe(update));
48
+
49
+ const items = $derived.by(() => {
50
+ watchStore();
51
+ return cloneBookmarkItems(store.items);
52
+ });
53
+ const visibleItems = $derived(filterBookmarkItems(items, query));
54
+ const filtering = $derived(query.trim().length > 0);
55
+ const groupCtimes = $derived.by(() => collectGroupCtimes(visibleItems));
56
+ const allExpanded = $derived(
57
+ groupCtimes.length > 0 && groupCtimes.every((ctime) => expanded.has(ctime)),
58
+ );
59
+
60
+ function collectGroupCtimes(nodes: BookmarkItem[]): number[] {
61
+ const ctimes: number[] = [];
62
+ for (const node of nodes) {
63
+ if (!isGroupBookmark(node)) continue;
64
+ ctimes.push(node.ctime);
65
+ ctimes.push(...collectGroupCtimes(node.items));
66
+ }
67
+ return ctimes;
68
+ }
69
+
70
+ function isExpanded(ctime: number): boolean {
71
+ return filtering || expanded.has(ctime);
72
+ }
73
+
74
+ function toggleGroup(ctime: number): void {
75
+ if (expanded.has(ctime)) expanded.delete(ctime);
76
+ else expanded.add(ctime);
77
+ }
78
+
79
+ function toggleCollapseAll(): void {
80
+ if (allExpanded) expanded.clear();
81
+ else for (const ctime of groupCtimes) expanded.add(ctime);
82
+ }
83
+
84
+ function startRename(item: BookmarkItem): void {
85
+ renamingCtime = item.ctime;
86
+ renameValue = item.title ?? bookmarkLabel(item);
87
+ }
88
+
89
+ async function commitRename(): Promise<void> {
90
+ if (renamingCtime === null) return;
91
+ await store.renameItem(renamingCtime, renameValue);
92
+ renamingCtime = null;
93
+ }
94
+
95
+ async function createGroup(parentCtime: number | null): Promise<void> {
96
+ if (parentCtime !== null) expanded.add(parentCtime);
97
+ const created = await onNewGroup(parentCtime);
98
+ expanded.add(created.ctime);
99
+ startRename({
100
+ type: "group",
101
+ ctime: created.ctime,
102
+ title: "Untitled group",
103
+ items: [],
104
+ });
105
+ }
106
+
107
+ async function removeBookmark(ctime: number): Promise<void> {
108
+ await store.removeItem(ctime);
109
+ if (selectedCtime === ctime) selectedCtime = null;
110
+ if (renamingCtime === ctime) renamingCtime = null;
111
+ expanded.delete(ctime);
112
+ }
113
+
114
+ function stopToolbarClick(event: MouseEvent): void {
115
+ event.preventDefault();
116
+ event.stopPropagation();
117
+ }
118
+
119
+ async function activate(item: BookmarkItem): Promise<void> {
120
+ selectedCtime = item.ctime;
121
+ if (isGroupBookmark(item)) {
122
+ toggleGroup(item.ctime);
123
+ return;
124
+ }
125
+ await activateBookmark(app, item);
126
+ }
127
+
128
+ function showMenuAtPointer(menu: Menu, event: MouseEvent): void {
129
+ const doc = event.view?.document ?? app.workspace.getCommandHostDocument();
130
+ const anchor = doc.createElement("div");
131
+ anchor.setAttribute("data-bookmarks-menu-anchor", "");
132
+ Object.assign(anchor.style, {
133
+ position: "fixed",
134
+ left: `${event.clientX}px`,
135
+ top: `${event.clientY}px`,
136
+ width: "1px",
137
+ height: "1px",
138
+ pointerEvents: "none",
139
+ });
140
+ doc.body.appendChild(anchor);
141
+ menu.onHide(() => {
142
+ anchor.remove();
143
+ });
144
+ menu.showAtElement(anchor);
145
+ }
146
+
147
+ function showMenu(event: MouseEvent, item: BookmarkItem): void {
148
+ event.preventDefault();
149
+ selectedCtime = item.ctime;
150
+ const menu = new Menu();
151
+ if (!isGroupBookmark(item)) {
152
+ menu.addItem((entry) => {
153
+ entry.setTitle("Open").onClick(() => void activateBookmark(app, item));
154
+ });
155
+ }
156
+ menu.addItem((entry) => {
157
+ entry.setTitle("Rename").onClick(() => startRename(item));
158
+ });
159
+ if (isGroupBookmark(item)) {
160
+ menu.addItem((entry) => {
161
+ entry.setTitle("New group").onClick(() => void createGroup(item.ctime));
162
+ });
163
+ }
164
+ menu.addItem((entry) => {
165
+ entry.setTitle("Remove").onClick(() => void removeBookmark(item.ctime));
166
+ });
167
+ showMenuAtPointer(menu, event);
168
+ }
169
+
170
+ function onRowKeydown(event: KeyboardEvent, item: BookmarkItem): void {
171
+ if (event.key === "Enter") {
172
+ event.preventDefault();
173
+ void activate(item);
174
+ }
175
+ if (event.key === "Delete" || event.key === "Backspace") {
176
+ event.preventDefault();
177
+ void removeBookmark(item.ctime);
178
+ }
179
+ if (event.key === "F2") {
180
+ event.preventDefault();
181
+ startRename(item);
182
+ }
183
+ }
184
+
185
+ function onDragStart(event: DragEvent, item: BookmarkItem): void {
186
+ if (!event.dataTransfer) return;
187
+ event.dataTransfer.setData("text/plain", String(item.ctime));
188
+ event.dataTransfer.effectAllowed = "move";
189
+ draggingCtime = item.ctime;
190
+ }
191
+
192
+ function dropKey(parentCtime: number | null, index: number): string {
193
+ return `${parentCtime ?? "root"}:${index}`;
194
+ }
195
+
196
+ async function dropAt(
197
+ event: DragEvent,
198
+ parentCtime: number | null,
199
+ index: number,
200
+ ): Promise<void> {
201
+ event.preventDefault();
202
+ const ctime = Number(
203
+ event.dataTransfer?.getData("text/plain") || draggingCtime || "",
204
+ );
205
+ draggingCtime = null;
206
+ dropTarget = null;
207
+ if (!Number.isFinite(ctime)) return;
208
+ await store.moveItem(ctime, parentCtime, index);
209
+ }
210
+
211
+ function flattenItems(nodes: BookmarkItem[]): BookmarkItem[] {
212
+ return nodes.flatMap((node) =>
213
+ isGroupBookmark(node) ? [node, ...flattenItems(node.items)] : [node],
214
+ );
215
+ }
216
+
217
+ function toolbarActionClass(className: unknown): string {
218
+ return typeof className === "string" && className
219
+ ? `bookmarks-panel__toolbar-action ${className}`
220
+ : "bookmarks-panel__toolbar-action";
221
+ }
222
+ </script>
223
+
224
+ {#snippet Tree(nodes: BookmarkItem[], parentCtime: number | null)}
225
+ {#each nodes as item, index (item.ctime)}
226
+ <div
227
+ class="bookmarks-panel__item"
228
+ class:bookmarks-panel__item--drop={dropTarget ===
229
+ dropKey(parentCtime, index)}
230
+ data-bookmark-type={item.type}
231
+ data-path={String(item.ctime)}
232
+ data-bookmark-icon={bookmarkIcon(item) ?? "group"}
233
+ role="treeitem"
234
+ tabindex="0"
235
+ aria-label={bookmarkLabel(item)}
236
+ aria-expanded={isGroupBookmark(item)
237
+ ? isExpanded(item.ctime)
238
+ : undefined}
239
+ aria-selected={selectedCtime === item.ctime}
240
+ data-drop={dropTarget === dropKey(parentCtime, index)
241
+ ? "true"
242
+ : undefined}
243
+ draggable="true"
244
+ ondragstart={(event) => onDragStart(event, item)}
245
+ ondragend={() => {
246
+ draggingCtime = null;
247
+ dropTarget = null;
248
+ }}
249
+ onclick={() => void activate(item)}
250
+ ondblclick={() => startRename(item)}
251
+ onkeydown={(event) => onRowKeydown(event, item)}
252
+ oncontextmenu={(event) => showMenu(event, item)}
253
+ ondragenter={(event) => {
254
+ event.preventDefault();
255
+ dropTarget = dropKey(parentCtime, index);
256
+ }}
257
+ ondragover={(event) => {
258
+ event.preventDefault();
259
+ if (event.dataTransfer) event.dataTransfer.dropEffect = "move";
260
+ dropTarget = dropKey(parentCtime, index);
261
+ }}
262
+ ondrop={(event) =>
263
+ void dropAt(
264
+ event,
265
+ isGroupBookmark(item) ? item.ctime : parentCtime,
266
+ isGroupBookmark(item) ? item.items.length : index,
267
+ )}
268
+ >
269
+ <div
270
+ class="bookmarks-panel__row"
271
+ class:bookmarks-panel__row--selected={selectedCtime === item.ctime}
272
+ >
273
+ {#if isGroupBookmark(item)}
274
+ <button
275
+ class="bookmarks-panel__disclosure"
276
+ type="button"
277
+ aria-label={isExpanded(item.ctime) ? "Collapse" : "Expand"}
278
+ onclick={(event) => {
279
+ event.stopPropagation();
280
+ toggleGroup(item.ctime);
281
+ }}
282
+ >
283
+ <ChevronRightIcon
284
+ class="bookmarks-panel__chevron"
285
+ data-open={isExpanded(item.ctime) ? "true" : undefined}
286
+ />
287
+ </button>
288
+ {/if}
289
+ {#if bookmarkIcon(item)}
290
+ <WorkspaceIcon class="bookmarks-panel__icon" name={bookmarkIcon(item)!} />
291
+ {/if}
292
+ {#if renamingCtime === item.ctime}
293
+ <Input
294
+ class="bookmarks-panel__rename"
295
+ bind:value={renameValue}
296
+ onblur={() => void commitRename()}
297
+ onkeydown={(event) => {
298
+ if (event.key === "Enter") {
299
+ event.preventDefault();
300
+ void commitRename();
301
+ }
302
+ if (event.key === "Escape") {
303
+ event.preventDefault();
304
+ renamingCtime = null;
305
+ }
306
+ }}
307
+ />
308
+ {:else}
309
+ <span class="bookmarks-panel__label">{bookmarkLabel(item)}</span>
310
+ {/if}
311
+ </div>
312
+ {#if isGroupBookmark(item) && isExpanded(item.ctime)}
313
+ <div class="bookmarks-panel__list" role="group">
314
+ {@render Tree(item.items, item.ctime)}
315
+ </div>
316
+ {/if}
317
+ </div>
318
+ {/each}
319
+ {/snippet}
320
+
321
+ <div
322
+ class="bookmarks-panel"
323
+ data-testid="bookmarks-panel"
324
+ data-ui-component="bookmarks-panel"
325
+ >
326
+ <Tooltip.Provider delayDuration={0}>
327
+ <div class="bookmarks-panel__toolbar">
328
+ <Tooltip.Root>
329
+ <Tooltip.Trigger>
330
+ {#snippet child({ props })}
331
+ <Button
332
+ {...props}
333
+ class={toolbarActionClass(props.class)}
334
+ variant="ghost"
335
+ size="icon"
336
+ aria-label="Bookmark the active tab"
337
+ onclick={(event) => {
338
+ stopToolbarClick(event);
339
+ onBookmarkActive();
340
+ }}
341
+ >
342
+ <BookmarkPlusIcon />
343
+ </Button>
344
+ {/snippet}
345
+ </Tooltip.Trigger>
346
+ <Tooltip.Content side="bottom">Bookmark the active tab</Tooltip.Content>
347
+ </Tooltip.Root>
348
+ <Tooltip.Root>
349
+ <Tooltip.Trigger>
350
+ {#snippet child({ props })}
351
+ <Button
352
+ {...props}
353
+ class={toolbarActionClass(props.class)}
354
+ variant="ghost"
355
+ size="icon"
356
+ aria-label="New group"
357
+ onclick={(event) => {
358
+ stopToolbarClick(event);
359
+ const selected = flattenItems(items).find(
360
+ (item) => item.ctime === selectedCtime,
361
+ );
362
+ void createGroup(
363
+ selected && isGroupBookmark(selected) ? selected.ctime : null,
364
+ );
365
+ }}
366
+ >
367
+ <FolderPlusIcon />
368
+ </Button>
369
+ {/snippet}
370
+ </Tooltip.Trigger>
371
+ <Tooltip.Content side="bottom">New group</Tooltip.Content>
372
+ </Tooltip.Root>
373
+ <Tooltip.Root>
374
+ <Tooltip.Trigger>
375
+ {#snippet child({ props })}
376
+ <Button
377
+ {...props}
378
+ class={toolbarActionClass(props.class)}
379
+ variant="ghost"
380
+ size="icon"
381
+ aria-label="Collapse all"
382
+ aria-pressed={allExpanded}
383
+ onclick={(event) => {
384
+ stopToolbarClick(event);
385
+ toggleCollapseAll();
386
+ }}
387
+ >
388
+ {#if allExpanded}
389
+ <ChevronsDownUpIcon />
390
+ {:else}
391
+ <ChevronsUpDownIcon />
392
+ {/if}
393
+ </Button>
394
+ {/snippet}
395
+ </Tooltip.Trigger>
396
+ <Tooltip.Content side="bottom">Collapse all/Expand all</Tooltip.Content>
397
+ </Tooltip.Root>
398
+ <Tooltip.Root>
399
+ <Tooltip.Trigger>
400
+ {#snippet child({ props })}
401
+ <Button
402
+ {...props}
403
+ class={toolbarActionClass(props.class)}
404
+ variant="ghost"
405
+ size="icon"
406
+ aria-label="Show search filter"
407
+ aria-pressed={showFilter}
408
+ onclick={(event) => {
409
+ stopToolbarClick(event);
410
+ showFilter = !showFilter;
411
+ if (!showFilter) query = "";
412
+ }}
413
+ >
414
+ <SearchIcon />
415
+ </Button>
416
+ {/snippet}
417
+ </Tooltip.Trigger>
418
+ <Tooltip.Content side="bottom">Show search filter</Tooltip.Content>
419
+ </Tooltip.Root>
420
+ </div>
421
+ </Tooltip.Provider>
422
+ {#if showFilter}
423
+ <SearchFilterBar
424
+ inputMode="plain"
425
+ placeholder="Search..."
426
+ ariaLabel="Search bookmarks"
427
+ value={query}
428
+ onValueChange={(value) => {
429
+ query = value;
430
+ }}
431
+ />
432
+ {/if}
433
+ <ScrollArea class="bookmarks-panel__scroll">
434
+ <div
435
+ class="bookmarks-panel__tree"
436
+ role="tree"
437
+ tabindex="0"
438
+ aria-label="Bookmarks"
439
+ data-drop={dropTarget === dropKey(null, items.length) ? "true" : undefined}
440
+ ondragenter={(event) => {
441
+ event.preventDefault();
442
+ dropTarget = dropKey(null, items.length);
443
+ }}
444
+ ondragover={(event) => {
445
+ event.preventDefault();
446
+ dropTarget = dropKey(null, items.length);
447
+ }}
448
+ ondrop={(event) => void dropAt(event, null, store.items.length)}
449
+ >
450
+ {@render Tree(visibleItems, null)}
451
+ </div>
452
+ </ScrollArea>
453
+ </div>
454
+
455
+ <style>
456
+ :global {
457
+ [data-ui-component="bookmarks-panel"] .bookmarks-panel__toolbar {
458
+ display: flex;
459
+ align-items: center;
460
+ justify-content: center;
461
+ gap: 0.125rem;
462
+ padding: 0.25rem;
463
+ }
464
+
465
+ [data-ui-component="bookmarks-panel"]
466
+ .bookmarks-panel__toolbar
467
+ [data-ui-component="button"].bookmarks-panel__toolbar-action {
468
+ box-sizing: border-box;
469
+ color: var(--ui-workspace-view-foreground);
470
+ }
471
+
472
+ [data-ui-component="bookmarks-panel"]
473
+ .bookmarks-panel__toolbar
474
+ [data-ui-component="button"].bookmarks-panel__toolbar-action:hover,
475
+ [data-ui-component="bookmarks-panel"]
476
+ .bookmarks-panel__toolbar
477
+ [data-ui-component="button"].bookmarks-panel__toolbar-action:focus-visible,
478
+ [data-ui-component="bookmarks-panel"]
479
+ .bookmarks-panel__toolbar
480
+ [data-ui-component="button"].bookmarks-panel__toolbar-action[aria-pressed="true"],
481
+ [data-ui-component="bookmarks-panel"]
482
+ .bookmarks-panel__toolbar
483
+ [data-ui-component="button"].bookmarks-panel__toolbar-action[aria-pressed="true"]:hover,
484
+ [data-ui-component="bookmarks-panel"]
485
+ .bookmarks-panel__toolbar
486
+ [data-ui-component="button"].bookmarks-panel__toolbar-action[aria-pressed="true"]:focus-visible {
487
+ border-color: transparent;
488
+ background-color: color-mix(
489
+ in srgb,
490
+ var(--ui-workspace-foreground, currentColor) 14%,
491
+ var(--ui-workspace-panel, var(--ui-workspace-view-background, #fff))
492
+ );
493
+ color: var(
494
+ --ui-workspace-explorer-toolbar-action-hover-foreground,
495
+ var(--ui-workspace-panel-foreground, inherit)
496
+ );
497
+ box-shadow: none;
498
+ }
499
+
500
+ [data-ui-component="bookmarks-panel"]
501
+ .bookmarks-panel__toolbar
502
+ [data-ui-component="button"].bookmarks-panel__toolbar-action[aria-pressed="true"],
503
+ [data-ui-component="bookmarks-panel"]
504
+ .bookmarks-panel__toolbar
505
+ [data-ui-component="button"].bookmarks-panel__toolbar-action[aria-pressed="true"]:hover,
506
+ [data-ui-component="bookmarks-panel"]
507
+ .bookmarks-panel__toolbar
508
+ [data-ui-component="button"].bookmarks-panel__toolbar-action[aria-pressed="true"]:focus-visible {
509
+ color: var(--ui-workspace-accent, inherit);
510
+ }
511
+
512
+ [data-ui-component="bookmarks-panel"] .bookmarks-panel__tree {
513
+ display: flex;
514
+ box-sizing: border-box;
515
+ min-height: 100%;
516
+ flex-direction: column;
517
+ gap: var(--ui-workspace-explorer-row-gap, 0.125rem);
518
+ padding: var(--ui-workspace-explorer-content-padding, 0.5rem);
519
+ padding-block-end: 2.5rem;
520
+ }
521
+ }
522
+
523
+ .bookmarks-panel {
524
+ display: flex;
525
+ flex-direction: column;
526
+ width: 100%;
527
+ height: 100%;
528
+ min-width: 0;
529
+ min-height: 0;
530
+ background: transparent;
531
+ color: var(--ui-workspace-view-foreground);
532
+ font-size: 0.75rem;
533
+ }
534
+
535
+ .bookmarks-panel :global(.bookmarks-panel__scroll) {
536
+ flex: 1;
537
+ min-height: 0;
538
+ }
539
+
540
+ .bookmarks-panel__item {
541
+ width: 100%;
542
+ min-width: 0;
543
+ margin: 0;
544
+ padding: 0;
545
+ }
546
+
547
+ .bookmarks-panel__list {
548
+ display: flex;
549
+ min-width: 0;
550
+ margin: 0;
551
+ padding: 0;
552
+ list-style: none;
553
+ flex-direction: column;
554
+ gap: var(--ui-workspace-explorer-row-gap, 0.125rem);
555
+ }
556
+
557
+ .bookmarks-panel__item > .bookmarks-panel__list {
558
+ position: relative;
559
+ width: auto;
560
+ max-width: 100%;
561
+ margin-inline-start: var(
562
+ --ui-workspace-explorer-indent,
563
+ calc(
564
+ var(--ui-workspace-explorer-row-border-width, 2px) +
565
+ var(--ui-workspace-explorer-row-padding-inline, 0.375rem) +
566
+ (var(--ui-workspace-icon-size, 1rem) / 2) -
567
+ (var(--ui-workspace-explorer-guide-width, 1px) / 2)
568
+ )
569
+ );
570
+ margin-inline-end: var(
571
+ --ui-workspace-explorer-indent,
572
+ calc(
573
+ var(--ui-workspace-explorer-row-border-width, 2px) +
574
+ var(--ui-workspace-explorer-row-padding-inline, 0.375rem) +
575
+ (var(--ui-workspace-icon-size, 1rem) / 2) -
576
+ (var(--ui-workspace-explorer-guide-width, 1px) / 2)
577
+ )
578
+ );
579
+ padding-inline-start: var(--ui-workspace-explorer-guide-gap, 0.5rem);
580
+ padding-block-start: var(--ui-workspace-explorer-folder-gap, 0.25rem);
581
+ border-inline-start: var(--ui-workspace-explorer-guide-width, 1px) solid
582
+ var(
583
+ --ui-workspace-explorer-guide-color,
584
+ color-mix(in srgb, var(--ui-workspace-view-foreground) 22%, transparent)
585
+ );
586
+ }
587
+
588
+ .bookmarks-panel__item--drop > .bookmarks-panel__row {
589
+ box-shadow: inset 0 2px 0 var(--ui-workspace-view-foreground);
590
+ }
591
+
592
+ .bookmarks-panel__row {
593
+ display: flex;
594
+ box-sizing: border-box;
595
+ width: 100%;
596
+ min-width: 0;
597
+ max-width: 100%;
598
+ min-height: var(--ui-workspace-explorer-row-height, 1.75rem);
599
+ align-items: center;
600
+ gap: 0.375rem;
601
+ border: var(--ui-workspace-explorer-row-border-width, 2px) solid transparent;
602
+ border-radius: var(--ui-workspace-radius-small, 0.25rem);
603
+ padding-block: 0.125rem;
604
+ padding-inline: var(--ui-workspace-explorer-row-padding-inline, 0.375rem);
605
+ cursor: default;
606
+ }
607
+
608
+ .bookmarks-panel__row:hover,
609
+ .bookmarks-panel__row--selected {
610
+ background: var(
611
+ --ui-workspace-explorer-row-hover-background,
612
+ color-mix(in srgb, var(--ui-workspace-view-foreground) 8%, transparent)
613
+ );
614
+ }
615
+
616
+ .bookmarks-panel__disclosure {
617
+ display: inline-flex;
618
+ flex: 0 0 auto;
619
+ width: var(--ui-workspace-icon-size, 1rem);
620
+ height: var(--ui-workspace-icon-size, 1rem);
621
+ align-items: center;
622
+ justify-content: center;
623
+ padding: 0;
624
+ border: 0;
625
+ background: transparent;
626
+ color: inherit;
627
+ }
628
+
629
+ .bookmarks-panel :global(.bookmarks-panel__chevron),
630
+ .bookmarks-panel :global(.bookmarks-panel__icon) {
631
+ flex: 0 0 auto;
632
+ width: var(--ui-workspace-icon-size, 1rem);
633
+ height: var(--ui-workspace-icon-size, 1rem);
634
+ }
635
+
636
+ .bookmarks-panel :global(.bookmarks-panel__chevron) {
637
+ transition: transform 120ms ease;
638
+ }
639
+
640
+ .bookmarks-panel :global(.bookmarks-panel__chevron[data-open="true"]) {
641
+ transform: rotate(90deg);
642
+ }
643
+
644
+ .bookmarks-panel__label {
645
+ overflow: hidden;
646
+ text-overflow: ellipsis;
647
+ white-space: nowrap;
648
+ }
649
+
650
+ .bookmarks-panel :global(.bookmarks-panel__rename) {
651
+ flex: 1;
652
+ min-width: 0;
653
+ }
654
+ </style>
@@ -0,0 +1,13 @@
1
+ import { type App } from "@lapis-notes/api";
2
+ import type { BookmarksStore } from "./bookmarks-store";
3
+ type $$ComponentProps = {
4
+ app: App;
5
+ store: BookmarksStore;
6
+ onBookmarkActive: () => void;
7
+ onNewGroup: (parentCtime: number | null) => Promise<{
8
+ ctime: number;
9
+ }>;
10
+ };
11
+ declare const BookmarksPanel: import("svelte").Component<$$ComponentProps, {}, "">;
12
+ type BookmarksPanel = ReturnType<typeof BookmarksPanel>;
13
+ export default BookmarksPanel;
@@ -0,0 +1,15 @@
1
+ import { Plugin, type App, type PluginManifest } from "@lapis-notes/api";
2
+ import { BookmarksStore } from "./bookmarks-store";
3
+ export declare class BookmarksPlugin extends Plugin {
4
+ readonly store: BookmarksStore;
5
+ constructor(app: App, manifest?: PluginManifest);
6
+ onload(): Promise<void>;
7
+ openBookmarks(): Promise<void>;
8
+ createGroup(parentCtime?: number | null): Promise<{
9
+ ctime: number;
10
+ }>;
11
+ promptAddBookmark(): Promise<void>;
12
+ promptBookmarkUrl(): Promise<void>;
13
+ private promptAdd;
14
+ private pickGroup;
15
+ }