@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,11 +1,12 @@
1
1
  /**
2
- * Dashboard Post Form
2
+ * Post Form
3
3
  *
4
4
  * Light DOM Lit component that manages post create/edit form state.
5
5
  * Dispatches `jant:post-submit` for the bridge to handle networking.
6
6
  */
7
7
 
8
8
  import { LitElement } from "lit";
9
+ import type { Editor, JSONContent } from "@tiptap/core";
9
10
  import type {
10
11
  PostFormInitial,
11
12
  PostFormLabels,
@@ -14,17 +15,20 @@ import type {
14
15
  PostSubmitDetail,
15
16
  PostFormat,
16
17
  PostStatus,
18
+ PostVisibility,
17
19
  } from "./post-form-types.js";
18
20
  import { renderPostForm } from "./post-form-template.js";
21
+ import { createTiptapEditor } from "../tiptap/create-editor.js";
19
22
 
20
23
  const DEFAULT_INITIAL: PostFormInitial = {
21
24
  format: "note",
22
25
  title: "",
26
+ slug: "",
23
27
  body: "",
24
28
  url: "",
25
29
  quoteText: "",
26
30
  status: "published",
27
- featured: false,
31
+ visibility: "public",
28
32
  pinned: false,
29
33
  rating: 0,
30
34
  collectionIds: [],
@@ -38,6 +42,9 @@ const EMPTY_LABELS: PostFormLabels = {
38
42
  quoteOption: "",
39
43
  titleLabel: "",
40
44
  titlePlaceholder: "",
45
+ slugLabel: "",
46
+ slugPlaceholder: "",
47
+ slugHelp: "",
41
48
  bodyLabel: "",
42
49
  bodyPlaceholder: "",
43
50
  urlLabel: "",
@@ -51,7 +58,9 @@ const EMPTY_LABELS: PostFormLabels = {
51
58
  statusLabel: "",
52
59
  statusPublished: "",
53
60
  statusDraft: "",
54
- featuredLabel: "",
61
+ visibilityLabel: "",
62
+ visibilityPublic: "",
63
+ visibilityUnlisted: "",
55
64
  pinnedLabel: "",
56
65
  collectionsLabel: "",
57
66
  submitLabel: "",
@@ -61,6 +70,7 @@ const EMPTY_LABELS: PostFormLabels = {
61
70
  mediaDialogLoading: "",
62
71
  submitSuccessMessage: "",
63
72
  submitErrorMessage: "",
73
+ draftFallbackMessage: "",
64
74
  };
65
75
 
66
76
  function parseJson<T>(value: unknown, fallback: T): T {
@@ -86,14 +96,17 @@ export class JantPostForm extends LitElement {
86
96
  action: { type: String },
87
97
  cancelHref: { type: String, attribute: "cancel-href" },
88
98
  mediaPickerUrl: { type: String, attribute: "media-picker-url" },
99
+ siteUrl: { type: String, attribute: "site-url" },
89
100
  isEdit: { type: Boolean, attribute: "is-edit" },
90
101
  _format: { state: true },
91
102
  _title: { state: true },
103
+ _slug: { state: true },
104
+ _slugManuallyEdited: { state: true },
92
105
  _body: { state: true },
93
106
  _url: { state: true },
94
107
  _quoteText: { state: true },
95
108
  _status: { state: true },
96
- _featured: { state: true },
109
+ _visibility: { state: true },
97
110
  _pinned: { state: true },
98
111
  _rating: { state: true },
99
112
  _collectionIds: { state: true },
@@ -108,21 +121,33 @@ export class JantPostForm extends LitElement {
108
121
  declare action: string;
109
122
  declare cancelHref: string;
110
123
  declare mediaPickerUrl: string;
124
+ declare siteUrl: string;
111
125
  declare isEdit: boolean;
112
126
  declare _format: PostFormat;
113
127
  declare _title: string;
128
+ declare _slug: string;
129
+ declare _slugManuallyEdited: boolean;
114
130
  declare _body: string;
115
131
  declare _url: string;
116
132
  declare _quoteText: string;
117
133
  declare _status: PostStatus;
118
- declare _featured: boolean;
134
+ declare _visibility: PostVisibility;
119
135
  declare _pinned: boolean;
120
136
  declare _rating: number;
121
137
  declare _collectionIds: number[];
122
138
  declare _mediaIds: string[];
123
139
  declare _loading: boolean;
124
140
 
141
+ _editor: Editor | null = null;
142
+ _bodyJson: JSONContent | null = null;
125
143
  #initialized = false;
144
+ #dirty = false;
145
+ #onBeforeUnload = (e: globalThis.BeforeUnloadEvent) => {
146
+ if (this.#dirty) {
147
+ e.preventDefault();
148
+ e.returnValue = "";
149
+ }
150
+ };
126
151
 
127
152
  createRenderRoot() {
128
153
  this.innerHTML = "";
@@ -136,16 +161,19 @@ export class JantPostForm extends LitElement {
136
161
  this.collections = [];
137
162
  this.media = [];
138
163
  this.action = "";
139
- this.cancelHref = "/dash/posts";
140
- this.mediaPickerUrl = "/dash/media/picker";
164
+ this.cancelHref = "/";
165
+ this.mediaPickerUrl = "";
166
+ this.siteUrl = "";
141
167
  this.isEdit = false;
142
168
  this._format = "note";
143
169
  this._title = "";
170
+ this._slug = "";
171
+ this._slugManuallyEdited = false;
144
172
  this._body = "";
145
173
  this._url = "";
146
174
  this._quoteText = "";
147
175
  this._status = "published";
148
- this._featured = false;
176
+ this._visibility = "public";
149
177
  this._pinned = false;
150
178
  this._rating = 0;
151
179
  this._collectionIds = [];
@@ -195,8 +223,16 @@ export class JantPostForm extends LitElement {
195
223
  return this.querySelector("#post-media-picker");
196
224
  }
197
225
 
226
+ connectedCallback() {
227
+ super.connectedCallback();
228
+ window.addEventListener("beforeunload", this.#onBeforeUnload);
229
+ }
230
+
198
231
  disconnectedCallback() {
199
232
  super.disconnectedCallback();
233
+ window.removeEventListener("beforeunload", this.#onBeforeUnload);
234
+ this._editor?.destroy();
235
+ this._editor = null;
200
236
  this.closeMediaPicker();
201
237
  }
202
238
 
@@ -204,23 +240,110 @@ export class JantPostForm extends LitElement {
204
240
  const init = this.initial ?? DEFAULT_INITIAL;
205
241
  this._format = init.format ?? "note";
206
242
  this._title = init.title ?? "";
243
+ this._slug = init.slug ?? "";
244
+ this._slugManuallyEdited = Boolean(init.slug);
207
245
  this._body = init.body ?? "";
208
246
  this._url = init.url ?? "";
209
247
  this._quoteText = init.quoteText ?? "";
210
248
  this._status = init.status ?? "published";
211
- this._featured = !!init.featured;
249
+ this._visibility = init.visibility ?? "public";
212
250
  this._pinned = !!init.pinned;
213
251
  this._rating = init.rating ?? 0;
214
252
  this._collectionIds = [...(init.collectionIds ?? [])];
215
253
  this._mediaIds = [...(init.mediaIds ?? [])];
216
254
  this.#initialized = true;
255
+
256
+ // Parse body as Tiptap JSON if it looks like JSON
257
+ if (this._body && this._body.startsWith("{")) {
258
+ try {
259
+ this._bodyJson = JSON.parse(this._body) as JSONContent;
260
+ } catch {
261
+ this._bodyJson = null;
262
+ }
263
+ } else {
264
+ this._bodyJson = null;
265
+ }
266
+ }
267
+
268
+ initEditor() {
269
+ if (this._editor) return;
270
+ const container = this.querySelector<HTMLElement>(".post-form-tiptap-body");
271
+ if (!container) return;
272
+
273
+ this._editor = createTiptapEditor({
274
+ element: container,
275
+ placeholder: this.labels.bodyPlaceholder ?? "Write something…",
276
+ content: this._bodyJson,
277
+ onUpdate: (json) => {
278
+ this._bodyJson = json;
279
+ this._body = JSON.stringify(json);
280
+ },
281
+ });
282
+ }
283
+
284
+ /** Content-relevant properties for dirty tracking */
285
+ static #CONTENT_PROPS = new Set([
286
+ "_format",
287
+ "_title",
288
+ "_slug",
289
+ "_body",
290
+ "_url",
291
+ "_quoteText",
292
+ "_status",
293
+ "_visibility",
294
+ "_pinned",
295
+ "_rating",
296
+ "_collectionIds",
297
+ "_mediaIds",
298
+ ]);
299
+
300
+ protected updated(changed: Map<string, unknown>) {
301
+ super.updated(changed);
302
+ if (!this._editor) {
303
+ this.initEditor();
304
+ }
305
+
306
+ // Mark dirty when user changes content after initial load
307
+ if (this.#initialized && !changed.has("initial")) {
308
+ for (const key of changed.keys()) {
309
+ if (JantPostForm.#CONTENT_PROPS.has(key as string)) {
310
+ this.#dirty = true;
311
+ break;
312
+ }
313
+ }
314
+ }
315
+ }
316
+
317
+ clearDirty() {
318
+ this.#dirty = false;
217
319
  }
218
320
 
219
- handleInput(field: "_title" | "_body" | "_url" | "_quoteText", e: Event) {
321
+ handleInput(
322
+ field: "_title" | "_slug" | "_body" | "_url" | "_quoteText",
323
+ e: Event,
324
+ ) {
220
325
  const target = e.target as HTMLInputElement | HTMLTextAreaElement;
221
326
  (this as unknown as Record<string, string>)[field] = target.value;
222
327
  }
223
328
 
329
+ handleTitleInput(e: Event) {
330
+ const target = e.target as HTMLInputElement;
331
+ this._title = target.value;
332
+ // Auto-generate slug from title if user hasn't manually edited it
333
+ if (!this._slugManuallyEdited) {
334
+ this._slug = target.value
335
+ .toLowerCase()
336
+ .replace(/[^a-z0-9]+/g, "-")
337
+ .replace(/^-|-$/g, "");
338
+ }
339
+ }
340
+
341
+ handleSlugInput(e: Event) {
342
+ const target = e.target as HTMLInputElement;
343
+ this._slug = target.value;
344
+ this._slugManuallyEdited = true;
345
+ }
346
+
224
347
  toggleCollection(id: number) {
225
348
  this._collectionIds = this._collectionIds.includes(id)
226
349
  ? this._collectionIds.filter((cid) => cid !== id)
@@ -253,15 +376,21 @@ export class JantPostForm extends LitElement {
253
376
  handleSubmit(e: Event) {
254
377
  e.preventDefault();
255
378
  if (this._loading || !this.action) return;
379
+ // Use Tiptap JSON for body
380
+ const bodyValue = this._bodyJson
381
+ ? JSON.stringify(this._bodyJson)
382
+ : this._body;
383
+
256
384
  const detail: PostSubmitDetail = {
257
385
  endpoint: this.action,
258
386
  isEdit: this.isEdit,
259
387
  data: {
260
388
  format: this._format,
261
389
  title: this._title.trim(),
262
- body: this._body,
390
+ slug: this._slug.trim() || undefined,
391
+ body: bodyValue,
263
392
  status: this._status,
264
- featured: this._featured,
393
+ visibility: this._visibility,
265
394
  pinned: this._pinned,
266
395
  url: this._url.trim(),
267
396
  quoteText: this._quoteText.trim(),