@jetcrab/pi-desk 1.1.18-dev → 1.1.21
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/docs/plugins.md +82 -2
- package/next.config.ts +1 -0
- package/package.json +4 -3
- package/src/app/page.tsx +6 -1
- package/src/app/project-preview/page.tsx +10 -0
- package/src/client/l1_entry/pages/l1-home-page.tsx +30 -0
- package/src/client/l1_entry/pages/l1-project-preview-page.tsx +33 -0
- package/src/client/l1_entry/providers/l1-app-providers.tsx +18 -15
- package/src/client/l1_entry/providers/l1-app-runtime-provider.tsx +2 -2
- package/src/client/l2_biz/auth/l2-login-biz.ts +3 -0
- package/src/client/l2_biz/auth/l2-login.tsx +2 -3
- package/src/client/l2_biz/plugin-host/l2-plugin-capability-details.tsx +6 -1
- package/src/client/l2_biz/plugin-host/l2-plugin-contribution-host.tsx +71 -10
- package/src/client/l2_biz/plugin-host/l2-plugin-management-controls.tsx +9 -0
- package/src/client/l2_biz/plugin-host/l2-plugin-session-header-layout.ts +18 -0
- package/src/client/l2_biz/plugin-host/l2-plugin-session-header.tsx +247 -0
- package/src/client/l2_biz/plugin-host/l2-plugin-widgets.tsx +50 -0
- package/src/client/l2_biz/plugin-host/views/l2-plugin-session-header-action.tsx +112 -0
- package/src/client/l2_biz/project-preview/hooks/l2-use-project-picker.ts +76 -0
- package/src/client/l2_biz/project-preview/hooks/l2-use-project-preview.ts +214 -0
- package/src/client/l2_biz/project-preview/l2-project-preview-biz.ts +49 -4
- package/src/client/l2_biz/project-preview/l2-project-preview-dialog.tsx +42 -450
- package/src/client/l2_biz/project-preview/l2-project-preview-page.tsx +61 -0
- package/src/client/l2_biz/project-preview/l2-project-preview.tsx +342 -0
- package/src/client/l2_biz/project-preview/runtime/l2-project-history-runtime.ts +9 -3
- package/src/client/l2_biz/project-preview/views/l2-project-picker.tsx +168 -0
- package/src/client/l2_biz/settings/hooks/l2-use-model-settings.ts +34 -31
- package/src/client/l2_biz/settings/views/l2-model-replacement-preview.tsx +7 -1
- package/src/client/l2_biz/workbench/file-workspace/l2-file-workspace-runtime.ts +1 -1
- package/src/client/l2_biz/workbench/file-workspace/l2-project-preview-biz.ts +4 -3
- package/src/client/l2_biz/workbench/hooks/l2-workbench-chat-reload.ts +42 -0
- package/src/client/l2_biz/workbench/hooks/l2-workbench-chat-viewport.ts +28 -21
- package/src/client/l2_biz/workbench/l2-work-session-columns.module.css +32 -0
- package/src/client/l2_biz/workbench/l2-work-session-columns.tsx +55 -16
- package/src/client/l2_biz/workbench/l2-work-session-sidebar.tsx +38 -9
- package/src/client/l2_biz/workbench/l2-workbench-chat-composer.tsx +12 -1
- package/src/client/l2_biz/workbench/l2-workbench-chat.ts +45 -0
- package/src/client/l2_biz/workbench/l2-workbench-initialization-notice.tsx +8 -3
- package/src/client/l2_biz/workbench/l2-workbench-plugin-slots.ts +13 -0
- package/src/client/l2_biz/workbench/l2-workbench.tsx +19 -2
- package/src/client/l3_modules/conversation/l3-conversation-markdown.tsx +10 -1
- package/src/client/l3_modules/conversation/l3-conversation-viewport.tsx +12 -6
- package/src/client/l3_modules/project-files/l3-markdown-preview.module.css +1 -1
- package/src/client/l3_modules/project-files/l3-markdown-preview.tsx +10 -1
- package/src/client/l3_modules/project-files/l3-project-document-git.ts +4 -3
- package/src/client/l3_modules/project-files/l3-project-document-view.tsx +6 -1
- package/src/client/l3_modules/project-files/l3-project-file-browser.tsx +171 -229
- package/src/client/l3_modules/project-files/l3-project-file-runtime.ts +133 -70
- package/src/client/l3_modules/project-files/l3-project-reader.ts +26 -37
- package/src/client/l3_modules/project-files/views/l3-project-search-controls.tsx +201 -0
- package/src/client/l3_modules/project-files/views/l3-project-search-results.tsx +333 -0
- package/src/client/l4_foundation/lib/l4-client-id.tsx +11 -0
- package/src/client/l4_foundation/plugin-host/l4-plugin-host-runtime.ts +316 -35
- package/src/client/l4_foundation/ui/code/l4-code-preview.tsx +47 -3
- package/src/client/l4_foundation/ui/markdown/l4-markdown-remark.ts +89 -0
- package/src/client/l4_foundation/ui/markdown/l4-markdown.module.css +58 -0
- package/src/client/l4_foundation/ui/markdown/l4-markdown.tsx +108 -0
- package/src/common/l2_biz/auth/l2-auth-return-to.ts +41 -0
- package/src/common/l2_biz/model-settings/l2-model-settings-contract.ts +5 -1
- package/src/common/l2_biz/plugin/l2-plugin-management-messages.ts +4 -0
- package/src/common/l2_biz/project-preview/l2-project-history-messages.ts +28 -0
- package/src/common/l2_biz/project-preview/l2-project-preview-navigation.ts +5 -0
- package/src/common/l2_biz/settings/l2-settings-messages.ts +4 -3
- package/src/common/l2_biz/work-session/l2-work-session-file-contract.ts +4 -3
- package/src/common/l2_biz/workbench/l2-workbench-locale-messages.ts +8 -0
- package/src/common/l3_modules/project-files/l3-project-files-contract.ts +2 -3
- package/src/common/l3_modules/project-files/l3-project-files-locale-messages.ts +56 -10
- package/src/common/l4_foundation/file/l4-project-file-contract.ts +51 -1
- package/src/common/l4_foundation/locale/l4-host-error-messages.ts +11 -0
- package/src/common/l4_foundation/locale/l4-locale-messages.ts +16 -0
- package/src/instrumentation.ts +0 -2
- package/src/server/l1_entry/api/l1-project-read-routes.ts +6 -4
- package/src/server/l1_entry/api/l1-project-route.ts +9 -8
- package/src/server/l1_entry/api/l1-work-session-file-error.ts +23 -0
- package/src/server/l1_entry/node/l1-server.ts +8 -4
- package/src/server/l2_biz/plugin-management/l2-plugin-management.ts +0 -3
- package/src/server/l2_biz/work-session/l2-work-session-chat-runtime.ts +30 -58
- package/src/server/l2_biz/work-session/l2-work-session-manage.ts +0 -4
- package/src/server/l4_foundation/file/l4-project-file-index.ts +276 -0
- package/src/server/l4_foundation/file/l4-project-search.ts +399 -0
- package/src/server/l4_foundation/file/l4-ripgrep-search.ts +192 -0
- package/src/server/l4_foundation/file/l4-work-session-project-file.ts +8 -332
- package/src/server/l4_foundation/model-settings/l4-model-settings-store.ts +29 -20
- package/src/server/l4_foundation/model-settings/l4-model-settings-types.ts +1 -1
- package/src/server/l4_foundation/pi/l4-pi-chat-worker.ts +22 -5
- package/src/server/l4_foundation/pi/l4-pi-package-maintenance.ts +0 -6
- package/src/server/l4_foundation/pi/l4-pi-plugin-capability-probe.ts +1 -3
- package/src/server/l4_foundation/pi/l4-pi-plugin-owner-runtime.ts +133 -13
- package/src/server/l4_foundation/pi/l4-pi-plugin-probe.ts +1 -3
- package/src/server/l4_foundation/pi/l4-pi-runner-process.ts +2 -2
- package/src/server/l4_foundation/pi/l4-pi-session-initialization.ts +9 -0
- package/src/server/l4_foundation/process/l4-process-tree.js +23 -4
- package/temp/build/pi-desk/release/.next/BUILD_ID +1 -1
- package/temp/build/pi-desk/release/.next/app-path-routes-manifest.json +3 -2
- package/temp/build/pi-desk/release/.next/build-manifest.json +3 -3
- package/temp/build/pi-desk/release/.next/prerender-manifest.json +3 -3
- package/temp/build/pi-desk/release/.next/react-loadable-manifest.json +1 -2
- package/temp/build/pi-desk/release/.next/required-server-files.js +4 -3
- package/temp/build/pi-desk/release/.next/required-server-files.json +4 -3
- package/temp/build/pi-desk/release/.next/routes-manifest.json +6 -0
- package/temp/build/pi-desk/release/.next/server/app/_global-error/page.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/_global-error.html +1 -1
- package/temp/build/pi-desk/release/.next/server/app/_global-error.rsc +3 -3
- package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_full.segment.rsc +3 -3
- package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +3 -3
- package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/temp/build/pi-desk/release/.next/server/app/_not-found/page.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/auth/login/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/auth/logout/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/auth-settings/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/auth-settings/replace/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/capability-modes/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/capability-modes/replace/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/chat/context/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/chat/images/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/chat/messages/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/health/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/mcp-check/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/mcp-settings/del/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/mcp-settings/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/mcp-settings/replace/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/model-catalog/list/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/model-settings/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/model-settings/preview/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/model-settings/replace/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/pi/directories/list/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/pi/directory-entries/list/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/pi/directory-ignores/list/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/pi/directory-ignores/replace/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/pi/models/list/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/pi/session-user-messages/list/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/pi/sessions/list/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/search/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/settings-get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/settings-replace/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugins/add/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugins/apply/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugins/batch/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugins/browser-resources/[resourceGroup]/[...resourcePath]/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugins/del/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugins/enabled-set/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugins/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugins/host-runtime/[runtimeVersion]/[...resourcePath]/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugins/list/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugins/reload/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugins/update/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/project-files/get/route.js +2 -1
- package/temp/build/pi-desk/release/.next/server/app/api/project-files/list/route.js +2 -1
- package/temp/build/pi-desk/release/.next/server/app/api/project-files/reveal/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/project-files/search/route.js +2 -1
- package/temp/build/pi-desk/release/.next/server/app/api/project-git/get/route.js +2 -1
- package/temp/build/pi-desk/release/.next/server/app/api/project-git-branches/list/route.js +2 -1
- package/temp/build/pi-desk/release/.next/server/app/api/project-git-changes/list/route.js +2 -1
- package/temp/build/pi-desk/release/.next/server/app/api/project-git-diff/get/route.js +2 -1
- package/temp/build/pi-desk/release/.next/server/app/api/project-git-heads/get/route.js +2 -1
- package/temp/build/pi-desk/release/.next/server/app/api/project-git-log/list/route.js +2 -1
- package/temp/build/pi-desk/release/.next/server/app/api/project-model-default/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/project-model-default/replace/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/skill-files/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/skill-files/list/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/skill-files/replace/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/skills/list/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/task-center/images/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/task-center/messages/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/work-session-git-branch/add/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/work-session-git-branch/replace/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/work-session-tree/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/work-session-tree-entry/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/add/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/branch/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/del/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/rename/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/replace/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/sort/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/favicon.ico/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/login/page.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/page.js +72 -72
- package/temp/build/pi-desk/release/.next/server/app/page_client-reference-manifest.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/project-preview/page.js +1 -0
- package/temp/build/pi-desk/release/.next/server/app/project-preview/page_client-reference-manifest.js +1 -0
- package/temp/build/pi-desk/release/.next/server/app-paths-manifest.json +3 -2
- package/temp/build/pi-desk/release/.next/server/chunks/10912.js +2 -2
- package/temp/build/pi-desk/release/.next/server/chunks/20460.js +15 -0
- package/temp/build/pi-desk/release/.next/server/chunks/23054.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/25406.js +10 -10
- package/temp/build/pi-desk/release/.next/server/chunks/35985.js +3 -17
- package/temp/build/pi-desk/release/.next/server/chunks/36361.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/48180.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/50520.js +2 -4
- package/temp/build/pi-desk/release/.next/server/chunks/56830.js +235 -0
- package/temp/build/pi-desk/release/.next/server/chunks/6917.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/70780.js +1 -0
- package/temp/build/pi-desk/release/.next/server/chunks/74528.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/78262.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/79686.js +62 -0
- package/temp/build/pi-desk/release/.next/server/chunks/80780.js +1 -0
- package/temp/build/pi-desk/release/.next/server/chunks/81363.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/82499.js +4 -4
- package/temp/build/pi-desk/release/.next/server/chunks/92386.js +8 -8
- package/temp/build/pi-desk/release/.next/server/instrumentation.js +1 -1
- package/temp/build/pi-desk/release/.next/server/middleware-build-manifest.js +1 -1
- package/temp/build/pi-desk/release/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/temp/build/pi-desk/release/.next/server/pages/500.html +1 -1
- package/temp/build/pi-desk/release/.next/server/server-reference-manifest.json +1 -1
- package/temp/build/pi-desk/release/.next/static/8Yd33M0zPi4tbRn4xCaRY/_buildManifest.js +1 -0
- package/temp/build/pi-desk/release/.next/static/chunks/11937-1d0a9822f2c7995b.js +1 -0
- package/temp/build/pi-desk/release/.next/static/chunks/14825-5579aed34a631ad7.js +62 -0
- package/temp/build/pi-desk/release/.next/static/chunks/15302-9c807afc6c60de0c.js +1 -0
- package/temp/build/pi-desk/release/.next/static/chunks/19991.8aa2e4b6df15fad6.js +1 -0
- package/temp/build/pi-desk/release/.next/static/chunks/22092-6d1bfbefa825ec03.js +1 -0
- package/temp/build/pi-desk/release/.next/static/chunks/37313-d5970e2d0c270d22.js +238 -0
- package/temp/build/pi-desk/release/.next/static/chunks/{49516-cc4dcd84d64ab4fe.js → 51474-8dc714cb76b2e60a.js} +2 -2
- package/temp/build/pi-desk/release/.next/static/chunks/57235-03ba1044f22b6c84.js +1 -0
- package/temp/build/pi-desk/release/.next/static/chunks/79267-7d21d7cdbd7cfca1.js +1 -0
- package/temp/build/pi-desk/release/.next/static/chunks/82371-f7ba4a07a5dbcf34.js +1 -0
- package/temp/build/pi-desk/release/.next/static/chunks/9038-03144ed96d613f75.js +15 -0
- package/temp/build/pi-desk/release/.next/static/chunks/app/{error-0296a3b20cd1bf00.js → error-216b14b5da089ed3.js} +1 -1
- package/temp/build/pi-desk/release/.next/static/chunks/app/layout-20718f1503910291.js +1 -0
- package/temp/build/pi-desk/release/.next/static/chunks/app/login/page-0d3d4827802108e5.js +1 -0
- package/temp/build/pi-desk/release/.next/static/chunks/app/{not-found-2aab8b33ebbf3257.js → not-found-720cdf6a606b821a.js} +1 -1
- package/temp/build/pi-desk/release/.next/static/chunks/app/page-6ebc1c877f6025dd.js +158 -0
- package/temp/build/pi-desk/release/.next/static/chunks/app/project-preview/page-d70ee4e9409b970a.js +1 -0
- package/temp/build/pi-desk/release/.next/static/chunks/webpack-b60891a214e0925d.js +1 -0
- package/temp/build/pi-desk/release/.next/static/css/1e780a4936aa6ba3.css +1 -0
- package/temp/build/pi-desk/release/.next/static/css/6dbae2922f88ca31.css +1 -0
- package/temp/build/pi-desk/release/.next/static/css/f646ca6c07245c4c.css +1 -0
- package/temp/build/pi-desk/release/.next/trace-build +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/33658.js +0 -296
- package/temp/build/pi-desk/release/.next/server/chunks/8584.js +0 -2
- package/temp/build/pi-desk/release/.next/static/chunks/31087-ba426d09d6f68bfd.js +0 -1
- package/temp/build/pi-desk/release/.next/static/chunks/3814-35cfab0ce3aa7c41.js +0 -1
- package/temp/build/pi-desk/release/.next/static/chunks/39654-b5fedd762ea0312e.js +0 -238
- package/temp/build/pi-desk/release/.next/static/chunks/70669.07f3670bfb7d6dd2.js +0 -1
- package/temp/build/pi-desk/release/.next/static/chunks/74220-795a2e7185bd66cb.js +0 -1
- package/temp/build/pi-desk/release/.next/static/chunks/75130-d2ca74894aff8085.js +0 -13
- package/temp/build/pi-desk/release/.next/static/chunks/78923-17a9c337b29dcecd.js +0 -1
- package/temp/build/pi-desk/release/.next/static/chunks/81881-f11dbe7dc0d04ed9.js +0 -1
- package/temp/build/pi-desk/release/.next/static/chunks/96995-8f8313ea2ac3abd7.js +0 -62
- package/temp/build/pi-desk/release/.next/static/chunks/app/layout-c0e1c5cef8620cf8.js +0 -3
- package/temp/build/pi-desk/release/.next/static/chunks/app/login/page-84892b06243891f0.js +0 -1
- package/temp/build/pi-desk/release/.next/static/chunks/app/page-a984139e99586041.js +0 -158
- package/temp/build/pi-desk/release/.next/static/chunks/webpack-40a782fc8a3535b9.js +0 -1
- package/temp/build/pi-desk/release/.next/static/css/6427d02b6edf406f.css +0 -1
- package/temp/build/pi-desk/release/.next/static/css/79e06f05df9edff0.css +0 -1
- package/temp/build/pi-desk/release/.next/static/n2LrXQgq8UkbH_AzhR3fw/_buildManifest.js +0 -1
- /package/temp/build/pi-desk/release/.next/static/{n2LrXQgq8UkbH_AzhR3fw → 8Yd33M0zPi4tbRn4xCaRY}/_ssgManifest.js +0 -0
|
@@ -27,6 +27,13 @@ import {
|
|
|
27
27
|
type BrowserPluginGlobalStateHost,
|
|
28
28
|
type BrowserPluginHost,
|
|
29
29
|
type BrowserPluginNotificationEventListener,
|
|
30
|
+
type BrowserSessionHeaderAction,
|
|
31
|
+
type BrowserSessionHeaderActionContext,
|
|
32
|
+
type BrowserSessionHeaderCustom,
|
|
33
|
+
type BrowserSessionHeaderItemDefinition,
|
|
34
|
+
type BrowserSessionHeaderItemTarget,
|
|
35
|
+
type BrowserWidgetDefinition,
|
|
36
|
+
type BrowserWidgetTarget,
|
|
30
37
|
type BrowserSessionSidebarHeaderAction,
|
|
31
38
|
type BrowserSessionSidebarTabContributionDefinition,
|
|
32
39
|
type BrowserSessionSidebarTabTarget,
|
|
@@ -83,7 +90,7 @@ const BUILTIN_ICON_NAMES = new Set([
|
|
|
83
90
|
'database',
|
|
84
91
|
'globe'
|
|
85
92
|
])
|
|
86
|
-
const CONTRIBUTION_KINDS = new Set<
|
|
93
|
+
const CONTRIBUTION_KINDS = new Set<BrowserContributionKind>([
|
|
87
94
|
'application',
|
|
88
95
|
'composer-panel',
|
|
89
96
|
'settings-page',
|
|
@@ -141,7 +148,7 @@ export interface L4PluginHostTransport {
|
|
|
141
148
|
}
|
|
142
149
|
|
|
143
150
|
export type L4PluginNotificationSink = (pluginName: string, input: BrowserNotificationInput) => void
|
|
144
|
-
export type L4PluginContributionKind =
|
|
151
|
+
export type L4PluginContributionKind = BrowserContributionDescriptor['kind']
|
|
145
152
|
|
|
146
153
|
export interface L4PluginHostWorkSessionInput {
|
|
147
154
|
workId: string
|
|
@@ -198,7 +205,9 @@ interface L4PluginContributionRegistration {
|
|
|
198
205
|
kind: L4PluginContributionKind
|
|
199
206
|
contributionName: string
|
|
200
207
|
descriptor: L4PluginBrowserContributionDescriptor
|
|
201
|
-
load
|
|
208
|
+
load?: () => Promise<BrowserContributionImplementation>
|
|
209
|
+
header?: BrowserSessionHeaderItemDefinition
|
|
210
|
+
headerActionScopes?: WeakMap<AbortSignal, BrowserSessionHeaderActionContext>
|
|
202
211
|
resolve?: BrowserApplicationContributionDefinition['resolve']
|
|
203
212
|
owner: L4PluginEntryOwner
|
|
204
213
|
}
|
|
@@ -233,6 +242,8 @@ export type L4PluginContributionTarget =
|
|
|
233
242
|
| BrowserSettingsPageTarget
|
|
234
243
|
| BrowserMessageViewTarget
|
|
235
244
|
| BrowserSessionSidebarTabTarget
|
|
245
|
+
| BrowserWidgetTarget
|
|
246
|
+
| BrowserSessionHeaderItemTarget
|
|
236
247
|
|
|
237
248
|
export interface L4PluginContributionMountInput {
|
|
238
249
|
pluginName: string
|
|
@@ -255,6 +266,20 @@ function pluginContributionKey(
|
|
|
255
266
|
return `${pluginName}\u0000${contributionKey(kind, contributionName)}`
|
|
256
267
|
}
|
|
257
268
|
|
|
269
|
+
function contributionLoadKey(
|
|
270
|
+
pluginName: string,
|
|
271
|
+
kind: L4PluginContributionKind,
|
|
272
|
+
contributionName: string,
|
|
273
|
+
presentation?: BrowserSessionHeaderItemTarget['presentation']
|
|
274
|
+
): string {
|
|
275
|
+
const key = pluginContributionKey(pluginName, kind, contributionName)
|
|
276
|
+
if (kind !== 'session-header-item') return key
|
|
277
|
+
if (presentation !== 'inline' && presentation !== 'overflow') {
|
|
278
|
+
throw new Error('会话标题视图必须指定 inline 或 overflow')
|
|
279
|
+
}
|
|
280
|
+
return `${key}\u0000${presentation}`
|
|
281
|
+
}
|
|
282
|
+
|
|
258
283
|
function defaultEntryImporter(url: string): Promise<L4PluginEntryNamespace> {
|
|
259
284
|
return import(/* webpackIgnore: true */ url) as Promise<L4PluginEntryNamespace>
|
|
260
285
|
}
|
|
@@ -369,6 +394,54 @@ function cloneIcon(value: unknown): BrowserContributionIcon | undefined {
|
|
|
369
394
|
throw new Error('Contribution icon type 无效')
|
|
370
395
|
}
|
|
371
396
|
|
|
397
|
+
function parseHeaderPresentation(
|
|
398
|
+
value: unknown,
|
|
399
|
+
presentation: BrowserSessionHeaderItemTarget['presentation']
|
|
400
|
+
): BrowserSessionHeaderAction | BrowserSessionHeaderCustom {
|
|
401
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
402
|
+
throw new Error(`会话标题 ${presentation} 必须提供 action 或 custom 定义`)
|
|
403
|
+
}
|
|
404
|
+
const input = value as Record<string, unknown>
|
|
405
|
+
const allowed =
|
|
406
|
+
input.type === 'action'
|
|
407
|
+
? ['type', 'icon', 'label', 'onSelect']
|
|
408
|
+
: presentation === 'inline'
|
|
409
|
+
? ['type', 'load', 'minWidthRem']
|
|
410
|
+
: ['type', 'load']
|
|
411
|
+
for (const key of Object.keys(input)) {
|
|
412
|
+
if (!allowed.includes(key)) {
|
|
413
|
+
throw new Error(`会话标题 ${presentation} ${String(input.type)} 不支持字段 ${key}`)
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if (input.type === 'action') {
|
|
417
|
+
if (typeof input.label !== 'string') throw new Error('会话标题 action label 必须是字符串')
|
|
418
|
+
const icon = cloneIcon(input.icon)
|
|
419
|
+
if (!icon) throw new Error('会话标题 action 必须提供 icon')
|
|
420
|
+
if (typeof input.onSelect !== 'function') throw new Error('会话标题 action 必须提供 onSelect()')
|
|
421
|
+
return {
|
|
422
|
+
type: 'action',
|
|
423
|
+
label: parseLabel(input.label, 'Session Header action label'),
|
|
424
|
+
icon,
|
|
425
|
+
onSelect: input.onSelect as BrowserSessionHeaderAction['onSelect']
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if (input.type !== 'custom') throw new Error('会话标题表现 type 必须是 action 或 custom')
|
|
429
|
+
if (typeof input.load !== 'function') throw new Error('会话标题 custom 必须提供 load()')
|
|
430
|
+
if (
|
|
431
|
+
presentation === 'inline' &&
|
|
432
|
+
(typeof input.minWidthRem !== 'number' ||
|
|
433
|
+
!Number.isFinite(input.minWidthRem) ||
|
|
434
|
+
input.minWidthRem <= 0)
|
|
435
|
+
) {
|
|
436
|
+
throw new Error('会话标题 inline custom minWidthRem 必须是正数')
|
|
437
|
+
}
|
|
438
|
+
return {
|
|
439
|
+
type: 'custom',
|
|
440
|
+
load: input.load as BrowserSessionHeaderCustom['load'],
|
|
441
|
+
...(presentation === 'inline' ? { minWidthRem: input.minWidthRem } : {})
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
372
445
|
function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
|
|
373
446
|
return (
|
|
374
447
|
typeof value === 'object' && value !== null && typeof Reflect.get(value, 'then') === 'function'
|
|
@@ -509,11 +582,12 @@ export class L4PluginHostRuntime {
|
|
|
509
582
|
getContributionLoadState(
|
|
510
583
|
pluginName: string,
|
|
511
584
|
kind: L4PluginContributionKind,
|
|
512
|
-
contributionName: string
|
|
585
|
+
contributionName: string,
|
|
586
|
+
presentation?: BrowserSessionHeaderItemTarget['presentation']
|
|
513
587
|
): BrowserContributionLoadState {
|
|
514
588
|
return (
|
|
515
589
|
this.contributionLoadStates.get(
|
|
516
|
-
|
|
590
|
+
contributionLoadKey(pluginName, kind, contributionName, presentation)
|
|
517
591
|
) ?? { status: 'unloaded' }
|
|
518
592
|
)
|
|
519
593
|
}
|
|
@@ -724,11 +798,13 @@ export class L4PluginHostRuntime {
|
|
|
724
798
|
retryContribution(
|
|
725
799
|
pluginName: string,
|
|
726
800
|
kind: L4PluginContributionKind,
|
|
727
|
-
contributionName: string
|
|
801
|
+
contributionName: string,
|
|
802
|
+
presentation?: BrowserSessionHeaderItemTarget['presentation']
|
|
728
803
|
): Promise<BrowserContributionImplementation> {
|
|
729
804
|
const key = pluginContributionKey(pluginName, kind, contributionName)
|
|
730
|
-
|
|
731
|
-
|
|
805
|
+
const loadKey = contributionLoadKey(pluginName, kind, contributionName, presentation)
|
|
806
|
+
if (this.contributionLoadStates.get(loadKey)?.status === 'failed') {
|
|
807
|
+
this.contributionLoadStates.delete(loadKey)
|
|
732
808
|
this.publishRegistryChange()
|
|
733
809
|
}
|
|
734
810
|
const registration = this.registrations.get(key)
|
|
@@ -742,7 +818,100 @@ export class L4PluginHostRuntime {
|
|
|
742
818
|
})
|
|
743
819
|
)
|
|
744
820
|
}
|
|
745
|
-
return this.loadContribution(registration)
|
|
821
|
+
return this.loadContribution(registration, presentation)
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
async invokeSessionHeaderAction(input: {
|
|
825
|
+
pluginName: string
|
|
826
|
+
contributionName: string
|
|
827
|
+
presentation: BrowserSessionHeaderItemTarget['presentation']
|
|
828
|
+
source: PluginSource
|
|
829
|
+
signal: AbortSignal
|
|
830
|
+
}): Promise<void> {
|
|
831
|
+
const key = pluginContributionKey(
|
|
832
|
+
input.pluginName,
|
|
833
|
+
'session-header-item',
|
|
834
|
+
input.contributionName
|
|
835
|
+
)
|
|
836
|
+
const registration = this.registrations.get(key)
|
|
837
|
+
const action = registration?.header?.[input.presentation]
|
|
838
|
+
if (!registration || action?.type !== 'action') {
|
|
839
|
+
throw new Error('会话标题 action 不存在')
|
|
840
|
+
}
|
|
841
|
+
input.signal.throwIfAborted()
|
|
842
|
+
if (!registration.owner.active) throw new Error('Browser Entry 已失效')
|
|
843
|
+
if (!this.workSessions.some((item) => sameSource(item.source, input.source))) {
|
|
844
|
+
throw new Error('工作会话来源已变化')
|
|
845
|
+
}
|
|
846
|
+
const scope = this.getHeaderActionScope(registration, input.source, input.signal)
|
|
847
|
+
try {
|
|
848
|
+
await action.onSelect({ ...scope })
|
|
849
|
+
scope.signal.throwIfAborted()
|
|
850
|
+
if (!registration.owner.active || this.registrations.get(key) !== registration) {
|
|
851
|
+
throw new Error('Browser Entry 已失效')
|
|
852
|
+
}
|
|
853
|
+
} catch (cause) {
|
|
854
|
+
const error = new BrowserPluginError({
|
|
855
|
+
pluginName: input.pluginName,
|
|
856
|
+
contributionName: input.contributionName,
|
|
857
|
+
phase: 'session-header-action',
|
|
858
|
+
message: cause instanceof Error ? cause.message : String(cause),
|
|
859
|
+
cause
|
|
860
|
+
})
|
|
861
|
+
if (!scope.signal.aborted) {
|
|
862
|
+
runPluginCallback(
|
|
863
|
+
() => {
|
|
864
|
+
throw error
|
|
865
|
+
},
|
|
866
|
+
`header-action:${input.contributionName}:${input.presentation}`,
|
|
867
|
+
input.pluginName
|
|
868
|
+
)
|
|
869
|
+
}
|
|
870
|
+
throw error
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
private getHeaderActionScope(
|
|
875
|
+
registration: L4PluginContributionRegistration,
|
|
876
|
+
sourceInput: PluginSource,
|
|
877
|
+
signal: AbortSignal
|
|
878
|
+
): BrowserSessionHeaderActionContext {
|
|
879
|
+
const scopes = (registration.headerActionScopes ??= new WeakMap())
|
|
880
|
+
const cached = scopes.get(signal)
|
|
881
|
+
if (cached) {
|
|
882
|
+
if (!sameSource(cached.source, sourceInput)) throw new Error('会话标题展示来源不一致')
|
|
883
|
+
return cached
|
|
884
|
+
}
|
|
885
|
+
const owner = registration.owner
|
|
886
|
+
const source = freezeSnapshot(structuredClone(sourceInput))
|
|
887
|
+
const controller = new AbortController()
|
|
888
|
+
const actionOwner = {
|
|
889
|
+
...owner,
|
|
890
|
+
subscriptionDisposers: new Set<() => void>(),
|
|
891
|
+
get active(): boolean {
|
|
892
|
+
return owner.active && !controller.signal.aborted
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
const dispose = (): void => {
|
|
896
|
+
controller.abort()
|
|
897
|
+
scopes.delete(signal)
|
|
898
|
+
this.workSessionListeners.delete(checkSource)
|
|
899
|
+
owner.subscriptionDisposers.delete(dispose)
|
|
900
|
+
signal.removeEventListener('abort', dispose)
|
|
901
|
+
for (const unsubscribe of actionOwner.subscriptionDisposers) {
|
|
902
|
+
runPluginCallback(unsubscribe, 'header-action-dispose', registration.pluginName)
|
|
903
|
+
}
|
|
904
|
+
actionOwner.subscriptionDisposers.clear()
|
|
905
|
+
}
|
|
906
|
+
const checkSource = (): void => {
|
|
907
|
+
if (!this.workSessions.some((item) => sameSource(item.source, source))) dispose()
|
|
908
|
+
}
|
|
909
|
+
const scope = { source, host: this.createBrowserHost(actionOwner), signal: controller.signal }
|
|
910
|
+
scopes.set(signal, scope)
|
|
911
|
+
this.workSessionListeners.add(checkSource)
|
|
912
|
+
owner.subscriptionDisposers.add(dispose)
|
|
913
|
+
signal.addEventListener('abort', dispose, { once: true })
|
|
914
|
+
return scope
|
|
746
915
|
}
|
|
747
916
|
|
|
748
917
|
async mountContribution(input: L4PluginContributionMountInput): Promise<PluginDisposer> {
|
|
@@ -756,11 +925,23 @@ export class L4PluginHostRuntime {
|
|
|
756
925
|
message: '插件 Contribution 不存在'
|
|
757
926
|
})
|
|
758
927
|
}
|
|
928
|
+
if (registration.kind !== input.target.kind) {
|
|
929
|
+
throw new Error('Contribution kind 与 mount target 不一致')
|
|
930
|
+
}
|
|
759
931
|
const controller = new AbortController()
|
|
760
932
|
const cancelled = new Promise<void>((resolve) => {
|
|
761
933
|
controller.signal.addEventListener('abort', () => resolve(), { once: true })
|
|
762
934
|
})
|
|
763
935
|
let target = input.target
|
|
936
|
+
if (target.kind === 'widget' || target.kind === 'session-header-item') {
|
|
937
|
+
target = { ...target, source: freezeSnapshot(structuredClone(target.source)) }
|
|
938
|
+
}
|
|
939
|
+
const source =
|
|
940
|
+
target.kind === 'widget' || target.kind === 'session-header-item' ? target.source : null
|
|
941
|
+
const presentation = target.kind === 'session-header-item' ? target.presentation : undefined
|
|
942
|
+
const checkSource = (): void => {
|
|
943
|
+
if (source && !this.workSessions.some((item) => sameSource(item.source, source))) cancel()
|
|
944
|
+
}
|
|
764
945
|
if (target.kind === 'session-sidebar-tab' && target.setHeaderActions) {
|
|
765
946
|
const publishActions = target.setHeaderActions
|
|
766
947
|
let currentActions: readonly BrowserSessionSidebarHeaderAction[] = []
|
|
@@ -831,7 +1012,7 @@ export class L4PluginHostRuntime {
|
|
|
831
1012
|
}
|
|
832
1013
|
const mounting = Promise.resolve().then(async () => {
|
|
833
1014
|
if (controller.signal.aborted) throw new Error('插件视图已关闭')
|
|
834
|
-
const implementation = await this.loadContribution(registration)
|
|
1015
|
+
const implementation = await this.loadContribution(registration, presentation)
|
|
835
1016
|
if (controller.signal.aborted || !registration.owner.active) {
|
|
836
1017
|
throw new Error('插件视图已失效')
|
|
837
1018
|
}
|
|
@@ -853,6 +1034,7 @@ export class L4PluginHostRuntime {
|
|
|
853
1034
|
if (disposal) return disposal
|
|
854
1035
|
registration.owner.mounts.delete(dispose)
|
|
855
1036
|
input.signal?.removeEventListener('abort', cancel)
|
|
1037
|
+
this.workSessionListeners.delete(checkSource)
|
|
856
1038
|
controller.abort()
|
|
857
1039
|
// 先取消,再有界等待;迟到的 mount 仍由同一次清理释放。
|
|
858
1040
|
disposal = disposePluginResource(
|
|
@@ -869,8 +1051,10 @@ export class L4PluginHostRuntime {
|
|
|
869
1051
|
void dispose()
|
|
870
1052
|
}
|
|
871
1053
|
registration.owner.mounts.add(dispose)
|
|
1054
|
+
if (source) this.workSessionListeners.add(checkSource)
|
|
872
1055
|
input.signal?.addEventListener('abort', cancel, { once: true })
|
|
873
1056
|
if (input.signal?.aborted) cancel()
|
|
1057
|
+
checkSource()
|
|
874
1058
|
try {
|
|
875
1059
|
await Promise.race([mounting, cancelled])
|
|
876
1060
|
if (controller.signal.aborted || !registration.owner.active) {
|
|
@@ -1096,6 +1280,19 @@ export class L4PluginHostRuntime {
|
|
|
1096
1280
|
}
|
|
1097
1281
|
|
|
1098
1282
|
private createBrowserFacade(pluginName: string, owner: L4PluginEntryOwner): BrowserPluginFacade {
|
|
1283
|
+
const register = (create: () => L4PluginContributionRegistration): void => {
|
|
1284
|
+
if (!owner.active || !owner.registrationOpen) {
|
|
1285
|
+
throw new Error('Browser Entry registration phase has ended')
|
|
1286
|
+
}
|
|
1287
|
+
const registration = create()
|
|
1288
|
+
const key = contributionKey(registration.kind, registration.contributionName)
|
|
1289
|
+
if (owner.registrations.has(key)) {
|
|
1290
|
+
throw new Error(
|
|
1291
|
+
`Browser Contribution 重复:${pluginName}/${registration.kind}/${registration.contributionName}`
|
|
1292
|
+
)
|
|
1293
|
+
}
|
|
1294
|
+
owner.registrations.set(key, registration)
|
|
1295
|
+
}
|
|
1099
1296
|
return {
|
|
1100
1297
|
name: pluginName,
|
|
1101
1298
|
host: owner.host,
|
|
@@ -1119,26 +1316,22 @@ export class L4PluginHostRuntime {
|
|
|
1119
1316
|
this.notifyPlugin(pluginName, structuredClone(input))
|
|
1120
1317
|
},
|
|
1121
1318
|
registerContribution: (kind, contributionName, definition) => {
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
contributionName,
|
|
1132
|
-
|
|
1133
|
-
|
|
1319
|
+
register(() => {
|
|
1320
|
+
if (!CONTRIBUTION_KINDS.has(kind)) {
|
|
1321
|
+
throw new Error(`Browser Contribution kind 无效:${String(kind)}`)
|
|
1322
|
+
}
|
|
1323
|
+
return this.createRegistration(pluginName, kind, contributionName, definition, owner)
|
|
1324
|
+
})
|
|
1325
|
+
},
|
|
1326
|
+
registerWidget: (contributionName, definition) => {
|
|
1327
|
+
register(() =>
|
|
1328
|
+
this.createWidgetRegistration(pluginName, contributionName, definition, owner)
|
|
1329
|
+
)
|
|
1330
|
+
},
|
|
1331
|
+
registerSessionHeaderItem: (contributionName, definition) => {
|
|
1332
|
+
register(() =>
|
|
1333
|
+
this.createHeaderRegistration(pluginName, contributionName, definition, owner)
|
|
1134
1334
|
)
|
|
1135
|
-
const key = contributionKey(kind, registration.contributionName)
|
|
1136
|
-
if (owner.registrations.has(key)) {
|
|
1137
|
-
throw new Error(
|
|
1138
|
-
`Browser Contribution 重复:${pluginName}/${kind}/${registration.contributionName}`
|
|
1139
|
-
)
|
|
1140
|
-
}
|
|
1141
|
-
owner.registrations.set(key, registration)
|
|
1142
1335
|
},
|
|
1143
1336
|
onPush: (listener) => {
|
|
1144
1337
|
if (!owner.active) throw new Error('Browser Entry owner is no longer active')
|
|
@@ -1150,7 +1343,7 @@ export class L4PluginHostRuntime {
|
|
|
1150
1343
|
}
|
|
1151
1344
|
}
|
|
1152
1345
|
|
|
1153
|
-
private createRegistration<K extends
|
|
1346
|
+
private createRegistration<K extends BrowserContributionKind>(
|
|
1154
1347
|
pluginName: string,
|
|
1155
1348
|
kind: K,
|
|
1156
1349
|
contributionNameInput: string,
|
|
@@ -1258,6 +1451,74 @@ export class L4PluginHostRuntime {
|
|
|
1258
1451
|
}
|
|
1259
1452
|
}
|
|
1260
1453
|
|
|
1454
|
+
private createWidgetRegistration(
|
|
1455
|
+
pluginName: string,
|
|
1456
|
+
contributionNameInput: string,
|
|
1457
|
+
definition: BrowserWidgetDefinition,
|
|
1458
|
+
owner: L4PluginEntryOwner
|
|
1459
|
+
): L4PluginContributionRegistration {
|
|
1460
|
+
const contributionName = parseContributionName(contributionNameInput)
|
|
1461
|
+
if (!definition || typeof definition !== 'object' || typeof definition.load !== 'function') {
|
|
1462
|
+
throw new Error('Browser Widget 必须提供 load()')
|
|
1463
|
+
}
|
|
1464
|
+
const placement = definition.placement ?? 'aboveEditor'
|
|
1465
|
+
if (placement !== 'aboveEditor' && placement !== 'belowEditor') {
|
|
1466
|
+
throw new Error('Browser Widget placement 必须是 aboveEditor 或 belowEditor')
|
|
1467
|
+
}
|
|
1468
|
+
return {
|
|
1469
|
+
pluginName,
|
|
1470
|
+
kind: 'widget',
|
|
1471
|
+
contributionName,
|
|
1472
|
+
descriptor: { kind: 'widget', contributionName, placement },
|
|
1473
|
+
load: definition.load,
|
|
1474
|
+
owner
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
private createHeaderRegistration(
|
|
1479
|
+
pluginName: string,
|
|
1480
|
+
contributionNameInput: string,
|
|
1481
|
+
definition: BrowserSessionHeaderItemDefinition,
|
|
1482
|
+
owner: L4PluginEntryOwner
|
|
1483
|
+
): L4PluginContributionRegistration {
|
|
1484
|
+
const contributionName = parseContributionName(contributionNameInput)
|
|
1485
|
+
if (!definition || typeof definition !== 'object' || Array.isArray(definition)) {
|
|
1486
|
+
throw new Error('会话标题条目必须提供 inline 和 overflow')
|
|
1487
|
+
}
|
|
1488
|
+
for (const key of Object.keys(definition)) {
|
|
1489
|
+
if (key !== 'inline' && key !== 'overflow') {
|
|
1490
|
+
throw new Error(`会话标题条目不支持顶层字段 ${key}`)
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
const inline = parseHeaderPresentation(
|
|
1494
|
+
definition.inline,
|
|
1495
|
+
'inline'
|
|
1496
|
+
) as BrowserSessionHeaderItemDefinition['inline']
|
|
1497
|
+
const overflow = parseHeaderPresentation(
|
|
1498
|
+
definition.overflow,
|
|
1499
|
+
'overflow'
|
|
1500
|
+
) as BrowserSessionHeaderItemDefinition['overflow']
|
|
1501
|
+
return {
|
|
1502
|
+
pluginName,
|
|
1503
|
+
kind: 'session-header-item',
|
|
1504
|
+
contributionName,
|
|
1505
|
+
descriptor: {
|
|
1506
|
+
kind: 'session-header-item',
|
|
1507
|
+
contributionName,
|
|
1508
|
+
inline:
|
|
1509
|
+
inline.type === 'action'
|
|
1510
|
+
? { type: 'action', icon: inline.icon, label: inline.label }
|
|
1511
|
+
: { type: 'custom', minWidthRem: inline.minWidthRem },
|
|
1512
|
+
overflow:
|
|
1513
|
+
overflow.type === 'action'
|
|
1514
|
+
? { type: 'action', icon: overflow.icon, label: overflow.label }
|
|
1515
|
+
: { type: 'custom' }
|
|
1516
|
+
},
|
|
1517
|
+
header: { inline, overflow },
|
|
1518
|
+
owner
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1261
1522
|
private commitRegistrations(owner: L4PluginEntryOwner): void {
|
|
1262
1523
|
for (const registration of owner.registrations.values()) {
|
|
1263
1524
|
const key = pluginContributionKey(
|
|
@@ -1273,26 +1534,42 @@ export class L4PluginHostRuntime {
|
|
|
1273
1534
|
}
|
|
1274
1535
|
|
|
1275
1536
|
private loadContribution(
|
|
1276
|
-
registration: L4PluginContributionRegistration
|
|
1537
|
+
registration: L4PluginContributionRegistration,
|
|
1538
|
+
presentation?: BrowserSessionHeaderItemTarget['presentation']
|
|
1277
1539
|
): Promise<BrowserContributionImplementation> {
|
|
1278
|
-
const
|
|
1540
|
+
const registrationKey = pluginContributionKey(
|
|
1279
1541
|
registration.pluginName,
|
|
1280
1542
|
registration.kind,
|
|
1281
1543
|
registration.contributionName
|
|
1282
1544
|
)
|
|
1545
|
+
const key = contributionLoadKey(
|
|
1546
|
+
registration.pluginName,
|
|
1547
|
+
registration.kind,
|
|
1548
|
+
registration.contributionName,
|
|
1549
|
+
presentation
|
|
1550
|
+
)
|
|
1551
|
+
const header = presentation ? registration.header?.[presentation] : undefined
|
|
1552
|
+
const load = header?.type === 'custom' ? header.load : registration.load
|
|
1553
|
+
if (!load) return Promise.reject(new Error('会话标题 action 不支持加载视图'))
|
|
1554
|
+
if (!registration.owner.active || this.registrations.get(registrationKey) !== registration) {
|
|
1555
|
+
return Promise.reject(new Error('Browser Contribution 已失效'))
|
|
1556
|
+
}
|
|
1283
1557
|
const state = this.contributionLoadStates.get(key)
|
|
1284
1558
|
if (state?.status === 'ready') return Promise.resolve(state.implementation)
|
|
1285
1559
|
if (state?.status === 'loading') return state.promise
|
|
1286
1560
|
if (state?.status === 'failed') return Promise.reject(state.error)
|
|
1287
1561
|
|
|
1288
1562
|
const promise = Promise.resolve()
|
|
1289
|
-
.then(
|
|
1563
|
+
.then(load)
|
|
1290
1564
|
.then(
|
|
1291
1565
|
(implementation) => {
|
|
1292
1566
|
if (!implementation || typeof implementation.mount !== 'function') {
|
|
1293
1567
|
throw new Error('Lazy Contribution Module 必须导出 mount()')
|
|
1294
1568
|
}
|
|
1295
|
-
if (
|
|
1569
|
+
if (
|
|
1570
|
+
!registration.owner.active ||
|
|
1571
|
+
this.registrations.get(registrationKey) !== registration
|
|
1572
|
+
) {
|
|
1296
1573
|
throw new Error('Browser Contribution 已失效')
|
|
1297
1574
|
}
|
|
1298
1575
|
this.contributionLoadStates.set(key, { status: 'ready', implementation })
|
|
@@ -1313,7 +1590,7 @@ export class L4PluginHostRuntime {
|
|
|
1313
1590
|
phase: 'contribution-load',
|
|
1314
1591
|
message: String(cause)
|
|
1315
1592
|
})
|
|
1316
|
-
if (registration.owner.active && this.registrations.get(
|
|
1593
|
+
if (registration.owner.active && this.registrations.get(registrationKey) === registration) {
|
|
1317
1594
|
this.contributionLoadStates.set(key, { status: 'failed', error })
|
|
1318
1595
|
this.publishRegistryChange()
|
|
1319
1596
|
}
|
|
@@ -1490,6 +1767,10 @@ export class L4PluginHostRuntime {
|
|
|
1490
1767
|
if (this.registrations.get(key) !== registration) continue
|
|
1491
1768
|
this.registrations.delete(key)
|
|
1492
1769
|
this.contributionLoadStates.delete(key)
|
|
1770
|
+
if (registration.kind === 'session-header-item') {
|
|
1771
|
+
this.contributionLoadStates.delete(`${key}\u0000inline`)
|
|
1772
|
+
this.contributionLoadStates.delete(`${key}\u0000overflow`)
|
|
1773
|
+
}
|
|
1493
1774
|
this.applicationVisibility.delete(key)
|
|
1494
1775
|
}
|
|
1495
1776
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { LoaderCircleIcon } from 'lucide-react'
|
|
4
4
|
import {
|
|
5
5
|
forwardRef,
|
|
6
|
+
useCallback,
|
|
6
7
|
useEffect,
|
|
7
8
|
useImperativeHandle,
|
|
8
9
|
useRef,
|
|
@@ -28,6 +29,10 @@ import markerStyles from './l4-code-line-markers.module.css'
|
|
|
28
29
|
|
|
29
30
|
export type L4CodePreviewState = editor.ICodeEditorViewState
|
|
30
31
|
|
|
32
|
+
export interface L4CodeLineNavigation {
|
|
33
|
+
readonly line: number
|
|
34
|
+
}
|
|
35
|
+
|
|
31
36
|
export interface L4CodePreviewHandle {
|
|
32
37
|
openSearch: () => void
|
|
33
38
|
gotoLine: () => void
|
|
@@ -48,6 +53,8 @@ interface L4CodePreviewProps {
|
|
|
48
53
|
originalContent?: string
|
|
49
54
|
onChanges?: (count: number | null) => void
|
|
50
55
|
onOpenDiff?: (lineNumber: number) => void
|
|
56
|
+
navigation?: L4CodeLineNavigation | null
|
|
57
|
+
onNavigation?: (target: L4CodeLineNavigation, exists: boolean) => void
|
|
51
58
|
}
|
|
52
59
|
|
|
53
60
|
function L4CodePreviewComponent(
|
|
@@ -60,7 +67,9 @@ function L4CodePreviewComponent(
|
|
|
60
67
|
onEscape,
|
|
61
68
|
originalContent,
|
|
62
69
|
onChanges,
|
|
63
|
-
onOpenDiff
|
|
70
|
+
onOpenDiff,
|
|
71
|
+
navigation,
|
|
72
|
+
onNavigation
|
|
64
73
|
}: L4CodePreviewProps,
|
|
65
74
|
ref: ForwardedRef<L4CodePreviewHandle>
|
|
66
75
|
): React.JSX.Element {
|
|
@@ -76,13 +85,29 @@ function L4CodePreviewComponent(
|
|
|
76
85
|
const markersRef = useRef<L4CodeLineMarkers | null>(null)
|
|
77
86
|
const changesRef = useRef(onChanges)
|
|
78
87
|
const openDiffRef = useRef(onOpenDiff)
|
|
88
|
+
const navigationCompleteRef = useRef(onNavigation)
|
|
89
|
+
const navigationMarkersRef = useRef<editor.IEditorDecorationsCollection | null>(null)
|
|
90
|
+
const highlightedLineRef = useRef<number | null>(null)
|
|
91
|
+
const paintNavigation = useCallback((codeEditor: editor.IStandaloneCodeEditor): void => {
|
|
92
|
+
const line = highlightedLineRef.current
|
|
93
|
+
navigationMarkersRef.current?.clear()
|
|
94
|
+
if (line === null || line > (codeEditor.getModel()?.getLineCount() ?? 0)) return
|
|
95
|
+
navigationMarkersRef.current ??= codeEditor.createDecorationsCollection()
|
|
96
|
+
navigationMarkersRef.current.set([
|
|
97
|
+
{
|
|
98
|
+
range: { startLineNumber: line, startColumn: 1, endLineNumber: line, endColumn: 1 },
|
|
99
|
+
options: { isWholeLine: true, className: 'bg-accent' }
|
|
100
|
+
}
|
|
101
|
+
])
|
|
102
|
+
}, [])
|
|
79
103
|
|
|
80
104
|
useEffect(() => {
|
|
81
105
|
savedStateRef.current = savedState
|
|
82
106
|
escapeRef.current = onEscape
|
|
83
107
|
changesRef.current = onChanges
|
|
84
108
|
openDiffRef.current = onOpenDiff
|
|
85
|
-
|
|
109
|
+
navigationCompleteRef.current = onNavigation
|
|
110
|
+
}, [onChanges, onEscape, onOpenDiff, onNavigation, savedState])
|
|
86
111
|
const [loadState, setLoadState] = useState<'loading' | 'ready' | 'error'>('loading')
|
|
87
112
|
|
|
88
113
|
useEffect(() => {
|
|
@@ -200,6 +225,7 @@ function L4CodePreviewComponent(
|
|
|
200
225
|
stopTypographyObserver = observeL4MonacoTypography((typography) =>
|
|
201
226
|
codeEditor?.updateOptions(typography)
|
|
202
227
|
)
|
|
228
|
+
paintNavigation(codeEditor)
|
|
203
229
|
setLoadState('ready')
|
|
204
230
|
})
|
|
205
231
|
.catch((error: unknown) => {
|
|
@@ -222,16 +248,34 @@ function L4CodePreviewComponent(
|
|
|
222
248
|
savedStateRef.current = codeEditor.saveViewState()
|
|
223
249
|
onSaveStateRef.current(path, savedStateRef.current)
|
|
224
250
|
}
|
|
251
|
+
navigationMarkersRef.current?.clear()
|
|
252
|
+
navigationMarkersRef.current = null
|
|
225
253
|
editorRef.current = null
|
|
226
254
|
codeEditor?.dispose()
|
|
227
255
|
model?.dispose()
|
|
228
256
|
}
|
|
229
|
-
}, [content, originalContent, path, projectFilesT, size, t])
|
|
257
|
+
}, [content, originalContent, paintNavigation, path, projectFilesT, size, t])
|
|
230
258
|
|
|
231
259
|
useEffect(() => {
|
|
232
260
|
editorRef.current?.updateOptions({ wordWrap: wrapLines ? 'on' : 'off' })
|
|
233
261
|
}, [wrapLines])
|
|
234
262
|
|
|
263
|
+
useEffect(() => {
|
|
264
|
+
const codeEditor = editorRef.current
|
|
265
|
+
if (!navigation || loadState !== 'ready' || !codeEditor) return
|
|
266
|
+
const model = codeEditor.getModel()
|
|
267
|
+
const exists = Boolean(model && navigation.line >= 1 && navigation.line <= model.getLineCount())
|
|
268
|
+
highlightedLineRef.current = exists ? navigation.line : null
|
|
269
|
+
paintNavigation(codeEditor)
|
|
270
|
+
if (exists) {
|
|
271
|
+
codeEditor.setPosition({ lineNumber: navigation.line, column: 1 })
|
|
272
|
+
codeEditor.revealLineInCenter(navigation.line)
|
|
273
|
+
savedStateRef.current = codeEditor.saveViewState()
|
|
274
|
+
onSaveStateRef.current(path, savedStateRef.current)
|
|
275
|
+
}
|
|
276
|
+
navigationCompleteRef.current?.(navigation, exists)
|
|
277
|
+
}, [loadState, navigation, paintNavigation, path])
|
|
278
|
+
|
|
235
279
|
return (
|
|
236
280
|
<div
|
|
237
281
|
data-testid="code-preview"
|