@jant/core 0.6.16 → 0.7.0

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 (508) hide show
  1. package/README.md +11 -11
  2. package/bin/commands/assets/upload.js +192 -15
  3. package/bin/commands/import-site.js +60 -4
  4. package/bin/commands/reset-password.js +21 -8
  5. package/bin/commands/site/snapshot/export.js +1 -1
  6. package/bin/commands/site/snapshot/import.js +1 -1
  7. package/bin/lib/d1-query.js +5 -16
  8. package/bin/lib/node-env.js +40 -4
  9. package/bin/lib/site-selection.js +85 -29
  10. package/bin/lib/wrangler-cli.js +28 -2
  11. package/bin/lib/wrangler-json.js +35 -0
  12. package/dist/{app-C7_4oab9.js → app-K_Aa1MMn.js} +52476 -13330
  13. package/dist/client/.vite/manifest.json +255 -90
  14. package/dist/client/_assets/chunks/confirm-D7GrwsCN.js +45 -0
  15. package/dist/client/_assets/chunks/create-editor-CD3FhrOB.js +257 -0
  16. package/dist/client/_assets/chunks/{native-DpcrFAPh.js → emoji-mart-C4RpRKcU.js} +716 -1
  17. package/dist/client/_assets/chunks/footnote-rail-CmeoVtnU.js +1 -0
  18. package/dist/client/_assets/chunks/heic-to-CuP2Qlsw.js +1 -0
  19. package/dist/client/_assets/chunks/jant-locale-picker-DL6mIOT5.js +69 -0
  20. package/dist/client/_assets/chunks/lit-DW8VmJAT.js +2 -0
  21. package/dist/client/_assets/chunks/mediabunny-DRaCQ1r-.js +31 -0
  22. package/dist/client/_assets/chunks/rolldown-runtime-hePW80VL.js +1 -0
  23. package/dist/client/_assets/chunks/settings-paths-BcdySNx6.js +84 -0
  24. package/dist/client/_assets/chunks/site-header-nav-DzXsFk05.js +1 -0
  25. package/dist/client/_assets/chunks/slug-format-CRXO1AL5.js +1 -0
  26. package/dist/client/_assets/chunks/slugify-373citbs.js +1 -0
  27. package/dist/client/_assets/chunks/sortable-list-CgaL2jCs.js +2 -0
  28. package/dist/client/_assets/chunks/types-D5iF3H1a.js +1 -0
  29. package/dist/client/_assets/chunks/unsafe-html-B5ZEXaxg.js +1 -0
  30. package/dist/client/_assets/chunks/unsafe-svg-0QCkP0vZ.js +2 -0
  31. package/dist/client/_assets/client-GiYENVw8.css +2 -0
  32. package/dist/client/_assets/client-VnFxJN7G.js +295 -0
  33. package/dist/client/_assets/client-auth-k8gJ6QJj.js +828 -0
  34. package/dist/client/_assets/client-author-DQ8R-8KR.css +2 -0
  35. package/dist/client/_assets/client-compose-B_kDtWkW.js +2004 -0
  36. package/dist/client/_assets/client-manage-M90aSTOk.js +2170 -0
  37. package/dist/client/_assets/client-settings-DfYs9n1F.js +1112 -0
  38. package/dist/{export-Dl5KCiEs.js → github-sync-BPAvT999.js} +2254 -107
  39. package/dist/index.js +2 -5
  40. package/dist/node.js +5 -7
  41. package/package.json +3 -3
  42. package/src/__tests__/auth-session-freshness.test.ts +57 -0
  43. package/src/__tests__/basecoat-variants.test.ts +121 -0
  44. package/src/__tests__/bin/assets-upload.test.ts +68 -0
  45. package/src/__tests__/export-hugo-build.test.ts +260 -0
  46. package/src/__tests__/export-service.test.ts +231 -0
  47. package/src/__tests__/helpers/component-css.ts +91 -0
  48. package/src/__tests__/helpers/db.ts +33 -0
  49. package/src/__tests__/helpers/export-fixtures.ts +3 -0
  50. package/src/__tests__/stylesheet-audience.test.ts +159 -0
  51. package/src/__tests__/stylesheet-breakpoints.test.ts +183 -0
  52. package/src/app.tsx +71 -16
  53. package/src/auth.ts +18 -1
  54. package/src/client/__tests__/collection-page-actions.test.ts +51 -2
  55. package/src/client/__tests__/compose-bridge-image-upload.test.ts +196 -0
  56. package/src/client/__tests__/compose-bridge-video-poster.test.ts +159 -0
  57. package/src/client/__tests__/compose-bridge.test.ts +272 -0
  58. package/src/client/__tests__/compose-launch.test.ts +62 -1
  59. package/src/client/__tests__/compose-shortcuts.test.ts +38 -9
  60. package/src/client/__tests__/compose-triggers.test.ts +90 -0
  61. package/src/client/__tests__/copy-field.test.ts +162 -0
  62. package/src/client/__tests__/custom-url-menu.test.ts +127 -6
  63. package/src/client/__tests__/image-processor.test.ts +324 -2
  64. package/src/client/__tests__/lazy-entries.test.ts +92 -0
  65. package/src/client/__tests__/media-scroll-hint.test.ts +155 -0
  66. package/src/client/__tests__/runtime-paths.test.ts +84 -0
  67. package/src/client/__tests__/smart-collection-page-actions.test.ts +158 -0
  68. package/src/client/__tests__/toast.test.ts +23 -0
  69. package/src/client/__tests__/video-processor-probe.test.ts +175 -0
  70. package/src/client/__tests__/video-processor.test.ts +181 -0
  71. package/src/client/audio-processor.ts +24 -15
  72. package/src/client/audio-waveform.ts +49 -0
  73. package/src/client/collection-dialog-host.ts +72 -0
  74. package/src/client/collection-navigation.ts +51 -16
  75. package/src/client/collection-page-actions.ts +26 -0
  76. package/src/client/components/__tests__/jant-collection-dialog.test.ts +329 -0
  77. package/src/client/components/__tests__/jant-collection-directory.test.ts +274 -10
  78. package/src/client/components/__tests__/jant-collection-form.test.ts +59 -178
  79. package/src/client/components/__tests__/jant-command-palette.test.ts +28 -0
  80. package/src/client/components/__tests__/jant-compose-dialog-draft-storage.test.ts +340 -0
  81. package/src/client/components/__tests__/jant-compose-dialog.test.ts +1250 -53
  82. package/src/client/components/__tests__/jant-compose-editor.test.ts +544 -40
  83. package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +0 -1
  84. package/src/client/components/__tests__/jant-locale-picker.test.ts +207 -0
  85. package/src/client/components/__tests__/jant-nav-manager.test.ts +234 -15
  86. package/src/client/components/__tests__/jant-post-menu.test.ts +413 -2
  87. package/src/client/components/__tests__/jant-post-picker.test.ts +197 -0
  88. package/src/client/components/__tests__/jant-settings-avatar.test.ts +15 -12
  89. package/src/client/components/__tests__/jant-settings-general.test.ts +541 -172
  90. package/src/client/components/__tests__/jant-settings-language.test.ts +851 -0
  91. package/src/client/components/__tests__/jant-smart-collection-dialog.test.ts +665 -0
  92. package/src/client/components/collection-dialog-types.ts +49 -0
  93. package/src/client/components/collection-manager-types.ts +32 -1
  94. package/src/client/components/collection-types.ts +1 -15
  95. package/src/client/components/compose-types.ts +75 -1
  96. package/src/client/components/jant-collection-dialog.ts +722 -0
  97. package/src/client/components/jant-collection-directory.ts +567 -182
  98. package/src/client/components/jant-collection-form.ts +53 -232
  99. package/src/client/components/jant-command-palette.ts +100 -49
  100. package/src/client/components/jant-compose-dialog.ts +1486 -707
  101. package/src/client/components/jant-compose-editor.ts +673 -336
  102. package/src/client/components/jant-compose-fullscreen.ts +51 -43
  103. package/src/client/components/jant-config-editor.ts +65 -53
  104. package/src/client/components/jant-confirm-dialog.ts +13 -11
  105. package/src/client/components/jant-locale-picker.ts +418 -0
  106. package/src/client/components/jant-media-lightbox.ts +146 -136
  107. package/src/client/components/jant-nav-manager.ts +690 -366
  108. package/src/client/components/jant-post-menu.ts +1086 -282
  109. package/src/client/components/jant-post-picker.ts +324 -0
  110. package/src/client/components/jant-repo-picker.ts +138 -106
  111. package/src/client/components/jant-settings-avatar.ts +31 -27
  112. package/src/client/components/jant-settings-general.ts +617 -543
  113. package/src/client/components/jant-settings-language.ts +1087 -0
  114. package/src/client/components/jant-smart-collection-dialog.ts +1021 -0
  115. package/src/client/components/jant-text-preview.ts +94 -86
  116. package/src/client/components/nav-manager-types.ts +62 -1
  117. package/src/client/components/settings-types.ts +14 -29
  118. package/src/client/components/smart-collection-conditions.ts +285 -0
  119. package/src/client/components/smart-collection-dialog-types.ts +68 -0
  120. package/src/client/compose-bridge.ts +214 -141
  121. package/src/client/compose-launch.ts +76 -2
  122. package/src/client/compose-shortcuts.ts +6 -8
  123. package/src/client/compose-triggers.ts +50 -0
  124. package/src/client/copy-field.ts +108 -0
  125. package/src/client/custom-url-menu.ts +60 -4
  126. package/src/client/icons.ts +14 -0
  127. package/src/client/image-plan.ts +165 -0
  128. package/src/client/image-processor.ts +195 -117
  129. package/src/client/image-worker-client.ts +45 -0
  130. package/src/client/image-worker.ts +135 -0
  131. package/src/client/lazy-entries.ts +95 -0
  132. package/src/client/lazy-slugify.ts +2 -2
  133. package/src/client/media-scroll-hint.ts +43 -2
  134. package/src/client/mediabunny.ts +29 -0
  135. package/src/client/post-picker.ts +41 -0
  136. package/src/client/runtime-paths.ts +74 -1
  137. package/src/client/settings-bridge.ts +4 -10
  138. package/src/client/site-header-nav.js +18 -6
  139. package/src/client/smart-collection-dialog-host.ts +116 -0
  140. package/src/client/smart-collection-page-actions.ts +208 -0
  141. package/src/client/tiptap/__tests__/bubble-menu.test.ts +110 -0
  142. package/src/client/tiptap/__tests__/floating-position.test.ts +136 -1
  143. package/src/client/tiptap/__tests__/inline-image-upload.test.ts +37 -0
  144. package/src/client/tiptap/bubble-menu.ts +47 -16
  145. package/src/client/tiptap/floating-position.ts +216 -2
  146. package/src/client/tiptap/inline-image-upload.ts +7 -3
  147. package/src/client/tiptap/link-toolbar.ts +22 -9
  148. package/src/client/toast.ts +13 -0
  149. package/src/client/types/vite-worker.d.ts +12 -0
  150. package/src/client/upload-with-metadata.ts +3 -13
  151. package/src/client/video-processor.ts +351 -107
  152. package/src/client-auth.ts +14 -31
  153. package/src/client-compose.ts +19 -0
  154. package/src/client-manage.ts +13 -0
  155. package/src/client-settings.ts +14 -0
  156. package/src/client-site.ts +10 -3
  157. package/src/client.ts +4 -0
  158. package/src/db/__tests__/backfill-nav-label-mirrors.test.ts +149 -0
  159. package/src/db/__tests__/schema-enum-sources.test.ts +33 -0
  160. package/src/db/__tests__/smart-collection-placement.test.ts +402 -0
  161. package/src/db/backfills/0006_clear_mirrored_page_nav_labels.sql +31 -0
  162. package/src/db/migrations/0032_married_tomas.sql +3 -0
  163. package/src/db/migrations/0033_busy_johnny_storm.sql +198 -0
  164. package/src/db/migrations/0034_cheerful_rumiko_fujikawa.sql +35 -0
  165. package/src/db/migrations/meta/0032_snapshot.json +2582 -0
  166. package/src/db/migrations/meta/0033_snapshot.json +2816 -0
  167. package/src/db/migrations/meta/0034_snapshot.json +2816 -0
  168. package/src/db/migrations/meta/_journal.json +21 -0
  169. package/src/db/migrations/pg/0030_abandoned_nighthawk.sql +3 -0
  170. package/src/db/migrations/pg/0031_strong_ultimatum.sql +128 -0
  171. package/src/db/migrations/pg/0032_furry_multiple_man.sql +6 -0
  172. package/src/db/migrations/pg/meta/0030_snapshot.json +3329 -0
  173. package/src/db/migrations/pg/meta/0031_snapshot.json +3660 -0
  174. package/src/db/migrations/pg/meta/0032_snapshot.json +3660 -0
  175. package/src/db/migrations/pg/meta/_journal.json +21 -0
  176. package/src/db/pg/schema.ts +195 -27
  177. package/src/db/post-visibility.ts +117 -0
  178. package/src/db/schema.ts +222 -27
  179. package/src/i18n/__tests__/detect.test.ts +26 -81
  180. package/src/i18n/__tests__/supported-locales.test.ts +83 -0
  181. package/src/i18n/context.tsx +12 -2
  182. package/src/i18n/detect.ts +7 -66
  183. package/src/i18n/locales/glossary.zh-Hans.yml +44 -4
  184. package/src/i18n/locales/glossary.zh-Hant.yml +44 -4
  185. package/src/i18n/locales/public/en.po +490 -158
  186. package/src/i18n/locales/public/en.ts +1 -1
  187. package/src/i18n/locales/public/zh-Hans.po +487 -155
  188. package/src/i18n/locales/public/zh-Hans.ts +1 -1
  189. package/src/i18n/locales/public/zh-Hant.po +487 -155
  190. package/src/i18n/locales/public/zh-Hant.ts +1 -1
  191. package/src/i18n/locales/settings/en.po +551 -69
  192. package/src/i18n/locales/settings/en.ts +1 -1
  193. package/src/i18n/locales/settings/zh-Hans.po +597 -115
  194. package/src/i18n/locales/settings/zh-Hans.ts +1 -1
  195. package/src/i18n/locales/settings/zh-Hant.po +610 -128
  196. package/src/i18n/locales/settings/zh-Hant.ts +1 -1
  197. package/src/i18n/locales.ts +94 -0
  198. package/src/i18n/middleware.ts +12 -2
  199. package/src/i18n/supported-locales.ts +80 -0
  200. package/src/lib/__tests__/api-settings.test.ts +11 -2
  201. package/src/lib/__tests__/asset-path.test.ts +92 -0
  202. package/src/lib/__tests__/deferred.test.ts +90 -0
  203. package/src/lib/__tests__/discover-ping.test.ts +89 -0
  204. package/src/lib/__tests__/discover.test.ts +410 -0
  205. package/src/lib/__tests__/feed-policy.test.ts +26 -0
  206. package/src/lib/__tests__/feed.test.ts +1526 -15
  207. package/src/lib/__tests__/filter-dimensions.test.ts +341 -0
  208. package/src/lib/__tests__/hosted-signin.test.ts +1 -1
  209. package/src/lib/__tests__/http-cache.test.ts +162 -0
  210. package/src/lib/__tests__/jant-docs-dev.test.ts +21 -0
  211. package/src/lib/__tests__/jant-docs.test.ts +23 -0
  212. package/src/lib/__tests__/lang-detect.test.ts +231 -0
  213. package/src/lib/__tests__/media-helpers.test.ts +81 -0
  214. package/src/lib/__tests__/navigation.test.ts +135 -1
  215. package/src/lib/__tests__/resolve-config.test.ts +64 -3
  216. package/src/lib/__tests__/schemas.test.ts +42 -4
  217. package/src/lib/__tests__/site-unavailable-page.test.ts +25 -0
  218. package/src/lib/__tests__/slug.test.ts +70 -0
  219. package/src/lib/__tests__/slugify.test.ts +85 -0
  220. package/src/lib/__tests__/storage.test.ts +32 -0
  221. package/src/lib/__tests__/thread-fold.test.ts +113 -0
  222. package/src/lib/__tests__/upload-sideload.test.ts +14 -0
  223. package/src/lib/__tests__/url-fetch.test.ts +1 -2
  224. package/src/lib/__tests__/url.test.ts +78 -72
  225. package/src/lib/__tests__/view-language.test.ts +413 -0
  226. package/src/lib/__tests__/worker-response-cache.test.ts +90 -0
  227. package/src/lib/address.ts +57 -0
  228. package/src/lib/api-posts.ts +12 -0
  229. package/src/lib/asset-path.ts +98 -1
  230. package/src/lib/build-env.ts +16 -0
  231. package/src/lib/collection-groups.ts +5 -1
  232. package/src/lib/collection-paths.ts +3 -10
  233. package/src/lib/constants.ts +28 -3
  234. package/src/lib/copy-field.ts +36 -0
  235. package/src/lib/crypto.ts +40 -0
  236. package/src/lib/csp-builder.ts +2 -1
  237. package/src/lib/deferred.ts +79 -0
  238. package/src/lib/discover-ping.ts +141 -0
  239. package/src/lib/discover.ts +437 -0
  240. package/src/lib/env.ts +134 -0
  241. package/src/lib/errors.ts +33 -0
  242. package/src/lib/feed-policy.ts +236 -2
  243. package/src/lib/feed.ts +926 -73
  244. package/src/lib/filter-dimensions.ts +1349 -0
  245. package/src/lib/github-sync-site-config.ts +3 -0
  246. package/src/lib/hosted-signin.ts +1 -1
  247. package/src/lib/http-cache.ts +148 -0
  248. package/src/lib/hugo-markdown.ts +16 -0
  249. package/src/lib/icons.ts +9 -2
  250. package/src/lib/ids.ts +3 -0
  251. package/src/lib/jant-docs.ts +42 -0
  252. package/src/lib/lang-detect.ts +308 -0
  253. package/src/lib/media-helpers.ts +13 -1
  254. package/src/lib/navigation.ts +78 -11
  255. package/src/lib/pagination.ts +28 -24
  256. package/src/lib/per-language-surfaces.ts +67 -0
  257. package/src/lib/post-meta.ts +19 -0
  258. package/src/lib/render.tsx +55 -7
  259. package/src/lib/resolve-config.ts +53 -5
  260. package/src/lib/schemas.ts +201 -10
  261. package/src/lib/site-header-fragment.tsx +2 -1
  262. package/src/lib/site-unavailable-page.ts +47 -0
  263. package/src/lib/slug.ts +51 -7
  264. package/src/lib/slugify.ts +113 -0
  265. package/src/lib/startup-config.ts +4 -6
  266. package/src/lib/storage.ts +7 -0
  267. package/src/lib/summary.ts +50 -0
  268. package/src/lib/thread-fold.ts +161 -0
  269. package/src/lib/time.ts +32 -0
  270. package/src/lib/timeline.ts +42 -1
  271. package/src/lib/tiptap-render.ts +1 -2
  272. package/src/lib/upload.ts +16 -13
  273. package/src/lib/url.ts +139 -86
  274. package/src/lib/version.ts +10 -8
  275. package/src/lib/view-language.ts +436 -0
  276. package/src/lib/view.ts +51 -23
  277. package/src/lib/viewer-context.ts +62 -0
  278. package/src/middleware/__tests__/auth.test.ts +70 -2
  279. package/src/middleware/__tests__/cache-control.test.ts +71 -3
  280. package/src/middleware/__tests__/onboarding.test.ts +76 -5
  281. package/src/middleware/__tests__/secure-headers.test.ts +1 -1
  282. package/src/middleware/__tests__/session.test.ts +181 -33
  283. package/src/middleware/auth.ts +50 -31
  284. package/src/middleware/cache-control.ts +75 -10
  285. package/src/middleware/config.ts +6 -6
  286. package/src/middleware/error-handler.ts +12 -0
  287. package/src/middleware/onboarding.ts +32 -10
  288. package/src/middleware/secure-headers.ts +1 -1
  289. package/src/middleware/session.ts +96 -8
  290. package/src/node/__tests__/cli-migrate.test.ts +21 -19
  291. package/src/node/__tests__/cli-reset-password.test.ts +81 -8
  292. package/src/node/__tests__/cli-runtime-target.test.ts +48 -1
  293. package/src/node/__tests__/cli-site-selection.test.ts +128 -38
  294. package/src/node/__tests__/runtime.test.ts +37 -0
  295. package/src/node/index.ts +1 -0
  296. package/src/preset.css +76 -22
  297. package/src/routes/__tests__/feed-validator.test.ts +156 -0
  298. package/src/routes/api/__tests__/collections.test.ts +114 -0
  299. package/src/routes/api/__tests__/discover.test.ts +222 -0
  300. package/src/routes/api/__tests__/nav-items.test.ts +212 -2
  301. package/src/routes/api/__tests__/post-translations.test.ts +829 -0
  302. package/src/routes/api/__tests__/settings.test.ts +24 -3
  303. package/src/routes/api/__tests__/telegram.test.ts +6 -5
  304. package/src/routes/api/collections.ts +49 -6
  305. package/src/routes/api/custom-urls.ts +0 -1
  306. package/src/routes/api/discover.ts +95 -0
  307. package/src/routes/api/export.ts +3 -0
  308. package/src/routes/api/internal/__tests__/sites.test.ts +42 -2
  309. package/src/routes/api/nav-items.ts +55 -8
  310. package/src/routes/api/posts.ts +159 -0
  311. package/src/routes/api/public/__tests__/archive.test.ts +111 -0
  312. package/src/routes/api/public/archive.ts +77 -81
  313. package/src/routes/api/public/posts.ts +23 -6
  314. package/src/routes/api/smart-collections.ts +132 -0
  315. package/src/routes/api/telegram.ts +2 -40
  316. package/src/routes/api/uploads.ts +1 -2
  317. package/src/routes/auth/__tests__/setup-auth.test.ts +137 -0
  318. package/src/routes/auth/__tests__/setup-discover.test.ts +204 -0
  319. package/src/routes/auth/__tests__/setup-flow.test.ts +270 -0
  320. package/src/routes/auth/__tests__/setup-page.test.tsx +329 -0
  321. package/src/routes/auth/__tests__/setup-steps.test.ts +310 -0
  322. package/src/routes/auth/__tests__/setup.test.ts +278 -26
  323. package/src/routes/auth/__tests__/signin.test.ts +135 -0
  324. package/src/routes/auth/setup.tsx +934 -186
  325. package/src/routes/auth/signin.tsx +10 -8
  326. package/src/routes/compose.tsx +5 -0
  327. package/src/routes/dash/__tests__/custom-urls-archive.test.ts +44 -0
  328. package/src/routes/dash/__tests__/custom-urls-page.test.ts +49 -0
  329. package/src/routes/dash/__tests__/delete-account.test.ts +106 -0
  330. package/src/routes/dash/__tests__/settings-discover.test.ts +213 -0
  331. package/src/routes/dash/custom-urls.tsx +1 -37
  332. package/src/routes/dash/settings.tsx +502 -41
  333. package/src/routes/discover-announce.ts +96 -0
  334. package/src/routes/feed/__tests__/feed.test.ts +165 -2
  335. package/src/routes/feed/feed.ts +69 -40
  336. package/src/routes/feed/sitemap.ts +95 -11
  337. package/src/routes/pages/__tests__/archive-indexing.test.ts +200 -0
  338. package/src/routes/pages/__tests__/archive-params.test.ts +370 -6
  339. package/src/routes/pages/__tests__/collection-routing.test.ts +16 -0
  340. package/src/routes/pages/__tests__/collections-feed-links.test.ts +72 -0
  341. package/src/routes/pages/__tests__/collections.test.ts +9 -3
  342. package/src/routes/pages/__tests__/custom-archive-url.test.ts +119 -0
  343. package/src/routes/pages/__tests__/feed-autodiscovery.test.ts +148 -0
  344. package/src/routes/pages/__tests__/language-routing.test.ts +679 -0
  345. package/src/routes/pages/__tests__/post-preview-partial.test.ts +96 -0
  346. package/src/routes/pages/__tests__/smart-collection.test.ts +298 -0
  347. package/src/routes/pages/__tests__/subscribe.test.ts +248 -0
  348. package/src/routes/pages/archive.tsx +471 -369
  349. package/src/routes/pages/collection.tsx +94 -96
  350. package/src/routes/pages/collections.tsx +38 -50
  351. package/src/routes/pages/featured.tsx +32 -13
  352. package/src/routes/pages/home.tsx +25 -6
  353. package/src/routes/pages/language.tsx +113 -0
  354. package/src/routes/pages/latest.tsx +26 -16
  355. package/src/routes/pages/new.tsx +3 -0
  356. package/src/routes/pages/page.tsx +178 -17
  357. package/src/routes/pages/partials.tsx +41 -0
  358. package/src/routes/pages/search.tsx +18 -3
  359. package/src/routes/pages/smart-collection.tsx +340 -0
  360. package/src/routes/pages/subscribe.tsx +182 -0
  361. package/src/runtime/__tests__/index.test.ts +19 -0
  362. package/src/runtime/__tests__/node.test.ts +129 -4
  363. package/src/runtime/cloudflare.ts +21 -2
  364. package/src/runtime/index.ts +27 -1
  365. package/src/runtime/node.ts +18 -1
  366. package/src/runtime/site.ts +95 -24
  367. package/src/services/__tests__/auth.test.ts +116 -7
  368. package/src/services/__tests__/collection.test.ts +108 -4
  369. package/src/services/__tests__/language.test.ts +495 -0
  370. package/src/services/__tests__/navigation.test.ts +213 -10
  371. package/src/services/__tests__/path.test.ts +180 -0
  372. package/src/services/__tests__/post-language.test.ts +830 -0
  373. package/src/services/__tests__/post.test.ts +46 -9
  374. package/src/services/__tests__/settings.test.ts +228 -17
  375. package/src/services/__tests__/site-admin.test.ts +5 -5
  376. package/src/services/__tests__/smart-collection.test.ts +651 -0
  377. package/src/services/about-page.ts +22 -1
  378. package/src/services/auth.ts +51 -49
  379. package/src/services/bootstrap.ts +188 -38
  380. package/src/services/collection-directory-position.ts +76 -0
  381. package/src/services/collection.ts +331 -46
  382. package/src/services/custom-url.ts +27 -22
  383. package/src/services/export-theme/assets/client-site.css +1 -1
  384. package/src/services/export-theme/assets/client-site.js +123 -123
  385. package/src/services/export-theme/layouts/_default/rss.xml +77 -1
  386. package/src/services/export-theme/layouts/partials/feed-post-content.xml +105 -2
  387. package/src/services/export-theme/layouts/partials/media-gallery.html +35 -21
  388. package/src/services/export-theme/layouts/partials/pagination.html +14 -13
  389. package/src/services/export-theme/layouts/partials/post-card.html +8 -4
  390. package/src/services/export-theme/layouts/partials/reply.html +2 -1
  391. package/src/services/export-theme/layouts/partials/thread-preview.html +40 -30
  392. package/src/services/export-theme/styles/main.css +13 -1
  393. package/src/services/export.ts +278 -19
  394. package/src/services/index.ts +34 -3
  395. package/src/services/language.ts +412 -0
  396. package/src/services/mcp.ts +3 -1
  397. package/src/services/media.ts +14 -6
  398. package/src/services/navigation.ts +429 -67
  399. package/src/services/path.ts +221 -6
  400. package/src/services/post.ts +1021 -152
  401. package/src/services/search.ts +39 -2
  402. package/src/services/settings.ts +238 -43
  403. package/src/services/site-admin.ts +24 -7
  404. package/src/services/site.ts +77 -1
  405. package/src/services/smart-collection.ts +636 -0
  406. package/src/services/telegram.ts +20 -22
  407. package/src/services/upload-session.ts +1 -3
  408. package/src/style-author.css +27 -0
  409. package/src/styles/components-author.css +1622 -0
  410. package/src/styles/components.css +24 -1571
  411. package/src/styles/site-media.css +16 -2
  412. package/src/styles/tokens.css +82 -12
  413. package/src/styles/ui-author.css +7602 -0
  414. package/src/styles/ui.css +1087 -7219
  415. package/src/types/app-context.ts +7 -0
  416. package/src/types/bindings.ts +3 -0
  417. package/src/types/config.ts +94 -15
  418. package/src/types/constants.ts +188 -16
  419. package/src/types/entities.ts +87 -1
  420. package/src/types/operations.ts +64 -2
  421. package/src/types/props.ts +157 -31
  422. package/src/types/views.ts +55 -1
  423. package/src/ui/__tests__/font-themes.test.ts +52 -12
  424. package/src/ui/compose/ComposeDialog.tsx +201 -7
  425. package/src/ui/compose/ComposePrompt.tsx +1 -5
  426. package/src/ui/compose/__tests__/ComposeDialog.test.tsx +125 -0
  427. package/src/ui/dash/appearance/AdvancedContent.tsx +2 -2
  428. package/src/ui/dash/appearance/CodeInjectionContent.tsx +12 -16
  429. package/src/ui/dash/appearance/NavigationContent.tsx +117 -16
  430. package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +33 -11
  431. package/src/ui/dash/appearance/__tests__/system-nav-descriptions.test.ts +44 -0
  432. package/src/ui/dash/appearance/system-nav-descriptions.ts +75 -0
  433. package/src/ui/dash/settings/AccountMenuContent.tsx +2 -2
  434. package/src/ui/dash/settings/ApiTokensContent.tsx +2 -1
  435. package/src/ui/dash/settings/ConfigEditorContent.tsx +35 -16
  436. package/src/ui/dash/settings/GeneralContent.tsx +275 -113
  437. package/src/ui/dash/settings/LanguageContent.tsx +409 -0
  438. package/src/ui/dash/settings/SettingsRootContent.tsx +19 -1
  439. package/src/ui/dash/settings/__tests__/GeneralContent.test.tsx +324 -4
  440. package/src/ui/dash/settings/__tests__/LanguageContent.test.tsx +144 -0
  441. package/src/ui/dash/settings/__tests__/discover-copy.test.ts +66 -0
  442. package/src/ui/dash/settings/discover-copy.ts +116 -0
  443. package/src/ui/feed/LinkCard.tsx +55 -45
  444. package/src/ui/feed/NoteCard.tsx +9 -4
  445. package/src/ui/feed/PostStatusBadges.tsx +90 -43
  446. package/src/ui/feed/QuoteCard.tsx +55 -30
  447. package/src/ui/feed/ThreadPreview.tsx +6 -2
  448. package/src/ui/feed/__tests__/thread-preview.test.ts +13 -16
  449. package/src/ui/feed/__tests__/timeline-cards.test.ts +314 -49
  450. package/src/ui/feed/thread-preview-state.ts +20 -6
  451. package/src/ui/font-themes.ts +39 -29
  452. package/src/ui/layouts/BaseLayout.tsx +155 -10
  453. package/src/ui/layouts/SiteLayout.tsx +192 -34
  454. package/src/ui/layouts/__tests__/BaseLayout.test.tsx +196 -17
  455. package/src/ui/layouts/__tests__/SiteLayout.test.tsx +109 -85
  456. package/src/ui/pages/ArchivePage.tsx +282 -289
  457. package/src/ui/pages/CollectionPage.tsx +86 -76
  458. package/src/ui/pages/CollectionsPage.tsx +5 -0
  459. package/src/ui/pages/ComposePage.tsx +8 -0
  460. package/src/ui/pages/PostPage.tsx +47 -0
  461. package/src/ui/pages/SearchPage.tsx +3 -3
  462. package/src/ui/pages/SmartCollectionPage.tsx +434 -0
  463. package/src/ui/pages/SubscribePage.tsx +236 -0
  464. package/src/ui/pages/__tests__/ArchivePage.test.tsx +65 -9
  465. package/src/ui/pages/__tests__/collections-directory-rows.test.tsx +92 -0
  466. package/src/ui/pages/__tests__/collections-view-links.test.tsx +81 -0
  467. package/src/ui/shared/CollectionDirectory.tsx +178 -3
  468. package/src/ui/shared/CollectionsManager.tsx +70 -17
  469. package/src/ui/shared/CopyField.tsx +127 -0
  470. package/src/ui/shared/EmptyPostContent.tsx +23 -0
  471. package/src/ui/shared/MediaGallery.tsx +125 -46
  472. package/src/ui/shared/Pagination.tsx +20 -5
  473. package/src/ui/shared/PostFooter.tsx +20 -11
  474. package/src/ui/shared/__tests__/archive-labels.test.ts +100 -0
  475. package/src/ui/shared/__tests__/media-gallery.test.ts +210 -2
  476. package/src/ui/shared/__tests__/navigation-labels.test.ts +16 -22
  477. package/src/ui/shared/__tests__/pagination.test.ts +38 -24
  478. package/src/ui/shared/__tests__/post-footer.test.ts +32 -0
  479. package/src/ui/shared/archive-labels.ts +98 -0
  480. package/src/ui/shared/collection-management-labels.ts +94 -16
  481. package/src/ui/shared/navigation-labels.ts +45 -71
  482. package/src/ui/shared/post-article-attributes.ts +2 -0
  483. package/src/ui/shared/smart-collection-labels.ts +343 -0
  484. package/dist/app-C75Zc8dD.js +0 -6
  485. package/dist/client/_assets/chunks/heic-to-DUUaO23q.js +0 -1
  486. package/dist/client/_assets/chunks/module-DcsAZQZ_.js +0 -716
  487. package/dist/client/_assets/chunks/url-CU9pEbk5.js +0 -1
  488. package/dist/client/_assets/client-Bq0vre8Y.js +0 -296
  489. package/dist/client/_assets/client-D3VZyG4L.css +0 -2
  490. package/dist/client/_assets/client-auth-F2rb9WfF.js +0 -5571
  491. package/dist/env-BPYViDJJ.js +0 -281
  492. package/dist/github-api-BNF35Lkx.js +0 -176
  493. package/dist/github-app-DdiD-bC4.js +0 -275
  494. package/dist/github-sync-Bqg62IvV.js +0 -4
  495. package/dist/github-sync-HdK2EgvJ.js +0 -431
  496. package/dist/url-CbLAtlZe.js +0 -900
  497. package/src/client/__tests__/collection-created-notice.test.ts +0 -36
  498. package/src/client/__tests__/collection-form-bridge.test.ts +0 -216
  499. package/src/client/__tests__/post-form-bridge.test.ts +0 -82
  500. package/src/client/collection-created-notice.ts +0 -66
  501. package/src/client/collection-form-bridge.ts +0 -191
  502. package/src/client/components/__tests__/jant-post-form.test.ts +0 -193
  503. package/src/client/components/jant-post-form.ts +0 -422
  504. package/src/client/components/post-form-template.ts +0 -352
  505. package/src/client/components/post-form-types.ts +0 -96
  506. package/src/client/media-metadata.ts +0 -247
  507. package/src/client/post-form-bridge.ts +0 -193
  508. package/src/ui/pages/CollectionEditorPage.tsx +0 -175
@@ -1,900 +0,0 @@
1
- import limax from "limax";
2
- //#region \0rolldown/runtime.js
3
- var __defProp = Object.defineProperty;
4
- var __exportAll = (all, no_symbols) => {
5
- let target = {};
6
- for (var name in all) __defProp(target, name, {
7
- get: all[name],
8
- enumerable: true
9
- });
10
- if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
11
- return target;
12
- };
13
- //#endregion
14
- //#region src/lib/translit.ts
15
- /**
16
- * Dictionary-free transliteration helpers for slug generation.
17
- *
18
- * These converters cover the scripts limax handles poorly:
19
- *
20
- * - Korean hangul → Revised Romanization (limax strips hangul entirely)
21
- * - Japanese kana → Hepburn romaji (limax mangles long vowels and digraphs)
22
- *
23
- * Both are pure algorithms over Unicode code points — no dictionaries — so
24
- * they stay small enough for the Workers bundle and the lazy client chunk.
25
- * Japanese kanji is intentionally out of scope: readings are context-dependent
26
- * and need a morphological dictionary, which is why `slugify` falls back to a
27
- * random ID for kanji-heavy Japanese titles instead of guessing.
28
- */ /** Matches hiragana, katakana, and the chōonpu (ー) long-vowel mark. */ var KANA_RE = /[ぁ-ゖァ-ヺー]/u;
29
- /** Matches precomposed hangul syllables and compatibility jamo. */ var HANGUL_RE = /[가-힣ㄱ-ㆎ]/u;
30
- /** Matches Han (CJK ideograph) characters. */ var HAN_RE = /\p{Script=Han}/u;
31
- var HANGUL_SYLLABLE_BASE = 44032;
32
- var HANGUL_SYLLABLE_END = 55203;
33
- var VOWEL_COUNT = 21;
34
- var FINAL_COUNT = 28;
35
- var RR_INITIALS = [
36
- "g",
37
- "kk",
38
- "n",
39
- "d",
40
- "tt",
41
- "r",
42
- "m",
43
- "b",
44
- "pp",
45
- "s",
46
- "ss",
47
- "",
48
- "j",
49
- "jj",
50
- "ch",
51
- "k",
52
- "t",
53
- "p",
54
- "h"
55
- ];
56
- var RR_VOWELS = [
57
- "a",
58
- "ae",
59
- "ya",
60
- "yae",
61
- "eo",
62
- "e",
63
- "yeo",
64
- "ye",
65
- "o",
66
- "wa",
67
- "wae",
68
- "oe",
69
- "yo",
70
- "u",
71
- "wo",
72
- "we",
73
- "wi",
74
- "yu",
75
- "eu",
76
- "ui",
77
- "i"
78
- ];
79
- var RR_FINALS = [
80
- "",
81
- "k",
82
- "k",
83
- "k",
84
- "n",
85
- "n",
86
- "n",
87
- "t",
88
- "l",
89
- "k",
90
- "m",
91
- "l",
92
- "l",
93
- "l",
94
- "p",
95
- "l",
96
- "m",
97
- "p",
98
- "p",
99
- "t",
100
- "t",
101
- "ng",
102
- "t",
103
- "t",
104
- "k",
105
- "t",
106
- "p",
107
- "t"
108
- ];
109
- var RR_LIAISON = [
110
- ["", ""],
111
- ["", "g"],
112
- ["", "kk"],
113
- ["k", "s"],
114
- ["", "n"],
115
- ["n", "j"],
116
- ["n", ""],
117
- ["", "d"],
118
- ["", "r"],
119
- ["l", "g"],
120
- ["l", "m"],
121
- ["l", "b"],
122
- ["l", "s"],
123
- ["l", "t"],
124
- ["l", "p"],
125
- ["l", ""],
126
- ["", "m"],
127
- ["", "b"],
128
- ["p", "s"],
129
- ["", "s"],
130
- ["", "ss"],
131
- ["ng", ""],
132
- ["", "j"],
133
- ["", "ch"],
134
- ["", "k"],
135
- ["", "t"],
136
- ["", "p"],
137
- ["", ""]
138
- ];
139
- var COMPAT_JAMO = {
140
- ㄱ: "g",
141
- ㄲ: "kk",
142
- ㄴ: "n",
143
- ㄷ: "d",
144
- ㄸ: "tt",
145
- ㄹ: "r",
146
- ㅁ: "m",
147
- ㅂ: "b",
148
- ㅃ: "pp",
149
- ㅅ: "s",
150
- ㅆ: "ss",
151
- ㅇ: "ng",
152
- ㅈ: "j",
153
- ㅉ: "jj",
154
- ㅊ: "ch",
155
- ㅋ: "k",
156
- ㅌ: "t",
157
- ㅍ: "p",
158
- ㅎ: "h",
159
- ㅏ: "a",
160
- ㅐ: "ae",
161
- ㅑ: "ya",
162
- ㅒ: "yae",
163
- ㅓ: "eo",
164
- ㅔ: "e",
165
- ㅕ: "yeo",
166
- ㅖ: "ye",
167
- ㅗ: "o",
168
- ㅘ: "wa",
169
- ㅙ: "wae",
170
- ㅚ: "oe",
171
- ㅛ: "yo",
172
- ㅜ: "u",
173
- ㅝ: "wo",
174
- ㅞ: "we",
175
- ㅟ: "wi",
176
- ㅠ: "yu",
177
- ㅡ: "eu",
178
- ㅢ: "ui",
179
- ㅣ: "i"
180
- };
181
- function decomposeHangul(codePoint) {
182
- if (codePoint < HANGUL_SYLLABLE_BASE || codePoint > HANGUL_SYLLABLE_END) return null;
183
- const index = codePoint - HANGUL_SYLLABLE_BASE;
184
- return {
185
- initial: Math.floor(index / (VOWEL_COUNT * FINAL_COUNT)),
186
- vowel: Math.floor(index / FINAL_COUNT) % VOWEL_COUNT,
187
- final: index % FINAL_COUNT
188
- };
189
- }
190
- /**
191
- * Transliterates Korean hangul to Latin using Revised Romanization.
192
- *
193
- * Handles precomposed syllables with basic liaison (a final consonant before
194
- * a silent-ㅇ syllable carries over as its initial) and standalone
195
- * compatibility jamo. Assimilation sound rules (e.g. 종로 → "Jongno") are
196
- * intentionally skipped — slugs need recognizable output, not phonetic
197
- * perfection. Non-hangul characters pass through unchanged.
198
- *
199
- * @param text - Input text, possibly mixing hangul with other characters
200
- * @returns Text with hangul replaced by its romanization
201
- *
202
- * @example
203
- * ```ts
204
- * hangulToLatin("안녕하세요"); // "annyeonghaseyo"
205
- * hangulToLatin("한국어"); // "hangugeo" (liaison)
206
- * hangulToLatin("개발 일지"); // "gaebal ilji"
207
- * ```
208
- */ function hangulToLatin(text) {
209
- const chars = [...text];
210
- let out = "";
211
- let carried = null;
212
- for (const [i, char] of chars.entries()) {
213
- const syllable = decomposeHangul(char.codePointAt(0) ?? 0);
214
- if (!syllable) {
215
- carried = null;
216
- out += COMPAT_JAMO[char] ?? char;
217
- continue;
218
- }
219
- const initial = carried ?? RR_INITIALS[syllable.initial] ?? "";
220
- carried = null;
221
- const next = decomposeHangul(chars[i + 1]?.codePointAt(0) ?? 0);
222
- let final = RR_FINALS[syllable.final] ?? "";
223
- if (syllable.final !== 0 && next && RR_INITIALS[next.initial] === "") {
224
- const [remains, carry] = RR_LIAISON[syllable.final] ?? ["", ""];
225
- final = remains;
226
- carried = carry;
227
- }
228
- out += initial + (RR_VOWELS[syllable.vowel] ?? "") + final;
229
- }
230
- return out;
231
- }
232
- var KANA_BASE = {
233
- あ: "a",
234
- い: "i",
235
- う: "u",
236
- え: "e",
237
- お: "o",
238
- か: "ka",
239
- き: "ki",
240
- く: "ku",
241
- け: "ke",
242
- こ: "ko",
243
- さ: "sa",
244
- し: "shi",
245
- す: "su",
246
- せ: "se",
247
- そ: "so",
248
- た: "ta",
249
- ち: "chi",
250
- つ: "tsu",
251
- て: "te",
252
- と: "to",
253
- な: "na",
254
- に: "ni",
255
- ぬ: "nu",
256
- ね: "ne",
257
- の: "no",
258
- は: "ha",
259
- ひ: "hi",
260
- ふ: "fu",
261
- へ: "he",
262
- ほ: "ho",
263
- ま: "ma",
264
- み: "mi",
265
- む: "mu",
266
- め: "me",
267
- も: "mo",
268
- や: "ya",
269
- ゆ: "yu",
270
- よ: "yo",
271
- ら: "ra",
272
- り: "ri",
273
- る: "ru",
274
- れ: "re",
275
- ろ: "ro",
276
- わ: "wa",
277
- ゐ: "i",
278
- ゑ: "e",
279
- を: "o",
280
- ん: "n",
281
- が: "ga",
282
- ぎ: "gi",
283
- ぐ: "gu",
284
- げ: "ge",
285
- ご: "go",
286
- ざ: "za",
287
- じ: "ji",
288
- ず: "zu",
289
- ぜ: "ze",
290
- ぞ: "zo",
291
- だ: "da",
292
- ぢ: "ji",
293
- づ: "zu",
294
- で: "de",
295
- ど: "do",
296
- ば: "ba",
297
- び: "bi",
298
- ぶ: "bu",
299
- べ: "be",
300
- ぼ: "bo",
301
- ぱ: "pa",
302
- ぴ: "pi",
303
- ぷ: "pu",
304
- ぺ: "pe",
305
- ぽ: "po",
306
- ゔ: "vu",
307
- ぁ: "a",
308
- ぃ: "i",
309
- ぅ: "u",
310
- ぇ: "e",
311
- ぉ: "o",
312
- ゃ: "ya",
313
- ゅ: "yu",
314
- ょ: "yo",
315
- ゎ: "wa"
316
- };
317
- var KANA_COMBO = {
318
- きゃ: "kya",
319
- きゅ: "kyu",
320
- きょ: "kyo",
321
- ぎゃ: "gya",
322
- ぎゅ: "gyu",
323
- ぎょ: "gyo",
324
- しゃ: "sha",
325
- しゅ: "shu",
326
- しょ: "sho",
327
- じゃ: "ja",
328
- じゅ: "ju",
329
- じょ: "jo",
330
- ちゃ: "cha",
331
- ちゅ: "chu",
332
- ちょ: "cho",
333
- ぢゃ: "ja",
334
- ぢゅ: "ju",
335
- ぢょ: "jo",
336
- にゃ: "nya",
337
- にゅ: "nyu",
338
- にょ: "nyo",
339
- ひゃ: "hya",
340
- ひゅ: "hyu",
341
- ひょ: "hyo",
342
- びゃ: "bya",
343
- びゅ: "byu",
344
- びょ: "byo",
345
- ぴゃ: "pya",
346
- ぴゅ: "pyu",
347
- ぴょ: "pyo",
348
- みゃ: "mya",
349
- みゅ: "myu",
350
- みょ: "myo",
351
- りゃ: "rya",
352
- りゅ: "ryu",
353
- りょ: "ryo",
354
- ふぁ: "fa",
355
- ふぃ: "fi",
356
- ふぇ: "fe",
357
- ふぉ: "fo",
358
- てぃ: "ti",
359
- でぃ: "di",
360
- とぅ: "tu",
361
- どぅ: "du",
362
- うぃ: "wi",
363
- うぇ: "we",
364
- うぉ: "wo",
365
- ゔぁ: "va",
366
- ゔぃ: "vi",
367
- ゔぇ: "ve",
368
- ゔぉ: "vo",
369
- ちぇ: "che",
370
- しぇ: "she",
371
- じぇ: "je",
372
- つぁ: "tsa",
373
- つぃ: "tsi",
374
- つぇ: "tse",
375
- つぉ: "tso",
376
- いぇ: "ye"
377
- };
378
- var KATAKANA_START = 12449;
379
- var KATAKANA_END = 12534;
380
- var KATAKANA_TO_HIRAGANA_SHIFT = 96;
381
- var SOKUON = "っ";
382
- var CHOONPU = "ー";
383
- function normalizeToHiragana(char) {
384
- const codePoint = char.codePointAt(0) ?? 0;
385
- if (codePoint >= KATAKANA_START && codePoint <= KATAKANA_END) return String.fromCodePoint(codePoint - KATAKANA_TO_HIRAGANA_SHIFT);
386
- return char;
387
- }
388
- /**
389
- * Transliterates Japanese kana to macron-less Hepburn romaji.
390
- *
391
- * Handles hiragana and katakana, digraphs (きゃ → "kya"), extended katakana
392
- * loan combos (ファ → "fa"), sokuon gemination (まっちゃ → "matcha"), and
393
- * drops the chōonpu long-vowel mark (ハンバーガー → "hanbaga"), matching how
394
- * macrons are conventionally stripped in URLs. Kanji and other non-kana
395
- * characters pass through unchanged.
396
- *
397
- * @param text - Input text, possibly mixing kana with other characters
398
- * @returns Text with kana replaced by romaji
399
- *
400
- * @example
401
- * ```ts
402
- * kanaToRomaji("カタカナタイトル"); // "katakanataitoru"
403
- * kanaToRomaji("ハンバーガー"); // "hanbaga"
404
- * kanaToRomaji("まっちゃ"); // "matcha"
405
- * ```
406
- */ function kanaToRomaji(text) {
407
- const chars = [...text].map(normalizeToHiragana);
408
- let out = "";
409
- let geminate = false;
410
- for (let i = 0; i < chars.length; i++) {
411
- const char = chars[i] ?? "";
412
- if (char === SOKUON) {
413
- geminate = true;
414
- continue;
415
- }
416
- if (char === CHOONPU) {
417
- geminate = false;
418
- continue;
419
- }
420
- const combo = KANA_COMBO[char + (chars[i + 1] ?? "")];
421
- const romaji = combo ?? KANA_BASE[char];
422
- if (combo) i++;
423
- if (romaji === void 0) {
424
- geminate = false;
425
- out += char;
426
- continue;
427
- }
428
- if (geminate) {
429
- geminate = false;
430
- if (/^[^aeiou]/.test(romaji)) out += romaji.startsWith("ch") ? "t" : romaji[0] ?? "";
431
- }
432
- out += romaji;
433
- }
434
- return out;
435
- }
436
- //#endregion
437
- //#region src/lib/url.ts
438
- /**
439
- * URL Utilities
440
- */ var url_exports = /* @__PURE__ */ __exportAll({
441
- buildSiteUrl: () => buildSiteUrl,
442
- extractDisplayDomain: () => extractDisplayDomain,
443
- extractDomain: () => extractDomain,
444
- getSiteOrigin: () => getSiteOrigin,
445
- getSitePathPrefix: () => getSitePathPrefix,
446
- isFullUrl: () => isFullUrl,
447
- isSafeAbsoluteUrl: () => isSafeAbsoluteUrl,
448
- isSafeInternalRedirect: () => isSafeInternalRedirect,
449
- normalizePath: () => normalizePath,
450
- normalizeSitePathPrefix: () => normalizeSitePathPrefix,
451
- normalizeSiteUrl: () => normalizeSiteUrl,
452
- sanitizeRichTextHref: () => sanitizeRichTextHref,
453
- sanitizeUrl: () => sanitizeUrl,
454
- slugify: () => slugify,
455
- stripSitePathPrefix: () => stripSitePathPrefix,
456
- toAbsoluteAssetUrl: () => toAbsoluteAssetUrl,
457
- toAbsoluteSiteUrl: () => toAbsoluteSiteUrl,
458
- toPublicHref: () => toPublicHref,
459
- toPublicPath: () => toPublicPath,
460
- toSameSitePath: () => toSameSitePath
461
- });
462
- function normalizeSitePathname(pathname) {
463
- if (pathname === "/" || pathname === "") return "";
464
- const trimmed = pathname.replace(/\/+$/, "");
465
- return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
466
- }
467
- /**
468
- * Normalize a configured public site path prefix.
469
- *
470
- * @param sitePathPrefix - Prefix such as `/blog` or `blog`
471
- * @returns Normalized prefix like `/blog`, or an empty string when rooted
472
- */ function normalizeSitePathPrefix(sitePathPrefix) {
473
- return normalizeSitePathname(sitePathPrefix);
474
- }
475
- /**
476
- * Extracts the hostname (domain) from a URL string.
477
- *
478
- * Parses a full URL and returns just the hostname portion (e.g., "example.com" from
479
- * "https://example.com/path"). Returns `null` if the URL is malformed or cannot be parsed.
480
- *
481
- * @param url - The full URL string to extract the domain from
482
- * @returns The hostname/domain if valid, or `null` if parsing fails
483
- *
484
- * @example
485
- * ```ts
486
- * const domain = extractDomain("https://www.example.com/path");
487
- * // Returns: "www.example.com"
488
- *
489
- * const invalid = extractDomain("not-a-url");
490
- * // Returns: null
491
- * ```
492
- */ function extractDomain(url) {
493
- try {
494
- return new URL(url).hostname;
495
- } catch {
496
- return null;
497
- }
498
- }
499
- /**
500
- * Normalizes a path by removing slashes and converting to lowercase.
501
- *
502
- * Trims whitespace, converts to lowercase, removes leading and trailing slashes,
503
- * and collapses multiple consecutive slashes into single slashes. Used to create
504
- * consistent path representations for routing and storage.
505
- *
506
- * @param path - The path string to normalize
507
- * @returns The normalized path string
508
- *
509
- * @example
510
- * ```ts
511
- * const normalized = normalizePath(" /About/Contact// ");
512
- * // Returns: "about/contact"
513
- * ```
514
- */ function normalizePath(path) {
515
- return path.trim().toLowerCase().replace(/^\/+|\/+$/g, "").replace(/\/+/g, "/");
516
- }
517
- /**
518
- * Checks if a string is a full URL with HTTP or HTTPS protocol.
519
- *
520
- * Validates whether a string starts with "http://" or "https://", indicating it's
521
- * a full URL rather than a relative path. Useful for distinguishing between internal
522
- * paths and external URLs.
523
- *
524
- * @param str - The string to check
525
- * @returns `true` if the string starts with http:// or https://, `false` otherwise
526
- *
527
- * @example
528
- * ```ts
529
- * isFullUrl("https://example.com"); // Returns: true
530
- * isFullUrl("/about"); // Returns: false
531
- * isFullUrl("example.com"); // Returns: false
532
- * ```
533
- */ function isFullUrl(str) {
534
- return str.startsWith("http://") || str.startsWith("https://");
535
- }
536
- /**
537
- * If a full URL points at the site's own host, return its same-site path
538
- * (`pathname` + `search` + `hash`). Returns `null` when the input is not a full
539
- * URL, is unparseable, or points at a different host.
540
- *
541
- * Self-referential absolute links — e.g. a nav item set to
542
- * `https://example.com/about` on `example.com` — should behave like the
543
- * internal path `/about`: no external-link icon, no `target="_blank"`.
544
- *
545
- * Matching is by **hostname**, not full origin: scheme and port differences are
546
- * treated as same-site. This keeps the check intuitive ("same domain") and
547
- * robust in dev, where the site is often served over `http://host:<port>` while
548
- * a nav link stores the canonical `https://host` URL. In production (canonical
549
- * https + default port) hostname match is equivalent to origin match.
550
- *
551
- * @param url - Candidate URL (full URL or relative path)
552
- * @param siteOrigin - The site's own origin, e.g. `https://example.com`
553
- * @returns The same-site path, or `null` when the URL is external/non-absolute
554
- *
555
- * @example
556
- * ```ts
557
- * toSameSitePath("https://example.com/about", "https://example.com"); // "/about"
558
- * toSameSitePath("https://example.com/about", "http://example.com:8787"); // "/about"
559
- * toSameSitePath("https://other.com/about", "https://example.com"); // null
560
- * toSameSitePath("/about", "https://example.com"); // null
561
- * ```
562
- */ function toSameSitePath(url, siteOrigin) {
563
- if (!siteOrigin || !isFullUrl(url)) return null;
564
- let parsed;
565
- let reference;
566
- try {
567
- parsed = new URL(url);
568
- reference = new URL(siteOrigin);
569
- } catch {
570
- return null;
571
- }
572
- if (parsed.hostname !== reference.hostname) return null;
573
- return `${parsed.pathname}${parsed.search}${parsed.hash}` || "/";
574
- }
575
- var HAN_GLOBAL_RE = /\p{Script=Han}/gu;
576
- var CONTENT_CHAR_RE = /[\p{L}\p{N}]/u;
577
- var ASCII_ALNUM_RE = /[a-zA-Z0-9]/;
578
- /**
579
- * Minimum fraction of a title's letters/digits the transliteration pipeline
580
- * must handle for the result to be usable. Below this, too much meaning is
581
- * lost (e.g. mixed kanji+kana Japanese where the semantic core is kanji, or
582
- * scripts limax drops entirely) and `slugify` returns "" so callers fall back
583
- * to their random-ID path instead of shipping a garbled slug.
584
- */ var SLUG_COVERAGE_THRESHOLD = .75;
585
- /** Per-character limax probe cache: does this char transliterate to anything? */ var charCoverageCache = /* @__PURE__ */ new Map();
586
- function isCharCovered(char) {
587
- if (ASCII_ALNUM_RE.test(char)) return true;
588
- let covered = charCoverageCache.get(char);
589
- if (covered === void 0) {
590
- covered = limax(char, { tone: false }).length > 0;
591
- charCoverageCache.set(char, covered);
592
- }
593
- return covered;
594
- }
595
- function slugCoverage(text, japanese) {
596
- let content = 0;
597
- let covered = 0;
598
- for (const char of text) {
599
- if (!CONTENT_CHAR_RE.test(char)) continue;
600
- content++;
601
- if (KANA_RE.test(char) || HANGUL_RE.test(char)) covered++;
602
- else if (japanese && HAN_RE.test(char)) {} else if (isCharCovered(char)) covered++;
603
- }
604
- return content === 0 ? 0 : covered / content;
605
- }
606
- /**
607
- * Converts text to a URL-friendly slug.
608
- *
609
- * Transliterates i18n text to Latin before slugifying: Korean hangul via
610
- * Revised Romanization, Japanese kana via Hepburn romaji (both in-house, see
611
- * `translit.ts`), everything else (Han → pinyin, Cyrillic, accents, …) via
612
- * limax. When the title contains kana it is treated as Japanese and Han
613
- * characters are dropped rather than misread as Chinese pinyin; Han without
614
- * kana is treated as Chinese.
615
- *
616
- * Returns `""` when transliteration would lose too much of the title (see
617
- * `SLUG_COVERAGE_THRESHOLD`) — every caller treats an empty base as "no
618
- * usable title" and falls back to a random ID, which beats publishing a
619
- * garbled slug.
620
- *
621
- * @param text - The text to convert to a slug
622
- * @returns The slugified string, or `""` when no usable slug can be derived
623
- *
624
- * @example
625
- * ```ts
626
- * slugify("Hello World! This is a Test.");
627
- * // Returns: "hello-world-this-is-a-test"
628
- *
629
- * slugify("书评");
630
- * // Returns: "shu-ping"
631
- *
632
- * slugify("안녕하세요 세계");
633
- * // Returns: "annyeonghaseyo-segye"
634
- *
635
- * slugify("ハンバーガー");
636
- * // Returns: "hanbaga"
637
- *
638
- * slugify("日本語のタイトルです");
639
- * // Returns: "" (kanji carries the meaning and can't be transliterated)
640
- * ```
641
- */ function slugify(text) {
642
- const japanese = KANA_RE.test(text);
643
- let pre = text;
644
- if (japanese) pre = kanaToRomaji(pre.replace(HAN_GLOBAL_RE, " "));
645
- if (HANGUL_RE.test(pre)) pre = hangulToLatin(pre);
646
- const base = limax(pre, { tone: false }).replace(/_/g, "-");
647
- if (!base) return "";
648
- return slugCoverage(text, japanese) >= SLUG_COVERAGE_THRESHOLD ? base : "";
649
- }
650
- /**
651
- * Extracts a human-friendly domain name from a URL for display purposes.
652
- *
653
- * Parses the URL, strips common prefixes (`www.`, `m.`, `mobile.`), and returns
654
- * a clean domain. Returns `null` if the URL is malformed.
655
- *
656
- * @param url - The full URL string
657
- * @returns A display-friendly domain string, or `null` if parsing fails
658
- *
659
- * @example
660
- * ```ts
661
- * extractDisplayDomain("https://www.example.com/path");
662
- * // Returns: "example.com"
663
- *
664
- * extractDisplayDomain("https://m.wikipedia.org/wiki/Test");
665
- * // Returns: "wikipedia.org"
666
- *
667
- * extractDisplayDomain("https://blog.example.com");
668
- * // Returns: "blog.example.com"
669
- * ```
670
- */ function extractDisplayDomain(url) {
671
- const hostname = extractDomain(url);
672
- if (!hostname) return null;
673
- return hostname.replace(/^(?:www|m|mobile)\./, "");
674
- }
675
- var SAFE_URL_PROTOCOLS = new Set([
676
- "http:",
677
- "https:",
678
- "mailto:"
679
- ]);
680
- var SAFE_RICH_TEXT_HREF_PROTOCOLS = new Set([
681
- ...SAFE_URL_PROTOCOLS,
682
- "tel:",
683
- "sms:"
684
- ]);
685
- function sanitizeUrlWithProtocols(url, protocols) {
686
- try {
687
- const parsed = new URL(url, "https://placeholder.invalid");
688
- if (protocols.has(parsed.protocol)) return url;
689
- return "";
690
- } catch {
691
- return "";
692
- }
693
- }
694
- /**
695
- * Checks whether a string is a safe absolute URL.
696
- *
697
- * Accepts absolute URLs that use `http:`, `https:`, or `mailto:`. Relative
698
- * paths are rejected because external post/source URLs must be fully qualified.
699
- *
700
- * @param url - The URL string to validate
701
- * @returns `true` when the URL is absolute and uses an allowed protocol
702
- *
703
- * @example
704
- * ```ts
705
- * isSafeAbsoluteUrl("https://example.com"); // true
706
- * isSafeAbsoluteUrl("mailto:hello@example.com"); // true
707
- * isSafeAbsoluteUrl("example.com"); // false
708
- * ```
709
- */ function isSafeAbsoluteUrl(url) {
710
- const trimmed = url.trim();
711
- if (!trimmed) return false;
712
- try {
713
- const parsed = new URL(trimmed);
714
- return SAFE_URL_PROTOCOLS.has(parsed.protocol);
715
- } catch {
716
- return false;
717
- }
718
- }
719
- /**
720
- * Sanitizes a URL by ensuring it uses a safe protocol.
721
- *
722
- * Returns the URL unchanged if it uses an allowed protocol (http:, https:, mailto:)
723
- * or is a relative path. Returns an empty string for dangerous protocols like
724
- * `javascript:`, `data:`, or `vbscript:`.
725
- *
726
- * @param url - The URL string to sanitize
727
- * @returns The original URL if safe, or an empty string if the protocol is disallowed
728
- *
729
- * @example
730
- * ```ts
731
- * sanitizeUrl("https://example.com"); // "https://example.com"
732
- * sanitizeUrl("/about"); // "/about"
733
- * sanitizeUrl("javascript:alert(1)"); // ""
734
- * sanitizeUrl("data:text/html,<h1>Hi</h1>"); // ""
735
- * ```
736
- */ function sanitizeUrl(url) {
737
- return sanitizeUrlWithProtocols(url, SAFE_URL_PROTOCOLS);
738
- }
739
- /**
740
- * Sanitizes a rich-text link destination.
741
- *
742
- * In addition to safe web and email URLs, rich-text links may launch the
743
- * device's phone or messaging app through `tel:` and `sms:`. Relative links
744
- * remain valid, while executable and resource-only protocols are rejected.
745
- *
746
- * @param url - The link destination to sanitize
747
- * @returns The original URL if safe for an anchor, or an empty string otherwise
748
- *
749
- * @example
750
- * ```ts
751
- * sanitizeRichTextHref("sms:+15551234567"); // "sms:+15551234567"
752
- * sanitizeRichTextHref("javascript:alert(1)"); // ""
753
- * ```
754
- */ function sanitizeRichTextHref(url) {
755
- return sanitizeUrlWithProtocols(url, SAFE_RICH_TEXT_HREF_PROTOCOLS);
756
- }
757
- /**
758
- * Normalize a public site URL by stripping any trailing slash from the path.
759
- *
760
- * @param siteUrl - Full site URL, optionally with a path prefix
761
- * @returns Normalized site URL, or an empty string when not configured
762
- */ function normalizeSiteUrl(siteUrl) {
763
- const trimmed = siteUrl.trim();
764
- if (!trimmed) return "";
765
- const parsed = new URL(trimmed);
766
- parsed.pathname = normalizeSitePathname(parsed.pathname) || "/";
767
- if (parsed.pathname !== "/") parsed.pathname = parsed.pathname.replace(/\/+$/, "");
768
- return parsed.toString().replace(/\/$/, parsed.pathname === "/" ? "/" : "");
769
- }
770
- /**
771
- * Extract the public path prefix from a full site URL.
772
- *
773
- * @param siteUrl - Full site URL
774
- * @returns Prefix like `/blog`, or an empty string when the site is rooted
775
- */ function getSitePathPrefix(siteUrl) {
776
- if (!siteUrl.trim()) return "";
777
- return normalizeSitePathname(new URL(siteUrl).pathname);
778
- }
779
- /**
780
- * Extract the origin from a full site URL.
781
- *
782
- * @param siteUrl - Full site URL
783
- * @returns Origin like `https://example.com`, or an empty string when missing
784
- */ function getSiteOrigin(siteUrl) {
785
- if (!siteUrl.trim()) return "";
786
- return new URL(siteUrl).origin;
787
- }
788
- /**
789
- * Build a normalized public site URL from an origin and optional path prefix.
790
- *
791
- * @param siteOrigin - Public site origin, such as `https://example.com`
792
- * @param sitePathPrefix - Public site path prefix, such as `/blog`
793
- * @returns Normalized site URL, or an empty string when no origin is configured
794
- */ function buildSiteUrl(siteOrigin, sitePathPrefix = "") {
795
- const trimmedOrigin = siteOrigin.trim();
796
- if (!trimmedOrigin) return "";
797
- const origin = new URL(trimmedOrigin).origin;
798
- return normalizeSiteUrl(`${origin}${normalizeSitePathPrefix(sitePathPrefix) || "/"}`);
799
- }
800
- /**
801
- * Prefix an internal app path with the public site path prefix.
802
- *
803
- * Internal paths are always rooted at `/` and never include the deployment
804
- * prefix. This helper converts them to public-facing paths.
805
- *
806
- * @param path - Internal app path, such as `/settings`
807
- * @param sitePathPrefix - Public site path prefix, such as `/blog`
808
- * @returns Public path
809
- */ function toPublicPath(path, sitePathPrefix = "") {
810
- if (!path) return sitePathPrefix || "/";
811
- const normalizedPath = path.startsWith("/") ? path : `/${path}`;
812
- if (!sitePathPrefix) return normalizedPath;
813
- if (normalizedPath === sitePathPrefix || normalizedPath.startsWith(`${sitePathPrefix}/`)) return normalizedPath;
814
- if (normalizedPath === "/") return sitePathPrefix;
815
- return `${sitePathPrefix}${normalizedPath}`;
816
- }
817
- /**
818
- * Convert an app-local href to its public path while leaving external URLs
819
- * unchanged.
820
- *
821
- * @param href - Internal app path or external URL
822
- * @param sitePathPrefix - Public site path prefix
823
- * @returns Public-facing href
824
- */ function toPublicHref(href, sitePathPrefix = "") {
825
- if (href.startsWith("http://") || href.startsWith("https://") || href.startsWith("//") || href.startsWith("mailto:") || href.startsWith("tel:") || href.startsWith("#")) return href;
826
- return toPublicPath(href, sitePathPrefix);
827
- }
828
- /**
829
- * Check whether a path is a safe same-origin redirect target.
830
- *
831
- * Accepts only paths that start with a single `/` (no protocol-relative
832
- * `//host`, no scheme, no control characters). Callers should use this to
833
- * validate user-supplied `redirect` query parameters before issuing a
834
- * `Location` header.
835
- *
836
- * @param path - Candidate redirect path
837
- * @returns `true` when the path is safe to use as an internal redirect
838
- *
839
- * @example
840
- * ```ts
841
- * isSafeInternalRedirect("/settings") // true
842
- * isSafeInternalRedirect("//evil.example") // false
843
- * isSafeInternalRedirect("https://evil.example") // false
844
- * ```
845
- */ function isSafeInternalRedirect(path) {
846
- if (typeof path !== "string") return false;
847
- if (!path.startsWith("/")) return false;
848
- if (path.startsWith("//")) return false;
849
- if (path.startsWith("/\\")) return false;
850
- for (let i = 0; i < path.length; i += 1) {
851
- const code = path.charCodeAt(i);
852
- if (code < 32 || code === 127) return false;
853
- }
854
- return true;
855
- }
856
- /**
857
- * Remove the site path prefix from a public request pathname.
858
- *
859
- * @param pathname - Public request pathname
860
- * @param sitePathPrefix - Configured public site path prefix
861
- * @returns Internal pathname, or `null` when the request is outside the site
862
- */ function stripSitePathPrefix(pathname, sitePathPrefix) {
863
- if (!sitePathPrefix) return pathname || "/";
864
- if (pathname === sitePathPrefix) return "/";
865
- if (pathname.startsWith(`${sitePathPrefix}/`)) return pathname.slice(sitePathPrefix.length) || "/";
866
- return null;
867
- }
868
- /**
869
- * Convert an internal or public path into an absolute site URL.
870
- *
871
- * @param path - Internal app path or already-public path
872
- * @param siteUrl - Normalized site URL
873
- * @param sitePathPrefix - Public site path prefix
874
- * @returns Absolute URL when siteUrl is configured, otherwise the original path
875
- */ function toAbsoluteSiteUrl(path, siteUrl, sitePathPrefix = "") {
876
- if (!siteUrl) return toPublicPath(path, sitePathPrefix);
877
- return new URL(toPublicPath(path, sitePathPrefix), siteUrl).toString();
878
- }
879
- /**
880
- * Resolve a possibly-relative asset URL to an absolute URL, leaving
881
- * already-absolute (`http(s):`) and protocol-relative (`//host`) URLs
882
- * untouched. Use for assets — like media — whose stored URL may be either an
883
- * app-local path or a full CDN URL.
884
- *
885
- * @param url - Asset URL: an internal path or an already-absolute URL
886
- * @param siteUrl - Normalized site URL
887
- * @param sitePathPrefix - Public site path prefix
888
- * @returns Absolute URL, or the original value when it is already absolute
889
- *
890
- * @example
891
- * ```ts
892
- * toAbsoluteAssetUrl("/m/a.png", "https://site.com"); // "https://site.com/m/a.png"
893
- * toAbsoluteAssetUrl("https://cdn.example/a.png", "x"); // "https://cdn.example/a.png"
894
- * ```
895
- */ function toAbsoluteAssetUrl(url, siteUrl, sitePathPrefix = "") {
896
- if (isFullUrl(url) || url.startsWith("//")) return url;
897
- return toAbsoluteSiteUrl(url, siteUrl, sitePathPrefix);
898
- }
899
- //#endregion
900
- export { toPublicHref as _, getSitePathPrefix as a, url_exports as b, normalizePath as c, sanitizeRichTextHref as d, sanitizeUrl as f, toAbsoluteSiteUrl as g, toAbsoluteAssetUrl as h, getSiteOrigin as i, normalizeSitePathPrefix as l, stripSitePathPrefix as m, extractDisplayDomain as n, isFullUrl as o, slugify as p, extractDomain as r, isSafeInternalRedirect as s, buildSiteUrl as t, normalizeSiteUrl as u, toPublicPath as v, __exportAll as x, toSameSitePath as y };