@tequila99/githuman 0.7.0 → 0.8.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 +39 -0
- package/dist/cli/config.js +13 -1
- package/dist/cli/server-runtime.js +3 -0
- package/dist/server/app.js +36 -4
- package/dist/server/config/agents.js +2 -1
- package/dist/server/config/openapi.js +4 -0
- package/dist/server/db/migrations.js +55 -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 +128 -0
- package/dist/server/repositories/notes.repo.js +402 -0
- package/dist/server/routes/notes.js +167 -0
- package/dist/server/routes/terminal.js +2 -1
- package/dist/server/services/git.service.js +15 -11
- package/dist/server/services/notes-identity.service.js +726 -0
- package/dist/server/services/notes-operations.service.js +298 -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 +125 -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/system-error.js +14 -0
- package/dist/shared/events/types.js +1 -0
- package/dist/shared/notes/asset-formats.js +39 -0
- package/dist/shared/notes/constants.js +2 -0
- package/dist/shared/notes/document.js +118 -0
- package/dist/shared/notes/identity.js +24 -0
- package/dist/shared/notes/links.js +44 -0
- package/dist/shared/notes/reference-path.js +54 -0
- package/dist/shared/notes/references.js +190 -0
- package/dist/shared/notes/schemas.js +164 -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/web/assets/AppPage-cl-3VfAl.js +1 -0
- package/dist/web/assets/ClosePopup-D2_HzWXW.js +1 -0
- package/dist/web/assets/CreateButton-pmDRGwna.js +93 -0
- package/dist/web/assets/CreateReviewFabButton-CcXWnmSN.js +1 -0
- package/dist/web/assets/{ErrorNotFound-CYOZg8yN.js → ErrorNotFound-Cq0wNQMu.js} +1 -1
- package/dist/web/assets/IndexPage-CmBmgu4A.js +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-DOGoQ-1m.js +6 -0
- package/dist/web/assets/NoteVisualEditor-CClIKW0X.css +1 -0
- package/dist/web/assets/NoteVisualEditor-zjZr9aod.js +1056 -0
- package/dist/web/assets/NotesPage-C5VXA9Bb.css +1 -0
- package/dist/web/assets/NotesPage-Jeva3Jta.js +114 -0
- package/dist/web/assets/{PdfPreview-CIiKnzQN.js → PdfPreview-CdgeqqEv.js} +1 -1
- package/dist/web/assets/QBtn-DoggJe1Q.js +1 -0
- package/dist/web/assets/QFab-DyUFgj0W.js +1 -0
- package/dist/web/assets/QForm-BiRkV9BY.js +4 -0
- package/dist/web/assets/QInput-DJgTh2Qy.js +1 -0
- package/dist/web/assets/QScrollArea-DfPW35JQ.js +1 -0
- package/dist/web/assets/QSelect-BpZ5KfWP.js +1 -0
- package/dist/web/assets/QSpace-BRLIXkHB.js +1 -0
- package/dist/web/assets/ReviewDetailPage-CpJPrSKO.js +1 -0
- package/dist/web/assets/ReviewsPage-BaxUZzUZ.js +1 -0
- package/dist/web/assets/{StatusSwitcher-DTD5inU1.css → StatusSwitcher-BCv_9MtI.css} +1 -1
- package/dist/web/assets/StatusSwitcher-D5UG_YWP.js +1 -0
- package/dist/web/assets/{TerminalView-BurzSD2L.css → TerminalView-C1jFlfLH.css} +1 -1
- package/dist/web/assets/{TerminalView-Ch-ndYr6.js → TerminalView-DMJTXE-G.js} +2 -2
- package/dist/web/assets/_baseMerge-BzZFQ6mI.js +1 -0
- package/dist/web/assets/{abnfDiagram-O67JEVCF-Cab71FlB.js → abnfDiagram-O67JEVCF-CiOkMSRz.js} +1 -1
- package/dist/web/assets/agent-icon-Dx8WBYX_.js +1 -0
- package/dist/web/assets/agent-store-B1VK6S0D.js +1 -0
- package/dist/web/assets/apl-gfMMiTNf.js +1 -0
- package/dist/web/assets/{app-info-store-BmD_P8q2.js → app-info-store-D76h_OdJ.js} +1 -1
- package/dist/web/assets/architecture-7GRP2DOG-Byc4Oz95.js +1 -0
- package/dist/web/assets/{architectureDiagram-NJMV4G6O-Dl1WKOEi.js → architectureDiagram-NJMV4G6O-BexHJffj.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-CEyB_5a1.js} +1 -1
- package/dist/web/assets/brainfuck-D5EjA2JK.js +1 -0
- package/dist/web/assets/{c4Diagram-YGBWAQC7-ULi06PEd.js → c4Diagram-YGBWAQC7-BRNWA7xI.js} +1 -1
- package/dist/web/assets/ccount-CcG1m-Yh.js +1 -0
- package/dist/web/assets/channel-DbRAFIx1.js +1 -0
- package/dist/web/assets/{chunk-3FUC2YCW-B1f60wx3.js → chunk-3FUC2YCW-BcQR1JDF.js} +1 -1
- package/dist/web/assets/{chunk-5DYCD2WN-f6Pcl3dF.js → chunk-5DYCD2WN-D6aQ1hFt.js} +1 -1
- package/dist/web/assets/{chunk-742MDFTN-szX1QirW.js → chunk-742MDFTN-BRgrFtkf.js} +1 -1
- package/dist/web/assets/{chunk-7INBJB4K-CP_ynmAQ.js → chunk-7INBJB4K-3RBjXeq6.js} +1 -1
- package/dist/web/assets/{chunk-7M6MHVWA-DPL74uZE.js → chunk-7M6MHVWA-Malugc_N.js} +5 -5
- package/dist/web/assets/{chunk-7PRAP22T-DH9TawZY.js → chunk-7PRAP22T-Bb3gaISA.js} +1 -1
- package/dist/web/assets/{chunk-GTNCS2PH-CKAuGvnf.js → chunk-GTNCS2PH-Cps7XhUC.js} +1 -1
- package/dist/web/assets/{chunk-GWA4HPMP-DTbHCHSN.js → chunk-GWA4HPMP-Bfp2Tg3_.js} +1 -1
- package/dist/web/assets/{chunk-MBY4JIJT-ClgvJMtC.js → chunk-MBY4JIJT-IfXbX1Ur.js} +1 -1
- package/dist/web/assets/{chunk-NETBCI7D-Dq5Z7z8S.js → chunk-NETBCI7D-r2fmahwk.js} +1 -1
- package/dist/web/assets/{chunk-O7XYJQB3-C0By5mcm.js → chunk-O7XYJQB3-sadsiWmA.js} +1 -1
- package/dist/web/assets/{chunk-UA2S7LBM-BtquYpxe.js → chunk-UA2S7LBM-DibfwtDn.js} +1 -1
- package/dist/web/assets/{chunk-WEXAMYUT-BlZL42XJ.js → chunk-WEXAMYUT-DBouMgEO.js} +1 -1
- package/dist/web/assets/{chunk-Z7XXMR3K-BbtQbAvv.js → chunk-Z7XXMR3K-fAHToON5.js} +1 -1
- package/dist/web/assets/{chunk-ZIGJFQKS-tC_YdRrm.js → chunk-ZIGJFQKS-CJG1UIHO.js} +1 -1
- package/dist/web/assets/{classDiagram-v2-NBCMYWYE-CvlA_A4N.js → classDiagram-v2-NBCMYWYE-CPHP5ynj.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-BU7_LYGR.js} +1 -1
- package/dist/web/assets/{cynefinDiagram-VND7K2PF-DHGaLNQN.js → cynefinDiagram-VND7K2PF-BXFEy9Io.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-aKpdpQ18.js +4 -0
- package/dist/web/assets/{diagram-22UHCM2B-Djjo5ftL.js → diagram-22UHCM2B-ddbRgIQ9.js} +8 -8
- package/dist/web/assets/{diagram-3UASUU5V-BcP7pJru.js → diagram-3UASUU5V-vNYy6WP_.js} +1 -1
- package/dist/web/assets/{diagram-ATOU4E4O-o_vsc8em.js → diagram-ATOU4E4O-B9Z3zFfp.js} +1 -1
- package/dist/web/assets/{diagram-CDSNMT55-eyN9Aru8.js → diagram-CDSNMT55-D3TaDPXU.js} +1 -1
- package/dist/web/assets/{diagram-MLGK6HIB-Dfjipnvb.js → diagram-MLGK6HIB-DOXcXm2Y.js} +1 -1
- package/dist/web/assets/{diagram-MPIPVDR6-C2chocRe.js → diagram-MPIPVDR6-B5E2J8pO.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-BFbA030x.js +1 -0
- package/dist/web/assets/dist-BGzvKGEu.js +1 -0
- package/dist/web/assets/dist-BX8UA0fl.js +11 -0
- package/dist/web/assets/dist-BXUopzfH.js +2 -0
- package/dist/web/assets/dist-BY24_62d.js +1 -0
- package/dist/web/assets/dist-C3sBgvRy.js +1 -0
- package/dist/web/assets/dist-C9STut2J.js +1 -0
- package/dist/web/assets/dist-C9cJQvJH.js +23 -0
- package/dist/web/assets/dist-CRoJr3t6.js +6 -0
- package/dist/web/assets/dist-CqGqStPp.js +1 -0
- package/dist/web/assets/dist-Cxd9qKh7.js +1 -0
- package/dist/web/assets/dist-D2Ce45aH.js +13 -0
- package/dist/web/assets/dist-D7Llw4iH.js +1 -0
- package/dist/web/assets/dist-DSPzgfNo.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-Kd0W7Y_b.js +9 -0
- package/dist/web/assets/dist-LFPPEX39.js +1 -0
- package/dist/web/assets/dist-PDsGXJi3.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-D-OCB_ud.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-CzjP8RHy.js} +1 -1
- package/dist/web/assets/elm-Cshzl8qu.js +1 -0
- package/dist/web/assets/equal-arrays-GA_7ZCAb.js +1 -0
- package/dist/web/assets/{erDiagram-OPXOYQCR-D9aOi7ZD.js → erDiagram-OPXOYQCR-Clohps7p.js} +1 -1
- package/dist/web/assets/erlang-BV0c4Hdn.js +1 -0
- package/dist/web/assets/eventmodeling-NTZA5JFV-FWK4tnN_.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-C10B5_6J.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-ZWH4HYq5.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-C3AfRnC8.js} +1 -1
- package/dist/web/assets/{gitGraphDiagram-X574FWY7-77GVnwya.js → gitGraphDiagram-X574FWY7-BobkQARX.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-CC_rvfan.js +1 -0
- package/dist/web/assets/idl-BUZw3wgd.js +1 -0
- package/dist/web/assets/index-Ic8yFbJg.js +2 -0
- package/dist/web/assets/{info-A6RAGUB7-Di1qRpwO.js → info-A6RAGUB7-Dh_SAe09.js} +1 -1
- package/dist/web/assets/{infoDiagram-VRGFBTTK-gceHneJ0.js → infoDiagram-VRGFBTTK-izDlhNTw.js} +1 -1
- package/dist/web/assets/{init-app-IHgDyoRH.js → init-app-C1a6BWWN.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-BJ89XzVJ.js} +1 -1
- package/dist/web/assets/javascript-Bu8mWwtz.js +1 -0
- package/dist/web/assets/{journeyDiagram-ZHPQQLJL-CVTMW0pg.js → journeyDiagram-ZHPQQLJL-poV-f08K.js} +1 -1
- package/dist/web/assets/julia-CQfgDRfP.js +1 -0
- package/dist/web/assets/{kanban-definition-PNTS6WVX-TNoWYrUr.js → kanban-definition-PNTS6WVX-Cn3FEnH7.js} +1 -1
- package/dist/web/assets/{line-B_1CHIjZ.js → line-bE9Mqcqy.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-D85iiL_9.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-Cua7H5pF.js} +3 -3
- package/dist/web/assets/{mermaid.core-DMppSzfM.js → mermaid.core-BMPOA8T-.js} +7 -7
- package/dist/web/assets/{mindmap-definition-NLK3R4M7-DJ-6nTWg.js → mindmap-definition-NLK3R4M7-DaTGr8Fs.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-DmLfghd9.js} +1 -1
- package/dist/web/assets/pascal-Dp_KdFgX.js +1 -0
- package/dist/web/assets/{pegDiagram-GJSIUBJH-BwQ6jaVw.js → pegDiagram-GJSIUBJH-CTlxXcbf.js} +1 -1
- package/dist/web/assets/perl-BkSkiI9m.js +1 -0
- package/dist/web/assets/{pie-WAS4IAKB-ZwlyCxls.js → pie-WAS4IAKB-52oNsEtc.js} +1 -1
- package/dist/web/assets/{pieDiagram-5QR66LMP-BDYLBnB1.js → pieDiagram-5QR66LMP-CbJtB1te.js} +1 -1
- package/dist/web/assets/pig-AF4Hwhju.js +1 -0
- package/dist/web/assets/pinia-BlCzIRX-.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-DCg-uD08.js} +1 -1
- package/dist/web/assets/r-CxDmU1I4.js +1 -0
- package/dist/web/assets/{radar-RG4KPBEZ-Ca7H5QOY.js → radar-RG4KPBEZ-9uA3AOZR.js} +1 -1
- package/dist/web/assets/{railroad-74A4TZTK-CX3Z2QYJ.js → railroad-74A4TZTK-BPxQfpxv.js} +1 -1
- package/dist/web/assets/railroad-abnf-HS5TGJTU-CPVHNg05.js +1 -0
- package/dist/web/assets/railroad-ebnf-LZEXJU2U-DAjTaOUg.js +1 -0
- package/dist/web/assets/railroad-peg-WCYAUIDC-DkxyKsj4.js +1 -0
- package/dist/web/assets/{railroadDiagram-XR7U4H2S-D8ii6sM4.js → railroadDiagram-XR7U4H2S-CwBNr6fr.js} +1 -1
- package/dist/web/assets/{requirementDiagram-PLB6GJNP-iHY-tE7c.js → requirementDiagram-PLB6GJNP-BbtZt0St.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-Bn-sHAYw.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-IPEJSGJF-CBprSUZk.js → sankeyDiagram-IPEJSGJF-DRWdMnFl.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-C4tWeWpt.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-pE0T1U5E.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-DKky6E3p.js} +1 -1
- package/dist/web/assets/swimlanesDiagram-TC7HE7FX-CPhibEqM.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-B-g7wQEs.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-CsRzcMLr.js} +1 -1
- package/dist/web/assets/toml-BPTmHmyx.js +1 -0
- package/dist/web/assets/{treeView-Q6P3EWNA-Ck-xt3KO.js → treeView-Q6P3EWNA-CQYqoPG3.js} +1 -1
- package/dist/web/assets/{treemap-WGGIJYW6-k9Ay9d-c.js → treemap-WGGIJYW6-BzFw4gFs.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-app-theme-BfRqhHi_.js → use-app-theme-9qFZUBqz.js} +1 -1
- package/dist/web/assets/use-dark-BHObfh0O.js +1 -0
- package/dist/web/assets/use-panel-width-Dyh49bod.js +1 -0
- package/dist/web/assets/use-router-link-BGc0noIO.js +1 -0
- package/dist/web/assets/use-server-events-24_soFuj.js +1 -0
- package/dist/web/assets/use-virtual-scroll-BSXYZjZ5.js +1 -0
- package/dist/web/assets/{usecaseDiagram-POWQR4AR-BlzYgvXB.js → usecaseDiagram-POWQR4AR-IMfiBw6h.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-JKdzpNy2.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-B9Yiwlp8.js} +1 -1
- package/dist/web/assets/{wardleyDiagram-VNRHLVJA-ktflnFLZ.js → wardleyDiagram-VNRHLVJA-BVoc2hGS.js} +1 -1
- package/dist/web/assets/webidl-D5jiJvOU.js +1 -0
- package/dist/web/assets/xquery-BKJx4mf9.js +1 -0
- package/dist/web/assets/{xychartDiagram-PMCCYNJV-Ch5MYgz0.js → xychartDiagram-PMCCYNJV-B_XV1aim.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 +8 -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-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/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,298 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { lstat } from 'node:fs/promises';
|
|
3
|
+
import { noteReferences, referenceTarget, rewriteReferences, sameReference } from "../../shared/notes/references.js";
|
|
4
|
+
import { NotFoundError } from "../errors/http.js";
|
|
5
|
+
import { NoteConflictError, NoteExistsError } from "../errors/notes.js";
|
|
6
|
+
import { errorMessage } from "../../shared/utils/error-message.js";
|
|
7
|
+
export class NotesOperationsService {
|
|
8
|
+
files;
|
|
9
|
+
catalog;
|
|
10
|
+
writer;
|
|
11
|
+
record;
|
|
12
|
+
changed;
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.files = options.files;
|
|
15
|
+
this.catalog = options.catalog;
|
|
16
|
+
this.writer = options.writer;
|
|
17
|
+
this.record = options.record;
|
|
18
|
+
this.changed = options.changed;
|
|
19
|
+
}
|
|
20
|
+
issue(id, message, noteId, _candidate, _kind) {
|
|
21
|
+
this.catalog.issue({
|
|
22
|
+
id,
|
|
23
|
+
message,
|
|
24
|
+
noteId,
|
|
25
|
+
candidateId: null,
|
|
26
|
+
kind: 'rewrite'
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
async prepare(moves, source, target, folder, moved, preview, restore) {
|
|
30
|
+
const operation = {
|
|
31
|
+
id: randomUUID(),
|
|
32
|
+
moves: moves.map(move => ({
|
|
33
|
+
...move,
|
|
34
|
+
version: this.record(move.id).version
|
|
35
|
+
})),
|
|
36
|
+
source,
|
|
37
|
+
target,
|
|
38
|
+
folder,
|
|
39
|
+
moved,
|
|
40
|
+
committed: false,
|
|
41
|
+
writes: []
|
|
42
|
+
};
|
|
43
|
+
if (restore)
|
|
44
|
+
operation.restore = restore;
|
|
45
|
+
const records = (preview ?? this.catalog.all()).filter(note => note.presence === 'present' && !note.redirectId);
|
|
46
|
+
const oldPath = (note) => moves.find(move => move.id === note.id)?.from ?? note.path;
|
|
47
|
+
const newPath = (note) => moves.find(move => move.id === note.id)?.to ?? note.path;
|
|
48
|
+
const oldPaths = records.map(oldPath);
|
|
49
|
+
for (const record of records) {
|
|
50
|
+
const disk = await this.files.read(moved ? record.path : oldPath(record));
|
|
51
|
+
if (!moved && disk.version !== record.version) {
|
|
52
|
+
throw new NoteConflictError();
|
|
53
|
+
}
|
|
54
|
+
const refs = noteReferences(disk.text);
|
|
55
|
+
const links = this.catalog.links(record.id);
|
|
56
|
+
const changes = [];
|
|
57
|
+
for (const ref of refs) {
|
|
58
|
+
const matches = links.filter(link => sameReference(link.reference, ref));
|
|
59
|
+
if (matches.some(link => link.ambiguous))
|
|
60
|
+
continue;
|
|
61
|
+
const known = new Set(matches.map(link => link.targetId)).size === 1
|
|
62
|
+
? matches[0]
|
|
63
|
+
: undefined;
|
|
64
|
+
const resolved = referenceTarget(ref, oldPath(record), oldPaths);
|
|
65
|
+
const targetRecord = known?.targetId
|
|
66
|
+
? this.catalog.get(known.targetId)
|
|
67
|
+
: records.find(note => oldPath(note) === resolved?.path);
|
|
68
|
+
let destination = targetRecord ? newPath(targetRecord) : resolved?.path;
|
|
69
|
+
if (destination &&
|
|
70
|
+
!targetRecord &&
|
|
71
|
+
source &&
|
|
72
|
+
(destination === source ||
|
|
73
|
+
(folder && destination.startsWith(source + '/')))) {
|
|
74
|
+
destination = target + destination.slice(source.length);
|
|
75
|
+
}
|
|
76
|
+
if (!destination)
|
|
77
|
+
continue;
|
|
78
|
+
if (newPath(record) !== oldPath(record) ||
|
|
79
|
+
(targetRecord && newPath(targetRecord) !== oldPath(targetRecord)) ||
|
|
80
|
+
destination !== resolved?.path) {
|
|
81
|
+
changes.push({
|
|
82
|
+
reference: ref,
|
|
83
|
+
from: newPath(record),
|
|
84
|
+
target: destination,
|
|
85
|
+
anchor: known?.anchor ?? resolved?.anchor ?? ''
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const after = rewriteReferences(disk.text, changes);
|
|
90
|
+
if (after !== disk.text) {
|
|
91
|
+
operation.writes.push({
|
|
92
|
+
id: record.id,
|
|
93
|
+
path: newPath(record),
|
|
94
|
+
before: disk.text,
|
|
95
|
+
after,
|
|
96
|
+
done: false
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (source) {
|
|
101
|
+
const stat = await lstat(await this.files.path(moved ? target : source, false));
|
|
102
|
+
operation.sourceFingerprint = stat.dev + ':' + stat.ino;
|
|
103
|
+
}
|
|
104
|
+
this.catalog.operation(operation);
|
|
105
|
+
return operation;
|
|
106
|
+
}
|
|
107
|
+
async execute(operation) {
|
|
108
|
+
await this.writer.assertOwner();
|
|
109
|
+
if (!operation.moved) {
|
|
110
|
+
const caseOnly = process.platform === 'win32' &&
|
|
111
|
+
operation.source !== operation.target &&
|
|
112
|
+
operation.source.toLowerCase() === operation.target.toLowerCase();
|
|
113
|
+
if (caseOnly && !operation.temporary) {
|
|
114
|
+
operation.temporary = [
|
|
115
|
+
...operation.source.split('/').slice(0, -1),
|
|
116
|
+
'__ght-case-' + operation.id + (operation.folder ? '' : '.md')
|
|
117
|
+
].join('/');
|
|
118
|
+
this.catalog.operation(operation);
|
|
119
|
+
}
|
|
120
|
+
let fromPath = operation.source;
|
|
121
|
+
if (operation.temporary) {
|
|
122
|
+
if (await this.files.exists(operation.temporary)) {
|
|
123
|
+
fromPath = operation.temporary;
|
|
124
|
+
}
|
|
125
|
+
else if (!(await this.files.exactEntry(operation.target))) {
|
|
126
|
+
const stat = await lstat(await this.files.path(operation.source, false));
|
|
127
|
+
if (operation.sourceFingerprint &&
|
|
128
|
+
operation.sourceFingerprint !== stat.dev + ':' + stat.ino) {
|
|
129
|
+
throw new NoteConflictError();
|
|
130
|
+
}
|
|
131
|
+
for (const move of operation.moves) {
|
|
132
|
+
const disk = await this.files.read(move.from);
|
|
133
|
+
if (move.version && disk.version !== move.version) {
|
|
134
|
+
throw new NoteConflictError();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
await this.files.moveEntry(operation.source, operation.temporary, operation.folder);
|
|
138
|
+
fromPath = operation.temporary;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const from = await this.files.exists(fromPath);
|
|
142
|
+
const to = await this.files.exists(operation.target);
|
|
143
|
+
if (from && to && !caseOnly)
|
|
144
|
+
throw new NoteExistsError();
|
|
145
|
+
const physical = from && !(caseOnly && (await this.files.exactEntry(operation.target)))
|
|
146
|
+
? fromPath
|
|
147
|
+
: operation.target;
|
|
148
|
+
if (!(await this.files.exists(physical))) {
|
|
149
|
+
throw new NotFoundError('Both operation paths are missing. Recovery needs manual attention.');
|
|
150
|
+
}
|
|
151
|
+
if (operation.sourceFingerprint) {
|
|
152
|
+
const stat = await lstat(await this.files.path(physical, false));
|
|
153
|
+
if (operation.sourceFingerprint !== stat.dev + ':' + stat.ino) {
|
|
154
|
+
throw new NoteConflictError();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
for (const move of operation.moves) {
|
|
158
|
+
const actual = physical === operation.target
|
|
159
|
+
? move.to
|
|
160
|
+
: fromPath + move.from.slice(operation.source.length);
|
|
161
|
+
const disk = await this.files.read(actual);
|
|
162
|
+
if (move.version && disk.version !== move.version) {
|
|
163
|
+
throw new NoteConflictError();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (physical !== operation.target) {
|
|
167
|
+
await this.files.moveEntry(fromPath, operation.target, operation.folder);
|
|
168
|
+
}
|
|
169
|
+
operation.moved = true;
|
|
170
|
+
this.catalog.operation(operation);
|
|
171
|
+
}
|
|
172
|
+
if (!operation.committed) {
|
|
173
|
+
if (operation.restore) {
|
|
174
|
+
const candidate = this.catalog.get(operation.restore.candidateId);
|
|
175
|
+
if (!candidate ||
|
|
176
|
+
candidate.presence !== 'present' ||
|
|
177
|
+
(await this.files.read(candidate.path)).version !==
|
|
178
|
+
operation.restore.version) {
|
|
179
|
+
throw new NoteConflictError();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
this.catalog.transaction(() => {
|
|
183
|
+
if (operation.restore) {
|
|
184
|
+
const candidate = this.record(operation.restore.candidateId);
|
|
185
|
+
this.catalog.put({
|
|
186
|
+
...candidate,
|
|
187
|
+
presence: 'missing',
|
|
188
|
+
redirectId: operation.restore.previousId
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
for (const move of operation.moves) {
|
|
192
|
+
const record = this.record(move.id);
|
|
193
|
+
this.catalog.put({ ...record, presence: 'missing' });
|
|
194
|
+
}
|
|
195
|
+
for (const move of operation.moves) {
|
|
196
|
+
const record = this.record(move.id);
|
|
197
|
+
this.catalog.put({ ...record, path: move.to, presence: 'present' });
|
|
198
|
+
this.catalog.history(record.id, move.from, move.to, operation.id);
|
|
199
|
+
}
|
|
200
|
+
if (operation.restore) {
|
|
201
|
+
this.catalog.setLinks(operation.restore.previousId, operation.restore.links);
|
|
202
|
+
}
|
|
203
|
+
operation.committed = true;
|
|
204
|
+
this.catalog.operation(operation);
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
let complete = true;
|
|
208
|
+
for (const write of operation.writes) {
|
|
209
|
+
if (write.done)
|
|
210
|
+
continue;
|
|
211
|
+
try {
|
|
212
|
+
await this.writer.assertOwner();
|
|
213
|
+
const disk = await this.files.read(write.path);
|
|
214
|
+
if (disk.text === write.after) {
|
|
215
|
+
write.done = true;
|
|
216
|
+
}
|
|
217
|
+
else if (disk.text === write.before) {
|
|
218
|
+
await this.files.save(write.path, write.after, disk.version);
|
|
219
|
+
write.done = true;
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
throw new NoteConflictError();
|
|
223
|
+
}
|
|
224
|
+
this.catalog.dismiss('rewrite:' + operation.id + ':' + write.id);
|
|
225
|
+
this.catalog.operation(operation);
|
|
226
|
+
const fresh = await this.files.read(write.path);
|
|
227
|
+
this.changed(write.id, fresh);
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
complete = false;
|
|
231
|
+
this.issue('rewrite:' + operation.id + ':' + write.id, 'Relative links need repair: ' + errorMessage(error), write.id, null, 'rewrite');
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (complete) {
|
|
235
|
+
this.catalog.dismiss('operation:' + operation.id);
|
|
236
|
+
this.catalog.finishOperation(operation.id);
|
|
237
|
+
}
|
|
238
|
+
return complete;
|
|
239
|
+
}
|
|
240
|
+
async recover() {
|
|
241
|
+
for (const operation of this.catalog.operations()) {
|
|
242
|
+
try {
|
|
243
|
+
await this.execute(operation);
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
this.issue('operation:' + operation.id, 'File operation needs attention: ' + errorMessage(error), operation.moves[0]?.id ?? null, null, 'rewrite');
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
async retry() {
|
|
251
|
+
await this.writer.acquire();
|
|
252
|
+
const paths = this.catalog
|
|
253
|
+
.all()
|
|
254
|
+
.filter(note => !note.redirectId)
|
|
255
|
+
.map(note => note.path);
|
|
256
|
+
for (const operation of this.catalog.operations()) {
|
|
257
|
+
if (!operation.moved || !operation.committed)
|
|
258
|
+
continue;
|
|
259
|
+
for (const write of operation.writes.filter(item => !item.done)) {
|
|
260
|
+
const disk = await this.files.read(write.path);
|
|
261
|
+
if (disk.text === write.before || disk.text === write.after)
|
|
262
|
+
continue;
|
|
263
|
+
const before = noteReferences(write.before);
|
|
264
|
+
const after = noteReferences(write.after);
|
|
265
|
+
const current = noteReferences(disk.text);
|
|
266
|
+
const changes = [];
|
|
267
|
+
let safe = before.length === after.length;
|
|
268
|
+
for (let i = 0; i < before.length && safe; i++) {
|
|
269
|
+
const old = before[i];
|
|
270
|
+
const next = after[i];
|
|
271
|
+
if (old.href === next.href)
|
|
272
|
+
continue;
|
|
273
|
+
const matches = current.filter(ref => sameReference(ref, old));
|
|
274
|
+
const corrected = current.filter(ref => sameReference(ref, next));
|
|
275
|
+
if (matches.length === 0 && corrected.length === 1)
|
|
276
|
+
continue;
|
|
277
|
+
const target = referenceTarget(next, write.path, paths);
|
|
278
|
+
if (matches.length !== 1 || !target) {
|
|
279
|
+
safe = false;
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
changes.push({
|
|
283
|
+
reference: matches[0],
|
|
284
|
+
from: write.path,
|
|
285
|
+
target: target.path,
|
|
286
|
+
anchor: target.anchor
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
if (!safe)
|
|
290
|
+
continue;
|
|
291
|
+
write.before = disk.text;
|
|
292
|
+
write.after = rewriteReferences(disk.text, changes);
|
|
293
|
+
this.catalog.operation(operation);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
await this.recover();
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -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,125 @@
|
|
|
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
|
+
const execFileAsync = promisify(execFile);
|
|
14
|
+
export class NotesService {
|
|
15
|
+
repository;
|
|
16
|
+
inspected;
|
|
17
|
+
exclusion;
|
|
18
|
+
identity;
|
|
19
|
+
snapshotValue = '';
|
|
20
|
+
warning = null;
|
|
21
|
+
projectPath;
|
|
22
|
+
root;
|
|
23
|
+
local;
|
|
24
|
+
bus;
|
|
25
|
+
constructor(projectPath, root, local, bus, db = createTestDatabase()) {
|
|
26
|
+
this.projectPath = projectPath;
|
|
27
|
+
this.root = root;
|
|
28
|
+
this.local = local;
|
|
29
|
+
this.bus = bus;
|
|
30
|
+
this.repository = new NotesRepository(root);
|
|
31
|
+
this.identity = new NotesIdentityService(this.repository, db);
|
|
32
|
+
}
|
|
33
|
+
async visibility(ensureIgnored = false) {
|
|
34
|
+
try {
|
|
35
|
+
const { stdout: gitRoot } = await execFileAsync('git', ['rev-parse', '--show-toplevel'], { cwd: this.projectPath });
|
|
36
|
+
const path = relative(gitRoot.trim(), this.root).replaceAll('\\', '/');
|
|
37
|
+
if (path.startsWith('../'))
|
|
38
|
+
return;
|
|
39
|
+
const { stdout: tracked } = await execFileAsync('git', ['ls-files', '--', path + '/'], { cwd: gitRoot.trim() });
|
|
40
|
+
if (tracked && this.local) {
|
|
41
|
+
this.warning =
|
|
42
|
+
'Some notes are already tracked by Git. Ignoring them does not remove them from Git.';
|
|
43
|
+
}
|
|
44
|
+
if (!this.local || !ensureIgnored)
|
|
45
|
+
return;
|
|
46
|
+
const { stdout: exclude } = await execFileAsync('git', ['rev-parse', '--git-path', 'info/exclude'], { cwd: this.projectPath });
|
|
47
|
+
const target = resolve(this.projectPath, exclude.trim());
|
|
48
|
+
const rule = '/' + path.replace(/[*?[\]#! ]/g, char => '\\' + char) + '/';
|
|
49
|
+
const content = await readFile(target, 'utf8').catch(() => '');
|
|
50
|
+
if (!content.split(/\r?\n/).includes(rule)) {
|
|
51
|
+
await appendFile(target, '\n' + rule + '\n');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
if (!(error instanceof Error &&
|
|
56
|
+
'stderr' in error &&
|
|
57
|
+
String(error.stderr).includes('not a git repository'))) {
|
|
58
|
+
this.warning = 'Could not verify Git exclusion: ' + errorMessage(error);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async index() {
|
|
63
|
+
this.inspected ??= this.visibility();
|
|
64
|
+
await this.inspected;
|
|
65
|
+
const index = await this.identity.index();
|
|
66
|
+
this.snapshotValue = JSON.stringify([
|
|
67
|
+
index,
|
|
68
|
+
this.identity.catalog
|
|
69
|
+
.all()
|
|
70
|
+
.map(note => [note.id, note.version, note.presence]),
|
|
71
|
+
this.warning
|
|
72
|
+
]);
|
|
73
|
+
return { ...index, warning: this.warning };
|
|
74
|
+
}
|
|
75
|
+
async snapshot() {
|
|
76
|
+
await this.index();
|
|
77
|
+
return this.snapshotValue;
|
|
78
|
+
}
|
|
79
|
+
read(path) {
|
|
80
|
+
return this.identity.readPath(path);
|
|
81
|
+
}
|
|
82
|
+
async save(path, text, version, id, storageId) {
|
|
83
|
+
await this.repository.path(path);
|
|
84
|
+
const metadata = noteMetadata(text);
|
|
85
|
+
if (metadata.error)
|
|
86
|
+
throw new BadRequestError(metadata.error);
|
|
87
|
+
this.exclusion ??= this.visibility(true);
|
|
88
|
+
await this.exclusion;
|
|
89
|
+
const note = id && storageId && version !== undefined
|
|
90
|
+
? await this.identity.saveId(id, storageId, text, version)
|
|
91
|
+
: await this.identity.save(path, text, version);
|
|
92
|
+
this.bus.publish({ type: 'notes:changed' });
|
|
93
|
+
return note;
|
|
94
|
+
}
|
|
95
|
+
async uploadAsset(bytes) {
|
|
96
|
+
if (!bytes.length || bytes.length > MAX_NOTE_ASSET_BYTES) {
|
|
97
|
+
throw new BadRequestError('Image must be under 10 MB');
|
|
98
|
+
}
|
|
99
|
+
const format = await detectNoteAssetFormat(bytes);
|
|
100
|
+
this.exclusion ??= this.visibility(true);
|
|
101
|
+
await this.exclusion;
|
|
102
|
+
const id = await this.identity.run(async () => {
|
|
103
|
+
await this.repository.path('assets/probe.png', false);
|
|
104
|
+
await this.identity.writer.acquire(true);
|
|
105
|
+
await this.identity.writer.assertOwner();
|
|
106
|
+
return this.repository.saveAsset(bytes, format.extension);
|
|
107
|
+
});
|
|
108
|
+
return { url: '/api/notes/assets/' + id };
|
|
109
|
+
}
|
|
110
|
+
readAsset(id) {
|
|
111
|
+
return this.repository.readAsset(id);
|
|
112
|
+
}
|
|
113
|
+
async rename(path, name, folder, target, id, revision) {
|
|
114
|
+
const result = await this.identity.rename(path, name, folder, target, id, revision);
|
|
115
|
+
this.bus.publish({ type: 'notes:changed' });
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
async folder(path) {
|
|
119
|
+
await this.repository.path(path, false);
|
|
120
|
+
this.exclusion ??= this.visibility(true);
|
|
121
|
+
await this.exclusion;
|
|
122
|
+
await this.identity.folder(path);
|
|
123
|
+
this.bus.publish({ type: 'notes:changed' });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -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) {
|