@jant/core 0.3.36 → 0.3.38
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/bin/commands/export.js +1 -1
- package/bin/commands/import-site.js +529 -0
- package/bin/commands/reset-password.js +3 -2
- package/dist/client/assets/heic-to-DIRPI3VF.js +1 -0
- package/dist/client/assets/url-FWFqPJPb.js +1 -0
- package/dist/client/client.css +1 -1
- package/dist/client/client.js +4012 -3276
- package/dist/index.js +10285 -5809
- package/package.json +11 -3
- package/src/__tests__/helpers/app.ts +9 -9
- package/src/__tests__/helpers/db.ts +91 -93
- package/src/app.tsx +157 -27
- package/src/auth.ts +20 -2
- package/src/client/archive-nav.js +187 -0
- package/src/client/audio-player.ts +478 -0
- package/src/client/audio-processor.ts +84 -0
- package/src/client/avatar-upload.ts +3 -2
- package/src/client/components/__tests__/jant-compose-dialog.test.ts +645 -49
- package/src/client/components/__tests__/jant-compose-editor.test.ts +208 -16
- package/src/client/components/__tests__/jant-post-form.test.ts +19 -9
- package/src/client/components/__tests__/jant-settings-avatar.test.ts +2 -2
- package/src/client/components/__tests__/jant-settings-general.test.ts +3 -3
- package/src/client/components/collection-sidebar-types.ts +7 -9
- package/src/client/components/compose-types.ts +101 -4
- package/src/client/components/jant-collection-form.ts +43 -7
- package/src/client/components/jant-collection-sidebar.ts +88 -84
- package/src/client/components/jant-compose-dialog.ts +1655 -219
- package/src/client/components/jant-compose-editor.ts +732 -168
- package/src/client/components/jant-compose-fullscreen.ts +23 -78
- package/src/client/components/jant-media-lightbox.ts +2 -0
- package/src/client/components/jant-nav-manager.ts +24 -284
- package/src/client/components/jant-post-form.ts +89 -9
- package/src/client/components/jant-post-menu.ts +1019 -0
- package/src/client/components/jant-settings-avatar.ts +3 -3
- package/src/client/components/jant-settings-general.ts +38 -4
- package/src/client/components/jant-text-preview.ts +232 -0
- package/src/client/components/nav-manager-types.ts +4 -19
- package/src/client/components/post-form-template.ts +107 -12
- package/src/client/components/post-form-types.ts +11 -4
- package/src/client/compose-bridge.ts +410 -109
- package/src/client/image-processor.ts +26 -8
- package/src/client/media-metadata.ts +247 -0
- package/src/client/multipart-upload.ts +160 -0
- package/src/client/post-form-bridge.ts +52 -1
- package/src/client/settings-bridge.ts +0 -12
- package/src/client/thread-context.ts +140 -0
- package/src/client/tiptap/create-editor.ts +46 -0
- package/src/client/tiptap/extensions.ts +5 -0
- package/src/client/tiptap/image-node.ts +2 -8
- package/src/client/tiptap/paste-image.ts +2 -13
- package/src/client/tiptap/slash-commands.ts +173 -63
- package/src/client/toast.ts +101 -3
- package/src/client/types/sortablejs.d.ts +15 -0
- package/src/client/upload-with-metadata.ts +54 -0
- package/src/client/video-processor.ts +207 -0
- package/src/client.ts +5 -2
- package/src/db/__tests__/migrations.test.ts +118 -0
- package/src/db/index.ts +52 -0
- package/src/db/migrations/0000_baseline.sql +269 -0
- package/src/db/migrations/0001_fts_setup.sql +31 -0
- package/src/db/migrations/meta/0000_snapshot.json +703 -119
- package/src/db/migrations/meta/0001_snapshot.json +1337 -0
- package/src/db/migrations/meta/_journal.json +4 -39
- package/src/db/schema.ts +409 -145
- package/src/i18n/__tests__/detect.test.ts +115 -0
- package/src/i18n/context.tsx +2 -2
- package/src/i18n/detect.ts +85 -1
- package/src/i18n/i18n.ts +1 -1
- package/src/i18n/index.ts +2 -1
- package/src/i18n/locales/en.po +487 -1217
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +613 -996
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +624 -1007
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/i18n/middleware.ts +6 -0
- package/src/index.ts +5 -7
- package/src/lib/__tests__/blurhash-placeholder.test.ts +75 -0
- package/src/lib/__tests__/constants.test.ts +0 -1
- package/src/lib/__tests__/markdown-to-tiptap.test.ts +358 -0
- package/src/lib/__tests__/nanoid.test.ts +26 -0
- package/src/lib/__tests__/schemas.test.ts +181 -63
- package/src/lib/__tests__/slug.test.ts +126 -0
- package/src/lib/__tests__/sse.test.ts +6 -6
- package/src/lib/__tests__/summary.test.ts +264 -0
- package/src/lib/__tests__/theme.test.ts +1 -1
- package/src/lib/__tests__/timeline.test.ts +33 -30
- package/src/lib/__tests__/tiptap-to-markdown.test.ts +346 -0
- package/src/lib/__tests__/view.test.ts +141 -66
- package/src/lib/blurhash-placeholder.ts +102 -0
- package/src/lib/constants.ts +3 -1
- package/src/lib/emoji-catalog.ts +885 -68
- package/src/lib/errors.ts +11 -8
- package/src/lib/feed.ts +78 -32
- package/src/lib/html.ts +2 -1
- package/src/lib/icon-catalog.ts +5033 -1
- package/src/lib/icons.ts +3 -2
- package/src/lib/index.ts +0 -1
- package/src/lib/markdown-to-tiptap.ts +286 -0
- package/src/lib/media-helpers.ts +12 -3
- package/src/lib/nanoid.ts +29 -0
- package/src/lib/navigation.ts +1 -1
- package/src/lib/render.tsx +20 -2
- package/src/lib/resolve-config.ts +6 -2
- package/src/lib/schemas.ts +224 -55
- package/src/lib/search-snippet.ts +34 -0
- package/src/lib/slug.ts +96 -0
- package/src/lib/sse.ts +6 -6
- package/src/lib/storage.ts +115 -7
- package/src/lib/summary.ts +66 -0
- package/src/lib/theme.ts +11 -8
- package/src/lib/timeline.ts +74 -34
- package/src/lib/tiptap-render.ts +5 -10
- package/src/lib/tiptap-to-markdown.ts +305 -0
- package/src/lib/upload.ts +190 -29
- package/src/lib/url.ts +31 -0
- package/src/lib/view.ts +204 -37
- package/src/middleware/__tests__/auth.test.ts +191 -11
- package/src/middleware/__tests__/onboarding.test.ts +12 -10
- package/src/middleware/auth.ts +63 -9
- package/src/middleware/onboarding.ts +1 -1
- package/src/middleware/secure-headers.ts +40 -0
- package/src/preset.css +45 -2
- package/src/routes/__tests__/compose.test.ts +17 -24
- package/src/routes/api/__tests__/collections.test.ts +109 -61
- package/src/routes/api/__tests__/nav-items.test.ts +46 -29
- package/src/routes/api/__tests__/posts.test.ts +132 -68
- package/src/routes/api/__tests__/search.test.ts +15 -2
- package/src/routes/api/__tests__/upload-multipart.test.ts +534 -0
- package/src/routes/api/collections.ts +51 -42
- package/src/routes/api/custom-urls.ts +80 -0
- package/src/routes/api/export.ts +31 -0
- package/src/routes/api/nav-items.ts +23 -19
- package/src/routes/api/posts.ts +43 -39
- package/src/routes/api/search.ts +3 -4
- package/src/routes/api/upload-multipart.ts +245 -0
- package/src/routes/api/upload.ts +85 -19
- package/src/routes/auth/__tests__/setup.test.ts +20 -60
- package/src/routes/auth/setup.tsx +26 -33
- package/src/routes/auth/signin.tsx +3 -7
- package/src/routes/compose.tsx +10 -55
- package/src/routes/dash/__tests__/settings-avatar.test.ts +1 -1
- package/src/routes/dash/custom-urls.tsx +414 -0
- package/src/routes/dash/settings.tsx +304 -232
- package/src/routes/feed/__tests__/rss.test.ts +27 -28
- package/src/routes/feed/rss.ts +6 -4
- package/src/routes/feed/sitemap.ts +2 -12
- package/src/routes/pages/__tests__/collections.test.ts +5 -6
- package/src/routes/pages/__tests__/featured.test.ts +41 -22
- package/src/routes/pages/archive.tsx +175 -39
- package/src/routes/pages/collection.tsx +22 -10
- package/src/routes/pages/collections.tsx +3 -3
- package/src/routes/pages/featured.tsx +28 -4
- package/src/routes/pages/home.tsx +16 -15
- package/src/routes/pages/latest.tsx +1 -11
- package/src/routes/pages/new.tsx +39 -0
- package/src/routes/pages/page.tsx +95 -49
- package/src/routes/pages/search.tsx +1 -1
- package/src/services/__tests__/api-token.test.ts +135 -0
- package/src/services/__tests__/collection.test.ts +275 -227
- package/src/services/__tests__/custom-url.test.ts +213 -0
- package/src/services/__tests__/media.test.ts +162 -22
- package/src/services/__tests__/navigation.test.ts +109 -68
- package/src/services/__tests__/post-timeline.test.ts +205 -32
- package/src/services/__tests__/post.test.ts +713 -234
- package/src/services/__tests__/search.test.ts +67 -10
- package/src/services/api-token.ts +166 -0
- package/src/services/auth.ts +17 -2
- package/src/services/collection.ts +397 -131
- package/src/services/custom-url.ts +188 -0
- package/src/services/export.ts +802 -0
- package/src/services/index.ts +26 -19
- package/src/services/media.ts +100 -22
- package/src/services/navigation.ts +158 -47
- package/src/services/path.ts +339 -0
- package/src/services/post.ts +687 -154
- package/src/services/search.ts +160 -75
- package/src/styles/components.css +58 -7
- package/src/styles/tokens.css +84 -6
- package/src/styles/ui.css +2964 -457
- package/src/types/bindings.ts +4 -1
- package/src/types/config.ts +12 -4
- package/src/types/constants.ts +15 -3
- package/src/types/entities.ts +74 -35
- package/src/types/operations.ts +11 -24
- package/src/types/props.ts +51 -16
- package/src/types/views.ts +45 -22
- package/src/ui/color-themes.ts +133 -23
- package/src/ui/compose/ComposeDialog.tsx +239 -17
- package/src/ui/compose/ComposePrompt.tsx +1 -1
- package/src/ui/dash/CrudPageHeader.tsx +1 -1
- package/src/ui/dash/ListItemRow.tsx +1 -1
- package/src/ui/dash/StatusBadge.tsx +3 -1
- package/src/ui/dash/appearance/AdvancedContent.tsx +22 -1
- package/src/ui/dash/appearance/ColorThemeContent.tsx +22 -2
- package/src/ui/dash/appearance/FontThemeContent.tsx +1 -1
- package/src/ui/dash/appearance/NavigationContent.tsx +5 -45
- package/src/ui/dash/index.ts +0 -3
- package/src/ui/dash/settings/AccountContent.tsx +3 -57
- package/src/ui/dash/settings/AccountMenuContent.tsx +147 -0
- package/src/ui/dash/settings/ApiTokensContent.tsx +232 -0
- package/src/ui/dash/settings/AvatarContent.tsx +8 -0
- package/src/ui/dash/settings/SessionsContent.tsx +159 -0
- package/src/ui/dash/settings/SettingsRootContent.tsx +45 -15
- package/src/ui/feed/LinkCard.tsx +89 -40
- package/src/ui/feed/NoteCard.tsx +39 -25
- package/src/ui/feed/PostStatusBadges.tsx +67 -0
- package/src/ui/feed/QuoteCard.tsx +38 -23
- package/src/ui/feed/ThreadPreview.tsx +90 -26
- package/src/ui/feed/TimelineFeed.tsx +3 -2
- package/src/ui/feed/TimelineItem.tsx +15 -6
- package/src/ui/feed/__tests__/thread-preview.test.ts +107 -0
- package/src/ui/feed/thread-preview-state.ts +61 -0
- package/src/ui/font-themes.ts +2 -2
- package/src/ui/layouts/BaseLayout.tsx +2 -2
- package/src/ui/layouts/SiteLayout.tsx +105 -92
- package/src/ui/pages/ArchivePage.tsx +923 -98
- package/src/ui/pages/ComposePage.tsx +54 -0
- package/src/ui/pages/PostPage.tsx +30 -45
- package/src/ui/pages/SearchPage.tsx +181 -37
- package/src/ui/shared/AdminBreadcrumb.tsx +29 -0
- package/src/ui/shared/CollectionsSidebar.tsx +47 -37
- package/src/ui/shared/MediaGallery.tsx +445 -149
- package/src/ui/shared/PostFooter.tsx +204 -0
- package/src/ui/shared/StarRating.tsx +27 -0
- package/src/ui/shared/__tests__/format-chars.test.ts +35 -0
- package/src/ui/shared/index.ts +0 -1
- package/dist/client/assets/url-8Dj-5CLW.js +0 -1
- package/src/client/media-upload.ts +0 -161
- package/src/client/page-slug-bridge.ts +0 -42
- package/src/db/migrations/0000_square_wallflower.sql +0 -118
- package/src/db/migrations/0001_add_search_fts.sql +0 -34
- package/src/db/migrations/0002_add_media_attachments.sql +0 -3
- package/src/db/migrations/0003_add_navigation_links.sql +0 -8
- package/src/db/migrations/0004_add_storage_provider.sql +0 -3
- package/src/db/migrations/0005_v2_schema_migration.sql +0 -268
- package/src/db/migrations/0006_rename_slug_to_path.sql +0 -5
- package/src/db/migrations/0007_post_collections_m2m.sql +0 -94
- package/src/db/migrations/0008_add_collection_dividers.sql +0 -8
- package/src/db/migrations/0009_drop_collection_show_divider.sql +0 -2
- package/src/db/migrations/0010_add_performance_indexes.sql +0 -16
- package/src/db/migrations/0011_add_path_registry.sql +0 -23
- package/src/db/migrations/0012_add_tiptap_columns.sql +0 -2
- package/src/db/migrations/0013_replace_featured_with_visibility.sql +0 -8
- package/src/db/migrations/meta/0003_snapshot.json +0 -821
- package/src/lib/__tests__/sqid.test.ts +0 -65
- package/src/lib/sqid.ts +0 -79
- package/src/routes/api/__tests__/pages.test.ts +0 -218
- package/src/routes/api/pages.ts +0 -73
- package/src/routes/dash/__tests__/pages.test.ts +0 -226
- package/src/routes/dash/index.tsx +0 -109
- package/src/routes/dash/media.tsx +0 -135
- package/src/routes/dash/pages.tsx +0 -245
- package/src/routes/dash/posts.tsx +0 -338
- package/src/routes/dash/redirects.tsx +0 -263
- package/src/routes/pages/post.tsx +0 -59
- package/src/services/__tests__/page.test.ts +0 -298
- package/src/services/__tests__/path-registry.test.ts +0 -165
- package/src/services/__tests__/redirect.test.ts +0 -159
- package/src/services/page.ts +0 -216
- package/src/services/path-registry.ts +0 -160
- package/src/services/redirect.ts +0 -97
- package/src/ui/dash/PageForm.tsx +0 -187
- package/src/ui/dash/PostList.tsx +0 -95
- package/src/ui/dash/media/MediaListContent.tsx +0 -206
- package/src/ui/dash/media/ViewMediaContent.tsx +0 -208
- package/src/ui/dash/pages/PagesContent.tsx +0 -75
- package/src/ui/dash/posts/PostForm.tsx +0 -260
- package/src/ui/layouts/DashLayout.tsx +0 -247
- package/src/ui/pages/SinglePage.tsx +0 -23
- package/src/ui/shared/ThreadView.tsx +0 -136
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
renderCollectionIcon,
|
|
24
24
|
getIconSvg,
|
|
25
25
|
} from "../../lib/icons.js";
|
|
26
|
-
import {
|
|
26
|
+
import { ALL_ICON_NAMES, ALL_ICON_CATEGORIES } from "../../lib/icon-catalog.js";
|
|
27
27
|
import { EMOJI_CATALOG } from "../../lib/emoji-catalog.js";
|
|
28
28
|
import { slugify } from "../lazy-slugify.js";
|
|
29
29
|
import type {
|
|
@@ -84,6 +84,16 @@ export class JantCollectionForm extends LitElement {
|
|
|
84
84
|
declare _loading: boolean;
|
|
85
85
|
|
|
86
86
|
#initialized = false;
|
|
87
|
+
#svgCache = new Map<string, string>();
|
|
88
|
+
|
|
89
|
+
#getCachedSvg(name: string): string | null {
|
|
90
|
+
const cached = this.#svgCache.get(name);
|
|
91
|
+
if (cached !== undefined) return cached;
|
|
92
|
+
const svg = getIconSvg(name);
|
|
93
|
+
if (svg) this.#svgCache.set(name, svg);
|
|
94
|
+
return svg;
|
|
95
|
+
}
|
|
96
|
+
|
|
87
97
|
#closePickerHandler = (e: Event) => {
|
|
88
98
|
const target = e.target as HTMLElement | null;
|
|
89
99
|
if (!target) return;
|
|
@@ -215,14 +225,15 @@ export class JantCollectionForm extends LitElement {
|
|
|
215
225
|
return "";
|
|
216
226
|
}
|
|
217
227
|
|
|
218
|
-
#
|
|
219
|
-
|
|
228
|
+
#allIconsByCategory: CatalogCategory[] | null = null;
|
|
229
|
+
|
|
230
|
+
#getAllIconsByCategory(): CatalogCategory[] {
|
|
231
|
+
if (this.#allIconsByCategory) return this.#allIconsByCategory;
|
|
220
232
|
const result: CatalogCategory[] = [];
|
|
221
|
-
for (const [category, names] of Object.entries(
|
|
233
|
+
for (const [category, names] of Object.entries(ALL_ICON_CATEGORIES)) {
|
|
222
234
|
const icons = names
|
|
223
|
-
.filter((name) => (q ? name.includes(q) : true))
|
|
224
235
|
.map((name) => {
|
|
225
|
-
const svg =
|
|
236
|
+
const svg = this.#getCachedSvg(name);
|
|
226
237
|
return svg ? { name, svg } : null;
|
|
227
238
|
})
|
|
228
239
|
.filter((icon): icon is { name: string; svg: string } => Boolean(icon));
|
|
@@ -230,9 +241,33 @@ export class JantCollectionForm extends LitElement {
|
|
|
230
241
|
result.push({ name: category, icons });
|
|
231
242
|
}
|
|
232
243
|
}
|
|
244
|
+
this.#allIconsByCategory = result;
|
|
233
245
|
return result;
|
|
234
246
|
}
|
|
235
247
|
|
|
248
|
+
#filteredCatalog(): CatalogCategory[] {
|
|
249
|
+
const q = this._iconSearch.trim().toLowerCase();
|
|
250
|
+
|
|
251
|
+
if (!q) {
|
|
252
|
+
// No search → show all icons grouped by official category
|
|
253
|
+
return this.#getAllIconsByCategory();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Search → filter ALL icon names + category names
|
|
257
|
+
const matching = ALL_ICON_NAMES.filter((name) => name.includes(q));
|
|
258
|
+
if (matching.length === 0) return [];
|
|
259
|
+
|
|
260
|
+
const icons = matching
|
|
261
|
+
.map((name) => {
|
|
262
|
+
const svg = this.#getCachedSvg(name);
|
|
263
|
+
return svg ? { name, svg } : null;
|
|
264
|
+
})
|
|
265
|
+
.filter((icon): icon is { name: string; svg: string } => Boolean(icon));
|
|
266
|
+
|
|
267
|
+
if (icons.length === 0) return [];
|
|
268
|
+
return [{ name: "results", icons }];
|
|
269
|
+
}
|
|
270
|
+
|
|
236
271
|
#filteredEmojiCatalog(): EmojiCategory[] {
|
|
237
272
|
const q = this._iconSearch.trim().toLowerCase();
|
|
238
273
|
const result: EmojiCategory[] = [];
|
|
@@ -379,6 +414,7 @@ export class JantCollectionForm extends LitElement {
|
|
|
379
414
|
type="button"
|
|
380
415
|
class=${`flex items-center justify-center w-8 h-8 rounded-md hover:bg-accent transition-colors${this._iconName === icon.name && this._iconSvg === icon.svg && !this._iconEmoji ? " ring-2 ring-primary" : ""}`}
|
|
381
416
|
data-icon-name=${icon.name}
|
|
417
|
+
title=${icon.name}
|
|
382
418
|
style=${`color:${this._iconColor}`}
|
|
383
419
|
@click=${() => this.#selectIcon(icon.name, icon.svg)}
|
|
384
420
|
>
|
|
@@ -489,7 +525,7 @@ export class JantCollectionForm extends LitElement {
|
|
|
489
525
|
</div>
|
|
490
526
|
|
|
491
527
|
<!-- Grid -->
|
|
492
|
-
<div class="overflow-y-auto max-h-
|
|
528
|
+
<div class="overflow-y-auto max-h-80">
|
|
493
529
|
${isIconsTab ? this.#renderIconsGrid() : this.#renderEmojisGrid()}
|
|
494
530
|
</div>
|
|
495
531
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Collection Sidebar Component
|
|
3
3
|
*
|
|
4
4
|
* Manages collections in the public /c page sidebar for authenticated users:
|
|
5
|
-
* - Renders collections + dividers
|
|
5
|
+
* - Renders sidebar items (collections + dividers) from a single pre-ordered list
|
|
6
6
|
* - Dropdown menus for "More" (reorder, add divider) and per-collection edit
|
|
7
7
|
* - SortableJS drag-and-drop reorder mode
|
|
8
8
|
* - Create/edit collection dialogs embedding <jant-collection-form>
|
|
@@ -25,26 +25,12 @@ import type { CollectionSubmitDetail } from "./collection-types.js";
|
|
|
25
25
|
import type {
|
|
26
26
|
CollectionSidebarLabels,
|
|
27
27
|
SidebarCollection,
|
|
28
|
-
|
|
29
|
-
SidebarItem,
|
|
28
|
+
ClientSidebarItem,
|
|
30
29
|
} from "./collection-sidebar-types.js";
|
|
31
30
|
|
|
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
31
|
export class JantCollectionSidebar extends LitElement {
|
|
45
32
|
static properties = {
|
|
46
|
-
|
|
47
|
-
dividers: { type: Array },
|
|
33
|
+
"sidebar-items": { type: Array },
|
|
48
34
|
labels: { type: Object },
|
|
49
35
|
activeSlug: { type: String, attribute: "active-slug" },
|
|
50
36
|
|
|
@@ -57,21 +43,21 @@ export class JantCollectionSidebar extends LitElement {
|
|
|
57
43
|
_showItemMenuId: { state: true },
|
|
58
44
|
};
|
|
59
45
|
|
|
60
|
-
declare
|
|
61
|
-
declare dividers: SidebarDivider[];
|
|
46
|
+
declare "sidebar-items": ClientSidebarItem[];
|
|
62
47
|
declare labels: CollectionSidebarLabels;
|
|
63
48
|
declare activeSlug: string;
|
|
64
49
|
|
|
65
|
-
declare _items:
|
|
50
|
+
declare _items: ClientSidebarItem[];
|
|
66
51
|
declare _reorderMode: boolean;
|
|
67
52
|
declare _dialogMode: "create" | "edit" | null;
|
|
68
53
|
declare _editingCollection: SidebarCollection | null;
|
|
69
54
|
declare _showMoreMenu: boolean;
|
|
70
|
-
declare _hoveringId:
|
|
71
|
-
declare _showItemMenuId:
|
|
55
|
+
declare _hoveringId: string | null;
|
|
56
|
+
declare _showItemMenuId: string | null;
|
|
72
57
|
|
|
73
58
|
#sortable: { destroy(): void } | null = null;
|
|
74
59
|
#initialized = false;
|
|
60
|
+
#revertNextSibling: Node | null = null;
|
|
75
61
|
|
|
76
62
|
#closeMoreMenu = () => {
|
|
77
63
|
this._showMoreMenu = false;
|
|
@@ -90,8 +76,7 @@ export class JantCollectionSidebar extends LitElement {
|
|
|
90
76
|
|
|
91
77
|
constructor() {
|
|
92
78
|
super();
|
|
93
|
-
this
|
|
94
|
-
this.dividers = [];
|
|
79
|
+
this["sidebar-items"] = [];
|
|
95
80
|
this.labels = {} as CollectionSidebarLabels;
|
|
96
81
|
this.activeSlug = "";
|
|
97
82
|
|
|
@@ -109,13 +94,9 @@ export class JantCollectionSidebar extends LitElement {
|
|
|
109
94
|
): void {
|
|
110
95
|
if (
|
|
111
96
|
!this.#initialized ||
|
|
112
|
-
changedProperties.has("
|
|
113
|
-
changedProperties.has("dividers")
|
|
97
|
+
changedProperties.has("sidebar-items" as keyof JantCollectionSidebar)
|
|
114
98
|
) {
|
|
115
|
-
this._items =
|
|
116
|
-
this.collections ?? [],
|
|
117
|
-
this.dividers ?? [],
|
|
118
|
-
);
|
|
99
|
+
this._items = [...(this["sidebar-items"] ?? [])];
|
|
119
100
|
this.#initialized = true;
|
|
120
101
|
}
|
|
121
102
|
super.update(changedProperties);
|
|
@@ -138,9 +119,20 @@ export class JantCollectionSidebar extends LitElement {
|
|
|
138
119
|
const res = await fetch("/api/collections");
|
|
139
120
|
if (!res.ok) return;
|
|
140
121
|
const json = await res.json();
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
122
|
+
|
|
123
|
+
// Build collection lookup from response
|
|
124
|
+
const collectionMap = new Map<string, SidebarCollection>();
|
|
125
|
+
for (const col of json.collections) {
|
|
126
|
+
collectionMap.set(col.id, col);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Map sidebar items, enriching with collection data
|
|
130
|
+
this._items = (json.sidebarItems as ClientSidebarItem[]).map((item) => ({
|
|
131
|
+
...item,
|
|
132
|
+
collection: item.collectionId
|
|
133
|
+
? collectionMap.get(item.collectionId)
|
|
134
|
+
: undefined,
|
|
135
|
+
}));
|
|
144
136
|
} catch {
|
|
145
137
|
// silent — stale list is acceptable
|
|
146
138
|
}
|
|
@@ -157,56 +149,64 @@ export class JantCollectionSidebar extends LitElement {
|
|
|
157
149
|
this.#sortable = Sortable.create(list, {
|
|
158
150
|
animation: 150,
|
|
159
151
|
handle: "[data-drag-handle]",
|
|
152
|
+
onStart: (evt) => {
|
|
153
|
+
// Capture the exact next sibling (including comment/marker nodes)
|
|
154
|
+
// so the DOM revert restores the element between the correct Lit markers.
|
|
155
|
+
this.#revertNextSibling = evt.item.nextSibling;
|
|
156
|
+
},
|
|
160
157
|
onEnd: (evt) => {
|
|
161
158
|
// Read new order from DOM BEFORE reverting
|
|
162
159
|
const els = [
|
|
163
160
|
...list.querySelectorAll<HTMLElement>("[data-sidebar-item]"),
|
|
164
161
|
];
|
|
165
|
-
const
|
|
162
|
+
const orderedIds = els
|
|
166
163
|
.map((el) => el.dataset.sidebarItem)
|
|
167
164
|
.filter((id): id is string => id !== undefined);
|
|
168
165
|
|
|
169
|
-
// Revert SortableJS DOM manipulation so Lit can re-render cleanly
|
|
166
|
+
// Revert SortableJS DOM manipulation so Lit can re-render cleanly.
|
|
167
|
+
// Use the captured nextSibling (which includes Lit's comment markers)
|
|
168
|
+
// to restore the element to its exact original position. Using
|
|
169
|
+
// list.children (element-only) would skip comment nodes and misalign
|
|
170
|
+
// Lit's internal template markers, leaving orphaned DOM nodes.
|
|
170
171
|
const { item, oldIndex, newIndex } = evt;
|
|
171
172
|
if (oldIndex != null && newIndex != null && oldIndex !== newIndex) {
|
|
172
173
|
item.parentNode?.removeChild(item);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
list.appendChild(item);
|
|
174
|
+
if (this.#revertNextSibling) {
|
|
175
|
+
list.insertBefore(item, this.#revertNextSibling);
|
|
176
176
|
} else {
|
|
177
|
-
list.
|
|
177
|
+
list.appendChild(item);
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
|
+
this.#revertNextSibling = null;
|
|
180
181
|
|
|
181
182
|
// Destroy sortable so it doesn't fight Lit's re-render
|
|
182
183
|
this.#sortable?.destroy();
|
|
183
184
|
this.#sortable = null;
|
|
184
185
|
|
|
186
|
+
// Find the moved item
|
|
187
|
+
const movedId = newIndex != null ? orderedIds[newIndex] : undefined;
|
|
188
|
+
if (!movedId) return;
|
|
189
|
+
|
|
190
|
+
// Compute after/before neighbors
|
|
191
|
+
const movedIdx = orderedIds.indexOf(movedId);
|
|
192
|
+
const afterId = movedIdx > 0 ? orderedIds[movedIdx - 1] : null;
|
|
193
|
+
const beforeId =
|
|
194
|
+
movedIdx < orderedIds.length - 1 ? orderedIds[movedIdx + 1] : null;
|
|
195
|
+
|
|
185
196
|
// Update internal state — rebuild items in new order
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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;
|
|
197
|
+
const itemMap = new Map(this._items.map((i) => [i.id, i]));
|
|
198
|
+
this._items = orderedIds
|
|
199
|
+
.map((id) => itemMap.get(id))
|
|
200
|
+
.filter((i): i is ClientSidebarItem => i !== undefined);
|
|
204
201
|
|
|
205
|
-
// Persist to server
|
|
206
|
-
fetch(
|
|
202
|
+
// Persist to server — single item move
|
|
203
|
+
fetch(`/api/collections/sidebar-items/${movedId}/move`, {
|
|
207
204
|
method: "PUT",
|
|
208
205
|
headers: { "Content-Type": "application/json" },
|
|
209
|
-
body: JSON.stringify({
|
|
206
|
+
body: JSON.stringify({
|
|
207
|
+
after: afterId ?? null,
|
|
208
|
+
before: beforeId ?? null,
|
|
209
|
+
}),
|
|
210
210
|
}).then((res) => {
|
|
211
211
|
if (res.ok) showToast(this.labels.orderSaved);
|
|
212
212
|
else showToast(this.labels.saveFailed, "error");
|
|
@@ -242,7 +242,7 @@ export class JantCollectionSidebar extends LitElement {
|
|
|
242
242
|
this._showMoreMenu = false;
|
|
243
243
|
document.removeEventListener("click", this.#closeMoreMenu);
|
|
244
244
|
try {
|
|
245
|
-
const res = await fetch("/api/collections/
|
|
245
|
+
const res = await fetch("/api/collections/sidebar-items", {
|
|
246
246
|
method: "POST",
|
|
247
247
|
headers: { "Content-Type": "application/json" },
|
|
248
248
|
});
|
|
@@ -253,16 +253,14 @@ export class JantCollectionSidebar extends LitElement {
|
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
255
|
|
|
256
|
-
async #deleteDivider(id:
|
|
256
|
+
async #deleteDivider(id: string) {
|
|
257
257
|
try {
|
|
258
|
-
const res = await fetch(`/api/collections/
|
|
258
|
+
const res = await fetch(`/api/collections/sidebar-items/${id}`, {
|
|
259
259
|
method: "DELETE",
|
|
260
260
|
});
|
|
261
261
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
262
262
|
// Remove locally for instant feedback
|
|
263
|
-
this._items = this._items.filter(
|
|
264
|
-
(item) => !(item.kind === "divider" && item.data.id === id),
|
|
265
|
-
);
|
|
263
|
+
this._items = this._items.filter((item) => item.id !== id);
|
|
266
264
|
} catch {
|
|
267
265
|
showToast(this.labels.saveFailed, "error");
|
|
268
266
|
}
|
|
@@ -510,13 +508,16 @@ export class JantCollectionSidebar extends LitElement {
|
|
|
510
508
|
`;
|
|
511
509
|
}
|
|
512
510
|
|
|
513
|
-
#renderCollectionItem(
|
|
511
|
+
#renderCollectionItem(item: ClientSidebarItem) {
|
|
512
|
+
const col = item.collection;
|
|
513
|
+
if (!col) return nothing;
|
|
514
|
+
|
|
514
515
|
const isActive = col.slug === this.activeSlug;
|
|
515
516
|
|
|
516
517
|
if (this._reorderMode) {
|
|
517
518
|
return html`
|
|
518
519
|
<div
|
|
519
|
-
data-sidebar-item="
|
|
520
|
+
data-sidebar-item="${item.id}"
|
|
520
521
|
class="flex items-center gap-2 px-3 py-2 text-sm rounded-md"
|
|
521
522
|
>
|
|
522
523
|
<div class="cursor-grab text-muted-foreground" data-drag-handle>
|
|
@@ -551,16 +552,16 @@ export class JantCollectionSidebar extends LitElement {
|
|
|
551
552
|
|
|
552
553
|
return html`
|
|
553
554
|
<div
|
|
554
|
-
data-sidebar-item="
|
|
555
|
+
data-sidebar-item="${item.id}"
|
|
555
556
|
class=${classMap({
|
|
556
557
|
"group relative": true,
|
|
557
|
-
"z-50": this._showItemMenuId ===
|
|
558
|
+
"z-50": this._showItemMenuId === item.id,
|
|
558
559
|
})}
|
|
559
560
|
@mouseenter=${() => {
|
|
560
|
-
this._hoveringId =
|
|
561
|
+
this._hoveringId = item.id;
|
|
561
562
|
}}
|
|
562
563
|
@mouseleave=${() => {
|
|
563
|
-
if (this._hoveringId ===
|
|
564
|
+
if (this._hoveringId === item.id) this._hoveringId = null;
|
|
564
565
|
}}
|
|
565
566
|
>
|
|
566
567
|
<a
|
|
@@ -579,15 +580,18 @@ export class JantCollectionSidebar extends LitElement {
|
|
|
579
580
|
</span>
|
|
580
581
|
<span class="truncate">${col.title}</span>
|
|
581
582
|
</a>
|
|
582
|
-
${this._hoveringId ===
|
|
583
|
-
? this.#renderItemMenu(
|
|
583
|
+
${this._hoveringId === item.id || this._showItemMenuId === item.id
|
|
584
|
+
? this.#renderItemMenu(item)
|
|
584
585
|
: nothing}
|
|
585
586
|
</div>
|
|
586
587
|
`;
|
|
587
588
|
}
|
|
588
589
|
|
|
589
|
-
#renderItemMenu(
|
|
590
|
-
const
|
|
590
|
+
#renderItemMenu(item: ClientSidebarItem) {
|
|
591
|
+
const col = item.collection;
|
|
592
|
+
if (!col) return nothing;
|
|
593
|
+
|
|
594
|
+
const isOpen = this._showItemMenuId === item.id;
|
|
591
595
|
|
|
592
596
|
return html`
|
|
593
597
|
<div class="absolute right-1 top-1/2 -translate-y-1/2">
|
|
@@ -601,7 +605,7 @@ export class JantCollectionSidebar extends LitElement {
|
|
|
601
605
|
this._showItemMenuId = null;
|
|
602
606
|
document.removeEventListener("click", this.#closeItemMenu);
|
|
603
607
|
} else {
|
|
604
|
-
this._showItemMenuId =
|
|
608
|
+
this._showItemMenuId = item.id;
|
|
605
609
|
setTimeout(() => {
|
|
606
610
|
document.addEventListener("click", this.#closeItemMenu);
|
|
607
611
|
});
|
|
@@ -651,11 +655,11 @@ export class JantCollectionSidebar extends LitElement {
|
|
|
651
655
|
`;
|
|
652
656
|
}
|
|
653
657
|
|
|
654
|
-
#renderDividerItem(
|
|
658
|
+
#renderDividerItem(item: ClientSidebarItem) {
|
|
655
659
|
if (this._reorderMode) {
|
|
656
660
|
return html`
|
|
657
661
|
<div
|
|
658
|
-
data-sidebar-item="
|
|
662
|
+
data-sidebar-item="${item.id}"
|
|
659
663
|
class="flex items-center gap-2 px-3 py-1"
|
|
660
664
|
>
|
|
661
665
|
<div class="cursor-grab text-muted-foreground" data-drag-handle>
|
|
@@ -683,7 +687,7 @@ export class JantCollectionSidebar extends LitElement {
|
|
|
683
687
|
type="button"
|
|
684
688
|
class="flex items-center justify-center w-5 h-5 rounded-md text-muted-foreground hover:text-destructive"
|
|
685
689
|
title=${this.labels.deleteDivider}
|
|
686
|
-
@click=${() => this.#deleteDivider(
|
|
690
|
+
@click=${() => this.#deleteDivider(item.id)}
|
|
687
691
|
>
|
|
688
692
|
<svg
|
|
689
693
|
xmlns="http://www.w3.org/2000/svg"
|
|
@@ -705,7 +709,7 @@ export class JantCollectionSidebar extends LitElement {
|
|
|
705
709
|
}
|
|
706
710
|
|
|
707
711
|
return html`
|
|
708
|
-
<div data-sidebar-item="
|
|
712
|
+
<div data-sidebar-item="${item.id}" class="px-3 py-1">
|
|
709
713
|
<hr class="border-border" />
|
|
710
714
|
</div>
|
|
711
715
|
`;
|
|
@@ -786,9 +790,9 @@ export class JantCollectionSidebar extends LitElement {
|
|
|
786
790
|
|
|
787
791
|
<div id="sidebar-collections-list" class="flex flex-col">
|
|
788
792
|
${this._items.map((item) =>
|
|
789
|
-
item.
|
|
790
|
-
? this.#renderCollectionItem(item
|
|
791
|
-
: this.#renderDividerItem(item
|
|
793
|
+
item.type === "collection"
|
|
794
|
+
? this.#renderCollectionItem(item)
|
|
795
|
+
: this.#renderDividerItem(item),
|
|
792
796
|
)}
|
|
793
797
|
</div>
|
|
794
798
|
|