@jant/core 0.3.35 → 0.3.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (307) hide show
  1. package/bin/commands/export.js +1 -1
  2. package/bin/commands/import-site.js +529 -0
  3. package/bin/commands/reset-password.js +3 -2
  4. package/dist/client/assets/heic-to-DIRPI3VF.js +1 -0
  5. package/dist/client/assets/module-RjUF93sV.js +716 -0
  6. package/dist/client/assets/native-48B9X9Wg.js +1 -0
  7. package/dist/client/assets/url-FWFqPJPb.js +1 -0
  8. package/dist/client/client.css +1 -1
  9. package/dist/client/client.js +4564 -3013
  10. package/dist/index.js +12885 -8161
  11. package/package.json +23 -6
  12. package/src/__tests__/helpers/app.ts +10 -10
  13. package/src/__tests__/helpers/db.ts +91 -87
  14. package/src/app.tsx +157 -31
  15. package/src/auth.ts +20 -2
  16. package/src/client/archive-nav.js +187 -0
  17. package/src/client/audio-player.ts +478 -0
  18. package/src/client/audio-processor.ts +84 -0
  19. package/src/{lib → client}/avatar-upload.ts +4 -3
  20. package/src/{lib → client}/collection-form-bridge.ts +2 -2
  21. package/src/{ui → client}/components/__tests__/jant-collection-form.test.ts +26 -9
  22. package/src/client/components/__tests__/jant-compose-dialog.test.ts +1140 -0
  23. package/src/client/components/__tests__/jant-compose-editor.test.ts +504 -0
  24. package/src/{ui → client}/components/__tests__/jant-post-form.test.ts +37 -17
  25. package/src/{ui → client}/components/__tests__/jant-settings-avatar.test.ts +2 -2
  26. package/src/{ui → client}/components/__tests__/jant-settings-general.test.ts +3 -3
  27. package/src/client/components/collection-sidebar-types.ts +43 -0
  28. package/src/{ui → client}/components/collection-types.ts +3 -4
  29. package/src/client/components/compose-types.ts +174 -0
  30. package/src/client/components/jant-collection-form.ts +667 -0
  31. package/src/client/components/jant-collection-sidebar.ts +805 -0
  32. package/src/client/components/jant-compose-dialog.ts +2161 -0
  33. package/src/client/components/jant-compose-editor.ts +1813 -0
  34. package/src/client/components/jant-compose-fullscreen.ts +283 -0
  35. package/src/client/components/jant-media-lightbox.ts +259 -0
  36. package/src/{ui → client}/components/jant-nav-manager.ts +97 -298
  37. package/src/{ui → client}/components/jant-post-form.ts +141 -12
  38. package/src/client/components/jant-post-menu.ts +1019 -0
  39. package/src/{ui → client}/components/jant-settings-avatar.ts +3 -3
  40. package/src/{ui → client}/components/jant-settings-general.ts +38 -4
  41. package/src/client/components/jant-text-preview.ts +232 -0
  42. package/src/{ui → client}/components/nav-manager-types.ts +6 -18
  43. package/src/{ui → client}/components/post-form-template.ts +137 -38
  44. package/src/{ui → client}/components/post-form-types.ts +15 -4
  45. package/src/client/compose-bridge.ts +583 -0
  46. package/src/{lib → client}/image-processor.ts +26 -8
  47. package/src/client/lazy-slugify.ts +51 -0
  48. package/src/client/media-metadata.ts +247 -0
  49. package/src/client/multipart-upload.ts +160 -0
  50. package/src/{lib → client}/nav-manager-bridge.ts +1 -1
  51. package/src/{lib → client}/post-form-bridge.ts +53 -2
  52. package/src/{lib → client}/settings-bridge.ts +3 -15
  53. package/src/client/thread-context.ts +140 -0
  54. package/src/client/tiptap/bubble-menu.ts +205 -0
  55. package/src/client/tiptap/create-editor.ts +86 -0
  56. package/src/client/tiptap/exitable-marks.ts +73 -0
  57. package/src/client/tiptap/extensions.ts +65 -0
  58. package/src/client/tiptap/image-node.ts +482 -0
  59. package/src/client/tiptap/link-toolbar.ts +371 -0
  60. package/src/client/tiptap/more-break.ts +50 -0
  61. package/src/client/tiptap/paste-image.ts +129 -0
  62. package/src/client/tiptap/slash-commands.ts +438 -0
  63. package/src/{lib → client}/toast.ts +101 -3
  64. package/src/client/types/sortablejs.d.ts +44 -0
  65. package/src/client/upload-with-metadata.ts +54 -0
  66. package/src/client/video-processor.ts +207 -0
  67. package/src/client.ts +27 -17
  68. package/src/db/__tests__/migrations.test.ts +118 -0
  69. package/src/db/index.ts +52 -0
  70. package/src/db/migrations/0000_baseline.sql +269 -0
  71. package/src/db/migrations/0001_fts_setup.sql +31 -0
  72. package/src/db/migrations/meta/0000_snapshot.json +703 -119
  73. package/src/db/migrations/meta/0001_snapshot.json +1337 -0
  74. package/src/db/migrations/meta/_journal.json +4 -39
  75. package/src/db/schema.ts +409 -140
  76. package/src/i18n/__tests__/detect.test.ts +115 -0
  77. package/src/i18n/context.tsx +2 -2
  78. package/src/i18n/detect.ts +85 -1
  79. package/src/i18n/i18n.ts +1 -1
  80. package/src/i18n/index.ts +2 -1
  81. package/src/i18n/locales/en.po +783 -1087
  82. package/src/i18n/locales/en.ts +1 -1
  83. package/src/i18n/locales/zh-Hans.po +867 -812
  84. package/src/i18n/locales/zh-Hans.ts +1 -1
  85. package/src/i18n/locales/zh-Hant.po +878 -823
  86. package/src/i18n/locales/zh-Hant.ts +1 -1
  87. package/src/i18n/middleware.ts +6 -0
  88. package/src/index.ts +5 -7
  89. package/src/lib/__tests__/blurhash-placeholder.test.ts +75 -0
  90. package/src/lib/__tests__/constants.test.ts +0 -1
  91. package/src/lib/__tests__/markdown-to-tiptap.test.ts +358 -0
  92. package/src/lib/__tests__/nanoid.test.ts +26 -0
  93. package/src/lib/__tests__/resolve-config.test.ts +2 -2
  94. package/src/lib/__tests__/schemas.test.ts +186 -65
  95. package/src/lib/__tests__/slug.test.ts +126 -0
  96. package/src/lib/__tests__/sse.test.ts +6 -6
  97. package/src/lib/__tests__/summary.test.ts +264 -0
  98. package/src/lib/__tests__/theme.test.ts +1 -1
  99. package/src/lib/__tests__/timeline.test.ts +33 -30
  100. package/src/lib/__tests__/tiptap-to-markdown.test.ts +346 -0
  101. package/src/lib/__tests__/url.test.ts +2 -2
  102. package/src/lib/__tests__/view.test.ts +140 -65
  103. package/src/lib/blurhash-placeholder.ts +102 -0
  104. package/src/lib/constants.ts +3 -1
  105. package/src/lib/emoji-catalog.ts +963 -0
  106. package/src/lib/errors.ts +11 -8
  107. package/src/lib/feed.ts +77 -31
  108. package/src/lib/html.ts +2 -1
  109. package/src/lib/icon-catalog.ts +5033 -1
  110. package/src/lib/icons.ts +3 -2
  111. package/src/lib/index.ts +0 -1
  112. package/src/lib/markdown-to-tiptap.ts +286 -0
  113. package/src/lib/media-helpers.ts +22 -12
  114. package/src/lib/nanoid.ts +29 -0
  115. package/src/lib/navigation.ts +1 -1
  116. package/src/lib/render.tsx +24 -5
  117. package/src/lib/resolve-config.ts +13 -2
  118. package/src/lib/schemas.ts +226 -58
  119. package/src/lib/search-snippet.ts +34 -0
  120. package/src/lib/slug.ts +96 -0
  121. package/src/lib/sse.ts +6 -6
  122. package/src/lib/storage.ts +115 -7
  123. package/src/lib/summary.ts +158 -0
  124. package/src/lib/theme.ts +11 -8
  125. package/src/lib/timeline.ts +76 -34
  126. package/src/lib/tiptap-render.ts +191 -0
  127. package/src/lib/tiptap-to-markdown.ts +305 -0
  128. package/src/lib/upload.ts +263 -14
  129. package/src/lib/url.ts +37 -22
  130. package/src/lib/view.ts +236 -55
  131. package/src/middleware/__tests__/auth.test.ts +191 -11
  132. package/src/middleware/__tests__/onboarding.test.ts +12 -10
  133. package/src/middleware/auth.ts +63 -9
  134. package/src/middleware/error-handler.ts +3 -3
  135. package/src/middleware/onboarding.ts +1 -1
  136. package/src/middleware/secure-headers.ts +40 -0
  137. package/src/preset.css +83 -2
  138. package/src/routes/__tests__/compose.test.ts +17 -24
  139. package/src/routes/api/__tests__/collections.test.ts +109 -61
  140. package/src/routes/api/__tests__/nav-items.test.ts +46 -29
  141. package/src/routes/api/__tests__/posts.test.ts +132 -68
  142. package/src/routes/api/__tests__/search.test.ts +15 -2
  143. package/src/routes/api/__tests__/upload-multipart.test.ts +534 -0
  144. package/src/routes/api/collections.ts +57 -31
  145. package/src/routes/api/custom-urls.ts +80 -0
  146. package/src/routes/api/export.ts +31 -0
  147. package/src/routes/api/nav-items.ts +23 -19
  148. package/src/routes/api/posts.ts +81 -62
  149. package/src/routes/api/search.ts +3 -4
  150. package/src/routes/api/upload-multipart.ts +245 -0
  151. package/src/routes/api/upload.ts +92 -24
  152. package/src/routes/auth/__tests__/setup.test.ts +20 -60
  153. package/src/routes/auth/reset.tsx +5 -4
  154. package/src/routes/auth/setup.tsx +39 -31
  155. package/src/routes/auth/signin.tsx +13 -14
  156. package/src/routes/compose.tsx +27 -63
  157. package/src/routes/dash/__tests__/settings-avatar.test.ts +44 -9
  158. package/src/routes/dash/custom-urls.tsx +414 -0
  159. package/src/routes/dash/settings.tsx +475 -99
  160. package/src/routes/feed/__tests__/rss.test.ts +22 -23
  161. package/src/routes/feed/rss.ts +6 -2
  162. package/src/routes/feed/sitemap.ts +2 -12
  163. package/src/routes/pages/__tests__/collections.test.ts +5 -6
  164. package/src/routes/pages/__tests__/featured.test.ts +36 -18
  165. package/src/routes/pages/archive.tsx +177 -37
  166. package/src/routes/pages/collection.tsx +43 -14
  167. package/src/routes/pages/collections.tsx +11 -2
  168. package/src/routes/pages/featured.tsx +27 -3
  169. package/src/routes/pages/home.tsx +15 -14
  170. package/src/routes/pages/latest.tsx +1 -11
  171. package/src/routes/pages/new.tsx +39 -0
  172. package/src/routes/pages/page.tsx +95 -49
  173. package/src/routes/pages/search.tsx +1 -1
  174. package/src/services/__tests__/api-token.test.ts +135 -0
  175. package/src/services/__tests__/collection.test.ts +275 -227
  176. package/src/services/__tests__/custom-url.test.ts +213 -0
  177. package/src/services/__tests__/media.test.ts +162 -22
  178. package/src/services/__tests__/navigation.test.ts +109 -68
  179. package/src/services/__tests__/post-timeline.test.ts +205 -32
  180. package/src/services/__tests__/post.test.ts +800 -230
  181. package/src/services/__tests__/search.test.ts +67 -10
  182. package/src/services/__tests__/settings.test.ts +3 -3
  183. package/src/services/api-token.ts +166 -0
  184. package/src/services/auth.ts +17 -2
  185. package/src/services/collection.ts +397 -131
  186. package/src/services/custom-url.ts +188 -0
  187. package/src/services/export.ts +802 -0
  188. package/src/services/index.ts +26 -19
  189. package/src/services/media.ts +100 -22
  190. package/src/services/navigation.ts +158 -47
  191. package/src/services/path.ts +339 -0
  192. package/src/services/post.ts +764 -172
  193. package/src/services/search.ts +161 -74
  194. package/src/services/settings.ts +6 -2
  195. package/src/styles/components.css +293 -62
  196. package/src/styles/tokens.css +93 -5
  197. package/src/styles/ui.css +4349 -766
  198. package/src/types/bindings.ts +8 -0
  199. package/src/types/config.ts +34 -4
  200. package/src/types/constants.ts +17 -2
  201. package/src/types/entities.ts +83 -37
  202. package/src/types/operations.ts +20 -27
  203. package/src/types/props.ts +52 -17
  204. package/src/types/views.ts +48 -24
  205. package/src/ui/color-themes.ts +133 -23
  206. package/src/ui/compose/ComposeDialog.tsx +255 -16
  207. package/src/ui/compose/ComposePrompt.tsx +1 -1
  208. package/src/ui/dash/CrudPageHeader.tsx +1 -1
  209. package/src/ui/dash/ListItemRow.tsx +1 -1
  210. package/src/ui/dash/StatusBadge.tsx +12 -2
  211. package/src/ui/dash/appearance/AdvancedContent.tsx +71 -59
  212. package/src/ui/dash/appearance/ColorThemeContent.tsx +48 -33
  213. package/src/ui/dash/appearance/FontThemeContent.tsx +65 -73
  214. package/src/ui/dash/appearance/NavigationContent.tsx +106 -135
  215. package/src/ui/dash/index.ts +0 -3
  216. package/src/ui/dash/settings/AccountContent.tsx +87 -146
  217. package/src/ui/dash/settings/AccountMenuContent.tsx +147 -0
  218. package/src/ui/dash/settings/ApiTokensContent.tsx +232 -0
  219. package/src/ui/dash/settings/AvatarContent.tsx +78 -0
  220. package/src/ui/dash/settings/GeneralContent.tsx +3 -62
  221. package/src/ui/dash/settings/SessionsContent.tsx +159 -0
  222. package/src/ui/dash/settings/SettingsRootContent.tsx +266 -0
  223. package/src/ui/feed/LinkCard.tsx +89 -40
  224. package/src/ui/feed/NoteCard.tsx +39 -25
  225. package/src/ui/feed/PostStatusBadges.tsx +67 -0
  226. package/src/ui/feed/QuoteCard.tsx +38 -23
  227. package/src/ui/feed/ThreadPreview.tsx +90 -26
  228. package/src/ui/feed/TimelineFeed.tsx +3 -2
  229. package/src/ui/feed/TimelineItem.tsx +15 -6
  230. package/src/ui/feed/__tests__/thread-preview.test.ts +107 -0
  231. package/src/ui/feed/thread-preview-state.ts +61 -0
  232. package/src/ui/font-themes.ts +2 -2
  233. package/src/ui/layouts/BaseLayout.tsx +2 -2
  234. package/src/ui/layouts/SiteLayout.tsx +116 -103
  235. package/src/ui/pages/ArchivePage.tsx +923 -95
  236. package/src/ui/pages/CollectionPage.tsx +6 -35
  237. package/src/ui/pages/CollectionsPage.tsx +2 -1
  238. package/src/ui/pages/ComposePage.tsx +54 -0
  239. package/src/ui/pages/FeaturedPage.tsx +2 -1
  240. package/src/ui/pages/HomePage.tsx +1 -1
  241. package/src/ui/pages/PostPage.tsx +30 -45
  242. package/src/ui/pages/SearchPage.tsx +182 -38
  243. package/src/ui/shared/AdminBreadcrumb.tsx +29 -0
  244. package/src/ui/shared/CollectionsSidebar.tsx +239 -4
  245. package/src/ui/shared/MediaGallery.tsx +475 -41
  246. package/src/ui/shared/PostFooter.tsx +204 -0
  247. package/src/ui/shared/StarRating.tsx +27 -0
  248. package/src/ui/shared/__tests__/format-chars.test.ts +35 -0
  249. package/src/ui/shared/index.ts +0 -1
  250. package/src/db/migrations/0000_square_wallflower.sql +0 -118
  251. package/src/db/migrations/0001_add_search_fts.sql +0 -34
  252. package/src/db/migrations/0002_add_media_attachments.sql +0 -3
  253. package/src/db/migrations/0003_add_navigation_links.sql +0 -8
  254. package/src/db/migrations/0004_add_storage_provider.sql +0 -3
  255. package/src/db/migrations/0005_v2_schema_migration.sql +0 -268
  256. package/src/db/migrations/0006_rename_slug_to_path.sql +0 -5
  257. package/src/db/migrations/0007_post_collections_m2m.sql +0 -94
  258. package/src/db/migrations/0008_add_collection_dividers.sql +0 -8
  259. package/src/db/migrations/0009_drop_collection_show_divider.sql +0 -2
  260. package/src/db/migrations/0010_add_performance_indexes.sql +0 -16
  261. package/src/db/migrations/0011_add_path_registry.sql +0 -23
  262. package/src/db/migrations/meta/0003_snapshot.json +0 -821
  263. package/src/lib/__tests__/sqid.test.ts +0 -65
  264. package/src/lib/collections-reorder.ts +0 -28
  265. package/src/lib/compose-bridge.ts +0 -280
  266. package/src/lib/media-upload.ts +0 -148
  267. package/src/lib/sqid.ts +0 -79
  268. package/src/routes/api/__tests__/pages.test.ts +0 -218
  269. package/src/routes/api/pages.ts +0 -73
  270. package/src/routes/dash/__tests__/pages.test.ts +0 -226
  271. package/src/routes/dash/appearance.tsx +0 -240
  272. package/src/routes/dash/collections.tsx +0 -211
  273. package/src/routes/dash/index.tsx +0 -103
  274. package/src/routes/dash/media.tsx +0 -132
  275. package/src/routes/dash/pages.tsx +0 -239
  276. package/src/routes/dash/posts.tsx +0 -334
  277. package/src/routes/dash/redirects.tsx +0 -257
  278. package/src/routes/pages/post.tsx +0 -59
  279. package/src/services/__tests__/page.test.ts +0 -298
  280. package/src/services/__tests__/path-registry.test.ts +0 -165
  281. package/src/services/__tests__/redirect.test.ts +0 -159
  282. package/src/services/page.ts +0 -203
  283. package/src/services/path-registry.ts +0 -160
  284. package/src/services/redirect.ts +0 -97
  285. package/src/types/sortablejs.d.ts +0 -29
  286. package/src/ui/components/__tests__/jant-compose-dialog.test.ts +0 -512
  287. package/src/ui/components/__tests__/jant-compose-editor.test.ts +0 -272
  288. package/src/ui/components/compose-types.ts +0 -75
  289. package/src/ui/components/jant-collection-form.ts +0 -512
  290. package/src/ui/components/jant-compose-dialog.ts +0 -495
  291. package/src/ui/components/jant-compose-editor.ts +0 -814
  292. package/src/ui/dash/PageForm.tsx +0 -185
  293. package/src/ui/dash/PostList.tsx +0 -95
  294. package/src/ui/dash/appearance/AppearanceNav.tsx +0 -60
  295. package/src/ui/dash/collections/CollectionForm.tsx +0 -166
  296. package/src/ui/dash/collections/CollectionsListContent.tsx +0 -146
  297. package/src/ui/dash/collections/IconPickerGrid.tsx +0 -50
  298. package/src/ui/dash/collections/ViewCollectionContent.tsx +0 -103
  299. package/src/ui/dash/media/MediaListContent.tsx +0 -201
  300. package/src/ui/dash/media/ViewMediaContent.tsx +0 -208
  301. package/src/ui/dash/pages/PagesContent.tsx +0 -74
  302. package/src/ui/dash/posts/PostForm.tsx +0 -248
  303. package/src/ui/dash/settings/SettingsNav.tsx +0 -52
  304. package/src/ui/layouts/DashLayout.tsx +0 -165
  305. package/src/ui/pages/SinglePage.tsx +0 -23
  306. package/src/ui/shared/ThreadView.tsx +0 -136
  307. /package/src/{ui → client}/components/settings-types.ts +0 -0
@@ -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 page/link forms
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
  *
@@ -15,9 +15,8 @@
15
15
  import { LitElement, html, nothing } from "lit";
16
16
  import type { PropertyValueMap } from "lit";
17
17
  import Sortable from "sortablejs";
18
- import { showToast } from "../../lib/toast.js";
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: number | null;
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,9 +85,8 @@ 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
- this.maxVisible = 3;
89
+ this.maxVisible = 2;
108
90
  this.homeDefaultView = "latest";
109
91
 
110
92
  this._items = [];
@@ -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.map((el) => Number(el.dataset.navId));
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("/api/nav-items/reorder", {
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({ ids }),
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("/dash/appearance/nav-max-visible", {
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("/dash/appearance/home-default-view", {
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 page / link handlers
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();
@@ -420,11 +375,14 @@ export class JantNavManager extends LitElement {
420
375
  const hasMore = overflow.length > 0;
421
376
 
422
377
  return html`
423
- <div class="border rounded-lg">
424
- <p class="text-xs text-muted-foreground px-4 pt-3">
425
- ${this.labels.preview}
426
- </p>
427
- <div class="px-5 py-3">
378
+ <div class="nav-preview">
379
+ <div class="nav-preview-chrome">
380
+ <div class="nav-preview-dots">
381
+ <span></span><span></span><span></span>
382
+ </div>
383
+ <span class="nav-preview-label">${this.labels.preview}</span>
384
+ </div>
385
+ <div class="nav-preview-content">
428
386
  <div class="site-header-top">
429
387
  <a href="/" class="site-logo">${this.siteName}</a>
430
388
  <div class="site-header-right">
@@ -530,12 +488,7 @@ export class JantNavManager extends LitElement {
530
488
  }
531
489
 
532
490
  #renderTypeBadge(type: string) {
533
- const label =
534
- type === "page"
535
- ? this.labels.page
536
- : type === "system"
537
- ? this.labels.system
538
- : this.labels.link;
491
+ const label = type === "system" ? this.labels.system : this.labels.link;
539
492
  return html`<span class="badge-secondary">${label}</span>`;
540
493
  }
541
494
 
@@ -544,7 +497,7 @@ export class JantNavManager extends LitElement {
544
497
 
545
498
  if (item.type === "link") {
546
499
  return html`
547
- <div class="pb-4 pl-8 flex flex-col gap-3">
500
+ <div class="nav-item-edit">
548
501
  <div class="field">
549
502
  <label class="label">${this.labels.label}</label>
550
503
  <input
@@ -569,14 +522,7 @@ export class JantNavManager extends LitElement {
569
522
  }}
570
523
  />
571
524
  </div>
572
- <div class="flex gap-2">
573
- <button
574
- type="button"
575
- class="btn-sm"
576
- @click=${() => this.#handleUpdate(item)}
577
- >
578
- ${this.labels.save}
579
- </button>
525
+ <div class="flex items-center justify-between">
580
526
  <button
581
527
  type="button"
582
528
  class="btn-sm-ghost text-destructive"
@@ -584,29 +530,12 @@ export class JantNavManager extends LitElement {
584
530
  >
585
531
  ${this.labels.delete}
586
532
  </button>
587
- </div>
588
- </div>
589
- `;
590
- }
591
-
592
- if (item.type === "page") {
593
- return html`
594
- <div class="pb-4 pl-8 flex flex-col gap-3">
595
- <code class="text-sm text-muted-foreground">${item.url}</code>
596
- <div class="flex gap-2">
597
- ${item.pageId
598
- ? html`<a
599
- href=${`/dash/pages/${item.pageId}/edit`}
600
- class="btn-sm-outline"
601
- >${this.labels.editPage}</a
602
- >`
603
- : nothing}
604
533
  <button
605
534
  type="button"
606
- class="btn-sm-ghost text-destructive"
607
- @click=${() => this.#handleDelete(item)}
535
+ class="btn-sm"
536
+ @click=${() => this.#handleUpdate(item)}
608
537
  >
609
- ${this.labels.remove}
538
+ ${this.labels.save}
610
539
  </button>
611
540
  </div>
612
541
  </div>
@@ -615,8 +544,7 @@ export class JantNavManager extends LitElement {
615
544
 
616
545
  if (item.type === "system") {
617
546
  return html`
618
- <div class="pb-4 pl-8 flex flex-col gap-3">
619
- <code class="text-sm text-muted-foreground">${item.url}</code>
547
+ <div class="nav-item-edit">
620
548
  <div class="field">
621
549
  <label class="label">${this.labels.label}</label>
622
550
  <input
@@ -629,20 +557,20 @@ export class JantNavManager extends LitElement {
629
557
  }}
630
558
  />
631
559
  </div>
632
- <div class="flex gap-2">
560
+ <div class="flex items-center justify-between">
633
561
  <button
634
562
  type="button"
635
- class="btn-sm"
636
- @click=${() => this.#handleUpdate(item)}
563
+ class="btn-sm-ghost text-destructive"
564
+ @click=${() => this.#handleDelete(item)}
637
565
  >
638
- ${this.labels.save}
566
+ ${this.labels.remove}
639
567
  </button>
640
568
  <button
641
569
  type="button"
642
- class="btn-sm-ghost text-destructive"
643
- @click=${() => this.#handleDelete(item)}
570
+ class="btn-sm"
571
+ @click=${() => this.#handleUpdate(item)}
644
572
  >
645
- ${this.labels.remove}
573
+ ${this.labels.save}
646
574
  </button>
647
575
  </div>
648
576
  </div>
@@ -653,17 +581,19 @@ export class JantNavManager extends LitElement {
653
581
  }
654
582
 
655
583
  #renderItem(item: NavManagerItem) {
584
+ const isEditing = this._editingId === item.id;
585
+
656
586
  return html`
657
- <div data-nav-id=${item.id}>
658
- <div class="flex items-center py-3 gap-2">
659
- <div
660
- class="flex items-center gap-3 cursor-grab flex-1 min-w-0"
661
- data-drag-handle
662
- >
587
+ <div
588
+ data-nav-id=${item.id}
589
+ class="nav-item${isEditing ? " nav-item-editing" : ""}"
590
+ >
591
+ <div class="nav-item-row">
592
+ <div class="nav-item-handle" data-drag-handle>
663
593
  <svg
664
594
  xmlns="http://www.w3.org/2000/svg"
665
- width="16"
666
- height="16"
595
+ width="14"
596
+ height="14"
667
597
  viewBox="0 0 24 24"
668
598
  fill="none"
669
599
  stroke="currentColor"
@@ -679,183 +609,42 @@ export class JantNavManager extends LitElement {
679
609
  <circle cx="15" cy="5" r="1" />
680
610
  <circle cx="15" cy="19" r="1" />
681
611
  </svg>
682
- <span class="font-medium truncate">${item.label}</span>
683
612
  </div>
684
- <div class="flex items-center gap-2 shrink-0">
685
- ${this.#renderTypeBadge(item.type)}
686
- <button
687
- type="button"
688
- class="btn-sm-ghost"
689
- @click=${() => this.#toggleEdit(item)}
690
- aria-label=${this.labels.toggleEdit}
613
+ <div class="nav-item-info" @click=${() => this.#toggleEdit(item)}>
614
+ <div class="flex items-center gap-2 min-w-0">
615
+ <span class="text-sm font-medium truncate">${item.label}</span>
616
+ ${this.#renderTypeBadge(item.type)}
617
+ </div>
618
+ <span class="text-xs text-muted-foreground truncate"
619
+ >${item.url}</span
691
620
  >
692
- <svg
693
- xmlns="http://www.w3.org/2000/svg"
694
- width="16"
695
- height="16"
696
- viewBox="0 0 24 24"
697
- fill="none"
698
- stroke="currentColor"
699
- stroke-width="2"
700
- stroke-linecap="round"
701
- stroke-linejoin="round"
702
- >
703
- <path d="m6 9 6 6 6-6" />
704
- </svg>
705
- </button>
706
621
  </div>
707
- </div>
708
- ${this.#renderEditPanel(item)}
709
- </div>
710
- `;
711
- }
712
-
713
- #renderAddArea() {
714
- return html`
715
- ${this.#renderAddPageSection()} ${this.#renderAddLinkSection()}
716
- `;
717
- }
718
-
719
- #renderAddPageSection() {
720
- const query = this._pageSearchQuery.toLowerCase();
721
- const filteredPages = query
722
- ? this._availablePages.filter((p) =>
723
- (p.title || p.slug).toLowerCase().includes(query),
724
- )
725
- : this._availablePages;
726
-
727
- return html`
728
- <section class="mt-8">
729
- <h2 class="text-lg font-semibold mb-3">
730
- ${this.labels.addPageToNavigation}
731
- </h2>
732
- <div id="nav-page-select" class="select">
733
622
  <button
734
623
  type="button"
735
- class="btn-outline w-full sm:w-[280px]"
736
- id="nav-page-select-trigger"
737
- aria-haspopup="listbox"
738
- aria-expanded=${this._showPagePicker}
739
- aria-controls="nav-page-select-listbox"
740
- @click=${(e: Event) => {
741
- e.stopPropagation();
742
- this._showPagePicker = !this._showPagePicker;
743
- this._pageSearchQuery = "";
744
- if (this._showPagePicker) {
745
- setTimeout(() => {
746
- document.addEventListener("click", this.#closePagePicker);
747
- this.querySelector<HTMLInputElement>(
748
- "#nav-page-search",
749
- )?.focus();
750
- });
751
- } else {
752
- document.removeEventListener("click", this.#closePagePicker);
753
- }
754
- }}
624
+ class="nav-item-toggle"
625
+ @click=${() => this.#toggleEdit(item)}
626
+ aria-label=${this.labels.toggleEdit}
755
627
  >
756
- <span class="truncate">${this.labels.choosePage}</span>
757
628
  <svg
758
629
  xmlns="http://www.w3.org/2000/svg"
759
- width="24"
760
- height="24"
630
+ width="14"
631
+ height="14"
761
632
  viewBox="0 0 24 24"
762
633
  fill="none"
763
634
  stroke="currentColor"
764
635
  stroke-width="2"
765
636
  stroke-linecap="round"
766
637
  stroke-linejoin="round"
767
- class="text-muted-foreground opacity-50 shrink-0"
638
+ style="transition: transform 0.15s; ${isEditing
639
+ ? "transform: rotate(180deg);"
640
+ : ""}"
768
641
  >
769
- <path d="m7 15 5 5 5-5" />
770
- <path d="m7 9 5-5 5 5" />
642
+ <path d="m6 9 6 6 6-6" />
771
643
  </svg>
772
644
  </button>
773
- ${this._showPagePicker
774
- ? html`
775
- <div
776
- id="nav-page-select-popover"
777
- data-popover
778
- aria-hidden="false"
779
- class="w-full sm:w-[280px]"
780
- @click=${(e: Event) => e.stopPropagation()}
781
- >
782
- <header>
783
- <svg
784
- xmlns="http://www.w3.org/2000/svg"
785
- width="24"
786
- height="24"
787
- viewBox="0 0 24 24"
788
- fill="none"
789
- stroke="currentColor"
790
- stroke-width="2"
791
- stroke-linecap="round"
792
- stroke-linejoin="round"
793
- >
794
- <circle cx="11" cy="11" r="8" />
795
- <path d="m21 21-4.3-4.3" />
796
- </svg>
797
- <input
798
- type="text"
799
- id="nav-page-search"
800
- .value=${this._pageSearchQuery}
801
- placeholder=${this.labels.searchPages}
802
- autocomplete="off"
803
- autocorrect="off"
804
- spellcheck="false"
805
- aria-autocomplete="list"
806
- role="combobox"
807
- aria-expanded="true"
808
- aria-controls="nav-page-select-listbox"
809
- aria-labelledby="nav-page-select-trigger"
810
- @input=${(e: Event) => {
811
- this._pageSearchQuery = (
812
- e.target as HTMLInputElement
813
- ).value;
814
- }}
815
- />
816
- </header>
817
- <div
818
- role="listbox"
819
- id="nav-page-select-listbox"
820
- aria-orientation="vertical"
821
- aria-labelledby="nav-page-select-trigger"
822
- data-empty=${this.labels.noPagesFound}
823
- >
824
- ${filteredPages.length > 0
825
- ? html`<div class="max-h-64 overflow-y-auto scrollbar">
826
- ${filteredPages.map(
827
- (page) => html`
828
- <div
829
- role="option"
830
- data-value=${page.id}
831
- @click=${() => {
832
- this._showPagePicker = false;
833
- this._pageSearchQuery = "";
834
- document.removeEventListener(
835
- "click",
836
- this.#closePagePicker,
837
- );
838
- this.#handleAddPage(page);
839
- }}
840
- >
841
- ${page.title || page.slug}
842
- </div>
843
- `,
844
- )}
845
- </div>`
846
- : html`<div
847
- class="py-6 text-center text-sm text-muted-foreground"
848
- >
849
- ${this._availablePages.length === 0
850
- ? this.labels.allPagesInNav
851
- : this.labels.noPagesFound}
852
- </div>`}
853
- </div>
854
- </div>
855
- `
856
- : nothing}
857
645
  </div>
858
- </section>
646
+ ${this.#renderEditPanel(item)}
647
+ </div>
859
648
  `;
860
649
  }
861
650
 
@@ -1007,15 +796,20 @@ export class JantNavManager extends LitElement {
1007
796
  return html`
1008
797
  ${this.#renderPreview()}
1009
798
 
1010
- <div class="flex flex-col gap-3 mt-3">
1011
- <div class="flex items-center gap-3">
1012
- <label class="text-sm" for="nav-max-visible">
1013
- ${this.labels.maxVisibleLinks}
1014
- </label>
799
+ <div class="flex flex-col gap-4 mt-3">
800
+ <div class="flex items-start justify-between gap-4">
801
+ <div class="flex flex-col gap-0.5">
802
+ <label class="text-sm font-medium" for="nav-max-visible">
803
+ ${this.labels.maxVisibleLinks}
804
+ </label>
805
+ <p class="text-xs text-muted-foreground">
806
+ ${this.labels.maxVisibleLinksDescription}
807
+ </p>
808
+ </div>
1015
809
  <input
1016
810
  type="number"
1017
811
  id="nav-max-visible"
1018
- class="input w-16 h-8"
812
+ class="input w-16 h-8 shrink-0"
1019
813
  min="0"
1020
814
  max="5"
1021
815
  .value=${String(this.maxVisible)}
@@ -1025,15 +819,20 @@ export class JantNavManager extends LitElement {
1025
819
  }}
1026
820
  />
1027
821
  </div>
1028
- <div class="flex items-center gap-3">
1029
- <label class="text-sm" for="nav-home-view">
1030
- ${this.labels.useFeaturedAsDefault}
1031
- </label>
822
+ <div class="flex items-start justify-between gap-4">
823
+ <div class="flex flex-col gap-0.5">
824
+ <label class="text-sm font-medium" for="nav-home-view">
825
+ ${this.labels.useFeaturedAsDefault}
826
+ </label>
827
+ <p class="text-xs text-muted-foreground">
828
+ ${this.labels.useFeaturedAsDefaultDescription}
829
+ </p>
830
+ </div>
1032
831
  <input
1033
832
  type="checkbox"
1034
833
  role="switch"
1035
834
  id="nav-home-view"
1036
- class="input"
835
+ class="input shrink-0"
1037
836
  .checked=${this.homeDefaultView === "featured"}
1038
837
  @change=${(e: Event) => {
1039
838
  this.#handleHomeViewToggle(
@@ -1053,13 +852,13 @@ export class JantNavManager extends LitElement {
1053
852
  ${this.labels.emptyState}
1054
853
  </p>`
1055
854
  : html`
1056
- <div id="nav-items-list" class="flex flex-col divide-y">
855
+ <div id="nav-items-list" class="nav-items-list">
1057
856
  ${this._items.map((item) => this.#renderItem(item))}
1058
857
  </div>
1059
858
  `}
1060
859
  </section>
1061
860
 
1062
- ${this.#renderAddArea()} ${this.#renderSystemToggles()}
861
+ ${this.#renderAddLinkSection()} ${this.#renderSystemToggles()}
1063
862
  `;
1064
863
  }
1065
864
  }