@jant/core 0.6.16 → 0.7.1
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/export.js +3 -1
- package/bin/commands/import-site.js +749 -220
- package/bin/commands/reset-password.js +21 -8
- package/bin/commands/setup.js +136 -0
- package/bin/commands/site/export.js +71 -34
- package/bin/commands/site/pull-media.js +2 -6
- package/bin/commands/site/snapshot/export.js +19 -61
- package/bin/commands/site/snapshot/import.js +23 -24
- package/bin/lib/d1-query.js +92 -18
- package/bin/lib/hugo-markdown.js +4 -0
- package/bin/lib/node-env.js +40 -4
- package/bin/lib/site-pull-media.js +21 -28
- package/bin/lib/site-selection.js +94 -29
- package/bin/lib/site-snapshot.js +338 -3
- package/bin/lib/sql-export.js +68 -5
- package/bin/lib/wrangler-cli.js +37 -2
- package/bin/lib/wrangler-json.js +35 -0
- package/bin/lib/zip-archive.js +187 -0
- package/dist/{app-C7_4oab9.js → app-ZcaI1kPN.js} +53190 -13529
- 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-B-m7X7S5.js +261 -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-BJyd-LXE.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-BkscJx69.js +2 -0
- package/dist/client/_assets/client-DAhoqPdr.css +2 -0
- package/dist/client/_assets/client-auth-CAMfTrKW.js +828 -0
- package/dist/client/_assets/client-author-DQ8R-8KR.css +2 -0
- package/dist/client/_assets/client-compose-D6rExKVK.js +2004 -0
- package/dist/client/_assets/client-knSEyUJO.js +295 -0
- package/dist/client/_assets/client-manage-sVOkqVbH.js +2170 -0
- package/dist/client/_assets/client-settings-CGR_nZ78.js +1112 -0
- package/dist/github-sync-Gw4orAHk.js +8398 -0
- package/dist/index.js +2 -5
- package/dist/node.js +11 -9
- package/package.json +15 -5
- 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__/dev-scripts.test.ts +203 -0
- package/src/__tests__/export-collection-order.test.ts +276 -0
- package/src/__tests__/export-feed-ids.test.ts +184 -0
- package/src/__tests__/export-feed-order.test.ts +294 -0
- package/src/__tests__/export-hugo-build.test.ts +390 -0
- package/src/__tests__/export-import-roundtrip.test.ts +94 -0
- package/src/__tests__/export-service.test.ts +842 -28
- package/src/__tests__/export-smart-collection.test.ts +329 -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__/helpers/hugo-site.ts +146 -0
- package/src/__tests__/import-site-command.test.ts +487 -1
- package/src/__tests__/mise-config.test.ts +75 -4
- package/src/__tests__/node-dev-tasks.test.ts +264 -0
- package/src/__tests__/site-export-canonical-import.test.ts +149 -0
- package/src/__tests__/snapshot-canonical-replay.test.ts +179 -0
- package/src/__tests__/snapshot-settings.test.ts +97 -0
- package/src/__tests__/snapshot-tables.test.ts +219 -0
- package/src/__tests__/sql-export.test.ts +173 -0
- package/src/__tests__/stylesheet-audience.test.ts +159 -0
- package/src/__tests__/stylesheet-breakpoints.test.ts +183 -0
- package/src/__tests__/zip-archive.test.ts +106 -0
- package/src/app.tsx +86 -22
- 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 +561 -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-types.ts +6 -1
- package/src/client/components/jant-repo-picker.ts +137 -108
- package/src/client/components/jant-settings-avatar.ts +31 -27
- package/src/client/components/jant-settings-general.ts +622 -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/__tests__/list-editing.test.ts +224 -86
- package/src/client/tiptap/__tests__/mark-exit.test.ts +1 -2
- package/src/client/tiptap/__tests__/markdown-clipboard.test.ts +26 -0
- package/src/client/tiptap/bubble-menu.ts +47 -16
- package/src/client/tiptap/extensions.ts +0 -3
- 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/tiptap/structural-keymap.ts +158 -47
- 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__/d1-query.test.ts +56 -1
- 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 +555 -73
- package/src/i18n/locales/settings/en.ts +1 -1
- package/src/i18n/locales/settings/zh-Hans.po +601 -119
- package/src/i18n/locales/settings/zh-Hans.ts +1 -1
- package/src/i18n/locales/settings/zh-Hant.po +614 -132
- 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__/github-sync-repo-name.test.ts +40 -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__/image.test.ts +27 -1
- 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-to-tiptap.test.ts +105 -0
- package/src/lib/__tests__/markdown.test.ts +10 -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 +131 -3
- package/src/lib/__tests__/schemas.test.ts +69 -5
- 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__/timeline.test.ts +87 -0
- package/src/lib/__tests__/tiptap-to-markdown.test.ts +172 -4
- 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 +439 -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-repo-name.ts +45 -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 +62 -0
- package/src/lib/icons.ts +9 -2
- package/src/lib/ids.ts +3 -0
- package/src/lib/image.ts +17 -4
- package/src/lib/jant-docs.ts +42 -0
- package/src/lib/lang-detect.ts +308 -0
- package/src/lib/markdown-manager.ts +392 -2
- 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-body-html.ts +11 -4
- package/src/lib/post-meta.ts +19 -0
- package/src/lib/render.tsx +55 -7
- package/src/lib/resolve-config.ts +111 -6
- package/src/lib/schemas.ts +251 -15
- 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 +43 -2
- package/src/lib/tiptap-render.ts +1 -2
- package/src/lib/tiptap-to-markdown.ts +13 -7
- package/src/lib/upload.ts +16 -13
- package/src/lib/url.ts +159 -86
- package/src/lib/version.ts +10 -8
- package/src/lib/view-language.ts +436 -0
- package/src/lib/view.ts +53 -25
- 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-setup.test.ts +163 -0
- package/src/node/__tests__/cli-site-selection.test.ts +128 -38
- package/src/node/__tests__/cli-site-snapshot.test.ts +67 -0
- package/src/node/__tests__/cli-snapshot-meta.test.ts +20 -0
- package/src/node/__tests__/runtime.test.ts +75 -0
- package/src/node/index.ts +3 -0
- package/src/node/request-handler.ts +3 -1
- 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__/posts.test.ts +24 -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/__tests__/upload.test.ts +34 -0
- 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 +6 -3
- package/src/routes/api/internal/__tests__/sites.test.ts +42 -2
- package/src/routes/api/internal/sites.ts +0 -1
- package/src/routes/api/nav-items.ts +55 -8
- package/src/routes/api/posts.ts +161 -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 +44 -7
- package/src/routes/api/smart-collections.ts +132 -0
- package/src/routes/api/telegram.ts +2 -40
- package/src/routes/api/upload.ts +10 -3
- 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 +9 -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__/github-sync-app.test.ts +365 -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 +714 -111
- 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-page-round-trips.test.ts +211 -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/__tests__/thread-order.test.ts +179 -0
- package/src/routes/pages/archive.tsx +487 -379
- package/src/routes/pages/collection.tsx +94 -96
- package/src/routes/pages/collections.tsx +38 -50
- package/src/routes/pages/featured.tsx +43 -18
- 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 +228 -45
- package/src/routes/pages/partials.tsx +41 -0
- package/src/routes/pages/search.tsx +22 -5
- 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/__tests__/readiness.test.ts +23 -0
- package/src/runtime/cloudflare.ts +21 -2
- package/src/runtime/index.ts +27 -1
- package/src/runtime/node.ts +67 -1
- package/src/runtime/readiness.ts +15 -0
- package/src/runtime/site.ts +95 -24
- package/src/services/__tests__/auth.test.ts +116 -7
- package/src/services/__tests__/bootstrap-setup-instance.test.ts +230 -0
- package/src/services/__tests__/collection.test.ts +108 -4
- package/src/services/__tests__/custom-url.test.ts +34 -0
- package/src/services/__tests__/github-app-installations.test.ts +153 -0
- package/src/services/__tests__/github-sync-push.test.ts +46 -0
- package/src/services/__tests__/language.test.ts +495 -0
- package/src/services/__tests__/media.test.ts +31 -0
- package/src/services/__tests__/navigation.test.ts +213 -10
- package/src/services/__tests__/path.test.ts +236 -0
- package/src/services/__tests__/post-language.test.ts +830 -0
- package/src/services/__tests__/post-timeline.test.ts +127 -0
- package/src/services/__tests__/post.test.ts +120 -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 +408 -39
- package/src/services/collection-directory-position.ts +76 -0
- package/src/services/collection.ts +331 -46
- package/src/services/custom-url.ts +29 -24
- 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/alias.html +27 -1
- package/src/services/export-theme/layouts/_default/list.html +2 -63
- package/src/services/export-theme/layouts/_default/rss.xml +103 -38
- package/src/services/export-theme/layouts/collections/list.html +3 -3
- package/src/services/export-theme/layouts/featured/list.html +1 -4
- package/src/services/export-theme/layouts/index.html +40 -26
- package/src/services/export-theme/layouts/partials/collection-members.html +100 -0
- package/src/services/export-theme/layouts/partials/collection-threads.html +33 -0
- package/src/services/export-theme/layouts/partials/featured-members.html +35 -0
- package/src/services/export-theme/layouts/partials/featured-thread.html +1 -1
- package/src/services/export-theme/layouts/partials/feed-post-content.xml +105 -2
- package/src/services/export-theme/layouts/partials/footer.html +1 -1
- package/src/services/export-theme/layouts/partials/head.html +1 -1
- package/src/services/export-theme/layouts/partials/header.html +5 -3
- package/src/services/export-theme/layouts/partials/jant-data.html +23 -0
- package/src/services/export-theme/layouts/partials/latest-members.html +48 -0
- 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/smart-collection-members.html +129 -0
- package/src/services/export-theme/layouts/partials/thread-preview.html +41 -31
- package/src/services/export-theme/layouts/post/list.html +1 -1
- package/src/services/export-theme/layouts/smart_collection/list.html +24 -0
- package/src/services/export-theme/styles/main.css +13 -2
- package/src/services/export-theme/theme.toml +1 -1
- package/src/services/export.ts +880 -72
- package/src/services/github-app-installations.ts +171 -4
- package/src/services/github-sync.ts +69 -15
- package/src/services/index.ts +34 -3
- package/src/services/language.ts +412 -0
- package/src/services/mcp.ts +22 -2
- package/src/services/media.ts +22 -7
- package/src/services/navigation.ts +429 -67
- package/src/services/path.ts +254 -6
- package/src/services/post.ts +1234 -190
- package/src/services/search.ts +39 -2
- package/src/services/settings.ts +238 -43
- package/src/services/site-admin.ts +27 -14
- package/src/services/site.ts +160 -32
- 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 +1090 -7216
- package/src/types/app-context.ts +23 -0
- package/src/types/bindings.ts +8 -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 +74 -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 +277 -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/export-Dl5KCiEs.js +0 -5562
- 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/client/tiptap/exitable-marks.ts +0 -73
- package/src/ui/pages/CollectionEditorPage.tsx +0 -175
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { readAllStylesheets } from "./helpers/component-css.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The sanctioned width breakpoints, mirroring the ledger comment in
|
|
6
|
+
* `styles/tokens.css`. Each number is one job; a job never gets a second
|
|
7
|
+
* number.
|
|
8
|
+
*
|
|
9
|
+
* This is not bookkeeping for its own sake. The composer once switched to its
|
|
10
|
+
* full-screen sheet at 700px while its own controls took their touch sizing at
|
|
11
|
+
* 760px, so a 750px window rendered a thumb-sized Post button (46px, against
|
|
12
|
+
* 33px one pixel wider) inside a windowed dialog. Nothing was wrong in either
|
|
13
|
+
* rule on its own — they had simply drifted to two numbers for one switch, and
|
|
14
|
+
* no test could see it.
|
|
15
|
+
*/
|
|
16
|
+
const LEDGER = [
|
|
17
|
+
480, // Header hamburger; the composer's post-meta pill sheds its words.
|
|
18
|
+
580, // Nav collapse tier-sm.
|
|
19
|
+
640, // Media grid and lightbox chrome.
|
|
20
|
+
700, // The phone switch.
|
|
21
|
+
780, // Nav collapse tier-md.
|
|
22
|
+
960, // Nav collapse tier-lg.
|
|
23
|
+
1024, // Tufte two-column → single-column.
|
|
24
|
+
1079, // Collections sidebar drops out.
|
|
25
|
+
1200, // Header search collapses to an icon.
|
|
26
|
+
] as const;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Height breakpoints are a separate axis with a separate job: a short viewport
|
|
30
|
+
* has no room for the composer's chrome regardless of how wide it is. One
|
|
31
|
+
* number, stated on both sides.
|
|
32
|
+
*/
|
|
33
|
+
const HEIGHT_LEDGER = [760] as const;
|
|
34
|
+
|
|
35
|
+
interface Condition {
|
|
36
|
+
/** The stylesheet the condition appears in. */
|
|
37
|
+
file: string;
|
|
38
|
+
/** 1-indexed line of the `@media` prelude the condition sits in. */
|
|
39
|
+
line: number;
|
|
40
|
+
/** `min` or `max`. */
|
|
41
|
+
bound: "min" | "max";
|
|
42
|
+
/** `width` or `height`. */
|
|
43
|
+
axis: "width" | "height";
|
|
44
|
+
/** The pixel value as written. */
|
|
45
|
+
value: number;
|
|
46
|
+
/** The condition as written, for failure messages. */
|
|
47
|
+
text: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Every `(min-width: …)` / `(max-width: …)` / `(min-height: …)` /
|
|
52
|
+
* `(max-height: …)` condition in an `@media` prelude across the hand-written
|
|
53
|
+
* stylesheets.
|
|
54
|
+
*
|
|
55
|
+
* `@media` only. A `@container` query carrying the same words measures its
|
|
56
|
+
* container, not the viewport, so it answers to the component it wraps rather
|
|
57
|
+
* than to this ledger — the archive filter bar splits on its own inline size
|
|
58
|
+
* at 39rem and is right to.
|
|
59
|
+
*
|
|
60
|
+
* A prelude may span several lines (the composer's full-screen sheet lists
|
|
61
|
+
* three conditions), so the line number is counted to the condition itself
|
|
62
|
+
* rather than to the `@media` that opens it.
|
|
63
|
+
*/
|
|
64
|
+
function collectConditions(): Condition[] {
|
|
65
|
+
const found: Condition[] = [];
|
|
66
|
+
|
|
67
|
+
for (const { name, css } of readAllStylesheets()) {
|
|
68
|
+
const preludes = /@media\b([^{;]*)/g;
|
|
69
|
+
let prelude: RegExpExecArray | null;
|
|
70
|
+
|
|
71
|
+
while ((prelude = preludes.exec(css)) !== null) {
|
|
72
|
+
const offset = prelude.index + "@media".length;
|
|
73
|
+
const pattern = /\((min|max)-(width|height):\s*([^)]+)\)/g;
|
|
74
|
+
let match: RegExpExecArray | null;
|
|
75
|
+
|
|
76
|
+
while ((match = pattern.exec(prelude[1])) !== null) {
|
|
77
|
+
const [text, bound, axis, raw] = match;
|
|
78
|
+
const px = /^(\d+(?:\.\d+)?)px$/.exec(raw.trim());
|
|
79
|
+
|
|
80
|
+
found.push({
|
|
81
|
+
file: name,
|
|
82
|
+
line: css.slice(0, offset + match.index).split("\n").length,
|
|
83
|
+
bound: bound as "min" | "max",
|
|
84
|
+
axis: axis as "width" | "height",
|
|
85
|
+
// A non-px unit lands as NaN and fails the ledger check by name,
|
|
86
|
+
// which is the point: `rem` in a media query resolves against the
|
|
87
|
+
// initial font-size rather than the root this document sets, so the
|
|
88
|
+
// number it reads as is not the number it is.
|
|
89
|
+
value: px ? Number(px[1]) : Number.NaN,
|
|
90
|
+
text,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return found;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The breakpoint a condition belongs to. `min-width: 700px` and
|
|
101
|
+
* `max-width: 699px` are the two sides of the same 700px switch, so the `max`
|
|
102
|
+
* side is stated one pixel below the number it divides on.
|
|
103
|
+
*/
|
|
104
|
+
function breakpointOf(condition: Condition): number {
|
|
105
|
+
return condition.bound === "max" ? condition.value + 1 : condition.value;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
describe("stylesheet breakpoints", () => {
|
|
109
|
+
it("uses only the breakpoints on the ledger", () => {
|
|
110
|
+
const strays = collectConditions()
|
|
111
|
+
.filter((condition) => {
|
|
112
|
+
const ledger = condition.axis === "width" ? LEDGER : HEIGHT_LEDGER;
|
|
113
|
+
// Both spellings of a breakpoint are accepted here — `max-width: 640px`
|
|
114
|
+
// and `max-width: 639px` are the same switch. Which one a file may use
|
|
115
|
+
// is settled by the overlap test below.
|
|
116
|
+
return !(
|
|
117
|
+
ledger.includes(condition.value as never) ||
|
|
118
|
+
ledger.includes(breakpointOf(condition) as never)
|
|
119
|
+
);
|
|
120
|
+
})
|
|
121
|
+
.map(
|
|
122
|
+
(condition) =>
|
|
123
|
+
`${condition.file}:${condition.line} ${condition.text}` +
|
|
124
|
+
` — not on the ledger in styles/tokens.css`,
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
expect(strays).toEqual([]);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("never lets both sides of a breakpoint match the same viewport", () => {
|
|
131
|
+
const conditions = collectConditions();
|
|
132
|
+
const overlaps: string[] = [];
|
|
133
|
+
|
|
134
|
+
for (const axis of ["width", "height"] as const) {
|
|
135
|
+
const onAxis = conditions.filter((condition) => condition.axis === axis);
|
|
136
|
+
|
|
137
|
+
for (const value of new Set(
|
|
138
|
+
onAxis.filter((c) => c.bound === "min").map((c) => c.value),
|
|
139
|
+
)) {
|
|
140
|
+
// `min-*: N` and `max-*: N` both match at exactly N. Wherever a
|
|
141
|
+
// breakpoint is written from both sides, the lower side has to sit one
|
|
142
|
+
// pixel below it — otherwise a viewport of exactly N gets the phone
|
|
143
|
+
// layout and the desktop spacing at once.
|
|
144
|
+
const clash = onAxis.filter(
|
|
145
|
+
(condition) => condition.bound === "max" && condition.value === value,
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
overlaps.push(
|
|
149
|
+
...clash.map(
|
|
150
|
+
(condition) =>
|
|
151
|
+
`${condition.file}:${condition.line} ${condition.text}` +
|
|
152
|
+
` overlaps (min-${axis}: ${value}px) at exactly ${value}px` +
|
|
153
|
+
` — write it as (max-${axis}: ${value - 1}px)`,
|
|
154
|
+
),
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
expect(overlaps).toEqual([]);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("keeps the phone switch on one number", () => {
|
|
163
|
+
// The regression this file exists for. Touch sizing and the mobile type
|
|
164
|
+
// scale used to ride on 760px while the layout they sit inside switched at
|
|
165
|
+
// 700px; anything between the two was a desktop window wearing phone
|
|
166
|
+
// controls. Any width breakpoint in the 700s that is not the nav ladder's
|
|
167
|
+
// 780px is that drift coming back.
|
|
168
|
+
const nearMisses = collectConditions()
|
|
169
|
+
.filter(
|
|
170
|
+
(condition) =>
|
|
171
|
+
condition.axis === "width" &&
|
|
172
|
+
condition.value > 700 &&
|
|
173
|
+
condition.value < 780,
|
|
174
|
+
)
|
|
175
|
+
.map(
|
|
176
|
+
(condition) =>
|
|
177
|
+
`${condition.file}:${condition.line} ${condition.text}` +
|
|
178
|
+
` — the phone switch is 700px; see the ledger in styles/tokens.css`,
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
expect(nearMisses).toEqual([]);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streaming ZIP helpers used by `site export`, `site import`,
|
|
3
|
+
* `site pull-media`, and `site snapshot`.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createWriteStream } from "node:fs";
|
|
7
|
+
import {
|
|
8
|
+
chmod,
|
|
9
|
+
mkdir,
|
|
10
|
+
mkdtemp,
|
|
11
|
+
readFile,
|
|
12
|
+
rm,
|
|
13
|
+
stat,
|
|
14
|
+
writeFile,
|
|
15
|
+
} from "node:fs/promises";
|
|
16
|
+
import { tmpdir } from "node:os";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
import { pipeline } from "node:stream/promises";
|
|
19
|
+
import { zipSync } from "fflate";
|
|
20
|
+
import yazl from "yazl";
|
|
21
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
22
|
+
import {
|
|
23
|
+
extractZipFile,
|
|
24
|
+
writeDirectoryToZip,
|
|
25
|
+
} from "../../bin/lib/zip-archive.js";
|
|
26
|
+
|
|
27
|
+
describe("zip-archive", () => {
|
|
28
|
+
let tempDir: string;
|
|
29
|
+
|
|
30
|
+
beforeEach(async () => {
|
|
31
|
+
tempDir = await mkdtemp(join(tmpdir(), "jant-zip-"));
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
afterEach(async () => {
|
|
35
|
+
await chmod(join(tempDir, "source", "locked.md"), 0o644).catch(() => {});
|
|
36
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("writes a directory and reads it back byte for byte", async () => {
|
|
40
|
+
const source = join(tempDir, "source");
|
|
41
|
+
const image = new Uint8Array(4096).map((_, index) => index % 251);
|
|
42
|
+
await mkdir(join(source, "content", "hello"), { recursive: true });
|
|
43
|
+
await mkdir(join(source, "static", "media"), { recursive: true });
|
|
44
|
+
await writeFile(
|
|
45
|
+
join(source, "hugo.toml"),
|
|
46
|
+
'baseURL = "https://a.example/"',
|
|
47
|
+
);
|
|
48
|
+
await writeFile(
|
|
49
|
+
join(source, "content", "hello", "_index.md"),
|
|
50
|
+
"---\n---\n你好",
|
|
51
|
+
);
|
|
52
|
+
await writeFile(join(source, "static", "media", "a.jpg"), image);
|
|
53
|
+
|
|
54
|
+
const zipPath = join(tempDir, "out", "site.zip");
|
|
55
|
+
expect(await writeDirectoryToZip(source, zipPath)).toEqual({ files: 3 });
|
|
56
|
+
|
|
57
|
+
const target = join(tempDir, "target");
|
|
58
|
+
expect(await extractZipFile(zipPath, target)).toEqual({ files: 3 });
|
|
59
|
+
expect(
|
|
60
|
+
await readFile(join(target, "content", "hello", "_index.md"), "utf8"),
|
|
61
|
+
).toBe("---\n---\n你好");
|
|
62
|
+
expect(
|
|
63
|
+
new Uint8Array(await readFile(join(target, "static", "media", "a.jpg"))),
|
|
64
|
+
).toEqual(image);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("reads a ZIP64 archive", async () => {
|
|
68
|
+
const zipFile = new yazl.ZipFile();
|
|
69
|
+
zipFile.addBuffer(Buffer.from("big enough to need ZIP64"), "data/a.txt");
|
|
70
|
+
zipFile.end({ forceZip64Format: true });
|
|
71
|
+
const zipPath = join(tempDir, "zip64.zip");
|
|
72
|
+
await pipeline(zipFile.outputStream, createWriteStream(zipPath));
|
|
73
|
+
|
|
74
|
+
await extractZipFile(zipPath, join(tempDir, "target"));
|
|
75
|
+
|
|
76
|
+
expect(
|
|
77
|
+
await readFile(join(tempDir, "target", "data", "a.txt"), "utf8"),
|
|
78
|
+
).toBe("big enough to need ZIP64");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("refuses an entry that climbs out of the target directory", async () => {
|
|
82
|
+
const zipPath = join(tempDir, "escape.zip");
|
|
83
|
+
await writeFile(
|
|
84
|
+
zipPath,
|
|
85
|
+
zipSync({ "../escaped.txt": new TextEncoder().encode("x") }),
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
await expect(
|
|
89
|
+
extractZipFile(zipPath, join(tempDir, "target")),
|
|
90
|
+
).rejects.toThrow();
|
|
91
|
+
await expect(stat(join(tempDir, "escaped.txt"))).rejects.toThrow();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("leaves no archive behind when writing fails", async () => {
|
|
95
|
+
if (process.getuid?.() === 0) return; // root reads a mode-000 file anyway
|
|
96
|
+
const source = join(tempDir, "source");
|
|
97
|
+
await mkdir(source, { recursive: true });
|
|
98
|
+
await writeFile(join(source, "locked.md"), "secret");
|
|
99
|
+
await chmod(join(source, "locked.md"), 0o000);
|
|
100
|
+
|
|
101
|
+
const zipPath = join(tempDir, "site.zip");
|
|
102
|
+
await expect(writeDirectoryToZip(source, zipPath)).rejects.toThrow();
|
|
103
|
+
await expect(stat(zipPath)).rejects.toThrow();
|
|
104
|
+
await expect(stat(`${zipPath}.partial`)).rejects.toThrow();
|
|
105
|
+
});
|
|
106
|
+
});
|
package/src/app.tsx
CHANGED
|
@@ -16,11 +16,13 @@ import { hostedDomainCheckRoutes } from "./routes/hosted/domain-check.js";
|
|
|
16
16
|
|
|
17
17
|
// Routes - Pages
|
|
18
18
|
import { homeRoutes } from "./routes/pages/home.js";
|
|
19
|
+
import { languageRoutes } from "./routes/pages/language.js";
|
|
19
20
|
import { pageRoutes } from "./routes/pages/page.js";
|
|
20
21
|
import { collectionRoutes } from "./routes/pages/collection.js";
|
|
21
22
|
import { archiveRoutes } from "./routes/pages/archive.js";
|
|
22
23
|
import { searchRoutes } from "./routes/pages/search.js";
|
|
23
24
|
import { featuredRoutes } from "./routes/pages/featured.js";
|
|
25
|
+
import { subscribeRoutes } from "./routes/pages/subscribe.js";
|
|
24
26
|
import { latestRoutes } from "./routes/pages/latest.js";
|
|
25
27
|
import { collectionsPageRoutes } from "./routes/pages/collections.js";
|
|
26
28
|
import { newPostRoutes } from "./routes/pages/new.js";
|
|
@@ -37,6 +39,7 @@ import { postsApiRoutes } from "./routes/api/posts.js";
|
|
|
37
39
|
import { attachmentsApiRoutes } from "./routes/api/attachments.js";
|
|
38
40
|
import { navItemsApiRoutes } from "./routes/api/nav-items.js";
|
|
39
41
|
import { collectionsApiRoutes } from "./routes/api/collections.js";
|
|
42
|
+
import { smartCollectionsApiRoutes } from "./routes/api/smart-collections.js";
|
|
40
43
|
import { settingsApiRoutes } from "./routes/api/settings.js";
|
|
41
44
|
import { uploadApiRoutes } from "./routes/api/upload.js";
|
|
42
45
|
import { uploadsApiRoutes } from "./routes/api/uploads.js";
|
|
@@ -59,6 +62,7 @@ import { internalPostBodyHtmlRoutes } from "./routes/api/internal/post-body-html
|
|
|
59
62
|
import { internalUploadsRoutes } from "./routes/api/internal/uploads.js";
|
|
60
63
|
import { publicArchiveApiRoutes } from "./routes/api/public/archive.js";
|
|
61
64
|
import { publicPostsApiRoutes } from "./routes/api/public/posts.js";
|
|
65
|
+
import { discoverApiRoutes } from "./routes/api/discover.js";
|
|
62
66
|
// Routes - Compose
|
|
63
67
|
import { composeRoutes } from "./routes/compose.js";
|
|
64
68
|
|
|
@@ -71,7 +75,7 @@ import { siteSkillRoutes } from "./routes/site-skill.js";
|
|
|
71
75
|
// Middleware
|
|
72
76
|
import { requireAuth } from "./middleware/auth.js";
|
|
73
77
|
import { attachSession } from "./middleware/session.js";
|
|
74
|
-
import { defaultCacheControl } from "./middleware/cache-control.js";
|
|
78
|
+
import { defaultCacheControl, noStore } from "./middleware/cache-control.js";
|
|
75
79
|
import { requireRssFeedsEnabled } from "./middleware/public-content-access.js";
|
|
76
80
|
import { requireOnboarding } from "./middleware/onboarding.js";
|
|
77
81
|
import { errorHandler } from "./middleware/error-handler.js";
|
|
@@ -89,7 +93,11 @@ import {
|
|
|
89
93
|
} from "./lib/jant-branding.js";
|
|
90
94
|
import { isAssetPath } from "./lib/asset-path.js";
|
|
91
95
|
import { getHostedCanonicalRedirect } from "./lib/hosted-domain.js";
|
|
92
|
-
import { stripSitePathPrefix, toPublicHref } from "./lib/url.js";
|
|
96
|
+
import { normalizePath, stripSitePathPrefix, toPublicHref } from "./lib/url.js";
|
|
97
|
+
import {
|
|
98
|
+
matchesIfNoneMatch,
|
|
99
|
+
withConditionalResponse,
|
|
100
|
+
} from "./lib/http-cache.js";
|
|
93
101
|
import { withWorkerResponseCache } from "./lib/worker-response-cache.js";
|
|
94
102
|
import { createRequestRuntime } from "./runtime/index.js";
|
|
95
103
|
import { getInstanceReadiness } from "./runtime/readiness.js";
|
|
@@ -265,22 +273,30 @@ export function createApp(): App {
|
|
|
265
273
|
const app = new Hono<{ Bindings: Bindings; Variables: AppVariables }>();
|
|
266
274
|
const defaultFetch = app.fetch.bind(app);
|
|
267
275
|
|
|
268
|
-
app.fetch = (request, env, executionCtx) => {
|
|
276
|
+
app.fetch = async (request, env, executionCtx) => {
|
|
269
277
|
const bindings = env as Bindings | undefined;
|
|
270
278
|
const preparedRequest = prepareRequestForRouting(
|
|
271
279
|
request,
|
|
272
280
|
getConfiguredSingleSitePathPrefix(bindings),
|
|
273
281
|
);
|
|
274
282
|
if (preparedRequest instanceof Response) {
|
|
275
|
-
return
|
|
283
|
+
return preparedRequest;
|
|
276
284
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
285
|
+
// Revalidation sits outside the response cache, so a reader holding the
|
|
286
|
+
// current version is answered from a cache hit as cheaply as from a miss,
|
|
287
|
+
// and a miss still stores the full document on its way out.
|
|
288
|
+
return withConditionalResponse(
|
|
280
289
|
request,
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
290
|
+
await withWorkerResponseCache({
|
|
291
|
+
bindings,
|
|
292
|
+
executionCtx,
|
|
293
|
+
request,
|
|
294
|
+
next: () =>
|
|
295
|
+
Promise.resolve(
|
|
296
|
+
defaultFetch(preparedRequest, bindings, executionCtx),
|
|
297
|
+
),
|
|
298
|
+
}),
|
|
299
|
+
);
|
|
284
300
|
};
|
|
285
301
|
|
|
286
302
|
// Global error handler: maps DomainError → HTTP responses
|
|
@@ -307,7 +323,11 @@ export function createApp(): App {
|
|
|
307
323
|
if (startupConfigError) {
|
|
308
324
|
return c.html(startupConfigError, 500);
|
|
309
325
|
}
|
|
310
|
-
const runtime = await createRequestRuntime(
|
|
326
|
+
const runtime = await createRequestRuntime(
|
|
327
|
+
c.env,
|
|
328
|
+
publicRequestUrl,
|
|
329
|
+
c.req.raw,
|
|
330
|
+
);
|
|
311
331
|
c.set("services", runtime.services);
|
|
312
332
|
c.set("servicesForSite", runtime.servicesForSite);
|
|
313
333
|
c.set("hostedHandoff", runtime.hostedHandoff);
|
|
@@ -324,12 +344,35 @@ export function createApp(): App {
|
|
|
324
344
|
// downstream handlers don't each call auth.api.getSession themselves.
|
|
325
345
|
app.use("*", attachSession());
|
|
326
346
|
|
|
327
|
-
// Default every response without an explicit Cache-Control
|
|
328
|
-
//
|
|
329
|
-
//
|
|
330
|
-
//
|
|
347
|
+
// Default every response without an explicit Cache-Control: `private,
|
|
348
|
+
// no-cache` for an anonymous reader, `private, no-store` once signed in.
|
|
349
|
+
// Jant pages are auth-variant, so a shared/CDN cache must never store them
|
|
350
|
+
// — that is what `private` is for in both — while a reader's own browser
|
|
351
|
+
// keeps the page and its back/forward cache. Routes serving genuinely public
|
|
352
|
+
// resources (media, feeds, sitemaps, favicons) set their own Cache-Control
|
|
353
|
+
// and are untouched.
|
|
331
354
|
app.use("*", defaultCacheControl());
|
|
332
355
|
|
|
356
|
+
// Credential surfaces override that default. Each of these is served to
|
|
357
|
+
// someone with no session yet, so the anonymous branch above would let the
|
|
358
|
+
// browser keep it — and its URL — on disk: a reset link's one-time token, a
|
|
359
|
+
// sign-in form, an SSO handoff. One list rather than a `use()` beside each
|
|
360
|
+
// route group, because it has to be registered up here anyway: `/api/auth/*`
|
|
361
|
+
// ends in a terminal handler that never yields, so a middleware registered
|
|
362
|
+
// after it never runs. The handlers themselves live under "Auth routes"
|
|
363
|
+
// below and in `app.all("/api/auth/*")`; a new one belongs in both places.
|
|
364
|
+
for (const path of [
|
|
365
|
+
"/setup",
|
|
366
|
+
"/signin",
|
|
367
|
+
"/signout",
|
|
368
|
+
"/reset",
|
|
369
|
+
"/__sso",
|
|
370
|
+
"/__dev/login",
|
|
371
|
+
"/api/auth/*",
|
|
372
|
+
]) {
|
|
373
|
+
app.use(path, noStore());
|
|
374
|
+
}
|
|
375
|
+
|
|
333
376
|
app.use("*", async (c, next) => {
|
|
334
377
|
const redirectUrl = await getHostedCanonicalRedirect({
|
|
335
378
|
currentSite: c.var.currentSite,
|
|
@@ -371,8 +414,10 @@ export function createApp(): App {
|
|
|
371
414
|
const storage = c.var.storage;
|
|
372
415
|
if (!storage) return c.notFound();
|
|
373
416
|
|
|
417
|
+
// The outer conditional layer would answer this too, but only after the
|
|
418
|
+
// object has been fetched and rendered; matching here skips that work.
|
|
374
419
|
const etag = `"${media.updatedAt}"`;
|
|
375
|
-
if (c.req.header("If-None-Match")
|
|
420
|
+
if (matchesIfNoneMatch(c.req.header("If-None-Match"), etag)) {
|
|
376
421
|
return new Response(null, { status: 304, headers: { ETag: etag } });
|
|
377
422
|
}
|
|
378
423
|
|
|
@@ -488,7 +533,13 @@ export function createApp(): App {
|
|
|
488
533
|
await next();
|
|
489
534
|
});
|
|
490
535
|
|
|
491
|
-
// Redirect middleware — only handles redirect-type custom URLs
|
|
536
|
+
// Redirect middleware — only handles redirect-type custom URLs.
|
|
537
|
+
//
|
|
538
|
+
// The lookup it needs is the same `path_registry` read the catch-all route
|
|
539
|
+
// makes to resolve a post or collection, so the record is stashed on the
|
|
540
|
+
// context and that route reuses it instead of asking again. Nothing between
|
|
541
|
+
// here and the route writes to the registry, so the record cannot go stale
|
|
542
|
+
// within the request.
|
|
492
543
|
app.use("*", async (c, next) => {
|
|
493
544
|
const path = new URL(c.req.url).pathname;
|
|
494
545
|
// Skip redirect lookup for fixed machine routes and static assets.
|
|
@@ -496,17 +547,20 @@ export function createApp(): App {
|
|
|
496
547
|
return next();
|
|
497
548
|
}
|
|
498
549
|
|
|
499
|
-
const
|
|
500
|
-
|
|
550
|
+
const storedPath = normalizePath(path);
|
|
551
|
+
const record = await c.var.services.paths.resolve(storedPath);
|
|
552
|
+
c.set("pathLookup", { path: storedPath, record });
|
|
553
|
+
|
|
554
|
+
if (record?.kind === "redirect" && record.redirectToPath) {
|
|
501
555
|
return c.redirect(
|
|
502
556
|
toPublicHref(
|
|
503
|
-
|
|
557
|
+
`/${record.redirectToPath}`,
|
|
504
558
|
getRuntimeSitePathPrefix({
|
|
505
559
|
env: c.env,
|
|
506
560
|
currentSiteDomain: c.var.currentSiteDomain,
|
|
507
561
|
}),
|
|
508
562
|
),
|
|
509
|
-
|
|
563
|
+
record.redirectType ?? 301,
|
|
510
564
|
);
|
|
511
565
|
}
|
|
512
566
|
|
|
@@ -529,15 +583,18 @@ export function createApp(): App {
|
|
|
529
583
|
// API Routes
|
|
530
584
|
app.route("/api/public/posts", publicPostsApiRoutes);
|
|
531
585
|
app.route("/api/public/archive", publicArchiveApiRoutes);
|
|
586
|
+
app.route("/api/discover", discoverApiRoutes);
|
|
532
587
|
app.route("/api/posts", postsApiRoutes);
|
|
533
588
|
app.route("/api/nav-items", navItemsApiRoutes);
|
|
534
589
|
app.route("/api/collections", collectionsApiRoutes);
|
|
590
|
+
app.route("/api/smart-collections", smartCollectionsApiRoutes);
|
|
535
591
|
app.route("/api/settings", settingsApiRoutes);
|
|
536
592
|
app.route("/api/custom-urls", customUrlsApiRoutes);
|
|
537
593
|
app.route("/api/export", exportApiRoutes);
|
|
538
594
|
app.route("/api/github-sync", githubSyncAdminRoutes);
|
|
539
595
|
|
|
540
|
-
// Auth routes
|
|
596
|
+
// Auth routes. Their paths are also listed in the `noStore()` block above,
|
|
597
|
+
// which is what keeps a credential page out of the browser's disk cache.
|
|
541
598
|
app.route("/", setupRoutes);
|
|
542
599
|
app.route("/", signinRoutes);
|
|
543
600
|
app.route("/", resetRoutes);
|
|
@@ -571,6 +628,7 @@ export function createApp(): App {
|
|
|
571
628
|
app.route("/", newPostRoutes);
|
|
572
629
|
app.route("/archive", archiveRoutes);
|
|
573
630
|
app.route("/featured", featuredRoutes);
|
|
631
|
+
app.route("/subscribe", subscribeRoutes);
|
|
574
632
|
app.route("/latest", latestRoutes);
|
|
575
633
|
app.route("/", partialPageRoutes);
|
|
576
634
|
app.route("/_", brandRoutes);
|
|
@@ -579,6 +637,12 @@ export function createApp(): App {
|
|
|
579
637
|
app.route("/collections", collectionRoutes);
|
|
580
638
|
app.route("/", homeRoutes);
|
|
581
639
|
|
|
640
|
+
// Per-language views of every surface above. Mounted here on purpose: after
|
|
641
|
+
// the static route groups, so `/archive` is the archive rather than a
|
|
642
|
+
// language named "archive", and before the catch-all, so `/en` is a language
|
|
643
|
+
// view rather than a lookup for a post whose slug is "en".
|
|
644
|
+
app.route("/:lang", languageRoutes);
|
|
645
|
+
|
|
582
646
|
// Custom page catch-all (must be last)
|
|
583
647
|
app.route("/", pageRoutes);
|
|
584
648
|
|
package/src/auth.ts
CHANGED
|
@@ -66,7 +66,24 @@ export function createAuth(
|
|
|
66
66
|
},
|
|
67
67
|
},
|
|
68
68
|
session: {
|
|
69
|
-
|
|
69
|
+
// An idle window, not a hard cap. `attachSession` writes better-auth's
|
|
70
|
+
// re-issued cookie back to the browser, so any visit inside the window
|
|
71
|
+
// pushes both the stored session and the cookie out another 90 days —
|
|
72
|
+
// a single author on their own machine should effectively never be
|
|
73
|
+
// asked to sign in again.
|
|
74
|
+
expiresIn: 3600 * 24 * 90, // 90 days
|
|
75
|
+
// How stale a session may get before a read renews it. Same as
|
|
76
|
+
// better-auth's default, but stated here because it sets the renewal
|
|
77
|
+
// cadence and that shouldn't be invisible library behaviour.
|
|
78
|
+
updateAge: 3600 * 24, // 1 day
|
|
79
|
+
// Turns off better-auth's re-authentication gate, whose default lets a
|
|
80
|
+
// session read `/list-sessions` only within 24 hours of sign-in — and it
|
|
81
|
+
// measures that from `createdAt`, which renewal never moves, so the
|
|
82
|
+
// Sessions page would answer "Session is not fresh" from day two onward.
|
|
83
|
+
// The gate also guards nothing here: revoking a session, the destructive
|
|
84
|
+
// half of that page, is not behind it, and the endpoints that would care
|
|
85
|
+
// (delete-user, unlink-account) are not part of Jant.
|
|
86
|
+
freshAge: 0,
|
|
70
87
|
cookieCache: {
|
|
71
88
|
enabled: true,
|
|
72
89
|
maxAge: 60 * 5, // 5 minutes
|
|
@@ -11,6 +11,10 @@ vi.mock("../confirm.js", () => ({
|
|
|
11
11
|
showConfirmDialog: vi.fn(),
|
|
12
12
|
}));
|
|
13
13
|
|
|
14
|
+
vi.mock("../collection-dialog-host.js", () => ({
|
|
15
|
+
openCollectionDialog: vi.fn(),
|
|
16
|
+
}));
|
|
17
|
+
|
|
14
18
|
function createMarkup() {
|
|
15
19
|
document.body.innerHTML = `
|
|
16
20
|
<div id="toast-container"></div>
|
|
@@ -29,9 +33,9 @@ function createMarkup() {
|
|
|
29
33
|
More actions
|
|
30
34
|
</button>
|
|
31
35
|
<div data-collection-page-menu hidden>
|
|
32
|
-
<
|
|
36
|
+
<button type="button" role="menuitem" data-collection-page-action="edit">
|
|
33
37
|
Edit
|
|
34
|
-
</
|
|
38
|
+
</button>
|
|
35
39
|
<button
|
|
36
40
|
type="button"
|
|
37
41
|
role="menuitem"
|
|
@@ -132,6 +136,51 @@ describe("collection detail page actions", () => {
|
|
|
132
136
|
expect(window.location.pathname).toBe("/collections");
|
|
133
137
|
});
|
|
134
138
|
|
|
139
|
+
it("edits the collection in the dialog, and follows a moved address", async () => {
|
|
140
|
+
createMarkup();
|
|
141
|
+
const { openCollectionDialog } =
|
|
142
|
+
await import("../collection-dialog-host.js");
|
|
143
|
+
vi.mocked(openCollectionDialog).mockResolvedValue({
|
|
144
|
+
changed: true,
|
|
145
|
+
collection: { id: "collection-1", slug: "renamed", title: "Renamed" },
|
|
146
|
+
});
|
|
147
|
+
await import("../collection-page-actions.js");
|
|
148
|
+
|
|
149
|
+
document
|
|
150
|
+
.querySelector<HTMLElement>("[data-collection-page-action='toggle-menu']")
|
|
151
|
+
?.click();
|
|
152
|
+
document
|
|
153
|
+
.querySelector<HTMLElement>("[data-collection-page-action='edit']")
|
|
154
|
+
?.click();
|
|
155
|
+
await flushAsyncWork();
|
|
156
|
+
|
|
157
|
+
expect(openCollectionDialog).toHaveBeenCalledWith({
|
|
158
|
+
collectionId: "collection-1",
|
|
159
|
+
});
|
|
160
|
+
expect(window.location.pathname).toBe("/renamed");
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("leaves the page where it is when the dialog is dismissed", async () => {
|
|
164
|
+
createMarkup();
|
|
165
|
+
const { openCollectionDialog } =
|
|
166
|
+
await import("../collection-dialog-host.js");
|
|
167
|
+
vi.mocked(openCollectionDialog).mockResolvedValue({ changed: false });
|
|
168
|
+
const reload = vi.fn();
|
|
169
|
+
vi.spyOn(window.location, "reload").mockImplementation(reload);
|
|
170
|
+
await import("../collection-page-actions.js");
|
|
171
|
+
|
|
172
|
+
document
|
|
173
|
+
.querySelector<HTMLElement>("[data-collection-page-action='toggle-menu']")
|
|
174
|
+
?.click();
|
|
175
|
+
document
|
|
176
|
+
.querySelector<HTMLElement>("[data-collection-page-action='edit']")
|
|
177
|
+
?.click();
|
|
178
|
+
await flushAsyncWork();
|
|
179
|
+
|
|
180
|
+
expect(reload).not.toHaveBeenCalled();
|
|
181
|
+
expect(window.location.pathname).toBe("/original-slug");
|
|
182
|
+
});
|
|
183
|
+
|
|
135
184
|
it("adds the collection to navigation and reveals the settings action", async () => {
|
|
136
185
|
createMarkup();
|
|
137
186
|
const fetchMock = vi.fn().mockResolvedValue(
|