@jant/core 0.3.4 → 0.3.6
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 +3 -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/middleware/onboarding.d.ts +26 -0
- package/dist/middleware/onboarding.d.ts.map +1 -0
- package/dist/middleware/onboarding.js +36 -0
- package/dist/routes/pages/home.js +0 -4
- package/package.json +1 -1
- package/src/app.tsx +4 -0
- package/src/i18n/locales/en.po +80 -54
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +80 -54
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +80 -54
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/middleware/__tests__/onboarding.test.ts +143 -0
- package/src/middleware/onboarding.ts +60 -0
- package/src/routes/pages/home.tsx +0 -5
|
@@ -45,13 +45,18 @@ msgstr "301(永久)"
|
|
|
45
45
|
msgid "302 (Temporary)"
|
|
46
46
|
msgstr "302(臨時)"
|
|
47
47
|
|
|
48
|
+
#. @context: Settings sub-navigation tab
|
|
49
|
+
#: src/routes/dash/settings.tsx:63
|
|
50
|
+
msgid "Account"
|
|
51
|
+
msgstr "帳戶"
|
|
52
|
+
|
|
48
53
|
#. @context: Archive filter - all types
|
|
49
54
|
#: src/routes/pages/archive.tsx:115
|
|
50
55
|
msgid "All"
|
|
51
56
|
msgstr "所有"
|
|
52
57
|
|
|
53
|
-
#. @context:
|
|
54
|
-
#: src/
|
|
58
|
+
#. @context: Settings sub-navigation tab
|
|
59
|
+
#: src/routes/dash/settings.tsx:55
|
|
55
60
|
msgid "Appearance"
|
|
56
61
|
msgstr "外觀"
|
|
57
62
|
|
|
@@ -77,7 +82,7 @@ msgid "Articles"
|
|
|
77
82
|
msgstr "文章"
|
|
78
83
|
|
|
79
84
|
#. @context: Button to go back to media list
|
|
80
|
-
#: src/routes/dash/media.tsx:
|
|
85
|
+
#: src/routes/dash/media.tsx:245
|
|
81
86
|
msgid "Back"
|
|
82
87
|
msgstr "返回"
|
|
83
88
|
|
|
@@ -105,13 +110,13 @@ msgstr "取消"
|
|
|
105
110
|
|
|
106
111
|
#. @context: Button to change password
|
|
107
112
|
#. @context: Settings section heading
|
|
108
|
-
#: src/routes/dash/settings.tsx:
|
|
109
|
-
#: src/routes/dash/settings.tsx:
|
|
113
|
+
#: src/routes/dash/settings.tsx:392
|
|
114
|
+
#: src/routes/dash/settings.tsx:452
|
|
110
115
|
msgid "Change Password"
|
|
111
116
|
msgstr "更改密碼"
|
|
112
117
|
|
|
113
118
|
#. @context: Hint to click image for lightbox
|
|
114
|
-
#: src/routes/dash/media.tsx:
|
|
119
|
+
#: src/routes/dash/media.tsx:278
|
|
115
120
|
msgid "Click image to view full size"
|
|
116
121
|
msgstr "點擊圖片以查看完整大小"
|
|
117
122
|
|
|
@@ -123,22 +128,22 @@ msgid "Collections"
|
|
|
123
128
|
msgstr "收藏夾"
|
|
124
129
|
|
|
125
130
|
#. @context: Appearance settings heading
|
|
126
|
-
#: src/routes/dash/
|
|
131
|
+
#: src/routes/dash/settings.tsx:301
|
|
127
132
|
msgid "Color theme"
|
|
128
133
|
msgstr "顏色主題"
|
|
129
134
|
|
|
130
135
|
#. @context: Setup form submit button
|
|
131
|
-
#: src/app.tsx:
|
|
136
|
+
#: src/app.tsx:245
|
|
132
137
|
msgid "Complete Setup"
|
|
133
138
|
msgstr "完成設置"
|
|
134
139
|
|
|
135
140
|
#. @context: Password form field
|
|
136
|
-
#: src/routes/dash/settings.tsx:
|
|
141
|
+
#: src/routes/dash/settings.tsx:434
|
|
137
142
|
msgid "Confirm New Password"
|
|
138
143
|
msgstr "確認新密碼"
|
|
139
144
|
|
|
140
145
|
#. @context: Password reset form field
|
|
141
|
-
#: src/app.tsx:
|
|
146
|
+
#: src/app.tsx:505
|
|
142
147
|
msgid "Confirm Password"
|
|
143
148
|
msgstr "確認密碼"
|
|
144
149
|
|
|
@@ -151,8 +156,8 @@ msgstr "內容"
|
|
|
151
156
|
|
|
152
157
|
#. @context: Button to copy Markdown to clipboard
|
|
153
158
|
#. @context: Button to copy URL to clipboard
|
|
154
|
-
#: src/routes/dash/media.tsx:
|
|
155
|
-
#: src/routes/dash/media.tsx:
|
|
159
|
+
#: src/routes/dash/media.tsx:316
|
|
160
|
+
#: src/routes/dash/media.tsx:353
|
|
156
161
|
msgid "Copy"
|
|
157
162
|
msgstr "複製"
|
|
158
163
|
|
|
@@ -172,7 +177,7 @@ msgid "Create Redirect"
|
|
|
172
177
|
msgstr "建立重定向"
|
|
173
178
|
|
|
174
179
|
#. @context: Setup page description
|
|
175
|
-
#: src/app.tsx:
|
|
180
|
+
#: src/app.tsx:187
|
|
176
181
|
msgid "Create your admin account."
|
|
177
182
|
msgstr "建立您的管理員帳戶。"
|
|
178
183
|
|
|
@@ -187,7 +192,7 @@ msgid "Create your first post"
|
|
|
187
192
|
msgstr "創建你的第一篇帖子"
|
|
188
193
|
|
|
189
194
|
#. @context: Password form field
|
|
190
|
-
#: src/routes/dash/settings.tsx:
|
|
195
|
+
#: src/routes/dash/settings.tsx:401
|
|
191
196
|
msgid "Current Password"
|
|
192
197
|
msgstr "當前密碼"
|
|
193
198
|
|
|
@@ -221,7 +226,7 @@ msgid "Delete Collection"
|
|
|
221
226
|
msgstr "刪除收藏夾"
|
|
222
227
|
|
|
223
228
|
#. @context: Button to delete media
|
|
224
|
-
#: src/routes/dash/media.tsx:
|
|
229
|
+
#: src/routes/dash/media.tsx:364
|
|
225
230
|
msgid "Delete Media"
|
|
226
231
|
msgstr "刪除媒體"
|
|
227
232
|
|
|
@@ -231,12 +236,12 @@ msgid "Delete Page"
|
|
|
231
236
|
msgstr "刪除頁面"
|
|
232
237
|
|
|
233
238
|
#. @context: Warning message before deleting media
|
|
234
|
-
#: src/routes/dash/media.tsx:
|
|
239
|
+
#: src/routes/dash/media.tsx:370
|
|
235
240
|
msgid "Deleting this media will remove it permanently from storage."
|
|
236
241
|
msgstr "刪除此媒體將永久從存儲中移除它。"
|
|
237
242
|
|
|
238
243
|
#. @context: Hint shown on signin page when demo credentials are pre-filled
|
|
239
|
-
#: src/app.tsx:
|
|
244
|
+
#: src/app.tsx:336
|
|
240
245
|
msgid "Demo account pre-filled. Just click Sign In."
|
|
241
246
|
msgstr "示範帳戶已預填。只需點擊登入。"
|
|
242
247
|
|
|
@@ -272,7 +277,7 @@ msgstr "草稿"
|
|
|
272
277
|
#: src/routes/dash/collections.tsx:210
|
|
273
278
|
#: src/routes/dash/pages.tsx:64
|
|
274
279
|
#: src/routes/dash/pages.tsx:134
|
|
275
|
-
#: src/routes/dash/posts.tsx:
|
|
280
|
+
#: src/routes/dash/posts.tsx:124
|
|
276
281
|
#: src/theme/components/ActionButtons.tsx:72
|
|
277
282
|
#: src/theme/components/PostList.tsx:46
|
|
278
283
|
msgid "Edit"
|
|
@@ -289,19 +294,19 @@ msgid "Edit Page"
|
|
|
289
294
|
msgstr "編輯頁面"
|
|
290
295
|
|
|
291
296
|
#. @context: Page heading
|
|
292
|
-
#: src/routes/dash/posts.tsx:
|
|
297
|
+
#: src/routes/dash/posts.tsx:153
|
|
293
298
|
msgid "Edit Post"
|
|
294
299
|
msgstr "編輯文章"
|
|
295
300
|
|
|
296
301
|
#. @context: Setup/signin form field - email
|
|
297
302
|
#. @context: Setup/signin form field - email
|
|
298
|
-
#: src/app.tsx:
|
|
299
|
-
#: src/app.tsx:
|
|
303
|
+
#: src/app.tsx:216
|
|
304
|
+
#: src/app.tsx:350
|
|
300
305
|
msgid "Email"
|
|
301
306
|
msgstr "電子郵件"
|
|
302
307
|
|
|
303
308
|
#. @context: Password reset page description
|
|
304
|
-
#: src/app.tsx:
|
|
309
|
+
#: src/app.tsx:475
|
|
305
310
|
msgid "Enter your new password."
|
|
306
311
|
msgstr "請輸入您的新密碼。"
|
|
307
312
|
|
|
@@ -330,7 +335,9 @@ msgid "From Path"
|
|
|
330
335
|
msgstr "來源路徑"
|
|
331
336
|
|
|
332
337
|
#. @context: Settings section heading
|
|
333
|
-
|
|
338
|
+
#. @context: Settings sub-navigation tab
|
|
339
|
+
#: src/routes/dash/settings.tsx:47
|
|
340
|
+
#: src/routes/dash/settings.tsx:130
|
|
334
341
|
msgid "General"
|
|
335
342
|
msgstr "一般設定"
|
|
336
343
|
|
|
@@ -349,17 +356,17 @@ msgid "Images"
|
|
|
349
356
|
msgstr "圖片"
|
|
350
357
|
|
|
351
358
|
#. @context: Media upload instructions - auto optimization
|
|
352
|
-
#: src/routes/dash/media.tsx:
|
|
359
|
+
#: src/routes/dash/media.tsx:158
|
|
353
360
|
msgid "Images are automatically optimized: resized to max 1920px, converted to WebP, and metadata stripped."
|
|
354
361
|
msgstr "圖片會自動優化:調整大小至最大 1920 像素,轉換為 WebP 格式,並去除元數據。"
|
|
355
362
|
|
|
356
363
|
#. @context: Password reset error heading
|
|
357
|
-
#: src/app.tsx:
|
|
364
|
+
#: src/app.tsx:540
|
|
358
365
|
msgid "Invalid or Expired Link"
|
|
359
366
|
msgstr "無效或已過期的連結"
|
|
360
367
|
|
|
361
368
|
#. @context: Settings form field
|
|
362
|
-
#: src/routes/dash/settings.tsx:
|
|
369
|
+
#: src/routes/dash/settings.tsx:171
|
|
363
370
|
msgid "Language"
|
|
364
371
|
msgstr "語言"
|
|
365
372
|
|
|
@@ -388,13 +395,13 @@ msgid "Lowercase letters, numbers, and hyphens only"
|
|
|
388
395
|
msgstr "僅限小寫字母、數字和連字符"
|
|
389
396
|
|
|
390
397
|
#. @context: Media detail section - Markdown snippet
|
|
391
|
-
#: src/routes/dash/media.tsx:
|
|
398
|
+
#: src/routes/dash/media.tsx:334
|
|
392
399
|
msgid "Markdown"
|
|
393
400
|
msgstr "Markdown"
|
|
394
401
|
|
|
395
402
|
#. @context: Dashboard navigation - media library
|
|
396
403
|
#. @context: Media main heading
|
|
397
|
-
#: src/routes/dash/media.tsx:
|
|
404
|
+
#: src/routes/dash/media.tsx:138
|
|
398
405
|
#: src/theme/layouts/DashLayout.tsx:105
|
|
399
406
|
msgid "Media"
|
|
400
407
|
msgstr "媒體"
|
|
@@ -404,6 +411,11 @@ msgstr "媒體"
|
|
|
404
411
|
msgid "My Collection"
|
|
405
412
|
msgstr "我的收藏"
|
|
406
413
|
|
|
414
|
+
#. @context: Account settings form field
|
|
415
|
+
#: src/routes/dash/settings.tsx:362
|
|
416
|
+
msgid "Name"
|
|
417
|
+
msgstr "名稱"
|
|
418
|
+
|
|
407
419
|
#. @context: Help text with link
|
|
408
420
|
#: src/routes/dash/index.tsx:78
|
|
409
421
|
msgid "Need help? Visit the <0>documentation</0>"
|
|
@@ -427,8 +439,8 @@ msgstr "新頁面"
|
|
|
427
439
|
|
|
428
440
|
#. @context: Password form field
|
|
429
441
|
#. @context: Password reset form field
|
|
430
|
-
#: src/app.tsx:
|
|
431
|
-
#: src/routes/dash/settings.tsx:
|
|
442
|
+
#: src/app.tsx:489
|
|
443
|
+
#: src/routes/dash/settings.tsx:417
|
|
432
444
|
msgid "New Password"
|
|
433
445
|
msgstr "新密碼"
|
|
434
446
|
|
|
@@ -463,7 +475,7 @@ msgid "No collections yet."
|
|
|
463
475
|
msgstr "尚未有任何收藏。"
|
|
464
476
|
|
|
465
477
|
#. @context: Empty state message when no media exists
|
|
466
|
-
#: src/routes/dash/media.tsx:
|
|
478
|
+
#: src/routes/dash/media.tsx:173
|
|
467
479
|
msgid "No media uploaded yet."
|
|
468
480
|
msgstr "尚未上傳任何媒體。"
|
|
469
481
|
|
|
@@ -550,8 +562,8 @@ msgstr "頁面"
|
|
|
550
562
|
|
|
551
563
|
#. @context: Setup/signin form field - password
|
|
552
564
|
#. @context: Setup/signin form field - password
|
|
553
|
-
#: src/app.tsx:
|
|
554
|
-
#: src/app.tsx:
|
|
565
|
+
#: src/app.tsx:231
|
|
566
|
+
#: src/app.tsx:359
|
|
555
567
|
msgid "Password"
|
|
556
568
|
msgstr "密碼"
|
|
557
569
|
|
|
@@ -568,7 +580,7 @@ msgid "Permalink"
|
|
|
568
580
|
msgstr "永久鏈接"
|
|
569
581
|
|
|
570
582
|
#. @context: Default post title
|
|
571
|
-
#: src/routes/dash/posts.tsx:
|
|
583
|
+
#: src/routes/dash/posts.tsx:113
|
|
572
584
|
msgid "Post"
|
|
573
585
|
msgstr "文章"
|
|
574
586
|
|
|
@@ -590,7 +602,7 @@ msgid "Posts in Collection ({count})"
|
|
|
590
602
|
msgstr "收藏中的帖子 ({count})"
|
|
591
603
|
|
|
592
604
|
#. @context: Media detail section - preview
|
|
593
|
-
#: src/routes/dash/media.tsx:
|
|
605
|
+
#: src/routes/dash/media.tsx:257
|
|
594
606
|
msgid "Preview"
|
|
595
607
|
msgstr "預覽"
|
|
596
608
|
|
|
@@ -602,10 +614,15 @@ msgid "Previous"
|
|
|
602
614
|
msgstr "上一頁"
|
|
603
615
|
|
|
604
616
|
#. @context: Upload status - processing
|
|
605
|
-
#: src/routes/dash/media.tsx:
|
|
617
|
+
#: src/routes/dash/media.tsx:106
|
|
606
618
|
msgid "Processing..."
|
|
607
619
|
msgstr "處理中..."
|
|
608
620
|
|
|
621
|
+
#. @context: Account settings section heading
|
|
622
|
+
#: src/routes/dash/settings.tsx:353
|
|
623
|
+
msgid "Profile"
|
|
624
|
+
msgstr "個人資料"
|
|
625
|
+
|
|
609
626
|
#. @context: Button to publish new post
|
|
610
627
|
#: src/theme/components/PostForm.tsx:175
|
|
611
628
|
msgid "Publish"
|
|
@@ -666,13 +683,18 @@ msgstr "移除"
|
|
|
666
683
|
|
|
667
684
|
#. @context: Password reset form submit button
|
|
668
685
|
#. @context: Password reset page heading
|
|
669
|
-
#: src/app.tsx:
|
|
670
|
-
#: src/app.tsx:
|
|
686
|
+
#: src/app.tsx:469
|
|
687
|
+
#: src/app.tsx:520
|
|
671
688
|
msgid "Reset Password"
|
|
672
689
|
msgstr "重設密碼"
|
|
673
690
|
|
|
691
|
+
#. @context: Button to save profile
|
|
692
|
+
#: src/routes/dash/settings.tsx:378
|
|
693
|
+
msgid "Save Profile"
|
|
694
|
+
msgstr "保存個人資料"
|
|
695
|
+
|
|
674
696
|
#. @context: Button to save settings
|
|
675
|
-
#: src/routes/dash/settings.tsx:
|
|
697
|
+
#: src/routes/dash/settings.tsx:192
|
|
676
698
|
msgid "Save Settings"
|
|
677
699
|
msgstr "保存設定"
|
|
678
700
|
|
|
@@ -688,17 +710,21 @@ msgstr "搜尋"
|
|
|
688
710
|
msgid "Search posts..."
|
|
689
711
|
msgstr "搜尋帖子..."
|
|
690
712
|
|
|
713
|
+
#. @context: Dashboard heading
|
|
714
|
+
#. @context: Dashboard heading
|
|
691
715
|
#. @context: Dashboard heading
|
|
692
716
|
#. @context: Dashboard navigation - site settings
|
|
693
|
-
#: src/routes/dash/settings.tsx:
|
|
717
|
+
#: src/routes/dash/settings.tsx:118
|
|
718
|
+
#: src/routes/dash/settings.tsx:290
|
|
719
|
+
#: src/routes/dash/settings.tsx:341
|
|
694
720
|
#: src/theme/layouts/DashLayout.tsx:133
|
|
695
721
|
msgid "Settings"
|
|
696
722
|
msgstr "設定"
|
|
697
723
|
|
|
698
724
|
#. @context: Sign in form submit button
|
|
699
725
|
#. @context: Sign in page heading
|
|
700
|
-
#: src/app.tsx:
|
|
701
|
-
#: src/app.tsx:
|
|
726
|
+
#: src/app.tsx:327
|
|
727
|
+
#: src/app.tsx:372
|
|
702
728
|
msgid "Sign In"
|
|
703
729
|
msgstr "登入"
|
|
704
730
|
|
|
@@ -708,12 +734,12 @@ msgid "Sign Out"
|
|
|
708
734
|
msgstr "登出"
|
|
709
735
|
|
|
710
736
|
#. @context: Settings form field
|
|
711
|
-
#: src/routes/dash/settings.tsx:
|
|
737
|
+
#: src/routes/dash/settings.tsx:154
|
|
712
738
|
msgid "Site Description"
|
|
713
739
|
msgstr "網站描述"
|
|
714
740
|
|
|
715
741
|
#. @context: Settings form field
|
|
716
|
-
#: src/routes/dash/settings.tsx:
|
|
742
|
+
#: src/routes/dash/settings.tsx:139
|
|
717
743
|
msgid "Site Name"
|
|
718
744
|
msgstr "網站名稱"
|
|
719
745
|
|
|
@@ -750,12 +776,12 @@ msgid "The URL path for this page. Use lowercase letters, numbers, and hyphens."
|
|
|
750
776
|
msgstr "此頁面的 URL 路徑。使用小寫字母、數字和連字符。"
|
|
751
777
|
|
|
752
778
|
#. @context: Password reset error description
|
|
753
|
-
#: src/app.tsx:
|
|
779
|
+
#: src/app.tsx:548
|
|
754
780
|
msgid "This password reset link is invalid or has expired. Please generate a new one."
|
|
755
781
|
msgstr "此密碼重設連結無效或已過期。請生成一個新的連結。"
|
|
756
782
|
|
|
757
783
|
#. @context: Appearance settings description
|
|
758
|
-
#: src/routes/dash/
|
|
784
|
+
#: src/routes/dash/settings.tsx:307
|
|
759
785
|
msgid "This will theme both your site and your dashboard. All color themes support dark mode."
|
|
760
786
|
msgstr "這將為您的網站和儀表板設置主題。所有顏色主題都支持深色模式。"
|
|
761
787
|
|
|
@@ -830,22 +856,22 @@ msgid "Update Page"
|
|
|
830
856
|
msgstr "更新頁面"
|
|
831
857
|
|
|
832
858
|
#. @context: Button to upload media file
|
|
833
|
-
#: src/routes/dash/media.tsx:
|
|
859
|
+
#: src/routes/dash/media.tsx:114
|
|
834
860
|
msgid "Upload"
|
|
835
861
|
msgstr "上傳"
|
|
836
862
|
|
|
837
863
|
#. @context: Upload error message
|
|
838
|
-
#: src/routes/dash/media.tsx:
|
|
864
|
+
#: src/routes/dash/media.tsx:118
|
|
839
865
|
msgid "Upload failed. Please try again."
|
|
840
866
|
msgstr "上傳失敗。請再試一次。"
|
|
841
867
|
|
|
842
868
|
#. @context: Upload status - uploading
|
|
843
|
-
#: src/routes/dash/media.tsx:
|
|
869
|
+
#: src/routes/dash/media.tsx:110
|
|
844
870
|
msgid "Uploading..."
|
|
845
871
|
msgstr "上傳中..."
|
|
846
872
|
|
|
847
873
|
#. @context: Media detail section - URL
|
|
848
|
-
#: src/routes/dash/media.tsx:
|
|
874
|
+
#: src/routes/dash/media.tsx:297
|
|
849
875
|
msgid "URL"
|
|
850
876
|
msgstr "網址"
|
|
851
877
|
|
|
@@ -855,7 +881,7 @@ msgid "URL-safe identifier (lowercase, numbers, hyphens)"
|
|
|
855
881
|
msgstr "網址安全識別碼(小寫、數字、連字符)"
|
|
856
882
|
|
|
857
883
|
#. @context: Media URL helper text
|
|
858
|
-
#: src/routes/dash/media.tsx:
|
|
884
|
+
#: src/routes/dash/media.tsx:323
|
|
859
885
|
msgid "Use this URL to embed the media in your posts."
|
|
860
886
|
msgstr "使用此 URL 將媒體嵌入到您的帖子中。"
|
|
861
887
|
|
|
@@ -870,7 +896,7 @@ msgstr "使用此 URL 將媒體嵌入到您的帖子中。"
|
|
|
870
896
|
#: src/routes/dash/collections.tsx:215
|
|
871
897
|
#: src/routes/dash/pages.tsx:73
|
|
872
898
|
#: src/routes/dash/pages.tsx:143
|
|
873
|
-
#: src/routes/dash/posts.tsx:
|
|
899
|
+
#: src/routes/dash/posts.tsx:129
|
|
874
900
|
#: src/theme/components/ActionButtons.tsx:76
|
|
875
901
|
#: src/theme/components/PostList.tsx:51
|
|
876
902
|
msgid "View"
|
|
@@ -892,7 +918,7 @@ msgid "Visibility"
|
|
|
892
918
|
msgstr "可見性"
|
|
893
919
|
|
|
894
920
|
#. @context: Setup page welcome heading
|
|
895
|
-
#: src/app.tsx:
|
|
921
|
+
#: src/app.tsx:181
|
|
896
922
|
msgid "Welcome to Jant"
|
|
897
923
|
msgstr "歡迎來到 Jant"
|
|
898
924
|
|
|
@@ -907,6 +933,6 @@ msgid "What's this collection about?"
|
|
|
907
933
|
msgstr "這個收藏是關於什麼的?"
|
|
908
934
|
|
|
909
935
|
#. @context: Setup form field - user name
|
|
910
|
-
#: src/app.tsx:
|
|
936
|
+
#: src/app.tsx:201
|
|
911
937
|
msgid "Your Name"
|
|
912
938
|
msgstr "您的姓名"
|
|
@@ -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\":[\"頁面\"],\"7G4SBz\":[\"頁面內容(支持Markdown)...\"],\"7Mk+/h\":[\"更新收藏集\"],\"7Q1KKN\":[\"來源路徑\"],\"7aECQB\":[\"無效或已過期的連結\"],\"7nGhhM\":[\"你在想什麼?\"],\"7p5kLi\":[\"儀表板\"],\"7vhWI8\":[\"新密碼\"],\"8ZsakT\":[\"密碼\"],\"90Luob\":[[\"count\"],\" 條回覆\"],\"A1taO8\":[\"搜尋\"],\"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\":[\"線程開始\"],\"t/YqKh\":[\"移除\"],\"tfrt7B\":[\"未配置任何重定向。\"],\"tiq7kl\":[\"頁面 \",[\"page\"]],\"u2f7vd\":[\"網站描述\"],\"u3wRF+\":[\"已發佈\"],\"u6Hp4N\":[\"Markdown\"],\"uAQUqI\":[\"狀態\"],\"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\":[\"筆記\"],\"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;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import { requireOnboarding, resetOnboardingCache } from "../onboarding.js";
|
|
4
|
+
import type { Bindings } from "../../types.js";
|
|
5
|
+
import type { AppVariables } from "../../app.js";
|
|
6
|
+
|
|
7
|
+
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
8
|
+
|
|
9
|
+
function createMockServices(complete: boolean) {
|
|
10
|
+
let callCount = 0;
|
|
11
|
+
const services = {
|
|
12
|
+
settings: {
|
|
13
|
+
isOnboardingComplete: async () => {
|
|
14
|
+
callCount++;
|
|
15
|
+
return complete;
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
} as AppVariables["services"];
|
|
19
|
+
return { services, getCallCount: () => callCount };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function createApp(complete: boolean) {
|
|
23
|
+
const mock = createMockServices(complete);
|
|
24
|
+
const app = new Hono<Env>();
|
|
25
|
+
|
|
26
|
+
app.use("*", async (c, next) => {
|
|
27
|
+
c.set("services", mock.services);
|
|
28
|
+
await next();
|
|
29
|
+
});
|
|
30
|
+
app.use("*", requireOnboarding());
|
|
31
|
+
|
|
32
|
+
// Register routes for testing
|
|
33
|
+
app.get("/", (c) => c.text("Home"));
|
|
34
|
+
app.get("/dash", (c) => c.text("Dashboard"));
|
|
35
|
+
app.get("/archive", (c) => c.text("Archive"));
|
|
36
|
+
app.get("/p/abc", (c) => c.text("Post"));
|
|
37
|
+
app.get("/setup", (c) => c.text("Setup"));
|
|
38
|
+
app.get("/health", (c) => c.text("OK"));
|
|
39
|
+
app.get("/signin", (c) => c.text("Signin"));
|
|
40
|
+
app.get("/signout", (c) => c.text("Signout"));
|
|
41
|
+
app.get("/reset", (c) => c.text("Reset"));
|
|
42
|
+
app.get("/api/auth/session", (c) => c.json({ ok: true }));
|
|
43
|
+
|
|
44
|
+
return { app, getCallCount: mock.getCallCount };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
describe("requireOnboarding", () => {
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
resetOnboardingCache();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("redirects to /setup when onboarding not complete", async () => {
|
|
53
|
+
const { app } = createApp(false);
|
|
54
|
+
const res = await app.request("/", { redirect: "manual" });
|
|
55
|
+
expect(res.status).toBe(302);
|
|
56
|
+
expect(res.headers.get("Location")).toBe("/setup");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("redirects /dash to /setup when onboarding not complete", async () => {
|
|
60
|
+
const { app } = createApp(false);
|
|
61
|
+
const res = await app.request("/dash", { redirect: "manual" });
|
|
62
|
+
expect(res.status).toBe(302);
|
|
63
|
+
expect(res.headers.get("Location")).toBe("/setup");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("redirects /archive to /setup when onboarding not complete", async () => {
|
|
67
|
+
const { app } = createApp(false);
|
|
68
|
+
const res = await app.request("/archive", { redirect: "manual" });
|
|
69
|
+
expect(res.status).toBe(302);
|
|
70
|
+
expect(res.headers.get("Location")).toBe("/setup");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("allows through when onboarding is complete", async () => {
|
|
74
|
+
const { app } = createApp(true);
|
|
75
|
+
const res = await app.request("/");
|
|
76
|
+
expect(res.status).toBe(200);
|
|
77
|
+
expect(await res.text()).toBe("Home");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("caches result — second request skips DB query", async () => {
|
|
81
|
+
const { app, getCallCount } = createApp(true);
|
|
82
|
+
|
|
83
|
+
await app.request("/");
|
|
84
|
+
expect(getCallCount()).toBe(1);
|
|
85
|
+
|
|
86
|
+
await app.request("/dash");
|
|
87
|
+
expect(getCallCount()).toBe(1); // still 1 — cached
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("does not cache incomplete status", async () => {
|
|
91
|
+
const { app, getCallCount } = createApp(false);
|
|
92
|
+
|
|
93
|
+
await app.request("/", { redirect: "manual" });
|
|
94
|
+
expect(getCallCount()).toBe(1);
|
|
95
|
+
|
|
96
|
+
await app.request("/dash", { redirect: "manual" });
|
|
97
|
+
expect(getCallCount()).toBe(2); // queried again
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe("bypass paths", () => {
|
|
101
|
+
it("allows /setup without checking DB", async () => {
|
|
102
|
+
const { app, getCallCount } = createApp(false);
|
|
103
|
+
const res = await app.request("/setup");
|
|
104
|
+
expect(res.status).toBe(200);
|
|
105
|
+
expect(getCallCount()).toBe(0);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("allows /health without checking DB", async () => {
|
|
109
|
+
const { app, getCallCount } = createApp(false);
|
|
110
|
+
const res = await app.request("/health");
|
|
111
|
+
expect(res.status).toBe(200);
|
|
112
|
+
expect(getCallCount()).toBe(0);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("allows /signin without checking DB", async () => {
|
|
116
|
+
const { app, getCallCount } = createApp(false);
|
|
117
|
+
const res = await app.request("/signin");
|
|
118
|
+
expect(res.status).toBe(200);
|
|
119
|
+
expect(getCallCount()).toBe(0);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("allows /signout without checking DB", async () => {
|
|
123
|
+
const { app, getCallCount } = createApp(false);
|
|
124
|
+
const res = await app.request("/signout");
|
|
125
|
+
expect(res.status).toBe(200);
|
|
126
|
+
expect(getCallCount()).toBe(0);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("allows /reset without checking DB", async () => {
|
|
130
|
+
const { app, getCallCount } = createApp(false);
|
|
131
|
+
const res = await app.request("/reset");
|
|
132
|
+
expect(res.status).toBe(200);
|
|
133
|
+
expect(getCallCount()).toBe(0);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("allows /api/auth/* without checking DB", async () => {
|
|
137
|
+
const { app, getCallCount } = createApp(false);
|
|
138
|
+
const res = await app.request("/api/auth/session");
|
|
139
|
+
expect(res.status).toBe(200);
|
|
140
|
+
expect(getCallCount()).toBe(0);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Onboarding Middleware
|
|
3
|
+
*
|
|
4
|
+
* Redirects all requests to /setup if onboarding hasn't been completed.
|
|
5
|
+
* Caches the result in memory so the DB is only queried once per isolate lifetime.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { MiddlewareHandler } from "hono";
|
|
9
|
+
import type { Bindings } from "../types.js";
|
|
10
|
+
import type { AppVariables } from "../app.js";
|
|
11
|
+
|
|
12
|
+
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
13
|
+
|
|
14
|
+
/** In-memory cache — persists across requests within a Worker isolate */
|
|
15
|
+
let onboardingComplete = false;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Middleware that redirects to /setup if onboarding is not complete.
|
|
19
|
+
* Uses module-level caching: once onboarding is confirmed complete,
|
|
20
|
+
* no further DB queries are made for the lifetime of the Worker isolate.
|
|
21
|
+
*/
|
|
22
|
+
export function requireOnboarding(): MiddlewareHandler<Env> {
|
|
23
|
+
return async (c, next) => {
|
|
24
|
+
if (onboardingComplete) {
|
|
25
|
+
return next();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const path = new URL(c.req.url).pathname;
|
|
29
|
+
if (shouldBypass(path)) {
|
|
30
|
+
return next();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const isComplete = await c.var.services.settings.isOnboardingComplete();
|
|
34
|
+
if (isComplete) {
|
|
35
|
+
onboardingComplete = true;
|
|
36
|
+
return next();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return c.redirect("/setup");
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function shouldBypass(path: string): boolean {
|
|
44
|
+
return (
|
|
45
|
+
path === "/setup" ||
|
|
46
|
+
path === "/health" ||
|
|
47
|
+
path === "/signin" ||
|
|
48
|
+
path === "/signout" ||
|
|
49
|
+
path === "/reset" ||
|
|
50
|
+
path.startsWith("/api/auth/")
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Reset the onboarding cache. Only for testing.
|
|
56
|
+
* @internal
|
|
57
|
+
*/
|
|
58
|
+
export function resetOnboardingCache() {
|
|
59
|
+
onboardingComplete = false;
|
|
60
|
+
}
|
|
@@ -102,11 +102,6 @@ function HomeContent({ siteName, posts }: { siteName: string; posts: Post[] }) {
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
homeRoutes.get("/", async (c) => {
|
|
105
|
-
const isComplete = await c.var.services.settings.isOnboardingComplete();
|
|
106
|
-
if (!isComplete) {
|
|
107
|
-
return c.redirect("/setup");
|
|
108
|
-
}
|
|
109
|
-
|
|
110
105
|
const siteName = await getSiteName(c);
|
|
111
106
|
|
|
112
107
|
const posts = await c.var.services.posts.list({
|