@jant/core 0.3.6 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +7 -21
- package/dist/db/schema.d.ts +36 -0
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +2 -0
- package/dist/i18n/locales/en.d.ts.map +1 -1
- package/dist/i18n/locales/en.js +1 -1
- package/dist/i18n/locales/zh-Hans.d.ts.map +1 -1
- package/dist/i18n/locales/zh-Hans.js +1 -1
- package/dist/i18n/locales/zh-Hant.d.ts.map +1 -1
- package/dist/i18n/locales/zh-Hant.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/schemas.d.ts +17 -0
- package/dist/lib/schemas.d.ts.map +1 -1
- package/dist/lib/schemas.js +32 -2
- package/dist/lib/sse.d.ts +3 -3
- package/dist/lib/sse.d.ts.map +1 -1
- package/dist/lib/sse.js +7 -8
- package/dist/routes/api/posts.d.ts.map +1 -1
- package/dist/routes/api/posts.js +101 -5
- package/dist/routes/dash/media.js +38 -0
- package/dist/routes/dash/posts.d.ts.map +1 -1
- package/dist/routes/dash/posts.js +45 -6
- package/dist/routes/feed/rss.d.ts.map +1 -1
- package/dist/routes/feed/rss.js +10 -1
- package/dist/routes/pages/home.d.ts.map +1 -1
- package/dist/routes/pages/home.js +37 -4
- package/dist/routes/pages/post.d.ts.map +1 -1
- package/dist/routes/pages/post.js +28 -2
- package/dist/services/collection.d.ts +1 -0
- package/dist/services/collection.d.ts.map +1 -1
- package/dist/services/collection.js +13 -0
- package/dist/services/media.d.ts +7 -0
- package/dist/services/media.d.ts.map +1 -1
- package/dist/services/media.js +54 -1
- package/dist/theme/components/MediaGallery.d.ts +13 -0
- package/dist/theme/components/MediaGallery.d.ts.map +1 -0
- package/dist/theme/components/MediaGallery.js +107 -0
- package/dist/theme/components/PostForm.d.ts +6 -1
- package/dist/theme/components/PostForm.d.ts.map +1 -1
- package/dist/theme/components/PostForm.js +158 -2
- package/dist/theme/components/index.d.ts +1 -0
- package/dist/theme/components/index.d.ts.map +1 -1
- package/dist/theme/components/index.js +1 -0
- package/dist/types.d.ts +24 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +27 -0
- package/package.json +1 -1
- package/src/__tests__/helpers/app.ts +6 -1
- package/src/__tests__/helpers/db.ts +10 -0
- package/src/app.tsx +7 -25
- package/src/db/migrations/0002_add_media_attachments.sql +3 -0
- package/src/db/schema.ts +2 -0
- package/src/i18n/locales/en.po +81 -37
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +81 -37
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +81 -37
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/index.ts +8 -1
- package/src/lib/__tests__/schemas.test.ts +89 -1
- package/src/lib/__tests__/sse.test.ts +13 -1
- package/src/lib/schemas.ts +47 -1
- package/src/lib/sse.ts +10 -11
- package/src/routes/api/__tests__/posts.test.ts +239 -0
- package/src/routes/api/posts.ts +134 -5
- package/src/routes/dash/media.tsx +50 -0
- package/src/routes/dash/posts.tsx +79 -7
- package/src/routes/feed/rss.ts +14 -1
- package/src/routes/pages/home.tsx +80 -36
- package/src/routes/pages/post.tsx +36 -3
- package/src/services/__tests__/collection.test.ts +102 -0
- package/src/services/__tests__/media.test.ts +248 -0
- package/src/services/collection.ts +19 -0
- package/src/services/media.ts +76 -1
- package/src/theme/components/MediaGallery.tsx +128 -0
- package/src/theme/components/PostForm.tsx +170 -2
- package/src/theme/components/index.ts +1 -0
- package/src/types.ts +36 -0
|
@@ -26,7 +26,7 @@ msgstr "← 返回收藏夾"
|
|
|
26
26
|
#. @context: Navigation link
|
|
27
27
|
#. @context: Navigation link
|
|
28
28
|
#: src/routes/pages/collection.tsx:76
|
|
29
|
-
#: src/routes/pages/post.tsx:
|
|
29
|
+
#: src/routes/pages/post.tsx:63
|
|
30
30
|
msgid "← Back to home"
|
|
31
31
|
msgstr "← 返回首頁"
|
|
32
32
|
|
|
@@ -50,6 +50,11 @@ msgstr "302(臨時)"
|
|
|
50
50
|
msgid "Account"
|
|
51
51
|
msgstr "帳戶"
|
|
52
52
|
|
|
53
|
+
#. @context: Button to open media picker
|
|
54
|
+
#: src/theme/components/PostForm.tsx:178
|
|
55
|
+
msgid "Add Media"
|
|
56
|
+
msgstr ""
|
|
57
|
+
|
|
53
58
|
#. @context: Archive filter - all types
|
|
54
59
|
#: src/routes/pages/archive.tsx:115
|
|
55
60
|
msgid "All"
|
|
@@ -63,7 +68,7 @@ msgstr "外觀"
|
|
|
63
68
|
#. @context: Archive page title
|
|
64
69
|
#. @context: Navigation link to archive page
|
|
65
70
|
#: src/routes/pages/archive.tsx:102
|
|
66
|
-
#: src/routes/pages/home.tsx:
|
|
71
|
+
#: src/routes/pages/home.tsx:40
|
|
67
72
|
msgid "Archive"
|
|
68
73
|
msgstr "檔案館"
|
|
69
74
|
|
|
@@ -71,7 +76,7 @@ msgstr "檔案館"
|
|
|
71
76
|
#. @context: Post type label - article
|
|
72
77
|
#. @context: Post type option
|
|
73
78
|
#: src/routes/pages/archive.tsx:28
|
|
74
|
-
#: src/theme/components/PostForm.tsx:
|
|
79
|
+
#: src/theme/components/PostForm.tsx:67
|
|
75
80
|
#: src/theme/components/TypeBadge.tsx:20
|
|
76
81
|
msgid "Article"
|
|
77
82
|
msgstr "文章"
|
|
@@ -81,6 +86,11 @@ msgstr "文章"
|
|
|
81
86
|
msgid "Articles"
|
|
82
87
|
msgstr "文章"
|
|
83
88
|
|
|
89
|
+
#. @context: Hint for image post type media requirement
|
|
90
|
+
#: src/theme/components/PostForm.tsx:130
|
|
91
|
+
msgid "At least 1 image required for image posts."
|
|
92
|
+
msgstr ""
|
|
93
|
+
|
|
84
94
|
#. @context: Button to go back to media list
|
|
85
95
|
#: src/routes/dash/media.tsx:245
|
|
86
96
|
msgid "Back"
|
|
@@ -104,7 +114,7 @@ msgstr "回到首頁"
|
|
|
104
114
|
#: src/routes/dash/collections.tsx:345
|
|
105
115
|
#: src/routes/dash/redirects.tsx:167
|
|
106
116
|
#: src/theme/components/PageForm.tsx:161
|
|
107
|
-
#: src/theme/components/PostForm.tsx:
|
|
117
|
+
#: src/theme/components/PostForm.tsx:312
|
|
108
118
|
msgid "Cancel"
|
|
109
119
|
msgstr "取消"
|
|
110
120
|
|
|
@@ -127,6 +137,11 @@ msgstr "點擊圖片以查看完整大小"
|
|
|
127
137
|
msgid "Collections"
|
|
128
138
|
msgstr "收藏夾"
|
|
129
139
|
|
|
140
|
+
#. @context: Post form field - assign to collections
|
|
141
|
+
#: src/theme/components/PostForm.tsx:261
|
|
142
|
+
msgid "Collections (optional)"
|
|
143
|
+
msgstr ""
|
|
144
|
+
|
|
130
145
|
#. @context: Appearance settings heading
|
|
131
146
|
#: src/routes/dash/settings.tsx:301
|
|
132
147
|
msgid "Color theme"
|
|
@@ -143,14 +158,14 @@ msgid "Confirm New Password"
|
|
|
143
158
|
msgstr "確認新密碼"
|
|
144
159
|
|
|
145
160
|
#. @context: Password reset form field
|
|
146
|
-
#: src/app.tsx:
|
|
161
|
+
#: src/app.tsx:487
|
|
147
162
|
msgid "Confirm Password"
|
|
148
163
|
msgstr "確認密碼"
|
|
149
164
|
|
|
150
165
|
#. @context: Page form field label - content
|
|
151
166
|
#. @context: Post form field
|
|
152
167
|
#: src/theme/components/PageForm.tsx:95
|
|
153
|
-
#: src/theme/components/PostForm.tsx:
|
|
168
|
+
#: src/theme/components/PostForm.tsx:103
|
|
154
169
|
msgid "Content"
|
|
155
170
|
msgstr "內容"
|
|
156
171
|
|
|
@@ -197,7 +212,7 @@ msgid "Current Password"
|
|
|
197
212
|
msgstr "當前密碼"
|
|
198
213
|
|
|
199
214
|
#. @context: Post form field
|
|
200
|
-
#: src/theme/components/PostForm.tsx:
|
|
215
|
+
#: src/theme/components/PostForm.tsx:285
|
|
201
216
|
msgid "Custom Path (optional)"
|
|
202
217
|
msgstr "自訂路徑(選填)"
|
|
203
218
|
|
|
@@ -252,11 +267,16 @@ msgstr "示範帳戶已預填。只需點擊登入。"
|
|
|
252
267
|
msgid "Description (optional)"
|
|
253
268
|
msgstr "描述(可選)"
|
|
254
269
|
|
|
270
|
+
#. @context: Close media picker button
|
|
271
|
+
#: src/theme/components/PostForm.tsx:334
|
|
272
|
+
msgid "Done"
|
|
273
|
+
msgstr ""
|
|
274
|
+
|
|
255
275
|
#. @context: Page status option - draft
|
|
256
276
|
#. @context: Post visibility badge - draft
|
|
257
277
|
#. @context: Post visibility option
|
|
258
278
|
#: src/theme/components/PageForm.tsx:132
|
|
259
|
-
#: src/theme/components/PostForm.tsx:
|
|
279
|
+
#: src/theme/components/PostForm.tsx:249
|
|
260
280
|
#: src/theme/components/VisibilityBadge.tsx:38
|
|
261
281
|
msgid "Draft"
|
|
262
282
|
msgstr "草稿"
|
|
@@ -266,6 +286,11 @@ msgstr "草稿"
|
|
|
266
286
|
msgid "Drafts"
|
|
267
287
|
msgstr "草稿"
|
|
268
288
|
|
|
289
|
+
#. @context: Source name placeholder
|
|
290
|
+
#: src/theme/components/PostForm.tsx:214
|
|
291
|
+
msgid "e.g. The Verge, John Doe"
|
|
292
|
+
msgstr ""
|
|
293
|
+
|
|
269
294
|
#. @context: Button to edit collection
|
|
270
295
|
#. @context: Button to edit collection
|
|
271
296
|
#. @context: Button to edit item
|
|
@@ -277,7 +302,7 @@ msgstr "草稿"
|
|
|
277
302
|
#: src/routes/dash/collections.tsx:210
|
|
278
303
|
#: src/routes/dash/pages.tsx:64
|
|
279
304
|
#: src/routes/dash/pages.tsx:134
|
|
280
|
-
#: src/routes/dash/posts.tsx:
|
|
305
|
+
#: src/routes/dash/posts.tsx:142
|
|
281
306
|
#: src/theme/components/ActionButtons.tsx:72
|
|
282
307
|
#: src/theme/components/PostList.tsx:46
|
|
283
308
|
msgid "Edit"
|
|
@@ -294,7 +319,7 @@ msgid "Edit Page"
|
|
|
294
319
|
msgstr "編輯頁面"
|
|
295
320
|
|
|
296
321
|
#. @context: Page heading
|
|
297
|
-
#: src/routes/dash/posts.tsx:
|
|
322
|
+
#: src/routes/dash/posts.tsx:185
|
|
298
323
|
msgid "Edit Post"
|
|
299
324
|
msgstr "編輯文章"
|
|
300
325
|
|
|
@@ -306,15 +331,15 @@ msgid "Email"
|
|
|
306
331
|
msgstr "電子郵件"
|
|
307
332
|
|
|
308
333
|
#. @context: Password reset page description
|
|
309
|
-
#: src/app.tsx:
|
|
334
|
+
#: src/app.tsx:457
|
|
310
335
|
msgid "Enter your new password."
|
|
311
336
|
msgstr "請輸入您的新密碼。"
|
|
312
337
|
|
|
313
338
|
#. @context: Post visibility badge
|
|
314
339
|
#. @context: Post visibility badge - featured
|
|
315
340
|
#. @context: Post visibility option
|
|
316
|
-
#: src/routes/pages/home.tsx:
|
|
317
|
-
#: src/theme/components/PostForm.tsx:
|
|
341
|
+
#: src/routes/pages/home.tsx:90
|
|
342
|
+
#: src/theme/components/PostForm.tsx:237
|
|
318
343
|
#: src/theme/components/VisibilityBadge.tsx:26
|
|
319
344
|
msgid "Featured"
|
|
320
345
|
msgstr "精選"
|
|
@@ -345,7 +370,7 @@ msgstr "一般設定"
|
|
|
345
370
|
#. @context: Post type label - image
|
|
346
371
|
#. @context: Post type option
|
|
347
372
|
#: src/routes/pages/archive.tsx:37
|
|
348
|
-
#: src/theme/components/PostForm.tsx:
|
|
373
|
+
#: src/theme/components/PostForm.tsx:76
|
|
349
374
|
#: src/theme/components/TypeBadge.tsx:29
|
|
350
375
|
msgid "Image"
|
|
351
376
|
msgstr "圖片"
|
|
@@ -361,7 +386,7 @@ msgid "Images are automatically optimized: resized to max 1920px, converted to W
|
|
|
361
386
|
msgstr "圖片會自動優化:調整大小至最大 1920 像素,轉換為 WebP 格式,並去除元數據。"
|
|
362
387
|
|
|
363
388
|
#. @context: Password reset error heading
|
|
364
|
-
#: src/app.tsx:
|
|
389
|
+
#: src/app.tsx:522
|
|
365
390
|
msgid "Invalid or Expired Link"
|
|
366
391
|
msgstr "無效或已過期的連結"
|
|
367
392
|
|
|
@@ -374,7 +399,7 @@ msgstr "語言"
|
|
|
374
399
|
#. @context: Post type label - link
|
|
375
400
|
#. @context: Post type option
|
|
376
401
|
#: src/routes/pages/archive.tsx:32
|
|
377
|
-
#: src/theme/components/PostForm.tsx:
|
|
402
|
+
#: src/theme/components/PostForm.tsx:70
|
|
378
403
|
#: src/theme/components/TypeBadge.tsx:24
|
|
379
404
|
msgid "Link"
|
|
380
405
|
msgstr "連結"
|
|
@@ -389,6 +414,11 @@ msgstr "連結"
|
|
|
389
414
|
msgid "Load more"
|
|
390
415
|
msgstr "載入更多"
|
|
391
416
|
|
|
417
|
+
#. @context: Loading state for media picker
|
|
418
|
+
#: src/theme/components/PostForm.tsx:345
|
|
419
|
+
msgid "Loading..."
|
|
420
|
+
msgstr ""
|
|
421
|
+
|
|
392
422
|
#. @context: Page path validation message
|
|
393
423
|
#: src/theme/components/PageForm.tsx:76
|
|
394
424
|
msgid "Lowercase letters, numbers, and hyphens only"
|
|
@@ -401,7 +431,9 @@ msgstr "Markdown"
|
|
|
401
431
|
|
|
402
432
|
#. @context: Dashboard navigation - media library
|
|
403
433
|
#. @context: Media main heading
|
|
434
|
+
#. @context: Post form field - media attachments
|
|
404
435
|
#: src/routes/dash/media.tsx:138
|
|
436
|
+
#: src/theme/components/PostForm.tsx:121
|
|
405
437
|
#: src/theme/layouts/DashLayout.tsx:105
|
|
406
438
|
msgid "Media"
|
|
407
439
|
msgstr "媒體"
|
|
@@ -439,7 +471,7 @@ msgstr "新頁面"
|
|
|
439
471
|
|
|
440
472
|
#. @context: Password form field
|
|
441
473
|
#. @context: Password reset form field
|
|
442
|
-
#: src/app.tsx:
|
|
474
|
+
#: src/app.tsx:471
|
|
443
475
|
#: src/routes/dash/settings.tsx:417
|
|
444
476
|
msgid "New Password"
|
|
445
477
|
msgstr "新密碼"
|
|
@@ -498,7 +530,7 @@ msgstr "此集合中沒有帖子。"
|
|
|
498
530
|
|
|
499
531
|
#. @context: Empty state message on home page
|
|
500
532
|
#. @context: Empty state message when no posts exist
|
|
501
|
-
#: src/routes/pages/home.tsx:
|
|
533
|
+
#: src/routes/pages/home.tsx:54
|
|
502
534
|
#: src/theme/components/PostList.tsx:25
|
|
503
535
|
msgid "No posts yet."
|
|
504
536
|
msgstr "尚未有帖子。"
|
|
@@ -517,7 +549,7 @@ msgstr "未找到結果。"
|
|
|
517
549
|
#. @context: Post type label - note
|
|
518
550
|
#. @context: Post type option
|
|
519
551
|
#: src/routes/pages/archive.tsx:27
|
|
520
|
-
#: src/theme/components/PostForm.tsx:
|
|
552
|
+
#: src/theme/components/PostForm.tsx:64
|
|
521
553
|
#: src/theme/components/TypeBadge.tsx:19
|
|
522
554
|
msgid "Note"
|
|
523
555
|
msgstr "備註"
|
|
@@ -574,18 +606,18 @@ msgstr "路徑"
|
|
|
574
606
|
|
|
575
607
|
#. @context: Link to individual post in thread
|
|
576
608
|
#. @context: Link to permanent URL of post
|
|
577
|
-
#: src/routes/pages/post.tsx:
|
|
609
|
+
#: src/routes/pages/post.tsx:53
|
|
578
610
|
#: src/theme/components/ThreadView.tsx:68
|
|
579
611
|
msgid "Permalink"
|
|
580
612
|
msgstr "永久鏈接"
|
|
581
613
|
|
|
582
614
|
#. @context: Default post title
|
|
583
|
-
#: src/routes/dash/posts.tsx:
|
|
615
|
+
#: src/routes/dash/posts.tsx:131
|
|
584
616
|
msgid "Post"
|
|
585
617
|
msgstr "文章"
|
|
586
618
|
|
|
587
619
|
#. @context: Post title placeholder
|
|
588
|
-
#: src/theme/components/PostForm.tsx:
|
|
620
|
+
#: src/theme/components/PostForm.tsx:93
|
|
589
621
|
msgid "Post title..."
|
|
590
622
|
msgstr "文章標題..."
|
|
591
623
|
|
|
@@ -624,7 +656,7 @@ msgid "Profile"
|
|
|
624
656
|
msgstr "個人資料"
|
|
625
657
|
|
|
626
658
|
#. @context: Button to publish new post
|
|
627
|
-
#: src/theme/components/PostForm.tsx:
|
|
659
|
+
#: src/theme/components/PostForm.tsx:306
|
|
628
660
|
msgid "Publish"
|
|
629
661
|
msgstr "發佈"
|
|
630
662
|
|
|
@@ -651,7 +683,7 @@ msgid "Quiet"
|
|
|
651
683
|
msgstr "安靜"
|
|
652
684
|
|
|
653
685
|
#. @context: Post visibility option
|
|
654
|
-
#: src/theme/components/PostForm.tsx:
|
|
686
|
+
#: src/theme/components/PostForm.tsx:231
|
|
655
687
|
msgid "Quiet (normal)"
|
|
656
688
|
msgstr "安靜(正常)"
|
|
657
689
|
|
|
@@ -659,7 +691,7 @@ msgstr "安靜(正常)"
|
|
|
659
691
|
#. @context: Post type label - quote
|
|
660
692
|
#. @context: Post type option
|
|
661
693
|
#: src/routes/pages/archive.tsx:33
|
|
662
|
-
#: src/theme/components/PostForm.tsx:
|
|
694
|
+
#: src/theme/components/PostForm.tsx:73
|
|
663
695
|
#: src/theme/components/TypeBadge.tsx:25
|
|
664
696
|
msgid "Quote"
|
|
665
697
|
msgstr "引用"
|
|
@@ -677,14 +709,16 @@ msgid "Redirects"
|
|
|
677
709
|
msgstr "重定向"
|
|
678
710
|
|
|
679
711
|
#. @context: Button to remove post from collection
|
|
712
|
+
#. @context: Remove media attachment button
|
|
680
713
|
#: src/routes/dash/collections.tsx:257
|
|
714
|
+
#: src/theme/components/PostForm.tsx:161
|
|
681
715
|
msgid "Remove"
|
|
682
716
|
msgstr "移除"
|
|
683
717
|
|
|
684
718
|
#. @context: Password reset form submit button
|
|
685
719
|
#. @context: Password reset page heading
|
|
686
|
-
#: src/app.tsx:
|
|
687
|
-
#: src/app.tsx:
|
|
720
|
+
#: src/app.tsx:451
|
|
721
|
+
#: src/app.tsx:502
|
|
688
722
|
msgid "Reset Password"
|
|
689
723
|
msgstr "重設密碼"
|
|
690
724
|
|
|
@@ -710,6 +744,11 @@ msgstr "搜尋"
|
|
|
710
744
|
msgid "Search posts..."
|
|
711
745
|
msgstr "搜尋帖子..."
|
|
712
746
|
|
|
747
|
+
#. @context: Media picker dialog title
|
|
748
|
+
#: src/theme/components/PostForm.tsx:324
|
|
749
|
+
msgid "Select Media"
|
|
750
|
+
msgstr ""
|
|
751
|
+
|
|
713
752
|
#. @context: Dashboard heading
|
|
714
753
|
#. @context: Dashboard heading
|
|
715
754
|
#. @context: Dashboard heading
|
|
@@ -750,8 +789,13 @@ msgstr "網站名稱"
|
|
|
750
789
|
msgid "Slug"
|
|
751
790
|
msgstr "縮略名"
|
|
752
791
|
|
|
792
|
+
#. @context: Post form field - name of the source website or author
|
|
793
|
+
#: src/theme/components/PostForm.tsx:204
|
|
794
|
+
msgid "Source Name (optional)"
|
|
795
|
+
msgstr ""
|
|
796
|
+
|
|
753
797
|
#. @context: Post form field
|
|
754
|
-
#: src/theme/components/PostForm.tsx:
|
|
798
|
+
#: src/theme/components/PostForm.tsx:188
|
|
755
799
|
msgid "Source URL (optional)"
|
|
756
800
|
msgstr "來源網址(選填)"
|
|
757
801
|
|
|
@@ -776,7 +820,7 @@ msgid "The URL path for this page. Use lowercase letters, numbers, and hyphens."
|
|
|
776
820
|
msgstr "此頁面的 URL 路徑。使用小寫字母、數字和連字符。"
|
|
777
821
|
|
|
778
822
|
#. @context: Password reset error description
|
|
779
|
-
#: src/app.tsx:
|
|
823
|
+
#: src/app.tsx:530
|
|
780
824
|
msgid "This password reset link is invalid or has expired. Please generate a new one."
|
|
781
825
|
msgstr "此密碼重設連結無效或已過期。請生成一個新的連結。"
|
|
782
826
|
|
|
@@ -810,7 +854,7 @@ msgid "Title"
|
|
|
810
854
|
msgstr "標題"
|
|
811
855
|
|
|
812
856
|
#. @context: Post form field
|
|
813
|
-
#: src/theme/components/PostForm.tsx:
|
|
857
|
+
#: src/theme/components/PostForm.tsx:84
|
|
814
858
|
msgid "Title (optional)"
|
|
815
859
|
msgstr "標題(選填)"
|
|
816
860
|
|
|
@@ -822,13 +866,13 @@ msgstr "到路徑"
|
|
|
822
866
|
#. @context: Post form field - post type
|
|
823
867
|
#. @context: Redirect form field
|
|
824
868
|
#: src/routes/dash/redirects.tsx:141
|
|
825
|
-
#: src/theme/components/PostForm.tsx:
|
|
869
|
+
#: src/theme/components/PostForm.tsx:57
|
|
826
870
|
msgid "Type"
|
|
827
871
|
msgstr "類型"
|
|
828
872
|
|
|
829
873
|
#. @context: Post visibility badge - unlisted
|
|
830
874
|
#. @context: Post visibility option
|
|
831
|
-
#: src/theme/components/PostForm.tsx:
|
|
875
|
+
#: src/theme/components/PostForm.tsx:243
|
|
832
876
|
#: src/theme/components/VisibilityBadge.tsx:34
|
|
833
877
|
msgid "Unlisted"
|
|
834
878
|
msgstr "不公開"
|
|
@@ -841,7 +885,7 @@ msgid "Untitled"
|
|
|
841
885
|
msgstr "無標題"
|
|
842
886
|
|
|
843
887
|
#. @context: Button to update existing post
|
|
844
|
-
#: src/theme/components/PostForm.tsx:
|
|
888
|
+
#: src/theme/components/PostForm.tsx:302
|
|
845
889
|
msgid "Update"
|
|
846
890
|
msgstr "更新"
|
|
847
891
|
|
|
@@ -896,14 +940,14 @@ msgstr "使用此 URL 將媒體嵌入到您的帖子中。"
|
|
|
896
940
|
#: src/routes/dash/collections.tsx:215
|
|
897
941
|
#: src/routes/dash/pages.tsx:73
|
|
898
942
|
#: src/routes/dash/pages.tsx:143
|
|
899
|
-
#: src/routes/dash/posts.tsx:
|
|
943
|
+
#: src/routes/dash/posts.tsx:147
|
|
900
944
|
#: src/theme/components/ActionButtons.tsx:76
|
|
901
945
|
#: src/theme/components/PostList.tsx:51
|
|
902
946
|
msgid "View"
|
|
903
947
|
msgstr "查看"
|
|
904
948
|
|
|
905
949
|
#. @context: Link to view all posts on archive page
|
|
906
|
-
#: src/routes/pages/home.tsx:
|
|
950
|
+
#: src/routes/pages/home.tsx:109
|
|
907
951
|
msgid "View all posts →"
|
|
908
952
|
msgstr "查看所有文章 →"
|
|
909
953
|
|
|
@@ -913,7 +957,7 @@ msgid "View Site"
|
|
|
913
957
|
msgstr "查看網站"
|
|
914
958
|
|
|
915
959
|
#. @context: Post form field
|
|
916
|
-
#: src/theme/components/PostForm.tsx:
|
|
960
|
+
#: src/theme/components/PostForm.tsx:224
|
|
917
961
|
msgid "Visibility"
|
|
918
962
|
msgstr "可見性"
|
|
919
963
|
|
|
@@ -923,7 +967,7 @@ msgid "Welcome to Jant"
|
|
|
923
967
|
msgstr "歡迎來到 Jant"
|
|
924
968
|
|
|
925
969
|
#. @context: Post content placeholder
|
|
926
|
-
#: src/theme/components/PostForm.tsx:
|
|
970
|
+
#: src/theme/components/PostForm.tsx:108
|
|
927
971
|
msgid "What's on your mind?"
|
|
928
972
|
msgstr "你在想什麼?"
|
|
929
973
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"+7Wr2a\":[\"Edit: \",[\"title\"]],\"+MACwa\":[\"尚未有任何收藏。\"],\"+owNNn\":[\"文章\"],\"+zy2Nq\":[\"類型\"],\"/0D1Xp\":[\"編輯收藏集\"],\"/Rj5P4\":[\"您的姓名\"],\"07Epll\":[\"這將為您的網站和儀表板設置主題。所有顏色主題都支持深色模式。\"],\"0JkyS7\":[\"建立您的第一個頁面\"],\"0a6MpL\":[\"新重定向\"],\"1CU1Td\":[\"網址安全識別碼(小寫、數字、連字符)\"],\"1DBGsz\":[\"筆記\"],\"2N0qpv\":[\"文章標題...\"],\"2q/Q7x\":[\"可見性\"],\"2rJGtU\":[\"頁面標題...\"],\"3Yvsaz\":[\"302(臨時)\"],\"4/SFQS\":[\"查看網站\"],\"40TVQj\":[\"自訂路徑(選填)\"],\"4KzVT6\":[\"刪除頁面\"],\"4b3oEV\":[\"內容\"],\"4mDPGp\":[\"此頁面的 URL 路徑。使用小寫字母、數字和連字符。\"],\"6WdDG7\":[\"頁面\"],\"6YtxFj\":[\"名稱\"],\"7G4SBz\":[\"頁面內容(支持Markdown)...\"],\"7Mk+/h\":[\"更新收藏集\"],\"7Q1KKN\":[\"來源路徑\"],\"7aECQB\":[\"無效或已過期的連結\"],\"7nGhhM\":[\"你在想什麼?\"],\"7p5kLi\":[\"儀表板\"],\"7vhWI8\":[\"新密碼\"],\"8ZsakT\":[\"密碼\"],\"90Luob\":[[\"count\"],\" 條回覆\"],\"A1taO8\":[\"搜尋\"],\"AeXO77\":[\"帳戶\"],\"AyHO4m\":[\"這個收藏是關於什麼的?\"],\"B373X+\":[\"編輯文章\"],\"B495Gs\":[\"檔案館\"],\"BjF0Jv\":[\"僅限小寫字母、數字和連字符\"],\"D9Oea+\":[\"永久鏈接\"],\"DCKkhU\":[\"當前密碼\"],\"DHhJ7s\":[\"上一頁\"],\"DoJzLz\":[\"收藏夾\"],\"E80cJw\":[\"刪除此媒體將永久從存儲中移除它。\"],\"EEYbdt\":[\"發佈\"],\"EGwzOK\":[\"完成設置\"],\"EkH9pt\":[\"更新\"],\"FGrimz\":[\"新帖子\"],\"FkMol5\":[\"精選\"],\"Fxf4jq\":[\"描述(可選)\"],\"GA5A5H\":[\"刪除收藏夾\"],\"GX2VMa\":[\"建立您的管理員帳戶。\"],\"GbVAnd\":[\"此密碼重設連結無效或已過期。請生成一個新的連結。\"],\"GorKul\":[\"歡迎來到 Jant\"],\"GrZ6fH\":[\"新頁面\"],\"GxkJXS\":[\"上傳中...\"],\"HfyyXl\":[\"My Blog\"],\"HiETwV\":[\"安靜(正常)\"],\"Hzi9AA\":[\"未找到任何帖子。\"],\"I6gXOa\":[\"路徑\"],\"IagCbF\":[\"網址\"],\"J4FNfC\":[\"此集合中沒有帖子。\"],\"JIBC/T\":[\"Supported formats: JPEG, PNG, GIF, WebP, SVG. Max size: 10MB.\"],\"Jed1wB\":[\"需要幫助嗎?請訪問<0>文檔</0>。\"],\"JiP4aa\":[\"已發佈的頁面可以通過其路徑訪問。草稿不可見。\"],\"K9NcLu\":[\"使用此 URL 將媒體嵌入到您的帖子中。\"],\"KbS2K9\":[\"重設密碼\"],\"KiJn9B\":[\"備註\"],\"L85WcV\":[\"縮略名\"],\"LkvLQe\":[\"尚未有頁面。\"],\"M1RvTd\":[\"點擊圖片以查看完整大小\"],\"M8kJqa\":[\"草稿\"],\"M9xgHy\":[\"重定向\"],\"MHrjPM\":[\"標題\"],\"MZbQHL\":[\"未找到結果。\"],\"Mhf/H/\":[\"建立重定向\"],\"MqghUt\":[\"搜尋帖子...\"],\"N40H+G\":[\"所有\"],\"O3oNi5\":[\"電子郵件\"],\"OCNZaU\":[\"重定向來源的路徑\"],\"ODiSoW\":[\"尚未有帖子。\"],\"ONWvwQ\":[\"上傳\"],\"Pbm2/N\":[\"創建收藏夾\"],\"RDjuBN\":[\"Setup\"],\"Rj01Fz\":[\"連結\"],\"RwGhWy\":[\"包含 \",[\"count\"],\" 則帖子的主題\"],\"SJmfuf\":[\"網站名稱\"],\"ST+lN2\":[\"尚未上傳任何媒體。\"],\"Tt5T6+\":[\"文章\"],\"TxE+Mj\":[\"1 條回覆\"],\"Tz0i8g\":[\"設定\"],\"U5v6Gh\":[\"編輯頁面\"],\"UDMjsP\":[\"快速操作\"],\"UGT5vp\":[\"保存設定\"],\"VUSy8D\":[\"Search failed. Please try again.\"],\"VhMDMg\":[\"更改密碼\"],\"WDcQq9\":[\"不公開\"],\"Weq9zb\":[\"一般設定\"],\"WmZ/rP\":[\"到路徑\"],\"Y+7JGK\":[\"創建頁面\"],\"ZQKLI1\":[\"危險區域\"],\"ZhhOwV\":[\"引用\"],\"aAIQg2\":[\"外觀\"],\"an5hVd\":[\"圖片\"],\"b+/jO6\":[\"301(永久)\"],\"bHYIks\":[\"登出\"],\"biOepV\":[\"← 返回首頁\"],\"cnGeoo\":[\"刪除\"],\"dEgA5A\":[\"取消\"],\"e6Jr7Q\":[\"← 返回收藏夾\"],\"ePK91l\":[\"編輯\"],\"eWLklq\":[\"引用\"],\"eneWvv\":[\"草稿\"],\"er8+x7\":[\"示範帳戶已預填。只需點擊登入。\"],\"f6e0Ry\":[\"文章\"],\"fG7BxZ\":[\"Upload images via the API: POST /api/upload with a file form field.\"],\"fttd2R\":[\"我的收藏\"],\"hG89Ed\":[\"圖片\"],\"hWOZIv\":[\"請輸入您的新密碼。\"],\"hXzOVo\":[\"下一頁\"],\"he3ygx\":[\"複製\"],\"iH8pgl\":[\"返回\"],\"ig4hg2\":[\"Let's set up your site.\"],\"jpctdh\":[\"查看\"],\"k1ifdL\":[\"處理中...\"],\"mTOYla\":[\"查看所有文章 →\"],\"n1ekoW\":[\"登入\"],\"oYPBa0\":[\"更新頁面\"],\"p2/GCq\":[\"確認密碼\"],\"pRhYH2\":[\"收藏中的帖子 (\",[\"count\"],\")\"],\"pZq3aX\":[\"上傳失敗。請再試一次。\"],\"qMyM2u\":[\"來源網址(選填)\"],\"r1MpXi\":[\"安靜\"],\"rFmBG3\":[\"顏色主題\"],\"rdUucN\":[\"預覽\"],\"rzNUSl\":[\"包含 1 則貼文的主題\"],\"sGajR7\":[\"線程開始\"],\"ssqvZi\":[\"保存個人資料\"],\"t/YqKh\":[\"移除\"],\"tfrt7B\":[\"未配置任何重定向。\"],\"tiq7kl\":[\"頁面 \",[\"page\"]],\"u2f7vd\":[\"網站描述\"],\"u3wRF+\":[\"已發佈\"],\"u6Hp4N\":[\"Markdown\"],\"uAQUqI\":[\"狀態\"],\"vERlcd\":[\"個人資料\"],\"vXIe7J\":[\"語言\"],\"vzU4k9\":[\"新收藏集\"],\"wEF6Ix\":[\"目的地路徑或 URL\"],\"wK4OTM\":[\"標題(選填)\"],\"wM5UXj\":[\"刪除媒體\"],\"wRR604\":[\"頁面\"],\"wja8aL\":[\"無標題\"],\"x+doid\":[\"圖片會自動優化:調整大小至最大 1920 像素,轉換為 WebP 格式,並去除元數據。\"],\"x0mzE0\":[\"創建你的第一篇帖子\"],\"x4RuFo\":[\"回到首頁\"],\"xYilR2\":[\"媒體\"],\"y28hnO\":[\"文章\"],\"yQ2kGp\":[\"載入更多\"],\"yjkELF\":[\"確認新密碼\"],\"yzF66j\":[\"連結\"],\"z8ajIE\":[\"找到 1 個結果\"],\"zH6KqE\":[\"找到 \",[\"count\"],\" 個結果\"]}")as Messages;
|
|
1
|
+
/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"+7Wr2a\":[\"Edit: \",[\"title\"]],\"+MACwa\":[\"尚未有任何收藏。\"],\"+owNNn\":[\"文章\"],\"+zy2Nq\":[\"類型\"],\"/0D1Xp\":[\"編輯收藏集\"],\"/Rj5P4\":[\"您的姓名\"],\"07Epll\":[\"這將為您的網站和儀表板設置主題。所有顏色主題都支持深色模式。\"],\"0JkyS7\":[\"建立您的第一個頁面\"],\"0a6MpL\":[\"新重定向\"],\"1CU1Td\":[\"網址安全識別碼(小寫、數字、連字符)\"],\"1DBGsz\":[\"筆記\"],\"1o+wgo\":[\"e.g. The Verge, John Doe\"],\"2N0qpv\":[\"文章標題...\"],\"2fUwEY\":[\"Select Media\"],\"2q/Q7x\":[\"可見性\"],\"2rJGtU\":[\"頁面標題...\"],\"3Yvsaz\":[\"302(臨時)\"],\"4/SFQS\":[\"查看網站\"],\"40TVQj\":[\"自訂路徑(選填)\"],\"4KzVT6\":[\"刪除頁面\"],\"4b3oEV\":[\"內容\"],\"4mDPGp\":[\"此頁面的 URL 路徑。使用小寫字母、數字和連字符。\"],\"6WdDG7\":[\"頁面\"],\"6YtxFj\":[\"名稱\"],\"7G4SBz\":[\"頁面內容(支持Markdown)...\"],\"7Mk+/h\":[\"更新收藏集\"],\"7Q1KKN\":[\"來源路徑\"],\"7aECQB\":[\"無效或已過期的連結\"],\"7nGhhM\":[\"你在想什麼?\"],\"7p5kLi\":[\"儀表板\"],\"7vhWI8\":[\"新密碼\"],\"8ZsakT\":[\"密碼\"],\"90Luob\":[[\"count\"],\" 條回覆\"],\"A1taO8\":[\"搜尋\"],\"AeXO77\":[\"帳戶\"],\"AyHO4m\":[\"這個收藏是關於什麼的?\"],\"B373X+\":[\"編輯文章\"],\"B495Gs\":[\"檔案館\"],\"BjF0Jv\":[\"僅限小寫字母、數字和連字符\"],\"D9Oea+\":[\"永久鏈接\"],\"DCKkhU\":[\"當前密碼\"],\"DHhJ7s\":[\"上一頁\"],\"DPfwMq\":[\"Done\"],\"DoJzLz\":[\"收藏夾\"],\"E80cJw\":[\"刪除此媒體將永久從存儲中移除它。\"],\"EEYbdt\":[\"發佈\"],\"EGwzOK\":[\"完成設置\"],\"EkH9pt\":[\"更新\"],\"FGrimz\":[\"新帖子\"],\"FkMol5\":[\"精選\"],\"Fxf4jq\":[\"描述(可選)\"],\"GA5A5H\":[\"刪除收藏夾\"],\"GX2VMa\":[\"建立您的管理員帳戶。\"],\"GbVAnd\":[\"此密碼重設連結無效或已過期。請生成一個新的連結。\"],\"GorKul\":[\"歡迎來到 Jant\"],\"GrZ6fH\":[\"新頁面\"],\"GxkJXS\":[\"上傳中...\"],\"HfyyXl\":[\"My Blog\"],\"HiETwV\":[\"安靜(正常)\"],\"Hzi9AA\":[\"未找到任何帖子。\"],\"I6gXOa\":[\"路徑\"],\"I8hDlV\":[\"At least 1 image required for image posts.\"],\"IagCbF\":[\"網址\"],\"J4FNfC\":[\"此集合中沒有帖子。\"],\"JIBC/T\":[\"Supported formats: JPEG, PNG, GIF, WebP, SVG. Max size: 10MB.\"],\"Jed1wB\":[\"需要幫助嗎?請訪問<0>文檔</0>。\"],\"JiP4aa\":[\"已發佈的頁面可以通過其路徑訪問。草稿不可見。\"],\"K9NcLu\":[\"使用此 URL 將媒體嵌入到您的帖子中。\"],\"KbS2K9\":[\"重設密碼\"],\"KiJn9B\":[\"備註\"],\"L85WcV\":[\"縮略名\"],\"LkvLQe\":[\"尚未有頁面。\"],\"M1RvTd\":[\"點擊圖片以查看完整大小\"],\"M8kJqa\":[\"草稿\"],\"M9xgHy\":[\"重定向\"],\"MHrjPM\":[\"標題\"],\"MWBOxm\":[\"Collections (optional)\"],\"MZbQHL\":[\"未找到結果。\"],\"Mhf/H/\":[\"建立重定向\"],\"MqghUt\":[\"搜尋帖子...\"],\"N40H+G\":[\"所有\"],\"O3oNi5\":[\"電子郵件\"],\"OCNZaU\":[\"重定向來源的路徑\"],\"ODiSoW\":[\"尚未有帖子。\"],\"ONWvwQ\":[\"上傳\"],\"Pbm2/N\":[\"創建收藏夾\"],\"RDjuBN\":[\"Setup\"],\"Rj01Fz\":[\"連結\"],\"RwGhWy\":[\"包含 \",[\"count\"],\" 則帖子的主題\"],\"SJmfuf\":[\"網站名稱\"],\"ST+lN2\":[\"尚未上傳任何媒體。\"],\"Tt5T6+\":[\"文章\"],\"TxE+Mj\":[\"1 條回覆\"],\"Tz0i8g\":[\"設定\"],\"U5v6Gh\":[\"編輯頁面\"],\"UDMjsP\":[\"快速操作\"],\"UGT5vp\":[\"保存設定\"],\"VUSy8D\":[\"Search failed. Please try again.\"],\"VhMDMg\":[\"更改密碼\"],\"WDcQq9\":[\"不公開\"],\"Weq9zb\":[\"一般設定\"],\"WmZ/rP\":[\"到路徑\"],\"Y+7JGK\":[\"創建頁面\"],\"Z3FXyt\":[\"Loading...\"],\"ZQKLI1\":[\"危險區域\"],\"ZhhOwV\":[\"引用\"],\"aAIQg2\":[\"外觀\"],\"an5hVd\":[\"圖片\"],\"b+/jO6\":[\"301(永久)\"],\"bHYIks\":[\"登出\"],\"biOepV\":[\"← 返回首頁\"],\"cnGeoo\":[\"刪除\"],\"dEgA5A\":[\"取消\"],\"e6Jr7Q\":[\"← 返回收藏夾\"],\"ePK91l\":[\"編輯\"],\"eWLklq\":[\"引用\"],\"eneWvv\":[\"草稿\"],\"er8+x7\":[\"示範帳戶已預填。只需點擊登入。\"],\"f6e0Ry\":[\"文章\"],\"fG7BxZ\":[\"Upload images via the API: POST /api/upload with a file form field.\"],\"fttd2R\":[\"我的收藏\"],\"hG89Ed\":[\"圖片\"],\"hWOZIv\":[\"請輸入您的新密碼。\"],\"hXzOVo\":[\"下一頁\"],\"he3ygx\":[\"複製\"],\"iH8pgl\":[\"返回\"],\"ig4hg2\":[\"Let's set up your site.\"],\"jpctdh\":[\"查看\"],\"k1ifdL\":[\"處理中...\"],\"mTOYla\":[\"查看所有文章 →\"],\"n1ekoW\":[\"登入\"],\"oJFOZk\":[\"Source Name (optional)\"],\"oYPBa0\":[\"更新頁面\"],\"p2/GCq\":[\"確認密碼\"],\"pRhYH2\":[\"收藏中的帖子 (\",[\"count\"],\")\"],\"pZq3aX\":[\"上傳失敗。請再試一次。\"],\"qMyM2u\":[\"來源網址(選填)\"],\"qiXmlF\":[\"Add Media\"],\"r1MpXi\":[\"安靜\"],\"rFmBG3\":[\"顏色主題\"],\"rdUucN\":[\"預覽\"],\"rzNUSl\":[\"包含 1 則貼文的主題\"],\"sGajR7\":[\"線程開始\"],\"ssqvZi\":[\"保存個人資料\"],\"t/YqKh\":[\"移除\"],\"tfrt7B\":[\"未配置任何重定向。\"],\"tiq7kl\":[\"頁面 \",[\"page\"]],\"u2f7vd\":[\"網站描述\"],\"u3wRF+\":[\"已發佈\"],\"u6Hp4N\":[\"Markdown\"],\"uAQUqI\":[\"狀態\"],\"vERlcd\":[\"個人資料\"],\"vXIe7J\":[\"語言\"],\"vzU4k9\":[\"新收藏集\"],\"wEF6Ix\":[\"目的地路徑或 URL\"],\"wK4OTM\":[\"標題(選填)\"],\"wM5UXj\":[\"刪除媒體\"],\"wRR604\":[\"頁面\"],\"wja8aL\":[\"無標題\"],\"x+doid\":[\"圖片會自動優化:調整大小至最大 1920 像素,轉換為 WebP 格式,並去除元數據。\"],\"x0mzE0\":[\"創建你的第一篇帖子\"],\"x4RuFo\":[\"回到首頁\"],\"xYilR2\":[\"媒體\"],\"y28hnO\":[\"文章\"],\"yQ2kGp\":[\"載入更多\"],\"yjkELF\":[\"確認新密碼\"],\"yzF66j\":[\"連結\"],\"z8ajIE\":[\"找到 1 個結果\"],\"zH6KqE\":[\"找到 \",[\"count\"],\" 個結果\"]}")as Messages;
|
package/src/index.ts
CHANGED
|
@@ -17,6 +17,8 @@ export type {
|
|
|
17
17
|
Bindings,
|
|
18
18
|
Post,
|
|
19
19
|
Media,
|
|
20
|
+
MediaAttachment,
|
|
21
|
+
PostWithMedia,
|
|
20
22
|
Collection,
|
|
21
23
|
PostCollection,
|
|
22
24
|
Redirect,
|
|
@@ -28,7 +30,12 @@ export type {
|
|
|
28
30
|
ThemeComponents,
|
|
29
31
|
} from "./types.js";
|
|
30
32
|
|
|
31
|
-
export {
|
|
33
|
+
export {
|
|
34
|
+
POST_TYPES,
|
|
35
|
+
VISIBILITY_LEVELS,
|
|
36
|
+
MAX_MEDIA_ATTACHMENTS,
|
|
37
|
+
POST_TYPE_MEDIA_RULES,
|
|
38
|
+
} from "./types.js";
|
|
32
39
|
|
|
33
40
|
// Utilities (for theme authors)
|
|
34
41
|
export * as time from "./lib/time.js";
|
|
@@ -7,9 +7,14 @@ import {
|
|
|
7
7
|
UpdatePostSchema,
|
|
8
8
|
parseFormData,
|
|
9
9
|
parseFormDataOptional,
|
|
10
|
+
validateMediaForPostType,
|
|
10
11
|
} from "../schemas.js";
|
|
11
12
|
import { z } from "zod";
|
|
12
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
POST_TYPES,
|
|
15
|
+
VISIBILITY_LEVELS,
|
|
16
|
+
MAX_MEDIA_ATTACHMENTS,
|
|
17
|
+
} from "../../types.js";
|
|
13
18
|
|
|
14
19
|
describe("PostTypeSchema", () => {
|
|
15
20
|
it("accepts all valid post types", () => {
|
|
@@ -145,6 +150,37 @@ describe("CreatePostSchema", () => {
|
|
|
145
150
|
).toThrow();
|
|
146
151
|
});
|
|
147
152
|
|
|
153
|
+
it("accepts valid mediaIds", () => {
|
|
154
|
+
const result = CreatePostSchema.parse({
|
|
155
|
+
...validPost,
|
|
156
|
+
mediaIds: ["id-1", "id-2"],
|
|
157
|
+
});
|
|
158
|
+
expect(result.mediaIds).toEqual(["id-1", "id-2"]);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("accepts empty mediaIds array", () => {
|
|
162
|
+
const result = CreatePostSchema.parse({
|
|
163
|
+
...validPost,
|
|
164
|
+
mediaIds: [],
|
|
165
|
+
});
|
|
166
|
+
expect(result.mediaIds).toEqual([]);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("accepts omitted mediaIds", () => {
|
|
170
|
+
const result = CreatePostSchema.parse(validPost);
|
|
171
|
+
expect(result.mediaIds).toBeUndefined();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("rejects mediaIds over MAX_MEDIA_ATTACHMENTS", () => {
|
|
175
|
+
const tooMany = Array.from(
|
|
176
|
+
{ length: MAX_MEDIA_ATTACHMENTS + 1 },
|
|
177
|
+
(_, i) => `id-${i}`,
|
|
178
|
+
);
|
|
179
|
+
expect(() =>
|
|
180
|
+
CreatePostSchema.parse({ ...validPost, mediaIds: tooMany }),
|
|
181
|
+
).toThrow();
|
|
182
|
+
});
|
|
183
|
+
|
|
148
184
|
it("rejects missing required fields", () => {
|
|
149
185
|
expect(() => CreatePostSchema.parse({})).toThrow();
|
|
150
186
|
expect(() => CreatePostSchema.parse({ type: "note" })).toThrow();
|
|
@@ -218,3 +254,55 @@ describe("parseFormDataOptional", () => {
|
|
|
218
254
|
expect(() => parseFormDataOptional(form, "type", PostTypeSchema)).toThrow();
|
|
219
255
|
});
|
|
220
256
|
});
|
|
257
|
+
|
|
258
|
+
describe("validateMediaForPostType", () => {
|
|
259
|
+
it("returns null for note with no media", () => {
|
|
260
|
+
expect(validateMediaForPostType("note", [])).toBeNull();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("returns null for note with media", () => {
|
|
264
|
+
expect(validateMediaForPostType("note", ["id-1", "id-2"])).toBeNull();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("returns null for article with media", () => {
|
|
268
|
+
expect(validateMediaForPostType("article", ["id-1"])).toBeNull();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it("returns null for image with at least 1 media", () => {
|
|
272
|
+
expect(validateMediaForPostType("image", ["id-1"])).toBeNull();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it("returns error for image with no media", () => {
|
|
276
|
+
const error = validateMediaForPostType("image", []);
|
|
277
|
+
expect(error).toBe("image posts require at least 1 media attachment");
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("returns null for link with 0 or 1 media", () => {
|
|
281
|
+
expect(validateMediaForPostType("link", [])).toBeNull();
|
|
282
|
+
expect(validateMediaForPostType("link", ["id-1"])).toBeNull();
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("returns error for link with more than 1 media", () => {
|
|
286
|
+
const error = validateMediaForPostType("link", ["id-1", "id-2"]);
|
|
287
|
+
expect(error).toBe("link posts allow at most 1 media attachment");
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it("returns error for page with any media", () => {
|
|
291
|
+
const error = validateMediaForPostType("page", ["id-1"]);
|
|
292
|
+
expect(error).toBe("page posts do not allow media attachments");
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it("returns null for page with no media", () => {
|
|
296
|
+
expect(validateMediaForPostType("page", [])).toBeNull();
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it("returns null for quote with media", () => {
|
|
300
|
+
expect(validateMediaForPostType("quote", ["id-1", "id-2"])).toBeNull();
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it("returns error when exceeding max for note", () => {
|
|
304
|
+
const tooMany = Array.from({ length: 21 }, (_, i) => `id-${i}`);
|
|
305
|
+
const error = validateMediaForPostType("note", tooMany);
|
|
306
|
+
expect(error).toBe("note posts allow at most 20 media attachments");
|
|
307
|
+
});
|
|
308
|
+
});
|
|
@@ -25,13 +25,25 @@ describe("dsRedirect", () => {
|
|
|
25
25
|
expect(body).toContain("\\'");
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
-
it("merges additional headers", () => {
|
|
28
|
+
it("merges additional headers from plain object", () => {
|
|
29
29
|
const res = dsRedirect("/dash", {
|
|
30
30
|
headers: { "Set-Cookie": "session=abc" },
|
|
31
31
|
});
|
|
32
32
|
expect(res.headers.get("Set-Cookie")).toBe("session=abc");
|
|
33
33
|
expect(res.headers.get("Content-Type")).toBe("text/html");
|
|
34
34
|
});
|
|
35
|
+
|
|
36
|
+
it("merges additional headers from Headers instance", () => {
|
|
37
|
+
const headers = new Headers();
|
|
38
|
+
headers.append("set-cookie", "session=abc; Path=/; HttpOnly");
|
|
39
|
+
headers.append("set-cookie", "data=xyz; Path=/; Max-Age=300");
|
|
40
|
+
const res = dsRedirect("/dash", { headers });
|
|
41
|
+
const cookies = res.headers.getSetCookie();
|
|
42
|
+
expect(cookies).toHaveLength(2);
|
|
43
|
+
expect(cookies[0]).toBe("session=abc; Path=/; HttpOnly");
|
|
44
|
+
expect(cookies[1]).toBe("data=xyz; Path=/; Max-Age=300");
|
|
45
|
+
expect(res.headers.get("Content-Type")).toBe("text/html");
|
|
46
|
+
});
|
|
35
47
|
});
|
|
36
48
|
|
|
37
49
|
describe("dsToast", () => {
|
package/src/lib/schemas.ts
CHANGED
|
@@ -9,7 +9,13 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { z } from "zod";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
POST_TYPES,
|
|
14
|
+
VISIBILITY_LEVELS,
|
|
15
|
+
MAX_MEDIA_ATTACHMENTS,
|
|
16
|
+
POST_TYPE_MEDIA_RULES,
|
|
17
|
+
} from "../types.js";
|
|
18
|
+
import type { PostType } from "../types.js";
|
|
13
19
|
|
|
14
20
|
/**
|
|
15
21
|
* Post type enum schema
|
|
@@ -46,6 +52,7 @@ export const CreatePostSchema = z.object({
|
|
|
46
52
|
.or(z.literal("")),
|
|
47
53
|
replyToId: z.string().optional(), // Sqid format
|
|
48
54
|
publishedAt: z.number().int().positive().optional(),
|
|
55
|
+
mediaIds: z.array(z.string()).max(MAX_MEDIA_ATTACHMENTS).optional(),
|
|
49
56
|
});
|
|
50
57
|
|
|
51
58
|
/**
|
|
@@ -94,3 +101,42 @@ export function parseFormDataOptional<T>(
|
|
|
94
101
|
}
|
|
95
102
|
return schema.parse(value);
|
|
96
103
|
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Validates media attachment count against post type rules.
|
|
107
|
+
*
|
|
108
|
+
* @param type - The post type to validate against
|
|
109
|
+
* @param mediaIds - Array of media IDs to attach
|
|
110
|
+
* @returns null if valid, error string if invalid
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```ts
|
|
114
|
+
* const error = validateMediaForPostType("image", []);
|
|
115
|
+
* // Returns: "image posts require at least 1 media attachment"
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
export function validateMediaForPostType(
|
|
119
|
+
type: PostType,
|
|
120
|
+
mediaIds: string[],
|
|
121
|
+
): string | null {
|
|
122
|
+
const rules = POST_TYPE_MEDIA_RULES[type];
|
|
123
|
+
|
|
124
|
+
if (rules === null) {
|
|
125
|
+
if (mediaIds.length > 0) {
|
|
126
|
+
return `${type} posts do not allow media attachments`;
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const [min, max] = rules;
|
|
132
|
+
|
|
133
|
+
if (mediaIds.length < min) {
|
|
134
|
+
return `${type} posts require at least ${min} media attachment${min !== 1 ? "s" : ""}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (mediaIds.length > max) {
|
|
138
|
+
return `${type} posts allow at most ${max} media attachment${max !== 1 ? "s" : ""}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return null;
|
|
142
|
+
}
|