@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,6 +1,11 @@
|
|
|
1
|
-
import { useMemo,
|
|
2
|
-
import
|
|
3
|
-
import
|
|
1
|
+
import { useMemo, useCallback, useState, useEffect } from "react";
|
|
2
|
+
import ReactMarkdown from "react-markdown";
|
|
3
|
+
import remarkGfm from "remark-gfm";
|
|
4
|
+
import remarkMath from "remark-math";
|
|
5
|
+
import remarkBreaks from "remark-breaks";
|
|
6
|
+
import rehypeRaw from "rehype-raw";
|
|
7
|
+
import rehypeKatex from "rehype-katex";
|
|
8
|
+
import rehypeHighlight from "rehype-highlight";
|
|
4
9
|
import { useTabStore } from "@/stores/tab-store";
|
|
5
10
|
import { useFileStore, type FileNode } from "@/stores/file-store";
|
|
6
11
|
import { useImageOverlay } from "@/stores/image-overlay-store";
|
|
@@ -8,35 +13,8 @@ import { useDiagramOverlay } from "@/stores/diagram-overlay-store";
|
|
|
8
13
|
import { openCommandPalette } from "@/hooks/use-global-keybindings";
|
|
9
14
|
import { api, projectUrl, getAuthToken } from "@/lib/api-client";
|
|
10
15
|
import { basename } from "@/lib/utils";
|
|
11
|
-
import
|
|
12
|
-
|
|
13
|
-
/** Mermaid keywords that start a diagram definition */
|
|
14
|
-
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/;
|
|
15
|
-
|
|
16
|
-
let mermaidInitialized = false;
|
|
17
|
-
function ensureMermaidInit() {
|
|
18
|
-
if (mermaidInitialized) return;
|
|
19
|
-
mermaid.initialize({
|
|
20
|
-
startOnLoad: false,
|
|
21
|
-
theme: "default",
|
|
22
|
-
securityLevel: "loose",
|
|
23
|
-
fontFamily: "ui-sans-serif, system-ui, sans-serif",
|
|
24
|
-
});
|
|
25
|
-
mermaidInitialized = true;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** Detect local absolute file paths (Unix or Windows) */
|
|
29
|
-
const LOCAL_PATH_RE = /^(\/|[A-Za-z]:[/\\])/;
|
|
30
|
-
|
|
31
|
-
// Configure marked globally
|
|
32
|
-
marked.use({ gfm: true, breaks: true });
|
|
33
|
-
marked.use(markedKatex({ throwOnError: false }));
|
|
34
|
-
|
|
35
|
-
/** Common text file extensions that PPM can open as editor tabs */
|
|
36
|
-
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";
|
|
37
|
-
const FILE_EXT_RE = new RegExp(`\\.(${FILE_EXTS})$`, "i");
|
|
38
|
-
/** Glob/regex chars that indicate a pattern, not a real file */
|
|
39
|
-
const GLOB_CHARS_RE = /[*?{}\[\]]/;
|
|
16
|
+
import { MdContext, useMdContext, FILE_EXT_RE, GLOB_CHARS_RE, LOCAL_PATH_RE } from "./markdown-context";
|
|
17
|
+
import { MdPre, MdCode } from "./markdown-code-block";
|
|
40
18
|
|
|
41
19
|
interface MarkdownRendererProps {
|
|
42
20
|
content: string;
|
|
@@ -46,286 +24,133 @@ interface MarkdownRendererProps {
|
|
|
46
24
|
isStreaming?: boolean;
|
|
47
25
|
}
|
|
48
26
|
|
|
49
|
-
/**
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
* - Add target=_blank to external links
|
|
53
|
-
* - Mark <a> file paths with data-file-path
|
|
54
|
-
* - Make inline <code> with file names clickable (via HTML transform, not DOM)
|
|
55
|
-
*/
|
|
56
|
-
function transformHtml(raw: string): string {
|
|
57
|
-
let html = raw;
|
|
27
|
+
/** Plugin arrays — stable references to avoid re-creating on each render */
|
|
28
|
+
const remarkPlugins = [remarkGfm, remarkMath, remarkBreaks] as any;
|
|
29
|
+
const rehypePlugins = [rehypeRaw, rehypeKatex, rehypeHighlight] as any;
|
|
58
30
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
html = html.replace(/<\/table>/g, "</table></div>");
|
|
31
|
+
/** Component map — stable references; dynamic state flows through MdContext */
|
|
32
|
+
const mdComponents = { a: MdLink, img: MdImage, pre: MdPre, code: MdCode, table: MdTable };
|
|
62
33
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
html = html.replace(/<a\s+href="([^"]+)"/g, (match, href: string) => {
|
|
71
|
-
if (/^https?:\/\//.test(href)) return match; // already handled
|
|
72
|
-
if (GLOB_CHARS_RE.test(href)) return match; // skip glob/regex patterns
|
|
73
|
-
if (!FILE_EXT_RE.test(href)) return match; // must have a file extension
|
|
74
|
-
return `<a href="${href}" data-file-path="${href}"`;
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
// Inline <code> with file-like names → make clickable
|
|
78
|
-
// Split by <pre>...</pre> blocks to avoid transforming code inside them
|
|
79
|
-
const parts = html.split(/(<pre[\s\S]*?<\/pre>)/g);
|
|
80
|
-
html = parts.map((part) => {
|
|
81
|
-
// Skip <pre> blocks
|
|
82
|
-
if (part.startsWith("<pre")) return part;
|
|
83
|
-
// Transform inline <code> in non-pre content
|
|
84
|
-
return part.replace(
|
|
85
|
-
/<code>([^<]+)<\/code>/g,
|
|
86
|
-
(match, text: string) => {
|
|
87
|
-
const trimmed = text.trim();
|
|
88
|
-
if (!trimmed || trimmed.includes(" ")) return match;
|
|
89
|
-
if (GLOB_CHARS_RE.test(trimmed)) return match; // skip glob/regex patterns
|
|
90
|
-
if (!FILE_EXT_RE.test(trimmed)) return match; // must have a file extension
|
|
91
|
-
return `<code data-file-clickable="${trimmed}" style="cursor:pointer;text-decoration:underline;text-decoration-style:dotted">${text}</code>`;
|
|
92
|
-
},
|
|
93
|
-
);
|
|
94
|
-
}).join("");
|
|
95
|
-
|
|
96
|
-
return html;
|
|
34
|
+
function findInTree(nodes: FileNode[], name: string): string[] {
|
|
35
|
+
const results: string[] = [];
|
|
36
|
+
for (const n of nodes) {
|
|
37
|
+
if (n.type === "file" && n.name === name) results.push(n.path);
|
|
38
|
+
if (n.children) results.push(...findInTree(n.children, name));
|
|
39
|
+
}
|
|
40
|
+
return results;
|
|
97
41
|
}
|
|
98
42
|
|
|
99
43
|
export function MarkdownRenderer({ content, projectName, className = "", codeActions = false, isStreaming = false }: MarkdownRendererProps) {
|
|
100
|
-
const html = useMemo(() => {
|
|
101
|
-
try {
|
|
102
|
-
const raw = marked.parse(content) as string;
|
|
103
|
-
return transformHtml(raw);
|
|
104
|
-
} catch {
|
|
105
|
-
return content;
|
|
106
|
-
}
|
|
107
|
-
}, [content]);
|
|
108
|
-
|
|
109
|
-
const containerRef = useRef<HTMLDivElement>(null);
|
|
110
44
|
const openTab = useTabStore((s) => s.openTab);
|
|
111
45
|
const fileTree = useFileStore((s) => s.tree);
|
|
112
|
-
const
|
|
113
|
-
const
|
|
46
|
+
const openImageOverlayFn = useImageOverlay((s) => s.open);
|
|
47
|
+
const openDiagramOverlayFn = useDiagramOverlay((s) => s.open);
|
|
114
48
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const renderMermaid = async () => {
|
|
121
|
-
ensureMermaidInit();
|
|
122
|
-
const pres = container.querySelectorAll("pre");
|
|
123
|
-
for (const pre of pres) {
|
|
124
|
-
const code = pre.querySelector("code");
|
|
125
|
-
if (!code) continue;
|
|
126
|
-
const langClass = code.className ?? "";
|
|
127
|
-
const text = (code.textContent ?? "").trim();
|
|
128
|
-
const isMermaid = langClass.includes("language-mermaid") || MERMAID_KEYWORDS.test(text);
|
|
129
|
-
if (!isMermaid) continue;
|
|
130
|
-
|
|
131
|
-
try {
|
|
132
|
-
const id = `mermaid-${Math.random().toString(36).slice(2, 9)}`;
|
|
133
|
-
const { svg } = await mermaid.render(id, text);
|
|
134
|
-
const wrapper = document.createElement("div");
|
|
135
|
-
wrapper.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";
|
|
136
|
-
wrapper.innerHTML = svg;
|
|
137
|
-
// Expand icon hint
|
|
138
|
-
const hint = document.createElement("div");
|
|
139
|
-
hint.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";
|
|
140
|
-
hint.textContent = "Click to expand";
|
|
141
|
-
wrapper.appendChild(hint);
|
|
142
|
-
// Click to open overlay
|
|
143
|
-
wrapper.addEventListener("click", () => openDiagramOverlay(svg));
|
|
144
|
-
pre.replaceWith(wrapper);
|
|
145
|
-
} catch {
|
|
146
|
-
// Render failed — leave as code block
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
};
|
|
150
|
-
renderMermaid();
|
|
151
|
-
|
|
152
|
-
// --- Click handler for file links and clickable code ---
|
|
153
|
-
const handleClick = (e: MouseEvent) => {
|
|
154
|
-
const target = e.target as HTMLElement;
|
|
49
|
+
const openFileOrSearch = useCallback((filePath: string) => {
|
|
50
|
+
if (!filePath) return;
|
|
51
|
+
const isAbsolute = /^(\/|[A-Za-z]:[/\\])/.test(filePath);
|
|
52
|
+
const isRelative = /^(\.\/|\.\.\/)/.test(filePath);
|
|
53
|
+
const fileName = basename(filePath);
|
|
155
54
|
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
if (
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Check clickable <code>
|
|
165
|
-
const code = target.closest("code[data-file-clickable]") as HTMLElement | null;
|
|
166
|
-
if (code && container.contains(code)) {
|
|
167
|
-
openFileOrSearch(code.getAttribute("data-file-clickable") ?? "");
|
|
168
|
-
return;
|
|
55
|
+
const searchAndOpen = (fp: string) => {
|
|
56
|
+
const matches = findInTree(fileTree, basename(fp));
|
|
57
|
+
if (matches.length === 1) {
|
|
58
|
+
openTab({ type: "editor", title: basename(fp), metadata: { filePath: matches[0], projectName }, projectId: projectName ?? null, closable: true });
|
|
59
|
+
} else {
|
|
60
|
+
openCommandPalette(fp);
|
|
169
61
|
}
|
|
170
62
|
};
|
|
171
63
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
return results;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function openFileOrSearch(filePath: string) {
|
|
183
|
-
if (!filePath) return;
|
|
184
|
-
const isAbsolute = /^(\/|[A-Za-z]:[/\\])/.test(filePath);
|
|
185
|
-
const isRelative = /^(\.\/|\.\.\/)/.test(filePath);
|
|
186
|
-
const fileName = basename(filePath);
|
|
187
|
-
|
|
188
|
-
// Absolute path → verify then open
|
|
189
|
-
if (isAbsolute) {
|
|
190
|
-
const meta: Record<string, unknown> = { filePath };
|
|
191
|
-
if (projectName) meta.projectName = projectName;
|
|
192
|
-
api.get(`/api/fs/read?path=${encodeURIComponent(filePath)}`).then(() => {
|
|
193
|
-
openTab({ type: "editor", title: fileName, metadata: meta, projectId: null, closable: true });
|
|
194
|
-
}).catch(() => openCommandPalette(filePath));
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Relative path with ./ or ../ → try exact path in project
|
|
199
|
-
if (isRelative && projectName) {
|
|
200
|
-
const meta: Record<string, unknown> = { filePath, projectName };
|
|
201
|
-
api.get(`${projectUrl(projectName)}/files/read?path=${encodeURIComponent(filePath)}`)
|
|
202
|
-
.then(() => {
|
|
203
|
-
openTab({ type: "editor", title: fileName, metadata: meta, projectId: projectName, closable: true });
|
|
204
|
-
})
|
|
205
|
-
.catch(() => searchAndOpen(filePath));
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Just a filename → search in project tree
|
|
210
|
-
searchAndOpen(filePath);
|
|
64
|
+
if (isAbsolute) {
|
|
65
|
+
const meta: Record<string, unknown> = { filePath };
|
|
66
|
+
if (projectName) meta.projectName = projectName;
|
|
67
|
+
api.get(`/api/fs/read?path=${encodeURIComponent(filePath)}`).then(() => {
|
|
68
|
+
openTab({ type: "editor", title: fileName, metadata: meta, projectId: null, closable: true });
|
|
69
|
+
}).catch(() => openCommandPalette(filePath));
|
|
70
|
+
return;
|
|
211
71
|
}
|
|
212
72
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const match = matches[0]!;
|
|
219
|
-
openTab({
|
|
220
|
-
type: "editor",
|
|
221
|
-
title: fileName,
|
|
222
|
-
metadata: { filePath: match, projectName },
|
|
223
|
-
projectId: projectName ?? null,
|
|
224
|
-
closable: true,
|
|
225
|
-
});
|
|
226
|
-
} else {
|
|
227
|
-
openCommandPalette(filePath);
|
|
228
|
-
}
|
|
73
|
+
if (isRelative && projectName) {
|
|
74
|
+
api.get(`${projectUrl(projectName)}/files/read?path=${encodeURIComponent(filePath)}`)
|
|
75
|
+
.then(() => openTab({ type: "editor", title: fileName, metadata: { filePath, projectName }, projectId: projectName, closable: true }))
|
|
76
|
+
.catch(() => searchAndOpen(filePath));
|
|
77
|
+
return;
|
|
229
78
|
}
|
|
230
79
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
// --- Auth-load images with local file paths ---
|
|
234
|
-
const blobUrls: string[] = [];
|
|
235
|
-
container.querySelectorAll("img").forEach((img) => {
|
|
236
|
-
const src = img.getAttribute("src") ?? "";
|
|
237
|
-
// Only intercept local file paths, not http/data/blob URLs
|
|
238
|
-
if (!LOCAL_PATH_RE.test(src)) return;
|
|
239
|
-
// Mark as loading
|
|
240
|
-
img.style.opacity = "0.3";
|
|
241
|
-
img.style.minHeight = "48px";
|
|
242
|
-
img.style.minWidth = "48px";
|
|
243
|
-
const token = getAuthToken();
|
|
244
|
-
fetch(`/api/fs/raw?path=${encodeURIComponent(src)}`, {
|
|
245
|
-
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
|
246
|
-
})
|
|
247
|
-
.then((r) => {
|
|
248
|
-
if (!r.ok) throw new Error("Failed to load");
|
|
249
|
-
return r.blob();
|
|
250
|
-
})
|
|
251
|
-
.then((blob) => {
|
|
252
|
-
const url = URL.createObjectURL(blob);
|
|
253
|
-
blobUrls.push(url);
|
|
254
|
-
img.src = url;
|
|
255
|
-
img.style.opacity = "";
|
|
256
|
-
img.style.minHeight = "";
|
|
257
|
-
img.style.minWidth = "";
|
|
258
|
-
// Style: constrain size, add border like AuthImage
|
|
259
|
-
img.style.maxHeight = "400px";
|
|
260
|
-
img.style.maxWidth = "100%";
|
|
261
|
-
img.style.objectFit = "contain";
|
|
262
|
-
img.style.borderRadius = "0.375rem";
|
|
263
|
-
img.style.border = "1px solid var(--color-border)";
|
|
264
|
-
img.style.cursor = "pointer";
|
|
265
|
-
img.onclick = () => openImageOverlay(url, img.alt || basename(src));
|
|
266
|
-
})
|
|
267
|
-
.catch(() => {
|
|
268
|
-
img.style.opacity = "0.5";
|
|
269
|
-
img.alt = `[Image not found: ${basename(src)}]`;
|
|
270
|
-
});
|
|
271
|
-
});
|
|
80
|
+
searchAndOpen(filePath);
|
|
81
|
+
}, [openTab, fileTree, projectName]);
|
|
272
82
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
const text = code?.textContent ?? pre.textContent ?? "";
|
|
279
|
-
const langClass = code?.className ?? "";
|
|
280
|
-
const isBash = /language-(bash|sh|shell|zsh)/.test(langClass)
|
|
281
|
-
|| (!langClass.includes("language-") && text.startsWith("$"));
|
|
83
|
+
const ctx = useMemo(() => ({
|
|
84
|
+
projectName, codeActions, openFileOrSearch,
|
|
85
|
+
openImageOverlay: openImageOverlayFn,
|
|
86
|
+
openDiagramOverlay: openDiagramOverlayFn,
|
|
87
|
+
}), [projectName, codeActions, openFileOrSearch, openImageOverlayFn, openDiagramOverlayFn]);
|
|
282
88
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
89
|
+
return (
|
|
90
|
+
<MdContext.Provider value={ctx}>
|
|
91
|
+
<div className={`markdown-content prose-sm ${isStreaming ? "is-streaming" : ""} ${className}`}>
|
|
92
|
+
<ReactMarkdown remarkPlugins={remarkPlugins} rehypePlugins={rehypePlugins} components={mdComponents}>
|
|
93
|
+
{content}
|
|
94
|
+
</ReactMarkdown>
|
|
95
|
+
</div>
|
|
96
|
+
</MdContext.Provider>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
288
99
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
actions.appendChild(copyBtn);
|
|
100
|
+
/** Link — external links open in new tab; file paths open in editor */
|
|
101
|
+
function MdLink({ href, children, node, ...props }: any) {
|
|
102
|
+
const { openFileOrSearch } = useMdContext();
|
|
103
|
+
if (href?.match(/^https?:\/\//)) {
|
|
104
|
+
return <a href={href} target="_blank" rel="noopener noreferrer" {...props}>{children}</a>;
|
|
105
|
+
}
|
|
106
|
+
if (href && !GLOB_CHARS_RE.test(href) && FILE_EXT_RE.test(href)) {
|
|
107
|
+
return <a href={href} onClick={(e: React.MouseEvent) => { e.preventDefault(); openFileOrSearch(href); }} {...props}>{children}</a>;
|
|
108
|
+
}
|
|
109
|
+
return <a href={href} {...props}>{children}</a>;
|
|
110
|
+
}
|
|
301
111
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
runBtn.addEventListener("click", () => {
|
|
308
|
-
navigator.clipboard.writeText(text.replace(/^\$\s*/gm, ""));
|
|
309
|
-
openTab({ type: "terminal", title: "Terminal", metadata: { projectName }, projectId: projectName, closable: true });
|
|
310
|
-
});
|
|
311
|
-
actions.appendChild(runBtn);
|
|
312
|
-
}
|
|
112
|
+
/** Image — auth-loads local file paths via API, click to open overlay */
|
|
113
|
+
function MdImage({ src, alt, node, ...props }: any) {
|
|
114
|
+
const { openImageOverlay } = useMdContext();
|
|
115
|
+
const [blobUrl, setBlobUrl] = useState<string | null>(null);
|
|
116
|
+
const [loading, setLoading] = useState(false);
|
|
313
117
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
if (!src || !LOCAL_PATH_RE.test(src)) return;
|
|
120
|
+
setLoading(true);
|
|
121
|
+
let cancelled = false;
|
|
122
|
+
let url: string | null = null;
|
|
123
|
+
const token = getAuthToken();
|
|
124
|
+
fetch(`/api/fs/raw?path=${encodeURIComponent(src)}`, {
|
|
125
|
+
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
|
126
|
+
})
|
|
127
|
+
.then((r) => { if (!r.ok) throw new Error(); return r.blob(); })
|
|
128
|
+
.then((blob) => {
|
|
129
|
+
if (cancelled) return;
|
|
130
|
+
url = URL.createObjectURL(blob);
|
|
131
|
+
setBlobUrl(url);
|
|
132
|
+
setLoading(false);
|
|
133
|
+
})
|
|
134
|
+
.catch(() => { if (!cancelled) setLoading(false); });
|
|
135
|
+
return () => { cancelled = true; if (url) URL.revokeObjectURL(url); };
|
|
136
|
+
}, [src]);
|
|
317
137
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
blobUrls.forEach((u) => URL.revokeObjectURL(u));
|
|
321
|
-
};
|
|
322
|
-
}, [html, projectName, openTab, codeActions, openImageOverlay, openDiagramOverlay]);
|
|
138
|
+
const displaySrc = blobUrl || src || "";
|
|
139
|
+
const name = alt || (src ? basename(src) : "");
|
|
323
140
|
|
|
324
141
|
return (
|
|
325
|
-
<
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
142
|
+
<img
|
|
143
|
+
src={displaySrc}
|
|
144
|
+
alt={name}
|
|
145
|
+
onClick={() => displaySrc && openImageOverlay(displaySrc, name)}
|
|
146
|
+
className="max-h-[400px] max-w-full object-contain rounded-md border border-border cursor-pointer"
|
|
147
|
+
style={{ opacity: loading ? 0.3 : 1, minHeight: loading ? 48 : undefined, minWidth: loading ? 48 : undefined }}
|
|
148
|
+
{...props}
|
|
329
149
|
/>
|
|
330
150
|
);
|
|
331
151
|
}
|
|
152
|
+
|
|
153
|
+
/** Table — wrap in scrollable container */
|
|
154
|
+
function MdTable({ children, node, ...props }: any) {
|
|
155
|
+
return <div className="table-scroll-wrapper overflow-x-auto"><table {...props}>{children}</table></div>;
|
|
156
|
+
}
|
|
@@ -111,7 +111,8 @@ export function useExtensionWs(enabled = true) {
|
|
|
111
111
|
});
|
|
112
112
|
break;
|
|
113
113
|
|
|
114
|
-
case "webview:create":
|
|
114
|
+
case "webview:create": {
|
|
115
|
+
const viewTypeSlug = msg.viewType.replace(/\.view$/, "");
|
|
115
116
|
store.addWebviewPanel({
|
|
116
117
|
id: msg.panelId,
|
|
117
118
|
extensionId: msg.extensionId,
|
|
@@ -119,15 +120,18 @@ export function useExtensionWs(enabled = true) {
|
|
|
119
120
|
title: msg.title,
|
|
120
121
|
html: "",
|
|
121
122
|
});
|
|
122
|
-
// Open a tab
|
|
123
|
+
// Open a tab — use stable viewType slug as identifier (survives reload)
|
|
124
|
+
// Include projectName so reload can resolve project path for re-trigger
|
|
125
|
+
const currentProject = useTabStore.getState().currentProject;
|
|
123
126
|
useTabStore.getState().openTab({
|
|
124
|
-
type: "extension
|
|
127
|
+
type: "extension",
|
|
125
128
|
title: msg.title,
|
|
126
129
|
projectId: null,
|
|
127
130
|
closable: true,
|
|
128
|
-
metadata: { panelId: msg.panelId, extensionId: msg.extensionId },
|
|
131
|
+
metadata: { viewType: viewTypeSlug, panelId: msg.panelId, extensionId: msg.extensionId, ...(currentProject && { projectName: currentProject }) },
|
|
129
132
|
});
|
|
130
133
|
break;
|
|
134
|
+
}
|
|
131
135
|
|
|
132
136
|
case "webview:html":
|
|
133
137
|
store.updateWebviewPanel(msg.panelId, { html: msg.html });
|
|
@@ -142,6 +146,20 @@ export function useExtensionWs(enabled = true) {
|
|
|
142
146
|
detail: { panelId: msg.panelId, message: msg.message },
|
|
143
147
|
}));
|
|
144
148
|
break;
|
|
149
|
+
|
|
150
|
+
case "tab:open":
|
|
151
|
+
useTabStore.getState().openTab({
|
|
152
|
+
type: (msg as any).tabType,
|
|
153
|
+
title: (msg as any).title,
|
|
154
|
+
projectId: (msg as any).projectId ?? null,
|
|
155
|
+
closable: (msg as any).closable ?? true,
|
|
156
|
+
metadata: (msg as any).metadata,
|
|
157
|
+
});
|
|
158
|
+
break;
|
|
159
|
+
|
|
160
|
+
case "project:switch":
|
|
161
|
+
useTabStore.getState().switchProject((msg as any).projectName);
|
|
162
|
+
break;
|
|
145
163
|
}
|
|
146
164
|
});
|
|
147
165
|
|
|
@@ -2,7 +2,8 @@ import { useEffect, useState, useCallback } from "react";
|
|
|
2
2
|
import { useTabStore } from "@/stores/tab-store";
|
|
3
3
|
import { useSettingsStore } from "@/stores/settings-store";
|
|
4
4
|
import { useProjectStore } from "@/stores/project-store";
|
|
5
|
-
import { useKeybindingsStore } from "@/stores/keybindings-store";
|
|
5
|
+
import { useKeybindingsStore, parseCombo, eventMatchesCombo } from "@/stores/keybindings-store";
|
|
6
|
+
import { useExtensionStore } from "@/stores/extension-store";
|
|
6
7
|
|
|
7
8
|
/** Dispatch this event to open the command palette from anywhere, optionally with initial query */
|
|
8
9
|
export function openCommandPalette(initialQuery?: string) {
|
|
@@ -101,11 +102,17 @@ export function useGlobalKeybindings() {
|
|
|
101
102
|
return;
|
|
102
103
|
}
|
|
103
104
|
|
|
105
|
+
// New file
|
|
106
|
+
if (match(e, "new-file")) {
|
|
107
|
+
e.preventDefault();
|
|
108
|
+
useTabStore.getState().openNewFile();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
104
112
|
// Open tab shortcuts
|
|
105
113
|
const tabShortcuts: { action: string; type: string; title: string }[] = [
|
|
106
114
|
{ action: "open-chat", type: "chat", title: "AI Chat" },
|
|
107
115
|
{ action: "open-terminal", type: "terminal", title: "Terminal" },
|
|
108
|
-
{ action: "open-git-graph", type: "git-graph", title: "Git Graph" },
|
|
109
116
|
];
|
|
110
117
|
for (const s of tabShortcuts) {
|
|
111
118
|
if (match(e, s.action)) {
|
|
@@ -169,6 +176,28 @@ export function useGlobalKeybindings() {
|
|
|
169
176
|
return;
|
|
170
177
|
}
|
|
171
178
|
}
|
|
179
|
+
|
|
180
|
+
// Extension-contributed keybindings (with user override support)
|
|
181
|
+
const extKbs = useExtensionStore.getState().contributions?.keybindings;
|
|
182
|
+
if (extKbs) {
|
|
183
|
+
const mac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.userAgent);
|
|
184
|
+
const { getBinding: getBind } = useKeybindingsStore.getState();
|
|
185
|
+
for (const kb of extKbs) {
|
|
186
|
+
// User override via "ext:<command>" key, fallback to extension default
|
|
187
|
+
const overrideCombo = getBind(`ext:${kb.command}`);
|
|
188
|
+
const combo = overrideCombo || ((mac && kb.mac) ? kb.mac : kb.key);
|
|
189
|
+
if (combo && eventMatchesCombo(e, parseCombo(combo))) {
|
|
190
|
+
e.preventDefault();
|
|
191
|
+
const project = useProjectStore.getState().activeProject;
|
|
192
|
+
const args: unknown[] = [];
|
|
193
|
+
if (project?.path) args.push(project.path);
|
|
194
|
+
window.dispatchEvent(new CustomEvent("ext:command:execute", {
|
|
195
|
+
detail: { command: kb.command, args },
|
|
196
|
+
}));
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
172
201
|
}
|
|
173
202
|
|
|
174
203
|
// Custom event listener for programmatic opening
|
|
@@ -14,7 +14,8 @@ export interface UrlState {
|
|
|
14
14
|
|
|
15
15
|
const VALID_TAB_TYPES: TabType[] = [
|
|
16
16
|
"terminal", "chat", "editor", "database", "sqlite",
|
|
17
|
-
"postgres", "git-
|
|
17
|
+
"postgres", "git-diff", "settings", "ports",
|
|
18
|
+
"extension",
|
|
18
19
|
];
|
|
19
20
|
|
|
20
21
|
// ---------------------------------------------------------------------------
|
|
@@ -108,7 +109,6 @@ function buildMetadataFromUrl(
|
|
|
108
109
|
return { ...(sessionId && { sessionId }), ...(providerId && { providerId }), projectName };
|
|
109
110
|
}
|
|
110
111
|
case "terminal": return { terminalIndex: parseInt(identifier ?? "1", 10), projectName };
|
|
111
|
-
case "git-graph": return { projectName };
|
|
112
112
|
case "git-diff": return identifier ? { filePath: identifier, projectName } : null;
|
|
113
113
|
case "settings": return {};
|
|
114
114
|
case "database": {
|
|
@@ -121,6 +121,7 @@ function buildMetadataFromUrl(
|
|
|
121
121
|
return connId ? { connectionId: connId, tableName: tableName ?? "" } : null;
|
|
122
122
|
}
|
|
123
123
|
case "ports": return null;
|
|
124
|
+
case "extension": return identifier ? { viewType: identifier, projectName } : null;
|
|
124
125
|
default: return null;
|
|
125
126
|
}
|
|
126
127
|
}
|
|
@@ -130,13 +131,17 @@ function buildTitleFromUrl(type: TabType, identifier: string | null): string {
|
|
|
130
131
|
case "editor": return identifier?.split("/").pop() ?? "File";
|
|
131
132
|
case "chat": return "Chat";
|
|
132
133
|
case "terminal": return `Terminal ${identifier ?? "1"}`;
|
|
133
|
-
case "git-graph": return "Git Graph";
|
|
134
134
|
case "git-diff": return identifier?.split("/").pop() ?? "Diff";
|
|
135
135
|
case "settings": return "Settings";
|
|
136
136
|
case "database": return identifier ?? "Database";
|
|
137
137
|
case "sqlite": return identifier?.split("/").pop() ?? "SQLite";
|
|
138
138
|
case "postgres": return identifier ?? "PostgreSQL";
|
|
139
139
|
case "ports": return "Ports";
|
|
140
|
+
case "extension": {
|
|
141
|
+
if (!identifier) return "Extension";
|
|
142
|
+
// "git-graph" → "Git Graph"
|
|
143
|
+
return identifier.split("-").map(w => (w[0]?.toUpperCase() ?? "") + w.slice(1)).join(" ");
|
|
144
|
+
}
|
|
140
145
|
default: return type;
|
|
141
146
|
}
|
|
142
147
|
}
|
package/src/web/main.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import { createRoot } from "react-dom/client";
|
|
|
3
3
|
import { App } from "./app.tsx";
|
|
4
4
|
import "./styles/globals.css";
|
|
5
5
|
import "katex/dist/katex.min.css";
|
|
6
|
+
import "highlight.js/styles/github-dark-dimmed.min.css";
|
|
6
7
|
|
|
7
8
|
createRoot(document.getElementById("root")!).render(
|
|
8
9
|
<StrictMode>
|
|
@@ -30,10 +30,10 @@ export const KEY_ACTIONS: KeyAction[] = [
|
|
|
30
30
|
// Tabs
|
|
31
31
|
{ id: "next-tab", label: "Next Tab", category: "tabs", defaultKey: "Alt+]" },
|
|
32
32
|
{ id: "prev-tab", label: "Previous Tab", category: "tabs", defaultKey: "Alt+[" },
|
|
33
|
+
{ id: "new-file", label: "New File", category: "tabs", defaultKey: "Mod+N" },
|
|
33
34
|
{ id: "open-chat", label: "Open Chat", category: "tabs", defaultKey: "Mod+L" },
|
|
34
35
|
{ id: "open-terminal", label: "Open Terminal", category: "tabs", defaultKey: "Mod+'" },
|
|
35
36
|
{ id: "open-settings", label: "Open Settings", category: "tabs", defaultKey: "Mod+," },
|
|
36
|
-
{ id: "open-git-graph", label: "Git Graph", category: "tabs", defaultKey: "Mod+G" },
|
|
37
37
|
{ id: "open-git-status", label: "Git Status (sidebar)", category: "tabs", defaultKey: "Mod+Shift+E" },
|
|
38
38
|
{ id: "open-search", label: "Search Files (sidebar)", category: "tabs", defaultKey: "Mod+Shift+F" },
|
|
39
39
|
{ id: "voice-input", label: "Voice Input", category: "general", defaultKey: "Mod+Shift+V", note: "Toggle speech-to-text in chat" },
|
|
@@ -61,7 +61,7 @@ interface ParsedCombo {
|
|
|
61
61
|
key: string; // lowercase
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
function parseCombo(combo: string): ParsedCombo {
|
|
64
|
+
export function parseCombo(combo: string): ParsedCombo {
|
|
65
65
|
const parts = combo.split("+");
|
|
66
66
|
const result: ParsedCombo = { ctrl: false, meta: false, alt: false, shift: false, key: "" };
|
|
67
67
|
for (const part of parts) {
|
|
@@ -81,7 +81,7 @@ function parseCombo(combo: string): ParsedCombo {
|
|
|
81
81
|
return result;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
function eventMatchesCombo(e: KeyboardEvent, combo: ParsedCombo): boolean {
|
|
84
|
+
export function eventMatchesCombo(e: KeyboardEvent, combo: ParsedCombo): boolean {
|
|
85
85
|
if (e.ctrlKey !== combo.ctrl) return false;
|
|
86
86
|
if (e.metaKey !== combo.meta) return false;
|
|
87
87
|
if (e.altKey !== combo.alt) return false;
|
|
@@ -16,10 +16,10 @@ import {
|
|
|
16
16
|
} from "./panel-utils";
|
|
17
17
|
|
|
18
18
|
/** Tab types that can only have 1 instance per project */
|
|
19
|
-
const SINGLETON_TYPES = new Set<TabType>(["
|
|
19
|
+
const SINGLETON_TYPES = new Set<TabType>(["settings"]);
|
|
20
20
|
|
|
21
21
|
/** Tab types removed in a prior version — filter them out when loading persisted state */
|
|
22
|
-
const OBSOLETE_TAB_TYPES = new Set(["projects", "git-status"]);
|
|
22
|
+
const OBSOLETE_TAB_TYPES = new Set(["projects", "git-status", "git-graph"]);
|
|
23
23
|
|
|
24
24
|
function pushHistory(history: string[], id: string): string[] {
|
|
25
25
|
const filtered = history.filter((h) => h !== id);
|