From f409216320fc9987940d53db0698fcd3edcf5e15 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Thu, 13 Feb 2025 14:43:46 +0100 Subject: [PATCH] First rought implementation of conflicts resolution view --- package.json | 4 + pnpm-lock.yaml | 308 +++++++++++++++++- src/main.ts | 31 +- src/views/conflicts-resolution/component.tsx | 142 ++++++++ .../diff-highlight-plugin.ts | 53 +++ src/views/conflicts-resolution/diff.ts | 69 ++++ src/views/conflicts-resolution/view.tsx | 75 +++++ 7 files changed, 670 insertions(+), 12 deletions(-) create mode 100644 src/views/conflicts-resolution/component.tsx create mode 100644 src/views/conflicts-resolution/diff-highlight-plugin.ts create mode 100644 src/views/conflicts-resolution/diff.ts create mode 100644 src/views/conflicts-resolution/view.tsx diff --git a/package.json b/package.json index 3cb8ac6..28b6e4a 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,10 @@ "typescript": "^5.7.2" }, "dependencies": { + "@codemirror/lang-markdown": "^6.3.2", + "@codemirror/state": "^6.5.2", + "@codemirror/view": "^6.36.2", + "codemirror": "^6.0.1", "react": "^19.0.0", "react-dom": "^19.0.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 24b8a56..59038fd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,18 @@ importers: .: dependencies: + '@codemirror/lang-markdown': + specifier: ^6.3.2 + version: 6.3.2 + '@codemirror/state': + specifier: ^6.5.2 + version: 6.5.2 + '@codemirror/view': + specifier: ^6.36.2 + version: 6.36.2 + codemirror: + specifier: ^6.0.1 + version: 6.0.1 react: specifier: ^19.0.0 version: 19.0.0 @@ -38,7 +50,7 @@ importers: version: 0.24.2 obsidian: specifier: ^1.7.2 - version: 1.7.2(@codemirror/state@6.5.0)(@codemirror/view@6.36.1) + version: 1.7.2(@codemirror/state@6.5.2)(@codemirror/view@6.36.2) tslib: specifier: ^2.8.1 version: 2.8.1 @@ -48,11 +60,46 @@ importers: packages: - '@codemirror/state@6.5.0': - resolution: {integrity: sha512-MwBHVK60IiIHDcoMet78lxt6iw5gJOGSbNbOIVBHWVXIH4/Nq1+GQgLLGgI1KlnN86WDXsPudVaqYHKBIx7Eyw==} + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} - '@codemirror/view@6.36.1': - resolution: {integrity: sha512-miD1nyT4m4uopZaDdO2uXU/LLHliKNYL9kB1C1wJHrunHLm/rpkb5QVSokqgw9hFqEZakrdlb/VGWX8aYZTslQ==} + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + + '@codemirror/autocomplete@6.18.6': + resolution: {integrity: sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==} + + '@codemirror/commands@6.8.0': + resolution: {integrity: sha512-q8VPEFaEP4ikSlt6ZxjB3zW72+7osfAYW9i8Zu943uqbKuz6utc1+F170hyLUCUltXORjQXRyYQNfkckzA/bPQ==} + + '@codemirror/lang-css@6.3.1': + resolution: {integrity: sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==} + + '@codemirror/lang-html@6.4.9': + resolution: {integrity: sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==} + + '@codemirror/lang-javascript@6.2.3': + resolution: {integrity: sha512-8PR3vIWg7pSu7ur8A07pGiYHgy3hHj+mRYRCSG8q+mPIrl0F02rgpGv+DsQTHRTc30rydOsf5PZ7yjKFg2Ackw==} + + '@codemirror/lang-markdown@6.3.2': + resolution: {integrity: sha512-c/5MYinGbFxYl4itE9q/rgN/sMTjOr8XL5OWnC+EaRMLfCbVUmmubTJfdgpfcSS2SCaT7b+Q+xi3l6CgoE+BsA==} + + '@codemirror/language@6.10.8': + resolution: {integrity: sha512-wcP8XPPhDH2vTqf181U8MbZnW+tDyPYy0UzVOa+oHORjyT+mhhom9vBd7dApJwoDz9Nb/a8kHjJIsuA/t8vNFw==} + + '@codemirror/lint@6.8.4': + resolution: {integrity: sha512-u4q7PnZlJUojeRe8FJa/njJcMctISGgPQ4PnWsd9268R4ZTtU+tfFYmwkBvgcrK2+QQ8tYFVALVb5fVJykKc5A==} + + '@codemirror/search@6.5.9': + resolution: {integrity: sha512-7DdQ9aaZMMxuWB1u6IIFWWuK9NocVZwvo4nG8QjJTS6oZGvteoLSiXw3EbVZVlO08Ri2ltO89JVInMpfcJxhtg==} + + '@codemirror/state@6.5.2': + resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==} + + '@codemirror/view@6.36.2': + resolution: {integrity: sha512-DZ6ONbs8qdJK0fdN7AB82CgI6tYXf4HWk1wSVa0+9bhVznCuuvhQtX8bFBoy3dv8rZSQqUd8GvhVAcielcidrA==} '@esbuild/aix-ppc64@0.24.2': resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} @@ -258,6 +305,33 @@ packages: resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==} engines: {node: '>=18.18'} + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@lezer/common@1.2.3': + resolution: {integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==} + + '@lezer/css@1.1.10': + resolution: {integrity: sha512-V5/89eDapjeAkWPBpWEfQjZ1Hag3aYUUJOL8213X0dFRuXJ4BXa5NKl9USzOnaLod4AOpmVCkduir2oKwZYZtg==} + + '@lezer/highlight@1.2.1': + resolution: {integrity: sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==} + + '@lezer/html@1.3.10': + resolution: {integrity: sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==} + + '@lezer/javascript@1.4.21': + resolution: {integrity: sha512-lL+1fcuxWYPURMM/oFZLEDm0XuLN128QPV+VuGtKpeaOGdcl9F2LYC3nh1S9LkPqx9M0mndZFdXCipNAZpzIkQ==} + + '@lezer/lr@1.4.2': + resolution: {integrity: sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==} + + '@lezer/markdown@1.4.1': + resolution: {integrity: sha512-Za5okfyWoNaX6sSZ2dm94XegaFXbkQ9UjKJ8hAoZX88XDpbu6DoR63IuSl+dqj1VkVQBQGsdr0JnTcMsogQDdw==} + + '@marijn/buildtool@0.1.6': + resolution: {integrity: sha512-rcA2wljsM24MFAwx2U5vSBrt7IdIaPh4WPRfJPS8PuCUlbuQ8Pmky4c/ec00v3YFu90rZSbkVLnPuCeb/mUEng==} + '@marijn/find-cluster-break@1.0.2': resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} @@ -282,6 +356,9 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/mocha@9.1.1': + resolution: {integrity: sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==} + '@types/node@22.10.5': resolution: {integrity: sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==} @@ -348,6 +425,10 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} + acorn@8.14.0: resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} engines: {node: '>=0.4.0'} @@ -388,6 +469,9 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + codemirror@6.0.1: + resolution: {integrity: sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -398,6 +482,9 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + crelt@1.0.6: + resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -503,6 +590,11 @@ packages: flatted@3.3.2: resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -549,6 +641,9 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true @@ -576,6 +671,9 @@ packages: lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -630,6 +728,9 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -662,6 +763,18 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rollup-plugin-dts@5.3.1: + resolution: {integrity: sha512-gusMi+Z4gY/JaEQeXnB0RUdU82h1kF0WYzCWgVmV4p3hWXqelaKuCvcJawfeg+EKn2T1Ie+YWF2OiN1/L8bTVg==} + engines: {node: '>=v14.21.3'} + peerDependencies: + rollup: ^3.0 + typescript: ^4.1 || ^5.0 + + rollup@3.29.5: + resolution: {integrity: sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==} + engines: {node: '>=14.18.0', npm: '>=8.0.0'} + hasBin: true + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -738,13 +851,98 @@ packages: snapshots: - '@codemirror/state@6.5.0': + '@babel/code-frame@7.26.2': + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 + optional: true + + '@babel/helper-validator-identifier@7.25.9': + optional: true + + '@codemirror/autocomplete@6.18.6': + dependencies: + '@codemirror/language': 6.10.8 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.36.2 + '@lezer/common': 1.2.3 + + '@codemirror/commands@6.8.0': + dependencies: + '@codemirror/language': 6.10.8 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.36.2 + '@lezer/common': 1.2.3 + + '@codemirror/lang-css@6.3.1': + dependencies: + '@codemirror/autocomplete': 6.18.6 + '@codemirror/language': 6.10.8 + '@codemirror/state': 6.5.2 + '@lezer/common': 1.2.3 + '@lezer/css': 1.1.10 + + '@codemirror/lang-html@6.4.9': + dependencies: + '@codemirror/autocomplete': 6.18.6 + '@codemirror/lang-css': 6.3.1 + '@codemirror/lang-javascript': 6.2.3 + '@codemirror/language': 6.10.8 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.36.2 + '@lezer/common': 1.2.3 + '@lezer/css': 1.1.10 + '@lezer/html': 1.3.10 + + '@codemirror/lang-javascript@6.2.3': + dependencies: + '@codemirror/autocomplete': 6.18.6 + '@codemirror/language': 6.10.8 + '@codemirror/lint': 6.8.4 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.36.2 + '@lezer/common': 1.2.3 + '@lezer/javascript': 1.4.21 + + '@codemirror/lang-markdown@6.3.2': + dependencies: + '@codemirror/autocomplete': 6.18.6 + '@codemirror/lang-html': 6.4.9 + '@codemirror/language': 6.10.8 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.36.2 + '@lezer/common': 1.2.3 + '@lezer/markdown': 1.4.1 + + '@codemirror/language@6.10.8': + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.36.2 + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + style-mod: 4.1.2 + + '@codemirror/lint@6.8.4': + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.36.2 + crelt: 1.0.6 + + '@codemirror/search@6.5.9': + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.36.2 + crelt: 1.0.6 + + '@codemirror/state@6.5.2': dependencies: '@marijn/find-cluster-break': 1.0.2 - '@codemirror/view@6.36.1': + '@codemirror/view@6.36.2': dependencies: - '@codemirror/state': 6.5.0 + '@codemirror/state': 6.5.2 style-mod: 4.1.2 w3c-keyname: 2.2.8 @@ -877,6 +1075,51 @@ snapshots: '@humanwhocodes/retry@0.4.1': {} + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@lezer/common@1.2.3': {} + + '@lezer/css@1.1.10': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/highlight@1.2.1': + dependencies: + '@lezer/common': 1.2.3 + + '@lezer/html@1.3.10': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/javascript@1.4.21': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/lr@1.4.2': + dependencies: + '@lezer/common': 1.2.3 + + '@lezer/markdown@1.4.1': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@marijn/buildtool': 0.1.6 + + '@marijn/buildtool@0.1.6': + dependencies: + '@types/mocha': 9.1.1 + acorn: 8.14.0 + acorn-walk: 8.3.4 + rollup: 3.29.5 + rollup-plugin-dts: 5.3.1(rollup@3.29.5)(typescript@5.7.2) + typescript: 5.7.2 + '@marijn/find-cluster-break@1.0.2': {} '@nodelib/fs.scandir@2.1.5': @@ -899,6 +1142,8 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/mocha@9.1.1': {} + '@types/node@22.10.5': dependencies: undici-types: 6.20.0 @@ -996,6 +1241,10 @@ snapshots: dependencies: acorn: 8.14.0 + acorn-walk@8.3.4: + dependencies: + acorn: 8.14.0 + acorn@8.14.0: {} ajv@6.12.6: @@ -1035,6 +1284,16 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + codemirror@6.0.1: + dependencies: + '@codemirror/autocomplete': 6.18.6 + '@codemirror/commands': 6.8.0 + '@codemirror/language': 6.10.8 + '@codemirror/lint': 6.8.4 + '@codemirror/search': 6.5.9 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.36.2 + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -1043,6 +1302,8 @@ snapshots: concat-map@0.0.1: {} + crelt@1.0.6: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -1191,6 +1452,9 @@ snapshots: flatted@3.3.2: {} + fsevents@2.3.3: + optional: true + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -1224,6 +1488,9 @@ snapshots: isexe@2.0.0: {} + js-tokens@4.0.0: + optional: true + js-yaml@4.1.0: dependencies: argparse: 2.0.1 @@ -1249,6 +1516,10 @@ snapshots: lodash.merge@4.6.2: {} + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + merge2@1.4.1: {} micromatch@4.0.8: @@ -1270,10 +1541,10 @@ snapshots: natural-compare@1.4.0: {} - obsidian@1.7.2(@codemirror/state@6.5.0)(@codemirror/view@6.36.1): + obsidian@1.7.2(@codemirror/state@6.5.2)(@codemirror/view@6.36.2): dependencies: - '@codemirror/state': 6.5.0 - '@codemirror/view': 6.36.1 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.36.2 '@types/codemirror': 5.60.8 moment: 2.29.4 @@ -1302,6 +1573,9 @@ snapshots: path-key@3.1.1: {} + picocolors@1.1.1: + optional: true + picomatch@2.3.1: {} prelude-ls@1.2.1: {} @@ -1321,6 +1595,18 @@ snapshots: reusify@1.0.4: {} + rollup-plugin-dts@5.3.1(rollup@3.29.5)(typescript@5.7.2): + dependencies: + magic-string: 0.30.17 + rollup: 3.29.5 + typescript: 5.7.2 + optionalDependencies: + '@babel/code-frame': 7.26.2 + + rollup@3.29.5: + optionalDependencies: + fsevents: 2.3.3 + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 diff --git a/src/main.ts b/src/main.ts index dad9d9e..a158b1e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,10 +1,14 @@ -import { EventRef, Plugin, Platform } from "obsidian"; +import { EventRef, Plugin, Platform, WorkspaceLeaf } from "obsidian"; import { GitHubSyncSettings, DEFAULT_SETTINGS } from "./settings/settings"; import GitHubSyncSettingsTab from "./settings/tab"; import SyncManager from "./sync-manager"; import { FileMetadata } from "./metadata-store"; import { OnboardingDialog } from "./views/onboarding/view"; import Logger from "./logger"; +import { + ConflictsResolutionView, + CONFLICTS_RESOLUTION_VIEW_TYPE, +} from "./views/conflicts-resolution/view"; export default class GitHubSyncPlugin extends Plugin { settings: GitHubSyncSettings; @@ -30,9 +34,34 @@ export default class GitHubSyncPlugin extends Plugin { } } + async activateView() { + const { workspace } = this.app; + let leaf: WorkspaceLeaf | null = null; + const leaves = workspace.getLeavesOfType(CONFLICTS_RESOLUTION_VIEW_TYPE); + if (leaves.length > 0) { + leaf = leaves[0]; + } else { + leaf = workspace.getRightLeaf(false); + await leaf.setViewState({ + type: CONFLICTS_RESOLUTION_VIEW_TYPE, + active: true, + }); + } + workspace.revealLeaf(leaf); + } + async onload() { await this.loadSettings(); + this.registerView( + CONFLICTS_RESOLUTION_VIEW_TYPE, + (leaf) => new ConflictsResolutionView(leaf, this), + ); + this.addRibbonIcon( + "dice", + "Activate view", + async () => await this.activateView(), + ); this.logger = new Logger(this.app.vault, this.settings.enableLogging); this.addSettingTab(new GitHubSyncSettingsTab(this.app, this)); diff --git a/src/views/conflicts-resolution/component.tsx b/src/views/conflicts-resolution/component.tsx new file mode 100644 index 0000000..1ac2dd1 --- /dev/null +++ b/src/views/conflicts-resolution/component.tsx @@ -0,0 +1,142 @@ +import { App } from "obsidian"; +import { useEffect, useRef } from "react"; +import { EditorView } from "@codemirror/view"; +import { EditorState } from "@codemirror/state"; +import { markdown } from "@codemirror/lang-markdown"; +import { basicSetup } from "codemirror"; +import diff from "./diff"; +import { createDiffHighlightPlugin } from "./diff-highlight-plugin"; + +// Add styles for diff highlighting +const styles = document.createElement("style"); +styles.innerHTML = ` + .diff-remove-background { + background-color: rgba(255, 0, 0, 0.1); + } + .diff-add-background { + background-color: rgba(0, 255, 0, 0.1); + } +`; +document.head.appendChild(styles); + +interface DiffViewProps { + oldText: string; + newText: string; + onResolve: (resolvedText: string) => void; + app: App; +} + +const DiffView: React.FC = ({ + oldText, + newText, + onResolve, + app, +}) => { + const containerRef = useRef(null); + const editorViewsRef = useRef([]); + + useEffect(() => { + if (!containerRef.current) return; + + const createEditor = ( + container: HTMLElement, + content: string, + readOnly: boolean = false, + isOriginal: boolean = false, + ) => { + const differences = diff(oldText, newText); + + const highlightPlugin = createDiffHighlightPlugin({ + diff: differences, + isOriginal, + }); + + const changeListener = !readOnly + ? [ + EditorView.updateListener.of((update) => { + if (update.docChanged) { + onResolve(update.state.doc.toString()); + } + }), + ] + : []; + + const state = EditorState.create({ + doc: content, + extensions: [ + basicSetup, + markdown(), + EditorView.lineWrapping, + EditorView.editable.of(!readOnly), + highlightPlugin, + ...changeListener, + ], + }); + + const view = new EditorView({ + state, + parent: container, + }); + + editorViewsRef.current.push(view); + return view; + }; + + // Create the three editors + const originalView = createEditor( + containerRef.current.querySelector(".original-editor")!, + oldText, + true, + true, + ); + + const modifiedView = createEditor( + containerRef.current.querySelector(".modified-editor")!, + newText, + true, + false, + ); + + const resultView = createEditor( + containerRef.current.querySelector(".result-editor")!, + newText, + false, + ); + + return () => { + editorViewsRef.current.forEach((view) => view.destroy()); + editorViewsRef.current = []; + }; + }, [oldText, newText]); + + return ( +
+
+
+
+
+ ); +}; + +export default DiffView; diff --git a/src/views/conflicts-resolution/diff-highlight-plugin.ts b/src/views/conflicts-resolution/diff-highlight-plugin.ts new file mode 100644 index 0000000..26ae04a --- /dev/null +++ b/src/views/conflicts-resolution/diff-highlight-plugin.ts @@ -0,0 +1,53 @@ +import { + ViewPlugin, + Decoration, + DecorationSet, + EditorView, +} from "@codemirror/view"; +import { DiffResult } from "./diff"; + +interface DiffHighlightPluginSpec { + diff: DiffResult[]; + isOriginal: boolean; +} + +export function createDiffHighlightPlugin(spec: DiffHighlightPluginSpec) { + return ViewPlugin.fromClass( + class { + decorations: DecorationSet; + + constructor(view: EditorView) { + this.decorations = this.buildDecorations(view); + } + + buildDecorations(view: EditorView): DecorationSet { + const doc = view.state.doc.toString(); + + const decorations = spec.diff + .map((d) => { + if ( + (spec.isOriginal && d.type === "remove") || + (!spec.isOriginal && d.type === "add") + ) { + // Find the text position in the actual document + const pos = doc.indexOf(d.value); + if (pos !== -1) { + return Decoration.mark({ + class: spec.isOriginal + ? "diff-remove-background" + : "diff-add-background", + }).range(pos, pos + d.value.length); + } + } + return null; + }) + .filter((d): d is NonNullable => d !== null); + + return Decoration.set(decorations, true); + } + }, + { + decorations: (v) => v.decorations, + }, + ); +} diff --git a/src/views/conflicts-resolution/diff.ts b/src/views/conflicts-resolution/diff.ts new file mode 100644 index 0000000..8a41d52 --- /dev/null +++ b/src/views/conflicts-resolution/diff.ts @@ -0,0 +1,69 @@ +export type DiffResult = { + type: "add" | "remove" | "equal"; + value: string; + from: number; + to: number; +}; + +export default function diff(oldText: string, newText: string): DiffResult[] { + const oldLines = oldText.split("\n"); + const newLines = newText.split("\n"); + + const matrix = Array(oldLines.length + 1) + .fill(null) + .map(() => Array(newLines.length + 1).fill(0)); + + // Fill the matrix + for (let i = 1; i <= oldLines.length; i++) { + for (let j = 1; j <= newLines.length; j++) { + if (oldLines[i - 1] === newLines[j - 1]) { + matrix[i][j] = matrix[i - 1][j - 1] + 1; + } else { + matrix[i][j] = Math.max(matrix[i - 1][j], matrix[i][j - 1]); + } + } + } + + // Backtrack to find differences + const result: DiffResult[] = []; + let i = oldLines.length; + let j = newLines.length; + let position = 0; + + while (i > 0 || j > 0) { + if (i > 0 && j > 0 && oldLines[i - 1] === newLines[j - 1]) { + const value = oldLines[i - 1]; + result.unshift({ + type: "equal", + value, + from: position, + to: position + value.length, + }); + position += value.length + 1; // +1 for newline + i--; + j--; + } else if (j > 0 && (i === 0 || matrix[i][j - 1] >= matrix[i - 1][j])) { + const value = newLines[j - 1]; + result.unshift({ + type: "add", + value, + from: position, + to: position + value.length, + }); + position += value.length + 1; + j--; + } else if (i > 0 && (j === 0 || matrix[i][j - 1] < matrix[i - 1][j])) { + const value = oldLines[i - 1]; + result.unshift({ + type: "remove", + value, + from: position, + to: position + value.length, + }); + position += value.length + 1; + i--; + } + } + + return result; +} diff --git a/src/views/conflicts-resolution/view.tsx b/src/views/conflicts-resolution/view.tsx new file mode 100644 index 0000000..ae10000 --- /dev/null +++ b/src/views/conflicts-resolution/view.tsx @@ -0,0 +1,75 @@ +import { IconName, ItemView, WorkspaceLeaf } from "obsidian"; +import { Root, createRoot } from "react-dom/client"; +import DiffView from "./component"; +import GitHubSyncPlugin from "src/main"; + +export const CONFLICTS_RESOLUTION_VIEW_TYPE = "conflicts-resolution-view"; + +// Test Case 1: Simple line changes +const oldText1 = `# My Document +This is a test +Some content here +Final line`; + +const newText1 = `# My Document +This is a modified test +Some new content here +Final line +Added line`; + +// Test Case 2: Markdown with formatting +const oldText2 = `# Title +## Subtitle +- List item 1 +- List item 2 + +**Bold text** and *italic* text +Regular paragraph`; + +const newText2 = `# Modified Title +## Subtitle +- List item 1 +- List item 2 +- New item + +**Bold text** and *italic* text +Modified paragraph +New paragraph`; + +export class ConflictsResolutionView extends ItemView { + icon: IconName = "merge"; + + constructor( + leaf: WorkspaceLeaf, + private plugin: GitHubSyncPlugin, + ) { + super(leaf); + } + + getViewType() { + return CONFLICTS_RESOLUTION_VIEW_TYPE; + } + + getDisplayText() { + return "Conflicts Resolution"; + } + + async onOpen() { + const container = this.containerEl.children[1]; + container.empty(); + const root: Root = createRoot(container); + root.render( + console.log("Resolved:", text)} + // registerExtension={(ext) => this.plugin.registerEditorExtension(ext)} + />, + ); + } + + async onClose() { + // Nothing to clean up. + } +}