@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
|
@@ -310,47 +310,51 @@ export class JantTextPreview extends LitElement {
|
|
|
310
310
|
</svg>
|
|
311
311
|
</button>
|
|
312
312
|
<div class="text-preview-toolbar-actions">
|
|
313
|
-
${
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
313
|
+
${
|
|
314
|
+
this.#shareHref
|
|
315
|
+
? html`<button
|
|
316
|
+
type="button"
|
|
317
|
+
class="text-preview-btn"
|
|
318
|
+
@click=${() => this.#copyLink()}
|
|
319
|
+
?disabled=${this._loading}
|
|
320
|
+
title="Copy link"
|
|
321
|
+
aria-label="Copy shareable link"
|
|
322
|
+
>
|
|
323
|
+
${
|
|
324
|
+
this._linkCopied
|
|
325
|
+
? html`<svg
|
|
326
|
+
width="16"
|
|
327
|
+
height="16"
|
|
328
|
+
viewBox="0 0 24 24"
|
|
329
|
+
fill="none"
|
|
330
|
+
stroke="currentColor"
|
|
331
|
+
stroke-width="2"
|
|
332
|
+
stroke-linecap="round"
|
|
333
|
+
stroke-linejoin="round"
|
|
334
|
+
>
|
|
335
|
+
<path d="M20 6 9 17l-5-5" />
|
|
336
|
+
</svg>`
|
|
337
|
+
: html`<svg
|
|
338
|
+
width="16"
|
|
339
|
+
height="16"
|
|
340
|
+
viewBox="0 0 24 24"
|
|
341
|
+
fill="none"
|
|
342
|
+
stroke="currentColor"
|
|
343
|
+
stroke-width="2"
|
|
344
|
+
stroke-linecap="round"
|
|
345
|
+
stroke-linejoin="round"
|
|
346
|
+
>
|
|
347
|
+
<path
|
|
348
|
+
d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"
|
|
349
|
+
/>
|
|
350
|
+
<path
|
|
351
|
+
d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"
|
|
352
|
+
/>
|
|
353
|
+
</svg>`
|
|
354
|
+
}
|
|
355
|
+
</button>`
|
|
356
|
+
: nothing
|
|
357
|
+
}
|
|
354
358
|
<button
|
|
355
359
|
type="button"
|
|
356
360
|
class="text-preview-btn"
|
|
@@ -359,54 +363,58 @@ export class JantTextPreview extends LitElement {
|
|
|
359
363
|
title="Copy text"
|
|
360
364
|
aria-label="Copy text content"
|
|
361
365
|
>
|
|
362
|
-
${
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
366
|
+
${
|
|
367
|
+
this._copied
|
|
368
|
+
? html`<svg
|
|
369
|
+
width="16"
|
|
370
|
+
height="16"
|
|
371
|
+
viewBox="0 0 24 24"
|
|
372
|
+
fill="none"
|
|
373
|
+
stroke="currentColor"
|
|
374
|
+
stroke-width="2"
|
|
375
|
+
stroke-linecap="round"
|
|
376
|
+
stroke-linejoin="round"
|
|
377
|
+
>
|
|
378
|
+
<path d="M20 6 9 17l-5-5" />
|
|
379
|
+
</svg>`
|
|
380
|
+
: html`<svg
|
|
381
|
+
width="16"
|
|
382
|
+
height="16"
|
|
383
|
+
viewBox="0 0 24 24"
|
|
384
|
+
fill="none"
|
|
385
|
+
stroke="currentColor"
|
|
386
|
+
stroke-width="2"
|
|
387
|
+
stroke-linecap="round"
|
|
388
|
+
stroke-linejoin="round"
|
|
389
|
+
>
|
|
390
|
+
<rect width="14" height="14" x="8" y="8" rx="2" />
|
|
391
|
+
<path
|
|
392
|
+
d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"
|
|
393
|
+
/>
|
|
394
|
+
</svg>`
|
|
395
|
+
}
|
|
390
396
|
</button>
|
|
391
397
|
</div>
|
|
392
398
|
</div>
|
|
393
|
-
${
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
399
|
+
${
|
|
400
|
+
this._loading
|
|
401
|
+
? html`<div class="text-preview-loading">
|
|
402
|
+
<svg
|
|
403
|
+
class="animate-spin size-5"
|
|
404
|
+
viewBox="0 0 24 24"
|
|
405
|
+
fill="none"
|
|
406
|
+
stroke="currentColor"
|
|
407
|
+
stroke-width="2"
|
|
408
|
+
stroke-linecap="round"
|
|
409
|
+
stroke-linejoin="round"
|
|
410
|
+
>
|
|
411
|
+
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
|
|
412
|
+
</svg>
|
|
413
|
+
</div>`
|
|
414
|
+
: html`<div class="text-preview-body prose">
|
|
415
|
+
${unsafeHTML(this._html)}
|
|
416
|
+
</div>`
|
|
417
|
+
}
|
|
410
418
|
</div>
|
|
411
419
|
</dialog>
|
|
412
420
|
`;
|
|
@@ -7,12 +7,17 @@ import type { CollectionFormLabels } from "./collection-types.js";
|
|
|
7
7
|
|
|
8
8
|
export interface NavManagerItem {
|
|
9
9
|
id: string;
|
|
10
|
-
type: "link" | "system" | "collection" | "page";
|
|
10
|
+
type: "link" | "system" | "collection" | "smart_collection" | "page";
|
|
11
11
|
systemKey?: SystemNavKey;
|
|
12
12
|
collectionId?: string;
|
|
13
|
+
smartCollectionId?: string;
|
|
13
14
|
postId?: string;
|
|
15
|
+
/** Author's override, or `""` when the item follows its target's title */
|
|
14
16
|
label: string;
|
|
17
|
+
/** Resolved label from the server-rendered list */
|
|
15
18
|
displayLabel?: string;
|
|
19
|
+
/** Target's current title, present on mutation responses */
|
|
20
|
+
targetTitle?: string;
|
|
16
21
|
url: string;
|
|
17
22
|
placement?: "header" | "more";
|
|
18
23
|
}
|
|
@@ -24,6 +29,36 @@ export interface NavManagerPage {
|
|
|
24
29
|
updatedAt: number;
|
|
25
30
|
}
|
|
26
31
|
|
|
32
|
+
/**
|
|
33
|
+
* What the server made of an address the author pasted into the page picker:
|
|
34
|
+
* the item it could become, or why it cannot become one.
|
|
35
|
+
*
|
|
36
|
+
* `address` is what to show back and what to prefill the link form with — the
|
|
37
|
+
* public path for anything on this site, the pasted URL for anything else.
|
|
38
|
+
*/
|
|
39
|
+
export type NavAddressResolution =
|
|
40
|
+
| { kind: "page"; address: string; page: NavManagerPage }
|
|
41
|
+
| {
|
|
42
|
+
kind: "collection";
|
|
43
|
+
address: string;
|
|
44
|
+
collection: { id: string; title: string; slug: string };
|
|
45
|
+
}
|
|
46
|
+
| {
|
|
47
|
+
kind: "smart_collection";
|
|
48
|
+
address: string;
|
|
49
|
+
smartCollection: { id: string; title: string; slug: string };
|
|
50
|
+
}
|
|
51
|
+
| {
|
|
52
|
+
kind:
|
|
53
|
+
| "external"
|
|
54
|
+
| "link_only"
|
|
55
|
+
| "untitled"
|
|
56
|
+
| "unpublished"
|
|
57
|
+
| "private"
|
|
58
|
+
| "not_found";
|
|
59
|
+
address: string;
|
|
60
|
+
};
|
|
61
|
+
|
|
27
62
|
export interface SystemNavConfig {
|
|
28
63
|
key: SystemNavKey;
|
|
29
64
|
label: string;
|
|
@@ -37,6 +72,14 @@ export interface NavManagerCollection {
|
|
|
37
72
|
slug: string;
|
|
38
73
|
/** Group label from directory divider, if this collection belongs to one */
|
|
39
74
|
group?: string | null;
|
|
75
|
+
/**
|
|
76
|
+
* True for a collection whose members come from conditions.
|
|
77
|
+
*
|
|
78
|
+
* Both kinds belong in navigation — a reader following either lands on a
|
|
79
|
+
* page of posts — so the picker lists them together and marks which is
|
|
80
|
+
* which, rather than keeping two lists that would need two empty states.
|
|
81
|
+
*/
|
|
82
|
+
isSmart?: boolean;
|
|
40
83
|
}
|
|
41
84
|
|
|
42
85
|
export interface NavManagerSuggestedLink {
|
|
@@ -84,7 +127,17 @@ export interface NavManagerLabels {
|
|
|
84
127
|
addPageDescription: string;
|
|
85
128
|
addPage: string;
|
|
86
129
|
searchPages: string;
|
|
130
|
+
searchPagesHint: string;
|
|
87
131
|
recentPages: string;
|
|
132
|
+
addressMatch: string;
|
|
133
|
+
addressAlreadyAdded: string;
|
|
134
|
+
addressNotFound: string;
|
|
135
|
+
addressUnpublished: string;
|
|
136
|
+
addressPrivate: string;
|
|
137
|
+
addressUntitled: string;
|
|
138
|
+
addressExternal: string;
|
|
139
|
+
addressLinkOnly: string;
|
|
140
|
+
addressAddAsLink: string;
|
|
88
141
|
searchingPages: string;
|
|
89
142
|
noMatchingPages: string;
|
|
90
143
|
noPages: string;
|
|
@@ -111,6 +164,8 @@ export interface NavManagerLabels {
|
|
|
111
164
|
back: string;
|
|
112
165
|
collection: string;
|
|
113
166
|
addCollection: string;
|
|
167
|
+
/** Tooltip on the icon marking a smart collection in the picker. */
|
|
168
|
+
smartCollectionLabel?: string;
|
|
114
169
|
addCollectionToNavigation: string;
|
|
115
170
|
addCollectionDescription: string;
|
|
116
171
|
allCollectionsAdded: string;
|
|
@@ -126,6 +181,12 @@ export interface NavManagerLabels {
|
|
|
126
181
|
confirmDeleteCollection: string;
|
|
127
182
|
headerSection: string;
|
|
128
183
|
moreSection: string;
|
|
184
|
+
/**
|
|
185
|
+
* Label on the preview's overflow button. Mirrors the real site header, so it
|
|
186
|
+
* comes from the public nav catalog (untranslated) rather than `moreSection`,
|
|
187
|
+
* which is the translated settings section heading.
|
|
188
|
+
*/
|
|
189
|
+
previewMore: string;
|
|
129
190
|
moreEmptyHint: string;
|
|
130
191
|
placementSaved: string;
|
|
131
192
|
cancel: string;
|
|
@@ -23,9 +23,9 @@ export interface SettingsLabels {
|
|
|
23
23
|
aboutPageConflict: string;
|
|
24
24
|
createAboutPage: string;
|
|
25
25
|
editAboutPage: string;
|
|
26
|
-
|
|
26
|
+
timeSection: string;
|
|
27
27
|
home: string;
|
|
28
|
-
|
|
28
|
+
siteVisibility: string;
|
|
29
29
|
siteName: string;
|
|
30
30
|
aboutBlog: string;
|
|
31
31
|
aboutBlogHelp: string;
|
|
@@ -37,6 +37,7 @@ export interface SettingsLabels {
|
|
|
37
37
|
mainRssFeedWarning: string;
|
|
38
38
|
availableFeedUrls: string;
|
|
39
39
|
availableFeedUrlsHelp: string;
|
|
40
|
+
feedsDocs: string;
|
|
40
41
|
mainFeedUrl: string;
|
|
41
42
|
latestFeedUrl: string;
|
|
42
43
|
featuredFeedUrl: string;
|
|
@@ -48,23 +49,20 @@ export interface SettingsLabels {
|
|
|
48
49
|
featuredFeedOptionDescription: string;
|
|
49
50
|
showJantBrandingOnHome: string;
|
|
50
51
|
markdownSupported: string;
|
|
51
|
-
siteLanguage: string;
|
|
52
|
-
siteLanguageHelp: string;
|
|
53
|
-
/** Placeholder shown inside the locale combobox search field. */
|
|
54
|
-
siteLanguageSearchPlaceholder: string;
|
|
55
|
-
/** Empty-state message when the search filters out every option. */
|
|
56
|
-
siteLanguageNoMatches: string;
|
|
57
|
-
/** Lead text before the live `<html lang>` preview. */
|
|
58
|
-
contentLanguagePreview: string;
|
|
59
|
-
dashboardLanguage: string;
|
|
60
|
-
dashboardLanguageHelp: string;
|
|
61
|
-
cjkFont: string;
|
|
62
|
-
cjkFontHelp: string;
|
|
63
52
|
timeZone: string;
|
|
64
53
|
|
|
65
54
|
// Search
|
|
66
55
|
allowIndexing: string;
|
|
67
56
|
demoSeoLocked: string;
|
|
57
|
+
discoverName: string;
|
|
58
|
+
discoverRules: string;
|
|
59
|
+
discoverEnabled: string;
|
|
60
|
+
discoverIntro: string;
|
|
61
|
+
discoverDemoLocked: string;
|
|
62
|
+
discoverFeedsOffLocked: string;
|
|
63
|
+
discoverSearchOff: string;
|
|
64
|
+
discoverAnnounce: string;
|
|
65
|
+
discoverAnnounceManual: string;
|
|
68
66
|
|
|
69
67
|
// Actions
|
|
70
68
|
save: string;
|
|
@@ -80,29 +78,16 @@ export interface SettingsTimezone {
|
|
|
80
78
|
label: string;
|
|
81
79
|
}
|
|
82
80
|
|
|
83
|
-
/** CJK font option for the select dropdown */
|
|
84
|
-
export interface SettingsCjkFont {
|
|
85
|
-
value: string;
|
|
86
|
-
label: string;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/** Dashboard UI language option for the select dropdown */
|
|
90
|
-
export interface SettingsDashboardLanguage {
|
|
91
|
-
value: string;
|
|
92
|
-
label: string;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
81
|
export interface SettingsInitialData {
|
|
96
82
|
siteName: string;
|
|
97
83
|
siteDescription: string;
|
|
98
|
-
siteLanguage: string;
|
|
99
|
-
dashboardLanguage: string;
|
|
100
|
-
cjkSerifFont: string;
|
|
101
84
|
mainRssFeed: string;
|
|
102
85
|
timeZone: string;
|
|
103
86
|
siteFooter: string;
|
|
104
87
|
showJantBrandingOnHome: boolean;
|
|
105
88
|
noindex: boolean;
|
|
89
|
+
/** Stored Discover choice; "" when the owner has never used the control. */
|
|
90
|
+
discover: string;
|
|
106
91
|
}
|
|
107
92
|
|
|
108
93
|
export type SettingsAboutPageStatus =
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Condition controls for the smart collection dialog.
|
|
3
|
+
*
|
|
4
|
+
* Each control renders one dimension and produces a string in that dimension's
|
|
5
|
+
* **URL spelling** — the same one a query string carries. That is the whole
|
|
6
|
+
* trick: the dialog never encodes a value itself, so it cannot invent a second
|
|
7
|
+
* vocabulary beside the registry's, and adding a dimension that reuses an
|
|
8
|
+
* existing control shape adds nothing here at all.
|
|
9
|
+
*
|
|
10
|
+
* There is no "unset" option in any control. A row exists or it does not, and
|
|
11
|
+
* that is already how "unset" is said; a second way to say it would be two
|
|
12
|
+
* switches for one state.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { html, type TemplateResult } from "lit";
|
|
16
|
+
import {
|
|
17
|
+
FILTER_DIMENSION_KEYS,
|
|
18
|
+
FILTER_DIMENSIONS,
|
|
19
|
+
parsePostFilterSelection,
|
|
20
|
+
serializePostFilterSelection,
|
|
21
|
+
type PostFilterSelection,
|
|
22
|
+
} from "../../lib/filter-dimensions.js";
|
|
23
|
+
import { FORMATS, MEDIA_KINDS } from "../../types/constants.js";
|
|
24
|
+
import type {
|
|
25
|
+
SmartCollectionDialogLabels,
|
|
26
|
+
SmartConditionRow,
|
|
27
|
+
} from "./smart-collection-dialog-types.js";
|
|
28
|
+
|
|
29
|
+
export type DimensionKey = (typeof FILTER_DIMENSION_KEYS)[number];
|
|
30
|
+
|
|
31
|
+
type Emit = (value: string) => void;
|
|
32
|
+
|
|
33
|
+
interface DimensionControl {
|
|
34
|
+
/** The value a freshly added row starts on. */
|
|
35
|
+
defaultValue: string;
|
|
36
|
+
render(
|
|
37
|
+
value: string,
|
|
38
|
+
emit: Emit,
|
|
39
|
+
labels: SmartCollectionDialogLabels,
|
|
40
|
+
): TemplateResult;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** A plain `<select>` over a fixed list of URL values. */
|
|
44
|
+
function selectControl(
|
|
45
|
+
dimension: DimensionKey,
|
|
46
|
+
values: readonly string[],
|
|
47
|
+
): DimensionControl["render"] {
|
|
48
|
+
return (value, emit, labels) => html`
|
|
49
|
+
<select
|
|
50
|
+
class="select smart-condition-select"
|
|
51
|
+
.value=${value}
|
|
52
|
+
@change=${(event: Event) =>
|
|
53
|
+
emit((event.target as HTMLSelectElement).value)}
|
|
54
|
+
>
|
|
55
|
+
${values.map(
|
|
56
|
+
(option) => html`
|
|
57
|
+
<option value=${option} ?selected=${option === value}>
|
|
58
|
+
${labels.values[`${dimension}.${option}`] ?? option}
|
|
59
|
+
</option>
|
|
60
|
+
`,
|
|
61
|
+
)}
|
|
62
|
+
</select>
|
|
63
|
+
`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** `any` / `none`, the two states a presence dimension has. */
|
|
67
|
+
function presenceControl(dimension: DimensionKey): DimensionControl["render"] {
|
|
68
|
+
return selectControl(dimension, ["any", "none"]);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The media control, over the one folded vocabulary.
|
|
73
|
+
*
|
|
74
|
+
* `any`, `none`, or a set of kinds — one value, because a presence flag beside
|
|
75
|
+
* a kind list would allow "no media, of kinds image and video", which means
|
|
76
|
+
* nothing. Ticking a kind while `any` or `none` is selected replaces it.
|
|
77
|
+
*/
|
|
78
|
+
const mediaControl: DimensionControl["render"] = (value, emit, labels) => {
|
|
79
|
+
const kinds = value === "any" || value === "none" ? [] : value.split(",");
|
|
80
|
+
|
|
81
|
+
const toggleKind = (kind: string, checked: boolean) => {
|
|
82
|
+
const next = checked
|
|
83
|
+
? [...kinds, kind]
|
|
84
|
+
: kinds.filter((entry) => entry !== kind);
|
|
85
|
+
// An empty set is not a third meaning — it falls back to "has media".
|
|
86
|
+
emit(next.length > 0 ? next.join(",") : "any");
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
return html`
|
|
90
|
+
<div class="smart-condition-media">
|
|
91
|
+
<select
|
|
92
|
+
class="select smart-condition-select"
|
|
93
|
+
.value=${value === "none" ? "none" : "any"}
|
|
94
|
+
@change=${(event: Event) =>
|
|
95
|
+
emit((event.target as HTMLSelectElement).value)}
|
|
96
|
+
>
|
|
97
|
+
<option value="any" ?selected=${value !== "none"}>
|
|
98
|
+
${labels.values["media.any"] ?? "any"}
|
|
99
|
+
</option>
|
|
100
|
+
<option value="none" ?selected=${value === "none"}>
|
|
101
|
+
${labels.values["media.none"] ?? "none"}
|
|
102
|
+
</option>
|
|
103
|
+
</select>
|
|
104
|
+
${
|
|
105
|
+
value === "none"
|
|
106
|
+
? null
|
|
107
|
+
: html`
|
|
108
|
+
<div class="smart-condition-media-kinds">
|
|
109
|
+
${MEDIA_KINDS.map(
|
|
110
|
+
(kind) => html`
|
|
111
|
+
<label class="smart-condition-kind">
|
|
112
|
+
<input
|
|
113
|
+
class="input"
|
|
114
|
+
type="checkbox"
|
|
115
|
+
.checked=${kinds.includes(kind)}
|
|
116
|
+
@change=${(event: Event) =>
|
|
117
|
+
toggleKind(
|
|
118
|
+
kind,
|
|
119
|
+
(event.target as HTMLInputElement).checked,
|
|
120
|
+
)}
|
|
121
|
+
/>
|
|
122
|
+
<span>${labels.values[`media.${kind}`] ?? kind}</span>
|
|
123
|
+
</label>
|
|
124
|
+
`,
|
|
125
|
+
)}
|
|
126
|
+
</div>
|
|
127
|
+
`
|
|
128
|
+
}
|
|
129
|
+
</div>
|
|
130
|
+
`;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/** A year, typed. Bounded below by the first year a Unix timestamp can name. */
|
|
134
|
+
const yearControl: DimensionControl["render"] = (value, emit) => html`
|
|
135
|
+
<input
|
|
136
|
+
class="input smart-condition-year"
|
|
137
|
+
type="number"
|
|
138
|
+
min="1971"
|
|
139
|
+
step="1"
|
|
140
|
+
.value=${value}
|
|
141
|
+
@input=${(event: Event) => emit((event.target as HTMLInputElement).value)}
|
|
142
|
+
/>
|
|
143
|
+
`;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* A collection, chosen from the ones this site has.
|
|
147
|
+
*
|
|
148
|
+
* The options are filled in by the dialog host, which knows the site's
|
|
149
|
+
* collections; the control only spells the chosen slug.
|
|
150
|
+
*/
|
|
151
|
+
const collectionControl: DimensionControl["render"] = (value, emit, labels) => {
|
|
152
|
+
const options = Object.entries(labels.values)
|
|
153
|
+
.filter(([key]) => key.startsWith("collection."))
|
|
154
|
+
.map(([key, label]) => [key.slice("collection.".length), label] as const);
|
|
155
|
+
|
|
156
|
+
return html`
|
|
157
|
+
<select
|
|
158
|
+
class="select smart-condition-select"
|
|
159
|
+
.value=${value}
|
|
160
|
+
@change=${(event: Event) =>
|
|
161
|
+
emit((event.target as HTMLSelectElement).value)}
|
|
162
|
+
>
|
|
163
|
+
${options.map(
|
|
164
|
+
([slug, label]) => html`
|
|
165
|
+
<option value=${slug} ?selected=${slug === value}>${label}</option>
|
|
166
|
+
`,
|
|
167
|
+
)}
|
|
168
|
+
</select>
|
|
169
|
+
`;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/** One control per dimension. Adding a dimension that reuses a shape adds none. */
|
|
173
|
+
export const DIMENSION_CONTROLS: Record<DimensionKey, DimensionControl> = {
|
|
174
|
+
collection: { defaultValue: "", render: collectionControl },
|
|
175
|
+
format: {
|
|
176
|
+
defaultValue: FORMATS[0],
|
|
177
|
+
render: selectControl("format", FORMATS),
|
|
178
|
+
},
|
|
179
|
+
title: { defaultValue: "any", render: presenceControl("title") },
|
|
180
|
+
year: {
|
|
181
|
+
defaultValue: String(new Date().getUTCFullYear()),
|
|
182
|
+
render: yearControl,
|
|
183
|
+
},
|
|
184
|
+
media: { defaultValue: "any", render: mediaControl },
|
|
185
|
+
replies: { defaultValue: "any", render: presenceControl("replies") },
|
|
186
|
+
visibility: {
|
|
187
|
+
defaultValue: "public",
|
|
188
|
+
// `private` is absent on purpose: a smart collection is a published page,
|
|
189
|
+
// so it can never name the one set only its author can see.
|
|
190
|
+
render: selectControl("visibility", ["public", "featured", "hidden"]),
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
/** The empty selection — no conditions, which means every post. */
|
|
195
|
+
export function emptySelection(): PostFilterSelection {
|
|
196
|
+
return {};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Read a stored selection into condition rows.
|
|
201
|
+
*
|
|
202
|
+
* Goes through the registry's serializer so every row holds the same URL
|
|
203
|
+
* spelling the controls produce.
|
|
204
|
+
*
|
|
205
|
+
* @param selection - A stored selection, as the API returns it
|
|
206
|
+
* @returns One row per set dimension, in registry order
|
|
207
|
+
* @example
|
|
208
|
+
* selectionToRows({ format: "quote" }); // [{ key: "format", value: "quote" }]
|
|
209
|
+
*/
|
|
210
|
+
export function selectionToRows(
|
|
211
|
+
selection: Record<string, unknown>,
|
|
212
|
+
): SmartConditionRow[] {
|
|
213
|
+
const params = serializePostFilterSelection(
|
|
214
|
+
selection as PostFilterSelection,
|
|
215
|
+
{ collections: collectionVocabulary() },
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
const rows: SmartConditionRow[] = [];
|
|
219
|
+
for (const key of FILTER_DIMENSION_KEYS) {
|
|
220
|
+
const value = params.get(FILTER_DIMENSIONS[key].url.param);
|
|
221
|
+
if (value !== null) rows.push({ key, value });
|
|
222
|
+
}
|
|
223
|
+
return rows;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Turn condition rows back into a selection the API accepts.
|
|
228
|
+
*
|
|
229
|
+
* Runs the rows through the registry's own parser rather than mapping them by
|
|
230
|
+
* hand — the values are already in the URL spelling, so this is exactly the
|
|
231
|
+
* read the archive performs.
|
|
232
|
+
*
|
|
233
|
+
* @param rows - The dialog's condition rows
|
|
234
|
+
* @returns The selection, with any unreadable row dropped
|
|
235
|
+
* @example
|
|
236
|
+
* rowsToSelection([{ key: "format", value: "quote" }]); // { format: "quote" }
|
|
237
|
+
*/
|
|
238
|
+
export function rowsToSelection(
|
|
239
|
+
rows: readonly SmartConditionRow[],
|
|
240
|
+
): PostFilterSelection {
|
|
241
|
+
const params = new URLSearchParams();
|
|
242
|
+
for (const row of rows) {
|
|
243
|
+
if (!row.value) continue;
|
|
244
|
+
const dimension = FILTER_DIMENSIONS[row.key];
|
|
245
|
+
if (dimension) params.set(dimension.url.param, row.value);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const { selection } = parsePostFilterSelection(
|
|
249
|
+
(key) => params.get(key) ?? undefined,
|
|
250
|
+
{ collections: collectionVocabulary() },
|
|
251
|
+
);
|
|
252
|
+
return selection;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* The site's collections, as slug↔id lookups.
|
|
257
|
+
*
|
|
258
|
+
* Filled in by the dialog host before the dialog opens. Kept module-level
|
|
259
|
+
* because both conversions above need it and neither is called from a place
|
|
260
|
+
* that has it to hand.
|
|
261
|
+
*/
|
|
262
|
+
let vocabulary = {
|
|
263
|
+
idBySlug: new Map<string, string>(),
|
|
264
|
+
slugById: new Map<string, string>(),
|
|
265
|
+
titleById: new Map<string, string>(),
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
export function setCollectionVocabulary(
|
|
269
|
+
collections: readonly { id: string; slug: string; title: string }[],
|
|
270
|
+
): void {
|
|
271
|
+
const idBySlug = new Map<string, string>();
|
|
272
|
+
const slugById = new Map<string, string>();
|
|
273
|
+
const titleById = new Map<string, string>();
|
|
274
|
+
for (const collection of collections) {
|
|
275
|
+
idBySlug.set(collection.slug, collection.id);
|
|
276
|
+
slugById.set(collection.id, collection.slug);
|
|
277
|
+
titleById.set(collection.id, collection.title);
|
|
278
|
+
}
|
|
279
|
+
vocabulary = { idBySlug, slugById, titleById };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** The current lookups. Exported so the upgrade check can resolve a slug. */
|
|
283
|
+
export function collectionVocabulary() {
|
|
284
|
+
return vocabulary;
|
|
285
|
+
}
|