@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
|
@@ -12,7 +12,7 @@ import { LitElement, html, nothing } from "lit";
|
|
|
12
12
|
import type { Editor, JSONContent } from "@tiptap/core";
|
|
13
13
|
import type { ComposeLabels } from "./compose-types.js";
|
|
14
14
|
import { createTiptapEditor } from "../tiptap/create-editor.js";
|
|
15
|
-
import {
|
|
15
|
+
import { uploadWithMetadata } from "../upload-with-metadata.js";
|
|
16
16
|
|
|
17
17
|
export class JantComposeFullscreen extends LitElement {
|
|
18
18
|
static properties = {
|
|
@@ -20,14 +20,12 @@ export class JantComposeFullscreen extends LitElement {
|
|
|
20
20
|
_open: { state: true },
|
|
21
21
|
_title: { state: true },
|
|
22
22
|
_showTitle: { state: true },
|
|
23
|
-
_actionsOpen: { state: true },
|
|
24
23
|
};
|
|
25
24
|
|
|
26
25
|
declare labels: ComposeLabels;
|
|
27
26
|
declare _open: boolean;
|
|
28
27
|
declare _title: string;
|
|
29
28
|
declare _showTitle: boolean;
|
|
30
|
-
declare _actionsOpen: boolean;
|
|
31
29
|
|
|
32
30
|
private _editor: Editor | null = null;
|
|
33
31
|
private _content: JSONContent | null = null;
|
|
@@ -43,7 +41,6 @@ export class JantComposeFullscreen extends LitElement {
|
|
|
43
41
|
this._open = false;
|
|
44
42
|
this._title = "";
|
|
45
43
|
this._showTitle = false;
|
|
46
|
-
this._actionsOpen = false;
|
|
47
44
|
}
|
|
48
45
|
|
|
49
46
|
connectedCallback() {
|
|
@@ -96,14 +93,7 @@ export class JantComposeFullscreen extends LitElement {
|
|
|
96
93
|
this._editor.chain().focus().setImage({ src: placeholderUrl }).run();
|
|
97
94
|
|
|
98
95
|
try {
|
|
99
|
-
const
|
|
100
|
-
formData.append("file", file);
|
|
101
|
-
const response = await fetch("/api/upload", {
|
|
102
|
-
method: "POST",
|
|
103
|
-
body: formData,
|
|
104
|
-
});
|
|
105
|
-
if (!response.ok) throw new Error(`Upload failed: ${response.status}`);
|
|
106
|
-
const data = (await response.json()) as { url: string };
|
|
96
|
+
const data = await uploadWithMetadata(file);
|
|
107
97
|
|
|
108
98
|
// Replace placeholder with real URL
|
|
109
99
|
const { doc } = this._editor.state;
|
|
@@ -161,7 +151,6 @@ export class JantComposeFullscreen extends LitElement {
|
|
|
161
151
|
// Always show title in fullscreen — it's the primary editing surface
|
|
162
152
|
this._showTitle = true;
|
|
163
153
|
this._open = true;
|
|
164
|
-
this._actionsOpen = false;
|
|
165
154
|
// Show as modal (top layer) and init editor after render
|
|
166
155
|
this.updateComplete.then(() => {
|
|
167
156
|
const dialog = this.querySelector<HTMLDialogElement>(
|
|
@@ -221,51 +210,10 @@ export class JantComposeFullscreen extends LitElement {
|
|
|
221
210
|
);
|
|
222
211
|
}
|
|
223
212
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
private _executeCommand(index: number) {
|
|
229
|
-
const commands = getSlashCommands();
|
|
230
|
-
const item = commands[index];
|
|
231
|
-
if (!item || !this._editor) {
|
|
232
|
-
this._actionsOpen = false;
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Image command: trigger file picker directly
|
|
237
|
-
if (item.label === "Image") {
|
|
238
|
-
this._actionsOpen = false;
|
|
239
|
-
this._triggerImagePicker();
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
const { from, to } = this._editor.state.selection;
|
|
244
|
-
item.command(this._editor, { from, to });
|
|
245
|
-
this._actionsOpen = false;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
private _renderActionsMenu() {
|
|
249
|
-
if (!this._actionsOpen) return nothing;
|
|
250
|
-
const commands = getSlashCommands();
|
|
251
|
-
return html`
|
|
252
|
-
<div class="tiptap-slash-menu compose-fullscreen-plus-dropdown">
|
|
253
|
-
${commands.map(
|
|
254
|
-
(item, i) => html`
|
|
255
|
-
<div
|
|
256
|
-
class="tiptap-slash-item"
|
|
257
|
-
@mousedown=${(e: Event) => {
|
|
258
|
-
e.preventDefault();
|
|
259
|
-
this._executeCommand(i);
|
|
260
|
-
}}
|
|
261
|
-
>
|
|
262
|
-
<span class="tiptap-slash-item-icon">${item.icon}</span>
|
|
263
|
-
<span class="tiptap-slash-item-label">${item.label}</span>
|
|
264
|
-
</div>
|
|
265
|
-
`,
|
|
266
|
-
)}
|
|
267
|
-
</div>
|
|
268
|
-
`;
|
|
213
|
+
/** Insert "/" at cursor to trigger the slash command popup */
|
|
214
|
+
private _insertSlash() {
|
|
215
|
+
if (!this._editor) return;
|
|
216
|
+
this._editor.chain().focus().insertContent("/").run();
|
|
269
217
|
}
|
|
270
218
|
|
|
271
219
|
render() {
|
|
@@ -275,27 +223,24 @@ export class JantComposeFullscreen extends LitElement {
|
|
|
275
223
|
<dialog class="compose-fullscreen-dialog" @cancel=${this._onDialogCancel}>
|
|
276
224
|
<div class="compose-fullscreen">
|
|
277
225
|
<div class="compose-fullscreen-toolbar">
|
|
278
|
-
<
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
226
|
+
<button
|
|
227
|
+
type="button"
|
|
228
|
+
class="compose-tool-btn"
|
|
229
|
+
@click=${() => this._insertSlash()}
|
|
230
|
+
>
|
|
231
|
+
<svg
|
|
232
|
+
width="18"
|
|
233
|
+
height="18"
|
|
234
|
+
viewBox="0 0 18 18"
|
|
235
|
+
fill="none"
|
|
236
|
+
stroke="currentColor"
|
|
237
|
+
stroke-width="2"
|
|
238
|
+
stroke-linecap="round"
|
|
283
239
|
>
|
|
284
|
-
<
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
fill="none"
|
|
289
|
-
stroke="currentColor"
|
|
290
|
-
stroke-width="2"
|
|
291
|
-
stroke-linecap="round"
|
|
292
|
-
>
|
|
293
|
-
<line x1="9" y1="3" x2="9" y2="15" />
|
|
294
|
-
<line x1="3" y1="9" x2="15" y2="9" />
|
|
295
|
-
</svg>
|
|
296
|
-
</button>
|
|
297
|
-
${this._renderActionsMenu()}
|
|
298
|
-
</div>
|
|
240
|
+
<line x1="9" y1="3" x2="9" y2="15" />
|
|
241
|
+
<line x1="3" y1="9" x2="15" y2="9" />
|
|
242
|
+
</svg>
|
|
243
|
+
</button>
|
|
299
244
|
<div class="flex-1"></div>
|
|
300
245
|
<button
|
|
301
246
|
type="button"
|
|
@@ -17,6 +17,7 @@ interface LightboxImage {
|
|
|
17
17
|
width?: number;
|
|
18
18
|
height?: number;
|
|
19
19
|
mimeType?: string;
|
|
20
|
+
posterUrl?: string;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export class JantMediaLightbox extends LitElement {
|
|
@@ -197,6 +198,7 @@ export class JantMediaLightbox extends LitElement {
|
|
|
197
198
|
? html`<video
|
|
198
199
|
class="media-lightbox-video"
|
|
199
200
|
src=${img.url}
|
|
201
|
+
poster=${img.posterUrl ?? ""}
|
|
200
202
|
controls
|
|
201
203
|
autoplay
|
|
202
204
|
playsinline
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* - Renders a preview bar that reflects current item order
|
|
6
6
|
* - Sortable list with inline edit/delete panels
|
|
7
7
|
* - SortableJS drag-and-drop reorder with immediate preview update
|
|
8
|
-
* - Add
|
|
8
|
+
* - Add link forms
|
|
9
9
|
* - System nav item toggles with immediate list/preview update
|
|
10
10
|
* - Dispatches events for update/delete (handled by bridge)
|
|
11
11
|
*
|
|
@@ -17,7 +17,6 @@ import type { PropertyValueMap } from "lit";
|
|
|
17
17
|
import Sortable from "sortablejs";
|
|
18
18
|
import { showToast } from "../toast.js";
|
|
19
19
|
import type {
|
|
20
|
-
AvailablePage,
|
|
21
20
|
NavManagerItem,
|
|
22
21
|
NavManagerLabels,
|
|
23
22
|
NavManagerUpdateDetail,
|
|
@@ -30,7 +29,6 @@ export class JantNavManager extends LitElement {
|
|
|
30
29
|
items: { type: Array },
|
|
31
30
|
labels: { type: Object },
|
|
32
31
|
systemNavItems: { type: Array, attribute: "system-nav-items" },
|
|
33
|
-
availablePages: { type: Array, attribute: "available-pages" },
|
|
34
32
|
siteName: { type: String, attribute: "site-name" },
|
|
35
33
|
maxVisible: { type: Number, attribute: "max-visible" },
|
|
36
34
|
homeDefaultView: { type: String, attribute: "home-default-view" },
|
|
@@ -41,40 +39,30 @@ export class JantNavManager extends LitElement {
|
|
|
41
39
|
_editUrl: { state: true },
|
|
42
40
|
_togglingKeys: { state: true },
|
|
43
41
|
_showOverflow: { state: true },
|
|
44
|
-
_showPagePicker: { state: true },
|
|
45
42
|
_showLinkForm: { state: true },
|
|
46
43
|
_newLinkLabel: { state: true },
|
|
47
44
|
_newLinkUrl: { state: true },
|
|
48
|
-
_availablePages: { state: true },
|
|
49
|
-
_addingPageId: { state: true },
|
|
50
45
|
_addingLink: { state: true },
|
|
51
|
-
_pageSearchQuery: { state: true },
|
|
52
46
|
};
|
|
53
47
|
|
|
54
48
|
declare items: NavManagerItem[];
|
|
55
49
|
declare labels: NavManagerLabels;
|
|
56
50
|
declare systemNavItems: SystemNavConfig[];
|
|
57
|
-
declare availablePages: AvailablePage[];
|
|
58
51
|
declare siteName: string;
|
|
59
52
|
declare maxVisible: number;
|
|
60
53
|
declare homeDefaultView: string;
|
|
61
54
|
|
|
62
55
|
declare _items: NavManagerItem[];
|
|
63
|
-
declare _editingId:
|
|
56
|
+
declare _editingId: string | null;
|
|
64
57
|
declare _editLabel: string;
|
|
65
58
|
declare _editUrl: string;
|
|
66
59
|
/** Keys currently mid-request (to disable switch during toggle) */
|
|
67
60
|
declare _togglingKeys: Set<string>;
|
|
68
61
|
declare _showOverflow: boolean;
|
|
69
|
-
declare _showPagePicker: boolean;
|
|
70
62
|
declare _showLinkForm: boolean;
|
|
71
63
|
declare _newLinkLabel: string;
|
|
72
64
|
declare _newLinkUrl: string;
|
|
73
|
-
declare _availablePages: AvailablePage[];
|
|
74
|
-
/** Page ID currently being added (to disable its button) */
|
|
75
|
-
declare _addingPageId: number | null;
|
|
76
65
|
declare _addingLink: boolean;
|
|
77
|
-
declare _pageSearchQuery: string;
|
|
78
66
|
|
|
79
67
|
#sortable: { destroy(): void } | null = null;
|
|
80
68
|
#initialized = false;
|
|
@@ -82,11 +70,6 @@ export class JantNavManager extends LitElement {
|
|
|
82
70
|
this._showOverflow = false;
|
|
83
71
|
document.removeEventListener("click", this.#closeOverflow);
|
|
84
72
|
};
|
|
85
|
-
#closePagePicker = () => {
|
|
86
|
-
this._showPagePicker = false;
|
|
87
|
-
this._pageSearchQuery = "";
|
|
88
|
-
document.removeEventListener("click", this.#closePagePicker);
|
|
89
|
-
};
|
|
90
73
|
#closeLinkForm = () => {
|
|
91
74
|
this._showLinkForm = false;
|
|
92
75
|
document.removeEventListener("click", this.#closeLinkForm);
|
|
@@ -102,7 +85,6 @@ export class JantNavManager extends LitElement {
|
|
|
102
85
|
this.items = [];
|
|
103
86
|
this.labels = {} as NavManagerLabels;
|
|
104
87
|
this.systemNavItems = [];
|
|
105
|
-
this.availablePages = [];
|
|
106
88
|
this.siteName = "";
|
|
107
89
|
this.maxVisible = 2;
|
|
108
90
|
this.homeDefaultView = "latest";
|
|
@@ -113,14 +95,10 @@ export class JantNavManager extends LitElement {
|
|
|
113
95
|
this._editUrl = "";
|
|
114
96
|
this._togglingKeys = new Set();
|
|
115
97
|
this._showOverflow = false;
|
|
116
|
-
this._showPagePicker = false;
|
|
117
98
|
this._showLinkForm = false;
|
|
118
99
|
this._newLinkLabel = "";
|
|
119
100
|
this._newLinkUrl = "";
|
|
120
|
-
this._availablePages = [];
|
|
121
|
-
this._addingPageId = null;
|
|
122
101
|
this._addingLink = false;
|
|
123
|
-
this._pageSearchQuery = "";
|
|
124
102
|
}
|
|
125
103
|
|
|
126
104
|
protected update(changedProperties: PropertyValueMap<JantNavManager>): void {
|
|
@@ -128,9 +106,6 @@ export class JantNavManager extends LitElement {
|
|
|
128
106
|
this._items = [...(this.items ?? [])];
|
|
129
107
|
this.#initialized = true;
|
|
130
108
|
}
|
|
131
|
-
if (changedProperties.has("availablePages" as keyof JantNavManager)) {
|
|
132
|
-
this._availablePages = [...(this.availablePages ?? [])];
|
|
133
|
-
}
|
|
134
109
|
super.update(changedProperties);
|
|
135
110
|
}
|
|
136
111
|
|
|
@@ -143,7 +118,6 @@ export class JantNavManager extends LitElement {
|
|
|
143
118
|
this.#sortable?.destroy();
|
|
144
119
|
this.#sortable = null;
|
|
145
120
|
document.removeEventListener("click", this.#closeOverflow);
|
|
146
|
-
document.removeEventListener("click", this.#closePagePicker);
|
|
147
121
|
document.removeEventListener("click", this.#closeLinkForm);
|
|
148
122
|
}
|
|
149
123
|
|
|
@@ -161,7 +135,9 @@ export class JantNavManager extends LitElement {
|
|
|
161
135
|
onEnd: (evt) => {
|
|
162
136
|
// Read new order from DOM BEFORE reverting
|
|
163
137
|
const els = [...list.querySelectorAll<HTMLElement>("[data-nav-id]")];
|
|
164
|
-
const ids = els
|
|
138
|
+
const ids = els
|
|
139
|
+
.map((el) => el.dataset.navId)
|
|
140
|
+
.filter((id): id is string => id !== undefined);
|
|
165
141
|
|
|
166
142
|
// Revert SortableJS DOM manipulation so Lit can re-render cleanly.
|
|
167
143
|
// SortableJS physically moved the element — put it back where it was.
|
|
@@ -182,17 +158,28 @@ export class JantNavManager extends LitElement {
|
|
|
182
158
|
this.#sortable?.destroy();
|
|
183
159
|
this.#sortable = null;
|
|
184
160
|
|
|
161
|
+
// Find the moved item and compute neighbors
|
|
162
|
+
const movedId = newIndex != null ? ids[newIndex] : undefined;
|
|
163
|
+
if (!movedId) return;
|
|
164
|
+
|
|
165
|
+
const movedIdx = ids.indexOf(movedId);
|
|
166
|
+
const afterId = movedIdx > 0 ? ids[movedIdx - 1] : null;
|
|
167
|
+
const beforeId = movedIdx < ids.length - 1 ? ids[movedIdx + 1] : null;
|
|
168
|
+
|
|
185
169
|
// Update internal state so Lit re-renders in the new order
|
|
186
170
|
const itemMap = new Map(this._items.map((i) => [i.id, i]));
|
|
187
171
|
this._items = ids
|
|
188
172
|
.map((id) => itemMap.get(id))
|
|
189
173
|
.filter((i): i is NavManagerItem => i !== undefined);
|
|
190
174
|
|
|
191
|
-
// Persist to server
|
|
192
|
-
fetch(
|
|
175
|
+
// Persist to server — single item move
|
|
176
|
+
fetch(`/api/nav-items/${movedId}/move`, {
|
|
193
177
|
method: "PUT",
|
|
194
178
|
headers: { "Content-Type": "application/json" },
|
|
195
|
-
body: JSON.stringify({
|
|
179
|
+
body: JSON.stringify({
|
|
180
|
+
after: afterId ?? null,
|
|
181
|
+
before: beforeId ?? null,
|
|
182
|
+
}),
|
|
196
183
|
}).then((res) => {
|
|
197
184
|
if (res.ok) showToast(this.labels.orderSaved);
|
|
198
185
|
else showToast(this.labels.saveFailed, "error");
|
|
@@ -253,7 +240,7 @@ export class JantNavManager extends LitElement {
|
|
|
253
240
|
const clamped = Math.max(0, Math.min(5, value));
|
|
254
241
|
this.maxVisible = clamped;
|
|
255
242
|
try {
|
|
256
|
-
const res = await fetch("/
|
|
243
|
+
const res = await fetch("/settings/navigation/nav-max-visible", {
|
|
257
244
|
method: "POST",
|
|
258
245
|
headers: { "Content-Type": "application/json" },
|
|
259
246
|
body: JSON.stringify({ value: clamped }),
|
|
@@ -268,7 +255,7 @@ export class JantNavManager extends LitElement {
|
|
|
268
255
|
async #handleHomeViewToggle(useFeatured: boolean) {
|
|
269
256
|
this.homeDefaultView = useFeatured ? "featured" : "latest";
|
|
270
257
|
try {
|
|
271
|
-
const res = await fetch("/
|
|
258
|
+
const res = await fetch("/settings/navigation/home-default-view", {
|
|
272
259
|
method: "POST",
|
|
273
260
|
headers: { "Content-Type": "application/json" },
|
|
274
261
|
body: JSON.stringify({ value: this.homeDefaultView }),
|
|
@@ -281,41 +268,9 @@ export class JantNavManager extends LitElement {
|
|
|
281
268
|
}
|
|
282
269
|
|
|
283
270
|
// ===========================================================================
|
|
284
|
-
// Add
|
|
271
|
+
// Add link handler
|
|
285
272
|
// ===========================================================================
|
|
286
273
|
|
|
287
|
-
async #handleAddPage(page: AvailablePage) {
|
|
288
|
-
this._addingPageId = page.id;
|
|
289
|
-
try {
|
|
290
|
-
const res = await fetch("/api/nav-items", {
|
|
291
|
-
method: "POST",
|
|
292
|
-
headers: {
|
|
293
|
-
"Content-Type": "application/json",
|
|
294
|
-
Accept: "application/json",
|
|
295
|
-
},
|
|
296
|
-
body: JSON.stringify({
|
|
297
|
-
type: "page",
|
|
298
|
-
label: page.title || page.slug,
|
|
299
|
-
url: `/${page.slug}`,
|
|
300
|
-
pageId: page.id,
|
|
301
|
-
}),
|
|
302
|
-
});
|
|
303
|
-
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
304
|
-
|
|
305
|
-
const created: NavManagerItem = await res.json();
|
|
306
|
-
this.#sortable?.destroy();
|
|
307
|
-
this.#sortable = null;
|
|
308
|
-
this._items = [...this._items, created];
|
|
309
|
-
this._availablePages = this._availablePages.filter(
|
|
310
|
-
(p) => p.id !== page.id,
|
|
311
|
-
);
|
|
312
|
-
} catch {
|
|
313
|
-
showToast(this.labels.saveFailed, "error");
|
|
314
|
-
} finally {
|
|
315
|
-
this._addingPageId = null;
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
274
|
async #handleAddLink() {
|
|
320
275
|
const label = this._newLinkLabel.trim();
|
|
321
276
|
const url = this._newLinkUrl.trim();
|
|
@@ -533,12 +488,7 @@ export class JantNavManager extends LitElement {
|
|
|
533
488
|
}
|
|
534
489
|
|
|
535
490
|
#renderTypeBadge(type: string) {
|
|
536
|
-
const label =
|
|
537
|
-
type === "page"
|
|
538
|
-
? this.labels.page
|
|
539
|
-
: type === "system"
|
|
540
|
-
? this.labels.system
|
|
541
|
-
: this.labels.link;
|
|
491
|
+
const label = type === "system" ? this.labels.system : this.labels.link;
|
|
542
492
|
return html`<span class="badge-secondary">${label}</span>`;
|
|
543
493
|
}
|
|
544
494
|
|
|
@@ -592,29 +542,6 @@ export class JantNavManager extends LitElement {
|
|
|
592
542
|
`;
|
|
593
543
|
}
|
|
594
544
|
|
|
595
|
-
if (item.type === "page") {
|
|
596
|
-
return html`
|
|
597
|
-
<div class="nav-item-edit">
|
|
598
|
-
<div class="flex items-center justify-between">
|
|
599
|
-
<button
|
|
600
|
-
type="button"
|
|
601
|
-
class="btn-sm-ghost text-destructive"
|
|
602
|
-
@click=${() => this.#handleDelete(item)}
|
|
603
|
-
>
|
|
604
|
-
${this.labels.remove}
|
|
605
|
-
</button>
|
|
606
|
-
${item.pageId
|
|
607
|
-
? html`<a
|
|
608
|
-
href=${`/dash/pages/${item.pageId}/edit`}
|
|
609
|
-
class="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
|
610
|
-
>${this.labels.editPage} →</a
|
|
611
|
-
>`
|
|
612
|
-
: nothing}
|
|
613
|
-
</div>
|
|
614
|
-
</div>
|
|
615
|
-
`;
|
|
616
|
-
}
|
|
617
|
-
|
|
618
545
|
if (item.type === "system") {
|
|
619
546
|
return html`
|
|
620
547
|
<div class="nav-item-edit">
|
|
@@ -721,193 +648,6 @@ export class JantNavManager extends LitElement {
|
|
|
721
648
|
`;
|
|
722
649
|
}
|
|
723
650
|
|
|
724
|
-
#renderAddArea() {
|
|
725
|
-
return html`
|
|
726
|
-
${this.#renderAddPageSection()} ${this.#renderAddLinkSection()}
|
|
727
|
-
`;
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
#renderAddPageSection() {
|
|
731
|
-
const query = this._pageSearchQuery.toLowerCase();
|
|
732
|
-
const filteredPages = query
|
|
733
|
-
? this._availablePages.filter((p) =>
|
|
734
|
-
(p.title || p.slug).toLowerCase().includes(query),
|
|
735
|
-
)
|
|
736
|
-
: this._availablePages;
|
|
737
|
-
|
|
738
|
-
return html`
|
|
739
|
-
<section class="mt-8">
|
|
740
|
-
<h2 class="text-lg font-semibold mb-3">
|
|
741
|
-
${this.labels.addPageToNavigation}
|
|
742
|
-
</h2>
|
|
743
|
-
<div id="nav-page-select" class="select">
|
|
744
|
-
<button
|
|
745
|
-
type="button"
|
|
746
|
-
class="btn-outline w-full sm:w-[280px]"
|
|
747
|
-
id="nav-page-select-trigger"
|
|
748
|
-
aria-haspopup="listbox"
|
|
749
|
-
aria-expanded=${this._showPagePicker}
|
|
750
|
-
aria-controls="nav-page-select-listbox"
|
|
751
|
-
@click=${(e: Event) => {
|
|
752
|
-
e.stopPropagation();
|
|
753
|
-
this._showPagePicker = !this._showPagePicker;
|
|
754
|
-
this._pageSearchQuery = "";
|
|
755
|
-
if (this._showPagePicker) {
|
|
756
|
-
setTimeout(() => {
|
|
757
|
-
document.addEventListener("click", this.#closePagePicker);
|
|
758
|
-
this.querySelector<HTMLInputElement>(
|
|
759
|
-
"#nav-page-search",
|
|
760
|
-
)?.focus();
|
|
761
|
-
});
|
|
762
|
-
} else {
|
|
763
|
-
document.removeEventListener("click", this.#closePagePicker);
|
|
764
|
-
}
|
|
765
|
-
}}
|
|
766
|
-
>
|
|
767
|
-
<span class="truncate">${this.labels.choosePage}</span>
|
|
768
|
-
<svg
|
|
769
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
770
|
-
width="24"
|
|
771
|
-
height="24"
|
|
772
|
-
viewBox="0 0 24 24"
|
|
773
|
-
fill="none"
|
|
774
|
-
stroke="currentColor"
|
|
775
|
-
stroke-width="2"
|
|
776
|
-
stroke-linecap="round"
|
|
777
|
-
stroke-linejoin="round"
|
|
778
|
-
class="text-muted-foreground opacity-50 shrink-0"
|
|
779
|
-
>
|
|
780
|
-
<path d="m7 15 5 5 5-5" />
|
|
781
|
-
<path d="m7 9 5-5 5 5" />
|
|
782
|
-
</svg>
|
|
783
|
-
</button>
|
|
784
|
-
${this._showPagePicker
|
|
785
|
-
? html`
|
|
786
|
-
<div
|
|
787
|
-
id="nav-page-select-popover"
|
|
788
|
-
data-popover
|
|
789
|
-
aria-hidden="false"
|
|
790
|
-
class="w-full sm:w-[280px]"
|
|
791
|
-
@click=${(e: Event) => e.stopPropagation()}
|
|
792
|
-
>
|
|
793
|
-
<header>
|
|
794
|
-
<svg
|
|
795
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
796
|
-
width="24"
|
|
797
|
-
height="24"
|
|
798
|
-
viewBox="0 0 24 24"
|
|
799
|
-
fill="none"
|
|
800
|
-
stroke="currentColor"
|
|
801
|
-
stroke-width="2"
|
|
802
|
-
stroke-linecap="round"
|
|
803
|
-
stroke-linejoin="round"
|
|
804
|
-
>
|
|
805
|
-
<circle cx="11" cy="11" r="8" />
|
|
806
|
-
<path d="m21 21-4.3-4.3" />
|
|
807
|
-
</svg>
|
|
808
|
-
<input
|
|
809
|
-
type="text"
|
|
810
|
-
id="nav-page-search"
|
|
811
|
-
.value=${this._pageSearchQuery}
|
|
812
|
-
placeholder=${this.labels.searchPages}
|
|
813
|
-
autocomplete="off"
|
|
814
|
-
autocorrect="off"
|
|
815
|
-
spellcheck="false"
|
|
816
|
-
aria-autocomplete="list"
|
|
817
|
-
role="combobox"
|
|
818
|
-
aria-expanded="true"
|
|
819
|
-
aria-controls="nav-page-select-listbox"
|
|
820
|
-
aria-labelledby="nav-page-select-trigger"
|
|
821
|
-
@input=${(e: Event) => {
|
|
822
|
-
this._pageSearchQuery = (
|
|
823
|
-
e.target as HTMLInputElement
|
|
824
|
-
).value;
|
|
825
|
-
}}
|
|
826
|
-
/>
|
|
827
|
-
</header>
|
|
828
|
-
<div
|
|
829
|
-
role="listbox"
|
|
830
|
-
id="nav-page-select-listbox"
|
|
831
|
-
aria-orientation="vertical"
|
|
832
|
-
aria-labelledby="nav-page-select-trigger"
|
|
833
|
-
data-empty=${this.labels.noPagesFound}
|
|
834
|
-
>
|
|
835
|
-
${filteredPages.length > 0
|
|
836
|
-
? html`<div class="max-h-64 overflow-y-auto scrollbar">
|
|
837
|
-
${filteredPages.map(
|
|
838
|
-
(page) => html`
|
|
839
|
-
<div
|
|
840
|
-
role="option"
|
|
841
|
-
data-value=${page.id}
|
|
842
|
-
@click=${() => {
|
|
843
|
-
this._showPagePicker = false;
|
|
844
|
-
this._pageSearchQuery = "";
|
|
845
|
-
document.removeEventListener(
|
|
846
|
-
"click",
|
|
847
|
-
this.#closePagePicker,
|
|
848
|
-
);
|
|
849
|
-
this.#handleAddPage(page);
|
|
850
|
-
}}
|
|
851
|
-
>
|
|
852
|
-
${page.title || page.slug}
|
|
853
|
-
</div>
|
|
854
|
-
`,
|
|
855
|
-
)}
|
|
856
|
-
<hr role="separator" />
|
|
857
|
-
<a href="/dash/pages/new" role="option">
|
|
858
|
-
<svg
|
|
859
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
860
|
-
width="24"
|
|
861
|
-
height="24"
|
|
862
|
-
viewBox="0 0 24 24"
|
|
863
|
-
fill="none"
|
|
864
|
-
stroke="currentColor"
|
|
865
|
-
stroke-width="2"
|
|
866
|
-
stroke-linecap="round"
|
|
867
|
-
stroke-linejoin="round"
|
|
868
|
-
>
|
|
869
|
-
<circle cx="12" cy="12" r="10" />
|
|
870
|
-
<path d="M8 12h8" />
|
|
871
|
-
<path d="M12 8v8" />
|
|
872
|
-
</svg>
|
|
873
|
-
${this.labels.createPage}
|
|
874
|
-
</a>
|
|
875
|
-
</div>`
|
|
876
|
-
: html`<div
|
|
877
|
-
class="py-6 text-center text-sm text-muted-foreground"
|
|
878
|
-
>
|
|
879
|
-
${this._availablePages.length === 0
|
|
880
|
-
? this.labels.allPagesInNav
|
|
881
|
-
: this.labels.noPagesFound}
|
|
882
|
-
</div>
|
|
883
|
-
<hr role="separator" />
|
|
884
|
-
<a href="/dash/pages/new" role="option">
|
|
885
|
-
<svg
|
|
886
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
887
|
-
width="24"
|
|
888
|
-
height="24"
|
|
889
|
-
viewBox="0 0 24 24"
|
|
890
|
-
fill="none"
|
|
891
|
-
stroke="currentColor"
|
|
892
|
-
stroke-width="2"
|
|
893
|
-
stroke-linecap="round"
|
|
894
|
-
stroke-linejoin="round"
|
|
895
|
-
>
|
|
896
|
-
<circle cx="12" cy="12" r="10" />
|
|
897
|
-
<path d="M8 12h8" />
|
|
898
|
-
<path d="M12 8v8" />
|
|
899
|
-
</svg>
|
|
900
|
-
${this.labels.createPage}
|
|
901
|
-
</a>`}
|
|
902
|
-
</div>
|
|
903
|
-
</div>
|
|
904
|
-
`
|
|
905
|
-
: nothing}
|
|
906
|
-
</div>
|
|
907
|
-
</section>
|
|
908
|
-
`;
|
|
909
|
-
}
|
|
910
|
-
|
|
911
651
|
#renderAddLinkSection() {
|
|
912
652
|
return html`
|
|
913
653
|
<section class="mt-8">
|
|
@@ -1118,7 +858,7 @@ export class JantNavManager extends LitElement {
|
|
|
1118
858
|
`}
|
|
1119
859
|
</section>
|
|
1120
860
|
|
|
1121
|
-
${this.#
|
|
861
|
+
${this.#renderAddLinkSection()} ${this.#renderSystemToggles()}
|
|
1122
862
|
`;
|
|
1123
863
|
}
|
|
1124
864
|
}
|