@lapis-notes/api 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/LICENSE.md +4 -0
- package/README.md +43 -0
- package/dist/__tests__/data-adapter-conformance.js +205 -0
- package/dist/__tests__/lapis-runtime-manifest.js +32 -0
- package/dist/__tests__/svelte-internal-client.d.ts +3 -0
- package/dist/agent-result-views.d.ts +52 -0
- package/dist/agent-result-views.js +89 -0
- package/dist/agent-skills.d.ts +99 -0
- package/dist/agent-skills.js +182 -0
- package/dist/agent-tools.d.ts +92 -0
- package/dist/agent-tools.js +139 -0
- package/dist/app-database-host.d.ts +4 -0
- package/dist/app-database-host.js +4 -0
- package/dist/app-shell-plugin-persistence.d.ts +15 -0
- package/dist/app-shell-plugin-persistence.js +34 -0
- package/dist/app-url.d.ts +54 -0
- package/dist/app-url.js +269 -0
- package/dist/application-compatibility.d.ts +31 -0
- package/dist/application-compatibility.js +64 -0
- package/dist/application-state.svelte.d.ts +14 -0
- package/dist/application-state.svelte.js +30 -0
- package/dist/assets/lapis-logo.svg +471 -0
- package/dist/bases.d.ts +281 -0
- package/dist/bases.js +475 -0
- package/dist/cache.svelte.d.ts +569 -0
- package/dist/cache.svelte.js +1433 -0
- package/dist/command.svelte.d.ts +337 -0
- package/dist/command.svelte.js +571 -0
- package/dist/compat.d.ts +29 -0
- package/dist/compat.js +30 -0
- package/dist/components/configuration/configuration.css +199 -0
- package/dist/components/configuration/date-setting.svelte +71 -0
- package/dist/components/configuration/date-setting.svelte.d.ts +8 -0
- package/dist/components/configuration/list-setting.svelte +128 -0
- package/dist/components/configuration/list-setting.svelte.d.ts +13 -0
- package/dist/components/configuration/object-array-row-grip.svelte +78 -0
- package/dist/components/configuration/object-array-row-grip.svelte.d.ts +9 -0
- package/dist/components/configuration/object-array-row.svelte +163 -0
- package/dist/components/configuration/object-array-row.svelte.d.ts +19 -0
- package/dist/components/configuration/object-array-setting.svelte +196 -0
- package/dist/components/configuration/object-array-setting.svelte.d.ts +15 -0
- package/dist/components/configuration/object-array-table-dnd.d.ts +8 -0
- package/dist/components/configuration/object-array-table-dnd.js +10 -0
- package/dist/components/configuration/object-array-utils.d.ts +11 -0
- package/dist/components/configuration/object-array-utils.js +103 -0
- package/dist/components/configuration/object-grid-setting.svelte +79 -0
- package/dist/components/configuration/object-grid-setting.svelte.d.ts +10 -0
- package/dist/components/configuration/object-map-row.svelte +147 -0
- package/dist/components/configuration/object-map-row.svelte.d.ts +15 -0
- package/dist/components/configuration/object-map-setting.svelte +137 -0
- package/dist/components/configuration/object-map-setting.svelte.d.ts +14 -0
- package/dist/components/configuration/object-map-types.d.ts +5 -0
- package/dist/components/configuration/object-map-types.js +1 -0
- package/dist/components/configuration/options-combobox-setting.svelte +129 -0
- package/dist/components/configuration/options-combobox-setting.svelte.d.ts +12 -0
- package/dist/components/copy-button/button.svelte +93 -0
- package/dist/components/copy-button/button.svelte.d.ts +13 -0
- package/dist/components/copy-button/index.d.ts +2 -0
- package/dist/components/copy-button/index.js +2 -0
- package/dist/components/editor/editor.css +565 -0
- package/dist/components/editor/editor.d.ts +33 -0
- package/dist/components/editor/editor.js +132 -0
- package/dist/components/editor/editor.svelte +176 -0
- package/dist/components/editor/editor.svelte.d.ts +15 -0
- package/dist/components/editor/embedded-editor-surface.d.ts +12 -0
- package/dist/components/editor/embedded-editor-surface.js +22 -0
- package/dist/components/editor/embedded-editor-surface.svelte +115 -0
- package/dist/components/editor/embedded-editor-surface.svelte.d.ts +17 -0
- package/dist/components/editor/extensions/autocomplete/index.d.ts +2 -0
- package/dist/components/editor/extensions/autocomplete/index.js +2 -0
- package/dist/components/editor/extensions/autocomplete/lapis-code-mirror-autocomplete.d.ts +16 -0
- package/dist/components/editor/extensions/autocomplete/lapis-code-mirror-autocomplete.js +34 -0
- package/dist/components/editor/extensions/autocomplete/vault-completion-sources.d.ts +27 -0
- package/dist/components/editor/extensions/autocomplete/vault-completion-sources.js +55 -0
- package/dist/components/editor/extensions/class-highlighter/index.d.ts +1 -0
- package/dist/components/editor/extensions/class-highlighter/index.js +47 -0
- package/dist/components/editor/extensions/fold-gutter/index.d.ts +8 -0
- package/dist/components/editor/extensions/fold-gutter/index.js +40 -0
- package/dist/components/editor/extensions/gutter-line-style-sync/index.d.ts +2 -0
- package/dist/components/editor/extensions/gutter-line-style-sync/index.js +118 -0
- package/dist/components/editor/extensions/lint/index.d.ts +4 -0
- package/dist/components/editor/extensions/lint/index.js +4 -0
- package/dist/components/editor/extensions/lint/lapis-code-mirror-lint.d.ts +26 -0
- package/dist/components/editor/extensions/lint/lapis-code-mirror-lint.js +101 -0
- package/dist/components/editor/extensions/lint/lapis-lint-diagnostic-helpers.d.ts +8 -0
- package/dist/components/editor/extensions/lint/lapis-lint-diagnostic-helpers.js +7 -0
- package/dist/components/editor/extensions/lint/lapis-lint-diagnostic.d.ts +20 -0
- package/dist/components/editor/extensions/lint/lapis-lint-diagnostic.js +47 -0
- package/dist/components/editor/extensions/lint/lapis-lint-hover-tooltip.d.ts +9 -0
- package/dist/components/editor/extensions/lint/lapis-lint-hover-tooltip.js +373 -0
- package/dist/components/editor/extensions/lint/lapis-lint-inline-widget.d.ts +31 -0
- package/dist/components/editor/extensions/lint/lapis-lint-inline-widget.js +191 -0
- package/dist/components/editor/extensions/lint/lapis-lint-tooltip.svelte +176 -0
- package/dist/components/editor/extensions/lint/lapis-lint-tooltip.svelte.d.ts +15 -0
- package/dist/components/editor/extensions/lint/lint-tooltip-actions.d.ts +11 -0
- package/dist/components/editor/extensions/lint/lint-tooltip-actions.js +21 -0
- package/dist/components/editor/extensions/lint/mount-lint-tooltip.d.ts +16 -0
- package/dist/components/editor/extensions/lint/mount-lint-tooltip.js +60 -0
- package/dist/components/editor/extensions/search/dom.d.ts +2 -0
- package/dist/components/editor/extensions/search/dom.js +6 -0
- package/dist/components/editor/extensions/search/index.d.ts +35 -0
- package/dist/components/editor/extensions/search/index.js +44 -0
- package/dist/components/editor/extensions/search/search.svelte +335 -0
- package/dist/components/editor/extensions/search/search.svelte.d.ts +8 -0
- package/dist/components/editor/index.d.ts +9 -0
- package/dist/components/editor/index.js +9 -0
- package/dist/components/editor/language-service/dom.d.ts +1 -0
- package/dist/components/editor/language-service/dom.js +6 -0
- package/dist/components/editor/language-service/index.d.ts +28 -0
- package/dist/components/editor/language-service/index.js +342 -0
- package/dist/components/empty-view/empty-view.css +39 -0
- package/dist/components/empty-view/empty-view.svelte +26 -0
- package/dist/components/empty-view/empty-view.svelte.d.ts +9 -0
- package/dist/components/empty-view/index.d.ts +2 -0
- package/dist/components/empty-view/index.js +2 -0
- package/dist/components/icon/icon-list.css +74 -0
- package/dist/components/icon/icon-list.svelte +213 -0
- package/dist/components/icon/icon-list.svelte.d.ts +11 -0
- package/dist/components/icon/icon.svelte +15 -0
- package/dist/components/icon/icon.svelte.d.ts +7 -0
- package/dist/components/icon/index.d.ts +3 -0
- package/dist/components/icon/index.js +3 -0
- package/dist/components/menu/context-menu.svelte +89 -0
- package/dist/components/menu/context-menu.svelte.d.ts +11 -0
- package/dist/components/menu/drawer-menu.svelte +83 -0
- package/dist/components/menu/drawer-menu.svelte.d.ts +8 -0
- package/dist/components/menu/dropdown-menu.svelte +89 -0
- package/dist/components/menu/dropdown-menu.svelte.d.ts +11 -0
- package/dist/components/menu/index.d.ts +6 -0
- package/dist/components/menu/index.js +6 -0
- package/dist/components/menu/menu.css +139 -0
- package/dist/components/menu/menu.svelte +37 -0
- package/dist/components/menu/menu.svelte.d.ts +11 -0
- package/dist/components/menu/mobile-menu-query.svelte.d.ts +3 -0
- package/dist/components/menu/mobile-menu-query.svelte.js +21 -0
- package/dist/components/menu/popover-menu.svelte +90 -0
- package/dist/components/menu/popover-menu.svelte.d.ts +10 -0
- package/dist/components/select/index.d.ts +2 -0
- package/dist/components/select/index.js +2 -0
- package/dist/components/select/select.svelte +72 -0
- package/dist/components/select/select.svelte.d.ts +25 -0
- package/dist/configuration-option-source-providers.d.ts +17 -0
- package/dist/configuration-option-source-providers.js +59 -0
- package/dist/configuration-option-source-registry.d.ts +59 -0
- package/dist/configuration-option-source-registry.js +123 -0
- package/dist/configuration.svelte.d.ts +258 -0
- package/dist/configuration.svelte.js +887 -0
- package/dist/context-keys.svelte.d.ts +42 -0
- package/dist/context-keys.svelte.js +278 -0
- package/dist/context.svelte.d.ts +247 -0
- package/dist/context.svelte.js +540 -0
- package/dist/daily-document-provider.d.ts +38 -0
- package/dist/daily-document-provider.js +176 -0
- package/dist/design-core-button-types.d.ts +7 -0
- package/dist/design-core-button-types.js +1 -0
- package/dist/diagnostics.d.ts +93 -0
- package/dist/diagnostics.js +84 -0
- package/dist/dialog-portal.d.ts +3 -0
- package/dist/dialog-portal.js +3 -0
- package/dist/docs-api.d.ts +44 -0
- package/dist/docs-api.js +29 -0
- package/dist/editor-view-registry.d.ts +35 -0
- package/dist/editor-view-registry.js +112 -0
- package/dist/editor.svelte.d.ts +126 -0
- package/dist/editor.svelte.js +454 -0
- package/dist/embed-registry.d.ts +15 -0
- package/dist/embed-registry.js +16 -0
- package/dist/enhance.d.ts +111 -0
- package/dist/enhance.js +309 -0
- package/dist/events.d.ts +28 -0
- package/dist/events.js +49 -0
- package/dist/file-manager.d.ts +90 -0
- package/dist/file-manager.js +289 -0
- package/dist/file-tools/app-tools.d.ts +3 -0
- package/dist/file-tools/app-tools.js +242 -0
- package/dist/file-tools/index.d.ts +3 -0
- package/dist/file-tools/index.js +3 -0
- package/dist/file-tools/paths.d.ts +14 -0
- package/dist/file-tools/paths.js +33 -0
- package/dist/file-tools/vault-operations.d.ts +3 -0
- package/dist/file-tools/vault-operations.js +60 -0
- package/dist/generated/plugin-host-modules.schema.generated.d.ts +371 -0
- package/dist/generated/plugin-host-modules.schema.generated.js +374 -0
- package/dist/glob.d.ts +15 -0
- package/dist/glob.js +68 -0
- package/dist/history.svelte.d.ts +24 -0
- package/dist/history.svelte.js +74 -0
- package/dist/hooks/createVirtualizer.svelte.d.ts +7 -0
- package/dist/hooks/createVirtualizer.svelte.js +70 -0
- package/dist/hooks/index.d.ts +5 -0
- package/dist/hooks/index.js +5 -0
- package/dist/hooks/is-mobile.svelte.d.ts +5 -0
- package/dist/hooks/is-mobile.svelte.js +23 -0
- package/dist/hooks/mountComponent.svelte.d.ts +7 -0
- package/dist/hooks/mountComponent.svelte.js +16 -0
- package/dist/hooks/use-text-highlight.d.ts +17 -0
- package/dist/hooks/use-text-highlight.js +82 -0
- package/dist/hooks/useBoundingRect.svelte.d.ts +11 -0
- package/dist/hooks/useBoundingRect.svelte.js +46 -0
- package/dist/icons/index.d.ts +57 -0
- package/dist/icons/index.js +288 -0
- package/dist/index.d.ts +87 -0
- package/dist/index.js +87 -0
- package/dist/language-service/index.d.ts +9 -0
- package/dist/language-service/index.js +7 -0
- package/dist/language-service/manager.d.ts +47 -0
- package/dist/language-service/manager.js +404 -0
- package/dist/language-service/types.d.ts +92 -0
- package/dist/language-service/types.js +1 -0
- package/dist/language-service/virtual-document.d.ts +11 -0
- package/dist/language-service/virtual-document.js +45 -0
- package/dist/language-service/worker-protocol.d.ts +63 -0
- package/dist/language-service/worker-protocol.js +1 -0
- package/dist/language-service/worker-provider.d.ts +9 -0
- package/dist/language-service/worker-provider.js +38 -0
- package/dist/lapis-extension.d.ts +244 -0
- package/dist/lapis-extension.js +591 -0
- package/dist/links.d.ts +34 -0
- package/dist/links.js +191 -0
- package/dist/loaders.d.ts +9 -0
- package/dist/loaders.js +59 -0
- package/dist/localization-manager.svelte.d.ts +56 -0
- package/dist/localization-manager.svelte.js +157 -0
- package/dist/logging.d.ts +172 -0
- package/dist/logging.js +352 -0
- package/dist/markdown-extension-registry.d.ts +31 -0
- package/dist/markdown-extension-registry.js +31 -0
- package/dist/markdown-file-surface-registry.d.ts +25 -0
- package/dist/markdown-file-surface-registry.js +23 -0
- package/dist/markdown.d.ts +205 -0
- package/dist/markdown.js +147 -0
- package/dist/menu-utils.d.ts +1 -0
- package/dist/menu-utils.js +47 -0
- package/dist/menu.svelte.d.ts +121 -0
- package/dist/menu.svelte.js +295 -0
- package/dist/metadata-value.d.ts +10 -0
- package/dist/metadata-value.js +173 -0
- package/dist/metadata.svelte.d.ts +106 -0
- package/dist/metadata.svelte.js +608 -0
- package/dist/notifications.d.ts +209 -0
- package/dist/notifications.js +338 -0
- package/dist/open-file.d.ts +23 -0
- package/dist/open-file.js +102 -0
- package/dist/platform.d.ts +86 -0
- package/dist/platform.js +86 -0
- package/dist/plugin-asset-server.d.ts +60 -0
- package/dist/plugin-asset-server.js +222 -0
- package/dist/plugin-assets.d.ts +4 -0
- package/dist/plugin-assets.js +3 -0
- package/dist/plugin-capability-facade.d.ts +104 -0
- package/dist/plugin-capability-facade.js +61 -0
- package/dist/plugin-dependency-resolver.d.ts +36 -0
- package/dist/plugin-dependency-resolver.js +54 -0
- package/dist/plugin-dependency-scanner.d.ts +9 -0
- package/dist/plugin-dependency-scanner.js +274 -0
- package/dist/plugin-distribution/canonical-json.d.ts +1 -0
- package/dist/plugin-distribution/canonical-json.js +40 -0
- package/dist/plugin-distribution/compatibility.d.ts +5 -0
- package/dist/plugin-distribution/compatibility.js +38 -0
- package/dist/plugin-distribution/errors.d.ts +11 -0
- package/dist/plugin-distribution/errors.js +13 -0
- package/dist/plugin-distribution/hashes.d.ts +2 -0
- package/dist/plugin-distribution/hashes.js +17 -0
- package/dist/plugin-distribution/index.d.ts +18 -0
- package/dist/plugin-distribution/index.js +18 -0
- package/dist/plugin-distribution/installed-plugin-state.d.ts +20 -0
- package/dist/plugin-distribution/installed-plugin-state.js +73 -0
- package/dist/plugin-distribution/installer.d.ts +89 -0
- package/dist/plugin-distribution/installer.js +395 -0
- package/dist/plugin-distribution/manager.d.ts +128 -0
- package/dist/plugin-distribution/manager.js +358 -0
- package/dist/plugin-distribution/path-safety.d.ts +2 -0
- package/dist/plugin-distribution/path-safety.js +20 -0
- package/dist/plugin-distribution/plugin-bundle-verifier.d.ts +28 -0
- package/dist/plugin-distribution/plugin-bundle-verifier.js +96 -0
- package/dist/plugin-distribution/plugin-bundle-worker.d.ts +34 -0
- package/dist/plugin-distribution/plugin-bundle-worker.js +99 -0
- package/dist/plugin-distribution/plugin-bundle.d.ts +16 -0
- package/dist/plugin-distribution/plugin-bundle.js +244 -0
- package/dist/plugin-distribution/plugin-bundle.worker.d.ts +1 -0
- package/dist/plugin-distribution/plugin-bundle.worker.js +38 -0
- package/dist/plugin-distribution/registry-cache.d.ts +26 -0
- package/dist/plugin-distribution/registry-cache.js +85 -0
- package/dist/plugin-distribution/registry-client.d.ts +36 -0
- package/dist/plugin-distribution/registry-client.js +115 -0
- package/dist/plugin-distribution/reserved-ids.d.ts +4 -0
- package/dist/plugin-distribution/reserved-ids.js +10 -0
- package/dist/plugin-distribution/runtime-validation.d.ts +14 -0
- package/dist/plugin-distribution/runtime-validation.js +388 -0
- package/dist/plugin-distribution/schemas.d.ts +4895 -0
- package/dist/plugin-distribution/schemas.js +299 -0
- package/dist/plugin-distribution/signing.d.ts +5 -0
- package/dist/plugin-distribution/signing.js +38 -0
- package/dist/plugin-distribution/types.d.ts +217 -0
- package/dist/plugin-distribution/types.js +1 -0
- package/dist/plugin-install-progress.d.ts +15 -0
- package/dist/plugin-install-progress.js +86 -0
- package/dist/plugin-manager.d.ts +306 -0
- package/dist/plugin-manager.js +2848 -0
- package/dist/plugin-runtime-entry.d.ts +19 -0
- package/dist/plugin-runtime-entry.js +135 -0
- package/dist/plugin.d.ts +350 -0
- package/dist/plugin.js +754 -0
- package/dist/popover.d.ts +20 -0
- package/dist/popover.js +32 -0
- package/dist/promise-worker.d.ts +22 -0
- package/dist/promise-worker.js +65 -0
- package/dist/prompt-confirm.d.ts +9 -0
- package/dist/prompt-confirm.js +33 -0
- package/dist/search-document-provider.d.ts +45 -0
- package/dist/search-document-provider.js +54 -0
- package/dist/search-query/index.d.ts +78 -0
- package/dist/search-query/index.js +426 -0
- package/dist/search-query/parser.d.ts +3 -0
- package/dist/search-query/parser.js +16 -0
- package/dist/search-query/parser.terms.js +26 -0
- package/dist/search-query/search-query.grammar +128 -0
- package/dist/search.d.ts +19 -0
- package/dist/search.js +104 -0
- package/dist/setting-section-layout.d.ts +19 -0
- package/dist/setting-section-layout.js +194 -0
- package/dist/settings.svelte.d.ts +474 -0
- package/dist/settings.svelte.js +1421 -0
- package/dist/source-text-file-view.d.ts +25 -0
- package/dist/source-text-file-view.js +78 -0
- package/dist/status-bar.svelte.d.ts +29 -0
- package/dist/status-bar.svelte.js +72 -0
- package/dist/storage/app-database.d.ts +860 -0
- package/dist/storage/app-database.js +2372 -0
- package/dist/storage/blob-store.d.ts +12 -0
- package/dist/storage/blob-store.js +1 -0
- package/dist/storage/browser-app-database-coordination.d.ts +35 -0
- package/dist/storage/browser-app-database-coordination.js +207 -0
- package/dist/storage/browser-coordinated-app-database.d.ts +109 -0
- package/dist/storage/browser-coordinated-app-database.js +704 -0
- package/dist/storage/browser.d.ts +153 -0
- package/dist/storage/browser.js +794 -0
- package/dist/storage/desktop-native.d.ts +184 -0
- package/dist/storage/desktop-native.js +415 -0
- package/dist/storage/fs.d.ts +188 -0
- package/dist/storage/fs.js +307 -0
- package/dist/storage/index-projection.d.ts +236 -0
- package/dist/storage/index-projection.js +402 -0
- package/dist/storage/index.d.ts +17 -0
- package/dist/storage/index.js +17 -0
- package/dist/storage/keyval.d.ts +94 -0
- package/dist/storage/keyval.js +199 -0
- package/dist/storage/memory-vault-adapter.d.ts +44 -0
- package/dist/storage/memory-vault-adapter.js +351 -0
- package/dist/storage/native-vault-bootstrap-kv.d.ts +22 -0
- package/dist/storage/native-vault-bootstrap-kv.js +96 -0
- package/dist/storage/path.d.ts +22 -0
- package/dist/storage/path.js +110 -0
- package/dist/storage/search-embedding-provider.d.ts +75 -0
- package/dist/storage/search-embedding-provider.js +446 -0
- package/dist/storage/search-embedding-worker.d.ts +1 -0
- package/dist/storage/search-embedding-worker.js +63 -0
- package/dist/storage/search-query-evaluator.d.ts +14 -0
- package/dist/storage/search-query-evaluator.js +678 -0
- package/dist/storage/task-projection.d.ts +152 -0
- package/dist/storage/task-projection.js +154 -0
- package/dist/storage/turso-app-database.d.ts +150 -0
- package/dist/storage/turso-app-database.js +2671 -0
- package/dist/storage/vault-session.d.ts +37 -0
- package/dist/storage/vault-session.js +196 -0
- package/dist/storage/vault-state.d.ts +74 -0
- package/dist/storage/vault-state.js +181 -0
- package/dist/storage/vault.svelte.d.ts +257 -0
- package/dist/storage/vault.svelte.js +661 -0
- package/dist/suggest.d.ts +193 -0
- package/dist/suggest.js +391 -0
- package/dist/tasks.d.ts +7 -0
- package/dist/tasks.js +18 -0
- package/dist/telemetry.d.ts +116 -0
- package/dist/telemetry.js +103 -0
- package/dist/utils.d.ts +55 -0
- package/dist/utils.js +314 -0
- package/dist/vault-api.d.ts +5 -0
- package/dist/vault-api.js +3 -0
- package/dist/vault-maintenance-commands.d.ts +5 -0
- package/dist/vault-maintenance-commands.js +32 -0
- package/dist/view.svelte.d.ts +215 -0
- package/dist/view.svelte.js +381 -0
- package/dist/vite-env.d.ts +2 -0
- package/dist/workspace-host-internal.d.ts +7 -0
- package/dist/workspace-host-internal.js +7 -0
- package/dist/workspace-host.d.ts +10 -0
- package/dist/workspace-host.js +14 -0
- package/dist/workspace-layout-commands.d.ts +3 -0
- package/dist/workspace-layout-commands.js +72 -0
- package/dist/workspace-layout-normalizer.d.ts +92 -0
- package/dist/workspace-layout-normalizer.js +301 -0
- package/dist/workspace-layouts.d.ts +7 -0
- package/dist/workspace-layouts.js +23 -0
- package/dist/workspace-trust.d.ts +32 -0
- package/dist/workspace-trust.js +72 -0
- package/dist/workspace.svelte.d.ts +796 -0
- package/dist/workspace.svelte.js +4380 -0
- package/package.json +240 -0
|
@@ -0,0 +1,4380 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { EventDispatcher } from "./events";
|
|
3
|
+
import { EmptyView, FileView, ItemView, TextFileView, View, } from "./view.svelte";
|
|
4
|
+
import { Menu, isMenuItem } from "./menu.svelte";
|
|
5
|
+
import { TFile } from "./storage/fs";
|
|
6
|
+
import { tick, untrack } from "svelte";
|
|
7
|
+
import { toast } from "svelte-sonner";
|
|
8
|
+
import { SidebarState, } from "@lapis-notes/ui/sidebar-custom";
|
|
9
|
+
import { uniqueId } from "./utils";
|
|
10
|
+
import { normalizeWorkspaceJson } from "./workspace-layout-normalizer";
|
|
11
|
+
import { promptLoadWorkspaceLayout, promptSaveWorkspaceLayout, } from "./workspace-layout-commands";
|
|
12
|
+
import { NAMED_WORKSPACES_FILE, parseNamedWorkspaceStore, serializeNamedWorkspaceStore, } from "./workspace-layouts";
|
|
13
|
+
import { debounce, isEqual } from "lodash-es";
|
|
14
|
+
import { basename, dirname, joinPath } from "./storage";
|
|
15
|
+
import { HistoryManager } from "./history.svelte";
|
|
16
|
+
import { EditorViewRegistry, } from "./editor-view-registry";
|
|
17
|
+
import { compareEditorAssociationPatternSpecificity, matchesEditorAssociationGlob, } from "./glob";
|
|
18
|
+
import { captureLeafViewState, cloneViewState, historyFilePathForViewState, resolveViewForOpenFile, } from "./open-file";
|
|
19
|
+
import { promptConfirm } from "./prompt-confirm";
|
|
20
|
+
import { isAbortError, withPluginInstallProgress, } from "./plugin-install-progress";
|
|
21
|
+
import { AppShellController, WorkspaceMenu as DesignWorkspaceMenu, } from "@lapismd/design-core/workspace/core";
|
|
22
|
+
import { notificationsPlugin } from "@lapismd/design-core/workspace/plugins/notifications";
|
|
23
|
+
import { fModePlugin } from "@lapismd/design-core/workspace/plugins/fmode";
|
|
24
|
+
import { problemsPlugin, PROBLEMS_VIEW_TYPE, } from "@lapismd/design-core/workspace/problems";
|
|
25
|
+
import { createAppShellPluginPersistence } from "./app-shell-plugin-persistence";
|
|
26
|
+
import { resolveWorkspaceHostBinding, setWorkspaceHostBinding, } from "./workspace-host-internal";
|
|
27
|
+
import { getWorkspaceHostBinding } from "./workspace-host";
|
|
28
|
+
import { DiagnosticsManager, pathFromDiagnosticResource } from "./diagnostics";
|
|
29
|
+
import { getApplicationCompatibility, resolveApplication, } from "./application-compatibility";
|
|
30
|
+
const DEFAULT_LAPIS_LOGO_URL = new URL("./assets/lapis-logo.svg", import.meta.url).href;
|
|
31
|
+
const WORKSPACE_HINT_TARGET_SELECTOR = "[data-hint-target]";
|
|
32
|
+
const FILE_EXPLORER_REVEAL_PATH_COMMAND = "lapis-file-explorer:reveal-path";
|
|
33
|
+
const DEFAULT_LEFT_SIDEBAR_VIEW_TYPES = [
|
|
34
|
+
"file-explorer",
|
|
35
|
+
"search",
|
|
36
|
+
"bookmarks",
|
|
37
|
+
];
|
|
38
|
+
const DEFAULT_RIGHT_SIDEBAR_VIEW_TYPES = [
|
|
39
|
+
"outline",
|
|
40
|
+
"file-properties",
|
|
41
|
+
"tag",
|
|
42
|
+
];
|
|
43
|
+
function normalizeHintText(value) {
|
|
44
|
+
return value?.replace(/\s+/gu, " ").trim() ?? "";
|
|
45
|
+
}
|
|
46
|
+
function filePathBreadcrumbs(app, filePath) {
|
|
47
|
+
if (!filePath)
|
|
48
|
+
return [];
|
|
49
|
+
const parts = dirname(filePath)
|
|
50
|
+
.split("/")
|
|
51
|
+
.filter((segment) => segment.length > 0);
|
|
52
|
+
return parts.map((label, index) => {
|
|
53
|
+
const path = parts.slice(0, index + 1).join("/");
|
|
54
|
+
return {
|
|
55
|
+
id: path,
|
|
56
|
+
label,
|
|
57
|
+
onSelect: () => {
|
|
58
|
+
void app.commands.executeCommand(FILE_EXPLORER_REVEAL_PATH_COMMAND, path);
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function menuItemTitle(title) {
|
|
64
|
+
return typeof title === "string" ? title : (title.textContent ?? "");
|
|
65
|
+
}
|
|
66
|
+
export function appendLapisMenuToWorkspaceMenu(target, source) {
|
|
67
|
+
for (const entries of Object.values(source.renderedItems)) {
|
|
68
|
+
if (entries.length === 0)
|
|
69
|
+
continue;
|
|
70
|
+
if (target.entries.length > 0)
|
|
71
|
+
target.addSeparator();
|
|
72
|
+
for (const entry of entries) {
|
|
73
|
+
if (entry === "separator") {
|
|
74
|
+
target.addSeparator();
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (isMenuItem(entry)) {
|
|
78
|
+
target.addItem((item) => {
|
|
79
|
+
item
|
|
80
|
+
.setTitle(menuItemTitle(entry.title))
|
|
81
|
+
.setDisabled(entry.disabled)
|
|
82
|
+
.setSection(entry.section)
|
|
83
|
+
.onClick((event) => entry.click(event));
|
|
84
|
+
if (entry.icon)
|
|
85
|
+
item.setIcon(entry.icon);
|
|
86
|
+
if (entry.checked !== null)
|
|
87
|
+
item.setChecked(entry.checked);
|
|
88
|
+
});
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
target.addMenu(entry.title, (submenu) => appendLapisMenuToWorkspaceMenu(submenu, entry));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function prependPaneMenu(target, source) {
|
|
96
|
+
const translated = new DesignWorkspaceMenu();
|
|
97
|
+
appendLapisMenuToWorkspaceMenu(translated, source);
|
|
98
|
+
if (translated.entries.length === 0)
|
|
99
|
+
return;
|
|
100
|
+
if (target.entries.length > 0)
|
|
101
|
+
translated.addSeparator();
|
|
102
|
+
target.entries.unshift(...translated.entries);
|
|
103
|
+
}
|
|
104
|
+
function replaceWorkspaceHostSplitActions(target, workspace, leaf) {
|
|
105
|
+
for (const entry of target.entries) {
|
|
106
|
+
if (entry.kind !== "item")
|
|
107
|
+
continue;
|
|
108
|
+
const direction = entry.title === "Split right"
|
|
109
|
+
? "vertical"
|
|
110
|
+
: entry.title === "Split down"
|
|
111
|
+
? "horizontal"
|
|
112
|
+
: null;
|
|
113
|
+
if (!direction)
|
|
114
|
+
continue;
|
|
115
|
+
entry.callback = async () => {
|
|
116
|
+
await workspace.duplicateLeaf(leaf, "split", direction);
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function toDesignEditorViewContribution(contribution) {
|
|
121
|
+
return {
|
|
122
|
+
id: contribution.id,
|
|
123
|
+
viewType: contribution.viewType,
|
|
124
|
+
label: contribution.label,
|
|
125
|
+
description: contribution.description,
|
|
126
|
+
filenamePatterns: [...contribution.filenamePatterns],
|
|
127
|
+
priority: contribution.priority,
|
|
128
|
+
pluginId: contribution.pluginId,
|
|
129
|
+
source: contribution.source === "manifest"
|
|
130
|
+
? "plugin"
|
|
131
|
+
: (contribution.source ?? "compat"),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function isHintTargetDisabled(element) {
|
|
135
|
+
if (element.getAttribute("aria-disabled") === "true") {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
return "disabled" in element && Boolean(element.disabled);
|
|
139
|
+
}
|
|
140
|
+
function isHintTargetVisible(element) {
|
|
141
|
+
if (element.getAttribute("aria-hidden") === "true") {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
const rect = element.getBoundingClientRect?.();
|
|
145
|
+
if (rect && rect.width === 0 && rect.height === 0) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
export class WorkspaceItem extends EventDispatcher {
|
|
151
|
+
parent;
|
|
152
|
+
#workspace = null;
|
|
153
|
+
id = uniqueId();
|
|
154
|
+
constructor() {
|
|
155
|
+
super();
|
|
156
|
+
}
|
|
157
|
+
/** Bind a root workspace item to its owning workspace. */
|
|
158
|
+
bindWorkspace(workspace) {
|
|
159
|
+
this.#workspace = workspace;
|
|
160
|
+
this._root = undefined;
|
|
161
|
+
return this;
|
|
162
|
+
}
|
|
163
|
+
/** The workspace that owns this item, when it has been attached. */
|
|
164
|
+
get workspace() {
|
|
165
|
+
return this.#workspace ?? this.parent?.workspace;
|
|
166
|
+
}
|
|
167
|
+
/** The application that owns this item, when it has been attached. */
|
|
168
|
+
get application() {
|
|
169
|
+
return this.workspace?.app;
|
|
170
|
+
}
|
|
171
|
+
/** Resolve the owning application before using the compatibility alias. */
|
|
172
|
+
get app() {
|
|
173
|
+
return resolveApplication(this.application);
|
|
174
|
+
}
|
|
175
|
+
_root = undefined;
|
|
176
|
+
getRoot() {
|
|
177
|
+
if (this._root) {
|
|
178
|
+
return this._root;
|
|
179
|
+
}
|
|
180
|
+
let parent = this.parent;
|
|
181
|
+
while (parent.parent) {
|
|
182
|
+
parent = parent.parent;
|
|
183
|
+
}
|
|
184
|
+
this._root = parent;
|
|
185
|
+
return parent;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
export class WorkspaceParent extends WorkspaceItem {
|
|
189
|
+
constructor() {
|
|
190
|
+
super();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function collectWorkspaceProjectionItem(item, items) {
|
|
194
|
+
items.set(item.id, item);
|
|
195
|
+
if (item instanceof WorkspaceSplit) {
|
|
196
|
+
item.children.forEach((child) => collectWorkspaceProjectionItem(child, items));
|
|
197
|
+
}
|
|
198
|
+
else if (item instanceof WorkspaceTabs) {
|
|
199
|
+
item.children.forEach((child) => collectWorkspaceProjectionItem(child, items));
|
|
200
|
+
}
|
|
201
|
+
else if (item instanceof WorkspaceSidebarGroup) {
|
|
202
|
+
item.children.forEach((child) => collectWorkspaceProjectionItem(child, items));
|
|
203
|
+
}
|
|
204
|
+
else if (item instanceof WorkspaceFloating) {
|
|
205
|
+
item.children.forEach((child) => collectWorkspaceProjectionItem(child, items));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function beginWorkspaceProjection(workspace) {
|
|
209
|
+
const items = new Map();
|
|
210
|
+
workspace.rootSplit.children.forEach((child) => collectWorkspaceProjectionItem(child, items));
|
|
211
|
+
workspace.leftSplit.children.forEach((child) => collectWorkspaceProjectionItem(child, items));
|
|
212
|
+
workspace.rightSplit.children.forEach((child) => collectWorkspaceProjectionItem(child, items));
|
|
213
|
+
workspace.bottomPanel.children.forEach((child) => collectWorkspaceProjectionItem(child, items));
|
|
214
|
+
workspace.floating.children.forEach((child) => collectWorkspaceProjectionItem(child, items));
|
|
215
|
+
return items;
|
|
216
|
+
}
|
|
217
|
+
function claimWorkspaceProjectionItem(projectionItems, id, matches, create) {
|
|
218
|
+
const item = projectionItems?.get(id);
|
|
219
|
+
if (item && matches(item)) {
|
|
220
|
+
projectionItems?.delete(id);
|
|
221
|
+
item._root = undefined;
|
|
222
|
+
return item;
|
|
223
|
+
}
|
|
224
|
+
return create();
|
|
225
|
+
}
|
|
226
|
+
function finishWorkspaceProjection(projectionItems) {
|
|
227
|
+
for (const item of projectionItems.values()) {
|
|
228
|
+
if (item instanceof WorkspaceLeaf) {
|
|
229
|
+
item.view.unload();
|
|
230
|
+
if (hasDestroyableEditor(item.view))
|
|
231
|
+
item.view.editor.destroy();
|
|
232
|
+
}
|
|
233
|
+
else if (item instanceof WorkspaceWindow) {
|
|
234
|
+
item.closePopoutWindow();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
export const WORKSPACE_ROOT_HOST_ID = "root";
|
|
239
|
+
export const WORKSPACE_POPOUT_UNSUPPORTED_ERROR_MESSAGE = "Workspace popout windows are not supported in the current renderer host.";
|
|
240
|
+
let workspacePopoutHost = null;
|
|
241
|
+
export function setWorkspacePopoutHost(host) {
|
|
242
|
+
workspacePopoutHost = host;
|
|
243
|
+
}
|
|
244
|
+
export function getWorkspacePopoutHost() {
|
|
245
|
+
return workspacePopoutHost;
|
|
246
|
+
}
|
|
247
|
+
export function supportsWorkspacePopouts() {
|
|
248
|
+
return workspacePopoutHost?.supportsPopouts() === true;
|
|
249
|
+
}
|
|
250
|
+
function openWorkspacePopoutHandle(data) {
|
|
251
|
+
if (!supportsWorkspacePopouts()) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
return workspacePopoutHost?.openWindow(data) ?? null;
|
|
255
|
+
}
|
|
256
|
+
export class WorkspaceSplit extends WorkspaceParent {
|
|
257
|
+
type = $state("vertical");
|
|
258
|
+
children = $state([]);
|
|
259
|
+
sizes = $state([]);
|
|
260
|
+
constructor(type = "vertical") {
|
|
261
|
+
super();
|
|
262
|
+
this.type = type;
|
|
263
|
+
}
|
|
264
|
+
loadJson(layout, projectionItems) {
|
|
265
|
+
const promises = [];
|
|
266
|
+
this.children.slice().forEach((it) => this.removeChild(it, true));
|
|
267
|
+
const sizes = layout.sizes || [];
|
|
268
|
+
layout.children.forEach((child, i) => {
|
|
269
|
+
if (child.type === "tabs") {
|
|
270
|
+
const tabs = claimWorkspaceProjectionItem(projectionItems, child.id, (item) => item instanceof WorkspaceTabs, () => new WorkspaceTabs({ leaves: [] }));
|
|
271
|
+
this.addChild(tabs);
|
|
272
|
+
promises.push(tabs.loadJson(child, projectionItems));
|
|
273
|
+
}
|
|
274
|
+
else if (child.type === "split") {
|
|
275
|
+
const split = claimWorkspaceProjectionItem(projectionItems, child.id, (item) => item instanceof WorkspaceView, () => new WorkspaceView(child.direction));
|
|
276
|
+
this.addChild(split);
|
|
277
|
+
promises.push(split.loadJson(child, projectionItems));
|
|
278
|
+
}
|
|
279
|
+
this.sizes[i] = sizes[i] ?? 50;
|
|
280
|
+
});
|
|
281
|
+
return Promise.all(promises).then(() => {
|
|
282
|
+
this.id = layout.id;
|
|
283
|
+
this.type = layout.direction;
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
toJson() {
|
|
287
|
+
return {
|
|
288
|
+
id: this.id,
|
|
289
|
+
direction: this.type,
|
|
290
|
+
sizes: this.sizes.slice(),
|
|
291
|
+
type: "split",
|
|
292
|
+
children: this.children.map((it) => it.toJson()),
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
addChild(child, index) {
|
|
296
|
+
if (typeof index === "number" &&
|
|
297
|
+
index >= 0 &&
|
|
298
|
+
index <= this.children.length) {
|
|
299
|
+
this.children.splice(index, 0, child);
|
|
300
|
+
this.sizes.splice(index, 0, 50);
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
this.children.push(child);
|
|
304
|
+
this.sizes.push(50);
|
|
305
|
+
}
|
|
306
|
+
child.parent = this;
|
|
307
|
+
}
|
|
308
|
+
iterateAllSplits(callback) {
|
|
309
|
+
const response = callback(this);
|
|
310
|
+
if (response !== undefined && response !== null) {
|
|
311
|
+
return response;
|
|
312
|
+
}
|
|
313
|
+
for (const child of this.children) {
|
|
314
|
+
if (child instanceof WorkspaceSplit) {
|
|
315
|
+
const response = child.iterateAllSplits(callback);
|
|
316
|
+
if (response !== undefined && response !== null) {
|
|
317
|
+
return response;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
iterateAllTabs(callback) {
|
|
323
|
+
for (const child of this.children) {
|
|
324
|
+
if (child instanceof WorkspaceTabs) {
|
|
325
|
+
const response = callback(child);
|
|
326
|
+
if (response !== undefined && response !== null) {
|
|
327
|
+
return response;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
const response = child.iterateAllTabs(callback);
|
|
332
|
+
if (response !== undefined && response !== null) {
|
|
333
|
+
return response;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
iterateAllLeaves(callback) {
|
|
339
|
+
for (const child of this.children) {
|
|
340
|
+
const response = child.iterateAllLeaves(callback);
|
|
341
|
+
if (response !== undefined && response !== null) {
|
|
342
|
+
return response;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
onEmpty() {
|
|
347
|
+
tick().then(() => this.addChild(new WorkspaceTabs()));
|
|
348
|
+
}
|
|
349
|
+
removeChild(index, softDelete = false) {
|
|
350
|
+
if (typeof index === "number") {
|
|
351
|
+
const child = this.children[index];
|
|
352
|
+
if (child) {
|
|
353
|
+
this.children.splice(index, 1);
|
|
354
|
+
this.sizes.splice(index, 1);
|
|
355
|
+
if (!softDelete &&
|
|
356
|
+
!this.children.length &&
|
|
357
|
+
this.parent instanceof WorkspaceView) {
|
|
358
|
+
this.parent.removeChild(this);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (!softDelete && !this.children.length && !this.parent) {
|
|
362
|
+
this.onEmpty();
|
|
363
|
+
}
|
|
364
|
+
return child;
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
return this.removeChild(this.children.findIndex((it) => it === index), softDelete);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
get topLeft() {
|
|
371
|
+
return this.iterateAllTabs((tab) => tab);
|
|
372
|
+
}
|
|
373
|
+
get topRight() {
|
|
374
|
+
return topRightView(this);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
function topRightView(view) {
|
|
378
|
+
if (view instanceof WorkspaceSplit) {
|
|
379
|
+
if (view.type === "vertical") {
|
|
380
|
+
return topRightView(view.children[view.children.length - 1]);
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
return topRightView(view.children[0]);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return view;
|
|
387
|
+
}
|
|
388
|
+
export class WorkspaceView extends WorkspaceSplit {
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Collapsible split container used for the left and right workspace sidebars.
|
|
392
|
+
*
|
|
393
|
+
* @public
|
|
394
|
+
*/
|
|
395
|
+
export class WorkspaceSidedock extends WorkspaceSplit {
|
|
396
|
+
open = $state(true);
|
|
397
|
+
sidebar;
|
|
398
|
+
constructor(options = {}) {
|
|
399
|
+
super();
|
|
400
|
+
const { initialOpen = true, ...sidebarOptions } = options;
|
|
401
|
+
this.open = initialOpen;
|
|
402
|
+
let initialized = false;
|
|
403
|
+
this.sidebar = new SidebarState({
|
|
404
|
+
id: this.id,
|
|
405
|
+
...sidebarOptions,
|
|
406
|
+
open: () => this.open,
|
|
407
|
+
setOpen: (value) => {
|
|
408
|
+
this.open = value;
|
|
409
|
+
},
|
|
410
|
+
});
|
|
411
|
+
$effect(() => {
|
|
412
|
+
this.trigger("sidebar-changed", this.sidebar.props.id, this.sidebar.open, this.sidebar.width);
|
|
413
|
+
if (initialized)
|
|
414
|
+
this.app.workspace.requestSaveLayout();
|
|
415
|
+
initialized = true;
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
loadJson(layout, projectionItems) {
|
|
419
|
+
return super.loadJson(layout, projectionItems).then(() => {
|
|
420
|
+
if (/^0(?:px|rem|em|%)?$/.test(layout.width.trim())) {
|
|
421
|
+
this.open = false;
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
this.open = true;
|
|
425
|
+
this.sidebar.width = layout.width;
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
toJson() {
|
|
430
|
+
return {
|
|
431
|
+
...super.toJson(),
|
|
432
|
+
width: this.collapsed ? "0" : this.sidebar.width,
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
onEmpty() { }
|
|
436
|
+
get size() {
|
|
437
|
+
return this.sidebar.size;
|
|
438
|
+
}
|
|
439
|
+
get collapsed() {
|
|
440
|
+
return !this.open;
|
|
441
|
+
}
|
|
442
|
+
toggle() {
|
|
443
|
+
this.open = !this.open;
|
|
444
|
+
}
|
|
445
|
+
collapse() {
|
|
446
|
+
this.open = false;
|
|
447
|
+
}
|
|
448
|
+
expand() {
|
|
449
|
+
this.open = true;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
export class WorkspaceContainer extends WorkspaceSplit {
|
|
453
|
+
}
|
|
454
|
+
export class WorkspaceRoot extends WorkspaceContainer {
|
|
455
|
+
win;
|
|
456
|
+
doc;
|
|
457
|
+
constructor(win = window, doc = document) {
|
|
458
|
+
super();
|
|
459
|
+
this.win = win;
|
|
460
|
+
this.doc = doc;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
export class WorkspaceWindow extends WorkspaceContainer {
|
|
464
|
+
mode = $state("floating");
|
|
465
|
+
displayState = $state("normal");
|
|
466
|
+
x = $state(96);
|
|
467
|
+
y = $state(96);
|
|
468
|
+
width = $state(480);
|
|
469
|
+
height = $state(320);
|
|
470
|
+
win;
|
|
471
|
+
doc;
|
|
472
|
+
focusHost = null;
|
|
473
|
+
closeHost = null;
|
|
474
|
+
detachHostClose = null;
|
|
475
|
+
constructor(data = {}, win = window, doc = document) {
|
|
476
|
+
super();
|
|
477
|
+
this.win = win;
|
|
478
|
+
this.doc = doc;
|
|
479
|
+
this.applyInitData(data);
|
|
480
|
+
}
|
|
481
|
+
applyInitData(data = {}) {
|
|
482
|
+
this.mode = data.mode ?? "floating";
|
|
483
|
+
this.displayState =
|
|
484
|
+
data.displayState === "collapsed" || data.displayState === "minimized"
|
|
485
|
+
? data.displayState
|
|
486
|
+
: "normal";
|
|
487
|
+
this.x = data.x ?? 96;
|
|
488
|
+
this.y = data.y ?? 96;
|
|
489
|
+
this.width = data.size?.width ?? 480;
|
|
490
|
+
this.height = data.size?.height ?? 320;
|
|
491
|
+
}
|
|
492
|
+
attachPopoutHandle(handle, onClose) {
|
|
493
|
+
this.mode = "popout";
|
|
494
|
+
this.win = handle.win;
|
|
495
|
+
this.doc = handle.doc;
|
|
496
|
+
this.focusHost = () => handle.focus();
|
|
497
|
+
this.closeHost = () => handle.close();
|
|
498
|
+
this.detachHostClose?.();
|
|
499
|
+
this.detachHostClose = handle.onClose(onClose);
|
|
500
|
+
}
|
|
501
|
+
focusPopoutWindow() {
|
|
502
|
+
this.focusHost?.();
|
|
503
|
+
}
|
|
504
|
+
closePopoutWindow() {
|
|
505
|
+
const detach = this.detachHostClose;
|
|
506
|
+
this.detachHostClose = null;
|
|
507
|
+
detach?.();
|
|
508
|
+
const close = this.closeHost;
|
|
509
|
+
this.closeHost = null;
|
|
510
|
+
this.focusHost = null;
|
|
511
|
+
close?.();
|
|
512
|
+
}
|
|
513
|
+
loadWindowJson(layout, projectionItems) {
|
|
514
|
+
return super
|
|
515
|
+
.loadJson({
|
|
516
|
+
id: layout.id,
|
|
517
|
+
type: "split",
|
|
518
|
+
direction: layout.direction,
|
|
519
|
+
sizes: layout.sizes,
|
|
520
|
+
children: layout.children,
|
|
521
|
+
}, projectionItems)
|
|
522
|
+
.then(() => {
|
|
523
|
+
this.id = layout.id;
|
|
524
|
+
this.applyInitData({
|
|
525
|
+
mode: layout.mode,
|
|
526
|
+
displayState: layout.displayState,
|
|
527
|
+
x: layout.x,
|
|
528
|
+
y: layout.y,
|
|
529
|
+
size: { width: layout.width, height: layout.height },
|
|
530
|
+
});
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
toWindowJson() {
|
|
534
|
+
const layout = super.toJson();
|
|
535
|
+
const displayState = this.displayState === "collapsed" || this.displayState === "minimized"
|
|
536
|
+
? this.displayState
|
|
537
|
+
: undefined;
|
|
538
|
+
return {
|
|
539
|
+
...layout,
|
|
540
|
+
type: "floating",
|
|
541
|
+
mode: this.mode,
|
|
542
|
+
...(displayState ? { displayState } : {}),
|
|
543
|
+
x: this.x,
|
|
544
|
+
y: this.y,
|
|
545
|
+
width: this.width,
|
|
546
|
+
height: this.height,
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
setBounds(bounds) {
|
|
550
|
+
if (typeof bounds.x === "number")
|
|
551
|
+
this.x = bounds.x;
|
|
552
|
+
if (typeof bounds.y === "number")
|
|
553
|
+
this.y = bounds.y;
|
|
554
|
+
if (typeof bounds.width === "number")
|
|
555
|
+
this.width = bounds.width;
|
|
556
|
+
if (typeof bounds.height === "number")
|
|
557
|
+
this.height = bounds.height;
|
|
558
|
+
}
|
|
559
|
+
setDisplayState(displayState) {
|
|
560
|
+
this.displayState = displayState;
|
|
561
|
+
}
|
|
562
|
+
removeChild(index, softDelete = false) {
|
|
563
|
+
const child = super.removeChild(index, softDelete);
|
|
564
|
+
if (!softDelete && !this.children.length) {
|
|
565
|
+
this.closePopoutWindow();
|
|
566
|
+
this.parent.removeChild(this);
|
|
567
|
+
}
|
|
568
|
+
return child;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
function hasDestroyableEditor(view) {
|
|
572
|
+
const candidate = view;
|
|
573
|
+
return typeof candidate.editor?.destroy === "function";
|
|
574
|
+
}
|
|
575
|
+
export class WorkspaceFloating extends WorkspaceParent {
|
|
576
|
+
children = $state([]);
|
|
577
|
+
loadJson(layouts = [], projectionItems) {
|
|
578
|
+
const promises = [];
|
|
579
|
+
this.children.slice().forEach((child) => this.removeChild(child, true));
|
|
580
|
+
layouts.forEach((layout) => {
|
|
581
|
+
const child = claimWorkspaceProjectionItem(projectionItems, layout.id, (item) => item instanceof WorkspaceWindow, () => new WorkspaceWindow());
|
|
582
|
+
this.addChild(child);
|
|
583
|
+
promises.push(child.loadWindowJson(layout, projectionItems).then(() => {
|
|
584
|
+
if (child.mode !== "popout") {
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
const handle = openWorkspacePopoutHandle({
|
|
588
|
+
x: child.x,
|
|
589
|
+
y: child.y,
|
|
590
|
+
size: { width: child.width, height: child.height },
|
|
591
|
+
mode: "popout",
|
|
592
|
+
});
|
|
593
|
+
if (!handle) {
|
|
594
|
+
child.mode = "floating";
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
child.attachPopoutHandle(handle, () => {
|
|
598
|
+
if (this.app.workspace.floating.children.includes(child)) {
|
|
599
|
+
this.app.workspace.closeFloatingWindow(child, {
|
|
600
|
+
closeHost: false,
|
|
601
|
+
operation: "close-popout-window",
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
}));
|
|
606
|
+
});
|
|
607
|
+
return Promise.all(promises);
|
|
608
|
+
}
|
|
609
|
+
toJson() {
|
|
610
|
+
return this.children.map((child) => child.toWindowJson());
|
|
611
|
+
}
|
|
612
|
+
addChild(child, index) {
|
|
613
|
+
if (typeof index === "number" &&
|
|
614
|
+
index >= 0 &&
|
|
615
|
+
index <= this.children.length) {
|
|
616
|
+
this.children.splice(index, 0, child);
|
|
617
|
+
}
|
|
618
|
+
else {
|
|
619
|
+
this.children.push(child);
|
|
620
|
+
}
|
|
621
|
+
child.parent = this;
|
|
622
|
+
}
|
|
623
|
+
bringToFront(child) {
|
|
624
|
+
const index = this.children.findIndex((candidate) => candidate === child);
|
|
625
|
+
if (index === -1 || index === this.children.length - 1) {
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
this.children.splice(index, 1);
|
|
629
|
+
this.children.push(child);
|
|
630
|
+
}
|
|
631
|
+
removeChild(index, softDelete = false) {
|
|
632
|
+
if (index instanceof WorkspaceWindow) {
|
|
633
|
+
return this.removeChild(this.children.findIndex((child) => child === index), softDelete);
|
|
634
|
+
}
|
|
635
|
+
const child = this.children[index];
|
|
636
|
+
if (child) {
|
|
637
|
+
this.children.splice(index, 1);
|
|
638
|
+
if (!softDelete && this.app.workspace.activeLeaf) {
|
|
639
|
+
const activeLeaf = this.app.workspace.activeLeaf;
|
|
640
|
+
const stillContained = child.iterateAllLeaves((leaf) => leaf === activeLeaf || undefined);
|
|
641
|
+
if (stillContained) {
|
|
642
|
+
this.app.workspace.activeLeaf = this.app.workspace.activeRootLeaf;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
return child;
|
|
647
|
+
}
|
|
648
|
+
iterateAllLeaves(callback) {
|
|
649
|
+
for (const child of this.children) {
|
|
650
|
+
const response = child.iterateAllLeaves(callback);
|
|
651
|
+
if (response !== undefined && response !== null) {
|
|
652
|
+
return response;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
export class WorkspaceMobileDrawer extends WorkspaceParent {
|
|
658
|
+
collapsed = false;
|
|
659
|
+
expand() {
|
|
660
|
+
this.collapsed = false;
|
|
661
|
+
}
|
|
662
|
+
collapse() {
|
|
663
|
+
this.collapsed = true;
|
|
664
|
+
}
|
|
665
|
+
toggle() {
|
|
666
|
+
this.collapsed = !this.collapsed;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
function isSidebarGroupJson(value) {
|
|
670
|
+
return value.type === "sidebar-group";
|
|
671
|
+
}
|
|
672
|
+
function normalizeSidebarGroupId(group) {
|
|
673
|
+
return group.trim();
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Sidebar-only container that groups leaves into a named view container.
|
|
677
|
+
*
|
|
678
|
+
* @public
|
|
679
|
+
*/
|
|
680
|
+
export class WorkspaceSidebarGroup extends WorkspaceParent {
|
|
681
|
+
name = $state("");
|
|
682
|
+
icon = $state(undefined);
|
|
683
|
+
hiddenLeafIds = $state([]);
|
|
684
|
+
collapsed = $state({});
|
|
685
|
+
panelSizes = $state({});
|
|
686
|
+
selectedLeafId = $state(undefined);
|
|
687
|
+
children = $state([]);
|
|
688
|
+
constructor(options = {}) {
|
|
689
|
+
super();
|
|
690
|
+
if (options.id) {
|
|
691
|
+
this.id = options.id;
|
|
692
|
+
}
|
|
693
|
+
this.name = options.name ?? options.id ?? "";
|
|
694
|
+
this.icon = options.icon;
|
|
695
|
+
this.hiddenLeafIds = [...(options.hiddenLeafIds ?? [])];
|
|
696
|
+
this.collapsed = { ...(options.collapsed ?? {}) };
|
|
697
|
+
this.panelSizes = { ...(options.panelSizes ?? {}) };
|
|
698
|
+
}
|
|
699
|
+
detach(softDelete = false) {
|
|
700
|
+
return this.parent.removeChild(this, softDelete);
|
|
701
|
+
}
|
|
702
|
+
addChild(child, index) {
|
|
703
|
+
const oldIndex = this.children.findIndex((it) => it === child);
|
|
704
|
+
if (child.parent && child.parent !== this) {
|
|
705
|
+
child.detach();
|
|
706
|
+
}
|
|
707
|
+
if (oldIndex !== -1) {
|
|
708
|
+
this.children.splice(oldIndex, 1);
|
|
709
|
+
}
|
|
710
|
+
if (typeof index === "number") {
|
|
711
|
+
const boundedIndex = Math.max(0, Math.min(index, this.children.length));
|
|
712
|
+
this.children.splice(boundedIndex, 0, child);
|
|
713
|
+
}
|
|
714
|
+
else {
|
|
715
|
+
this.children.push(child);
|
|
716
|
+
}
|
|
717
|
+
child.parent = this;
|
|
718
|
+
}
|
|
719
|
+
removeChild(index, softDelete = false) {
|
|
720
|
+
void softDelete;
|
|
721
|
+
if (index instanceof WorkspaceLeaf) {
|
|
722
|
+
return this.removeChild(this.children.findIndex((it) => it === index));
|
|
723
|
+
}
|
|
724
|
+
const child = this.children[index];
|
|
725
|
+
if (!child) {
|
|
726
|
+
return undefined;
|
|
727
|
+
}
|
|
728
|
+
this.children.splice(index, 1);
|
|
729
|
+
this.hiddenLeafIds = this.hiddenLeafIds.filter((id) => id !== child.id);
|
|
730
|
+
this.collapsed = Object.fromEntries(Object.entries(this.collapsed).filter(([id]) => id !== child.id));
|
|
731
|
+
this.panelSizes = Object.fromEntries(Object.entries(this.panelSizes).filter(([id]) => id !== child.id));
|
|
732
|
+
if (!this.children.length && !softDelete) {
|
|
733
|
+
this.parent.removeChild(this);
|
|
734
|
+
}
|
|
735
|
+
if (this.app.workspace.activeLeaf === child) {
|
|
736
|
+
this.app.workspace.activeLeaf = this.getSelectedLeaf();
|
|
737
|
+
}
|
|
738
|
+
return child;
|
|
739
|
+
}
|
|
740
|
+
getSelectedLeaf() {
|
|
741
|
+
const selected = this.selectedLeafId
|
|
742
|
+
? this.children.find((leaf) => leaf.id === this.selectedLeafId && !this.isLeafHidden(leaf))
|
|
743
|
+
: undefined;
|
|
744
|
+
return (selected ??
|
|
745
|
+
this.children.find((leaf) => !this.isLeafHidden(leaf)) ??
|
|
746
|
+
null);
|
|
747
|
+
}
|
|
748
|
+
selectLeaf(leaf) {
|
|
749
|
+
if (!this.children.includes(leaf)) {
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
this.selectedLeafId = leaf.id;
|
|
753
|
+
if (this.isLeafHidden(leaf)) {
|
|
754
|
+
this.setLeafHidden(leaf, false);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
iterateAllLeaves(callback) {
|
|
758
|
+
for (const child of this.children) {
|
|
759
|
+
const response = callback(child);
|
|
760
|
+
if (response !== undefined && response !== null) {
|
|
761
|
+
return response;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
isLeafHidden(leaf) {
|
|
766
|
+
const id = typeof leaf === "string" ? leaf : leaf.id;
|
|
767
|
+
return this.hiddenLeafIds.includes(id);
|
|
768
|
+
}
|
|
769
|
+
setLeafHidden(leaf, hidden) {
|
|
770
|
+
const id = typeof leaf === "string" ? leaf : leaf.id;
|
|
771
|
+
const ids = new Set(this.hiddenLeafIds);
|
|
772
|
+
if (hidden) {
|
|
773
|
+
ids.add(id);
|
|
774
|
+
}
|
|
775
|
+
else {
|
|
776
|
+
ids.delete(id);
|
|
777
|
+
}
|
|
778
|
+
this.hiddenLeafIds = [...ids];
|
|
779
|
+
this.app.workspace.requestSaveLayout();
|
|
780
|
+
}
|
|
781
|
+
isLeafCollapsed(leaf) {
|
|
782
|
+
const id = typeof leaf === "string" ? leaf : leaf.id;
|
|
783
|
+
return this.collapsed[id] ?? false;
|
|
784
|
+
}
|
|
785
|
+
setLeafCollapsed(leaf, collapsed) {
|
|
786
|
+
const id = typeof leaf === "string" ? leaf : leaf.id;
|
|
787
|
+
this.collapsed = { ...this.collapsed, [id]: collapsed };
|
|
788
|
+
this.app.workspace.requestSaveLayout();
|
|
789
|
+
}
|
|
790
|
+
getLeafPanelSize(leaf) {
|
|
791
|
+
const id = typeof leaf === "string" ? leaf : leaf.id;
|
|
792
|
+
return this.panelSizes[id];
|
|
793
|
+
}
|
|
794
|
+
setPanelSizes(leaves, sizes) {
|
|
795
|
+
const next = { ...this.panelSizes };
|
|
796
|
+
leaves.forEach((leaf, index) => {
|
|
797
|
+
const size = sizes[index];
|
|
798
|
+
if (Number.isFinite(size) && size > 0) {
|
|
799
|
+
next[leaf.id] = Number(size.toFixed(4));
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
this.panelSizes = next;
|
|
803
|
+
this.app.workspace.requestSaveLayout();
|
|
804
|
+
}
|
|
805
|
+
serializedPanelSizes() {
|
|
806
|
+
const childIds = new Set(this.children.map((leaf) => leaf.id));
|
|
807
|
+
return Object.fromEntries(Object.entries(this.panelSizes).filter(([id, size]) => childIds.has(id) && Number.isFinite(size) && size > 0));
|
|
808
|
+
}
|
|
809
|
+
loadJson(layout, projectionItems) {
|
|
810
|
+
const promises = [];
|
|
811
|
+
this.children.slice().forEach((it) => this.removeChild(it, true));
|
|
812
|
+
layout.children.forEach((config) => {
|
|
813
|
+
const leaf = claimWorkspaceProjectionItem(projectionItems, config.id, (item) => item instanceof WorkspaceLeaf, () => new WorkspaceLeaf());
|
|
814
|
+
this.addChild(leaf);
|
|
815
|
+
promises.push(leaf.loadJson(config));
|
|
816
|
+
});
|
|
817
|
+
return Promise.all(promises).then(() => {
|
|
818
|
+
this.id = layout.id;
|
|
819
|
+
this.name = layout.name;
|
|
820
|
+
this.icon = layout.icon;
|
|
821
|
+
this.hiddenLeafIds = [...(layout.hiddenLeafIds ?? [])];
|
|
822
|
+
this.collapsed = { ...(layout.collapsed ?? {}) };
|
|
823
|
+
this.panelSizes = { ...(layout.panelSizes ?? {}) };
|
|
824
|
+
this.selectedLeafId = this.children.some((leaf) => leaf.id === layout.selectedLeafId)
|
|
825
|
+
? layout.selectedLeafId
|
|
826
|
+
: undefined;
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
toJson() {
|
|
830
|
+
const panelSizes = this.serializedPanelSizes();
|
|
831
|
+
const selectedLeaf = this.getSelectedLeaf();
|
|
832
|
+
return {
|
|
833
|
+
id: this.id,
|
|
834
|
+
type: "sidebar-group",
|
|
835
|
+
name: this.name,
|
|
836
|
+
...(this.icon ? { icon: this.icon } : {}),
|
|
837
|
+
...(this.hiddenLeafIds.length
|
|
838
|
+
? { hiddenLeafIds: [...this.hiddenLeafIds] }
|
|
839
|
+
: {}),
|
|
840
|
+
...(Object.keys(this.collapsed).length
|
|
841
|
+
? { collapsed: { ...this.collapsed } }
|
|
842
|
+
: {}),
|
|
843
|
+
...(Object.keys(panelSizes).length ? { panelSizes } : {}),
|
|
844
|
+
...(selectedLeaf ? { selectedLeafId: selectedLeaf.id } : {}),
|
|
845
|
+
children: this.children.map((it) => it.toJson()),
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
850
|
+
* Tab strip container for root and sidebar workspace leaves.
|
|
851
|
+
*
|
|
852
|
+
* @public
|
|
853
|
+
*/
|
|
854
|
+
export class WorkspaceTabs extends WorkspaceParent {
|
|
855
|
+
containerEl = $state(null);
|
|
856
|
+
stacked = $state(false);
|
|
857
|
+
_selected = $state("0");
|
|
858
|
+
children = $state([]);
|
|
859
|
+
constructor(props = {}) {
|
|
860
|
+
super();
|
|
861
|
+
const { leaves = [new WorkspaceLeaf()], stacked = false } = props;
|
|
862
|
+
this.stacked = stacked;
|
|
863
|
+
leaves.forEach((it) => this.addChild(it));
|
|
864
|
+
}
|
|
865
|
+
loadJson(layout, projectionItems) {
|
|
866
|
+
const promises = [];
|
|
867
|
+
this.children.slice().forEach((it) => this.removeChild(it, true));
|
|
868
|
+
layout.children.forEach((config) => {
|
|
869
|
+
if (isSidebarGroupJson(config)) {
|
|
870
|
+
const group = claimWorkspaceProjectionItem(projectionItems, config.id, (item) => item instanceof WorkspaceSidebarGroup, () => new WorkspaceSidebarGroup());
|
|
871
|
+
this.addChild(group);
|
|
872
|
+
promises.push(group.loadJson(config, projectionItems));
|
|
873
|
+
}
|
|
874
|
+
else {
|
|
875
|
+
const leaf = claimWorkspaceProjectionItem(projectionItems, config.id, (item) => item instanceof WorkspaceLeaf, () => new WorkspaceLeaf());
|
|
876
|
+
this.addChild(leaf);
|
|
877
|
+
promises.push(leaf.loadJson(config));
|
|
878
|
+
}
|
|
879
|
+
});
|
|
880
|
+
return Promise.all(promises).then(() => {
|
|
881
|
+
this.id = layout.id;
|
|
882
|
+
this.stacked = layout.stacked;
|
|
883
|
+
this.selected = layout.currentTab;
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
toJson() {
|
|
887
|
+
return {
|
|
888
|
+
id: this.id,
|
|
889
|
+
stacked: this.stacked,
|
|
890
|
+
type: "tabs",
|
|
891
|
+
children: this.children.map((it) => it.toJson()),
|
|
892
|
+
currentTab: this.selectedIndex,
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
get selected() {
|
|
896
|
+
return this._selected;
|
|
897
|
+
}
|
|
898
|
+
get sideBar() {
|
|
899
|
+
let parent = this.parent;
|
|
900
|
+
while (parent) {
|
|
901
|
+
if (parent instanceof WorkspaceSidedock) {
|
|
902
|
+
return parent;
|
|
903
|
+
}
|
|
904
|
+
parent = parent.parent;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
inSideBar() {
|
|
908
|
+
return this.sideBar !== undefined;
|
|
909
|
+
}
|
|
910
|
+
get selectedIndex() {
|
|
911
|
+
return +this._selected;
|
|
912
|
+
}
|
|
913
|
+
get selectedChild() {
|
|
914
|
+
return this.children[this.selectedIndex];
|
|
915
|
+
}
|
|
916
|
+
get selectedLeaf() {
|
|
917
|
+
const child = this.selectedChild;
|
|
918
|
+
if (child instanceof WorkspaceLeaf) {
|
|
919
|
+
return child;
|
|
920
|
+
}
|
|
921
|
+
return child?.getSelectedLeaf() ?? null;
|
|
922
|
+
}
|
|
923
|
+
set selected(value) {
|
|
924
|
+
if (value instanceof WorkspaceLeaf ||
|
|
925
|
+
value instanceof WorkspaceSidebarGroup) {
|
|
926
|
+
value = this.children.findIndex((it) => it === value);
|
|
927
|
+
}
|
|
928
|
+
this._selected = value?.toString() || "";
|
|
929
|
+
}
|
|
930
|
+
detach(softDelete = false) {
|
|
931
|
+
return this.parent.removeChild(this, softDelete);
|
|
932
|
+
}
|
|
933
|
+
addChild(child, index) {
|
|
934
|
+
const oldIndex = this.children.findIndex((it) => it === child);
|
|
935
|
+
if (child.parent && child.parent !== this) {
|
|
936
|
+
child.detach();
|
|
937
|
+
}
|
|
938
|
+
if (typeof index === "number") {
|
|
939
|
+
if (index < 0)
|
|
940
|
+
index = 0;
|
|
941
|
+
if (index >= this.children.length)
|
|
942
|
+
index = this.children.length - 1;
|
|
943
|
+
}
|
|
944
|
+
if (typeof index === "number" &&
|
|
945
|
+
index >= 0 &&
|
|
946
|
+
index < this.children.length) {
|
|
947
|
+
if (oldIndex !== -1) {
|
|
948
|
+
this.children.splice(oldIndex, 1);
|
|
949
|
+
}
|
|
950
|
+
this.children.splice(index, 0, child);
|
|
951
|
+
}
|
|
952
|
+
else if (oldIndex === -1) {
|
|
953
|
+
this.children.push(child);
|
|
954
|
+
}
|
|
955
|
+
child.parent = this;
|
|
956
|
+
if (this.selectedIndex === oldIndex) {
|
|
957
|
+
this.selected = child;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
iterateAllLeaves(callback) {
|
|
961
|
+
for (const child of this.children) {
|
|
962
|
+
const response = child instanceof WorkspaceLeaf
|
|
963
|
+
? callback(child)
|
|
964
|
+
: child.iterateAllLeaves(callback);
|
|
965
|
+
if (response !== undefined && response !== null) {
|
|
966
|
+
return response;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
removeChild(index, softDelete = false) {
|
|
971
|
+
if (index instanceof WorkspaceLeaf ||
|
|
972
|
+
index instanceof WorkspaceSidebarGroup) {
|
|
973
|
+
return this.removeChild(this.children.findIndex((it) => it === index), softDelete);
|
|
974
|
+
}
|
|
975
|
+
const child = this.children[index];
|
|
976
|
+
if (child) {
|
|
977
|
+
if (child instanceof WorkspaceLeaf) {
|
|
978
|
+
this.app.workspace.clearFocusModeForLeaf(child);
|
|
979
|
+
}
|
|
980
|
+
else {
|
|
981
|
+
child.iterateAllLeaves((leaf) => {
|
|
982
|
+
this.app.workspace.clearFocusModeForLeaf(leaf);
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
this.children.splice(index, 1);
|
|
986
|
+
const selectedIndex = +this.selected;
|
|
987
|
+
if (selectedIndex === index && index > 0) {
|
|
988
|
+
this.selected = (index - 1).toString();
|
|
989
|
+
}
|
|
990
|
+
else if (index < selectedIndex) {
|
|
991
|
+
this.selected = (selectedIndex - 1).toString();
|
|
992
|
+
}
|
|
993
|
+
if (child instanceof WorkspaceLeaf &&
|
|
994
|
+
this.app.workspace.activeLeaf === child) {
|
|
995
|
+
this.app.workspace.activeLeaf = this.selectedLeaf;
|
|
996
|
+
}
|
|
997
|
+
else if (child instanceof WorkspaceSidebarGroup &&
|
|
998
|
+
child.children.includes(this.app.workspace.activeLeaf)) {
|
|
999
|
+
this.app.workspace.activeLeaf = this.selectedLeaf;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
if (!this.children.length) {
|
|
1003
|
+
if (child instanceof WorkspaceLeaf &&
|
|
1004
|
+
this.app.workspace.activeLeaf === child) {
|
|
1005
|
+
this.app.workspace.activeLeaf = null;
|
|
1006
|
+
}
|
|
1007
|
+
if (!softDelete) {
|
|
1008
|
+
this.parent.removeChild(this);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
return child;
|
|
1012
|
+
}
|
|
1013
|
+
closeAll() {
|
|
1014
|
+
this.children.slice().forEach((it) => this.removeChild(it, true));
|
|
1015
|
+
this.parent.removeChild(this);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
1019
|
+
* Stable compatibility wrapper for design-core's bottom workspace dock.
|
|
1020
|
+
*
|
|
1021
|
+
* The panel remains a single tabs surface. Its height encodes both the last
|
|
1022
|
+
* expanded size and its persisted open state, matching the workspace JSON
|
|
1023
|
+
* contract used by design-core.
|
|
1024
|
+
*
|
|
1025
|
+
* @public
|
|
1026
|
+
*/
|
|
1027
|
+
export class WorkspaceBottomPanel extends WorkspaceTabs {
|
|
1028
|
+
ownerWorkspace;
|
|
1029
|
+
open = $state(false);
|
|
1030
|
+
height = $state("240px");
|
|
1031
|
+
constructor(ownerWorkspace) {
|
|
1032
|
+
super({ leaves: [] });
|
|
1033
|
+
this.ownerWorkspace = ownerWorkspace;
|
|
1034
|
+
this.id = "bottom-panel";
|
|
1035
|
+
}
|
|
1036
|
+
loadJson(layout, projectionItems) {
|
|
1037
|
+
return super.loadJson(layout, projectionItems).then(() => {
|
|
1038
|
+
const collapsed = /^0(?:px|rem|em|%)?$/.test(layout.height.trim());
|
|
1039
|
+
this.open = !collapsed;
|
|
1040
|
+
if (!collapsed)
|
|
1041
|
+
this.height = layout.height;
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
toJson() {
|
|
1045
|
+
return {
|
|
1046
|
+
...super.toJson(),
|
|
1047
|
+
height: this.open ? this.height : "0px",
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
get size() {
|
|
1051
|
+
const pixels = workspaceCssLengthToPixels(this.height);
|
|
1052
|
+
const size = Number.parseFloat(pixels);
|
|
1053
|
+
return Number.isFinite(size) ? size : 240;
|
|
1054
|
+
}
|
|
1055
|
+
get collapsed() {
|
|
1056
|
+
return !this.open;
|
|
1057
|
+
}
|
|
1058
|
+
expand() {
|
|
1059
|
+
this.ownerWorkspace.setBottomPanelOpen(true);
|
|
1060
|
+
}
|
|
1061
|
+
collapse() {
|
|
1062
|
+
this.ownerWorkspace.setBottomPanelOpen(false);
|
|
1063
|
+
}
|
|
1064
|
+
toggle() {
|
|
1065
|
+
this.ownerWorkspace.toggleBottomPanel();
|
|
1066
|
+
}
|
|
1067
|
+
detach() {
|
|
1068
|
+
return undefined;
|
|
1069
|
+
}
|
|
1070
|
+
removeChild(index, _softDelete = false) {
|
|
1071
|
+
return super.removeChild(index, true);
|
|
1072
|
+
}
|
|
1073
|
+
closeAll() {
|
|
1074
|
+
this.children.slice().forEach((child) => this.removeChild(child, true));
|
|
1075
|
+
}
|
|
1076
|
+
getRoot() {
|
|
1077
|
+
return this;
|
|
1078
|
+
}
|
|
1079
|
+
/** @internal */
|
|
1080
|
+
_applyOpen(open) {
|
|
1081
|
+
this.open = open;
|
|
1082
|
+
}
|
|
1083
|
+
/** @internal */
|
|
1084
|
+
_applySize(size) {
|
|
1085
|
+
this.height = `${size}px`;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
export const ON_DEMAND_PLUGIN_INSTALL_VIEW_TYPE = "plugin-install-prompt";
|
|
1089
|
+
function registryContributionMatchesPath(entry, path) {
|
|
1090
|
+
const normalizedExtension = extensionFromPath(path);
|
|
1091
|
+
return (entry.contributes?.editorViews?.some((view) => {
|
|
1092
|
+
if (view.extensions?.some((extension) => normalizeRegistryExtension(extension) === normalizedExtension)) {
|
|
1093
|
+
return true;
|
|
1094
|
+
}
|
|
1095
|
+
return view.filenamePatterns?.some((pattern) => matchesEditorAssociationGlob(pattern, path));
|
|
1096
|
+
}) ?? false);
|
|
1097
|
+
}
|
|
1098
|
+
function extensionFromPath(path) {
|
|
1099
|
+
const basename = path.split("/").pop() ?? path;
|
|
1100
|
+
const dotIndex = basename.lastIndexOf(".");
|
|
1101
|
+
return dotIndex === -1 ? "" : basename.slice(dotIndex + 1).toLowerCase();
|
|
1102
|
+
}
|
|
1103
|
+
function normalizeRegistryExtension(extension) {
|
|
1104
|
+
return extension.trim().replace(/^\./u, "").toLowerCase();
|
|
1105
|
+
}
|
|
1106
|
+
function formatUnknownError(error) {
|
|
1107
|
+
return error instanceof Error ? error.message : String(error);
|
|
1108
|
+
}
|
|
1109
|
+
export class WorkspaceRibbon {
|
|
1110
|
+
workspace;
|
|
1111
|
+
side;
|
|
1112
|
+
open = $state(true);
|
|
1113
|
+
containerEl = $state(null);
|
|
1114
|
+
ribbonSettingsEl = $state(null);
|
|
1115
|
+
ribbonItemsEl = $state(null);
|
|
1116
|
+
_items = $state({});
|
|
1117
|
+
hostDisposers = new Map();
|
|
1118
|
+
constructor(workspace, side = "left") {
|
|
1119
|
+
this.workspace = workspace;
|
|
1120
|
+
this.side = side;
|
|
1121
|
+
}
|
|
1122
|
+
get items() {
|
|
1123
|
+
return Object.values(this._items);
|
|
1124
|
+
}
|
|
1125
|
+
addItem(item) {
|
|
1126
|
+
if (!this._items[item.id]) {
|
|
1127
|
+
this._items[item.id] = item;
|
|
1128
|
+
if (!item.hidden) {
|
|
1129
|
+
this.hostDisposers.set(item.id, this.workspace.projectCompatibilityRibbonItem(item, this.side));
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
else {
|
|
1133
|
+
new Notice(`Ribbon item: ${item.id} already exists`);
|
|
1134
|
+
}
|
|
1135
|
+
return () => {
|
|
1136
|
+
this.removeItem(item.id);
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
removeItem(id) {
|
|
1140
|
+
this.hostDisposers.get(id)?.();
|
|
1141
|
+
this.hostDisposers.delete(id);
|
|
1142
|
+
if (this._items[id]) {
|
|
1143
|
+
delete this._items[id];
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
function workspaceCssLengthToPixels(value) {
|
|
1148
|
+
const normalized = value.trim().toLowerCase();
|
|
1149
|
+
if (/^0(?:px|rem|em|%)?$/.test(normalized))
|
|
1150
|
+
return "0px";
|
|
1151
|
+
const amount = Number.parseFloat(normalized);
|
|
1152
|
+
if (!Number.isFinite(amount) || amount <= 0)
|
|
1153
|
+
return value;
|
|
1154
|
+
if (normalized.endsWith("rem") || normalized.endsWith("em")) {
|
|
1155
|
+
return `${amount * 16}px`;
|
|
1156
|
+
}
|
|
1157
|
+
return normalized.endsWith("px") ? normalized : `${amount}px`;
|
|
1158
|
+
}
|
|
1159
|
+
function translateWorkspaceSplitDirection(direction) {
|
|
1160
|
+
return direction === "horizontal" ? "vertical" : "horizontal";
|
|
1161
|
+
}
|
|
1162
|
+
function translateWorkspaceSplitDirections(split) {
|
|
1163
|
+
return {
|
|
1164
|
+
...split,
|
|
1165
|
+
direction: translateWorkspaceSplitDirection(split.direction),
|
|
1166
|
+
children: split.children.map((child) => child.type === "split" ? translateWorkspaceSplitDirections(child) : child),
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
function translateWorkspaceWindowDirections(win) {
|
|
1170
|
+
return {
|
|
1171
|
+
...win,
|
|
1172
|
+
direction: translateWorkspaceSplitDirection(win.direction),
|
|
1173
|
+
children: win.children.map((child) => child.type === "split" ? translateWorkspaceSplitDirections(child) : child),
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
function translateWorkspaceJsonSplitDirections(layout) {
|
|
1177
|
+
return {
|
|
1178
|
+
...layout,
|
|
1179
|
+
main: translateWorkspaceSplitDirections(layout.main),
|
|
1180
|
+
left: translateWorkspaceSplitDirections(layout.left),
|
|
1181
|
+
right: translateWorkspaceSplitDirections(layout.right),
|
|
1182
|
+
floating: layout.floating?.map(translateWorkspaceWindowDirections),
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
function workspaceJsonForDesignCore(layout) {
|
|
1186
|
+
const translated = translateWorkspaceJsonSplitDirections(layout);
|
|
1187
|
+
return {
|
|
1188
|
+
...translated,
|
|
1189
|
+
left: {
|
|
1190
|
+
...translated.left,
|
|
1191
|
+
width: workspaceCssLengthToPixels(translated.left.width),
|
|
1192
|
+
},
|
|
1193
|
+
right: {
|
|
1194
|
+
...translated.right,
|
|
1195
|
+
width: workspaceCssLengthToPixels(translated.right.width),
|
|
1196
|
+
},
|
|
1197
|
+
bottom: {
|
|
1198
|
+
...translated.bottom,
|
|
1199
|
+
height: workspaceCssLengthToPixels(translated.bottom.height),
|
|
1200
|
+
},
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
function workspaceJsonForCompatibility(layout) {
|
|
1204
|
+
return translateWorkspaceJsonSplitDirections(layout);
|
|
1205
|
+
}
|
|
1206
|
+
function designLayoutEventFromCompatibility(event) {
|
|
1207
|
+
const source = event.source === "drag-drop"
|
|
1208
|
+
? "drag-drop"
|
|
1209
|
+
: event.source === "resize"
|
|
1210
|
+
? "resize"
|
|
1211
|
+
: event.source === "layout-load"
|
|
1212
|
+
? "layout-restore"
|
|
1213
|
+
: event.source === "bottom-panel"
|
|
1214
|
+
? "bottom-panel"
|
|
1215
|
+
: event.source === "popout"
|
|
1216
|
+
? "window-open"
|
|
1217
|
+
: "layout-replace";
|
|
1218
|
+
return { source, operation: event.operation };
|
|
1219
|
+
}
|
|
1220
|
+
function compatibilityLayoutEventFromDesign(event) {
|
|
1221
|
+
const source = event.source === "drag-drop"
|
|
1222
|
+
? "drag-drop"
|
|
1223
|
+
: event.source === "resize"
|
|
1224
|
+
? "resize"
|
|
1225
|
+
: event.source === "layout-restore"
|
|
1226
|
+
? "layout-load"
|
|
1227
|
+
: event.source === "bottom-panel"
|
|
1228
|
+
? "bottom-panel"
|
|
1229
|
+
: event.source.startsWith("window-")
|
|
1230
|
+
? "popout"
|
|
1231
|
+
: "api";
|
|
1232
|
+
return { source, operation: event.operation ?? event.source };
|
|
1233
|
+
}
|
|
1234
|
+
function createWorkspaceLayoutDropEvent(options) {
|
|
1235
|
+
return {
|
|
1236
|
+
...options,
|
|
1237
|
+
defaultPrevented: false,
|
|
1238
|
+
preventDefault() {
|
|
1239
|
+
this.defaultPrevented = true;
|
|
1240
|
+
},
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
/**
|
|
1244
|
+
* Owns the workspace layout tree, leaf registry, and view-type routing.
|
|
1245
|
+
*
|
|
1246
|
+
* `Workspace` coordinates default file navigation, sidebar placement, layout
|
|
1247
|
+
* persistence, and the active editor/leaf state that plugins interact with.
|
|
1248
|
+
*
|
|
1249
|
+
* @public
|
|
1250
|
+
*/
|
|
1251
|
+
export class Workspace extends EventDispatcher {
|
|
1252
|
+
app;
|
|
1253
|
+
viewTypes = new Map([
|
|
1254
|
+
["empty", (leaf) => new EmptyView(leaf)],
|
|
1255
|
+
]);
|
|
1256
|
+
viewExtensions = new Map();
|
|
1257
|
+
editorViews = new EditorViewRegistry();
|
|
1258
|
+
sidebarViewPlacements = new Map();
|
|
1259
|
+
hoverLinkSources = new Map();
|
|
1260
|
+
rootContainer = new WorkspaceRoot();
|
|
1261
|
+
rootSplit = new WorkspaceView();
|
|
1262
|
+
displayMode = $state("desktop");
|
|
1263
|
+
_activeLeaf = $state(null);
|
|
1264
|
+
lastActiveFile = null;
|
|
1265
|
+
recentFilePaths = [];
|
|
1266
|
+
static RECENT_FILE_LIMIT = 20;
|
|
1267
|
+
focusMode = $state(null);
|
|
1268
|
+
focusedHostId = $state(WORKSPACE_ROOT_HOST_ID);
|
|
1269
|
+
leftSplit = new WorkspaceSidedock({
|
|
1270
|
+
id: "left",
|
|
1271
|
+
initialWidth: "22rem",
|
|
1272
|
+
});
|
|
1273
|
+
rightSplit = new WorkspaceSidedock({
|
|
1274
|
+
id: "right",
|
|
1275
|
+
collapsedSize: "0px",
|
|
1276
|
+
initialOpen: false,
|
|
1277
|
+
});
|
|
1278
|
+
bottomPanel = new WorkspaceBottomPanel(this);
|
|
1279
|
+
floating = new WorkspaceFloating();
|
|
1280
|
+
layoutReady = $state(false);
|
|
1281
|
+
layoutHandlers = [];
|
|
1282
|
+
suspendedLayoutPersistence = 0;
|
|
1283
|
+
#workspaceHostController = null;
|
|
1284
|
+
diagnostics;
|
|
1285
|
+
#syncingWorkspaceHost = 0;
|
|
1286
|
+
#workspaceHostProjection = Promise.resolve();
|
|
1287
|
+
#workspaceHostViewDisposers = new Map();
|
|
1288
|
+
#configurationBridgeDisposer = null;
|
|
1289
|
+
#editorViewBridgeDisposer = null;
|
|
1290
|
+
#pluginSettingsBridgeDisposer = null;
|
|
1291
|
+
#statusBarBridgeDisposer = null;
|
|
1292
|
+
#notificationProgressBridgeDisposer = null;
|
|
1293
|
+
#commandBridgeDisposer = null;
|
|
1294
|
+
workspaceLayoutFile = "workspace.json";
|
|
1295
|
+
statusEl = $state();
|
|
1296
|
+
statusCompatEl = $state();
|
|
1297
|
+
leftRibbon = new WorkspaceRibbon(this, "left");
|
|
1298
|
+
rightRibbon = new WorkspaceRibbon(this, "right");
|
|
1299
|
+
containerEl = $state(createDiv("workspace"));
|
|
1300
|
+
saveLayoutDebounced = debounce(() => {
|
|
1301
|
+
tick().then(() => {
|
|
1302
|
+
if (!this.layoutReady) {
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
return this.app.vault.create(joinPath("/.obsidian", this.workspaceLayoutFile), JSON.stringify(this.toJson(), null, 2));
|
|
1306
|
+
});
|
|
1307
|
+
}, 1000);
|
|
1308
|
+
requestSaveLayout(event = { source: "api" }) {
|
|
1309
|
+
if (this.suspendedLayoutPersistence > 0) {
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
untrack(() => this.commitCompatibilityLayoutToHost(event));
|
|
1313
|
+
this.emitLayoutChangeAndScheduleSave(event);
|
|
1314
|
+
}
|
|
1315
|
+
emitLayoutChangeAndScheduleSave(event) {
|
|
1316
|
+
this.trigger("layout-change", event);
|
|
1317
|
+
this.saveLayoutDebounced();
|
|
1318
|
+
}
|
|
1319
|
+
commitCompatibilityLayoutToHost(event) {
|
|
1320
|
+
if (!this.#workspaceHostController)
|
|
1321
|
+
return;
|
|
1322
|
+
this.#syncingWorkspaceHost += 1;
|
|
1323
|
+
try {
|
|
1324
|
+
this.#workspaceHostController.workspace.changeLayout(workspaceJsonForDesignCore(this.toJson()), designLayoutEventFromCompatibility(event));
|
|
1325
|
+
}
|
|
1326
|
+
finally {
|
|
1327
|
+
this.#syncingWorkspaceHost -= 1;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
projectCompatibilityRibbonItem(item, side) {
|
|
1331
|
+
const registry = this.#workspaceHostController?.ribbon;
|
|
1332
|
+
if (!registry)
|
|
1333
|
+
return () => { };
|
|
1334
|
+
return registry.addItem({
|
|
1335
|
+
id: item.id,
|
|
1336
|
+
side,
|
|
1337
|
+
section: "top",
|
|
1338
|
+
label: item.title,
|
|
1339
|
+
icon: item.icon,
|
|
1340
|
+
onSelect: (event) => item.callback(event),
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
async withoutLayoutPersistence(callback) {
|
|
1344
|
+
this.suspendedLayoutPersistence += 1;
|
|
1345
|
+
try {
|
|
1346
|
+
return await callback();
|
|
1347
|
+
}
|
|
1348
|
+
finally {
|
|
1349
|
+
this.suspendedLayoutPersistence -= 1;
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
dispatchLayoutDropEvent(eventName, options) {
|
|
1353
|
+
const event = createWorkspaceLayoutDropEvent(options);
|
|
1354
|
+
this.trigger(eventName, event);
|
|
1355
|
+
return event;
|
|
1356
|
+
}
|
|
1357
|
+
willShowLayoutDropOverlay(options) {
|
|
1358
|
+
return this.dispatchLayoutDropEvent("layout-will-show-overlay", options);
|
|
1359
|
+
}
|
|
1360
|
+
startLayoutDrag(options) {
|
|
1361
|
+
return this.dispatchLayoutDropEvent("layout-drag-start", options);
|
|
1362
|
+
}
|
|
1363
|
+
endLayoutDrag(options) {
|
|
1364
|
+
return this.dispatchLayoutDropEvent("layout-drag-end", options);
|
|
1365
|
+
}
|
|
1366
|
+
moveWorkspaceChildToTabIndex(item, target, index, options) {
|
|
1367
|
+
if (item === target.children[index]) {
|
|
1368
|
+
return false;
|
|
1369
|
+
}
|
|
1370
|
+
const operation = options.operation ?? "tab-reorder";
|
|
1371
|
+
const eventOptions = {
|
|
1372
|
+
source: options.source ?? "api",
|
|
1373
|
+
operation,
|
|
1374
|
+
position: options.position,
|
|
1375
|
+
target,
|
|
1376
|
+
item,
|
|
1377
|
+
};
|
|
1378
|
+
const willDrop = this.dispatchLayoutDropEvent("layout-will-drop", eventOptions);
|
|
1379
|
+
if (willDrop.defaultPrevented) {
|
|
1380
|
+
return false;
|
|
1381
|
+
}
|
|
1382
|
+
const boundedIndex = Math.max(0, Math.min(index, target.children.length));
|
|
1383
|
+
const existingIndex = target.children.findIndex((child) => child === item);
|
|
1384
|
+
if (existingIndex !== -1) {
|
|
1385
|
+
target.removeChild(item, true);
|
|
1386
|
+
}
|
|
1387
|
+
else if (item.parent) {
|
|
1388
|
+
item.detach(true);
|
|
1389
|
+
}
|
|
1390
|
+
const insertionIndex = existingIndex !== -1 && existingIndex < boundedIndex
|
|
1391
|
+
? boundedIndex - 1
|
|
1392
|
+
: boundedIndex;
|
|
1393
|
+
if (insertionIndex >= target.children.length) {
|
|
1394
|
+
target.children.push(item);
|
|
1395
|
+
item.parent = target;
|
|
1396
|
+
}
|
|
1397
|
+
else {
|
|
1398
|
+
target.addChild(item, insertionIndex);
|
|
1399
|
+
}
|
|
1400
|
+
if (item instanceof WorkspaceLeaf) {
|
|
1401
|
+
target.selected = item;
|
|
1402
|
+
this.activeLeaf = item;
|
|
1403
|
+
}
|
|
1404
|
+
else {
|
|
1405
|
+
target.selected = item;
|
|
1406
|
+
this.activeLeaf = item.getSelectedLeaf();
|
|
1407
|
+
}
|
|
1408
|
+
if (target instanceof WorkspaceBottomPanel) {
|
|
1409
|
+
target._applyOpen(true);
|
|
1410
|
+
}
|
|
1411
|
+
this.requestSaveLayout({ source: "drag-drop", operation });
|
|
1412
|
+
this.dispatchLayoutDropEvent("layout-did-drop", eventOptions);
|
|
1413
|
+
return true;
|
|
1414
|
+
}
|
|
1415
|
+
async dropWorkspaceItemOnTabs(parent, options) {
|
|
1416
|
+
const item = options.item;
|
|
1417
|
+
const file = options.file;
|
|
1418
|
+
if (!item && !file) {
|
|
1419
|
+
return false;
|
|
1420
|
+
}
|
|
1421
|
+
if (parent instanceof WorkspaceBottomPanel &&
|
|
1422
|
+
options.position !== "center") {
|
|
1423
|
+
return false;
|
|
1424
|
+
}
|
|
1425
|
+
const targetTabs = parent instanceof WorkspaceTabs
|
|
1426
|
+
? parent
|
|
1427
|
+
: new WorkspaceTabs({ leaves: [] });
|
|
1428
|
+
const operation = options.operation ??
|
|
1429
|
+
(options.position === "center" ? "tab-drop" : "split-drop");
|
|
1430
|
+
const eventOptions = {
|
|
1431
|
+
source: options.source ?? "api",
|
|
1432
|
+
operation,
|
|
1433
|
+
position: options.position,
|
|
1434
|
+
target: targetTabs,
|
|
1435
|
+
...(item ? { item } : {}),
|
|
1436
|
+
...(file ? { file } : {}),
|
|
1437
|
+
};
|
|
1438
|
+
const willDrop = this.dispatchLayoutDropEvent("layout-will-drop", eventOptions);
|
|
1439
|
+
if (willDrop.defaultPrevented) {
|
|
1440
|
+
return false;
|
|
1441
|
+
}
|
|
1442
|
+
if (options.position === "center") {
|
|
1443
|
+
if (parent instanceof WorkspaceSplit) {
|
|
1444
|
+
parent.addChild(targetTabs);
|
|
1445
|
+
}
|
|
1446
|
+
if (file) {
|
|
1447
|
+
const leaf = new WorkspaceLeaf();
|
|
1448
|
+
targetTabs.addChild(leaf);
|
|
1449
|
+
targetTabs.selected = leaf;
|
|
1450
|
+
await this.withoutLayoutPersistence(() => leaf.openFile(file));
|
|
1451
|
+
this.activeLeaf = leaf;
|
|
1452
|
+
}
|
|
1453
|
+
else if (item) {
|
|
1454
|
+
const movedLeaf = item.detach();
|
|
1455
|
+
if (!(movedLeaf instanceof WorkspaceLeaf)) {
|
|
1456
|
+
return false;
|
|
1457
|
+
}
|
|
1458
|
+
targetTabs.addChild(movedLeaf);
|
|
1459
|
+
targetTabs.selected = movedLeaf;
|
|
1460
|
+
this.activeLeaf = movedLeaf;
|
|
1461
|
+
}
|
|
1462
|
+
if (targetTabs instanceof WorkspaceBottomPanel) {
|
|
1463
|
+
targetTabs._applyOpen(true);
|
|
1464
|
+
}
|
|
1465
|
+
this.requestSaveLayout({ source: "drag-drop", operation });
|
|
1466
|
+
this.dispatchLayoutDropEvent("layout-did-drop", eventOptions);
|
|
1467
|
+
return true;
|
|
1468
|
+
}
|
|
1469
|
+
if (!(parent instanceof WorkspaceTabs)) {
|
|
1470
|
+
return false;
|
|
1471
|
+
}
|
|
1472
|
+
const leaf = new WorkspaceLeaf();
|
|
1473
|
+
if (file) {
|
|
1474
|
+
await this.withoutLayoutPersistence(() => leaf.openFile(file));
|
|
1475
|
+
}
|
|
1476
|
+
else if (item) {
|
|
1477
|
+
await this.withoutLayoutPersistence(() => leaf.setViewState({ ...item.state }));
|
|
1478
|
+
}
|
|
1479
|
+
const newTabs = new WorkspaceTabs({ leaves: [leaf] });
|
|
1480
|
+
const activeSplit = parent.parent;
|
|
1481
|
+
if (options.position === "top" || options.position === "bottom") {
|
|
1482
|
+
if (parent.parent.type === "horizontal") {
|
|
1483
|
+
parent.parent.addChild(newTabs, options.position === "top" ? 0 : undefined);
|
|
1484
|
+
}
|
|
1485
|
+
else {
|
|
1486
|
+
const tab = activeSplit.removeChild(parent, true);
|
|
1487
|
+
if (!tab) {
|
|
1488
|
+
return false;
|
|
1489
|
+
}
|
|
1490
|
+
const split = new WorkspaceView("horizontal");
|
|
1491
|
+
if (options.position === "top") {
|
|
1492
|
+
split.addChild(newTabs);
|
|
1493
|
+
split.addChild(tab);
|
|
1494
|
+
}
|
|
1495
|
+
else {
|
|
1496
|
+
split.addChild(tab);
|
|
1497
|
+
split.addChild(newTabs);
|
|
1498
|
+
}
|
|
1499
|
+
activeSplit.addChild(split);
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
else {
|
|
1503
|
+
if (parent.parent.type === "vertical") {
|
|
1504
|
+
parent.parent.addChild(newTabs, options.position === "left" ? 0 : undefined);
|
|
1505
|
+
}
|
|
1506
|
+
else {
|
|
1507
|
+
const tab = activeSplit.removeChild(parent, true);
|
|
1508
|
+
if (!tab) {
|
|
1509
|
+
return false;
|
|
1510
|
+
}
|
|
1511
|
+
const split = new WorkspaceView("vertical");
|
|
1512
|
+
if (options.position === "left") {
|
|
1513
|
+
split.addChild(newTabs);
|
|
1514
|
+
split.addChild(tab);
|
|
1515
|
+
}
|
|
1516
|
+
else {
|
|
1517
|
+
split.addChild(tab);
|
|
1518
|
+
split.addChild(newTabs);
|
|
1519
|
+
}
|
|
1520
|
+
activeSplit.addChild(split);
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
if (item) {
|
|
1524
|
+
item.detach();
|
|
1525
|
+
}
|
|
1526
|
+
this.activeLeaf = leaf;
|
|
1527
|
+
this.requestSaveLayout({ source: "drag-drop", operation });
|
|
1528
|
+
this.dispatchLayoutDropEvent("layout-did-drop", {
|
|
1529
|
+
...eventOptions,
|
|
1530
|
+
target: newTabs,
|
|
1531
|
+
});
|
|
1532
|
+
return true;
|
|
1533
|
+
}
|
|
1534
|
+
moveLeafToSidebarGroupIndex(leaf, group, index, options) {
|
|
1535
|
+
const operation = options.operation ?? "sidebar-group-reorder";
|
|
1536
|
+
const eventOptions = {
|
|
1537
|
+
source: options.source ?? "api",
|
|
1538
|
+
operation,
|
|
1539
|
+
position: options.position,
|
|
1540
|
+
target: group,
|
|
1541
|
+
item: leaf,
|
|
1542
|
+
};
|
|
1543
|
+
const willDrop = this.dispatchLayoutDropEvent("layout-will-drop", eventOptions);
|
|
1544
|
+
if (willDrop.defaultPrevented) {
|
|
1545
|
+
return false;
|
|
1546
|
+
}
|
|
1547
|
+
const boundedIndex = Math.max(0, Math.min(index, group.children.length));
|
|
1548
|
+
const existingIndex = group.children.findIndex((child) => child === leaf);
|
|
1549
|
+
if (existingIndex !== -1) {
|
|
1550
|
+
group.removeChild(leaf, true);
|
|
1551
|
+
}
|
|
1552
|
+
else if (leaf.parent) {
|
|
1553
|
+
leaf.detach(true);
|
|
1554
|
+
}
|
|
1555
|
+
const insertionIndex = existingIndex !== -1 && existingIndex < boundedIndex
|
|
1556
|
+
? boundedIndex - 1
|
|
1557
|
+
: boundedIndex;
|
|
1558
|
+
group.addChild(leaf, insertionIndex);
|
|
1559
|
+
group.hiddenLeafIds = group.hiddenLeafIds.filter((id) => id !== leaf.id);
|
|
1560
|
+
group.parent.selected = group;
|
|
1561
|
+
this.activeLeaf = leaf;
|
|
1562
|
+
this.requestSaveLayout({ source: "drag-drop", operation });
|
|
1563
|
+
this.dispatchLayoutDropEvent("layout-did-drop", eventOptions);
|
|
1564
|
+
return true;
|
|
1565
|
+
}
|
|
1566
|
+
moveLeafToSidebarGroup(leaf, group, options = {}) {
|
|
1567
|
+
const operation = options.operation ?? "sidebar-group-drop";
|
|
1568
|
+
const eventOptions = {
|
|
1569
|
+
source: options.source ?? "api",
|
|
1570
|
+
operation,
|
|
1571
|
+
position: "center",
|
|
1572
|
+
target: group,
|
|
1573
|
+
item: leaf,
|
|
1574
|
+
};
|
|
1575
|
+
const willDrop = this.dispatchLayoutDropEvent("layout-will-drop", eventOptions);
|
|
1576
|
+
if (willDrop.defaultPrevented) {
|
|
1577
|
+
return false;
|
|
1578
|
+
}
|
|
1579
|
+
if (leaf.parent) {
|
|
1580
|
+
leaf.detach(true);
|
|
1581
|
+
}
|
|
1582
|
+
group.addChild(leaf);
|
|
1583
|
+
group.hiddenLeafIds = group.hiddenLeafIds.filter((id) => id !== leaf.id);
|
|
1584
|
+
group.parent.selected = group;
|
|
1585
|
+
this.activeLeaf = leaf;
|
|
1586
|
+
this.requestSaveLayout({ source: "drag-drop", operation });
|
|
1587
|
+
this.dispatchLayoutDropEvent("layout-did-drop", eventOptions);
|
|
1588
|
+
return true;
|
|
1589
|
+
}
|
|
1590
|
+
containsLeaf(target) {
|
|
1591
|
+
if (!target) {
|
|
1592
|
+
return false;
|
|
1593
|
+
}
|
|
1594
|
+
return Boolean(this.iterateAllLeaves((leaf) => leaf === target || undefined));
|
|
1595
|
+
}
|
|
1596
|
+
containsRootLeaf(target) {
|
|
1597
|
+
if (!target) {
|
|
1598
|
+
return false;
|
|
1599
|
+
}
|
|
1600
|
+
return Boolean(this.rootSplit.iterateAllLeaves((leaf) => leaf === target || undefined));
|
|
1601
|
+
}
|
|
1602
|
+
floatingWindowForLeaf(target) {
|
|
1603
|
+
if (!target) {
|
|
1604
|
+
return null;
|
|
1605
|
+
}
|
|
1606
|
+
for (const floatingWindow of this.floating.children) {
|
|
1607
|
+
const containsTarget = floatingWindow.iterateAllLeaves((leaf) => leaf === target || undefined);
|
|
1608
|
+
if (containsTarget) {
|
|
1609
|
+
return floatingWindow;
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
return null;
|
|
1613
|
+
}
|
|
1614
|
+
getCommandHostIdForLeaf(target) {
|
|
1615
|
+
return this.floatingWindowForLeaf(target)?.id ?? WORKSPACE_ROOT_HOST_ID;
|
|
1616
|
+
}
|
|
1617
|
+
getCommandHostLeaf(hostId) {
|
|
1618
|
+
if (hostId === WORKSPACE_ROOT_HOST_ID) {
|
|
1619
|
+
return this.activeRootLeaf;
|
|
1620
|
+
}
|
|
1621
|
+
const window = this.floating.children.find((entry) => entry.id === hostId);
|
|
1622
|
+
return window ? this.selectedLeafForWindow(window) : null;
|
|
1623
|
+
}
|
|
1624
|
+
getCommandHostDocument(hostId = this.getFocusedCommandHostId()) {
|
|
1625
|
+
if (hostId === WORKSPACE_ROOT_HOST_ID) {
|
|
1626
|
+
return document;
|
|
1627
|
+
}
|
|
1628
|
+
return (this.floating.children.find((entry) => entry.id === hostId)?.doc ??
|
|
1629
|
+
document);
|
|
1630
|
+
}
|
|
1631
|
+
getFocusedCommandHostId() {
|
|
1632
|
+
if (this.focusedHostId === WORKSPACE_ROOT_HOST_ID) {
|
|
1633
|
+
return WORKSPACE_ROOT_HOST_ID;
|
|
1634
|
+
}
|
|
1635
|
+
return this.floating.children.some((entry) => entry.id === this.focusedHostId)
|
|
1636
|
+
? this.focusedHostId
|
|
1637
|
+
: WORKSPACE_ROOT_HOST_ID;
|
|
1638
|
+
}
|
|
1639
|
+
focusRootHost() {
|
|
1640
|
+
this.focusedHostId = WORKSPACE_ROOT_HOST_ID;
|
|
1641
|
+
}
|
|
1642
|
+
get activeLeaf() {
|
|
1643
|
+
if (this.containsLeaf(this._activeLeaf)) {
|
|
1644
|
+
return this._activeLeaf;
|
|
1645
|
+
}
|
|
1646
|
+
return (this.rootSplit.iterateAllTabs((tabs) => tabs.selectedLeaf || undefined) ||
|
|
1647
|
+
null);
|
|
1648
|
+
}
|
|
1649
|
+
get activeRootLeaf() {
|
|
1650
|
+
if (this.containsRootLeaf(this._activeLeaf)) {
|
|
1651
|
+
return this._activeLeaf;
|
|
1652
|
+
}
|
|
1653
|
+
return (this.rootSplit.iterateAllTabs((tabs) => tabs.selectedLeaf || undefined) ||
|
|
1654
|
+
null);
|
|
1655
|
+
}
|
|
1656
|
+
set activeLeaf(leaf) {
|
|
1657
|
+
this._activeLeaf = leaf;
|
|
1658
|
+
}
|
|
1659
|
+
get activeEditor() {
|
|
1660
|
+
const view = this.activeLeaf?.view;
|
|
1661
|
+
if (view instanceof TextFileView) {
|
|
1662
|
+
return {
|
|
1663
|
+
app: this.app,
|
|
1664
|
+
get file() {
|
|
1665
|
+
return view.file;
|
|
1666
|
+
},
|
|
1667
|
+
editor: view.editor,
|
|
1668
|
+
};
|
|
1669
|
+
}
|
|
1670
|
+
return null;
|
|
1671
|
+
}
|
|
1672
|
+
/** @public */
|
|
1673
|
+
get isMobileMode() {
|
|
1674
|
+
return this.displayMode === "mobile";
|
|
1675
|
+
}
|
|
1676
|
+
/** @public */
|
|
1677
|
+
setDisplayMode(mode, reason = "manual") {
|
|
1678
|
+
if (this.displayMode === mode) {
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
const previousMode = this.displayMode;
|
|
1682
|
+
this.displayMode = mode;
|
|
1683
|
+
if (this.#workspaceHostController) {
|
|
1684
|
+
this.#syncingWorkspaceHost += 1;
|
|
1685
|
+
try {
|
|
1686
|
+
this.#workspaceHostController.renderer.setDisplayMode(mode);
|
|
1687
|
+
}
|
|
1688
|
+
finally {
|
|
1689
|
+
this.#syncingWorkspaceHost -= 1;
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
this.trigger("display-mode-change", {
|
|
1693
|
+
mode,
|
|
1694
|
+
previousMode,
|
|
1695
|
+
reason,
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
enterFocusMode(leaf = this.activeRootLeaf) {
|
|
1699
|
+
if (!leaf || !this.containsRootLeaf(leaf)) {
|
|
1700
|
+
return false;
|
|
1701
|
+
}
|
|
1702
|
+
const tabs = this.tabsForLeaf(leaf);
|
|
1703
|
+
if (tabs.inSideBar()) {
|
|
1704
|
+
return false;
|
|
1705
|
+
}
|
|
1706
|
+
tabs.selected = leaf;
|
|
1707
|
+
this.activeLeaf = leaf;
|
|
1708
|
+
this.focusRootHost();
|
|
1709
|
+
return this.setFocusMode({ leaf, tabs });
|
|
1710
|
+
}
|
|
1711
|
+
exitFocusMode() {
|
|
1712
|
+
return this.setFocusMode(null);
|
|
1713
|
+
}
|
|
1714
|
+
clearFocusModeForLeaf(leaf) {
|
|
1715
|
+
if (this.focusMode?.leaf !== leaf) {
|
|
1716
|
+
return false;
|
|
1717
|
+
}
|
|
1718
|
+
return this.exitFocusMode();
|
|
1719
|
+
}
|
|
1720
|
+
isFocusModeForTabs(tabs) {
|
|
1721
|
+
return this.focusMode?.tabs === tabs;
|
|
1722
|
+
}
|
|
1723
|
+
toJson() {
|
|
1724
|
+
return {
|
|
1725
|
+
main: this.rootSplit.toJson(),
|
|
1726
|
+
left: this.leftSplit.toJson(),
|
|
1727
|
+
right: this.rightSplit.toJson(),
|
|
1728
|
+
bottom: this.bottomPanel.toJson(),
|
|
1729
|
+
floating: this.floating.toJson(),
|
|
1730
|
+
active: this.activeLeaf?.id,
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
async restoreLayoutJson(config) {
|
|
1734
|
+
this.exitFocusMode();
|
|
1735
|
+
const normalized = normalizeWorkspaceJson(config);
|
|
1736
|
+
await this.activateLayoutPlugins(normalized);
|
|
1737
|
+
const projectionItems = beginWorkspaceProjection(this);
|
|
1738
|
+
try {
|
|
1739
|
+
await Promise.all([
|
|
1740
|
+
this.rootSplit.loadJson(normalized.main, projectionItems),
|
|
1741
|
+
this.leftSplit.loadJson(normalized.left, projectionItems),
|
|
1742
|
+
this.rightSplit.loadJson(normalized.right, projectionItems),
|
|
1743
|
+
this.bottomPanel.loadJson(normalized.bottom, projectionItems),
|
|
1744
|
+
this.floating.loadJson(normalized.floating, projectionItems),
|
|
1745
|
+
]);
|
|
1746
|
+
}
|
|
1747
|
+
finally {
|
|
1748
|
+
finishWorkspaceProjection(projectionItems);
|
|
1749
|
+
}
|
|
1750
|
+
if (!normalized.active) {
|
|
1751
|
+
if (!this.containsLeaf(this.activeLeaf))
|
|
1752
|
+
this.activeLeaf = null;
|
|
1753
|
+
return;
|
|
1754
|
+
}
|
|
1755
|
+
this.iterateAllLeaves((leaf) => {
|
|
1756
|
+
if (leaf.id === normalized.active) {
|
|
1757
|
+
this.activateLeaf(leaf, {
|
|
1758
|
+
saveLayout: false,
|
|
1759
|
+
focusRootHost: false,
|
|
1760
|
+
source: "layout-load",
|
|
1761
|
+
operation: "restore-active-leaf",
|
|
1762
|
+
});
|
|
1763
|
+
return false;
|
|
1764
|
+
}
|
|
1765
|
+
});
|
|
1766
|
+
}
|
|
1767
|
+
loadLayout(file = "workspace.json") {
|
|
1768
|
+
this.workspaceLayoutFile = file;
|
|
1769
|
+
return this.app.telemetry.measureAsync("workspace.load_layout", async (span) => {
|
|
1770
|
+
span.setAttribute("workspace.layout_file", file);
|
|
1771
|
+
const workspaceFile = this.app.vault.getFileByPath(joinPath("/.obsidian", file));
|
|
1772
|
+
this.layoutReady = false;
|
|
1773
|
+
if (workspaceFile) {
|
|
1774
|
+
await this.restoreLayoutJson(JSON.parse(await this.app.vault.read(workspaceFile)));
|
|
1775
|
+
}
|
|
1776
|
+
else {
|
|
1777
|
+
await this.withoutLayoutPersistence(() => this.seedDefaultSidebarLayout());
|
|
1778
|
+
}
|
|
1779
|
+
this.commitCompatibilityLayoutToHost({ source: "layout-load" });
|
|
1780
|
+
this.layoutReady = true;
|
|
1781
|
+
this.layoutHandlers.forEach((it) => it());
|
|
1782
|
+
this.trigger("layout-ready");
|
|
1783
|
+
}, {
|
|
1784
|
+
attributes: { "workspace.layout_file": file },
|
|
1785
|
+
slowThresholdMs: 250,
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1788
|
+
async seedDefaultSideLeaf(type, side) {
|
|
1789
|
+
if (!this.viewTypes.has(type))
|
|
1790
|
+
return null;
|
|
1791
|
+
const existing = this.getLeavesOfType(type)[0];
|
|
1792
|
+
if (existing)
|
|
1793
|
+
return existing;
|
|
1794
|
+
const leaf = this.ensureSideLeaf(type, side);
|
|
1795
|
+
await leaf.setViewState({ type, state: {} });
|
|
1796
|
+
return leaf;
|
|
1797
|
+
}
|
|
1798
|
+
async seedDefaultSidebarLayout() {
|
|
1799
|
+
const leftLeaves = [];
|
|
1800
|
+
const rightLeaves = [];
|
|
1801
|
+
for (const type of DEFAULT_LEFT_SIDEBAR_VIEW_TYPES) {
|
|
1802
|
+
const leaf = await this.seedDefaultSideLeaf(type, "left");
|
|
1803
|
+
if (leaf)
|
|
1804
|
+
leftLeaves.push(leaf);
|
|
1805
|
+
}
|
|
1806
|
+
for (const type of DEFAULT_RIGHT_SIDEBAR_VIEW_TYPES) {
|
|
1807
|
+
const leaf = await this.seedDefaultSideLeaf(type, "right");
|
|
1808
|
+
if (leaf)
|
|
1809
|
+
rightLeaves.push(leaf);
|
|
1810
|
+
}
|
|
1811
|
+
if (leftLeaves[0])
|
|
1812
|
+
await this.revealLeaf(leftLeaves[0]);
|
|
1813
|
+
if (rightLeaves[0])
|
|
1814
|
+
await this.revealLeaf(rightLeaves[0]);
|
|
1815
|
+
}
|
|
1816
|
+
namedWorkspacesPath() {
|
|
1817
|
+
return joinPath("/.obsidian", NAMED_WORKSPACES_FILE);
|
|
1818
|
+
}
|
|
1819
|
+
async readNamedWorkspaceStore() {
|
|
1820
|
+
const file = this.app.vault.getFileByPath(this.namedWorkspacesPath());
|
|
1821
|
+
if (!file)
|
|
1822
|
+
return { workspaces: {} };
|
|
1823
|
+
return parseNamedWorkspaceStore(await this.app.vault.read(file));
|
|
1824
|
+
}
|
|
1825
|
+
async writeNamedWorkspaceStore(store) {
|
|
1826
|
+
await this.app.vault.create(this.namedWorkspacesPath(), serializeNamedWorkspaceStore(store));
|
|
1827
|
+
}
|
|
1828
|
+
async listNamedLayouts() {
|
|
1829
|
+
const store = await this.readNamedWorkspaceStore();
|
|
1830
|
+
return Object.keys(store.workspaces).sort((left, right) => left.localeCompare(right));
|
|
1831
|
+
}
|
|
1832
|
+
async saveNamedLayout(name) {
|
|
1833
|
+
const normalized = name.trim();
|
|
1834
|
+
if (!normalized) {
|
|
1835
|
+
throw new Error("Workspace layout name must not be empty");
|
|
1836
|
+
}
|
|
1837
|
+
const store = await this.readNamedWorkspaceStore();
|
|
1838
|
+
store.workspaces[normalized] = this.getLayout();
|
|
1839
|
+
store.active = normalized;
|
|
1840
|
+
await this.writeNamedWorkspaceStore(store);
|
|
1841
|
+
}
|
|
1842
|
+
async loadNamedLayout(name) {
|
|
1843
|
+
const store = await this.readNamedWorkspaceStore();
|
|
1844
|
+
const layout = store.workspaces[name];
|
|
1845
|
+
if (!layout) {
|
|
1846
|
+
new Notice(`No saved workspace layout named “${name}”`, undefined, this.app);
|
|
1847
|
+
return;
|
|
1848
|
+
}
|
|
1849
|
+
store.active = name;
|
|
1850
|
+
await this.writeNamedWorkspaceStore(store);
|
|
1851
|
+
await this.changeLayout(layout);
|
|
1852
|
+
}
|
|
1853
|
+
async resetLayoutToDefault() {
|
|
1854
|
+
this.layoutReady = false;
|
|
1855
|
+
try {
|
|
1856
|
+
await this.withoutLayoutPersistence(async () => {
|
|
1857
|
+
await this.restoreLayoutJson({});
|
|
1858
|
+
await this.seedDefaultSidebarLayout();
|
|
1859
|
+
});
|
|
1860
|
+
this.commitCompatibilityLayoutToHost({
|
|
1861
|
+
source: "api",
|
|
1862
|
+
operation: "reset-layout",
|
|
1863
|
+
});
|
|
1864
|
+
}
|
|
1865
|
+
finally {
|
|
1866
|
+
this.layoutReady = true;
|
|
1867
|
+
}
|
|
1868
|
+
this.layoutHandlers.forEach((handler) => handler());
|
|
1869
|
+
this.trigger("layout-ready");
|
|
1870
|
+
this.requestSaveLayout({ source: "api", operation: "reset-layout" });
|
|
1871
|
+
}
|
|
1872
|
+
installWorkspaceLayoutCommands() {
|
|
1873
|
+
this.app.commands.registerCommand({
|
|
1874
|
+
id: "workspace:save-layout",
|
|
1875
|
+
name: "Save workspace layout",
|
|
1876
|
+
title: "Save workspace layout",
|
|
1877
|
+
category: "Workspace",
|
|
1878
|
+
icon: "save",
|
|
1879
|
+
sourcePlugin: "app",
|
|
1880
|
+
callback: () => {
|
|
1881
|
+
void this.listNamedLayouts().then((names) => {
|
|
1882
|
+
promptSaveWorkspaceLayout(this.app, names, (name) => this.saveNamedLayout(name));
|
|
1883
|
+
});
|
|
1884
|
+
},
|
|
1885
|
+
});
|
|
1886
|
+
this.app.commands.registerCommand({
|
|
1887
|
+
id: "workspace:load-layout",
|
|
1888
|
+
name: "Load workspace layout",
|
|
1889
|
+
title: "Load workspace layout",
|
|
1890
|
+
category: "Workspace",
|
|
1891
|
+
icon: "folder-open",
|
|
1892
|
+
sourcePlugin: "app",
|
|
1893
|
+
callback: () => {
|
|
1894
|
+
void this.listNamedLayouts().then((names) => {
|
|
1895
|
+
if (names.length === 0) {
|
|
1896
|
+
new Notice("No saved workspace layouts", undefined, this.app);
|
|
1897
|
+
return;
|
|
1898
|
+
}
|
|
1899
|
+
promptLoadWorkspaceLayout(this.app, names, (name) => this.loadNamedLayout(name));
|
|
1900
|
+
});
|
|
1901
|
+
},
|
|
1902
|
+
});
|
|
1903
|
+
this.app.commands.registerCommand({
|
|
1904
|
+
id: "workspace:reset-layout",
|
|
1905
|
+
name: "Reset workspace layout",
|
|
1906
|
+
title: "Reset workspace layout",
|
|
1907
|
+
category: "Workspace",
|
|
1908
|
+
icon: "rotate-ccw",
|
|
1909
|
+
sourcePlugin: "app",
|
|
1910
|
+
callback: () => {
|
|
1911
|
+
void promptConfirm(this.getCommandHostDocument(), {
|
|
1912
|
+
title: "Reset workspace layout",
|
|
1913
|
+
description: "Reset the workspace to File Explorer and Search on the left, and Outline, File Properties, and Tags on the right?",
|
|
1914
|
+
confirmLabel: "Reset",
|
|
1915
|
+
}).then((confirmed) => {
|
|
1916
|
+
if (confirmed)
|
|
1917
|
+
return this.resetLayoutToDefault();
|
|
1918
|
+
});
|
|
1919
|
+
},
|
|
1920
|
+
});
|
|
1921
|
+
this.app.commands.registerCommand({
|
|
1922
|
+
id: "app:go-to-file",
|
|
1923
|
+
name: "Go to file",
|
|
1924
|
+
title: "Go to file",
|
|
1925
|
+
category: "Workspace",
|
|
1926
|
+
icon: "file",
|
|
1927
|
+
sourcePlugin: "app",
|
|
1928
|
+
hotkeys: [{ modifiers: ["Mod"], key: "o" }],
|
|
1929
|
+
callback: () => {
|
|
1930
|
+
getWorkspaceHostBinding(this).controller.commands.openPalette({
|
|
1931
|
+
tab: "files",
|
|
1932
|
+
});
|
|
1933
|
+
},
|
|
1934
|
+
});
|
|
1935
|
+
}
|
|
1936
|
+
/**
|
|
1937
|
+
* Runs the callback function right away if layout is already ready, or push
|
|
1938
|
+
* it to a queue to be called later when layout is ready.
|
|
1939
|
+
*
|
|
1940
|
+
* @public
|
|
1941
|
+
*/
|
|
1942
|
+
onLayoutReady(callback) {
|
|
1943
|
+
if (this.layoutReady) {
|
|
1944
|
+
callback();
|
|
1945
|
+
return;
|
|
1946
|
+
}
|
|
1947
|
+
this.layoutHandlers.push(callback);
|
|
1948
|
+
}
|
|
1949
|
+
constructor(app) {
|
|
1950
|
+
super();
|
|
1951
|
+
this.app = app;
|
|
1952
|
+
this.rootContainer.bindWorkspace(this);
|
|
1953
|
+
this.rootSplit.bindWorkspace(this);
|
|
1954
|
+
this.leftSplit.bindWorkspace(this);
|
|
1955
|
+
this.rightSplit.bindWorkspace(this);
|
|
1956
|
+
this.bottomPanel.bindWorkspace(this);
|
|
1957
|
+
this.floating.bindWorkspace(this);
|
|
1958
|
+
this.rootSplit.addChild(new WorkspaceTabs());
|
|
1959
|
+
const shellOptions = this.app.props?.workspaceShell;
|
|
1960
|
+
const application = shellOptions?.application;
|
|
1961
|
+
this.#workspaceHostController = new AppShellController({
|
|
1962
|
+
layout: workspaceJsonForDesignCore(this.toJson()),
|
|
1963
|
+
application: {
|
|
1964
|
+
name: application?.name ?? "Lapis Notes",
|
|
1965
|
+
version: this.app.version,
|
|
1966
|
+
logoUrl: application?.logoUrl === null
|
|
1967
|
+
? undefined
|
|
1968
|
+
: (application?.logoUrl ?? DEFAULT_LAPIS_LOGO_URL),
|
|
1969
|
+
buildTime: application?.buildTime,
|
|
1970
|
+
commitHash: application?.commitHash,
|
|
1971
|
+
copyright: application?.copyright,
|
|
1972
|
+
},
|
|
1973
|
+
plugins: [
|
|
1974
|
+
...(shellOptions?.notifications === false
|
|
1975
|
+
? []
|
|
1976
|
+
: [notificationsPlugin()]),
|
|
1977
|
+
fModePlugin({ enabled: false }),
|
|
1978
|
+
problemsPlugin({
|
|
1979
|
+
navigation: {
|
|
1980
|
+
open: (location) => this.openDiagnosticLocation(location),
|
|
1981
|
+
},
|
|
1982
|
+
clipboard: {
|
|
1983
|
+
writeText: async (_label, value) => {
|
|
1984
|
+
await globalThis.navigator?.clipboard?.writeText(value);
|
|
1985
|
+
},
|
|
1986
|
+
},
|
|
1987
|
+
}),
|
|
1988
|
+
],
|
|
1989
|
+
persistence: {
|
|
1990
|
+
configuration: {
|
|
1991
|
+
load: async () => ({
|
|
1992
|
+
version: 1,
|
|
1993
|
+
values: Object.fromEntries(this.app.configuration.getConfiguration().entries()),
|
|
1994
|
+
}),
|
|
1995
|
+
save: async (snapshot) => {
|
|
1996
|
+
await this.app.configuration.updateConfigurationOptions(snapshot.values);
|
|
1997
|
+
},
|
|
1998
|
+
},
|
|
1999
|
+
plugins: createAppShellPluginPersistence(() => this.app.vault),
|
|
2000
|
+
},
|
|
2001
|
+
});
|
|
2002
|
+
setWorkspaceHostBinding(this, {
|
|
2003
|
+
controller: this.#workspaceHostController,
|
|
2004
|
+
});
|
|
2005
|
+
this.installCommandBridge(this.#workspaceHostController);
|
|
2006
|
+
this.installWorkspaceLayoutCommands();
|
|
2007
|
+
this.on("file-open", (file) => {
|
|
2008
|
+
if (file)
|
|
2009
|
+
this.recordRecentFile(file);
|
|
2010
|
+
});
|
|
2011
|
+
this.installStatusBarBridge(this.#workspaceHostController);
|
|
2012
|
+
this.installNotificationProgressBridge(this.#workspaceHostController);
|
|
2013
|
+
this.diagnostics = new DiagnosticsManager(this.#workspaceHostController.diagnostics, () => new Menu(), appendLapisMenuToWorkspaceMenu);
|
|
2014
|
+
this.installWorkspaceHostEventBridge(this.#workspaceHostController);
|
|
2015
|
+
const editorViewBridgeRef = this.editorViews.on("changed", ({ id }) => {
|
|
2016
|
+
this.syncWorkspaceHostEditorView(id);
|
|
2017
|
+
});
|
|
2018
|
+
this.#editorViewBridgeDisposer = () => this.editorViews.offref(editorViewBridgeRef);
|
|
2019
|
+
$effect(() => {
|
|
2020
|
+
const activeLeaf = this.activeLeaf;
|
|
2021
|
+
if (activeLeaf?.view instanceof FileView && activeLeaf.view.file) {
|
|
2022
|
+
this.lastActiveFile = activeLeaf.view.file;
|
|
2023
|
+
}
|
|
2024
|
+
this.trigger("active-leaf-change", activeLeaf);
|
|
2025
|
+
});
|
|
2026
|
+
$effect(() => {
|
|
2027
|
+
this.trigger("resize", {
|
|
2028
|
+
id: "left",
|
|
2029
|
+
props: { width: this.leftSplit.sidebar.width },
|
|
2030
|
+
});
|
|
2031
|
+
});
|
|
2032
|
+
$effect(() => {
|
|
2033
|
+
this.trigger("resize", {
|
|
2034
|
+
id: "right",
|
|
2035
|
+
props: { width: this.rightSplit.sidebar.width },
|
|
2036
|
+
});
|
|
2037
|
+
});
|
|
2038
|
+
}
|
|
2039
|
+
async openDiagnosticLocation(location) {
|
|
2040
|
+
const path = pathFromDiagnosticResource(location.resource);
|
|
2041
|
+
if (!path)
|
|
2042
|
+
return;
|
|
2043
|
+
const file = this.app.vault.getFileByPath(path);
|
|
2044
|
+
if (!file)
|
|
2045
|
+
return;
|
|
2046
|
+
const existingLeaf = this.iterateAllLeaves((leaf) => leaf.view instanceof FileView && leaf.view.file?.path === file.path
|
|
2047
|
+
? leaf
|
|
2048
|
+
: undefined);
|
|
2049
|
+
if (existingLeaf) {
|
|
2050
|
+
this.setActiveLeaf(existingLeaf);
|
|
2051
|
+
}
|
|
2052
|
+
else {
|
|
2053
|
+
await this.openLinkText(file.path, "");
|
|
2054
|
+
}
|
|
2055
|
+
const editor = this.activeEditor?.editor;
|
|
2056
|
+
if (!location.range || !editor)
|
|
2057
|
+
return;
|
|
2058
|
+
editor.setSelection({
|
|
2059
|
+
line: location.range.start.line,
|
|
2060
|
+
ch: location.range.start.character,
|
|
2061
|
+
}, {
|
|
2062
|
+
line: location.range.end.line,
|
|
2063
|
+
ch: location.range.end.character,
|
|
2064
|
+
});
|
|
2065
|
+
editor.focus();
|
|
2066
|
+
}
|
|
2067
|
+
/** @internal Complete the configuration bridge after App construction. */
|
|
2068
|
+
bindConfiguration() {
|
|
2069
|
+
this.#configurationBridgeDisposer?.();
|
|
2070
|
+
const controller = this.#workspaceHostController;
|
|
2071
|
+
if (!controller)
|
|
2072
|
+
return;
|
|
2073
|
+
const syncAll = () => {
|
|
2074
|
+
const snapshot = controller.settings.getSnapshot();
|
|
2075
|
+
const apiValues = Object.fromEntries(this.app.configuration.getConfiguration().entries());
|
|
2076
|
+
const next = { ...snapshot.values };
|
|
2077
|
+
for (const key of Object.keys(next)) {
|
|
2078
|
+
if (Object.hasOwn(apiValues, key))
|
|
2079
|
+
next[key] = apiValues[key];
|
|
2080
|
+
}
|
|
2081
|
+
if (!isEqual(next, snapshot.values)) {
|
|
2082
|
+
controller.settings.changeSnapshot({ version: 1, values: next });
|
|
2083
|
+
}
|
|
2084
|
+
};
|
|
2085
|
+
const configurationRef = this.app.configuration.on("updated", ({ key, value }) => {
|
|
2086
|
+
const snapshot = controller.settings.getSnapshot();
|
|
2087
|
+
if (!Object.hasOwn(snapshot.values, key))
|
|
2088
|
+
return;
|
|
2089
|
+
const next = { ...snapshot.values };
|
|
2090
|
+
if (value === undefined)
|
|
2091
|
+
delete next[key];
|
|
2092
|
+
else
|
|
2093
|
+
next[key] = value;
|
|
2094
|
+
if (!isEqual(next, snapshot.values)) {
|
|
2095
|
+
controller.settings.changeSnapshot({ version: 1, values: next });
|
|
2096
|
+
}
|
|
2097
|
+
});
|
|
2098
|
+
const schemaRef = controller.settings.on("schema-change", syncAll);
|
|
2099
|
+
this.#configurationBridgeDisposer = () => {
|
|
2100
|
+
this.app.configuration.offref(configurationRef);
|
|
2101
|
+
controller.settings.offref(schemaRef);
|
|
2102
|
+
};
|
|
2103
|
+
}
|
|
2104
|
+
/** @internal Connect the API plugin lifecycle to Design Core presentation. */
|
|
2105
|
+
bindPlugins() {
|
|
2106
|
+
this.#pluginSettingsBridgeDisposer?.();
|
|
2107
|
+
const controller = this.#workspaceHostController;
|
|
2108
|
+
if (!controller)
|
|
2109
|
+
return;
|
|
2110
|
+
const manager = this.app.plugins;
|
|
2111
|
+
const source = {
|
|
2112
|
+
id: "lapis",
|
|
2113
|
+
getEntries: () => manager.corePluginEntries.map((entry) => ({
|
|
2114
|
+
id: entry.manifest.id,
|
|
2115
|
+
name: entry.manifest.name,
|
|
2116
|
+
description: entry.manifest.description,
|
|
2117
|
+
required: entry.required,
|
|
2118
|
+
enabled: entry.enabled,
|
|
2119
|
+
status: entry.errorMessage
|
|
2120
|
+
? "failed"
|
|
2121
|
+
: entry.enabled
|
|
2122
|
+
? "enabled"
|
|
2123
|
+
: "disabled",
|
|
2124
|
+
distribution: entry.distribution,
|
|
2125
|
+
error: entry.errorMessage ?? undefined,
|
|
2126
|
+
})),
|
|
2127
|
+
enable: (id) => manager.enablePlugin(id),
|
|
2128
|
+
disable: (id) => manager.disablePlugin(id),
|
|
2129
|
+
subscribe: (listener) => {
|
|
2130
|
+
const loaded = manager.on("plugins-loaded", listener);
|
|
2131
|
+
const enabled = manager.on("plugin-enabled", listener);
|
|
2132
|
+
const disabled = manager.on("plugin-disabled", listener);
|
|
2133
|
+
const error = manager.on("plugin-error", listener);
|
|
2134
|
+
return () => {
|
|
2135
|
+
manager.offref(loaded);
|
|
2136
|
+
manager.offref(enabled);
|
|
2137
|
+
manager.offref(disabled);
|
|
2138
|
+
manager.offref(error);
|
|
2139
|
+
};
|
|
2140
|
+
},
|
|
2141
|
+
};
|
|
2142
|
+
this.#pluginSettingsBridgeDisposer =
|
|
2143
|
+
controller.managedPlugins.registerSource(source);
|
|
2144
|
+
}
|
|
2145
|
+
/** Dispose the API-owned shell controller and its compatibility bridges. */
|
|
2146
|
+
async disposeWorkspaceHost() {
|
|
2147
|
+
this.app.languageServices.unbindDiagnostics();
|
|
2148
|
+
this.#configurationBridgeDisposer?.();
|
|
2149
|
+
this.#configurationBridgeDisposer = null;
|
|
2150
|
+
this.#pluginSettingsBridgeDisposer?.();
|
|
2151
|
+
this.#pluginSettingsBridgeDisposer = null;
|
|
2152
|
+
this.#statusBarBridgeDisposer?.();
|
|
2153
|
+
this.#statusBarBridgeDisposer = null;
|
|
2154
|
+
this.#notificationProgressBridgeDisposer?.();
|
|
2155
|
+
this.#notificationProgressBridgeDisposer = null;
|
|
2156
|
+
this.#commandBridgeDisposer?.();
|
|
2157
|
+
this.#commandBridgeDisposer = null;
|
|
2158
|
+
this.#editorViewBridgeDisposer?.();
|
|
2159
|
+
this.#editorViewBridgeDisposer = null;
|
|
2160
|
+
this.#workspaceHostViewDisposers.forEach((dispose) => dispose());
|
|
2161
|
+
this.#workspaceHostViewDisposers.clear();
|
|
2162
|
+
await this.#workspaceHostController?.dispose();
|
|
2163
|
+
}
|
|
2164
|
+
installCommandBridge(controller) {
|
|
2165
|
+
const disposers = new Map();
|
|
2166
|
+
const sync = (command) => {
|
|
2167
|
+
disposers.get(command.id)?.();
|
|
2168
|
+
const projected = {
|
|
2169
|
+
id: command.id,
|
|
2170
|
+
title: command.name,
|
|
2171
|
+
category: command.category,
|
|
2172
|
+
icon: command.icon,
|
|
2173
|
+
sourcePlugin: command.sourcePlugin,
|
|
2174
|
+
hotkeys: this.app.commands.getEffectiveHotkeys(command.id),
|
|
2175
|
+
when: () => this.app.commands.isCommandAvailable(command.id),
|
|
2176
|
+
callback: () => this.app.commands.executeCommand(command.id),
|
|
2177
|
+
};
|
|
2178
|
+
disposers.set(command.id, controller.commands.register(projected));
|
|
2179
|
+
};
|
|
2180
|
+
const remove = (command) => {
|
|
2181
|
+
disposers.get(command.id)?.();
|
|
2182
|
+
disposers.delete(command.id);
|
|
2183
|
+
};
|
|
2184
|
+
for (const command of Object.values(this.app.commands.commands)) {
|
|
2185
|
+
sync(command);
|
|
2186
|
+
}
|
|
2187
|
+
const registered = this.app.commands.on("register", sync);
|
|
2188
|
+
const unregistered = this.app.commands.on("unregister", remove);
|
|
2189
|
+
const hotkeysUpdated = this.app.commands.on("hotkeys-updated", (event) => {
|
|
2190
|
+
if (event.commandId) {
|
|
2191
|
+
const command = this.app.commands.getCommand(event.commandId);
|
|
2192
|
+
if (command)
|
|
2193
|
+
sync(command);
|
|
2194
|
+
return;
|
|
2195
|
+
}
|
|
2196
|
+
for (const command of Object.values(this.app.commands.commands)) {
|
|
2197
|
+
sync(command);
|
|
2198
|
+
}
|
|
2199
|
+
});
|
|
2200
|
+
this.#commandBridgeDisposer = () => {
|
|
2201
|
+
this.app.commands.offref(registered);
|
|
2202
|
+
this.app.commands.offref(unregistered);
|
|
2203
|
+
this.app.commands.offref(hotkeysUpdated);
|
|
2204
|
+
disposers.forEach((dispose) => dispose());
|
|
2205
|
+
disposers.clear();
|
|
2206
|
+
};
|
|
2207
|
+
}
|
|
2208
|
+
projectStatusBarItem(controller, item, align) {
|
|
2209
|
+
controller.status.addItem({
|
|
2210
|
+
id: item.id,
|
|
2211
|
+
align,
|
|
2212
|
+
priority: item.priority,
|
|
2213
|
+
label: item.text,
|
|
2214
|
+
segments: item.segments ?? (item.text ? [item.text] : undefined),
|
|
2215
|
+
tooltip: item.tooltip,
|
|
2216
|
+
icon: item.icon,
|
|
2217
|
+
busy: item.spin,
|
|
2218
|
+
onSelect: item.command
|
|
2219
|
+
? () => void this.app.commands.executeCommand(item.command)
|
|
2220
|
+
: undefined,
|
|
2221
|
+
buildMenu: item.buildMenu
|
|
2222
|
+
? (workspaceMenu) => {
|
|
2223
|
+
const menu = new Menu();
|
|
2224
|
+
item.buildMenu(menu);
|
|
2225
|
+
appendLapisMenuToWorkspaceMenu(workspaceMenu, menu);
|
|
2226
|
+
}
|
|
2227
|
+
: undefined,
|
|
2228
|
+
});
|
|
2229
|
+
}
|
|
2230
|
+
installStatusBarBridge(controller) {
|
|
2231
|
+
const projectedIds = new Set();
|
|
2232
|
+
const sync = () => {
|
|
2233
|
+
for (const id of projectedIds)
|
|
2234
|
+
controller.status.removeItem(id);
|
|
2235
|
+
projectedIds.clear();
|
|
2236
|
+
for (const item of this.app.statusBar.getVisibleItems("left", this.app.contextKeys)) {
|
|
2237
|
+
projectedIds.add(item.id);
|
|
2238
|
+
this.projectStatusBarItem(controller, item, "left");
|
|
2239
|
+
}
|
|
2240
|
+
for (const item of this.app.statusBar.getVisibleItems("right", this.app.contextKeys)) {
|
|
2241
|
+
projectedIds.add(item.id);
|
|
2242
|
+
this.projectStatusBarItem(controller, item, "right");
|
|
2243
|
+
}
|
|
2244
|
+
};
|
|
2245
|
+
const unsubscribeStatus = this.app.statusBar.subscribe(sync);
|
|
2246
|
+
const contextRef = this.app.contextKeys.on("change", sync);
|
|
2247
|
+
this.#statusBarBridgeDisposer = () => {
|
|
2248
|
+
unsubscribeStatus();
|
|
2249
|
+
this.app.contextKeys.offref(contextRef);
|
|
2250
|
+
for (const id of projectedIds)
|
|
2251
|
+
controller.status.removeItem(id);
|
|
2252
|
+
projectedIds.clear();
|
|
2253
|
+
};
|
|
2254
|
+
}
|
|
2255
|
+
installNotificationProgressBridge(controller) {
|
|
2256
|
+
const notifications = this.app.notifications;
|
|
2257
|
+
if (typeof notifications?.on !== "function") {
|
|
2258
|
+
return;
|
|
2259
|
+
}
|
|
2260
|
+
const projected = new Map();
|
|
2261
|
+
const sync = () => {
|
|
2262
|
+
const visible = notifications.activeProgress.filter((entry) => entry.location !== "silent" &&
|
|
2263
|
+
(entry.status === "running" || entry.status === "cancelling"));
|
|
2264
|
+
const visibleIds = new Set(visible.map((entry) => entry.id));
|
|
2265
|
+
for (const [id, handle] of projected) {
|
|
2266
|
+
if (!visibleIds.has(id)) {
|
|
2267
|
+
handle.complete();
|
|
2268
|
+
projected.delete(id);
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
for (const snapshot of visible) {
|
|
2272
|
+
let handle = projected.get(snapshot.id);
|
|
2273
|
+
if (!handle) {
|
|
2274
|
+
handle = controller.notifications.createProgress({
|
|
2275
|
+
id: snapshot.id,
|
|
2276
|
+
title: snapshot.title,
|
|
2277
|
+
message: snapshot.message,
|
|
2278
|
+
source: snapshot.source,
|
|
2279
|
+
location: snapshot.location,
|
|
2280
|
+
cancellable: snapshot.cancellable,
|
|
2281
|
+
});
|
|
2282
|
+
projected.set(snapshot.id, handle);
|
|
2283
|
+
}
|
|
2284
|
+
handle.report({
|
|
2285
|
+
message: snapshot.message,
|
|
2286
|
+
current: snapshot.current,
|
|
2287
|
+
total: snapshot.total,
|
|
2288
|
+
indeterminate: snapshot.indeterminate,
|
|
2289
|
+
});
|
|
2290
|
+
}
|
|
2291
|
+
};
|
|
2292
|
+
const changed = notifications.on("changed", sync);
|
|
2293
|
+
sync();
|
|
2294
|
+
this.#notificationProgressBridgeDisposer = () => {
|
|
2295
|
+
notifications.offref(changed);
|
|
2296
|
+
for (const handle of projected.values()) {
|
|
2297
|
+
handle.complete();
|
|
2298
|
+
}
|
|
2299
|
+
projected.clear();
|
|
2300
|
+
};
|
|
2301
|
+
}
|
|
2302
|
+
syncWorkspaceHostEditorView(id) {
|
|
2303
|
+
const registry = this.#workspaceHostController?.editorViews;
|
|
2304
|
+
if (!registry)
|
|
2305
|
+
return;
|
|
2306
|
+
const contribution = this.editorViews.get(id);
|
|
2307
|
+
if (!contribution) {
|
|
2308
|
+
registry.unregister(id);
|
|
2309
|
+
return;
|
|
2310
|
+
}
|
|
2311
|
+
registry.unregister(id);
|
|
2312
|
+
registry.register(toDesignEditorViewContribution(contribution));
|
|
2313
|
+
}
|
|
2314
|
+
installWorkspaceHostEventBridge(controller) {
|
|
2315
|
+
controller.on("layout-change", (event) => {
|
|
2316
|
+
if (this.#syncingWorkspaceHost > 0 || event.source === "display-mode") {
|
|
2317
|
+
return;
|
|
2318
|
+
}
|
|
2319
|
+
this.enqueueWorkspaceHostProjection(controller.getLayout(), event);
|
|
2320
|
+
});
|
|
2321
|
+
controller.on("display-mode-change", (mode) => {
|
|
2322
|
+
if (this.#syncingWorkspaceHost > 0 || this.displayMode === mode)
|
|
2323
|
+
return;
|
|
2324
|
+
const previousMode = this.displayMode;
|
|
2325
|
+
this.displayMode = mode;
|
|
2326
|
+
this.trigger("display-mode-change", {
|
|
2327
|
+
mode,
|
|
2328
|
+
previousMode,
|
|
2329
|
+
reason: "viewport",
|
|
2330
|
+
});
|
|
2331
|
+
});
|
|
2332
|
+
controller.on("layout-will-show-overlay", (event) => {
|
|
2333
|
+
this.forwardWorkspaceHostDropEvent("layout-will-show-overlay", event);
|
|
2334
|
+
});
|
|
2335
|
+
controller.on("layout-will-drop", (event) => {
|
|
2336
|
+
this.forwardWorkspaceHostDropEvent("layout-will-drop", event);
|
|
2337
|
+
});
|
|
2338
|
+
controller.on("layout-did-drop", (event) => {
|
|
2339
|
+
this.forwardWorkspaceHostDropEvent("layout-did-drop", event);
|
|
2340
|
+
});
|
|
2341
|
+
controller.on("layout-drag-start", (event) => {
|
|
2342
|
+
this.forwardWorkspaceHostDragEvent("layout-drag-start", event);
|
|
2343
|
+
});
|
|
2344
|
+
controller.on("layout-drag-end", (event) => {
|
|
2345
|
+
this.forwardWorkspaceHostDragEvent("layout-drag-end", event);
|
|
2346
|
+
});
|
|
2347
|
+
}
|
|
2348
|
+
enqueueWorkspaceHostProjection(layout, event) {
|
|
2349
|
+
const projected = workspaceJsonForCompatibility(normalizeWorkspaceJson(layout));
|
|
2350
|
+
const preserveSidebarWidth = (side, current) => {
|
|
2351
|
+
const value = projected[side].width.trim();
|
|
2352
|
+
if (/^0(?:px|rem|em|%)?$/.test(value)) {
|
|
2353
|
+
projected[side].width = "0";
|
|
2354
|
+
}
|
|
2355
|
+
else if (!(event.source === "resize" && event.id === side)) {
|
|
2356
|
+
projected[side].width = current.sidebar.width;
|
|
2357
|
+
}
|
|
2358
|
+
};
|
|
2359
|
+
preserveSidebarWidth("left", this.leftSplit);
|
|
2360
|
+
preserveSidebarWidth("right", this.rightSplit);
|
|
2361
|
+
const run = this.#workspaceHostProjection.then(async () => {
|
|
2362
|
+
await this.withoutLayoutPersistence(async () => {
|
|
2363
|
+
await this.restoreLayoutJson(projected);
|
|
2364
|
+
// Sidedock compatibility effects flush after loadJson resolves. Keep
|
|
2365
|
+
// persistence suppressed through that flush so an older queued
|
|
2366
|
+
// projection cannot be committed back over a newer controller layout.
|
|
2367
|
+
await tick();
|
|
2368
|
+
});
|
|
2369
|
+
this.emitLayoutChangeAndScheduleSave(compatibilityLayoutEventFromDesign(event));
|
|
2370
|
+
});
|
|
2371
|
+
this.#workspaceHostProjection = run.catch((error) => {
|
|
2372
|
+
console.error("Unable to project design-core workspace layout", error);
|
|
2373
|
+
});
|
|
2374
|
+
}
|
|
2375
|
+
forwardWorkspaceHostDropEvent(eventName, event) {
|
|
2376
|
+
const item = this.getLeafById(event.tabId) ?? undefined;
|
|
2377
|
+
const target = this.getWorkspaceTabsById(event.targetPaneId) ??
|
|
2378
|
+
item?.parent ??
|
|
2379
|
+
this.rootSplit;
|
|
2380
|
+
const compatibilityEvent = createWorkspaceLayoutDropEvent({
|
|
2381
|
+
source: event.source,
|
|
2382
|
+
operation: event.operation,
|
|
2383
|
+
position: event.position,
|
|
2384
|
+
target,
|
|
2385
|
+
item,
|
|
2386
|
+
});
|
|
2387
|
+
this.trigger(eventName, compatibilityEvent);
|
|
2388
|
+
if (compatibilityEvent.defaultPrevented)
|
|
2389
|
+
event.preventDefault();
|
|
2390
|
+
}
|
|
2391
|
+
forwardWorkspaceHostDragEvent(eventName, event) {
|
|
2392
|
+
const item = this.getLeafById(event.tabId) ?? undefined;
|
|
2393
|
+
this.trigger(eventName, createWorkspaceLayoutDropEvent({
|
|
2394
|
+
source: event.source,
|
|
2395
|
+
operation: "tab-drag",
|
|
2396
|
+
position: "center",
|
|
2397
|
+
target: item?.parent ?? this.rootSplit,
|
|
2398
|
+
item,
|
|
2399
|
+
}));
|
|
2400
|
+
}
|
|
2401
|
+
getWorkspaceTabsById(id) {
|
|
2402
|
+
if (this.bottomPanel.id === id)
|
|
2403
|
+
return this.bottomPanel;
|
|
2404
|
+
const roots = [
|
|
2405
|
+
this.rootSplit,
|
|
2406
|
+
this.leftSplit,
|
|
2407
|
+
this.rightSplit,
|
|
2408
|
+
...this.floating.children,
|
|
2409
|
+
];
|
|
2410
|
+
for (const root of roots) {
|
|
2411
|
+
const match = root.iterateAllTabs((tabs) => tabs.id === id ? tabs : undefined);
|
|
2412
|
+
if (match)
|
|
2413
|
+
return match;
|
|
2414
|
+
}
|
|
2415
|
+
return null;
|
|
2416
|
+
}
|
|
2417
|
+
registerWorkspaceHostView(type) {
|
|
2418
|
+
this.#workspaceHostViewDisposers.get(type)?.();
|
|
2419
|
+
this.#workspaceHostViewDisposers.delete(type);
|
|
2420
|
+
if (type === "empty" || !this.#workspaceHostController)
|
|
2421
|
+
return;
|
|
2422
|
+
const dispose = this.#workspaceHostController.renderer.registry.register({
|
|
2423
|
+
kind: "imperative",
|
|
2424
|
+
type,
|
|
2425
|
+
showHeader: true,
|
|
2426
|
+
getChrome: (context) => {
|
|
2427
|
+
const leaf = this.getLeafById(context.tab.id);
|
|
2428
|
+
const view = leaf?.view;
|
|
2429
|
+
const file = view instanceof FileView ? view.file : null;
|
|
2430
|
+
const actions = view instanceof ItemView ? view.actions : [];
|
|
2431
|
+
const breadcrumbPath = view?.getBreadcrumbFilePath() ?? file?.path ?? null;
|
|
2432
|
+
const contributedBreadcrumbs = view?.getBreadcrumbs() ?? [];
|
|
2433
|
+
return {
|
|
2434
|
+
title: file?.name ??
|
|
2435
|
+
(breadcrumbPath ? basename(breadcrumbPath) : undefined) ??
|
|
2436
|
+
leaf?.getDisplayText() ??
|
|
2437
|
+
context.tab.title,
|
|
2438
|
+
titleEditable: file != null,
|
|
2439
|
+
onTitleCommit: async (nextTitle) => {
|
|
2440
|
+
const currentLeaf = this.getLeafById(context.tab.id);
|
|
2441
|
+
const currentView = currentLeaf?.view;
|
|
2442
|
+
const currentFile = currentView instanceof FileView ? currentView.file : null;
|
|
2443
|
+
if (!currentFile)
|
|
2444
|
+
return;
|
|
2445
|
+
const trimmed = nextTitle.trim();
|
|
2446
|
+
if (!trimmed || trimmed === currentFile.name)
|
|
2447
|
+
return;
|
|
2448
|
+
const parent = dirname(currentFile.path);
|
|
2449
|
+
const nextPath = parent === "/" ? trimmed : joinPath(parent, trimmed);
|
|
2450
|
+
await this.app.fileManager.renameFile(currentFile, nextPath);
|
|
2451
|
+
const renamed = this.app.vault.getFileByPath(nextPath);
|
|
2452
|
+
if (renamed && currentView instanceof FileView) {
|
|
2453
|
+
await currentView.onRename(renamed);
|
|
2454
|
+
}
|
|
2455
|
+
},
|
|
2456
|
+
breadcrumbs: [
|
|
2457
|
+
...contributedBreadcrumbs,
|
|
2458
|
+
...filePathBreadcrumbs(this.app, breadcrumbPath),
|
|
2459
|
+
],
|
|
2460
|
+
canGoBack: leaf?.history.hasBackward ?? false,
|
|
2461
|
+
canGoForward: leaf?.history.hasForward ?? false,
|
|
2462
|
+
onGoBack: () => {
|
|
2463
|
+
void leaf?.history.back();
|
|
2464
|
+
},
|
|
2465
|
+
onGoForward: () => {
|
|
2466
|
+
void leaf?.history.forward();
|
|
2467
|
+
},
|
|
2468
|
+
actions: actions.map((action, index) => ({
|
|
2469
|
+
id: `view-action:${index}:${action.icon}`,
|
|
2470
|
+
label: action.title,
|
|
2471
|
+
icon: action.icon,
|
|
2472
|
+
disabled: action.disabled,
|
|
2473
|
+
onSelect: async (event) => {
|
|
2474
|
+
await action.callback(event);
|
|
2475
|
+
},
|
|
2476
|
+
})),
|
|
2477
|
+
buildPaneMenu: (menu) => {
|
|
2478
|
+
const currentLeaf = this.getLeafById(context.tab.id);
|
|
2479
|
+
const currentView = currentLeaf?.view;
|
|
2480
|
+
if (!currentView)
|
|
2481
|
+
return;
|
|
2482
|
+
replaceWorkspaceHostSplitActions(menu, this, currentLeaf);
|
|
2483
|
+
const paneMenu = new Menu();
|
|
2484
|
+
currentView.onPaneMenu(paneMenu, "more-options");
|
|
2485
|
+
prependPaneMenu(menu, paneMenu);
|
|
2486
|
+
},
|
|
2487
|
+
};
|
|
2488
|
+
},
|
|
2489
|
+
mount: (target, context) => {
|
|
2490
|
+
const leaf = this.getLeafById(context.tab.id);
|
|
2491
|
+
if (!leaf)
|
|
2492
|
+
return;
|
|
2493
|
+
target.replaceChildren(leaf.containerEl);
|
|
2494
|
+
return () => {
|
|
2495
|
+
if (leaf.containerEl.parentElement === target) {
|
|
2496
|
+
leaf.containerEl.remove();
|
|
2497
|
+
}
|
|
2498
|
+
};
|
|
2499
|
+
},
|
|
2500
|
+
});
|
|
2501
|
+
this.#workspaceHostViewDisposers.set(type, dispose);
|
|
2502
|
+
}
|
|
2503
|
+
getMostRecentLeaf() {
|
|
2504
|
+
return this.activeLeaf;
|
|
2505
|
+
}
|
|
2506
|
+
getVisibleHintTargets() {
|
|
2507
|
+
const root = this.containerEl;
|
|
2508
|
+
if (!root) {
|
|
2509
|
+
return [];
|
|
2510
|
+
}
|
|
2511
|
+
const elements = Array.from(root.querySelectorAll(WORKSPACE_HINT_TARGET_SELECTOR));
|
|
2512
|
+
const seenIds = new Map();
|
|
2513
|
+
return elements.flatMap((element, index) => {
|
|
2514
|
+
const type = normalizeHintText(element.dataset.hintTarget);
|
|
2515
|
+
if (!type ||
|
|
2516
|
+
isHintTargetDisabled(element) ||
|
|
2517
|
+
!isHintTargetVisible(element)) {
|
|
2518
|
+
return [];
|
|
2519
|
+
}
|
|
2520
|
+
const label = normalizeHintText(element.dataset.hintLabel) ||
|
|
2521
|
+
normalizeHintText(element.getAttribute("aria-label")) ||
|
|
2522
|
+
normalizeHintText(element.textContent) ||
|
|
2523
|
+
type;
|
|
2524
|
+
const baseId = normalizeHintText(element.dataset.hintTargetId) ||
|
|
2525
|
+
`workspace-hint-target:${type}:${index}`;
|
|
2526
|
+
const duplicateCount = seenIds.get(baseId) ?? 0;
|
|
2527
|
+
seenIds.set(baseId, duplicateCount + 1);
|
|
2528
|
+
return [
|
|
2529
|
+
{
|
|
2530
|
+
id: duplicateCount === 0 ? baseId : `${baseId}:${duplicateCount}`,
|
|
2531
|
+
type,
|
|
2532
|
+
label,
|
|
2533
|
+
action: normalizeHintText(element.dataset.hintAction) || "click",
|
|
2534
|
+
element,
|
|
2535
|
+
leafId: normalizeHintText(element.dataset.hintLeafId) || undefined,
|
|
2536
|
+
commandId: normalizeHintText(element.dataset.hintCommandId) || undefined,
|
|
2537
|
+
description: normalizeHintText(element.dataset.hintDescription) || undefined,
|
|
2538
|
+
group: normalizeHintText(element.dataset.hintGroup) || undefined,
|
|
2539
|
+
},
|
|
2540
|
+
];
|
|
2541
|
+
});
|
|
2542
|
+
}
|
|
2543
|
+
async changeLayout(workspace) {
|
|
2544
|
+
this.layoutReady = false;
|
|
2545
|
+
try {
|
|
2546
|
+
await this.restoreLayoutJson(workspace);
|
|
2547
|
+
this.layoutReady = true;
|
|
2548
|
+
this.layoutHandlers.forEach((it) => it());
|
|
2549
|
+
this.trigger("layout-ready");
|
|
2550
|
+
this.requestSaveLayout();
|
|
2551
|
+
}
|
|
2552
|
+
catch (error) {
|
|
2553
|
+
this.layoutReady = true;
|
|
2554
|
+
throw error;
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
getLayout() {
|
|
2558
|
+
return this.toJson();
|
|
2559
|
+
}
|
|
2560
|
+
/**
|
|
2561
|
+
* Bring a given leaf to the foreground. If the leaf is in a sidebar, the
|
|
2562
|
+
* sidebar will be uncollapsed. `await` this function to ensure your view has
|
|
2563
|
+
* been fully loaded and is not deferred.
|
|
2564
|
+
*
|
|
2565
|
+
* @public
|
|
2566
|
+
*/
|
|
2567
|
+
async revealLeaf(leaf) {
|
|
2568
|
+
let child = leaf;
|
|
2569
|
+
let parent = leaf.parent;
|
|
2570
|
+
while (parent) {
|
|
2571
|
+
if (parent instanceof WorkspaceTabs) {
|
|
2572
|
+
if (child instanceof WorkspaceLeaf ||
|
|
2573
|
+
child instanceof WorkspaceSidebarGroup) {
|
|
2574
|
+
parent.selected = child;
|
|
2575
|
+
}
|
|
2576
|
+
if (parent instanceof WorkspaceBottomPanel) {
|
|
2577
|
+
parent._applyOpen(true);
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
else if (parent instanceof WorkspaceSidedock) {
|
|
2581
|
+
parent.sidebar.setOpen(true);
|
|
2582
|
+
}
|
|
2583
|
+
child = parent;
|
|
2584
|
+
parent = parent.parent;
|
|
2585
|
+
}
|
|
2586
|
+
const floatingWindow = this.floatingWindowForLeaf(leaf);
|
|
2587
|
+
if (floatingWindow) {
|
|
2588
|
+
this.focusFloatingWindow(floatingWindow);
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
registerView(type, viewCreator) {
|
|
2592
|
+
this.viewTypes.set(type, viewCreator);
|
|
2593
|
+
this.registerWorkspaceHostView(type);
|
|
2594
|
+
}
|
|
2595
|
+
registerEditorView(contribution) {
|
|
2596
|
+
return this.editorViews.register(contribution);
|
|
2597
|
+
}
|
|
2598
|
+
registerSidebarView(type, options = {}) {
|
|
2599
|
+
this.sidebarViewPlacements.set(type, { ...options });
|
|
2600
|
+
}
|
|
2601
|
+
unregisterSidebarView(type) {
|
|
2602
|
+
this.sidebarViewPlacements.delete(type);
|
|
2603
|
+
}
|
|
2604
|
+
/**
|
|
2605
|
+
* Registers a view with the 'Page preview' core plugin as an emitter of the
|
|
2606
|
+
* 'hover-link' event.
|
|
2607
|
+
*
|
|
2608
|
+
* @public
|
|
2609
|
+
*/
|
|
2610
|
+
registerHoverLinkSource(id, info) {
|
|
2611
|
+
this.hoverLinkSources.set(id, { ...info });
|
|
2612
|
+
}
|
|
2613
|
+
unregisterHoverLinkSource(id) {
|
|
2614
|
+
this.hoverLinkSources.delete(id);
|
|
2615
|
+
}
|
|
2616
|
+
updateOptions() {
|
|
2617
|
+
this.app.telemetry.measure("workspace.update_options", (span) => {
|
|
2618
|
+
let editorCount = 0;
|
|
2619
|
+
this.iterateAllLeaves((leaf) => {
|
|
2620
|
+
if (leaf.view instanceof TextFileView) {
|
|
2621
|
+
editorCount += 1;
|
|
2622
|
+
const editor = leaf.view.editor;
|
|
2623
|
+
editor.updateExtensions([], leaf.view.getState());
|
|
2624
|
+
}
|
|
2625
|
+
});
|
|
2626
|
+
span.setAttribute("workspace.updated_editor_count", editorCount);
|
|
2627
|
+
}, { slowThresholdMs: 100 });
|
|
2628
|
+
}
|
|
2629
|
+
unregisterView(type) {
|
|
2630
|
+
this.#workspaceHostViewDisposers.get(type)?.();
|
|
2631
|
+
this.#workspaceHostViewDisposers.delete(type);
|
|
2632
|
+
return this.viewTypes.delete(type);
|
|
2633
|
+
}
|
|
2634
|
+
normalizeExtensionKey(extension) {
|
|
2635
|
+
const normalized = extension.trim().toLowerCase();
|
|
2636
|
+
if (!normalized) {
|
|
2637
|
+
return "";
|
|
2638
|
+
}
|
|
2639
|
+
return normalized.startsWith(".") ? normalized : `.${normalized}`;
|
|
2640
|
+
}
|
|
2641
|
+
resolveViewTypeForExtensionKey(extensionKey) {
|
|
2642
|
+
if (!extensionKey) {
|
|
2643
|
+
return undefined;
|
|
2644
|
+
}
|
|
2645
|
+
return this.viewExtensions.get(extensionKey);
|
|
2646
|
+
}
|
|
2647
|
+
resolveViewTypeForPath(path) {
|
|
2648
|
+
const normalizedPath = path.trim().toLowerCase();
|
|
2649
|
+
if (!normalizedPath) {
|
|
2650
|
+
return undefined;
|
|
2651
|
+
}
|
|
2652
|
+
let matchedViewType;
|
|
2653
|
+
let matchedExtensionLength = -1;
|
|
2654
|
+
for (const [extension, viewType] of this.viewExtensions.entries()) {
|
|
2655
|
+
if (!normalizedPath.endsWith(extension) ||
|
|
2656
|
+
extension.length <= matchedExtensionLength) {
|
|
2657
|
+
continue;
|
|
2658
|
+
}
|
|
2659
|
+
matchedViewType = viewType;
|
|
2660
|
+
matchedExtensionLength = extension.length;
|
|
2661
|
+
}
|
|
2662
|
+
return matchedViewType;
|
|
2663
|
+
}
|
|
2664
|
+
extensionKeyToFilenamePattern(extensionKey) {
|
|
2665
|
+
return `*${extensionKey}`;
|
|
2666
|
+
}
|
|
2667
|
+
humanizeViewType(viewType) {
|
|
2668
|
+
return viewType
|
|
2669
|
+
.replace(/[._-]+/g, " ")
|
|
2670
|
+
.replace(/\b\w/gu, (char) => char.toUpperCase());
|
|
2671
|
+
}
|
|
2672
|
+
registeredEditorViewForPath(path) {
|
|
2673
|
+
return this.editorViews
|
|
2674
|
+
.getAll()
|
|
2675
|
+
.flatMap((view, viewIndex) => view.filenamePatterns
|
|
2676
|
+
.filter((pattern) => matchesEditorAssociationGlob(pattern, path))
|
|
2677
|
+
.map((pattern) => ({ view, viewIndex, pattern })))
|
|
2678
|
+
.filter(({ view }) => this.viewTypes.has(view.viewType))
|
|
2679
|
+
.sort((left, right) => {
|
|
2680
|
+
const priority = { option: 0, default: 1, exclusive: 2 };
|
|
2681
|
+
const priorityDelta = priority[right.view.priority] - priority[left.view.priority];
|
|
2682
|
+
if (priorityDelta !== 0) {
|
|
2683
|
+
return priorityDelta;
|
|
2684
|
+
}
|
|
2685
|
+
const specificity = compareEditorAssociationPatternSpecificity(right.pattern, left.pattern);
|
|
2686
|
+
if (specificity !== 0) {
|
|
2687
|
+
return specificity;
|
|
2688
|
+
}
|
|
2689
|
+
return left.viewIndex - right.viewIndex;
|
|
2690
|
+
})[0]?.view;
|
|
2691
|
+
}
|
|
2692
|
+
getEditorAssociationForPath(path) {
|
|
2693
|
+
const config = this.app.configuration
|
|
2694
|
+
?.getConfiguration?.()
|
|
2695
|
+
?.get("workspace.editorAssociations", {});
|
|
2696
|
+
const associations = config && typeof config === "object" && !Array.isArray(config)
|
|
2697
|
+
? config
|
|
2698
|
+
: {};
|
|
2699
|
+
return Object.entries(associations)
|
|
2700
|
+
.map(([pattern, editorViewId], index) => ({
|
|
2701
|
+
pattern,
|
|
2702
|
+
editorViewId,
|
|
2703
|
+
index,
|
|
2704
|
+
}))
|
|
2705
|
+
.filter(({ pattern, editorViewId }) => typeof editorViewId === "string" &&
|
|
2706
|
+
editorViewId.trim().length > 0 &&
|
|
2707
|
+
matchesEditorAssociationGlob(pattern, path))
|
|
2708
|
+
.sort((left, right) => {
|
|
2709
|
+
const specificity = compareEditorAssociationPatternSpecificity(right.pattern, left.pattern);
|
|
2710
|
+
return specificity || right.index - left.index;
|
|
2711
|
+
})
|
|
2712
|
+
.map(({ pattern, editorViewId }) => ({
|
|
2713
|
+
pattern,
|
|
2714
|
+
editorViewId,
|
|
2715
|
+
view: this.editorViews.get(editorViewId),
|
|
2716
|
+
}))[0];
|
|
2717
|
+
}
|
|
2718
|
+
resolveEditorAssociationViewType(path) {
|
|
2719
|
+
const association = this.getEditorAssociationForPath(path);
|
|
2720
|
+
if (association?.view && this.viewTypes.has(association.view.viewType)) {
|
|
2721
|
+
return association.view.viewType;
|
|
2722
|
+
}
|
|
2723
|
+
return this.registeredEditorViewForPath(path)?.viewType;
|
|
2724
|
+
}
|
|
2725
|
+
registerExtensions(extensions, viewType) {
|
|
2726
|
+
const filenamePatterns = [];
|
|
2727
|
+
extensions.forEach((ext) => {
|
|
2728
|
+
const extensionKey = this.normalizeExtensionKey(ext);
|
|
2729
|
+
if (!extensionKey) {
|
|
2730
|
+
return;
|
|
2731
|
+
}
|
|
2732
|
+
this.viewExtensions.set(extensionKey, viewType);
|
|
2733
|
+
filenamePatterns.push(this.extensionKeyToFilenamePattern(extensionKey));
|
|
2734
|
+
});
|
|
2735
|
+
if (filenamePatterns.length === 0) {
|
|
2736
|
+
return;
|
|
2737
|
+
}
|
|
2738
|
+
const existing = this.editorViews.getByViewType(viewType)[0];
|
|
2739
|
+
if (existing) {
|
|
2740
|
+
this.editorViews.update(existing.id, { filenamePatterns });
|
|
2741
|
+
return;
|
|
2742
|
+
}
|
|
2743
|
+
this.editorViews.upsert({
|
|
2744
|
+
id: viewType,
|
|
2745
|
+
viewType,
|
|
2746
|
+
label: this.humanizeViewType(viewType),
|
|
2747
|
+
filenamePatterns,
|
|
2748
|
+
priority: "default",
|
|
2749
|
+
source: "compat",
|
|
2750
|
+
});
|
|
2751
|
+
}
|
|
2752
|
+
unregisterExtensions(extensions, viewType) {
|
|
2753
|
+
const filenamePatterns = [];
|
|
2754
|
+
extensions.forEach((ext) => {
|
|
2755
|
+
const extensionKey = this.normalizeExtensionKey(ext);
|
|
2756
|
+
if (this.viewExtensions.get(extensionKey) === viewType) {
|
|
2757
|
+
this.viewExtensions.delete(extensionKey);
|
|
2758
|
+
}
|
|
2759
|
+
if (extensionKey) {
|
|
2760
|
+
filenamePatterns.push(this.extensionKeyToFilenamePattern(extensionKey));
|
|
2761
|
+
}
|
|
2762
|
+
});
|
|
2763
|
+
for (const view of this.editorViews.getByViewType(viewType)) {
|
|
2764
|
+
this.editorViews.removeFilenamePatterns(view.id, filenamePatterns);
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
determineViewType(type) {
|
|
2768
|
+
if (this.viewTypes.has(type)) {
|
|
2769
|
+
return type;
|
|
2770
|
+
}
|
|
2771
|
+
return this.resolveViewTypeForExtensionKey(this.normalizeExtensionKey(type));
|
|
2772
|
+
}
|
|
2773
|
+
determineViewTypeForPath(path) {
|
|
2774
|
+
return (this.resolveEditorAssociationViewType(path) ??
|
|
2775
|
+
this.resolveViewTypeForPath(path));
|
|
2776
|
+
}
|
|
2777
|
+
async createOnDemandPluginInstallView(leaf, file, options = {}) {
|
|
2778
|
+
const confirm = options.confirm ?? true;
|
|
2779
|
+
if (confirm && !(await this.confirmOnDemandRegistryLookup(file.path))) {
|
|
2780
|
+
return null;
|
|
2781
|
+
}
|
|
2782
|
+
const entry = await this.findOnDemandPluginForPath(file.path);
|
|
2783
|
+
if (!entry) {
|
|
2784
|
+
return null;
|
|
2785
|
+
}
|
|
2786
|
+
return new OnDemandPluginInstallView(leaf, entry, this.app);
|
|
2787
|
+
}
|
|
2788
|
+
onDemandRegistryLookupConfirmed = new Set();
|
|
2789
|
+
async findOnDemandPluginForPath(path) {
|
|
2790
|
+
const distribution = this.app.pluginDistribution;
|
|
2791
|
+
if (!distribution) {
|
|
2792
|
+
return null;
|
|
2793
|
+
}
|
|
2794
|
+
try {
|
|
2795
|
+
await distribution.refreshCatalog();
|
|
2796
|
+
}
|
|
2797
|
+
catch (error) {
|
|
2798
|
+
new Notice(`Unable to check the official plugin registry: ${formatUnknownError(error)}`, 6000);
|
|
2799
|
+
return null;
|
|
2800
|
+
}
|
|
2801
|
+
return (distribution
|
|
2802
|
+
.search({
|
|
2803
|
+
channel: "official",
|
|
2804
|
+
compatibleOnly: true,
|
|
2805
|
+
})
|
|
2806
|
+
.find((entry) => entry.channel === "official" &&
|
|
2807
|
+
registryContributionMatchesPath(entry, path)) ?? null);
|
|
2808
|
+
}
|
|
2809
|
+
confirmOnDemandRegistryLookup(path) {
|
|
2810
|
+
if (this.onDemandRegistryLookupConfirmed.has(path)) {
|
|
2811
|
+
return Promise.resolve(true);
|
|
2812
|
+
}
|
|
2813
|
+
return promptConfirm(this.getCommandHostDocument(), {
|
|
2814
|
+
title: "No editor available",
|
|
2815
|
+
description: `No installed editor can open ${path}. Check the official plugin registry for a verified plugin?`,
|
|
2816
|
+
confirmLabel: "Check registry",
|
|
2817
|
+
cancelLabel: "Cancel",
|
|
2818
|
+
}).then((confirmed) => {
|
|
2819
|
+
if (confirmed) {
|
|
2820
|
+
this.onDemandRegistryLookupConfirmed.add(path);
|
|
2821
|
+
}
|
|
2822
|
+
return confirmed;
|
|
2823
|
+
});
|
|
2824
|
+
}
|
|
2825
|
+
async activateLayoutPlugins(layout) {
|
|
2826
|
+
const viewTypes = new Set();
|
|
2827
|
+
collectLayoutViewTypes(layout.main, viewTypes);
|
|
2828
|
+
collectLayoutViewTypes(layout.left, viewTypes);
|
|
2829
|
+
collectLayoutViewTypes(layout.right, viewTypes);
|
|
2830
|
+
collectLayoutViewTypes(layout.bottom, viewTypes);
|
|
2831
|
+
for (const window of layout.floating ?? []) {
|
|
2832
|
+
collectLayoutViewTypes(window, viewTypes);
|
|
2833
|
+
}
|
|
2834
|
+
for (const viewType of viewTypes) {
|
|
2835
|
+
await this.app.plugins.activateForViewType(viewType);
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
viewCreator(type) {
|
|
2839
|
+
if (typeof type === "string") {
|
|
2840
|
+
const viewType = this.determineViewType(type);
|
|
2841
|
+
if (viewType) {
|
|
2842
|
+
return this.viewTypes.get(viewType);
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
else if (type instanceof TFile) {
|
|
2846
|
+
const viewType = this.determineViewTypeForPath(type.path) ??
|
|
2847
|
+
this.determineViewType(type.extension);
|
|
2848
|
+
if (viewType) {
|
|
2849
|
+
return this.viewTypes.get(viewType);
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
setActiveLeaf(leaf, params) {
|
|
2854
|
+
this.activeLeaf = leaf;
|
|
2855
|
+
this.revealLeaf(leaf);
|
|
2856
|
+
}
|
|
2857
|
+
/**
|
|
2858
|
+
* Retrieve a leaf by its id.
|
|
2859
|
+
*
|
|
2860
|
+
* @param id Id of the leaf to retrieve.
|
|
2861
|
+
* @public
|
|
2862
|
+
*/
|
|
2863
|
+
getLeafById(id) {
|
|
2864
|
+
return (this.iterateAllLeaves((leaf) => {
|
|
2865
|
+
return leaf.id === id ? leaf : undefined;
|
|
2866
|
+
}) || null);
|
|
2867
|
+
}
|
|
2868
|
+
/**
|
|
2869
|
+
* Get the currently active view of a given type.
|
|
2870
|
+
*
|
|
2871
|
+
* @public
|
|
2872
|
+
*/
|
|
2873
|
+
getActiveViewOfType(type) {
|
|
2874
|
+
const leaf = this.activeLeaf;
|
|
2875
|
+
if (leaf && leaf.view instanceof type) {
|
|
2876
|
+
return leaf.view;
|
|
2877
|
+
}
|
|
2878
|
+
return null;
|
|
2879
|
+
}
|
|
2880
|
+
/**
|
|
2881
|
+
* Iterate through all leaves in the main area of the workspace.
|
|
2882
|
+
*
|
|
2883
|
+
* @public
|
|
2884
|
+
*/
|
|
2885
|
+
iterateRootLeaves(callback) {
|
|
2886
|
+
this.rootSplit.iterateAllLeaves(callback);
|
|
2887
|
+
}
|
|
2888
|
+
/**
|
|
2889
|
+
* Returns the file for the current view if it's a `FileView`. Otherwise, it
|
|
2890
|
+
* will return the most recently active file.
|
|
2891
|
+
*
|
|
2892
|
+
* @public
|
|
2893
|
+
*/
|
|
2894
|
+
getActiveFile() {
|
|
2895
|
+
const leaf = this.activeLeaf;
|
|
2896
|
+
if (leaf && leaf.view instanceof FileView) {
|
|
2897
|
+
if (leaf.view.file)
|
|
2898
|
+
this.lastActiveFile = leaf.view.file;
|
|
2899
|
+
return leaf.view.file ?? this.lastActiveFile;
|
|
2900
|
+
}
|
|
2901
|
+
return this.lastActiveFile;
|
|
2902
|
+
}
|
|
2903
|
+
/**
|
|
2904
|
+
* Iterate through all leaves, including main area leaves, floating leaves,
|
|
2905
|
+
* and sidebar leaves.
|
|
2906
|
+
*
|
|
2907
|
+
* @public
|
|
2908
|
+
*/
|
|
2909
|
+
iterateAllLeaves(callback) {
|
|
2910
|
+
for (const child of [
|
|
2911
|
+
this.rootSplit,
|
|
2912
|
+
this.leftSplit,
|
|
2913
|
+
this.rightSplit,
|
|
2914
|
+
this.bottomPanel,
|
|
2915
|
+
this.floating,
|
|
2916
|
+
]) {
|
|
2917
|
+
const response = child.iterateAllLeaves(callback);
|
|
2918
|
+
if (response !== undefined && response !== null) {
|
|
2919
|
+
return response;
|
|
2920
|
+
}
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2923
|
+
/** @public */
|
|
2924
|
+
getOpenLeafEntries(options = {}) {
|
|
2925
|
+
const includeMain = options.includeMain ?? true;
|
|
2926
|
+
const includeLeftSidebar = options.includeLeftSidebar ?? true;
|
|
2927
|
+
const includeRightSidebar = options.includeRightSidebar ?? true;
|
|
2928
|
+
const includeBottomPanel = options.includeBottomPanel ?? true;
|
|
2929
|
+
const includeFloating = options.includeFloating ?? true;
|
|
2930
|
+
const includePopout = options.includePopout ?? true;
|
|
2931
|
+
const entries = [];
|
|
2932
|
+
const pushLeaf = (leaf, region, parentWindow) => {
|
|
2933
|
+
const tabs = this.tabsForLeaf(leaf);
|
|
2934
|
+
const group = leaf.parent instanceof WorkspaceSidebarGroup ? leaf.parent : undefined;
|
|
2935
|
+
const view = leaf.view;
|
|
2936
|
+
const isSelectedInParent = group
|
|
2937
|
+
? tabs.selectedChild === group && !group.isLeafHidden(leaf)
|
|
2938
|
+
: tabs.selectedLeaf === leaf;
|
|
2939
|
+
entries.push({
|
|
2940
|
+
id: leaf.id,
|
|
2941
|
+
leaf,
|
|
2942
|
+
title: view.getDisplayText(),
|
|
2943
|
+
icon: view.getIcon(),
|
|
2944
|
+
viewType: view.getViewType(),
|
|
2945
|
+
...(view instanceof FileView && view.file?.path
|
|
2946
|
+
? { filePath: view.file.path }
|
|
2947
|
+
: {}),
|
|
2948
|
+
region,
|
|
2949
|
+
active: this.activeLeaf === leaf,
|
|
2950
|
+
selectedInParent: isSelectedInParent,
|
|
2951
|
+
parentTabsId: tabs.id,
|
|
2952
|
+
...(group
|
|
2953
|
+
? {
|
|
2954
|
+
parentGroupId: group.id,
|
|
2955
|
+
parentGroupName: group.name,
|
|
2956
|
+
}
|
|
2957
|
+
: {}),
|
|
2958
|
+
...(parentWindow
|
|
2959
|
+
? {
|
|
2960
|
+
parentWindowId: parentWindow.id,
|
|
2961
|
+
parentWindowMode: parentWindow.mode,
|
|
2962
|
+
}
|
|
2963
|
+
: {}),
|
|
2964
|
+
});
|
|
2965
|
+
};
|
|
2966
|
+
if (includeMain) {
|
|
2967
|
+
this.rootSplit.iterateAllLeaves((leaf) => {
|
|
2968
|
+
pushLeaf(leaf, "main");
|
|
2969
|
+
});
|
|
2970
|
+
}
|
|
2971
|
+
if (includeLeftSidebar) {
|
|
2972
|
+
this.leftSplit.iterateAllLeaves((leaf) => {
|
|
2973
|
+
pushLeaf(leaf, "left");
|
|
2974
|
+
});
|
|
2975
|
+
}
|
|
2976
|
+
if (includeRightSidebar) {
|
|
2977
|
+
this.rightSplit.iterateAllLeaves((leaf) => {
|
|
2978
|
+
pushLeaf(leaf, "right");
|
|
2979
|
+
});
|
|
2980
|
+
}
|
|
2981
|
+
if (includeBottomPanel) {
|
|
2982
|
+
this.bottomPanel.iterateAllLeaves((leaf) => {
|
|
2983
|
+
pushLeaf(leaf, "bottom");
|
|
2984
|
+
});
|
|
2985
|
+
}
|
|
2986
|
+
if (includeFloating || includePopout) {
|
|
2987
|
+
this.floating.children.forEach((window) => {
|
|
2988
|
+
const region = window.mode === "popout"
|
|
2989
|
+
? "popout"
|
|
2990
|
+
: "floating";
|
|
2991
|
+
if ((region === "floating" && !includeFloating) ||
|
|
2992
|
+
(region === "popout" && !includePopout)) {
|
|
2993
|
+
return;
|
|
2994
|
+
}
|
|
2995
|
+
window.iterateAllLeaves((leaf) => {
|
|
2996
|
+
pushLeaf(leaf, region, window);
|
|
2997
|
+
});
|
|
2998
|
+
});
|
|
2999
|
+
}
|
|
3000
|
+
return entries;
|
|
3001
|
+
}
|
|
3002
|
+
/**
|
|
3003
|
+
* Remove all leaves of the given type.
|
|
3004
|
+
*
|
|
3005
|
+
* @public
|
|
3006
|
+
*/
|
|
3007
|
+
detachLeavesOfType(viewType) {
|
|
3008
|
+
this.getLeavesOfType(viewType).forEach((leaf) => {
|
|
3009
|
+
leaf.close();
|
|
3010
|
+
});
|
|
3011
|
+
}
|
|
3012
|
+
getLeavesOfType(viewType) {
|
|
3013
|
+
const leaves = [];
|
|
3014
|
+
this.iterateAllLeaves((leaf) => {
|
|
3015
|
+
if (leaf.view.getViewType() === viewType) {
|
|
3016
|
+
leaves.push(leaf);
|
|
3017
|
+
}
|
|
3018
|
+
});
|
|
3019
|
+
return leaves;
|
|
3020
|
+
}
|
|
3021
|
+
/** @public */
|
|
3022
|
+
activateLeaf(leaf, options = {}) {
|
|
3023
|
+
if (!this.containsLeaf(leaf)) {
|
|
3024
|
+
return false;
|
|
3025
|
+
}
|
|
3026
|
+
const tabs = this.tabsForLeaf(leaf);
|
|
3027
|
+
if (leaf.parent instanceof WorkspaceSidebarGroup) {
|
|
3028
|
+
leaf.parent.selectLeaf(leaf);
|
|
3029
|
+
tabs.selected = leaf.parent;
|
|
3030
|
+
}
|
|
3031
|
+
else {
|
|
3032
|
+
tabs.selected = leaf;
|
|
3033
|
+
}
|
|
3034
|
+
if (tabs instanceof WorkspaceBottomPanel) {
|
|
3035
|
+
tabs._applyOpen(true);
|
|
3036
|
+
}
|
|
3037
|
+
const floatingWindow = this.floatingWindowForLeaf(leaf);
|
|
3038
|
+
if (floatingWindow) {
|
|
3039
|
+
this.floating.bringToFront(floatingWindow);
|
|
3040
|
+
if (this.isMobileMode) {
|
|
3041
|
+
if (options.focusRootHost ?? true) {
|
|
3042
|
+
this.focusRootHost();
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
else {
|
|
3046
|
+
this.focusedHostId = floatingWindow.id;
|
|
3047
|
+
if (floatingWindow.mode === "popout") {
|
|
3048
|
+
floatingWindow.focusPopoutWindow();
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3051
|
+
}
|
|
3052
|
+
else if (options.focusRootHost ?? true) {
|
|
3053
|
+
this.focusRootHost();
|
|
3054
|
+
}
|
|
3055
|
+
this.activeLeaf = leaf;
|
|
3056
|
+
if (options.saveLayout ?? true) {
|
|
3057
|
+
this.requestSaveLayout({
|
|
3058
|
+
source: options.source ?? "api",
|
|
3059
|
+
operation: options.operation ?? "activate-leaf",
|
|
3060
|
+
});
|
|
3061
|
+
}
|
|
3062
|
+
return true;
|
|
3063
|
+
}
|
|
3064
|
+
/** @public */
|
|
3065
|
+
closeLeafAndSelectFallback(leaf, options = {}) {
|
|
3066
|
+
if (!this.containsLeaf(leaf)) {
|
|
3067
|
+
return this.activeLeaf;
|
|
3068
|
+
}
|
|
3069
|
+
const wasActive = this.activeLeaf === leaf;
|
|
3070
|
+
leaf.close();
|
|
3071
|
+
let next = this.activeLeaf;
|
|
3072
|
+
if (!this.containsLeaf(next) || next === leaf) {
|
|
3073
|
+
next = null;
|
|
3074
|
+
}
|
|
3075
|
+
if (!next && (options.preferRoot ?? true)) {
|
|
3076
|
+
next = this.activeRootLeaf;
|
|
3077
|
+
}
|
|
3078
|
+
if (!next) {
|
|
3079
|
+
next = this.getOpenLeafEntries()[0]?.leaf ?? null;
|
|
3080
|
+
}
|
|
3081
|
+
if (wasActive && next) {
|
|
3082
|
+
this.activateLeaf(next, {
|
|
3083
|
+
saveLayout: false,
|
|
3084
|
+
source: "api",
|
|
3085
|
+
operation: "close-leaf-select-fallback",
|
|
3086
|
+
});
|
|
3087
|
+
}
|
|
3088
|
+
if (options.saveLayout ?? true) {
|
|
3089
|
+
this.requestSaveLayout({
|
|
3090
|
+
source: "api",
|
|
3091
|
+
operation: "close-leaf-mobile",
|
|
3092
|
+
});
|
|
3093
|
+
}
|
|
3094
|
+
return next;
|
|
3095
|
+
}
|
|
3096
|
+
createLeafInParent(parent, index) {
|
|
3097
|
+
const leaf = new WorkspaceLeaf();
|
|
3098
|
+
parent.addChild(new WorkspaceTabs({ leaves: [leaf] }), index);
|
|
3099
|
+
return leaf;
|
|
3100
|
+
}
|
|
3101
|
+
createLeafBySplit(leaf, direction = "vertical", before = false) {
|
|
3102
|
+
const sibling = new WorkspaceLeaf();
|
|
3103
|
+
const tab = this.tabsForLeaf(leaf);
|
|
3104
|
+
if (tab instanceof WorkspaceBottomPanel) {
|
|
3105
|
+
throw new Error("Bottom panel does not support split panes");
|
|
3106
|
+
}
|
|
3107
|
+
const split = tab.parent;
|
|
3108
|
+
const index = split.children.findIndex((it) => it === tab);
|
|
3109
|
+
const newTab = new WorkspaceTabs({ leaves: [sibling] });
|
|
3110
|
+
if (split.type === direction) {
|
|
3111
|
+
split.addChild(newTab, before ? index : index + 1);
|
|
3112
|
+
}
|
|
3113
|
+
else {
|
|
3114
|
+
const oldTab = split.removeChild(tab, true);
|
|
3115
|
+
const nested = new WorkspaceView(direction);
|
|
3116
|
+
if (before) {
|
|
3117
|
+
nested.addChild(newTab);
|
|
3118
|
+
oldTab && nested.addChild(oldTab);
|
|
3119
|
+
}
|
|
3120
|
+
else {
|
|
3121
|
+
oldTab && nested.addChild(oldTab);
|
|
3122
|
+
nested.addChild(newTab);
|
|
3123
|
+
}
|
|
3124
|
+
split.addChild(nested, index);
|
|
3125
|
+
}
|
|
3126
|
+
return sibling;
|
|
3127
|
+
}
|
|
3128
|
+
splitActiveLeaf(direction) {
|
|
3129
|
+
return this.getLeaf("split", direction);
|
|
3130
|
+
}
|
|
3131
|
+
getUnpinnedLeaf() {
|
|
3132
|
+
return this.getLeaf(false);
|
|
3133
|
+
}
|
|
3134
|
+
getGroupLeaves(group) {
|
|
3135
|
+
const leaves = [];
|
|
3136
|
+
this.iterateAllLeaves((leaf) => {
|
|
3137
|
+
if (leaf.group === group) {
|
|
3138
|
+
leaves.push(leaf);
|
|
3139
|
+
}
|
|
3140
|
+
});
|
|
3141
|
+
return leaves;
|
|
3142
|
+
}
|
|
3143
|
+
sideDock(side) {
|
|
3144
|
+
return side === "left" ? this.leftSplit : this.rightSplit;
|
|
3145
|
+
}
|
|
3146
|
+
tabsForLeaf(leaf) {
|
|
3147
|
+
return leaf.parent instanceof WorkspaceSidebarGroup
|
|
3148
|
+
? leaf.parent.parent
|
|
3149
|
+
: leaf.parent;
|
|
3150
|
+
}
|
|
3151
|
+
setFocusMode(state) {
|
|
3152
|
+
if (this.focusMode?.leaf === state?.leaf &&
|
|
3153
|
+
this.focusMode?.tabs === state?.tabs) {
|
|
3154
|
+
return false;
|
|
3155
|
+
}
|
|
3156
|
+
this.focusMode = state;
|
|
3157
|
+
this.trigger("focus-mode-change", state);
|
|
3158
|
+
return true;
|
|
3159
|
+
}
|
|
3160
|
+
selectedLeafForWindow(window) {
|
|
3161
|
+
return (window.iterateAllTabs((tabs) => tabs.selectedLeaf || undefined) ?? null);
|
|
3162
|
+
}
|
|
3163
|
+
getSidebarGroups(side) {
|
|
3164
|
+
const groups = [];
|
|
3165
|
+
const sections = side
|
|
3166
|
+
? [this.sideDock(side)]
|
|
3167
|
+
: [this.leftSplit, this.rightSplit];
|
|
3168
|
+
sections.forEach((section) => {
|
|
3169
|
+
section.iterateAllTabs((tabs) => {
|
|
3170
|
+
tabs.children.forEach((child) => {
|
|
3171
|
+
if (child instanceof WorkspaceSidebarGroup) {
|
|
3172
|
+
groups.push(child);
|
|
3173
|
+
}
|
|
3174
|
+
});
|
|
3175
|
+
});
|
|
3176
|
+
});
|
|
3177
|
+
return groups;
|
|
3178
|
+
}
|
|
3179
|
+
getSidebarGroup(side, group) {
|
|
3180
|
+
const id = normalizeSidebarGroupId(group);
|
|
3181
|
+
return (this.getSidebarGroups(side).find((candidate) => candidate.id === id || candidate.name === group) ?? null);
|
|
3182
|
+
}
|
|
3183
|
+
getOrCreateSidebarGroup(side, group, options = {}) {
|
|
3184
|
+
const id = normalizeSidebarGroupId(options.id ?? group);
|
|
3185
|
+
const existing = this.getSidebarGroup(side, id);
|
|
3186
|
+
if (existing) {
|
|
3187
|
+
if (options.name !== undefined)
|
|
3188
|
+
existing.name = options.name;
|
|
3189
|
+
if (options.icon !== undefined)
|
|
3190
|
+
existing.icon = options.icon;
|
|
3191
|
+
return existing;
|
|
3192
|
+
}
|
|
3193
|
+
const sidebarGroup = new WorkspaceSidebarGroup({
|
|
3194
|
+
id,
|
|
3195
|
+
name: options.name ?? group,
|
|
3196
|
+
icon: options.icon,
|
|
3197
|
+
hiddenLeafIds: options.hiddenLeafIds,
|
|
3198
|
+
collapsed: options.collapsed,
|
|
3199
|
+
});
|
|
3200
|
+
this.findOrCreateTab(this.sideDock(side)).addChild(sidebarGroup);
|
|
3201
|
+
this.requestSaveLayout();
|
|
3202
|
+
return sidebarGroup;
|
|
3203
|
+
}
|
|
3204
|
+
getSidebarGroupLeaves(side, group) {
|
|
3205
|
+
return this.getSidebarGroup(side, group)?.children ?? [];
|
|
3206
|
+
}
|
|
3207
|
+
convertSidebarLeavesToGroup(side, leaves, options = {}) {
|
|
3208
|
+
if (!leaves.length) {
|
|
3209
|
+
throw new Error("Cannot create a sidebar group without leaves");
|
|
3210
|
+
}
|
|
3211
|
+
const groupId = options.id ?? options.group ?? leaves[0].view.getViewType();
|
|
3212
|
+
const group = new WorkspaceSidebarGroup({
|
|
3213
|
+
id: normalizeSidebarGroupId(groupId),
|
|
3214
|
+
name: options.name ?? groupId,
|
|
3215
|
+
icon: options.icon,
|
|
3216
|
+
hiddenLeafIds: options.hiddenLeafIds,
|
|
3217
|
+
collapsed: options.collapsed,
|
|
3218
|
+
});
|
|
3219
|
+
const tabs = this.tabsForLeaf(leaves[0]);
|
|
3220
|
+
const insertIndex = Math.max(0, Math.min(...leaves.map((leaf) => tabs.children.findIndex((it) => it === leaf))));
|
|
3221
|
+
leaves.forEach((leaf) => {
|
|
3222
|
+
if (this.tabsForLeaf(leaf) !== tabs) {
|
|
3223
|
+
throw new Error("Sidebar group conversion requires leaves in one tab set");
|
|
3224
|
+
}
|
|
3225
|
+
tabs.removeChild(leaf, true);
|
|
3226
|
+
leaf.parent = undefined;
|
|
3227
|
+
group.addChild(leaf);
|
|
3228
|
+
});
|
|
3229
|
+
tabs.addChild(group, insertIndex);
|
|
3230
|
+
tabs.selected = group;
|
|
3231
|
+
if (side !== "left" && side !== "right") {
|
|
3232
|
+
throw new Error(`Unsupported sidebar side: ${side}`);
|
|
3233
|
+
}
|
|
3234
|
+
this.requestSaveLayout();
|
|
3235
|
+
return group;
|
|
3236
|
+
}
|
|
3237
|
+
convertSidebarGroupToLeaves(group) {
|
|
3238
|
+
const tabs = group.parent;
|
|
3239
|
+
const index = tabs.children.findIndex((it) => it === group);
|
|
3240
|
+
const leaves = [...group.children];
|
|
3241
|
+
tabs.removeChild(group, true);
|
|
3242
|
+
group.children = [];
|
|
3243
|
+
group.hiddenLeafIds = [];
|
|
3244
|
+
group.collapsed = {};
|
|
3245
|
+
leaves.forEach((leaf, offset) => {
|
|
3246
|
+
tabs.addChild(leaf, index + offset);
|
|
3247
|
+
});
|
|
3248
|
+
tabs.selected = leaves[0] ?? "0";
|
|
3249
|
+
this.requestSaveLayout();
|
|
3250
|
+
return leaves;
|
|
3251
|
+
}
|
|
3252
|
+
getLastOpenFiles() {
|
|
3253
|
+
if (this.recentFilePaths.length > 0)
|
|
3254
|
+
return [...this.recentFilePaths];
|
|
3255
|
+
const files = [];
|
|
3256
|
+
this.iterateAllLeaves((leaf) => {
|
|
3257
|
+
if (leaf.view instanceof FileView && leaf.view.file) {
|
|
3258
|
+
files.push(leaf.view.file.path);
|
|
3259
|
+
}
|
|
3260
|
+
});
|
|
3261
|
+
return [...new Set(files)];
|
|
3262
|
+
}
|
|
3263
|
+
getRecentFiles() {
|
|
3264
|
+
return this.getLastOpenFiles()
|
|
3265
|
+
.map((path) => this.app.vault.getFileByPath(path))
|
|
3266
|
+
.filter((file) => file instanceof TFile);
|
|
3267
|
+
}
|
|
3268
|
+
recordRecentFile(file) {
|
|
3269
|
+
this.recentFilePaths = [
|
|
3270
|
+
file.path,
|
|
3271
|
+
...this.recentFilePaths.filter((path) => path !== file.path),
|
|
3272
|
+
].slice(0, Workspace.RECENT_FILE_LIMIT);
|
|
3273
|
+
}
|
|
3274
|
+
async openLinkText(linktext, sourcePath, newLeaf, openState) {
|
|
3275
|
+
const linkpath = linktext.split(/[|#]/, 1)[0];
|
|
3276
|
+
const candidates = [linkpath, `${linkpath}.md`, `${linkpath}.markdown`];
|
|
3277
|
+
const file = candidates
|
|
3278
|
+
.map((path) => this.app.vault.getFileByPath(path))
|
|
3279
|
+
.find((file) => file instanceof TFile);
|
|
3280
|
+
if (!file) {
|
|
3281
|
+
new Notice(`Unable to find file: ${linktext}`);
|
|
3282
|
+
return;
|
|
3283
|
+
}
|
|
3284
|
+
const leaf = this.getLeaf(newLeaf);
|
|
3285
|
+
const state = openState?.state
|
|
3286
|
+
? { type: leaf.view.getViewType(), state: openState.state }
|
|
3287
|
+
: undefined;
|
|
3288
|
+
if (openState?.active !== false) {
|
|
3289
|
+
this.activeLeaf = leaf;
|
|
3290
|
+
}
|
|
3291
|
+
await leaf.openFile(file, { state });
|
|
3292
|
+
}
|
|
3293
|
+
ensureSideLeaf(type, sideOrOptions = "right", options = {}) {
|
|
3294
|
+
const registeredOptions = this.sidebarViewPlacements.get(type) ?? {};
|
|
3295
|
+
const requestedOptions = arguments.length < 2
|
|
3296
|
+
? {}
|
|
3297
|
+
: typeof sideOrOptions === "string"
|
|
3298
|
+
? { ...options, side: sideOrOptions }
|
|
3299
|
+
: sideOrOptions;
|
|
3300
|
+
const placement = { ...registeredOptions, ...requestedOptions };
|
|
3301
|
+
const side = placement.side ?? "right";
|
|
3302
|
+
if (placement.group) {
|
|
3303
|
+
const group = this.getOrCreateSidebarGroup(side, placement.group, {
|
|
3304
|
+
id: placement.group,
|
|
3305
|
+
name: placement.groupTitle ?? placement.title ?? placement.group,
|
|
3306
|
+
icon: placement.groupIcon ?? placement.icon,
|
|
3307
|
+
});
|
|
3308
|
+
const existing = group.children.find((leaf) => leaf.view.getViewType() === type);
|
|
3309
|
+
if (existing)
|
|
3310
|
+
return existing;
|
|
3311
|
+
const leaf = new WorkspaceLeaf();
|
|
3312
|
+
group.addChild(leaf);
|
|
3313
|
+
if (placement.hidden) {
|
|
3314
|
+
group.setLeafHidden(leaf, true);
|
|
3315
|
+
}
|
|
3316
|
+
this.requestSaveLayout();
|
|
3317
|
+
return leaf;
|
|
3318
|
+
}
|
|
3319
|
+
const existing = this.getLeavesOfType(type)[0];
|
|
3320
|
+
if (existing)
|
|
3321
|
+
return existing;
|
|
3322
|
+
const leaf = side === "left" ? this.getLeftLeaf(false) : this.getRightLeaf(false);
|
|
3323
|
+
if (!leaf) {
|
|
3324
|
+
throw new Error(`Unable to create ${side} leaf`);
|
|
3325
|
+
}
|
|
3326
|
+
return leaf;
|
|
3327
|
+
}
|
|
3328
|
+
handleLinkContextMenu(menu, linktext, sourcePath, event) {
|
|
3329
|
+
menu.addItem((item) => item.setTitle("Open link").onClick(() => {
|
|
3330
|
+
this.openLinkText(linktext, sourcePath);
|
|
3331
|
+
}));
|
|
3332
|
+
}
|
|
3333
|
+
moveLeafToPopout(leaf, data) {
|
|
3334
|
+
return this.moveWorkspaceChildToWindow(leaf, data, {
|
|
3335
|
+
mode: "popout",
|
|
3336
|
+
source: "api",
|
|
3337
|
+
operation: "move-leaf-to-popout",
|
|
3338
|
+
});
|
|
3339
|
+
}
|
|
3340
|
+
moveWorkspaceChildToPopout(item, data) {
|
|
3341
|
+
return this.moveWorkspaceChildToWindow(item, data, {
|
|
3342
|
+
mode: "popout",
|
|
3343
|
+
source: "api",
|
|
3344
|
+
operation: "move-workspace-child-to-popout",
|
|
3345
|
+
});
|
|
3346
|
+
}
|
|
3347
|
+
openPopoutLeaf(data) {
|
|
3348
|
+
const window = this.createWorkspaceWindow({ ...data, mode: "popout" });
|
|
3349
|
+
const tabs = new WorkspaceTabs({ leaves: [] });
|
|
3350
|
+
const leaf = new WorkspaceLeaf();
|
|
3351
|
+
tabs.addChild(leaf);
|
|
3352
|
+
tabs.selected = leaf;
|
|
3353
|
+
window.addChild(tabs);
|
|
3354
|
+
this.floating.addChild(window);
|
|
3355
|
+
this.floating.bringToFront(window);
|
|
3356
|
+
this.activeLeaf = leaf;
|
|
3357
|
+
this.requestSaveLayout({
|
|
3358
|
+
source: "popout",
|
|
3359
|
+
operation: "open-popout-window",
|
|
3360
|
+
});
|
|
3361
|
+
return leaf;
|
|
3362
|
+
}
|
|
3363
|
+
openFloatingLeaf(data) {
|
|
3364
|
+
const window = this.createWorkspaceWindow({ ...data, mode: "floating" });
|
|
3365
|
+
const tabs = new WorkspaceTabs({ leaves: [] });
|
|
3366
|
+
const leaf = new WorkspaceLeaf();
|
|
3367
|
+
tabs.addChild(leaf);
|
|
3368
|
+
tabs.selected = leaf;
|
|
3369
|
+
window.addChild(tabs);
|
|
3370
|
+
this.floating.addChild(window);
|
|
3371
|
+
this.floating.bringToFront(window);
|
|
3372
|
+
this.activeLeaf = leaf;
|
|
3373
|
+
this.requestSaveLayout({
|
|
3374
|
+
source: "api",
|
|
3375
|
+
operation: "open-floating-window",
|
|
3376
|
+
});
|
|
3377
|
+
return leaf;
|
|
3378
|
+
}
|
|
3379
|
+
moveWorkspaceChildToFloating(item, data, options = {}) {
|
|
3380
|
+
return this.moveWorkspaceChildToWindow(item, data, {
|
|
3381
|
+
...options,
|
|
3382
|
+
mode: "floating",
|
|
3383
|
+
});
|
|
3384
|
+
}
|
|
3385
|
+
supportsPopoutWindows() {
|
|
3386
|
+
return supportsWorkspacePopouts();
|
|
3387
|
+
}
|
|
3388
|
+
createWorkspaceWindow(data = {}) {
|
|
3389
|
+
if (data.mode !== "popout") {
|
|
3390
|
+
return new WorkspaceWindow({ ...data, mode: "floating" });
|
|
3391
|
+
}
|
|
3392
|
+
const handle = openWorkspacePopoutHandle(data);
|
|
3393
|
+
if (!handle) {
|
|
3394
|
+
throw new Error(WORKSPACE_POPOUT_UNSUPPORTED_ERROR_MESSAGE);
|
|
3395
|
+
}
|
|
3396
|
+
const window = new WorkspaceWindow({ ...data, mode: "popout" }, handle.win, handle.doc);
|
|
3397
|
+
window.attachPopoutHandle(handle, () => {
|
|
3398
|
+
if (this.floating.children.includes(window)) {
|
|
3399
|
+
this.closeFloatingWindow(window, {
|
|
3400
|
+
closeHost: false,
|
|
3401
|
+
operation: "close-popout-window",
|
|
3402
|
+
});
|
|
3403
|
+
}
|
|
3404
|
+
});
|
|
3405
|
+
return window;
|
|
3406
|
+
}
|
|
3407
|
+
moveWorkspaceChildToWindow(item, data, options) {
|
|
3408
|
+
const window = this.createWorkspaceWindow({ ...data, mode: options.mode });
|
|
3409
|
+
const operation = options.operation ?? "float-pane";
|
|
3410
|
+
const eventOptions = {
|
|
3411
|
+
source: options.source ?? "api",
|
|
3412
|
+
operation,
|
|
3413
|
+
position: "center",
|
|
3414
|
+
target: window,
|
|
3415
|
+
item,
|
|
3416
|
+
};
|
|
3417
|
+
const willDrop = this.dispatchLayoutDropEvent("layout-will-drop", eventOptions);
|
|
3418
|
+
if (willDrop.defaultPrevented) {
|
|
3419
|
+
window.closePopoutWindow();
|
|
3420
|
+
return window;
|
|
3421
|
+
}
|
|
3422
|
+
// Build and commit the destination tree before detaching the item from its
|
|
3423
|
+
// source. This ensures the item always has a reachable parent: if anything
|
|
3424
|
+
// between the detach and the addChild were to throw, the window is already
|
|
3425
|
+
// registered in this.floating and the item is still in its original tree.
|
|
3426
|
+
const tabs = new WorkspaceTabs({ leaves: [] });
|
|
3427
|
+
window.addChild(tabs);
|
|
3428
|
+
this.floating.addChild(window);
|
|
3429
|
+
if (item.parent) {
|
|
3430
|
+
item.detach(true);
|
|
3431
|
+
}
|
|
3432
|
+
tabs.addChild(item);
|
|
3433
|
+
tabs.selected = item;
|
|
3434
|
+
this.floating.bringToFront(window);
|
|
3435
|
+
this.activeLeaf =
|
|
3436
|
+
item instanceof WorkspaceLeaf ? item : item.getSelectedLeaf();
|
|
3437
|
+
this.requestSaveLayout({
|
|
3438
|
+
source: options.mode === "popout"
|
|
3439
|
+
? "popout"
|
|
3440
|
+
: options.source === "api"
|
|
3441
|
+
? "api"
|
|
3442
|
+
: "drag-drop",
|
|
3443
|
+
operation,
|
|
3444
|
+
});
|
|
3445
|
+
this.dispatchLayoutDropEvent("layout-did-drop", eventOptions);
|
|
3446
|
+
return window;
|
|
3447
|
+
}
|
|
3448
|
+
focusFloatingWindow(window) {
|
|
3449
|
+
const alreadyTopmost = this.floating.children.at(-1) === window;
|
|
3450
|
+
this.focusedHostId = window.id;
|
|
3451
|
+
this.floating.bringToFront(window);
|
|
3452
|
+
window.focusPopoutWindow();
|
|
3453
|
+
const selectedLeaf = this.selectedLeafForWindow(window);
|
|
3454
|
+
if (selectedLeaf) {
|
|
3455
|
+
this.activeLeaf = selectedLeaf;
|
|
3456
|
+
}
|
|
3457
|
+
if (!alreadyTopmost) {
|
|
3458
|
+
this.requestSaveLayout({
|
|
3459
|
+
source: "api",
|
|
3460
|
+
operation: "focus-floating-pane",
|
|
3461
|
+
});
|
|
3462
|
+
}
|
|
3463
|
+
}
|
|
3464
|
+
setFloatingWindowDisplayState(window, displayState, operation = `set-floating-pane-${displayState}`) {
|
|
3465
|
+
window.setDisplayState(displayState);
|
|
3466
|
+
this.floating.bringToFront(window);
|
|
3467
|
+
const selectedLeaf = this.selectedLeafForWindow(window);
|
|
3468
|
+
if (selectedLeaf && displayState !== "minimized") {
|
|
3469
|
+
this.activeLeaf = selectedLeaf;
|
|
3470
|
+
}
|
|
3471
|
+
this.requestSaveLayout({ source: "api", operation });
|
|
3472
|
+
}
|
|
3473
|
+
collapseFloatingWindow(window) {
|
|
3474
|
+
this.setFloatingWindowDisplayState(window, "collapsed", "collapse-floating-pane");
|
|
3475
|
+
}
|
|
3476
|
+
minimizeFloatingWindow(window) {
|
|
3477
|
+
this.setFloatingWindowDisplayState(window, "minimized", "minimize-floating-pane");
|
|
3478
|
+
}
|
|
3479
|
+
maximizeFloatingWindow(window) {
|
|
3480
|
+
this.setFloatingWindowDisplayState(window, "maximized", "maximize-floating-pane");
|
|
3481
|
+
}
|
|
3482
|
+
restoreFloatingWindow(window) {
|
|
3483
|
+
this.setFloatingWindowDisplayState(window, "normal", "restore-floating-pane");
|
|
3484
|
+
}
|
|
3485
|
+
setFloatingWindowBounds(window, bounds) {
|
|
3486
|
+
window.setBounds(bounds);
|
|
3487
|
+
}
|
|
3488
|
+
commitFloatingWindowBounds(window, bounds, operation = "resize-floating-pane") {
|
|
3489
|
+
window.setBounds(bounds);
|
|
3490
|
+
this.requestSaveLayout({ source: "resize", operation });
|
|
3491
|
+
}
|
|
3492
|
+
closeFloatingWindow(window, options = {}) {
|
|
3493
|
+
if (this.focusedHostId === window.id) {
|
|
3494
|
+
this.focusedHostId = WORKSPACE_ROOT_HOST_ID;
|
|
3495
|
+
}
|
|
3496
|
+
if (options.closeHost !== false) {
|
|
3497
|
+
window.closePopoutWindow();
|
|
3498
|
+
}
|
|
3499
|
+
const leaves = [];
|
|
3500
|
+
window.iterateAllLeaves((leaf) => {
|
|
3501
|
+
leaves.push(leaf);
|
|
3502
|
+
});
|
|
3503
|
+
leaves.forEach((leaf) => leaf.close());
|
|
3504
|
+
if (this.floating.children.includes(window)) {
|
|
3505
|
+
this.floating.removeChild(window);
|
|
3506
|
+
}
|
|
3507
|
+
this.activeLeaf = this.activeRootLeaf;
|
|
3508
|
+
this.requestSaveLayout({
|
|
3509
|
+
source: window.mode === "popout" ? "popout" : "api",
|
|
3510
|
+
operation: options.operation ??
|
|
3511
|
+
(window.mode === "popout"
|
|
3512
|
+
? "close-popout-window"
|
|
3513
|
+
: "close-floating-pane"),
|
|
3514
|
+
});
|
|
3515
|
+
}
|
|
3516
|
+
createSideLeaf(section) {
|
|
3517
|
+
const leaf = new WorkspaceLeaf();
|
|
3518
|
+
const currentTab = this.findOrCreateTab(section);
|
|
3519
|
+
const layout = currentTab.parent;
|
|
3520
|
+
const index = layout.children.findIndex((child) => child === currentTab);
|
|
3521
|
+
const tab = layout.removeChild(currentTab, true);
|
|
3522
|
+
if (!tab || index === -1) {
|
|
3523
|
+
return null;
|
|
3524
|
+
}
|
|
3525
|
+
const nested = new WorkspaceView("horizontal");
|
|
3526
|
+
nested.addChild(tab);
|
|
3527
|
+
nested.addChild(new WorkspaceTabs({ leaves: [leaf] }));
|
|
3528
|
+
layout.addChild(nested, index);
|
|
3529
|
+
return leaf;
|
|
3530
|
+
}
|
|
3531
|
+
findOrCreateTab(section) {
|
|
3532
|
+
const rootLeaf = section.iterateAllLeaves((leaf) => leaf);
|
|
3533
|
+
let currentTab;
|
|
3534
|
+
if (rootLeaf) {
|
|
3535
|
+
currentTab = this.tabsForLeaf(rootLeaf);
|
|
3536
|
+
}
|
|
3537
|
+
else {
|
|
3538
|
+
const tab = section.iterateAllTabs((tab) => tab);
|
|
3539
|
+
if (tab) {
|
|
3540
|
+
currentTab = tab;
|
|
3541
|
+
}
|
|
3542
|
+
else {
|
|
3543
|
+
let split = section.iterateAllSplits((split) => split);
|
|
3544
|
+
currentTab = new WorkspaceTabs({ leaves: [] });
|
|
3545
|
+
if (split) {
|
|
3546
|
+
split.addChild(currentTab);
|
|
3547
|
+
}
|
|
3548
|
+
else {
|
|
3549
|
+
const view = new WorkspaceView();
|
|
3550
|
+
view.addChild(currentTab);
|
|
3551
|
+
section.addChild(view);
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
return currentTab;
|
|
3556
|
+
}
|
|
3557
|
+
/**
|
|
3558
|
+
* Create a new leaf inside the right sidebar.
|
|
3559
|
+
*
|
|
3560
|
+
* @param split Should the existing split be split up?
|
|
3561
|
+
* @public
|
|
3562
|
+
*/
|
|
3563
|
+
getRightLeaf(split) {
|
|
3564
|
+
if (split) {
|
|
3565
|
+
return this.createSideLeaf(this.rightSplit);
|
|
3566
|
+
}
|
|
3567
|
+
const leaf = new WorkspaceLeaf();
|
|
3568
|
+
this.findOrCreateTab(this.rightSplit).addChild(leaf);
|
|
3569
|
+
return leaf;
|
|
3570
|
+
}
|
|
3571
|
+
/**
|
|
3572
|
+
* Create a new leaf inside the left sidebar.
|
|
3573
|
+
*
|
|
3574
|
+
* @param split Should the existing split be split up?
|
|
3575
|
+
* @public
|
|
3576
|
+
*/
|
|
3577
|
+
getLeftLeaf(split) {
|
|
3578
|
+
if (split) {
|
|
3579
|
+
return this.createSideLeaf(this.leftSplit);
|
|
3580
|
+
}
|
|
3581
|
+
const leaf = new WorkspaceLeaf();
|
|
3582
|
+
this.findOrCreateTab(this.leftSplit).addChild(leaf);
|
|
3583
|
+
return leaf;
|
|
3584
|
+
}
|
|
3585
|
+
/** Create and activate a new leaf in the bottom panel. @public */
|
|
3586
|
+
getBottomLeaf() {
|
|
3587
|
+
const leaf = new WorkspaceLeaf();
|
|
3588
|
+
this.bottomPanel.addChild(leaf);
|
|
3589
|
+
this.bottomPanel.selected = leaf;
|
|
3590
|
+
this.bottomPanel._applyOpen(true);
|
|
3591
|
+
this.activeLeaf = leaf;
|
|
3592
|
+
this.focusRootHost();
|
|
3593
|
+
this.requestSaveLayout({
|
|
3594
|
+
source: "bottom-panel",
|
|
3595
|
+
operation: "open-leaf",
|
|
3596
|
+
});
|
|
3597
|
+
return leaf;
|
|
3598
|
+
}
|
|
3599
|
+
/** Open or close the bottom panel without changing its last size. @public */
|
|
3600
|
+
setBottomPanelOpen(open) {
|
|
3601
|
+
if (this.bottomPanel.collapsed === !open)
|
|
3602
|
+
return;
|
|
3603
|
+
this.bottomPanel._applyOpen(open);
|
|
3604
|
+
this.requestSaveLayout({
|
|
3605
|
+
source: "bottom-panel",
|
|
3606
|
+
operation: open ? "expand" : "collapse",
|
|
3607
|
+
});
|
|
3608
|
+
}
|
|
3609
|
+
/** Resize the bottom panel using design-core's supported range. @public */
|
|
3610
|
+
setBottomPanelSize(size) {
|
|
3611
|
+
if (!Number.isFinite(size))
|
|
3612
|
+
return;
|
|
3613
|
+
const nextSize = Math.min(640, Math.max(120, size));
|
|
3614
|
+
if (this.bottomPanel.size === nextSize)
|
|
3615
|
+
return;
|
|
3616
|
+
this.bottomPanel._applySize(nextSize);
|
|
3617
|
+
this.requestSaveLayout({
|
|
3618
|
+
source: "bottom-panel",
|
|
3619
|
+
operation: "resize",
|
|
3620
|
+
});
|
|
3621
|
+
}
|
|
3622
|
+
/** Toggle the bottom panel while retaining its last expanded size. @public */
|
|
3623
|
+
toggleBottomPanel() {
|
|
3624
|
+
this.setBottomPanelOpen(this.bottomPanel.collapsed);
|
|
3625
|
+
}
|
|
3626
|
+
/** Configured desktop span of the bottom panel. @public */
|
|
3627
|
+
get bottomPanelAlignment() {
|
|
3628
|
+
return (this.#workspaceHostController?.workspace.bottomPanelAlignment ?? "center");
|
|
3629
|
+
}
|
|
3630
|
+
/** Update the live design-core shell setting without writing workspace JSON. */
|
|
3631
|
+
setBottomPanelAlignment(alignment) {
|
|
3632
|
+
return (this.#workspaceHostController?.workspace.setBottomPanelAlignment(alignment) ?? false);
|
|
3633
|
+
}
|
|
3634
|
+
duplicateLeaf(leaf, leafType, direction = "vertical") {
|
|
3635
|
+
if (leafType === "split" &&
|
|
3636
|
+
this.tabsForLeaf(leaf) instanceof WorkspaceBottomPanel) {
|
|
3637
|
+
return Promise.reject(new Error("Bottom panel does not support split panes"));
|
|
3638
|
+
}
|
|
3639
|
+
if (leafType === "window") {
|
|
3640
|
+
const popoutLeaf = this.openPopoutLeaf();
|
|
3641
|
+
return popoutLeaf.setViewState({ ...leaf.state }).then(() => {
|
|
3642
|
+
this.app.workspace.activeLeaf = popoutLeaf;
|
|
3643
|
+
this.app.workspace.requestSaveLayout({
|
|
3644
|
+
source: "popout",
|
|
3645
|
+
operation: "duplicate-leaf-window",
|
|
3646
|
+
});
|
|
3647
|
+
return popoutLeaf;
|
|
3648
|
+
});
|
|
3649
|
+
}
|
|
3650
|
+
const duplicateLeaf = new WorkspaceLeaf();
|
|
3651
|
+
return duplicateLeaf.setViewState({ ...leaf.state }).then(() => {
|
|
3652
|
+
const leafTabs = this.tabsForLeaf(leaf);
|
|
3653
|
+
const split = leafTabs.parent;
|
|
3654
|
+
switch (leafType) {
|
|
3655
|
+
case true:
|
|
3656
|
+
case "tab":
|
|
3657
|
+
leafTabs.addChild(duplicateLeaf);
|
|
3658
|
+
leafTabs.selected = (leafTabs.children.length - 1).toString();
|
|
3659
|
+
break;
|
|
3660
|
+
case "split":
|
|
3661
|
+
direction ||= "vertical";
|
|
3662
|
+
if (split.type === direction) {
|
|
3663
|
+
split.addChild(new WorkspaceTabs({ leaves: [duplicateLeaf] }));
|
|
3664
|
+
}
|
|
3665
|
+
else {
|
|
3666
|
+
const index = split.children.findIndex((it) => it === leafTabs);
|
|
3667
|
+
const tab = split.removeChild(leafTabs, true);
|
|
3668
|
+
if (tab) {
|
|
3669
|
+
const layout = new WorkspaceView(direction);
|
|
3670
|
+
layout.addChild(tab);
|
|
3671
|
+
layout.addChild(new WorkspaceTabs({ leaves: [duplicateLeaf] }));
|
|
3672
|
+
split.addChild(layout, index);
|
|
3673
|
+
}
|
|
3674
|
+
}
|
|
3675
|
+
}
|
|
3676
|
+
this.app.workspace.activeLeaf = duplicateLeaf;
|
|
3677
|
+
this.app.workspace.requestSaveLayout({
|
|
3678
|
+
source: "api",
|
|
3679
|
+
operation: leafType === "split" ? `duplicate-leaf-${direction}` : "duplicate-leaf-tab",
|
|
3680
|
+
});
|
|
3681
|
+
return duplicateLeaf;
|
|
3682
|
+
});
|
|
3683
|
+
}
|
|
3684
|
+
/**
|
|
3685
|
+
* If newLeaf is false (or not set) then an existing leaf which can be
|
|
3686
|
+
* navigated is returned, or a new leaf will be created if there was no leaf
|
|
3687
|
+
* available.
|
|
3688
|
+
*
|
|
3689
|
+
* If newLeaf is `'tab'` or `true` then a new leaf will be created in the
|
|
3690
|
+
* preferred location within the root split and returned.
|
|
3691
|
+
*
|
|
3692
|
+
* If newLeaf is `'split'` then a new leaf will be created adjacent to the
|
|
3693
|
+
* currently active leaf.
|
|
3694
|
+
*
|
|
3695
|
+
* If newLeaf is `'window'` then a popout window will be created with a new
|
|
3696
|
+
* leaf inside.
|
|
3697
|
+
*
|
|
3698
|
+
* @public
|
|
3699
|
+
*/
|
|
3700
|
+
getLeaf(newLeaf, direction) {
|
|
3701
|
+
if (!newLeaf) {
|
|
3702
|
+
const focusedHostId = this.getFocusedCommandHostId();
|
|
3703
|
+
if (focusedHostId !== WORKSPACE_ROOT_HOST_ID) {
|
|
3704
|
+
const focusedHostLeaf = this.getCommandHostLeaf(focusedHostId);
|
|
3705
|
+
if (canReuseLeafForFileNavigation(focusedHostLeaf)) {
|
|
3706
|
+
return focusedHostLeaf;
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
const activeFloatingWindow = this.floatingWindowForLeaf(this._activeLeaf);
|
|
3710
|
+
if (activeFloatingWindow &&
|
|
3711
|
+
canReuseLeafForFileNavigation(this._activeLeaf)) {
|
|
3712
|
+
return this._activeLeaf;
|
|
3713
|
+
}
|
|
3714
|
+
if (canReuseLeafForFileNavigation(this.activeRootLeaf)) {
|
|
3715
|
+
return this.activeRootLeaf;
|
|
3716
|
+
}
|
|
3717
|
+
const tab = this.findOrCreateTab(this.rootSplit);
|
|
3718
|
+
const reusable = tab.children.find((child) => child instanceof WorkspaceLeaf &&
|
|
3719
|
+
canReuseLeafForFileNavigation(child));
|
|
3720
|
+
if (reusable) {
|
|
3721
|
+
return reusable;
|
|
3722
|
+
}
|
|
3723
|
+
if (canReuseLeafForFileNavigation(tab.selectedLeaf)) {
|
|
3724
|
+
return tab.selectedLeaf;
|
|
3725
|
+
}
|
|
3726
|
+
const leaf = new WorkspaceLeaf();
|
|
3727
|
+
tab.addChild(leaf);
|
|
3728
|
+
return leaf;
|
|
3729
|
+
}
|
|
3730
|
+
if (newLeaf === "tab" || newLeaf === true) {
|
|
3731
|
+
const tab = this.findOrCreateTab(this.rootSplit);
|
|
3732
|
+
const leaf = new WorkspaceLeaf();
|
|
3733
|
+
tab.addChild(leaf);
|
|
3734
|
+
return leaf;
|
|
3735
|
+
}
|
|
3736
|
+
if (newLeaf === "split") {
|
|
3737
|
+
const activeTab = (this.activeLeaf &&
|
|
3738
|
+
this.containsRootLeaf(this.activeLeaf) &&
|
|
3739
|
+
this.tabsForLeaf(this.activeLeaf)) ||
|
|
3740
|
+
this.findOrCreateTab(this.rootSplit);
|
|
3741
|
+
const leaf = new WorkspaceLeaf();
|
|
3742
|
+
const view = activeTab.parent;
|
|
3743
|
+
const dir = direction ?? "vertical";
|
|
3744
|
+
if (view.type === dir) {
|
|
3745
|
+
view.addChild(new WorkspaceTabs({ leaves: [leaf] }));
|
|
3746
|
+
}
|
|
3747
|
+
else {
|
|
3748
|
+
const tab = view.removeChild(activeTab);
|
|
3749
|
+
if (tab) {
|
|
3750
|
+
const split = new WorkspaceView(dir);
|
|
3751
|
+
split.addChild(tab);
|
|
3752
|
+
split.addChild(new WorkspaceTabs({ leaves: [leaf] }));
|
|
3753
|
+
view.addChild(split);
|
|
3754
|
+
}
|
|
3755
|
+
}
|
|
3756
|
+
return leaf;
|
|
3757
|
+
}
|
|
3758
|
+
if (newLeaf === "window") {
|
|
3759
|
+
return this.openPopoutLeaf();
|
|
3760
|
+
}
|
|
3761
|
+
throw new Error(`Unsupported pane: ${newLeaf}`);
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3764
|
+
export class OnDemandPluginInstallView extends FileView {
|
|
3765
|
+
plugin;
|
|
3766
|
+
status = $state("idle");
|
|
3767
|
+
errorMessage = $state(null);
|
|
3768
|
+
constructor(leaf, plugin, application) {
|
|
3769
|
+
super(leaf);
|
|
3770
|
+
this.plugin = plugin;
|
|
3771
|
+
this.application = resolveApplication(application ?? leaf.application);
|
|
3772
|
+
}
|
|
3773
|
+
application;
|
|
3774
|
+
getViewType() {
|
|
3775
|
+
return ON_DEMAND_PLUGIN_INSTALL_VIEW_TYPE;
|
|
3776
|
+
}
|
|
3777
|
+
getDisplayText() {
|
|
3778
|
+
return this.file?.name ?? "Install plugin";
|
|
3779
|
+
}
|
|
3780
|
+
getIcon() {
|
|
3781
|
+
return "download";
|
|
3782
|
+
}
|
|
3783
|
+
canAcceptExtension() {
|
|
3784
|
+
return true;
|
|
3785
|
+
}
|
|
3786
|
+
async onLoadFile(file) {
|
|
3787
|
+
this.file = file;
|
|
3788
|
+
this.render();
|
|
3789
|
+
}
|
|
3790
|
+
async onUnloadFile() {
|
|
3791
|
+
this.file = null;
|
|
3792
|
+
}
|
|
3793
|
+
async onRename(file) {
|
|
3794
|
+
this.file = file;
|
|
3795
|
+
this.render();
|
|
3796
|
+
}
|
|
3797
|
+
getErrorMessage() {
|
|
3798
|
+
return this.errorMessage;
|
|
3799
|
+
}
|
|
3800
|
+
async installAndOpen() {
|
|
3801
|
+
if (!this.file || this.status === "installing") {
|
|
3802
|
+
return false;
|
|
3803
|
+
}
|
|
3804
|
+
this.status = "installing";
|
|
3805
|
+
this.errorMessage = null;
|
|
3806
|
+
this.render();
|
|
3807
|
+
try {
|
|
3808
|
+
await withPluginInstallProgress(this.application, {
|
|
3809
|
+
pluginId: this.plugin.id,
|
|
3810
|
+
title: `Installing ${this.plugin.name}`,
|
|
3811
|
+
source: "Plugin install",
|
|
3812
|
+
}, (signal) => this.application.pluginDistribution.install(this.plugin.id, {
|
|
3813
|
+
requireOfficial: true,
|
|
3814
|
+
enable: true,
|
|
3815
|
+
signal,
|
|
3816
|
+
}));
|
|
3817
|
+
const file = this.file;
|
|
3818
|
+
const viewCreator = this.application.workspace.viewCreator(file);
|
|
3819
|
+
if (!viewCreator) {
|
|
3820
|
+
throw new Error(`Plugin ${this.plugin.name} installed, but it did not register a handler for ${file.path}.`);
|
|
3821
|
+
}
|
|
3822
|
+
await this.leaf.openFile(file, { result: { history: false } });
|
|
3823
|
+
return true;
|
|
3824
|
+
}
|
|
3825
|
+
catch (error) {
|
|
3826
|
+
if (isAbortError(error)) {
|
|
3827
|
+
this.status = "idle";
|
|
3828
|
+
this.errorMessage = null;
|
|
3829
|
+
this.render();
|
|
3830
|
+
return false;
|
|
3831
|
+
}
|
|
3832
|
+
this.status = "failed";
|
|
3833
|
+
this.errorMessage =
|
|
3834
|
+
error instanceof Error ? error.message : String(error);
|
|
3835
|
+
this.render();
|
|
3836
|
+
return false;
|
|
3837
|
+
}
|
|
3838
|
+
}
|
|
3839
|
+
load() {
|
|
3840
|
+
this.render();
|
|
3841
|
+
}
|
|
3842
|
+
unload() {
|
|
3843
|
+
this.leaf?.contentEl?.replaceChildren();
|
|
3844
|
+
}
|
|
3845
|
+
onOpen() {
|
|
3846
|
+
return Promise.resolve();
|
|
3847
|
+
}
|
|
3848
|
+
onClose() {
|
|
3849
|
+
return Promise.resolve();
|
|
3850
|
+
}
|
|
3851
|
+
render() {
|
|
3852
|
+
const fileName = this.file?.name ?? "this file";
|
|
3853
|
+
const root = document.createElement("section");
|
|
3854
|
+
root.className = "plugin-install-prompt";
|
|
3855
|
+
const panel = document.createElement("div");
|
|
3856
|
+
panel.className = "plugin-install-prompt__panel";
|
|
3857
|
+
root.appendChild(panel);
|
|
3858
|
+
const title = document.createElement("h2");
|
|
3859
|
+
title.className = "plugin-install-prompt__title";
|
|
3860
|
+
title.textContent = `${this.plugin.name} is required`;
|
|
3861
|
+
panel.appendChild(title);
|
|
3862
|
+
const body = document.createElement("p");
|
|
3863
|
+
body.className = "plugin-install-prompt__body";
|
|
3864
|
+
body.textContent = `${fileName} can be opened by the verified official ${this.plugin.name} plugin.`;
|
|
3865
|
+
panel.appendChild(body);
|
|
3866
|
+
if (this.errorMessage) {
|
|
3867
|
+
const error = document.createElement("pre");
|
|
3868
|
+
error.className = "plugin-install-prompt__error";
|
|
3869
|
+
error.textContent = this.errorMessage;
|
|
3870
|
+
panel.appendChild(error);
|
|
3871
|
+
}
|
|
3872
|
+
const button = document.createElement("button");
|
|
3873
|
+
button.type = "button";
|
|
3874
|
+
button.className = "plugin-install-prompt__button";
|
|
3875
|
+
button.disabled = this.status === "installing";
|
|
3876
|
+
button.textContent =
|
|
3877
|
+
this.status === "installing"
|
|
3878
|
+
? "Installing..."
|
|
3879
|
+
: this.status === "failed"
|
|
3880
|
+
? "Retry install"
|
|
3881
|
+
: "Install and open";
|
|
3882
|
+
button.addEventListener("click", () => {
|
|
3883
|
+
void this.installAndOpen();
|
|
3884
|
+
});
|
|
3885
|
+
panel.appendChild(button);
|
|
3886
|
+
this.leaf?.contentEl?.replaceChildren(root);
|
|
3887
|
+
}
|
|
3888
|
+
}
|
|
3889
|
+
function canReuseLeafForFileNavigation(leaf) {
|
|
3890
|
+
if (!leaf)
|
|
3891
|
+
return false;
|
|
3892
|
+
return (leaf.view.getViewType() === "empty" || leaf.view instanceof FileView);
|
|
3893
|
+
}
|
|
3894
|
+
function isPoorerViewState(incoming, live) {
|
|
3895
|
+
const next = incoming ?? {};
|
|
3896
|
+
const liveEntries = Object.entries(live).filter(([, value]) => value !== undefined);
|
|
3897
|
+
if (liveEntries.length === 0)
|
|
3898
|
+
return false;
|
|
3899
|
+
if (Object.keys(next).length === 0)
|
|
3900
|
+
return true;
|
|
3901
|
+
return liveEntries.some(([key]) => !(key in next));
|
|
3902
|
+
}
|
|
3903
|
+
function collectLayoutViewTypes(node, target) {
|
|
3904
|
+
if (node.type === "split" || node.type === "floating") {
|
|
3905
|
+
for (const child of node.children) {
|
|
3906
|
+
collectLayoutViewTypes(child, target);
|
|
3907
|
+
}
|
|
3908
|
+
return;
|
|
3909
|
+
}
|
|
3910
|
+
for (const child of node.children) {
|
|
3911
|
+
if (child.type === "leaf" && typeof child.state?.type === "string") {
|
|
3912
|
+
target.add(child.state.type);
|
|
3913
|
+
}
|
|
3914
|
+
}
|
|
3915
|
+
}
|
|
3916
|
+
/**
|
|
3917
|
+
* Compatibility stand-in for a Design Core class view. The host owns the
|
|
3918
|
+
* live mount; this view only preserves type and chrome across API commits.
|
|
3919
|
+
*/
|
|
3920
|
+
class HostOwnedWorkspaceView extends View {
|
|
3921
|
+
#viewType;
|
|
3922
|
+
#title;
|
|
3923
|
+
constructor(leaf, viewType, title, icon) {
|
|
3924
|
+
super(leaf);
|
|
3925
|
+
this.#viewType = viewType;
|
|
3926
|
+
this.#title = title;
|
|
3927
|
+
this.icon = icon;
|
|
3928
|
+
}
|
|
3929
|
+
onOpen() {
|
|
3930
|
+
return Promise.resolve();
|
|
3931
|
+
}
|
|
3932
|
+
onClose() {
|
|
3933
|
+
return Promise.resolve();
|
|
3934
|
+
}
|
|
3935
|
+
getViewType() {
|
|
3936
|
+
return this.#viewType;
|
|
3937
|
+
}
|
|
3938
|
+
getDisplayText() {
|
|
3939
|
+
return this.#title;
|
|
3940
|
+
}
|
|
3941
|
+
}
|
|
3942
|
+
function isHostOwnedViewType(workspace, type) {
|
|
3943
|
+
if (!type || type === "empty" || workspace.viewCreator(type)) {
|
|
3944
|
+
return false;
|
|
3945
|
+
}
|
|
3946
|
+
if (type === PROBLEMS_VIEW_TYPE) {
|
|
3947
|
+
return true;
|
|
3948
|
+
}
|
|
3949
|
+
return (resolveWorkspaceHostBinding(workspace)?.controller.renderer.registry.resolve(type) != null);
|
|
3950
|
+
}
|
|
3951
|
+
function hostOwnedViewChrome(type, definition, chrome) {
|
|
3952
|
+
const title = typeof chrome?.title === "string" &&
|
|
3953
|
+
chrome.title.length > 0 &&
|
|
3954
|
+
chrome.title !== type
|
|
3955
|
+
? chrome.title
|
|
3956
|
+
: type === PROBLEMS_VIEW_TYPE
|
|
3957
|
+
? "Problems"
|
|
3958
|
+
: type;
|
|
3959
|
+
const icon = typeof chrome?.icon === "string" && chrome.icon.length > 0
|
|
3960
|
+
? chrome.icon
|
|
3961
|
+
: (definition?.icon ??
|
|
3962
|
+
(type === PROBLEMS_VIEW_TYPE ? "circle-alert" : "file"));
|
|
3963
|
+
return { title, icon };
|
|
3964
|
+
}
|
|
3965
|
+
/**
|
|
3966
|
+
* Single navigable pane in the workspace that hosts one {@link View} at a time.
|
|
3967
|
+
*
|
|
3968
|
+
* @public
|
|
3969
|
+
*/
|
|
3970
|
+
export class WorkspaceLeaf extends WorkspaceItem {
|
|
3971
|
+
_view = $state();
|
|
3972
|
+
hoverPopover = null;
|
|
3973
|
+
pinned = $state(false);
|
|
3974
|
+
group = $state(null);
|
|
3975
|
+
ephemeralState = {};
|
|
3976
|
+
contentEl = $state();
|
|
3977
|
+
containerEl = $state();
|
|
3978
|
+
history;
|
|
3979
|
+
currentState = Object.freeze({
|
|
3980
|
+
type: "empty",
|
|
3981
|
+
state: Object.freeze({}),
|
|
3982
|
+
});
|
|
3983
|
+
detach(softDelete = false) {
|
|
3984
|
+
return this.parent.removeChild(this, softDelete);
|
|
3985
|
+
}
|
|
3986
|
+
get state() {
|
|
3987
|
+
const value = { ...this.currentState };
|
|
3988
|
+
value.state ||= {};
|
|
3989
|
+
Object.freeze(value.state);
|
|
3990
|
+
return Object.freeze(value);
|
|
3991
|
+
}
|
|
3992
|
+
set state(value) {
|
|
3993
|
+
value = { ...value };
|
|
3994
|
+
value.state ||= {};
|
|
3995
|
+
Object.freeze(value.state);
|
|
3996
|
+
this.currentState = Object.freeze(value);
|
|
3997
|
+
}
|
|
3998
|
+
constructor(view) {
|
|
3999
|
+
super();
|
|
4000
|
+
if (view) {
|
|
4001
|
+
view.leaf = this;
|
|
4002
|
+
this.view = view;
|
|
4003
|
+
}
|
|
4004
|
+
else {
|
|
4005
|
+
this.view = new EmptyView(this);
|
|
4006
|
+
}
|
|
4007
|
+
this.containerEl = createDiv("h-full");
|
|
4008
|
+
// Imperative views own this compatibility root, so its sizing cannot rely
|
|
4009
|
+
// on a host-wide utility stylesheet being present.
|
|
4010
|
+
this.containerEl.style.height = "100%";
|
|
4011
|
+
this.contentEl = this.containerEl.createDiv();
|
|
4012
|
+
this.history = new HistoryManager(this.updateHistory.bind(this));
|
|
4013
|
+
}
|
|
4014
|
+
updateHistory(state) {
|
|
4015
|
+
const nextState = cloneViewState(state);
|
|
4016
|
+
this.state = nextState;
|
|
4017
|
+
this.pinned = !!nextState.pinned;
|
|
4018
|
+
const filePath = historyFilePathForViewState(nextState);
|
|
4019
|
+
const file = filePath ? this.app.vault.getFileByPath(filePath) : null;
|
|
4020
|
+
if (file instanceof TFile) {
|
|
4021
|
+
this.ensureContentEl();
|
|
4022
|
+
return this.openFile(file, {
|
|
4023
|
+
state: nextState,
|
|
4024
|
+
result: { history: false },
|
|
4025
|
+
});
|
|
4026
|
+
}
|
|
4027
|
+
this.ensureContentEl();
|
|
4028
|
+
return this.setViewState(nextState, { history: false });
|
|
4029
|
+
}
|
|
4030
|
+
captureCurrentViewState() {
|
|
4031
|
+
return captureLeafViewState(this);
|
|
4032
|
+
}
|
|
4033
|
+
snapshotHistoryBeforeNavigation(result, capturedState) {
|
|
4034
|
+
if (result?.history === false) {
|
|
4035
|
+
return;
|
|
4036
|
+
}
|
|
4037
|
+
const currentState = capturedState
|
|
4038
|
+
? cloneViewState(capturedState)
|
|
4039
|
+
: this.captureCurrentViewState();
|
|
4040
|
+
this.state = currentState;
|
|
4041
|
+
if (this.history.history.length === 0) {
|
|
4042
|
+
this.history.pushState(cloneViewState(currentState));
|
|
4043
|
+
}
|
|
4044
|
+
else {
|
|
4045
|
+
this.history.replaceState(cloneViewState(currentState));
|
|
4046
|
+
}
|
|
4047
|
+
}
|
|
4048
|
+
pushHistoryAfterNavigation(result) {
|
|
4049
|
+
if (result?.history === false) {
|
|
4050
|
+
return;
|
|
4051
|
+
}
|
|
4052
|
+
const nextState = cloneViewState(this.state);
|
|
4053
|
+
if (isEqual(this.history.history.state, nextState)) {
|
|
4054
|
+
return;
|
|
4055
|
+
}
|
|
4056
|
+
this.history.pushState(nextState);
|
|
4057
|
+
}
|
|
4058
|
+
get view() {
|
|
4059
|
+
return this._view;
|
|
4060
|
+
}
|
|
4061
|
+
set view(view) {
|
|
4062
|
+
if (this._view === view) {
|
|
4063
|
+
return;
|
|
4064
|
+
}
|
|
4065
|
+
if (this._view) {
|
|
4066
|
+
const previousView = this._view;
|
|
4067
|
+
previousView.unload();
|
|
4068
|
+
if (hasDestroyableEditor(previousView)) {
|
|
4069
|
+
previousView.editor.destroy();
|
|
4070
|
+
}
|
|
4071
|
+
}
|
|
4072
|
+
view.leaf = this;
|
|
4073
|
+
if (hasDestroyableEditor(view) && this.application) {
|
|
4074
|
+
view.editor.bindApplication?.(this.application);
|
|
4075
|
+
}
|
|
4076
|
+
this._view = view;
|
|
4077
|
+
}
|
|
4078
|
+
open(view, result = { history: true }, state) {
|
|
4079
|
+
view.leaf = this;
|
|
4080
|
+
const filePath = (state || this.state)?.state?.["file"]?.toString();
|
|
4081
|
+
const file = filePath ? this.app.vault.getFileByPath(filePath) : null;
|
|
4082
|
+
this.view = view;
|
|
4083
|
+
if (view instanceof FileView && file instanceof TFile) {
|
|
4084
|
+
return this.openFile(file, { view, result, state }).then(() => view);
|
|
4085
|
+
}
|
|
4086
|
+
else if (view instanceof FileView && filePath) {
|
|
4087
|
+
new Notice(`Unable to load file: ${filePath}`, undefined, this.app);
|
|
4088
|
+
}
|
|
4089
|
+
this.prepareContainerForView(view);
|
|
4090
|
+
if (result?.history !== false) {
|
|
4091
|
+
this.snapshotHistoryBeforeNavigation(result);
|
|
4092
|
+
}
|
|
4093
|
+
this.view.load();
|
|
4094
|
+
if (result?.history !== false) {
|
|
4095
|
+
this.pushHistoryAfterNavigation(result);
|
|
4096
|
+
}
|
|
4097
|
+
this.app.workspace.requestSaveLayout();
|
|
4098
|
+
return Promise.resolve(this.view);
|
|
4099
|
+
}
|
|
4100
|
+
ensureContentEl() {
|
|
4101
|
+
if (this.contentEl.parentElement === this.containerEl) {
|
|
4102
|
+
return;
|
|
4103
|
+
}
|
|
4104
|
+
this.containerEl.empty();
|
|
4105
|
+
this.containerEl.appendChild(this.contentEl);
|
|
4106
|
+
}
|
|
4107
|
+
prepareContainerForView(view) {
|
|
4108
|
+
if (view instanceof ItemView) {
|
|
4109
|
+
this.ensureContentEl();
|
|
4110
|
+
return;
|
|
4111
|
+
}
|
|
4112
|
+
this.containerEl.empty();
|
|
4113
|
+
}
|
|
4114
|
+
openFile(file, { view, result, state, } = {}) {
|
|
4115
|
+
const historyStateBeforeNavigation = result?.history === false ? undefined : this.captureCurrentViewState();
|
|
4116
|
+
return this.app.telemetry
|
|
4117
|
+
.measureAsync("workspace.leaf.prepare_open_file", async () => {
|
|
4118
|
+
await this.app.plugins.activateForPath(file.path);
|
|
4119
|
+
const languageIds = this.app.plugins.findLanguageIdsForPath(file.path);
|
|
4120
|
+
for (const languageId of languageIds) {
|
|
4121
|
+
await this.app.plugins.activateForLanguage(languageId);
|
|
4122
|
+
}
|
|
4123
|
+
const association = this.app.workspace.getEditorAssociationForPath(file.path);
|
|
4124
|
+
if (association?.view) {
|
|
4125
|
+
await this.app.plugins.activateForViewType(association.view.viewType);
|
|
4126
|
+
}
|
|
4127
|
+
}, { slowThresholdMs: 100 })
|
|
4128
|
+
.then(async () => {
|
|
4129
|
+
view =
|
|
4130
|
+
view ??
|
|
4131
|
+
resolveViewForOpenFile(this, file) ??
|
|
4132
|
+
(await this.app.workspace.createOnDemandPluginInstallView(this, file)) ??
|
|
4133
|
+
undefined;
|
|
4134
|
+
return this.app.telemetry.measureAsync("workspace.leaf.open_file", async (span) => {
|
|
4135
|
+
span.setAttribute("leaf.id", this.id);
|
|
4136
|
+
span.setAttribute("file.extension", file.extension);
|
|
4137
|
+
span.setAttribute("view.type", view?.getViewType?.() ?? "unknown");
|
|
4138
|
+
if (view instanceof FileView) {
|
|
4139
|
+
if (result?.history !== false) {
|
|
4140
|
+
this.snapshotHistoryBeforeNavigation(result, historyStateBeforeNavigation);
|
|
4141
|
+
}
|
|
4142
|
+
this.ensureContentEl();
|
|
4143
|
+
const mergedState = {
|
|
4144
|
+
...view.getState(),
|
|
4145
|
+
...(state?.state ?? {}),
|
|
4146
|
+
file: file.path,
|
|
4147
|
+
};
|
|
4148
|
+
await view.setState(mergedState);
|
|
4149
|
+
this.state = {
|
|
4150
|
+
type: view.getViewType(),
|
|
4151
|
+
state: mergedState,
|
|
4152
|
+
};
|
|
4153
|
+
this.view = view;
|
|
4154
|
+
await view.onLoadFile(file);
|
|
4155
|
+
this.app.workspace.trigger("file-open", file);
|
|
4156
|
+
if (result?.history !== false) {
|
|
4157
|
+
this.pushHistoryAfterNavigation(result);
|
|
4158
|
+
}
|
|
4159
|
+
this.view.load();
|
|
4160
|
+
this.app.workspace.requestSaveLayout();
|
|
4161
|
+
return;
|
|
4162
|
+
}
|
|
4163
|
+
}, {
|
|
4164
|
+
attributes: {
|
|
4165
|
+
"leaf.id": this.id,
|
|
4166
|
+
"file.extension": file.extension,
|
|
4167
|
+
},
|
|
4168
|
+
slowThresholdMs: 250,
|
|
4169
|
+
});
|
|
4170
|
+
});
|
|
4171
|
+
}
|
|
4172
|
+
async setViewState(viewState, eState) {
|
|
4173
|
+
return this.app.telemetry.measureAsync("workspace.leaf.set_view_state", async (span) => {
|
|
4174
|
+
span.setAttribute("leaf.id", this.id);
|
|
4175
|
+
span.setAttribute("view.type", viewState.type);
|
|
4176
|
+
const persistedMissingViewType = viewState.type === "empty" &&
|
|
4177
|
+
typeof viewState.state?.["__missingViewType"] === "string" &&
|
|
4178
|
+
viewState.state["__missingViewType"].length > 0
|
|
4179
|
+
? viewState.state["__missingViewType"]
|
|
4180
|
+
: null;
|
|
4181
|
+
const requestedViewType = persistedMissingViewType ?? viewState.type;
|
|
4182
|
+
const filePath = viewState.state?.["file"]?.toString();
|
|
4183
|
+
const file = filePath ? this.app.vault.getFileByPath(filePath) : null;
|
|
4184
|
+
if (requestedViewType === ON_DEMAND_PLUGIN_INSTALL_VIEW_TYPE &&
|
|
4185
|
+
file instanceof TFile) {
|
|
4186
|
+
const promptView = await this.app.workspace.createOnDemandPluginInstallView(this, file, { confirm: false });
|
|
4187
|
+
if (promptView) {
|
|
4188
|
+
this.state = viewState;
|
|
4189
|
+
this.pinned = !!viewState.pinned;
|
|
4190
|
+
const result = {
|
|
4191
|
+
history: eState?.history === true,
|
|
4192
|
+
};
|
|
4193
|
+
await promptView.setState({ ...(viewState.state || {}) });
|
|
4194
|
+
await this.open(promptView, result, viewState);
|
|
4195
|
+
return;
|
|
4196
|
+
}
|
|
4197
|
+
}
|
|
4198
|
+
if (eState?.activatePlugins !== false) {
|
|
4199
|
+
await this.app.plugins.activateForViewType(requestedViewType);
|
|
4200
|
+
}
|
|
4201
|
+
let viewCreator = this.app.workspace.viewCreator(requestedViewType);
|
|
4202
|
+
let resolvedViewState = viewState;
|
|
4203
|
+
if (viewCreator && persistedMissingViewType) {
|
|
4204
|
+
const restoredState = { ...(viewState.state ?? {}) };
|
|
4205
|
+
delete restoredState["__missingViewType"];
|
|
4206
|
+
resolvedViewState = {
|
|
4207
|
+
...viewState,
|
|
4208
|
+
type: requestedViewType,
|
|
4209
|
+
state: restoredState,
|
|
4210
|
+
};
|
|
4211
|
+
}
|
|
4212
|
+
else if (!viewCreator &&
|
|
4213
|
+
isHostOwnedViewType(this.app.workspace, requestedViewType)) {
|
|
4214
|
+
const restoredState = { ...(viewState.state ?? {}) };
|
|
4215
|
+
delete restoredState["__missingViewType"];
|
|
4216
|
+
resolvedViewState = {
|
|
4217
|
+
...viewState,
|
|
4218
|
+
type: requestedViewType,
|
|
4219
|
+
state: restoredState,
|
|
4220
|
+
};
|
|
4221
|
+
const definition = resolveWorkspaceHostBinding(this.app.workspace)?.controller.renderer.registry.resolve(requestedViewType);
|
|
4222
|
+
const chrome = hostOwnedViewChrome(requestedViewType, definition, persistedMissingViewType
|
|
4223
|
+
? undefined
|
|
4224
|
+
: { title: eState?.title, icon: eState?.icon });
|
|
4225
|
+
viewCreator = (leaf) => new HostOwnedWorkspaceView(leaf, requestedViewType, chrome.title, chrome.icon);
|
|
4226
|
+
}
|
|
4227
|
+
else if (!viewCreator) {
|
|
4228
|
+
new Notice(`Unknown view type: ${requestedViewType}`, undefined, this.app);
|
|
4229
|
+
resolvedViewState = {
|
|
4230
|
+
...viewState,
|
|
4231
|
+
type: "empty",
|
|
4232
|
+
state: {
|
|
4233
|
+
...(viewState.state ?? {}),
|
|
4234
|
+
__missingViewType: requestedViewType,
|
|
4235
|
+
},
|
|
4236
|
+
};
|
|
4237
|
+
viewCreator =
|
|
4238
|
+
this.app.workspace.viewCreator("empty") ||
|
|
4239
|
+
((leaf) => new EmptyView(leaf));
|
|
4240
|
+
}
|
|
4241
|
+
this.state = resolvedViewState;
|
|
4242
|
+
this.pinned = !!viewState.pinned;
|
|
4243
|
+
const view = viewCreator(this);
|
|
4244
|
+
const result = { history: eState?.history === true };
|
|
4245
|
+
await view.setState({ ...(resolvedViewState.state || {}) });
|
|
4246
|
+
await this.open(view, result, resolvedViewState);
|
|
4247
|
+
}, {
|
|
4248
|
+
attributes: {
|
|
4249
|
+
"leaf.id": this.id,
|
|
4250
|
+
"view.type": viewState.type,
|
|
4251
|
+
},
|
|
4252
|
+
slowThresholdMs: 150,
|
|
4253
|
+
});
|
|
4254
|
+
}
|
|
4255
|
+
getViewState() {
|
|
4256
|
+
return this.state;
|
|
4257
|
+
}
|
|
4258
|
+
get isDeferred() {
|
|
4259
|
+
return false;
|
|
4260
|
+
}
|
|
4261
|
+
loadIfDeferred() {
|
|
4262
|
+
return Promise.resolve();
|
|
4263
|
+
}
|
|
4264
|
+
getEphemeralState() {
|
|
4265
|
+
return this.ephemeralState;
|
|
4266
|
+
}
|
|
4267
|
+
setEphemeralState(state) {
|
|
4268
|
+
this.ephemeralState = state;
|
|
4269
|
+
}
|
|
4270
|
+
togglePinned() {
|
|
4271
|
+
this.setPinned(!this.pinned);
|
|
4272
|
+
}
|
|
4273
|
+
setPinned(pinned) {
|
|
4274
|
+
this.pinned = pinned;
|
|
4275
|
+
this.state = { ...this.state, pinned };
|
|
4276
|
+
this.trigger("pinned-change", pinned);
|
|
4277
|
+
}
|
|
4278
|
+
setGroupMember(other) {
|
|
4279
|
+
if (other.group) {
|
|
4280
|
+
this.setGroup(other.group);
|
|
4281
|
+
}
|
|
4282
|
+
}
|
|
4283
|
+
setGroup(group) {
|
|
4284
|
+
this.group = group;
|
|
4285
|
+
this.trigger("group-change", !!group);
|
|
4286
|
+
}
|
|
4287
|
+
getIcon() {
|
|
4288
|
+
return this.view.getIcon();
|
|
4289
|
+
}
|
|
4290
|
+
getDisplayText() {
|
|
4291
|
+
return this.view.getDisplayText();
|
|
4292
|
+
}
|
|
4293
|
+
getContainer() {
|
|
4294
|
+
return this.app.workspace.rootContainer;
|
|
4295
|
+
}
|
|
4296
|
+
onResize() { }
|
|
4297
|
+
close() {
|
|
4298
|
+
this.parent.removeChild(this);
|
|
4299
|
+
}
|
|
4300
|
+
loadJson(layout) {
|
|
4301
|
+
this.id = layout.id;
|
|
4302
|
+
const liveState = this.view.getState();
|
|
4303
|
+
if (this.view.getViewType() === layout.state.type &&
|
|
4304
|
+
(isEqual(liveState, layout.state.state) ||
|
|
4305
|
+
isPoorerViewState(layout.state.state, liveState))) {
|
|
4306
|
+
this.state = {
|
|
4307
|
+
type: layout.state.type,
|
|
4308
|
+
state: { ...liveState },
|
|
4309
|
+
};
|
|
4310
|
+
return Promise.resolve();
|
|
4311
|
+
}
|
|
4312
|
+
return this.setViewState({
|
|
4313
|
+
type: layout.state.type,
|
|
4314
|
+
state: layout.state.state,
|
|
4315
|
+
}, {
|
|
4316
|
+
history: true,
|
|
4317
|
+
title: layout.state.title,
|
|
4318
|
+
icon: layout.state.icon,
|
|
4319
|
+
});
|
|
4320
|
+
}
|
|
4321
|
+
reload() {
|
|
4322
|
+
this.loadJson(this.toJson());
|
|
4323
|
+
}
|
|
4324
|
+
toJson() {
|
|
4325
|
+
return {
|
|
4326
|
+
id: this.id,
|
|
4327
|
+
type: "leaf",
|
|
4328
|
+
state: {
|
|
4329
|
+
type: this.view.getViewType(),
|
|
4330
|
+
state: this.view.getState(),
|
|
4331
|
+
icon: this.view.getIcon(),
|
|
4332
|
+
title: this.view.getDisplayText(),
|
|
4333
|
+
},
|
|
4334
|
+
};
|
|
4335
|
+
}
|
|
4336
|
+
}
|
|
4337
|
+
export class Notice {
|
|
4338
|
+
id;
|
|
4339
|
+
noticeEl = createDiv();
|
|
4340
|
+
containerEl = this.noticeEl;
|
|
4341
|
+
messageEl = this.noticeEl.createDiv();
|
|
4342
|
+
application;
|
|
4343
|
+
constructor(message, duration, application) {
|
|
4344
|
+
this.application = application ?? getApplicationCompatibility();
|
|
4345
|
+
this.messageEl.setText(message);
|
|
4346
|
+
if (this.application?.notifications) {
|
|
4347
|
+
const record = this.application.notifications.notify({
|
|
4348
|
+
message,
|
|
4349
|
+
id: createNoticeId(),
|
|
4350
|
+
});
|
|
4351
|
+
this.id = record.id;
|
|
4352
|
+
return;
|
|
4353
|
+
}
|
|
4354
|
+
this.id = toast(message, { duration });
|
|
4355
|
+
}
|
|
4356
|
+
setMessage(message) {
|
|
4357
|
+
this.messageEl.setText(message);
|
|
4358
|
+
const id = this.id;
|
|
4359
|
+
if (this.application?.notifications) {
|
|
4360
|
+
this.application.notifications.notify({
|
|
4361
|
+
message,
|
|
4362
|
+
id: String(id),
|
|
4363
|
+
});
|
|
4364
|
+
return;
|
|
4365
|
+
}
|
|
4366
|
+
this.id = toast(message, { id });
|
|
4367
|
+
}
|
|
4368
|
+
hide() {
|
|
4369
|
+
toast.dismiss(this.id);
|
|
4370
|
+
this.noticeEl.detach();
|
|
4371
|
+
}
|
|
4372
|
+
}
|
|
4373
|
+
function createNoticeId() {
|
|
4374
|
+
if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
|
|
4375
|
+
return `notice-${crypto.randomUUID()}`;
|
|
4376
|
+
}
|
|
4377
|
+
return `notice-${Date.now().toString(36)}-${Math.random()
|
|
4378
|
+
.toString(36)
|
|
4379
|
+
.slice(2)}`;
|
|
4380
|
+
}
|