@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
@@ -1,495 +0,0 @@
1
- /**
2
- * Compose Dialog
3
- *
4
- * Outer shell for the compose dialog: header with format switcher,
5
- * collection selector, action row, and attachment upload coordination.
6
- *
7
- * Light DOM only — BaseCoat and Tailwind classes apply directly.
8
- */
9
-
10
- import { LitElement, html, nothing } from "lit";
11
- import { classMap } from "lit/directives/class-map.js";
12
- import { unsafeHTML } from "lit/directives/unsafe-html.js";
13
- import type {
14
- ComposeFormat,
15
- ComposeLabels,
16
- ComposeCollection,
17
- ComposeSubmitDetail,
18
- } from "./compose-types.js";
19
- import type { JantComposeEditor } from "./jant-compose-editor.js";
20
-
21
- export class JantComposeDialog extends LitElement {
22
- static properties = {
23
- collections: { type: Array },
24
- labels: { type: Object },
25
- _format: { state: true },
26
- _status: { state: true },
27
- _loading: { state: true },
28
- _collectionIds: { state: true },
29
- _showCollection: { state: true },
30
- _showMoreMenu: { state: true },
31
- _collectionSearch: { state: true },
32
- };
33
-
34
- declare collections: ComposeCollection[];
35
- declare labels: ComposeLabels;
36
- declare _format: ComposeFormat;
37
- declare _status: "published" | "draft";
38
- declare _loading: boolean;
39
- declare _collectionIds: number[];
40
- declare _showCollection: boolean;
41
- declare _showMoreMenu: boolean;
42
- declare _collectionSearch: string;
43
-
44
- createRenderRoot() {
45
- this.innerHTML = "";
46
- return this;
47
- }
48
-
49
- constructor() {
50
- super();
51
- this.collections = [];
52
- this.labels = {} as ComposeLabels;
53
- this._format = "note";
54
- this._status = "published";
55
- this._loading = false;
56
- this._collectionIds = [];
57
- this._showCollection = false;
58
- this._showMoreMenu = false;
59
- this._collectionSearch = "";
60
- }
61
-
62
- private get _editor(): JantComposeEditor | null {
63
- return this.querySelector("jant-compose-editor");
64
- }
65
-
66
- reset() {
67
- this._format = "note";
68
- this._status = "published";
69
- this._loading = false;
70
- this._collectionIds = [];
71
- this._showCollection = false;
72
- this._showMoreMenu = false;
73
- this._collectionSearch = "";
74
- this._editor?.reset();
75
- }
76
-
77
- set loading(v: boolean) {
78
- this._loading = v;
79
- }
80
-
81
- private _closeDialog() {
82
- this.closest("dialog")?.close();
83
- }
84
-
85
- private _buildSubmitDetail(
86
- status: "published" | "draft",
87
- ): ComposeSubmitDetail | null {
88
- const editor = this._editor;
89
- if (!editor) return null;
90
-
91
- const editorData = editor.getData();
92
- const attachments = editorData.attachments ?? [];
93
-
94
- // Collect mediaIds from completed uploads
95
- const mediaIds = attachments
96
- .filter((a) => a.status === "done" && a.mediaId)
97
- .map((a) => a.mediaId as string);
98
-
99
- // Collect alt text keyed by mediaId
100
- const mediaAlts: Record<string, string> = {};
101
- for (const a of attachments) {
102
- if (a.mediaId && a.alt) {
103
- mediaAlts[a.mediaId] = a.alt;
104
- }
105
- }
106
-
107
- return {
108
- format: this._format,
109
- title: editorData.title,
110
- body: editorData.body,
111
- url: editorData.url,
112
- quoteText: editorData.quoteText,
113
- quoteAuthor: editorData.quoteAuthor,
114
- status,
115
- rating: editorData.rating,
116
- collectionIds: [...this._collectionIds],
117
- mediaIds,
118
- mediaAlts,
119
- attachedText: editorData.attachedText,
120
- };
121
- }
122
-
123
- private _submit(status: "published" | "draft") {
124
- if (this._loading) return;
125
- const editor = this._editor;
126
- if (!editor) return;
127
-
128
- const attachments = editor._attachments ?? [];
129
- const hasPending = attachments.some(
130
- (a) => a.status === "pending" || a.status === "uploading",
131
- );
132
-
133
- const detail = this._buildSubmitDetail(status);
134
- if (!detail) return;
135
-
136
- if (hasPending) {
137
- // Deferred submit: close dialog, let bridge finish uploads and post
138
- this.dispatchEvent(
139
- new CustomEvent("jant:compose-submit-deferred", {
140
- bubbles: true,
141
- detail: {
142
- ...detail,
143
- pendingAttachments: attachments.filter(
144
- (a) => a.status === "pending" || a.status === "uploading",
145
- ),
146
- },
147
- }),
148
- );
149
- this._closeDialog();
150
- // Prevent browser from restoring focus to the trigger button
151
- (document.activeElement as HTMLElement)?.blur();
152
- this.reset();
153
- } else {
154
- this.dispatchEvent(
155
- new CustomEvent("jant:compose-submit", {
156
- bubbles: true,
157
- detail,
158
- }),
159
- );
160
- }
161
- }
162
-
163
- private _toggleCollection(id: number) {
164
- if (this._collectionIds.includes(id)) {
165
- this._collectionIds = this._collectionIds.filter((cid) => cid !== id);
166
- } else {
167
- this._collectionIds = [...this._collectionIds, id];
168
- }
169
- }
170
-
171
- connectedCallback() {
172
- super.connectedCallback();
173
- this.addEventListener("keydown", this._handleKeydown);
174
- }
175
-
176
- disconnectedCallback() {
177
- super.disconnectedCallback();
178
- this.removeEventListener("keydown", this._handleKeydown);
179
- }
180
-
181
- private _handleKeydown = (e: Event) => {
182
- const ke = e as globalThis.KeyboardEvent;
183
- if ((ke.metaKey || ke.ctrlKey) && ke.key === "Enter") {
184
- e.preventDefault();
185
- this._submit("published");
186
- }
187
- };
188
-
189
- // ── Render helpers ────────────────────────────────────────────────
190
-
191
- private _renderHeader() {
192
- const formats: ComposeFormat[] = ["note", "link", "quote"];
193
- const formatLabels: Record<ComposeFormat, string> = {
194
- note: this.labels.note,
195
- link: this.labels.link,
196
- quote: this.labels.quote,
197
- };
198
-
199
- return html`
200
- <header class="compose-dialog-header">
201
- <button
202
- type="button"
203
- class="compose-dialog-cancel"
204
- @click=${() => this._closeDialog()}
205
- >
206
- ${this.labels.cancel}
207
- </button>
208
-
209
- <div class="compose-dialog-header-center">
210
- <div class="compose-segmented">
211
- <div
212
- class=${classMap({
213
- "compose-format-pill": true,
214
- "compose-format-pill-link": this._format === "link",
215
- "compose-format-pill-quote": this._format === "quote",
216
- })}
217
- ></div>
218
- ${formats.map(
219
- (f) => html`
220
- <button
221
- type="button"
222
- class=${classMap({
223
- "compose-segmented-item": true,
224
- "compose-segmented-item-active": this._format === f,
225
- })}
226
- @click=${() => {
227
- this._format = f;
228
- globalThis.requestAnimationFrame(() =>
229
- this._editor?.focusInput(),
230
- );
231
- }}
232
- >
233
- ${formatLabels[f]}
234
- </button>
235
- `,
236
- )}
237
- </div>
238
- </div>
239
-
240
- <div class="flex items-center gap-0.5 shrink-0">
241
- <button
242
- type="button"
243
- class="compose-dialog-header-btn"
244
- title=${this.labels.saveDraft}
245
- ?disabled=${this._loading}
246
- @click=${() => this._submit("draft")}
247
- >
248
- <svg
249
- class="icon-fine"
250
- width="18"
251
- height="18"
252
- viewBox="0 0 18 18"
253
- fill="none"
254
- stroke="currentColor"
255
- stroke-width="1.3"
256
- stroke-linecap="round"
257
- stroke-linejoin="round"
258
- >
259
- <path d="M14 2.5L15.5 4 7 12.5l-3 .5.5-3L14 2.5z" />
260
- <path d="M4 15h10" />
261
- </svg>
262
- </button>
263
-
264
- ${this._renderMoreMenu()}
265
- </div>
266
- </header>
267
- `;
268
- }
269
-
270
- private _renderMoreMenu() {
271
- return html`
272
- <div class="relative">
273
- ${this._showMoreMenu
274
- ? html`<div
275
- class="compose-dropdown-backdrop"
276
- @click=${() => {
277
- this._showMoreMenu = false;
278
- }}
279
- ></div>`
280
- : nothing}
281
- <button
282
- type="button"
283
- class="compose-dialog-header-btn"
284
- @click=${() => {
285
- this._showMoreMenu = !this._showMoreMenu;
286
- }}
287
- >
288
- <svg width="18" height="18" viewBox="0 0 18 18" fill="currentColor">
289
- <circle cx="4.5" cy="9" r="1.3" />
290
- <circle cx="9" cy="9" r="1.3" />
291
- <circle cx="13.5" cy="9" r="1.3" />
292
- </svg>
293
- </button>
294
- ${this._showMoreMenu
295
- ? html`
296
- <div class="compose-dropdown compose-dropdown-right">
297
- <button
298
- type="button"
299
- class="compose-dropdown-item"
300
- @click=${() => {
301
- this._submit("draft");
302
- this._showMoreMenu = false;
303
- }}
304
- >
305
- ${this.labels.saveAsDraft}
306
- </button>
307
- <div class="compose-dropdown-divider"></div>
308
- <button
309
- type="button"
310
- class="compose-dropdown-item compose-dropdown-item-danger"
311
- @click=${() => {
312
- this._closeDialog();
313
- this._showMoreMenu = false;
314
- }}
315
- >
316
- ${this.labels.discard}
317
- </button>
318
- </div>
319
- `
320
- : nothing}
321
- </div>
322
- `;
323
- }
324
-
325
- private _renderCollectionSelector() {
326
- if (!this.collections || this.collections.length === 0) {
327
- return html`<div class="flex-1"></div>`;
328
- }
329
-
330
- const search = this._collectionSearch.toLowerCase();
331
- const filtered = search
332
- ? this.collections.filter((c) => c.title.toLowerCase().includes(search))
333
- : this.collections;
334
- const selectedCount = this._collectionIds.length;
335
-
336
- return html`
337
- <div class="flex-1 min-w-0">
338
- ${this._showCollection
339
- ? html`<div
340
- class="compose-dropdown-backdrop"
341
- @click=${() => {
342
- this._showCollection = false;
343
- this._collectionSearch = "";
344
- }}
345
- ></div>`
346
- : nothing}
347
- <div class="select compose-collection-select">
348
- <button
349
- type="button"
350
- class="compose-collection-trigger"
351
- @click=${() => {
352
- this._showCollection = !this._showCollection;
353
- if (!this._showCollection) {
354
- this._collectionSearch = "";
355
- }
356
- }}
357
- >
358
- <svg
359
- width="14"
360
- height="14"
361
- viewBox="0 0 18 18"
362
- fill="none"
363
- stroke="currentColor"
364
- stroke-width="1.4"
365
- stroke-linecap="round"
366
- stroke-linejoin="round"
367
- class="shrink-0 icon-fine"
368
- >
369
- <rect x="3" y="5" width="12" height="10" rx="2" />
370
- <path d="M6 5V4a1 1 0 011-1h4a1 1 0 011 1v1" />
371
- </svg>
372
- ${selectedCount > 0
373
- ? html`<span class="badge compose-collection-badge"
374
- >${selectedCount}</span
375
- >`
376
- : html`<span>${this.labels.collection}</span>`}
377
- <svg
378
- width="10"
379
- height="10"
380
- viewBox="0 0 10 10"
381
- fill="none"
382
- stroke="currentColor"
383
- stroke-width="1.4"
384
- stroke-linecap="round"
385
- stroke-linejoin="round"
386
- class="shrink-0 opacity-50 icon-fine"
387
- >
388
- <path d="M3 4l2 2 2-2" />
389
- </svg>
390
- </button>
391
- <div
392
- data-popover
393
- data-side="top"
394
- aria-hidden=${this._showCollection ? "false" : "true"}
395
- >
396
- <header>
397
- <svg
398
- width="16"
399
- height="16"
400
- viewBox="0 0 24 24"
401
- fill="none"
402
- stroke="currentColor"
403
- stroke-width="2"
404
- stroke-linecap="round"
405
- stroke-linejoin="round"
406
- >
407
- <circle cx="11" cy="11" r="8" />
408
- <path d="m21 21-4.3-4.3" />
409
- </svg>
410
- <input
411
- type="text"
412
- role="combobox"
413
- placeholder=${this.labels.searchCollections}
414
- autocomplete="off"
415
- autocorrect="off"
416
- spellcheck="false"
417
- .value=${this._collectionSearch}
418
- @input=${(e: Event) => {
419
- this._collectionSearch = (e.target as HTMLInputElement).value;
420
- }}
421
- />
422
- </header>
423
- <div
424
- role="listbox"
425
- aria-multiselectable="true"
426
- data-empty=${this.labels.noCollections}
427
- >
428
- ${filtered.map(
429
- (col) => html`
430
- <div
431
- role="option"
432
- data-value=${col.id}
433
- aria-selected=${this._collectionIds.includes(col.id)
434
- ? "true"
435
- : nothing}
436
- @click=${() => this._toggleCollection(col.id)}
437
- >
438
- ${col.iconHtml
439
- ? html`<span
440
- class="inline-flex items-center justify-center w-4 h-4 shrink-0"
441
- >${unsafeHTML(col.iconHtml)}</span
442
- >`
443
- : nothing}
444
- ${col.title}
445
- </div>
446
- `,
447
- )}
448
- </div>
449
- </div>
450
- </div>
451
- </div>
452
- `;
453
- }
454
-
455
- render() {
456
- return html`
457
- <div class="compose-dialog-inner">
458
- ${this._renderHeader()}
459
- <jant-compose-editor
460
- .format=${this._format}
461
- .labels=${this.labels}
462
- ></jant-compose-editor>
463
-
464
- <div class="compose-action-row">
465
- ${this._renderCollectionSelector()}
466
- <button
467
- type="button"
468
- class="compose-post-btn"
469
- ?disabled=${this._loading}
470
- @click=${() => this._submit("published")}
471
- >
472
- ${this._loading
473
- ? html`<svg
474
- class="animate-spin size-4"
475
- xmlns="http://www.w3.org/2000/svg"
476
- viewBox="0 0 24 24"
477
- fill="none"
478
- stroke="currentColor"
479
- stroke-width="2"
480
- stroke-linecap="round"
481
- stroke-linejoin="round"
482
- role="status"
483
- >
484
- <path d="M21 12a9 9 0 1 1-6.219-8.56" />
485
- </svg>`
486
- : nothing}
487
- ${this.labels.post}
488
- </button>
489
- </div>
490
- </div>
491
- `;
492
- }
493
- }
494
-
495
- customElements.define("jant-compose-dialog", JantComposeDialog);