@jant/core 0.3.35 → 0.3.36
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/dist/client/assets/module-RjUF93sV.js +716 -0
- package/dist/client/assets/native-48B9X9Wg.js +1 -0
- package/dist/client/assets/url-8Dj-5CLW.js +1 -0
- package/dist/client/client.css +1 -1
- package/dist/client/client.js +3109 -2294
- package/dist/index.js +3026 -2778
- package/package.json +13 -4
- package/src/__tests__/helpers/app.ts +1 -1
- package/src/__tests__/helpers/db.ts +6 -0
- package/src/app.tsx +1 -5
- package/src/{lib → client}/avatar-upload.ts +1 -1
- package/src/{lib → client}/collection-form-bridge.ts +2 -2
- package/src/{ui → client}/components/__tests__/jant-collection-form.test.ts +26 -9
- package/src/{ui → client}/components/__tests__/jant-compose-dialog.test.ts +46 -14
- package/src/{ui → client}/components/__tests__/jant-compose-editor.test.ts +64 -24
- package/src/{ui → client}/components/__tests__/jant-post-form.test.ts +24 -14
- package/src/{ui → client}/components/__tests__/jant-settings-general.test.ts +3 -3
- package/src/client/components/collection-sidebar-types.ts +45 -0
- package/src/{ui → client}/components/collection-types.ts +3 -4
- package/src/{ui → client}/components/compose-types.ts +3 -1
- package/src/{ui → client}/components/jant-collection-form.ts +301 -182
- package/src/client/components/jant-collection-sidebar.ts +801 -0
- package/src/{ui → client}/components/jant-compose-dialog.ts +231 -1
- package/src/client/components/jant-compose-editor.ts +1249 -0
- package/src/client/components/jant-compose-fullscreen.ts +338 -0
- package/src/client/components/jant-media-lightbox.ts +257 -0
- package/src/{ui → client}/components/jant-nav-manager.ts +143 -84
- package/src/{ui → client}/components/jant-post-form.ts +57 -8
- package/src/{ui → client}/components/jant-settings-general.ts +2 -2
- package/src/{ui → client}/components/nav-manager-types.ts +3 -0
- package/src/{ui → client}/components/post-form-template.ts +35 -31
- package/src/{ui → client}/components/post-form-types.ts +7 -3
- package/src/{lib → client}/compose-bridge.ts +9 -7
- package/src/client/lazy-slugify.ts +51 -0
- package/src/{lib → client}/media-upload.ts +16 -3
- package/src/{lib → client}/nav-manager-bridge.ts +1 -1
- package/src/client/page-slug-bridge.ts +42 -0
- package/src/{lib → client}/post-form-bridge.ts +2 -2
- package/src/{lib → client}/settings-bridge.ts +3 -3
- package/src/client/tiptap/bubble-menu.ts +205 -0
- package/src/client/tiptap/create-editor.ts +40 -0
- package/src/client/tiptap/exitable-marks.ts +73 -0
- package/src/client/tiptap/extensions.ts +60 -0
- package/src/client/tiptap/image-node.ts +488 -0
- package/src/client/tiptap/link-toolbar.ts +371 -0
- package/src/client/tiptap/more-break.ts +50 -0
- package/src/client/tiptap/paste-image.ts +140 -0
- package/src/client/tiptap/slash-commands.ts +328 -0
- package/src/{types → client/types}/sortablejs.d.ts +1 -1
- package/src/client.ts +24 -17
- package/src/db/migrations/0012_add_tiptap_columns.sql +2 -0
- package/src/db/migrations/0013_replace_featured_with_visibility.sql +8 -0
- package/src/db/schema.ts +6 -1
- package/src/i18n/locales/en.po +641 -215
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +642 -204
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +642 -204
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/lib/__tests__/resolve-config.test.ts +2 -2
- package/src/lib/__tests__/schemas.test.ts +9 -6
- package/src/lib/__tests__/url.test.ts +2 -2
- package/src/lib/__tests__/view.test.ts +9 -9
- package/src/lib/emoji-catalog.ts +146 -0
- package/src/lib/feed.ts +1 -1
- package/src/lib/media-helpers.ts +10 -9
- package/src/lib/render.tsx +4 -3
- package/src/lib/resolve-config.ts +8 -1
- package/src/lib/schemas.ts +2 -3
- package/src/lib/summary.ts +92 -0
- package/src/lib/timeline.ts +2 -0
- package/src/lib/tiptap-render.ts +196 -0
- package/src/lib/upload.ts +97 -9
- package/src/lib/url.ts +7 -23
- package/src/lib/view.ts +33 -19
- package/src/middleware/error-handler.ts +3 -3
- package/src/preset.css +38 -0
- package/src/routes/api/collections.ts +20 -3
- package/src/routes/api/posts.ts +48 -33
- package/src/routes/api/upload.ts +7 -5
- package/src/routes/auth/reset.tsx +5 -4
- package/src/routes/auth/setup.tsx +26 -11
- package/src/routes/auth/signin.tsx +10 -7
- package/src/routes/compose.tsx +20 -11
- package/src/routes/dash/__tests__/settings-avatar.test.ts +43 -8
- package/src/routes/dash/index.tsx +7 -1
- package/src/routes/dash/media.tsx +3 -0
- package/src/routes/dash/pages.tsx +8 -2
- package/src/routes/dash/posts.tsx +6 -2
- package/src/routes/dash/redirects.tsx +15 -9
- package/src/routes/dash/settings.tsx +336 -32
- package/src/routes/feed/__tests__/rss.test.ts +7 -7
- package/src/routes/feed/rss.ts +8 -6
- package/src/routes/pages/__tests__/featured.test.ts +6 -7
- package/src/routes/pages/archive.tsx +11 -7
- package/src/routes/pages/collection.tsx +32 -15
- package/src/routes/pages/collections.tsx +11 -2
- package/src/routes/pages/featured.tsx +1 -1
- package/src/routes/pages/home.tsx +1 -1
- package/src/services/__tests__/post.test.ts +124 -33
- package/src/services/__tests__/settings.test.ts +3 -3
- package/src/services/page.ts +16 -3
- package/src/services/post.ts +96 -37
- package/src/services/search.ts +4 -2
- package/src/services/settings.ts +6 -2
- package/src/styles/components.css +240 -60
- package/src/styles/tokens.css +10 -0
- package/src/styles/ui.css +1157 -81
- package/src/types/bindings.ts +5 -0
- package/src/types/config.ts +23 -1
- package/src/types/constants.ts +3 -0
- package/src/types/entities.ts +9 -2
- package/src/types/operations.ts +9 -3
- package/src/types/props.ts +3 -3
- package/src/types/views.ts +3 -2
- package/src/ui/compose/ComposeDialog.tsx +24 -7
- package/src/ui/dash/PageForm.tsx +2 -0
- package/src/ui/dash/PostList.tsx +5 -5
- package/src/ui/dash/StatusBadge.tsx +13 -5
- package/src/ui/dash/appearance/AdvancedContent.tsx +52 -61
- package/src/ui/dash/appearance/ColorThemeContent.tsx +30 -35
- package/src/ui/dash/appearance/FontThemeContent.tsx +65 -73
- package/src/ui/dash/appearance/NavigationContent.tsx +107 -96
- package/src/ui/dash/media/MediaListContent.tsx +9 -4
- package/src/ui/dash/media/ViewMediaContent.tsx +2 -2
- package/src/ui/dash/pages/PagesContent.tsx +2 -1
- package/src/ui/dash/posts/PostForm.tsx +19 -7
- package/src/ui/dash/settings/AccountContent.tsx +133 -138
- package/src/ui/dash/settings/AvatarContent.tsx +70 -0
- package/src/ui/dash/settings/GeneralContent.tsx +3 -62
- package/src/ui/dash/settings/SettingsRootContent.tsx +236 -0
- package/src/ui/layouts/DashLayout.tsx +157 -75
- package/src/ui/layouts/SiteLayout.tsx +13 -13
- package/src/ui/pages/ArchivePage.tsx +10 -7
- package/src/ui/pages/CollectionPage.tsx +6 -35
- package/src/ui/pages/CollectionsPage.tsx +2 -1
- package/src/ui/pages/FeaturedPage.tsx +2 -1
- package/src/ui/pages/HomePage.tsx +1 -1
- package/src/ui/pages/SearchPage.tsx +1 -1
- package/src/ui/shared/CollectionsSidebar.tsx +228 -3
- package/src/ui/shared/MediaGallery.tsx +179 -41
- package/src/lib/collections-reorder.ts +0 -28
- package/src/routes/dash/appearance.tsx +0 -240
- package/src/routes/dash/collections.tsx +0 -211
- package/src/ui/components/jant-compose-editor.ts +0 -814
- package/src/ui/dash/appearance/AppearanceNav.tsx +0 -60
- package/src/ui/dash/collections/CollectionForm.tsx +0 -166
- package/src/ui/dash/collections/CollectionsListContent.tsx +0 -146
- package/src/ui/dash/collections/IconPickerGrid.tsx +0 -50
- package/src/ui/dash/collections/ViewCollectionContent.tsx +0 -103
- package/src/ui/dash/settings/SettingsNav.tsx +0 -52
- /package/src/{ui → client}/components/__tests__/jant-settings-avatar.test.ts +0 -0
- /package/src/{ui → client}/components/jant-settings-avatar.ts +0 -0
- /package/src/{ui → client}/components/settings-types.ts +0 -0
- /package/src/{lib → client}/image-processor.ts +0 -0
- /package/src/{lib → client}/toast.ts +0 -0
|
@@ -0,0 +1,801 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collection Sidebar Component
|
|
3
|
+
*
|
|
4
|
+
* Manages collections in the public /c page sidebar for authenticated users:
|
|
5
|
+
* - Renders collections + dividers as an interleaved sorted list
|
|
6
|
+
* - Dropdown menus for "More" (reorder, add divider) and per-collection edit
|
|
7
|
+
* - SortableJS drag-and-drop reorder mode
|
|
8
|
+
* - Create/edit collection dialogs embedding <jant-collection-form>
|
|
9
|
+
* - Divider CRUD
|
|
10
|
+
*
|
|
11
|
+
* Anonymous users see a static list rendered server-side; this component
|
|
12
|
+
* is only instantiated for authenticated users.
|
|
13
|
+
*
|
|
14
|
+
* Light DOM only — BaseCoat and Tailwind classes apply directly.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { LitElement, html, nothing } from "lit";
|
|
18
|
+
import type { PropertyValueMap } from "lit";
|
|
19
|
+
import { classMap } from "lit/directives/class-map.js";
|
|
20
|
+
import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
|
21
|
+
import Sortable from "sortablejs";
|
|
22
|
+
import { showToast } from "../toast.js";
|
|
23
|
+
import { renderCollectionIcon } from "../../lib/icons.js";
|
|
24
|
+
import type { CollectionSubmitDetail } from "./collection-types.js";
|
|
25
|
+
import type {
|
|
26
|
+
CollectionSidebarLabels,
|
|
27
|
+
SidebarCollection,
|
|
28
|
+
SidebarDivider,
|
|
29
|
+
SidebarItem,
|
|
30
|
+
} from "./collection-sidebar-types.js";
|
|
31
|
+
|
|
32
|
+
function interleaveItems(
|
|
33
|
+
collections: SidebarCollection[],
|
|
34
|
+
dividers: SidebarDivider[],
|
|
35
|
+
): SidebarItem[] {
|
|
36
|
+
const items: SidebarItem[] = [
|
|
37
|
+
...collections.map((c) => ({ kind: "collection", data: c }) as SidebarItem),
|
|
38
|
+
...dividers.map((d) => ({ kind: "divider", data: d }) as SidebarItem),
|
|
39
|
+
];
|
|
40
|
+
items.sort((a, b) => a.data.position - b.data.position);
|
|
41
|
+
return items;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export class JantCollectionSidebar extends LitElement {
|
|
45
|
+
static properties = {
|
|
46
|
+
collections: { type: Array },
|
|
47
|
+
dividers: { type: Array },
|
|
48
|
+
labels: { type: Object },
|
|
49
|
+
activeSlug: { type: String, attribute: "active-slug" },
|
|
50
|
+
|
|
51
|
+
_items: { state: true },
|
|
52
|
+
_reorderMode: { state: true },
|
|
53
|
+
_dialogMode: { state: true },
|
|
54
|
+
_editingCollection: { state: true },
|
|
55
|
+
_showMoreMenu: { state: true },
|
|
56
|
+
_hoveringId: { state: true },
|
|
57
|
+
_showItemMenuId: { state: true },
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
declare collections: SidebarCollection[];
|
|
61
|
+
declare dividers: SidebarDivider[];
|
|
62
|
+
declare labels: CollectionSidebarLabels;
|
|
63
|
+
declare activeSlug: string;
|
|
64
|
+
|
|
65
|
+
declare _items: SidebarItem[];
|
|
66
|
+
declare _reorderMode: boolean;
|
|
67
|
+
declare _dialogMode: "create" | "edit" | null;
|
|
68
|
+
declare _editingCollection: SidebarCollection | null;
|
|
69
|
+
declare _showMoreMenu: boolean;
|
|
70
|
+
declare _hoveringId: number | null;
|
|
71
|
+
declare _showItemMenuId: number | null;
|
|
72
|
+
|
|
73
|
+
#sortable: { destroy(): void } | null = null;
|
|
74
|
+
#initialized = false;
|
|
75
|
+
|
|
76
|
+
#closeMoreMenu = () => {
|
|
77
|
+
this._showMoreMenu = false;
|
|
78
|
+
document.removeEventListener("click", this.#closeMoreMenu);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
#closeItemMenu = () => {
|
|
82
|
+
this._showItemMenuId = null;
|
|
83
|
+
document.removeEventListener("click", this.#closeItemMenu);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
createRenderRoot() {
|
|
87
|
+
this.innerHTML = "";
|
|
88
|
+
return this;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
constructor() {
|
|
92
|
+
super();
|
|
93
|
+
this.collections = [];
|
|
94
|
+
this.dividers = [];
|
|
95
|
+
this.labels = {} as CollectionSidebarLabels;
|
|
96
|
+
this.activeSlug = "";
|
|
97
|
+
|
|
98
|
+
this._items = [];
|
|
99
|
+
this._reorderMode = false;
|
|
100
|
+
this._dialogMode = null;
|
|
101
|
+
this._editingCollection = null;
|
|
102
|
+
this._showMoreMenu = false;
|
|
103
|
+
this._hoveringId = null;
|
|
104
|
+
this._showItemMenuId = null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
protected update(
|
|
108
|
+
changedProperties: PropertyValueMap<JantCollectionSidebar>,
|
|
109
|
+
): void {
|
|
110
|
+
if (
|
|
111
|
+
!this.#initialized ||
|
|
112
|
+
changedProperties.has("collections") ||
|
|
113
|
+
changedProperties.has("dividers")
|
|
114
|
+
) {
|
|
115
|
+
this._items = interleaveItems(
|
|
116
|
+
this.collections ?? [],
|
|
117
|
+
this.dividers ?? [],
|
|
118
|
+
);
|
|
119
|
+
this.#initialized = true;
|
|
120
|
+
}
|
|
121
|
+
super.update(changedProperties);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
disconnectedCallback() {
|
|
125
|
+
super.disconnectedCallback();
|
|
126
|
+
this.#sortable?.destroy();
|
|
127
|
+
this.#sortable = null;
|
|
128
|
+
document.removeEventListener("click", this.#closeMoreMenu);
|
|
129
|
+
document.removeEventListener("click", this.#closeItemMenu);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ===========================================================================
|
|
133
|
+
// Data fetching
|
|
134
|
+
// ===========================================================================
|
|
135
|
+
|
|
136
|
+
async #refreshList() {
|
|
137
|
+
try {
|
|
138
|
+
const res = await fetch("/api/collections");
|
|
139
|
+
if (!res.ok) return;
|
|
140
|
+
const json = await res.json();
|
|
141
|
+
this.collections = json.collections;
|
|
142
|
+
this.dividers = json.dividers;
|
|
143
|
+
// update triggers via the `update` lifecycle
|
|
144
|
+
} catch {
|
|
145
|
+
// silent — stale list is acceptable
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ===========================================================================
|
|
150
|
+
// SortableJS
|
|
151
|
+
// ===========================================================================
|
|
152
|
+
|
|
153
|
+
#initSortable() {
|
|
154
|
+
const list = this.querySelector<HTMLElement>("#sidebar-collections-list");
|
|
155
|
+
if (!list || this.#sortable) return;
|
|
156
|
+
|
|
157
|
+
this.#sortable = Sortable.create(list, {
|
|
158
|
+
animation: 150,
|
|
159
|
+
handle: "[data-drag-handle]",
|
|
160
|
+
onEnd: (evt) => {
|
|
161
|
+
// Read new order from DOM BEFORE reverting
|
|
162
|
+
const els = [
|
|
163
|
+
...list.querySelectorAll<HTMLElement>("[data-sidebar-item]"),
|
|
164
|
+
];
|
|
165
|
+
const items = els
|
|
166
|
+
.map((el) => el.dataset.sidebarItem)
|
|
167
|
+
.filter((id): id is string => id !== undefined);
|
|
168
|
+
|
|
169
|
+
// Revert SortableJS DOM manipulation so Lit can re-render cleanly
|
|
170
|
+
const { item, oldIndex, newIndex } = evt;
|
|
171
|
+
if (oldIndex != null && newIndex != null && oldIndex !== newIndex) {
|
|
172
|
+
item.parentNode?.removeChild(item);
|
|
173
|
+
const children = list.children;
|
|
174
|
+
if (oldIndex >= children.length) {
|
|
175
|
+
list.appendChild(item);
|
|
176
|
+
} else {
|
|
177
|
+
list.insertBefore(item, children[oldIndex]);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Destroy sortable so it doesn't fight Lit's re-render
|
|
182
|
+
this.#sortable?.destroy();
|
|
183
|
+
this.#sortable = null;
|
|
184
|
+
|
|
185
|
+
// Update internal state — rebuild items in new order
|
|
186
|
+
const collectionMap = new Map(
|
|
187
|
+
(this.collections ?? []).map((c) => [`c-${c.id}`, c]),
|
|
188
|
+
);
|
|
189
|
+
const dividerMap = new Map(
|
|
190
|
+
(this.dividers ?? []).map((d) => [`d-${d.id}`, d]),
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
const newItems: SidebarItem[] = [];
|
|
194
|
+
for (const prefixed of items) {
|
|
195
|
+
if (prefixed.startsWith("c-")) {
|
|
196
|
+
const col = collectionMap.get(prefixed);
|
|
197
|
+
if (col) newItems.push({ kind: "collection", data: col });
|
|
198
|
+
} else if (prefixed.startsWith("d-")) {
|
|
199
|
+
const div = dividerMap.get(prefixed);
|
|
200
|
+
if (div) newItems.push({ kind: "divider", data: div });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
this._items = newItems;
|
|
204
|
+
|
|
205
|
+
// Persist to server
|
|
206
|
+
fetch("/api/collections/reorder", {
|
|
207
|
+
method: "PUT",
|
|
208
|
+
headers: { "Content-Type": "application/json" },
|
|
209
|
+
body: JSON.stringify({ items }),
|
|
210
|
+
}).then((res) => {
|
|
211
|
+
if (res.ok) showToast(this.labels.orderSaved);
|
|
212
|
+
else showToast(this.labels.saveFailed, "error");
|
|
213
|
+
});
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
#enterReorderMode() {
|
|
219
|
+
this._reorderMode = true;
|
|
220
|
+
this._showMoreMenu = false;
|
|
221
|
+
document.removeEventListener("click", this.#closeMoreMenu);
|
|
222
|
+
// SortableJS will be initialized after Lit re-renders (in updated())
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
#exitReorderMode() {
|
|
226
|
+
this._reorderMode = false;
|
|
227
|
+
this.#sortable?.destroy();
|
|
228
|
+
this.#sortable = null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
protected updated(): void {
|
|
232
|
+
if (this._reorderMode) {
|
|
233
|
+
this.#initSortable();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ===========================================================================
|
|
238
|
+
// Divider handlers
|
|
239
|
+
// ===========================================================================
|
|
240
|
+
|
|
241
|
+
async #addDivider() {
|
|
242
|
+
this._showMoreMenu = false;
|
|
243
|
+
document.removeEventListener("click", this.#closeMoreMenu);
|
|
244
|
+
try {
|
|
245
|
+
const res = await fetch("/api/collections/dividers", {
|
|
246
|
+
method: "POST",
|
|
247
|
+
headers: { "Content-Type": "application/json" },
|
|
248
|
+
});
|
|
249
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
250
|
+
await this.#refreshList();
|
|
251
|
+
} catch {
|
|
252
|
+
showToast(this.labels.saveFailed, "error");
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async #deleteDivider(id: number) {
|
|
257
|
+
try {
|
|
258
|
+
const res = await fetch(`/api/collections/dividers/${id}`, {
|
|
259
|
+
method: "DELETE",
|
|
260
|
+
});
|
|
261
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
262
|
+
// Remove locally for instant feedback
|
|
263
|
+
this._items = this._items.filter(
|
|
264
|
+
(item) => !(item.kind === "divider" && item.data.id === id),
|
|
265
|
+
);
|
|
266
|
+
} catch {
|
|
267
|
+
showToast(this.labels.saveFailed, "error");
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ===========================================================================
|
|
272
|
+
// Collection CRUD handlers
|
|
273
|
+
// ===========================================================================
|
|
274
|
+
|
|
275
|
+
#openCreateDialog() {
|
|
276
|
+
this._dialogMode = "create";
|
|
277
|
+
this._editingCollection = null;
|
|
278
|
+
this._showMoreMenu = false;
|
|
279
|
+
document.removeEventListener("click", this.#closeMoreMenu);
|
|
280
|
+
// Wait for render, then show the dialog
|
|
281
|
+
this.updateComplete.then(() => {
|
|
282
|
+
this.querySelector<HTMLDialogElement>(
|
|
283
|
+
"#sidebar-collection-dialog",
|
|
284
|
+
)?.showModal();
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
#openEditDialog(col: SidebarCollection) {
|
|
289
|
+
this._dialogMode = "edit";
|
|
290
|
+
this._editingCollection = col;
|
|
291
|
+
this._showItemMenuId = null;
|
|
292
|
+
document.removeEventListener("click", this.#closeItemMenu);
|
|
293
|
+
this.updateComplete.then(() => {
|
|
294
|
+
this.querySelector<HTMLDialogElement>(
|
|
295
|
+
"#sidebar-collection-dialog",
|
|
296
|
+
)?.showModal();
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
#closeDialog() {
|
|
301
|
+
this.querySelector<HTMLDialogElement>(
|
|
302
|
+
"#sidebar-collection-dialog",
|
|
303
|
+
)?.close();
|
|
304
|
+
this._dialogMode = null;
|
|
305
|
+
this._editingCollection = null;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async #handleCollectionSubmit(e: Event) {
|
|
309
|
+
const event = e as CustomEvent<CollectionSubmitDetail>;
|
|
310
|
+
event.stopPropagation(); // prevent global bridge from handling
|
|
311
|
+
|
|
312
|
+
const detail = event.detail;
|
|
313
|
+
if (!detail) return;
|
|
314
|
+
|
|
315
|
+
const formEl = this.querySelector("jant-collection-form") as
|
|
316
|
+
| (HTMLElement & {
|
|
317
|
+
loading: boolean;
|
|
318
|
+
})
|
|
319
|
+
| null;
|
|
320
|
+
if (formEl) formEl.loading = true;
|
|
321
|
+
|
|
322
|
+
try {
|
|
323
|
+
const isEdit = detail.isEdit;
|
|
324
|
+
const url = isEdit
|
|
325
|
+
? `/api/collections/${this._editingCollection?.id}`
|
|
326
|
+
: "/api/collections";
|
|
327
|
+
const method = isEdit ? "PUT" : "POST";
|
|
328
|
+
|
|
329
|
+
const res = await fetch(url, {
|
|
330
|
+
method,
|
|
331
|
+
headers: { "Content-Type": "application/json" },
|
|
332
|
+
body: JSON.stringify(detail.data),
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
336
|
+
|
|
337
|
+
showToast(this.labels.saved);
|
|
338
|
+
this.#closeDialog();
|
|
339
|
+
await this.#refreshList();
|
|
340
|
+
} catch {
|
|
341
|
+
showToast(this.labels.saveFailed, "error");
|
|
342
|
+
} finally {
|
|
343
|
+
if (formEl) formEl.loading = false;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
async #deleteCollection(col: SidebarCollection) {
|
|
348
|
+
if (!window.confirm(this.labels.confirmDelete)) return;
|
|
349
|
+
|
|
350
|
+
this._showItemMenuId = null;
|
|
351
|
+
document.removeEventListener("click", this.#closeItemMenu);
|
|
352
|
+
|
|
353
|
+
try {
|
|
354
|
+
const res = await fetch(`/api/collections/${col.id}`, {
|
|
355
|
+
method: "DELETE",
|
|
356
|
+
});
|
|
357
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
358
|
+
|
|
359
|
+
showToast(this.labels.deleted);
|
|
360
|
+
await this.#refreshList();
|
|
361
|
+
} catch {
|
|
362
|
+
showToast(this.labels.saveFailed, "error");
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// ===========================================================================
|
|
367
|
+
// Render
|
|
368
|
+
// ===========================================================================
|
|
369
|
+
|
|
370
|
+
#renderHeading() {
|
|
371
|
+
return html`
|
|
372
|
+
<div class="flex items-center justify-between px-3 pb-2">
|
|
373
|
+
<h2
|
|
374
|
+
class="text-xs font-semibold uppercase tracking-wider text-muted-foreground"
|
|
375
|
+
>
|
|
376
|
+
${this.labels.collections}
|
|
377
|
+
</h2>
|
|
378
|
+
<div class="flex items-center gap-1">
|
|
379
|
+
${this._reorderMode
|
|
380
|
+
? html`
|
|
381
|
+
<button
|
|
382
|
+
type="button"
|
|
383
|
+
class="text-xs font-medium text-primary hover:underline"
|
|
384
|
+
@click=${() => this.#exitReorderMode()}
|
|
385
|
+
>
|
|
386
|
+
${this.labels.done}
|
|
387
|
+
</button>
|
|
388
|
+
`
|
|
389
|
+
: html` ${this.#renderMoreButton()} ${this.#renderAddButton()} `}
|
|
390
|
+
</div>
|
|
391
|
+
</div>
|
|
392
|
+
`;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
#renderMoreButton() {
|
|
396
|
+
return html`
|
|
397
|
+
<div class="relative">
|
|
398
|
+
<button
|
|
399
|
+
type="button"
|
|
400
|
+
class="flex items-center justify-center w-6 h-6 rounded-md text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
401
|
+
aria-label=${this.labels.moreActions}
|
|
402
|
+
@click=${(e: Event) => {
|
|
403
|
+
e.stopPropagation();
|
|
404
|
+
this._showMoreMenu = !this._showMoreMenu;
|
|
405
|
+
if (this._showMoreMenu) {
|
|
406
|
+
setTimeout(() => {
|
|
407
|
+
document.addEventListener("click", this.#closeMoreMenu);
|
|
408
|
+
});
|
|
409
|
+
} else {
|
|
410
|
+
document.removeEventListener("click", this.#closeMoreMenu);
|
|
411
|
+
}
|
|
412
|
+
}}
|
|
413
|
+
>
|
|
414
|
+
<svg
|
|
415
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
416
|
+
width="14"
|
|
417
|
+
height="14"
|
|
418
|
+
viewBox="0 0 24 24"
|
|
419
|
+
fill="none"
|
|
420
|
+
stroke="currentColor"
|
|
421
|
+
stroke-width="2"
|
|
422
|
+
stroke-linecap="round"
|
|
423
|
+
stroke-linejoin="round"
|
|
424
|
+
>
|
|
425
|
+
<circle cx="12" cy="5" r="1" />
|
|
426
|
+
<circle cx="12" cy="12" r="1" />
|
|
427
|
+
<circle cx="12" cy="19" r="1" />
|
|
428
|
+
</svg>
|
|
429
|
+
</button>
|
|
430
|
+
${this._showMoreMenu
|
|
431
|
+
? html`
|
|
432
|
+
<div
|
|
433
|
+
class="absolute right-0 top-full mt-1 z-50 min-w-[160px] rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md"
|
|
434
|
+
@click=${(e: Event) => e.stopPropagation()}
|
|
435
|
+
>
|
|
436
|
+
<button
|
|
437
|
+
type="button"
|
|
438
|
+
class="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm hover:bg-accent hover:text-accent-foreground"
|
|
439
|
+
@click=${() => this.#enterReorderMode()}
|
|
440
|
+
>
|
|
441
|
+
<svg
|
|
442
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
443
|
+
width="14"
|
|
444
|
+
height="14"
|
|
445
|
+
viewBox="0 0 24 24"
|
|
446
|
+
fill="none"
|
|
447
|
+
stroke="currentColor"
|
|
448
|
+
stroke-width="2"
|
|
449
|
+
stroke-linecap="round"
|
|
450
|
+
stroke-linejoin="round"
|
|
451
|
+
>
|
|
452
|
+
<path d="m3 16 4 4 4-4" />
|
|
453
|
+
<path d="M7 20V4" />
|
|
454
|
+
<path d="m21 8-4-4-4 4" />
|
|
455
|
+
<path d="M17 4v16" />
|
|
456
|
+
</svg>
|
|
457
|
+
${this.labels.reorder}
|
|
458
|
+
</button>
|
|
459
|
+
<button
|
|
460
|
+
type="button"
|
|
461
|
+
class="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm hover:bg-accent hover:text-accent-foreground"
|
|
462
|
+
@click=${() => this.#addDivider()}
|
|
463
|
+
>
|
|
464
|
+
<svg
|
|
465
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
466
|
+
width="14"
|
|
467
|
+
height="14"
|
|
468
|
+
viewBox="0 0 24 24"
|
|
469
|
+
fill="none"
|
|
470
|
+
stroke="currentColor"
|
|
471
|
+
stroke-width="2"
|
|
472
|
+
stroke-linecap="round"
|
|
473
|
+
stroke-linejoin="round"
|
|
474
|
+
>
|
|
475
|
+
<path d="M3 12h18" />
|
|
476
|
+
</svg>
|
|
477
|
+
${this.labels.addDivider}
|
|
478
|
+
</button>
|
|
479
|
+
</div>
|
|
480
|
+
`
|
|
481
|
+
: nothing}
|
|
482
|
+
</div>
|
|
483
|
+
`;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
#renderAddButton() {
|
|
487
|
+
return html`
|
|
488
|
+
<button
|
|
489
|
+
type="button"
|
|
490
|
+
class="flex items-center justify-center w-6 h-6 rounded-md text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
491
|
+
title=${this.labels.newCollection}
|
|
492
|
+
aria-label=${this.labels.newCollection}
|
|
493
|
+
@click=${() => this.#openCreateDialog()}
|
|
494
|
+
>
|
|
495
|
+
<svg
|
|
496
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
497
|
+
width="14"
|
|
498
|
+
height="14"
|
|
499
|
+
viewBox="0 0 24 24"
|
|
500
|
+
fill="none"
|
|
501
|
+
stroke="currentColor"
|
|
502
|
+
stroke-width="2"
|
|
503
|
+
stroke-linecap="round"
|
|
504
|
+
stroke-linejoin="round"
|
|
505
|
+
>
|
|
506
|
+
<path d="M5 12h14" />
|
|
507
|
+
<path d="M12 5v14" />
|
|
508
|
+
</svg>
|
|
509
|
+
</button>
|
|
510
|
+
`;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
#renderCollectionItem(col: SidebarCollection) {
|
|
514
|
+
const isActive = col.slug === this.activeSlug;
|
|
515
|
+
|
|
516
|
+
if (this._reorderMode) {
|
|
517
|
+
return html`
|
|
518
|
+
<div
|
|
519
|
+
data-sidebar-item="c-${col.id}"
|
|
520
|
+
class="flex items-center gap-2 px-3 py-2 text-sm rounded-md"
|
|
521
|
+
>
|
|
522
|
+
<div class="cursor-grab text-muted-foreground" data-drag-handle>
|
|
523
|
+
<svg
|
|
524
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
525
|
+
width="14"
|
|
526
|
+
height="14"
|
|
527
|
+
viewBox="0 0 24 24"
|
|
528
|
+
fill="none"
|
|
529
|
+
stroke="currentColor"
|
|
530
|
+
stroke-width="2"
|
|
531
|
+
stroke-linecap="round"
|
|
532
|
+
stroke-linejoin="round"
|
|
533
|
+
>
|
|
534
|
+
<circle cx="9" cy="12" r="1" />
|
|
535
|
+
<circle cx="9" cy="5" r="1" />
|
|
536
|
+
<circle cx="9" cy="19" r="1" />
|
|
537
|
+
<circle cx="15" cy="12" r="1" />
|
|
538
|
+
<circle cx="15" cy="5" r="1" />
|
|
539
|
+
<circle cx="15" cy="19" r="1" />
|
|
540
|
+
</svg>
|
|
541
|
+
</div>
|
|
542
|
+
<span class="flex items-center justify-center w-4 h-4 shrink-0">
|
|
543
|
+
${unsafeHTML(
|
|
544
|
+
renderCollectionIcon(col.icon, { size: 16, fallback: true }),
|
|
545
|
+
)}
|
|
546
|
+
</span>
|
|
547
|
+
<span class="truncate">${col.title}</span>
|
|
548
|
+
</div>
|
|
549
|
+
`;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
return html`
|
|
553
|
+
<div
|
|
554
|
+
data-sidebar-item="c-${col.id}"
|
|
555
|
+
class=${classMap({
|
|
556
|
+
"group relative": true,
|
|
557
|
+
"z-50": this._showItemMenuId === col.id,
|
|
558
|
+
})}
|
|
559
|
+
@mouseenter=${() => {
|
|
560
|
+
this._hoveringId = col.id;
|
|
561
|
+
}}
|
|
562
|
+
@mouseleave=${() => {
|
|
563
|
+
if (this._hoveringId === col.id) this._hoveringId = null;
|
|
564
|
+
}}
|
|
565
|
+
>
|
|
566
|
+
<a
|
|
567
|
+
href=${`/c/${col.slug}`}
|
|
568
|
+
class=${classMap({
|
|
569
|
+
"flex items-center gap-2.5 px-3 py-2 text-sm rounded-md truncate": true,
|
|
570
|
+
"bg-accent text-accent-foreground font-medium": isActive,
|
|
571
|
+
"text-muted-foreground hover:bg-accent hover:text-accent-foreground":
|
|
572
|
+
!isActive,
|
|
573
|
+
})}
|
|
574
|
+
>
|
|
575
|
+
<span class="flex items-center justify-center w-4 h-4 shrink-0">
|
|
576
|
+
${unsafeHTML(
|
|
577
|
+
renderCollectionIcon(col.icon, { size: 16, fallback: true }),
|
|
578
|
+
)}
|
|
579
|
+
</span>
|
|
580
|
+
<span class="truncate">${col.title}</span>
|
|
581
|
+
</a>
|
|
582
|
+
${this._hoveringId === col.id || this._showItemMenuId === col.id
|
|
583
|
+
? this.#renderItemMenu(col)
|
|
584
|
+
: nothing}
|
|
585
|
+
</div>
|
|
586
|
+
`;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
#renderItemMenu(col: SidebarCollection) {
|
|
590
|
+
const isOpen = this._showItemMenuId === col.id;
|
|
591
|
+
|
|
592
|
+
return html`
|
|
593
|
+
<div class="absolute right-1 top-1/2 -translate-y-1/2">
|
|
594
|
+
<button
|
|
595
|
+
type="button"
|
|
596
|
+
class="flex items-center justify-center w-6 h-6 rounded-md text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
597
|
+
@click=${(e: Event) => {
|
|
598
|
+
e.preventDefault();
|
|
599
|
+
e.stopPropagation();
|
|
600
|
+
if (isOpen) {
|
|
601
|
+
this._showItemMenuId = null;
|
|
602
|
+
document.removeEventListener("click", this.#closeItemMenu);
|
|
603
|
+
} else {
|
|
604
|
+
this._showItemMenuId = col.id;
|
|
605
|
+
setTimeout(() => {
|
|
606
|
+
document.addEventListener("click", this.#closeItemMenu);
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
}}
|
|
610
|
+
>
|
|
611
|
+
<svg
|
|
612
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
613
|
+
width="14"
|
|
614
|
+
height="14"
|
|
615
|
+
viewBox="0 0 24 24"
|
|
616
|
+
fill="none"
|
|
617
|
+
stroke="currentColor"
|
|
618
|
+
stroke-width="2"
|
|
619
|
+
stroke-linecap="round"
|
|
620
|
+
stroke-linejoin="round"
|
|
621
|
+
>
|
|
622
|
+
<circle cx="12" cy="5" r="1" />
|
|
623
|
+
<circle cx="12" cy="12" r="1" />
|
|
624
|
+
<circle cx="12" cy="19" r="1" />
|
|
625
|
+
</svg>
|
|
626
|
+
</button>
|
|
627
|
+
${isOpen
|
|
628
|
+
? html`
|
|
629
|
+
<div
|
|
630
|
+
class="absolute right-0 top-full mt-1 z-50 min-w-[120px] rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md"
|
|
631
|
+
@click=${(e: Event) => e.stopPropagation()}
|
|
632
|
+
>
|
|
633
|
+
<button
|
|
634
|
+
type="button"
|
|
635
|
+
class="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm hover:bg-accent hover:text-accent-foreground"
|
|
636
|
+
@click=${() => this.#openEditDialog(col)}
|
|
637
|
+
>
|
|
638
|
+
${this.labels.edit}
|
|
639
|
+
</button>
|
|
640
|
+
<button
|
|
641
|
+
type="button"
|
|
642
|
+
class="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm text-destructive hover:bg-accent"
|
|
643
|
+
@click=${() => this.#deleteCollection(col)}
|
|
644
|
+
>
|
|
645
|
+
${this.labels.deleteCollection}
|
|
646
|
+
</button>
|
|
647
|
+
</div>
|
|
648
|
+
`
|
|
649
|
+
: nothing}
|
|
650
|
+
</div>
|
|
651
|
+
`;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
#renderDividerItem(div: SidebarDivider) {
|
|
655
|
+
if (this._reorderMode) {
|
|
656
|
+
return html`
|
|
657
|
+
<div
|
|
658
|
+
data-sidebar-item="d-${div.id}"
|
|
659
|
+
class="flex items-center gap-2 px-3 py-1"
|
|
660
|
+
>
|
|
661
|
+
<div class="cursor-grab text-muted-foreground" data-drag-handle>
|
|
662
|
+
<svg
|
|
663
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
664
|
+
width="14"
|
|
665
|
+
height="14"
|
|
666
|
+
viewBox="0 0 24 24"
|
|
667
|
+
fill="none"
|
|
668
|
+
stroke="currentColor"
|
|
669
|
+
stroke-width="2"
|
|
670
|
+
stroke-linecap="round"
|
|
671
|
+
stroke-linejoin="round"
|
|
672
|
+
>
|
|
673
|
+
<circle cx="9" cy="12" r="1" />
|
|
674
|
+
<circle cx="9" cy="5" r="1" />
|
|
675
|
+
<circle cx="9" cy="19" r="1" />
|
|
676
|
+
<circle cx="15" cy="12" r="1" />
|
|
677
|
+
<circle cx="15" cy="5" r="1" />
|
|
678
|
+
<circle cx="15" cy="19" r="1" />
|
|
679
|
+
</svg>
|
|
680
|
+
</div>
|
|
681
|
+
<hr class="flex-1 border-border" />
|
|
682
|
+
<button
|
|
683
|
+
type="button"
|
|
684
|
+
class="flex items-center justify-center w-5 h-5 rounded-md text-muted-foreground hover:text-destructive"
|
|
685
|
+
title=${this.labels.deleteDivider}
|
|
686
|
+
@click=${() => this.#deleteDivider(div.id)}
|
|
687
|
+
>
|
|
688
|
+
<svg
|
|
689
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
690
|
+
width="12"
|
|
691
|
+
height="12"
|
|
692
|
+
viewBox="0 0 24 24"
|
|
693
|
+
fill="none"
|
|
694
|
+
stroke="currentColor"
|
|
695
|
+
stroke-width="2"
|
|
696
|
+
stroke-linecap="round"
|
|
697
|
+
stroke-linejoin="round"
|
|
698
|
+
>
|
|
699
|
+
<path d="M18 6 6 18" />
|
|
700
|
+
<path d="m6 6 12 12" />
|
|
701
|
+
</svg>
|
|
702
|
+
</button>
|
|
703
|
+
</div>
|
|
704
|
+
`;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
return html`
|
|
708
|
+
<div data-sidebar-item="d-${div.id}" class="px-3 py-1">
|
|
709
|
+
<hr class="border-border" />
|
|
710
|
+
</div>
|
|
711
|
+
`;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
#renderDialog() {
|
|
715
|
+
if (!this._dialogMode) return nothing;
|
|
716
|
+
|
|
717
|
+
const isEdit = this._dialogMode === "edit";
|
|
718
|
+
const col = this._editingCollection;
|
|
719
|
+
|
|
720
|
+
const formLabels = this.labels.formLabels;
|
|
721
|
+
const initial =
|
|
722
|
+
isEdit && col
|
|
723
|
+
? {
|
|
724
|
+
title: col.title,
|
|
725
|
+
slug: col.slug,
|
|
726
|
+
description: col.description ?? "",
|
|
727
|
+
sortOrder: col.sortOrder ?? "newest",
|
|
728
|
+
icon: col.icon ?? "",
|
|
729
|
+
}
|
|
730
|
+
: {
|
|
731
|
+
title: "",
|
|
732
|
+
slug: "",
|
|
733
|
+
description: "",
|
|
734
|
+
sortOrder: "newest",
|
|
735
|
+
icon: "",
|
|
736
|
+
};
|
|
737
|
+
|
|
738
|
+
const dialogLabels = {
|
|
739
|
+
...formLabels,
|
|
740
|
+
submitLabel: isEdit ? formLabels.submitLabel : formLabels.submitLabel,
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
return html`
|
|
744
|
+
<dialog
|
|
745
|
+
id="sidebar-collection-dialog"
|
|
746
|
+
class="m-auto rounded-lg border border-border bg-background text-foreground p-6 w-full max-w-md shadow-lg backdrop:bg-black/50"
|
|
747
|
+
@cancel=${() => this.#closeDialog()}
|
|
748
|
+
@close=${() => {
|
|
749
|
+
this._dialogMode = null;
|
|
750
|
+
this._editingCollection = null;
|
|
751
|
+
}}
|
|
752
|
+
@click=${(e: Event) => {
|
|
753
|
+
// Backdrop click — target is the <dialog> itself when clicking outside the box
|
|
754
|
+
if (e.target === e.currentTarget) {
|
|
755
|
+
this.#closeDialog();
|
|
756
|
+
}
|
|
757
|
+
}}
|
|
758
|
+
>
|
|
759
|
+
<jant-collection-form
|
|
760
|
+
.labels=${dialogLabels}
|
|
761
|
+
.initial=${initial}
|
|
762
|
+
action=${isEdit && col
|
|
763
|
+
? `/api/collections/${col.id}`
|
|
764
|
+
: "/api/collections"}
|
|
765
|
+
cancel-href="javascript:void(0)"
|
|
766
|
+
?is-edit=${isEdit}
|
|
767
|
+
@jant:collection-submit=${(e: Event) =>
|
|
768
|
+
this.#handleCollectionSubmit(e)}
|
|
769
|
+
@click=${(e: Event) => {
|
|
770
|
+
// Intercept cancel link click
|
|
771
|
+
const target = (e.target as HTMLElement).closest?.("a.btn-outline");
|
|
772
|
+
if (target) {
|
|
773
|
+
e.preventDefault();
|
|
774
|
+
this.#closeDialog();
|
|
775
|
+
}
|
|
776
|
+
}}
|
|
777
|
+
></jant-collection-form>
|
|
778
|
+
</dialog>
|
|
779
|
+
`;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
render() {
|
|
783
|
+
return html`
|
|
784
|
+
<nav class="flex flex-col gap-1 pt-6">
|
|
785
|
+
${this.#renderHeading()}
|
|
786
|
+
|
|
787
|
+
<div id="sidebar-collections-list" class="flex flex-col">
|
|
788
|
+
${this._items.map((item) =>
|
|
789
|
+
item.kind === "collection"
|
|
790
|
+
? this.#renderCollectionItem(item.data as SidebarCollection)
|
|
791
|
+
: this.#renderDividerItem(item.data as SidebarDivider),
|
|
792
|
+
)}
|
|
793
|
+
</div>
|
|
794
|
+
|
|
795
|
+
${this.#renderDialog()}
|
|
796
|
+
</nav>
|
|
797
|
+
`;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
customElements.define("jant-collection-sidebar", JantCollectionSidebar);
|