@jant/core 0.6.15 → 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 +67 -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-C-oi_t8W.js → app-K_Aa1MMn.js} +52623 -13193
- 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-CsFx6F_M.js → github-sync-BPAvT999.js} +2343 -110
- 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__/mise-config.test.ts +22 -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 +524 -2
- package/src/client/__tests__/compose-launch.test.ts +62 -1
- package/src/client/__tests__/compose-shortcuts.test.ts +57 -8
- 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 +88 -1
- 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__/compose-format-convert.test.ts +0 -19
- 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 +79 -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 +3283 -389
- package/src/client/components/__tests__/jant-compose-editor.test.ts +581 -86
- package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +34 -89
- 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-format-convert.ts +5 -1
- package/src/client/components/compose-types.ts +121 -3
- 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 +106 -49
- package/src/client/components/jant-compose-dialog.ts +2757 -1194
- package/src/client/components/jant-compose-editor.ts +866 -397
- package/src/client/components/jant-compose-fullscreen.ts +53 -47
- 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 +1142 -268
- 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 +336 -242
- package/src/client/compose-launch.ts +89 -2
- package/src/client/compose-shortcuts.ts +9 -7
- 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/post-refresh.ts +135 -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/__tests__/link-input-rules.test.ts +121 -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-input-rules.ts +4 -4
- package/src/client/tiptap/link-toolbar.ts +22 -9
- package/src/client/toast.ts +143 -38
- 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__/backfill-thread-activity.test.ts +234 -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/__tests__/thread-activity.test.ts +113 -0
- package/src/db/backfills/0005_split_thread_activity_from_quiet_replies.sql +68 -0
- package/src/db/backfills/0006_clear_mirrored_page_nav_labels.sql +31 -0
- package/src/db/migrations/0031_many_ego.sql +3 -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/0031_snapshot.json +2562 -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 +28 -0
- package/src/db/migrations/pg/0029_rare_hairball.sql +3 -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/0029_snapshot.json +3289 -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 +28 -0
- package/src/db/pg/schema.ts +213 -27
- package/src/db/post-visibility.ts +117 -0
- package/src/db/schema.ts +242 -27
- package/src/db/thread-activity.ts +101 -0
- 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 +568 -168
- package/src/i18n/locales/public/en.ts +1 -1
- package/src/i18n/locales/public/zh-Hans.po +562 -162
- package/src/i18n/locales/public/zh-Hans.ts +1 -1
- package/src/i18n/locales/public/zh-Hant.po +562 -162
- 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 +1691 -12
- 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__/markdown.test.ts +16 -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 +88 -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__/translit.test.ts +87 -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 +93 -34
- 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 +1021 -66
- 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 +21 -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/link-preview.ts +25 -0
- package/src/lib/markdown-manager.ts +6 -1
- 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-display.ts +42 -9
- 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 +81 -10
- package/src/lib/tiptap-render.ts +5 -4
- package/src/lib/translit.ts +288 -0
- package/src/lib/upload.ts +16 -13
- package/src/lib/url.ts +188 -24
- package/src/lib/version.ts +10 -8
- package/src/lib/view-language.ts +436 -0
- package/src/lib/view.ts +79 -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__/palette.test.ts +44 -0
- 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 +172 -9
- package/src/routes/api/public/__tests__/archive.test.ts +111 -0
- package/src/routes/api/public/__tests__/posts.test.ts +38 -0
- package/src/routes/api/public/archive.ts +77 -81
- package/src/routes/api/public/posts.ts +30 -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 +11 -2
- 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/__tests__/sitemap.test.ts +68 -1
- 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 +657 -1
- 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__/featured.test.ts +6 -2
- 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__/preview.test.ts +3 -1
- 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 +507 -357
- 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 +183 -19
- 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 +139 -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-timeline.test.ts +142 -11
- package/src/services/__tests__/post.test.ts +323 -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 +330 -47
- 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 +281 -24
- 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 +225 -7
- package/src/services/post.ts +1318 -252
- package/src/services/search.ts +48 -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 -1585
- package/src/styles/site-media.css +20 -6
- package/src/styles/tokens.css +144 -13
- package/src/styles/ui-author.css +7602 -0
- package/src/styles/ui.css +1224 -7276
- 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 +92 -1
- package/src/types/operations.ts +64 -2
- package/src/types/props.ts +168 -30
- package/src/types/views.ts +61 -1
- package/src/ui/__tests__/font-themes.test.ts +52 -12
- package/src/ui/compose/ComposeDialog.tsx +215 -21
- 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/LinkPreview.tsx +2 -7
- package/src/ui/feed/NoteCard.tsx +9 -4
- package/src/ui/feed/PostStatusBadges.tsx +103 -17
- 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 +401 -301
- 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 +122 -3
- 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/DraftPreviewBar.tsx +25 -9
- 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 +55 -23
- 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/custom-icons.ts +5 -0
- package/src/ui/shared/navigation-labels.ts +45 -71
- package/src/ui/shared/post-article-attributes.ts +8 -0
- package/src/ui/shared/smart-collection-labels.ts +343 -0
- package/dist/app-BxCOR3Uc.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-pLre2DM_.js +0 -1
- package/dist/client/_assets/client-B_eGKN5p.css +0 -2
- package/dist/client/_assets/client-DxY5BFe8.js +0 -296
- package/dist/client/_assets/client-auth-CakPESv9.js +0 -5533
- package/dist/env-OHRKGcMj.js +0 -281
- package/dist/github-api-BgSiE71w.js +0 -176
- package/dist/github-app-BbklkFmU.js +0 -275
- package/dist/github-sync-Cq1pzzOI.js +0 -431
- package/dist/github-sync-ppWXN3jb.js +0 -4
- package/dist/url-BMYO-Zlt.js +0 -395
- 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
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart Collection Page
|
|
3
|
+
*
|
|
4
|
+
* The same shape as a collection page — title, description, thread count, feed
|
|
5
|
+
* link, timeline — minus the archive's chip bar. A collection page has no
|
|
6
|
+
* filter bar either, and this one is not a place to filter: the conditions are
|
|
7
|
+
* edited in a dialog.
|
|
8
|
+
*
|
|
9
|
+
* What marks it as smart is the funnel after the title, the same marker the
|
|
10
|
+
* directory puts on a smart row, and the same tooltip. The conditions
|
|
11
|
+
* themselves stand in for a description the owner has not written; once one is
|
|
12
|
+
* written, it takes the slot and the marker carries the signal alone.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { msg } from "@lingui/core/macro";
|
|
16
|
+
import type { FC } from "hono/jsx";
|
|
17
|
+
import { useLingui } from "../../i18n/context.js";
|
|
18
|
+
import type { SmartCollectionPageProps } from "../../types.js";
|
|
19
|
+
import { getCollectionsDirectoryPath } from "../../lib/collection-paths.js";
|
|
20
|
+
import { render as renderMarkdown } from "../../lib/markdown.js";
|
|
21
|
+
import { formatPageLabel } from "../../lib/pagination.js";
|
|
22
|
+
import { toPublicPath } from "../../lib/url.js";
|
|
23
|
+
import { TimelineFeed } from "../feed/TimelineFeed.js";
|
|
24
|
+
import { getCollectionMutationLabels } from "../shared/collection-management-labels.js";
|
|
25
|
+
import {
|
|
26
|
+
getSmartCollectionDialogLabels,
|
|
27
|
+
getSmartCollectionLabels,
|
|
28
|
+
} from "../shared/smart-collection-labels.js";
|
|
29
|
+
import { getIconSvg } from "../../lib/icons.js";
|
|
30
|
+
import { Icon } from "../shared/Icon.js";
|
|
31
|
+
import { NAVIGATION_SETTINGS_PATH } from "../../lib/settings-paths.js";
|
|
32
|
+
|
|
33
|
+
const escapeJson = (data: unknown) =>
|
|
34
|
+
JSON.stringify(data).replace(/</g, "\\u003c");
|
|
35
|
+
|
|
36
|
+
export const SmartCollectionPage: FC<SmartCollectionPageProps> = ({
|
|
37
|
+
smartCollection,
|
|
38
|
+
items,
|
|
39
|
+
totalThreadCount,
|
|
40
|
+
currentPage,
|
|
41
|
+
totalPages,
|
|
42
|
+
pagePath,
|
|
43
|
+
baseUrl,
|
|
44
|
+
currentSort,
|
|
45
|
+
defaultSort,
|
|
46
|
+
showRatingSort,
|
|
47
|
+
conditionSummary,
|
|
48
|
+
conditionHref,
|
|
49
|
+
isAuthenticated,
|
|
50
|
+
isInNavigation = false,
|
|
51
|
+
sitePathPrefix = "",
|
|
52
|
+
basePath = sitePathPrefix,
|
|
53
|
+
feedHref,
|
|
54
|
+
}) => {
|
|
55
|
+
const { i18n } = useLingui();
|
|
56
|
+
const labels = getSmartCollectionLabels(i18n);
|
|
57
|
+
const mutationLabels = getCollectionMutationLabels(i18n);
|
|
58
|
+
const pageUrl = toPublicPath(pagePath, basePath);
|
|
59
|
+
const navigationSettingsUrl = toPublicPath(
|
|
60
|
+
NAVIGATION_SETTINGS_PATH,
|
|
61
|
+
sitePathPrefix,
|
|
62
|
+
);
|
|
63
|
+
const sortTriggerId = `smart-collection-sort-trigger-${smartCollection.id}`;
|
|
64
|
+
const sortPopoverId = `smart-collection-sort-popover-${smartCollection.id}`;
|
|
65
|
+
const pageLabel =
|
|
66
|
+
currentPage > 1 ? formatPageLabel(currentPage, totalPages) : null;
|
|
67
|
+
|
|
68
|
+
const sortOptions = [
|
|
69
|
+
{
|
|
70
|
+
value: "newest",
|
|
71
|
+
label: i18n._(
|
|
72
|
+
msg({
|
|
73
|
+
message: "Newest first",
|
|
74
|
+
comment: "@context: Collection sort order option",
|
|
75
|
+
}),
|
|
76
|
+
),
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
value: "oldest",
|
|
80
|
+
label: i18n._(
|
|
81
|
+
msg({
|
|
82
|
+
message: "Oldest first",
|
|
83
|
+
comment: "@context: Collection sort order option",
|
|
84
|
+
}),
|
|
85
|
+
),
|
|
86
|
+
},
|
|
87
|
+
...(showRatingSort
|
|
88
|
+
? [
|
|
89
|
+
{
|
|
90
|
+
value: "rating_desc",
|
|
91
|
+
label: i18n._(
|
|
92
|
+
msg({
|
|
93
|
+
message: "Highest rated",
|
|
94
|
+
comment: "@context: Collection sort order option",
|
|
95
|
+
}),
|
|
96
|
+
),
|
|
97
|
+
},
|
|
98
|
+
]
|
|
99
|
+
: []),
|
|
100
|
+
] as const;
|
|
101
|
+
const currentSortLabel =
|
|
102
|
+
sortOptions.find((option) => option.value === currentSort)?.label ??
|
|
103
|
+
sortOptions[0].label;
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<div
|
|
107
|
+
class="py-6"
|
|
108
|
+
// The same page kind a theme already styles; the second attribute is the
|
|
109
|
+
// hook for anything that needs to tell the two apart.
|
|
110
|
+
data-page="collection"
|
|
111
|
+
data-smart-collection={smartCollection.slug}
|
|
112
|
+
data-collection-mode="smart"
|
|
113
|
+
>
|
|
114
|
+
<header class="collection-page-header">
|
|
115
|
+
<div class="collection-page-topbar">
|
|
116
|
+
{/* Only the return trip is left. A breadcrumb's last crumb named this
|
|
117
|
+
page, which the title one line below already does, word for
|
|
118
|
+
word — so the trail was two thirds repetition. */}
|
|
119
|
+
<a
|
|
120
|
+
class="collection-page-back"
|
|
121
|
+
href={toPublicPath(getCollectionsDirectoryPath(), basePath)}
|
|
122
|
+
>
|
|
123
|
+
<Icon name="arrow-left" class="collection-page-back-icon" />
|
|
124
|
+
{i18n._(
|
|
125
|
+
msg({
|
|
126
|
+
message: "Collections",
|
|
127
|
+
comment:
|
|
128
|
+
"@context: Link back to the collections directory from a collection page",
|
|
129
|
+
}),
|
|
130
|
+
)}
|
|
131
|
+
</a>
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
<div class="collection-page-title-block">
|
|
135
|
+
<h1 class="collection-page-title">
|
|
136
|
+
<span>
|
|
137
|
+
{smartCollection.title}
|
|
138
|
+
{/* The marker every reader gets, in the same slot and the same
|
|
139
|
+
words the directory uses: which kind of collection this is
|
|
140
|
+
changes how an absence reads. Unlike the directory's — which
|
|
141
|
+
sits inside the row's link and so cannot be one itself — this
|
|
142
|
+
one carries the way to the archive, so a description written
|
|
143
|
+
over the condition line does not take it away. */}
|
|
144
|
+
<a
|
|
145
|
+
href={toPublicPath(conditionHref, basePath)}
|
|
146
|
+
class="collection-page-smart-icon"
|
|
147
|
+
title={conditionSummary}
|
|
148
|
+
aria-label={conditionSummary}
|
|
149
|
+
dangerouslySetInnerHTML={{
|
|
150
|
+
__html: getIconSvg("funnel", "icon-fine") ?? "",
|
|
151
|
+
}}
|
|
152
|
+
/>
|
|
153
|
+
</span>
|
|
154
|
+
</h1>
|
|
155
|
+
{smartCollection.description ? (
|
|
156
|
+
<div
|
|
157
|
+
class="collection-page-description prose"
|
|
158
|
+
dangerouslySetInnerHTML={{
|
|
159
|
+
__html: renderMarkdown(smartCollection.description, {
|
|
160
|
+
namespace: smartCollection.id,
|
|
161
|
+
}),
|
|
162
|
+
}}
|
|
163
|
+
/>
|
|
164
|
+
) : (
|
|
165
|
+
/* Nothing written, so the conditions describe the page — and the
|
|
166
|
+
link is there for a reader who wants to narrow further, since
|
|
167
|
+
the archive already reads this vocabulary. */
|
|
168
|
+
<p class="collection-page-description smart-collection-conditions">
|
|
169
|
+
<a href={toPublicPath(conditionHref, basePath)}>
|
|
170
|
+
{conditionSummary}
|
|
171
|
+
</a>
|
|
172
|
+
</p>
|
|
173
|
+
)}
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
<div class="collection-page-subhead">
|
|
177
|
+
<div class="collection-page-meta-row">
|
|
178
|
+
<p class="collection-page-meta">
|
|
179
|
+
{totalThreadCount}{" "}
|
|
180
|
+
{totalThreadCount === 1
|
|
181
|
+
? i18n._(
|
|
182
|
+
msg({
|
|
183
|
+
message: "thread",
|
|
184
|
+
comment:
|
|
185
|
+
"@context: Singular thread count label on collection detail page",
|
|
186
|
+
}),
|
|
187
|
+
)
|
|
188
|
+
: i18n._(
|
|
189
|
+
msg({
|
|
190
|
+
message: "threads",
|
|
191
|
+
comment:
|
|
192
|
+
"@context: Plural thread count label on collection detail page",
|
|
193
|
+
}),
|
|
194
|
+
)}
|
|
195
|
+
{pageLabel ? <span> / {pageLabel}</span> : null}
|
|
196
|
+
{feedHref ? (
|
|
197
|
+
<>
|
|
198
|
+
{" "}
|
|
199
|
+
<a
|
|
200
|
+
href={toPublicPath(feedHref, basePath)}
|
|
201
|
+
class="feed-link"
|
|
202
|
+
title={i18n._(
|
|
203
|
+
msg({
|
|
204
|
+
message: "RSS feed",
|
|
205
|
+
comment:
|
|
206
|
+
"@context: Tooltip for the RSS feed link on a collection page",
|
|
207
|
+
}),
|
|
208
|
+
)}
|
|
209
|
+
rel="noopener noreferrer"
|
|
210
|
+
>
|
|
211
|
+
<span
|
|
212
|
+
dangerouslySetInnerHTML={{
|
|
213
|
+
__html: getIconSvg("rss") ?? "",
|
|
214
|
+
}}
|
|
215
|
+
/>
|
|
216
|
+
</a>
|
|
217
|
+
</>
|
|
218
|
+
) : null}
|
|
219
|
+
</p>
|
|
220
|
+
<span class="collection-page-meta-divider" aria-hidden="true">
|
|
221
|
+
·
|
|
222
|
+
</span>
|
|
223
|
+
<div class="dropdown-menu collection-sort-menu">
|
|
224
|
+
<button
|
|
225
|
+
type="button"
|
|
226
|
+
id={sortTriggerId}
|
|
227
|
+
class="collection-sort-trigger"
|
|
228
|
+
aria-haspopup="menu"
|
|
229
|
+
aria-controls={sortPopoverId}
|
|
230
|
+
aria-expanded="false"
|
|
231
|
+
>
|
|
232
|
+
<span class="sr-only">
|
|
233
|
+
{i18n._(
|
|
234
|
+
msg({
|
|
235
|
+
message: "Sort",
|
|
236
|
+
comment:
|
|
237
|
+
"@context: Sort menu label on collection detail page",
|
|
238
|
+
}),
|
|
239
|
+
)}
|
|
240
|
+
:{" "}
|
|
241
|
+
</span>
|
|
242
|
+
<span>{currentSortLabel}</span>
|
|
243
|
+
<Icon name="chevron-down" size={14} />
|
|
244
|
+
</button>
|
|
245
|
+
<div
|
|
246
|
+
id={sortPopoverId}
|
|
247
|
+
class="collection-sort-popover"
|
|
248
|
+
data-popover
|
|
249
|
+
data-align="end"
|
|
250
|
+
aria-hidden="true"
|
|
251
|
+
>
|
|
252
|
+
<div
|
|
253
|
+
class="collection-sort-options"
|
|
254
|
+
role="menu"
|
|
255
|
+
aria-labelledby={sortTriggerId}
|
|
256
|
+
data-collection-sort-options
|
|
257
|
+
>
|
|
258
|
+
{sortOptions.map((option) => (
|
|
259
|
+
<a
|
|
260
|
+
key={option.value}
|
|
261
|
+
href={
|
|
262
|
+
option.value === defaultSort
|
|
263
|
+
? pageUrl
|
|
264
|
+
: `${pageUrl}?sort=${option.value}`
|
|
265
|
+
}
|
|
266
|
+
role="menuitem"
|
|
267
|
+
class={`collection-sort-option ${
|
|
268
|
+
option.value === currentSort
|
|
269
|
+
? "collection-sort-option-active"
|
|
270
|
+
: ""
|
|
271
|
+
}`}
|
|
272
|
+
aria-current={
|
|
273
|
+
option.value === currentSort ? "true" : undefined
|
|
274
|
+
}
|
|
275
|
+
>
|
|
276
|
+
<span class="collection-sort-option-leading">
|
|
277
|
+
<span>{option.label}</span>
|
|
278
|
+
</span>
|
|
279
|
+
</a>
|
|
280
|
+
))}
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
283
|
+
</div>
|
|
284
|
+
</div>
|
|
285
|
+
|
|
286
|
+
{isAuthenticated ? (
|
|
287
|
+
<div class="collection-page-owner-tools">
|
|
288
|
+
{/* The dialog is a Lit component and cannot reach the i18n
|
|
289
|
+
catalogs, so the page that can open it carries its strings. */}
|
|
290
|
+
<div
|
|
291
|
+
hidden
|
|
292
|
+
data-smart-collection-dialog-labels={escapeJson(
|
|
293
|
+
getSmartCollectionDialogLabels(i18n),
|
|
294
|
+
)}
|
|
295
|
+
/>
|
|
296
|
+
<div
|
|
297
|
+
class="collection-page-manage"
|
|
298
|
+
data-smart-collection-page-actions
|
|
299
|
+
data-smart-collection-id={smartCollection.id}
|
|
300
|
+
data-collection-page-labels={escapeJson({
|
|
301
|
+
...mutationLabels,
|
|
302
|
+
confirmDelete: labels.confirmDelete,
|
|
303
|
+
deleteCollection: labels.deleteSmartCollection,
|
|
304
|
+
deleted: i18n._(
|
|
305
|
+
msg({
|
|
306
|
+
message: "Smart collection deleted.",
|
|
307
|
+
comment:
|
|
308
|
+
"@context: Confirmation after deleting a smart collection",
|
|
309
|
+
}),
|
|
310
|
+
),
|
|
311
|
+
})}
|
|
312
|
+
data-collection-in-navigation={String(isInNavigation)}
|
|
313
|
+
data-collection-page-redirect-url={toPublicPath(
|
|
314
|
+
getCollectionsDirectoryPath(),
|
|
315
|
+
basePath,
|
|
316
|
+
)}
|
|
317
|
+
>
|
|
318
|
+
<button
|
|
319
|
+
type="button"
|
|
320
|
+
class="collection-page-manage-trigger"
|
|
321
|
+
aria-label={mutationLabels.moreActions}
|
|
322
|
+
aria-expanded="false"
|
|
323
|
+
aria-haspopup="menu"
|
|
324
|
+
data-collection-page-action="toggle-menu"
|
|
325
|
+
>
|
|
326
|
+
<svg
|
|
327
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
328
|
+
width="16"
|
|
329
|
+
height="16"
|
|
330
|
+
viewBox="0 0 24 24"
|
|
331
|
+
fill="currentColor"
|
|
332
|
+
>
|
|
333
|
+
<circle cx="5" cy="12" r="2" />
|
|
334
|
+
<circle cx="12" cy="12" r="2" />
|
|
335
|
+
<circle cx="19" cy="12" r="2" />
|
|
336
|
+
</svg>
|
|
337
|
+
</button>
|
|
338
|
+
|
|
339
|
+
<div
|
|
340
|
+
class="collections-page-menu"
|
|
341
|
+
role="menu"
|
|
342
|
+
data-collection-page-menu
|
|
343
|
+
hidden
|
|
344
|
+
>
|
|
345
|
+
{/* Opens the dialog rather than navigating: there is no
|
|
346
|
+
editor page, so creating and editing are one surface. */}
|
|
347
|
+
<button
|
|
348
|
+
type="button"
|
|
349
|
+
class="collections-page-menu-item"
|
|
350
|
+
role="menuitem"
|
|
351
|
+
data-collection-page-action="edit"
|
|
352
|
+
>
|
|
353
|
+
<span
|
|
354
|
+
class="collections-page-menu-item-icon"
|
|
355
|
+
aria-hidden="true"
|
|
356
|
+
>
|
|
357
|
+
<Icon name="pencil" />
|
|
358
|
+
</span>
|
|
359
|
+
<span class="collections-page-menu-item-label">
|
|
360
|
+
{mutationLabels.edit}
|
|
361
|
+
</span>
|
|
362
|
+
</button>
|
|
363
|
+
<button
|
|
364
|
+
type="button"
|
|
365
|
+
class="collections-page-menu-item"
|
|
366
|
+
role="menuitem"
|
|
367
|
+
data-collection-page-action="add-to-navigation"
|
|
368
|
+
hidden={isInNavigation}
|
|
369
|
+
>
|
|
370
|
+
<span
|
|
371
|
+
class="collections-page-menu-item-icon"
|
|
372
|
+
aria-hidden="true"
|
|
373
|
+
>
|
|
374
|
+
<Icon name="plus" />
|
|
375
|
+
</span>
|
|
376
|
+
<span class="collections-page-menu-item-label">
|
|
377
|
+
{mutationLabels.addToNavigation}
|
|
378
|
+
</span>
|
|
379
|
+
</button>
|
|
380
|
+
<a
|
|
381
|
+
href={navigationSettingsUrl}
|
|
382
|
+
class="collections-page-menu-item"
|
|
383
|
+
role="menuitem"
|
|
384
|
+
data-collection-page-edit-navigation
|
|
385
|
+
hidden={!isInNavigation}
|
|
386
|
+
>
|
|
387
|
+
<span
|
|
388
|
+
class="collections-page-menu-item-icon"
|
|
389
|
+
aria-hidden="true"
|
|
390
|
+
>
|
|
391
|
+
<Icon name="list" />
|
|
392
|
+
</span>
|
|
393
|
+
<span class="collections-page-menu-item-label">
|
|
394
|
+
{mutationLabels.editNavigation}
|
|
395
|
+
</span>
|
|
396
|
+
</a>
|
|
397
|
+
<button
|
|
398
|
+
type="button"
|
|
399
|
+
class="collections-page-menu-item collections-page-menu-item-danger"
|
|
400
|
+
role="menuitem"
|
|
401
|
+
data-collection-page-action="delete"
|
|
402
|
+
>
|
|
403
|
+
<span
|
|
404
|
+
class="collections-page-menu-item-icon"
|
|
405
|
+
aria-hidden="true"
|
|
406
|
+
>
|
|
407
|
+
<Icon name="trash-2" />
|
|
408
|
+
</span>
|
|
409
|
+
<span class="collections-page-menu-item-label">
|
|
410
|
+
{labels.deleteSmartCollection}
|
|
411
|
+
</span>
|
|
412
|
+
</button>
|
|
413
|
+
</div>
|
|
414
|
+
</div>
|
|
415
|
+
</div>
|
|
416
|
+
) : null}
|
|
417
|
+
</div>
|
|
418
|
+
</header>
|
|
419
|
+
|
|
420
|
+
<main>
|
|
421
|
+
{items.length === 0 ? (
|
|
422
|
+
<p class="text-muted-foreground">{labels.emptyMatches}</p>
|
|
423
|
+
) : (
|
|
424
|
+
<TimelineFeed
|
|
425
|
+
items={items}
|
|
426
|
+
baseUrl={baseUrl}
|
|
427
|
+
currentPage={currentPage}
|
|
428
|
+
totalPages={totalPages}
|
|
429
|
+
/>
|
|
430
|
+
)}
|
|
431
|
+
</main>
|
|
432
|
+
</div>
|
|
433
|
+
);
|
|
434
|
+
};
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import type { FC } from "hono/jsx";
|
|
2
|
+
import { msg } from "@lingui/core/macro";
|
|
3
|
+
import { useLingui } from "../../i18n/context.js";
|
|
4
|
+
import { getIconSvg } from "../../lib/icons.js";
|
|
5
|
+
import { CopyField } from "../shared/CopyField.js";
|
|
6
|
+
import { PaginatedPageHeader } from "../shared/PaginatedPageHeader.js";
|
|
7
|
+
|
|
8
|
+
/** One feed offered on this page. */
|
|
9
|
+
export interface SubscribeFeed {
|
|
10
|
+
/** What to call it, as the reader would ask for it. */
|
|
11
|
+
label: string;
|
|
12
|
+
/** Absolute address, for pasting into a reader. */
|
|
13
|
+
url: string;
|
|
14
|
+
/** One line on what this feed carries that the others do not. */
|
|
15
|
+
description: string;
|
|
16
|
+
/**
|
|
17
|
+
* The page whose posts this feed carries, linked from the label.
|
|
18
|
+
*
|
|
19
|
+
* Feeds live at `{page}/feed`, so each one has a page a reader can open to
|
|
20
|
+
* see exactly what they would be subscribing to. That page is the only
|
|
21
|
+
* honest preview — describing a feed in a sentence can only approximate it.
|
|
22
|
+
*/
|
|
23
|
+
pageUrl: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface SubscribePageProps {
|
|
27
|
+
/**
|
|
28
|
+
* The feed to lead with — whichever `/feed` currently returns. Presented on
|
|
29
|
+
* its own because the page's job is to answer "which one do I use", and a
|
|
30
|
+
* menu of equals hands that question back to the reader.
|
|
31
|
+
*/
|
|
32
|
+
mainFeed: SubscribeFeed;
|
|
33
|
+
/**
|
|
34
|
+
* The other two, at lower visual weight: the opposite end of the same list
|
|
35
|
+
* from the main feed, and the complete record.
|
|
36
|
+
*/
|
|
37
|
+
otherFeeds: readonly SubscribeFeed[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The page's measure.
|
|
42
|
+
*
|
|
43
|
+
* The addresses are copy fields — a read-only input with the button laid over
|
|
44
|
+
* its right edge — and the site's content column runs to 1088px. Left at full
|
|
45
|
+
* width the button sits some 700px from the address it copies, and three of
|
|
46
|
+
* them stacked read as a settings form. 576px holds a long site URL with a
|
|
47
|
+
* language prefix whole while keeping the button next to its address.
|
|
48
|
+
*/
|
|
49
|
+
const COLUMN_CLASS = "max-w-xl";
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* How to follow this site.
|
|
53
|
+
*
|
|
54
|
+
* Every address here is rendered by the server. This page is its addresses —
|
|
55
|
+
* there is nothing else on it — so none of them may wait for JavaScript.
|
|
56
|
+
*
|
|
57
|
+
* @param props - The main feed and the two shown beneath it
|
|
58
|
+
* @returns The subscribe page content
|
|
59
|
+
*/
|
|
60
|
+
export const SubscribePage: FC<SubscribePageProps> = ({
|
|
61
|
+
mainFeed,
|
|
62
|
+
otherFeeds,
|
|
63
|
+
}) => {
|
|
64
|
+
const { i18n } = useLingui();
|
|
65
|
+
|
|
66
|
+
const copyLabel = i18n._(
|
|
67
|
+
msg({
|
|
68
|
+
message: "Copy",
|
|
69
|
+
comment: "@context: Button that copies a feed address to the clipboard",
|
|
70
|
+
}),
|
|
71
|
+
);
|
|
72
|
+
const copiedMessage = i18n._(
|
|
73
|
+
msg({
|
|
74
|
+
message: "Feed URL copied.",
|
|
75
|
+
comment: "@context: Toast after copying a feed URL to the clipboard",
|
|
76
|
+
}),
|
|
77
|
+
);
|
|
78
|
+
// One string for all three rows: it explains the affordance, and the row's
|
|
79
|
+
// own description already names which page it opens.
|
|
80
|
+
const pageLinkTitle = i18n._(
|
|
81
|
+
msg({
|
|
82
|
+
message: "Open the page this feed carries",
|
|
83
|
+
comment:
|
|
84
|
+
"@context: Tooltip on the subscribe page feed names, which link to the page whose posts that feed carries",
|
|
85
|
+
}),
|
|
86
|
+
);
|
|
87
|
+
const failedMessage = i18n._(
|
|
88
|
+
msg({
|
|
89
|
+
message: "Could not copy. Select the address and copy it.",
|
|
90
|
+
comment:
|
|
91
|
+
"@context: Toast when the clipboard is unavailable, pointing at selecting the address by hand",
|
|
92
|
+
}),
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<div data-page="subscribe" class={COLUMN_CLASS}>
|
|
97
|
+
<PaginatedPageHeader
|
|
98
|
+
title={i18n._(
|
|
99
|
+
msg({
|
|
100
|
+
message: "Subscribe",
|
|
101
|
+
comment: "@context: Page title for the feed subscription page",
|
|
102
|
+
}),
|
|
103
|
+
)}
|
|
104
|
+
// The page is about feeds and carried no feed mark at all. It also
|
|
105
|
+
// ends by telling the reader to look for this glyph elsewhere, which
|
|
106
|
+
// only works once they have seen it here.
|
|
107
|
+
iconHtml={
|
|
108
|
+
getIconSvg("rss", "size-6 text-muted-foreground") ?? undefined
|
|
109
|
+
}
|
|
110
|
+
description={i18n._(
|
|
111
|
+
msg({
|
|
112
|
+
message:
|
|
113
|
+
"This site publishes Atom feeds. Copy one of these addresses into a feed reader.",
|
|
114
|
+
comment:
|
|
115
|
+
"@context: Subscribe page introduction, above the feed addresses. States the format the site serves, then the one action to take.",
|
|
116
|
+
}),
|
|
117
|
+
)}
|
|
118
|
+
/>
|
|
119
|
+
|
|
120
|
+
<div class="card p-5">
|
|
121
|
+
<CopyField
|
|
122
|
+
label={mainFeed.label}
|
|
123
|
+
labelHref={mainFeed.pageUrl}
|
|
124
|
+
labelTitle={pageLinkTitle}
|
|
125
|
+
value={mainFeed.url}
|
|
126
|
+
description={mainFeed.description}
|
|
127
|
+
copyLabel={copyLabel}
|
|
128
|
+
copiedMessage={copiedMessage}
|
|
129
|
+
failedMessage={failedMessage}
|
|
130
|
+
/>
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
<div class="mt-8 flex flex-col gap-5">
|
|
134
|
+
{otherFeeds.map((feed) => (
|
|
135
|
+
<CopyField
|
|
136
|
+
key={feed.url}
|
|
137
|
+
label={feed.label}
|
|
138
|
+
labelHref={feed.pageUrl}
|
|
139
|
+
labelTitle={pageLinkTitle}
|
|
140
|
+
value={feed.url}
|
|
141
|
+
description={feed.description}
|
|
142
|
+
copyLabel={copyLabel}
|
|
143
|
+
copiedMessage={copiedMessage}
|
|
144
|
+
failedMessage={failedMessage}
|
|
145
|
+
/>
|
|
146
|
+
))}
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<p class="mt-8 flex items-start gap-2 text-sm text-muted-foreground">
|
|
150
|
+
{/* The specimen the sentence points at. Decorative — the sentence
|
|
151
|
+
names it, so a screen reader announcing the glyph twice would only
|
|
152
|
+
get in the way. */}
|
|
153
|
+
<span
|
|
154
|
+
class="mt-0.5 shrink-0 opacity-70"
|
|
155
|
+
aria-hidden="true"
|
|
156
|
+
dangerouslySetInnerHTML={{
|
|
157
|
+
__html: getIconSvg("rss", "size-3.5") ?? "",
|
|
158
|
+
}}
|
|
159
|
+
/>
|
|
160
|
+
<span>
|
|
161
|
+
{i18n._(
|
|
162
|
+
msg({
|
|
163
|
+
message:
|
|
164
|
+
"Collections and filtered archive views have their own feeds. Look for this icon on those pages.",
|
|
165
|
+
comment:
|
|
166
|
+
"@context: Subscribe page closing note, next to a small feed icon standing in for the ones on collection and archive pages",
|
|
167
|
+
}),
|
|
168
|
+
)}
|
|
169
|
+
</span>
|
|
170
|
+
</p>
|
|
171
|
+
|
|
172
|
+
{/* Last, under a rule: a reader who already keeps a feed reader never
|
|
173
|
+
has to read it, and one who does not can find it by its heading.
|
|
174
|
+
Naming specific readers was considered and dropped — the list would
|
|
175
|
+
be identical on every Jant site, which makes the software the one
|
|
176
|
+
endorsing them, and it would rot as readers shut down or change
|
|
177
|
+
hands. */}
|
|
178
|
+
<section class="mt-10 border-t pt-6">
|
|
179
|
+
<h2 class="text-base font-medium">
|
|
180
|
+
{i18n._(
|
|
181
|
+
msg({
|
|
182
|
+
message: "What a feed reader does",
|
|
183
|
+
comment:
|
|
184
|
+
"@context: Subscribe page heading over the explanation of feed readers, for readers who have not used one",
|
|
185
|
+
}),
|
|
186
|
+
)}
|
|
187
|
+
</h2>
|
|
188
|
+
<p class="mt-2 text-sm text-muted-foreground">
|
|
189
|
+
{i18n._(
|
|
190
|
+
msg({
|
|
191
|
+
message:
|
|
192
|
+
"A feed reader checks these addresses for new posts and collects them in one place. Most are an app on your phone or computer; some are a website you sign in to.",
|
|
193
|
+
comment:
|
|
194
|
+
"@context: Subscribe page explanation of what a feed reader is and where it runs",
|
|
195
|
+
}),
|
|
196
|
+
)}
|
|
197
|
+
</p>
|
|
198
|
+
<p class="mt-2 text-sm text-muted-foreground">
|
|
199
|
+
{i18n._(
|
|
200
|
+
msg({
|
|
201
|
+
message:
|
|
202
|
+
"Subscribing creates no account here. To stop, delete the address from your reader.",
|
|
203
|
+
comment:
|
|
204
|
+
"@context: Subscribe page note that subscribing is one-sided and how to undo it",
|
|
205
|
+
}),
|
|
206
|
+
)}
|
|
207
|
+
</p>
|
|
208
|
+
{/* The whole sentence is the link, so its accessible name describes
|
|
209
|
+
where it goes and no fragment has to be assembled around an anchor.
|
|
210
|
+
aboutfeeds.com is the one widely used vendor-neutral explainer
|
|
211
|
+
written for exactly this reader — someone who just met a feed
|
|
212
|
+
address. It does name a few readers, which is the point: a third
|
|
213
|
+
party can recommend them without every Jant site appearing to. If
|
|
214
|
+
it ever goes dark the two paragraphs above still stand on their
|
|
215
|
+
own. */}
|
|
216
|
+
<p class="mt-4 text-sm">
|
|
217
|
+
<a
|
|
218
|
+
href="https://aboutfeeds.com/"
|
|
219
|
+
class="underline decoration-1 underline-offset-4"
|
|
220
|
+
target="_blank"
|
|
221
|
+
rel="noopener noreferrer"
|
|
222
|
+
>
|
|
223
|
+
{i18n._(
|
|
224
|
+
msg({
|
|
225
|
+
message:
|
|
226
|
+
"A longer introduction to feeds and readers, at aboutfeeds.com",
|
|
227
|
+
comment:
|
|
228
|
+
"@context: Subscribe page link to the independent aboutfeeds.com explainer. The whole sentence is the link text.",
|
|
229
|
+
}),
|
|
230
|
+
)}
|
|
231
|
+
</a>
|
|
232
|
+
</p>
|
|
233
|
+
</section>
|
|
234
|
+
</div>
|
|
235
|
+
);
|
|
236
|
+
};
|