@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.
- package/README.md +11 -11
- package/bin/commands/assets/upload.js +192 -15
- package/bin/commands/import-site.js +60 -4
- package/bin/commands/reset-password.js +21 -8
- package/bin/commands/site/snapshot/export.js +1 -1
- package/bin/commands/site/snapshot/import.js +1 -1
- package/bin/lib/d1-query.js +5 -16
- package/bin/lib/node-env.js +40 -4
- package/bin/lib/site-selection.js +85 -29
- package/bin/lib/wrangler-cli.js +28 -2
- package/bin/lib/wrangler-json.js +35 -0
- package/dist/{app-C7_4oab9.js → app-K_Aa1MMn.js} +52476 -13330
- package/dist/client/.vite/manifest.json +255 -90
- package/dist/client/_assets/chunks/confirm-D7GrwsCN.js +45 -0
- package/dist/client/_assets/chunks/create-editor-CD3FhrOB.js +257 -0
- package/dist/client/_assets/chunks/{native-DpcrFAPh.js → emoji-mart-C4RpRKcU.js} +716 -1
- package/dist/client/_assets/chunks/footnote-rail-CmeoVtnU.js +1 -0
- package/dist/client/_assets/chunks/heic-to-CuP2Qlsw.js +1 -0
- package/dist/client/_assets/chunks/jant-locale-picker-DL6mIOT5.js +69 -0
- package/dist/client/_assets/chunks/lit-DW8VmJAT.js +2 -0
- package/dist/client/_assets/chunks/mediabunny-DRaCQ1r-.js +31 -0
- package/dist/client/_assets/chunks/rolldown-runtime-hePW80VL.js +1 -0
- package/dist/client/_assets/chunks/settings-paths-BcdySNx6.js +84 -0
- package/dist/client/_assets/chunks/site-header-nav-DzXsFk05.js +1 -0
- package/dist/client/_assets/chunks/slug-format-CRXO1AL5.js +1 -0
- package/dist/client/_assets/chunks/slugify-373citbs.js +1 -0
- package/dist/client/_assets/chunks/sortable-list-CgaL2jCs.js +2 -0
- package/dist/client/_assets/chunks/types-D5iF3H1a.js +1 -0
- package/dist/client/_assets/chunks/unsafe-html-B5ZEXaxg.js +1 -0
- package/dist/client/_assets/chunks/unsafe-svg-0QCkP0vZ.js +2 -0
- package/dist/client/_assets/client-GiYENVw8.css +2 -0
- package/dist/client/_assets/client-VnFxJN7G.js +295 -0
- package/dist/client/_assets/client-auth-k8gJ6QJj.js +828 -0
- package/dist/client/_assets/client-author-DQ8R-8KR.css +2 -0
- package/dist/client/_assets/client-compose-B_kDtWkW.js +2004 -0
- package/dist/client/_assets/client-manage-M90aSTOk.js +2170 -0
- package/dist/client/_assets/client-settings-DfYs9n1F.js +1112 -0
- package/dist/{export-Dl5KCiEs.js → github-sync-BPAvT999.js} +2254 -107
- package/dist/index.js +2 -5
- package/dist/node.js +5 -7
- package/package.json +3 -3
- package/src/__tests__/auth-session-freshness.test.ts +57 -0
- package/src/__tests__/basecoat-variants.test.ts +121 -0
- package/src/__tests__/bin/assets-upload.test.ts +68 -0
- package/src/__tests__/export-hugo-build.test.ts +260 -0
- package/src/__tests__/export-service.test.ts +231 -0
- package/src/__tests__/helpers/component-css.ts +91 -0
- package/src/__tests__/helpers/db.ts +33 -0
- package/src/__tests__/helpers/export-fixtures.ts +3 -0
- package/src/__tests__/stylesheet-audience.test.ts +159 -0
- package/src/__tests__/stylesheet-breakpoints.test.ts +183 -0
- package/src/app.tsx +71 -16
- package/src/auth.ts +18 -1
- package/src/client/__tests__/collection-page-actions.test.ts +51 -2
- package/src/client/__tests__/compose-bridge-image-upload.test.ts +196 -0
- package/src/client/__tests__/compose-bridge-video-poster.test.ts +159 -0
- package/src/client/__tests__/compose-bridge.test.ts +272 -0
- package/src/client/__tests__/compose-launch.test.ts +62 -1
- package/src/client/__tests__/compose-shortcuts.test.ts +38 -9
- package/src/client/__tests__/compose-triggers.test.ts +90 -0
- package/src/client/__tests__/copy-field.test.ts +162 -0
- package/src/client/__tests__/custom-url-menu.test.ts +127 -6
- package/src/client/__tests__/image-processor.test.ts +324 -2
- package/src/client/__tests__/lazy-entries.test.ts +92 -0
- package/src/client/__tests__/media-scroll-hint.test.ts +155 -0
- package/src/client/__tests__/runtime-paths.test.ts +84 -0
- package/src/client/__tests__/smart-collection-page-actions.test.ts +158 -0
- package/src/client/__tests__/toast.test.ts +23 -0
- package/src/client/__tests__/video-processor-probe.test.ts +175 -0
- package/src/client/__tests__/video-processor.test.ts +181 -0
- package/src/client/audio-processor.ts +24 -15
- package/src/client/audio-waveform.ts +49 -0
- package/src/client/collection-dialog-host.ts +72 -0
- package/src/client/collection-navigation.ts +51 -16
- package/src/client/collection-page-actions.ts +26 -0
- package/src/client/components/__tests__/jant-collection-dialog.test.ts +329 -0
- package/src/client/components/__tests__/jant-collection-directory.test.ts +274 -10
- package/src/client/components/__tests__/jant-collection-form.test.ts +59 -178
- package/src/client/components/__tests__/jant-command-palette.test.ts +28 -0
- package/src/client/components/__tests__/jant-compose-dialog-draft-storage.test.ts +340 -0
- package/src/client/components/__tests__/jant-compose-dialog.test.ts +1250 -53
- package/src/client/components/__tests__/jant-compose-editor.test.ts +544 -40
- package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +0 -1
- package/src/client/components/__tests__/jant-locale-picker.test.ts +207 -0
- package/src/client/components/__tests__/jant-nav-manager.test.ts +234 -15
- package/src/client/components/__tests__/jant-post-menu.test.ts +413 -2
- package/src/client/components/__tests__/jant-post-picker.test.ts +197 -0
- package/src/client/components/__tests__/jant-settings-avatar.test.ts +15 -12
- package/src/client/components/__tests__/jant-settings-general.test.ts +541 -172
- package/src/client/components/__tests__/jant-settings-language.test.ts +851 -0
- package/src/client/components/__tests__/jant-smart-collection-dialog.test.ts +665 -0
- package/src/client/components/collection-dialog-types.ts +49 -0
- package/src/client/components/collection-manager-types.ts +32 -1
- package/src/client/components/collection-types.ts +1 -15
- package/src/client/components/compose-types.ts +75 -1
- package/src/client/components/jant-collection-dialog.ts +722 -0
- package/src/client/components/jant-collection-directory.ts +567 -182
- package/src/client/components/jant-collection-form.ts +53 -232
- package/src/client/components/jant-command-palette.ts +100 -49
- package/src/client/components/jant-compose-dialog.ts +1486 -707
- package/src/client/components/jant-compose-editor.ts +673 -336
- package/src/client/components/jant-compose-fullscreen.ts +51 -43
- package/src/client/components/jant-config-editor.ts +65 -53
- package/src/client/components/jant-confirm-dialog.ts +13 -11
- package/src/client/components/jant-locale-picker.ts +418 -0
- package/src/client/components/jant-media-lightbox.ts +146 -136
- package/src/client/components/jant-nav-manager.ts +690 -366
- package/src/client/components/jant-post-menu.ts +1086 -282
- package/src/client/components/jant-post-picker.ts +324 -0
- package/src/client/components/jant-repo-picker.ts +138 -106
- package/src/client/components/jant-settings-avatar.ts +31 -27
- package/src/client/components/jant-settings-general.ts +617 -543
- package/src/client/components/jant-settings-language.ts +1087 -0
- package/src/client/components/jant-smart-collection-dialog.ts +1021 -0
- package/src/client/components/jant-text-preview.ts +94 -86
- package/src/client/components/nav-manager-types.ts +62 -1
- package/src/client/components/settings-types.ts +14 -29
- package/src/client/components/smart-collection-conditions.ts +285 -0
- package/src/client/components/smart-collection-dialog-types.ts +68 -0
- package/src/client/compose-bridge.ts +214 -141
- package/src/client/compose-launch.ts +76 -2
- package/src/client/compose-shortcuts.ts +6 -8
- package/src/client/compose-triggers.ts +50 -0
- package/src/client/copy-field.ts +108 -0
- package/src/client/custom-url-menu.ts +60 -4
- package/src/client/icons.ts +14 -0
- package/src/client/image-plan.ts +165 -0
- package/src/client/image-processor.ts +195 -117
- package/src/client/image-worker-client.ts +45 -0
- package/src/client/image-worker.ts +135 -0
- package/src/client/lazy-entries.ts +95 -0
- package/src/client/lazy-slugify.ts +2 -2
- package/src/client/media-scroll-hint.ts +43 -2
- package/src/client/mediabunny.ts +29 -0
- package/src/client/post-picker.ts +41 -0
- package/src/client/runtime-paths.ts +74 -1
- package/src/client/settings-bridge.ts +4 -10
- package/src/client/site-header-nav.js +18 -6
- package/src/client/smart-collection-dialog-host.ts +116 -0
- package/src/client/smart-collection-page-actions.ts +208 -0
- package/src/client/tiptap/__tests__/bubble-menu.test.ts +110 -0
- package/src/client/tiptap/__tests__/floating-position.test.ts +136 -1
- package/src/client/tiptap/__tests__/inline-image-upload.test.ts +37 -0
- package/src/client/tiptap/bubble-menu.ts +47 -16
- package/src/client/tiptap/floating-position.ts +216 -2
- package/src/client/tiptap/inline-image-upload.ts +7 -3
- package/src/client/tiptap/link-toolbar.ts +22 -9
- package/src/client/toast.ts +13 -0
- package/src/client/types/vite-worker.d.ts +12 -0
- package/src/client/upload-with-metadata.ts +3 -13
- package/src/client/video-processor.ts +351 -107
- package/src/client-auth.ts +14 -31
- package/src/client-compose.ts +19 -0
- package/src/client-manage.ts +13 -0
- package/src/client-settings.ts +14 -0
- package/src/client-site.ts +10 -3
- package/src/client.ts +4 -0
- package/src/db/__tests__/backfill-nav-label-mirrors.test.ts +149 -0
- package/src/db/__tests__/schema-enum-sources.test.ts +33 -0
- package/src/db/__tests__/smart-collection-placement.test.ts +402 -0
- package/src/db/backfills/0006_clear_mirrored_page_nav_labels.sql +31 -0
- package/src/db/migrations/0032_married_tomas.sql +3 -0
- package/src/db/migrations/0033_busy_johnny_storm.sql +198 -0
- package/src/db/migrations/0034_cheerful_rumiko_fujikawa.sql +35 -0
- package/src/db/migrations/meta/0032_snapshot.json +2582 -0
- package/src/db/migrations/meta/0033_snapshot.json +2816 -0
- package/src/db/migrations/meta/0034_snapshot.json +2816 -0
- package/src/db/migrations/meta/_journal.json +21 -0
- package/src/db/migrations/pg/0030_abandoned_nighthawk.sql +3 -0
- package/src/db/migrations/pg/0031_strong_ultimatum.sql +128 -0
- package/src/db/migrations/pg/0032_furry_multiple_man.sql +6 -0
- package/src/db/migrations/pg/meta/0030_snapshot.json +3329 -0
- package/src/db/migrations/pg/meta/0031_snapshot.json +3660 -0
- package/src/db/migrations/pg/meta/0032_snapshot.json +3660 -0
- package/src/db/migrations/pg/meta/_journal.json +21 -0
- package/src/db/pg/schema.ts +195 -27
- package/src/db/post-visibility.ts +117 -0
- package/src/db/schema.ts +222 -27
- package/src/i18n/__tests__/detect.test.ts +26 -81
- package/src/i18n/__tests__/supported-locales.test.ts +83 -0
- package/src/i18n/context.tsx +12 -2
- package/src/i18n/detect.ts +7 -66
- package/src/i18n/locales/glossary.zh-Hans.yml +44 -4
- package/src/i18n/locales/glossary.zh-Hant.yml +44 -4
- package/src/i18n/locales/public/en.po +490 -158
- package/src/i18n/locales/public/en.ts +1 -1
- package/src/i18n/locales/public/zh-Hans.po +487 -155
- package/src/i18n/locales/public/zh-Hans.ts +1 -1
- package/src/i18n/locales/public/zh-Hant.po +487 -155
- package/src/i18n/locales/public/zh-Hant.ts +1 -1
- package/src/i18n/locales/settings/en.po +551 -69
- package/src/i18n/locales/settings/en.ts +1 -1
- package/src/i18n/locales/settings/zh-Hans.po +597 -115
- package/src/i18n/locales/settings/zh-Hans.ts +1 -1
- package/src/i18n/locales/settings/zh-Hant.po +610 -128
- package/src/i18n/locales/settings/zh-Hant.ts +1 -1
- package/src/i18n/locales.ts +94 -0
- package/src/i18n/middleware.ts +12 -2
- package/src/i18n/supported-locales.ts +80 -0
- package/src/lib/__tests__/api-settings.test.ts +11 -2
- package/src/lib/__tests__/asset-path.test.ts +92 -0
- package/src/lib/__tests__/deferred.test.ts +90 -0
- package/src/lib/__tests__/discover-ping.test.ts +89 -0
- package/src/lib/__tests__/discover.test.ts +410 -0
- package/src/lib/__tests__/feed-policy.test.ts +26 -0
- package/src/lib/__tests__/feed.test.ts +1526 -15
- package/src/lib/__tests__/filter-dimensions.test.ts +341 -0
- package/src/lib/__tests__/hosted-signin.test.ts +1 -1
- package/src/lib/__tests__/http-cache.test.ts +162 -0
- package/src/lib/__tests__/jant-docs-dev.test.ts +21 -0
- package/src/lib/__tests__/jant-docs.test.ts +23 -0
- package/src/lib/__tests__/lang-detect.test.ts +231 -0
- package/src/lib/__tests__/media-helpers.test.ts +81 -0
- package/src/lib/__tests__/navigation.test.ts +135 -1
- package/src/lib/__tests__/resolve-config.test.ts +64 -3
- package/src/lib/__tests__/schemas.test.ts +42 -4
- package/src/lib/__tests__/site-unavailable-page.test.ts +25 -0
- package/src/lib/__tests__/slug.test.ts +70 -0
- package/src/lib/__tests__/slugify.test.ts +85 -0
- package/src/lib/__tests__/storage.test.ts +32 -0
- package/src/lib/__tests__/thread-fold.test.ts +113 -0
- package/src/lib/__tests__/upload-sideload.test.ts +14 -0
- package/src/lib/__tests__/url-fetch.test.ts +1 -2
- package/src/lib/__tests__/url.test.ts +78 -72
- package/src/lib/__tests__/view-language.test.ts +413 -0
- package/src/lib/__tests__/worker-response-cache.test.ts +90 -0
- package/src/lib/address.ts +57 -0
- package/src/lib/api-posts.ts +12 -0
- package/src/lib/asset-path.ts +98 -1
- package/src/lib/build-env.ts +16 -0
- package/src/lib/collection-groups.ts +5 -1
- package/src/lib/collection-paths.ts +3 -10
- package/src/lib/constants.ts +28 -3
- package/src/lib/copy-field.ts +36 -0
- package/src/lib/crypto.ts +40 -0
- package/src/lib/csp-builder.ts +2 -1
- package/src/lib/deferred.ts +79 -0
- package/src/lib/discover-ping.ts +141 -0
- package/src/lib/discover.ts +437 -0
- package/src/lib/env.ts +134 -0
- package/src/lib/errors.ts +33 -0
- package/src/lib/feed-policy.ts +236 -2
- package/src/lib/feed.ts +926 -73
- package/src/lib/filter-dimensions.ts +1349 -0
- package/src/lib/github-sync-site-config.ts +3 -0
- package/src/lib/hosted-signin.ts +1 -1
- package/src/lib/http-cache.ts +148 -0
- package/src/lib/hugo-markdown.ts +16 -0
- package/src/lib/icons.ts +9 -2
- package/src/lib/ids.ts +3 -0
- package/src/lib/jant-docs.ts +42 -0
- package/src/lib/lang-detect.ts +308 -0
- package/src/lib/media-helpers.ts +13 -1
- package/src/lib/navigation.ts +78 -11
- package/src/lib/pagination.ts +28 -24
- package/src/lib/per-language-surfaces.ts +67 -0
- package/src/lib/post-meta.ts +19 -0
- package/src/lib/render.tsx +55 -7
- package/src/lib/resolve-config.ts +53 -5
- package/src/lib/schemas.ts +201 -10
- package/src/lib/site-header-fragment.tsx +2 -1
- package/src/lib/site-unavailable-page.ts +47 -0
- package/src/lib/slug.ts +51 -7
- package/src/lib/slugify.ts +113 -0
- package/src/lib/startup-config.ts +4 -6
- package/src/lib/storage.ts +7 -0
- package/src/lib/summary.ts +50 -0
- package/src/lib/thread-fold.ts +161 -0
- package/src/lib/time.ts +32 -0
- package/src/lib/timeline.ts +42 -1
- package/src/lib/tiptap-render.ts +1 -2
- package/src/lib/upload.ts +16 -13
- package/src/lib/url.ts +139 -86
- package/src/lib/version.ts +10 -8
- package/src/lib/view-language.ts +436 -0
- package/src/lib/view.ts +51 -23
- package/src/lib/viewer-context.ts +62 -0
- package/src/middleware/__tests__/auth.test.ts +70 -2
- package/src/middleware/__tests__/cache-control.test.ts +71 -3
- package/src/middleware/__tests__/onboarding.test.ts +76 -5
- package/src/middleware/__tests__/secure-headers.test.ts +1 -1
- package/src/middleware/__tests__/session.test.ts +181 -33
- package/src/middleware/auth.ts +50 -31
- package/src/middleware/cache-control.ts +75 -10
- package/src/middleware/config.ts +6 -6
- package/src/middleware/error-handler.ts +12 -0
- package/src/middleware/onboarding.ts +32 -10
- package/src/middleware/secure-headers.ts +1 -1
- package/src/middleware/session.ts +96 -8
- package/src/node/__tests__/cli-migrate.test.ts +21 -19
- package/src/node/__tests__/cli-reset-password.test.ts +81 -8
- package/src/node/__tests__/cli-runtime-target.test.ts +48 -1
- package/src/node/__tests__/cli-site-selection.test.ts +128 -38
- package/src/node/__tests__/runtime.test.ts +37 -0
- package/src/node/index.ts +1 -0
- package/src/preset.css +76 -22
- package/src/routes/__tests__/feed-validator.test.ts +156 -0
- package/src/routes/api/__tests__/collections.test.ts +114 -0
- package/src/routes/api/__tests__/discover.test.ts +222 -0
- package/src/routes/api/__tests__/nav-items.test.ts +212 -2
- package/src/routes/api/__tests__/post-translations.test.ts +829 -0
- package/src/routes/api/__tests__/settings.test.ts +24 -3
- package/src/routes/api/__tests__/telegram.test.ts +6 -5
- package/src/routes/api/collections.ts +49 -6
- package/src/routes/api/custom-urls.ts +0 -1
- package/src/routes/api/discover.ts +95 -0
- package/src/routes/api/export.ts +3 -0
- package/src/routes/api/internal/__tests__/sites.test.ts +42 -2
- package/src/routes/api/nav-items.ts +55 -8
- package/src/routes/api/posts.ts +159 -0
- package/src/routes/api/public/__tests__/archive.test.ts +111 -0
- package/src/routes/api/public/archive.ts +77 -81
- package/src/routes/api/public/posts.ts +23 -6
- package/src/routes/api/smart-collections.ts +132 -0
- package/src/routes/api/telegram.ts +2 -40
- package/src/routes/api/uploads.ts +1 -2
- package/src/routes/auth/__tests__/setup-auth.test.ts +137 -0
- package/src/routes/auth/__tests__/setup-discover.test.ts +204 -0
- package/src/routes/auth/__tests__/setup-flow.test.ts +270 -0
- package/src/routes/auth/__tests__/setup-page.test.tsx +329 -0
- package/src/routes/auth/__tests__/setup-steps.test.ts +310 -0
- package/src/routes/auth/__tests__/setup.test.ts +278 -26
- package/src/routes/auth/__tests__/signin.test.ts +135 -0
- package/src/routes/auth/setup.tsx +934 -186
- package/src/routes/auth/signin.tsx +10 -8
- package/src/routes/compose.tsx +5 -0
- package/src/routes/dash/__tests__/custom-urls-archive.test.ts +44 -0
- package/src/routes/dash/__tests__/custom-urls-page.test.ts +49 -0
- package/src/routes/dash/__tests__/delete-account.test.ts +106 -0
- package/src/routes/dash/__tests__/settings-discover.test.ts +213 -0
- package/src/routes/dash/custom-urls.tsx +1 -37
- package/src/routes/dash/settings.tsx +502 -41
- package/src/routes/discover-announce.ts +96 -0
- package/src/routes/feed/__tests__/feed.test.ts +165 -2
- package/src/routes/feed/feed.ts +69 -40
- package/src/routes/feed/sitemap.ts +95 -11
- package/src/routes/pages/__tests__/archive-indexing.test.ts +200 -0
- package/src/routes/pages/__tests__/archive-params.test.ts +370 -6
- package/src/routes/pages/__tests__/collection-routing.test.ts +16 -0
- package/src/routes/pages/__tests__/collections-feed-links.test.ts +72 -0
- package/src/routes/pages/__tests__/collections.test.ts +9 -3
- package/src/routes/pages/__tests__/custom-archive-url.test.ts +119 -0
- package/src/routes/pages/__tests__/feed-autodiscovery.test.ts +148 -0
- package/src/routes/pages/__tests__/language-routing.test.ts +679 -0
- package/src/routes/pages/__tests__/post-preview-partial.test.ts +96 -0
- package/src/routes/pages/__tests__/smart-collection.test.ts +298 -0
- package/src/routes/pages/__tests__/subscribe.test.ts +248 -0
- package/src/routes/pages/archive.tsx +471 -369
- package/src/routes/pages/collection.tsx +94 -96
- package/src/routes/pages/collections.tsx +38 -50
- package/src/routes/pages/featured.tsx +32 -13
- package/src/routes/pages/home.tsx +25 -6
- package/src/routes/pages/language.tsx +113 -0
- package/src/routes/pages/latest.tsx +26 -16
- package/src/routes/pages/new.tsx +3 -0
- package/src/routes/pages/page.tsx +178 -17
- package/src/routes/pages/partials.tsx +41 -0
- package/src/routes/pages/search.tsx +18 -3
- package/src/routes/pages/smart-collection.tsx +340 -0
- package/src/routes/pages/subscribe.tsx +182 -0
- package/src/runtime/__tests__/index.test.ts +19 -0
- package/src/runtime/__tests__/node.test.ts +129 -4
- package/src/runtime/cloudflare.ts +21 -2
- package/src/runtime/index.ts +27 -1
- package/src/runtime/node.ts +18 -1
- package/src/runtime/site.ts +95 -24
- package/src/services/__tests__/auth.test.ts +116 -7
- package/src/services/__tests__/collection.test.ts +108 -4
- package/src/services/__tests__/language.test.ts +495 -0
- package/src/services/__tests__/navigation.test.ts +213 -10
- package/src/services/__tests__/path.test.ts +180 -0
- package/src/services/__tests__/post-language.test.ts +830 -0
- package/src/services/__tests__/post.test.ts +46 -9
- package/src/services/__tests__/settings.test.ts +228 -17
- package/src/services/__tests__/site-admin.test.ts +5 -5
- package/src/services/__tests__/smart-collection.test.ts +651 -0
- package/src/services/about-page.ts +22 -1
- package/src/services/auth.ts +51 -49
- package/src/services/bootstrap.ts +188 -38
- package/src/services/collection-directory-position.ts +76 -0
- package/src/services/collection.ts +331 -46
- package/src/services/custom-url.ts +27 -22
- package/src/services/export-theme/assets/client-site.css +1 -1
- package/src/services/export-theme/assets/client-site.js +123 -123
- package/src/services/export-theme/layouts/_default/rss.xml +77 -1
- package/src/services/export-theme/layouts/partials/feed-post-content.xml +105 -2
- package/src/services/export-theme/layouts/partials/media-gallery.html +35 -21
- package/src/services/export-theme/layouts/partials/pagination.html +14 -13
- package/src/services/export-theme/layouts/partials/post-card.html +8 -4
- package/src/services/export-theme/layouts/partials/reply.html +2 -1
- package/src/services/export-theme/layouts/partials/thread-preview.html +40 -30
- package/src/services/export-theme/styles/main.css +13 -1
- package/src/services/export.ts +278 -19
- package/src/services/index.ts +34 -3
- package/src/services/language.ts +412 -0
- package/src/services/mcp.ts +3 -1
- package/src/services/media.ts +14 -6
- package/src/services/navigation.ts +429 -67
- package/src/services/path.ts +221 -6
- package/src/services/post.ts +1021 -152
- package/src/services/search.ts +39 -2
- package/src/services/settings.ts +238 -43
- package/src/services/site-admin.ts +24 -7
- package/src/services/site.ts +77 -1
- package/src/services/smart-collection.ts +636 -0
- package/src/services/telegram.ts +20 -22
- package/src/services/upload-session.ts +1 -3
- package/src/style-author.css +27 -0
- package/src/styles/components-author.css +1622 -0
- package/src/styles/components.css +24 -1571
- package/src/styles/site-media.css +16 -2
- package/src/styles/tokens.css +82 -12
- package/src/styles/ui-author.css +7602 -0
- package/src/styles/ui.css +1087 -7219
- package/src/types/app-context.ts +7 -0
- package/src/types/bindings.ts +3 -0
- package/src/types/config.ts +94 -15
- package/src/types/constants.ts +188 -16
- package/src/types/entities.ts +87 -1
- package/src/types/operations.ts +64 -2
- package/src/types/props.ts +157 -31
- package/src/types/views.ts +55 -1
- package/src/ui/__tests__/font-themes.test.ts +52 -12
- package/src/ui/compose/ComposeDialog.tsx +201 -7
- package/src/ui/compose/ComposePrompt.tsx +1 -5
- package/src/ui/compose/__tests__/ComposeDialog.test.tsx +125 -0
- package/src/ui/dash/appearance/AdvancedContent.tsx +2 -2
- package/src/ui/dash/appearance/CodeInjectionContent.tsx +12 -16
- package/src/ui/dash/appearance/NavigationContent.tsx +117 -16
- package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +33 -11
- package/src/ui/dash/appearance/__tests__/system-nav-descriptions.test.ts +44 -0
- package/src/ui/dash/appearance/system-nav-descriptions.ts +75 -0
- package/src/ui/dash/settings/AccountMenuContent.tsx +2 -2
- package/src/ui/dash/settings/ApiTokensContent.tsx +2 -1
- package/src/ui/dash/settings/ConfigEditorContent.tsx +35 -16
- package/src/ui/dash/settings/GeneralContent.tsx +275 -113
- package/src/ui/dash/settings/LanguageContent.tsx +409 -0
- package/src/ui/dash/settings/SettingsRootContent.tsx +19 -1
- package/src/ui/dash/settings/__tests__/GeneralContent.test.tsx +324 -4
- package/src/ui/dash/settings/__tests__/LanguageContent.test.tsx +144 -0
- package/src/ui/dash/settings/__tests__/discover-copy.test.ts +66 -0
- package/src/ui/dash/settings/discover-copy.ts +116 -0
- package/src/ui/feed/LinkCard.tsx +55 -45
- package/src/ui/feed/NoteCard.tsx +9 -4
- package/src/ui/feed/PostStatusBadges.tsx +90 -43
- package/src/ui/feed/QuoteCard.tsx +55 -30
- package/src/ui/feed/ThreadPreview.tsx +6 -2
- package/src/ui/feed/__tests__/thread-preview.test.ts +13 -16
- package/src/ui/feed/__tests__/timeline-cards.test.ts +314 -49
- package/src/ui/feed/thread-preview-state.ts +20 -6
- package/src/ui/font-themes.ts +39 -29
- package/src/ui/layouts/BaseLayout.tsx +155 -10
- package/src/ui/layouts/SiteLayout.tsx +192 -34
- package/src/ui/layouts/__tests__/BaseLayout.test.tsx +196 -17
- package/src/ui/layouts/__tests__/SiteLayout.test.tsx +109 -85
- package/src/ui/pages/ArchivePage.tsx +282 -289
- package/src/ui/pages/CollectionPage.tsx +86 -76
- package/src/ui/pages/CollectionsPage.tsx +5 -0
- package/src/ui/pages/ComposePage.tsx +8 -0
- package/src/ui/pages/PostPage.tsx +47 -0
- package/src/ui/pages/SearchPage.tsx +3 -3
- package/src/ui/pages/SmartCollectionPage.tsx +434 -0
- package/src/ui/pages/SubscribePage.tsx +236 -0
- package/src/ui/pages/__tests__/ArchivePage.test.tsx +65 -9
- package/src/ui/pages/__tests__/collections-directory-rows.test.tsx +92 -0
- package/src/ui/pages/__tests__/collections-view-links.test.tsx +81 -0
- package/src/ui/shared/CollectionDirectory.tsx +178 -3
- package/src/ui/shared/CollectionsManager.tsx +70 -17
- package/src/ui/shared/CopyField.tsx +127 -0
- package/src/ui/shared/EmptyPostContent.tsx +23 -0
- package/src/ui/shared/MediaGallery.tsx +125 -46
- package/src/ui/shared/Pagination.tsx +20 -5
- package/src/ui/shared/PostFooter.tsx +20 -11
- package/src/ui/shared/__tests__/archive-labels.test.ts +100 -0
- package/src/ui/shared/__tests__/media-gallery.test.ts +210 -2
- package/src/ui/shared/__tests__/navigation-labels.test.ts +16 -22
- package/src/ui/shared/__tests__/pagination.test.ts +38 -24
- package/src/ui/shared/__tests__/post-footer.test.ts +32 -0
- package/src/ui/shared/archive-labels.ts +98 -0
- package/src/ui/shared/collection-management-labels.ts +94 -16
- package/src/ui/shared/navigation-labels.ts +45 -71
- package/src/ui/shared/post-article-attributes.ts +2 -0
- package/src/ui/shared/smart-collection-labels.ts +343 -0
- package/dist/app-C75Zc8dD.js +0 -6
- package/dist/client/_assets/chunks/heic-to-DUUaO23q.js +0 -1
- package/dist/client/_assets/chunks/module-DcsAZQZ_.js +0 -716
- package/dist/client/_assets/chunks/url-CU9pEbk5.js +0 -1
- package/dist/client/_assets/client-Bq0vre8Y.js +0 -296
- package/dist/client/_assets/client-D3VZyG4L.css +0 -2
- package/dist/client/_assets/client-auth-F2rb9WfF.js +0 -5571
- package/dist/env-BPYViDJJ.js +0 -281
- package/dist/github-api-BNF35Lkx.js +0 -176
- package/dist/github-app-DdiD-bC4.js +0 -275
- package/dist/github-sync-Bqg62IvV.js +0 -4
- package/dist/github-sync-HdK2EgvJ.js +0 -431
- package/dist/url-CbLAtlZe.js +0 -900
- package/src/client/__tests__/collection-created-notice.test.ts +0 -36
- package/src/client/__tests__/collection-form-bridge.test.ts +0 -216
- package/src/client/__tests__/post-form-bridge.test.ts +0 -82
- package/src/client/collection-created-notice.ts +0 -66
- package/src/client/collection-form-bridge.ts +0 -191
- package/src/client/components/__tests__/jant-post-form.test.ts +0 -193
- package/src/client/components/jant-post-form.ts +0 -422
- package/src/client/components/post-form-template.ts +0 -352
- package/src/client/components/post-form-types.ts +0 -96
- package/src/client/media-metadata.ts +0 -247
- package/src/client/post-form-bridge.ts +0 -193
- package/src/ui/pages/CollectionEditorPage.tsx +0 -175
package/dist/url-CbLAtlZe.js
DELETED
|
@@ -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 };
|