@tequila99/githuman 0.7.0 → 0.9.0
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.md +75 -0
- package/dist/cli/config.js +13 -1
- package/dist/cli/server-runtime.js +3 -0
- package/dist/server/app.js +37 -4
- package/dist/server/config/agents.js +2 -1
- package/dist/server/config/openapi.js +4 -0
- package/dist/server/db/migrations.js +61 -0
- package/dist/server/errors/note-assets.js +3 -0
- package/dist/server/errors/notes.js +3 -0
- package/dist/server/hooks/notes/same-origin.js +37 -0
- package/dist/server/hooks/terminal/local-origin.js +2 -1
- package/dist/server/repositories/notes-catalog.repo.js +129 -0
- package/dist/server/repositories/notes.repo.js +444 -0
- package/dist/server/routes/agent.js +1 -0
- package/dist/server/routes/notes.js +213 -0
- package/dist/server/routes/terminal.js +2 -1
- package/dist/server/services/agent/context-builder.js +13 -0
- package/dist/server/services/git.service.js +15 -11
- package/dist/server/services/notes-external-assets.service.js +177 -0
- package/dist/server/services/notes-identity.service.js +811 -0
- package/dist/server/services/notes-operations.service.js +296 -0
- package/dist/server/services/notes-watcher.service.js +44 -0
- package/dist/server/services/notes-writer.service.js +116 -0
- package/dist/server/services/notes.service.js +150 -0
- package/dist/server/services/terminal/backend.js +6 -3
- package/dist/server/services/terminal/channel.js +6 -3
- package/dist/server/services/terminal/registry.js +10 -5
- package/dist/server/services/terminal/session.js +10 -5
- package/dist/server/services/terminal/tokens.js +4 -2
- package/dist/server/utils/hash.js +4 -0
- package/dist/server/utils/note-asset-format.js +19 -0
- package/dist/server/utils/remote-asset-request.js +193 -0
- package/dist/server/utils/request-abort.js +20 -0
- package/dist/server/utils/system-error.js +14 -0
- package/dist/shared/agents/schemas.js +14 -0
- package/dist/shared/events/types.js +1 -0
- package/dist/shared/notes/asset-formats.js +57 -0
- package/dist/shared/notes/constants.js +7 -0
- package/dist/shared/notes/document.js +118 -0
- package/dist/shared/notes/external-assets.js +153 -0
- package/dist/shared/notes/identity.js +24 -0
- package/dist/shared/notes/links.js +44 -0
- package/dist/shared/notes/note-path.js +17 -0
- package/dist/shared/notes/reference-path.js +55 -0
- package/dist/shared/notes/references.js +190 -0
- package/dist/shared/notes/schemas.js +222 -0
- package/dist/shared/notes/types.js +1 -0
- package/dist/shared/utils/heading-slug.js +7 -0
- package/dist/shared/utils/is-uuid.js +5 -0
- package/dist/shared/utils/safe-decode.js +9 -0
- package/dist/shared/utils/web-url.js +15 -0
- package/dist/web/assets/AppPage-DdmqiV2R.js +1 -0
- package/dist/web/assets/ClosePopup-lpH_1Ju2.js +1 -0
- package/dist/web/assets/CreateButton-Dnpwobd9.js +93 -0
- package/dist/web/assets/CreateReviewFabButton-KvKPEJzI.js +1 -0
- package/dist/web/assets/{ErrorNotFound-CYOZg8yN.js → ErrorNotFound-Cq0wNQMu.js} +1 -1
- package/dist/web/assets/IndexPage-DE-IkAhK.js +1 -0
- package/dist/web/assets/IndexPage-F8MMAAkn.css +1 -0
- package/dist/web/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/web/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/web/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/web/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/web/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/web/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/web/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/web/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/web/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/web/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/web/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/web/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/web/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/web/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/web/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/web/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/web/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/web/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/web/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/web/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/web/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/web/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/web/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/web/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/web/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/web/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/web/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/web/assets/KaTeX_Size3-Regular-gV2CO0n9.woff2 +0 -0
- package/dist/web/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/web/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/web/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/web/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/web/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/web/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/web/assets/MainLayout-DM30ynlR.css +1 -0
- package/dist/web/assets/MainLayout-wWA8ckBg.js +6 -0
- package/dist/web/assets/NoteVisualEditor-Bj13_oEl.js +1054 -0
- package/dist/web/assets/NoteVisualEditor-IH3s3d3W.css +1 -0
- package/dist/web/assets/NotesPage-FidBg83M.js +4 -0
- package/dist/web/assets/NotesPage-KaCEnfdz.css +1 -0
- package/dist/web/assets/PanelResize-C51WwwLD.css +1 -0
- package/dist/web/assets/PanelResize-DY0Nzmql.js +1 -0
- package/dist/web/assets/{PdfPreview-CIiKnzQN.js → PdfPreview-C7HomxEg.js} +1 -1
- package/dist/web/assets/QBtn-DoggJe1Q.js +1 -0
- package/dist/web/assets/QFab-C-YZWgmW.js +1 -0
- package/dist/web/assets/QForm-DpEhYVCa.js +4 -0
- package/dist/web/assets/QInput-DJgTh2Qy.js +1 -0
- package/dist/web/assets/QResizeObserver-DeRFR_WF.js +1 -0
- package/dist/web/assets/QScrollArea-BSSZoaV5.js +1 -0
- package/dist/web/assets/QSelect-2iOBkp1K.js +1 -0
- package/dist/web/assets/QSpace-B5NneHAd.js +1 -0
- package/dist/web/assets/QVirtualScroll-CZJMBpmR.js +1 -0
- package/dist/web/assets/ReviewDetailPage-BI1iAhSf.js +1 -0
- package/dist/web/assets/ReviewsPage-RyST0FlH.js +1 -0
- package/dist/web/assets/{StatusSwitcher-DTD5inU1.css → StatusSwitcher-BCv_9MtI.css} +1 -1
- package/dist/web/assets/StatusSwitcher-DAeNzNP7.js +1 -0
- package/dist/web/assets/{TerminalView-Ch-ndYr6.js → TerminalView-BAzJgUTq.js} +2 -2
- package/dist/web/assets/{TerminalView-BurzSD2L.css → TerminalView-C1jFlfLH.css} +1 -1
- package/dist/web/assets/_baseMerge-BzZFQ6mI.js +1 -0
- package/dist/web/assets/{abnfDiagram-O67JEVCF-Cab71FlB.js → abnfDiagram-O67JEVCF-BNZQ7duT.js} +1 -1
- package/dist/web/assets/agent-store-DP6Vrw6L.js +113 -0
- package/dist/web/assets/apl-gfMMiTNf.js +1 -0
- package/dist/web/assets/app-info-store-u6ZVFjv5.js +1 -0
- package/dist/web/assets/architecture-7GRP2DOG-BSdnjp9Z.js +1 -0
- package/dist/web/assets/{architectureDiagram-NJMV4G6O-Dl1WKOEi.js → architectureDiagram-NJMV4G6O-Bkj0kfFn.js} +1 -1
- package/dist/web/assets/asciiarmor-C_eisQCp.js +1 -0
- package/dist/web/assets/asn1-DCl_8MCW.js +1 -0
- package/dist/web/assets/asterisk-BEn0Cwpx.js +1 -0
- package/dist/web/assets/{blockDiagram-BEXU5L5S-CZ3Lxybj.js → blockDiagram-BEXU5L5S-CXO3zN2c.js} +1 -1
- package/dist/web/assets/brainfuck-D5EjA2JK.js +1 -0
- package/dist/web/assets/{c4Diagram-YGBWAQC7-ULi06PEd.js → c4Diagram-YGBWAQC7-BHfMeZNf.js} +1 -1
- package/dist/web/assets/ccount-CcG1m-Yh.js +1 -0
- package/dist/web/assets/channel-DmoOM6AB.js +1 -0
- package/dist/web/assets/{chunk-3FUC2YCW-B1f60wx3.js → chunk-3FUC2YCW-z1WbH7Gi.js} +1 -1
- package/dist/web/assets/{chunk-5DYCD2WN-f6Pcl3dF.js → chunk-5DYCD2WN-K9HPch97.js} +1 -1
- package/dist/web/assets/{chunk-742MDFTN-szX1QirW.js → chunk-742MDFTN-Bhjp9fC-.js} +1 -1
- package/dist/web/assets/{chunk-7INBJB4K-CP_ynmAQ.js → chunk-7INBJB4K-Do0EmU14.js} +1 -1
- package/dist/web/assets/{chunk-7M6MHVWA-DPL74uZE.js → chunk-7M6MHVWA-veeEjsOh.js} +5 -5
- package/dist/web/assets/{chunk-7PRAP22T-DH9TawZY.js → chunk-7PRAP22T-CPqB5tCK.js} +1 -1
- package/dist/web/assets/{chunk-GTNCS2PH-CKAuGvnf.js → chunk-GTNCS2PH-Civ1V52P.js} +1 -1
- package/dist/web/assets/{chunk-GWA4HPMP-DTbHCHSN.js → chunk-GWA4HPMP-C7LzAlyZ.js} +1 -1
- package/dist/web/assets/{chunk-MBY4JIJT-ClgvJMtC.js → chunk-MBY4JIJT-DfmJy0o_.js} +1 -1
- package/dist/web/assets/{chunk-NETBCI7D-Dq5Z7z8S.js → chunk-NETBCI7D-BZIKmMvL.js} +1 -1
- package/dist/web/assets/{chunk-O7XYJQB3-C0By5mcm.js → chunk-O7XYJQB3-DcHeu6b7.js} +1 -1
- package/dist/web/assets/{chunk-UA2S7LBM-BtquYpxe.js → chunk-UA2S7LBM-0SR2og6x.js} +1 -1
- package/dist/web/assets/{chunk-WEXAMYUT-BlZL42XJ.js → chunk-WEXAMYUT-CJMSoM-Z.js} +1 -1
- package/dist/web/assets/{chunk-Z7XXMR3K-BbtQbAvv.js → chunk-Z7XXMR3K-q6fce66x.js} +1 -1
- package/dist/web/assets/{chunk-ZIGJFQKS-tC_YdRrm.js → chunk-ZIGJFQKS-DsPzen4Y.js} +1 -1
- package/dist/web/assets/{classDiagram-v2-NBCMYWYE-CvlA_A4N.js → classDiagram-v2-NBCMYWYE-Cpm_bvzb.js} +4 -4
- package/dist/web/assets/clike-D_6L3tnO.js +1 -0
- package/dist/web/assets/clojure-BflJGmDX.js +1 -0
- package/dist/web/assets/cmake-84nbDH0I.js +1 -0
- package/dist/web/assets/cobol-DHLQUbN7.js +1 -0
- package/dist/web/assets/coffeescript-De_zI4J0.js +1 -0
- package/dist/web/assets/commonlisp-DgAKBe0r.js +1 -0
- package/dist/web/assets/core-Bumj8M77.js +12 -0
- package/dist/web/assets/crystal-Bsdphhtm.js +1 -0
- package/dist/web/assets/css-CnaYoQib.js +1 -0
- package/dist/web/assets/{cynefin-OW5HDTMX-DFbhKkIi.js → cynefin-OW5HDTMX-ZTr5XZMW.js} +1 -1
- package/dist/web/assets/{cynefinDiagram-VND7K2PF-DHGaLNQN.js → cynefinDiagram-VND7K2PF-ByUmUx58.js} +1 -1
- package/dist/web/assets/cypher-p9eYesGn.js +1 -0
- package/dist/web/assets/d-DWkQyNnU.js +1 -0
- package/dist/web/assets/dagre-6A5THRUB-mQOy0E3r.js +4 -0
- package/dist/web/assets/{diagram-22UHCM2B-Djjo5ftL.js → diagram-22UHCM2B-BW0LPtyk.js} +8 -8
- package/dist/web/assets/{diagram-3UASUU5V-BcP7pJru.js → diagram-3UASUU5V-jo148ec5.js} +1 -1
- package/dist/web/assets/{diagram-ATOU4E4O-o_vsc8em.js → diagram-ATOU4E4O-CJt4F0i4.js} +1 -1
- package/dist/web/assets/{diagram-CDSNMT55-eyN9Aru8.js → diagram-CDSNMT55-DEShJv56.js} +1 -1
- package/dist/web/assets/{diagram-MLGK6HIB-Dfjipnvb.js → diagram-MLGK6HIB-DOFOADfs.js} +1 -1
- package/dist/web/assets/{diagram-MPIPVDR6-C2chocRe.js → diagram-MPIPVDR6-ux537BMl.js} +1 -1
- package/dist/web/assets/diff-ChtP43wD.js +1 -0
- package/dist/web/assets/dist-3B0OaUZ-.js +1 -0
- package/dist/web/assets/dist-7xWvOKEv.js +1 -0
- package/dist/web/assets/dist-BThMRW5U.js +23 -0
- package/dist/web/assets/dist-BWa3ecCV.js +2 -0
- package/dist/web/assets/dist-BY24_62d.js +1 -0
- package/dist/web/assets/dist-BbdrgL5V.js +11 -0
- package/dist/web/assets/dist-Bo7anpPj.js +9 -0
- package/dist/web/assets/dist-Bu9yRqTU.js +1 -0
- package/dist/web/assets/dist-Bvnostf5.js +1 -0
- package/dist/web/assets/dist-C3sBgvRy.js +1 -0
- package/dist/web/assets/dist-CK1va5C_.js +1 -0
- package/dist/web/assets/dist-CLv-grWO.js +1 -0
- package/dist/web/assets/dist-CqGqStPp.js +1 -0
- package/dist/web/assets/dist-D1EUrS6y.js +6 -0
- package/dist/web/assets/dist-D2Ce45aH.js +13 -0
- package/dist/web/assets/dist-D7Llw4iH.js +1 -0
- package/dist/web/assets/dist-DL0dBIkt.js +1 -0
- package/dist/web/assets/dist-DUt0feNB.js +1 -0
- package/dist/web/assets/dist-DZQ2IsS9.js +1 -0
- package/dist/web/assets/dist-DpKJcdvz.js +1 -0
- package/dist/web/assets/dist-DuZv0LJj.js +1 -0
- package/dist/web/assets/dist-LFPPEX39.js +1 -0
- package/dist/web/assets/dist-lC0EKTFH.js +1 -0
- package/dist/web/assets/dockerfile-DJkHQgkl.js +1 -0
- package/dist/web/assets/dtd-DTF72YNO.js +1 -0
- package/dist/web/assets/dylan-BCDoL_-p.js +1 -0
- package/dist/web/assets/ebnf-D4c0_ac3.js +1 -0
- package/dist/web/assets/{ebnfDiagram-ZINNZB2B-CE58dwC-.js → ebnfDiagram-ZINNZB2B-DEybuZle.js} +1 -1
- package/dist/web/assets/ecl-BVIeWmwY.js +1 -0
- package/dist/web/assets/eiffel-BEjRio4Q.js +1 -0
- package/dist/web/assets/{elk-276RUBZZ-KyTn9aJi.js → elk-276RUBZZ-CZXZRpGb.js} +1 -1
- package/dist/web/assets/elm-Cshzl8qu.js +1 -0
- package/dist/web/assets/{erDiagram-OPXOYQCR-D9aOi7ZD.js → erDiagram-OPXOYQCR-BgRqGAt5.js} +1 -1
- package/dist/web/assets/erlang-BV0c4Hdn.js +1 -0
- package/dist/web/assets/eventmodeling-NTZA5JFV-ZuRq6Sae.js +1 -0
- package/dist/web/assets/factor-LKH5gN8L.js +1 -0
- package/dist/web/assets/fcl-ClOhbFR7.js +1 -0
- package/dist/web/assets/flowDiagram-KWPJA3E3-DRyHAdLb.js +1 -0
- package/dist/web/assets/forth-D6RzAl64.js +1 -0
- package/dist/web/assets/fortran-Bt6PBEDR.js +1 -0
- package/dist/web/assets/{ganttDiagram-FUAMR5RP-DRAI6xWM.js → ganttDiagram-FUAMR5RP-CBJwX1gk.js} +1 -1
- package/dist/web/assets/gas-BHEdbvp9.js +1 -0
- package/dist/web/assets/gherkin-oBAE_ms0.js +1 -0
- package/dist/web/assets/{gitGraph-4MIJSDKK-D1lAB6tr.js → gitGraph-4MIJSDKK-Dw-XA3G1.js} +1 -1
- package/dist/web/assets/{gitGraphDiagram-X574FWY7-77GVnwya.js → gitGraphDiagram-X574FWY7-BpGccoWp.js} +1 -1
- package/dist/web/assets/graphlib-C64HifUL.js +1 -0
- package/dist/web/assets/groovy-CUpZ7dEl.js +1 -0
- package/dist/web/assets/haskell-BRsoo5mP.js +1 -0
- package/dist/web/assets/haxe-DjVOVvNP.js +1 -0
- package/dist/web/assets/http-CLVgA2GD.js +1 -0
- package/dist/web/assets/i18n-sfJvmpVb.js +1 -0
- package/dist/web/assets/idl-BUZw3wgd.js +1 -0
- package/dist/web/assets/index-B2CzafCr.js +2 -0
- package/dist/web/assets/{info-A6RAGUB7-Di1qRpwO.js → info-A6RAGUB7-Dj5RfJsg.js} +1 -1
- package/dist/web/assets/{infoDiagram-VRGFBTTK-gceHneJ0.js → infoDiagram-VRGFBTTK-DyVSH991.js} +1 -1
- package/dist/web/assets/{init-app-IHgDyoRH.js → init-app-DSS44aRI.js} +1 -1
- package/dist/web/assets/{is-DssfAlMe.js → is-CE4jkrGQ.js} +1 -1
- package/dist/web/assets/isArrayLikeObject-BbaDyxId.js +1 -0
- package/dist/web/assets/{ishikawaDiagram-OU5B5YK6-B9QDQoSG.js → ishikawaDiagram-OU5B5YK6-Cu0n2oqT.js} +1 -1
- package/dist/web/assets/javascript-Bu8mWwtz.js +1 -0
- package/dist/web/assets/{journeyDiagram-ZHPQQLJL-CVTMW0pg.js → journeyDiagram-ZHPQQLJL-Bz1Ot6Zg.js} +1 -1
- package/dist/web/assets/julia-CQfgDRfP.js +1 -0
- package/dist/web/assets/{kanban-definition-PNTS6WVX-TNoWYrUr.js → kanban-definition-PNTS6WVX-C42vuY7H.js} +1 -1
- package/dist/web/assets/{line-B_1CHIjZ.js → line-C5WKurbS.js} +1 -1
- package/dist/web/assets/livescript-BXxG0Yva.js +1 -0
- package/dist/web/assets/lua-DPCEkfD_.js +1 -0
- package/dist/web/assets/markdown-DBTEhbhQ.js +15 -0
- package/dist/web/assets/mathematica-u8YMmbU0.js +1 -0
- package/dist/web/assets/mbox-BEMVcqjs.js +1 -0
- package/dist/web/assets/{mermaid-parser.core-LAeoGxkL.js → mermaid-parser.core-B7l8qh8B.js} +3 -3
- package/dist/web/assets/{mermaid.core-DMppSzfM.js → mermaid.core-Drs2mjGg.js} +7 -7
- package/dist/web/assets/{mindmap-definition-NLK3R4M7-DJ-6nTWg.js → mindmap-definition-NLK3R4M7-BV1FNwCn.js} +1 -1
- package/dist/web/assets/mirc-C9zpzAZU.js +1 -0
- package/dist/web/assets/mllike-B45xgp2S.js +1 -0
- package/dist/web/assets/modelica-CqHI_q-D.js +1 -0
- package/dist/web/assets/mscgen-qgSLujhx.js +1 -0
- package/dist/web/assets/mumps-BSzj6Xyz.js +1 -0
- package/dist/web/assets/nginx-8H7HVH4w.js +1 -0
- package/dist/web/assets/nsis-viPvXFiq.js +1 -0
- package/dist/web/assets/ntriples-B9h1VB_g.js +1 -0
- package/dist/web/assets/octave-D2Q8cUp1.js +1 -0
- package/dist/web/assets/oz-BwTct_5T.js +1 -0
- package/dist/web/assets/{packet-AYTQ26CC-B4tvN4nf.js → packet-AYTQ26CC-BPmUY1Ig.js} +1 -1
- package/dist/web/assets/pascal-Dp_KdFgX.js +1 -0
- package/dist/web/assets/{pegDiagram-GJSIUBJH-BwQ6jaVw.js → pegDiagram-GJSIUBJH-D6NPxPdr.js} +1 -1
- package/dist/web/assets/perl-BkSkiI9m.js +1 -0
- package/dist/web/assets/{pie-WAS4IAKB-ZwlyCxls.js → pie-WAS4IAKB-CTp2jNAc.js} +1 -1
- package/dist/web/assets/{pieDiagram-5QR66LMP-BDYLBnB1.js → pieDiagram-5QR66LMP-BVkABrg7.js} +1 -1
- package/dist/web/assets/pig-AF4Hwhju.js +1 -0
- package/dist/web/assets/pinia-BVWaJ8NX.js +1 -0
- package/dist/web/assets/powershell-BcIto6Sf.js +1 -0
- package/dist/web/assets/properties-C357ku5U.js +1 -0
- package/dist/web/assets/protobuf-D1ij_kNL.js +1 -0
- package/dist/web/assets/pug-3K-JMW4g.js +1 -0
- package/dist/web/assets/puppet-BZyAS7I4.js +1 -0
- package/dist/web/assets/purify.es--06brOip.js +3 -0
- package/dist/web/assets/python-B_1Jd8HM.js +1 -0
- package/dist/web/assets/q-B9NhS7L_.js +1 -0
- package/dist/web/assets/{quadrantDiagram-O4NWA36T-CkmLCxOG.js → quadrantDiagram-O4NWA36T-LZCjVd08.js} +1 -1
- package/dist/web/assets/r-CxDmU1I4.js +1 -0
- package/dist/web/assets/{radar-RG4KPBEZ-Ca7H5QOY.js → radar-RG4KPBEZ-CCiUrmsg.js} +1 -1
- package/dist/web/assets/{railroad-74A4TZTK-CX3Z2QYJ.js → railroad-74A4TZTK-CZn8VJbK.js} +1 -1
- package/dist/web/assets/railroad-abnf-HS5TGJTU-B-9tG9pW.js +1 -0
- package/dist/web/assets/railroad-ebnf-LZEXJU2U-D7lUyGOq.js +1 -0
- package/dist/web/assets/railroad-peg-WCYAUIDC-ja94t32K.js +1 -0
- package/dist/web/assets/{railroadDiagram-XR7U4H2S-D8ii6sM4.js → railroadDiagram-XR7U4H2S-nopy-mdw.js} +1 -1
- package/dist/web/assets/{requirementDiagram-PLB6GJNP-iHY-tE7c.js → requirementDiagram-PLB6GJNP-BOeMzf-x.js} +1 -1
- package/dist/web/assets/rpm-gpDK5sbt.js +1 -0
- package/dist/web/assets/ruby-ChfNwlCY.js +1 -0
- package/dist/web/assets/{safe-storage-BCP757jk.js → safe-storage-aRoyeFxv.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-IPEJSGJF-CBprSUZk.js → sankeyDiagram-IPEJSGJF-Bbk0AQUe.js} +1 -1
- package/dist/web/assets/sas-C2OyD2Y6.js +1 -0
- package/dist/web/assets/scheme-D9qYN0V5.js +1 -0
- package/dist/web/assets/{sequenceDiagram-PO4LG4MO-BkphpOQ_.js → sequenceDiagram-PO4LG4MO-ZpubjKFc.js} +1 -1
- package/dist/web/assets/shell-DwuoZtxw.js +1 -0
- package/dist/web/assets/sieve-tLWDfEkZ.js +1 -0
- package/dist/web/assets/simple-mode-DXiLM1-G.js +1 -0
- package/dist/web/assets/smalltalk-BLp5-jwC.js +1 -0
- package/dist/web/assets/solr-B78wSQJQ.js +1 -0
- package/dist/web/assets/sparql-Dz-mlCAC.js +1 -0
- package/dist/web/assets/spreadsheet-CTIncYxj.js +1 -0
- package/dist/web/assets/sql-DuQMFDvy.js +1 -0
- package/dist/web/assets/{stateDiagram-v2-GCMORJYK-HN1RwAPZ.js → stateDiagram-v2-GCMORJYK-DNjDh9yZ.js} +2 -2
- package/dist/web/assets/stex-DNzd62Q_.js +1 -0
- package/dist/web/assets/stylus-BOVgKnng.js +1 -0
- package/dist/web/assets/swift-DKB6_1j6.js +1 -0
- package/dist/web/assets/{swimlanes-2SLR337P-Cc8jyETz.js → swimlanes-2SLR337P-C-_UzZF_.js} +1 -1
- package/dist/web/assets/swimlanesDiagram-TC7HE7FX-CVSZNvS2.js +8 -0
- package/dist/web/assets/tcl-BGfruO1u.js +1 -0
- package/dist/web/assets/textile-C4jVoHG1.js +1 -0
- package/dist/web/assets/theme-CeQhGDjh.js +1 -0
- package/dist/web/assets/tiddlywiki-VlUiK86J.js +1 -0
- package/dist/web/assets/tiki-AK6FwT8q.js +1 -0
- package/dist/web/assets/{timeline-definition-EJHVYXUP-Bas0Ksw6.js → timeline-definition-EJHVYXUP-BiaJyHdJ.js} +1 -1
- package/dist/web/assets/toml-BPTmHmyx.js +1 -0
- package/dist/web/assets/{treeView-Q6P3EWNA-Ck-xt3KO.js → treeView-Q6P3EWNA-CENjlerX.js} +1 -1
- package/dist/web/assets/{treemap-WGGIJYW6-k9Ay9d-c.js → treemap-WGGIJYW6-CBpdf3L2.js} +1 -1
- package/dist/web/assets/troff-C2dAgh7P.js +1 -0
- package/dist/web/assets/ttcn-PPSTk1ow.js +1 -0
- package/dist/web/assets/ttcn-cfg-2b4Gb5sE.js +1 -0
- package/dist/web/assets/turtle-Cx3rYX2g.js +1 -0
- package/dist/web/assets/use-add-agent-context-SApxn9fW.js +1 -0
- package/dist/web/assets/{use-app-theme-BfRqhHi_.js → use-app-theme-DP8Ymwwb.js} +1 -1
- package/dist/web/assets/use-dark-BHObfh0O.js +1 -0
- package/dist/web/assets/use-panel-width-CTXD_csP.js +1 -0
- package/dist/web/assets/use-router-link-BGc0noIO.js +1 -0
- package/dist/web/assets/use-server-events-C13Sw-rU.js +1 -0
- package/dist/web/assets/use-virtual-scroll-BSXYZjZ5.js +1 -0
- package/dist/web/assets/{usecaseDiagram-POWQR4AR-BlzYgvXB.js → usecaseDiagram-POWQR4AR-CcJ83_Ve.js} +1 -1
- package/dist/web/assets/vb-BthdM_4N.js +1 -0
- package/dist/web/assets/vbscript-D9f6rSsU.js +1 -0
- package/dist/web/assets/velocity-CqftRsjg.js +1 -0
- package/dist/web/assets/{vennDiagram-UO4OBE2U-gjloIQdf.js → vennDiagram-UO4OBE2U-DBjzpbFr.js} +1 -1
- package/dist/web/assets/verilog-CjI9Y6RI.js +1 -0
- package/dist/web/assets/vhdl-kS471soi.js +1 -0
- package/dist/web/assets/{wardley-WFR3VGLG-BnsYd7B2.js → wardley-WFR3VGLG-DmlbUWUM.js} +1 -1
- package/dist/web/assets/{wardleyDiagram-VNRHLVJA-ktflnFLZ.js → wardleyDiagram-VNRHLVJA-CWSZCqf4.js} +1 -1
- package/dist/web/assets/webidl-D5jiJvOU.js +1 -0
- package/dist/web/assets/window-store-DNnKUhxs.js +1 -0
- package/dist/web/assets/xquery-BKJx4mf9.js +1 -0
- package/dist/web/assets/{xychartDiagram-PMCCYNJV-Ch5MYgz0.js → xychartDiagram-PMCCYNJV-CMnNSaZv.js} +1 -1
- package/dist/web/assets/yacas-CXXgtw0p.js +1 -0
- package/dist/web/assets/z80-Co3PbHdE.js +1 -0
- package/dist/web/index.html +6 -3
- package/package.json +10 -2
- package/dist/web/assets/AppPage-BGcEFW_o.js +0 -93
- package/dist/web/assets/CreateReviewFabButton-CdUXVriU.js +0 -1
- package/dist/web/assets/IndexPage-CYFIGWy9.css +0 -1
- package/dist/web/assets/IndexPage-OqgW0yH8.js +0 -1
- package/dist/web/assets/MainLayout-Bi0JE3ER.js +0 -11
- package/dist/web/assets/MainLayout-DPkSlqMr.css +0 -1
- package/dist/web/assets/QBtn-CzrOO1zA.js +0 -1
- package/dist/web/assets/QScrollArea-C4BKutrt.js +0 -1
- package/dist/web/assets/QSelect-BXUaq4ky.js +0 -1
- package/dist/web/assets/QSpace-BMpRu3a-.js +0 -13
- package/dist/web/assets/ReviewDetailPage-DlR7Vxkw.js +0 -1
- package/dist/web/assets/ReviewsPage-DM8XtlUK.js +0 -1
- package/dist/web/assets/StatusSwitcher-Blg5JmAa.js +0 -1
- package/dist/web/assets/agent-store-CDuiBhST.js +0 -1
- package/dist/web/assets/app-info-store-BmD_P8q2.js +0 -1
- package/dist/web/assets/architecture-7GRP2DOG-DMgDJZyQ.js +0 -1
- package/dist/web/assets/channel-BnUyOMnk.js +0 -1
- package/dist/web/assets/core-C37KF-j0.js +0 -12
- package/dist/web/assets/dagre-6A5THRUB-Bk3Po5o5.js +0 -4
- package/dist/web/assets/eventmodeling-NTZA5JFV-7YdTAmVS.js +0 -1
- package/dist/web/assets/flowDiagram-KWPJA3E3-eOIiY2oF.js +0 -1
- package/dist/web/assets/graphlib-DS17s2tU.js +0 -1
- package/dist/web/assets/i18n-C5Xnmkwe.js +0 -1
- package/dist/web/assets/index-BqedItFI.js +0 -2
- package/dist/web/assets/pinia-BKoxNmoW.js +0 -1
- package/dist/web/assets/railroad-abnf-HS5TGJTU-DZSn8MK2.js +0 -1
- package/dist/web/assets/railroad-ebnf-LZEXJU2U-BVTJtDBq.js +0 -1
- package/dist/web/assets/railroad-peg-WCYAUIDC-DMzsNGzj.js +0 -1
- package/dist/web/assets/swimlanesDiagram-TC7HE7FX-BxXXHQ70.js +0 -8
- package/dist/web/assets/theme-CPoPdFci.js +0 -1
- package/dist/web/assets/use-panel-width-C9dWftsJ.js +0 -1
- package/dist/web/assets/use-server-events-znzHKZZx.js +0 -1
- package/dist/web/assets/use-virtual-scroll-CIwaJCUj.js +0 -1
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { noteChildPath, noteParentPath } from "../../shared/notes/note-path.js";
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { lstat } from 'node:fs/promises';
|
|
4
|
+
import { noteReferences, referenceTarget, rewriteReferences, sameReference } from "../../shared/notes/references.js";
|
|
5
|
+
import { NotFoundError } from "../errors/http.js";
|
|
6
|
+
import { NoteConflictError, NoteExistsError } from "../errors/notes.js";
|
|
7
|
+
import { errorMessage } from "../../shared/utils/error-message.js";
|
|
8
|
+
export class NotesOperationsService {
|
|
9
|
+
files;
|
|
10
|
+
catalog;
|
|
11
|
+
writer;
|
|
12
|
+
record;
|
|
13
|
+
changed;
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.files = options.files;
|
|
16
|
+
this.catalog = options.catalog;
|
|
17
|
+
this.writer = options.writer;
|
|
18
|
+
this.record = options.record;
|
|
19
|
+
this.changed = options.changed;
|
|
20
|
+
}
|
|
21
|
+
issue(id, message, noteId, _candidate, _kind) {
|
|
22
|
+
this.catalog.issue({
|
|
23
|
+
id,
|
|
24
|
+
message,
|
|
25
|
+
noteId,
|
|
26
|
+
candidateId: null,
|
|
27
|
+
kind: 'rewrite'
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
async prepare(moves, source, target, folder, moved, preview, restore) {
|
|
31
|
+
const operation = {
|
|
32
|
+
id: randomUUID(),
|
|
33
|
+
moves: moves.map(move => ({
|
|
34
|
+
...move,
|
|
35
|
+
version: this.record(move.id).version
|
|
36
|
+
})),
|
|
37
|
+
source,
|
|
38
|
+
target,
|
|
39
|
+
folder,
|
|
40
|
+
moved,
|
|
41
|
+
committed: false,
|
|
42
|
+
writes: []
|
|
43
|
+
};
|
|
44
|
+
if (restore)
|
|
45
|
+
operation.restore = restore;
|
|
46
|
+
const records = (preview ?? this.catalog.all()).filter(note => note.presence === 'present' && !note.redirectId && !note.deletedAt);
|
|
47
|
+
const oldPath = (note) => moves.find(move => move.id === note.id)?.from ?? note.path;
|
|
48
|
+
const newPath = (note) => moves.find(move => move.id === note.id)?.to ?? note.path;
|
|
49
|
+
const oldPaths = records.map(oldPath);
|
|
50
|
+
for (const record of records) {
|
|
51
|
+
const disk = await this.files.read(moved ? record.path : oldPath(record));
|
|
52
|
+
if (!moved && disk.version !== record.version) {
|
|
53
|
+
throw new NoteConflictError();
|
|
54
|
+
}
|
|
55
|
+
const refs = noteReferences(disk.text);
|
|
56
|
+
const links = this.catalog.links(record.id);
|
|
57
|
+
const changes = [];
|
|
58
|
+
for (const ref of refs) {
|
|
59
|
+
const matches = links.filter(link => sameReference(link.reference, ref));
|
|
60
|
+
if (matches.some(link => link.ambiguous))
|
|
61
|
+
continue;
|
|
62
|
+
const known = new Set(matches.map(link => link.targetId)).size === 1
|
|
63
|
+
? matches[0]
|
|
64
|
+
: undefined;
|
|
65
|
+
const resolved = referenceTarget(ref, oldPath(record), oldPaths);
|
|
66
|
+
const targetRecord = known?.targetId
|
|
67
|
+
? this.catalog.get(known.targetId)
|
|
68
|
+
: records.find(note => oldPath(note) === resolved?.path);
|
|
69
|
+
let destination = targetRecord ? newPath(targetRecord) : resolved?.path;
|
|
70
|
+
if (destination &&
|
|
71
|
+
!targetRecord &&
|
|
72
|
+
source &&
|
|
73
|
+
(destination === source ||
|
|
74
|
+
(folder && destination.startsWith(source + '/')))) {
|
|
75
|
+
destination = target + destination.slice(source.length);
|
|
76
|
+
}
|
|
77
|
+
if (!destination)
|
|
78
|
+
continue;
|
|
79
|
+
if (newPath(record) !== oldPath(record) ||
|
|
80
|
+
(targetRecord && newPath(targetRecord) !== oldPath(targetRecord)) ||
|
|
81
|
+
destination !== resolved?.path) {
|
|
82
|
+
changes.push({
|
|
83
|
+
reference: ref,
|
|
84
|
+
from: newPath(record),
|
|
85
|
+
target: destination,
|
|
86
|
+
anchor: known?.anchor ?? resolved?.anchor ?? ''
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const after = rewriteReferences(disk.text, changes);
|
|
91
|
+
if (after !== disk.text) {
|
|
92
|
+
operation.writes.push({
|
|
93
|
+
id: record.id,
|
|
94
|
+
path: newPath(record),
|
|
95
|
+
before: disk.text,
|
|
96
|
+
after,
|
|
97
|
+
done: false
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (source) {
|
|
102
|
+
const stat = await lstat(await this.files.path(moved ? target : source, false));
|
|
103
|
+
operation.sourceFingerprint = stat.dev + ':' + stat.ino;
|
|
104
|
+
}
|
|
105
|
+
this.catalog.operation(operation);
|
|
106
|
+
return operation;
|
|
107
|
+
}
|
|
108
|
+
async execute(operation) {
|
|
109
|
+
await this.writer.assertOwner();
|
|
110
|
+
if (!operation.moved) {
|
|
111
|
+
const caseOnly = process.platform === 'win32' &&
|
|
112
|
+
operation.source !== operation.target &&
|
|
113
|
+
operation.source.toLowerCase() === operation.target.toLowerCase();
|
|
114
|
+
if (caseOnly && !operation.temporary) {
|
|
115
|
+
operation.temporary = noteChildPath(noteParentPath(operation.source), '__ght-case-' + operation.id + (operation.folder ? '' : '.md'));
|
|
116
|
+
this.catalog.operation(operation);
|
|
117
|
+
}
|
|
118
|
+
let fromPath = operation.source;
|
|
119
|
+
if (operation.temporary) {
|
|
120
|
+
if (await this.files.exists(operation.temporary)) {
|
|
121
|
+
fromPath = operation.temporary;
|
|
122
|
+
}
|
|
123
|
+
else if (!(await this.files.exactEntry(operation.target))) {
|
|
124
|
+
const stat = await lstat(await this.files.path(operation.source, false));
|
|
125
|
+
if (operation.sourceFingerprint &&
|
|
126
|
+
operation.sourceFingerprint !== stat.dev + ':' + stat.ino) {
|
|
127
|
+
throw new NoteConflictError();
|
|
128
|
+
}
|
|
129
|
+
for (const move of operation.moves) {
|
|
130
|
+
const disk = await this.files.read(move.from);
|
|
131
|
+
if (move.version && disk.version !== move.version) {
|
|
132
|
+
throw new NoteConflictError();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
await this.files.moveEntry(operation.source, operation.temporary, operation.folder);
|
|
136
|
+
fromPath = operation.temporary;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const from = await this.files.exists(fromPath);
|
|
140
|
+
const to = await this.files.exists(operation.target);
|
|
141
|
+
if (from && to && !caseOnly)
|
|
142
|
+
throw new NoteExistsError();
|
|
143
|
+
const physical = from && !(caseOnly && (await this.files.exactEntry(operation.target)))
|
|
144
|
+
? fromPath
|
|
145
|
+
: operation.target;
|
|
146
|
+
if (!(await this.files.exists(physical))) {
|
|
147
|
+
throw new NotFoundError('Both operation paths are missing. Recovery needs manual attention.');
|
|
148
|
+
}
|
|
149
|
+
if (operation.sourceFingerprint) {
|
|
150
|
+
const stat = await lstat(await this.files.path(physical, false));
|
|
151
|
+
if (operation.sourceFingerprint !== stat.dev + ':' + stat.ino) {
|
|
152
|
+
throw new NoteConflictError();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
for (const move of operation.moves) {
|
|
156
|
+
const actual = physical === operation.target
|
|
157
|
+
? move.to
|
|
158
|
+
: fromPath + move.from.slice(operation.source.length);
|
|
159
|
+
const disk = await this.files.read(actual);
|
|
160
|
+
if (move.version && disk.version !== move.version) {
|
|
161
|
+
throw new NoteConflictError();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (physical !== operation.target) {
|
|
165
|
+
await this.files.moveEntry(fromPath, operation.target, operation.folder);
|
|
166
|
+
}
|
|
167
|
+
operation.moved = true;
|
|
168
|
+
this.catalog.operation(operation);
|
|
169
|
+
}
|
|
170
|
+
if (!operation.committed) {
|
|
171
|
+
if (operation.restore) {
|
|
172
|
+
const candidate = this.catalog.get(operation.restore.candidateId);
|
|
173
|
+
if (!candidate ||
|
|
174
|
+
candidate.presence !== 'present' ||
|
|
175
|
+
(await this.files.read(candidate.path)).version !==
|
|
176
|
+
operation.restore.version) {
|
|
177
|
+
throw new NoteConflictError();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
this.catalog.transaction(() => {
|
|
181
|
+
if (operation.restore) {
|
|
182
|
+
const candidate = this.record(operation.restore.candidateId);
|
|
183
|
+
this.catalog.put({
|
|
184
|
+
...candidate,
|
|
185
|
+
presence: 'missing',
|
|
186
|
+
redirectId: operation.restore.previousId
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
for (const move of operation.moves) {
|
|
190
|
+
const record = this.record(move.id);
|
|
191
|
+
this.catalog.put({ ...record, presence: 'missing' });
|
|
192
|
+
}
|
|
193
|
+
for (const move of operation.moves) {
|
|
194
|
+
const record = this.record(move.id);
|
|
195
|
+
this.catalog.put({ ...record, path: move.to, presence: 'present' });
|
|
196
|
+
this.catalog.history(record.id, move.from, move.to, operation.id);
|
|
197
|
+
}
|
|
198
|
+
if (operation.restore) {
|
|
199
|
+
this.catalog.setLinks(operation.restore.previousId, operation.restore.links);
|
|
200
|
+
}
|
|
201
|
+
operation.committed = true;
|
|
202
|
+
this.catalog.operation(operation);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
let complete = true;
|
|
206
|
+
for (const write of operation.writes) {
|
|
207
|
+
if (write.done)
|
|
208
|
+
continue;
|
|
209
|
+
try {
|
|
210
|
+
await this.writer.assertOwner();
|
|
211
|
+
const disk = await this.files.read(write.path);
|
|
212
|
+
if (disk.text === write.after) {
|
|
213
|
+
write.done = true;
|
|
214
|
+
}
|
|
215
|
+
else if (disk.text === write.before) {
|
|
216
|
+
await this.files.save(write.path, write.after, disk.version);
|
|
217
|
+
write.done = true;
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
throw new NoteConflictError();
|
|
221
|
+
}
|
|
222
|
+
this.catalog.dismiss('rewrite:' + operation.id + ':' + write.id);
|
|
223
|
+
this.catalog.operation(operation);
|
|
224
|
+
const fresh = await this.files.read(write.path);
|
|
225
|
+
this.changed(write.id, fresh);
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
complete = false;
|
|
229
|
+
this.issue('rewrite:' + operation.id + ':' + write.id, 'Relative links need repair: ' + errorMessage(error), write.id, null, 'rewrite');
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (complete) {
|
|
233
|
+
this.catalog.dismiss('operation:' + operation.id);
|
|
234
|
+
this.catalog.finishOperation(operation.id);
|
|
235
|
+
}
|
|
236
|
+
return complete;
|
|
237
|
+
}
|
|
238
|
+
async recover() {
|
|
239
|
+
for (const operation of this.catalog.operations()) {
|
|
240
|
+
try {
|
|
241
|
+
await this.execute(operation);
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
this.issue('operation:' + operation.id, 'File operation needs attention: ' + errorMessage(error), operation.moves[0]?.id ?? null, null, 'rewrite');
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
async retry() {
|
|
249
|
+
await this.writer.acquire();
|
|
250
|
+
const paths = this.catalog
|
|
251
|
+
.all()
|
|
252
|
+
.filter(note => !note.redirectId)
|
|
253
|
+
.map(note => note.path);
|
|
254
|
+
for (const operation of this.catalog.operations()) {
|
|
255
|
+
if (!operation.moved || !operation.committed)
|
|
256
|
+
continue;
|
|
257
|
+
for (const write of operation.writes.filter(item => !item.done)) {
|
|
258
|
+
const disk = await this.files.read(write.path);
|
|
259
|
+
if (disk.text === write.before || disk.text === write.after)
|
|
260
|
+
continue;
|
|
261
|
+
const before = noteReferences(write.before);
|
|
262
|
+
const after = noteReferences(write.after);
|
|
263
|
+
const current = noteReferences(disk.text);
|
|
264
|
+
const changes = [];
|
|
265
|
+
let safe = before.length === after.length;
|
|
266
|
+
for (let i = 0; i < before.length && safe; i++) {
|
|
267
|
+
const old = before[i];
|
|
268
|
+
const next = after[i];
|
|
269
|
+
if (old.href === next.href)
|
|
270
|
+
continue;
|
|
271
|
+
const matches = current.filter(ref => sameReference(ref, old));
|
|
272
|
+
const corrected = current.filter(ref => sameReference(ref, next));
|
|
273
|
+
if (matches.length === 0 && corrected.length === 1)
|
|
274
|
+
continue;
|
|
275
|
+
const target = referenceTarget(next, write.path, paths);
|
|
276
|
+
if (matches.length !== 1 || !target) {
|
|
277
|
+
safe = false;
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
changes.push({
|
|
281
|
+
reference: matches[0],
|
|
282
|
+
from: write.path,
|
|
283
|
+
target: target.path,
|
|
284
|
+
anchor: target.anchor
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
if (!safe)
|
|
288
|
+
continue;
|
|
289
|
+
write.before = disk.text;
|
|
290
|
+
write.after = rewriteReferences(disk.text, changes);
|
|
291
|
+
this.catalog.operation(operation);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
await this.recover();
|
|
295
|
+
}
|
|
296
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { setInterval, clearInterval } from 'node:timers';
|
|
2
|
+
import { errorMessage } from "../../shared/utils/error-message.js";
|
|
3
|
+
// File metadata detects changes without reading every note body on each poll.
|
|
4
|
+
const POLL_INTERVAL_MS = 2000;
|
|
5
|
+
export function watchNotes(service, onChange) {
|
|
6
|
+
let previous = '';
|
|
7
|
+
// Last snapshot error. Publish an event only when this state changes.
|
|
8
|
+
let previousError = '';
|
|
9
|
+
let pending = false;
|
|
10
|
+
let closed = false;
|
|
11
|
+
async function check() {
|
|
12
|
+
if (pending || closed)
|
|
13
|
+
return;
|
|
14
|
+
pending = true;
|
|
15
|
+
try {
|
|
16
|
+
const next = await service.snapshot();
|
|
17
|
+
if (!closed && (previousError || (previous && previous !== next))) {
|
|
18
|
+
onChange();
|
|
19
|
+
}
|
|
20
|
+
previous = next;
|
|
21
|
+
previousError = '';
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
const message = errorMessage(error);
|
|
25
|
+
if (!closed && message !== previousError)
|
|
26
|
+
onChange();
|
|
27
|
+
previousError = message;
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
pending = false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
void check();
|
|
34
|
+
const timer = setInterval(() => {
|
|
35
|
+
void check();
|
|
36
|
+
}, POLL_INTERVAL_MS);
|
|
37
|
+
timer.unref();
|
|
38
|
+
return {
|
|
39
|
+
close() {
|
|
40
|
+
closed = true;
|
|
41
|
+
clearInterval(timer);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { open, readFile, unlink, mkdir, realpath, lstat } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { hostname } from 'node:os';
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
import { isRecord } from "../../shared/utils/guards.js";
|
|
6
|
+
import { BadRequestError } from "../errors/http.js";
|
|
7
|
+
import { isSystemError } from "../utils/system-error.js";
|
|
8
|
+
// Same-process instances must also honor the filesystem writer lock.
|
|
9
|
+
const OWNERS = new Set();
|
|
10
|
+
async function readOwner(path) {
|
|
11
|
+
if ((await lstat(path)).isSymbolicLink()) {
|
|
12
|
+
throw new BadRequestError('Notes writer lock cannot be a symbolic link.');
|
|
13
|
+
}
|
|
14
|
+
const text = await readFile(path, 'utf8');
|
|
15
|
+
const value = JSON.parse(text);
|
|
16
|
+
if (!isRecord(value) ||
|
|
17
|
+
typeof value.pid !== 'number' ||
|
|
18
|
+
typeof value.host !== 'string' ||
|
|
19
|
+
typeof value.token !== 'string') {
|
|
20
|
+
throw new BadRequestError('Invalid notes writer lock. Verify its owner before removing it.');
|
|
21
|
+
}
|
|
22
|
+
return { pid: value.pid, host: value.host, token: value.token, text };
|
|
23
|
+
}
|
|
24
|
+
export class NotesWriter {
|
|
25
|
+
root = null;
|
|
26
|
+
token = randomUUID();
|
|
27
|
+
configuredRoot;
|
|
28
|
+
constructor(configuredRoot) {
|
|
29
|
+
this.configuredRoot = configuredRoot;
|
|
30
|
+
}
|
|
31
|
+
async acquire(create = false) {
|
|
32
|
+
if (this.root)
|
|
33
|
+
return this.assertOwner();
|
|
34
|
+
if (create)
|
|
35
|
+
await mkdir(this.configuredRoot, { recursive: true });
|
|
36
|
+
let root;
|
|
37
|
+
try {
|
|
38
|
+
root = await realpath(this.configuredRoot);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
if (isSystemError(error, 'ENOENT'))
|
|
42
|
+
return;
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
if (OWNERS.has(root)) {
|
|
46
|
+
throw new BadRequestError('This notes storage already has an active writer.');
|
|
47
|
+
}
|
|
48
|
+
const path = join(root, '.githuman-writer.lock');
|
|
49
|
+
const data = JSON.stringify({
|
|
50
|
+
pid: process.pid,
|
|
51
|
+
host: hostname(),
|
|
52
|
+
token: this.token
|
|
53
|
+
});
|
|
54
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
55
|
+
try {
|
|
56
|
+
const file = await open(path, 'wx', 0o600);
|
|
57
|
+
try {
|
|
58
|
+
await file.writeFile(data);
|
|
59
|
+
await file.sync();
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
await file.close();
|
|
63
|
+
}
|
|
64
|
+
this.root = root;
|
|
65
|
+
OWNERS.add(root);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
if (!isSystemError(error, 'EEXIST'))
|
|
70
|
+
throw error;
|
|
71
|
+
const owner = await readOwner(path);
|
|
72
|
+
const old = owner.text;
|
|
73
|
+
if (owner.host !== hostname() ||
|
|
74
|
+
!Number.isSafeInteger(owner.pid) ||
|
|
75
|
+
owner.pid <= 0) {
|
|
76
|
+
throw new BadRequestError('Notes writer lock belongs to another host or has invalid ownership.');
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
process.kill(owner.pid, 0);
|
|
80
|
+
}
|
|
81
|
+
catch (killError) {
|
|
82
|
+
if (isSystemError(killError, 'ESRCH')) {
|
|
83
|
+
if ((await readFile(path, 'utf8')) === old)
|
|
84
|
+
await unlink(path);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
throw killError;
|
|
88
|
+
}
|
|
89
|
+
throw new BadRequestError('This notes storage already has an active writer.');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
throw new BadRequestError('Could not acquire the notes writer lock.');
|
|
93
|
+
}
|
|
94
|
+
async assertOwner() {
|
|
95
|
+
if (!this.root)
|
|
96
|
+
throw new BadRequestError('Notes writer is not active.');
|
|
97
|
+
const data = await readOwner(join(this.root, '.githuman-writer.lock'));
|
|
98
|
+
if (data.token !== this.token) {
|
|
99
|
+
throw new BadRequestError('Notes writer ownership changed.');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async close() {
|
|
103
|
+
if (!this.root)
|
|
104
|
+
return;
|
|
105
|
+
try {
|
|
106
|
+
await this.assertOwner();
|
|
107
|
+
await unlink(join(this.root, '.githuman-writer.lock'));
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
if (!isSystemError(error, 'ENOENT'))
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
OWNERS.delete(this.root);
|
|
114
|
+
this.root = null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import { appendFile, readFile } from 'node:fs/promises';
|
|
4
|
+
import { relative, resolve } from 'node:path';
|
|
5
|
+
import { createTestDatabase } from "../db/index.js";
|
|
6
|
+
import { NotesIdentityService } from "./notes-identity.service.js";
|
|
7
|
+
import { NotesRepository } from "../repositories/notes.repo.js";
|
|
8
|
+
import { noteMetadata } from "../../shared/notes/document.js";
|
|
9
|
+
import { BadRequestError } from "../errors/http.js";
|
|
10
|
+
import { errorMessage } from "../../shared/utils/error-message.js";
|
|
11
|
+
import { MAX_NOTE_ASSET_BYTES } from "../../shared/notes/constants.js";
|
|
12
|
+
import { detectNoteAssetFormat } from "../utils/note-asset-format.js";
|
|
13
|
+
// Git error text depends on the locale. The "not a git repository" check below needs English.
|
|
14
|
+
const GIT_ENV = { ...process.env, LC_ALL: 'C' };
|
|
15
|
+
const execFileAsync = promisify(execFile);
|
|
16
|
+
export class NotesService {
|
|
17
|
+
repository;
|
|
18
|
+
inspected;
|
|
19
|
+
exclusion;
|
|
20
|
+
identity;
|
|
21
|
+
snapshotValue = '';
|
|
22
|
+
warning = null;
|
|
23
|
+
projectPath;
|
|
24
|
+
root;
|
|
25
|
+
local;
|
|
26
|
+
bus;
|
|
27
|
+
constructor(projectPath, root, local, bus, db = createTestDatabase()) {
|
|
28
|
+
this.projectPath = projectPath;
|
|
29
|
+
this.root = root;
|
|
30
|
+
this.local = local;
|
|
31
|
+
this.bus = bus;
|
|
32
|
+
this.repository = new NotesRepository(root);
|
|
33
|
+
this.identity = new NotesIdentityService(this.repository, db);
|
|
34
|
+
}
|
|
35
|
+
async visibility(ensureIgnored = false) {
|
|
36
|
+
try {
|
|
37
|
+
const { stdout: gitRoot } = await execFileAsync('git', ['rev-parse', '--show-toplevel'], { cwd: this.projectPath, env: GIT_ENV });
|
|
38
|
+
const path = relative(gitRoot.trim(), this.root).replaceAll('\\', '/');
|
|
39
|
+
if (path.startsWith('../'))
|
|
40
|
+
return;
|
|
41
|
+
const { stdout: tracked } = await execFileAsync('git', ['ls-files', '--', path + '/'], { cwd: gitRoot.trim(), env: GIT_ENV });
|
|
42
|
+
if (tracked && this.local) {
|
|
43
|
+
this.warning =
|
|
44
|
+
'Some notes are already tracked by Git. Ignoring them does not remove them from Git.';
|
|
45
|
+
}
|
|
46
|
+
if (!this.local || !ensureIgnored)
|
|
47
|
+
return;
|
|
48
|
+
const { stdout: exclude } = await execFileAsync('git', ['rev-parse', '--git-path', 'info/exclude'], { cwd: this.projectPath, env: GIT_ENV });
|
|
49
|
+
const target = resolve(this.projectPath, exclude.trim());
|
|
50
|
+
const rule = '/' + path.replace(/[*?[\]#! ]/g, char => '\\' + char) + '/';
|
|
51
|
+
const content = await readFile(target, 'utf8').catch(() => '');
|
|
52
|
+
if (!content.split(/\r?\n/).includes(rule)) {
|
|
53
|
+
await appendFile(target, '\n' + rule + '\n');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
if (!(error instanceof Error &&
|
|
58
|
+
'stderr' in error &&
|
|
59
|
+
String(error.stderr).includes('not a git repository'))) {
|
|
60
|
+
this.warning = 'Could not verify Git exclusion: ' + errorMessage(error);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async index() {
|
|
65
|
+
this.inspected ??= this.visibility();
|
|
66
|
+
await this.inspected;
|
|
67
|
+
const index = await this.identity.index();
|
|
68
|
+
this.snapshotValue = JSON.stringify([
|
|
69
|
+
index,
|
|
70
|
+
this.identity.catalog
|
|
71
|
+
.all()
|
|
72
|
+
.map(note => [note.id, note.version, note.presence, note.deletedAt]),
|
|
73
|
+
this.warning
|
|
74
|
+
]);
|
|
75
|
+
return { ...index, warning: this.warning };
|
|
76
|
+
}
|
|
77
|
+
async snapshot() {
|
|
78
|
+
await this.index();
|
|
79
|
+
return this.snapshotValue;
|
|
80
|
+
}
|
|
81
|
+
read(path) {
|
|
82
|
+
return this.identity.readPath(path);
|
|
83
|
+
}
|
|
84
|
+
async save(path, text, version, id, storageId) {
|
|
85
|
+
await this.repository.path(path);
|
|
86
|
+
const metadata = noteMetadata(text);
|
|
87
|
+
if (metadata.error)
|
|
88
|
+
throw new BadRequestError(metadata.error);
|
|
89
|
+
this.exclusion ??= this.visibility(true);
|
|
90
|
+
await this.exclusion;
|
|
91
|
+
const note = id && storageId && version !== undefined
|
|
92
|
+
? await this.identity.saveId(id, storageId, text, version)
|
|
93
|
+
: await this.identity.save(path, text, version);
|
|
94
|
+
this.bus.publish({ type: 'notes:changed' });
|
|
95
|
+
return note;
|
|
96
|
+
}
|
|
97
|
+
async delete(id, storageId) {
|
|
98
|
+
await this.identity.deleteId(id, storageId);
|
|
99
|
+
this.bus.publish({ type: 'notes:changed' });
|
|
100
|
+
}
|
|
101
|
+
async uploadAsset(bytes) {
|
|
102
|
+
if (!bytes.length || bytes.length > MAX_NOTE_ASSET_BYTES) {
|
|
103
|
+
throw new BadRequestError('Image must be under 10 MB');
|
|
104
|
+
}
|
|
105
|
+
const format = await detectNoteAssetFormat(bytes);
|
|
106
|
+
if (format.display !== 'image') {
|
|
107
|
+
throw new BadRequestError('Only image uploads are supported');
|
|
108
|
+
}
|
|
109
|
+
this.exclusion ??= this.visibility(true);
|
|
110
|
+
await this.exclusion;
|
|
111
|
+
const id = await this.identity.run(async () => {
|
|
112
|
+
await this.repository.path('assets/probe.png', false);
|
|
113
|
+
await this.identity.writer.acquire(true);
|
|
114
|
+
await this.identity.writer.assertOwner();
|
|
115
|
+
return this.repository.saveAsset(bytes, format.extension);
|
|
116
|
+
});
|
|
117
|
+
return { url: '/api/notes/assets/' + id };
|
|
118
|
+
}
|
|
119
|
+
async importAsset(chunks, extension, signal) {
|
|
120
|
+
this.exclusion ??= this.visibility(true);
|
|
121
|
+
await this.exclusion;
|
|
122
|
+
const staged = await this.repository.stageAsset(chunks, extension, signal);
|
|
123
|
+
try {
|
|
124
|
+
const id = await this.identity.run(async () => {
|
|
125
|
+
await this.identity.writer.acquire(true);
|
|
126
|
+
await this.identity.writer.assertOwner();
|
|
127
|
+
return staged.commit();
|
|
128
|
+
});
|
|
129
|
+
return '/api/notes/assets/' + id;
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
await staged.dispose();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
readAsset(id) {
|
|
136
|
+
return this.repository.readAsset(id);
|
|
137
|
+
}
|
|
138
|
+
async rename(path, name, folder, target, id, revision) {
|
|
139
|
+
const result = await this.identity.rename(path, name, folder, target, id, revision);
|
|
140
|
+
this.bus.publish({ type: 'notes:changed' });
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
async folder(path) {
|
|
144
|
+
await this.repository.path(path, false);
|
|
145
|
+
this.exclusion ??= this.visibility(true);
|
|
146
|
+
await this.exclusion;
|
|
147
|
+
await this.identity.folder(path);
|
|
148
|
+
this.bus.publish({ type: 'notes:changed' });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -24,8 +24,9 @@ function promptPrefix(shellName) {
|
|
|
24
24
|
function promptSetup(shellName) {
|
|
25
25
|
const assign = `PS1=${promptPrefix(shellName)}"$PS1"`;
|
|
26
26
|
const marker = "printf '\\033]133;A\\007'";
|
|
27
|
-
if (shellName === 'bash')
|
|
27
|
+
if (shellName === 'bash') {
|
|
28
28
|
return `${assign}; history -d $HISTCMD || true; ${marker}`;
|
|
29
|
+
}
|
|
29
30
|
return `${assign}; ${marker}`;
|
|
30
31
|
}
|
|
31
32
|
async function shellActivity(pid, prompt, rows) {
|
|
@@ -150,8 +151,9 @@ export class PipeBackend {
|
|
|
150
151
|
this.child.stdout.setEncoding('utf8');
|
|
151
152
|
this.child.stdout.on('data', (data) => {
|
|
152
153
|
this.prompt.output(data);
|
|
153
|
-
for (const listener of this.dataListeners)
|
|
154
|
+
for (const listener of this.dataListeners) {
|
|
154
155
|
listener(data.replace(/\n/g, '\r\n'));
|
|
156
|
+
}
|
|
155
157
|
});
|
|
156
158
|
this.child.on('error', () => {
|
|
157
159
|
for (const listener of this.exitListeners)
|
|
@@ -198,8 +200,9 @@ export class PipeBackend {
|
|
|
198
200
|
}
|
|
199
201
|
}
|
|
200
202
|
export async function createBackend(cwd, cols, rows) {
|
|
201
|
-
if (!terminalSupported())
|
|
203
|
+
if (!terminalSupported()) {
|
|
202
204
|
throw new TerminalError('The terminal is not available on Windows.');
|
|
205
|
+
}
|
|
203
206
|
const module = await loadPty();
|
|
204
207
|
if (!module)
|
|
205
208
|
return new PipeBackend(cwd);
|
|
@@ -45,12 +45,13 @@ export class TerminalChannel {
|
|
|
45
45
|
switch (value.type) {
|
|
46
46
|
case 'create': {
|
|
47
47
|
const session = await this.registry.create(value.cols, value.rows);
|
|
48
|
-
if (!this.stopped)
|
|
48
|
+
if (!this.stopped) {
|
|
49
49
|
this.send({
|
|
50
50
|
type: 'created',
|
|
51
51
|
requestId: value.requestId,
|
|
52
52
|
terminalId: session.id
|
|
53
53
|
});
|
|
54
|
+
}
|
|
54
55
|
break;
|
|
55
56
|
}
|
|
56
57
|
case 'subscribe':
|
|
@@ -83,13 +84,14 @@ export class TerminalChannel {
|
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
catch (error) {
|
|
86
|
-
if (!this.stopped)
|
|
87
|
+
if (!this.stopped) {
|
|
87
88
|
this.send({
|
|
88
89
|
type: 'error',
|
|
89
90
|
...('requestId' in value ? { requestId: value.requestId } : {}),
|
|
90
91
|
code: 'GHT_TERMINAL',
|
|
91
92
|
message: errorMessage(error)
|
|
92
93
|
});
|
|
94
|
+
}
|
|
93
95
|
}
|
|
94
96
|
return undefined;
|
|
95
97
|
})
|
|
@@ -134,11 +136,12 @@ export class TerminalChannel {
|
|
|
134
136
|
this.disconnect();
|
|
135
137
|
return;
|
|
136
138
|
}
|
|
137
|
-
for (const [key, bytes] of credit.frames)
|
|
139
|
+
for (const [key, bytes] of credit.frames) {
|
|
138
140
|
if (key <= sequence) {
|
|
139
141
|
credit.bytes -= bytes;
|
|
140
142
|
credit.frames.delete(key);
|
|
141
143
|
}
|
|
144
|
+
}
|
|
142
145
|
this.flow(id, credit);
|
|
143
146
|
}
|
|
144
147
|
flow(id, credit) {
|