@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
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { useState, useEffect, useRef, useCallback, useMemo } from "react";
|
|
2
2
|
import {
|
|
3
|
-
Terminal, MessageSquare,
|
|
3
|
+
Terminal, MessageSquare, Database,
|
|
4
4
|
FileDiff, FileCode, Settings, Menu, X, ArrowLeft, ArrowRight, SplitSquareVertical, MoveVertical, Layers, Plus,
|
|
5
|
-
ChevronRight, Globe, Puzzle,
|
|
5
|
+
ChevronRight, Globe, Puzzle, Copy, Download, Pencil, Trash2,
|
|
6
6
|
} from "lucide-react";
|
|
7
7
|
import { usePanelStore } from "@/stores/panel-store";
|
|
8
8
|
import { useProjectStore, resolveOrder } from "@/stores/project-store";
|
|
9
|
+
import { useFileStore, type FileNode } from "@/stores/file-store";
|
|
9
10
|
import { findPanelPosition, MAX_ROWS } from "@/stores/panel-utils";
|
|
10
11
|
import { resolveProjectColor } from "@/lib/project-palette";
|
|
11
12
|
import { getProjectInitials } from "@/lib/project-avatar";
|
|
@@ -14,18 +15,20 @@ import { cn } from "@/lib/utils";
|
|
|
14
15
|
import { openCommandPalette } from "@/hooks/use-global-keybindings";
|
|
15
16
|
import { useNotificationStore, notificationColor } from "@/stores/notification-store";
|
|
16
17
|
import { useTabOverflow, getHiddenUnreadDirection } from "@/hooks/use-tab-overflow";
|
|
18
|
+
import { downloadFile } from "@/lib/file-download";
|
|
19
|
+
import { FileActions } from "@/components/explorer/file-actions";
|
|
17
20
|
|
|
18
21
|
const NEW_TAB_OPTIONS: { type: TabType; label: string }[] = [
|
|
19
22
|
{ type: "terminal", label: "Terminal" },
|
|
20
23
|
{ type: "chat", label: "AI Chat" },
|
|
21
|
-
{ type: "git-graph", label: "Git Graph" },
|
|
22
24
|
{ type: "settings", label: "Settings" },
|
|
23
25
|
];
|
|
24
26
|
const NEW_TAB_LABELS: Partial<Record<TabType, string>> = Object.fromEntries(NEW_TAB_OPTIONS.map((o) => [o.type, o.label]));
|
|
25
27
|
|
|
26
28
|
const TAB_ICONS: Record<TabType, React.ElementType> = {
|
|
27
29
|
terminal: Terminal, chat: MessageSquare, editor: FileCode, database: Database, sqlite: Database, postgres: Database,
|
|
28
|
-
"git-
|
|
30
|
+
"git-diff": FileDiff, settings: Settings, ports: Globe,
|
|
31
|
+
extension: Puzzle,
|
|
29
32
|
"extension-webview": Puzzle,
|
|
30
33
|
};
|
|
31
34
|
|
|
@@ -112,11 +115,33 @@ export function MobileNav({ onMenuPress, onProjectsPress }: MobileNavProps) {
|
|
|
112
115
|
usePanelStore.getState().moveTab(tabId, pid, targetPanelId);
|
|
113
116
|
}
|
|
114
117
|
|
|
118
|
+
const [fileActionState, setFileActionState] = useState<{ action: string; node: FileNode; tabId: string } | null>(null);
|
|
119
|
+
|
|
120
|
+
function handleFileAction(tab: Tab, action: string) {
|
|
121
|
+
const filePath = tab.metadata?.filePath as string | undefined;
|
|
122
|
+
const projectName = tab.metadata?.projectName as string | undefined;
|
|
123
|
+
switch (action) {
|
|
124
|
+
case "copy-path":
|
|
125
|
+
if (filePath) navigator.clipboard.writeText(filePath).catch(() => {});
|
|
126
|
+
break;
|
|
127
|
+
case "download":
|
|
128
|
+
if (filePath && projectName) downloadFile(projectName, filePath);
|
|
129
|
+
break;
|
|
130
|
+
case "rename":
|
|
131
|
+
case "delete":
|
|
132
|
+
if (filePath) {
|
|
133
|
+
setFileActionState({ action, tabId: tab.id, node: { name: tab.title, path: filePath, type: "file" } });
|
|
134
|
+
}
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
setMenuTabId(null);
|
|
138
|
+
}
|
|
139
|
+
|
|
115
140
|
const { activeProject: activeProjectForTab } = useProjectStore.getState();
|
|
116
141
|
function handleNewTab(type: TabType) {
|
|
117
142
|
const state = usePanelStore.getState();
|
|
118
143
|
const firstPanelId = state.grid[0]?.[0] ?? state.focusedPanelId;
|
|
119
|
-
const needsProject = type === "git-
|
|
144
|
+
const needsProject = type === "git-diff" || type === "terminal" || type === "chat";
|
|
120
145
|
const metadata = needsProject ? { projectName: activeProjectForTab?.name } : undefined;
|
|
121
146
|
state.openTab(
|
|
122
147
|
{ type, title: NEW_TAB_LABELS[type] ?? type, metadata, projectId: activeProjectForTab?.name ?? null, closable: true },
|
|
@@ -186,7 +211,7 @@ export function MobileNav({ onMenuPress, onProjectsPress }: MobileNavProps) {
|
|
|
186
211
|
<div className="flex-1 min-w-0 relative flex items-center h-12 -ml-4">
|
|
187
212
|
<div ref={mobileScrollRef} className="flex-1 min-w-0 flex items-center h-12 overflow-x-auto scrollbar-none pl-4">
|
|
188
213
|
{tabs.map((tab) => {
|
|
189
|
-
const Icon = TAB_ICONS[tab.type];
|
|
214
|
+
const Icon = TAB_ICONS[tab.type] || Puzzle;
|
|
190
215
|
const isActive = tab.id === activeTabId;
|
|
191
216
|
const sessionId = tab.type === "chat" ? (tab.metadata?.sessionId as string) : undefined;
|
|
192
217
|
const entry = sessionId ? notifications.get(sessionId) : undefined;
|
|
@@ -269,13 +294,40 @@ export function MobileNav({ onMenuPress, onProjectsPress }: MobileNavProps) {
|
|
|
269
294
|
<div className="px-3 py-2 text-xs text-text-secondary border-b border-border truncate">
|
|
270
295
|
{menuTab.title}
|
|
271
296
|
</div>
|
|
297
|
+
{menuTab.type === "editor" && (
|
|
298
|
+
<>
|
|
299
|
+
<button onClick={() => handleFileAction(menuTab, "copy-path")}
|
|
300
|
+
className="flex items-center gap-2 w-full px-3 py-2.5 text-sm text-foreground active:bg-surface-elevated">
|
|
301
|
+
<Copy className="size-4" /> Copy Path
|
|
302
|
+
</button>
|
|
303
|
+
<button onClick={() => handleFileAction(menuTab, "download")}
|
|
304
|
+
className="flex items-center gap-2 w-full px-3 py-2.5 text-sm text-foreground active:bg-surface-elevated">
|
|
305
|
+
<Download className="size-4" /> Download
|
|
306
|
+
</button>
|
|
307
|
+
<button onClick={() => handleFileAction(menuTab, "rename")}
|
|
308
|
+
className="flex items-center gap-2 w-full px-3 py-2.5 text-sm text-foreground active:bg-surface-elevated">
|
|
309
|
+
<Pencil className="size-4" /> Rename
|
|
310
|
+
</button>
|
|
311
|
+
<button onClick={() => handleFileAction(menuTab, "delete")}
|
|
312
|
+
className="flex items-center gap-2 w-full px-3 py-2.5 text-sm text-error active:bg-surface-elevated">
|
|
313
|
+
<Trash2 className="size-4" /> Delete
|
|
314
|
+
</button>
|
|
315
|
+
<div className="h-px bg-border mx-2" />
|
|
316
|
+
</>
|
|
317
|
+
)}
|
|
318
|
+
{menuTab.closable && (
|
|
319
|
+
<button onClick={() => { usePanelStore.getState().closeTab(menuTabId!); setMenuTabId(null); }}
|
|
320
|
+
className="flex items-center gap-2 w-full px-3 py-2.5 text-sm text-foreground active:bg-surface-elevated">
|
|
321
|
+
<X className="size-4" /> Close
|
|
322
|
+
</button>
|
|
323
|
+
)}
|
|
272
324
|
{menuTabIdx > 0 && (
|
|
273
325
|
<button onClick={() => { moveTabLeft(menuTabId!); setMenuTabId(null); }}
|
|
274
326
|
className="flex items-center gap-2 w-full px-3 py-2.5 text-sm text-foreground active:bg-surface-elevated">
|
|
275
327
|
<ArrowLeft className="size-4" /> Move Left
|
|
276
328
|
</button>
|
|
277
329
|
)}
|
|
278
|
-
{menuTabIdx <
|
|
330
|
+
{menuTabIdx < menuTabPanelTabs.length - 1 && (
|
|
279
331
|
<button onClick={() => { moveTabRight(menuTabId!); setMenuTabId(null); }}
|
|
280
332
|
className="flex items-center gap-2 w-full px-3 py-2.5 text-sm text-foreground active:bg-surface-elevated">
|
|
281
333
|
<ArrowRight className="size-4" /> Move Right
|
|
@@ -296,6 +348,21 @@ export function MobileNav({ onMenuPress, onProjectsPress }: MobileNavProps) {
|
|
|
296
348
|
</div>
|
|
297
349
|
</>
|
|
298
350
|
)}
|
|
351
|
+
|
|
352
|
+
{fileActionState && (
|
|
353
|
+
<FileActions
|
|
354
|
+
action={fileActionState.action}
|
|
355
|
+
node={fileActionState.node}
|
|
356
|
+
projectName={activeProjectForTab?.name ?? ""}
|
|
357
|
+
onClose={() => setFileActionState(null)}
|
|
358
|
+
onRefresh={() => {
|
|
359
|
+
if (activeProjectForTab) useFileStore.getState().fetchTree(activeProjectForTab.name);
|
|
360
|
+
if (fileActionState.action === "delete") {
|
|
361
|
+
usePanelStore.getState().closeTab(fileActionState.tabId);
|
|
362
|
+
}
|
|
363
|
+
}}
|
|
364
|
+
/>
|
|
365
|
+
)}
|
|
299
366
|
</nav>
|
|
300
367
|
);
|
|
301
368
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { useEffect, useRef, useCallback } from "react";
|
|
1
|
+
import { useEffect, useRef, useCallback, useState } from "react";
|
|
2
2
|
import {
|
|
3
3
|
Plus,
|
|
4
4
|
Terminal,
|
|
5
5
|
MessageSquare,
|
|
6
|
-
GitBranch,
|
|
7
6
|
FileDiff,
|
|
8
7
|
Settings,
|
|
9
8
|
FileCode,
|
|
@@ -16,6 +15,7 @@ import {
|
|
|
16
15
|
import { useTabStore, type TabType } from "@/stores/tab-store";
|
|
17
16
|
import { usePanelStore } from "@/stores/panel-store";
|
|
18
17
|
import { useProjectStore } from "@/stores/project-store";
|
|
18
|
+
import { useFileStore, type FileNode } from "@/stores/file-store";
|
|
19
19
|
import { useTabDrag } from "@/hooks/use-tab-drag";
|
|
20
20
|
import { useTouchTabDrag, wasTouchDragRecent } from "@/hooks/use-touch-tab-drag";
|
|
21
21
|
import { openCommandPalette } from "@/hooks/use-global-keybindings";
|
|
@@ -25,6 +25,8 @@ import { useTabOverflow, getHiddenUnreadDirection } from "@/hooks/use-tab-overfl
|
|
|
25
25
|
import { DraggableTab } from "./draggable-tab";
|
|
26
26
|
import { cn } from "@/lib/utils";
|
|
27
27
|
import type { Tab } from "@/stores/tab-store";
|
|
28
|
+
import { downloadFile } from "@/lib/file-download";
|
|
29
|
+
import { FileActions } from "@/components/explorer/file-actions";
|
|
28
30
|
|
|
29
31
|
const TAB_ICONS: Record<TabType, React.ElementType> = {
|
|
30
32
|
terminal: Terminal,
|
|
@@ -33,10 +35,10 @@ const TAB_ICONS: Record<TabType, React.ElementType> = {
|
|
|
33
35
|
database: Database,
|
|
34
36
|
sqlite: Database,
|
|
35
37
|
postgres: Database,
|
|
36
|
-
"git-graph": GitBranch,
|
|
37
38
|
"git-diff": FileDiff,
|
|
38
39
|
settings: Settings,
|
|
39
40
|
ports: Globe,
|
|
41
|
+
extension: Puzzle,
|
|
40
42
|
"extension-webview": Puzzle,
|
|
41
43
|
};
|
|
42
44
|
|
|
@@ -86,6 +88,56 @@ export function TabBar({ panelId }: TabBarProps) {
|
|
|
86
88
|
}
|
|
87
89
|
}, []);
|
|
88
90
|
|
|
91
|
+
// File action dialog state for tab context menu (rename/delete)
|
|
92
|
+
const [fileActionState, setFileActionState] = useState<{ action: string; node: FileNode; tabId: string } | null>(null);
|
|
93
|
+
|
|
94
|
+
/** Handle context menu actions on a tab */
|
|
95
|
+
const handleTabContextAction = useCallback((tab: Tab, action: string) => {
|
|
96
|
+
const panelState = usePanelStore.getState();
|
|
97
|
+
const pTabs = panelState.panels[effectivePanelId]?.tabs ?? [];
|
|
98
|
+
|
|
99
|
+
switch (action) {
|
|
100
|
+
case "close":
|
|
101
|
+
panelState.closeTab(tab.id, effectivePanelId);
|
|
102
|
+
break;
|
|
103
|
+
case "close-others":
|
|
104
|
+
for (const t of pTabs) {
|
|
105
|
+
if (t.id !== tab.id && t.closable) panelState.closeTab(t.id, effectivePanelId);
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
case "close-right": {
|
|
109
|
+
const idx = pTabs.findIndex((t) => t.id === tab.id);
|
|
110
|
+
for (let i = idx + 1; i < pTabs.length; i++) {
|
|
111
|
+
if (pTabs[i]!.closable) panelState.closeTab(pTabs[i]!.id, effectivePanelId);
|
|
112
|
+
}
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
case "copy-path": {
|
|
116
|
+
const filePath = tab.metadata?.filePath as string | undefined;
|
|
117
|
+
if (filePath) navigator.clipboard.writeText(filePath).catch(() => {});
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
case "download": {
|
|
121
|
+
const filePath = tab.metadata?.filePath as string | undefined;
|
|
122
|
+
const projectName = tab.metadata?.projectName as string | undefined;
|
|
123
|
+
if (filePath && projectName) downloadFile(projectName, filePath);
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
case "rename":
|
|
127
|
+
case "delete": {
|
|
128
|
+
const filePath = tab.metadata?.filePath as string | undefined;
|
|
129
|
+
if (filePath) {
|
|
130
|
+
setFileActionState({
|
|
131
|
+
action,
|
|
132
|
+
tabId: tab.id,
|
|
133
|
+
node: { name: tab.title, path: filePath, type: "file" },
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}, [effectivePanelId]);
|
|
140
|
+
|
|
89
141
|
/** Double-click on empty bar area → open command palette */
|
|
90
142
|
function handleBarDoubleClick(e: React.MouseEvent) {
|
|
91
143
|
// Only trigger if clicking directly on the bar or scroll container (not on a tab)
|
|
@@ -103,6 +155,7 @@ export function TabBar({ panelId }: TabBarProps) {
|
|
|
103
155
|
}
|
|
104
156
|
|
|
105
157
|
return (
|
|
158
|
+
<>
|
|
106
159
|
<div
|
|
107
160
|
className="hidden md:flex items-center h-10 border-b border-border bg-background relative"
|
|
108
161
|
onDragOver={handleDragOverBar}
|
|
@@ -140,7 +193,7 @@ export function TabBar({ panelId }: TabBarProps) {
|
|
|
140
193
|
key={tab.id}
|
|
141
194
|
tab={tab}
|
|
142
195
|
isActive={tab.id === activeTabId}
|
|
143
|
-
icon={TAB_ICONS[tab.type]}
|
|
196
|
+
icon={TAB_ICONS[tab.type] || Puzzle}
|
|
144
197
|
showDropBefore={dropIndex === i}
|
|
145
198
|
notificationType={notiType}
|
|
146
199
|
onSelect={() => {
|
|
@@ -160,6 +213,7 @@ export function TabBar({ panelId }: TabBarProps) {
|
|
|
160
213
|
else tabRefs.current.delete(tab.id);
|
|
161
214
|
}}
|
|
162
215
|
onRename={tab.type === "chat" ? (title) => handleRenameTab(tab, title) : undefined}
|
|
216
|
+
onContextAction={(action) => handleTabContextAction(tab, action)}
|
|
163
217
|
/>
|
|
164
218
|
);
|
|
165
219
|
})}
|
|
@@ -194,5 +248,23 @@ export function TabBar({ panelId }: TabBarProps) {
|
|
|
194
248
|
</button>
|
|
195
249
|
)}
|
|
196
250
|
</div>
|
|
251
|
+
|
|
252
|
+
{fileActionState && (
|
|
253
|
+
<FileActions
|
|
254
|
+
action={fileActionState.action}
|
|
255
|
+
node={fileActionState.node}
|
|
256
|
+
projectName={activeProject?.name ?? ""}
|
|
257
|
+
onClose={() => setFileActionState(null)}
|
|
258
|
+
onRefresh={() => {
|
|
259
|
+
if (activeProject) useFileStore.getState().fetchTree(activeProject.name);
|
|
260
|
+
// Close tab after file deletion (onRefresh only called on success)
|
|
261
|
+
if (fileActionState.action === "delete") {
|
|
262
|
+
usePanelStore.getState().closeTab(fileActionState.tabId, effectivePanelId);
|
|
263
|
+
}
|
|
264
|
+
}}
|
|
265
|
+
/>
|
|
266
|
+
)}
|
|
267
|
+
</>
|
|
197
268
|
);
|
|
198
269
|
}
|
|
270
|
+
|
|
@@ -33,11 +33,6 @@ const TAB_COMPONENTS: Record<TabType, React.LazyExoticComponent<React.ComponentT
|
|
|
33
33
|
default: m.PostgresViewer,
|
|
34
34
|
})),
|
|
35
35
|
),
|
|
36
|
-
"git-graph": lazy(() =>
|
|
37
|
-
import("@/components/git/git-graph").then((m) => ({
|
|
38
|
-
default: m.GitGraph,
|
|
39
|
-
})),
|
|
40
|
-
),
|
|
41
36
|
"git-diff": lazy(() =>
|
|
42
37
|
import("@/components/editor/diff-viewer").then((m) => ({
|
|
43
38
|
default: m.DiffViewer,
|
|
@@ -53,6 +48,11 @@ const TAB_COMPONENTS: Record<TabType, React.LazyExoticComponent<React.ComponentT
|
|
|
53
48
|
default: m.PortForwardingTab,
|
|
54
49
|
})),
|
|
55
50
|
),
|
|
51
|
+
extension: lazy(() =>
|
|
52
|
+
import("@/components/extensions/extension-webview").then((m) => ({
|
|
53
|
+
default: m.ExtensionWebview,
|
|
54
|
+
})),
|
|
55
|
+
),
|
|
56
56
|
"extension-webview": lazy(() =>
|
|
57
57
|
import("@/components/extensions/extension-webview").then((m) => ({
|
|
58
58
|
default: m.ExtensionWebview,
|
|
@@ -84,6 +84,13 @@ export function TabContent() {
|
|
|
84
84
|
{tabs.map((tab) => {
|
|
85
85
|
const Component = TAB_COMPONENTS[tab.type];
|
|
86
86
|
const isActive = tab.id === activeTabId;
|
|
87
|
+
if (!Component) {
|
|
88
|
+
return (
|
|
89
|
+
<div key={tab.id} className={isActive ? "h-full w-full flex items-center justify-center text-muted-foreground" : "hidden"}>
|
|
90
|
+
Unknown tab type: {tab.type}
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
87
94
|
return (
|
|
88
95
|
<div
|
|
89
96
|
key={tab.id}
|
|
@@ -96,6 +96,9 @@ export function UpgradeBanner({ onVisibilityChange }: UpgradeBannerProps) {
|
|
|
96
96
|
// No supervisor — manual restart needed
|
|
97
97
|
toast.info(data.message || "Upgrade installed. Restart PPM manually.");
|
|
98
98
|
setUpgrading(false);
|
|
99
|
+
if (availableVersion) {
|
|
100
|
+
sessionStorage.setItem(DISMISS_KEY_PREFIX + availableVersion, "1");
|
|
101
|
+
}
|
|
99
102
|
setDismissed(true);
|
|
100
103
|
}
|
|
101
104
|
} catch (e) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState, useEffect, useCallback, useRef } from "react";
|
|
2
|
-
import { RotateCcw, AlertTriangle, Lock } from "lucide-react";
|
|
2
|
+
import { RotateCcw, AlertTriangle, Lock, Puzzle } from "lucide-react";
|
|
3
3
|
import { Button } from "@/components/ui/button";
|
|
4
4
|
import {
|
|
5
5
|
KEY_ACTIONS,
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
comboFromEvent,
|
|
9
9
|
type KeyCategory,
|
|
10
10
|
} from "@/stores/keybindings-store";
|
|
11
|
+
import { useExtensionStore } from "@/stores/extension-store";
|
|
11
12
|
|
|
12
13
|
const CATEGORIES: { key: KeyCategory; label: string }[] = [
|
|
13
14
|
{ key: "general", label: "General" },
|
|
@@ -104,6 +105,9 @@ function ShortcutBadge({
|
|
|
104
105
|
|
|
105
106
|
export function KeyboardShortcutsSection() {
|
|
106
107
|
const { getBinding, resetBinding, resetAll, overrides } = useKeybindingsStore();
|
|
108
|
+
const extContributions = useExtensionStore((s) => s.contributions);
|
|
109
|
+
const extKeybindings = extContributions?.keybindings ?? [];
|
|
110
|
+
const extCommands = extContributions?.commands ?? [];
|
|
107
111
|
|
|
108
112
|
return (
|
|
109
113
|
<div className="space-y-3">
|
|
@@ -177,6 +181,47 @@ export function KeyboardShortcutsSection() {
|
|
|
177
181
|
</div>
|
|
178
182
|
);
|
|
179
183
|
})}
|
|
184
|
+
|
|
185
|
+
{/* Extension-contributed keybindings */}
|
|
186
|
+
{extKeybindings.length > 0 && (
|
|
187
|
+
<div className="space-y-1">
|
|
188
|
+
<span className="text-[10px] font-medium text-muted-foreground uppercase tracking-wide">
|
|
189
|
+
Extensions
|
|
190
|
+
</span>
|
|
191
|
+
<div className="space-y-0.5">
|
|
192
|
+
{extKeybindings.map((kb) => {
|
|
193
|
+
const cmd = extCommands.find((c) => c.command === kb.command);
|
|
194
|
+
const label = cmd?.title ?? kb.command;
|
|
195
|
+
const actionId = `ext:${kb.command}`;
|
|
196
|
+
const currentCombo = getBinding(actionId) || kb.key;
|
|
197
|
+
const isOverridden = actionId in overrides;
|
|
198
|
+
return (
|
|
199
|
+
<div
|
|
200
|
+
key={actionId}
|
|
201
|
+
className="flex items-center justify-between py-1 px-1 rounded hover:bg-surface-elevated/50 transition-colors"
|
|
202
|
+
>
|
|
203
|
+
<div className="flex items-center gap-1.5 min-w-0">
|
|
204
|
+
<Puzzle className="size-3 text-muted-foreground shrink-0" />
|
|
205
|
+
<span className="text-xs text-foreground">{label}</span>
|
|
206
|
+
</div>
|
|
207
|
+
<div className="flex items-center gap-1 shrink-0 ml-2">
|
|
208
|
+
<ShortcutBadge actionId={actionId} combo={currentCombo} />
|
|
209
|
+
{isOverridden && (
|
|
210
|
+
<button
|
|
211
|
+
onClick={() => resetBinding(actionId)}
|
|
212
|
+
className="flex items-center justify-center size-5 rounded text-muted-foreground hover:text-foreground hover:bg-surface-elevated transition-colors"
|
|
213
|
+
title="Reset to default"
|
|
214
|
+
>
|
|
215
|
+
<RotateCcw className="size-3" />
|
|
216
|
+
</button>
|
|
217
|
+
)}
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
);
|
|
221
|
+
})}
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
)}
|
|
180
225
|
</div>
|
|
181
226
|
);
|
|
182
227
|
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { useState, useEffect, useRef, type ReactNode } from "react";
|
|
2
|
+
import mermaid from "mermaid";
|
|
3
|
+
import { useMdContext, FILE_EXT_RE, GLOB_CHARS_RE } from "./markdown-context";
|
|
4
|
+
import { useTabStore } from "@/stores/tab-store";
|
|
5
|
+
|
|
6
|
+
const MERMAID_KEYWORDS = /^(sequenceDiagram|flowchart|graph\s|classDiagram|stateDiagram|erDiagram|journey|gantt|pie|quadrantChart|requirementDiagram|gitGraph|mindmap|timeline|sankey|xychart|block-beta|packet-beta|architecture-beta|kanban)\b/;
|
|
7
|
+
|
|
8
|
+
let mermaidInitialized = false;
|
|
9
|
+
function ensureMermaidInit() {
|
|
10
|
+
if (mermaidInitialized) return;
|
|
11
|
+
mermaid.initialize({ startOnLoad: false, theme: "default", securityLevel: "loose", fontFamily: "ui-sans-serif, system-ui, sans-serif" });
|
|
12
|
+
mermaidInitialized = true;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Extract plain text from a hast node tree */
|
|
16
|
+
function hastToText(node: any): string {
|
|
17
|
+
if (!node) return "";
|
|
18
|
+
if (node.type === "text") return node.value ?? "";
|
|
19
|
+
if (node.children) return node.children.map(hastToText).join("");
|
|
20
|
+
return "";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Pre — code block wrapper with mermaid detection and action buttons */
|
|
24
|
+
export function MdPre({ children, node, ...rest }: any) {
|
|
25
|
+
const { codeActions, projectName, openDiagramOverlay } = useMdContext();
|
|
26
|
+
const openTab = useTabStore((s) => s.openTab);
|
|
27
|
+
|
|
28
|
+
const codeNode = node?.children?.[0];
|
|
29
|
+
const langClass = (codeNode?.properties?.className ?? []).find((c: string) => c.startsWith("language-"));
|
|
30
|
+
const lang = langClass?.replace("language-", "");
|
|
31
|
+
const text = hastToText(codeNode);
|
|
32
|
+
|
|
33
|
+
// Mermaid detection
|
|
34
|
+
if (lang === "mermaid" || (!lang && MERMAID_KEYWORDS.test(text.trim()))) {
|
|
35
|
+
return <MermaidDiagram source={text.trim()} />;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const isBash = /^(bash|sh|shell|zsh)$/.test(lang || "") || (!lang && text.startsWith("$"));
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<pre {...rest} className={`relative group ${rest.className || ""}`}>
|
|
42
|
+
{children}
|
|
43
|
+
{codeActions && (
|
|
44
|
+
<div className="code-actions absolute top-1 right-1 flex gap-1">
|
|
45
|
+
<ActionBtn title="Copy" icon={<CopyIcon />} activeIcon={<CheckIcon />} onClick={() => navigator.clipboard.writeText(text)} />
|
|
46
|
+
{isBash && projectName && (
|
|
47
|
+
<ActionBtn
|
|
48
|
+
title="Run in terminal"
|
|
49
|
+
icon={<PlayIcon />}
|
|
50
|
+
onClick={() => {
|
|
51
|
+
navigator.clipboard.writeText(text.replace(/^\$\s*/gm, ""));
|
|
52
|
+
openTab({ type: "terminal", title: "Terminal", metadata: { projectName }, projectId: projectName, closable: true });
|
|
53
|
+
}}
|
|
54
|
+
/>
|
|
55
|
+
)}
|
|
56
|
+
</div>
|
|
57
|
+
)}
|
|
58
|
+
</pre>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Code — inline code with file clicking; block code passes through */
|
|
63
|
+
export function MdCode({ className, children, node, ...rest }: any) {
|
|
64
|
+
const { openFileOrSearch } = useMdContext();
|
|
65
|
+
|
|
66
|
+
// Block code (has language/hljs class from rehype-highlight) — render as-is
|
|
67
|
+
if (className) return <code className={className} {...rest}>{children}</code>;
|
|
68
|
+
|
|
69
|
+
// Inline code — check for clickable file paths
|
|
70
|
+
const text = String(children ?? "").trim();
|
|
71
|
+
if (text && !text.includes(" ") && !GLOB_CHARS_RE.test(text) && FILE_EXT_RE.test(text)) {
|
|
72
|
+
return (
|
|
73
|
+
<code
|
|
74
|
+
onClick={() => openFileOrSearch(text)}
|
|
75
|
+
style={{ cursor: "pointer", textDecoration: "underline", textDecorationStyle: "dotted" as const }}
|
|
76
|
+
{...rest}
|
|
77
|
+
>
|
|
78
|
+
{children}
|
|
79
|
+
</code>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return <code {...rest}>{children}</code>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Mermaid diagram renderer with click-to-expand */
|
|
87
|
+
function MermaidDiagram({ source }: { source: string }) {
|
|
88
|
+
const { openDiagramOverlay } = useMdContext();
|
|
89
|
+
const [svg, setSvg] = useState<string | null>(null);
|
|
90
|
+
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
ensureMermaidInit();
|
|
93
|
+
const id = `mermaid-${Math.random().toString(36).slice(2, 9)}`;
|
|
94
|
+
mermaid.render(id, source).then(({ svg }) => setSvg(svg)).catch(() => {});
|
|
95
|
+
}, [source]);
|
|
96
|
+
|
|
97
|
+
if (!svg) return <pre><code>{source}</code></pre>;
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<div
|
|
101
|
+
className="mermaid-diagram group relative cursor-pointer rounded-lg border border-border bg-white dark:bg-zinc-50 p-3 overflow-x-auto my-2"
|
|
102
|
+
onClick={() => openDiagramOverlay(svg)}
|
|
103
|
+
>
|
|
104
|
+
<div dangerouslySetInnerHTML={{ __html: svg }} />
|
|
105
|
+
<div className="absolute top-2 right-2 flex items-center gap-1 px-2 py-1 rounded bg-black/60 text-white text-xs can-hover:opacity-0 can-hover:group-hover:opacity-100 transition-opacity pointer-events-none">
|
|
106
|
+
Click to expand
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Reusable code-block action button with optional active state */
|
|
113
|
+
function ActionBtn({ title, icon, activeIcon, onClick }: { title: string; icon: ReactNode; activeIcon?: ReactNode; onClick: () => void }) {
|
|
114
|
+
const [active, setActive] = useState(false);
|
|
115
|
+
return (
|
|
116
|
+
<button
|
|
117
|
+
className="flex items-center justify-center size-6 rounded bg-surface-elevated/80 hover:bg-surface-elevated text-text-secondary hover:text-text-primary transition-colors border border-border/50"
|
|
118
|
+
title={title}
|
|
119
|
+
onClick={() => { onClick(); if (activeIcon) { setActive(true); setTimeout(() => setActive(false), 2000); } }}
|
|
120
|
+
>
|
|
121
|
+
{active && activeIcon ? activeIcon : icon}
|
|
122
|
+
</button>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const CopyIcon = () => (
|
|
127
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
128
|
+
<rect width="14" height="14" x="8" y="8" rx="2" ry="2" /><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
|
|
129
|
+
</svg>
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const CheckIcon = () => (
|
|
133
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
134
|
+
<polyline points="20 6 9 17 4 12" />
|
|
135
|
+
</svg>
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const PlayIcon = () => (
|
|
139
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
140
|
+
<polyline points="4 17 10 11 4 5" /><line x1="12" y1="19" x2="20" y2="19" />
|
|
141
|
+
</svg>
|
|
142
|
+
);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
|
|
3
|
+
/** Common text file extensions that PPM can open as editor tabs */
|
|
4
|
+
const FILE_EXTS = "ts|tsx|js|jsx|mjs|cjs|py|json|md|mdx|yaml|yml|toml|css|scss|less|html|htm|sh|bash|zsh|go|rs|sql|rb|java|kt|swift|c|cpp|h|hpp|cs|vue|svelte|txt|env|cfg|conf|ini|xml|csv|log|dockerfile|makefile|gradle";
|
|
5
|
+
export const FILE_EXT_RE = new RegExp(`\\.(${FILE_EXTS})$`, "i");
|
|
6
|
+
/** Glob/regex chars that indicate a pattern, not a real file */
|
|
7
|
+
export const GLOB_CHARS_RE = /[*?{}\[\]]/;
|
|
8
|
+
/** Detect local absolute file paths (Unix or Windows) */
|
|
9
|
+
export const LOCAL_PATH_RE = /^(\/|[A-Za-z]:[/\\])/;
|
|
10
|
+
|
|
11
|
+
export interface MdContextValue {
|
|
12
|
+
projectName?: string;
|
|
13
|
+
codeActions: boolean;
|
|
14
|
+
openFileOrSearch: (path: string) => void;
|
|
15
|
+
openImageOverlay: (url: string, alt: string) => void;
|
|
16
|
+
openDiagramOverlay: (svg: string) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const MdContext = createContext<MdContextValue>(null!);
|
|
20
|
+
export const useMdContext = () => useContext(MdContext);
|