@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/src/services/export.ts
CHANGED
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
} from "../lib/jant-branding.js";
|
|
30
30
|
import { getRootActivityAt } from "../db/thread-activity.js";
|
|
31
31
|
import { tiptapJsonToMarkdown } from "../lib/tiptap-to-markdown.js";
|
|
32
|
-
import { extractBodyText } from "../lib/summary.js";
|
|
32
|
+
import { extractBodyText, extractTimelineSummary } from "../lib/summary.js";
|
|
33
33
|
import { getMediaUrl, getPublicUrlForProvider } from "../lib/image.js";
|
|
34
34
|
import { render as renderMarkdown } from "../lib/markdown.js";
|
|
35
35
|
import { formatRelativeAge, toISOString } from "../lib/time.js";
|
|
@@ -85,6 +85,7 @@ import type { StorageDriver } from "../lib/storage.js";
|
|
|
85
85
|
import { base64ToUint8Array } from "../lib/favicon.js";
|
|
86
86
|
import {
|
|
87
87
|
SYSTEM_NAV_KEYS,
|
|
88
|
+
isFeedNavKey,
|
|
88
89
|
type Collection,
|
|
89
90
|
type Media,
|
|
90
91
|
type NavItem,
|
|
@@ -110,6 +111,10 @@ export interface SiteConfig {
|
|
|
110
111
|
siteUrl: string;
|
|
111
112
|
siteDescription: string;
|
|
112
113
|
siteLanguage: string;
|
|
114
|
+
/** Whether the source site serves per-language browsing views. */
|
|
115
|
+
multilingualEnabled: boolean;
|
|
116
|
+
/** Non-primary content languages, in switcher order. */
|
|
117
|
+
additionalLanguages: readonly string[];
|
|
113
118
|
showJantBrandingOnHome: boolean;
|
|
114
119
|
/** Whether anonymous JSON reads are enabled on the source Jant site. */
|
|
115
120
|
publicApiEnabled: boolean;
|
|
@@ -117,6 +122,8 @@ export interface SiteConfig {
|
|
|
117
122
|
rssFeedsEnabled: boolean;
|
|
118
123
|
/** "latest" or "featured" — drives the default RSS nav link in the exported site. */
|
|
119
124
|
mainRssFeed: string;
|
|
125
|
+
/** "list" or "grid" — the layout the exported archive page opens with. */
|
|
126
|
+
archiveDefaultLayout: string;
|
|
120
127
|
siteFooter: string;
|
|
121
128
|
showHeaderAvatar: boolean;
|
|
122
129
|
siteAvatarUrl: string;
|
|
@@ -137,7 +144,13 @@ export interface SiteConfig {
|
|
|
137
144
|
sitePathPrefix?: string;
|
|
138
145
|
navItems: Pick<
|
|
139
146
|
NavItem,
|
|
140
|
-
|
|
147
|
+
| "type"
|
|
148
|
+
| "systemKey"
|
|
149
|
+
| "label"
|
|
150
|
+
| "targetTitle"
|
|
151
|
+
| "url"
|
|
152
|
+
| "position"
|
|
153
|
+
| "placement"
|
|
141
154
|
>[];
|
|
142
155
|
/** Items per page for Hugo pagination — kept in sync with the main site's PAGE_SIZE. */
|
|
143
156
|
pageSize: number;
|
|
@@ -255,7 +268,10 @@ export function createExportService(
|
|
|
255
268
|
async generateHugoFiles() {
|
|
256
269
|
const collectionDirectoryDataPromise =
|
|
257
270
|
typeof services.collections.listDirectoryData === "function"
|
|
258
|
-
?
|
|
271
|
+
? // The export builds a public static site, so it sees what a
|
|
272
|
+
// signed-out reader sees. Language narrowing is not applied: the
|
|
273
|
+
// export walks every language itself.
|
|
274
|
+
services.collections.listDirectoryData({ isAuthenticated: false })
|
|
259
275
|
: Promise.resolve(null);
|
|
260
276
|
|
|
261
277
|
// 1. Query all data
|
|
@@ -304,14 +320,30 @@ export function createExportService(
|
|
|
304
320
|
allPosts,
|
|
305
321
|
collectionsByRoot,
|
|
306
322
|
);
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
323
|
+
// Smart collections are left out of a static export. Their membership is
|
|
324
|
+
// a query, and the exported site has no database to run it against, so
|
|
325
|
+
// the honest options are "omit" or "freeze today's matches under a name
|
|
326
|
+
// that promises to keep updating". Round-tripping them is out of scope
|
|
327
|
+
// (see the smart collections design notes); omitting is what that
|
|
328
|
+
// decision means here.
|
|
329
|
+
const exportableDirectoryItems: ExportCollectionDirectorySourceItem[] =
|
|
330
|
+
collectionDirectoryData?.items
|
|
331
|
+
? collectionDirectoryData.items
|
|
332
|
+
.filter((item) => item.type !== "smart_collection")
|
|
333
|
+
.map((item) => ({
|
|
334
|
+
type: item.type as "collection" | "divider" | "link",
|
|
335
|
+
label: item.label,
|
|
336
|
+
url: item.url,
|
|
337
|
+
description: item.description,
|
|
338
|
+
collection: item.collection,
|
|
339
|
+
}))
|
|
340
|
+
: allCollections.map((collection) => ({
|
|
312
341
|
type: "collection" as const,
|
|
313
342
|
collection,
|
|
314
|
-
}))
|
|
343
|
+
}));
|
|
344
|
+
const exportedCollectionDirectoryItems =
|
|
345
|
+
buildExportedCollectionDirectoryItems(
|
|
346
|
+
exportableDirectoryItems,
|
|
315
347
|
collectionSlugMap,
|
|
316
348
|
collectionMetrics,
|
|
317
349
|
);
|
|
@@ -397,13 +429,28 @@ export function createExportService(
|
|
|
397
429
|
const usedSlugs = new Set<string>();
|
|
398
430
|
for (const s of slugMap.values()) usedSlugs.add(s);
|
|
399
431
|
for (const s of collectionSlugMap.values()) usedSlugs.add(s);
|
|
400
|
-
|
|
432
|
+
const hasFeaturedSection = !usedSlugs.has("featured");
|
|
433
|
+
if (hasFeaturedSection) {
|
|
401
434
|
exportFiles.push({
|
|
402
435
|
path: "content/featured/_index.md",
|
|
403
436
|
content: await buildFeaturedSection(siteConfig.rssFeedsEnabled),
|
|
404
437
|
});
|
|
405
438
|
}
|
|
406
439
|
|
|
440
|
+
// Feed subscribers are the one audience the export can lose silently:
|
|
441
|
+
// their reader polls a `/feed` address that this site does not serve.
|
|
442
|
+
// With feeds off there is nothing to redirect to, and the live site had
|
|
443
|
+
// no feeds to have been subscribed to either.
|
|
444
|
+
if (siteConfig.rssFeedsEnabled) {
|
|
445
|
+
exportFiles.push({
|
|
446
|
+
path: "static/_redirects",
|
|
447
|
+
content: buildFeedRedirects(
|
|
448
|
+
siteConfig.mainRssFeed,
|
|
449
|
+
hasFeaturedSection,
|
|
450
|
+
),
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
407
454
|
// Single data file consumed by templates via `hugo.Data.jant`. The
|
|
408
455
|
// collection directory lives on the same object as `directory` so
|
|
409
456
|
// everything Jant owns round-trips in one place.
|
|
@@ -546,7 +593,7 @@ export function createExportService(
|
|
|
546
593
|
|
|
547
594
|
exportFiles.push({
|
|
548
595
|
path: "README.md",
|
|
549
|
-
content: buildReadme(siteConfig
|
|
596
|
+
content: buildReadme(siteConfig, hasFeaturedSection),
|
|
550
597
|
});
|
|
551
598
|
exportFiles.push({
|
|
552
599
|
path: ".gitignore",
|
|
@@ -676,6 +723,25 @@ function buildExportedCollectionEntriesForThread(
|
|
|
676
723
|
return resolved;
|
|
677
724
|
}
|
|
678
725
|
|
|
726
|
+
/**
|
|
727
|
+
* Whether the site's timeline cuts this post's text short.
|
|
728
|
+
*
|
|
729
|
+
* Shares the boundary with the served feed rather than approximating it: Hugo
|
|
730
|
+
* has its own summary rule and would cut somewhere else, so the answer is
|
|
731
|
+
* computed here and carried as front matter.
|
|
732
|
+
*
|
|
733
|
+
* @param post - The post being exported
|
|
734
|
+
* @returns true when the timeline shows less than the post's page
|
|
735
|
+
* @example
|
|
736
|
+
* isPostTruncated(longArticle); // true
|
|
737
|
+
*/
|
|
738
|
+
function isPostTruncated(post: Post): boolean {
|
|
739
|
+
return (
|
|
740
|
+
extractTimelineSummary(post.body, !!post.title, { namespace: post.id })
|
|
741
|
+
?.hasMore === true
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
|
|
679
745
|
function collectionEntriesToRefs(
|
|
680
746
|
entries: readonly ExportedCollectionEntry[],
|
|
681
747
|
): HugoCollectionRef[] {
|
|
@@ -886,7 +952,13 @@ async function buildThreadBundle(
|
|
|
886
952
|
format: root.format,
|
|
887
953
|
status: root.status,
|
|
888
954
|
visibility: root.visibility,
|
|
955
|
+
// Language is Thread-uniform, so only the root emits it; the importer
|
|
956
|
+
// re-derives replies from their root. `translation_group` is an opaque
|
|
957
|
+
// shared key — importing the whole site keeps translated posts linked.
|
|
958
|
+
language: root.language ?? undefined,
|
|
959
|
+
translation_group: root.translationGroupId ?? undefined,
|
|
889
960
|
summary_text: getArchiveSummaryText(root) ?? undefined,
|
|
961
|
+
truncated: isPostTruncated(root) || undefined,
|
|
890
962
|
link_url: root.format === "link" && root.url ? root.url : undefined,
|
|
891
963
|
source_name: root.format === "quote" && root.title ? root.title : undefined,
|
|
892
964
|
source_url: root.format === "quote" && root.url ? root.url : undefined,
|
|
@@ -969,6 +1041,7 @@ async function buildThreadBundle(
|
|
|
969
1041
|
status: reply.status,
|
|
970
1042
|
visibility: reply.visibility,
|
|
971
1043
|
summary_text: getArchiveSummaryText(reply) ?? undefined,
|
|
1044
|
+
truncated: isPostTruncated(reply) || undefined,
|
|
972
1045
|
link_url: reply.format === "link" && reply.url ? reply.url : undefined,
|
|
973
1046
|
source_name:
|
|
974
1047
|
reply.format === "quote" && reply.title ? reply.title : undefined,
|
|
@@ -1356,13 +1429,21 @@ function buildExportedCollectionMetrics(
|
|
|
1356
1429
|
// Nav item resolution
|
|
1357
1430
|
// ---------------------------------------------------------------------------
|
|
1358
1431
|
|
|
1432
|
+
/**
|
|
1433
|
+
* Label to bake into the static site.
|
|
1434
|
+
*
|
|
1435
|
+
* Mirrors `getNavItemDisplayLabel`: a stored label is the author's own words
|
|
1436
|
+
* and wins, an empty one means the item follows what it points at. The export
|
|
1437
|
+
* has no i18n runtime, so built-in destinations fall back to their English
|
|
1438
|
+
* default rather than a translated one.
|
|
1439
|
+
*/
|
|
1359
1440
|
function resolveNavItemLabel(item: SiteConfig["navItems"][number]): string {
|
|
1360
1441
|
if (item.label) return item.label;
|
|
1361
1442
|
if (item.systemKey) {
|
|
1362
1443
|
const definition = SYSTEM_NAV_KEYS[item.systemKey as SystemNavKey];
|
|
1363
1444
|
if (definition) return definition.defaultLabel;
|
|
1364
1445
|
}
|
|
1365
|
-
return item.label;
|
|
1446
|
+
return item.targetTitle?.trim() || item.label;
|
|
1366
1447
|
}
|
|
1367
1448
|
|
|
1368
1449
|
/**
|
|
@@ -1389,7 +1470,10 @@ function resolveNavItemUrl(
|
|
|
1389
1470
|
}
|
|
1390
1471
|
if (item.systemKey === "collections") return "/collections/";
|
|
1391
1472
|
if (item.systemKey === "archive") return "/archive/";
|
|
1392
|
-
|
|
1473
|
+
// `subscribe` addresses a Jant route, and the exported site has no page at
|
|
1474
|
+
// it. Rather than exporting a nav entry that 404s, it resolves to the feed
|
|
1475
|
+
// that page would have recommended — the same file `rss` points at.
|
|
1476
|
+
if (item.systemKey === "rss" || item.systemKey === "subscribe") {
|
|
1393
1477
|
return mainRssFeed === "featured" ? "/featured/index.xml" : "/index.xml";
|
|
1394
1478
|
}
|
|
1395
1479
|
return item.url;
|
|
@@ -1468,6 +1552,7 @@ function buildHugoToml(config: SiteConfig): string {
|
|
|
1468
1552
|
"[params]",
|
|
1469
1553
|
` description = "${escapeTomlString(config.siteDescription)}"`,
|
|
1470
1554
|
` main_rss_feed = "${escapeTomlString(config.mainRssFeed)}"`,
|
|
1555
|
+
` archive_default_layout = "${escapeTomlString(config.archiveDefaultLayout)}"`,
|
|
1471
1556
|
` public_api_enabled = ${config.publicApiEnabled}`,
|
|
1472
1557
|
` rss_feeds_enabled = ${config.rssFeedsEnabled}`,
|
|
1473
1558
|
` show_jant_branding_on_home = ${config.showJantBrandingOnHome}`,
|
|
@@ -1510,7 +1595,10 @@ function buildJantDataToml(
|
|
|
1510
1595
|
`site_name = "${escapeTomlString(config.siteName)}"`,
|
|
1511
1596
|
`site_description = "${escapeTomlString(config.siteDescription)}"`,
|
|
1512
1597
|
`site_language = "${escapeTomlString(config.siteLanguage)}"`,
|
|
1598
|
+
`multilingual_enabled = ${config.multilingualEnabled}`,
|
|
1599
|
+
`additional_languages = "${escapeTomlString(config.additionalLanguages.join(","))}"`,
|
|
1513
1600
|
`main_rss_feed = "${escapeTomlString(config.mainRssFeed)}"`,
|
|
1601
|
+
`archive_default_layout = "${escapeTomlString(config.archiveDefaultLayout)}"`,
|
|
1514
1602
|
`public_api_enabled = ${config.publicApiEnabled}`,
|
|
1515
1603
|
`rss_feeds_enabled = ${config.rssFeedsEnabled}`,
|
|
1516
1604
|
`show_jant_branding_on_home = ${config.showJantBrandingOnHome}`,
|
|
@@ -1550,10 +1638,12 @@ function buildJantDataToml(
|
|
|
1550
1638
|
// `settings` is authenticated-only and has no corresponding page in the
|
|
1551
1639
|
// static Hugo site — drop it at export time so it never shows up in nav.
|
|
1552
1640
|
if (item.systemKey === "settings") continue;
|
|
1641
|
+
// Both feed entries resolve to a feed file, so with feeds off both would
|
|
1642
|
+
// export a link to something that was never written.
|
|
1553
1643
|
if (
|
|
1554
1644
|
!config.rssFeedsEnabled &&
|
|
1555
1645
|
item.type === "system" &&
|
|
1556
|
-
item.systemKey
|
|
1646
|
+
isFeedNavKey(item.systemKey)
|
|
1557
1647
|
) {
|
|
1558
1648
|
continue;
|
|
1559
1649
|
}
|
|
@@ -1614,9 +1704,95 @@ function buildJantDataToml(
|
|
|
1614
1704
|
}
|
|
1615
1705
|
|
|
1616
1706
|
// ---------------------------------------------------------------------------
|
|
1617
|
-
// README + .gitignore
|
|
1707
|
+
// README + .gitignore + _redirects
|
|
1618
1708
|
// ---------------------------------------------------------------------------
|
|
1619
1709
|
|
|
1710
|
+
/**
|
|
1711
|
+
* Build the `static/_redirects` file that keeps existing feed subscribers
|
|
1712
|
+
* working after the site moves to this export.
|
|
1713
|
+
*
|
|
1714
|
+
* Jant serves its feeds under `/feed` (`/feed`, `/latest/feed`,
|
|
1715
|
+
* `/featured/feed`, `/archive/feed`, `/{collection}/feed`); Hugo serves the
|
|
1716
|
+
* same documents as `index.xml` inside each section. The two namespaces do
|
|
1717
|
+
* not overlap, so every subscriber's saved address 404s the moment the
|
|
1718
|
+
* exported site goes up.
|
|
1719
|
+
*
|
|
1720
|
+
* Hugo's `aliases:` mechanism cannot cover this the way it covers post URLs:
|
|
1721
|
+
* an alias page is a meta-refresh plus a script, and feed readers fetch and
|
|
1722
|
+
* parse XML without running either. Only an HTTP redirect reaches them, which
|
|
1723
|
+
* is host configuration rather than Hugo output — so the export emits the one
|
|
1724
|
+
* format two common static hosts read verbatim (Cloudflare Pages and Netlify).
|
|
1725
|
+
* Hosts that do not read `_redirects` ignore the file; the README carries the
|
|
1726
|
+
* rule for them to translate.
|
|
1727
|
+
*
|
|
1728
|
+
* @param mainRssFeed - The site's main feed setting, "latest" or "featured".
|
|
1729
|
+
* @param hasFeaturedSection - Whether the export emitted a featured section.
|
|
1730
|
+
* It is skipped when a post or collection already owns the `featured` slug,
|
|
1731
|
+
* and pointing a redirect at a feed that was never built would turn a live
|
|
1732
|
+
* subscription into a 404 rather than leaving it merely stale.
|
|
1733
|
+
* @returns The `_redirects` file body.
|
|
1734
|
+
* @example
|
|
1735
|
+
* buildFeedRedirects("latest", true); // "# ...\n/feed /index.xml 301\n..."
|
|
1736
|
+
*/
|
|
1737
|
+
function buildFeedRedirects(
|
|
1738
|
+
mainRssFeed: string,
|
|
1739
|
+
hasFeaturedSection: boolean,
|
|
1740
|
+
): string {
|
|
1741
|
+
const LATEST = "/index.xml";
|
|
1742
|
+
const FEATURED = "/featured/index.xml";
|
|
1743
|
+
// `/feed` follows the site's main-feed setting, mirroring `renderMainFeed`.
|
|
1744
|
+
// Without a featured section there is nothing to point it at, so it falls
|
|
1745
|
+
// back to the latest feed: a wider feed than the reader subscribed to, but
|
|
1746
|
+
// a live one.
|
|
1747
|
+
const main =
|
|
1748
|
+
mainRssFeed === "featured" && hasFeaturedSection ? FEATURED : LATEST;
|
|
1749
|
+
const featured = hasFeaturedSection ? FEATURED : LATEST;
|
|
1750
|
+
|
|
1751
|
+
const rules: [string, string][] = [
|
|
1752
|
+
["/feed", main],
|
|
1753
|
+
["/latest/feed", LATEST],
|
|
1754
|
+
["/featured/feed", featured],
|
|
1755
|
+
["/archive/feed", "/archive/index.xml"],
|
|
1756
|
+
];
|
|
1757
|
+
const legacy: [string, string][] = [
|
|
1758
|
+
["/feed/latest", LATEST],
|
|
1759
|
+
["/feed/all", LATEST],
|
|
1760
|
+
["/feed/featured", featured],
|
|
1761
|
+
["/feed/atom.xml", main],
|
|
1762
|
+
["/feed/latest/atom.xml", LATEST],
|
|
1763
|
+
["/feed/all/atom.xml", LATEST],
|
|
1764
|
+
["/feed/featured/atom.xml", featured],
|
|
1765
|
+
];
|
|
1766
|
+
|
|
1767
|
+
// The format only needs whitespace between the columns; padding to the
|
|
1768
|
+
// widest source path keeps the file readable for whoever has to translate
|
|
1769
|
+
// it into another host's redirect syntax.
|
|
1770
|
+
const width = Math.max(
|
|
1771
|
+
...[...rules, ...legacy].map(([from]) => from.length),
|
|
1772
|
+
"/:slug/feed".length,
|
|
1773
|
+
);
|
|
1774
|
+
const line = ([from, to]: [string, string]) =>
|
|
1775
|
+
`${from.padEnd(width)} ${to} 301`;
|
|
1776
|
+
|
|
1777
|
+
return `# Feed addresses moved in this export.
|
|
1778
|
+
#
|
|
1779
|
+
# Jant served its feeds under /feed; Hugo serves them as index.xml inside each
|
|
1780
|
+
# section. Cloudflare Pages and Netlify read this file and issue the redirects
|
|
1781
|
+
# below, so readers who subscribed to the old addresses keep receiving posts.
|
|
1782
|
+
# Other hosts ignore this file — see README.md for the same rules to translate
|
|
1783
|
+
# into your host's configuration.
|
|
1784
|
+
|
|
1785
|
+
${rules.map(line).join("\n")}
|
|
1786
|
+
|
|
1787
|
+
# Aliases the live site answered with a 308. They would die at the same moment.
|
|
1788
|
+
${legacy.map(line).join("\n")}
|
|
1789
|
+
|
|
1790
|
+
# Collection feeds, by slug. Collections are the only root-level sections that
|
|
1791
|
+
# emit Atom, so any other slug 404s here exactly as it did on the live site.
|
|
1792
|
+
${line(["/:slug/feed", "/:slug/index.xml"])}
|
|
1793
|
+
`;
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1620
1796
|
function buildGitignore(): string {
|
|
1621
1797
|
return `# Hugo build output
|
|
1622
1798
|
public/
|
|
@@ -1634,7 +1810,90 @@ Thumbs.db
|
|
|
1634
1810
|
`;
|
|
1635
1811
|
}
|
|
1636
1812
|
|
|
1637
|
-
|
|
1813
|
+
/**
|
|
1814
|
+
* Render a two-column Markdown table with padded cells.
|
|
1815
|
+
*
|
|
1816
|
+
* The values differ per site, so building the rows by hand would leave the
|
|
1817
|
+
* committed README ragged for some exports and aligned for others.
|
|
1818
|
+
*
|
|
1819
|
+
* @param headers - The two column headers.
|
|
1820
|
+
* @param rows - Cell pairs; each cell is wrapped in backticks.
|
|
1821
|
+
* @returns The table as Markdown, without a trailing newline.
|
|
1822
|
+
* @example
|
|
1823
|
+
* renderMarkdownTable(["Jant", "This export"], [["/feed", "/index.xml"]]);
|
|
1824
|
+
*/
|
|
1825
|
+
function renderMarkdownTable(
|
|
1826
|
+
headers: [string, string],
|
|
1827
|
+
rows: [string, string][],
|
|
1828
|
+
): string {
|
|
1829
|
+
const cells = rows.map(
|
|
1830
|
+
([from, to]) => [`\`${from}\``, `\`${to}\``] as [string, string],
|
|
1831
|
+
);
|
|
1832
|
+
const widths: [number, number] = [
|
|
1833
|
+
Math.max(headers[0].length, ...cells.map((r) => r[0].length)),
|
|
1834
|
+
Math.max(headers[1].length, ...cells.map((r) => r[1].length)),
|
|
1835
|
+
];
|
|
1836
|
+
const line = (a: string, b: string) =>
|
|
1837
|
+
`| ${a.padEnd(widths[0])} | ${b.padEnd(widths[1])} |`;
|
|
1838
|
+
return [
|
|
1839
|
+
line(headers[0], headers[1]),
|
|
1840
|
+
`| ${"-".repeat(widths[0])} | ${"-".repeat(widths[1])} |`,
|
|
1841
|
+
...cells.map((r) => line(r[0], r[1])),
|
|
1842
|
+
].join("\n");
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
/**
|
|
1846
|
+
* Build the export's README.
|
|
1847
|
+
*
|
|
1848
|
+
* @param config - The exported site's configuration.
|
|
1849
|
+
* @param hasFeaturedSection - Whether a featured section was emitted, which
|
|
1850
|
+
* decides where the feed redirects point.
|
|
1851
|
+
* @returns Markdown for `README.md` at the root of the export.
|
|
1852
|
+
* @example
|
|
1853
|
+
* buildReadme(config, true); // "# My Site — Hugo Export\n..."
|
|
1854
|
+
*/
|
|
1855
|
+
function buildReadme(config: SiteConfig, hasFeaturedSection: boolean): string {
|
|
1856
|
+
const siteName = config.siteName;
|
|
1857
|
+
const featuredFeed = hasFeaturedSection
|
|
1858
|
+
? "/featured/index.xml"
|
|
1859
|
+
: "/index.xml";
|
|
1860
|
+
const mainFeed =
|
|
1861
|
+
config.mainRssFeed === "featured" ? featuredFeed : "/index.xml";
|
|
1862
|
+
// Nothing to say about feeds on a site that publishes none, and no
|
|
1863
|
+
// `_redirects` file was written for it either.
|
|
1864
|
+
const staticListing = config.rssFeedsEnabled
|
|
1865
|
+
? "\n _redirects — Feed redirects (see Feeds above)"
|
|
1866
|
+
: "";
|
|
1867
|
+
// The Notes list below says post URLs survive the move. Left alone, that
|
|
1868
|
+
// reads as a promise about every URL, and feed addresses are the exception.
|
|
1869
|
+
const feedNote = config.rssFeedsEnabled
|
|
1870
|
+
? "\n- Feed addresses are the exception: they move to `index.xml` and stay reachable only through `static/_redirects`. See [Feeds](#feeds)."
|
|
1871
|
+
: "";
|
|
1872
|
+
// Nothing to say about feeds on a site that publishes none, and no
|
|
1873
|
+
// `_redirects` file was written for it either.
|
|
1874
|
+
const feedRows: [string, string][] = [
|
|
1875
|
+
["/feed", mainFeed],
|
|
1876
|
+
["/latest/feed", "/index.xml"],
|
|
1877
|
+
["/featured/feed", featuredFeed],
|
|
1878
|
+
["/archive/feed", "/archive/index.xml"],
|
|
1879
|
+
["/{collection}/feed", "/{collection}/index.xml"],
|
|
1880
|
+
];
|
|
1881
|
+
const feedTable = renderMarkdownTable(["Jant", "This export"], feedRows);
|
|
1882
|
+
const feedsSection = config.rssFeedsEnabled
|
|
1883
|
+
? `## Feeds
|
|
1884
|
+
|
|
1885
|
+
The feed addresses changed. Jant served them under \`/feed\`; Hugo serves them as \`index.xml\` inside each section:
|
|
1886
|
+
|
|
1887
|
+
${feedTable}
|
|
1888
|
+
|
|
1889
|
+
A reader who is already subscribed holds one of the old addresses, and a feed reader that gets a 404 stops delivering posts. \`static/_redirects\` maps every old address to its new one with a 301. Cloudflare Pages and Netlify read that file as published; on any other host, translate its rules into that host's redirect configuration before you point the domain here.
|
|
1890
|
+
|
|
1891
|
+
Hugo's \`aliases:\` cannot cover this. An alias page redirects with a meta refresh and a script, and feed readers fetch XML without running either — only an HTTP redirect reaches them.
|
|
1892
|
+
|
|
1893
|
+
The **Subscribe** entry in the site navigation points at \`${mainFeed}\`. The exported site has no \`/subscribe\` page; that page belongs to the Jant runtime.
|
|
1894
|
+
|
|
1895
|
+
`
|
|
1896
|
+
: "";
|
|
1638
1897
|
return `# ${siteName} — Hugo Export
|
|
1639
1898
|
|
|
1640
1899
|
This is a static site exported from [Jant](https://github.com/jant-me/jant), ready to build with [Hugo](https://gohugo.io/).
|
|
@@ -1679,7 +1938,7 @@ hugo --minify
|
|
|
1679
1938
|
|
|
1680
1939
|
The output goes to the \`public/\` directory. Upload it to any static host (Netlify, Vercel, Cloudflare Pages, GitHub Pages, etc.).
|
|
1681
1940
|
|
|
1682
|
-
## Project structure
|
|
1941
|
+
${feedsSection}## Project structure
|
|
1683
1942
|
|
|
1684
1943
|
\`\`\`
|
|
1685
1944
|
hugo.toml — Site configuration (baseURL, title, theme, params)
|
|
@@ -1695,7 +1954,7 @@ content/
|
|
|
1695
1954
|
data/
|
|
1696
1955
|
jant.toml — Nav items, branding, display preferences, ordered collections directory
|
|
1697
1956
|
themes/jant/ — Bundled Hugo theme (overrideable via layouts/ at the site root)
|
|
1698
|
-
static/ — Copy files here to add them to the published site
|
|
1957
|
+
static/ — Copy files here to add them to the published site${staticListing}
|
|
1699
1958
|
\`\`\`
|
|
1700
1959
|
|
|
1701
1960
|
## Customizing
|
|
@@ -1721,7 +1980,7 @@ Safe to re-run; files already on disk are reused. Anything that fails to downloa
|
|
|
1721
1980
|
## Notes
|
|
1722
1981
|
|
|
1723
1982
|
- Each thread is a Hugo branch bundle. Replies live as nested leaf bundles with \`build.render = "never"\` so they do not produce standalone URLs; they render inside the thread page.
|
|
1724
|
-
- \`/{reply-slug}/\` URLs are preserved via \`aliases:\` on the root post, so old links still land on the right thread anchor
|
|
1983
|
+
- \`/{reply-slug}/\` URLs are preserved via \`aliases:\` on the root post, so old links still land on the right thread anchor.${feedNote}
|
|
1725
1984
|
- Media is emitted under \`static/media/{id}.ext\` and referenced from a flat \`media:\` array on each post. When a storage provider has a configured public URL (R2/S3/local proxy), the exporter links to the provider URL instead of re-bundling the bytes.
|
|
1726
1985
|
- Posts with \`draft: true\` in front matter are only built when you pass \`--buildDrafts\` to \`hugo\` / \`hugo serve\`.
|
|
1727
1986
|
`;
|
package/src/services/index.ts
CHANGED
|
@@ -21,6 +21,10 @@ import {
|
|
|
21
21
|
createCollectionService,
|
|
22
22
|
type CollectionService,
|
|
23
23
|
} from "./collection.js";
|
|
24
|
+
import {
|
|
25
|
+
createSmartCollectionService,
|
|
26
|
+
type SmartCollectionService,
|
|
27
|
+
} from "./smart-collection.js";
|
|
24
28
|
import { createSearchService, type SearchService } from "./search.js";
|
|
25
29
|
import { createNavItemService, type NavItemService } from "./navigation.js";
|
|
26
30
|
import { createAuthService, type AuthService } from "./auth.js";
|
|
@@ -36,6 +40,7 @@ import {
|
|
|
36
40
|
type SiteProfileService,
|
|
37
41
|
} from "./site-profile.js";
|
|
38
42
|
import { createAboutPageService, type AboutPageService } from "./about-page.js";
|
|
43
|
+
import { createLanguageService, type LanguageService } from "./language.js";
|
|
39
44
|
import {
|
|
40
45
|
createUploadSessionService,
|
|
41
46
|
type UploadSessionService,
|
|
@@ -57,6 +62,7 @@ export interface Services {
|
|
|
57
62
|
media: MediaService;
|
|
58
63
|
uploads: UploadSessionService;
|
|
59
64
|
collections: CollectionService;
|
|
65
|
+
smartCollections: SmartCollectionService;
|
|
60
66
|
search: SearchService;
|
|
61
67
|
navItems: NavItemService;
|
|
62
68
|
auth: AuthService;
|
|
@@ -66,6 +72,7 @@ export interface Services {
|
|
|
66
72
|
siteMembers: SiteMemberService;
|
|
67
73
|
siteProfile: SiteProfileService;
|
|
68
74
|
aboutPage: AboutPageService;
|
|
75
|
+
language: LanguageService;
|
|
69
76
|
githubAppInstallations: GitHubAppInstallationsService;
|
|
70
77
|
telegram: TelegramService;
|
|
71
78
|
}
|
|
@@ -82,6 +89,8 @@ export function createServices(
|
|
|
82
89
|
siteResolutionMode?: "single-site" | "host-based";
|
|
83
90
|
enforceHostedMediaQuota?: boolean;
|
|
84
91
|
hostedControlPlane?: HostedControlPlaneClient | null;
|
|
92
|
+
/** Signing key for derived tokens, e.g. the account-deletion CSRF token. */
|
|
93
|
+
authSecret?: string;
|
|
85
94
|
},
|
|
86
95
|
): Services {
|
|
87
96
|
const databaseSchema = config?.schema ?? sqliteSchemaBundle;
|
|
@@ -89,7 +98,7 @@ export function createServices(
|
|
|
89
98
|
const site = createSiteService(db, databaseSchema);
|
|
90
99
|
const settings = createSettingsService(db, siteId, databaseSchema, dialect);
|
|
91
100
|
const paths = createPathService(db, siteId, databaseSchema);
|
|
92
|
-
const navItems = createNavItemService(db, siteId, databaseSchema);
|
|
101
|
+
const navItems = createNavItemService(db, siteId, databaseSchema, paths);
|
|
93
102
|
const posts = createPostService(
|
|
94
103
|
db,
|
|
95
104
|
{
|
|
@@ -100,12 +109,21 @@ export function createServices(
|
|
|
100
109
|
paths,
|
|
101
110
|
databaseSchema,
|
|
102
111
|
);
|
|
112
|
+
const smartCollections = createSmartCollectionService(
|
|
113
|
+
db,
|
|
114
|
+
siteId,
|
|
115
|
+
paths,
|
|
116
|
+
posts,
|
|
117
|
+
databaseSchema,
|
|
118
|
+
config?.slugIdLength ?? 5,
|
|
119
|
+
);
|
|
103
120
|
const collections = createCollectionService(
|
|
104
121
|
db,
|
|
105
122
|
siteId,
|
|
106
123
|
paths,
|
|
107
124
|
databaseSchema,
|
|
108
125
|
dialect,
|
|
126
|
+
smartCollections,
|
|
109
127
|
);
|
|
110
128
|
const media = createMediaService(db, siteId, databaseSchema, dialect, {
|
|
111
129
|
enforceHostedQuota: config?.enforceHostedMediaQuota ?? false,
|
|
@@ -121,6 +139,7 @@ export function createServices(
|
|
|
121
139
|
media,
|
|
122
140
|
uploads: createUploadSessionService(db, siteId, media, databaseSchema),
|
|
123
141
|
collections,
|
|
142
|
+
smartCollections,
|
|
124
143
|
search: createSearchService(rawQuery, siteId, dialect),
|
|
125
144
|
navItems,
|
|
126
145
|
auth: createAuthService(
|
|
@@ -128,13 +147,16 @@ export function createServices(
|
|
|
128
147
|
settings,
|
|
129
148
|
{
|
|
130
149
|
databaseDialect: dialect,
|
|
150
|
+
authSecret: config?.authSecret,
|
|
131
151
|
},
|
|
132
152
|
databaseSchema,
|
|
133
153
|
),
|
|
134
154
|
apiTokens: createApiTokenService(db, siteId, databaseSchema),
|
|
135
|
-
bootstrap: createBootstrapService(db, {
|
|
155
|
+
bootstrap: createBootstrapService(db, siteId, {
|
|
136
156
|
schema: databaseSchema,
|
|
157
|
+
databaseDialect: dialect,
|
|
137
158
|
bootstrapSite: config?.bootstrapSite,
|
|
159
|
+
siteResolutionMode: config?.siteResolutionMode,
|
|
138
160
|
}),
|
|
139
161
|
siteAdmin: createSiteAdminService(db, databaseSchema, dialect, {
|
|
140
162
|
siteResolutionMode: config?.siteResolutionMode,
|
|
@@ -156,7 +178,9 @@ export function createServices(
|
|
|
156
178
|
paths,
|
|
157
179
|
posts,
|
|
158
180
|
collections,
|
|
181
|
+
smartCollections,
|
|
159
182
|
}),
|
|
183
|
+
language: createLanguageService({ settings, posts, paths }),
|
|
160
184
|
githubAppInstallations: createGitHubAppInstallationsService(
|
|
161
185
|
db,
|
|
162
186
|
databaseSchema,
|
|
@@ -170,17 +194,24 @@ export type { SiteService } from "./site.js";
|
|
|
170
194
|
export type { PathService } from "./path.js";
|
|
171
195
|
export type { PostService, PostFilters, PostDeleteDeps } from "./post.js";
|
|
172
196
|
export type { CustomUrlService } from "./custom-url.js";
|
|
197
|
+
export type {
|
|
198
|
+
SmartCollectionService,
|
|
199
|
+
SmartCollectionViewer,
|
|
200
|
+
} from "./smart-collection.js";
|
|
173
201
|
export type { MediaService, MediaFilters } from "./media.js";
|
|
174
202
|
export type { UploadSessionService } from "./upload-session.js";
|
|
175
203
|
export type { CollectionService } from "./collection.js";
|
|
176
204
|
export type { SearchService, SearchResult, SearchOptions } from "./search.js";
|
|
177
205
|
export type { NavItemService } from "./navigation.js";
|
|
178
206
|
export type { AboutPageService, AboutPageStatus } from "./about-page.js";
|
|
207
|
+
export type { LanguageService, LanguageState } from "./language.js";
|
|
179
208
|
export type { AuthService, DeleteAccountDeps } from "./auth.js";
|
|
180
209
|
export type { ApiTokenService } from "./api-token.js";
|
|
181
210
|
export type {
|
|
182
211
|
BootstrapService,
|
|
183
|
-
|
|
212
|
+
CompleteSiteSetupData,
|
|
213
|
+
CompleteSiteSetupDeps,
|
|
214
|
+
ProvisionOwnerAccountData,
|
|
184
215
|
} from "./bootstrap.js";
|
|
185
216
|
export type { SiteMemberService } from "./site-member.js";
|
|
186
217
|
export type { SiteProfileService } from "./site-profile.js";
|