@hienlh/ppm 0.9.84 → 0.9.86
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/260413-1354-new-file-editor-tab/reports/code-reviewer-260413-1420-new-file-tab-review.md +210 -0
- package/CHANGELOG.md +23 -0
- package/bun.lock +259 -9
- package/dist/web/assets/{_basePickBy-5PGDJbfF.js → _basePickBy-Bj0dI1ei.js} +1 -1
- package/dist/web/assets/{_baseUniq-BT4Ow4Kk.js → _baseUniq-CyzdZeQH.js} +1 -1
- package/dist/web/assets/ai-settings-section-Bo9lCaTd.js +1 -0
- package/dist/web/assets/{api-settings-Bn-bIxD1.js → api-settings-CUxg9RE5.js} +1 -1
- package/dist/web/assets/{arc-BAOivWpI.js → arc-CxgHJ7Z4.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-DDFO_NKq.js +1 -0
- package/dist/web/assets/{architectureDiagram-2XIMDMQ5-Z-4eN4za.js → architectureDiagram-2XIMDMQ5-D16OotsC.js} +1 -1
- package/dist/web/assets/arrow-up-I9-21gkR.js +1 -0
- package/dist/web/assets/{blockDiagram-WCTKOSBZ-BCLqzhuZ.js → blockDiagram-WCTKOSBZ-Ct57Wtfk.js} +1 -1
- package/dist/web/assets/{c4Diagram-IC4MRINW-0Vp0Jeas.js → c4Diagram-IC4MRINW-BIymcNsg.js} +1 -1
- package/dist/web/assets/channel-wumTB1if.js +1 -0
- package/dist/web/assets/chat-tab-BEEd-Km4.js +10 -0
- package/dist/web/assets/chevron-right-DY_wImxB.js +1 -0
- package/dist/web/assets/{chunk-4BX2VUAB-D4tOov49.js → chunk-4BX2VUAB-CENmY7Kw.js} +1 -1
- package/dist/web/assets/{chunk-55IACEB6-DJ6BynZ4.js → chunk-55IACEB6-DhZGI1l3.js} +1 -1
- package/dist/web/assets/{chunk-7E7YKBS2-CiyUJxNI.js → chunk-7E7YKBS2-DZcnC7Ow.js} +1 -1
- package/dist/web/assets/{chunk-7R4GIKGN-Dv-4cAYn.js → chunk-7R4GIKGN-y8bfHEy-.js} +2 -2
- package/dist/web/assets/{chunk-C72U2L5F-D21mS_6G.js → chunk-C72U2L5F-BHPkfQj2.js} +1 -1
- package/dist/web/assets/{chunk-EGIJ26TM-DzqmU2Z7.js → chunk-EGIJ26TM-nant2LXl.js} +1 -1
- package/dist/web/assets/{chunk-FMBD7UC4-DXncblvW.js → chunk-FMBD7UC4-Bog4cpN-.js} +1 -1
- package/dist/web/assets/{chunk-GEFDOKGD-D-pKjlVd.js → chunk-GEFDOKGD-86LFbsAC.js} +1 -1
- package/dist/web/assets/chunk-GLR3WWYH-Re-5eSlQ.js +2 -0
- package/dist/web/assets/chunk-HHEYEP7N-C45i5G_3.js +1 -0
- package/dist/web/assets/{chunk-JSJVCQXG-99JzIdPr.js → chunk-JSJVCQXG-23eG9mgt.js} +1 -1
- package/dist/web/assets/{chunk-KX2RTZJC-CRq1OBZv.js → chunk-KX2RTZJC-CHj8TnTB.js} +1 -1
- package/dist/web/assets/{chunk-KYZI473N-Bb0MCaIO.js → chunk-KYZI473N-gqRLpJ4w.js} +1 -1
- package/dist/web/assets/{chunk-L3YUKLVL-C7qGJrfV.js → chunk-L3YUKLVL-DnSMmNFC.js} +1 -1
- package/dist/web/assets/{chunk-MX3YWQON-BpS_PtKp.js → chunk-MX3YWQON-B6g1ZH9X.js} +1 -1
- package/dist/web/assets/{chunk-NQ4KR5QH-z_blpjxi.js → chunk-NQ4KR5QH-DX32345Y.js} +1 -1
- package/dist/web/assets/{chunk-O4XLMI2P-nDhi_cVu.js → chunk-O4XLMI2P-Vp_V4P-b.js} +1 -1
- package/dist/web/assets/{chunk-OZEHJAEY-BXhYx3nO.js → chunk-OZEHJAEY-lKq2SWjA.js} +1 -1
- package/dist/web/assets/{chunk-PQ6SQG4A-TF58UVMU.js → chunk-PQ6SQG4A-Bik13fTV.js} +1 -1
- package/dist/web/assets/{chunk-PU5JKC2W-ek7k4QVB.js → chunk-PU5JKC2W-DD95Rx35.js} +1 -1
- package/dist/web/assets/chunk-QZHKN3VN-N3VXx1VH.js +1 -0
- package/dist/web/assets/{chunk-R5LLSJPH-CFwSJijQ.js → chunk-R5LLSJPH-dRhXRnrb.js} +1 -1
- package/dist/web/assets/{chunk-WL4C6EOR-ByUrSRin.js → chunk-WL4C6EOR-B1iIvLOG.js} +1 -1
- package/dist/web/assets/{chunk-XIRO2GV7-Djlmrely.js → chunk-XIRO2GV7-DZBoNl1_.js} +1 -1
- package/dist/web/assets/{chunk-XPW4576I-BPQQBakK.js → chunk-XPW4576I-CgLyyW03.js} +1 -1
- package/dist/web/assets/{chunk-XZSTWKYB-DxAOx4hG.js → chunk-XZSTWKYB-DjV8xl5A.js} +1 -1
- package/dist/web/assets/{chunk-YBOYWFTD-rQG3QH5s.js → chunk-YBOYWFTD-D_ILLe6_.js} +1 -1
- package/dist/web/assets/classDiagram-VBA2DB6C-mr-Cb1me.js +1 -0
- package/dist/web/assets/classDiagram-v2-RAHNMMFH-BKe8_uda.js +1 -0
- package/dist/web/assets/clone--z5KLAuR.js +1 -0
- package/dist/web/assets/code-editor-Ij4p30cr.js +8 -0
- package/dist/web/assets/columns-2-IeETSfON.js +1 -0
- package/dist/web/assets/{cose-bilkent-S5V4N54A-B_AWZsOP.js → cose-bilkent-S5V4N54A-BGNPFv3x.js} +1 -1
- package/dist/web/assets/{csv-preview-D2pJJj3K.js → csv-preview-CwQnOa3E.js} +2 -2
- package/dist/web/assets/{dagre-DHq9bhnd.js → dagre-CkhlMHnx.js} +1 -1
- package/dist/web/assets/{dagre-KLK3FWXG-BdJr7Byp.js → dagre-KLK3FWXG-Cnp996VG.js} +1 -1
- package/dist/web/assets/database-CgTomMxt.js +1 -0
- package/dist/web/assets/{database-viewer-Camu01H4.js → database-viewer-C1UHSgft.js} +2 -2
- package/dist/web/assets/{diagram-E7M64L7V-_db4pBVA.js → diagram-E7M64L7V-BZF0tSOr.js} +1 -1
- package/dist/web/assets/{diagram-IFDJBPK2-xKoeuiJx.js → diagram-IFDJBPK2-nUcO8sN8.js} +1 -1
- package/dist/web/assets/{diagram-P4PSJMXO-C8tjJsev.js → diagram-P4PSJMXO-CW0eCkwC.js} +1 -1
- package/dist/web/assets/diff-viewer-CVx5naBA.js +4 -0
- package/dist/web/assets/dist-CM0oD8tQ.js +1 -0
- package/dist/web/assets/{erDiagram-INFDFZHY-BSh2z9Df.js → erDiagram-INFDFZHY-DSkriYZ9.js} +1 -1
- package/dist/web/assets/extension-webview-CHVVpV34.js +3 -0
- package/dist/web/assets/{flowDiagram-PKNHOUZH-oYaovqyp.js → flowDiagram-PKNHOUZH-CFYAfZBx.js} +1 -1
- package/dist/web/assets/{ganttDiagram-A5KZAMGK-DmL26q2P.js → ganttDiagram-A5KZAMGK-KSn4XAU4.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-OkvBPi6H.js +1 -0
- package/dist/web/assets/{gitGraphDiagram-K3NZZRJ6-CMoukSrY.js → gitGraphDiagram-K3NZZRJ6-BMgjjVys.js} +1 -1
- package/dist/web/assets/{graphlib-BcsNnGcW.js → graphlib-BWe1iK_s.js} +1 -1
- package/dist/web/assets/index-OqgGFmh8.js +26 -0
- package/dist/web/assets/index-vA7juDri.css +2 -0
- package/dist/web/assets/info-3K5VOQVL-BDU2_bYD.js +1 -0
- package/dist/web/assets/infoDiagram-LFFYTUFH-Diq4Cyc3.js +2 -0
- package/dist/web/assets/input-BHj0veau.js +45 -0
- package/dist/web/assets/{isEmpty-bnrF3Qbc.js → isEmpty-BfLnxq-B.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-PHBUUO56-D05_LyL7.js → ishikawaDiagram-PHBUUO56-CiVEvp8o.js} +1 -1
- package/dist/web/assets/{journeyDiagram-4ABVD52K-B_L20qMe.js → journeyDiagram-4ABVD52K-CG_v5Aho.js} +1 -1
- package/dist/web/assets/jsx-runtime-BRW_vwa9.js +1 -0
- package/dist/web/assets/{kanban-definition-K7BYSVSG-CZ535BbZ.js → kanban-definition-K7BYSVSG-miB0-_Zq.js} +1 -1
- package/dist/web/assets/keybindings-store-BQxgPV5o.js +1 -0
- package/dist/web/assets/{line-CVvo3dRu.js → line-CSuSrJ9J.js} +1 -1
- package/dist/web/assets/{linear-DP4mkX3m.js → linear-DFN_MPsw.js} +1 -1
- package/dist/web/assets/markdown-renderer-CRy8xw2B.js +306 -0
- package/dist/web/assets/{mermaid-parser.core-C7UwoIh6.js → mermaid-parser.core-CFdP1Z5_.js} +2 -2
- package/dist/web/assets/{mindmap-definition-YRQLILUH-x0MTutJp.js → mindmap-definition-YRQLILUH-pYPWwASE.js} +1 -1
- package/dist/web/assets/{ordinal-_K3x1fkz.js → ordinal-DpFn432U.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-BwpIpYB3.js +1 -0
- package/dist/web/assets/pie-UPGHQEXC-BPgAfmes.js +1 -0
- package/dist/web/assets/{pieDiagram-SKSYHLDU-C1Gjrtzy.js → pieDiagram-SKSYHLDU-Dovdlvhu.js} +1 -1
- package/dist/web/assets/plus-DQGIb4mQ.js +1 -0
- package/dist/web/assets/port-forwarding-tab-Biua8ov5.js +1 -0
- package/dist/web/assets/{postgres-viewer-BQdPMowm.js → postgres-viewer-BcVjCAl4.js} +3 -3
- package/dist/web/assets/{quadrantDiagram-337W2JSQ-C8bzJCjQ.js → quadrantDiagram-337W2JSQ-TXe6cU_F.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-TqxBkWx-.js +1 -0
- package/dist/web/assets/refresh-cw-Clk8fdUD.js +1 -0
- package/dist/web/assets/{requirementDiagram-Z7DCOOCP-pQyah6WB.js → requirementDiagram-Z7DCOOCP-CuiiuGS9.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-WA2Y5GQK-T6RgG-N8.js → sankeyDiagram-WA2Y5GQK-BbRmhv0t.js} +1 -1
- package/dist/web/assets/scroll-area-BpXCNme3.js +1 -0
- package/dist/web/assets/{sequenceDiagram-2WXFIKYE-BQDJ4CVs.js → sequenceDiagram-2WXFIKYE-B2D8IQDb.js} +1 -1
- package/dist/web/assets/settings-tab-C9X-N8hE.js +1 -0
- package/dist/web/assets/{sql-query-editor-CY61vWBg.js → sql-query-editor-BFvRvJn0.js} +1 -1
- package/dist/web/assets/sqlite-viewer-CPfvwFl4.js +1 -0
- package/dist/web/assets/square-vBdqj0bF.js +1 -0
- package/dist/web/assets/{stateDiagram-RAJIS63D-66vhiIuk.js → stateDiagram-RAJIS63D-ylr4HxPu.js} +1 -1
- package/dist/web/assets/stateDiagram-v2-FVOUBMTO-D6zvxf3M.js +1 -0
- package/dist/web/assets/table-Bi27fEaN.js +1 -0
- package/dist/web/assets/{terminal-tab-TIJmxHl6.js → terminal-tab-mWwk_weB.js} +2 -2
- package/dist/web/assets/text-wrap-D_OmSzhp.js +1 -0
- package/dist/web/assets/{timeline-definition-YZTLITO2-DwZqB3nn.js → timeline-definition-YZTLITO2-pMv1grvM.js} +1 -1
- package/dist/web/assets/trash-2-CNuB-htI.js +1 -0
- package/dist/web/assets/treemap-KZPCXAKY-Kck06FKU.js +1 -0
- package/dist/web/assets/{use-monaco-theme-BHn-LEm7.js → use-monaco-theme-CPaeSMAA.js} +1 -1
- package/dist/web/assets/{vennDiagram-LZ73GAT5-s9Z71fz-.js → vennDiagram-LZ73GAT5-C-rkIUbo.js} +1 -1
- package/dist/web/assets/x-Dw3TjeY_.js +1 -0
- package/dist/web/assets/{xychartDiagram-JWTSCODW-DRa_TH4B.js → xychartDiagram-JWTSCODW-CtpjAakO.js} +1 -1
- package/dist/web/index.html +18 -12
- package/dist/web/sw.js +1 -1
- package/docs/codebase-summary.md +134 -11
- package/docs/extension-development-guide.md +98 -1
- package/docs/journals/260414-1400-ext-git-graph-port-complete.md +147 -0
- package/docs/journals/260414-1452-git-graph-faithful-port.md +144 -0
- package/docs/journals/260414-1810-git-graph-ui-improvements-complete.md +261 -0
- package/docs/journals/260414-2001-bundled-extensions.md +219 -0
- package/docs/project-changelog.md +63 -22
- package/docs/project-roadmap.md +1 -0
- package/docs/system-architecture.md +33 -5
- package/package.json +9 -3
- package/packages/ext-git-graph/package.json +30 -0
- package/packages/ext-git-graph/src/extension-integration.test.ts +230 -0
- package/packages/ext-git-graph/src/extension-parsers.test.ts +193 -0
- package/packages/ext-git-graph/src/extension.ts +800 -0
- package/packages/ext-git-graph/src/git-log-parser.test.ts +271 -0
- package/packages/ext-git-graph/src/git-log-parser.ts +38 -0
- package/packages/ext-git-graph/src/types.ts +181 -0
- package/packages/ext-git-graph/src/webview-html.test.ts +142 -0
- package/packages/ext-git-graph/src/webview-html.ts +2199 -0
- package/packages/vscode-compat/src/index.ts +4 -0
- package/packages/vscode-compat/src/process.ts +25 -0
- package/packages/vscode-compat/src/window.ts +10 -0
- package/src/cli/commands/ext-cmd.ts +3 -1
- package/src/server/index.ts +1 -1
- package/src/server/ws/extensions.ts +6 -2
- package/src/services/contribution-registry.ts +14 -1
- package/src/services/extension-host-worker.ts +7 -3
- package/src/services/extension-manifest.ts +18 -1
- package/src/services/extension-rpc-handlers.ts +68 -2
- package/src/services/extension.service.ts +46 -6
- package/src/types/extension-messages.ts +2 -0
- package/src/types/extension.ts +8 -0
- package/src/web/components/editor/code-editor.tsx +83 -8
- package/src/web/components/editor/save-as-dialog.tsx +75 -0
- package/src/web/components/extensions/extension-webview.tsx +111 -12
- package/src/web/components/layout/command-palette.tsx +43 -17
- package/src/web/components/layout/draggable-tab.tsx +120 -67
- package/src/web/components/layout/editor-panel.tsx +15 -4
- package/src/web/components/layout/mobile-nav.tsx +74 -7
- package/src/web/components/layout/tab-bar.tsx +76 -4
- package/src/web/components/layout/tab-content.tsx +12 -5
- package/src/web/components/layout/upgrade-banner.tsx +3 -0
- package/src/web/components/settings/keyboard-shortcuts-section.tsx +46 -1
- package/src/web/components/shared/markdown-code-block.tsx +142 -0
- package/src/web/components/shared/markdown-context.ts +20 -0
- package/src/web/components/shared/markdown-renderer.tsx +113 -288
- package/src/web/hooks/use-extension-ws.ts +22 -4
- package/src/web/hooks/use-global-keybindings.ts +31 -2
- package/src/web/hooks/use-url-sync.ts +8 -3
- package/src/web/main.tsx +1 -0
- package/src/web/stores/keybindings-store.ts +3 -3
- package/src/web/stores/panel-store.ts +2 -2
- package/src/web/stores/panel-utils.ts +17 -2
- package/src/web/stores/tab-store.ts +17 -1
- package/src/web/styles/globals.css +6 -0
- package/.opencode/.env.example +0 -98
- package/.opencode/skills/ads-management/scripts/.env.example +0 -13
- package/.opencode/skills/ai-multimodal/.env.example +0 -230
- package/.opencode/skills/cip-design/.env.example +0 -6
- package/.opencode/skills/devops/.env.example +0 -76
- package/.opencode/skills/docs-seeker/.env.example +0 -15
- package/.opencode/skills/elevenlabs/.env.example +0 -3
- package/.opencode/skills/marketing-dashboard/.env.example +0 -15
- package/.opencode/skills/marketing-dashboard/app/.env.example +0 -2
- package/.opencode/skills/marketing-dashboard/server/.env.example +0 -2
- package/.opencode/skills/mcp-management/scripts/dist/analyze-tools.js +0 -70
- package/.opencode/skills/mcp-management/scripts/dist/cli.js +0 -160
- package/.opencode/skills/mcp-management/scripts/dist/mcp-client.js +0 -183
- package/.opencode/skills/payment-integration/scripts/.env.example +0 -20
- package/.opencode/skills/sequential-thinking/.env.example +0 -8
- package/dist/web/assets/architecture-PBZL5I3N-DEO2f3VD.js +0 -1
- package/dist/web/assets/arrow-up-BYhx9ckd.js +0 -1
- package/dist/web/assets/channel-By7bn0Yq.js +0 -1
- package/dist/web/assets/chat-tab-CT2XUgsc.js +0 -10
- package/dist/web/assets/chevron-right-4zq1jPv6.js +0 -1
- package/dist/web/assets/chunk-GLR3WWYH-DKikpoJM.js +0 -2
- package/dist/web/assets/chunk-HHEYEP7N-C7vxA5i9.js +0 -1
- package/dist/web/assets/chunk-QZHKN3VN-CYaTbeZf.js +0 -1
- package/dist/web/assets/classDiagram-VBA2DB6C-BA8Nj-_C.js +0 -1
- package/dist/web/assets/classDiagram-v2-RAHNMMFH-DjYu-6mn.js +0 -1
- package/dist/web/assets/clone-LRxlvnMj.js +0 -1
- package/dist/web/assets/code-editor-DQiPtcNd.js +0 -8
- package/dist/web/assets/columns-2-BoZAN-iw.js +0 -1
- package/dist/web/assets/createLucideIcon-PuMiQgHl.js +0 -1
- package/dist/web/assets/diff-viewer-CTwcVIP_.js +0 -4
- package/dist/web/assets/dist-DIV6WgAG.js +0 -41
- package/dist/web/assets/extension-webview-pU1xJyoc.js +0 -3
- package/dist/web/assets/git-graph-BnFbmpom.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-Bwna3and.js +0 -1
- package/dist/web/assets/index-CP9KnaGh.js +0 -30
- package/dist/web/assets/index-Cxz7oGXY.css +0 -2
- package/dist/web/assets/info-3K5VOQVL-_vRxVNUm.js +0 -1
- package/dist/web/assets/infoDiagram-LFFYTUFH-DWwumDkq.js +0 -2
- package/dist/web/assets/jsx-runtime-kMwlnEGE.js +0 -1
- package/dist/web/assets/keybindings-store-DdhEeehv.js +0 -1
- package/dist/web/assets/markdown-renderer-BjYurPV4.js +0 -326
- package/dist/web/assets/packet-RMMSAZCW-DY5PNnZU.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-BHncZutv.js +0 -1
- package/dist/web/assets/port-forwarding-tab-Bgr8dmsw.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-DH0AOkUy.js +0 -1
- package/dist/web/assets/settings-tab-BNoboN6E.js +0 -1
- package/dist/web/assets/sqlite-viewer-srSbGg1D.js +0 -1
- package/dist/web/assets/square-oPKIkJiw.js +0 -1
- package/dist/web/assets/stateDiagram-v2-FVOUBMTO-BGVqj_g9.js +0 -1
- package/dist/web/assets/table-DFevCOMd.js +0 -1
- package/dist/web/assets/tag-CXMT0QB6.js +0 -1
- package/dist/web/assets/text-wrap-BWNOVswA.js +0 -1
- package/dist/web/assets/treemap-KZPCXAKY-B2Xkyv-K.js +0 -1
- package/dist/web/assets/x-D2_KzIET.js +0 -1
- package/src/web/components/git/git-graph-branch-label.tsx +0 -124
- package/src/web/components/git/git-graph-constants.ts +0 -185
- package/src/web/components/git/git-graph-detail.tsx +0 -107
- package/src/web/components/git/git-graph-dialog.tsx +0 -72
- package/src/web/components/git/git-graph-row.tsx +0 -167
- package/src/web/components/git/git-graph-settings-dialog.tsx +0 -104
- package/src/web/components/git/git-graph-svg.tsx +0 -54
- package/src/web/components/git/git-graph-toolbar.tsx +0 -195
- package/src/web/components/git/git-graph.tsx +0 -193
- package/src/web/components/git/use-column-resize.ts +0 -33
- package/src/web/components/git/use-git-graph.ts +0 -201
- /package/dist/web/assets/{api-client-BfBM3I7n.js → api-client-BvxmRZUi.js} +0 -0
- /package/dist/web/assets/{array-B9UHiPd-.js → array-BFDiaBgf.js} +0 -0
- /package/dist/web/assets/{csv-parser-CNNw2RVA.js → csv-parser-i7fjqP2H.js} +0 -0
- /package/dist/web/assets/{cytoscape.esm-BW-DbntU.js → cytoscape.esm-C8i2jUzT.js} +0 -0
- /package/dist/web/assets/{defaultLocale-5eAKkKJC.js → defaultLocale-ZeknFqNe.js} +0 -0
- /package/dist/web/assets/{dist-CSJdAyA9.js → dist-DZmJeHOA.js} +0 -0
- /package/dist/web/assets/{init-DlZdxViB.js → init-0VJVrkRJ.js} +0 -0
- /package/dist/web/assets/{isArrayLikeObject-B_v2FtYn.js → isArrayLikeObject-ClzWCpcm.js} +0 -0
- /package/dist/web/assets/{katex-Bqvo_ZG0.js → katex-DR0kdMDv.js} +0 -0
- /package/dist/web/assets/{lib-DurwGtQO.js → lib-CeBVkQ-7.js} +0 -0
- /package/dist/web/assets/{math-069Z4SuC.js → math-CRc16Nj6.js} +0 -0
- /package/dist/web/assets/{path-6uRLdFF7.js → path-INs8XTPH.js} +0 -0
- /package/dist/web/assets/{preload-helper-Bf_JiD2A.js → preload-helper-mr3rCizq.js} +0 -0
- /package/dist/web/assets/{react-SKk5z-bm.js → react-0tkk-ztn.js} +0 -0
- /package/dist/web/assets/{rough.esm-JX0wREDd.js → rough.esm-eLccZ4OJ.js} +0 -0
- /package/dist/web/assets/{sql-completion-provider-DM9Qov6L.js → sql-completion-provider-B8uUWWej.js} +0 -0
- /package/dist/web/assets/{src-BqX54PbV.js → src-CqyWLlNZ.js} +0 -0
- /package/dist/web/assets/{utils-BNytJOb1.js → utils-DX8jb5qv.js} +0 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { useState, useCallback } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter,
|
|
4
|
+
} from "@/components/ui/dialog";
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
import { Input } from "@/components/ui/input";
|
|
7
|
+
import { FileBrowserPicker } from "@/components/ui/file-browser-picker";
|
|
8
|
+
import { useProjectStore } from "@/stores/project-store";
|
|
9
|
+
|
|
10
|
+
interface SaveAsDialogProps {
|
|
11
|
+
open: boolean;
|
|
12
|
+
defaultName: string;
|
|
13
|
+
content: string;
|
|
14
|
+
onSave: (fullPath: string, content: string) => void;
|
|
15
|
+
onCancel: () => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function SaveAsDialog({ open, defaultName, content, onSave, onCancel }: SaveAsDialogProps) {
|
|
19
|
+
const [filename, setFilename] = useState(defaultName);
|
|
20
|
+
const [showPicker, setShowPicker] = useState(false);
|
|
21
|
+
const [error, setError] = useState("");
|
|
22
|
+
const activeProject = useProjectStore((s) => s.activeProject);
|
|
23
|
+
|
|
24
|
+
const validateAndProceed = useCallback(() => {
|
|
25
|
+
const trimmed = filename.trim();
|
|
26
|
+
if (!trimmed) { setError("Filename cannot be empty"); return; }
|
|
27
|
+
if (/[/\\]/.test(trimmed)) { setError("Filename cannot contain / or \\"); return; }
|
|
28
|
+
setError("");
|
|
29
|
+
setShowPicker(true);
|
|
30
|
+
}, [filename]);
|
|
31
|
+
|
|
32
|
+
const handleFolderSelect = useCallback((dirPath: string) => {
|
|
33
|
+
const sep = dirPath.includes("\\") ? "\\" : "/";
|
|
34
|
+
const fullPath = dirPath.endsWith(sep) ? `${dirPath}${filename.trim()}` : `${dirPath}${sep}${filename.trim()}`;
|
|
35
|
+
onSave(fullPath, content);
|
|
36
|
+
}, [filename, content, onSave]);
|
|
37
|
+
|
|
38
|
+
if (showPicker) {
|
|
39
|
+
return (
|
|
40
|
+
<FileBrowserPicker
|
|
41
|
+
open
|
|
42
|
+
mode="folder"
|
|
43
|
+
root={activeProject?.path}
|
|
44
|
+
title={`Save "${filename.trim()}" to...`}
|
|
45
|
+
onSelect={handleFolderSelect}
|
|
46
|
+
onCancel={() => setShowPicker(false)}
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<Dialog open={open} onOpenChange={(v) => { if (!v) onCancel(); }}>
|
|
53
|
+
<DialogContent className="sm:max-w-md">
|
|
54
|
+
<DialogHeader>
|
|
55
|
+
<DialogTitle>Save As</DialogTitle>
|
|
56
|
+
</DialogHeader>
|
|
57
|
+
<div className="flex flex-col gap-2 py-2">
|
|
58
|
+
<label className="text-sm text-muted-foreground">Filename</label>
|
|
59
|
+
<Input
|
|
60
|
+
value={filename}
|
|
61
|
+
onChange={(e) => { setFilename(e.target.value); setError(""); }}
|
|
62
|
+
onKeyDown={(e) => { if (e.key === "Enter") validateAndProceed(); }}
|
|
63
|
+
placeholder="e.g. my-file.ts"
|
|
64
|
+
autoFocus
|
|
65
|
+
/>
|
|
66
|
+
{error && <p className="text-xs text-destructive">{error}</p>}
|
|
67
|
+
</div>
|
|
68
|
+
<DialogFooter>
|
|
69
|
+
<Button variant="outline" onClick={onCancel}>Cancel</Button>
|
|
70
|
+
<Button onClick={validateAndProceed}>Choose Folder...</Button>
|
|
71
|
+
</DialogFooter>
|
|
72
|
+
</DialogContent>
|
|
73
|
+
</Dialog>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { useRef, useEffect,
|
|
1
|
+
import { useRef, useEffect, useState } from "react";
|
|
2
2
|
import { useExtensionStore } from "@/stores/extension-store";
|
|
3
|
+
import { useTabStore } from "@/stores/tab-store";
|
|
4
|
+
import { Loader2 } from "lucide-react";
|
|
3
5
|
|
|
4
6
|
/** Inject acquireVsCodeApi() shim so extension webviews can postMessage to parent */
|
|
5
7
|
const VSCODE_API_SHIM = `<script>
|
|
@@ -22,49 +24,146 @@ interface ExtensionWebviewProps {
|
|
|
22
24
|
|
|
23
25
|
/**
|
|
24
26
|
* iframe-based webview container for extension-contributed webview panels.
|
|
25
|
-
*
|
|
27
|
+
* Matches panel by panelId (direct) or viewType (reload recovery).
|
|
26
28
|
*/
|
|
27
29
|
export function ExtensionWebview({ metadata }: ExtensionWebviewProps) {
|
|
28
30
|
const panelId = metadata?.panelId as string | undefined;
|
|
29
|
-
const
|
|
31
|
+
const viewType = metadata?.viewType as string | undefined;
|
|
32
|
+
const currentProject = useTabStore((s) => s.currentProject);
|
|
33
|
+
const projectName = (metadata?.projectName as string | undefined) || currentProject || undefined;
|
|
34
|
+
const [timedOut, setTimedOut] = useState(false);
|
|
35
|
+
|
|
36
|
+
// Match panel: prefer panelId (exact), fallback to viewType match (reload recovery)
|
|
37
|
+
const panel = useExtensionStore((s) => {
|
|
38
|
+
if (panelId && s.webviewPanels[panelId]) return s.webviewPanels[panelId];
|
|
39
|
+
if (viewType) {
|
|
40
|
+
// Find panel whose viewType matches (with or without .view suffix)
|
|
41
|
+
const fullViewType = viewType.includes(".") ? viewType : `${viewType}.view`;
|
|
42
|
+
return Object.values(s.webviewPanels).find(
|
|
43
|
+
(p) => p.viewType === viewType || p.viewType === fullViewType,
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const resolvedPanelId = panel?.id ?? panelId;
|
|
30
50
|
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
31
51
|
|
|
32
52
|
// Inject acquireVsCodeApi shim + write HTML into iframe via srcdoc
|
|
33
53
|
const rawHtml = panel?.html ?? "";
|
|
34
54
|
const html = injectVscodeApiShim(rawHtml);
|
|
35
55
|
|
|
56
|
+
// On reload: resolve project path, then dispatch command with retry
|
|
57
|
+
// Retry needed because WS connection may not be ready on first attempt
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (panel || !viewType) return;
|
|
60
|
+
const command = viewType.includes(".") ? viewType : `${viewType}.view`;
|
|
61
|
+
let cancelled = false;
|
|
62
|
+
let resolvedArgs: unknown[] | null = null;
|
|
63
|
+
|
|
64
|
+
async function resolveArgs(): Promise<unknown[]> {
|
|
65
|
+
if (resolvedArgs) return resolvedArgs;
|
|
66
|
+
if (!projectName) return [];
|
|
67
|
+
try {
|
|
68
|
+
const res = await fetch("/api/projects");
|
|
69
|
+
const json = await res.json() as { ok: boolean; data?: { name: string; path: string }[] };
|
|
70
|
+
const match = json.data?.find((p) => p.name === projectName);
|
|
71
|
+
resolvedArgs = match ? [match.path] : [];
|
|
72
|
+
} catch {
|
|
73
|
+
resolvedArgs = [];
|
|
74
|
+
}
|
|
75
|
+
return resolvedArgs;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function attempt() {
|
|
79
|
+
const args = await resolveArgs();
|
|
80
|
+
if (cancelled) return;
|
|
81
|
+
window.dispatchEvent(new CustomEvent("ext:command:execute", {
|
|
82
|
+
detail: { command, args },
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// First attempt after short delay (let WS connect), then retry every 2s
|
|
87
|
+
const initialTimer = setTimeout(() => {
|
|
88
|
+
if (!cancelled) attempt();
|
|
89
|
+
}, 500);
|
|
90
|
+
const retryTimer = setInterval(() => {
|
|
91
|
+
if (!cancelled) attempt();
|
|
92
|
+
}, 2_000);
|
|
93
|
+
|
|
94
|
+
return () => { cancelled = true; clearTimeout(initialTimer); clearInterval(retryTimer); };
|
|
95
|
+
}, [panel, viewType, projectName]);
|
|
96
|
+
|
|
97
|
+
// When project switches while panel exists, re-dispatch command with new project path
|
|
98
|
+
const prevProjectRef = useRef(currentProject);
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
if (!panel || !viewType || !currentProject || currentProject === prevProjectRef.current) {
|
|
101
|
+
prevProjectRef.current = currentProject;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
prevProjectRef.current = currentProject;
|
|
105
|
+
// Resolve new project path and dispatch command
|
|
106
|
+
(async () => {
|
|
107
|
+
try {
|
|
108
|
+
const res = await fetch("/api/projects");
|
|
109
|
+
const json = await res.json() as { ok: boolean; data?: { name: string; path: string }[] };
|
|
110
|
+
const match = json.data?.find((p: { name: string }) => p.name === currentProject);
|
|
111
|
+
if (match) {
|
|
112
|
+
const command = viewType.includes(".") ? viewType : `${viewType}.view`;
|
|
113
|
+
window.dispatchEvent(new CustomEvent("ext:command:execute", {
|
|
114
|
+
detail: { command, args: [match.path] },
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
117
|
+
} catch {}
|
|
118
|
+
})();
|
|
119
|
+
}, [panel, viewType, currentProject]);
|
|
120
|
+
|
|
121
|
+
// Timeout: if panel doesn't appear within 10s, show error
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
if (panel) { setTimedOut(false); return; }
|
|
124
|
+
const timer = setTimeout(() => setTimedOut(true), 10_000);
|
|
125
|
+
return () => clearTimeout(timer);
|
|
126
|
+
}, [panel]);
|
|
127
|
+
|
|
36
128
|
// Listen for postMessage from iframe → forward to extension via WS bridge
|
|
37
129
|
useEffect(() => {
|
|
130
|
+
if (!resolvedPanelId) return;
|
|
38
131
|
const handler = (event: MessageEvent) => {
|
|
39
132
|
if (iframeRef.current && event.source === iframeRef.current.contentWindow) {
|
|
40
|
-
// Forward to server via custom event → picked up by useExtensionWs
|
|
41
133
|
window.dispatchEvent(new CustomEvent("ext:webview:send", {
|
|
42
|
-
detail: { panelId, message: event.data },
|
|
134
|
+
detail: { panelId: resolvedPanelId, message: event.data },
|
|
43
135
|
}));
|
|
44
136
|
}
|
|
45
137
|
};
|
|
46
138
|
window.addEventListener("message", handler);
|
|
47
139
|
return () => window.removeEventListener("message", handler);
|
|
48
|
-
}, [
|
|
140
|
+
}, [resolvedPanelId]);
|
|
49
141
|
|
|
50
142
|
// Listen for server→webview messages (dispatched by useExtensionWs)
|
|
51
|
-
// targetOrigin "*" is safe here because sandbox omits allow-same-origin,
|
|
52
|
-
// so iframe origin is opaque "null". MUST restrict if allow-same-origin is ever added.
|
|
53
143
|
useEffect(() => {
|
|
144
|
+
if (!resolvedPanelId) return;
|
|
54
145
|
const handler = (e: Event) => {
|
|
55
146
|
const { panelId: targetId, message } = (e as CustomEvent).detail;
|
|
56
|
-
if (targetId ===
|
|
147
|
+
if (targetId === resolvedPanelId) {
|
|
57
148
|
iframeRef.current?.contentWindow?.postMessage(message, "*");
|
|
58
149
|
}
|
|
59
150
|
};
|
|
60
151
|
window.addEventListener("ext:webview:message", handler);
|
|
61
152
|
return () => window.removeEventListener("ext:webview:message", handler);
|
|
62
|
-
}, [
|
|
153
|
+
}, [resolvedPanelId]);
|
|
63
154
|
|
|
155
|
+
// Loading state — waiting for extension to create the panel
|
|
64
156
|
if (!panel) {
|
|
65
157
|
return (
|
|
66
|
-
<div className="flex items-center justify-center h-full text-sm text-text-subtle">
|
|
67
|
-
|
|
158
|
+
<div className="flex flex-col items-center justify-center h-full gap-2 text-sm text-text-subtle">
|
|
159
|
+
{timedOut ? (
|
|
160
|
+
<span>Extension failed to load webview panel</span>
|
|
161
|
+
) : (
|
|
162
|
+
<>
|
|
163
|
+
<Loader2 className="size-5 animate-spin" />
|
|
164
|
+
<span>Loading extension...</span>
|
|
165
|
+
</>
|
|
166
|
+
)}
|
|
68
167
|
</div>
|
|
69
168
|
);
|
|
70
169
|
}
|
|
@@ -2,17 +2,20 @@ import { useState, useEffect, useRef, useMemo, useCallback } from "react";
|
|
|
2
2
|
import {
|
|
3
3
|
Terminal,
|
|
4
4
|
MessageSquare,
|
|
5
|
-
GitBranch,
|
|
6
5
|
GitCommitHorizontal,
|
|
6
|
+
GitBranch,
|
|
7
|
+
Puzzle,
|
|
7
8
|
Settings,
|
|
8
9
|
Database,
|
|
9
10
|
Search,
|
|
10
11
|
FileCode,
|
|
12
|
+
FilePlus,
|
|
11
13
|
FolderOpen,
|
|
12
14
|
Loader2,
|
|
13
15
|
Globe,
|
|
14
16
|
Mic,
|
|
15
|
-
|
|
17
|
+
RefreshCw,
|
|
18
|
+
Plus,
|
|
16
19
|
} from "lucide-react";
|
|
17
20
|
import { useTabStore, type TabType } from "@/stores/tab-store";
|
|
18
21
|
import { useProjectStore } from "@/stores/project-store";
|
|
@@ -37,6 +40,19 @@ interface CommandItem {
|
|
|
37
40
|
|
|
38
41
|
const isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.userAgent);
|
|
39
42
|
|
|
43
|
+
/** Map extension icon string names to lucide components */
|
|
44
|
+
const EXT_ICON_MAP: Record<string, React.ElementType> = {
|
|
45
|
+
"git-branch": GitBranch,
|
|
46
|
+
"database": Database,
|
|
47
|
+
"refresh": RefreshCw,
|
|
48
|
+
"plus": Plus,
|
|
49
|
+
"terminal": Terminal,
|
|
50
|
+
"settings": Settings,
|
|
51
|
+
"search": Search,
|
|
52
|
+
"file-code": FileCode,
|
|
53
|
+
"globe": Globe,
|
|
54
|
+
};
|
|
55
|
+
|
|
40
56
|
/** Format a keybinding combo for display (e.g. "Mod+G" → "⌘G" on Mac, "Ctrl+G" on others) */
|
|
41
57
|
function formatShortcut(combo: string): string {
|
|
42
58
|
if (!combo) return "";
|
|
@@ -159,8 +175,8 @@ export function CommandPalette({ open, onClose, initialQuery = "" }: { open: boo
|
|
|
159
175
|
|
|
160
176
|
const builtIn: CommandItem[] = [
|
|
161
177
|
{ id: "chat", label: "New AI Chat", icon: MessageSquare, action: openNewTab("chat", "AI Chat"), keywords: "ai assistant claude", group: "action", shortcut: formatShortcut(getBinding("open-chat")) },
|
|
178
|
+
{ id: "new-file", label: "New File", icon: FilePlus, action: () => { useTabStore.getState().openNewFile(); onClose(); }, keywords: "create untitled blank empty", group: "action", shortcut: formatShortcut(getBinding("new-file")) },
|
|
162
179
|
{ id: "terminal", label: "New Terminal", icon: Terminal, action: openNewTab("terminal", "Terminal"), keywords: "bash shell console", group: "action", shortcut: formatShortcut(getBinding("open-terminal")) },
|
|
163
|
-
{ id: "git-graph", label: "Git Graph", icon: GitBranch, action: openNewTab("git-graph", "Git Graph"), keywords: "branch history log", group: "action", shortcut: formatShortcut(getBinding("open-git-graph")) },
|
|
164
180
|
{ id: "ports", label: "Port Forwarding", icon: Globe, action: openNewTab("ports", "Ports"), keywords: "web preview localhost port forward tunnel url", group: "action" },
|
|
165
181
|
{ id: "postgres", label: "PostgreSQL", icon: Database, action: openNewTab("postgres", "PostgreSQL"), keywords: "database pg sql query", group: "action" },
|
|
166
182
|
{ id: "voice-input", label: "Voice Input", icon: Mic, action: () => { window.dispatchEvent(new CustomEvent("toggle-voice-input")); onClose(); }, keywords: "speech microphone dictate voice", group: "action", shortcut: formatShortcut(getBinding("voice-input")) },
|
|
@@ -178,20 +194,30 @@ export function CommandPalette({ open, onClose, initialQuery = "" }: { open: boo
|
|
|
178
194
|
},
|
|
179
195
|
];
|
|
180
196
|
|
|
181
|
-
// Append extension-contributed commands
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
197
|
+
// Append extension-contributed commands (with keybinding shortcuts, respecting user overrides)
|
|
198
|
+
const extKbs = extContributions?.keybindings ?? [];
|
|
199
|
+
const extCmds: CommandItem[] = (extContributions?.commands ?? []).map((cmd) => {
|
|
200
|
+
const kb = extKbs.find((k) => k.command === cmd.command);
|
|
201
|
+
const overrideCombo = kb ? getBinding(`ext:${kb.command}`) : "";
|
|
202
|
+
const shortcutCombo = overrideCombo || (kb ? ((isMac && kb.mac) ? kb.mac : kb.key) : "");
|
|
203
|
+
return {
|
|
204
|
+
id: `ext:${cmd.command}`,
|
|
205
|
+
label: cmd.title,
|
|
206
|
+
hint: cmd.category,
|
|
207
|
+
icon: (cmd.icon && EXT_ICON_MAP[cmd.icon]) || Puzzle,
|
|
208
|
+
group: "action" as const,
|
|
209
|
+
keywords: `extension ${cmd.command} ${cmd.category ?? ""}`,
|
|
210
|
+
shortcut: shortcutCombo ? formatShortcut(shortcutCombo) : undefined,
|
|
211
|
+
action: () => {
|
|
212
|
+
const args: unknown[] = [];
|
|
213
|
+
if (activeProject?.path) args.push(activeProject.path);
|
|
214
|
+
window.dispatchEvent(new CustomEvent("ext:command:execute", {
|
|
215
|
+
detail: { command: cmd.command, args },
|
|
216
|
+
}));
|
|
217
|
+
onClose();
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
});
|
|
195
221
|
|
|
196
222
|
return [...builtIn, ...extCmds];
|
|
197
223
|
}, [activeProject, openTab, onClose, setSidebarActiveTab, sidebarCollapsed, toggleSidebar, getBinding, extContributions]);
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { useState, useRef, useEffect } from "react";
|
|
2
|
-
import { X } from "lucide-react";
|
|
2
|
+
import { X, Download } from "lucide-react";
|
|
3
3
|
import type { Tab, TabType } from "@/stores/tab-store";
|
|
4
4
|
import { cn } from "@/lib/utils";
|
|
5
5
|
import { isDarkColor } from "@/lib/color-utils";
|
|
6
6
|
import { notificationColor } from "@/stores/notification-store";
|
|
7
|
+
import {
|
|
8
|
+
ContextMenu,
|
|
9
|
+
ContextMenuContent,
|
|
10
|
+
ContextMenuItem,
|
|
11
|
+
ContextMenuSeparator,
|
|
12
|
+
ContextMenuTrigger,
|
|
13
|
+
} from "@/components/ui/context-menu";
|
|
7
14
|
|
|
8
15
|
interface DraggableTabProps {
|
|
9
16
|
tab: Tab;
|
|
@@ -23,11 +30,13 @@ interface DraggableTabProps {
|
|
|
23
30
|
tabRef: (el: HTMLButtonElement | null) => void;
|
|
24
31
|
/** If provided, double-clicking the title enters inline rename mode */
|
|
25
32
|
onRename?: (newTitle: string) => void;
|
|
33
|
+
/** Context menu action handler — receives action name */
|
|
34
|
+
onContextAction?: (action: string) => void;
|
|
26
35
|
}
|
|
27
36
|
|
|
28
37
|
export function DraggableTab({
|
|
29
38
|
tab, isActive, icon: Icon, showDropBefore, notificationType, onSelect, onClose,
|
|
30
|
-
onDragStart, onDragOver, onDragEnd, onTouchStart, onTouchMove, onTouchEnd, tabRef, onRename,
|
|
39
|
+
onDragStart, onDragOver, onDragEnd, onTouchStart, onTouchMove, onTouchEnd, tabRef, onRename, onContextAction,
|
|
31
40
|
}: DraggableTabProps) {
|
|
32
41
|
const [editing, setEditing] = useState(false);
|
|
33
42
|
const [editValue, setEditValue] = useState(tab.title);
|
|
@@ -56,76 +65,120 @@ export function DraggableTab({
|
|
|
56
65
|
}
|
|
57
66
|
: undefined;
|
|
58
67
|
|
|
68
|
+
const isFile = tab.type === "editor";
|
|
69
|
+
|
|
70
|
+
const tabButton = (
|
|
71
|
+
<button
|
|
72
|
+
ref={tabRef}
|
|
73
|
+
data-tab-item
|
|
74
|
+
draggable={!editing}
|
|
75
|
+
onClick={onSelect}
|
|
76
|
+
onAuxClick={(e) => { if (e.button === 1 && tab.closable) { e.preventDefault(); onClose(); } }}
|
|
77
|
+
onDragStart={onDragStart}
|
|
78
|
+
onDragOver={onDragOver}
|
|
79
|
+
onDragEnd={onDragEnd}
|
|
80
|
+
onTouchStart={onTouchStart}
|
|
81
|
+
onTouchMove={onTouchMove}
|
|
82
|
+
onTouchEnd={onTouchEnd}
|
|
83
|
+
style={colorStyle}
|
|
84
|
+
className={cn(
|
|
85
|
+
"group flex items-center gap-1 px-3 h-10 whitespace-nowrap text-xs transition-colors",
|
|
86
|
+
"border-b-2 -mb-px cursor-grab active:cursor-grabbing",
|
|
87
|
+
!colorStyle && (isActive
|
|
88
|
+
? "border-primary text-primary"
|
|
89
|
+
: "border-transparent text-text-secondary hover:text-foreground"),
|
|
90
|
+
colorStyle && "border-transparent",
|
|
91
|
+
)}
|
|
92
|
+
>
|
|
93
|
+
<span className="relative">
|
|
94
|
+
<Icon className="size-4" />
|
|
95
|
+
{notificationType && !isActive && (
|
|
96
|
+
<span className={cn("absolute -top-1 -right-1 size-2 rounded-full", notificationColor(notificationType))} />
|
|
97
|
+
)}
|
|
98
|
+
</span>
|
|
99
|
+
{editing ? (
|
|
100
|
+
<input
|
|
101
|
+
ref={inputRef}
|
|
102
|
+
value={editValue}
|
|
103
|
+
onChange={(e) => setEditValue(e.target.value)}
|
|
104
|
+
onBlur={commitRename}
|
|
105
|
+
onKeyDown={(e) => {
|
|
106
|
+
if (e.key === "Enter") commitRename();
|
|
107
|
+
if (e.key === "Escape") setEditing(false);
|
|
108
|
+
e.stopPropagation();
|
|
109
|
+
}}
|
|
110
|
+
onClick={(e) => e.stopPropagation()}
|
|
111
|
+
className="max-w-[120px] bg-surface-elevated text-xs px-1 py-0.5 rounded border border-border outline-none focus:border-primary"
|
|
112
|
+
autoFocus
|
|
113
|
+
/>
|
|
114
|
+
) : (
|
|
115
|
+
<span
|
|
116
|
+
className="max-w-[120px] truncate"
|
|
117
|
+
onDoubleClick={(e) => {
|
|
118
|
+
if (onRename) { e.stopPropagation(); setEditing(true); }
|
|
119
|
+
}}
|
|
120
|
+
>
|
|
121
|
+
{tab.title}
|
|
122
|
+
</span>
|
|
123
|
+
)}
|
|
124
|
+
{tab.closable && !editing && (
|
|
125
|
+
<span
|
|
126
|
+
role="button"
|
|
127
|
+
tabIndex={0}
|
|
128
|
+
onClick={(e) => { e.stopPropagation(); onClose(); }}
|
|
129
|
+
onKeyDown={(e) => { if (e.key === "Enter") { e.stopPropagation(); onClose(); } }}
|
|
130
|
+
className="ml-1 can-hover:opacity-0 can-hover:group-hover:opacity-100 rounded-sm hover:bg-surface-elevated p-0.5 transition-opacity"
|
|
131
|
+
>
|
|
132
|
+
<X className="size-3" />
|
|
133
|
+
</span>
|
|
134
|
+
)}
|
|
135
|
+
</button>
|
|
136
|
+
);
|
|
137
|
+
|
|
59
138
|
return (
|
|
60
139
|
<div className="relative flex items-center">
|
|
61
140
|
{showDropBefore && (
|
|
62
141
|
<div className="absolute left-0 top-1 bottom-1 w-0.5 bg-primary rounded-full z-10" />
|
|
63
142
|
)}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
onClick={(e) => e.stopPropagation()}
|
|
104
|
-
className="max-w-[120px] bg-surface-elevated text-xs px-1 py-0.5 rounded border border-border outline-none focus:border-primary"
|
|
105
|
-
autoFocus
|
|
106
|
-
/>
|
|
107
|
-
) : (
|
|
108
|
-
<span
|
|
109
|
-
className="max-w-[120px] truncate"
|
|
110
|
-
onDoubleClick={(e) => {
|
|
111
|
-
if (onRename) { e.stopPropagation(); setEditing(true); }
|
|
112
|
-
}}
|
|
113
|
-
>
|
|
114
|
-
{tab.title}
|
|
115
|
-
</span>
|
|
116
|
-
)}
|
|
117
|
-
{tab.closable && !editing && (
|
|
118
|
-
<span
|
|
119
|
-
role="button"
|
|
120
|
-
tabIndex={0}
|
|
121
|
-
onClick={(e) => { e.stopPropagation(); onClose(); }}
|
|
122
|
-
onKeyDown={(e) => { if (e.key === "Enter") { e.stopPropagation(); onClose(); } }}
|
|
123
|
-
className="ml-1 can-hover:opacity-0 can-hover:group-hover:opacity-100 rounded-sm hover:bg-surface-elevated p-0.5 transition-opacity"
|
|
124
|
-
>
|
|
125
|
-
<X className="size-3" />
|
|
126
|
-
</span>
|
|
127
|
-
)}
|
|
128
|
-
</button>
|
|
143
|
+
{onContextAction ? (
|
|
144
|
+
<ContextMenu>
|
|
145
|
+
<ContextMenuTrigger asChild>
|
|
146
|
+
{tabButton}
|
|
147
|
+
</ContextMenuTrigger>
|
|
148
|
+
<ContextMenuContent>
|
|
149
|
+
{isFile && (
|
|
150
|
+
<>
|
|
151
|
+
<ContextMenuItem onClick={() => onContextAction("copy-path")}>
|
|
152
|
+
Copy Path
|
|
153
|
+
</ContextMenuItem>
|
|
154
|
+
<ContextMenuItem onClick={() => onContextAction("download")}>
|
|
155
|
+
<Download className="size-3.5 mr-2" />
|
|
156
|
+
Download
|
|
157
|
+
</ContextMenuItem>
|
|
158
|
+
<ContextMenuSeparator />
|
|
159
|
+
<ContextMenuItem onClick={() => onContextAction("rename")}>
|
|
160
|
+
Rename
|
|
161
|
+
</ContextMenuItem>
|
|
162
|
+
<ContextMenuItem variant="destructive" onClick={() => onContextAction("delete")}>
|
|
163
|
+
Delete
|
|
164
|
+
</ContextMenuItem>
|
|
165
|
+
<ContextMenuSeparator />
|
|
166
|
+
</>
|
|
167
|
+
)}
|
|
168
|
+
{tab.closable && (
|
|
169
|
+
<ContextMenuItem onClick={() => onContextAction("close")}>
|
|
170
|
+
Close
|
|
171
|
+
</ContextMenuItem>
|
|
172
|
+
)}
|
|
173
|
+
<ContextMenuItem onClick={() => onContextAction("close-others")}>
|
|
174
|
+
Close Others
|
|
175
|
+
</ContextMenuItem>
|
|
176
|
+
<ContextMenuItem onClick={() => onContextAction("close-right")}>
|
|
177
|
+
Close to the Right
|
|
178
|
+
</ContextMenuItem>
|
|
179
|
+
</ContextMenuContent>
|
|
180
|
+
</ContextMenu>
|
|
181
|
+
) : tabButton}
|
|
129
182
|
</div>
|
|
130
183
|
);
|
|
131
184
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Suspense, lazy, useEffect, useState, useCallback } from "react";
|
|
2
|
-
import { ChevronDown, ChevronUp, Loader2, Terminal, MessageSquare,
|
|
2
|
+
import { ChevronDown, ChevronUp, Loader2, Terminal, MessageSquare, FilePlus, Pin, PinOff } from "lucide-react";
|
|
3
3
|
import { usePanelStore } from "@/stores/panel-store";
|
|
4
4
|
import { useProjectStore } from "@/stores/project-store";
|
|
5
|
-
import type
|
|
5
|
+
import { useTabStore, type TabType } from "@/stores/tab-store";
|
|
6
6
|
import { api, projectUrl } from "@/lib/api-client";
|
|
7
7
|
import type { SessionInfo } from "../../../types/chat";
|
|
8
8
|
import { TabBar } from "./tab-bar";
|
|
@@ -12,7 +12,7 @@ import { cn } from "@/lib/utils";
|
|
|
12
12
|
const QUICK_OPEN_TABS: { type: TabType; label: string; icon: React.ElementType }[] = [
|
|
13
13
|
{ type: "terminal", label: "Terminal", icon: Terminal },
|
|
14
14
|
{ type: "chat", label: "AI Chat", icon: MessageSquare },
|
|
15
|
-
{ type: "
|
|
15
|
+
{ type: "editor", label: "New File", icon: FilePlus },
|
|
16
16
|
];
|
|
17
17
|
|
|
18
18
|
const TAB_COMPONENTS: Record<TabType, React.LazyExoticComponent<React.ComponentType<{ metadata?: Record<string, unknown>; tabId?: string }>>> = {
|
|
@@ -22,10 +22,10 @@ const TAB_COMPONENTS: Record<TabType, React.LazyExoticComponent<React.ComponentT
|
|
|
22
22
|
database: lazy(() => import("@/components/database/database-viewer").then((m) => ({ default: m.DatabaseViewer }))),
|
|
23
23
|
sqlite: lazy(() => import("@/components/sqlite/sqlite-viewer").then((m) => ({ default: m.SqliteViewer }))),
|
|
24
24
|
postgres: lazy(() => import("@/components/postgres/postgres-viewer").then((m) => ({ default: m.PostgresViewer }))),
|
|
25
|
-
"git-graph": lazy(() => import("@/components/git/git-graph").then((m) => ({ default: m.GitGraph }))),
|
|
26
25
|
"git-diff": lazy(() => import("@/components/editor/diff-viewer").then((m) => ({ default: m.DiffViewer }))),
|
|
27
26
|
settings: lazy(() => import("@/components/settings/settings-tab").then((m) => ({ default: m.SettingsTab }))),
|
|
28
27
|
ports: lazy(() => import("@/components/ports/port-forwarding-tab").then((m) => ({ default: m.PortForwardingTab }))),
|
|
28
|
+
extension: lazy(() => import("@/components/extensions/extension-webview").then((m) => ({ default: m.ExtensionWebview }))),
|
|
29
29
|
"extension-webview": lazy(() => import("@/components/extensions/extension-webview").then((m) => ({ default: m.ExtensionWebview }))),
|
|
30
30
|
};
|
|
31
31
|
|
|
@@ -62,6 +62,13 @@ export function EditorPanel({ panelId, projectName }: EditorPanelProps) {
|
|
|
62
62
|
panel.tabs.map((tab) => {
|
|
63
63
|
const Component = TAB_COMPONENTS[tab.type];
|
|
64
64
|
const isActive = tab.id === panel.activeTabId;
|
|
65
|
+
if (!Component) {
|
|
66
|
+
return (
|
|
67
|
+
<div key={tab.id} className={isActive ? "h-full w-full flex items-center justify-center text-muted-foreground" : "hidden"}>
|
|
68
|
+
Unknown tab type: {tab.type}
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
65
72
|
return (
|
|
66
73
|
<div key={tab.id} className={isActive ? "h-full w-full" : "hidden"}>
|
|
67
74
|
<Suspense fallback={<div className="flex items-center justify-center h-full"><Loader2 className="size-6 animate-spin text-primary" /></div>}>
|
|
@@ -143,6 +150,10 @@ function EmptyPanel({ panelId }: { panelId: string }) {
|
|
|
143
150
|
}, [activeProject?.name]);
|
|
144
151
|
|
|
145
152
|
function openTab(type: TabType) {
|
|
153
|
+
if (type === "editor") {
|
|
154
|
+
useTabStore.getState().openNewFile();
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
146
157
|
const needsProject = type !== "settings";
|
|
147
158
|
const metadata = needsProject && activeProject ? { projectName: activeProject.name } : undefined;
|
|
148
159
|
usePanelStore.getState().openTab(
|