@jant/core 0.3.36 → 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 (271) 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/url-FWFqPJPb.js +1 -0
  6. package/dist/client/client.css +1 -1
  7. package/dist/client/client.js +4012 -3276
  8. package/dist/index.js +10285 -5809
  9. package/package.json +11 -3
  10. package/src/__tests__/helpers/app.ts +9 -9
  11. package/src/__tests__/helpers/db.ts +91 -93
  12. package/src/app.tsx +157 -27
  13. package/src/auth.ts +20 -2
  14. package/src/client/archive-nav.js +187 -0
  15. package/src/client/audio-player.ts +478 -0
  16. package/src/client/audio-processor.ts +84 -0
  17. package/src/client/avatar-upload.ts +3 -2
  18. package/src/client/components/__tests__/jant-compose-dialog.test.ts +645 -49
  19. package/src/client/components/__tests__/jant-compose-editor.test.ts +208 -16
  20. package/src/client/components/__tests__/jant-post-form.test.ts +19 -9
  21. package/src/client/components/__tests__/jant-settings-avatar.test.ts +2 -2
  22. package/src/client/components/__tests__/jant-settings-general.test.ts +3 -3
  23. package/src/client/components/collection-sidebar-types.ts +7 -9
  24. package/src/client/components/compose-types.ts +101 -4
  25. package/src/client/components/jant-collection-form.ts +43 -7
  26. package/src/client/components/jant-collection-sidebar.ts +88 -84
  27. package/src/client/components/jant-compose-dialog.ts +1655 -219
  28. package/src/client/components/jant-compose-editor.ts +732 -168
  29. package/src/client/components/jant-compose-fullscreen.ts +23 -78
  30. package/src/client/components/jant-media-lightbox.ts +2 -0
  31. package/src/client/components/jant-nav-manager.ts +24 -284
  32. package/src/client/components/jant-post-form.ts +89 -9
  33. package/src/client/components/jant-post-menu.ts +1019 -0
  34. package/src/client/components/jant-settings-avatar.ts +3 -3
  35. package/src/client/components/jant-settings-general.ts +38 -4
  36. package/src/client/components/jant-text-preview.ts +232 -0
  37. package/src/client/components/nav-manager-types.ts +4 -19
  38. package/src/client/components/post-form-template.ts +107 -12
  39. package/src/client/components/post-form-types.ts +11 -4
  40. package/src/client/compose-bridge.ts +410 -109
  41. package/src/client/image-processor.ts +26 -8
  42. package/src/client/media-metadata.ts +247 -0
  43. package/src/client/multipart-upload.ts +160 -0
  44. package/src/client/post-form-bridge.ts +52 -1
  45. package/src/client/settings-bridge.ts +0 -12
  46. package/src/client/thread-context.ts +140 -0
  47. package/src/client/tiptap/create-editor.ts +46 -0
  48. package/src/client/tiptap/extensions.ts +5 -0
  49. package/src/client/tiptap/image-node.ts +2 -8
  50. package/src/client/tiptap/paste-image.ts +2 -13
  51. package/src/client/tiptap/slash-commands.ts +173 -63
  52. package/src/client/toast.ts +101 -3
  53. package/src/client/types/sortablejs.d.ts +15 -0
  54. package/src/client/upload-with-metadata.ts +54 -0
  55. package/src/client/video-processor.ts +207 -0
  56. package/src/client.ts +5 -2
  57. package/src/db/__tests__/migrations.test.ts +118 -0
  58. package/src/db/index.ts +52 -0
  59. package/src/db/migrations/0000_baseline.sql +269 -0
  60. package/src/db/migrations/0001_fts_setup.sql +31 -0
  61. package/src/db/migrations/meta/0000_snapshot.json +703 -119
  62. package/src/db/migrations/meta/0001_snapshot.json +1337 -0
  63. package/src/db/migrations/meta/_journal.json +4 -39
  64. package/src/db/schema.ts +409 -145
  65. package/src/i18n/__tests__/detect.test.ts +115 -0
  66. package/src/i18n/context.tsx +2 -2
  67. package/src/i18n/detect.ts +85 -1
  68. package/src/i18n/i18n.ts +1 -1
  69. package/src/i18n/index.ts +2 -1
  70. package/src/i18n/locales/en.po +487 -1217
  71. package/src/i18n/locales/en.ts +1 -1
  72. package/src/i18n/locales/zh-Hans.po +613 -996
  73. package/src/i18n/locales/zh-Hans.ts +1 -1
  74. package/src/i18n/locales/zh-Hant.po +624 -1007
  75. package/src/i18n/locales/zh-Hant.ts +1 -1
  76. package/src/i18n/middleware.ts +6 -0
  77. package/src/index.ts +5 -7
  78. package/src/lib/__tests__/blurhash-placeholder.test.ts +75 -0
  79. package/src/lib/__tests__/constants.test.ts +0 -1
  80. package/src/lib/__tests__/markdown-to-tiptap.test.ts +358 -0
  81. package/src/lib/__tests__/nanoid.test.ts +26 -0
  82. package/src/lib/__tests__/schemas.test.ts +181 -63
  83. package/src/lib/__tests__/slug.test.ts +126 -0
  84. package/src/lib/__tests__/sse.test.ts +6 -6
  85. package/src/lib/__tests__/summary.test.ts +264 -0
  86. package/src/lib/__tests__/theme.test.ts +1 -1
  87. package/src/lib/__tests__/timeline.test.ts +33 -30
  88. package/src/lib/__tests__/tiptap-to-markdown.test.ts +346 -0
  89. package/src/lib/__tests__/view.test.ts +141 -66
  90. package/src/lib/blurhash-placeholder.ts +102 -0
  91. package/src/lib/constants.ts +3 -1
  92. package/src/lib/emoji-catalog.ts +885 -68
  93. package/src/lib/errors.ts +11 -8
  94. package/src/lib/feed.ts +78 -32
  95. package/src/lib/html.ts +2 -1
  96. package/src/lib/icon-catalog.ts +5033 -1
  97. package/src/lib/icons.ts +3 -2
  98. package/src/lib/index.ts +0 -1
  99. package/src/lib/markdown-to-tiptap.ts +286 -0
  100. package/src/lib/media-helpers.ts +12 -3
  101. package/src/lib/nanoid.ts +29 -0
  102. package/src/lib/navigation.ts +1 -1
  103. package/src/lib/render.tsx +20 -2
  104. package/src/lib/resolve-config.ts +6 -2
  105. package/src/lib/schemas.ts +224 -55
  106. package/src/lib/search-snippet.ts +34 -0
  107. package/src/lib/slug.ts +96 -0
  108. package/src/lib/sse.ts +6 -6
  109. package/src/lib/storage.ts +115 -7
  110. package/src/lib/summary.ts +66 -0
  111. package/src/lib/theme.ts +11 -8
  112. package/src/lib/timeline.ts +74 -34
  113. package/src/lib/tiptap-render.ts +5 -10
  114. package/src/lib/tiptap-to-markdown.ts +305 -0
  115. package/src/lib/upload.ts +190 -29
  116. package/src/lib/url.ts +31 -0
  117. package/src/lib/view.ts +204 -37
  118. package/src/middleware/__tests__/auth.test.ts +191 -11
  119. package/src/middleware/__tests__/onboarding.test.ts +12 -10
  120. package/src/middleware/auth.ts +63 -9
  121. package/src/middleware/onboarding.ts +1 -1
  122. package/src/middleware/secure-headers.ts +40 -0
  123. package/src/preset.css +45 -2
  124. package/src/routes/__tests__/compose.test.ts +17 -24
  125. package/src/routes/api/__tests__/collections.test.ts +109 -61
  126. package/src/routes/api/__tests__/nav-items.test.ts +46 -29
  127. package/src/routes/api/__tests__/posts.test.ts +132 -68
  128. package/src/routes/api/__tests__/search.test.ts +15 -2
  129. package/src/routes/api/__tests__/upload-multipart.test.ts +534 -0
  130. package/src/routes/api/collections.ts +51 -42
  131. package/src/routes/api/custom-urls.ts +80 -0
  132. package/src/routes/api/export.ts +31 -0
  133. package/src/routes/api/nav-items.ts +23 -19
  134. package/src/routes/api/posts.ts +43 -39
  135. package/src/routes/api/search.ts +3 -4
  136. package/src/routes/api/upload-multipart.ts +245 -0
  137. package/src/routes/api/upload.ts +85 -19
  138. package/src/routes/auth/__tests__/setup.test.ts +20 -60
  139. package/src/routes/auth/setup.tsx +26 -33
  140. package/src/routes/auth/signin.tsx +3 -7
  141. package/src/routes/compose.tsx +10 -55
  142. package/src/routes/dash/__tests__/settings-avatar.test.ts +1 -1
  143. package/src/routes/dash/custom-urls.tsx +414 -0
  144. package/src/routes/dash/settings.tsx +304 -232
  145. package/src/routes/feed/__tests__/rss.test.ts +27 -28
  146. package/src/routes/feed/rss.ts +6 -4
  147. package/src/routes/feed/sitemap.ts +2 -12
  148. package/src/routes/pages/__tests__/collections.test.ts +5 -6
  149. package/src/routes/pages/__tests__/featured.test.ts +41 -22
  150. package/src/routes/pages/archive.tsx +175 -39
  151. package/src/routes/pages/collection.tsx +22 -10
  152. package/src/routes/pages/collections.tsx +3 -3
  153. package/src/routes/pages/featured.tsx +28 -4
  154. package/src/routes/pages/home.tsx +16 -15
  155. package/src/routes/pages/latest.tsx +1 -11
  156. package/src/routes/pages/new.tsx +39 -0
  157. package/src/routes/pages/page.tsx +95 -49
  158. package/src/routes/pages/search.tsx +1 -1
  159. package/src/services/__tests__/api-token.test.ts +135 -0
  160. package/src/services/__tests__/collection.test.ts +275 -227
  161. package/src/services/__tests__/custom-url.test.ts +213 -0
  162. package/src/services/__tests__/media.test.ts +162 -22
  163. package/src/services/__tests__/navigation.test.ts +109 -68
  164. package/src/services/__tests__/post-timeline.test.ts +205 -32
  165. package/src/services/__tests__/post.test.ts +713 -234
  166. package/src/services/__tests__/search.test.ts +67 -10
  167. package/src/services/api-token.ts +166 -0
  168. package/src/services/auth.ts +17 -2
  169. package/src/services/collection.ts +397 -131
  170. package/src/services/custom-url.ts +188 -0
  171. package/src/services/export.ts +802 -0
  172. package/src/services/index.ts +26 -19
  173. package/src/services/media.ts +100 -22
  174. package/src/services/navigation.ts +158 -47
  175. package/src/services/path.ts +339 -0
  176. package/src/services/post.ts +687 -154
  177. package/src/services/search.ts +160 -75
  178. package/src/styles/components.css +58 -7
  179. package/src/styles/tokens.css +84 -6
  180. package/src/styles/ui.css +2964 -457
  181. package/src/types/bindings.ts +4 -1
  182. package/src/types/config.ts +12 -4
  183. package/src/types/constants.ts +15 -3
  184. package/src/types/entities.ts +74 -35
  185. package/src/types/operations.ts +11 -24
  186. package/src/types/props.ts +51 -16
  187. package/src/types/views.ts +45 -22
  188. package/src/ui/color-themes.ts +133 -23
  189. package/src/ui/compose/ComposeDialog.tsx +239 -17
  190. package/src/ui/compose/ComposePrompt.tsx +1 -1
  191. package/src/ui/dash/CrudPageHeader.tsx +1 -1
  192. package/src/ui/dash/ListItemRow.tsx +1 -1
  193. package/src/ui/dash/StatusBadge.tsx +3 -1
  194. package/src/ui/dash/appearance/AdvancedContent.tsx +22 -1
  195. package/src/ui/dash/appearance/ColorThemeContent.tsx +22 -2
  196. package/src/ui/dash/appearance/FontThemeContent.tsx +1 -1
  197. package/src/ui/dash/appearance/NavigationContent.tsx +5 -45
  198. package/src/ui/dash/index.ts +0 -3
  199. package/src/ui/dash/settings/AccountContent.tsx +3 -57
  200. package/src/ui/dash/settings/AccountMenuContent.tsx +147 -0
  201. package/src/ui/dash/settings/ApiTokensContent.tsx +232 -0
  202. package/src/ui/dash/settings/AvatarContent.tsx +8 -0
  203. package/src/ui/dash/settings/SessionsContent.tsx +159 -0
  204. package/src/ui/dash/settings/SettingsRootContent.tsx +45 -15
  205. package/src/ui/feed/LinkCard.tsx +89 -40
  206. package/src/ui/feed/NoteCard.tsx +39 -25
  207. package/src/ui/feed/PostStatusBadges.tsx +67 -0
  208. package/src/ui/feed/QuoteCard.tsx +38 -23
  209. package/src/ui/feed/ThreadPreview.tsx +90 -26
  210. package/src/ui/feed/TimelineFeed.tsx +3 -2
  211. package/src/ui/feed/TimelineItem.tsx +15 -6
  212. package/src/ui/feed/__tests__/thread-preview.test.ts +107 -0
  213. package/src/ui/feed/thread-preview-state.ts +61 -0
  214. package/src/ui/font-themes.ts +2 -2
  215. package/src/ui/layouts/BaseLayout.tsx +2 -2
  216. package/src/ui/layouts/SiteLayout.tsx +105 -92
  217. package/src/ui/pages/ArchivePage.tsx +923 -98
  218. package/src/ui/pages/ComposePage.tsx +54 -0
  219. package/src/ui/pages/PostPage.tsx +30 -45
  220. package/src/ui/pages/SearchPage.tsx +181 -37
  221. package/src/ui/shared/AdminBreadcrumb.tsx +29 -0
  222. package/src/ui/shared/CollectionsSidebar.tsx +47 -37
  223. package/src/ui/shared/MediaGallery.tsx +445 -149
  224. package/src/ui/shared/PostFooter.tsx +204 -0
  225. package/src/ui/shared/StarRating.tsx +27 -0
  226. package/src/ui/shared/__tests__/format-chars.test.ts +35 -0
  227. package/src/ui/shared/index.ts +0 -1
  228. package/dist/client/assets/url-8Dj-5CLW.js +0 -1
  229. package/src/client/media-upload.ts +0 -161
  230. package/src/client/page-slug-bridge.ts +0 -42
  231. package/src/db/migrations/0000_square_wallflower.sql +0 -118
  232. package/src/db/migrations/0001_add_search_fts.sql +0 -34
  233. package/src/db/migrations/0002_add_media_attachments.sql +0 -3
  234. package/src/db/migrations/0003_add_navigation_links.sql +0 -8
  235. package/src/db/migrations/0004_add_storage_provider.sql +0 -3
  236. package/src/db/migrations/0005_v2_schema_migration.sql +0 -268
  237. package/src/db/migrations/0006_rename_slug_to_path.sql +0 -5
  238. package/src/db/migrations/0007_post_collections_m2m.sql +0 -94
  239. package/src/db/migrations/0008_add_collection_dividers.sql +0 -8
  240. package/src/db/migrations/0009_drop_collection_show_divider.sql +0 -2
  241. package/src/db/migrations/0010_add_performance_indexes.sql +0 -16
  242. package/src/db/migrations/0011_add_path_registry.sql +0 -23
  243. package/src/db/migrations/0012_add_tiptap_columns.sql +0 -2
  244. package/src/db/migrations/0013_replace_featured_with_visibility.sql +0 -8
  245. package/src/db/migrations/meta/0003_snapshot.json +0 -821
  246. package/src/lib/__tests__/sqid.test.ts +0 -65
  247. package/src/lib/sqid.ts +0 -79
  248. package/src/routes/api/__tests__/pages.test.ts +0 -218
  249. package/src/routes/api/pages.ts +0 -73
  250. package/src/routes/dash/__tests__/pages.test.ts +0 -226
  251. package/src/routes/dash/index.tsx +0 -109
  252. package/src/routes/dash/media.tsx +0 -135
  253. package/src/routes/dash/pages.tsx +0 -245
  254. package/src/routes/dash/posts.tsx +0 -338
  255. package/src/routes/dash/redirects.tsx +0 -263
  256. package/src/routes/pages/post.tsx +0 -59
  257. package/src/services/__tests__/page.test.ts +0 -298
  258. package/src/services/__tests__/path-registry.test.ts +0 -165
  259. package/src/services/__tests__/redirect.test.ts +0 -159
  260. package/src/services/page.ts +0 -216
  261. package/src/services/path-registry.ts +0 -160
  262. package/src/services/redirect.ts +0 -97
  263. package/src/ui/dash/PageForm.tsx +0 -187
  264. package/src/ui/dash/PostList.tsx +0 -95
  265. package/src/ui/dash/media/MediaListContent.tsx +0 -206
  266. package/src/ui/dash/media/ViewMediaContent.tsx +0 -208
  267. package/src/ui/dash/pages/PagesContent.tsx +0 -75
  268. package/src/ui/dash/posts/PostForm.tsx +0 -260
  269. package/src/ui/layouts/DashLayout.tsx +0 -247
  270. package/src/ui/pages/SinglePage.tsx +0 -23
  271. package/src/ui/shared/ThreadView.tsx +0 -136
@@ -1,161 +0,0 @@
1
- /**
2
- * Client-side Media Upload Handler
3
- *
4
- * Handles file upload flow:
5
- * 1. User selects file via [data-media-upload] input
6
- * 2. Creates placeholder in grid with spinner
7
- * 3. Processes image via ImageProcessor (resize/convert to WebP)
8
- * 4. Sets processed file on hidden Datastar form via DataTransfer API
9
- * 5. Triggers form.requestSubmit() — Datastar handles upload + SSE response
10
- */
11
-
12
- import { ImageProcessor } from "./image-processor.js";
13
- import { validateUploadFile } from "../lib/upload.js";
14
- import { showToast } from "./toast.js";
15
-
16
- /**
17
- * Format file size for display
18
- */
19
- function formatFileSize(bytes: number): string {
20
- if (bytes < 1024) return `${bytes} B`;
21
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
22
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
23
- }
24
-
25
- /**
26
- * Ensure the media grid exists, removing empty state if needed
27
- */
28
- function ensureGridExists(): HTMLElement {
29
- let grid = document.getElementById("media-grid");
30
- if (grid) return grid;
31
-
32
- document.getElementById("empty-state")?.remove();
33
-
34
- grid = document.createElement("div");
35
- grid.id = "media-grid";
36
- grid.className =
37
- "grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4";
38
- document.getElementById("media-content")?.appendChild(grid);
39
- return grid;
40
- }
41
-
42
- /**
43
- * Create a placeholder card with spinner in the media grid
44
- */
45
- function createPlaceholder(
46
- fileName: string,
47
- fileSize: number,
48
- statusText: string,
49
- ): HTMLElement {
50
- const placeholder = document.createElement("div");
51
- placeholder.id = "upload-placeholder";
52
- placeholder.className = "group relative";
53
- placeholder.innerHTML = `
54
- <div class="aspect-square bg-muted rounded-lg overflow-hidden border flex items-center justify-center">
55
- <div class="text-center px-2">
56
- <svg class="animate-spin h-6 w-6 text-muted-foreground mx-auto mb-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
57
- <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
58
- <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
59
- </svg>
60
- <span id="upload-status" class="text-xs text-muted-foreground">${statusText}</span>
61
- </div>
62
- </div>
63
- <div class="mt-2 text-xs truncate" title="${fileName}">${fileName}</div>
64
- <div class="text-xs text-muted-foreground">${formatFileSize(fileSize)}</div>
65
- `;
66
- return placeholder;
67
- }
68
-
69
- /**
70
- * Replace placeholder content with an error message
71
- */
72
- function showPlaceholderError(
73
- placeholder: HTMLElement,
74
- fileName: string,
75
- errorMessage: string,
76
- ): void {
77
- placeholder.innerHTML = `
78
- <div class="aspect-square bg-destructive/10 rounded-lg overflow-hidden border border-destructive flex items-center justify-center">
79
- <div class="text-center px-2">
80
- <span class="text-xs text-destructive">${errorMessage}</span>
81
- </div>
82
- </div>
83
- <div class="mt-2 text-xs truncate text-destructive">${fileName}</div>
84
- <button type="button" class="text-xs text-muted-foreground hover:underline" onclick="this.closest('.group').remove()">Remove</button>
85
- `;
86
- }
87
-
88
- /**
89
- * Handle the upload flow for a selected file
90
- */
91
- async function handleUpload(
92
- input: HTMLInputElement,
93
- file: File,
94
- ): Promise<void> {
95
- const maxFileSizeMB = parseInt(input.dataset.maxFileSize || "500", 10) || 500;
96
- const processingText = input.dataset.textProcessing || "Processing...";
97
- const uploadingText = input.dataset.textUploading || "Uploading...";
98
- const errorText =
99
- input.dataset.textError || "Upload failed. Please try again.";
100
-
101
- // Validate before creating placeholder — reject immediately with toast
102
- const validationError = validateUploadFile(file, { maxFileSizeMB });
103
- if (validationError) {
104
- showToast(validationError, "error");
105
- input.value = "";
106
- return;
107
- }
108
-
109
- const grid = ensureGridExists();
110
- const placeholder = createPlaceholder(file.name, file.size, processingText);
111
- grid.prepend(placeholder);
112
-
113
- try {
114
- // Process images client-side (resize, convert to WebP); upload non-images as-is
115
- const toUpload = file.type.startsWith("image/")
116
- ? await ImageProcessor.processToFile(file)
117
- : file;
118
-
119
- // Update status
120
- const statusEl = document.getElementById("upload-status");
121
- if (statusEl) statusEl.textContent = uploadingText;
122
-
123
- // Set processed file on hidden form input via DataTransfer API
124
- const formInput = document.getElementById(
125
- "upload-file-input",
126
- ) as HTMLInputElement | null;
127
- const form = document.getElementById(
128
- "upload-form",
129
- ) as HTMLFormElement | null;
130
- if (!formInput || !form) throw new Error("Upload form not found");
131
-
132
- const dt = new DataTransfer();
133
- dt.items.add(toUpload);
134
- formInput.files = dt.files;
135
-
136
- // Trigger Datastar-intercepted form submission
137
- form.requestSubmit();
138
- } catch (err) {
139
- const message = err instanceof Error ? err.message : errorText;
140
- showPlaceholderError(placeholder, file.name, message);
141
- }
142
-
143
- // Reset file input so the same file can be re-selected
144
- input.value = "";
145
- }
146
-
147
- /**
148
- * Initialize media upload via event delegation
149
- */
150
- function initMediaUpload(): void {
151
- document.addEventListener("change", (e) => {
152
- const input = (e.target as HTMLElement).closest(
153
- "[data-media-upload]",
154
- ) as HTMLInputElement | null;
155
- if (!input?.files?.[0]) return;
156
-
157
- handleUpload(input, input.files[0]);
158
- });
159
- }
160
-
161
- initMediaUpload();
@@ -1,42 +0,0 @@
1
- /**
2
- * Page Slug Bridge
3
- *
4
- * Auto-generates a slug from the page title in create mode.
5
- * Listens for `input` events on the title field inside `[data-page-form]`
6
- * and writes the slugified value into the slug input, dispatching an
7
- * `input` event so Datastar picks up the signal change.
8
- *
9
- * Skipped in edit mode (detected via `data-page-edit` attribute).
10
- */
11
-
12
- import { preloadSlug, slugify } from "./lazy-slugify.js";
13
-
14
- function init() {
15
- const form = document.querySelector<HTMLFormElement>("[data-page-form]");
16
- if (!form || form.hasAttribute("data-page-edit")) return;
17
-
18
- preloadSlug();
19
-
20
- const titleInput = form.querySelector<HTMLInputElement>(
21
- '[data-bind="title"]',
22
- );
23
- const slugInput = form.querySelector<HTMLInputElement>('[data-bind="slug"]');
24
- if (!titleInput || !slugInput) return;
25
-
26
- titleInput.addEventListener("input", () => {
27
- const currentTitle = titleInput.value;
28
- slugify(currentTitle).then((slug) => {
29
- if (titleInput.value === currentTitle) {
30
- slugInput.value = slug;
31
- slugInput.dispatchEvent(new Event("input", { bubbles: true }));
32
- }
33
- });
34
- });
35
- }
36
-
37
- // Run on DOMContentLoaded if the document isn't ready yet, otherwise run now.
38
- if (document.readyState === "loading") {
39
- document.addEventListener("DOMContentLoaded", init);
40
- } else {
41
- init();
42
- }
@@ -1,118 +0,0 @@
1
- CREATE TABLE `account` (
2
- `id` text PRIMARY KEY NOT NULL,
3
- `account_id` text NOT NULL,
4
- `provider_id` text NOT NULL,
5
- `user_id` text NOT NULL,
6
- `access_token` text,
7
- `refresh_token` text,
8
- `id_token` text,
9
- `access_token_expires_at` integer,
10
- `refresh_token_expires_at` integer,
11
- `scope` text,
12
- `password` text,
13
- `created_at` integer NOT NULL,
14
- `updated_at` integer NOT NULL,
15
- FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action
16
- );
17
- --> statement-breakpoint
18
- CREATE TABLE `collections` (
19
- `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
20
- `path` text,
21
- `title` text NOT NULL,
22
- `description` text,
23
- `created_at` integer NOT NULL,
24
- `updated_at` integer NOT NULL
25
- );
26
- --> statement-breakpoint
27
- CREATE UNIQUE INDEX `collections_path_unique` ON `collections` (`path`);--> statement-breakpoint
28
- CREATE TABLE `media` (
29
- `id` text PRIMARY KEY NOT NULL,
30
- `post_id` integer,
31
- `filename` text NOT NULL,
32
- `original_name` text NOT NULL,
33
- `mime_type` text NOT NULL,
34
- `size` integer NOT NULL,
35
- `r2_key` text NOT NULL,
36
- `width` integer,
37
- `height` integer,
38
- `alt` text,
39
- `created_at` integer NOT NULL,
40
- FOREIGN KEY (`post_id`) REFERENCES `posts`(`id`) ON UPDATE no action ON DELETE no action
41
- );
42
- --> statement-breakpoint
43
- CREATE TABLE `post_collections` (
44
- `post_id` integer NOT NULL,
45
- `collection_id` integer NOT NULL,
46
- `added_at` integer NOT NULL,
47
- PRIMARY KEY(`post_id`, `collection_id`),
48
- FOREIGN KEY (`post_id`) REFERENCES `posts`(`id`) ON UPDATE no action ON DELETE no action,
49
- FOREIGN KEY (`collection_id`) REFERENCES `collections`(`id`) ON UPDATE no action ON DELETE no action
50
- );
51
- --> statement-breakpoint
52
- CREATE TABLE `posts` (
53
- `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
54
- `type` text NOT NULL,
55
- `visibility` text DEFAULT 'quiet' NOT NULL,
56
- `title` text,
57
- `path` text,
58
- `content` text,
59
- `content_html` text,
60
- `source_url` text,
61
- `source_name` text,
62
- `source_domain` text,
63
- `reply_to_id` integer,
64
- `thread_id` integer,
65
- `deleted_at` integer,
66
- `published_at` integer NOT NULL,
67
- `created_at` integer NOT NULL,
68
- `updated_at` integer NOT NULL
69
- );
70
- --> statement-breakpoint
71
- CREATE TABLE `redirects` (
72
- `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
73
- `from_path` text NOT NULL,
74
- `to_path` text NOT NULL,
75
- `type` integer DEFAULT 301 NOT NULL,
76
- `created_at` integer NOT NULL
77
- );
78
- --> statement-breakpoint
79
- CREATE UNIQUE INDEX `redirects_from_path_unique` ON `redirects` (`from_path`);--> statement-breakpoint
80
- CREATE TABLE `session` (
81
- `id` text PRIMARY KEY NOT NULL,
82
- `expires_at` integer NOT NULL,
83
- `token` text NOT NULL,
84
- `created_at` integer NOT NULL,
85
- `updated_at` integer NOT NULL,
86
- `ip_address` text,
87
- `user_agent` text,
88
- `user_id` text NOT NULL,
89
- FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action
90
- );
91
- --> statement-breakpoint
92
- CREATE UNIQUE INDEX `session_token_unique` ON `session` (`token`);--> statement-breakpoint
93
- CREATE TABLE `settings` (
94
- `key` text PRIMARY KEY NOT NULL,
95
- `value` text NOT NULL,
96
- `updated_at` integer NOT NULL
97
- );
98
- --> statement-breakpoint
99
- CREATE TABLE `user` (
100
- `id` text PRIMARY KEY NOT NULL,
101
- `name` text NOT NULL,
102
- `email` text NOT NULL,
103
- `email_verified` integer DEFAULT false NOT NULL,
104
- `image` text,
105
- `role` text DEFAULT 'admin',
106
- `created_at` integer NOT NULL,
107
- `updated_at` integer NOT NULL
108
- );
109
- --> statement-breakpoint
110
- CREATE UNIQUE INDEX `user_email_unique` ON `user` (`email`);--> statement-breakpoint
111
- CREATE TABLE `verification` (
112
- `id` text PRIMARY KEY NOT NULL,
113
- `identifier` text NOT NULL,
114
- `value` text NOT NULL,
115
- `expires_at` integer NOT NULL,
116
- `created_at` integer,
117
- `updated_at` integer
118
- );
@@ -1,34 +0,0 @@
1
- -- FTS5 virtual table for full-text search (trigram tokenizer for CJK support)
2
- CREATE VIRTUAL TABLE IF NOT EXISTS posts_fts USING fts5(
3
- title,
4
- content,
5
- content='posts',
6
- content_rowid='id',
7
- tokenize='trigram'
8
- );
9
-
10
- -- Populate FTS with existing posts
11
- INSERT INTO posts_fts(rowid, title, content)
12
- SELECT id, COALESCE(title, ''), COALESCE(content, '')
13
- FROM posts WHERE deleted_at IS NULL;
14
-
15
- -- Trigger: sync FTS on INSERT
16
- CREATE TRIGGER posts_fts_insert AFTER INSERT ON posts
17
- WHEN NEW.deleted_at IS NULL
18
- BEGIN
19
- INSERT INTO posts_fts(rowid, title, content)
20
- VALUES (NEW.id, COALESCE(NEW.title, ''), COALESCE(NEW.content, ''));
21
- END;
22
-
23
- -- Trigger: sync FTS on UPDATE
24
- CREATE TRIGGER posts_fts_update AFTER UPDATE ON posts BEGIN
25
- DELETE FROM posts_fts WHERE rowid = OLD.id;
26
- INSERT INTO posts_fts(rowid, title, content)
27
- SELECT NEW.id, COALESCE(NEW.title, ''), COALESCE(NEW.content, '')
28
- WHERE NEW.deleted_at IS NULL;
29
- END;
30
-
31
- -- Trigger: sync FTS on DELETE
32
- CREATE TRIGGER posts_fts_delete AFTER DELETE ON posts BEGIN
33
- DELETE FROM posts_fts WHERE rowid = OLD.id;
34
- END;
@@ -1,3 +0,0 @@
1
- ALTER TABLE `media` ADD COLUMN `position` integer NOT NULL DEFAULT 0;
2
- --> statement-breakpoint
3
- ALTER TABLE `media` ADD COLUMN `blurhash` text;
@@ -1,8 +0,0 @@
1
- CREATE TABLE `navigation_links` (
2
- `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
3
- `label` text NOT NULL,
4
- `url` text NOT NULL,
5
- `position` integer DEFAULT 0 NOT NULL,
6
- `created_at` integer NOT NULL,
7
- `updated_at` integer NOT NULL
8
- );
@@ -1,3 +0,0 @@
1
- ALTER TABLE `media` ADD COLUMN `provider` text NOT NULL DEFAULT 'r2';
2
- --> statement-breakpoint
3
- ALTER TABLE `media` RENAME COLUMN `r2_key` TO `storage_key`;
@@ -1,268 +0,0 @@
1
- -- v2 Schema Migration
2
- -- Restructures posts, creates pages, updates collections, replaces navigation_links with nav_items
3
-
4
- -- Disable FK checks for migration (dropping/recreating tables with cross-references)
5
- PRAGMA foreign_keys = OFF;
6
- --> statement-breakpoint
7
-
8
- -- =============================================================================
9
- -- 1. Create pages table (before modifying posts, migrate type='page' data)
10
- -- =============================================================================
11
-
12
- CREATE TABLE `pages` (
13
- `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
14
- `slug` text NOT NULL,
15
- `title` text,
16
- `body` text,
17
- `body_html` text,
18
- `status` text DEFAULT 'published' NOT NULL,
19
- `created_at` integer NOT NULL,
20
- `updated_at` integer NOT NULL
21
- );
22
- --> statement-breakpoint
23
- CREATE UNIQUE INDEX `pages_slug_unique` ON `pages` (`slug`);
24
- --> statement-breakpoint
25
-
26
- -- Migrate type='page' posts into pages table
27
- INSERT INTO `pages` (`slug`, `title`, `body`, `body_html`, `status`, `created_at`, `updated_at`)
28
- SELECT
29
- CASE
30
- WHEN `path` IS NOT NULL AND `path` != '' THEN REPLACE(`path`, '/', '')
31
- ELSE 'page-' || `id`
32
- END,
33
- `title`,
34
- `content`,
35
- `content_html`,
36
- CASE WHEN `visibility` = 'draft' THEN 'draft' ELSE 'published' END,
37
- `created_at`,
38
- `updated_at`
39
- FROM `posts`
40
- WHERE `type` = 'page';
41
- --> statement-breakpoint
42
-
43
- -- =============================================================================
44
- -- 2. Restructure posts table (create new → migrate → drop old → rename)
45
- -- =============================================================================
46
-
47
- CREATE TABLE `posts_new` (
48
- `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
49
- `format` text DEFAULT 'note' NOT NULL,
50
- `status` text DEFAULT 'published' NOT NULL,
51
- `featured` integer DEFAULT 0 NOT NULL,
52
- `pinned` integer DEFAULT 0 NOT NULL,
53
- `slug` text,
54
- `title` text,
55
- `url` text,
56
- `body` text,
57
- `body_html` text,
58
- `quote_text` text,
59
- `rating` integer,
60
- `collection_id` integer,
61
- `reply_to_id` integer,
62
- `thread_id` integer,
63
- `deleted_at` integer,
64
- `published_at` integer NOT NULL,
65
- `created_at` integer NOT NULL,
66
- `updated_at` integer NOT NULL,
67
- FOREIGN KEY (`collection_id`) REFERENCES `collections`(`id`) ON UPDATE no action ON DELETE set null
68
- );
69
- --> statement-breakpoint
70
-
71
- -- Migrate data from old posts to new posts (excluding type='page')
72
- INSERT INTO `posts_new` (
73
- `id`, `format`, `status`, `featured`, `pinned`,
74
- `slug`, `title`, `url`, `body`, `body_html`, `quote_text`, `rating`,
75
- `collection_id`, `reply_to_id`, `thread_id`,
76
- `deleted_at`, `published_at`, `created_at`, `updated_at`
77
- )
78
- SELECT
79
- `id`,
80
- -- format mapping: article→note, image→note, note→note, link→link, quote→quote
81
- CASE
82
- WHEN `type` IN ('article', 'image', 'note') THEN 'note'
83
- WHEN `type` = 'link' THEN 'link'
84
- WHEN `type` = 'quote' THEN 'quote'
85
- ELSE 'note'
86
- END,
87
- -- status mapping from visibility
88
- CASE WHEN `visibility` = 'draft' THEN 'draft' ELSE 'published' END,
89
- -- featured mapping from visibility
90
- CASE WHEN `visibility` = 'featured' THEN 1 ELSE 0 END,
91
- -- pinned: default 0
92
- 0,
93
- -- slug: migrate from path (strip leading /)
94
- CASE
95
- WHEN `path` IS NOT NULL AND `path` != '' THEN REPLACE(`path`, '/', '')
96
- ELSE NULL
97
- END,
98
- `title`,
99
- `source_url`,
100
- `content`,
101
- `content_html`,
102
- -- quote_text: for quote type, content was the quote; set to null (manual fix may be needed)
103
- NULL,
104
- -- rating: null
105
- NULL,
106
- -- collection_id: migrate from post_collections (take first collection for each post)
107
- (SELECT `collection_id` FROM `post_collections` WHERE `post_collections`.`post_id` = `posts`.`id` LIMIT 1),
108
- `reply_to_id`,
109
- `thread_id`,
110
- `deleted_at`,
111
- `published_at`,
112
- `created_at`,
113
- `updated_at`
114
- FROM `posts`
115
- WHERE `type` != 'page';
116
- --> statement-breakpoint
117
-
118
- -- Update media references to point at new table (foreign keys reference posts)
119
- -- media.post_id still works since IDs are preserved
120
- --> statement-breakpoint
121
-
122
- -- Drop old posts table and rename new one
123
- DROP TABLE `posts`;
124
- --> statement-breakpoint
125
- ALTER TABLE `posts_new` RENAME TO `posts`;
126
- --> statement-breakpoint
127
- CREATE UNIQUE INDEX `posts_slug_unique` ON `posts` (`slug`);
128
- --> statement-breakpoint
129
-
130
- -- =============================================================================
131
- -- 3. Update collections table (add new columns, rename path→slug)
132
- -- =============================================================================
133
-
134
- CREATE TABLE `collections_new` (
135
- `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
136
- `slug` text NOT NULL,
137
- `title` text NOT NULL,
138
- `description` text,
139
- `icon` text,
140
- `sort_order` text DEFAULT 'newest' NOT NULL,
141
- `position` integer DEFAULT 0 NOT NULL,
142
- `show_divider` integer DEFAULT 0 NOT NULL,
143
- `created_at` integer NOT NULL,
144
- `updated_at` integer NOT NULL
145
- );
146
- --> statement-breakpoint
147
- CREATE UNIQUE INDEX `collections_new_slug_unique` ON `collections_new` (`slug`);
148
- --> statement-breakpoint
149
-
150
- INSERT INTO `collections_new` (`id`, `slug`, `title`, `description`, `icon`, `sort_order`, `position`, `show_divider`, `created_at`, `updated_at`)
151
- SELECT
152
- `id`,
153
- COALESCE(`path`, 'collection-' || `id`),
154
- `title`,
155
- `description`,
156
- NULL,
157
- 'newest',
158
- 0,
159
- 0,
160
- `created_at`,
161
- `updated_at`
162
- FROM `collections`;
163
- --> statement-breakpoint
164
-
165
- DROP TABLE `collections`;
166
- --> statement-breakpoint
167
- ALTER TABLE `collections_new` RENAME TO `collections`;
168
- --> statement-breakpoint
169
- CREATE UNIQUE INDEX `collections_slug_unique` ON `collections` (`slug`);
170
- --> statement-breakpoint
171
-
172
- -- =============================================================================
173
- -- 4. Replace navigation_links with nav_items
174
- -- =============================================================================
175
-
176
- CREATE TABLE `nav_items` (
177
- `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
178
- `type` text DEFAULT 'link' NOT NULL,
179
- `label` text NOT NULL,
180
- `url` text NOT NULL,
181
- `page_id` integer,
182
- `position` integer DEFAULT 0 NOT NULL,
183
- `created_at` integer NOT NULL,
184
- `updated_at` integer NOT NULL,
185
- FOREIGN KEY (`page_id`) REFERENCES `pages`(`id`) ON UPDATE no action ON DELETE cascade
186
- );
187
- --> statement-breakpoint
188
-
189
- -- Migrate existing navigation_links as type='link'
190
- INSERT INTO `nav_items` (`type`, `label`, `url`, `page_id`, `position`, `created_at`, `updated_at`)
191
- SELECT 'link', `label`, `url`, NULL, `position`, `created_at`, `updated_at`
192
- FROM `navigation_links`;
193
- --> statement-breakpoint
194
-
195
- DROP TABLE `navigation_links`;
196
- --> statement-breakpoint
197
-
198
- -- =============================================================================
199
- -- 5. Drop post_collections table (replaced by posts.collection_id)
200
- -- =============================================================================
201
-
202
- DROP TABLE `post_collections`;
203
- --> statement-breakpoint
204
-
205
- -- =============================================================================
206
- -- 6. Rebuild FTS5 (column rename: content→body, add quote_text)
207
- -- =============================================================================
208
-
209
- -- Drop old FTS triggers
210
- DROP TRIGGER IF EXISTS posts_fts_insert;
211
- --> statement-breakpoint
212
- DROP TRIGGER IF EXISTS posts_fts_update;
213
- --> statement-breakpoint
214
- DROP TRIGGER IF EXISTS posts_fts_delete;
215
- --> statement-breakpoint
216
-
217
- -- Drop old FTS table
218
- DROP TABLE IF EXISTS posts_fts;
219
- --> statement-breakpoint
220
-
221
- -- Create new FTS table with updated columns
222
- CREATE VIRTUAL TABLE IF NOT EXISTS posts_fts USING fts5(
223
- title,
224
- body,
225
- quote_text,
226
- content=posts,
227
- content_rowid=id,
228
- tokenize='trigram'
229
- );
230
- --> statement-breakpoint
231
-
232
- -- Populate FTS with migrated data
233
- INSERT INTO posts_fts(rowid, title, body, quote_text)
234
- SELECT id, COALESCE(title, ''), COALESCE(body, ''), COALESCE(quote_text, '')
235
- FROM posts WHERE deleted_at IS NULL;
236
- --> statement-breakpoint
237
-
238
- -- Trigger: sync FTS on INSERT
239
- CREATE TRIGGER posts_fts_insert AFTER INSERT ON posts
240
- WHEN NEW.deleted_at IS NULL
241
- BEGIN
242
- INSERT INTO posts_fts(rowid, title, body, quote_text)
243
- VALUES (NEW.id, COALESCE(NEW.title, ''), COALESCE(NEW.body, ''), COALESCE(NEW.quote_text, ''));
244
- END;
245
- --> statement-breakpoint
246
-
247
- -- Trigger: sync FTS on UPDATE
248
- CREATE TRIGGER posts_fts_update AFTER UPDATE ON posts BEGIN
249
- DELETE FROM posts_fts WHERE rowid = OLD.id;
250
- INSERT INTO posts_fts(rowid, title, body, quote_text)
251
- SELECT NEW.id, COALESCE(NEW.title, ''), COALESCE(NEW.body, ''), COALESCE(NEW.quote_text, '')
252
- WHERE NEW.deleted_at IS NULL;
253
- END;
254
- --> statement-breakpoint
255
-
256
- -- Trigger: sync FTS on DELETE
257
- CREATE TRIGGER posts_fts_delete AFTER DELETE ON posts BEGIN
258
- DELETE FROM posts_fts WHERE rowid = OLD.id;
259
- END;
260
- --> statement-breakpoint
261
-
262
- -- =============================================================================
263
- -- 7. Re-enable FK checks and verify integrity
264
- -- =============================================================================
265
-
266
- PRAGMA foreign_keys = ON;
267
- --> statement-breakpoint
268
- PRAGMA foreign_key_check;
@@ -1,5 +0,0 @@
1
- ALTER TABLE posts RENAME COLUMN slug TO path;
2
- --> statement-breakpoint
3
- DROP INDEX IF EXISTS posts_slug_unique;
4
- --> statement-breakpoint
5
- CREATE UNIQUE INDEX posts_path_unique ON posts (path);