cli-jaw 2.0.6 → 2.0.13
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/README.ja.md +24 -11
- package/README.ko.md +24 -11
- package/README.md +95 -11
- package/README.zh-CN.md +24 -11
- package/dist/bin/cli-jaw.js +4 -1
- package/dist/bin/cli-jaw.js.map +1 -1
- package/dist/bin/commands/browser-web-ai.js +2 -2
- package/dist/bin/commands/browser-web-ai.js.map +1 -1
- package/dist/bin/commands/doctor.js +12 -2
- package/dist/bin/commands/doctor.js.map +1 -1
- package/dist/bin/commands/init.js +4 -2
- package/dist/bin/commands/init.js.map +1 -1
- package/dist/bin/commands/project.js +167 -0
- package/dist/bin/commands/project.js.map +1 -0
- package/dist/bin/commands/tui/types.js +20 -4
- package/dist/bin/commands/tui/types.js.map +1 -1
- package/dist/bin/postinstall.js +226 -24
- package/dist/bin/postinstall.js.map +1 -1
- package/dist/lib/mime-detect.js +5 -0
- package/dist/lib/mime-detect.js.map +1 -1
- package/dist/scripts/fresh-install-smoke.js +78 -6
- package/dist/scripts/fresh-install-smoke.js.map +1 -1
- package/dist/src/agent/agy-runtime.js +15 -0
- package/dist/src/agent/agy-runtime.js.map +1 -0
- package/dist/src/agent/args.js +44 -5
- package/dist/src/agent/args.js.map +1 -1
- package/dist/src/agent/cli-helpers.js +1 -1
- package/dist/src/agent/cli-helpers.js.map +1 -1
- package/dist/src/agent/lifecycle-handler.js +6 -3
- package/dist/src/agent/lifecycle-handler.js.map +1 -1
- package/dist/src/agent/session-persistence.js +1 -1
- package/dist/src/agent/session-persistence.js.map +1 -1
- package/dist/src/agent/smoke-detector.js +3 -0
- package/dist/src/agent/smoke-detector.js.map +1 -1
- package/dist/src/agent/spawn/resume.js +2 -0
- package/dist/src/agent/spawn/resume.js.map +1 -1
- package/dist/src/agent/spawn-env.js +6 -0
- package/dist/src/agent/spawn-env.js.map +1 -1
- package/dist/src/agent/spawn.js +120 -15
- package/dist/src/agent/spawn.js.map +1 -1
- package/dist/src/browser/web-ai/capability-observation-presets.js +1 -1
- package/dist/src/browser/web-ai/capability-observation-presets.js.map +1 -1
- package/dist/src/browser/web-ai/capability-registry.js +6 -5
- package/dist/src/browser/web-ai/capability-registry.js.map +1 -1
- package/dist/src/browser/web-ai/chatgpt-attachments.js +7 -0
- package/dist/src/browser/web-ai/chatgpt-attachments.js.map +1 -1
- package/dist/src/browser/web-ai/context-pack/builder.js +19 -4
- package/dist/src/browser/web-ai/context-pack/builder.js.map +1 -1
- package/dist/src/browser/web-ai/context-pack/zip-writer.js +116 -0
- package/dist/src/browser/web-ai/context-pack/zip-writer.js.map +1 -0
- package/dist/src/browser/web-ai/gemini-live.js +21 -3
- package/dist/src/browser/web-ai/gemini-live.js.map +1 -1
- package/dist/src/browser/web-ai/gemini-model.js +26 -8
- package/dist/src/browser/web-ai/gemini-model.js.map +1 -1
- package/dist/src/cli/commands.js +2 -0
- package/dist/src/cli/commands.js.map +1 -1
- package/dist/src/cli/handlers-project.js +55 -0
- package/dist/src/cli/handlers-project.js.map +1 -0
- package/dist/src/cli/readiness.js +6 -1
- package/dist/src/cli/readiness.js.map +1 -1
- package/dist/src/cli/registry.js +11 -0
- package/dist/src/cli/registry.js.map +1 -1
- package/dist/src/core/claude-install.js +10 -1
- package/dist/src/core/claude-install.js.map +1 -1
- package/dist/src/core/config.js +63 -7
- package/dist/src/core/config.js.map +1 -1
- package/dist/src/core/db.js +11 -4
- package/dist/src/core/db.js.map +1 -1
- package/dist/src/core/runtime-settings.js +4 -1
- package/dist/src/core/runtime-settings.js.map +1 -1
- package/dist/src/manager/metadata.js +7 -2
- package/dist/src/manager/metadata.js.map +1 -1
- package/dist/src/manager/notes/routes.js +7 -0
- package/dist/src/manager/notes/routes.js.map +1 -1
- package/dist/src/manager/notes/store.js +72 -0
- package/dist/src/manager/notes/store.js.map +1 -1
- package/dist/src/manager/registry.js +1 -1
- package/dist/src/manager/registry.js.map +1 -1
- package/dist/src/manager/scan.js +2 -0
- package/dist/src/manager/scan.js.map +1 -1
- package/dist/src/messaging/send.js +1 -1
- package/dist/src/messaging/send.js.map +1 -1
- package/dist/src/orchestrator/distribute.js +25 -4
- package/dist/src/orchestrator/distribute.js.map +1 -1
- package/dist/src/orchestrator/pipeline.js +21 -5
- package/dist/src/orchestrator/pipeline.js.map +1 -1
- package/dist/src/orchestrator/state-machine.js +7 -1
- package/dist/src/orchestrator/state-machine.js.map +1 -1
- package/dist/src/orchestrator/workspace-context.js +49 -14
- package/dist/src/orchestrator/workspace-context.js.map +1 -1
- package/dist/src/routes/messaging.js +1 -1
- package/dist/src/routes/messaging.js.map +1 -1
- package/dist/src/routes/orchestrate.js +11 -4
- package/dist/src/routes/orchestrate.js.map +1 -1
- package/dist/src/routes/settings.js +73 -8
- package/dist/src/routes/settings.js.map +1 -1
- package/dist/src/security/path-guards.js +35 -14
- package/dist/src/security/path-guards.js.map +1 -1
- package/dist/src/types/cli-engine.js +1 -0
- package/dist/src/types/cli-engine.js.map +1 -1
- package/package.json +19 -4
- package/public/css/chat.css +6 -2
- package/public/dist/assets/{AdvancedExport-CBvz4_IZ.js → AdvancedExport-CZEVPqHb.js} +1 -1
- package/public/dist/assets/{Agent-DlUqCMXJ.js → Agent-BBrJGd5m.js} +1 -1
- package/public/dist/assets/{Browser-66BpLQck.js → Browser-dBaU9G9D.js} +1 -1
- package/public/dist/assets/{ChannelsDiscord-BifT2Dum.js → ChannelsDiscord-CbmWms9-.js} +1 -1
- package/public/dist/assets/{ChannelsTelegram-CK6tYQ8k.js → ChannelsTelegram-BD4Eyztg.js} +1 -1
- package/public/dist/assets/{DashboardMeta-Cc03HT5R.js → DashboardMeta-LVAL7TtE.js} +1 -1
- package/public/dist/assets/{Display-arOjcMQZ.js → Display-CFm1UmEo.js} +1 -1
- package/public/dist/assets/{Employees-YbW-mI67.js → Employees-DcMChleU.js} +1 -1
- package/public/dist/assets/{Heartbeat-CW4eIhtJ.js → Heartbeat-BoobN35p.js} +1 -1
- package/public/dist/assets/{Mcp-ZQ6ARQb6.js → Mcp-9NU4YkKO.js} +1 -1
- package/public/dist/assets/{Memory-BAUWJMqc.js → Memory-uZT30fww.js} +1 -1
- package/public/dist/assets/{MilkdownWysiwygEditor-IAqFtXvM.js → MilkdownWysiwygEditor-Otw40DDF.js} +1 -1
- package/public/dist/assets/{ModelProvider-DUtmFh0J.js → ModelProvider-CsIOyoZP.js} +1 -1
- package/public/dist/assets/{Network-B_4E82zQ.js → Network-D7hGwnyG.js} +1 -1
- package/public/dist/assets/NotesGraphView-CJuk9lKn.js +1 -0
- package/public/dist/assets/Permissions-CcIBptg3.js +1 -0
- package/public/dist/assets/{Permissions-BkADXjQ4.js → Permissions-DwdU3wi8.js} +1 -1
- package/public/dist/assets/{Profile-BcnTCSYM.js → Profile-BNTKD_Ex.js} +1 -1
- package/public/dist/assets/{Prompts-CUs_RoRE.js → Prompts-D_mCfYiu.js} +1 -1
- package/public/dist/assets/{SpeechKeys-qxc-ROWN.js → SpeechKeys-BTXCM-Fs.js} +1 -1
- package/public/dist/assets/agent-meta-BC7mLqo6.js +1 -0
- package/public/dist/assets/{app-CGqTcIeF.js → app-C7MAcAMu.js} +5 -5
- package/public/dist/assets/app-CkFBKUf8.css +1 -0
- package/public/dist/assets/{architectureDiagram-3BPJPVTR-kYIVzmWe.js → architectureDiagram-3BPJPVTR-CJCPEsxo.js} +1 -1
- package/public/dist/assets/{blockDiagram-GPEHLZMM-B204CPhe.js → blockDiagram-GPEHLZMM-CO1bkn0E.js} +2 -2
- package/public/dist/assets/{c4Diagram-AAUBKEIU-DAv6TAjg.js → c4Diagram-AAUBKEIU-D2R36Xu6.js} +1 -1
- package/public/dist/assets/{chunk-3OPIFGDE-DOAvk9x8.js → chunk-3OPIFGDE-DixnEkZw.js} +1 -1
- package/public/dist/assets/chunk-55IACEB6-DhWWzzdj.js +1 -0
- package/public/dist/assets/{chunk-5ZQYHXKU-Bd-RFO5i.js → chunk-5ZQYHXKU-Bli4k6GI.js} +2 -2
- package/public/dist/assets/{chunk-727SXJPM-BrsRzj8z.js → chunk-727SXJPM-sp4W8bhf.js} +1 -1
- package/public/dist/assets/{chunk-AQP2D5EJ-Di6Gstk-.js → chunk-AQP2D5EJ-Co6ucdGq.js} +1 -1
- package/public/dist/assets/{chunk-KSCS5N6A-D9a4ZkOy.js → chunk-KSCS5N6A-Beq1s5eO.js} +2 -2
- package/public/dist/assets/{chunk-L5ZTLDWV-D208QbfC.js → chunk-L5ZTLDWV-DfUlBdXN.js} +1 -1
- package/public/dist/assets/{chunk-LZXEDZCA-CE2KFduj.js → chunk-LZXEDZCA-B_yP4UKw.js} +1 -1
- package/public/dist/assets/{chunk-ND2GUHAM-zZ7h1CPf.js → chunk-ND2GUHAM-3qbNb7I9.js} +1 -1
- package/public/dist/assets/{chunk-O5CBEL6O-BTuvXKFi.js → chunk-O5CBEL6O-D-jDqsN2.js} +1 -1
- package/public/dist/assets/{chunk-WU5MYG2G-C3fKlSvP.js → chunk-WU5MYG2G-DlBJtD7-.js} +1 -1
- package/public/dist/assets/classDiagram-4FO5ZUOK-DmPJvqRM.js +1 -0
- package/public/dist/assets/classDiagram-v2-Q7XG4LA2-ECmTUt7U.js +1 -0
- package/public/dist/assets/constants-BXWl7LTi.js +1 -0
- package/public/dist/assets/{cose-bilkent-S5V4N54A-D1zmarou.js → cose-bilkent-S5V4N54A-Bt9AAJCx.js} +1 -1
- package/public/dist/assets/{dagre-BM42HDAG-BVkBttzA.js → dagre-BM42HDAG-CXEA1wvZ.js} +1 -1
- package/public/dist/assets/{dagre-DnoxfNXr.js → dagre-Ve-IAfyz.js} +1 -1
- package/public/dist/assets/{diagram-2AECGRRQ-Bs32KPnT.js → diagram-2AECGRRQ-CCB0OKc2.js} +1 -1
- package/public/dist/assets/{diagram-5GNKFQAL-YDYgPOQj.js → diagram-5GNKFQAL-Cki9HABy.js} +1 -1
- package/public/dist/assets/{diagram-KO2AKTUF-1tIdFl26.js → diagram-KO2AKTUF-DNBDjE2o.js} +1 -1
- package/public/dist/assets/{diagram-LMA3HP47-B1y15kav.js → diagram-LMA3HP47-Cy23RDPa.js} +1 -1
- package/public/dist/assets/{diagram-OG6HWLK6-C25j1jGg.js → diagram-OG6HWLK6-CT_YF8cA.js} +1 -1
- package/public/dist/assets/dist-3R3GxjCf.js +13 -0
- package/public/dist/assets/{dist-D0PYXGt2.js → dist-BLd7xdyq.js} +1 -1
- package/public/dist/assets/{dist-fJAyK82z.js → dist-BaRrwDy3.js} +1 -1
- package/public/dist/assets/{dist-Rs_Khtjb.js → dist-Bbsp4OF7.js} +1 -1
- package/public/dist/assets/{dist-Cv8S3FhF.js → dist-BkBGlG70.js} +1 -1
- package/public/dist/assets/{dist-0v1UKkQ3.js → dist-BkJ_1mj9.js} +1 -1
- package/public/dist/assets/{dist-BQNQk-1M.js → dist-BxCRVW5D.js} +1 -1
- package/public/dist/assets/{dist-DDDFJ8jG.js → dist-C-v7Rm67.js} +1 -1
- package/public/dist/assets/dist-CNgDEAy8.js +1 -0
- package/public/dist/assets/dist-CO_W5rr2.js +1 -0
- package/public/dist/assets/{dist-vQskrHxt.js → dist-ChAUfTbO.js} +1 -1
- package/public/dist/assets/{dist-CjPin7Mr.js → dist-D-k-lzQT.js} +1 -1
- package/public/dist/assets/dist-D1hPukqY.js +1 -0
- package/public/dist/assets/{dist-C1VqWrYZ.js → dist-D5Qx-jQQ.js} +1 -1
- package/public/dist/assets/{dist-DTQW91xt.js → dist-D7VNzYBv.js} +1 -1
- package/public/dist/assets/{dist-DHQI31dn.js → dist-DAxnodoa.js} +1 -1
- package/public/dist/assets/{dist-CnIrGAPx.js → dist-DHSyODkR.js} +1 -1
- package/public/dist/assets/{dist-CE4s7vg82.js → dist-DaYTPjbb2.js} +1 -1
- package/public/dist/assets/{dist-jcptfm_T.js → dist-DlWi9tVO.js} +1 -1
- package/public/dist/assets/{dist-D66X8iAn.js → dist-DuuoJfRB.js} +1 -1
- package/public/dist/assets/{dist-DzT7frM2.js → dist-Dv36UUXz.js} +1 -1
- package/public/dist/assets/{dist-BVpa_e6W.js → dist-F4CVhitP.js} +1 -1
- package/public/dist/assets/{dist-Qs7HHNCC.js → dist-LHbJYy3N.js} +1 -1
- package/public/dist/assets/{dist-Ck6PnIlo.js → dist-VIlx7JdU.js} +1 -1
- package/public/dist/assets/{dist-D5GUqT-6.js → dist-cgDt2PmY.js} +1 -1
- package/public/dist/assets/dist-eW2LjRIK.js +1 -0
- package/public/dist/assets/{dist-Dm7RAaUo.js → dist-vGn3_sWA.js} +1 -1
- package/public/dist/assets/{employees-DRmX2RDd.js → employees-BmxY8Mw3.js} +1 -1
- package/public/dist/assets/{erDiagram-TEJ5UH35-CRvCbztR.js → erDiagram-TEJ5UH35-Cq7oHdXg.js} +1 -1
- package/public/dist/assets/{flowDiagram-I6XJVG4X-CzvVnib_.js → flowDiagram-I6XJVG4X-D8CdEnfM.js} +1 -1
- package/public/dist/assets/ganttDiagram-6RSMTGT7-B1rzKXi_.js +292 -0
- package/public/dist/assets/{gitGraphDiagram-PVQCEYII-CbKdM7nW.js → gitGraphDiagram-PVQCEYII-CqLh-joi.js} +1 -1
- package/public/dist/assets/{graphlib-BLOO0H5r.js → graphlib-W5bESv3F.js} +1 -1
- package/public/dist/assets/{infoDiagram-5YYISTIA-BrZmg1At.js → infoDiagram-5YYISTIA-C1zVxumj.js} +1 -1
- package/public/dist/assets/{ishikawaDiagram-YF4QCWOH-BDWXqJi5.js → ishikawaDiagram-YF4QCWOH-x-9J6xPM.js} +1 -1
- package/public/dist/assets/{journeyDiagram-JHISSGLW-CPFRd_M3.js → journeyDiagram-JHISSGLW-BDYzHrtn.js} +1 -1
- package/public/dist/assets/{kanban-definition-UN3LZRKU-BcQbBplS.js → kanban-definition-UN3LZRKU-BJ-06i6Z.js} +1 -1
- package/public/dist/assets/manager-B2NWzG7j.css +1 -0
- package/public/dist/assets/manager-C5e1IxEN.js +25 -0
- package/public/dist/assets/memory-BqqibBIE.js +1 -0
- package/public/dist/assets/{memory-BA-ki3gn.js → memory-CRL72HB-.js} +1 -1
- package/public/dist/assets/mermaid-loader-Dqb4ZUz6.js +1 -0
- package/public/dist/assets/{mermaid.core-ArqR5iFa.js → mermaid.core-D8VnfThU.js} +2 -2
- package/public/dist/assets/mermaid.core-DYHvk6Pd.js +1 -0
- package/public/dist/assets/{mindmap-definition-RKZ34NQL-C35cGkNU.js → mindmap-definition-RKZ34NQL-D8NDuIhn.js} +1 -1
- package/public/dist/assets/{pieDiagram-4H26LBE5-B8C_hoqJ.js → pieDiagram-4H26LBE5-CpBePil_.js} +3 -3
- package/public/dist/assets/{quadrantDiagram-W4KKPZXB-BEPrine5.js → quadrantDiagram-W4KKPZXB-C1WWpn88.js} +1 -1
- package/public/dist/assets/{render-C7_e0J_X.js → render-o_YEiCEO.js} +2 -2
- package/public/dist/assets/{requirementDiagram-4Y6WPE33-ClDDon9c.js → requirementDiagram-4Y6WPE33-C_D5VbNk.js} +1 -1
- package/public/dist/assets/{sankeyDiagram-5OEKKPKP-Bjs3OE-1.js → sankeyDiagram-5OEKKPKP--RH3Es8R.js} +3 -3
- package/public/dist/assets/{sequenceDiagram-3UESZ5HK-BM8IAaEe.js → sequenceDiagram-3UESZ5HK-CVI4AjhN.js} +1 -1
- package/public/dist/assets/settings-B0RlyAkI.js +1 -0
- package/public/dist/assets/settings-CCXRL5Ar.js +46 -0
- package/public/dist/assets/sidebar-DRJffqO1.js +14 -0
- package/public/dist/assets/skills-CFdzXbtK.js +1 -0
- package/public/dist/assets/{skills-Dq7fD8I3.js → skills-DbRVI-zf.js} +1 -1
- package/public/dist/assets/slash-commands-BUM6u-Ex.js +1 -0
- package/public/dist/assets/{slash-commands-DRsKtuGT.js → slash-commands-C2YBMaQV.js} +1 -1
- package/public/dist/assets/{stateDiagram-AJRCARHV-BvNABuXE.js → stateDiagram-AJRCARHV-4bx1rPZ3.js} +1 -1
- package/public/dist/assets/stateDiagram-v2-BHNVJYJU-CrfGPzHb.js +1 -0
- package/public/dist/assets/{timeline-definition-PNZ67QCA-CIvTvac3.js → timeline-definition-PNZ67QCA-DeQoGRE2.js} +1 -1
- package/public/dist/assets/{trace-drawer-DDFdU07_.js → trace-drawer-CIVBXyRH.js} +1 -1
- package/public/dist/assets/ui-C3ArwMhv.js +140 -0
- package/public/dist/assets/ui-CboUuc_E.js +1 -0
- package/public/dist/assets/vendor-utils-rVejrfMR.js +1 -0
- package/public/dist/assets/{vennDiagram-CIIHVFJN-C5cbS2KC.js → vennDiagram-CIIHVFJN-CejyJK0L.js} +1 -1
- package/public/dist/assets/{wardleyDiagram-YWT4CUSO-BsRBBGFP.js → wardleyDiagram-YWT4CUSO-DqXV0sa1.js} +1 -1
- package/public/dist/assets/wiki-link-suggestions-e5BzJAZH.js +23 -0
- package/public/dist/assets/{xychartDiagram-2RQKCTM6-B6rqui6F.js → xychartDiagram-2RQKCTM6-CWLoVl2b.js} +2 -2
- package/public/dist/index.html +20 -2
- package/public/dist/manager/index.html +2 -2
- package/public/index.html +18 -0
- package/public/js/constants.ts +12 -1
- package/public/js/features/settings-cli-status.ts +27 -4
- package/public/js/features/settings-core.ts +28 -15
- package/public/js/features/settings-types.ts +1 -0
- package/public/js/features/ui-status.ts +3 -0
- package/public/js/ws.ts +5 -1
- package/public/manager/src/SidebarRailRouter.tsx +5 -2
- package/public/manager/src/api.ts +13 -0
- package/public/manager/src/components/WorkbenchHeader.tsx +10 -0
- package/public/manager/src/manager-notes.css +228 -0
- package/public/manager/src/notes/NotesBacklinksPanel.tsx +91 -0
- package/public/manager/src/notes/NotesCommandPalette.tsx +212 -0
- package/public/manager/src/notes/NotesGraphView.tsx +145 -0
- package/public/manager/src/notes/NotesSidebar.tsx +183 -5
- package/public/manager/src/notes/NotesToolbar.tsx +9 -3
- package/public/manager/src/notes/NotesWorkspace.tsx +140 -4
- package/public/manager/src/notes/notes-api.ts +2 -0
- package/public/manager/src/notes/notes-command-registry.tsx +79 -0
- package/public/manager/src/notes/notes-quick-switcher.css +151 -0
- package/public/manager/src/notes/notes-shortcuts.ts +23 -0
- package/public/manager/src/notes/notes-types.ts +1 -1
- package/public/manager/src/notes/template-engine.ts +27 -0
- package/public/manager/src/settings/pages/components/agent/agent-meta.ts +7 -1
- package/public/manager/src/types.ts +2 -1
- package/scripts/audit-fresh-install-evidence.mjs +278 -0
- package/scripts/collect-fresh-install-evidence.sh +459 -0
- package/scripts/fresh-install-smoke.ts +85 -6
- package/scripts/install-officecli.ps1 +14 -1
- package/scripts/install-risk-gate.mjs +161 -0
- package/scripts/install-wsl.sh +78 -16
- package/scripts/install.sh +272 -72
- package/scripts/postinstall-guard.cjs +2 -2
- package/scripts/release-preview.sh +25 -3
- package/scripts/release.sh +37 -8
- package/scripts/require-release-evidence.mjs +236 -0
- package/scripts/verify-fresh-install.sh +120 -0
- package/scripts/verify-release-evidence.mjs +158 -0
- package/public/dist/assets/Permissions-DvUCB8wA.js +0 -1
- package/public/dist/assets/agent-meta-Du8y6mSM.js +0 -1
- package/public/dist/assets/app-Dpk6cE6C.css +0 -1
- package/public/dist/assets/chunk-55IACEB6-Dx2JkQLw.js +0 -1
- package/public/dist/assets/classDiagram-4FO5ZUOK-Cb-qL5u3.js +0 -1
- package/public/dist/assets/classDiagram-v2-Q7XG4LA2-BPhGdj15.js +0 -1
- package/public/dist/assets/constants-BxG09J6S.js +0 -1
- package/public/dist/assets/dist-BMi_buEb.js +0 -1
- package/public/dist/assets/dist-BkrbW_LS.js +0 -1
- package/public/dist/assets/dist-C3vh7d65.js +0 -13
- package/public/dist/assets/dist-D815C1Fv.js +0 -1
- package/public/dist/assets/dist-sOV2kBhQ.js +0 -1
- package/public/dist/assets/ganttDiagram-6RSMTGT7-BbUYP6iv.js +0 -292
- package/public/dist/assets/manager-B5OmizBL.css +0 -1
- package/public/dist/assets/manager-Cs3eMBcx.js +0 -25
- package/public/dist/assets/memory-DLQbyBA3.js +0 -1
- package/public/dist/assets/mermaid-loader-BVPsKrpr.js +0 -1
- package/public/dist/assets/mermaid.core-BAwu4U2Y.js +0 -1
- package/public/dist/assets/settings-DjyKdqev.js +0 -1
- package/public/dist/assets/settings-pUJzyeX2.js +0 -42
- package/public/dist/assets/sidebar-CO9Qjj6v.js +0 -14
- package/public/dist/assets/skills-CX3qJ77s.js +0 -1
- package/public/dist/assets/slash-commands-UbS8w9ij.js +0 -1
- package/public/dist/assets/stateDiagram-v2-BHNVJYJU-CCIo81qy.js +0 -1
- package/public/dist/assets/ui-CeBmBBZM.js +0 -140
- package/public/dist/assets/ui-TMXjvH0r.js +0 -1
- package/public/dist/assets/vendor-utils-BnxL60_-.js +0 -1
- package/public/dist/assets/wiki-link-suggestions-BHwNQXT_.js +0 -23
- /package/public/dist/assets/{HealthBadge-DEPFkcA0.js → HealthBadge-CfLTPTPW.js} +0 -0
- /package/public/dist/assets/{InlineWarn-DoJS7AgM.js → InlineWarn-C7ISyzDI.js} +0 -0
- /package/public/dist/assets/{fields-DUawAHWZ.js → fields-BwD_NGxe.js} +0 -0
- /package/public/dist/assets/{locale-BHMJIzyw.js → locale-DT1WRaeJ.js} +0 -0
- /package/public/dist/assets/{page-shell-CVAobxbP.js → page-shell-uNOxwdbr.js} +0 -0
- /package/public/dist/assets/{path-utils-r2bJIu28.js → path-utils-h2GAj3hH.js} +0 -0
- /package/public/dist/assets/{provider-icons-dxT69zGg.js → provider-icons-ClVd2suJ.js} +0 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { useCallback, useState, type ReactNode } from 'react';
|
|
2
|
+
import type { NoteLinkRef } from '../types';
|
|
3
|
+
|
|
4
|
+
type NotesBacklinksPanelProps = {
|
|
5
|
+
backlinks: NoteLinkRef[];
|
|
6
|
+
onNavigate: (path: string) => void;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
function backlinkTitle(path: string): string {
|
|
10
|
+
const name = path.split('/').pop() ?? path;
|
|
11
|
+
return name.endsWith('.md') ? name.slice(0, -3) : name;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function highlightWikilink(context: string): ReactNode[] {
|
|
15
|
+
const parts: ReactNode[] = [];
|
|
16
|
+
const regex = /\[\[([^\]]+)\]\]/g;
|
|
17
|
+
let lastIndex = 0;
|
|
18
|
+
let match: RegExpExecArray | null;
|
|
19
|
+
let key = 0;
|
|
20
|
+
while ((match = regex.exec(context)) !== null) {
|
|
21
|
+
if (match.index > lastIndex) {
|
|
22
|
+
parts.push(context.slice(lastIndex, match.index));
|
|
23
|
+
}
|
|
24
|
+
parts.push(
|
|
25
|
+
<mark key={key++} className="backlink-highlight">
|
|
26
|
+
{'[[' + match[1] + ']]'}
|
|
27
|
+
</mark>,
|
|
28
|
+
);
|
|
29
|
+
lastIndex = regex.lastIndex;
|
|
30
|
+
}
|
|
31
|
+
if (lastIndex < context.length) {
|
|
32
|
+
parts.push(context.slice(lastIndex));
|
|
33
|
+
}
|
|
34
|
+
return parts;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function NotesBacklinksPanel(props: NotesBacklinksPanelProps) {
|
|
38
|
+
const [collapsed, setCollapsed] = useState(false);
|
|
39
|
+
|
|
40
|
+
const handleClick = useCallback((path: string) => {
|
|
41
|
+
props.onNavigate(path);
|
|
42
|
+
}, [props.onNavigate]);
|
|
43
|
+
|
|
44
|
+
const count = props.backlinks.length;
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div className="notes-backlinks-panel">
|
|
48
|
+
<button
|
|
49
|
+
className="notes-backlinks-header"
|
|
50
|
+
onClick={() => setCollapsed(c => !c)}
|
|
51
|
+
>
|
|
52
|
+
<span className="notes-backlinks-chevron">
|
|
53
|
+
{collapsed ? '▶' : '▼'}
|
|
54
|
+
</span>
|
|
55
|
+
<span>Backlinks</span>
|
|
56
|
+
<span className="notes-backlinks-count">{count}</span>
|
|
57
|
+
</button>
|
|
58
|
+
{!collapsed && (
|
|
59
|
+
<div className="notes-backlinks-list">
|
|
60
|
+
{count === 0 && (
|
|
61
|
+
<div className="notes-backlinks-empty">
|
|
62
|
+
No backlinks found
|
|
63
|
+
</div>
|
|
64
|
+
)}
|
|
65
|
+
{props.backlinks.map((entry, i) => {
|
|
66
|
+
const context = entry.raw.length > 120
|
|
67
|
+
? entry.raw.slice(0, 120) + '...'
|
|
68
|
+
: entry.raw;
|
|
69
|
+
return (
|
|
70
|
+
<button
|
|
71
|
+
key={`${entry.sourcePath}:${entry.line}:${i}`}
|
|
72
|
+
className="notes-backlink-entry"
|
|
73
|
+
onClick={() => handleClick(entry.sourcePath)}
|
|
74
|
+
>
|
|
75
|
+
<span className="notes-backlink-title">
|
|
76
|
+
{backlinkTitle(entry.sourcePath)}
|
|
77
|
+
</span>
|
|
78
|
+
<span className="notes-backlink-line">
|
|
79
|
+
L{entry.line}
|
|
80
|
+
</span>
|
|
81
|
+
<span className="notes-backlink-context">
|
|
82
|
+
{highlightWikilink(context)}
|
|
83
|
+
</span>
|
|
84
|
+
</button>
|
|
85
|
+
);
|
|
86
|
+
})}
|
|
87
|
+
</div>
|
|
88
|
+
)}
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { useNoteCommands, type NoteCommand } from './notes-command-registry';
|
|
3
|
+
import { isCommandPaletteShortcut } from './notes-shortcuts';
|
|
4
|
+
|
|
5
|
+
type NotesCommandPaletteProps = {
|
|
6
|
+
active: boolean;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type NoteCommandResult = {
|
|
10
|
+
command: NoteCommand;
|
|
11
|
+
score: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function normalize(value: string): string {
|
|
15
|
+
return value.trim().toLowerCase();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function fuzzyScore(value: string, query: string): number | null {
|
|
19
|
+
const haystack = normalize(value);
|
|
20
|
+
const needle = normalize(query);
|
|
21
|
+
if (!needle) return 1;
|
|
22
|
+
if (haystack.startsWith(needle)) return 1000 - haystack.length;
|
|
23
|
+
const contains = haystack.indexOf(needle);
|
|
24
|
+
if (contains >= 0) return 700 - contains;
|
|
25
|
+
|
|
26
|
+
let lastIndex = -1;
|
|
27
|
+
let consecutive = 0;
|
|
28
|
+
let score = 0;
|
|
29
|
+
for (const char of needle) {
|
|
30
|
+
const index = haystack.indexOf(char, lastIndex + 1);
|
|
31
|
+
if (index < 0) return null;
|
|
32
|
+
consecutive = index === lastIndex + 1 ? consecutive + 1 : 0;
|
|
33
|
+
score += 4 + consecutive * 3 - Math.min(index, 40) * 0.1;
|
|
34
|
+
lastIndex = index;
|
|
35
|
+
}
|
|
36
|
+
return score;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function scoreCommand(command: NoteCommand, query: string): NoteCommandResult | null {
|
|
40
|
+
const candidates = [
|
|
41
|
+
{ value: command.label, boost: 120 },
|
|
42
|
+
{ value: command.section, boost: 40 },
|
|
43
|
+
...(command.keywords || []).map(keyword => ({ value: keyword, boost: 80 })),
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
let best: NoteCommandResult | null = null;
|
|
47
|
+
for (const candidate of candidates) {
|
|
48
|
+
const fieldScore = fuzzyScore(candidate.value, query);
|
|
49
|
+
if (fieldScore == null) continue;
|
|
50
|
+
const score = fieldScore + candidate.boost;
|
|
51
|
+
if (!best || score > best.score) best = { command, score };
|
|
52
|
+
}
|
|
53
|
+
return best;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function filterNoteCommands(commands: NoteCommand[], query: string): NoteCommandResult[] {
|
|
57
|
+
return commands
|
|
58
|
+
.map(command => scoreCommand(command, query.trim()))
|
|
59
|
+
.filter((result): result is NoteCommandResult => Boolean(result))
|
|
60
|
+
.sort((a, b) => {
|
|
61
|
+
if (b.score !== a.score) return b.score - a.score;
|
|
62
|
+
const section = a.command.section.localeCompare(b.command.section);
|
|
63
|
+
if (section !== 0) return section;
|
|
64
|
+
return a.command.label.localeCompare(b.command.label);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function nextIndex(current: number, delta: number, count: number): number {
|
|
69
|
+
if (count <= 0) return 0;
|
|
70
|
+
return Math.max(0, Math.min(count - 1, current + delta));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function NotesCommandPalette(props: NotesCommandPaletteProps) {
|
|
74
|
+
const commands = useNoteCommands();
|
|
75
|
+
const [open, setOpen] = useState(false);
|
|
76
|
+
const [query, setQuery] = useState('');
|
|
77
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
78
|
+
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
79
|
+
|
|
80
|
+
const results = useMemo(() => filterNoteCommands(commands, query), [commands, query]);
|
|
81
|
+
const activeId = results[activeIndex] ? `notes-command-palette-option-${activeIndex}` : undefined;
|
|
82
|
+
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (!props.active) {
|
|
85
|
+
setOpen(false);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
function handleShortcut(event: KeyboardEvent): void {
|
|
89
|
+
if (!isCommandPaletteShortcut(event)) return;
|
|
90
|
+
event.preventDefault();
|
|
91
|
+
setOpen(current => !current);
|
|
92
|
+
}
|
|
93
|
+
window.addEventListener('keydown', handleShortcut);
|
|
94
|
+
return () => window.removeEventListener('keydown', handleShortcut);
|
|
95
|
+
}, [props.active]);
|
|
96
|
+
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
if (!open) return;
|
|
99
|
+
setQuery('');
|
|
100
|
+
setActiveIndex(0);
|
|
101
|
+
requestAnimationFrame(() => inputRef.current?.focus());
|
|
102
|
+
}, [open]);
|
|
103
|
+
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (activeIndex >= results.length) setActiveIndex(Math.max(0, results.length - 1));
|
|
106
|
+
}, [activeIndex, results.length]);
|
|
107
|
+
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
const active = activeId ? document.getElementById(activeId) : null;
|
|
110
|
+
active?.scrollIntoView({ block: 'nearest' });
|
|
111
|
+
}, [activeId]);
|
|
112
|
+
|
|
113
|
+
function executeCommand(command: NoteCommand): void {
|
|
114
|
+
if (command.disabled) return;
|
|
115
|
+
setOpen(false);
|
|
116
|
+
try {
|
|
117
|
+
void Promise.resolve(command.run()).catch(error => {
|
|
118
|
+
console.warn('[notes-command-palette]', error);
|
|
119
|
+
});
|
|
120
|
+
} catch (error) {
|
|
121
|
+
console.warn('[notes-command-palette]', error);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function runActive(): void {
|
|
126
|
+
const command = results[activeIndex]?.command;
|
|
127
|
+
if (command) executeCommand(command);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function handleInputKey(event: React.KeyboardEvent<HTMLInputElement>): void {
|
|
131
|
+
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'p') {
|
|
132
|
+
event.preventDefault();
|
|
133
|
+
event.stopPropagation();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (event.key === 'ArrowDown') {
|
|
137
|
+
event.preventDefault();
|
|
138
|
+
setActiveIndex(index => nextIndex(index, 1, results.length));
|
|
139
|
+
} else if (event.key === 'ArrowUp') {
|
|
140
|
+
event.preventDefault();
|
|
141
|
+
setActiveIndex(index => nextIndex(index, -1, results.length));
|
|
142
|
+
} else if (event.key === 'Enter') {
|
|
143
|
+
event.preventDefault();
|
|
144
|
+
runActive();
|
|
145
|
+
} else if (event.key === 'Escape') {
|
|
146
|
+
event.preventDefault();
|
|
147
|
+
setOpen(false);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (!open) return null;
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<div className="notes-command-palette-backdrop" role="presentation" onClick={() => setOpen(false)} data-notes-palette>
|
|
155
|
+
<section
|
|
156
|
+
className="notes-command-palette"
|
|
157
|
+
role="dialog"
|
|
158
|
+
aria-modal="true"
|
|
159
|
+
aria-label="Notes command palette"
|
|
160
|
+
onClick={event => event.stopPropagation()}
|
|
161
|
+
onKeyDown={event => event.stopPropagation()}
|
|
162
|
+
>
|
|
163
|
+
<input
|
|
164
|
+
ref={inputRef}
|
|
165
|
+
className="notes-command-palette-input"
|
|
166
|
+
type="search"
|
|
167
|
+
placeholder="Run command"
|
|
168
|
+
value={query}
|
|
169
|
+
onChange={event => { setQuery(event.currentTarget.value); setActiveIndex(0); }}
|
|
170
|
+
onKeyDown={handleInputKey}
|
|
171
|
+
aria-label="Run Notes command"
|
|
172
|
+
aria-controls="notes-command-palette-results"
|
|
173
|
+
aria-activedescendant={activeId}
|
|
174
|
+
/>
|
|
175
|
+
<div
|
|
176
|
+
id="notes-command-palette-results"
|
|
177
|
+
className="notes-command-palette-list"
|
|
178
|
+
role="listbox"
|
|
179
|
+
aria-label="Matching commands"
|
|
180
|
+
>
|
|
181
|
+
{results.map((result, index) => {
|
|
182
|
+
const command = result.command;
|
|
183
|
+
const active = index === activeIndex;
|
|
184
|
+
return (
|
|
185
|
+
<button
|
|
186
|
+
id={`notes-command-palette-option-${index}`}
|
|
187
|
+
key={command.id}
|
|
188
|
+
type="button"
|
|
189
|
+
role="option"
|
|
190
|
+
aria-selected={active}
|
|
191
|
+
aria-disabled={command.disabled || undefined}
|
|
192
|
+
className={`notes-command-palette-item${active ? ' is-active' : ''}${command.disabled ? ' is-disabled' : ''}`}
|
|
193
|
+
onMouseEnter={() => setActiveIndex(index)}
|
|
194
|
+
onClick={() => executeCommand(command)}
|
|
195
|
+
>
|
|
196
|
+
<span className="notes-command-palette-title">{command.label}</span>
|
|
197
|
+
<span className="notes-command-palette-meta">{command.disabledReason || command.section}</span>
|
|
198
|
+
{command.shortcut && <kbd className="notes-command-palette-shortcut">{command.shortcut}</kbd>}
|
|
199
|
+
</button>
|
|
200
|
+
);
|
|
201
|
+
})}
|
|
202
|
+
{results.length === 0 && <p className="notes-command-palette-empty">No matching commands.</p>}
|
|
203
|
+
</div>
|
|
204
|
+
<footer className="notes-command-palette-footer">
|
|
205
|
+
<span><kbd>Up/Down</kbd> move</span>
|
|
206
|
+
<span><kbd>Enter</kbd> run</span>
|
|
207
|
+
<span><kbd>Esc</kbd> close</span>
|
|
208
|
+
</footer>
|
|
209
|
+
</section>
|
|
210
|
+
</div>
|
|
211
|
+
);
|
|
212
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
import * as d3 from 'd3';
|
|
3
|
+
import type { NoteGraphNode, NoteGraphEdge } from '../types';
|
|
4
|
+
|
|
5
|
+
type NotesGraphViewProps = {
|
|
6
|
+
graph: { nodes: NoteGraphNode[]; edges: NoteGraphEdge[] };
|
|
7
|
+
selectedPath: string | null;
|
|
8
|
+
onNavigate: (path: string) => void;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type SimNode = NoteGraphNode & d3.SimulationNodeDatum;
|
|
12
|
+
type SimEdge = { source: SimNode; target: SimNode; raw: string };
|
|
13
|
+
|
|
14
|
+
function connectionCount(nodeId: string, edges: NoteGraphEdge[]): number {
|
|
15
|
+
return edges.filter(e => e.source === nodeId || e.target === nodeId).length;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function nodeColor(node: NoteGraphNode, selectedPath: string | null): string {
|
|
19
|
+
if (node.id === selectedPath) return 'var(--accent-primary, #8ab4ff)';
|
|
20
|
+
if (node.kind === 'missing') return 'var(--status-error, #f87171)';
|
|
21
|
+
if (node.kind === 'ambiguous') return 'var(--status-warning, #fbbf24)';
|
|
22
|
+
return 'var(--text-tertiary, #888)';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function NotesGraphView(props: NotesGraphViewProps) {
|
|
26
|
+
const svgRef = useRef<SVGSVGElement | null>(null);
|
|
27
|
+
const navigateRef = useRef(props.onNavigate);
|
|
28
|
+
navigateRef.current = props.onNavigate;
|
|
29
|
+
|
|
30
|
+
const { graph, selectedPath } = props;
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (!svgRef.current || graph.nodes.length === 0) return;
|
|
34
|
+
const svg = d3.select(svgRef.current);
|
|
35
|
+
svg.selectAll('*').remove();
|
|
36
|
+
|
|
37
|
+
const width = svgRef.current.clientWidth || 800;
|
|
38
|
+
const height = svgRef.current.clientHeight || 600;
|
|
39
|
+
|
|
40
|
+
const nodes: SimNode[] = graph.nodes.map(n => ({ ...n }));
|
|
41
|
+
const nodeById = new Map(nodes.map(n => [n.id, n]));
|
|
42
|
+
const edges: SimEdge[] = graph.edges
|
|
43
|
+
.filter(e => nodeById.has(e.source) && nodeById.has(e.target))
|
|
44
|
+
.map(e => ({
|
|
45
|
+
source: nodeById.get(e.source)!,
|
|
46
|
+
target: nodeById.get(e.target)!,
|
|
47
|
+
raw: e.raw,
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
const maxConn = d3.max(nodes, n => connectionCount(n.id, graph.edges)) ?? 1;
|
|
51
|
+
const radiusScale = d3.scaleSqrt().domain([0, maxConn]).range([4, 16]);
|
|
52
|
+
|
|
53
|
+
const g = svg.append('g');
|
|
54
|
+
|
|
55
|
+
const zoom = d3.zoom<SVGSVGElement, unknown>()
|
|
56
|
+
.scaleExtent([0.2, 5])
|
|
57
|
+
.on('zoom', (event) => { g.attr('transform', event.transform); });
|
|
58
|
+
svg.call(zoom);
|
|
59
|
+
|
|
60
|
+
const simulation = d3.forceSimulation(nodes)
|
|
61
|
+
.force('link', d3.forceLink<SimNode, SimEdge>(edges).id((d) => d.id).distance(80))
|
|
62
|
+
.force('charge', d3.forceManyBody().strength(-120))
|
|
63
|
+
.force('center', d3.forceCenter(width / 2, height / 2))
|
|
64
|
+
.force('collision', d3.forceCollide<SimNode>().radius((d) =>
|
|
65
|
+
radiusScale(connectionCount(d.id, graph.edges)) + 4,
|
|
66
|
+
));
|
|
67
|
+
|
|
68
|
+
const link = g.append('g')
|
|
69
|
+
.selectAll('line')
|
|
70
|
+
.data(edges)
|
|
71
|
+
.join('line')
|
|
72
|
+
.attr('stroke', 'var(--border-secondary, #444)')
|
|
73
|
+
.attr('stroke-width', 1)
|
|
74
|
+
.attr('stroke-opacity', 0.5);
|
|
75
|
+
|
|
76
|
+
const node = g.append('g')
|
|
77
|
+
.selectAll<SVGGElement, SimNode>('g')
|
|
78
|
+
.data(nodes)
|
|
79
|
+
.join('g')
|
|
80
|
+
.attr('class', 'graph-node')
|
|
81
|
+
.attr('cursor', 'pointer')
|
|
82
|
+
.on('click', (_event: MouseEvent, d: SimNode) => {
|
|
83
|
+
if (d.kind === 'note') navigateRef.current(d.id);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
node.append('circle')
|
|
87
|
+
.attr('r', d => radiusScale(connectionCount(d.id, graph.edges)))
|
|
88
|
+
.attr('fill', d => nodeColor(d, selectedPath))
|
|
89
|
+
.attr('stroke', d => d.id === selectedPath ? 'var(--accent-hover, #aac8ff)' : 'none')
|
|
90
|
+
.attr('stroke-width', 2);
|
|
91
|
+
|
|
92
|
+
node.append('text')
|
|
93
|
+
.text(d => d.title)
|
|
94
|
+
.attr('dx', d => radiusScale(connectionCount(d.id, graph.edges)) + 4)
|
|
95
|
+
.attr('dy', '0.35em')
|
|
96
|
+
.attr('fill', 'var(--text-secondary, #aaa)')
|
|
97
|
+
.attr('font-size', '11px')
|
|
98
|
+
.attr('pointer-events', 'none');
|
|
99
|
+
|
|
100
|
+
const drag = d3.drag<SVGGElement, SimNode>()
|
|
101
|
+
.on('start', (event, d) => {
|
|
102
|
+
if (!event.active) simulation.alphaTarget(0.3).restart();
|
|
103
|
+
d.fx = d.x;
|
|
104
|
+
d.fy = d.y;
|
|
105
|
+
})
|
|
106
|
+
.on('drag', (event, d) => {
|
|
107
|
+
d.fx = event.x;
|
|
108
|
+
d.fy = event.y;
|
|
109
|
+
})
|
|
110
|
+
.on('end', (event, d) => {
|
|
111
|
+
if (!event.active) simulation.alphaTarget(0);
|
|
112
|
+
d.fx = null;
|
|
113
|
+
d.fy = null;
|
|
114
|
+
});
|
|
115
|
+
node.call(drag);
|
|
116
|
+
|
|
117
|
+
simulation.on('tick', () => {
|
|
118
|
+
link
|
|
119
|
+
.attr('x1', (d) => (d.source as SimNode).x ?? 0)
|
|
120
|
+
.attr('y1', (d) => (d.source as SimNode).y ?? 0)
|
|
121
|
+
.attr('x2', (d) => (d.target as SimNode).x ?? 0)
|
|
122
|
+
.attr('y2', (d) => (d.target as SimNode).y ?? 0);
|
|
123
|
+
node.attr('transform', (d) => `translate(${d.x ?? 0},${d.y ?? 0})`);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
return () => { simulation.stop(); };
|
|
127
|
+
}, [graph, selectedPath]);
|
|
128
|
+
|
|
129
|
+
if (graph.nodes.length === 0) {
|
|
130
|
+
return (
|
|
131
|
+
<div className="notes-graph-view notes-graph-empty">
|
|
132
|
+
No notes to display
|
|
133
|
+
</div>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<div className="notes-graph-view">
|
|
139
|
+
<svg ref={svgRef} className="notes-graph-svg" width="100%" height="100%" />
|
|
140
|
+
<div className="notes-graph-info">
|
|
141
|
+
{graph.nodes.filter(n => n.kind === 'note').length} notes · {graph.edges.length} links
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { useEffect, useState, type CSSProperties } from 'react';
|
|
2
|
-
import { createNoteFile, createNoteFolder, renameNotePath, trashNotePath } from './notes-api';
|
|
1
|
+
import { useEffect, useMemo, useState, type CSSProperties } from 'react';
|
|
2
|
+
import { createNoteFile, createNoteFolder, fetchNoteTemplate, fetchNoteTemplates, renameNotePath, trashNotePath } from './notes-api';
|
|
3
|
+
import { applyTemplate } from './template-engine';
|
|
3
4
|
import { NewFolderIcon, NewNoteIcon, NotesFileTree, RefreshIcon } from './NotesFileTree';
|
|
4
5
|
import { NotesSearchSidebar } from './NotesSearchSidebar';
|
|
6
|
+
import { useRegisterNoteCommands, type NoteCommand } from './notes-command-registry';
|
|
5
7
|
import { publishInvalidation } from '../sync/invalidation-bus';
|
|
8
|
+
import { DashboardApiError, type NoteTemplate } from '../api';
|
|
6
9
|
import type { NotesTreeEntry } from './notes-types';
|
|
7
|
-
|
|
8
10
|
export type NotesSidebarMode = 'files' | 'search';
|
|
9
|
-
|
|
10
11
|
type NotesSidebarProps = {
|
|
11
12
|
tree: NotesTreeEntry[];
|
|
12
13
|
loading: boolean;
|
|
@@ -94,19 +95,108 @@ function SearchIcon() {
|
|
|
94
95
|
);
|
|
95
96
|
}
|
|
96
97
|
|
|
98
|
+
function hasFile(entries: NotesTreeEntry[], name: string): boolean {
|
|
99
|
+
return entries.some(e => e.kind === 'file' && e.name === name);
|
|
100
|
+
}
|
|
101
|
+
|
|
97
102
|
export function NotesSidebar(props: NotesSidebarProps) {
|
|
98
103
|
const style = { '--notes-tree-width': `${props.treeWidth}px` } as CSSProperties;
|
|
99
104
|
const [error, setError] = useState<string | null>(null);
|
|
100
105
|
const [status, setStatus] = useState<string | null>(null);
|
|
101
106
|
const [selectedFolderPath, setSelectedFolderPath] = useState<string | null>(null);
|
|
107
|
+
const [templates, setTemplates] = useState<NoteTemplate[]>([]);
|
|
108
|
+
const [templatesLoaded, setTemplatesLoaded] = useState<'pending' | 'ok' | 'error'>('pending');
|
|
109
|
+
const [pendingNewNote, setPendingNewNote] = useState<string | null>(null);
|
|
110
|
+
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
void fetchNoteTemplates()
|
|
113
|
+
.then(t => { setTemplates(t); setTemplatesLoaded('ok'); })
|
|
114
|
+
.catch(() => { setTemplatesLoaded('error'); });
|
|
115
|
+
}, []);
|
|
116
|
+
|
|
117
|
+
async function openToday(): Promise<void> {
|
|
118
|
+
const now = new Date();
|
|
119
|
+
const pad = (n: number) => String(n).padStart(2, '0');
|
|
120
|
+
const todayName = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}.md`;
|
|
121
|
+
if (hasFile(props.tree, todayName)) {
|
|
122
|
+
props.onSelectedPathChange(todayName);
|
|
123
|
+
setError(null);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (templatesLoaded === 'pending') {
|
|
127
|
+
setError('Templates are still loading. Please try again.');
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (templatesLoaded === 'error') {
|
|
131
|
+
setError('Template list failed to load. Cannot determine daily template.');
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
setStatus(null);
|
|
136
|
+
setError(null);
|
|
137
|
+
const dailyTemplate = templates.find(t => t.name === 'daily');
|
|
138
|
+
let content = '';
|
|
139
|
+
if (dailyTemplate) {
|
|
140
|
+
try {
|
|
141
|
+
const tmpl = await fetchNoteTemplate(dailyTemplate.name);
|
|
142
|
+
content = applyTemplate(tmpl.content, { title: todayName.replace(/\.md$/, '') });
|
|
143
|
+
} catch {
|
|
144
|
+
setError('Daily template failed to load. Note was not created.');
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const created = await createNoteFile(todayName, content);
|
|
149
|
+
props.onSelectedPathChange(created.path);
|
|
150
|
+
await props.onRefreshTree(created.path);
|
|
151
|
+
publishInvalidation({ topics: ['notes'], reason: 'note:created', source: 'ui', sourceId: 'notes-sidebar' });
|
|
152
|
+
} catch (err) {
|
|
153
|
+
if (err instanceof DashboardApiError && err.status === 409 && err.code === 'note_path_exists') {
|
|
154
|
+
setError(null);
|
|
155
|
+
props.onSelectedPathChange(todayName);
|
|
156
|
+
await props.onRefreshTree(todayName);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
setError((err as Error).message);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
102
162
|
|
|
103
163
|
async function createNote(): Promise<void> {
|
|
164
|
+
if (templatesLoaded === 'pending') {
|
|
165
|
+
setError('Templates are still loading. Please try again.');
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (templatesLoaded === 'error') {
|
|
169
|
+
setError('Template list failed to load. Cannot offer template picker.');
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
104
172
|
const fallback = selectedFolderPath ? `${selectedFolderPath}/untitled.md` : 'untitled.md';
|
|
105
173
|
const name = window.prompt('Note path', fallback);
|
|
106
174
|
if (!name) return;
|
|
175
|
+
const notePath = name.endsWith('.md') ? name : `${name}.md`;
|
|
176
|
+
if (templates.length > 0) {
|
|
177
|
+
setPendingNewNote(notePath);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
await finishCreateNote(notePath, null);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function finishCreateNote(notePath: string, templateName: string | null): Promise<void> {
|
|
184
|
+
setPendingNewNote(null);
|
|
185
|
+
let content = '';
|
|
186
|
+
if (templateName) {
|
|
187
|
+
try {
|
|
188
|
+
const tmpl = await fetchNoteTemplate(templateName);
|
|
189
|
+
const title = (notePath.endsWith('.md') ? notePath.slice(0, -3) : notePath).split('/').pop() ?? '';
|
|
190
|
+
content = applyTemplate(tmpl.content, { title });
|
|
191
|
+
} catch {
|
|
192
|
+
setError(`Template "${templateName}" failed to load. Note was not created.`);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
107
196
|
try {
|
|
108
197
|
setStatus(null);
|
|
109
|
-
|
|
198
|
+
setError(null);
|
|
199
|
+
const created = await createNoteFile(notePath, content);
|
|
110
200
|
props.onSelectedPathChange(created.path);
|
|
111
201
|
await props.onRefreshTree(created.path);
|
|
112
202
|
publishInvalidation({ topics: ['notes'], reason: 'note:created', source: 'ui', sourceId: 'notes-sidebar' });
|
|
@@ -251,6 +341,77 @@ export function NotesSidebar(props: NotesSidebarProps) {
|
|
|
251
341
|
}
|
|
252
342
|
}
|
|
253
343
|
|
|
344
|
+
const templateDisabledReason = templatesLoaded === 'pending'
|
|
345
|
+
? 'Templates are still loading'
|
|
346
|
+
: templatesLoaded === 'error'
|
|
347
|
+
? 'Templates failed to load'
|
|
348
|
+
: undefined;
|
|
349
|
+
const sidebarCommands = useMemo<NoteCommand[]>(() => [
|
|
350
|
+
{
|
|
351
|
+
id: 'sidebar:new-note',
|
|
352
|
+
section: 'Create',
|
|
353
|
+
label: 'New note',
|
|
354
|
+
shortcut: 'Alt+N',
|
|
355
|
+
keywords: ['create', 'file'],
|
|
356
|
+
disabled: templatesLoaded !== 'ok',
|
|
357
|
+
disabledReason: templateDisabledReason,
|
|
358
|
+
run: () => void createNote(),
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
id: 'sidebar:new-folder',
|
|
362
|
+
section: 'Create',
|
|
363
|
+
label: 'New folder',
|
|
364
|
+
keywords: ['directory'],
|
|
365
|
+
run: () => void createFolder(),
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
id: 'sidebar:today',
|
|
369
|
+
section: 'Create',
|
|
370
|
+
label: 'Open today',
|
|
371
|
+
keywords: ['daily', 'journal'],
|
|
372
|
+
disabled: templatesLoaded !== 'ok',
|
|
373
|
+
disabledReason: templateDisabledReason,
|
|
374
|
+
run: () => void openToday(),
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
id: 'sidebar:search',
|
|
378
|
+
section: 'Navigate',
|
|
379
|
+
label: 'Search notes',
|
|
380
|
+
shortcut: 'Cmd+Shift+F',
|
|
381
|
+
run: props.onOpenSearch,
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
id: 'sidebar:refresh',
|
|
385
|
+
section: 'File',
|
|
386
|
+
label: 'Refresh notes',
|
|
387
|
+
disabled: props.loading,
|
|
388
|
+
disabledReason: 'Refresh already running',
|
|
389
|
+
run: () => void props.onRefreshTree(),
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
id: 'sidebar:clear-tag-filter',
|
|
393
|
+
section: 'Navigate',
|
|
394
|
+
label: 'Clear tag filter',
|
|
395
|
+
disabled: !props.tagFilter,
|
|
396
|
+
disabledReason: 'No active tag filter',
|
|
397
|
+
run: props.onClearTagFilter,
|
|
398
|
+
},
|
|
399
|
+
], [
|
|
400
|
+
props.loading,
|
|
401
|
+
props.onClearTagFilter,
|
|
402
|
+
props.onOpenSearch,
|
|
403
|
+
props.onRefreshTree,
|
|
404
|
+
props.onSelectedPathChange,
|
|
405
|
+
props.selectedPath,
|
|
406
|
+
props.tagFilter,
|
|
407
|
+
props.tree,
|
|
408
|
+
selectedFolderPath,
|
|
409
|
+
templateDisabledReason,
|
|
410
|
+
templates,
|
|
411
|
+
templatesLoaded,
|
|
412
|
+
]);
|
|
413
|
+
useRegisterNoteCommands(sidebarCommands);
|
|
414
|
+
|
|
254
415
|
return (
|
|
255
416
|
<aside className="notes-tree" style={style}>
|
|
256
417
|
{(props.error || error) && <section className="state error-state">{props.error || error}</section>}
|
|
@@ -295,6 +456,23 @@ export function NotesSidebar(props: NotesSidebarProps) {
|
|
|
295
456
|
</button>
|
|
296
457
|
</div>
|
|
297
458
|
)}
|
|
459
|
+
{pendingNewNote && (
|
|
460
|
+
<div className="notes-template-picker">
|
|
461
|
+
<div className="notes-template-picker-label">Template for {pathName(pendingNewNote)}</div>
|
|
462
|
+
<div className="notes-template-picker-options">
|
|
463
|
+
<button type="button" className="notes-template-picker-option" onClick={() => void finishCreateNote(pendingNewNote, null)}>Blank</button>
|
|
464
|
+
{templates.map(t => (
|
|
465
|
+
<button key={t.name} type="button" className="notes-template-picker-option" onClick={() => void finishCreateNote(pendingNewNote, t.name)}>{t.name}</button>
|
|
466
|
+
))}
|
|
467
|
+
</div>
|
|
468
|
+
<button type="button" className="notes-template-picker-cancel" onClick={() => setPendingNewNote(null)}>Cancel</button>
|
|
469
|
+
</div>
|
|
470
|
+
)}
|
|
471
|
+
<div className="notes-today-bar">
|
|
472
|
+
<button type="button" className="notes-today-button" onClick={() => void openToday()}>
|
|
473
|
+
Today
|
|
474
|
+
</button>
|
|
475
|
+
</div>
|
|
298
476
|
<NotesFileTree
|
|
299
477
|
entries={props.tree}
|
|
300
478
|
selectedPath={props.selectedPath}
|