@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
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import { afterEach, describe, expect, it } from "vitest";
|
|
4
4
|
import { Editor } from "@tiptap/core";
|
|
5
5
|
import { createMarkdownContentExtensions } from "../../../lib/markdown-manager.js";
|
|
6
|
-
import { ExitableMarks } from "../exitable-marks.js";
|
|
7
6
|
import { toggleMarkAndExit } from "../bubble-menu.js";
|
|
8
7
|
|
|
9
8
|
const editors: Editor[] = [];
|
|
@@ -13,7 +12,7 @@ function createEditor(content: string): Editor {
|
|
|
13
12
|
document.body.appendChild(element);
|
|
14
13
|
const editor = new Editor({
|
|
15
14
|
element,
|
|
16
|
-
extensions:
|
|
15
|
+
extensions: createMarkdownContentExtensions(),
|
|
17
16
|
content,
|
|
18
17
|
});
|
|
19
18
|
editor.view.dispatch(editor.state.tr);
|
|
@@ -150,6 +150,32 @@ describe("isCodeEditorHtml", () => {
|
|
|
150
150
|
});
|
|
151
151
|
|
|
152
152
|
describe("MarkdownClipboard", () => {
|
|
153
|
+
it("keeps pasted plain text that opens with a word and a period", () => {
|
|
154
|
+
const element = document.createElement("div");
|
|
155
|
+
document.body.appendChild(element);
|
|
156
|
+
const editor = createTiptapEditor({ element });
|
|
157
|
+
editors.push(editor);
|
|
158
|
+
|
|
159
|
+
dispatchMarkdownPaste(editor, "PS. 补充一句");
|
|
160
|
+
|
|
161
|
+
expect(editor.getJSON().content).toEqual([
|
|
162
|
+
{ type: "paragraph", content: [{ type: "text", text: "PS. 补充一句" }] },
|
|
163
|
+
]);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("turns pasted plain numbered lines into an ordered list", () => {
|
|
167
|
+
const element = document.createElement("div");
|
|
168
|
+
document.body.appendChild(element);
|
|
169
|
+
const editor = createTiptapEditor({ element });
|
|
170
|
+
editors.push(editor);
|
|
171
|
+
|
|
172
|
+
dispatchMarkdownPaste(editor, "1. First\n2. Second");
|
|
173
|
+
|
|
174
|
+
const list = editor.getJSON().content?.[0];
|
|
175
|
+
expect(list?.type).toBe("orderedList");
|
|
176
|
+
expect(list?.content).toHaveLength(2);
|
|
177
|
+
});
|
|
178
|
+
|
|
153
179
|
it("prefers an explicit text/markdown flavor over HTML and plain text", () => {
|
|
154
180
|
const element = document.createElement("div");
|
|
155
181
|
document.body.appendChild(element);
|
|
@@ -7,14 +7,23 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { Extension, type Editor } from "@tiptap/core";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
AllSelection,
|
|
12
|
+
Plugin,
|
|
13
|
+
PluginKey,
|
|
14
|
+
Selection,
|
|
15
|
+
TextSelection,
|
|
16
|
+
} from "@tiptap/pm/state";
|
|
11
17
|
import { liftTarget } from "@tiptap/pm/transform";
|
|
12
18
|
import type { EditorView } from "@tiptap/pm/view";
|
|
13
19
|
import { isLinkToolbarInputActive } from "./link-toolbar.js";
|
|
14
20
|
import type { FormattingToolbarMode } from "./toolbar-mode.js";
|
|
15
21
|
import {
|
|
16
22
|
getFixedFloatingContainerRect,
|
|
23
|
+
getFloatingArrowOffset,
|
|
17
24
|
getFloatingPosition,
|
|
25
|
+
getRangeAnchorRect,
|
|
26
|
+
getVisibleClipRect,
|
|
18
27
|
} from "./floating-position.js";
|
|
19
28
|
|
|
20
29
|
const bubbleMenuKey = new PluginKey("bubbleMenu");
|
|
@@ -315,20 +324,22 @@ export const BubbleMenu = Extension.create({
|
|
|
315
324
|
if (!el) return;
|
|
316
325
|
el.style.display = "flex";
|
|
317
326
|
|
|
318
|
-
//
|
|
327
|
+
// Anchor on the first visible line of the selection, centered on its
|
|
328
|
+
// horizontal span. Select-all lands on the text you are looking at
|
|
329
|
+
// rather than at the top of a document scrolled out of view.
|
|
319
330
|
const { from, to } = view.state.selection;
|
|
320
|
-
const start = view.coordsAtPos(from);
|
|
321
|
-
const end = view.coordsAtPos(to);
|
|
322
331
|
const dialog = view.dom.closest("dialog");
|
|
332
|
+
const containerRect = getFixedFloatingContainerRect(dialog);
|
|
333
|
+
const anchorRect = getRangeAnchorRect(
|
|
334
|
+
view,
|
|
335
|
+
from,
|
|
336
|
+
to,
|
|
337
|
+
getVisibleClipRect(view.dom, containerRect),
|
|
338
|
+
);
|
|
323
339
|
const rect = el.getBoundingClientRect();
|
|
324
340
|
const layout = getFloatingPosition({
|
|
325
|
-
anchorRect
|
|
326
|
-
|
|
327
|
-
right: end.right,
|
|
328
|
-
top: Math.min(start.top, end.top),
|
|
329
|
-
bottom: Math.max(start.bottom, end.bottom),
|
|
330
|
-
},
|
|
331
|
-
containerRect: getFixedFloatingContainerRect(dialog),
|
|
341
|
+
anchorRect,
|
|
342
|
+
containerRect,
|
|
332
343
|
floatingWidth: rect.width,
|
|
333
344
|
floatingHeight: rect.height,
|
|
334
345
|
preferredPlacement: "top",
|
|
@@ -338,6 +349,16 @@ export const BubbleMenu = Extension.create({
|
|
|
338
349
|
|
|
339
350
|
el.style.left = `${layout.left}px`;
|
|
340
351
|
el.style.top = `${layout.top}px`;
|
|
352
|
+
el.dataset.placement = layout.placement;
|
|
353
|
+
el.style.setProperty(
|
|
354
|
+
"--floating-arrow-x",
|
|
355
|
+
`${getFloatingArrowOffset({
|
|
356
|
+
anchorRect,
|
|
357
|
+
containerRect,
|
|
358
|
+
floatingLeft: layout.left,
|
|
359
|
+
floatingWidth: rect.width,
|
|
360
|
+
})}px`,
|
|
361
|
+
);
|
|
341
362
|
|
|
342
363
|
syncActive();
|
|
343
364
|
}
|
|
@@ -358,13 +379,23 @@ export const BubbleMenu = Extension.create({
|
|
|
358
379
|
function shouldShow(view: EditorView): boolean {
|
|
359
380
|
const { state } = view;
|
|
360
381
|
const { selection } = state;
|
|
361
|
-
|
|
362
|
-
if (!(selection instanceof TextSelection) || selection.empty)
|
|
363
|
-
return false;
|
|
364
|
-
if (!selection.$from.parent.isTextblock) return false;
|
|
382
|
+
if (selection.empty) return false;
|
|
365
383
|
// Hide when link input popup is open
|
|
366
384
|
if (isLinkToolbarInputActive()) return false;
|
|
367
|
-
|
|
385
|
+
|
|
386
|
+
// Select-all reports an AllSelection, not a TextSelection. Clearing
|
|
387
|
+
// formatting off a pasted draft and quoting a whole note are exactly
|
|
388
|
+
// what a document-wide selection is for, so it gets the same toolbar as
|
|
389
|
+
// dragging over everything — as long as there is text to format.
|
|
390
|
+
if (selection instanceof AllSelection) {
|
|
391
|
+
return (
|
|
392
|
+
state.doc.textBetween(selection.from, selection.to, " ").trim() !== ""
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Otherwise only non-empty text selections (not node selections)
|
|
397
|
+
if (!(selection instanceof TextSelection)) return false;
|
|
398
|
+
return selection.$from.parent.isTextblock;
|
|
368
399
|
}
|
|
369
400
|
|
|
370
401
|
return [
|
|
@@ -12,7 +12,6 @@ import { PasteMedia } from "./paste-media.js";
|
|
|
12
12
|
import type { PasteMediaOptions } from "./paste-media.js";
|
|
13
13
|
import { BubbleMenu } from "./bubble-menu.js";
|
|
14
14
|
import { LinkToolbar } from "./link-toolbar.js";
|
|
15
|
-
import { ExitableMarks } from "./exitable-marks.js";
|
|
16
15
|
import { CodeBlockIndent } from "./tab-indent.js";
|
|
17
16
|
import { StructuralKeymap } from "./structural-keymap.js";
|
|
18
17
|
import { ContinuousLists } from "./continuous-lists.js";
|
|
@@ -88,7 +87,6 @@ export function createSettingsEditorExtensions(
|
|
|
88
87
|
ReclaimModEnter,
|
|
89
88
|
LinkInputRules,
|
|
90
89
|
MarkdownClipboard,
|
|
91
|
-
ExitableMarks,
|
|
92
90
|
InsertParagraphAround,
|
|
93
91
|
StructuralKeymap,
|
|
94
92
|
ContinuousLists,
|
|
@@ -133,7 +131,6 @@ export function createEditorExtensions(
|
|
|
133
131
|
toolbarMode: options.toolbarMode ?? "default",
|
|
134
132
|
}),
|
|
135
133
|
LinkToolbar,
|
|
136
|
-
ExitableMarks,
|
|
137
134
|
InsertParagraphAround,
|
|
138
135
|
StructuralKeymap,
|
|
139
136
|
ContinuousLists,
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
import type { EditorView } from "@tiptap/pm/view";
|
|
2
|
+
|
|
3
|
+
export interface FloatingAnchorRect {
|
|
2
4
|
left: number;
|
|
3
5
|
right: number;
|
|
4
6
|
top: number;
|
|
5
7
|
bottom: number;
|
|
6
8
|
}
|
|
7
9
|
|
|
8
|
-
interface FloatingContainerRect {
|
|
10
|
+
export interface FloatingContainerRect {
|
|
9
11
|
left: number;
|
|
10
12
|
top: number;
|
|
11
13
|
width: number;
|
|
@@ -165,3 +167,215 @@ export function getFloatingPosition(
|
|
|
165
167
|
maxHeight,
|
|
166
168
|
};
|
|
167
169
|
}
|
|
170
|
+
|
|
171
|
+
/** Overflow values that clip a descendant out of view. */
|
|
172
|
+
const CLIPPING_OVERFLOW = new Set([
|
|
173
|
+
"auto",
|
|
174
|
+
"scroll",
|
|
175
|
+
"hidden",
|
|
176
|
+
"clip",
|
|
177
|
+
"overlay",
|
|
178
|
+
]);
|
|
179
|
+
|
|
180
|
+
function intersectRects(
|
|
181
|
+
a: FloatingContainerRect,
|
|
182
|
+
b: FloatingContainerRect,
|
|
183
|
+
): FloatingContainerRect {
|
|
184
|
+
const left = Math.max(a.left, b.left);
|
|
185
|
+
const top = Math.max(a.top, b.top);
|
|
186
|
+
const right = Math.min(a.left + a.width, b.left + b.width);
|
|
187
|
+
const bottom = Math.min(a.top + a.height, b.top + b.height);
|
|
188
|
+
return { left, top, width: right - left, height: bottom - top };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Narrows a floating container down to the region where the editor's content
|
|
193
|
+
* is actually on screen, by intersecting it with every clipping ancestor.
|
|
194
|
+
*
|
|
195
|
+
* The composer's editor sits inside its own scroll region, so a selection can
|
|
196
|
+
* run well past what the reader can see. Anchoring to the raw container would
|
|
197
|
+
* point the surface at text hidden behind the scroll edge; this keeps the
|
|
198
|
+
* anchor inside the scrollport.
|
|
199
|
+
*
|
|
200
|
+
* @param element - Editor element whose visible region is wanted
|
|
201
|
+
* @param containerRect - Bounds of the fixed-position floating container
|
|
202
|
+
* @returns Visible region in viewport coordinates
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* const clip = getVisibleClipRect(view.dom, getFixedFloatingContainerRect(dialog));
|
|
206
|
+
*/
|
|
207
|
+
export function getVisibleClipRect(
|
|
208
|
+
element: HTMLElement,
|
|
209
|
+
containerRect: FloatingContainerRect,
|
|
210
|
+
): FloatingContainerRect {
|
|
211
|
+
let clip = containerRect;
|
|
212
|
+
let node = element.parentElement;
|
|
213
|
+
|
|
214
|
+
while (node && node !== document.body) {
|
|
215
|
+
const style = getComputedStyle(node);
|
|
216
|
+
if (
|
|
217
|
+
CLIPPING_OVERFLOW.has(style.overflowY) ||
|
|
218
|
+
CLIPPING_OVERFLOW.has(style.overflowX)
|
|
219
|
+
) {
|
|
220
|
+
const rect = node.getBoundingClientRect();
|
|
221
|
+
const next = intersectRects(clip, {
|
|
222
|
+
left: rect.left,
|
|
223
|
+
top: rect.top,
|
|
224
|
+
width: rect.width,
|
|
225
|
+
height: rect.height,
|
|
226
|
+
});
|
|
227
|
+
// A zero-size intersection means the ancestor reported no layout at all
|
|
228
|
+
// (detached nodes, test environments). Keep the wider region instead of
|
|
229
|
+
// collapsing the anchor to nothing.
|
|
230
|
+
if (next.width > 0 && next.height > 0) clip = next;
|
|
231
|
+
}
|
|
232
|
+
node = node.parentElement;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return clip;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Reduces the line boxes of a selection to the rectangle a floating surface
|
|
240
|
+
* should point at.
|
|
241
|
+
*
|
|
242
|
+
* Horizontally this is the union of everything visible, so a selection that
|
|
243
|
+
* covers whole blocks centers on the text column rather than on wherever the
|
|
244
|
+
* first and last lines happen to break. Vertically it is the *first* visible
|
|
245
|
+
* line only: a selection running past the bottom of the screen still gets an
|
|
246
|
+
* anchor the reader can see, and flipping the surface below that line lands it
|
|
247
|
+
* inside the selection instead of somewhere off-screen.
|
|
248
|
+
*
|
|
249
|
+
* @param rects - Client rects of the selection, in viewport coordinates
|
|
250
|
+
* @param clipRect - Region the anchor must stay inside
|
|
251
|
+
* @returns Anchor rectangle, or null when nothing is visible
|
|
252
|
+
*
|
|
253
|
+
* @example
|
|
254
|
+
* const anchor = getVisibleAnchorRect(range.getClientRects(), clipRect);
|
|
255
|
+
*/
|
|
256
|
+
export function getVisibleAnchorRect(
|
|
257
|
+
rects: readonly FloatingAnchorRect[],
|
|
258
|
+
clipRect: FloatingContainerRect,
|
|
259
|
+
): FloatingAnchorRect | null {
|
|
260
|
+
const clipRight = clipRect.left + clipRect.width;
|
|
261
|
+
const clipBottom = clipRect.top + clipRect.height;
|
|
262
|
+
|
|
263
|
+
let left = Number.POSITIVE_INFINITY;
|
|
264
|
+
let right = Number.NEGATIVE_INFINITY;
|
|
265
|
+
let top = Number.POSITIVE_INFINITY;
|
|
266
|
+
let bottom = Number.POSITIVE_INFINITY;
|
|
267
|
+
let found = false;
|
|
268
|
+
|
|
269
|
+
for (const rect of rects) {
|
|
270
|
+
const rectTop = Math.max(rect.top, clipRect.top);
|
|
271
|
+
const rectBottom = Math.min(rect.bottom, clipBottom);
|
|
272
|
+
if (rectBottom <= rectTop) continue;
|
|
273
|
+
const rectLeft = Math.max(rect.left, clipRect.left);
|
|
274
|
+
const rectRight = Math.min(rect.right, clipRight);
|
|
275
|
+
if (rectRight < rectLeft) continue;
|
|
276
|
+
|
|
277
|
+
left = Math.min(left, rectLeft);
|
|
278
|
+
right = Math.max(right, rectRight);
|
|
279
|
+
top = Math.min(top, rectTop);
|
|
280
|
+
// Every kept rect ends below `top`, so the smallest bottom belongs to the
|
|
281
|
+
// shortest box starting there — the first line, even when the browser also
|
|
282
|
+
// reports a full-height rect for an enclosing block.
|
|
283
|
+
bottom = Math.min(bottom, rectBottom);
|
|
284
|
+
found = true;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return found ? { left, right, top, bottom } : null;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function measureRangeRects(
|
|
291
|
+
view: EditorView,
|
|
292
|
+
from: number,
|
|
293
|
+
to: number,
|
|
294
|
+
): FloatingAnchorRect[] {
|
|
295
|
+
if (typeof document === "undefined" || !document.createRange) return [];
|
|
296
|
+
|
|
297
|
+
try {
|
|
298
|
+
const start = view.domAtPos(from);
|
|
299
|
+
const end = view.domAtPos(to);
|
|
300
|
+
const range = document.createRange();
|
|
301
|
+
range.setStart(start.node, start.offset);
|
|
302
|
+
range.setEnd(end.node, end.offset);
|
|
303
|
+
const rects = range.getClientRects?.();
|
|
304
|
+
return rects ? Array.from(rects) : [];
|
|
305
|
+
} catch {
|
|
306
|
+
// Positions that do not resolve to a DOM range (node selections, widgets)
|
|
307
|
+
// fall back to the caller's endpoint coordinates.
|
|
308
|
+
return [];
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Resolves the rectangle a floating surface should point at for a document
|
|
314
|
+
* range.
|
|
315
|
+
*
|
|
316
|
+
* Measuring the caret at each end is only correct while the range stays on one
|
|
317
|
+
* line: as soon as it wraps, the "anchor" mixes the first line's left edge with
|
|
318
|
+
* the last line's right edge and centers on a point inside neither. This
|
|
319
|
+
* measures the range's real line boxes instead.
|
|
320
|
+
*
|
|
321
|
+
* @param view - Editor view owning the range
|
|
322
|
+
* @param from - Range start position
|
|
323
|
+
* @param to - Range end position
|
|
324
|
+
* @param clipRect - Region the anchor must stay inside
|
|
325
|
+
* @returns Anchor rectangle in viewport coordinates
|
|
326
|
+
*
|
|
327
|
+
* @example
|
|
328
|
+
* const anchor = getRangeAnchorRect(view, from, to, clipRect);
|
|
329
|
+
*/
|
|
330
|
+
export function getRangeAnchorRect(
|
|
331
|
+
view: EditorView,
|
|
332
|
+
from: number,
|
|
333
|
+
to: number,
|
|
334
|
+
clipRect: FloatingContainerRect,
|
|
335
|
+
): FloatingAnchorRect {
|
|
336
|
+
const measured = getVisibleAnchorRect(
|
|
337
|
+
measureRangeRects(view, from, to),
|
|
338
|
+
clipRect,
|
|
339
|
+
);
|
|
340
|
+
if (measured) return measured;
|
|
341
|
+
|
|
342
|
+
const start = view.coordsAtPos(from);
|
|
343
|
+
const end = view.coordsAtPos(to);
|
|
344
|
+
const endpoints: FloatingAnchorRect = {
|
|
345
|
+
left: Math.min(start.left, end.left),
|
|
346
|
+
right: Math.max(start.right, end.right),
|
|
347
|
+
top: start.top,
|
|
348
|
+
bottom: start.bottom,
|
|
349
|
+
};
|
|
350
|
+
return getVisibleAnchorRect([endpoints], clipRect) ?? endpoints;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Horizontal offset of a floating surface's pointer arrow, measured from the
|
|
355
|
+
* surface's own left edge.
|
|
356
|
+
*
|
|
357
|
+
* The surface is clamped to the container, so a centered arrow drifts off the
|
|
358
|
+
* anchor near the edges of the screen. This keeps it on the anchor without
|
|
359
|
+
* letting it slide past the rounded corners.
|
|
360
|
+
*
|
|
361
|
+
* @param options - Anchor, container, and the surface's placed geometry
|
|
362
|
+
* @returns Offset in pixels from the surface's left edge
|
|
363
|
+
*
|
|
364
|
+
* @example
|
|
365
|
+
* el.style.setProperty("--floating-arrow-x", `${getFloatingArrowOffset({...})}px`);
|
|
366
|
+
*/
|
|
367
|
+
export function getFloatingArrowOffset(options: {
|
|
368
|
+
anchorRect: FloatingAnchorRect;
|
|
369
|
+
containerRect: FloatingContainerRect;
|
|
370
|
+
floatingLeft: number;
|
|
371
|
+
floatingWidth: number;
|
|
372
|
+
inset?: number;
|
|
373
|
+
}): number {
|
|
374
|
+
const inset = options.inset ?? 10;
|
|
375
|
+
const anchorCenter =
|
|
376
|
+
(options.anchorRect.left + options.anchorRect.right) / 2 -
|
|
377
|
+
options.containerRect.left;
|
|
378
|
+
const min = Math.min(inset, options.floatingWidth / 2);
|
|
379
|
+
const max = Math.max(options.floatingWidth - inset, min);
|
|
380
|
+
return clamp(anchorCenter - options.floatingLeft, min, max);
|
|
381
|
+
}
|
|
@@ -204,7 +204,9 @@ export async function rehostInlineImage(
|
|
|
204
204
|
* Scans the editor's document for image srcs that match pending registry entries
|
|
205
205
|
* (`blob:` placeholders from the insert flow, or remote/`data:` placeholders
|
|
206
206
|
* from the paste-rehost flow). For each match, takes ownership from the original
|
|
207
|
-
* editor and sets up replacement/removal watchers on the new editor.
|
|
207
|
+
* editor and sets up replacement/removal watchers on the new editor. Content can
|
|
208
|
+
* pass through several editors before it is submitted, so an upload adopted once
|
|
209
|
+
* can be adopted again.
|
|
208
210
|
*
|
|
209
211
|
* Call this immediately after `setContent` with JSON that may contain pending
|
|
210
212
|
* placeholders (e.g. after fullscreen close transfers content back to compose).
|
|
@@ -223,12 +225,14 @@ export function adoptPendingInlineImageUploads(
|
|
|
223
225
|
const src = node.attrs.src as string;
|
|
224
226
|
if (typeof src !== "string") return;
|
|
225
227
|
|
|
226
|
-
const uploaded = inflightUploads.get(src);
|
|
228
|
+
const uploaded = inflightUploads.get(src) ?? adoptedUploads.get(src);
|
|
227
229
|
if (!uploaded) return;
|
|
228
230
|
|
|
229
231
|
// Take ownership — prevents original editor's finally from revoking the URL.
|
|
230
232
|
// Move to adoptedUploads so resolveInlineImageUrls can still find the promise
|
|
231
|
-
// if the user submits before the upload completes.
|
|
233
|
+
// if the user submits before the upload completes. Content can be handed on
|
|
234
|
+
// more than once (fullscreen → compose → a format switch), so an entry
|
|
235
|
+
// already sitting in adoptedUploads is adopted again rather than skipped.
|
|
232
236
|
inflightUploads.delete(src);
|
|
233
237
|
adoptedUploads.set(src, uploaded);
|
|
234
238
|
|
|
@@ -15,7 +15,10 @@ import type { EditorState } from "@tiptap/pm/state";
|
|
|
15
15
|
import type { EditorView } from "@tiptap/pm/view";
|
|
16
16
|
import {
|
|
17
17
|
getFixedFloatingContainerRect,
|
|
18
|
+
getFloatingArrowOffset,
|
|
18
19
|
getFloatingPosition,
|
|
20
|
+
getRangeAnchorRect,
|
|
21
|
+
getVisibleClipRect,
|
|
19
22
|
} from "./floating-position.js";
|
|
20
23
|
|
|
21
24
|
const linkToolbarKey = new PluginKey("linkToolbar");
|
|
@@ -195,17 +198,17 @@ export const LinkToolbar = Extension.create({
|
|
|
195
198
|
el.style.display = "flex";
|
|
196
199
|
|
|
197
200
|
const dialog = view.dom.closest("dialog");
|
|
198
|
-
const
|
|
199
|
-
const
|
|
201
|
+
const containerRect = getFixedFloatingContainerRect(dialog);
|
|
202
|
+
const anchorRect = getRangeAnchorRect(
|
|
203
|
+
view,
|
|
204
|
+
from,
|
|
205
|
+
to,
|
|
206
|
+
getVisibleClipRect(view.dom, containerRect),
|
|
207
|
+
);
|
|
200
208
|
const rect = el.getBoundingClientRect();
|
|
201
209
|
const layout = getFloatingPosition({
|
|
202
|
-
anchorRect
|
|
203
|
-
|
|
204
|
-
right: end.right,
|
|
205
|
-
top: Math.min(start.top, end.top),
|
|
206
|
-
bottom: Math.max(start.bottom, end.bottom),
|
|
207
|
-
},
|
|
208
|
-
containerRect: getFixedFloatingContainerRect(dialog),
|
|
210
|
+
anchorRect,
|
|
211
|
+
containerRect,
|
|
209
212
|
floatingWidth: rect.width,
|
|
210
213
|
floatingHeight: rect.height,
|
|
211
214
|
preferredPlacement: "top",
|
|
@@ -215,6 +218,16 @@ export const LinkToolbar = Extension.create({
|
|
|
215
218
|
|
|
216
219
|
el.style.left = `${layout.left}px`;
|
|
217
220
|
el.style.top = `${layout.top}px`;
|
|
221
|
+
el.dataset.placement = layout.placement;
|
|
222
|
+
el.style.setProperty(
|
|
223
|
+
"--floating-arrow-x",
|
|
224
|
+
`${getFloatingArrowOffset({
|
|
225
|
+
anchorRect,
|
|
226
|
+
containerRect,
|
|
227
|
+
floatingLeft: layout.left,
|
|
228
|
+
floatingWidth: rect.width,
|
|
229
|
+
})}px`,
|
|
230
|
+
);
|
|
218
231
|
}
|
|
219
232
|
|
|
220
233
|
interface ShowInputOptions {
|