From 6ae1cc18e20fe35643281f5f6493562598647167 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 09:37:03 +0200 Subject: [PATCH 01/47] chore(web-clipper): remove IDEA file --- apps/web-clipper/trilium-web-clipper.iml | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 apps/web-clipper/trilium-web-clipper.iml diff --git a/apps/web-clipper/trilium-web-clipper.iml b/apps/web-clipper/trilium-web-clipper.iml deleted file mode 100644 index c3e779f97..000000000 --- a/apps/web-clipper/trilium-web-clipper.iml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file From 5113e2ab975508369ed6816bb3c0d6e44c191081 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 09:42:02 +0200 Subject: [PATCH 02/47] chore(web-clipper): create package JSON --- apps/web-clipper/package.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 apps/web-clipper/package.json diff --git a/apps/web-clipper/package.json b/apps/web-clipper/package.json new file mode 100644 index 000000000..86fe91e97 --- /dev/null +++ b/apps/web-clipper/package.json @@ -0,0 +1,11 @@ +{ + "name": "@triliumnext/web-clipper", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "packageManager": "pnpm@10.28.1" +} From f8e5f31970a0a32fab71a325a303c22758d8c602 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 09:45:05 +0200 Subject: [PATCH 03/47] chore(web-clipper): install WXT --- apps/web-clipper/.wxt/tsconfig.json | 1 + apps/web-clipper/package.json | 13 +- pnpm-lock.yaml | 1346 +++++++++++++++++++++++++-- 3 files changed, 1299 insertions(+), 61 deletions(-) create mode 100644 apps/web-clipper/.wxt/tsconfig.json diff --git a/apps/web-clipper/.wxt/tsconfig.json b/apps/web-clipper/.wxt/tsconfig.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/apps/web-clipper/.wxt/tsconfig.json @@ -0,0 +1 @@ +{} diff --git a/apps/web-clipper/package.json b/apps/web-clipper/package.json index 86fe91e97..e7ebd634a 100644 --- a/apps/web-clipper/package.json +++ b/apps/web-clipper/package.json @@ -4,8 +4,17 @@ "description": "", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "dev": "wxt", + "dev:firefox": "wxt -b firefox", + "build": "wxt build", + "build:firefox": "wxt build -b firefox", + "zip": "wxt zip", + "zip:firefox": "wxt zip -b firefox", + "postinstall": "wxt prepare" }, "keywords": [], - "packageManager": "pnpm@10.28.1" + "packageManager": "pnpm@10.28.1", + "devDependencies": { + "wxt": "0.20.13" + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b66963f22..cddc67cb8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -817,6 +817,12 @@ importers: specifier: 17.2.3 version: 17.2.3 + apps/web-clipper: + devDependencies: + wxt: + specifier: 0.20.13 + version: 0.20.13(@types/node@24.10.9)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.31.1)(rollup@4.52.0)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) + apps/website: dependencies: i18next: @@ -1461,9 +1467,22 @@ importers: packages: + '@1natsu/wait-element@4.1.2': + resolution: {integrity: sha512-qWxSJD+Q5b8bKOvESFifvfZ92DuMsY+03SBNjTO34ipJLP6mZ9yK4bQz/vlh48aEQXoJfaZBqUwKL5BdI5iiWw==} + '@adobe/css-tools@4.4.4': resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} + '@aklinker1/rollup-plugin-visualizer@5.12.0': + resolution: {integrity: sha512-X24LvEGw6UFmy0lpGJDmXsMyBD58XmX1bbwsaMLhNoM+UMQfQ3b2RtC+nz4b/NoRK5r6QJSKJHBNVeUdwqybaQ==} + engines: {node: '>=14'} + hasBin: true + peerDependencies: + rollup: 2.x || 3.x || 4.x + peerDependenciesMeta: + rollup: + optional: true + '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} @@ -1724,6 +1743,10 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/runtime@7.28.2': + resolution: {integrity: sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==} + engines: {node: '>=6.9.0'} + '@babel/runtime@7.28.4': resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} engines: {node: '>=6.9.0'} @@ -2240,6 +2263,19 @@ packages: peerDependencies: postcss-selector-parser: ^7.1.1 + '@devicefarmer/adbkit-logcat@2.1.3': + resolution: {integrity: sha512-yeaGFjNBc/6+svbDeul1tNHtNChw6h8pSHAt5D+JsedUrMTN7tla7B15WLDyekxsuS2XlZHRxpuC6m92wiwCNw==} + engines: {node: '>= 4'} + + '@devicefarmer/adbkit-monkey@1.2.1': + resolution: {integrity: sha512-ZzZY/b66W2Jd6NHbAhLyDWOEIBWC11VizGFk7Wx7M61JZRz7HR9Cq5P+65RKWUU7u6wgsE8Lmh9nE4Mz+U2eTg==} + engines: {node: '>= 0.10.4'} + + '@devicefarmer/adbkit@3.3.8': + resolution: {integrity: sha512-7rBLLzWQnBwutH2WZ0EWUkQdihqrnLYCUMaB44hSol9e0/cdIhuNFcqZO0xNheAU6qqHVA8sMiLofkYTgb+lmw==} + engines: {node: '>= 0.10.4'} + hasBin: true + '@digitak/esrun@3.2.26': resolution: {integrity: sha512-mL0bw7NhKVghp7mVsPwnAMhCn4NGAsk0KKFmAfnrYAZ/QCXR5xLXIYP82zLMjcsQag8DD6i1c+Yrm/57StYVzg==} engines: {node: '>=14.0'} @@ -3480,6 +3516,9 @@ packages: '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} @@ -4031,6 +4070,18 @@ packages: engines: {node: '>=18'} hasBin: true + '@pnpm/config.env-replace@1.1.0': + resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} + engines: {node: '>=12.22.0'} + + '@pnpm/network.ca-file@1.0.2': + resolution: {integrity: sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==} + engines: {node: '>=12.22.0'} + + '@pnpm/npm-conf@3.0.2': + resolution: {integrity: sha512-h104Kh26rR8tm+a3Qkc5S4VLYint3FE48as7+/5oCEcKR2idC/pF1G6AhIXKI+eHPJa/3J9i5z0Al47IeGHPkA==} + engines: {node: '>=12'} + '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} @@ -5360,6 +5411,12 @@ packages: '@types/express@5.0.6': resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} + '@types/filesystem@0.0.36': + resolution: {integrity: sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==} + + '@types/filewriter@0.0.33': + resolution: {integrity: sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==} + '@types/fs-extra@11.0.4': resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} @@ -5372,6 +5429,9 @@ packages: '@types/geojson@7946.0.16': resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + '@types/har-format@1.2.16': + resolution: {integrity: sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==} + '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} @@ -5441,6 +5501,9 @@ packages: '@types/mime@1.3.5': resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + '@types/minimatch@3.0.5': + resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==} + '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} @@ -5954,6 +6017,21 @@ packages: '@webcomponents/webcomponentsjs@2.8.0': resolution: {integrity: sha512-loGD63sacRzOzSJgQnB9ZAhaQGkN7wl2Zuw7tsphI5Isa0irijrRo6EnJii/GgjGefIFO8AIO7UivzRhFaEk9w==} + '@webext-core/fake-browser@1.3.4': + resolution: {integrity: sha512-nZcVWr3JpwpS5E6hKpbAwAMBM/AXZShnfW0F76udW8oLd6Kv0nbW6vFS07md4Na/0ntQonk3hFnlQYGYBAlTrA==} + + '@webext-core/isolated-element@1.1.3': + resolution: {integrity: sha512-rbtnReIGdiVQb2UhK3MiECU6JqsiIo2K/luWvOdOw57Ot770Iw4KLCEPXUQMITIH5V5er2jfVK8hSWXaEOQGNQ==} + + '@webext-core/match-patterns@1.0.3': + resolution: {integrity: sha512-NY39ACqCxdKBmHgw361M9pfJma8e4AZo20w9AY+5ZjIj1W2dvXC8J31G5fjfOGbulW9w4WKpT8fPooi0mLkn9A==} + + '@wxt-dev/browser@0.1.32': + resolution: {integrity: sha512-jvfSppeLzlH4sOkIvMBJoA1pKoI+U5gTkjDwMKdkTWh0P/fj+KDyze3lzo3S6372viCm8tXUKNez+VKyVz2ZDw==} + + '@wxt-dev/storage@1.2.6': + resolution: {integrity: sha512-f6AknnpJvhNHW4s0WqwSGCuZAj0fjP3EVNPBO5kB30pY+3Zt/nqZGqJN6FgBLCSkYjPJ8VL1hNX5LMVmvxQoDw==} + '@xmldom/xmldom@0.8.10': resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==} engines: {node: '>=10.0.0'} @@ -6041,6 +6119,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + adm-zip@0.5.16: + resolution: {integrity: sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==} + engines: {node: '>=12.0'} + agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} @@ -6113,6 +6195,9 @@ packages: amator@1.1.0: resolution: {integrity: sha512-V5+aH8pe+Z3u/UG3L3pG3BaFQGXAyXHVQDroRwjPHdh08bcUEchAVsU1MCuJSCaU5o60wTK6KaE6te5memzgYw==} + ansi-align@3.0.1: + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} @@ -6210,6 +6295,10 @@ packages: resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} + array-differ@4.0.0: + resolution: {integrity: sha512-Q6VPTLMsmXZ47ENG3V+wQyZS1ZxXMxFyYzA+Z/GMrJ6yIutAIEf9wTyroTzmGjNfox9/h3GdGBCVh43GVFx4Uw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + array-flatten@1.1.1: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} @@ -6221,6 +6310,10 @@ packages: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} + array-union@3.0.1: + resolution: {integrity: sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==} + engines: {node: '>=12'} + array.prototype.findlast@1.2.5: resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} engines: {node: '>= 0.4'} @@ -6286,6 +6379,13 @@ packages: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} engines: {node: '>= 4.0.0'} + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + + atomically@2.1.0: + resolution: {integrity: sha512-+gDffFXRW6sl/HCwbta7zK4uNqbPjv4YJEAdz7Vu+FLQHe77eZ4bvbJGi4hE0QPeJlMYMA3piXEr1UL3dAwx7Q==} + author-regex@1.0.0: resolution: {integrity: sha512-KbWgR8wOYRAPekEmMXrYYdc7BRyhn2Ftk7KWfMUnQ43hFdojWEFRxhhRUm3/OFEdPa1r0KAvTTg9YQK57xTe0g==} engines: {node: '>=0.8'} @@ -6458,6 +6558,10 @@ packages: bowser@2.11.0: resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} + boxen@8.0.1: + resolution: {integrity: sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==} + engines: {node: '>=18'} + boxicons@2.1.4: resolution: {integrity: sha512-BvJNfYfnE4g9WQ7GL91fftxMOTwAleWlPFwvQJPYb/Ju7aLjlQ/Eu55AH9JLNk/OR82z+ZSq4TbKzbV/e5Rr0A==} @@ -6536,6 +6640,18 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + c12@3.3.3: + resolution: {integrity: sha512-750hTRvgBy5kcMNPdh95Qo+XUBeGo8C7nsKSmedDmaQI+E0r82DwHeM6vBewDe4rGFbnxoa4V9pw+sPh5+Iz8Q==} + peerDependencies: + magicast: '*' + peerDependenciesMeta: + magicast: + optional: true + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + cacache@15.3.0: resolution: {integrity: sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==} engines: {node: '>= 10'} @@ -6590,6 +6706,10 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} + camelcase@8.0.0: + resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} + engines: {node: '>=16'} + camelize@1.0.1: resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} @@ -6682,10 +6802,25 @@ packages: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} + chrome-launcher@1.2.0: + resolution: {integrity: sha512-JbuGuBNss258bvGil7FT4HKdC3SC2K7UAEUqiPy3ACS3Yxo3hAW6bvFpCu2HsIJLgTqxgEX6BkujvzZfLpUD0Q==} + engines: {node: '>=12.13.0'} + hasBin: true + chrome-trace-event@1.0.4: resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} engines: {node: '>=6.0'} + ci-info@4.3.1: + resolution: {integrity: sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==} + engines: {node: '>=8'} + + citty@0.1.6: + resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + + citty@0.2.0: + resolution: {integrity: sha512-8csy5IBFI2ex2hTVpaHN2j+LNE199AgiI7y4dMintrr8i0lQiFn+0AWMZrWdHKIgMOer65f8IThysYhoReqjWA==} + ckeditor5-collaboration@47.4.0: resolution: {integrity: sha512-SNwRWFy6DcU1R9wHpRvXq6YLbpMExRoGwms+JF1bKZK9afHznGOouoMPSpHNrkE27OMem6r0SlXAryUPQh4Pfg==} @@ -6704,6 +6839,10 @@ packages: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} + cli-boxes@3.0.0: + resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} + engines: {node: '>=10'} + cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} @@ -6728,6 +6867,10 @@ packages: resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + cli-truncate@4.0.0: + resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} + engines: {node: '>=18'} + cli-truncate@5.1.0: resolution: {integrity: sha512-7JDGG+4Zp0CsknDCedl0DYdaeOhc46QNpXi3NLQblkZpXXgA6LncLDUUyvrjSvZeF3VRQa+KiMGomazQrC1V8g==} engines: {node: '>=20'} @@ -6860,6 +7003,10 @@ packages: commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + commander@2.9.0: + resolution: {integrity: sha512-bmkUukX8wAOjHdN26xj5c4ctEV22TQ7dQYhSmuckKhToXrkUn0iIaolHdIxYYqD55nhpSPA9zPQ1yP57GdXP2A==} + engines: {node: '>= 0.6.x'} + commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -6934,6 +7081,13 @@ packages: confbox@0.2.2: resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + config-chain@1.1.13: + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + + configstore@7.1.0: + resolution: {integrity: sha512-N4oog6YJWbR9kGyXvS7jEykLDXIE2C0ILYqNBZBp9iwiJpoCBWYsuAdW6PPFn6w06jjnC+3JstVvWHO4cZqvRg==} + engines: {node: '>=18'} + connect-history-api-fallback@2.0.0: resolution: {integrity: sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==} engines: {node: '>=0.8'} @@ -6942,6 +7096,10 @@ packages: resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==} engines: {node: '>= 0.10.0'} + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + console-control-strings@1.1.0: resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} @@ -7211,6 +7369,9 @@ packages: cssom@0.4.4: resolution: {integrity: sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==} + cssom@0.5.0: + resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==} + cssstyle@2.3.0: resolution: {integrity: sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==} engines: {node: '>=8'} @@ -7435,6 +7596,9 @@ packages: de-indent@1.0.2: resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} + debounce@1.2.1: + resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} + debounce@3.0.0: resolution: {integrity: sha512-64byRbF0/AirwbuHqB3/ZpMG9/nckDa6ZA0yd6UnaQNwbbemCOwvz2sL5sjXLHhZHADyiwLm0M5qMhltUUx+TA==} engines: {node: '>=20'} @@ -7540,6 +7704,10 @@ packages: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} + define-lazy-prop@2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} + define-lazy-prop@3.0.0: resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} engines: {node: '>=12'} @@ -7551,6 +7719,9 @@ packages: defined@1.0.1: resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==} + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + degenerator@5.0.1: resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} engines: {node: '>= 14'} @@ -7577,6 +7748,9 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + destroy@1.2.0: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -7688,6 +7862,14 @@ packages: domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + dot-prop@9.0.0: + resolution: {integrity: sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==} + engines: {node: '>=18'} + + dotenv-expand@12.0.3: + resolution: {integrity: sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==} + engines: {node: '>=12'} + dotenv@16.4.7: resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} engines: {node: '>=12'} @@ -7912,6 +8094,9 @@ packages: es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-module-lexer@2.0.0: + resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -8201,6 +8386,9 @@ packages: exsolve@1.0.5: resolution: {integrity: sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg==} + exsolve@1.0.8: + resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} + ext-list@2.2.2: resolution: {integrity: sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==} engines: {node: '>=0.10.0'} @@ -8244,6 +8432,10 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-redact@3.5.0: + resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==} + engines: {node: '>=6'} + fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} @@ -8328,6 +8520,10 @@ packages: resolution: {integrity: sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==} engines: {node: '>=8'} + filesize@11.0.13: + resolution: {integrity: sha512-mYJ/qXKvREuO0uH8LTQJ6v7GsUvVOguqxg2VTwQUkyTPXXRRWPdjuUPVqdBrJQhvci48OHlNGRnux+Slr2Rnvw==} + engines: {node: '>= 10.8.0'} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -8356,6 +8552,11 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} + firefox-profile@4.7.0: + resolution: {integrity: sha512-aGApEu5bfCNbA4PGUZiRJAIU6jKmghV2UVdklXAofnNtiDjqYw0czLS46W7IfFqVKgKhFB8Ao2YoNGHY4BoIMQ==} + engines: {node: '>=18'} + hasBin: true + flat-cache@4.0.1: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} @@ -8405,6 +8606,10 @@ packages: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} + form-data-encoder@4.1.0: + resolution: {integrity: sha512-G6NsmEW15s0Uw9XnCg+33H3ViYRyiM0hMrMhhqQOR8NFc5GhYrI+6I3u7OTw7b91J2g8rtvMBZJDbcGb2YUniw==} + engines: {node: '>= 18'} + form-data@4.0.4: resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} engines: {node: '>= 6'} @@ -8417,6 +8622,10 @@ packages: resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} engines: {node: '>=0.4.x'} + formdata-node@6.0.3: + resolution: {integrity: sha512-8e1++BCiTzUno9v5IZ2J6bv4RU+3UKDmqWUQD0MIMVCd9AdhWkO1gw57oo1mNEX1dMq2EGI+FbWz4B92pscSQg==} + engines: {node: '>= 18'} + formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} @@ -8515,6 +8724,10 @@ packages: fuzzysort@3.1.0: resolution: {integrity: sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==} + fx-runner@1.4.0: + resolution: {integrity: sha512-rci1g6U0rdTg6bAaBboP7XdRu01dzTAaKXxFf+PUqGuCv6Xu7o8NZdY1D5MvKGIjb6EdS1g3VlXOgksir1uGkg==} + hasBin: true + galactus@1.0.0: resolution: {integrity: sha512-R1fam6D4CyKQGNlvJne4dkNF+PvUUl7TAJInvTGa9fti9qAv95quQz29GXapA4d8Ec266mJJxFVh82M4GIIGDQ==} engines: {node: '>= 12'} @@ -8570,6 +8783,9 @@ packages: resolution: {integrity: sha512-SCbprXGAPdIhKAXiG+Mk6yeoFH61JlYunqdFQFHDtLjJlDjFf6x07dsS8acO+xWt52jpdVo49AlVDnUVK1sDNw==} engines: {node: '>= 4.0'} + get-port-please@3.2.0: + resolution: {integrity: sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==} + get-port@7.1.0: resolution: {integrity: sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==} engines: {node: '>=16'} @@ -8607,6 +8823,10 @@ packages: gifwrap@0.10.1: resolution: {integrity: sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==} + giget@2.0.0: + resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} + hasBin: true + github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} @@ -8667,6 +8887,10 @@ packages: resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} engines: {node: '>=10.0'} + global-directory@4.0.1: + resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} + engines: {node: '>=18'} + global-dirs@3.0.1: resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} engines: {node: '>=10'} @@ -8725,9 +8949,15 @@ packages: resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==} engines: {node: '>=10.19.0'} + graceful-fs@4.2.10: + resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + graceful-readlink@1.0.1: + resolution: {integrity: sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w==} + grapheme-splitter@1.0.4: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} @@ -8738,6 +8968,9 @@ packages: resolution: {integrity: sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + growly@1.3.0: + resolution: {integrity: sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==} + hachure-fill@0.5.2: resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} @@ -8868,6 +9101,9 @@ packages: hoist-non-react-statics@2.5.5: resolution: {integrity: sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==} + hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + hookified@1.13.0: resolution: {integrity: sha512-6sPYUY8olshgM/1LDNW4QZQN0IqgKhtl/1C8koNZBJrKLBk3AZl6chQtNwpNztvfiApHMEwMHek5rv993PRbWw==} @@ -8900,6 +9136,9 @@ packages: html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + html-escaper@3.0.3: + resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} + html-parse-stringify@3.0.1: resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} @@ -9150,6 +9389,10 @@ packages: resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==} engines: {node: '>=10'} + ini@4.1.1: + resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + ini@4.1.3: resolution: {integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -9200,6 +9443,10 @@ packages: resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==} engines: {node: '>= 10'} + is-absolute@0.1.7: + resolution: {integrity: sha512-Xi9/ZSn4NFapG8RP98iNPMOeaV3mXPisxKxzKtHVqr3g56j/fBn+yZmnxSVAA8lmZbl2J9b/a4kJvfU3hqQYgA==} + engines: {node: '>=0.10.0'} + is-animated@2.0.2: resolution: {integrity: sha512-+Hi3UdXHV/3ZgxdO9Ik45ciNhDlYrDOIdGz7Cj7ybddWnYBi4kwBuGMn79Xa2Js4VldgX5e3943Djsr/KYSPbA==} @@ -9288,11 +9535,20 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-in-ci@1.0.0: + resolution: {integrity: sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg==} + engines: {node: '>=18'} + hasBin: true + is-inside-container@1.0.0: resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} engines: {node: '>=14.16'} hasBin: true + is-installed-globally@1.0.0: + resolution: {integrity: sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ==} + engines: {node: '>=18'} + is-interactive@1.0.0: resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} engines: {node: '>=8'} @@ -9328,6 +9584,10 @@ packages: is-node-process@1.2.0: resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} + is-npm@6.1.0: + resolution: {integrity: sha512-O2z4/kNgyjhQwVR1Wpkbfc19JIhggF97NZNCpWTnjH7kVcZMUrnut9XSN7txI7VdyIYk5ZatOq3zvSuWpU8hoA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + is-number-object@1.1.1: resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} engines: {node: '>= 0.4'} @@ -9356,6 +9616,10 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} + is-plain-object@2.0.4: + resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} + engines: {node: '>=0.10.0'} + is-plain-object@5.0.0: resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} engines: {node: '>=0.10.0'} @@ -9363,6 +9627,10 @@ packages: is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-primitive@3.0.1: + resolution: {integrity: sha512-GljRxhWvlCNRfZyORiH77FwdFwGcMO620o37EOYC0ORWdq+WYNVqW0w2Juzew4M+L81l6/QS3t5gkkihyRqv9w==} + engines: {node: '>=0.10.0'} + is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} @@ -9380,6 +9648,10 @@ packages: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} + is-relative@0.1.3: + resolution: {integrity: sha512-wBOr+rNM4gkAZqoLRJI4myw5WzzIdQosFAAbnvfXP5z1LyzgAI3ivOKehC5KfqlQJZoihVhirgtCBj378Eg8GA==} + engines: {node: '>=0.10.0'} + is-set@2.0.3: resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} engines: {node: '>= 0.4'} @@ -9416,6 +9688,14 @@ packages: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} + is-unicode-supported@1.3.0: + resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} + engines: {node: '>=12'} + + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + is-weakmap@2.0.2: resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} engines: {node: '>= 0.4'} @@ -9452,6 +9732,9 @@ packages: resolution: {integrity: sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==} engines: {node: '>= 8.0.0'} + isexe@1.1.2: + resolution: {integrity: sha512-d2eJzK691yZwPHcv1LbeAOa91yMJ9QmfTgSO1oXB65ezVhXQsxBac2vEB4bMVms9cGzaA99n6V2viHMq82VLDw==} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -9459,6 +9742,10 @@ packages: resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} engines: {node: '>=16'} + isobject@3.0.1: + resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} + engines: {node: '>=0.10.0'} + istanbul-lib-coverage@3.2.2: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} @@ -9602,6 +9889,10 @@ packages: json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json-parse-even-better-errors@3.0.2: + resolution: {integrity: sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + json-parse-even-better-errors@4.0.0: resolution: {integrity: sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==} engines: {node: ^18.17.0 || >=20.5.0} @@ -9724,6 +10015,10 @@ packages: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + klona@2.0.6: resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==} engines: {node: '>= 8'} @@ -9737,10 +10032,18 @@ packages: kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + ky@1.14.2: + resolution: {integrity: sha512-q3RBbsO5A5zrPhB6CaCS8ZUv+NWCXv6JJT4Em0i264G9W0fdPB8YRfnnEi7Dm7X7omAkBIPojzYJ2D1oHTHqug==} + engines: {node: '>=18'} + langium@3.3.1: resolution: {integrity: sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==} engines: {node: '>=16.0.0'} + latest-version@9.0.0: + resolution: {integrity: sha512-7W0vV3rqv5tokqkBAFV1LbR7HPOWzXQDpDgEuib/aJ1jsZZx6x3c2mBI+TJhJzOhkGeaLbCKEHXEXLfirtG2JA==} + engines: {node: '>=18'} + launch-editor@2.11.1: resolution: {integrity: sha512-SEET7oNfgSaB6Ym0jufAdCeo3meJVeCaaDyzRygy0xsp2BFKCprcfHljTq4QkzTLUxEKkFK6OK4811YM2oSrRg==} @@ -9782,6 +10085,9 @@ packages: lie@3.3.0: resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + lighthouse-logger@2.0.2: + resolution: {integrity: sha512-vWl2+u5jgOQuZR55Z1WM0XDdrJT6mzMP8zHUct7xTlWhuQs+eV0g+QL0RQdFjT54zVmbhLCP8vIVpy1wGn/gCg==} + lightningcss-android-arm64@1.31.1: resolution: {integrity: sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==} engines: {node: '>= 12.0.0'} @@ -9866,6 +10172,19 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + lines-and-columns@2.0.4: + resolution: {integrity: sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + linkedom@0.18.12: + resolution: {integrity: sha512-jalJsOwIKuQJSeTvsgzPe9iJzyfVaEJiEXl+25EkKevsULHvMJzpNqwvj1jOESWdmgKDiXObyjOYwlUqG7wo1Q==} + engines: {node: '>=16'} + peerDependencies: + canvas: '>= 2' + peerDependenciesMeta: + canvas: + optional: true + linkify-it@5.0.0: resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} @@ -9878,6 +10197,10 @@ packages: resolution: {integrity: sha512-rJysbR9GKIalhTbVL2tYbF2hVyDnrf7pFUZBwjPaMIdadYHmeT+EVi/Bu3qd7ETQPahTotg2WRCatXwRBW554g==} engines: {node: '>=16.0.0'} + listr2@8.3.3: + resolution: {integrity: sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==} + engines: {node: '>=18.0.0'} + listr2@9.0.5: resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==} engines: {node: '>=20.0.0'} @@ -9898,6 +10221,10 @@ packages: resolution: {integrity: sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==} engines: {node: '>=14'} + local-pkg@1.1.2: + resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} + engines: {node: '>=14'} + locate-app@2.5.0: resolution: {integrity: sha512-xIqbzPMBYArJRmPGUZD9CzV9wOqmVtQnaAn3wrj3s6WYW0bQvPI7x+sPYUGmDTYMHefVK//zc6HEYZ1qnxIK+Q==} @@ -9972,6 +10299,10 @@ packages: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} + log-symbols@6.0.0: + resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} + engines: {node: '>=18'} + log-update@5.0.1: resolution: {integrity: sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -10045,6 +10376,9 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + magicast@0.3.5: + resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + magicast@0.5.1: resolution: {integrity: sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==} @@ -10075,6 +10409,9 @@ packages: resolution: {integrity: sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==} engines: {node: '>= 10'} + many-keys-map@2.0.1: + resolution: {integrity: sha512-DHnZAD4phTbZ+qnJdjoNEVU1NecYoSdbOOoVmTDH46AuxDkEVh3MxTVpXq10GtcTC6mndN9dkv1rNfpjRcLnOw==} + map-age-cleaner@0.1.3: resolution: {integrity: sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==} engines: {node: '>=6'} @@ -10108,6 +10445,9 @@ packages: engines: {node: '>= 12'} hasBin: true + marky@1.3.0: + resolution: {integrity: sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==} + matcher@3.0.0: resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} engines: {node: '>=10'} @@ -10477,6 +10817,9 @@ packages: mlly@1.7.4: resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==} + mlly@1.8.0: + resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + mobx-react-lite@4.1.1: resolution: {integrity: sha512-iUxiMpsvNraCKXU+yPotsOncNNmyeS2B5DKL+TL6Tar/xm+wwNJAubJmtRSeAoYawdZqwv8Z/+5nPRHeQxTiXg==} peerDependencies: @@ -10553,6 +10896,10 @@ packages: resolution: {integrity: sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==} hasBin: true + multimatch@6.0.0: + resolution: {integrity: sha512-I7tSVxHGPlmPN/enE3mS1aOSo6bWBfls+3HmuEeCUBCE7gWnm3cBXCBkpurzFjVRwC6Kld8lLaZ1Iv5vOcjvcQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + multimath@2.0.0: resolution: {integrity: sha512-toRx66cAMJ+Ccz7pMIg38xSIrtnbozk0dchXezwQDMgQmbGpfxjtv68H+L00iFL8hxDaVjrmwAFSb3I6bg8Q2g==} @@ -10576,6 +10923,10 @@ packages: nan@2.22.2: resolution: {integrity: sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==} + nano-spawn@1.0.3: + resolution: {integrity: sha512-jtpsQDetTnvS2Ts1fiRdci5rx0VYws5jGyC+4IYOTnIQ/wwdf6JdomlHBwqC3bJYOvaKu0C2GSZ1A60anrYpaA==} + engines: {node: '>=20.17'} + nano-spawn@2.0.0: resolution: {integrity: sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==} engines: {node: '>=20.17'} @@ -10650,6 +11001,9 @@ packages: resolution: {integrity: sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==} engines: {node: 4.x || >=6.0.0} + node-fetch-native@1.6.7: + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -10687,6 +11041,9 @@ packages: node-html-parser@7.0.2: resolution: {integrity: sha512-DxodLVh7a6JMkYzWyc8nBX9MaF4M0lLFYkJHlWOiu7+9/I6mwNK9u5TbAMC7qfqDJEPX9OIoWA2A9t4C2l1mUQ==} + node-notifier@10.0.1: + resolution: {integrity: sha512-YX7TSyDukOZ0g+gmzjB6abKu+hTGvO8+8+gIFDsRCU2t8fLV/P2unmt+LGFaIa4y64aX98Qksa97rgz4vMNeLQ==} + node-readfiles@0.2.0: resolution: {integrity: sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA==} @@ -10779,6 +11136,11 @@ packages: nwsapi@2.2.23: resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} + nypm@0.6.4: + resolution: {integrity: sha512-1TvCKjZyyklN+JJj2TS3P4uSQEInrM/HkkuSXsEzm1ApPgBffOn8gFguNnZf07r/1X6vlryfIqMUkJKQMzlZiw==} + engines: {node: '>=18'} + hasBin: true + oas-kit-common@1.0.8: resolution: {integrity: sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==} @@ -10844,6 +11206,12 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + ofetch@1.5.1: + resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} + + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + oidc-token-hash@5.1.0: resolution: {integrity: sha512-y0W+X7Ppo7oZX6eovsRkuzcSM40Bicg2JEJkDJ4irIt1wsYAP5MLSNv+QAogO8xivMffw/9OvV3um1pxXgt1uA==} engines: {node: ^10.13.0 || >=12.0.0} @@ -10854,6 +11222,10 @@ packages: omggif@1.0.10: resolution: {integrity: sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==} + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + on-finished@2.3.0: resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} engines: {node: '>= 0.8'} @@ -10884,6 +11256,10 @@ packages: resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} engines: {node: '>=18'} + open@8.4.2: + resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} + engines: {node: '>=12'} + openai@6.16.0: resolution: {integrity: sha512-fZ1uBqjFUjXzbGc35fFtYKEOxd20kd9fDpFeqWtsOZWiubY8CZ1NAlXHW3iathaFvqmNtCWMIsosCuyeI7Joxg==} hasBin: true @@ -10918,6 +11294,14 @@ packages: resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} engines: {node: '>=10'} + ora@8.2.0: + resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==} + engines: {node: '>=18'} + + os-shim@0.1.3: + resolution: {integrity: sha512-jd0cvB8qQ5uVt0lvCIexBaROw1KyKm5sbulg2fWOHjETisuCzWyt+eTZKEMs8v6HwzoGs8xik26jg7eCM6pS+A==} + engines: {node: '>= 0.4.0'} + os-tmpdir@1.0.2: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} @@ -10999,6 +11383,10 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + package-json@10.0.1: + resolution: {integrity: sha512-ua1L4OgXSBdsu1FPb7F3tYH0F48a6kxvod4pLUlGY9COeJAJQNX/sNH2IiEmsxw7lqYiAwrdHMjz1FctOsyDQg==} + engines: {node: '>=18'} + package-manager-detector@1.3.0: resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==} @@ -11044,6 +11432,10 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} + parse-json@7.1.1: + resolution: {integrity: sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==} + engines: {node: '>=16'} + parse-node-version@1.0.1: resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==} engines: {node: '>= 0.10'} @@ -11164,6 +11556,9 @@ packages: pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + perfect-debounce@2.1.0: + resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==} + perfect-freehand@1.2.0: resolution: {integrity: sha512-h/0ikF1M3phW7CwpZ5MMvKnfpHficWoOEyr//KVNTxV4F6deRK1eYMtHyBKEAKFK0aXIEUK9oBvlF6PNXMDsAw==} @@ -11197,6 +11592,16 @@ packages: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} + pino-abstract-transport@2.0.0: + resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} + + pino-std-serializers@7.1.0: + resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} + + pino@9.7.0: + resolution: {integrity: sha512-vnMCM6xZTb1WDmLvtG2lE/2p+t9hDEIvTWJsu6FejkE62vB7gDhvzrpFR4Cw2to+9JNQxVnkAKVPA1KPB98vWg==} + hasBin: true + pirates@4.0.7: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} @@ -11215,6 +11620,9 @@ packages: pkg-types@2.1.0: resolution: {integrity: sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==} + pkg-types@2.3.0: + resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + playwright-core@1.57.0: resolution: {integrity: sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==} engines: {node: '>=18'} @@ -11763,6 +12171,9 @@ packages: process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + process@0.11.10: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} @@ -11783,12 +12194,23 @@ packages: resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} engines: {node: '>=10'} + promise-toolbox@0.21.0: + resolution: {integrity: sha512-NV8aTmpwrZv+Iys54sSFOBx3tuVaOBvvrft5PNppnxy9xpU/akHbaWIril22AB22zaPgrgwKdD0KsrM0ptUtpg==} + engines: {node: '>=6'} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + proto-list@1.2.4: + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + protobufjs@7.5.0: resolution: {integrity: sha512-Z2E/kOY1QjoMlCytmexzYfDm/w5fKAiRwpSzGtdnXW1zC88Z2yXazHHrOtwCzn+7wSxyE8PYM4rvVcMphF9sOA==} engines: {node: '>=12.0.0'} @@ -11813,6 +12235,10 @@ packages: psl@1.15.0: resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} + publish-browser-extension@3.0.3: + resolution: {integrity: sha512-cBINZCkLo7YQaGoUvEHthZ0sDzgJQht28IS+SFMT2omSNhGsPiVNRkWir3qLiTrhGhW9Ci2KVHpA1QAMoBdL2g==} + hasBin: true + pump@3.0.3: resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} @@ -11861,6 +12287,9 @@ packages: quansync@0.2.10: resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==} + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + query-selector-shadow-dom@1.0.1: resolution: {integrity: sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==} @@ -11874,6 +12303,9 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + quick-lru@5.1.1: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} @@ -11916,6 +12348,9 @@ packages: peerDependencies: webpack: ^4.0.0 || ^5.0.0 + rc9@2.1.2: + resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} + rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true @@ -12062,6 +12497,10 @@ packages: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + rechoir@0.8.0: resolution: {integrity: sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==} engines: {node: '>= 10.13.0'} @@ -12098,6 +12537,14 @@ packages: resolution: {integrity: sha512-BtizvGtFQKGPUcTy56o3nk1bGRp4SZOTYrDtGNlqCQufptV5IkkLN6Emw+yunAJjzf+C9FQFtvq7IoA3+oMYHQ==} engines: {node: '>=4'} + registry-auth-token@5.1.1: + resolution: {integrity: sha512-P7B4+jq8DeD2nMsAcdfaqHbssgHtZ7Z5+++a5ask90fvmJ8p5je4mOa+wzu+DB4vQ5tdJV/xywY+UnVFeQLV5Q==} + engines: {node: '>=14'} + + registry-url@6.0.1: + resolution: {integrity: sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==} + engines: {node: '>=12'} + regjsgen@0.5.2: resolution: {integrity: sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==} @@ -12352,6 +12799,10 @@ packages: safe-regex2@5.0.0: resolution: {integrity: sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==} + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -12509,6 +12960,9 @@ packages: script-loader@0.7.2: resolution: {integrity: sha512-UMNLEvgOAQuzK8ji8qIscM3GIrRCWN6MmMXGD4SD5l6cSycgGsCo0tX5xRnfQcoghqct0tjHjcykgI1PyBE2aA==} + scule@1.3.0: + resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} + secure-compare@3.0.1: resolution: {integrity: sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==} @@ -12596,6 +13050,10 @@ packages: resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} engines: {node: '>= 0.4'} + set-value@4.1.0: + resolution: {integrity: sha512-zTEg4HL0RwVrqcWs3ztF+x1vkxfm0lP+MQQFPiMJTKVceBwEV0A569Ou8l9IYQG8jOZdMVI1hGsc0tmeD2o/Lw==} + engines: {node: '>=11.0'} + setimmediate@1.0.5: resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} @@ -12624,6 +13082,9 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shell-quote@1.7.3: + resolution: {integrity: sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==} + shell-quote@1.8.3: resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} engines: {node: '>= 0.4'} @@ -12632,6 +13093,9 @@ packages: resolution: {integrity: sha512-Jex+xw5Mg2qMZL3qnzXIfaxEtBaC4n7xifqaqtrZDdlheR70OGkydrPJWT0V1cA1k3nanC86x9FwAmQl6w3Klw==} engines: {node: '>=18'} + shellwords@0.1.1: + resolution: {integrity: sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==} + shimmer@1.2.1: resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==} @@ -12706,6 +13170,9 @@ packages: resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} engines: {node: '>=18'} + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -12778,6 +13245,9 @@ packages: resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + sonic-boom@4.2.0: + resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} + sort-keys-length@1.0.1: resolution: {integrity: sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==} engines: {node: '>=0.10.0'} @@ -12814,6 +13284,9 @@ packages: spacetrim@0.11.59: resolution: {integrity: sha512-lLYsktklSRKprreOm7NXReW8YiX2VBjbgmXYEziOoGf/qsJqAEACaDvoTtUOycwjpaSh+bT8eu0KrJn7UNxiCg==} + spawn-sync@1.0.15: + resolution: {integrity: sha512-9DWBgrgYZzNghseho0JOuh+5fg9u6QWhAWa51QC7+U5rCheZ/j1DrEZnyE0RBBRqZ9uEXGPgSSM0nky6burpVw==} + spdx-correct@3.2.0: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} @@ -12845,6 +13318,9 @@ packages: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} + split@1.0.1: + resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==} + sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} @@ -12902,6 +13378,10 @@ packages: std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + stdin-discarder@0.2.2: + resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} + engines: {node: '>=18'} + stickyfill@1.1.1: resolution: {integrity: sha512-GCp7vHAfpao+Qh/3Flh9DXEJ/qSi0KJwJw6zYlZOtRYXWUIpMM6mC2rIep/dK8RQqwW0KxGJIllmjPIBOGN8AA==} @@ -13016,6 +13496,13 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strip-json-comments@5.0.2: + resolution: {integrity: sha512-4X2FR3UwhNUE9G49aIsJW5hRRR3GXGTBTZRMfv568O60ojM8HcWjV/VxAxCDW3SUND33O6ZY66ZuRcdkj73q2g==} + engines: {node: '>=14.16'} + + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + strip-outer@1.0.1: resolution: {integrity: sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==} engines: {node: '>=0.10.0'} @@ -13037,6 +13524,12 @@ packages: resolution: {integrity: sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==} engines: {node: '>=10'} + stubborn-fs@2.0.0: + resolution: {integrity: sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==} + + stubborn-utils@1.0.2: + resolution: {integrity: sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==} + style-loader@2.0.0: resolution: {integrity: sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==} engines: {node: '>= 10.13.0'} @@ -13289,9 +13782,15 @@ packages: peerDependencies: tslib: ^2 + thread-stream@3.1.0: + resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} + through2@4.0.2: resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + thunky@1.1.0: resolution: {integrity: sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==} @@ -13504,6 +14003,10 @@ packages: resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} engines: {node: '>=10'} + type-fest@3.13.1: + resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} + engines: {node: '>=14.16'} + type-fest@4.26.0: resolution: {integrity: sha512-OduNjVJsFbifKb57UqZ2EMP1i4u64Xwow3NYXUtBbD4vIwJdQd4+xl8YDou1dlm4DVrtwT/7Ky8z8WyCULVfxw==} engines: {node: '>=16'} @@ -13600,6 +14103,9 @@ packages: engines: {node: '>=0.8.0'} hasBin: true + uhyphen@0.2.0: + resolution: {integrity: sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==} + uid-safe@2.1.5: resolution: {integrity: sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==} engines: {node: '>= 0.8'} @@ -13660,6 +14166,10 @@ packages: unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + unimport@5.6.0: + resolution: {integrity: sha512-8rqAmtJV8o60x46kBAJKtHpJDJWkA2xcBqWKPI14MgUb05o1pnpnCnXSxedUXyeq7p8fR5g3pTo2BaswZ9lD9A==} + engines: {node: '>=18.12.0'} + union@0.5.0: resolution: {integrity: sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==} engines: {node: '>= 0.8.0'} @@ -13724,6 +14234,14 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} + unplugin-utils@0.3.1: + resolution: {integrity: sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==} + engines: {node: '>=20.19.0'} + + unplugin@2.3.11: + resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} + engines: {node: '>=18.12.0'} + unused-filename@4.0.1: resolution: {integrity: sha512-ZX6U1J04K1FoSUeoX1OicAhw4d0aro2qo+L8RhJkiGTNtBNkd/Fi1Wxoc9HzcVu6HfOzm0si/N15JjxFmD1z6A==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -13738,6 +14256,10 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + update-notifier@7.3.1: + resolution: {integrity: sha512-+dwUY4L35XFYEzE+OAL3sarJdUioVovq+8f7lcIJ7wnmnYQV5UD1Y/lcwaMSyaQ6Bj3JMj1XSTjZbNLHn/19yA==} + engines: {node: '>=18'} + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -13857,6 +14379,11 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + vite-node@5.3.0: + resolution: {integrity: sha512-8f20COPYJujc3OKPX6OuyBy3ZIv2det4eRRU4GY1y2MjbeGSUmPjedxg1b72KnTagCofwvZ65ThzjxDW2AtQFQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + vite-plugin-dts@4.5.4: resolution: {integrity: sha512-d4sOM8M/8z7vRXHHq/ebbblfaxENjogAAekcfcDCCwAyvGqnPrc7f4NZbvItS+g4WTgerW0xDwSz5qz11JT3vg==} peerDependencies: @@ -14024,6 +14551,10 @@ packages: wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + web-ext-run@0.2.4: + resolution: {integrity: sha512-rQicL7OwuqWdQWI33JkSXKcp7cuv1mJG8u3jRQwx/8aDsmhbTHs9ZRmNYOL+LX0wX8edIEQX8jj4bB60GoXtKA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + web-namespaces@2.0.1: resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} @@ -14088,6 +14619,9 @@ packages: resolution: {integrity: sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==} engines: {node: '>=10.13.0'} + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + webpack@5.101.3: resolution: {integrity: sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==} engines: {node: '>=10.13.0'} @@ -14151,6 +14685,12 @@ packages: wheel@1.0.0: resolution: {integrity: sha512-XiCMHibOiqalCQ+BaNSwRoZ9FDTAvOsXxGHXChBugewDj7HC8VBIER71dEOiRH1fSdLbRCQzngKTSiZ06ZQzeA==} + when-exit@2.1.5: + resolution: {integrity: sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg==} + + when@3.7.7: + resolution: {integrity: sha512-9lFZp/KHoqH6bPKjbWqa+3Dg/K/r2v0X/3/G2x4DBGchVS2QX2VXL3cZV994WQVnTM1/PD71Az25nAzryEUugw==} + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -14167,6 +14707,10 @@ packages: resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} engines: {node: '>= 0.4'} + which@1.2.4: + resolution: {integrity: sha512-zDRAqDSBudazdfM9zpiI30Fu9ve47htYXcGi3ln0wfKu2a7SmrT6F3VDoYONu//48V8Vz4TdCRNPjtvyRO3yBA==} + hasBin: true + which@1.3.1: resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} hasBin: true @@ -14199,6 +14743,13 @@ packages: wide-align@1.1.5: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + widest-line@5.0.0: + resolution: {integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==} + engines: {node: '>=18'} + + winreg@0.0.12: + resolution: {integrity: sha512-typ/+JRmi7RqP1NanzFULK36vczznSNN8kWVA9vIqXyv8GhghUlwhGp1Xj3Nms1FsPcNnsQrJOR10N58/nQ9hQ==} + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -14276,6 +14827,14 @@ packages: resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} engines: {node: '>=18'} + wxt@0.20.13: + resolution: {integrity: sha512-FwQEk+0a4/pYha6rTKGl5iicU6kRYDBDiElJf55CFEfoJKqvGzBTZpphafurQfqU1X0hvAm9w5GEWC0thXI6wQ==} + hasBin: true + + xdg-basedir@5.1.0: + resolution: {integrity: sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==} + engines: {node: '>=12'} + xml-name-validator@3.0.0: resolution: {integrity: sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==} @@ -14399,6 +14958,9 @@ packages: engines: {node: '>=8.0.0'} hasBin: true + zip-dir@2.0.0: + resolution: {integrity: sha512-uhlsJZWz26FLYXOD6WVuq+fIcZ3aBPGo/cFdiLlv3KNwpa52IF3ISV8fLhQLiqVu5No3VhlqlgthN6gehil1Dg==} + zip-stream@6.0.1: resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} engines: {node: '>= 14'} @@ -14429,8 +14991,22 @@ packages: snapshots: + '@1natsu/wait-element@4.1.2': + dependencies: + defu: 6.1.4 + many-keys-map: 2.0.1 + '@adobe/css-tools@4.4.4': {} + '@aklinker1/rollup-plugin-visualizer@5.12.0(rollup@4.52.0)': + dependencies: + open: 8.4.2 + picomatch: 2.3.1 + source-map: 0.7.6 + yargs: 17.7.2 + optionalDependencies: + rollup: 4.52.0 + '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.13 @@ -14982,6 +15558,8 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/runtime@7.28.2': {} + '@babel/runtime@7.28.4': {} '@babel/template@7.27.2': @@ -15102,6 +15680,8 @@ snapshots: '@ckeditor/ckeditor5-core': 47.4.0 '@ckeditor/ckeditor5-upload': 47.4.0 ckeditor5: 47.4.0 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-ai@47.4.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)': dependencies: @@ -15242,12 +15822,16 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.4.0 '@ckeditor/ckeditor5-widget': 47.4.0 es-toolkit: 1.39.5 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-cloud-services@47.4.0': dependencies: '@ckeditor/ckeditor5-core': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-code-block@47.4.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)': dependencies: @@ -15313,6 +15897,8 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.4.0 '@ckeditor/ckeditor5-watchdog': 47.4.0 es-toolkit: 1.39.5 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-dev-build-tools@54.3.2(@swc/helpers@0.5.17)(tslib@2.8.1)(typescript@5.9.3)': dependencies: @@ -15438,6 +16024,8 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 es-toolkit: 1.39.5 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-editor-classic@47.4.0': dependencies: @@ -15447,6 +16035,8 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 es-toolkit: 1.39.5 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-editor-decoupled@47.4.0': dependencies: @@ -15456,6 +16046,8 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 es-toolkit: 1.39.5 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-editor-inline@47.4.0': dependencies: @@ -15489,8 +16081,6 @@ snapshots: '@ckeditor/ckeditor5-table': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-emoji@47.4.0': dependencies: @@ -15503,8 +16093,6 @@ snapshots: ckeditor5: 47.4.0 es-toolkit: 1.39.5 fuzzysort: 3.1.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-engine@47.4.0': dependencies: @@ -15547,8 +16135,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-export-word@47.4.0': dependencies: @@ -15582,8 +16168,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-footnotes@47.4.0': dependencies: @@ -15614,8 +16198,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-heading@47.4.0': dependencies: @@ -15626,8 +16208,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-highlight@47.4.0': dependencies: @@ -15636,8 +16216,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-horizontal-line@47.4.0': dependencies: @@ -15647,6 +16225,8 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.4.0 '@ckeditor/ckeditor5-widget': 47.4.0 ckeditor5: 47.4.0 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-html-embed@47.4.0': dependencies: @@ -15656,8 +16236,6 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.4.0 '@ckeditor/ckeditor5-widget': 47.4.0 ckeditor5: 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-html-support@47.4.0': dependencies: @@ -15673,8 +16251,6 @@ snapshots: '@ckeditor/ckeditor5-widget': 47.4.0 ckeditor5: 47.4.0 es-toolkit: 1.39.5 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-icons@47.4.0': {} @@ -15692,8 +16268,6 @@ snapshots: '@ckeditor/ckeditor5-widget': 47.4.0 ckeditor5: 47.4.0 es-toolkit: 1.39.5 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-import-word@47.4.0': dependencies: @@ -15706,8 +16280,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-indent@47.4.0': dependencies: @@ -15719,8 +16291,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-inspector@5.0.0': {} @@ -15730,8 +16300,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-line-height@47.4.0': dependencies: @@ -15756,8 +16324,6 @@ snapshots: '@ckeditor/ckeditor5-widget': 47.4.0 ckeditor5: 47.4.0 es-toolkit: 1.39.5 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-list-multi-level@47.4.0': dependencies: @@ -15781,8 +16347,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-markdown-gfm@47.4.0': dependencies: @@ -15820,8 +16384,6 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.4.0 '@ckeditor/ckeditor5-widget': 47.4.0 ckeditor5: 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-mention@47.4.0(patch_hash=5981fb59ba35829e4dff1d39cf771000f8a8fdfa7a34b51d8af9549541f2d62d)': dependencies: @@ -15831,8 +16393,6 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 es-toolkit: 1.39.5 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-merge-fields@47.4.0': dependencies: @@ -15845,8 +16405,6 @@ snapshots: '@ckeditor/ckeditor5-widget': 47.4.0 ckeditor5: 47.4.0 es-toolkit: 1.39.5 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-minimap@47.4.0': dependencies: @@ -15855,8 +16413,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-operations-compressor@47.4.0': dependencies: @@ -15911,8 +16467,6 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.4.0 '@ckeditor/ckeditor5-widget': 47.4.0 ckeditor5: 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-pagination@47.4.0': dependencies: @@ -16020,8 +16574,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-slash-command@47.4.0': dependencies: @@ -16034,8 +16586,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-source-editing-enhanced@47.4.0': dependencies: @@ -16083,8 +16633,6 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 es-toolkit: 1.39.5 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-table@47.4.0': dependencies: @@ -16097,8 +16645,6 @@ snapshots: '@ckeditor/ckeditor5-widget': 47.4.0 ckeditor5: 47.4.0 es-toolkit: 1.39.5 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-template@47.4.0': dependencies: @@ -16209,8 +16755,6 @@ snapshots: '@ckeditor/ckeditor5-engine': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 es-toolkit: 1.39.5 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-widget@47.4.0': dependencies: @@ -16230,8 +16774,6 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 es-toolkit: 1.39.5 - transitivePeerDependencies: - - supports-color '@codemirror/autocomplete@6.18.6': dependencies: @@ -16457,6 +16999,22 @@ snapshots: dependencies: postcss-selector-parser: 7.1.1 + '@devicefarmer/adbkit-logcat@2.1.3': {} + + '@devicefarmer/adbkit-monkey@1.2.1': {} + + '@devicefarmer/adbkit@3.3.8': + dependencies: + '@devicefarmer/adbkit-logcat': 2.1.3 + '@devicefarmer/adbkit-monkey': 1.2.1 + bluebird: 3.7.2 + commander: 9.5.0 + debug: 4.3.7 + node-forge: 1.3.1 + split: 1.0.1 + transitivePeerDependencies: + - supports-color + '@digitak/esrun@3.2.26': dependencies: '@digitak/grubber': 3.1.4 @@ -17895,6 +18453,11 @@ snapshots: '@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/trace-mapping': 0.3.31 + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + '@jridgewell/resolve-uri@3.1.2': {} '@jridgewell/source-map@0.3.11': @@ -18500,6 +19063,18 @@ snapshots: dependencies: playwright: 1.57.0 + '@pnpm/config.env-replace@1.1.0': {} + + '@pnpm/network.ca-file@1.0.2': + dependencies: + graceful-fs: 4.2.10 + + '@pnpm/npm-conf@3.0.2': + dependencies: + '@pnpm/config.env-replace': 1.1.0 + '@pnpm/network.ca-file': 1.0.2 + config-chain: 1.1.13 + '@polka/url@1.0.0-next.29': {} '@popperjs/core@2.11.8': {} @@ -20034,6 +20609,12 @@ snapshots: '@types/express-serve-static-core': 5.1.0 '@types/serve-static': 2.2.0 + '@types/filesystem@0.0.36': + dependencies: + '@types/filewriter': 0.0.33 + + '@types/filewriter@0.0.33': {} + '@types/fs-extra@11.0.4': dependencies: '@types/jsonfile': 6.1.4 @@ -20050,6 +20631,8 @@ snapshots: '@types/geojson@7946.0.16': {} + '@types/har-format@1.2.16': {} + '@types/hast@3.0.4': dependencies: '@types/unist': 3.0.3 @@ -20120,6 +20703,8 @@ snapshots: '@types/mime@1.3.5': {} + '@types/minimatch@3.0.5': {} + '@types/ms@2.1.0': {} '@types/multer@2.0.0': @@ -20869,6 +21454,27 @@ snapshots: '@webcomponents/webcomponentsjs@2.8.0': {} + '@webext-core/fake-browser@1.3.4': + dependencies: + lodash.merge: 4.6.2 + + '@webext-core/isolated-element@1.1.3': + dependencies: + is-potential-custom-element-name: 1.0.1 + + '@webext-core/match-patterns@1.0.3': {} + + '@wxt-dev/browser@0.1.32': + dependencies: + '@types/filesystem': 0.0.36 + '@types/har-format': 1.2.16 + + '@wxt-dev/storage@1.2.6': + dependencies: + '@wxt-dev/browser': 0.1.32 + async-mutex: 0.5.0 + dequal: 2.0.3 + '@xmldom/xmldom@0.8.10': {} '@xtuc/ieee754@1.2.0': {} @@ -20934,6 +21540,8 @@ snapshots: acorn@8.15.0: {} + adm-zip@0.5.16: {} + agent-base@6.0.2: dependencies: debug: 4.4.3(supports-color@8.1.1) @@ -21016,6 +21624,10 @@ snapshots: dependencies: bezier-easing: 2.1.0 + ansi-align@3.0.1: + dependencies: + string-width: 4.2.3 + ansi-escapes@4.3.2: dependencies: type-fest: 0.21.3 @@ -21121,6 +21733,8 @@ snapshots: call-bound: 1.0.4 is-array-buffer: 3.0.5 + array-differ@4.0.0: {} + array-flatten@1.1.1: {} array-includes@3.1.9: @@ -21136,6 +21750,8 @@ snapshots: array-union@2.1.0: {} + array-union@3.0.1: {} + array.prototype.findlast@1.2.5: dependencies: call-bind: 1.0.8 @@ -21225,6 +21841,13 @@ snapshots: at-least-node@1.0.0: {} + atomic-sleep@1.0.0: {} + + atomically@2.1.0: + dependencies: + stubborn-fs: 2.0.0 + when-exit: 2.1.5 + author-regex@1.0.0: {} autocomplete.js@0.38.1: @@ -21404,6 +22027,17 @@ snapshots: bowser@2.11.0: {} + boxen@8.0.1: + dependencies: + ansi-align: 3.0.1 + camelcase: 8.0.0 + chalk: 5.6.2 + cli-boxes: 3.0.0 + string-width: 7.2.0 + type-fest: 4.41.0 + widest-line: 5.0.0 + wrap-ansi: 9.0.2 + boxicons@2.1.4: dependencies: '@webcomponents/webcomponentsjs': 2.8.0 @@ -21498,6 +22132,25 @@ snapshots: bytes@3.1.2: {} + c12@3.3.3(magicast@0.3.5): + dependencies: + chokidar: 5.0.0 + confbox: 0.2.2 + defu: 6.1.4 + dotenv: 17.2.3 + exsolve: 1.0.8 + giget: 2.0.0 + jiti: 2.6.1 + ohash: 2.0.11 + pathe: 2.0.3 + perfect-debounce: 2.1.0 + pkg-types: 2.3.0 + rc9: 2.1.2 + optionalDependencies: + magicast: 0.3.5 + + cac@6.7.14: {} + cacache@15.3.0: dependencies: '@npmcli/fs': 1.1.1 @@ -21619,6 +22272,8 @@ snapshots: camelcase@6.3.0: {} + camelcase@8.0.0: {} + camelize@1.0.1: {} caniuse-api@3.0.0: @@ -21742,13 +22397,28 @@ snapshots: chownr@3.0.0: {} + chrome-launcher@1.2.0: + dependencies: + '@types/node': 24.10.9 + escape-string-regexp: 4.0.0 + is-wsl: 2.2.0 + lighthouse-logger: 2.0.2 + transitivePeerDependencies: + - supports-color + chrome-trace-event@1.0.4: {} + ci-info@4.3.1: {} + + citty@0.1.6: + dependencies: + consola: 3.4.2 + + citty@0.2.0: {} + ckeditor5-collaboration@47.4.0: dependencies: '@ckeditor/ckeditor5-collaboration-core': 47.4.0 - transitivePeerDependencies: - - supports-color ckeditor5-premium-features@47.4.0(bufferutil@4.0.9)(ckeditor5@47.4.0)(utf-8-validate@6.0.5): dependencies: @@ -21854,6 +22524,8 @@ snapshots: clean-stack@2.2.0: {} + cli-boxes@3.0.0: {} + cli-cursor@3.1.0: dependencies: restore-cursor: 3.1.0 @@ -21875,6 +22547,11 @@ snapshots: slice-ansi: 5.0.0 string-width: 5.1.2 + cli-truncate@4.0.0: + dependencies: + slice-ansi: 5.0.0 + string-width: 7.2.0 + cli-truncate@5.1.0: dependencies: slice-ansi: 7.1.2 @@ -22001,6 +22678,10 @@ snapshots: commander@2.20.3: {} + commander@2.9.0: + dependencies: + graceful-readlink: 1.0.1 + commander@4.1.1: {} commander@5.1.0: {} @@ -22069,6 +22750,18 @@ snapshots: confbox@0.2.2: {} + config-chain@1.1.13: + dependencies: + ini: 1.3.8 + proto-list: 1.2.4 + + configstore@7.1.0: + dependencies: + atomically: 2.1.0 + dot-prop: 9.0.0 + graceful-fs: 4.2.11 + xdg-basedir: 5.1.0 + connect-history-api-fallback@2.0.0: {} connect@3.7.0: @@ -22080,6 +22773,8 @@ snapshots: transitivePeerDependencies: - supports-color + consola@3.4.2: {} + console-control-strings@1.1.0: optional: true @@ -22418,6 +23113,8 @@ snapshots: cssom@0.4.4: {} + cssom@0.5.0: {} + cssstyle@2.3.0: dependencies: cssom: 0.3.8 @@ -22673,6 +23370,8 @@ snapshots: de-indent@1.0.2: {} + debounce@1.2.1: {} + debounce@3.0.0: {} debug@2.6.9: @@ -22749,6 +23448,8 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + define-lazy-prop@2.0.0: {} + define-lazy-prop@3.0.0: {} define-properties@1.2.1: @@ -22759,6 +23460,8 @@ snapshots: defined@1.0.1: {} + defu@6.1.4: {} + degenerator@5.0.1: dependencies: ast-types: 0.13.4 @@ -22780,6 +23483,8 @@ snapshots: dequal@2.0.3: {} + destr@2.0.5: {} + destroy@1.2.0: {} detect-hover@1.0.3: {} @@ -22894,6 +23599,14 @@ snapshots: domelementtype: 2.3.0 domhandler: 5.0.3 + dot-prop@9.0.0: + dependencies: + type-fest: 4.41.0 + + dotenv-expand@12.0.3: + dependencies: + dotenv: 16.4.7 + dotenv@16.4.7: {} dotenv@17.2.3: {} @@ -23312,6 +24025,8 @@ snapshots: es-module-lexer@1.7.0: {} + es-module-lexer@2.0.0: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -23335,8 +24050,7 @@ snapshots: es-toolkit@1.39.5: {} - es6-error@4.1.1: - optional: true + es6-error@4.1.1: {} es6-promise-pool@2.5.0: {} @@ -23810,6 +24524,8 @@ snapshots: exsolve@1.0.5: {} + exsolve@1.0.8: {} + ext-list@2.2.2: dependencies: mime-db: 1.54.0 @@ -23859,6 +24575,8 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-redact@3.5.0: {} + fast-safe-stringify@2.1.1: {} fast-uri@3.1.0: {} @@ -23943,6 +24661,8 @@ snapshots: strip-outer: 1.0.1 trim-repeated: 1.0.0 + filesize@11.0.13: {} + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -23993,6 +24713,14 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 + firefox-profile@4.7.0: + dependencies: + adm-zip: 0.5.16 + fs-extra: 11.3.3 + ini: 4.1.3 + minimist: 1.2.8 + xml2js: 0.6.2 + flat-cache@4.0.1: dependencies: flatted: 3.3.3 @@ -24059,6 +24787,8 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 + form-data-encoder@4.1.0: {} + form-data@4.0.4: dependencies: asynckit: 0.4.0 @@ -24077,6 +24807,8 @@ snapshots: format@0.2.2: {} + formdata-node@6.0.3: {} + formdata-polyfill@4.0.10: dependencies: fetch-blob: 3.2.0 @@ -24172,6 +24904,15 @@ snapshots: fuzzysort@3.1.0: {} + fx-runner@1.4.0: + dependencies: + commander: 2.9.0 + shell-quote: 1.7.3 + spawn-sync: 1.0.15 + when: 3.7.7 + which: 1.2.4 + winreg: 0.0.12 + galactus@1.0.0: dependencies: debug: 4.4.3(supports-color@8.1.1) @@ -24254,6 +24995,8 @@ snapshots: transitivePeerDependencies: - supports-color + get-port-please@3.2.0: {} + get-port@7.1.0: {} get-proto@1.0.1: @@ -24296,6 +25039,15 @@ snapshots: image-q: 4.0.0 omggif: 1.0.10 + giget@2.0.0: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + defu: 6.1.4 + node-fetch-native: 1.6.7 + nypm: 0.6.4 + pathe: 2.0.3 + github-from-package@0.0.0: {} github-slugger@2.0.0: {} @@ -24385,6 +25137,10 @@ snapshots: serialize-error: 7.0.1 optional: true + global-directory@4.0.1: + dependencies: + ini: 4.1.1 + global-dirs@3.0.1: dependencies: ini: 2.0.0 @@ -24456,8 +25212,12 @@ snapshots: p-cancelable: 2.1.1 responselike: 2.0.1 + graceful-fs@4.2.10: {} + graceful-fs@4.2.11: {} + graceful-readlink@1.0.1: {} + grapheme-splitter@1.0.4: {} graphemer@1.4.0: {} @@ -24465,6 +25225,8 @@ snapshots: graphql@16.12.0: optional: true + growly@1.3.0: {} + hachure-fill@0.5.2: {} handle-thing@2.0.1: {} @@ -24654,6 +25416,8 @@ snapshots: hoist-non-react-statics@2.5.5: {} + hookable@5.5.3: {} + hookified@1.13.0: {} hosted-git-info@2.8.9: {} @@ -24688,6 +25452,8 @@ snapshots: html-escaper@2.0.2: {} + html-escaper@3.0.3: {} + html-parse-stringify@3.0.1: dependencies: void-elements: 3.1.0 @@ -24979,6 +25745,8 @@ snapshots: ini@2.0.0: {} + ini@4.1.1: {} + ini@4.1.3: {} ini@5.0.0: {} @@ -25015,6 +25783,10 @@ snapshots: ipaddr.js@2.2.0: {} + is-absolute@0.1.7: + dependencies: + is-relative: 0.1.3 + is-animated@2.0.2: {} is-arguments@1.2.0: @@ -25099,10 +25871,17 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-in-ci@1.0.0: {} + is-inside-container@1.0.0: dependencies: is-docker: 3.0.0 + is-installed-globally@1.0.0: + dependencies: + global-directory: 4.0.1 + is-path-inside: 4.0.0 + is-interactive@1.0.0: {} is-interactive@2.0.0: {} @@ -25132,6 +25911,8 @@ snapshots: is-node-process@1.2.0: optional: true + is-npm@6.1.0: {} + is-number-object@1.1.1: dependencies: call-bound: 1.0.4 @@ -25149,10 +25930,16 @@ snapshots: is-plain-obj@4.1.0: {} + is-plain-object@2.0.4: + dependencies: + isobject: 3.0.1 + is-plain-object@5.0.0: {} is-potential-custom-element-name@1.0.1: {} + is-primitive@3.0.1: {} + is-promise@4.0.0: {} is-property@1.0.2: @@ -25174,6 +25961,8 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 + is-relative@0.1.3: {} + is-set@2.0.3: {} is-shared-array-buffer@1.0.4: @@ -25205,6 +25994,10 @@ snapshots: is-unicode-supported@0.1.0: {} + is-unicode-supported@1.3.0: {} + + is-unicode-supported@2.1.0: {} + is-weakmap@2.0.2: {} is-weakref@1.1.1: @@ -25235,10 +26028,14 @@ snapshots: isbinaryfile@4.0.10: {} + isexe@1.1.2: {} + isexe@2.0.0: {} isexe@3.1.1: {} + isobject@3.0.1: {} + istanbul-lib-coverage@3.2.2: {} istanbul-lib-instrument@6.0.3: @@ -25447,6 +26244,8 @@ snapshots: json-parse-even-better-errors@2.3.1: {} + json-parse-even-better-errors@3.0.2: {} + json-parse-even-better-errors@4.0.0: {} json-pointer@0.6.2: @@ -25592,6 +26391,8 @@ snapshots: kind-of@6.0.3: {} + kleur@3.0.3: {} + klona@2.0.6: {} knockout@3.5.1: {} @@ -25600,6 +26401,8 @@ snapshots: kolorist@1.8.0: {} + ky@1.14.2: {} + langium@3.3.1: dependencies: chevrotain: 11.0.3 @@ -25608,6 +26411,10 @@ snapshots: vscode-languageserver-textdocument: 1.0.12 vscode-uri: 3.0.8 + latest-version@9.0.0: + dependencies: + package-json: 10.0.1 + launch-editor@2.11.1: dependencies: picocolors: 1.1.1 @@ -25658,6 +26465,13 @@ snapshots: dependencies: immediate: 3.0.6 + lighthouse-logger@2.0.2: + dependencies: + debug: 4.4.3(supports-color@8.1.1) + marky: 1.3.0 + transitivePeerDependencies: + - supports-color + lightningcss-android-arm64@1.31.1: optional: true @@ -25715,6 +26529,16 @@ snapshots: lines-and-columns@1.2.4: {} + lines-and-columns@2.0.4: {} + + linkedom@0.18.12: + dependencies: + css-select: 5.2.2 + cssom: 0.5.0 + html-escaper: 3.0.3 + htmlparser2: 10.1.0 + uhyphen: 0.2.0 + linkify-it@5.0.0: dependencies: uc.micro: 2.1.0 @@ -25738,6 +26562,15 @@ snapshots: rfdc: 1.4.1 wrap-ansi: 8.1.0 + listr2@8.3.3: + dependencies: + cli-truncate: 4.0.0 + colorette: 2.0.20 + eventemitter3: 5.0.1 + log-update: 6.1.0 + rfdc: 1.4.1 + wrap-ansi: 9.0.2 + listr2@9.0.5: dependencies: cli-truncate: 5.1.0 @@ -25768,6 +26601,12 @@ snapshots: pkg-types: 2.1.0 quansync: 0.2.10 + local-pkg@1.1.2: + dependencies: + mlly: 1.8.0 + pkg-types: 2.3.0 + quansync: 0.2.11 + locate-app@2.5.0: dependencies: '@promptbook/utils': 0.69.5 @@ -25826,6 +26665,11 @@ snapshots: chalk: 4.1.2 is-unicode-supported: 0.1.0 + log-symbols@6.0.0: + dependencies: + chalk: 5.6.2 + is-unicode-supported: 1.3.0 + log-update@5.0.1: dependencies: ansi-escapes: 5.0.0 @@ -25901,6 +26745,12 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + magicast@0.3.5: + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + source-map-js: 1.2.1 + magicast@0.5.1: dependencies: '@babel/parser': 7.28.5 @@ -25996,6 +26846,8 @@ snapshots: - supports-color optional: true + many-keys-map@2.0.1: {} + map-age-cleaner@0.1.3: dependencies: p-defer: 1.0.0 @@ -26048,6 +26900,8 @@ snapshots: marked@4.3.0: {} + marky@1.3.0: {} + matcher@3.0.0: dependencies: escape-string-regexp: 4.0.0 @@ -26611,6 +27465,13 @@ snapshots: pkg-types: 1.3.1 ufo: 1.6.1 + mlly@1.8.0: + dependencies: + acorn: 8.15.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.1 + mobx-react-lite@4.1.1(mobx@6.15.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: mobx: 6.15.0 @@ -26714,6 +27575,13 @@ snapshots: dns-packet: 5.6.1 thunky: 1.1.0 + multimatch@6.0.0: + dependencies: + '@types/minimatch': 3.0.5 + array-differ: 4.0.0 + array-union: 3.0.1 + minimatch: 3.1.2 + multimath@2.0.0: dependencies: glur: 1.1.2 @@ -26742,6 +27610,8 @@ snapshots: nan@2.22.2: optional: true + nano-spawn@1.0.3: {} + nano-spawn@2.0.0: {} nanoid@3.3.11: {} @@ -26790,6 +27660,8 @@ snapshots: dependencies: http2-client: 1.3.5 + node-fetch-native@1.6.7: {} + node-fetch@2.7.0(encoding@0.1.13): dependencies: whatwg-url: 5.0.0 @@ -26849,6 +27721,15 @@ snapshots: css-select: 5.2.2 he: 1.2.0 + node-notifier@10.0.1: + dependencies: + growly: 1.3.0 + is-wsl: 2.2.0 + semver: 7.7.3 + shellwords: 0.1.1 + uuid: 8.3.2 + which: 2.0.2 + node-readfiles@0.2.0: dependencies: es6-promise: 3.3.1 @@ -26956,6 +27837,12 @@ snapshots: nwsapi@2.2.23: optional: true + nypm@0.6.4: + dependencies: + citty: 0.2.0 + pathe: 2.0.3 + tinyexec: 1.0.2 + oas-kit-common@1.0.8: dependencies: fast-safe-stringify: 2.1.1 @@ -27046,6 +27933,14 @@ snapshots: obug@2.1.1: {} + ofetch@1.5.1: + dependencies: + destr: 2.0.5 + node-fetch-native: 1.6.7 + ufo: 1.6.1 + + ohash@2.0.11: {} + oidc-token-hash@5.1.0: {} ollama@0.6.3: @@ -27054,6 +27949,8 @@ snapshots: omggif@1.0.10: {} + on-exit-leak-free@2.1.2: {} + on-finished@2.3.0: dependencies: ee-first: 1.1.1 @@ -27085,6 +27982,12 @@ snapshots: is-inside-container: 1.0.0 wsl-utils: 0.1.0 + open@8.4.2: + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + openai@6.16.0(ws@8.19.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(zod@4.1.12): optionalDependencies: ws: 8.19.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) @@ -27131,6 +28034,20 @@ snapshots: strip-ansi: 6.0.1 wcwidth: 1.0.1 + ora@8.2.0: + dependencies: + chalk: 5.6.2 + cli-cursor: 5.0.0 + cli-spinners: 2.9.2 + is-interactive: 2.0.0 + is-unicode-supported: 2.1.0 + log-symbols: 6.0.0 + stdin-discarder: 0.2.2 + string-width: 7.2.0 + strip-ansi: 7.1.2 + + os-shim@0.1.3: {} + os-tmpdir@1.0.2: {} outdent@0.8.0: {} @@ -27211,6 +28128,13 @@ snapshots: package-json-from-dist@1.0.1: {} + package-json@10.0.1: + dependencies: + ky: 1.14.2 + registry-auth-token: 5.1.1 + registry-url: 6.0.1 + semver: 7.7.3 + package-manager-detector@1.3.0: {} pacote@21.0.1: @@ -27278,6 +28202,14 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + parse-json@7.1.1: + dependencies: + '@babel/code-frame': 7.27.1 + error-ex: 1.3.2 + json-parse-even-better-errors: 3.0.2 + lines-and-columns: 2.0.4 + type-fest: 3.13.1 + parse-node-version@1.0.1: optional: true @@ -27375,6 +28307,8 @@ snapshots: pend@1.2.0: {} + perfect-debounce@2.1.0: {} + perfect-freehand@1.2.0: {} perfect-scrollbar@1.5.6: {} @@ -27400,6 +28334,26 @@ snapshots: pify@4.0.1: optional: true + pino-abstract-transport@2.0.0: + dependencies: + split2: 4.2.0 + + pino-std-serializers@7.1.0: {} + + pino@9.7.0: + dependencies: + atomic-sleep: 1.0.0 + fast-redact: 3.5.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 2.0.0 + pino-std-serializers: 7.1.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.0 + thread-stream: 3.1.0 + pirates@4.0.7: {} pixelmatch@5.3.0: @@ -27413,7 +28367,7 @@ snapshots: pkg-types@1.3.1: dependencies: confbox: 0.1.8 - mlly: 1.7.4 + mlly: 1.8.0 pathe: 2.0.3 pkg-types@2.1.0: @@ -27422,6 +28376,12 @@ snapshots: exsolve: 1.0.5 pathe: 2.0.3 + pkg-types@2.3.0: + dependencies: + confbox: 0.2.2 + exsolve: 1.0.8 + pathe: 2.0.3 + playwright-core@1.57.0: {} playwright@1.57.0: @@ -27938,6 +28898,8 @@ snapshots: process-nextick-args@2.0.1: {} + process-warning@5.0.0: {} + process@0.11.10: {} progress@2.0.3: {} @@ -27949,6 +28911,15 @@ snapshots: err-code: 2.0.3 retry: 0.12.0 + promise-toolbox@0.21.0: + dependencies: + make-error: 1.3.6 + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -27957,6 +28928,8 @@ snapshots: property-information@7.1.0: {} + proto-list@1.2.4: {} + protobufjs@7.5.0: dependencies: '@protobufjs/aspromise': 1.1.2 @@ -28001,6 +28974,17 @@ snapshots: dependencies: punycode: 2.3.1 + publish-browser-extension@3.0.3: + dependencies: + cac: 6.7.14 + consola: 3.4.2 + dotenv: 17.2.3 + form-data-encoder: 4.1.0 + formdata-node: 6.0.3 + listr2: 8.3.3 + ofetch: 1.5.1 + zod: 4.1.12 + pump@3.0.3: dependencies: end-of-stream: 1.4.5 @@ -28045,6 +29029,8 @@ snapshots: quansync@0.2.10: {} + quansync@0.2.11: {} + query-selector-shadow-dom@1.0.1: {} query-string@7.1.3: @@ -28058,6 +29044,8 @@ snapshots: queue-microtask@1.2.3: {} + quick-format-unescaped@4.0.4: {} + quick-lru@5.1.1: {} quickselect@3.0.0: {} @@ -28100,6 +29088,11 @@ snapshots: schema-utils: 3.3.0 webpack: 5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2) + rc9@2.1.2: + dependencies: + defu: 6.1.4 + destr: 2.0.5 + rc@1.2.8: dependencies: deep-extend: 0.6.0 @@ -28270,6 +29263,8 @@ snapshots: readdirp@5.0.0: {} + real-require@0.2.0: {} + rechoir@0.8.0: dependencies: resolve: 1.22.10 @@ -28344,6 +29339,14 @@ snapshots: unicode-match-property-ecmascript: 1.0.4 unicode-match-property-value-ecmascript: 1.2.0 + registry-auth-token@5.1.1: + dependencies: + '@pnpm/npm-conf': 3.0.2 + + registry-url@6.0.1: + dependencies: + rc: 1.2.8 + regjsgen@0.5.2: {} regjsparser@0.6.9: @@ -28677,6 +29680,8 @@ snapshots: dependencies: ret: 0.5.0 + safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: {} sanitize-filename@1.6.3: @@ -28831,6 +29836,8 @@ snapshots: dependencies: raw-loader: 0.5.1 + scule@1.3.0: {} + secure-compare@3.0.1: {} selderee@0.11.0: @@ -28969,6 +29976,11 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.1.1 + set-value@4.1.0: + dependencies: + is-plain-object: 2.0.4 + is-primitive: 3.0.1 + setimmediate@1.0.5: {} setprototypeof@1.1.0: {} @@ -28989,6 +30001,8 @@ snapshots: shebang-regex@3.0.0: {} + shell-quote@1.7.3: {} + shell-quote@1.8.3: {} shelljs@0.10.0: @@ -28996,6 +30010,8 @@ snapshots: execa: 5.1.1 fast-glob: 3.3.3 + shellwords@0.1.1: {} + shimmer@1.2.1: {} should-equal@2.0.0: @@ -29109,6 +30125,8 @@ snapshots: mrmime: 2.0.1 totalist: 3.0.1 + sisteransi@1.0.5: {} + slash@3.0.0: {} slash@5.1.0: {} @@ -29220,6 +30238,10 @@ snapshots: ip-address: 10.1.0 smart-buffer: 4.2.0 + sonic-boom@4.2.0: + dependencies: + atomic-sleep: 1.0.0 + sort-keys-length@1.0.1: dependencies: sort-keys: 1.1.2 @@ -29247,6 +30269,11 @@ snapshots: spacetrim@0.11.59: {} + spawn-sync@1.0.15: + dependencies: + concat-stream: 1.6.2 + os-shim: 0.1.3 + spdx-correct@3.2.0: dependencies: spdx-expression-parse: 3.0.1 @@ -29288,6 +30315,10 @@ snapshots: split2@4.2.0: {} + split@1.0.1: + dependencies: + through: 2.3.8 + sprintf-js@1.0.3: {} sprintf-js@1.1.3: @@ -29341,6 +30372,8 @@ snapshots: std-env@3.10.0: {} + stdin-discarder@0.2.2: {} + stickyfill@1.1.1: {} stop-iteration-iterator@1.1.0: @@ -29479,6 +30512,12 @@ snapshots: strip-json-comments@3.1.1: {} + strip-json-comments@5.0.2: {} + + strip-literal@3.1.0: + dependencies: + js-tokens: 9.0.1 + strip-outer@1.0.1: dependencies: escape-string-regexp: 1.0.5 @@ -29499,6 +30538,12 @@ snapshots: '@tokenizer/token': 0.3.0 peek-readable: 4.1.0 + stubborn-fs@2.0.0: + dependencies: + stubborn-utils: 1.0.2 + + stubborn-utils@1.0.2: {} + style-loader@2.0.0(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)): dependencies: loader-utils: 2.0.4 @@ -29922,10 +30967,16 @@ snapshots: dependencies: tslib: 2.8.1 + thread-stream@3.1.0: + dependencies: + real-require: 0.2.0 + through2@4.0.2: dependencies: readable-stream: 3.6.2 + through@2.3.8: {} + thunky@1.1.0: {} time2fa@1.4.2: {} @@ -30166,6 +31217,8 @@ snapshots: type-fest@1.4.0: {} + type-fest@3.13.1: {} + type-fest@4.26.0: {} type-fest@4.41.0: {} @@ -30268,6 +31321,8 @@ snapshots: uglify-js@3.19.3: optional: true + uhyphen@0.2.0: {} + uid-safe@2.1.5: dependencies: random-bytes: 1.0.0 @@ -30322,6 +31377,23 @@ snapshots: trough: 2.2.0 vfile: 6.0.3 + unimport@5.6.0: + dependencies: + acorn: 8.15.0 + escape-string-regexp: 5.0.0 + estree-walker: 3.0.3 + local-pkg: 1.1.2 + magic-string: 0.30.21 + mlly: 1.8.0 + pathe: 2.0.3 + picomatch: 4.0.3 + pkg-types: 2.3.0 + scule: 1.3.0 + strip-literal: 3.1.0 + tinyglobby: 0.2.15 + unplugin: 2.3.11 + unplugin-utils: 0.3.1 + union@0.5.0: dependencies: qs: 6.14.0 @@ -30391,6 +31463,18 @@ snapshots: unpipe@1.0.0: {} + unplugin-utils@0.3.1: + dependencies: + pathe: 2.0.3 + picomatch: 4.0.3 + + unplugin@2.3.11: + dependencies: + '@jridgewell/remapping': 2.3.5 + acorn: 8.15.0 + picomatch: 4.0.3 + webpack-virtual-modules: 0.6.2 + unused-filename@4.0.1: dependencies: escape-string-regexp: 5.0.0 @@ -30404,6 +31488,19 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + update-notifier@7.3.1: + dependencies: + boxen: 8.0.1 + chalk: 5.6.2 + configstore: 7.1.0 + is-in-ci: 1.0.0 + is-installed-globally: 1.0.0 + is-npm: 6.1.0 + latest-version: 9.0.0 + pupa: 3.1.0 + semver: 7.7.3 + xdg-basedir: 5.1.0 + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -30504,6 +31601,26 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 + vite-node@5.3.0(@types/node@24.10.9)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.31.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1): + dependencies: + cac: 6.7.14 + es-module-lexer: 2.0.0 + obug: 2.1.1 + pathe: 2.0.3 + vite: 7.3.1(@types/node@24.10.9)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.31.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml + vite-plugin-dts@4.5.4(@types/node@24.10.9)(rollup@4.52.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.31.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)): dependencies: '@microsoft/api-extractor': 7.52.8(@types/node@24.10.9) @@ -30678,6 +31795,31 @@ snapshots: dependencies: defaults: 1.0.4 + web-ext-run@0.2.4: + dependencies: + '@babel/runtime': 7.28.2 + '@devicefarmer/adbkit': 3.3.8 + chrome-launcher: 1.2.0 + debounce: 1.2.1 + es6-error: 4.1.1 + firefox-profile: 4.7.0 + fx-runner: 1.4.0 + multimatch: 6.0.0 + node-notifier: 10.0.1 + parse-json: 7.1.1 + pino: 9.7.0 + promise-toolbox: 0.21.0 + set-value: 4.1.0 + source-map-support: 0.5.21 + strip-bom: 5.0.0 + strip-json-comments: 5.0.2 + tmp: 0.2.5 + update-notifier: 7.3.1 + watchpack: 2.4.4 + zip-dir: 2.0.0 + transitivePeerDependencies: + - supports-color + web-namespaces@2.0.1: {} web-streams-polyfill@3.3.3: {} @@ -30799,6 +31941,8 @@ snapshots: webpack-sources@3.3.3: {} + webpack-virtual-modules@0.6.2: {} + webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2): dependencies: '@types/eslint-scope': 3.7.7 @@ -30912,6 +32056,10 @@ snapshots: wheel@1.0.0: {} + when-exit@2.1.5: {} + + when@3.7.7: {} + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 @@ -30953,6 +32101,11 @@ snapshots: gopd: 1.2.0 has-tostringtag: 1.0.2 + which@1.2.4: + dependencies: + is-absolute: 0.1.7 + isexe: 1.1.2 + which@1.3.1: dependencies: isexe: 2.0.0 @@ -30983,6 +32136,12 @@ snapshots: string-width: 4.2.3 optional: true + widest-line@5.0.0: + dependencies: + string-width: 7.2.0 + + winreg@0.0.12: {} + word-wrap@1.2.5: {} wordwrap@1.0.0: {} @@ -31044,6 +32203,71 @@ snapshots: dependencies: is-wsl: 3.1.0 + wxt@0.20.13(@types/node@24.10.9)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.31.1)(rollup@4.52.0)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1): + dependencies: + '@1natsu/wait-element': 4.1.2 + '@aklinker1/rollup-plugin-visualizer': 5.12.0(rollup@4.52.0) + '@webext-core/fake-browser': 1.3.4 + '@webext-core/isolated-element': 1.1.3 + '@webext-core/match-patterns': 1.0.3 + '@wxt-dev/browser': 0.1.32 + '@wxt-dev/storage': 1.2.6 + async-mutex: 0.5.0 + c12: 3.3.3(magicast@0.3.5) + cac: 6.7.14 + chokidar: 4.0.3 + ci-info: 4.3.1 + consola: 3.4.2 + defu: 6.1.4 + dotenv: 17.2.3 + dotenv-expand: 12.0.3 + esbuild: 0.27.2 + fast-glob: 3.3.3 + filesize: 11.0.13 + fs-extra: 11.3.3 + get-port-please: 3.2.0 + giget: 2.0.0 + hookable: 5.5.3 + import-meta-resolve: 4.2.0 + is-wsl: 3.1.0 + json5: 2.2.3 + jszip: 3.10.1 + linkedom: 0.18.12 + magicast: 0.3.5 + minimatch: 10.1.1 + nano-spawn: 1.0.3 + normalize-path: 3.0.0 + nypm: 0.6.4 + ohash: 2.0.11 + open: 10.2.0 + ora: 8.2.0 + perfect-debounce: 2.1.0 + picocolors: 1.1.1 + prompts: 2.4.2 + publish-browser-extension: 3.0.3 + scule: 1.3.0 + unimport: 5.6.0 + vite: 7.3.1(@types/node@24.10.9)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.31.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) + vite-node: 5.3.0(@types/node@24.10.9)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.31.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) + web-ext-run: 0.2.4 + transitivePeerDependencies: + - '@types/node' + - canvas + - jiti + - less + - lightningcss + - rollup + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + xdg-basedir@5.1.0: {} + xml-name-validator@3.0.0: {} xml-name-validator@5.0.0: @@ -31163,6 +32387,11 @@ snapshots: optionalDependencies: commander: 9.5.0 + zip-dir@2.0.0: + dependencies: + async: 3.2.6 + jszip: 3.10.1 + zip-stream@6.0.1: dependencies: archiver-utils: 5.0.2 @@ -31171,8 +32400,7 @@ snapshots: zod@3.24.4: {} - zod@4.1.12: - optional: true + zod@4.1.12: {} zustand@4.5.6(@types/react@19.1.7)(react@19.2.3): dependencies: From e4d319c7a183a2af62bfccabb46e6789069b4988 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 09:50:25 +0200 Subject: [PATCH 04/47] chore(web-clipper): define entrypoints --- .../{background.js => entrypoints/background/index.js} | 0 .../{content.js => entrypoints/content/index.js} | 0 .../options.html => entrypoints/options/index.html} | 0 .../{options/options.js => entrypoints/options/index.js} | 0 .../{popup/popup.html => entrypoints/popup/index.html} | 0 apps/web-clipper/{ => entrypoints}/popup/popup.css | 0 apps/web-clipper/{ => entrypoints}/popup/popup.js | 0 apps/web-clipper/manifest.json | 9 ++------- 8 files changed, 2 insertions(+), 7 deletions(-) rename apps/web-clipper/{background.js => entrypoints/background/index.js} (100%) rename apps/web-clipper/{content.js => entrypoints/content/index.js} (100%) rename apps/web-clipper/{options/options.html => entrypoints/options/index.html} (100%) rename apps/web-clipper/{options/options.js => entrypoints/options/index.js} (100%) rename apps/web-clipper/{popup/popup.html => entrypoints/popup/index.html} (100%) rename apps/web-clipper/{ => entrypoints}/popup/popup.css (100%) rename apps/web-clipper/{ => entrypoints}/popup/popup.js (100%) diff --git a/apps/web-clipper/background.js b/apps/web-clipper/entrypoints/background/index.js similarity index 100% rename from apps/web-clipper/background.js rename to apps/web-clipper/entrypoints/background/index.js diff --git a/apps/web-clipper/content.js b/apps/web-clipper/entrypoints/content/index.js similarity index 100% rename from apps/web-clipper/content.js rename to apps/web-clipper/entrypoints/content/index.js diff --git a/apps/web-clipper/options/options.html b/apps/web-clipper/entrypoints/options/index.html similarity index 100% rename from apps/web-clipper/options/options.html rename to apps/web-clipper/entrypoints/options/index.html diff --git a/apps/web-clipper/options/options.js b/apps/web-clipper/entrypoints/options/index.js similarity index 100% rename from apps/web-clipper/options/options.js rename to apps/web-clipper/entrypoints/options/index.js diff --git a/apps/web-clipper/popup/popup.html b/apps/web-clipper/entrypoints/popup/index.html similarity index 100% rename from apps/web-clipper/popup/popup.html rename to apps/web-clipper/entrypoints/popup/index.html diff --git a/apps/web-clipper/popup/popup.css b/apps/web-clipper/entrypoints/popup/popup.css similarity index 100% rename from apps/web-clipper/popup/popup.css rename to apps/web-clipper/entrypoints/popup/popup.css diff --git a/apps/web-clipper/popup/popup.js b/apps/web-clipper/entrypoints/popup/popup.js similarity index 100% rename from apps/web-clipper/popup/popup.js rename to apps/web-clipper/entrypoints/popup/popup.js diff --git a/apps/web-clipper/manifest.json b/apps/web-clipper/manifest.json index fe3b98302..8e0c1ba2b 100644 --- a/apps/web-clipper/manifest.json +++ b/apps/web-clipper/manifest.json @@ -31,8 +31,7 @@ ], "js": [ "lib/browser-polyfill.js", - "utils.js", - "content.js" + "utils.js" ] } ], @@ -40,13 +39,9 @@ "scripts": [ "lib/browser-polyfill.js", "utils.js", - "trilium_server_facade.js", - "background.js" + "trilium_server_facade.js" ] }, - "options_ui": { - "page": "options/options.html" - }, "commands": { "saveSelection": { "description": "Save the selected text into a note", From cb8b9686373d0cd1df67a7904fe6005ec3709d98 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 09:59:36 +0200 Subject: [PATCH 05/47] chore(web-clipper): make entrypoints actually run --- apps/web-clipper/.gitignore | 3 +- apps/web-clipper/.wxt/tsconfig.json | 1 - .../entrypoints/background/index.js | 896 +++++++++--------- apps/web-clipper/entrypoints/content/index.js | 709 +++++++------- apps/web-clipper/manifest.json | 3 - 5 files changed, 809 insertions(+), 803 deletions(-) delete mode 100644 apps/web-clipper/.wxt/tsconfig.json diff --git a/apps/web-clipper/.gitignore b/apps/web-clipper/.gitignore index 77738287f..3e99175bf 100644 --- a/apps/web-clipper/.gitignore +++ b/apps/web-clipper/.gitignore @@ -1 +1,2 @@ -dist/ \ No newline at end of file +.output +.wxt \ No newline at end of file diff --git a/apps/web-clipper/.wxt/tsconfig.json b/apps/web-clipper/.wxt/tsconfig.json deleted file mode 100644 index 0967ef424..000000000 --- a/apps/web-clipper/.wxt/tsconfig.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/apps/web-clipper/entrypoints/background/index.js b/apps/web-clipper/entrypoints/background/index.js index 4074987ab..ee92f709a 100644 --- a/apps/web-clipper/entrypoints/background/index.js +++ b/apps/web-clipper/entrypoints/background/index.js @@ -1,451 +1,453 @@ -// Keyboard shortcuts -chrome.commands.onCommand.addListener(async function (command) { - if (command == "saveSelection") { - await saveSelection(); - } else if (command == "saveWholePage") { - await saveWholePage(); - } else if (command == "saveTabs") { - await saveTabs(); - } else if (command == "saveCroppedScreenshot") { +export default defineBackground(() => { + // Keyboard shortcuts + chrome.commands.onCommand.addListener(async function (command) { + if (command == "saveSelection") { + await saveSelection(); + } else if (command == "saveWholePage") { + await saveWholePage(); + } else if (command == "saveTabs") { + await saveTabs(); + } else if (command == "saveCroppedScreenshot") { + const activeTab = await getActiveTab(); + + await saveCroppedScreenshot(activeTab.url); + } else { + console.log("Unrecognized command", command); + } + }); + + function cropImage(newArea, dataUrl) { + return new Promise((resolve, reject) => { + const img = new Image(); + + img.onload = function () { + const canvas = document.createElement('canvas'); + canvas.width = newArea.width; + canvas.height = newArea.height; + + const ctx = canvas.getContext('2d'); + + ctx.drawImage(img, newArea.x, newArea.y, newArea.width, newArea.height, 0, 0, newArea.width, newArea.height); + + resolve(canvas.toDataURL()); + }; + + img.src = dataUrl; + }); + } + + async function takeCroppedScreenshot(cropRect) { + const activeTab = await getActiveTab(); + const zoom = await browser.tabs.getZoom(activeTab.id) * window.devicePixelRatio; + + const newArea = Object.assign({}, cropRect); + newArea.x *= zoom; + newArea.y *= zoom; + newArea.width *= zoom; + newArea.height *= zoom; + + const dataUrl = await browser.tabs.captureVisibleTab(null, { format: 'png' }); + + return await cropImage(newArea, dataUrl); + } + + async function takeWholeScreenshot() { + // this saves only visible portion of the page + // workaround to save the whole page is to scroll & stitch + // example in https://github.com/mrcoles/full-page-screen-capture-chrome-extension + // see page.js and popup.js + return await browser.tabs.captureVisibleTab(null, { format: 'png' }); + } + + browser.runtime.onInstalled.addListener(() => { + if (isDevEnv()) { + browser.browserAction.setIcon({ + path: 'icons/32-dev.png', + }); + } + }); + + browser.contextMenus.create({ + id: "trilium-save-selection", + title: "Save selection to Trilium", + contexts: ["selection"] + }); + + browser.contextMenus.create({ + id: "trilium-save-cropped-screenshot", + title: "Clip screenshot to Trilium", + contexts: ["page"] + }); + + browser.contextMenus.create({ + id: "trilium-save-cropped-screenshot", + title: "Crop screen shot to Trilium", + contexts: ["page"] + }); + + browser.contextMenus.create({ + id: "trilium-save-whole-screenshot", + title: "Save whole screen shot to Trilium", + contexts: ["page"] + }); + + browser.contextMenus.create({ + id: "trilium-save-page", + title: "Save whole page to Trilium", + contexts: ["page"] + }); + + browser.contextMenus.create({ + id: "trilium-save-link", + title: "Save link to Trilium", + contexts: ["link"] + }); + + browser.contextMenus.create({ + id: "trilium-save-image", + title: "Save image to Trilium", + contexts: ["image"] + }); + + async function getActiveTab() { + const tabs = await browser.tabs.query({ + active: true, + currentWindow: true + }); + + return tabs[0]; + } + + async function getWindowTabs() { + const tabs = await browser.tabs.query({ + currentWindow: true + }); + + return tabs; + } + + async function sendMessageToActiveTab(message) { const activeTab = await getActiveTab(); - await saveCroppedScreenshot(activeTab.url); - } else { - console.log("Unrecognized command", command); + if (!activeTab) { + throw new Error("No active tab."); + } + + try { + return await browser.tabs.sendMessage(activeTab.id, message); + } + catch (e) { + throw e; + } } -}); - -function cropImage(newArea, dataUrl) { - return new Promise((resolve, reject) => { - const img = new Image(); - - img.onload = function () { - const canvas = document.createElement('canvas'); - canvas.width = newArea.width; - canvas.height = newArea.height; - - const ctx = canvas.getContext('2d'); - - ctx.drawImage(img, newArea.x, newArea.y, newArea.width, newArea.height, 0, 0, newArea.width, newArea.height); - - resolve(canvas.toDataURL()); - }; - - img.src = dataUrl; - }); -} - -async function takeCroppedScreenshot(cropRect) { - const activeTab = await getActiveTab(); - const zoom = await browser.tabs.getZoom(activeTab.id) * window.devicePixelRatio; - - const newArea = Object.assign({}, cropRect); - newArea.x *= zoom; - newArea.y *= zoom; - newArea.width *= zoom; - newArea.height *= zoom; - - const dataUrl = await browser.tabs.captureVisibleTab(null, { format: 'png' }); - - return await cropImage(newArea, dataUrl); -} - -async function takeWholeScreenshot() { - // this saves only visible portion of the page - // workaround to save the whole page is to scroll & stitch - // example in https://github.com/mrcoles/full-page-screen-capture-chrome-extension - // see page.js and popup.js - return await browser.tabs.captureVisibleTab(null, { format: 'png' }); -} - -browser.runtime.onInstalled.addListener(() => { - if (isDevEnv()) { - browser.browserAction.setIcon({ - path: 'icons/32-dev.png', - }); - } -}); - -browser.contextMenus.create({ - id: "trilium-save-selection", - title: "Save selection to Trilium", - contexts: ["selection"] -}); - -browser.contextMenus.create({ - id: "trilium-save-cropped-screenshot", - title: "Clip screenshot to Trilium", - contexts: ["page"] -}); - -browser.contextMenus.create({ - id: "trilium-save-cropped-screenshot", - title: "Crop screen shot to Trilium", - contexts: ["page"] -}); - -browser.contextMenus.create({ - id: "trilium-save-whole-screenshot", - title: "Save whole screen shot to Trilium", - contexts: ["page"] -}); - -browser.contextMenus.create({ - id: "trilium-save-page", - title: "Save whole page to Trilium", - contexts: ["page"] -}); - -browser.contextMenus.create({ - id: "trilium-save-link", - title: "Save link to Trilium", - contexts: ["link"] -}); - -browser.contextMenus.create({ - id: "trilium-save-image", - title: "Save image to Trilium", - contexts: ["image"] -}); - -async function getActiveTab() { - const tabs = await browser.tabs.query({ - active: true, - currentWindow: true - }); - - return tabs[0]; -} - -async function getWindowTabs() { - const tabs = await browser.tabs.query({ - currentWindow: true - }); - - return tabs; -} - -async function sendMessageToActiveTab(message) { - const activeTab = await getActiveTab(); - - if (!activeTab) { - throw new Error("No active tab."); - } - - try { - return await browser.tabs.sendMessage(activeTab.id, message); - } - catch (e) { - throw e; - } -} - -function toast(message, noteId = null, tabIds = null) { - sendMessageToActiveTab({ - name: 'toast', - message: message, - noteId: noteId, - tabIds: tabIds - }); -} - -function blob2base64(blob) { - return new Promise(resolve => { - const reader = new FileReader(); - reader.onloadend = function() { - resolve(reader.result); - }; - reader.readAsDataURL(blob); - }); -} - -async function fetchImage(url) { - const resp = await fetch(url); - const blob = await resp.blob(); - - return await blob2base64(blob); -} - -async function postProcessImage(image) { - if (image.src.startsWith("data:image/")) { - image.dataUrl = image.src; - image.src = "inline." + image.src.substr(11, 3); // this should extract file type - png/jpg - } - else { - try { - image.dataUrl = await fetchImage(image.src, image); - } - catch (e) { - console.log(`Cannot fetch image from ${image.src}`); - } - } -} - -async function postProcessImages(resp) { - if (resp.images) { - for (const image of resp.images) { - await postProcessImage(image); - } - } -} - -async function saveSelection() { - const payload = await sendMessageToActiveTab({name: 'trilium-save-selection'}); - - await postProcessImages(payload); - - const resp = await triliumServerFacade.callService('POST', 'clippings', payload); - - if (!resp) { - return; - } - - toast("Selection has been saved to Trilium.", resp.noteId); -} - -async function getImagePayloadFromSrc(src, pageUrl) { - const image = { - imageId: randomString(20), - src: src - }; - - await postProcessImage(image); - - const activeTab = await getActiveTab(); - - return { - title: activeTab.title, - content: ``, - images: [image], - pageUrl: pageUrl - }; -} - -async function saveCroppedScreenshot(pageUrl) { - const cropRect = await sendMessageToActiveTab({name: 'trilium-get-rectangle-for-screenshot'}); - - const src = await takeCroppedScreenshot(cropRect); - - const payload = await getImagePayloadFromSrc(src, pageUrl); - - const resp = await triliumServerFacade.callService("POST", "clippings", payload); - - if (!resp) { - return; - } - - toast("Screenshot has been saved to Trilium.", resp.noteId); -} - -async function saveWholeScreenshot(pageUrl) { - const src = await takeWholeScreenshot(); - - const payload = await getImagePayloadFromSrc(src, pageUrl); - - const resp = await triliumServerFacade.callService("POST", "clippings", payload); - - if (!resp) { - return; - } - - toast("Screenshot has been saved to Trilium.", resp.noteId); -} - -async function saveImage(srcUrl, pageUrl) { - const payload = await getImagePayloadFromSrc(srcUrl, pageUrl); - - const resp = await triliumServerFacade.callService("POST", "clippings", payload); - - if (!resp) { - return; - } - - toast("Image has been saved to Trilium.", resp.noteId); -} - -async function saveWholePage() { - const payload = await sendMessageToActiveTab({name: 'trilium-save-page'}); - - await postProcessImages(payload); - - const resp = await triliumServerFacade.callService('POST', 'notes', payload); - - if (!resp) { - return; - } - - toast("Page has been saved to Trilium.", resp.noteId); -} - -async function saveLinkWithNote(title, content) { - const activeTab = await getActiveTab(); - - if (!title.trim()) { - title = activeTab.title; - } - - const resp = await triliumServerFacade.callService('POST', 'notes', { - title: title, - content: content, - clipType: 'note', - pageUrl: activeTab.url - }); - - if (!resp) { - return false; - } - - toast("Link with note has been saved to Trilium.", resp.noteId); - - return true; -} - -async function getTabsPayload(tabs) { - let content = '
    '; - tabs.forEach(tab => { - content += `
  • ${tab.title}
  • ` - }); - content += '
'; - - const domainsCount = tabs.map(tab => tab.url) - .reduce((acc, url) => { - const hostname = new URL(url).hostname - return acc.set(hostname, (acc.get(hostname) || 0) + 1) - }, new Map()); - - let topDomains = [...domainsCount] - .sort((a, b) => {return b[1]-a[1]}) - .slice(0,3) - .map(domain=>domain[0]) - .join(', ') - - if (tabs.length > 3) { topDomains += '...' } - - return { - title: `${tabs.length} browser tabs: ${topDomains}`, - content: content, - clipType: 'tabs' - }; -} - -async function saveTabs() { - const tabs = await getWindowTabs(); - - const payload = await getTabsPayload(tabs); - - const resp = await triliumServerFacade.callService('POST', 'notes', payload); - - if (!resp) { - return; - } - - const tabIds = tabs.map(tab=>{return tab.id}); - - toast(`${tabs.length} links have been saved to Trilium.`, resp.noteId, tabIds); -} - -browser.contextMenus.onClicked.addListener(async function(info, tab) { - if (info.menuItemId === 'trilium-save-selection') { - await saveSelection(); - } - else if (info.menuItemId === 'trilium-save-cropped-screenshot') { - await saveCroppedScreenshot(info.pageUrl); - } - else if (info.menuItemId === 'trilium-save-whole-screenshot') { - await saveWholeScreenshot(info.pageUrl); - } - else if (info.menuItemId === 'trilium-save-image') { - await saveImage(info.srcUrl, info.pageUrl); - } - else if (info.menuItemId === 'trilium-save-link') { - const link = document.createElement("a"); - link.href = info.linkUrl; - // linkText might be available only in firefox - link.appendChild(document.createTextNode(info.linkText || info.linkUrl)); - - const activeTab = await getActiveTab(); - - const resp = await triliumServerFacade.callService('POST', 'clippings', { - title: activeTab.title, - content: link.outerHTML, - pageUrl: info.pageUrl - }); - - if (!resp) { - return; - } - - toast("Link has been saved to Trilium.", resp.noteId); - } - else if (info.menuItemId === 'trilium-save-page') { - await saveWholePage(); - } - else { - console.log("Unrecognized menuItemId", info.menuItemId); - } -}); - -browser.runtime.onMessage.addListener(async request => { - console.log("Received", request); - - if (request.name === 'openNoteInTrilium') { - const resp = await triliumServerFacade.callService('POST', 'open/' + request.noteId); - - if (!resp) { - return; - } - - // desktop app is not available so we need to open in browser - if (resp.result === 'open-in-browser') { - const {triliumServerUrl} = await browser.storage.sync.get("triliumServerUrl"); - - if (triliumServerUrl) { - const noteUrl = triliumServerUrl + '/#' + request.noteId; - - console.log("Opening new tab in browser", noteUrl); - - browser.tabs.create({ - url: noteUrl - }); - } - else { - console.error("triliumServerUrl not found in local storage."); - } - } - } - else if (request.name === 'closeTabs') { - return await browser.tabs.remove(request.tabIds) - } - else if (request.name === 'load-script') { - return await browser.tabs.executeScript({file: request.file}); - } - else if (request.name === 'save-cropped-screenshot') { - const activeTab = await getActiveTab(); - - return await saveCroppedScreenshot(activeTab.url); - } - else if (request.name === 'save-whole-screenshot') { - const activeTab = await getActiveTab(); - - return await saveWholeScreenshot(activeTab.url); - } - else if (request.name === 'save-whole-page') { - return await saveWholePage(); - } - else if (request.name === 'save-link-with-note') { - return await saveLinkWithNote(request.title, request.content); - } - else if (request.name === 'save-tabs') { - return await saveTabs(); - } - else if (request.name === 'trigger-trilium-search') { - triliumServerFacade.triggerSearchForTrilium(); - } - else if (request.name === 'send-trilium-search-status') { - triliumServerFacade.sendTriliumSearchStatusToPopup(); - } - else if (request.name === 'trigger-trilium-search-note-url') { - const activeTab = await getActiveTab(); - triliumServerFacade.triggerSearchNoteByUrl(activeTab.url); - } + + function toast(message, noteId = null, tabIds = null) { + sendMessageToActiveTab({ + name: 'toast', + message: message, + noteId: noteId, + tabIds: tabIds + }); + } + + function blob2base64(blob) { + return new Promise(resolve => { + const reader = new FileReader(); + reader.onloadend = function() { + resolve(reader.result); + }; + reader.readAsDataURL(blob); + }); + } + + async function fetchImage(url) { + const resp = await fetch(url); + const blob = await resp.blob(); + + return await blob2base64(blob); + } + + async function postProcessImage(image) { + if (image.src.startsWith("data:image/")) { + image.dataUrl = image.src; + image.src = "inline." + image.src.substr(11, 3); // this should extract file type - png/jpg + } + else { + try { + image.dataUrl = await fetchImage(image.src, image); + } + catch (e) { + console.log(`Cannot fetch image from ${image.src}`); + } + } + } + + async function postProcessImages(resp) { + if (resp.images) { + for (const image of resp.images) { + await postProcessImage(image); + } + } + } + + async function saveSelection() { + const payload = await sendMessageToActiveTab({name: 'trilium-save-selection'}); + + await postProcessImages(payload); + + const resp = await triliumServerFacade.callService('POST', 'clippings', payload); + + if (!resp) { + return; + } + + toast("Selection has been saved to Trilium.", resp.noteId); + } + + async function getImagePayloadFromSrc(src, pageUrl) { + const image = { + imageId: randomString(20), + src: src + }; + + await postProcessImage(image); + + const activeTab = await getActiveTab(); + + return { + title: activeTab.title, + content: ``, + images: [image], + pageUrl: pageUrl + }; + } + + async function saveCroppedScreenshot(pageUrl) { + const cropRect = await sendMessageToActiveTab({name: 'trilium-get-rectangle-for-screenshot'}); + + const src = await takeCroppedScreenshot(cropRect); + + const payload = await getImagePayloadFromSrc(src, pageUrl); + + const resp = await triliumServerFacade.callService("POST", "clippings", payload); + + if (!resp) { + return; + } + + toast("Screenshot has been saved to Trilium.", resp.noteId); + } + + async function saveWholeScreenshot(pageUrl) { + const src = await takeWholeScreenshot(); + + const payload = await getImagePayloadFromSrc(src, pageUrl); + + const resp = await triliumServerFacade.callService("POST", "clippings", payload); + + if (!resp) { + return; + } + + toast("Screenshot has been saved to Trilium.", resp.noteId); + } + + async function saveImage(srcUrl, pageUrl) { + const payload = await getImagePayloadFromSrc(srcUrl, pageUrl); + + const resp = await triliumServerFacade.callService("POST", "clippings", payload); + + if (!resp) { + return; + } + + toast("Image has been saved to Trilium.", resp.noteId); + } + + async function saveWholePage() { + const payload = await sendMessageToActiveTab({name: 'trilium-save-page'}); + + await postProcessImages(payload); + + const resp = await triliumServerFacade.callService('POST', 'notes', payload); + + if (!resp) { + return; + } + + toast("Page has been saved to Trilium.", resp.noteId); + } + + async function saveLinkWithNote(title, content) { + const activeTab = await getActiveTab(); + + if (!title.trim()) { + title = activeTab.title; + } + + const resp = await triliumServerFacade.callService('POST', 'notes', { + title: title, + content: content, + clipType: 'note', + pageUrl: activeTab.url + }); + + if (!resp) { + return false; + } + + toast("Link with note has been saved to Trilium.", resp.noteId); + + return true; + } + + async function getTabsPayload(tabs) { + let content = '
    '; + tabs.forEach(tab => { + content += `
  • ${tab.title}
  • ` + }); + content += '
'; + + const domainsCount = tabs.map(tab => tab.url) + .reduce((acc, url) => { + const hostname = new URL(url).hostname + return acc.set(hostname, (acc.get(hostname) || 0) + 1) + }, new Map()); + + let topDomains = [...domainsCount] + .sort((a, b) => {return b[1]-a[1]}) + .slice(0,3) + .map(domain=>domain[0]) + .join(', ') + + if (tabs.length > 3) { topDomains += '...' } + + return { + title: `${tabs.length} browser tabs: ${topDomains}`, + content: content, + clipType: 'tabs' + }; + } + + async function saveTabs() { + const tabs = await getWindowTabs(); + + const payload = await getTabsPayload(tabs); + + const resp = await triliumServerFacade.callService('POST', 'notes', payload); + + if (!resp) { + return; + } + + const tabIds = tabs.map(tab=>{return tab.id}); + + toast(`${tabs.length} links have been saved to Trilium.`, resp.noteId, tabIds); + } + + browser.contextMenus.onClicked.addListener(async function(info, tab) { + if (info.menuItemId === 'trilium-save-selection') { + await saveSelection(); + } + else if (info.menuItemId === 'trilium-save-cropped-screenshot') { + await saveCroppedScreenshot(info.pageUrl); + } + else if (info.menuItemId === 'trilium-save-whole-screenshot') { + await saveWholeScreenshot(info.pageUrl); + } + else if (info.menuItemId === 'trilium-save-image') { + await saveImage(info.srcUrl, info.pageUrl); + } + else if (info.menuItemId === 'trilium-save-link') { + const link = document.createElement("a"); + link.href = info.linkUrl; + // linkText might be available only in firefox + link.appendChild(document.createTextNode(info.linkText || info.linkUrl)); + + const activeTab = await getActiveTab(); + + const resp = await triliumServerFacade.callService('POST', 'clippings', { + title: activeTab.title, + content: link.outerHTML, + pageUrl: info.pageUrl + }); + + if (!resp) { + return; + } + + toast("Link has been saved to Trilium.", resp.noteId); + } + else if (info.menuItemId === 'trilium-save-page') { + await saveWholePage(); + } + else { + console.log("Unrecognized menuItemId", info.menuItemId); + } + }); + + browser.runtime.onMessage.addListener(async request => { + console.log("Received", request); + + if (request.name === 'openNoteInTrilium') { + const resp = await triliumServerFacade.callService('POST', 'open/' + request.noteId); + + if (!resp) { + return; + } + + // desktop app is not available so we need to open in browser + if (resp.result === 'open-in-browser') { + const {triliumServerUrl} = await browser.storage.sync.get("triliumServerUrl"); + + if (triliumServerUrl) { + const noteUrl = triliumServerUrl + '/#' + request.noteId; + + console.log("Opening new tab in browser", noteUrl); + + browser.tabs.create({ + url: noteUrl + }); + } + else { + console.error("triliumServerUrl not found in local storage."); + } + } + } + else if (request.name === 'closeTabs') { + return await browser.tabs.remove(request.tabIds) + } + else if (request.name === 'load-script') { + return await browser.tabs.executeScript({file: request.file}); + } + else if (request.name === 'save-cropped-screenshot') { + const activeTab = await getActiveTab(); + + return await saveCroppedScreenshot(activeTab.url); + } + else if (request.name === 'save-whole-screenshot') { + const activeTab = await getActiveTab(); + + return await saveWholeScreenshot(activeTab.url); + } + else if (request.name === 'save-whole-page') { + return await saveWholePage(); + } + else if (request.name === 'save-link-with-note') { + return await saveLinkWithNote(request.title, request.content); + } + else if (request.name === 'save-tabs') { + return await saveTabs(); + } + else if (request.name === 'trigger-trilium-search') { + triliumServerFacade.triggerSearchForTrilium(); + } + else if (request.name === 'send-trilium-search-status') { + triliumServerFacade.sendTriliumSearchStatusToPopup(); + } + else if (request.name === 'trigger-trilium-search-note-url') { + const activeTab = await getActiveTab(); + triliumServerFacade.triggerSearchNoteByUrl(activeTab.url); + } + }); }); diff --git a/apps/web-clipper/entrypoints/content/index.js b/apps/web-clipper/entrypoints/content/index.js index faacfa546..c6ba6c438 100644 --- a/apps/web-clipper/entrypoints/content/index.js +++ b/apps/web-clipper/entrypoints/content/index.js @@ -1,351 +1,358 @@ -function absoluteUrl(url) { - if (!url) { - return url; - } - - const protocol = url.toLowerCase().split(':')[0]; - if (['http', 'https', 'file'].indexOf(protocol) >= 0) { - return url; - } - - if (url.indexOf('//') === 0) { - return location.protocol + url; - } else if (url[0] === '/') { - return location.protocol + '//' + location.host + url; - } else { - return getBaseUrl() + '/' + url; - } -} - -function pageTitle() { - const titleElements = document.getElementsByTagName("title"); - - return titleElements.length ? titleElements[0].text.trim() : document.title.trim(); -} - -function getReadableDocument() { - // Readability directly change the passed document, so clone to preserve the original web page. - const documentCopy = document.cloneNode(true); - const readability = new Readability(documentCopy, { - serializer: el => el // so that .content is returned as DOM element instead of HTML - }); - - const article = readability.parse(); - - if (!article) { - throw new Error('Could not parse HTML document with Readability'); - } - - return { - title: article.title, - body: article.content, - } -} - -function getDocumentDates() { - var dates = { - publishedDate: null, - modifiedDate: null, - }; - - const articlePublishedTime = document.querySelector("meta[property='article:published_time']"); - if (articlePublishedTime && articlePublishedTime.getAttribute('content')) { - dates.publishedDate = new Date(articlePublishedTime.getAttribute('content')); - } - - const articleModifiedTime = document.querySelector("meta[property='article:modified_time']"); - if (articleModifiedTime && articleModifiedTime.getAttribute('content')) { - dates.modifiedDate = new Date(articleModifiedTime.getAttribute('content')); - } - - // TODO: if we didn't get dates from meta, then try to get them from JSON-LD - - return dates; -} - -function getRectangleArea() { - return new Promise((resolve, reject) => { - const overlay = document.createElement('div'); - overlay.style.opacity = '0.6'; - overlay.style.background = 'black'; - overlay.style.width = '100%'; - overlay.style.height = '100%'; - overlay.style.zIndex = 99999999; - overlay.style.top = 0; - overlay.style.left = 0; - overlay.style.position = 'fixed'; - - document.body.appendChild(overlay); - - const messageComp = document.createElement('div'); - - const messageCompWidth = 300; - messageComp.setAttribute("tabindex", "0"); // so that it can be focused - messageComp.style.position = 'fixed'; - messageComp.style.opacity = '0.95'; - messageComp.style.fontSize = '14px'; - messageComp.style.width = messageCompWidth + 'px'; - messageComp.style.maxWidth = messageCompWidth + 'px'; - messageComp.style.border = '1px solid black'; - messageComp.style.background = 'white'; - messageComp.style.color = 'black'; - messageComp.style.top = '10px'; - messageComp.style.textAlign = 'center'; - messageComp.style.padding = '10px'; - messageComp.style.left = Math.round(document.body.clientWidth / 2 - messageCompWidth / 2) + 'px'; - messageComp.style.zIndex = overlay.style.zIndex + 1; - - messageComp.textContent = 'Drag and release to capture a screenshot'; - - document.body.appendChild(messageComp); - - const selection = document.createElement('div'); - selection.style.opacity = '0.5'; - selection.style.border = '1px solid red'; - selection.style.background = 'white'; - selection.style.border = '2px solid black'; - selection.style.zIndex = overlay.style.zIndex - 1; - selection.style.top = 0; - selection.style.left = 0; - selection.style.position = 'fixed'; - - document.body.appendChild(selection); - - messageComp.focus(); // we listen on keypresses on this element to cancel on escape - - let isDragging = false; - let draggingStartPos = null; - let selectionArea = {}; - - function updateSelection() { - selection.style.left = selectionArea.x + 'px'; - selection.style.top = selectionArea.y + 'px'; - selection.style.width = selectionArea.width + 'px'; - selection.style.height = selectionArea.height + 'px'; - } - - function setSelectionSizeFromMouse(event) { - if (event.clientX < draggingStartPos.x) { - selectionArea.x = event.clientX; - } - - if (event.clientY < draggingStartPos.y) { - selectionArea.y = event.clientY; - } - - selectionArea.width = Math.max(1, Math.abs(event.clientX - draggingStartPos.x)); - selectionArea.height = Math.max(1, Math.abs(event.clientY - draggingStartPos.y)); - updateSelection(); - } - - function selection_mouseDown(event) { - selectionArea = {x: event.clientX, y: event.clientY, width: 0, height: 0}; - draggingStartPos = {x: event.clientX, y: event.clientY}; - isDragging = true; - updateSelection(); - } - - function selection_mouseMove(event) { - if (!isDragging) return; - setSelectionSizeFromMouse(event); - } - - function removeOverlay() { - isDragging = false; - - overlay.removeEventListener('mousedown', selection_mouseDown); - overlay.removeEventListener('mousemove', selection_mouseMove); - overlay.removeEventListener('mouseup', selection_mouseUp); - - document.body.removeChild(overlay); - document.body.removeChild(selection); - document.body.removeChild(messageComp); - } - - function selection_mouseUp(event) { - setSelectionSizeFromMouse(event); - - removeOverlay(); - - console.info('selectionArea:', selectionArea); - - if (!selectionArea || !selectionArea.width || !selectionArea.height) { - return; - } - - // Need to wait a bit before taking the screenshot to make sure - // the overlays have been removed and don't appear in the - // screenshot. 10ms is not enough. - setTimeout(() => resolve(selectionArea), 100); - } - - function cancel(event) { - if (event.key === "Escape") { - removeOverlay(); - } - } - - overlay.addEventListener('mousedown', selection_mouseDown); - overlay.addEventListener('mousemove', selection_mouseMove); - overlay.addEventListener('mouseup', selection_mouseUp); - overlay.addEventListener('mouseup', selection_mouseUp); - messageComp.addEventListener('keydown', cancel); - }); -} - -function makeLinksAbsolute(container) { - for (const link of container.getElementsByTagName('a')) { - if (link.href) { - link.href = absoluteUrl(link.href); - } - } -} - -function getImages(container) { - const images = []; - - for (const img of container.getElementsByTagName('img')) { - if (!img.src) { - continue; - } - - const existingImage = images.find(image => image.src === img.src); - - if (existingImage) { - img.src = existingImage.imageId; - } - else { - const imageId = randomString(20); - - images.push({ - imageId: imageId, - src: img.src - }); - - img.src = imageId; - } - } - - return images; -} - -function createLink(clickAction, text, color = "lightskyblue") { - const link = document.createElement('a'); - link.href = "javascript:"; - link.style.color = color; - link.appendChild(document.createTextNode(text)); - link.addEventListener("click", () => { - browser.runtime.sendMessage(null, clickAction) - }); - - return link -} - -async function prepareMessageResponse(message) { - console.info('Message: ' + message.name); - - if (message.name === "toast") { - let messageText; - - if (message.noteId) { - messageText = document.createElement('p'); - messageText.setAttribute("style", "padding: 0; margin: 0; font-size: larger;") - messageText.appendChild(document.createTextNode(message.message + " ")); - messageText.appendChild(createLink( - {name: 'openNoteInTrilium', noteId: message.noteId}, - "Open in Trilium." - )); - - // only after saving tabs - if (message.tabIds) { - messageText.appendChild(document.createElement("br")); - messageText.appendChild(createLink( - {name: 'closeTabs', tabIds: message.tabIds}, - "Close saved tabs.", - "tomato" - )); - } - } - else { - messageText = message.message; - } - - await requireLib('/lib/toast.js'); - - showToast(messageText, { - settings: { - duration: 7000 - } - }); - } - else if (message.name === "trilium-save-selection") { - const container = document.createElement('div'); - - const selection = window.getSelection(); - - for (let i = 0; i < selection.rangeCount; i++) { - const range = selection.getRangeAt(i); - - container.appendChild(range.cloneContents()); - } - - makeLinksAbsolute(container); - - const images = getImages(container); - - return { - title: pageTitle(), - content: container.innerHTML, - images: images, - pageUrl: getPageLocationOrigin() + location.pathname + location.search + location.hash - }; - - } - else if (message.name === 'trilium-get-rectangle-for-screenshot') { - return getRectangleArea(); - } - else if (message.name === "trilium-save-page") { - await requireLib("/lib/JSDOMParser.js"); - await requireLib("/lib/Readability.js"); - await requireLib("/lib/Readability-readerable.js"); - - const {title, body} = getReadableDocument(); - - makeLinksAbsolute(body); - - const images = getImages(body); - - var labels = {}; - const dates = getDocumentDates(); - if (dates.publishedDate) { - labels['publishedDate'] = dates.publishedDate.toISOString().substring(0, 10); - } - if (dates.modifiedDate) { - labels['modifiedDate'] = dates.publishedDate.toISOString().substring(0, 10); - } - - return { - title: title, - content: body.innerHTML, - images: images, - pageUrl: getPageLocationOrigin() + location.pathname + location.search, - clipType: 'page', - labels: labels - }; - } - else { - throw new Error('Unknown command: ' + JSON.stringify(message)); - } -} - -browser.runtime.onMessage.addListener(prepareMessageResponse); - -const loadedLibs = []; - -async function requireLib(libPath) { - if (!loadedLibs.includes(libPath)) { - loadedLibs.push(libPath); - - await browser.runtime.sendMessage({name: 'load-script', file: libPath}); - } -} +export default defineContentScript({ + matches: [ + "" + ], + main: () => { + function absoluteUrl(url) { + if (!url) { + return url; + } + + const protocol = url.toLowerCase().split(':')[0]; + if (['http', 'https', 'file'].indexOf(protocol) >= 0) { + return url; + } + + if (url.indexOf('//') === 0) { + return location.protocol + url; + } else if (url[0] === '/') { + return location.protocol + '//' + location.host + url; + } else { + return getBaseUrl() + '/' + url; + } + } + + function pageTitle() { + const titleElements = document.getElementsByTagName("title"); + + return titleElements.length ? titleElements[0].text.trim() : document.title.trim(); + } + + function getReadableDocument() { + // Readability directly change the passed document, so clone to preserve the original web page. + const documentCopy = document.cloneNode(true); + const readability = new Readability(documentCopy, { + serializer: el => el // so that .content is returned as DOM element instead of HTML + }); + + const article = readability.parse(); + + if (!article) { + throw new Error('Could not parse HTML document with Readability'); + } + + return { + title: article.title, + body: article.content, + } + } + + function getDocumentDates() { + var dates = { + publishedDate: null, + modifiedDate: null, + }; + + const articlePublishedTime = document.querySelector("meta[property='article:published_time']"); + if (articlePublishedTime && articlePublishedTime.getAttribute('content')) { + dates.publishedDate = new Date(articlePublishedTime.getAttribute('content')); + } + + const articleModifiedTime = document.querySelector("meta[property='article:modified_time']"); + if (articleModifiedTime && articleModifiedTime.getAttribute('content')) { + dates.modifiedDate = new Date(articleModifiedTime.getAttribute('content')); + } + + // TODO: if we didn't get dates from meta, then try to get them from JSON-LD + + return dates; + } + + function getRectangleArea() { + return new Promise((resolve, reject) => { + const overlay = document.createElement('div'); + overlay.style.opacity = '0.6'; + overlay.style.background = 'black'; + overlay.style.width = '100%'; + overlay.style.height = '100%'; + overlay.style.zIndex = 99999999; + overlay.style.top = 0; + overlay.style.left = 0; + overlay.style.position = 'fixed'; + + document.body.appendChild(overlay); + + const messageComp = document.createElement('div'); + + const messageCompWidth = 300; + messageComp.setAttribute("tabindex", "0"); // so that it can be focused + messageComp.style.position = 'fixed'; + messageComp.style.opacity = '0.95'; + messageComp.style.fontSize = '14px'; + messageComp.style.width = messageCompWidth + 'px'; + messageComp.style.maxWidth = messageCompWidth + 'px'; + messageComp.style.border = '1px solid black'; + messageComp.style.background = 'white'; + messageComp.style.color = 'black'; + messageComp.style.top = '10px'; + messageComp.style.textAlign = 'center'; + messageComp.style.padding = '10px'; + messageComp.style.left = Math.round(document.body.clientWidth / 2 - messageCompWidth / 2) + 'px'; + messageComp.style.zIndex = overlay.style.zIndex + 1; + + messageComp.textContent = 'Drag and release to capture a screenshot'; + + document.body.appendChild(messageComp); + + const selection = document.createElement('div'); + selection.style.opacity = '0.5'; + selection.style.border = '1px solid red'; + selection.style.background = 'white'; + selection.style.border = '2px solid black'; + selection.style.zIndex = overlay.style.zIndex - 1; + selection.style.top = 0; + selection.style.left = 0; + selection.style.position = 'fixed'; + + document.body.appendChild(selection); + + messageComp.focus(); // we listen on keypresses on this element to cancel on escape + + let isDragging = false; + let draggingStartPos = null; + let selectionArea = {}; + + function updateSelection() { + selection.style.left = selectionArea.x + 'px'; + selection.style.top = selectionArea.y + 'px'; + selection.style.width = selectionArea.width + 'px'; + selection.style.height = selectionArea.height + 'px'; + } + + function setSelectionSizeFromMouse(event) { + if (event.clientX < draggingStartPos.x) { + selectionArea.x = event.clientX; + } + + if (event.clientY < draggingStartPos.y) { + selectionArea.y = event.clientY; + } + + selectionArea.width = Math.max(1, Math.abs(event.clientX - draggingStartPos.x)); + selectionArea.height = Math.max(1, Math.abs(event.clientY - draggingStartPos.y)); + updateSelection(); + } + + function selection_mouseDown(event) { + selectionArea = {x: event.clientX, y: event.clientY, width: 0, height: 0}; + draggingStartPos = {x: event.clientX, y: event.clientY}; + isDragging = true; + updateSelection(); + } + + function selection_mouseMove(event) { + if (!isDragging) return; + setSelectionSizeFromMouse(event); + } + + function removeOverlay() { + isDragging = false; + + overlay.removeEventListener('mousedown', selection_mouseDown); + overlay.removeEventListener('mousemove', selection_mouseMove); + overlay.removeEventListener('mouseup', selection_mouseUp); + + document.body.removeChild(overlay); + document.body.removeChild(selection); + document.body.removeChild(messageComp); + } + + function selection_mouseUp(event) { + setSelectionSizeFromMouse(event); + + removeOverlay(); + + console.info('selectionArea:', selectionArea); + + if (!selectionArea || !selectionArea.width || !selectionArea.height) { + return; + } + + // Need to wait a bit before taking the screenshot to make sure + // the overlays have been removed and don't appear in the + // screenshot. 10ms is not enough. + setTimeout(() => resolve(selectionArea), 100); + } + + function cancel(event) { + if (event.key === "Escape") { + removeOverlay(); + } + } + + overlay.addEventListener('mousedown', selection_mouseDown); + overlay.addEventListener('mousemove', selection_mouseMove); + overlay.addEventListener('mouseup', selection_mouseUp); + overlay.addEventListener('mouseup', selection_mouseUp); + messageComp.addEventListener('keydown', cancel); + }); + } + + function makeLinksAbsolute(container) { + for (const link of container.getElementsByTagName('a')) { + if (link.href) { + link.href = absoluteUrl(link.href); + } + } + } + + function getImages(container) { + const images = []; + + for (const img of container.getElementsByTagName('img')) { + if (!img.src) { + continue; + } + + const existingImage = images.find(image => image.src === img.src); + + if (existingImage) { + img.src = existingImage.imageId; + } + else { + const imageId = randomString(20); + + images.push({ + imageId: imageId, + src: img.src + }); + + img.src = imageId; + } + } + + return images; + } + + function createLink(clickAction, text, color = "lightskyblue") { + const link = document.createElement('a'); + link.href = "javascript:"; + link.style.color = color; + link.appendChild(document.createTextNode(text)); + link.addEventListener("click", () => { + browser.runtime.sendMessage(null, clickAction) + }); + + return link + } + + async function prepareMessageResponse(message) { + console.info('Message: ' + message.name); + + if (message.name === "toast") { + let messageText; + + if (message.noteId) { + messageText = document.createElement('p'); + messageText.setAttribute("style", "padding: 0; margin: 0; font-size: larger;") + messageText.appendChild(document.createTextNode(message.message + " ")); + messageText.appendChild(createLink( + {name: 'openNoteInTrilium', noteId: message.noteId}, + "Open in Trilium." + )); + + // only after saving tabs + if (message.tabIds) { + messageText.appendChild(document.createElement("br")); + messageText.appendChild(createLink( + {name: 'closeTabs', tabIds: message.tabIds}, + "Close saved tabs.", + "tomato" + )); + } + } + else { + messageText = message.message; + } + + await requireLib('/lib/toast.js'); + + showToast(messageText, { + settings: { + duration: 7000 + } + }); + } + else if (message.name === "trilium-save-selection") { + const container = document.createElement('div'); + + const selection = window.getSelection(); + + for (let i = 0; i < selection.rangeCount; i++) { + const range = selection.getRangeAt(i); + + container.appendChild(range.cloneContents()); + } + + makeLinksAbsolute(container); + + const images = getImages(container); + + return { + title: pageTitle(), + content: container.innerHTML, + images: images, + pageUrl: getPageLocationOrigin() + location.pathname + location.search + location.hash + }; + + } + else if (message.name === 'trilium-get-rectangle-for-screenshot') { + return getRectangleArea(); + } + else if (message.name === "trilium-save-page") { + await requireLib("/lib/JSDOMParser.js"); + await requireLib("/lib/Readability.js"); + await requireLib("/lib/Readability-readerable.js"); + + const {title, body} = getReadableDocument(); + + makeLinksAbsolute(body); + + const images = getImages(body); + + var labels = {}; + const dates = getDocumentDates(); + if (dates.publishedDate) { + labels['publishedDate'] = dates.publishedDate.toISOString().substring(0, 10); + } + if (dates.modifiedDate) { + labels['modifiedDate'] = dates.publishedDate.toISOString().substring(0, 10); + } + + return { + title: title, + content: body.innerHTML, + images: images, + pageUrl: getPageLocationOrigin() + location.pathname + location.search, + clipType: 'page', + labels: labels + }; + } + else { + throw new Error('Unknown command: ' + JSON.stringify(message)); + } + } + + const loadedLibs = []; + + async function requireLib(libPath) { + if (!loadedLibs.includes(libPath)) { + loadedLibs.push(libPath); + + await browser.runtime.sendMessage({name: 'load-script', file: libPath}); + } + } + + browser.runtime.onMessage.addListener(prepareMessageResponse); + } +}); diff --git a/apps/web-clipper/manifest.json b/apps/web-clipper/manifest.json index 8e0c1ba2b..70da3d1b1 100644 --- a/apps/web-clipper/manifest.json +++ b/apps/web-clipper/manifest.json @@ -26,9 +26,6 @@ }, "content_scripts": [ { - "matches": [ - "" - ], "js": [ "lib/browser-polyfill.js", "utils.js" From ab95f6dcc2e086ad08367f28ecd03fbbcb0dea1c Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 10:08:29 +0200 Subject: [PATCH 06/47] fix(web-clipper): script imports --- apps/web-clipper/entrypoints/options/index.html | 6 +++--- apps/web-clipper/entrypoints/popup/index.html | 10 +++++----- apps/web-clipper/entrypoints/popup/popup.js | 4 +++- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/apps/web-clipper/entrypoints/options/index.html b/apps/web-clipper/entrypoints/options/index.html index 2363567a5..16e084ded 100644 --- a/apps/web-clipper/entrypoints/options/index.html +++ b/apps/web-clipper/entrypoints/options/index.html @@ -54,9 +54,9 @@

Note that the entered password is not stored anywhere, it will be only used to retrieve an authorization token from the server instance which will be then used to send the clipped notes.

- - - + + + diff --git a/apps/web-clipper/entrypoints/popup/index.html b/apps/web-clipper/entrypoints/popup/index.html index be415744c..7bd985801 100644 --- a/apps/web-clipper/entrypoints/popup/index.html +++ b/apps/web-clipper/entrypoints/popup/index.html @@ -46,11 +46,11 @@
Status: unknown
- - - - - + + + + + diff --git a/apps/web-clipper/entrypoints/popup/popup.js b/apps/web-clipper/entrypoints/popup/popup.js index adac36126..eca8d790e 100644 --- a/apps/web-clipper/entrypoints/popup/popup.js +++ b/apps/web-clipper/entrypoints/popup/popup.js @@ -1,3 +1,5 @@ +console.log("Popup script loaded"); + async function sendMessage(message) { try { return await browser.runtime.sendMessage(message); @@ -164,7 +166,7 @@ browser.runtime.onMessage.addListener(request => { }else{ $alreadyVisited.html(''); } - + } }); From 22308a101e9b6511cc02a7957d6dca7e955119ed Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 10:18:59 +0200 Subject: [PATCH 07/47] fix(web-clipper): missing permissions --- apps/web-clipper/manifest.json | 9 --------- apps/web-clipper/wxt.config.js | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 9 deletions(-) create mode 100644 apps/web-clipper/wxt.config.js diff --git a/apps/web-clipper/manifest.json b/apps/web-clipper/manifest.json index 70da3d1b1..fc7e5b921 100644 --- a/apps/web-clipper/manifest.json +++ b/apps/web-clipper/manifest.json @@ -10,15 +10,6 @@ "48": "icons/48.png", "96": "icons/96.png" }, - "permissions": [ - "activeTab", - "tabs", - "http://*/", - "https://*/", - "", - "storage", - "contextMenus" - ], "browser_action": { "default_icon": "icons/32.png", "default_title": "Trilium Web Clipper", diff --git a/apps/web-clipper/wxt.config.js b/apps/web-clipper/wxt.config.js new file mode 100644 index 000000000..5dd59801a --- /dev/null +++ b/apps/web-clipper/wxt.config.js @@ -0,0 +1,15 @@ +import { defineConfig } from "vite"; + +export default defineConfig({ + manifest: { + permissions: [ + "activeTab", + "tabs", + "http://*/", + "https://*/", + "", + "storage", + "contextMenus" + ] + } +}); From 957590523c3e16a8dd77958d21d2a07eb47475e2 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 10:23:56 +0200 Subject: [PATCH 08/47] fix(web-clipper): integrate trilium server facade --- apps/web-clipper/entrypoints/background/index.js | 4 ++++ .../{ => entrypoints/background}/trilium_server_facade.js | 6 ++---- apps/web-clipper/entrypoints/popup/popup.js | 1 + apps/web-clipper/manifest.json | 3 +-- 4 files changed, 8 insertions(+), 6 deletions(-) rename apps/web-clipper/{ => entrypoints/background}/trilium_server_facade.js (98%) diff --git a/apps/web-clipper/entrypoints/background/index.js b/apps/web-clipper/entrypoints/background/index.js index ee92f709a..b92bfdb72 100644 --- a/apps/web-clipper/entrypoints/background/index.js +++ b/apps/web-clipper/entrypoints/background/index.js @@ -1,4 +1,8 @@ +import TriliumServerFacade, { isDevEnv } from "./trilium_server_facade"; + export default defineBackground(() => { + const triliumServerFacade = new TriliumServerFacade(); + // Keyboard shortcuts chrome.commands.onCommand.addListener(async function (command) { if (command == "saveSelection") { diff --git a/apps/web-clipper/trilium_server_facade.js b/apps/web-clipper/entrypoints/background/trilium_server_facade.js similarity index 98% rename from apps/web-clipper/trilium_server_facade.js rename to apps/web-clipper/entrypoints/background/trilium_server_facade.js index 6f46893e5..b53b54847 100644 --- a/apps/web-clipper/trilium_server_facade.js +++ b/apps/web-clipper/entrypoints/background/trilium_server_facade.js @@ -1,12 +1,12 @@ const PROTOCOL_VERSION_MAJOR = 1; -function isDevEnv() { +export function isDevEnv() { const manifest = browser.runtime.getManifest(); return manifest.name.endsWith('(dev)'); } -class TriliumServerFacade { +export default class TriliumServerFacade { constructor() { this.triggerSearchForTrilium(); @@ -221,5 +221,3 @@ class TriliumServerFacade { (absoff % 60).toString().padStart(2,'0')); } } - -window.triliumServerFacade = new TriliumServerFacade(); diff --git a/apps/web-clipper/entrypoints/popup/popup.js b/apps/web-clipper/entrypoints/popup/popup.js index eca8d790e..be571e428 100644 --- a/apps/web-clipper/entrypoints/popup/popup.js +++ b/apps/web-clipper/entrypoints/popup/popup.js @@ -2,6 +2,7 @@ console.log("Popup script loaded"); async function sendMessage(message) { try { + console.log("Sending message", message); return await browser.runtime.sendMessage(message); } catch (e) { diff --git a/apps/web-clipper/manifest.json b/apps/web-clipper/manifest.json index fc7e5b921..2a7b80fb4 100644 --- a/apps/web-clipper/manifest.json +++ b/apps/web-clipper/manifest.json @@ -26,8 +26,7 @@ "background": { "scripts": [ "lib/browser-polyfill.js", - "utils.js", - "trilium_server_facade.js" + "utils.js" ] }, "commands": { From a9218960e964dcb7baf7da68858ebe3129f43dc7 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 10:40:16 +0200 Subject: [PATCH 09/47] fix(web-clipper): the storage API will not work with a temporary addon ID --- apps/web-clipper/manifest.json | 7 +------ apps/web-clipper/wxt.config.js | 7 ++++++- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/web-clipper/manifest.json b/apps/web-clipper/manifest.json index 2a7b80fb4..59a693e37 100644 --- a/apps/web-clipper/manifest.json +++ b/apps/web-clipper/manifest.json @@ -48,10 +48,5 @@ "default": "Ctrl+Shift+E" } } - }, - "browser_specific_settings": { - "gecko": { - "id": "{1410742d-b377-40e7-a9db-63dc9c6ec99c}" - } - } + } } diff --git a/apps/web-clipper/wxt.config.js b/apps/web-clipper/wxt.config.js index 5dd59801a..f33731423 100644 --- a/apps/web-clipper/wxt.config.js +++ b/apps/web-clipper/wxt.config.js @@ -10,6 +10,11 @@ export default defineConfig({ "", "storage", "contextMenus" - ] + ], + browser_specific_settings: { + gecko: { + id: "{1410742d-b377-40e7-a9db-63dc9c6ec99c}" + } + } } }); From 276b3f834bd38a5d4df3391d0943290b6b51b5ad Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 10:47:41 +0200 Subject: [PATCH 10/47] fix(web-clipper): triliumServerFacade is not defined --- .../web-clipper/entrypoints/background/trilium_server_facade.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web-clipper/entrypoints/background/trilium_server_facade.js b/apps/web-clipper/entrypoints/background/trilium_server_facade.js index b53b54847..b1a56a7ec 100644 --- a/apps/web-clipper/entrypoints/background/trilium_server_facade.js +++ b/apps/web-clipper/entrypoints/background/trilium_server_facade.js @@ -132,7 +132,7 @@ export default class TriliumServerFacade { } async triggerSearchNoteByUrl(noteUrl) { - const resp = await triliumServerFacade.callService('GET', 'notes-by-url/' + encodeURIComponent(noteUrl)) + const resp = await this.callService('GET', 'notes-by-url/' + encodeURIComponent(noteUrl)) let newStatus = { status: 'not-found', noteId: null From 17f906fb65e3eaf729fd2b423e98790e65078f4d Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 10:58:20 +0200 Subject: [PATCH 11/47] chore(web-clipper): reintegrate icon --- .../{icons/96.png => assets/icon.png} | Bin apps/web-clipper/icons/32-dev.png | Bin 6518 -> 0 bytes apps/web-clipper/icons/32.png | Bin 1153 -> 0 bytes apps/web-clipper/icons/48.png | Bin 1654 -> 0 bytes apps/web-clipper/wxt.config.js | 1 + pnpm-lock.yaml | 299 +++++++++++++++++- 6 files changed, 298 insertions(+), 2 deletions(-) rename apps/web-clipper/{icons/96.png => assets/icon.png} (100%) delete mode 100644 apps/web-clipper/icons/32-dev.png delete mode 100644 apps/web-clipper/icons/32.png delete mode 100644 apps/web-clipper/icons/48.png diff --git a/apps/web-clipper/icons/96.png b/apps/web-clipper/assets/icon.png similarity index 100% rename from apps/web-clipper/icons/96.png rename to apps/web-clipper/assets/icon.png diff --git a/apps/web-clipper/icons/32-dev.png b/apps/web-clipper/icons/32-dev.png deleted file mode 100644 index d280a31bbd185a74c85c57fc1becaa380bb13219..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6518 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 diff --git a/apps/web-clipper/icons/32.png b/apps/web-clipper/icons/32.png deleted file mode 100644 index 9aeeb66fe96a83e3abce5748ac88862bccd738b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1153 zcmV-{1b+L8P)EX>4Tx04R}tkv&MmKpe$i(@Onlaj=7kLx$>PK~%(1t5Adrp;l;o12rOiLp`5<5%ypW>NMI35kRU=q6(y8mBSx!EiiH&I$2<6kT)#vvg=bb;{@U#SB#pQP7X zTJ#9$-v%zOTbi;5TgF}~)6010qNS#tmY z4c7nw4c7reD4Tcy000McNlirueSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00LP_L_t(o!|j((XcIvc#(xRb3KrBHl(oA^(FjdYC`C{s z1`pMX5<$em3f`np^i=e)2p)_FwI>gvUIYzZJh-0xb6V<3!O(-ah()22)xFe{wpbE~ zmx&D1-Hn87MCgNsnR##C_syF(^LAiO{;{OG?XXnI9|7(II~CP)V8S-NFNqK+Rr2G& z~AbmXd3C$jVf*4d5^^1MCIrw&`Vo<(PS|qrf9CBHz;900&~6?g08HbD3oz z1H6l{%h{&)p`%(La$^zL5TgF3mxDoZ6i0xn6wU9zaohA}yH<>ROB)7`0Y8BUdO2v+ ziiH=z3BWfTcWY0pVXatb0s-(6I0s-jZb!b-9f1E5&FL1n7@tX;K&@CXfc{o?E9yhN zh=3!t>mdX;Crw~TYVQfF&WY!MBoiTHo029lC$;wjD~kR{(gY4F{VPg8NR)u#xGUl> zrzO`ElZC`{z;N6W@K$u!i)z@C&pmq=)QW|(z(a*xs%NLbaNI`%@ZQ?{?!T7b6bqo- Tw838S00000NkvXXu0mjfOh^kB diff --git a/apps/web-clipper/icons/48.png b/apps/web-clipper/icons/48.png deleted file mode 100644 index da66c56f64aacdda06c4d181e9b2ee1cb174f19c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1654 zcmV-+28sEJP)EX>4Tx04R}tkv&MmKpe$i(@Onlaj=7kLx$>PK~%(1t5Adrp;l;o12rOiLp`5<5%ypW>NMI35kRU=q6(y8mBSx!EiiH&I$2<6kT)#vvg=bb;{@U#SB#pQP7X zTJ#9$-v%zOTbi;5TgF}~)6010qNS#tmY z4c7nw4c7reD4Tcy000McNlirueSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00d4+L_t(&-tCxOY!p=#$A7zR0i}Kv6dj$x2OyL#qHPq# zeOO~ON-AuuX|&!%O?^Pali!IGeIeslK;KMh^hMeDg@#GdfCS^JBi64Fb;Jisf-}kV z!A4Y+6xziHZsRzanVoI6>ooKvo83M4o^${Id+x`%2j*l>&Th%10YtHsTM8@(E(FE_ z7dT`J`(!E!6ic~zz*E2@zAP)=!4*++^fFB3CNU|PXT@k=zz}3MfU=Y}<8s3SB*ZG_D!Ipa+-N=*sJde9X1_NhPF5by)Q z@fyGb^tifmI7A->{##%H&0sK3D@c$v;>PnldE58Gqrn%Oaxw>NdX1^<<-&>}z zuL0f#9T4_6svqpK@7%HOwzv=5rX?kkpZ z9|Ao!fIkMT2y19|b)^;fIiPO=mdTG_egyge8+Z=r4)Hl8(}Pt3{imj|Hx)~{mw@#V zKr123`V?@vDeOv^K~^=qNz?{-&L zz5rS_cPv^f*K`YT6Rd@2F~c7Xwe$K%m>zm3^w=-2m*Bx%)lx z#@v-~$rNe&>(6X4o}J_l68d&qYmbOXC%`d5IbO<_Mfqfbg#S2}=FNKO=# zUe)k+22zS%8O=@^^)cXM;AKDMV+00*oaAjBCR@@-Km`6z#5^U7 z^(4?-hvl<4M7<=&cXQezuslSU1OA91y&z2l!V0imGBuBq`Dr5XQ;4n!_%g<3A~giG zvNbMWPC>aY#_UAePN8Qaj?1GFq${Z+pp~uXWi81sZNMAMu++0QnhVkO1Cz6YfL6AC z0j>|IFOwCFSrGxWvNbGgN&ZdU$-g!JsAD%B1SE+goT+>6ib&g?R1=swWwf$&hpY!3 z3^b3Vo`8>tqm`{r;2hvl&eT?CbWhC5oSbd)4}^9=irlz;F#rGn07*qoM6N<$f~=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + '@inquirer/ansi@1.0.2': resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} engines: {node: '>=18'} @@ -6026,6 +6166,11 @@ packages: '@webext-core/match-patterns@1.0.3': resolution: {integrity: sha512-NY39ACqCxdKBmHgw361M9pfJma8e4AZo20w9AY+5ZjIj1W2dvXC8J31G5fjfOGbulW9w4WKpT8fPooi0mLkn9A==} + '@wxt-dev/auto-icons@1.1.0': + resolution: {integrity: sha512-lDFZjDbrY5gDaapUuUOYTPudE88oB3Z7rTdg0N7iq2WIWga1h0bhzCJDaqNqMvPN2DCYvHFfA0cnqA12vEJjiA==} + peerDependencies: + wxt: '>=0.19.0' + '@wxt-dev/browser@0.1.32': resolution: {integrity: sha512-jvfSppeLzlH4sOkIvMBJoA1pKoI+U5gTkjDwMKdkTWh0P/fj+KDyze3lzo3S6372viCm8tXUKNez+VKyVz2ZDw==} @@ -13066,6 +13211,10 @@ packages: shallowequal@1.1.0: resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==} + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + shebang-command@1.2.0: resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} engines: {node: '>=0.10.0'} @@ -16093,6 +16242,8 @@ snapshots: ckeditor5: 47.4.0 es-toolkit: 1.39.5 fuzzysort: 3.1.0 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-engine@47.4.0': dependencies: @@ -16135,6 +16286,8 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-export-word@47.4.0': dependencies: @@ -16225,8 +16378,6 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.4.0 '@ckeditor/ckeditor5-widget': 47.4.0 ckeditor5: 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-html-embed@47.4.0': dependencies: @@ -16280,6 +16431,8 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-indent@47.4.0': dependencies: @@ -16393,6 +16546,8 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 es-toolkit: 1.39.5 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-merge-fields@47.4.0': dependencies: @@ -16405,6 +16560,8 @@ snapshots: '@ckeditor/ckeditor5-widget': 47.4.0 ckeditor5: 47.4.0 es-toolkit: 1.39.5 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-minimap@47.4.0': dependencies: @@ -16413,6 +16570,8 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-operations-compressor@47.4.0': dependencies: @@ -16586,6 +16745,8 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-source-editing-enhanced@47.4.0': dependencies: @@ -18108,6 +18269,102 @@ snapshots: transitivePeerDependencies: - supports-color + '@img/colour@1.0.0': {} + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.8.1 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + '@inquirer/ansi@1.0.2': optional: true @@ -21464,6 +21721,13 @@ snapshots: '@webext-core/match-patterns@1.0.3': {} + '@wxt-dev/auto-icons@1.1.0(wxt@0.20.13(@types/node@24.10.9)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.31.1)(rollup@4.52.0)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))': + dependencies: + defu: 6.1.4 + fs-extra: 11.3.3 + sharp: 0.34.5 + wxt: 0.20.13(@types/node@24.10.9)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.31.1)(rollup@4.52.0)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) + '@wxt-dev/browser@0.1.32': dependencies: '@types/filesystem': 0.0.36 @@ -29989,6 +30253,37 @@ snapshots: shallowequal@1.1.0: {} + sharp@0.34.5: + dependencies: + '@img/colour': 1.0.0 + detect-libc: 2.1.2 + semver: 7.7.3 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + shebang-command@1.2.0: dependencies: shebang-regex: 1.0.0 From 5600a707d321363cc7b39288081b713cebf83bee Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 10:59:51 +0200 Subject: [PATCH 12/47] chore(web-clipper): reintegrate name and description --- apps/web-clipper/manifest.json | 2 -- apps/web-clipper/wxt.config.js | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web-clipper/manifest.json b/apps/web-clipper/manifest.json index 59a693e37..282d5230a 100644 --- a/apps/web-clipper/manifest.json +++ b/apps/web-clipper/manifest.json @@ -1,8 +1,6 @@ { "manifest_version": 2, - "name": "Trilium Web Clipper (dev)", "version": "1.0.1", - "description": "Save web clippings to Trilium Notes.", "homepage_url": "https://github.com/zadam/trilium-web-clipper", "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'", "icons": { diff --git a/apps/web-clipper/wxt.config.js b/apps/web-clipper/wxt.config.js index 298165d8c..ec61bac13 100644 --- a/apps/web-clipper/wxt.config.js +++ b/apps/web-clipper/wxt.config.js @@ -3,6 +3,8 @@ import { defineConfig } from "vite"; export default defineConfig({ modules: ['@wxt-dev/auto-icons'], manifest: { + name: "Trilium Web Clipper", + description: "Save web clippings to Trilium Notes.", permissions: [ "activeTab", "tabs", From c0a2ae99cf3d592b059de7826a98685b47afa254 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 11:13:17 +0200 Subject: [PATCH 13/47] fix(web-clipper): toast not working --- apps/web-clipper/entrypoints/background/index.js | 3 --- apps/web-clipper/entrypoints/content/index.js | 12 +----------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/apps/web-clipper/entrypoints/background/index.js b/apps/web-clipper/entrypoints/background/index.js index b92bfdb72..4492e862b 100644 --- a/apps/web-clipper/entrypoints/background/index.js +++ b/apps/web-clipper/entrypoints/background/index.js @@ -421,9 +421,6 @@ export default defineBackground(() => { else if (request.name === 'closeTabs') { return await browser.tabs.remove(request.tabIds) } - else if (request.name === 'load-script') { - return await browser.tabs.executeScript({file: request.file}); - } else if (request.name === 'save-cropped-screenshot') { const activeTab = await getActiveTab(); diff --git a/apps/web-clipper/entrypoints/content/index.js b/apps/web-clipper/entrypoints/content/index.js index c6ba6c438..99d30ae6a 100644 --- a/apps/web-clipper/entrypoints/content/index.js +++ b/apps/web-clipper/entrypoints/content/index.js @@ -275,7 +275,7 @@ export default defineContentScript({ messageText = message.message; } - await requireLib('/lib/toast.js'); + await import("../../lib/toast"); showToast(messageText, { settings: { @@ -343,16 +343,6 @@ export default defineContentScript({ } } - const loadedLibs = []; - - async function requireLib(libPath) { - if (!loadedLibs.includes(libPath)) { - loadedLibs.push(libPath); - - await browser.runtime.sendMessage({name: 'load-script', file: libPath}); - } - } - browser.runtime.onMessage.addListener(prepareMessageResponse); } }); From b5ff71b1a03acbd300554daa9780ba0750c7eb93 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 11:25:32 +0200 Subject: [PATCH 14/47] fix(web-clipper): missing utils import --- apps/web-clipper/entrypoints/background/index.js | 1 + apps/web-clipper/entrypoints/content/index.js | 2 ++ apps/web-clipper/manifest.json | 6 ++---- apps/web-clipper/utils.js | 6 +++--- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/web-clipper/entrypoints/background/index.js b/apps/web-clipper/entrypoints/background/index.js index 4492e862b..e8d3c6d03 100644 --- a/apps/web-clipper/entrypoints/background/index.js +++ b/apps/web-clipper/entrypoints/background/index.js @@ -1,3 +1,4 @@ +import { randomString } from "../../utils"; import TriliumServerFacade, { isDevEnv } from "./trilium_server_facade"; export default defineBackground(() => { diff --git a/apps/web-clipper/entrypoints/content/index.js b/apps/web-clipper/entrypoints/content/index.js index 99d30ae6a..671122384 100644 --- a/apps/web-clipper/entrypoints/content/index.js +++ b/apps/web-clipper/entrypoints/content/index.js @@ -1,3 +1,5 @@ +import { getBaseUrl, getPageLocationOrigin, randomString } from "../../utils.js"; + export default defineContentScript({ matches: [ "" diff --git a/apps/web-clipper/manifest.json b/apps/web-clipper/manifest.json index 282d5230a..12694338c 100644 --- a/apps/web-clipper/manifest.json +++ b/apps/web-clipper/manifest.json @@ -16,15 +16,13 @@ "content_scripts": [ { "js": [ - "lib/browser-polyfill.js", - "utils.js" + "lib/browser-polyfill.js" ] } ], "background": { "scripts": [ - "lib/browser-polyfill.js", - "utils.js" + "lib/browser-polyfill.js" ] }, "commands": { diff --git a/apps/web-clipper/utils.js b/apps/web-clipper/utils.js index 9ec82b2c2..aab69e12c 100644 --- a/apps/web-clipper/utils.js +++ b/apps/web-clipper/utils.js @@ -1,4 +1,4 @@ -function randomString(len) { +export function randomString(len) { let text = ""; const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; @@ -9,7 +9,7 @@ function randomString(len) { return text; } -function getBaseUrl() { +export function getBaseUrl() { let output = getPageLocationOrigin() + location.pathname; if (output[output.length - 1] !== '/') { @@ -21,7 +21,7 @@ function getBaseUrl() { return output; } -function getPageLocationOrigin() { +export function getPageLocationOrigin() { // location.origin normally returns the protocol + domain + port (eg. https://example.com:8080) // but for file:// protocol this is browser dependant and in particular Firefox returns "null" in this case. return location.protocol === 'file:' ? 'file://' : location.origin; From 5d07a079efa1d57bc20d917ba92a4ec79f3247c7 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 11:46:07 +0200 Subject: [PATCH 15/47] feat(web-clipper): improve error handling for content entrypoint --- apps/web-clipper/entrypoints/content/index.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/web-clipper/entrypoints/content/index.js b/apps/web-clipper/entrypoints/content/index.js index 671122384..72e4815ce 100644 --- a/apps/web-clipper/entrypoints/content/index.js +++ b/apps/web-clipper/entrypoints/content/index.js @@ -312,10 +312,6 @@ export default defineContentScript({ return getRectangleArea(); } else if (message.name === "trilium-save-page") { - await requireLib("/lib/JSDOMParser.js"); - await requireLib("/lib/Readability.js"); - await requireLib("/lib/Readability-readerable.js"); - const {title, body} = getReadableDocument(); makeLinksAbsolute(body); @@ -345,6 +341,14 @@ export default defineContentScript({ } } - browser.runtime.onMessage.addListener(prepareMessageResponse); + browser.runtime.onMessage.addListener(async (message) => { + try { + const response = await prepareMessageResponse(message); + return response; + } catch (err) { + console.error(err); + throw err; + } + }); } }); From 59f2fc8d033641dbe4b5e8df561c5c26ab38cfa4 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 11:53:26 +0200 Subject: [PATCH 16/47] fix(web-clipper): clipping whole page not working --- apps/web-clipper/entrypoints/content/index.js | 1 + apps/web-clipper/lib/Readability.js | 6 +----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/web-clipper/entrypoints/content/index.js b/apps/web-clipper/entrypoints/content/index.js index 72e4815ce..973172b4e 100644 --- a/apps/web-clipper/entrypoints/content/index.js +++ b/apps/web-clipper/entrypoints/content/index.js @@ -1,4 +1,5 @@ import { getBaseUrl, getPageLocationOrigin, randomString } from "../../utils.js"; +import Readability from "../../lib/Readability.js"; export default defineContentScript({ matches: [ diff --git a/apps/web-clipper/lib/Readability.js b/apps/web-clipper/lib/Readability.js index ce06df459..c5335f80b 100644 --- a/apps/web-clipper/lib/Readability.js +++ b/apps/web-clipper/lib/Readability.js @@ -25,7 +25,7 @@ * @param {HTMLDocument} doc The document to parse. * @param {Object} options The options object. */ -function Readability(doc, options) { +export default function Readability(doc, options) { // In some older versions, people passed a URI as the first argument. Cope: if (options && options.documentElement) { doc = options; @@ -2277,7 +2277,3 @@ Readability.prototype = { }; } }; - -if (typeof module === "object") { - module.exports = Readability; -} From f0b1319f959a76005e2727dc03db00eef6e854a0 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 11:54:00 +0200 Subject: [PATCH 17/47] refactor(web-clipper): remove unnecessary libraries --- apps/web-clipper/lib/JSDOMParser.js | 1196 ----------------- .../web-clipper/lib/Readability-readerable.js | 108 -- 2 files changed, 1304 deletions(-) delete mode 100644 apps/web-clipper/lib/JSDOMParser.js delete mode 100644 apps/web-clipper/lib/Readability-readerable.js diff --git a/apps/web-clipper/lib/JSDOMParser.js b/apps/web-clipper/lib/JSDOMParser.js deleted file mode 100644 index 7bfa2acf5..000000000 --- a/apps/web-clipper/lib/JSDOMParser.js +++ /dev/null @@ -1,1196 +0,0 @@ -/*eslint-env es6:false*/ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * This is a relatively lightweight DOMParser that is safe to use in a web - * worker. This is far from a complete DOM implementation; however, it should - * contain the minimal set of functionality necessary for Readability.js. - * - * Aside from not implementing the full DOM API, there are other quirks to be - * aware of when using the JSDOMParser: - * - * 1) Properly formed HTML/XML must be used. This means you should be extra - * careful when using this parser on anything received directly from an - * XMLHttpRequest. Providing a serialized string from an XMLSerializer, - * however, should be safe (since the browser's XMLSerializer should - * generate valid HTML/XML). Therefore, if parsing a document from an XHR, - * the recommended approach is to do the XHR in the main thread, use - * XMLSerializer.serializeToString() on the responseXML, and pass the - * resulting string to the worker. - * - * 2) Live NodeLists are not supported. DOM methods and properties such as - * getElementsByTagName() and childNodes return standard arrays. If you - * want these lists to be updated when nodes are removed or added to the - * document, you must take care to manually update them yourself. - */ -(function (global) { - - // XML only defines these and the numeric ones: - - var entityTable = { - "lt": "<", - "gt": ">", - "amp": "&", - "quot": '"', - "apos": "'", - }; - - var reverseEntityTable = { - "<": "<", - ">": ">", - "&": "&", - '"': """, - "'": "'", - }; - - function encodeTextContentHTML(s) { - return s.replace(/[&<>]/g, function(x) { - return reverseEntityTable[x]; - }); - } - - function encodeHTML(s) { - return s.replace(/[&<>'"]/g, function(x) { - return reverseEntityTable[x]; - }); - } - - function decodeHTML(str) { - return str.replace(/&(quot|amp|apos|lt|gt);/g, function(match, tag) { - return entityTable[tag]; - }).replace(/&#(?:x([0-9a-z]{1,4})|([0-9]{1,4}));/gi, function(match, hex, numStr) { - var num = parseInt(hex || numStr, hex ? 16 : 10); // read num - return String.fromCharCode(num); - }); - } - - // When a style is set in JS, map it to the corresponding CSS attribute - var styleMap = { - "alignmentBaseline": "alignment-baseline", - "background": "background", - "backgroundAttachment": "background-attachment", - "backgroundClip": "background-clip", - "backgroundColor": "background-color", - "backgroundImage": "background-image", - "backgroundOrigin": "background-origin", - "backgroundPosition": "background-position", - "backgroundPositionX": "background-position-x", - "backgroundPositionY": "background-position-y", - "backgroundRepeat": "background-repeat", - "backgroundRepeatX": "background-repeat-x", - "backgroundRepeatY": "background-repeat-y", - "backgroundSize": "background-size", - "baselineShift": "baseline-shift", - "border": "border", - "borderBottom": "border-bottom", - "borderBottomColor": "border-bottom-color", - "borderBottomLeftRadius": "border-bottom-left-radius", - "borderBottomRightRadius": "border-bottom-right-radius", - "borderBottomStyle": "border-bottom-style", - "borderBottomWidth": "border-bottom-width", - "borderCollapse": "border-collapse", - "borderColor": "border-color", - "borderImage": "border-image", - "borderImageOutset": "border-image-outset", - "borderImageRepeat": "border-image-repeat", - "borderImageSlice": "border-image-slice", - "borderImageSource": "border-image-source", - "borderImageWidth": "border-image-width", - "borderLeft": "border-left", - "borderLeftColor": "border-left-color", - "borderLeftStyle": "border-left-style", - "borderLeftWidth": "border-left-width", - "borderRadius": "border-radius", - "borderRight": "border-right", - "borderRightColor": "border-right-color", - "borderRightStyle": "border-right-style", - "borderRightWidth": "border-right-width", - "borderSpacing": "border-spacing", - "borderStyle": "border-style", - "borderTop": "border-top", - "borderTopColor": "border-top-color", - "borderTopLeftRadius": "border-top-left-radius", - "borderTopRightRadius": "border-top-right-radius", - "borderTopStyle": "border-top-style", - "borderTopWidth": "border-top-width", - "borderWidth": "border-width", - "bottom": "bottom", - "boxShadow": "box-shadow", - "boxSizing": "box-sizing", - "captionSide": "caption-side", - "clear": "clear", - "clip": "clip", - "clipPath": "clip-path", - "clipRule": "clip-rule", - "color": "color", - "colorInterpolation": "color-interpolation", - "colorInterpolationFilters": "color-interpolation-filters", - "colorProfile": "color-profile", - "colorRendering": "color-rendering", - "content": "content", - "counterIncrement": "counter-increment", - "counterReset": "counter-reset", - "cursor": "cursor", - "direction": "direction", - "display": "display", - "dominantBaseline": "dominant-baseline", - "emptyCells": "empty-cells", - "enableBackground": "enable-background", - "fill": "fill", - "fillOpacity": "fill-opacity", - "fillRule": "fill-rule", - "filter": "filter", - "cssFloat": "float", - "floodColor": "flood-color", - "floodOpacity": "flood-opacity", - "font": "font", - "fontFamily": "font-family", - "fontSize": "font-size", - "fontStretch": "font-stretch", - "fontStyle": "font-style", - "fontVariant": "font-variant", - "fontWeight": "font-weight", - "glyphOrientationHorizontal": "glyph-orientation-horizontal", - "glyphOrientationVertical": "glyph-orientation-vertical", - "height": "height", - "imageRendering": "image-rendering", - "kerning": "kerning", - "left": "left", - "letterSpacing": "letter-spacing", - "lightingColor": "lighting-color", - "lineHeight": "line-height", - "listStyle": "list-style", - "listStyleImage": "list-style-image", - "listStylePosition": "list-style-position", - "listStyleType": "list-style-type", - "margin": "margin", - "marginBottom": "margin-bottom", - "marginLeft": "margin-left", - "marginRight": "margin-right", - "marginTop": "margin-top", - "marker": "marker", - "markerEnd": "marker-end", - "markerMid": "marker-mid", - "markerStart": "marker-start", - "mask": "mask", - "maxHeight": "max-height", - "maxWidth": "max-width", - "minHeight": "min-height", - "minWidth": "min-width", - "opacity": "opacity", - "orphans": "orphans", - "outline": "outline", - "outlineColor": "outline-color", - "outlineOffset": "outline-offset", - "outlineStyle": "outline-style", - "outlineWidth": "outline-width", - "overflow": "overflow", - "overflowX": "overflow-x", - "overflowY": "overflow-y", - "padding": "padding", - "paddingBottom": "padding-bottom", - "paddingLeft": "padding-left", - "paddingRight": "padding-right", - "paddingTop": "padding-top", - "page": "page", - "pageBreakAfter": "page-break-after", - "pageBreakBefore": "page-break-before", - "pageBreakInside": "page-break-inside", - "pointerEvents": "pointer-events", - "position": "position", - "quotes": "quotes", - "resize": "resize", - "right": "right", - "shapeRendering": "shape-rendering", - "size": "size", - "speak": "speak", - "src": "src", - "stopColor": "stop-color", - "stopOpacity": "stop-opacity", - "stroke": "stroke", - "strokeDasharray": "stroke-dasharray", - "strokeDashoffset": "stroke-dashoffset", - "strokeLinecap": "stroke-linecap", - "strokeLinejoin": "stroke-linejoin", - "strokeMiterlimit": "stroke-miterlimit", - "strokeOpacity": "stroke-opacity", - "strokeWidth": "stroke-width", - "tableLayout": "table-layout", - "textAlign": "text-align", - "textAnchor": "text-anchor", - "textDecoration": "text-decoration", - "textIndent": "text-indent", - "textLineThrough": "text-line-through", - "textLineThroughColor": "text-line-through-color", - "textLineThroughMode": "text-line-through-mode", - "textLineThroughStyle": "text-line-through-style", - "textLineThroughWidth": "text-line-through-width", - "textOverflow": "text-overflow", - "textOverline": "text-overline", - "textOverlineColor": "text-overline-color", - "textOverlineMode": "text-overline-mode", - "textOverlineStyle": "text-overline-style", - "textOverlineWidth": "text-overline-width", - "textRendering": "text-rendering", - "textShadow": "text-shadow", - "textTransform": "text-transform", - "textUnderline": "text-underline", - "textUnderlineColor": "text-underline-color", - "textUnderlineMode": "text-underline-mode", - "textUnderlineStyle": "text-underline-style", - "textUnderlineWidth": "text-underline-width", - "top": "top", - "unicodeBidi": "unicode-bidi", - "unicodeRange": "unicode-range", - "vectorEffect": "vector-effect", - "verticalAlign": "vertical-align", - "visibility": "visibility", - "whiteSpace": "white-space", - "widows": "widows", - "width": "width", - "wordBreak": "word-break", - "wordSpacing": "word-spacing", - "wordWrap": "word-wrap", - "writingMode": "writing-mode", - "zIndex": "z-index", - "zoom": "zoom", - }; - - // Elements that can be self-closing - var voidElems = { - "area": true, - "base": true, - "br": true, - "col": true, - "command": true, - "embed": true, - "hr": true, - "img": true, - "input": true, - "link": true, - "meta": true, - "param": true, - "source": true, - "wbr": true - }; - - var whitespace = [" ", "\t", "\n", "\r"]; - - // See https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType - var nodeTypes = { - ELEMENT_NODE: 1, - ATTRIBUTE_NODE: 2, - TEXT_NODE: 3, - CDATA_SECTION_NODE: 4, - ENTITY_REFERENCE_NODE: 5, - ENTITY_NODE: 6, - PROCESSING_INSTRUCTION_NODE: 7, - COMMENT_NODE: 8, - DOCUMENT_NODE: 9, - DOCUMENT_TYPE_NODE: 10, - DOCUMENT_FRAGMENT_NODE: 11, - NOTATION_NODE: 12 - }; - - function getElementsByTagName(tag) { - tag = tag.toUpperCase(); - var elems = []; - var allTags = (tag === "*"); - function getElems(node) { - var length = node.children.length; - for (var i = 0; i < length; i++) { - var child = node.children[i]; - if (allTags || (child.tagName === tag)) - elems.push(child); - getElems(child); - } - } - getElems(this); - elems._isLiveNodeList = true; - return elems; - } - - var Node = function () {}; - - Node.prototype = { - attributes: null, - childNodes: null, - localName: null, - nodeName: null, - parentNode: null, - textContent: null, - nextSibling: null, - previousSibling: null, - - get firstChild() { - return this.childNodes[0] || null; - }, - - get firstElementChild() { - return this.children[0] || null; - }, - - get lastChild() { - return this.childNodes[this.childNodes.length - 1] || null; - }, - - get lastElementChild() { - return this.children[this.children.length - 1] || null; - }, - - appendChild: function (child) { - if (child.parentNode) { - child.parentNode.removeChild(child); - } - - var last = this.lastChild; - if (last) - last.nextSibling = child; - child.previousSibling = last; - - if (child.nodeType === Node.ELEMENT_NODE) { - child.previousElementSibling = this.children[this.children.length - 1] || null; - this.children.push(child); - child.previousElementSibling && (child.previousElementSibling.nextElementSibling = child); - } - this.childNodes.push(child); - child.parentNode = this; - }, - - removeChild: function (child) { - var childNodes = this.childNodes; - var childIndex = childNodes.indexOf(child); - if (childIndex === -1) { - throw "removeChild: node not found"; - } else { - child.parentNode = null; - var prev = child.previousSibling; - var next = child.nextSibling; - if (prev) - prev.nextSibling = next; - if (next) - next.previousSibling = prev; - - if (child.nodeType === Node.ELEMENT_NODE) { - prev = child.previousElementSibling; - next = child.nextElementSibling; - if (prev) - prev.nextElementSibling = next; - if (next) - next.previousElementSibling = prev; - this.children.splice(this.children.indexOf(child), 1); - } - - child.previousSibling = child.nextSibling = null; - child.previousElementSibling = child.nextElementSibling = null; - - return childNodes.splice(childIndex, 1)[0]; - } - }, - - replaceChild: function (newNode, oldNode) { - var childNodes = this.childNodes; - var childIndex = childNodes.indexOf(oldNode); - if (childIndex === -1) { - throw "replaceChild: node not found"; - } else { - // This will take care of updating the new node if it was somewhere else before: - if (newNode.parentNode) - newNode.parentNode.removeChild(newNode); - - childNodes[childIndex] = newNode; - - // update the new node's sibling properties, and its new siblings' sibling properties - newNode.nextSibling = oldNode.nextSibling; - newNode.previousSibling = oldNode.previousSibling; - if (newNode.nextSibling) - newNode.nextSibling.previousSibling = newNode; - if (newNode.previousSibling) - newNode.previousSibling.nextSibling = newNode; - - newNode.parentNode = this; - - // Now deal with elements before we clear out those values for the old node, - // because it can help us take shortcuts here: - if (newNode.nodeType === Node.ELEMENT_NODE) { - if (oldNode.nodeType === Node.ELEMENT_NODE) { - // Both were elements, which makes this easier, we just swap things out: - newNode.previousElementSibling = oldNode.previousElementSibling; - newNode.nextElementSibling = oldNode.nextElementSibling; - if (newNode.previousElementSibling) - newNode.previousElementSibling.nextElementSibling = newNode; - if (newNode.nextElementSibling) - newNode.nextElementSibling.previousElementSibling = newNode; - this.children[this.children.indexOf(oldNode)] = newNode; - } else { - // Hard way: - newNode.previousElementSibling = (function() { - for (var i = childIndex - 1; i >= 0; i--) { - if (childNodes[i].nodeType === Node.ELEMENT_NODE) - return childNodes[i]; - } - return null; - })(); - if (newNode.previousElementSibling) { - newNode.nextElementSibling = newNode.previousElementSibling.nextElementSibling; - } else { - newNode.nextElementSibling = (function() { - for (var i = childIndex + 1; i < childNodes.length; i++) { - if (childNodes[i].nodeType === Node.ELEMENT_NODE) - return childNodes[i]; - } - return null; - })(); - } - if (newNode.previousElementSibling) - newNode.previousElementSibling.nextElementSibling = newNode; - if (newNode.nextElementSibling) - newNode.nextElementSibling.previousElementSibling = newNode; - - if (newNode.nextElementSibling) - this.children.splice(this.children.indexOf(newNode.nextElementSibling), 0, newNode); - else - this.children.push(newNode); - } - } else if (oldNode.nodeType === Node.ELEMENT_NODE) { - // new node is not an element node. - // if the old one was, update its element siblings: - if (oldNode.previousElementSibling) - oldNode.previousElementSibling.nextElementSibling = oldNode.nextElementSibling; - if (oldNode.nextElementSibling) - oldNode.nextElementSibling.previousElementSibling = oldNode.previousElementSibling; - this.children.splice(this.children.indexOf(oldNode), 1); - - // If the old node wasn't an element, neither the new nor the old node was an element, - // and the children array and its members shouldn't need any updating. - } - - - oldNode.parentNode = null; - oldNode.previousSibling = null; - oldNode.nextSibling = null; - if (oldNode.nodeType === Node.ELEMENT_NODE) { - oldNode.previousElementSibling = null; - oldNode.nextElementSibling = null; - } - return oldNode; - } - }, - - __JSDOMParser__: true, - }; - - for (var nodeType in nodeTypes) { - Node[nodeType] = Node.prototype[nodeType] = nodeTypes[nodeType]; - } - - var Attribute = function (name, value) { - this.name = name; - this._value = value; - }; - - Attribute.prototype = { - get value() { - return this._value; - }, - setValue: function(newValue) { - this._value = newValue; - }, - getEncodedValue: function() { - return encodeHTML(this._value); - }, - }; - - var Comment = function () { - this.childNodes = []; - }; - - Comment.prototype = { - __proto__: Node.prototype, - - nodeName: "#comment", - nodeType: Node.COMMENT_NODE - }; - - var Text = function () { - this.childNodes = []; - }; - - Text.prototype = { - __proto__: Node.prototype, - - nodeName: "#text", - nodeType: Node.TEXT_NODE, - get textContent() { - if (typeof this._textContent === "undefined") { - this._textContent = decodeHTML(this._innerHTML || ""); - } - return this._textContent; - }, - get innerHTML() { - if (typeof this._innerHTML === "undefined") { - this._innerHTML = encodeTextContentHTML(this._textContent || ""); - } - return this._innerHTML; - }, - - set innerHTML(newHTML) { - this._innerHTML = newHTML; - delete this._textContent; - }, - set textContent(newText) { - this._textContent = newText; - delete this._innerHTML; - }, - }; - - var Document = function (url) { - this.documentURI = url; - this.styleSheets = []; - this.childNodes = []; - this.children = []; - }; - - Document.prototype = { - __proto__: Node.prototype, - - nodeName: "#document", - nodeType: Node.DOCUMENT_NODE, - title: "", - - getElementsByTagName: getElementsByTagName, - - getElementById: function (id) { - function getElem(node) { - var length = node.children.length; - if (node.id === id) - return node; - for (var i = 0; i < length; i++) { - var el = getElem(node.children[i]); - if (el) - return el; - } - return null; - } - return getElem(this); - }, - - createElement: function (tag) { - var node = new Element(tag); - return node; - }, - - createTextNode: function (text) { - var node = new Text(); - node.textContent = text; - return node; - }, - - get baseURI() { - if (!this.hasOwnProperty("_baseURI")) { - this._baseURI = this.documentURI; - var baseElements = this.getElementsByTagName("base"); - var href = baseElements[0] && baseElements[0].getAttribute("href"); - if (href) { - try { - this._baseURI = (new URL(href, this._baseURI)).href; - } catch (ex) {/* Just fall back to documentURI */} - } - } - return this._baseURI; - }, - }; - - var Element = function (tag) { - // We use this to find the closing tag. - this._matchingTag = tag; - // We're explicitly a non-namespace aware parser, we just pretend it's all HTML. - var lastColonIndex = tag.lastIndexOf(":"); - if (lastColonIndex != -1) { - tag = tag.substring(lastColonIndex + 1); - } - this.attributes = []; - this.childNodes = []; - this.children = []; - this.nextElementSibling = this.previousElementSibling = null; - this.localName = tag.toLowerCase(); - this.tagName = tag.toUpperCase(); - this.style = new Style(this); - }; - - Element.prototype = { - __proto__: Node.prototype, - - nodeType: Node.ELEMENT_NODE, - - getElementsByTagName: getElementsByTagName, - - get className() { - return this.getAttribute("class") || ""; - }, - - set className(str) { - this.setAttribute("class", str); - }, - - get id() { - return this.getAttribute("id") || ""; - }, - - set id(str) { - this.setAttribute("id", str); - }, - - get href() { - return this.getAttribute("href") || ""; - }, - - set href(str) { - this.setAttribute("href", str); - }, - - get src() { - return this.getAttribute("src") || ""; - }, - - set src(str) { - this.setAttribute("src", str); - }, - - get srcset() { - return this.getAttribute("srcset") || ""; - }, - - set srcset(str) { - this.setAttribute("srcset", str); - }, - - get nodeName() { - return this.tagName; - }, - - get innerHTML() { - function getHTML(node) { - var i = 0; - for (i = 0; i < node.childNodes.length; i++) { - var child = node.childNodes[i]; - if (child.localName) { - arr.push("<" + child.localName); - - // serialize attribute list - for (var j = 0; j < child.attributes.length; j++) { - var attr = child.attributes[j]; - // the attribute value will be HTML escaped. - var val = attr.getEncodedValue(); - var quote = (val.indexOf('"') === -1 ? '"' : "'"); - arr.push(" " + attr.name + "=" + quote + val + quote); - } - - if (child.localName in voidElems && !child.childNodes.length) { - // if this is a self-closing element, end it here - arr.push("/>"); - } else { - // otherwise, add its children - arr.push(">"); - getHTML(child); - arr.push(""); - } - } else { - // This is a text node, so asking for innerHTML won't recurse. - arr.push(child.innerHTML); - } - } - } - - // Using Array.join() avoids the overhead from lazy string concatenation. - var arr = []; - getHTML(this); - return arr.join(""); - }, - - set innerHTML(html) { - var parser = new JSDOMParser(); - var node = parser.parse(html); - var i; - for (i = this.childNodes.length; --i >= 0;) { - this.childNodes[i].parentNode = null; - } - this.childNodes = node.childNodes; - this.children = node.children; - for (i = this.childNodes.length; --i >= 0;) { - this.childNodes[i].parentNode = this; - } - }, - - set textContent(text) { - // clear parentNodes for existing children - for (var i = this.childNodes.length; --i >= 0;) { - this.childNodes[i].parentNode = null; - } - - var node = new Text(); - this.childNodes = [ node ]; - this.children = []; - node.textContent = text; - node.parentNode = this; - }, - - get textContent() { - function getText(node) { - var nodes = node.childNodes; - for (var i = 0; i < nodes.length; i++) { - var child = nodes[i]; - if (child.nodeType === 3) { - text.push(child.textContent); - } else { - getText(child); - } - } - } - - // Using Array.join() avoids the overhead from lazy string concatenation. - // See http://blog.cdleary.com/2012/01/string-representation-in-spidermonkey/#ropes - var text = []; - getText(this); - return text.join(""); - }, - - getAttribute: function (name) { - for (var i = this.attributes.length; --i >= 0;) { - var attr = this.attributes[i]; - if (attr.name === name) { - return attr.value; - } - } - return undefined; - }, - - setAttribute: function (name, value) { - for (var i = this.attributes.length; --i >= 0;) { - var attr = this.attributes[i]; - if (attr.name === name) { - attr.setValue(value); - return; - } - } - this.attributes.push(new Attribute(name, value)); - }, - - removeAttribute: function (name) { - for (var i = this.attributes.length; --i >= 0;) { - var attr = this.attributes[i]; - if (attr.name === name) { - this.attributes.splice(i, 1); - break; - } - } - }, - - hasAttribute: function (name) { - return this.attributes.some(function (attr) { - return attr.name == name; - }); - }, - }; - - var Style = function (node) { - this.node = node; - }; - - // getStyle() and setStyle() use the style attribute string directly. This - // won't be very efficient if there are a lot of style manipulations, but - // it's the easiest way to make sure the style attribute string and the JS - // style property stay in sync. Readability.js doesn't do many style - // manipulations, so this should be okay. - Style.prototype = { - getStyle: function (styleName) { - var attr = this.node.getAttribute("style"); - if (!attr) - return undefined; - - var styles = attr.split(";"); - for (var i = 0; i < styles.length; i++) { - var style = styles[i].split(":"); - var name = style[0].trim(); - if (name === styleName) - return style[1].trim(); - } - - return undefined; - }, - - setStyle: function (styleName, styleValue) { - var value = this.node.getAttribute("style") || ""; - var index = 0; - do { - var next = value.indexOf(";", index) + 1; - var length = next - index - 1; - var style = (length > 0 ? value.substr(index, length) : value.substr(index)); - if (style.substr(0, style.indexOf(":")).trim() === styleName) { - value = value.substr(0, index).trim() + (next ? " " + value.substr(next).trim() : ""); - break; - } - index = next; - } while (index); - - value += " " + styleName + ": " + styleValue + ";"; - this.node.setAttribute("style", value.trim()); - } - }; - - // For each item in styleMap, define a getter and setter on the style - // property. - for (var jsName in styleMap) { - (function (cssName) { - Style.prototype.__defineGetter__(jsName, function () { - return this.getStyle(cssName); - }); - Style.prototype.__defineSetter__(jsName, function (value) { - this.setStyle(cssName, value); - }); - })(styleMap[jsName]); - } - - var JSDOMParser = function () { - this.currentChar = 0; - - // In makeElementNode() we build up many strings one char at a time. Using - // += for this results in lots of short-lived intermediate strings. It's - // better to build an array of single-char strings and then join() them - // together at the end. And reusing a single array (i.e. |this.strBuf|) - // over and over for this purpose uses less memory than using a new array - // for each string. - this.strBuf = []; - - // Similarly, we reuse this array to return the two arguments from - // makeElementNode(), which saves us from having to allocate a new array - // every time. - this.retPair = []; - - this.errorState = ""; - }; - - JSDOMParser.prototype = { - error: function(m) { - if (typeof dump !== "undefined") { - dump("JSDOMParser error: " + m + "\n"); - } else if (typeof console !== "undefined") { - console.log("JSDOMParser error: " + m + "\n"); - } - this.errorState += m + "\n"; - }, - - /** - * Look at the next character without advancing the index. - */ - peekNext: function () { - return this.html[this.currentChar]; - }, - - /** - * Get the next character and advance the index. - */ - nextChar: function () { - return this.html[this.currentChar++]; - }, - - /** - * Called after a quote character is read. This finds the next quote - * character and returns the text string in between. - */ - readString: function (quote) { - var str; - var n = this.html.indexOf(quote, this.currentChar); - if (n === -1) { - this.currentChar = this.html.length; - str = null; - } else { - str = this.html.substring(this.currentChar, n); - this.currentChar = n + 1; - } - - return str; - }, - - /** - * Called when parsing a node. This finds the next name/value attribute - * pair and adds the result to the attributes list. - */ - readAttribute: function (node) { - var name = ""; - - var n = this.html.indexOf("=", this.currentChar); - if (n === -1) { - this.currentChar = this.html.length; - } else { - // Read until a '=' character is hit; this will be the attribute key - name = this.html.substring(this.currentChar, n); - this.currentChar = n + 1; - } - - if (!name) - return; - - // After a '=', we should see a '"' for the attribute value - var c = this.nextChar(); - if (c !== '"' && c !== "'") { - this.error("Error reading attribute " + name + ", expecting '\"'"); - return; - } - - // Read the attribute value (and consume the matching quote) - var value = this.readString(c); - - node.attributes.push(new Attribute(name, decodeHTML(value))); - - return; - }, - - /** - * Parses and returns an Element node. This is called after a '<' has been - * read. - * - * @returns an array; the first index of the array is the parsed node; - * the second index is a boolean indicating whether this is a void - * Element - */ - makeElementNode: function (retPair) { - var c = this.nextChar(); - - // Read the Element tag name - var strBuf = this.strBuf; - strBuf.length = 0; - while (whitespace.indexOf(c) == -1 && c !== ">" && c !== "/") { - if (c === undefined) - return false; - strBuf.push(c); - c = this.nextChar(); - } - var tag = strBuf.join(""); - - if (!tag) - return false; - - var node = new Element(tag); - - // Read Element attributes - while (c !== "/" && c !== ">") { - if (c === undefined) - return false; - while (whitespace.indexOf(this.html[this.currentChar++]) != -1) { - // Advance cursor to first non-whitespace char. - } - this.currentChar--; - c = this.nextChar(); - if (c !== "/" && c !== ">") { - --this.currentChar; - this.readAttribute(node); - } - } - - // If this is a self-closing tag, read '/>' - var closed = false; - if (c === "/") { - closed = true; - c = this.nextChar(); - if (c !== ">") { - this.error("expected '>' to close " + tag); - return false; - } - } - - retPair[0] = node; - retPair[1] = closed; - return true; - }, - - /** - * If the current input matches this string, advance the input index; - * otherwise, do nothing. - * - * @returns whether input matched string - */ - match: function (str) { - var strlen = str.length; - if (this.html.substr(this.currentChar, strlen).toLowerCase() === str.toLowerCase()) { - this.currentChar += strlen; - return true; - } - return false; - }, - - /** - * Searches the input until a string is found and discards all input up to - * and including the matched string. - */ - discardTo: function (str) { - var index = this.html.indexOf(str, this.currentChar) + str.length; - if (index === -1) - this.currentChar = this.html.length; - this.currentChar = index; - }, - - /** - * Reads child nodes for the given node. - */ - readChildren: function (node) { - var child; - while ((child = this.readNode())) { - // Don't keep Comment nodes - if (child.nodeType !== 8) { - node.appendChild(child); - } - } - }, - - discardNextComment: function() { - if (this.match("--")) { - this.discardTo("-->"); - } else { - var c = this.nextChar(); - while (c !== ">") { - if (c === undefined) - return null; - if (c === '"' || c === "'") - this.readString(c); - c = this.nextChar(); - } - } - return new Comment(); - }, - - - /** - * Reads the next child node from the input. If we're reading a closing - * tag, or if we've reached the end of input, return null. - * - * @returns the node - */ - readNode: function () { - var c = this.nextChar(); - - if (c === undefined) - return null; - - // Read any text as Text node - var textNode; - if (c !== "<") { - --this.currentChar; - textNode = new Text(); - var n = this.html.indexOf("<", this.currentChar); - if (n === -1) { - textNode.innerHTML = this.html.substring(this.currentChar, this.html.length); - this.currentChar = this.html.length; - } else { - textNode.innerHTML = this.html.substring(this.currentChar, n); - this.currentChar = n; - } - return textNode; - } - - if (this.match("![CDATA[")) { - var endChar = this.html.indexOf("]]>", this.currentChar); - if (endChar === -1) { - this.error("unclosed CDATA section"); - return null; - } - textNode = new Text(); - textNode.textContent = this.html.substring(this.currentChar, endChar); - this.currentChar = endChar + ("]]>").length; - return textNode; - } - - c = this.peekNext(); - - // Read Comment node. Normally, Comment nodes know their inner - // textContent, but we don't really care about Comment nodes (we throw - // them away in readChildren()). So just returning an empty Comment node - // here is sufficient. - if (c === "!" || c === "?") { - // We're still before the ! or ? that is starting this comment: - this.currentChar++; - return this.discardNextComment(); - } - - // If we're reading a closing tag, return null. This means we've reached - // the end of this set of child nodes. - if (c === "/") { - --this.currentChar; - return null; - } - - // Otherwise, we're looking at an Element node - var result = this.makeElementNode(this.retPair); - if (!result) - return null; - - var node = this.retPair[0]; - var closed = this.retPair[1]; - var localName = node.localName; - - // If this isn't a void Element, read its child nodes - if (!closed) { - this.readChildren(node); - var closingTag = ""; - if (!this.match(closingTag)) { - this.error("expected '" + closingTag + "' and got " + this.html.substr(this.currentChar, closingTag.length)); - return null; - } - } - - // Only use the first title, because SVG might have other - // title elements which we don't care about (medium.com - // does this, at least). - if (localName === "title" && !this.doc.title) { - this.doc.title = node.textContent.trim(); - } else if (localName === "head") { - this.doc.head = node; - } else if (localName === "body") { - this.doc.body = node; - } else if (localName === "html") { - this.doc.documentElement = node; - } - - return node; - }, - - /** - * Parses an HTML string and returns a JS implementation of the Document. - */ - parse: function (html, url) { - this.html = html; - var doc = this.doc = new Document(url); - this.readChildren(doc); - - // If this is an HTML document, remove root-level children except for the - // node - if (doc.documentElement) { - for (var i = doc.childNodes.length; --i >= 0;) { - var child = doc.childNodes[i]; - if (child !== doc.documentElement) { - doc.removeChild(child); - } - } - } - - return doc; - } - }; - - // Attach the standard DOM types to the global scope - global.Node = Node; - global.Comment = Comment; - global.Document = Document; - global.Element = Element; - global.Text = Text; - - // Attach JSDOMParser to the global scope - global.JSDOMParser = JSDOMParser; - -})(this); - -if (typeof module === "object") { - module.exports = this.JSDOMParser; -} diff --git a/apps/web-clipper/lib/Readability-readerable.js b/apps/web-clipper/lib/Readability-readerable.js deleted file mode 100644 index 64be5e15e..000000000 --- a/apps/web-clipper/lib/Readability-readerable.js +++ /dev/null @@ -1,108 +0,0 @@ -/* eslint-env es6:false */ -/* - * Copyright (c) 2010 Arc90 Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - * This code is heavily based on Arc90's readability.js (1.7.1) script - * available at: http://code.google.com/p/arc90labs-readability - */ - -var REGEXPS = { - // NOTE: These two regular expressions are duplicated in - // Readability.js. Please keep both copies in sync. - unlikelyCandidates: /-ad-|ai2html|banner|breadcrumbs|combx|comment|community|cover-wrap|disqus|extra|footer|gdpr|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote/i, - okMaybeItsACandidate: /and|article|body|column|content|main|shadow/i, -}; - -function isNodeVisible(node) { - // Have to null-check node.style and node.className.indexOf to deal with SVG and MathML nodes. - return (!node.style || node.style.display != "none") - && !node.hasAttribute("hidden") - //check for "fallback-image" so that wikimedia math images are displayed - && (!node.hasAttribute("aria-hidden") || node.getAttribute("aria-hidden") != "true" || (node.className && node.className.indexOf && node.className.indexOf("fallback-image") !== -1)); -} - -/** - * Decides whether or not the document is reader-able without parsing the whole thing. - * @param {Object} options Configuration object. - * @param {number} [options.minContentLength=140] The minimum node content length used to decide if the document is readerable. - * @param {number} [options.minScore=20] The minumum cumulated 'score' used to determine if the document is readerable. - * @param {Function} [options.visibilityChecker=isNodeVisible] The function used to determine if a node is visible. - * @return {boolean} Whether or not we suspect Readability.parse() will suceeed at returning an article object. - */ -function isProbablyReaderable(doc, options = {}) { - // For backward compatibility reasons 'options' can either be a configuration object or the function used - // to determine if a node is visible. - if (typeof options == "function") { - options = { visibilityChecker: options }; - } - - var defaultOptions = { minScore: 20, minContentLength: 140, visibilityChecker: isNodeVisible }; - options = Object.assign(defaultOptions, options); - - var nodes = doc.querySelectorAll("p, pre, article"); - - // Get
nodes which have
node(s) and append them into the `nodes` variable. - // Some articles' DOM structures might look like - //
- // Sentences
- //
- // Sentences
- //
- var brNodes = doc.querySelectorAll("div > br"); - if (brNodes.length) { - var set = new Set(nodes); - [].forEach.call(brNodes, function (node) { - set.add(node.parentNode); - }); - nodes = Array.from(set); - } - - var score = 0; - // This is a little cheeky, we use the accumulator 'score' to decide what to return from - // this callback: - return [].some.call(nodes, function (node) { - if (!options.visibilityChecker(node)) { - return false; - } - - var matchString = node.className + " " + node.id; - if (REGEXPS.unlikelyCandidates.test(matchString) && - !REGEXPS.okMaybeItsACandidate.test(matchString)) { - return false; - } - - if (node.matches("li p")) { - return false; - } - - var textContentLength = node.textContent.trim().length; - if (textContentLength < options.minContentLength) { - return false; - } - - score += Math.sqrt(textContentLength - options.minContentLength); - - if (score > options.minScore) { - return true; - } - return false; - }); -} - -if (typeof module === "object") { - module.exports = isProbablyReaderable; -} From 75e88c69bd0648fb2f587832b18873153dc3db45 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 12:12:07 +0200 Subject: [PATCH 18/47] fix(web-clipper): createLink not defined in popup --- apps/web-clipper/entrypoints/content/index.js | 14 +------------- apps/web-clipper/entrypoints/popup/index.html | 1 - apps/web-clipper/entrypoints/popup/popup.js | 2 +- apps/web-clipper/utils.js | 12 ++++++++++++ 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/apps/web-clipper/entrypoints/content/index.js b/apps/web-clipper/entrypoints/content/index.js index 973172b4e..e5c364247 100644 --- a/apps/web-clipper/entrypoints/content/index.js +++ b/apps/web-clipper/entrypoints/content/index.js @@ -1,4 +1,4 @@ -import { getBaseUrl, getPageLocationOrigin, randomString } from "../../utils.js"; +import { createLink, getBaseUrl, getPageLocationOrigin, randomString } from "../../utils.js"; import Readability from "../../lib/Readability.js"; export default defineContentScript({ @@ -237,18 +237,6 @@ export default defineContentScript({ return images; } - function createLink(clickAction, text, color = "lightskyblue") { - const link = document.createElement('a'); - link.href = "javascript:"; - link.style.color = color; - link.appendChild(document.createTextNode(text)); - link.addEventListener("click", () => { - browser.runtime.sendMessage(null, clickAction) - }); - - return link - } - async function prepareMessageResponse(message) { console.info('Message: ' + message.name); diff --git a/apps/web-clipper/entrypoints/popup/index.html b/apps/web-clipper/entrypoints/popup/index.html index 7bd985801..0479588e7 100644 --- a/apps/web-clipper/entrypoints/popup/index.html +++ b/apps/web-clipper/entrypoints/popup/index.html @@ -50,7 +50,6 @@ - diff --git a/apps/web-clipper/entrypoints/popup/popup.js b/apps/web-clipper/entrypoints/popup/popup.js index be571e428..775a3ec0c 100644 --- a/apps/web-clipper/entrypoints/popup/popup.js +++ b/apps/web-clipper/entrypoints/popup/popup.js @@ -1,4 +1,4 @@ -console.log("Popup script loaded"); +import { createLink } from "../../utils"; async function sendMessage(message) { try { diff --git a/apps/web-clipper/utils.js b/apps/web-clipper/utils.js index aab69e12c..a69a00dba 100644 --- a/apps/web-clipper/utils.js +++ b/apps/web-clipper/utils.js @@ -26,3 +26,15 @@ export function getPageLocationOrigin() { // but for file:// protocol this is browser dependant and in particular Firefox returns "null" in this case. return location.protocol === 'file:' ? 'file://' : location.origin; } + +export function createLink(clickAction, text, color = "lightskyblue") { + const link = document.createElement('a'); + link.href = "javascript:"; + link.style.color = color; + link.appendChild(document.createTextNode(text)); + link.addEventListener("click", () => { + browser.runtime.sendMessage(null, clickAction) + }); + + return link +} From 423038100e151c826a3bf8b00889d413900c60a1 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 12:12:51 +0200 Subject: [PATCH 19/47] fix(web-clipper): undefined variable in popup --- apps/web-clipper/entrypoints/popup/popup.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/web-clipper/entrypoints/popup/popup.js b/apps/web-clipper/entrypoints/popup/popup.js index 775a3ec0c..ec5ee67f8 100644 --- a/apps/web-clipper/entrypoints/popup/popup.js +++ b/apps/web-clipper/entrypoints/popup/popup.js @@ -161,8 +161,7 @@ browser.runtime.onMessage.addListener(request => { if (searchNote.status === 'found'){ const a = createLink({name: 'openNoteInTrilium', noteId: searchNote.noteId}, "Open in Trilium.") - noteFound = `Already visited website!`; - $alreadyVisited.html(noteFound); + $alreadyVisited.text(`Already visited website!`); $alreadyVisited[0].appendChild(a); }else{ $alreadyVisited.html(''); From 2e144fac5eb8b94ccbc1c124bfe4065ae412f091 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 12:35:31 +0200 Subject: [PATCH 20/47] chore(web-clipper): set up for TypeScript --- apps/web-clipper/tsconfig.json | 6 ++++++ apps/web-clipper/{wxt.config.js => wxt.config.ts} | 2 +- package.json | 2 +- tsconfig.json | 3 +++ 4 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 apps/web-clipper/tsconfig.json rename apps/web-clipper/{wxt.config.js => wxt.config.ts} (93%) diff --git a/apps/web-clipper/tsconfig.json b/apps/web-clipper/tsconfig.json new file mode 100644 index 000000000..bed55354b --- /dev/null +++ b/apps/web-clipper/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": [ + "../../tsconfig.base.json", + "./.wxt/tsconfig.json" + ] +} \ No newline at end of file diff --git a/apps/web-clipper/wxt.config.js b/apps/web-clipper/wxt.config.ts similarity index 93% rename from apps/web-clipper/wxt.config.js rename to apps/web-clipper/wxt.config.ts index ec61bac13..7a3ec383c 100644 --- a/apps/web-clipper/wxt.config.js +++ b/apps/web-clipper/wxt.config.ts @@ -1,4 +1,4 @@ -import { defineConfig } from "vite"; +import { defineConfig } from "wxt"; export default defineConfig({ modules: ['@wxt-dev/auto-icons'], diff --git a/package.json b/package.json index e71d37e9b..7ec68ed23 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "dev:linter-check": "cross-env NODE_OPTIONS=--max_old_space_size=4096 eslint .", "dev:linter-fix": "cross-env NODE_OPTIONS=--max_old_space_size=4096 eslint . --fix", "postinstall": "tsx scripts/electron-rebuild.mts && pnpm prepare", - "prepare": "pnpm run --filter pdfjs-viewer --filter share-theme build" + "prepare": "pnpm run --filter pdfjs-viewer --filter share-theme build && pnpm run --filter web-clipper postinstall" }, "private": true, "devDependencies": { diff --git a/tsconfig.json b/tsconfig.json index fb9d2774a..9fc01bb4d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,6 +24,9 @@ { "path": "./apps/website" }, + { + "path": "./apps/web-clipper" + }, { "path": "./apps/dump-db" }, From 266494ba8c54aea831e79b15ff6a46901baa96e8 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 13:20:27 +0200 Subject: [PATCH 21/47] chore(web-clipper): port most files to TypeScript --- .../background/trilium_server_facade.js | 223 --------------- .../background/trilium_server_facade.ts | 253 ++++++++++++++++++ .../content/{index.js => index.ts} | 98 +++---- .../entrypoints/options/index.html | 2 +- .../options/{index.js => index.ts} | 22 +- apps/web-clipper/entrypoints/popup/index.html | 2 +- .../entrypoints/popup/{popup.js => popup.ts} | 12 +- apps/web-clipper/types.d.ts | 7 + 8 files changed, 329 insertions(+), 290 deletions(-) delete mode 100644 apps/web-clipper/entrypoints/background/trilium_server_facade.js create mode 100644 apps/web-clipper/entrypoints/background/trilium_server_facade.ts rename apps/web-clipper/entrypoints/content/{index.js => index.ts} (79%) rename apps/web-clipper/entrypoints/options/{index.js => index.ts} (77%) rename apps/web-clipper/entrypoints/popup/{popup.js => popup.ts} (95%) create mode 100644 apps/web-clipper/types.d.ts diff --git a/apps/web-clipper/entrypoints/background/trilium_server_facade.js b/apps/web-clipper/entrypoints/background/trilium_server_facade.js deleted file mode 100644 index b1a56a7ec..000000000 --- a/apps/web-clipper/entrypoints/background/trilium_server_facade.js +++ /dev/null @@ -1,223 +0,0 @@ -const PROTOCOL_VERSION_MAJOR = 1; - -export function isDevEnv() { - const manifest = browser.runtime.getManifest(); - - return manifest.name.endsWith('(dev)'); -} - -export default class TriliumServerFacade { - constructor() { - this.triggerSearchForTrilium(); - - // continually scan for changes (if e.g. desktop app is started after browser) - setInterval(() => this.triggerSearchForTrilium(), 60 * 1000); - } - - async sendTriliumSearchStatusToPopup() { - try { - await browser.runtime.sendMessage({ - name: "trilium-search-status", - triliumSearch: this.triliumSearch - }); - } - catch (e) {} // nothing might be listening - } - async sendTriliumSearchNoteToPopup(){ - try{ - await browser.runtime.sendMessage({ - name: "trilium-previously-visited", - searchNote: this.triliumSearchNote - }) - - } - catch (e) {} // nothing might be listening - } - - setTriliumSearchNote(st){ - this.triliumSearchNote = st; - this.sendTriliumSearchNoteToPopup(); - } - - setTriliumSearch(ts) { - this.triliumSearch = ts; - - this.sendTriliumSearchStatusToPopup(); - } - - setTriliumSearchWithVersionCheck(json, resp) { - const [major, minor] = json.protocolVersion - .split(".") - .map(chunk => parseInt(chunk)); - - // minor version is intended to be used to dynamically limit features provided by extension - // if some specific Trilium API is not supported. So far not needed. - - if (major !== PROTOCOL_VERSION_MAJOR) { - this.setTriliumSearch({ - status: 'version-mismatch', - extensionMajor: PROTOCOL_VERSION_MAJOR, - triliumMajor: major - }); - } - else { - this.setTriliumSearch(resp); - } - } - - async triggerSearchForTrilium() { - this.setTriliumSearch({ status: 'searching' }); - - try { - const port = await this.getPort(); - - console.debug('Trying port ' + port); - - const resp = await fetch(`http://127.0.0.1:${port}/api/clipper/handshake`); - - const text = await resp.text(); - - console.log("Received response:", text); - - const json = JSON.parse(text); - - if (json.appName === 'trilium') { - this.setTriliumSearchWithVersionCheck(json, { - status: 'found-desktop', - port: port, - url: 'http://127.0.0.1:' + port - }); - - return; - } - } - catch (error) { - // continue - } - - const {triliumServerUrl} = await browser.storage.sync.get("triliumServerUrl"); - const {authToken} = await browser.storage.sync.get("authToken"); - - if (triliumServerUrl && authToken) { - try { - const resp = await fetch(triliumServerUrl + '/api/clipper/handshake', { - headers: { - Authorization: authToken - } - }); - - const text = await resp.text(); - - console.log("Received response:", text); - - const json = JSON.parse(text); - - if (json.appName === 'trilium') { - this.setTriliumSearchWithVersionCheck(json, { - status: 'found-server', - url: triliumServerUrl, - token: authToken - }); - - return; - } - } - catch (e) { - console.log("Request to the configured server instance failed with:", e); - } - } - - // if all above fails it's not found - this.setTriliumSearch({ status: 'not-found' }); - } - - async triggerSearchNoteByUrl(noteUrl) { - const resp = await this.callService('GET', 'notes-by-url/' + encodeURIComponent(noteUrl)) - let newStatus = { - status: 'not-found', - noteId: null - } - if (resp && resp.noteId) { - newStatus.noteId = resp.noteId; - newStatus.status = 'found'; - } - this.setTriliumSearchNote(newStatus); - } - async waitForTriliumSearch() { - return new Promise((res, rej) => { - const checkStatus = () => { - if (this.triliumSearch.status === "searching") { - setTimeout(checkStatus, 500); - } - else if (this.triliumSearch.status === 'not-found') { - rej(new Error("Trilium instance has not been found.")); - } - else { - res(); - } - }; - - checkStatus(); - }); - } - - async getPort() { - const {triliumDesktopPort} = await browser.storage.sync.get("triliumDesktopPort"); - - if (triliumDesktopPort) { - return parseInt(triliumDesktopPort); - } - else { - return isDevEnv() ? 37740 : 37840; - } - } - - async callService(method, path, body) { - const fetchOptions = { - method: method, - headers: { - 'Content-Type': 'application/json' - }, - }; - - if (body) { - fetchOptions.body = typeof body === 'string' ? body : JSON.stringify(body); - } - - try { - await this.waitForTriliumSearch(); - - fetchOptions.headers.Authorization = this.triliumSearch.token || ""; - fetchOptions.headers['trilium-local-now-datetime'] = this.localNowDateTime(); - - const url = this.triliumSearch.url + "/api/clipper/" + path; - - console.log(`Sending ${method} request to ${url}`); - - const response = await fetch(url, fetchOptions); - - if (!response.ok) { - throw new Error(await response.text()); - } - - return await response.json(); - } - catch (e) { - console.log("Sending request to trilium failed", e); - - toast('Your request failed because we could not contact Trilium instance. Please make sure Trilium is running and is accessible.'); - - return null; - } - } - - localNowDateTime() { - const date = new Date(); - const off = date.getTimezoneOffset(); - const absoff = Math.abs(off); - return (new Date(date.getTime() - off * 60 * 1000).toISOString().substr(0,23).replace("T", " ") + - (off > 0 ? '-' : '+') + - (absoff / 60).toFixed(0).padStart(2,'0') + ':' + - (absoff % 60).toString().padStart(2,'0')); - } -} diff --git a/apps/web-clipper/entrypoints/background/trilium_server_facade.ts b/apps/web-clipper/entrypoints/background/trilium_server_facade.ts new file mode 100644 index 000000000..f9854437f --- /dev/null +++ b/apps/web-clipper/entrypoints/background/trilium_server_facade.ts @@ -0,0 +1,253 @@ +const PROTOCOL_VERSION_MAJOR = 1; + +export function isDevEnv() { + const manifest = browser.runtime.getManifest(); + + return manifest.name.endsWith('(dev)'); +} + +type TriliumSearchStatus = { + status: "searching"; +} | { + status: "not-found" +} | { + status: "found-desktop", + port: number; + url: string; +} | { + status: "found-server", + url: string; + token: string; +} | { + status: "version-mismatch"; + extensionMajor: number; + triliumMajor: number; +}; + +type TriliumSearchNoteStatus = { + status: "not-found", + noteId: null +} | { + status: "found", + noteId: string +}; + +export default class TriliumServerFacade { + private triliumSearch?: TriliumSearchStatus; + private triliumSearchNote?: TriliumSearchNoteStatus; + + constructor() { + this.triggerSearchForTrilium(); + + // continually scan for changes (if e.g. desktop app is started after browser) + setInterval(() => this.triggerSearchForTrilium(), 60 * 1000); + } + + async sendTriliumSearchStatusToPopup() { + try { + await browser.runtime.sendMessage({ + name: "trilium-search-status", + triliumSearch: this.triliumSearch + }); + } + catch (e) {} // nothing might be listening + } + async sendTriliumSearchNoteToPopup(){ + try{ + await browser.runtime.sendMessage({ + name: "trilium-previously-visited", + searchNote: this.triliumSearchNote + }); + + } + catch (e) {} // nothing might be listening + } + + setTriliumSearchNote(st){ + this.triliumSearchNote = st; + this.sendTriliumSearchNoteToPopup(); + } + + setTriliumSearch(ts: TriliumSearchStatus) { + this.triliumSearch = ts; + + this.sendTriliumSearchStatusToPopup(); + } + + setTriliumSearchWithVersionCheck(json: { protocolVersion: string }, resp: TriliumSearchStatus) { + const [major, minor] = json.protocolVersion + .split(".") + .map(chunk => parseInt(chunk, 10)); + + // minor version is intended to be used to dynamically limit features provided by extension + // if some specific Trilium API is not supported. So far not needed. + + if (major !== PROTOCOL_VERSION_MAJOR) { + this.setTriliumSearch({ + status: 'version-mismatch', + extensionMajor: PROTOCOL_VERSION_MAJOR, + triliumMajor: major + }); + } + else { + this.setTriliumSearch(resp); + } + } + + async triggerSearchForTrilium() { + this.setTriliumSearch({ status: 'searching' }); + + try { + const port = await this.getPort(); + + console.debug(`Trying port ${ port}`); + + const resp = await fetch(`http://127.0.0.1:${port}/api/clipper/handshake`); + + const text = await resp.text(); + + console.log("Received response:", text); + + const json = JSON.parse(text); + + if (json.appName === 'trilium') { + this.setTriliumSearchWithVersionCheck(json, { + status: 'found-desktop', + port, + url: `http://127.0.0.1:${port}` + }); + + return; + } + } + catch (error) { + // continue + } + + const {triliumServerUrl} = await browser.storage.sync.get<{ triliumServerUrl: string }>("triliumServerUrl"); + const {authToken} = await browser.storage.sync.get<{ authToken: string }>("authToken"); + + if (triliumServerUrl && authToken) { + try { + const resp = await fetch(`${triliumServerUrl }/api/clipper/handshake`, { + headers: { + Authorization: authToken + } + }); + + const text = await resp.text(); + + console.log("Received response:", text); + + const json = JSON.parse(text); + + if (json.appName === 'trilium') { + this.setTriliumSearchWithVersionCheck(json, { + status: 'found-server', + url: triliumServerUrl, + token: authToken + }); + + return; + } + } + catch (e) { + console.log("Request to the configured server instance failed with:", e); + } + } + + // if all above fails it's not found + this.setTriliumSearch({ status: 'not-found' }); + } + + async triggerSearchNoteByUrl(noteUrl) { + const resp = await this.callService('GET', `notes-by-url/${encodeURIComponent(noteUrl)}`); + let newStatus: TriliumSearchNoteStatus; + if (resp && resp.noteId) { + newStatus = { + status: 'found', + noteId: resp.noteId, + }; + } else { + newStatus = { + status: 'not-found', + noteId: null + }; + } + this.setTriliumSearchNote(newStatus); + } + async waitForTriliumSearch() { + return new Promise((res, rej) => { + const checkStatus = () => { + if (this.triliumSearch?.status === "searching") { + setTimeout(checkStatus, 500); + } else if (this.triliumSearch?.status === 'not-found') { + rej(new Error("Trilium instance has not been found.")); + } else { + res(); + } + }; + + checkStatus(); + }); + } + + async getPort() { + const {triliumDesktopPort} = await browser.storage.sync.get<{ triliumDesktopPort: string }>("triliumDesktopPort"); + + if (triliumDesktopPort) { + return parseInt(triliumDesktopPort, 10); + } + + return isDevEnv() ? 37740 : 37840; + } + + async callService(method: string, path: string, body?: string | object) { + await this.waitForTriliumSearch(); + if (!this.triliumSearch || (this.triliumSearch.status !== 'found-desktop' && this.triliumSearch.status !== 'found-server')) return; + + try { + const fetchOptions: RequestInit = { + method, + headers: { + Authorization: "token" in this.triliumSearch ? this.triliumSearch.token ?? "" : "", + 'Content-Type': 'application/json', + 'trilium-local-now-datetime': this.localNowDateTime() + }, + }; + + if (body) { + fetchOptions.body = typeof body === 'string' ? body : JSON.stringify(body); + } + + const url = `${this.triliumSearch.url}/api/clipper/${path}`; + + console.log(`Sending ${method} request to ${url}`); + + const response = await fetch(url, fetchOptions); + + if (!response.ok) { + throw new Error(await response.text()); + } + + return await response.json(); + } + catch (e) { + console.log("Sending request to trilium failed", e); + + window.showToast('Your request failed because we could not contact Trilium instance. Please make sure Trilium is running and is accessible.'); + + return null; + } + } + + localNowDateTime() { + const date = new Date(); + const off = date.getTimezoneOffset(); + const absoff = Math.abs(off); + return (`${new Date(date.getTime() - off * 60 * 1000).toISOString().substr(0,23).replace("T", " ") + + (off > 0 ? '-' : '+') + + (absoff / 60).toFixed(0).padStart(2,'0') }:${ + (absoff % 60).toString().padStart(2,'0')}`); + } +} diff --git a/apps/web-clipper/entrypoints/content/index.js b/apps/web-clipper/entrypoints/content/index.ts similarity index 79% rename from apps/web-clipper/entrypoints/content/index.js rename to apps/web-clipper/entrypoints/content/index.ts index e5c364247..0ee35ef0d 100644 --- a/apps/web-clipper/entrypoints/content/index.js +++ b/apps/web-clipper/entrypoints/content/index.ts @@ -1,5 +1,5 @@ -import { createLink, getBaseUrl, getPageLocationOrigin, randomString } from "../../utils.js"; import Readability from "../../lib/Readability.js"; +import { createLink, getBaseUrl, getPageLocationOrigin, randomString } from "../../utils.js"; export default defineContentScript({ matches: [ @@ -19,10 +19,10 @@ export default defineContentScript({ if (url.indexOf('//') === 0) { return location.protocol + url; } else if (url[0] === '/') { - return location.protocol + '//' + location.host + url; - } else { - return getBaseUrl() + '/' + url; + return `${location.protocol}//${location.host}${url}`; } + return `${getBaseUrl()}/${url}`; + } function pageTitle() { @@ -47,40 +47,37 @@ export default defineContentScript({ return { title: article.title, body: article.content, - } + }; } function getDocumentDates() { - var dates = { - publishedDate: null, - modifiedDate: null, - }; + let publishedDate: Date | null = null; + let modifiedDate: Date | null = null; - const articlePublishedTime = document.querySelector("meta[property='article:published_time']"); - if (articlePublishedTime && articlePublishedTime.getAttribute('content')) { - dates.publishedDate = new Date(articlePublishedTime.getAttribute('content')); + const articlePublishedTime = document.querySelector("meta[property='article:published_time']")?.getAttribute('content'); + if (articlePublishedTime && articlePublishedTime) { + publishedDate = new Date(articlePublishedTime); } - const articleModifiedTime = document.querySelector("meta[property='article:modified_time']"); - if (articleModifiedTime && articleModifiedTime.getAttribute('content')) { - dates.modifiedDate = new Date(articleModifiedTime.getAttribute('content')); + const articleModifiedTime = document.querySelector("meta[property='article:modified_time']")?.getAttribute('content'); + if (articleModifiedTime && articleModifiedTime) { + modifiedDate = new Date(articleModifiedTime); } // TODO: if we didn't get dates from meta, then try to get them from JSON-LD - - return dates; + return { publishedDate, modifiedDate }; } function getRectangleArea() { - return new Promise((resolve, reject) => { + return new Promise((resolve) => { const overlay = document.createElement('div'); overlay.style.opacity = '0.6'; overlay.style.background = 'black'; overlay.style.width = '100%'; overlay.style.height = '100%'; - overlay.style.zIndex = 99999999; - overlay.style.top = 0; - overlay.style.left = 0; + overlay.style.zIndex = "99999999"; + overlay.style.top = "0"; + overlay.style.left = "0"; overlay.style.position = 'fixed'; document.body.appendChild(overlay); @@ -92,15 +89,15 @@ export default defineContentScript({ messageComp.style.position = 'fixed'; messageComp.style.opacity = '0.95'; messageComp.style.fontSize = '14px'; - messageComp.style.width = messageCompWidth + 'px'; - messageComp.style.maxWidth = messageCompWidth + 'px'; + messageComp.style.width = `${messageCompWidth }px`; + messageComp.style.maxWidth = `${messageCompWidth }px`; messageComp.style.border = '1px solid black'; messageComp.style.background = 'white'; messageComp.style.color = 'black'; messageComp.style.top = '10px'; messageComp.style.textAlign = 'center'; messageComp.style.padding = '10px'; - messageComp.style.left = Math.round(document.body.clientWidth / 2 - messageCompWidth / 2) + 'px'; + messageComp.style.left = `${Math.round(document.body.clientWidth / 2 - messageCompWidth / 2) }px`; messageComp.style.zIndex = overlay.style.zIndex + 1; messageComp.textContent = 'Drag and release to capture a screenshot'; @@ -112,9 +109,9 @@ export default defineContentScript({ selection.style.border = '1px solid red'; selection.style.background = 'white'; selection.style.border = '2px solid black'; - selection.style.zIndex = overlay.style.zIndex - 1; - selection.style.top = 0; - selection.style.left = 0; + selection.style.zIndex = String(parseInt(overlay.style.zIndex, 10) - 1); + selection.style.top = "0"; + selection.style.left = "0"; selection.style.position = 'fixed'; document.body.appendChild(selection); @@ -122,17 +119,19 @@ export default defineContentScript({ messageComp.focus(); // we listen on keypresses on this element to cancel on escape let isDragging = false; - let draggingStartPos = null; - let selectionArea = {}; + let draggingStartPos: {x: number, y: number} | null = null; + let selectionArea: {x?: number, y?: number, width?: number, height?: number} = {}; function updateSelection() { - selection.style.left = selectionArea.x + 'px'; - selection.style.top = selectionArea.y + 'px'; - selection.style.width = selectionArea.width + 'px'; - selection.style.height = selectionArea.height + 'px'; + selection.style.left = `${selectionArea.x}px`; + selection.style.top = `${selectionArea.y}px`; + selection.style.width = `${selectionArea.width}px`; + selection.style.height = `${selectionArea.height}px`; } function setSelectionSizeFromMouse(event) { + if (!draggingStartPos) return; + if (event.clientX < draggingStartPos.x) { selectionArea.x = event.clientX; } @@ -209,8 +208,8 @@ export default defineContentScript({ } } - function getImages(container) { - const images = []; + function getImages(container: HTMLElement) { + const images: {imageId: string, src: string}[] = []; for (const img of container.getElementsByTagName('img')) { if (!img.src) { @@ -226,7 +225,7 @@ export default defineContentScript({ const imageId = randomString(20); images.push({ - imageId: imageId, + imageId, src: img.src }); @@ -237,16 +236,16 @@ export default defineContentScript({ return images; } - async function prepareMessageResponse(message) { - console.info('Message: ' + message.name); + async function prepareMessageResponse(message: {name: string, noteId?: string, message?: string, tabIds?: string[]}) { + console.info(`Message: ${ message.name}`); if (message.name === "toast") { let messageText; if (message.noteId) { messageText = document.createElement('p'); - messageText.setAttribute("style", "padding: 0; margin: 0; font-size: larger;") - messageText.appendChild(document.createTextNode(message.message + " ")); + messageText.setAttribute("style", "padding: 0; margin: 0; font-size: larger;"); + messageText.appendChild(document.createTextNode(`${message.message } `)); messageText.appendChild(createLink( {name: 'openNoteInTrilium', noteId: message.noteId}, "Open in Trilium." @@ -268,7 +267,7 @@ export default defineContentScript({ await import("../../lib/toast"); - showToast(messageText, { + window.showToast(messageText, { settings: { duration: 7000 } @@ -278,6 +277,9 @@ export default defineContentScript({ const container = document.createElement('div'); const selection = window.getSelection(); + if (!selection || selection.rangeCount === 0) { + throw new Error('No selection available to clip'); + } for (let i = 0; i < selection.rangeCount; i++) { const range = selection.getRangeAt(i); @@ -292,7 +294,7 @@ export default defineContentScript({ return { title: pageTitle(), content: container.innerHTML, - images: images, + images, pageUrl: getPageLocationOrigin() + location.pathname + location.search + location.hash }; @@ -307,26 +309,26 @@ export default defineContentScript({ const images = getImages(body); - var labels = {}; + const labels = {}; const dates = getDocumentDates(); if (dates.publishedDate) { labels['publishedDate'] = dates.publishedDate.toISOString().substring(0, 10); } if (dates.modifiedDate) { - labels['modifiedDate'] = dates.publishedDate.toISOString().substring(0, 10); + labels['modifiedDate'] = dates.modifiedDate.toISOString().substring(0, 10); } return { - title: title, + title, content: body.innerHTML, - images: images, + images, pageUrl: getPageLocationOrigin() + location.pathname + location.search, clipType: 'page', - labels: labels + labels }; } else { - throw new Error('Unknown command: ' + JSON.stringify(message)); + throw new Error(`Unknown command: ${ JSON.stringify(message)}`); } } diff --git a/apps/web-clipper/entrypoints/options/index.html b/apps/web-clipper/entrypoints/options/index.html index 16e084ded..4aa2969c2 100644 --- a/apps/web-clipper/entrypoints/options/index.html +++ b/apps/web-clipper/entrypoints/options/index.html @@ -56,7 +56,7 @@ - + diff --git a/apps/web-clipper/entrypoints/options/index.js b/apps/web-clipper/entrypoints/options/index.ts similarity index 77% rename from apps/web-clipper/entrypoints/options/index.js rename to apps/web-clipper/entrypoints/options/index.ts index 03c05822c..da8fa1850 100644 --- a/apps/web-clipper/entrypoints/options/index.js +++ b/apps/web-clipper/entrypoints/options/index.ts @@ -17,8 +17,8 @@ function showSuccess(message) { async function saveTriliumServerSetup(e) { e.preventDefault(); - if ($triliumServerUrl.val().trim().length === 0 - || $triliumServerPassword.val().trim().length === 0) { + if (($triliumServerUrl.val() as string | undefined)?.trim().length === 0 + || ($triliumServerPassword.val() as string | undefined)?.trim().length === 0) { showError("One or more mandatory inputs are missing. Please fill in server URL and password."); return; @@ -27,7 +27,7 @@ async function saveTriliumServerSetup(e) { let resp; try { - resp = await fetch($triliumServerUrl.val() + '/api/login/token', { + resp = await fetch(`${$triliumServerUrl.val() }/api/login/token`, { method: "POST", headers: { 'Accept': 'application/json', @@ -39,7 +39,8 @@ async function saveTriliumServerSetup(e) { }); } catch (e) { - showError("Unknown error: " + e.message); + const message = e instanceof Error ? e.message : String(e); + showError(`Unknown error: ${message}`); return; } @@ -47,7 +48,7 @@ async function saveTriliumServerSetup(e) { showError("Incorrect credentials."); } else if (resp.status !== 200) { - showError("Unrecognised response with status code " + resp.status); + showError(`Unrecognised response with status code ${ resp.status}`); } else { const json = await resp.json(); @@ -89,8 +90,8 @@ const $triilumDesktopSetupForm = $("#trilium-desktop-setup-form"); $triilumDesktopSetupForm.on("submit", e => { e.preventDefault(); - const port = $triliumDesktopPort.val().trim(); - const portNum = parseInt(port); + const port = ($triliumDesktopPort.val() as string | undefined ?? "").trim(); + const portNum = parseInt(port, 10); if (port && (isNaN(portNum) || portNum <= 0 || portNum >= 65536)) { showError(`Please enter valid port number.`); @@ -105,8 +106,8 @@ $triilumDesktopSetupForm.on("submit", e => { }); async function restoreOptions() { - const {triliumServerUrl} = await browser.storage.sync.get("triliumServerUrl"); - const {authToken} = await browser.storage.sync.get("authToken"); + const {triliumServerUrl} = await browser.storage.sync.get<{ triliumServerUrl: string }>("triliumServerUrl"); + const {authToken} = await browser.storage.sync.get<{ authToken: string }>("authToken"); $errorMessage.hide(); $successMessage.hide(); @@ -127,8 +128,7 @@ async function restoreOptions() { $triliumServerConfiguredDiv.hide(); } - const {triliumDesktopPort} = await browser.storage.sync.get("triliumDesktopPort"); - + const {triliumDesktopPort} = await browser.storage.sync.get<{ triliumDesktopPort: string }>("triliumDesktopPort"); $triliumDesktopPort.val(triliumDesktopPort); } diff --git a/apps/web-clipper/entrypoints/popup/index.html b/apps/web-clipper/entrypoints/popup/index.html index 0479588e7..a1051659f 100644 --- a/apps/web-clipper/entrypoints/popup/index.html +++ b/apps/web-clipper/entrypoints/popup/index.html @@ -48,7 +48,7 @@ - + diff --git a/apps/web-clipper/entrypoints/popup/popup.js b/apps/web-clipper/entrypoints/popup/popup.ts similarity index 95% rename from apps/web-clipper/entrypoints/popup/popup.js rename to apps/web-clipper/entrypoints/popup/popup.ts index ec5ee67f8..8f8220de1 100644 --- a/apps/web-clipper/entrypoints/popup/popup.js +++ b/apps/web-clipper/entrypoints/popup/popup.ts @@ -38,9 +38,9 @@ $saveTabsButton.on("click", () => sendMessage({name: 'save-tabs'})); const $saveLinkWithNoteWrapper = $("#save-link-with-note-wrapper"); const $textNote = $("#save-link-with-note-textarea"); -const $keepTitle = $("#keep-title-checkbox"); +const $keepTitle = $("#keep-title-checkbox"); -$textNote.on('keypress', function (event) { +$textNote.on('keypress', (event) => { if ((event.which === 10 || event.which === 13) && event.ctrlKey) { saveLinkWithNote(); return false; @@ -63,7 +63,7 @@ $("#cancel-button").on("click", () => { }); async function saveLinkWithNote() { - const textNoteVal = $textNote.val().trim(); + const textNoteVal = ($textNote.val() as string | undefined ?? "").trim(); let title, content; if (!textNoteVal) { @@ -111,7 +111,7 @@ function escapeHtml(string) { const htmlWithPars = pre.innerHTML.replace(/\n/g, "

"); - return '

' + htmlWithPars + '

'; + return `

${ htmlWithPars }

`; } const $connectionStatus = $("#connection-status"); @@ -160,7 +160,7 @@ browser.runtime.onMessage.addListener(request => { const {searchNote} = request; if (searchNote.status === 'found'){ const a = createLink({name: 'openNoteInTrilium', noteId: searchNote.noteId}, - "Open in Trilium.") + "Open in Trilium."); $alreadyVisited.text(`Already visited website!`); $alreadyVisited[0].appendChild(a); }else{ @@ -176,7 +176,7 @@ const $checkConnectionButton = $("#check-connection-button"); $checkConnectionButton.on("click", () => { browser.runtime.sendMessage({ name: "trigger-trilium-search" - }) + }); }); $(() => browser.runtime.sendMessage({name: "send-trilium-search-status"})); diff --git a/apps/web-clipper/types.d.ts b/apps/web-clipper/types.d.ts new file mode 100644 index 000000000..4d83d0f28 --- /dev/null +++ b/apps/web-clipper/types.d.ts @@ -0,0 +1,7 @@ +interface Window { + showToast(message: string, opts?: { + settings?: { + duration: number; + } + }): void; +} From 4011771b64e3deae02b6d6ef067b7749abcea6ea Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 13:58:23 +0200 Subject: [PATCH 22/47] chore(web-clipper): port the remaining files to TypeScript --- apps/web-clipper/build.js | 1 - apps/web-clipper/build.ts | 1 + .../background/{index.js => index.ts} | 93 +++++++++---------- apps/web-clipper/entrypoints/popup/index.html | 1 - apps/web-clipper/{utils.js => utils.ts} | 14 +-- 5 files changed, 51 insertions(+), 59 deletions(-) delete mode 100644 apps/web-clipper/build.js create mode 100644 apps/web-clipper/build.ts rename apps/web-clipper/entrypoints/background/{index.js => index.ts} (83%) rename apps/web-clipper/{utils.js => utils.ts} (75%) diff --git a/apps/web-clipper/build.js b/apps/web-clipper/build.js deleted file mode 100644 index 3826b2524..000000000 --- a/apps/web-clipper/build.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = { buildDate:"2022-10-29T15:25:37+02:00", buildRevision: "c9c10a90aa9b94efdf150b0b2fd57f9df5bf2d0a" }; diff --git a/apps/web-clipper/build.ts b/apps/web-clipper/build.ts new file mode 100644 index 000000000..2f5056d47 --- /dev/null +++ b/apps/web-clipper/build.ts @@ -0,0 +1 @@ +export default { buildDate:"2022-10-29T15:25:37+02:00", buildRevision: "c9c10a90aa9b94efdf150b0b2fd57f9df5bf2d0a" }; diff --git a/apps/web-clipper/entrypoints/background/index.js b/apps/web-clipper/entrypoints/background/index.ts similarity index 83% rename from apps/web-clipper/entrypoints/background/index.js rename to apps/web-clipper/entrypoints/background/index.ts index e8d3c6d03..c36aa4899 100644 --- a/apps/web-clipper/entrypoints/background/index.js +++ b/apps/web-clipper/entrypoints/background/index.ts @@ -1,11 +1,13 @@ import { randomString } from "../../utils"; import TriliumServerFacade, { isDevEnv } from "./trilium_server_facade"; +type Rect = { x: number, y: number, width: number, height: number }; + export default defineBackground(() => { const triliumServerFacade = new TriliumServerFacade(); // Keyboard shortcuts - chrome.commands.onCommand.addListener(async function (command) { + browser.commands.onCommand.addListener(async (command) => { if (command == "saveSelection") { await saveSelection(); } else if (command == "saveWholePage") { @@ -21,8 +23,8 @@ export default defineBackground(() => { } }); - function cropImage(newArea, dataUrl) { - return new Promise((resolve, reject) => { + function cropImage(newArea: Rect, dataUrl: string) { + return new Promise((resolve) => { const img = new Image(); img.onload = function () { @@ -31,8 +33,7 @@ export default defineBackground(() => { canvas.height = newArea.height; const ctx = canvas.getContext('2d'); - - ctx.drawImage(img, newArea.x, newArea.y, newArea.width, newArea.height, 0, 0, newArea.width, newArea.height); + ctx?.drawImage(img, newArea.x, newArea.y, newArea.width, newArea.height, 0, 0, newArea.width, newArea.height); resolve(canvas.toDataURL()); }; @@ -41,17 +42,17 @@ export default defineBackground(() => { }); } - async function takeCroppedScreenshot(cropRect) { + async function takeCroppedScreenshot(cropRect: Rect) { const activeTab = await getActiveTab(); const zoom = await browser.tabs.getZoom(activeTab.id) * window.devicePixelRatio; - const newArea = Object.assign({}, cropRect); + const newArea: Rect = Object.assign({}, cropRect); newArea.x *= zoom; newArea.y *= zoom; newArea.width *= zoom; newArea.height *= zoom; - const dataUrl = await browser.tabs.captureVisibleTab(null, { format: 'png' }); + const dataUrl = await browser.tabs.captureVisibleTab({ format: 'png' }); return await cropImage(newArea, dataUrl); } @@ -61,7 +62,7 @@ export default defineBackground(() => { // workaround to save the whole page is to scroll & stitch // example in https://github.com/mrcoles/full-page-screen-capture-chrome-extension // see page.js and popup.js - return await browser.tabs.captureVisibleTab(null, { format: 'png' }); + return await browser.tabs.captureVisibleTab({ format: 'png' }); } browser.runtime.onInstalled.addListener(() => { @@ -134,52 +135,47 @@ export default defineBackground(() => { async function sendMessageToActiveTab(message) { const activeTab = await getActiveTab(); - if (!activeTab) { + if (!activeTab?.id) { throw new Error("No active tab."); } - try { - return await browser.tabs.sendMessage(activeTab.id, message); - } - catch (e) { - throw e; - } + return await browser.tabs.sendMessage(activeTab.id, message); } - function toast(message, noteId = null, tabIds = null) { + function toast(message: string, noteId: string | null = null, tabIds: number[] | null = null) { sendMessageToActiveTab({ name: 'toast', - message: message, - noteId: noteId, - tabIds: tabIds + message, + noteId, + tabIds }); } - function blob2base64(blob) { - return new Promise(resolve => { + function blob2base64(blob: Blob) { + return new Promise(resolve => { const reader = new FileReader(); reader.onloadend = function() { - resolve(reader.result); + resolve(reader.result as string | null); }; reader.readAsDataURL(blob); }); } - async function fetchImage(url) { + async function fetchImage(url: string) { const resp = await fetch(url); const blob = await resp.blob(); return await blob2base64(blob); } - async function postProcessImage(image) { + async function postProcessImage(image: { src: string, dataUrl?: string | null }) { if (image.src.startsWith("data:image/")) { image.dataUrl = image.src; - image.src = "inline." + image.src.substr(11, 3); // this should extract file type - png/jpg + image.src = `inline.${ image.src.substr(11, 3)}`; // this should extract file type - png/jpg } else { try { - image.dataUrl = await fetchImage(image.src, image); + image.dataUrl = await fetchImage(image.src); } catch (e) { console.log(`Cannot fetch image from ${image.src}`); @@ -187,7 +183,7 @@ export default defineBackground(() => { } } - async function postProcessImages(resp) { + async function postProcessImages(resp: { images?: { src: string, dataUrl?: string }[] }) { if (resp.images) { for (const image of resp.images) { await postProcessImage(image); @@ -212,7 +208,7 @@ export default defineBackground(() => { async function getImagePayloadFromSrc(src, pageUrl) { const image = { imageId: randomString(20), - src: src + src }; await postProcessImage(image); @@ -223,7 +219,7 @@ export default defineBackground(() => { title: activeTab.title, content: ``, images: [image], - pageUrl: pageUrl + pageUrl }; } @@ -291,8 +287,8 @@ export default defineBackground(() => { } const resp = await triliumServerFacade.callService('POST', 'notes', { - title: title, - content: content, + title, + content, clipType: 'note', pageUrl: activeTab.url }); @@ -309,27 +305,27 @@ export default defineBackground(() => { async function getTabsPayload(tabs) { let content = ''; const domainsCount = tabs.map(tab => tab.url) .reduce((acc, url) => { - const hostname = new URL(url).hostname - return acc.set(hostname, (acc.get(hostname) || 0) + 1) + const hostname = new URL(url).hostname; + return acc.set(hostname, (acc.get(hostname) || 0) + 1); }, new Map()); let topDomains = [...domainsCount] - .sort((a, b) => {return b[1]-a[1]}) + .sort((a, b) => {return b[1]-a[1];}) .slice(0,3) .map(domain=>domain[0]) - .join(', ') + .join(', '); - if (tabs.length > 3) { topDomains += '...' } + if (tabs.length > 3) { topDomains += '...'; } return { title: `${tabs.length} browser tabs: ${topDomains}`, - content: content, + content, clipType: 'tabs' }; } @@ -340,17 +336,13 @@ export default defineBackground(() => { const payload = await getTabsPayload(tabs); const resp = await triliumServerFacade.callService('POST', 'notes', payload); + if (!resp) return; - if (!resp) { - return; - } - - const tabIds = tabs.map(tab=>{return tab.id}); - + const tabIds = tabs.map(tab => tab.id!).filter(id => id !== undefined) as number[]; toast(`${tabs.length} links have been saved to Trilium.`, resp.noteId, tabIds); } - browser.contextMenus.onClicked.addListener(async function(info, tab) { + browser.contextMenus.onClicked.addListener(async (info, tab) => { if (info.menuItemId === 'trilium-save-selection') { await saveSelection(); } @@ -365,8 +357,9 @@ export default defineBackground(() => { } else if (info.menuItemId === 'trilium-save-link') { const link = document.createElement("a"); + if (!info.linkUrl) return; link.href = info.linkUrl; - // linkText might be available only in firefox + // linkText is not supported in Chrome/Edge; fallback to selected text or URL link.appendChild(document.createTextNode(info.linkText || info.linkUrl)); const activeTab = await getActiveTab(); @@ -395,7 +388,7 @@ export default defineBackground(() => { console.log("Received", request); if (request.name === 'openNoteInTrilium') { - const resp = await triliumServerFacade.callService('POST', 'open/' + request.noteId); + const resp = await triliumServerFacade.callService('POST', `open/${ request.noteId}`); if (!resp) { return; @@ -406,7 +399,7 @@ export default defineBackground(() => { const {triliumServerUrl} = await browser.storage.sync.get("triliumServerUrl"); if (triliumServerUrl) { - const noteUrl = triliumServerUrl + '/#' + request.noteId; + const noteUrl = `${triliumServerUrl }/#${ request.noteId}`; console.log("Opening new tab in browser", noteUrl); @@ -420,7 +413,7 @@ export default defineBackground(() => { } } else if (request.name === 'closeTabs') { - return await browser.tabs.remove(request.tabIds) + return await browser.tabs.remove(request.tabIds); } else if (request.name === 'save-cropped-screenshot') { const activeTab = await getActiveTab(); diff --git a/apps/web-clipper/entrypoints/popup/index.html b/apps/web-clipper/entrypoints/popup/index.html index a1051659f..ee4b13871 100644 --- a/apps/web-clipper/entrypoints/popup/index.html +++ b/apps/web-clipper/entrypoints/popup/index.html @@ -49,7 +49,6 @@ - diff --git a/apps/web-clipper/utils.js b/apps/web-clipper/utils.ts similarity index 75% rename from apps/web-clipper/utils.js rename to apps/web-clipper/utils.ts index a69a00dba..5c1b3efd6 100644 --- a/apps/web-clipper/utils.js +++ b/apps/web-clipper/utils.ts @@ -1,4 +1,4 @@ -export function randomString(len) { +export function randomString(len: number) { let text = ""; const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; @@ -13,9 +13,9 @@ export function getBaseUrl() { let output = getPageLocationOrigin() + location.pathname; if (output[output.length - 1] !== '/') { - output = output.split('/'); - output.pop(); - output = output.join('/'); + const outputArr = output.split('/'); + outputArr.pop(); + output = outputArr.join('/'); } return output; @@ -27,14 +27,14 @@ export function getPageLocationOrigin() { return location.protocol === 'file:' ? 'file://' : location.origin; } -export function createLink(clickAction, text, color = "lightskyblue") { +export function createLink(clickAction: object, text: string, color = "lightskyblue") { const link = document.createElement('a'); link.href = "javascript:"; link.style.color = color; link.appendChild(document.createTextNode(text)); link.addEventListener("click", () => { - browser.runtime.sendMessage(null, clickAction) + browser.runtime.sendMessage(null, clickAction); }); - return link + return link; } From a9b8ffd94ce271c1987d93d1f47ab8c58a2f92f2 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 14:26:05 +0200 Subject: [PATCH 23/47] chore(web-clipper): fix package lock --- apps/web-clipper/package.json | 13 +++++++------ pnpm-lock.yaml | 10 ---------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/apps/web-clipper/package.json b/apps/web-clipper/package.json index e7ebd634a..8dcc7ec6c 100644 --- a/apps/web-clipper/package.json +++ b/apps/web-clipper/package.json @@ -4,17 +4,18 @@ "description": "", "main": "index.js", "scripts": { - "dev": "wxt", - "dev:firefox": "wxt -b firefox", - "build": "wxt build", - "build:firefox": "wxt build -b firefox", - "zip": "wxt zip", - "zip:firefox": "wxt zip -b firefox", + "dev": "wxt", + "dev:firefox": "wxt -b firefox", + "build": "wxt build", + "build:firefox": "wxt build -b firefox", + "zip": "wxt zip", + "zip:firefox": "wxt zip -b firefox", "postinstall": "wxt prepare" }, "keywords": [], "packageManager": "pnpm@10.28.1", "devDependencies": { + "@wxt-dev/auto-icons": "1.1.0", "wxt": "0.20.13" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d170e3d26..f4fde97a4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16286,8 +16286,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-export-word@47.4.0': dependencies: @@ -16431,8 +16429,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-indent@47.4.0': dependencies: @@ -16560,8 +16556,6 @@ snapshots: '@ckeditor/ckeditor5-widget': 47.4.0 ckeditor5: 47.4.0 es-toolkit: 1.39.5 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-minimap@47.4.0': dependencies: @@ -16570,8 +16564,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-operations-compressor@47.4.0': dependencies: @@ -16745,8 +16737,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.4.0 '@ckeditor/ckeditor5-utils': 47.4.0 ckeditor5: 47.4.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-source-editing-enhanced@47.4.0': dependencies: From e37487a1cfaa447fc5464b778a9760f3e38c30b6 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 15:28:47 +0200 Subject: [PATCH 24/47] feat(web-clipper): handle manifest V3 --- .../entrypoints/background/index.ts | 71 ++++++++++--------- apps/web-clipper/entrypoints/content/index.ts | 11 ++- .../entrypoints/offscreen/index.html | 9 +++ .../entrypoints/offscreen/index.ts | 24 +++++++ apps/web-clipper/utils.ts | 2 + apps/web-clipper/wxt.config.ts | 3 +- 6 files changed, 84 insertions(+), 36 deletions(-) create mode 100644 apps/web-clipper/entrypoints/offscreen/index.html create mode 100644 apps/web-clipper/entrypoints/offscreen/index.ts diff --git a/apps/web-clipper/entrypoints/background/index.ts b/apps/web-clipper/entrypoints/background/index.ts index c36aa4899..a27571884 100644 --- a/apps/web-clipper/entrypoints/background/index.ts +++ b/apps/web-clipper/entrypoints/background/index.ts @@ -1,7 +1,6 @@ -import { randomString } from "../../utils"; -import TriliumServerFacade, { isDevEnv } from "./trilium_server_facade"; +import { randomString, Rect } from "@/utils"; -type Rect = { x: number, y: number, width: number, height: number }; +import TriliumServerFacade, { isDevEnv } from "./trilium_server_facade"; export default defineBackground(() => { const triliumServerFacade = new TriliumServerFacade(); @@ -23,38 +22,46 @@ export default defineBackground(() => { } }); - function cropImage(newArea: Rect, dataUrl: string) { - return new Promise((resolve) => { - const img = new Image(); - - img.onload = function () { - const canvas = document.createElement('canvas'); - canvas.width = newArea.width; - canvas.height = newArea.height; - - const ctx = canvas.getContext('2d'); - ctx?.drawImage(img, newArea.x, newArea.y, newArea.width, newArea.height, 0, 0, newArea.width, newArea.height); - - resolve(canvas.toDataURL()); - }; - - img.src = dataUrl; - }); - } - - async function takeCroppedScreenshot(cropRect: Rect) { + async function takeCroppedScreenshot(cropRect: Rect, devicePixelRatio: number = 1) { const activeTab = await getActiveTab(); - const zoom = await browser.tabs.getZoom(activeTab.id) * window.devicePixelRatio; + const zoom = await browser.tabs.getZoom(activeTab.id) * devicePixelRatio; - const newArea: Rect = Object.assign({}, cropRect); - newArea.x *= zoom; - newArea.y *= zoom; - newArea.width *= zoom; - newArea.height *= zoom; + const newArea: Rect = { + x: cropRect.x * zoom, + y: cropRect.y * zoom, + width: cropRect.width * zoom, + height: cropRect.height * zoom + }; const dataUrl = await browser.tabs.captureVisibleTab({ format: 'png' }); - return await cropImage(newArea, dataUrl); + // Create offscreen document if it doesn't exist + await ensureOffscreenDocument(); + + // Send cropping task to offscreen document + const croppedDataUrl = await browser.runtime.sendMessage({ + type: 'CROP_IMAGE', + dataUrl, + cropRect: newArea + }); + + return croppedDataUrl; + } + + async function ensureOffscreenDocument() { + const existingContexts = await browser.runtime.getContexts({ + contextTypes: ['OFFSCREEN_DOCUMENT'] + }); + + if (existingContexts.length > 0) { + return; // Already exists + } + + await browser.offscreen.createDocument({ + url: browser.runtime.getURL('/offscreen.html'), + reasons: ['DOM_SCRAPING'], // or 'DISPLAY_MEDIA' depending on browser support + justification: 'Image cropping requires canvas API' + }); } async function takeWholeScreenshot() { @@ -224,9 +231,9 @@ export default defineBackground(() => { } async function saveCroppedScreenshot(pageUrl) { - const cropRect = await sendMessageToActiveTab({name: 'trilium-get-rectangle-for-screenshot'}); + const { rect, devicePixelRatio } = await sendMessageToActiveTab({name: 'trilium-get-rectangle-for-screenshot'}); - const src = await takeCroppedScreenshot(cropRect); + const src = await takeCroppedScreenshot(rect, devicePixelRatio); const payload = await getImagePayloadFromSrc(src, pageUrl); diff --git a/apps/web-clipper/entrypoints/content/index.ts b/apps/web-clipper/entrypoints/content/index.ts index 0ee35ef0d..c085dd867 100644 --- a/apps/web-clipper/entrypoints/content/index.ts +++ b/apps/web-clipper/entrypoints/content/index.ts @@ -1,3 +1,5 @@ +import { Rect } from "@/utils.js"; + import Readability from "../../lib/Readability.js"; import { createLink, getBaseUrl, getPageLocationOrigin, randomString } from "../../utils.js"; @@ -69,7 +71,7 @@ export default defineContentScript({ } function getRectangleArea() { - return new Promise((resolve) => { + return new Promise((resolve) => { const overlay = document.createElement('div'); overlay.style.opacity = '0.6'; overlay.style.background = 'black'; @@ -120,7 +122,7 @@ export default defineContentScript({ let isDragging = false; let draggingStartPos: {x: number, y: number} | null = null; - let selectionArea: {x?: number, y?: number, width?: number, height?: number} = {}; + let selectionArea: Rect; function updateSelection() { selection.style.left = `${selectionArea.x}px`; @@ -300,7 +302,10 @@ export default defineContentScript({ } else if (message.name === 'trilium-get-rectangle-for-screenshot') { - return getRectangleArea(); + return { + rect: await getRectangleArea(), + devicePixelRatio: window.devicePixelRatio + }; } else if (message.name === "trilium-save-page") { const {title, body} = getReadableDocument(); diff --git a/apps/web-clipper/entrypoints/offscreen/index.html b/apps/web-clipper/entrypoints/offscreen/index.html new file mode 100644 index 000000000..b6a07c8d2 --- /dev/null +++ b/apps/web-clipper/entrypoints/offscreen/index.html @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/web-clipper/entrypoints/offscreen/index.ts b/apps/web-clipper/entrypoints/offscreen/index.ts new file mode 100644 index 000000000..6c63f5f4c --- /dev/null +++ b/apps/web-clipper/entrypoints/offscreen/index.ts @@ -0,0 +1,24 @@ +browser.runtime.onMessage.addListener((message, _sender, sendResponse) => { + if (message.type === 'CROP_IMAGE') { + cropImage(message.cropRect, message.dataUrl).then(sendResponse); + return true; // Keep channel open for async response + } +}); + +function cropImage(newArea: { x: number, y: number, width: number, height: number }, dataUrl: string) { + return new Promise((resolve) => { + const img = new Image(); + img.onload = () => { + const canvas = document.createElement('canvas'); + canvas.width = newArea.width; + canvas.height = newArea.height; + const ctx = canvas.getContext('2d'); + if (ctx) { + ctx.drawImage(img, newArea.x, newArea.y, newArea.width, newArea.height, + 0, 0, newArea.width, newArea.height); + } + resolve(canvas.toDataURL()); + }; + img.src = dataUrl; + }); +} diff --git a/apps/web-clipper/utils.ts b/apps/web-clipper/utils.ts index 5c1b3efd6..7dbbafd03 100644 --- a/apps/web-clipper/utils.ts +++ b/apps/web-clipper/utils.ts @@ -1,3 +1,5 @@ +export type Rect = { x: number, y: number, width: number, height: number }; + export function randomString(len: number) { let text = ""; const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; diff --git a/apps/web-clipper/wxt.config.ts b/apps/web-clipper/wxt.config.ts index 7a3ec383c..f824eb64c 100644 --- a/apps/web-clipper/wxt.config.ts +++ b/apps/web-clipper/wxt.config.ts @@ -12,7 +12,8 @@ export default defineConfig({ "https://*/", "", "storage", - "contextMenus" + "contextMenus", + "offscreen" ], browser_specific_settings: { gecko: { From 1e820439992197fbe66d4ee1912af70875139bef Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 15:42:04 +0200 Subject: [PATCH 25/47] fix(web-clipper): saving links not working under MV3 --- apps/web-clipper/entrypoints/background/index.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/apps/web-clipper/entrypoints/background/index.ts b/apps/web-clipper/entrypoints/background/index.ts index a27571884..acc0460c3 100644 --- a/apps/web-clipper/entrypoints/background/index.ts +++ b/apps/web-clipper/entrypoints/background/index.ts @@ -363,24 +363,18 @@ export default defineBackground(() => { await saveImage(info.srcUrl, info.pageUrl); } else if (info.menuItemId === 'trilium-save-link') { - const link = document.createElement("a"); if (!info.linkUrl) return; - link.href = info.linkUrl; - // linkText is not supported in Chrome/Edge; fallback to selected text or URL - link.appendChild(document.createTextNode(info.linkText || info.linkUrl)); - + const linkText = info.linkText || info.linkUrl; + const content = `${linkText}`; const activeTab = await getActiveTab(); const resp = await triliumServerFacade.callService('POST', 'clippings', { title: activeTab.title, - content: link.outerHTML, + content, pageUrl: info.pageUrl }); - if (!resp) { - return; - } - + if (!resp) return; toast("Link has been saved to Trilium.", resp.noteId); } else if (info.menuItemId === 'trilium-save-page') { From ac109c2ece75230efba6ba76307531e53febe94b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 16:07:15 +0200 Subject: [PATCH 26/47] feat(web-clipper): support manifest V2 for Firefox --- .../entrypoints/background/index.ts | 48 ++++++++++++++----- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/apps/web-clipper/entrypoints/background/index.ts b/apps/web-clipper/entrypoints/background/index.ts index acc0460c3..953ba552f 100644 --- a/apps/web-clipper/entrypoints/background/index.ts +++ b/apps/web-clipper/entrypoints/background/index.ts @@ -22,6 +22,40 @@ export default defineBackground(() => { } }); + function cropImageManifestV2(newArea: Rect, dataUrl: string) { + return new Promise((resolve, reject) => { + const img = new Image(); + + img.onload = function () { + const canvas = document.createElement('canvas'); + canvas.width = newArea.width; + canvas.height = newArea.height; + + const ctx = canvas.getContext('2d'); + if (!ctx) { + reject(); + return; + } + ctx.drawImage(img, newArea.x, newArea.y, newArea.width, newArea.height, 0, 0, newArea.width, newArea.height); + resolve(canvas.toDataURL()); + }; + + img.src = dataUrl; + }); + } + + async function cropImageManifestV3(newArea: Rect, dataUrl: string) { + // Create offscreen document if it doesn't exist + await ensureOffscreenDocument(); + + // Send cropping task to offscreen document + return await browser.runtime.sendMessage({ + type: 'CROP_IMAGE', + dataUrl, + cropRect: newArea + }); + } + async function takeCroppedScreenshot(cropRect: Rect, devicePixelRatio: number = 1) { const activeTab = await getActiveTab(); const zoom = await browser.tabs.getZoom(activeTab.id) * devicePixelRatio; @@ -34,18 +68,8 @@ export default defineBackground(() => { }; const dataUrl = await browser.tabs.captureVisibleTab({ format: 'png' }); - - // Create offscreen document if it doesn't exist - await ensureOffscreenDocument(); - - // Send cropping task to offscreen document - const croppedDataUrl = await browser.runtime.sendMessage({ - type: 'CROP_IMAGE', - dataUrl, - cropRect: newArea - }); - - return croppedDataUrl; + const cropImage = (import.meta.env.MANIFEST_VERSION === 3 ? cropImageManifestV3 : cropImageManifestV2); + return await cropImage(newArea, dataUrl); } async function ensureOffscreenDocument() { From 785ace64addcd1a7c86746af8b9766e31d01759f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 16:20:36 +0200 Subject: [PATCH 27/47] feat(web-clipper): use new Trilium icon for now --- apps/web-clipper/assets/icon.png | Bin 12869 -> 10710 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/apps/web-clipper/assets/icon.png b/apps/web-clipper/assets/icon.png index f4783da589bfb1b777e943d71bdf3552264b2ffa..2d4da35c43a20974961be21d9535ae5c8f995eb3 100644 GIT binary patch literal 10710 zcmcI~i91wp`1d)pnX!(2jj>bqoyb^2WXrw{k~K+W=Zt*~*|PLSMJk2J5;2wvDWsyZ zOqT3xWapiJzxOYAuj`%bndjWg=iK+_zRz-9=eeE~v&$FhsJW;C0H8B8(6s;n7)68u zXgKB2T`ANH0C2#}_==vf_f5hJ{6UhvL$%tRq2B79>sxst6Ez-oPnB2i-a1Y9{T(Oz zni0WdY%TLD(_lV#S^lQ(K^&nHUwHJEzVU8HyyGc|Jip!M_=&zFY3FB=85I4tJqEaqs> zTU)Hx1_!*lLYe<1N>8D{798ysvo-x*U8bmceZ&Pfr~eWDdA(j5E@qxz$WWP2S6a+B z{Fk~PT1Zpas15diW&S?^V@jOO+8|0mib|R1j13l??6218dDR=;Y_y^vS3-g*GsU%h z>qf|HM+$zy*`5Mk%(_5{{J$suwlw>{!WA>0`hPL=`eWDAk}1Msj^3iz)%g@TN;#Cs z6xe#rt=ztt_1d82;(r_{D#b-XQe6LP@w!GKNg0%GP>jpB0yb&_DFi6UnD%QFQH7P!~ zk7B|9OXTXj=;8>qd#se%Q?jg-IZ%ugR?9bhmv8znmsmu$5mx@QUJeOf^7CFKI8)ee z)cxx@1sUGvLK%^5u2F4nE2TDTH9@Nno!0Au1Dmba3*wd{UDh8(E<|3;?u}eaioext zvtAOh91&((ac(w(b4K$#B}ibC6(!wb-o<}y^l+PvJ{S4z%mUzm$JWBo*a zEiHM;^XhLC!#O?eIS5C&c|7X$2`@C)o_O7P7ZOMEx!q#FmJqj8V63)s0A0tMK_n6-l(*mpqPT6iIhj8C&=QmB`}ZNJ)uQ|$5{Bu31Xj> z{y=h_`hs@51Q@U_qo54^}cLs@5001i3Bl=X6vg{?`JnmJ|u6< zdA8+DHSw3UTlB=pE{|p;?YrdpK0UXZvoCHFt&*i3yrz2F(86Qza(cN##m(wFb1lE$ z_mw91+pXroR4<^9C?iK)?N&5E(Gv_uvC`Qg8 zJ=-A58`pwDTmwCTkdP25Z-3ug?ydnIQa1uUi`Ue-000LV>S|pH{jus45_!ey$A!f{ z>|KI31_S0x;c$A`0_O@2T`Y@ubQSKcDISZx3Llm zeuGF!RbcE~zx;9F%{*m%yLD}He)s3AY`NX2wXn(Eu&G=0+7wRze}A>TOXq!Uo6JLk z`7|$xh=|C`OG!z6&rng8lb3gYH@Kp)RNiMgSpsIB4a^Gqlfy8fD6cFp)l%K{g2kh+ zuw~*@ZRJ{MO|^lI z?-UqVx^H)_iPuzHsCwvVvXJ$*67XN`_ufMF3*SG{_TzAs)4yBqh3!O1g2^digr%>$ z{HQKBro@bzMtO8CI1sjb>%)rQ18#(<{Zy0~%(ydWp#3ll&Xw67^7i|8)HW=G<>Vgq z`DG5It6cwbvEB7~K!(i=ZU6i2_ZGZjPd`8#wmx|r*)#PYX87|N>7&-V4L_tP0&)fJ zXXozvltWi9Pcw)^?Y-ga&LMj=%V8;+%|N(7H~UFfX(%IbXq;Sh=a=RzU5u-sG7K2O zAAF56-JgsGJzG*VY2y|W+J0}`$^~N?s+kjO{Ap7)l{s;=h~>DI9UFS&+m70z;ys(I zTH6h2r)^J0OsV$pCdZ(e0jNz`+MEWxa{abU#KXfgR3SPU! z%mj z+ee#O$Hr;VcovJF(vc$hkC#rPVvYt=kYtIAn>D2C7@i+rXBVix#Wv(=gxtD&BK_yR zivZyG6}o2#A-$2-4;o$^zj(gV6m3H_E&T#$mLmp85v+(`4c;`*v^p8vq<$BKZL&My zBPD^G_ZSMUse;r?Qf#cO8h;n5#CdvL{J>2Gv|H0D>z_q62U95ask@#(&;IOk0Y_#> z6&1;jI{7*xQ;LuT!fr>tmdbv<=c%x)DFUB00&BUu4(jkw>}LR;nkp69BtwPRz6@kaIL(HM*|d})C+P%QTkPKNc;I60h* zmasfADO41nt(FSAj~AFm+Z;feD{8Gw`-<2TQ#rXSl0|@RT!)2uUU+)%Dt_3!;6t8f zRNlw_y+N9k)Aa5|JeotEb0LA$h~yjfSLw{*s*1@uD8zVyEBNk!9a|gGzVN%9yci z`vEPy@wuEOP%D!4AGO(pq~JUM)o5~xpCssZQKo?{z`g8dj{AoaBMbbQG}1u(nH&>7 z1e2?>!zD<8E=z`d{b67y=I-8%5B@l@vtPg5-{ad9{YarC%wQbCnhu?VzvGDGv=m0D zIC&AMv7?N^P24SKuDHI7v=mc~T(^~uwC4iAGFd8&*-!%n^HvoeT!NB?< z9aO25tnvOu&+C|~yQz4Bwh;8)Dg6_$DOJWRjxVfL+r73x*qEzWNrQUn19nkC6AmR` zb&P;&nkpSZTz$>(mh>ZzDDMkVhp@kTct=|5cN>@9nGi!owCFkS!g4W(4Qe6|w{U3b z1gPO$yf`wxeE6+?K7?S#`wHSNdkx)|KMHuVlpj=DswfSNFk_9tf_U&XC+y9m&G*HC z{o~H@)`r(V=`Jew4^GvjzEac*0@PQ)RzBTnhlr)yMBeLt9nU^>tP zzH~Q|&AmOoo^uczx0rG_?LNC4>CP8Z=+zx4^ch;5Zb+25M+(^E!t2m4NSzpG#pE@? zrLJDb7`&APnKTUuBx_22cVSh7p!`IFU5NZ;)~Zito3u=pVZHDQe~3)wbvyoLunQ5?sXT9b%;$xo08QG!48 zb?yI>7|HSfsgeisul04*eye(eD^b;HX0f4m^U*3;?oe_P?d*#Jf=3ae5eIS;=Oomz zsiw;4Rs>m>wDTa}kj$t8BcJbldOP8Dnr8j1%RUDhHS@ID78W3oRpWYP+r;q*qeOXe zMQzTjKCE+>W7v(8o^`{JtY9xLwl1hr;r$T9yRjN?OYOF5c6^%0*c>H5j`Ic_3})wX za)?#{n~MJ-P}X&>_l?PU%UTH?%L_-w;_R1zcl3cR7mPm-B1PDUxq8s!J$ciU;AY-7 zdGysVZU2HBohO8&yq zb4c9w$6t!1Uhw;J1!=t}BG;N{7k$nEaSx0a1{|ngih7!pn8X|0aJ= zieck|VwW$#5Y*`IZoz)%Yg4Yoap5&g z=#_n03p)f7_XD4hN=fxB=KP#Bf%z=)%kV>ods!%^d`OS$I^iW9%Ouqnd(7_-9bF@3 z15Dsx$Ft5j*bARNRf6@UL4W<)-^S+3K0&Q93rd61K7qupS!gHlZ7J(k&7OkH zNCd3HTjx5>XCpdeIOY-w@VK)PE_$EA;4jgw(pL0T%^os1eU>odto7l10areXX$7AU zMqm)FU@*Yp%l7KEPl8_o*`O#@g+|;v$!hstI-wwhJbNw^7B-t8!ww||Z0uPRhmEkvlpE1ykZ*&5L##xemjqMg90{a<3QcFi-7ORZAu zzfE3o#vd-^1Hu(#Inn2g={I9@;O={8p0^mQjP$~{gU>5IXgD_M5*M!as{Ft|_NRL$@2AY!1&hhT*hl3K`1292=>Torof4z#Z`a#W(RrU+~0(jNDW zBkp^^f+nB!kCsvg>n~jKLUhe|94S#ZPmgd<0RM%$x5TaPXoMH_xRUKlef@j|WE7Y6 z2o#bxXgt?k)Q%QoxxeFIcf?%+aMW2o>em5-QNLvS6$1Q2byOa*Mlo(MH}2INoggtq zZlOo!v7eY@tD##mywI0ecXnC2Ny>>c1%0zSl=xHs!|@#R30!kRa2qm$IHFr<{!(T4UYjgt_A8_e!afB$UUb-pipbRcFM}t?&!mTzzAVLt1Fm>xos-S zEwAxEnPIvh6YZdjI{n{)9saj`{r#^Tl3aUrX%(-%Pb!5O(RKZ4;3C@_YtXTMLscp_$R7E=-k0T|a!vJ#+4xi+7EyRI&m)lYeUfgXkMNGh06O2yeTc8$Ph)^mYrO zucjc<$jnOD7de}w-&248EP7)}q9^{SxDY@c;b!j88JwB|j)BQ_q z|LCfFVsaDs*k&#e+_0Y1OSzY-6bTLj*Xa$mh-*>&3m+(}1SfQyu>AMu^m%dL9y~sM zAFqf`Nx*q4Wi3$U+`@}qI63{KZ+zv{-&jANU`)*Rs>8{o=qvrh*uf_&KZ11^%w{Z; zS@)IS05YrSmWFcTMBl>^uN>e3s=1au^=cM7Fs{<8+*x_)PQ_Nuni4iD%WHez{38qQ zADd<1D1Pt5Wop6NO^u%PBO3jycRUdIne_H6kFJdAli&M~BluM@I3E&(C?XQl8TWh) z`ggj@>klOrfU|CuI2zY3j9ZsdEPhd35#479*uigqPLDIhY+Uc<-p!rguBtzL-UrKB z?l9EOpJrwL#KhLfsBqf$!YBuD(*#=e2=$ib%sduHZV%Mon z1Nxg_+G!nxedP?~*TblqcJ>@bZVu0HbUBmP5g$EVoyH`x9MP{)XZg@{SX{$iPOoMm z@<2@6F7QwBdN<{gZOgA-CVu|Z8h3GIoH_pLxnxJQf4%rRVh_DhlfSiP%EKEnQubOq zYGHZMt$jHn`OLl0vgQzOtt>4A5ugQ|>qYYM+`C48Bk(HfsTp!yeXO*^u`&z8`c->& zdGJJc&tKy0)cCW6ry_S~PKH}MCbeA9sfq-jP*^-$%+{9lIa<%4K+49CD*Mdoh{ABH%3@MVzSd(fy1hUAJdu zCv-^PP36ODg-=t;3p%S0*3ec=xX)0!19QIV`HyV0(FQT*LhNvMh7!ySl z!EjVHu#fp;K5Lx>ziXm>H+7Vz_mnNMafj))M&&qW47w_v_LuMh`>5tiyQh;i>OPIes|X!UB; zBf;{`NZ+avvl23zdhe);)C{(N*++lC?>W$EqdCmXUQ5q<0T|?vLMc<$bKGdbug74% zM_T(_?hOf-BkmXVyfH*@)x_X**H8_jkc!Dx;*HBvXU58LpH<~5n^51}t#3Qf{1Wi# z`c&u?{;-^WP2m?l;~AG9EL$j;Z0j#Keie<+96Gp@S8zeXejg$8eF@bN|K+X2T_Zf^%F;&a*Wr zJ|@$w8}ldcw9F;{C8Orh1gq5bn=A7IdZ8;fKD&5sChfcCKseFRrws$q@_Pd1w>pXq zVEhO3u(knhb=UG2RQ-)AH8Kaf)IoNCyzVV-{JySLEc~I`L{0Mc@vJxV7qTvSBIKbB z(=?5H5X(uLUM|TT=Q`mxO<%5lcSo9sN4r^ve6UW3`kj#R*wh(gc&PxlR`#L%bMAVy z{Dn?4lG1gxPRw2^;h1J-TBFN^nnykCK=3sw?bp@wl~M=J)a2X5 z8%_$iy)grpm$Dmzi&8%^t0jRdAsbiYoF%-}ZywBQEoRzkAa17EJXLse@IJMHyhl7@ zxVii8@nGT40VubO`{83F6Kexcw8 zdN|vXs8x2PH%24aB-PJQh_MxUbuu^Wl8B)~iYSy^h??%sc8BV9YtS+bc)=QM?UazmAvp|GdBB^hI*xq@Yy& zM4n(m{jsK6?<80!Q`tP8ue~CS){Xkmavtf#oM*bG+KYc3E8zCqJKO4sAp% zb)GqpJko4dApE_2w0iTq19?@MWwF$rFS15eB}s38J-dJ?aE6a(xN&6}8`;T3is=4R zop<{2hDf;>zPQHpj1BAI%u_{|yD%Yg%q#vs`F8rmC&l>>ygH}prubCCk6>?$VwS2d z*M+$7?CUiP;M}_oEhS}+^G$lS%;S3$|GRd?Ky~LowP>OQhFz*z@HEc9JR}5x^3RjD zc3yt<_DcfcLz^GV_0S8UDt`|7=y_Dml5^SDIyA~|j32k0@>EPf)ddmHl@buol?J(E zA22c9U|8Vm&t#V4jd>8X^Jq(OX`4v;Vy`?+|A0NWZ&`3JH^p2OU zIazX+Zqgmj=pk=93f8%P{PSq4d}Q_GoZ)6Kcio)U-?BD(!EKiKCm*()KWPK5>LK%f z-48WG$ZlJor>>olf9JVI+}u2Re2U-8QSB~>d}SO#9Pa`xM)c^OU~>?&twnKC)-jD2>Enk??F=O4QIsCp!dlS3xTiT!)&&%QHj zokRth^0CZS<#%U~zTQftjR_ly4+{%>^XV?sjmma^OO&f|8$=Q@;yY&u1##pyTRRvFrcBxLArN8RrjGY<&RDh+HY0*XEvl*v< zar-saw=28cCuWb6479OG{{}4s32fYbxaa({NcJROUYZKE-XQSW2i89K*q}8`>>Yp(WT<_!Bwo6svk4=9-Pnh`bYbBSjWO}1PZt+GL5tbeSf`E;XPt);?TCUFSRKCqr;>BM;XV| zuhS-F0@_S{&$|~T$PhzoH-CJpdI;*XdEV_&w95_W885G+z+E zIdh%N|G;1qsNfnh%m?23^0TK`G09wkGf{nz2d+ZySp$>Vvs{dEKP}0Ifv*9qd$`WM z{92Zu<*Hb5-Yi?VznLyH-620qx+*yMFth`=MYKen56;hebm6Y~bzaeB$=WINz*iFnv2X=Y(w$M&;>(8bTc3iS<8 z8_%#?tx+*6s?uVK%^bS#=I-&A(pDz~$}p)z-HK}ghl*rV z;g40`cgsnU1Yo+221kCW{NnJl_%5 z#?2J8qU$Z{&0P@XOL{LXe3lJV zF+y!BP&dxYiPT=8Yj(H;j0F;8wi80-Kq*&vD~xf1V6Ga#`Wd*4J?~Pf#SL z-`z+xu@?nTxV(#%E=<6#uk z-4l9ZJNfO8m1*vIgqR(wCj|&f7}Mr{iX_`tJpAl1@cz8I*wkUM1Y9bCKAFe~+47-; zq=B{C2*t9F?xd|yJ7KR_b?nVo{OQd=!|jXX&%o02Mf{hDSW~YHJh*PAZx#}OW_Xv= zBIl-YHLV-;@Gm)dE3~a(D$*lBLtjJgn8NR<5nYcsN5b%X!TDuM0=L`q+QB^W% zhL%}@Io@T2AXGyz8GBkBYrGM?fRTj_AOX*-2ui=W>k!!La^I~&+!!(W>&>BOFJU;c z-R|h5^&L;>;THRMML-Rf{57sq>H{?dT@dS~t29JPrDXC^$>hU%={-Tkj+y`HrWn+E zaf?ug{I-I61Y1^F1B%7-h?lNenifn}BQMj}Bq(5IRk6-GYoLJw?u|MR9KD0j$OP|` z7wZ?`)0U(up=_y`uH!lRE+&%s9Vf)66=IZj^7pWz4?QBaXm0a>OZR*jH}YYBwBQ`1 znN$lBpKSS#7il>VL6U|Q;NiBNxAnwvpZ@H4LC-Dn!8?1t@w(V003~CaLbFGsRLdFhni4Oz{F8AdRkT6fQF~$8bCX*Tn(H;T75ODLsZPaQri0Q&9pSC3P{o*Pn zK$$~UkuD&`AKdW4$50{R+LoQ&GOV%p0%N5|Bmrl-y^>f!zAkBQm*J%fL`1RD{iql+J()23e|C=hoxDej{yJ~@oz6c1rXZ#P2j#Q;;8aS!@5xmU5bW*)q0-^7K}so zVH`GMKXLIUh;mMdV-ck|+zL^e_1FleazbfLGV}Jcv&^I#a2)_cm0?oPjc zp263QazYOrZ2koMU%4tMZdV(DWu`(pTP?%FHr2w}(IinfD))5!($10A4?1m1!QX7j z!ix9FSh{udRr(`W8(+EwshhFed*A0DTt+)76nE0br^gea{9q(4Ves z5_bjT`BpCWv9zxwY@7@KMypB*$*aNbHS#E&M`9uBa`|)I^sm19*oX#eN+Y~z;96~N z3BS({JqHK1mG9Y%{WoHd48M~po#c$I*-NXm&YJq3@lQ60yi5h#F89NnGXHn}{N7`I zzTSu6E|Gaa(!!^KnN*kki7xS+yKUEdt_*x8Y5)+-a!@yacpsR%BKhkFB6O*`P%GjU zBWakgF)^?Wv4DpzXFH7Lhf&GUNLyz=lUj=58Ru0lX{^F++_Oe1?Q--nk)jwg5 zWwl3{oFd|1JQV?_?X*|nlX~r|WdQCyMe-w)1J%*u_#i>O zc^$aV$gjc&UTZYk)TA^2NhX=Nl2Ld;TxxdJLvAcXQv9C<)R!&^e9%nr1d~>?I9hK9 z;wYi(5x*N$L>oHMnpPesIe1cw)7D&#vqgyNWwl?g_G1nAK{*8 zZi5fF8>uJ@kQ2!j$_eLwHF~jpt(5UYr^uih61>d_fQcaXMOvmmiX>(*%U>r+3x~mT zDztp%D5eU5=>Wl%~FOB%OKjVCo}mxo=fKS=AH3J zUe`rK4Hz>-4@fZVTq1rf5X%OMiBWa4!fOFt_=TDTB<}T12R>VX!smS54ne(ynngnn zdPb$0IM1ZFLoO9=A^0x9baw>IFvZ{l+Qs7!uF7UXGdmaNvOs;syOR^k?7%&E%4H${ zbSfHLzj#0?4)Oe#ZkE@(RLyQ7ng7?#O(caQ&7gMrb)U?vf7>U&~(y z4i@TrCD}&=03Z+X(=+nYf%$;kJX~$;oe&_e$8HD^!q?sg0PvlyeQS$c6L}MQ3#X{Y z@U5ns@wCCNzW%}eN$W$&&hCDR-b0ccWn$kA8s5R1wp-{%z(vyg;rj(To&#Twcmt&s91#C+6s?09_e{j>Ymrt|Z&uigGbw|7n+uZkUv z)k~e+Ffd9nFdhW9-bjpyKVQlapF4eYNJxGyo_ldExk$ZkFhHbgdfejqlxWg*k&ir6 z7s+}g;Ai^KrMcqkM_Gxa`+hsNitCS$MGt@c^enN}c!qtKOUgfDnCp3~`PS2KY;Cl+ zqKUH0g#_DJU*jKE$e2HIo!B$vZHxU}PxZ5}ZP9qj^nU%F*7s+SGtJZQr~8FR>_4Fl z8LNv}{T3Bcd8eN9`I#@nE9Nd3ZS>j}z640+l1fF^pKX?ARPTshWjI;Ira(4*v@`SP zebPk8%bpl-ygN+ywb(aYeK+gER&VlAL(Jh&)iZ;R@$Q1h0)73#H?M>Tif#VOlUFKH z-lg%;8*IkNdpiRStEzUe(1+A&4y400!NCIo#1ztnSnLXbysy_dB zWmyvMuE;D#B~_4!7Ib2!{q*EJKgrWSbF(C+gTLP0jDWLBrKgMsJ`)j0SMB4m*@ok! ztc_sKr#eP8ZC57 ze`a&qTlzDtv2Ez>lj;rEoZw}pAN@4psz2^5W3Z`+GQ%as_#Y(#@n$(~>poDm@C$Fz zpdXA-^a%{)t#K8`yy|F3*X;FQ-#6X3;dQ+~b<6I0vpGnezkl#4O?&5A|9r*ctLLTQ zR~^mNP3CqkTFzNNMPSqWj(1<&%vEeiwHz|iMX-OYp#!Qo4$3N%h_dOo(bZk%zY3rk`D%w~miZhw~gkDE~nyM$P)#oj(wB$f6-({VD z4GruFK^E>=oclIqXv;t8;d+6;B9klU^edG`2l5UR81kXQ(n=9E02g3Bp5DDofcZk< zOG=GBz#Utuo-pC>;mrSJs zX-a8d!56%?8F?|@!rfmn&L1raspy!2sgqcYQ~TKFvxAELCZO)wAb#k4RH0v_3m4&2VuZ3&2p!-i!-(^>_ zng`-w$Pz?8QJ73L4{bf*ke!G=7%XC`H&E9Iz~0~W$F4c*diqtO;>-H0hVPX=`OnT5 zjL*zD#@OB)dB<#)d)>iBCoIeopkmF^$8dDVBrmm!~+NBN_LDp}6OrN^~_T zSeSNQIpwPy-$8vIiftW~cBe*N4q<&7s@^(fW%WX%Na;kPP?sT$9hOC-(&3*N>?P(TsQ68dk3;GE72T;KJT$ozqgmpqg7BqWkjqlIvXE<_ z+$`oNlkjTjiDJ4BN3^JDSrPKm;d4*oeDb7ff=v~|s}w?M^hO1N&T*O(X1y>_f=1&P z397BaJ;2eVG}_zay6?{sr^Cb!wb4FNYf)rGL`0& z93)K>z3h(N%NQp6ty~A&-SK_9NEuz8lII)9z<#WpE(z939K%S&=(^7sI?z(!^r3q9 zk{X@zYc%SDXAla4k9$P*7X924ih5MwvUoc>3)`%MFw+nm=)|TSIqY45cay$A{rzaW zBq@11LU=u61d>7XY@0QrMB1^aQ0^=H6aYIpNq;-3*?pc2ye@DeN@U*3*~p!6M>kHs zJT4x1?mc&JX3a7Cco+=nvf9KUeY)s<>!1J5fLSAvdhxlzSb7T1b;fk_Q$l-nE|Ek8 z-U$aX*vJ5D*Nmm6IwJ+M?gVU(cG6E9uAGG79eMe+Ks!yr+t-q0P_ZSy^Tk-0KcCVX zRf?~SJyN{U-EG22|kbPFX`XlL&bK>GU=4KjdOLp~9Jw}>CTx~ieWzdhFbZR#Op zQWsl7+(RM9A^Wg%OO5Bm zAdO8cfZUgZ90G}@3zjlsm6rq&@#e4gfNKC*-9LAL-**O$chKN7sl_6Q+a(24v>4N>a#@2rm3D(+k2smfSF;p>ia3Occqd&DBJAJ* z5(=_Z39PISS%>{g9aHeax{LO$1i`z6K|!StXuAOr47s6)%5t_J+l@}S?%aap&3g^E zFeVC2$2;s+bVfc#gqzLNsCo9lo@Ej=b#GyYZ@u!c>;il+5_#5ZDtCX0E3mfpJ)MN@ zk1Fg5%xb9@$+{}+Um!!+_ePGSVAab6&!q$-u@sG@1aXs3tt{T|J`Ph&pp-l_GHCq{ zWQ%rfFJF2unr69>xA#me9kLQn4CcZqM9yZg+CHG4=*s@O*jMIP-1T%Lyzjk^{ZH}N zN^oiP^1d&+aj~D6gZO<2a-v4~?x5}Vz6_Jks|$1*A|*|)Ok6$WwoFlDwd8x;(a+qR zR0n1+i6IUA8oN^x7vAzb^5u$&9-*4QyjXI@2F@f!YRwCwN+P^6ys(m_tmlOp(ZTPZ ziY}&z=5MjXvtL>A!8ZzuqL3PQ_9GYS#O>>IoF8UkOgj!kKWSP%|G1|Y)FwW9Gd6-7 z;n0+ejObzle=3e~ErHgRI6Exk`?C{=bi4&(XA-(3^|!nCLwMqezI{*nCJYS1{Gc_D z+y0pe-DKd&u2>p0E`5&0>vh7&(@1%>ui>9O8n`^?jJIVGmIGM4qVJIX?z5G$&LYln zCt3?w!$Y67ztm$Vv(1Mp_uff-P9eUfRM#U{@um zox++S!#!!YSl?XR)^(aZ4f4o-il&%8(;3XurH*#y8h)lO9(hrbpqAnY_jb;S7cW1J zP1hHa&;%;tOb!rq1{aP=heSzo`_fr?rRF`bVAnkRD`i#5W(Da< zF10aPd89S{-umcH^h4!Y0ERDdP$`)!Jy5&itqNeZ`ON*a7|L9mhkiXxXf+e5R%*%$ zZ{tZ(LfhaIwH=F?b{YSw_qYIifSrev`u4-7jZ|vDZ~cKERA}f|!bZow zGc>v2Os}7I)R?AjN+Ke!qr4<(d=G^7(#baoqq%a1aKJKo9wcEYLj_gUfV&EzkE}Xy zFib-50?ye()5d+KEQ&89mt?NnD+V;P-1naRAj-K&y)V=jWi=R@OhrCjF3zeINR~Iq zIcAz3U|s*2@#z%@2~sI62e)JGCsa5`L^=qP8X^C91^ z;tc3PoMxpvjd?n&$L99V_$`C~)~iFVd5VOpSu~EQ!W}+C4@2q1)iE&VVn0%+F@6t6 z__-9$>=K>%k>1l!dL?!D3RVMiOR#iVaNW?;Wmrc!aivTI4n3g7)wU_2Mf3#K?WE8f zirH=D7v8{EOb(WUl5{$&6iVgvbOxev zGNaO{!lEgCfige*CsB=A-XPp?v+)FeF?m|hsyxOT?u?H_)u5ZF7$!66+SB0>^I^Uc z{Eg<@QtE&`g_DkSyKcuHzIDA!!oVeSDS(p@_`P?6v~ZH+k-ph`^fLi#{)aI+r8)sR4(o|7itl`L&L10y z)o1XZu5AlW_{0=1s7p3r4!!3$*w|9h9@CW>lfrw$YZC>t+w^+AM2$5uaaEm}dH8`k z+}BSH6wt4Kq?O9GaP{DD=yDeyxqQrymRj;zo&80cIaO{DM>Nn znMQwer2C~Z0b24$@C-CY2`|L_^q%AK!%yleH4i9%L`s{CoqcL#B;GOTOlG#j#4FYP z<_3kzR35q{r>mXcHR}%pRRt|HtsZxeAB>b+1HTB=hBo>5k+Q{PTtrp!jYbEdWhJWsC zKNPB&u9l0NyQOD8N{FCJtjnMh9d>>Y3-r{0Hee3qI{-1+ai*sBV?RZdcTaVCb*ME@ zPECCXX}QVfJ{V7v9!DqtW(RM<`SJbB&^i^j+I$(9vzQ|h+2;fzxD9$N{w4H7<>>5+ z2~V6*QX92vo^JA!aZz z1dLWOgpK(YeK!Oxe;}kI(o*ncsCNa}_+_+0lLV2cYO8w)OXG;xD0xw3VO(-Q`I1pU#^7$dAVER(S} z-#V!I5B>YeYXlrad^(}dGII;1LXFzyk2~)Y>zufwaWyH~DU6|WSP@1vfVSP&fNm%E zQkHwa@Q&m2R|=tDA7_q@Buur3)T%s1J@zx{$EOys9-T+3DopZ2_B1J>Q_-KWjgAiE&!dm}hkFUN@ckr+ z55hxoUWt%H4~kw4mlLRPjgA&NmhX&9p35}t68hU!+)m&-pKMj~SECcOI11ud%$$zW z4wm_#-^K(dcA0j=VCVu{+6x`D%b$%P44%?oLYr8u1gE=ar=5v=bb5}T%~V0(WPG|m zeG6ex$vd z7yQ?LBs8jtB#`%x+!M_XoVs;ODz>D&tX#`mxdv&m+Y_$opLf1ItipeJC<1pFX`6R0 zVs1pDIw(oladEc0~?1I;{YW#|D9`Kw3?!u(-f&KshFvMO?PD@2j?jJj#C_icb zub|3Z(o|uKbq^Id32lhoag5TrBBDebD5A9PP2iP@C7WGhtJB1j-&N|UU0NEpn(XK? zHpDiRA-)E>dwS}4y&`Q>#RFS4uQr6Ax1O)>i-U}K1U~OjVGAw@=|(mr_mxL28=gpK zi9Uy=7=KJ-VO%2^|OxPc&K<-tl($#x>+pl_+i*Ok)O?@AwQ z-gGK!SQu}JmEB{hj9^tJ&d!P=?XM)sHD~LQjT%~&ypfsad8K;K5I2SnzexJ|?q$dA zl8daNWIzVbT#$%ApPGknPQ@vx4+L1O=R}+J~I&;IUU9Aw@zRqr_(f|M?qU=?*e5yXli2u*;_-I3?b^g>TYrfJ9}k64}`9th92C{5iV-YBqfO_;VXs$a7K8+ zK)%jSE}mk(P^RCwVyN?9Vjd>YZxt^`D3g)87D&$30|64`7UbpyEBM+Y`I#i~KoTC- zHex#Rihn|&uAoeIUS4iuJUl)=KHNS6+^!zBJba>}qCC9(JpBA%lm^)Iv5ObX7wqE6 z{0rg_40(hn+yhn5_O33VUzji}S8p#U6BBA2^pE*DyQ!=H1@GedCkrS(czj`QJbc`| zJkHKMf4A`TQb3|W{+!UiweZwKZS3>tAUs{YJ>UohB*Mju`R@?c@W1Tcygi(Lhhq)r zK{z3tQL3IOuYCXFQb|Q!>o1F66xiB3yZyF8k^L{4UiLQsB!7w2! zA+V?rFE2053c-i4w*DK0riVSMDq&84pVcoYYZR0ST+rIuhF=&gENUYF7KB^F!6Mc| z0$>;)KSG3;SJavhA@m!{8ZM^j>fsDSrPJOSW{co)bFuwB@QZLU87&nk6F)caKP6gD zFfSXF0hCG2-o@MZp96aK&InyE*e^Eugam{I1^Gk-1o$8VqPznCR5Cz#c%mxt7bYJs zH^1QTkzZ*MLj{8(7WS)7Q2@W?s9410JPki;KJQMUI) zX*~W_@qgC5F2enft3Pf5C;Q(+Akgo!6@$V5aN-F=BL1KgW%tJr+z#eqi$LAqe-_j~ z!SA5|()O`KpqSv~lXJKY!NNf71mB^gk#65x@VV>wk3p zM-2Q&%Kxjb|IzgyG4LNL|F63KztM&F&jt?Rf_kd+LA7V~l*#g_)(*=`RY@N39bg2| z3LW2dK}m4kl#M-6{owSk11QU$>WdO$d#R`^V6WqolZz6l>~gN6MATjiMqYBR&cFHq z!0*RXgf+;=-pda3>+#eui1027fK^3aM$dP4Fa1d(*(80hR8#x@k(==wr4$4c6PoNq z4*R6JSCv)mMFf(fjM++iBFf~nXof7&fVRw}OsOnm%ApogSIaS|9mTGrdk2rQXT|RE zMc@l8J?RX5e5cFapZPKN_fsS&(Ff1d(yS9wQ&Jbtzb<~e**8C2>UfS##314O$H^0N zL8=4#Y$mc0*TwaseH|W^FE&#z6 zniKXE8fOqukaq!$wV683{16+E1T;j$78jhuiAkJJ0@|Yg4Dtv+D{JpQ!A)xU~N4Ki+L42hw?f&ham+XM~)Zjl3vb7)@NV|z(J zA~en9u-eCM%+h*8p6z{$ZibTR`&iFxX$%IQ$ppqct;Ps(?e$o?->ycpcjRxhDYYwm z;TY7-usaJ{dTgl(+Ldz4WI}~!_~nLC}X`BexBVaU&3^jqRvQ|zz9E8i+G3ecx* z?5fc(#1EfXQn+DS0wyuNyzdfY?AIxM-s4BNk|{HLy3LX5x{wpT63K+$M%SRK2#4?v zDP*0RM&hRBEcFJ3cT0XnOUtyCenVrb#!tXG@6woMr1cKH9PK3#E1IS#F18++`7wAF zZ3%Np0ZQ)rpwqb*t#>wNF1EU65!j3yqVshTO@c|=jgwcuTwgy#vK;GUN*IJ^A-v}# z*g$p7hs!N%Fp%(Qiz6=vI|KNpH+Xgph0i`R+5A8O^+xHa)+bQ1?w|%303=LugyfLA zPgAH|;0MCfLo$;HA~0(S+ufF)9L;NnTw@+)#Jo>+&XppgM7z74eKD4|GbZ<4C<%iO zv-$NZ=(6@sVpAYRyY&*qcqM62<*jnxBAv!Yw`n8(=TVN2$hY=Jb5Zg5QJA$3mP{OS zA~g{Lw)7);;VTtA!L#y~uNb1*z6A$j2fp)cp3W~CwBfOgvNuXagWX2TcGJ1@4FIKh ziMQ@m*Ryd@Kml8+GA`oUkqxHmT_(Fi?4RymXtb|ebzE(1`#EELu(hiu5z*f5)VfUGsD}4f z;{=HCTJZb%AJZ)(j(nNrW-P$1Ke(yW1>iEy%3+Q#KgnI(Vtu7pHjEPX(u^wMN+79- zr2n=mdjU`y*xll)E9ON*h4YEBHS%9=8c8U0Dl!}GEsAH6#6(0uM&c6(P<)*B7w z-gUP{DRkg?Wh-9DPe^-o`}y1h?&hAdeN9^I?qCxzM}VZ*bH)}-Bc30Ld;C#i=#U(q zCL5e_r6^hx9$g8F;ze=IF+hMiZtF|8A6GXRW>z#)FQrit@_i~olHTZ&Dy@}fCUhQv z!>r}8;2-ouT6HsoBTefc#+#}J*^IvHT0<>d3I{A-4$#k4k`J&xx5NC{TeWeDNRot8 zIWL6UiI;t3$_QfY*_PV~(@@}3W^FUtAr{Pyu_VTyb~uQ{!}8sG4A4A-QsVPVDT(q_ zCI+$NwI|F^M%i3e(vRvlm`Khvm#j#hVJ}rlge>mxzo`mjTw{LPL_l>huCP8@u2l}!#VZU^?J9K6^+&5gw+#(pVJ6+5-n7l)rPJ{V$osJ#pP?N1}30NML)u=f6P~=g;lzdU-dANhm@3Tz9@X< z7%o$$lk+VzyI&@aSB|J>7`19J_bjN2>#BA6DLeH5sPz~RPbuC##I5w}ccKe2`B3V! zeayBni?7zj;am9zfz#=yqIh^D5zHVwgDZh|tw+=&tSodYqjyj5!?QB%o%=-ZCRw+W zW;a-u1jj39Sw`Nnz4Gqd;B}JAe?vq(44$@L6RJ}1v3Ym?AQ?bQqHopyvEOc+`D1ap zvOE_y_k`T{(3NA&_&nnzLFd?HiPAIXrGZ4Jbw$etdn3kytu}K#Gd|lr&US(V82G^k z`yd;wUkueqkiu>+|B?ul+=A_#3Rhy_mZw}$oxwod{G?*>?B;U;Y=N~?%4EK?pXubC zdO3xxeq$|#DM8{U`?a5n1OeArkqMp|Pf3miSCRQ$e23Slnjsc>WQ8Su`N`vvV9&S5 z(jkku7wRM-i%}*N;&@cfG*bh6_nsb&1i4079DLkd2ra>Rf?bPOOYBSGs}mxLcacrZ zr#riVom`b=kQ=_jsc_n2+hoI*YMapQT<1_7xyxM&#-M2LDe_(&jrU)tWkV&O%#Zyh z0*5D+dl!UU!AaqxI1-U>&MZ->X>}czH6lQEkAt4y>IR*OQv(&rs#?+xB5pi z)ZbXRuTGDwu%7d%0(t9wvl{9+mKFLX@uGSEXnZEWT!Z14N|Wo1Z&|ZwaGS~T9iXRL z5?B~o5YbG4a@;AqTia*a#*Ay-jqO9~k{rJrdn41?=)mCds;Vp@oMf!DZ~R`vn8(`D z0;yIqdtR2$#_C698>gAFJQ5`SQa^+(H?mZU@v?rDqli^yd-;PyBnhYa^y>WckyNgEY@i>=tt| zW+JwNOMIy~J4IPkvuYHML}FFY_5(h&HCAPtN#uFADtj4w!n^w786HpFjV!n1>1N;= zdVyJ+QS<^2T`T%PKR)@&OtA=+658EGdlx5mGPJ^M{xtNbueOptzaa1oOz8H+Mb|}L zZZZHBWQ@;t#mSSJo`i2 z$b55ueqL%NcAbaJ*Aw<_0sP6amNwwdVwpN~Jie#1km{;729^|}sQ?oN~hMI!V4$|pcc zioQFga~^&6C^QNN;J#7-5pw!Augl{xG8IXMmWFyM!yxr)rkyV)Zc{vv&8@!gQfC;a ziT3A0cZJWa)oj?T+4lzy`ui$NXnLez-TZmvQo9bp$U}Q9!B>w14!B8dFdfz%tK^3a zaYZ$XDK>bqv6sxBA)Y?oL*3(_1a50knwzWhFG`f+y`oTwj zSd0O-Sa8!Ij@+lB>p#njIHDcxGsUKZwK>AQB{rhv$$q6qpeBWhK`UU4Q9uBvmN(NO zu=k2-)DbEy*hF6>^Exlrh~B4RRLTl z5%aLSthbS$XQ57q-n$^yHl(_}7~|RfojY0FR*cBLBBboOVD-~PC26Ec8CeC~r^Pnl zadYNsZMYwXb+1tPIadCO&z%wzrn?i5F&Zkn2y>;pH4lwf$~S_!8Gv7^$ZKDGdbx(> zCaximP?)YJ%Cg2eRLDuyPLgwIe%a^OV;TZa`C&A4sp)N7 z3ej=cay^W&+ z6(P~Xzb35(y15jSYL$4)q|5gbDLIq9Js6qRWMTK@81o}5O46Zp#aiRVM`j_lB%jZk zZ`Z)rLH7zKmyh8r9%u6-63R0e^Ptzx^($sTKCDf2DY3Y@Z4(CI1D2XE&AGkwZ@p#H zTi3lLH??8|J0q2|hH%pNM)I&W>EBAm2Cl@71dFMx7t;l(GNP{Hd4aVzO{4464yr$d z_iAsYvKp(FLEn#4KQ-4m1H+R_I6q9^5K&vOTt@PcLK$m~j=iv$=OL`5(05nO9O)?{jsO=wnl^mD1TRm`2V2>`DG;PULWx+8n;tkEMV69*63x O04fR^^5wFY!T$%<5MZ|e From 28ed93dcdc0ab2aa22f75efdce361dfcb99d08b6 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 16:22:43 +0200 Subject: [PATCH 28/47] refactor(web-clipper): use @-imports --- apps/web-clipper/entrypoints/content/index.ts | 8 +++----- apps/web-clipper/entrypoints/popup/popup.ts | 4 ++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/apps/web-clipper/entrypoints/content/index.ts b/apps/web-clipper/entrypoints/content/index.ts index c085dd867..618ad11e1 100644 --- a/apps/web-clipper/entrypoints/content/index.ts +++ b/apps/web-clipper/entrypoints/content/index.ts @@ -1,7 +1,5 @@ -import { Rect } from "@/utils.js"; - -import Readability from "../../lib/Readability.js"; -import { createLink, getBaseUrl, getPageLocationOrigin, randomString } from "../../utils.js"; +import Readability from "@/lib/Readability.js"; +import { createLink, getBaseUrl, getPageLocationOrigin, randomString, Rect } from "@/utils.js"; export default defineContentScript({ matches: [ @@ -267,7 +265,7 @@ export default defineContentScript({ messageText = message.message; } - await import("../../lib/toast"); + await import("@/lib/toast"); window.showToast(messageText, { settings: { diff --git a/apps/web-clipper/entrypoints/popup/popup.ts b/apps/web-clipper/entrypoints/popup/popup.ts index 8f8220de1..40ca451a8 100644 --- a/apps/web-clipper/entrypoints/popup/popup.ts +++ b/apps/web-clipper/entrypoints/popup/popup.ts @@ -1,6 +1,6 @@ -import { createLink } from "../../utils"; +import { createLink } from "@/utils"; -async function sendMessage(message) { +async function sendMessage(message: object) { try { console.log("Sending message", message); return await browser.runtime.sendMessage(message); From bf736977ab1427a73ba76026f61a7f349987c845 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 16:26:16 +0200 Subject: [PATCH 29/47] chore(web-clipper): don't render offscreen for MV2 --- apps/web-clipper/entrypoints/offscreen/index.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/web-clipper/entrypoints/offscreen/index.html b/apps/web-clipper/entrypoints/offscreen/index.html index b6a07c8d2..232bb6390 100644 --- a/apps/web-clipper/entrypoints/offscreen/index.html +++ b/apps/web-clipper/entrypoints/offscreen/index.html @@ -1,7 +1,8 @@ - + + From 680817d81cf0c240a656771fe2e214a2bb4ee1e9 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 16:39:00 +0200 Subject: [PATCH 30/47] fix(web-clipper): duplicate context menu entry --- apps/web-clipper/entrypoints/background/index.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/apps/web-clipper/entrypoints/background/index.ts b/apps/web-clipper/entrypoints/background/index.ts index 953ba552f..5aa2642e8 100644 --- a/apps/web-clipper/entrypoints/background/index.ts +++ b/apps/web-clipper/entrypoints/background/index.ts @@ -110,12 +110,6 @@ export default defineBackground(() => { contexts: ["selection"] }); - browser.contextMenus.create({ - id: "trilium-save-cropped-screenshot", - title: "Clip screenshot to Trilium", - contexts: ["page"] - }); - browser.contextMenus.create({ id: "trilium-save-cropped-screenshot", title: "Crop screen shot to Trilium", From 1fb360e34fc9e263a73df665e17507d6ee26c584 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 16:40:00 +0200 Subject: [PATCH 31/47] chore(web-clipper): remove polyfill --- .../entrypoints/options/index.html | 1 - apps/web-clipper/entrypoints/popup/index.html | 1 - apps/web-clipper/lib/browser-polyfill.js | 1224 ----------------- apps/web-clipper/manifest.json | 12 - 4 files changed, 1238 deletions(-) delete mode 100644 apps/web-clipper/lib/browser-polyfill.js diff --git a/apps/web-clipper/entrypoints/options/index.html b/apps/web-clipper/entrypoints/options/index.html index 4aa2969c2..331a36a02 100644 --- a/apps/web-clipper/entrypoints/options/index.html +++ b/apps/web-clipper/entrypoints/options/index.html @@ -55,7 +55,6 @@ - diff --git a/apps/web-clipper/entrypoints/popup/index.html b/apps/web-clipper/entrypoints/popup/index.html index ee4b13871..cdf2241cf 100644 --- a/apps/web-clipper/entrypoints/popup/index.html +++ b/apps/web-clipper/entrypoints/popup/index.html @@ -46,7 +46,6 @@
Status: unknown
- diff --git a/apps/web-clipper/lib/browser-polyfill.js b/apps/web-clipper/lib/browser-polyfill.js deleted file mode 100644 index c0b5dfd07..000000000 --- a/apps/web-clipper/lib/browser-polyfill.js +++ /dev/null @@ -1,1224 +0,0 @@ -(function (global, factory) { - if (typeof define === "function" && define.amd) { - define("webextension-polyfill", ["module"], factory); - } else if (typeof exports !== "undefined") { - factory(module); - } else { - var mod = { - exports: {} - }; - factory(mod); - global.browser = mod.exports; - } -})(typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : this, function (module) { - /* webextension-polyfill - v0.6.0 - Mon Dec 23 2019 12:32:53 */ - - /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ - - /* vim: set sts=2 sw=2 et tw=80: */ - - /* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - "use strict"; - - if (typeof browser === "undefined" || Object.getPrototypeOf(browser) !== Object.prototype) { - const CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE = "The message port closed before a response was received."; - const SEND_RESPONSE_DEPRECATION_WARNING = "Returning a Promise is the preferred way to send a reply from an onMessage/onMessageExternal listener, as the sendResponse will be removed from the specs (See https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onMessage)"; // Wrapping the bulk of this polyfill in a one-time-use function is a minor - // optimization for Firefox. Since Spidermonkey does not fully parse the - // contents of a function until the first time it's called, and since it will - // never actually need to be called, this allows the polyfill to be included - // in Firefox nearly for free. - - const wrapAPIs = extensionAPIs => { - // NOTE: apiMetadata is associated to the content of the api-metadata.json file - // at build time by replacing the following "include" with the content of the - // JSON file. - const apiMetadata = { - "alarms": { - "clear": { - "minArgs": 0, - "maxArgs": 1 - }, - "clearAll": { - "minArgs": 0, - "maxArgs": 0 - }, - "get": { - "minArgs": 0, - "maxArgs": 1 - }, - "getAll": { - "minArgs": 0, - "maxArgs": 0 - } - }, - "bookmarks": { - "create": { - "minArgs": 1, - "maxArgs": 1 - }, - "get": { - "minArgs": 1, - "maxArgs": 1 - }, - "getChildren": { - "minArgs": 1, - "maxArgs": 1 - }, - "getRecent": { - "minArgs": 1, - "maxArgs": 1 - }, - "getSubTree": { - "minArgs": 1, - "maxArgs": 1 - }, - "getTree": { - "minArgs": 0, - "maxArgs": 0 - }, - "move": { - "minArgs": 2, - "maxArgs": 2 - }, - "remove": { - "minArgs": 1, - "maxArgs": 1 - }, - "removeTree": { - "minArgs": 1, - "maxArgs": 1 - }, - "search": { - "minArgs": 1, - "maxArgs": 1 - }, - "update": { - "minArgs": 2, - "maxArgs": 2 - } - }, - "browserAction": { - "disable": { - "minArgs": 0, - "maxArgs": 1, - "fallbackToNoCallback": true - }, - "enable": { - "minArgs": 0, - "maxArgs": 1, - "fallbackToNoCallback": true - }, - "getBadgeBackgroundColor": { - "minArgs": 1, - "maxArgs": 1 - }, - "getBadgeText": { - "minArgs": 1, - "maxArgs": 1 - }, - "getPopup": { - "minArgs": 1, - "maxArgs": 1 - }, - "getTitle": { - "minArgs": 1, - "maxArgs": 1 - }, - "openPopup": { - "minArgs": 0, - "maxArgs": 0 - }, - "setBadgeBackgroundColor": { - "minArgs": 1, - "maxArgs": 1, - "fallbackToNoCallback": true - }, - "setBadgeText": { - "minArgs": 1, - "maxArgs": 1, - "fallbackToNoCallback": true - }, - "setIcon": { - "minArgs": 1, - "maxArgs": 1 - }, - "setPopup": { - "minArgs": 1, - "maxArgs": 1, - "fallbackToNoCallback": true - }, - "setTitle": { - "minArgs": 1, - "maxArgs": 1, - "fallbackToNoCallback": true - } - }, - "browsingData": { - "remove": { - "minArgs": 2, - "maxArgs": 2 - }, - "removeCache": { - "minArgs": 1, - "maxArgs": 1 - }, - "removeCookies": { - "minArgs": 1, - "maxArgs": 1 - }, - "removeDownloads": { - "minArgs": 1, - "maxArgs": 1 - }, - "removeFormData": { - "minArgs": 1, - "maxArgs": 1 - }, - "removeHistory": { - "minArgs": 1, - "maxArgs": 1 - }, - "removeLocalStorage": { - "minArgs": 1, - "maxArgs": 1 - }, - "removePasswords": { - "minArgs": 1, - "maxArgs": 1 - }, - "removePluginData": { - "minArgs": 1, - "maxArgs": 1 - }, - "settings": { - "minArgs": 0, - "maxArgs": 0 - } - }, - "commands": { - "getAll": { - "minArgs": 0, - "maxArgs": 0 - } - }, - "contextMenus": { - "remove": { - "minArgs": 1, - "maxArgs": 1 - }, - "removeAll": { - "minArgs": 0, - "maxArgs": 0 - }, - "update": { - "minArgs": 2, - "maxArgs": 2 - } - }, - "cookies": { - "get": { - "minArgs": 1, - "maxArgs": 1 - }, - "getAll": { - "minArgs": 1, - "maxArgs": 1 - }, - "getAllCookieStores": { - "minArgs": 0, - "maxArgs": 0 - }, - "remove": { - "minArgs": 1, - "maxArgs": 1 - }, - "set": { - "minArgs": 1, - "maxArgs": 1 - } - }, - "devtools": { - "inspectedWindow": { - "eval": { - "minArgs": 1, - "maxArgs": 2, - "singleCallbackArg": false - } - }, - "panels": { - "create": { - "minArgs": 3, - "maxArgs": 3, - "singleCallbackArg": true - } - } - }, - "downloads": { - "cancel": { - "minArgs": 1, - "maxArgs": 1 - }, - "download": { - "minArgs": 1, - "maxArgs": 1 - }, - "erase": { - "minArgs": 1, - "maxArgs": 1 - }, - "getFileIcon": { - "minArgs": 1, - "maxArgs": 2 - }, - "open": { - "minArgs": 1, - "maxArgs": 1, - "fallbackToNoCallback": true - }, - "pause": { - "minArgs": 1, - "maxArgs": 1 - }, - "removeFile": { - "minArgs": 1, - "maxArgs": 1 - }, - "resume": { - "minArgs": 1, - "maxArgs": 1 - }, - "search": { - "minArgs": 1, - "maxArgs": 1 - }, - "show": { - "minArgs": 1, - "maxArgs": 1, - "fallbackToNoCallback": true - } - }, - "extension": { - "isAllowedFileSchemeAccess": { - "minArgs": 0, - "maxArgs": 0 - }, - "isAllowedIncognitoAccess": { - "minArgs": 0, - "maxArgs": 0 - } - }, - "history": { - "addUrl": { - "minArgs": 1, - "maxArgs": 1 - }, - "deleteAll": { - "minArgs": 0, - "maxArgs": 0 - }, - "deleteRange": { - "minArgs": 1, - "maxArgs": 1 - }, - "deleteUrl": { - "minArgs": 1, - "maxArgs": 1 - }, - "getVisits": { - "minArgs": 1, - "maxArgs": 1 - }, - "search": { - "minArgs": 1, - "maxArgs": 1 - } - }, - "i18n": { - "detectLanguage": { - "minArgs": 1, - "maxArgs": 1 - }, - "getAcceptLanguages": { - "minArgs": 0, - "maxArgs": 0 - } - }, - "identity": { - "launchWebAuthFlow": { - "minArgs": 1, - "maxArgs": 1 - } - }, - "idle": { - "queryState": { - "minArgs": 1, - "maxArgs": 1 - } - }, - "management": { - "get": { - "minArgs": 1, - "maxArgs": 1 - }, - "getAll": { - "minArgs": 0, - "maxArgs": 0 - }, - "getSelf": { - "minArgs": 0, - "maxArgs": 0 - }, - "setEnabled": { - "minArgs": 2, - "maxArgs": 2 - }, - "uninstallSelf": { - "minArgs": 0, - "maxArgs": 1 - } - }, - "notifications": { - "clear": { - "minArgs": 1, - "maxArgs": 1 - }, - "create": { - "minArgs": 1, - "maxArgs": 2 - }, - "getAll": { - "minArgs": 0, - "maxArgs": 0 - }, - "getPermissionLevel": { - "minArgs": 0, - "maxArgs": 0 - }, - "update": { - "minArgs": 2, - "maxArgs": 2 - } - }, - "pageAction": { - "getPopup": { - "minArgs": 1, - "maxArgs": 1 - }, - "getTitle": { - "minArgs": 1, - "maxArgs": 1 - }, - "hide": { - "minArgs": 1, - "maxArgs": 1, - "fallbackToNoCallback": true - }, - "setIcon": { - "minArgs": 1, - "maxArgs": 1 - }, - "setPopup": { - "minArgs": 1, - "maxArgs": 1, - "fallbackToNoCallback": true - }, - "setTitle": { - "minArgs": 1, - "maxArgs": 1, - "fallbackToNoCallback": true - }, - "show": { - "minArgs": 1, - "maxArgs": 1, - "fallbackToNoCallback": true - } - }, - "permissions": { - "contains": { - "minArgs": 1, - "maxArgs": 1 - }, - "getAll": { - "minArgs": 0, - "maxArgs": 0 - }, - "remove": { - "minArgs": 1, - "maxArgs": 1 - }, - "request": { - "minArgs": 1, - "maxArgs": 1 - } - }, - "runtime": { - "getBackgroundPage": { - "minArgs": 0, - "maxArgs": 0 - }, - "getPlatformInfo": { - "minArgs": 0, - "maxArgs": 0 - }, - "openOptionsPage": { - "minArgs": 0, - "maxArgs": 0 - }, - "requestUpdateCheck": { - "minArgs": 0, - "maxArgs": 0 - }, - "sendMessage": { - "minArgs": 1, - "maxArgs": 3 - }, - "sendNativeMessage": { - "minArgs": 2, - "maxArgs": 2 - }, - "setUninstallURL": { - "minArgs": 1, - "maxArgs": 1 - } - }, - "sessions": { - "getDevices": { - "minArgs": 0, - "maxArgs": 1 - }, - "getRecentlyClosed": { - "minArgs": 0, - "maxArgs": 1 - }, - "restore": { - "minArgs": 0, - "maxArgs": 1 - } - }, - "storage": { - "local": { - "clear": { - "minArgs": 0, - "maxArgs": 0 - }, - "get": { - "minArgs": 0, - "maxArgs": 1 - }, - "getBytesInUse": { - "minArgs": 0, - "maxArgs": 1 - }, - "remove": { - "minArgs": 1, - "maxArgs": 1 - }, - "set": { - "minArgs": 1, - "maxArgs": 1 - } - }, - "managed": { - "get": { - "minArgs": 0, - "maxArgs": 1 - }, - "getBytesInUse": { - "minArgs": 0, - "maxArgs": 1 - } - }, - "sync": { - "clear": { - "minArgs": 0, - "maxArgs": 0 - }, - "get": { - "minArgs": 0, - "maxArgs": 1 - }, - "getBytesInUse": { - "minArgs": 0, - "maxArgs": 1 - }, - "remove": { - "minArgs": 1, - "maxArgs": 1 - }, - "set": { - "minArgs": 1, - "maxArgs": 1 - } - } - }, - "tabs": { - "captureVisibleTab": { - "minArgs": 0, - "maxArgs": 2 - }, - "create": { - "minArgs": 1, - "maxArgs": 1 - }, - "detectLanguage": { - "minArgs": 0, - "maxArgs": 1 - }, - "discard": { - "minArgs": 0, - "maxArgs": 1 - }, - "duplicate": { - "minArgs": 1, - "maxArgs": 1 - }, - "executeScript": { - "minArgs": 1, - "maxArgs": 2 - }, - "get": { - "minArgs": 1, - "maxArgs": 1 - }, - "getCurrent": { - "minArgs": 0, - "maxArgs": 0 - }, - "getZoom": { - "minArgs": 0, - "maxArgs": 1 - }, - "getZoomSettings": { - "minArgs": 0, - "maxArgs": 1 - }, - "highlight": { - "minArgs": 1, - "maxArgs": 1 - }, - "insertCSS": { - "minArgs": 1, - "maxArgs": 2 - }, - "move": { - "minArgs": 2, - "maxArgs": 2 - }, - "query": { - "minArgs": 1, - "maxArgs": 1 - }, - "reload": { - "minArgs": 0, - "maxArgs": 2 - }, - "remove": { - "minArgs": 1, - "maxArgs": 1 - }, - "removeCSS": { - "minArgs": 1, - "maxArgs": 2 - }, - "sendMessage": { - "minArgs": 2, - "maxArgs": 3 - }, - "setZoom": { - "minArgs": 1, - "maxArgs": 2 - }, - "setZoomSettings": { - "minArgs": 1, - "maxArgs": 2 - }, - "update": { - "minArgs": 1, - "maxArgs": 2 - } - }, - "topSites": { - "get": { - "minArgs": 0, - "maxArgs": 0 - } - }, - "webNavigation": { - "getAllFrames": { - "minArgs": 1, - "maxArgs": 1 - }, - "getFrame": { - "minArgs": 1, - "maxArgs": 1 - } - }, - "webRequest": { - "handlerBehaviorChanged": { - "minArgs": 0, - "maxArgs": 0 - } - }, - "windows": { - "create": { - "minArgs": 0, - "maxArgs": 1 - }, - "get": { - "minArgs": 1, - "maxArgs": 2 - }, - "getAll": { - "minArgs": 0, - "maxArgs": 1 - }, - "getCurrent": { - "minArgs": 0, - "maxArgs": 1 - }, - "getLastFocused": { - "minArgs": 0, - "maxArgs": 1 - }, - "remove": { - "minArgs": 1, - "maxArgs": 1 - }, - "update": { - "minArgs": 2, - "maxArgs": 2 - } - } - }; - - if (Object.keys(apiMetadata).length === 0) { - throw new Error("api-metadata.json has not been included in browser-polyfill"); - } - /** - * A WeakMap subclass which creates and stores a value for any key which does - * not exist when accessed, but behaves exactly as an ordinary WeakMap - * otherwise. - * - * @param {function} createItem - * A function which will be called in order to create the value for any - * key which does not exist, the first time it is accessed. The - * function receives, as its only argument, the key being created. - */ - - - class DefaultWeakMap extends WeakMap { - constructor(createItem, items = undefined) { - super(items); - this.createItem = createItem; - } - - get(key) { - if (!this.has(key)) { - this.set(key, this.createItem(key)); - } - - return super.get(key); - } - - } - /** - * Returns true if the given object is an object with a `then` method, and can - * therefore be assumed to behave as a Promise. - * - * @param {*} value The value to test. - * @returns {boolean} True if the value is thenable. - */ - - - const isThenable = value => { - return value && typeof value === "object" && typeof value.then === "function"; - }; - /** - * Creates and returns a function which, when called, will resolve or reject - * the given promise based on how it is called: - * - * - If, when called, `chrome.runtime.lastError` contains a non-null object, - * the promise is rejected with that value. - * - If the function is called with exactly one argument, the promise is - * resolved to that value. - * - Otherwise, the promise is resolved to an array containing all of the - * function's arguments. - * - * @param {object} promise - * An object containing the resolution and rejection functions of a - * promise. - * @param {function} promise.resolve - * The promise's resolution function. - * @param {function} promise.rejection - * The promise's rejection function. - * @param {object} metadata - * Metadata about the wrapped method which has created the callback. - * @param {integer} metadata.maxResolvedArgs - * The maximum number of arguments which may be passed to the - * callback created by the wrapped async function. - * - * @returns {function} - * The generated callback function. - */ - - - const makeCallback = (promise, metadata) => { - return (...callbackArgs) => { - if (extensionAPIs.runtime.lastError) { - promise.reject(extensionAPIs.runtime.lastError); - } else if (metadata.singleCallbackArg || callbackArgs.length <= 1 && metadata.singleCallbackArg !== false) { - promise.resolve(callbackArgs[0]); - } else { - promise.resolve(callbackArgs); - } - }; - }; - - const pluralizeArguments = numArgs => numArgs == 1 ? "argument" : "arguments"; - /** - * Creates a wrapper function for a method with the given name and metadata. - * - * @param {string} name - * The name of the method which is being wrapped. - * @param {object} metadata - * Metadata about the method being wrapped. - * @param {integer} metadata.minArgs - * The minimum number of arguments which must be passed to the - * function. If called with fewer than this number of arguments, the - * wrapper will raise an exception. - * @param {integer} metadata.maxArgs - * The maximum number of arguments which may be passed to the - * function. If called with more than this number of arguments, the - * wrapper will raise an exception. - * @param {integer} metadata.maxResolvedArgs - * The maximum number of arguments which may be passed to the - * callback created by the wrapped async function. - * - * @returns {function(object, ...*)} - * The generated wrapper function. - */ - - - const wrapAsyncFunction = (name, metadata) => { - return function asyncFunctionWrapper(target, ...args) { - if (args.length < metadata.minArgs) { - throw new Error(`Expected at least ${metadata.minArgs} ${pluralizeArguments(metadata.minArgs)} for ${name}(), got ${args.length}`); - } - - if (args.length > metadata.maxArgs) { - throw new Error(`Expected at most ${metadata.maxArgs} ${pluralizeArguments(metadata.maxArgs)} for ${name}(), got ${args.length}`); - } - - return new Promise((resolve, reject) => { - if (metadata.fallbackToNoCallback) { - // This API method has currently no callback on Chrome, but it return a promise on Firefox, - // and so the polyfill will try to call it with a callback first, and it will fallback - // to not passing the callback if the first call fails. - try { - target[name](...args, makeCallback({ - resolve, - reject - }, metadata)); - } catch (cbError) { - console.warn(`${name} API method doesn't seem to support the callback parameter, ` + "falling back to call it without a callback: ", cbError); - target[name](...args); // Update the API method metadata, so that the next API calls will not try to - // use the unsupported callback anymore. - - metadata.fallbackToNoCallback = false; - metadata.noCallback = true; - resolve(); - } - } else if (metadata.noCallback) { - target[name](...args); - resolve(); - } else { - target[name](...args, makeCallback({ - resolve, - reject - }, metadata)); - } - }); - }; - }; - /** - * Wraps an existing method of the target object, so that calls to it are - * intercepted by the given wrapper function. The wrapper function receives, - * as its first argument, the original `target` object, followed by each of - * the arguments passed to the original method. - * - * @param {object} target - * The original target object that the wrapped method belongs to. - * @param {function} method - * The method being wrapped. This is used as the target of the Proxy - * object which is created to wrap the method. - * @param {function} wrapper - * The wrapper function which is called in place of a direct invocation - * of the wrapped method. - * - * @returns {Proxy} - * A Proxy object for the given method, which invokes the given wrapper - * method in its place. - */ - - - const wrapMethod = (target, method, wrapper) => { - return new Proxy(method, { - apply(targetMethod, thisObj, args) { - return wrapper.call(thisObj, target, ...args); - } - - }); - }; - - let hasOwnProperty = Function.call.bind(Object.prototype.hasOwnProperty); - /** - * Wraps an object in a Proxy which intercepts and wraps certain methods - * based on the given `wrappers` and `metadata` objects. - * - * @param {object} target - * The target object to wrap. - * - * @param {object} [wrappers = {}] - * An object tree containing wrapper functions for special cases. Any - * function present in this object tree is called in place of the - * method in the same location in the `target` object tree. These - * wrapper methods are invoked as described in {@see wrapMethod}. - * - * @param {object} [metadata = {}] - * An object tree containing metadata used to automatically generate - * Promise-based wrapper functions for asynchronous. Any function in - * the `target` object tree which has a corresponding metadata object - * in the same location in the `metadata` tree is replaced with an - * automatically-generated wrapper function, as described in - * {@see wrapAsyncFunction} - * - * @returns {Proxy} - */ - - const wrapObject = (target, wrappers = {}, metadata = {}) => { - let cache = Object.create(null); - let handlers = { - has(proxyTarget, prop) { - return prop in target || prop in cache; - }, - - get(proxyTarget, prop, receiver) { - if (prop in cache) { - return cache[prop]; - } - - if (!(prop in target)) { - return undefined; - } - - let value = target[prop]; - - if (typeof value === "function") { - // This is a method on the underlying object. Check if we need to do - // any wrapping. - if (typeof wrappers[prop] === "function") { - // We have a special-case wrapper for this method. - value = wrapMethod(target, target[prop], wrappers[prop]); - } else if (hasOwnProperty(metadata, prop)) { - // This is an async method that we have metadata for. Create a - // Promise wrapper for it. - let wrapper = wrapAsyncFunction(prop, metadata[prop]); - value = wrapMethod(target, target[prop], wrapper); - } else { - // This is a method that we don't know or care about. Return the - // original method, bound to the underlying object. - value = value.bind(target); - } - } else if (typeof value === "object" && value !== null && (hasOwnProperty(wrappers, prop) || hasOwnProperty(metadata, prop))) { - // This is an object that we need to do some wrapping for the children - // of. Create a sub-object wrapper for it with the appropriate child - // metadata. - value = wrapObject(value, wrappers[prop], metadata[prop]); - } else if (hasOwnProperty(metadata, "*")) { - // Wrap all properties in * namespace. - value = wrapObject(value, wrappers[prop], metadata["*"]); - } else { - // We don't need to do any wrapping for this property, - // so just forward all access to the underlying object. - Object.defineProperty(cache, prop, { - configurable: true, - enumerable: true, - - get() { - return target[prop]; - }, - - set(value) { - target[prop] = value; - } - - }); - return value; - } - - cache[prop] = value; - return value; - }, - - set(proxyTarget, prop, value, receiver) { - if (prop in cache) { - cache[prop] = value; - } else { - target[prop] = value; - } - - return true; - }, - - defineProperty(proxyTarget, prop, desc) { - return Reflect.defineProperty(cache, prop, desc); - }, - - deleteProperty(proxyTarget, prop) { - return Reflect.deleteProperty(cache, prop); - } - - }; // Per contract of the Proxy API, the "get" proxy handler must return the - // original value of the target if that value is declared read-only and - // non-configurable. For this reason, we create an object with the - // prototype set to `target` instead of using `target` directly. - // Otherwise we cannot return a custom object for APIs that - // are declared read-only and non-configurable, such as `chrome.devtools`. - // - // The proxy handlers themselves will still use the original `target` - // instead of the `proxyTarget`, so that the methods and properties are - // dereferenced via the original targets. - - let proxyTarget = Object.create(target); - return new Proxy(proxyTarget, handlers); - }; - /** - * Creates a set of wrapper functions for an event object, which handles - * wrapping of listener functions that those messages are passed. - * - * A single wrapper is created for each listener function, and stored in a - * map. Subsequent calls to `addListener`, `hasListener`, or `removeListener` - * retrieve the original wrapper, so that attempts to remove a - * previously-added listener work as expected. - * - * @param {DefaultWeakMap} wrapperMap - * A DefaultWeakMap object which will create the appropriate wrapper - * for a given listener function when one does not exist, and retrieve - * an existing one when it does. - * - * @returns {object} - */ - - - const wrapEvent = wrapperMap => ({ - addListener(target, listener, ...args) { - target.addListener(wrapperMap.get(listener), ...args); - }, - - hasListener(target, listener) { - return target.hasListener(wrapperMap.get(listener)); - }, - - removeListener(target, listener) { - target.removeListener(wrapperMap.get(listener)); - } - - }); // Keep track if the deprecation warning has been logged at least once. - - - let loggedSendResponseDeprecationWarning = false; - const onMessageWrappers = new DefaultWeakMap(listener => { - if (typeof listener !== "function") { - return listener; - } - /** - * Wraps a message listener function so that it may send responses based on - * its return value, rather than by returning a sentinel value and calling a - * callback. If the listener function returns a Promise, the response is - * sent when the promise either resolves or rejects. - * - * @param {*} message - * The message sent by the other end of the channel. - * @param {object} sender - * Details about the sender of the message. - * @param {function(*)} sendResponse - * A callback which, when called with an arbitrary argument, sends - * that value as a response. - * @returns {boolean} - * True if the wrapped listener returned a Promise, which will later - * yield a response. False otherwise. - */ - - - return function onMessage(message, sender, sendResponse) { - let didCallSendResponse = false; - let wrappedSendResponse; - let sendResponsePromise = new Promise(resolve => { - wrappedSendResponse = function (response) { - if (!loggedSendResponseDeprecationWarning) { - console.warn(SEND_RESPONSE_DEPRECATION_WARNING, new Error().stack); - loggedSendResponseDeprecationWarning = true; - } - - didCallSendResponse = true; - resolve(response); - }; - }); - let result; - - try { - result = listener(message, sender, wrappedSendResponse); - } catch (err) { - result = Promise.reject(err); - } - - const isResultThenable = result !== true && isThenable(result); // If the listener didn't returned true or a Promise, or called - // wrappedSendResponse synchronously, we can exit earlier - // because there will be no response sent from this listener. - - if (result !== true && !isResultThenable && !didCallSendResponse) { - return false; - } // A small helper to send the message if the promise resolves - // and an error if the promise rejects (a wrapped sendMessage has - // to translate the message into a resolved promise or a rejected - // promise). - - - const sendPromisedResult = promise => { - promise.then(msg => { - // send the message value. - sendResponse(msg); - }, error => { - // Send a JSON representation of the error if the rejected value - // is an instance of error, or the object itself otherwise. - let message; - - if (error && (error instanceof Error || typeof error.message === "string")) { - message = error.message; - } else { - message = "An unexpected error occurred"; - } - - sendResponse({ - __mozWebExtensionPolyfillReject__: true, - message - }); - }).catch(err => { - // Print an error on the console if unable to send the response. - console.error("Failed to send onMessage rejected reply", err); - }); - }; // If the listener returned a Promise, send the resolved value as a - // result, otherwise wait the promise related to the wrappedSendResponse - // callback to resolve and send it as a response. - - - if (isResultThenable) { - sendPromisedResult(result); - } else { - sendPromisedResult(sendResponsePromise); - } // Let Chrome know that the listener is replying. - - - return true; - }; - }); - - const wrappedSendMessageCallback = ({ - reject, - resolve - }, reply) => { - if (extensionAPIs.runtime.lastError) { - // Detect when none of the listeners replied to the sendMessage call and resolve - // the promise to undefined as in Firefox. - // See https://github.com/mozilla/webextension-polyfill/issues/130 - if (extensionAPIs.runtime.lastError.message === CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE) { - resolve(); - } else { - reject(extensionAPIs.runtime.lastError); - } - } else if (reply && reply.__mozWebExtensionPolyfillReject__) { - // Convert back the JSON representation of the error into - // an Error instance. - reject(new Error(reply.message)); - } else { - resolve(reply); - } - }; - - const wrappedSendMessage = (name, metadata, apiNamespaceObj, ...args) => { - if (args.length < metadata.minArgs) { - throw new Error(`Expected at least ${metadata.minArgs} ${pluralizeArguments(metadata.minArgs)} for ${name}(), got ${args.length}`); - } - - if (args.length > metadata.maxArgs) { - throw new Error(`Expected at most ${metadata.maxArgs} ${pluralizeArguments(metadata.maxArgs)} for ${name}(), got ${args.length}`); - } - - return new Promise((resolve, reject) => { - const wrappedCb = wrappedSendMessageCallback.bind(null, { - resolve, - reject - }); - args.push(wrappedCb); - apiNamespaceObj.sendMessage(...args); - }); - }; - - const staticWrappers = { - runtime: { - onMessage: wrapEvent(onMessageWrappers), - onMessageExternal: wrapEvent(onMessageWrappers), - sendMessage: wrappedSendMessage.bind(null, "sendMessage", { - minArgs: 1, - maxArgs: 3 - }) - }, - tabs: { - sendMessage: wrappedSendMessage.bind(null, "sendMessage", { - minArgs: 2, - maxArgs: 3 - }) - } - }; - const settingMetadata = { - clear: { - minArgs: 1, - maxArgs: 1 - }, - get: { - minArgs: 1, - maxArgs: 1 - }, - set: { - minArgs: 1, - maxArgs: 1 - } - }; - apiMetadata.privacy = { - network: { - "*": settingMetadata - }, - services: { - "*": settingMetadata - }, - websites: { - "*": settingMetadata - } - }; - return wrapObject(extensionAPIs, staticWrappers, apiMetadata); - }; - - if (typeof chrome != "object" || !chrome || !chrome.runtime || !chrome.runtime.id) { - throw new Error("This script should only be loaded in a browser extension."); - } // The build process adds a UMD wrapper around this file, which makes the - // `module` variable available. - - - module.exports = wrapAPIs(chrome); - } else { - module.exports = browser; - } -}); -//# sourceMappingURL=browser-polyfill.js.map diff --git a/apps/web-clipper/manifest.json b/apps/web-clipper/manifest.json index 12694338c..8c7754a06 100644 --- a/apps/web-clipper/manifest.json +++ b/apps/web-clipper/manifest.json @@ -13,18 +13,6 @@ "default_title": "Trilium Web Clipper", "default_popup": "popup/popup.html" }, - "content_scripts": [ - { - "js": [ - "lib/browser-polyfill.js" - ] - } - ], - "background": { - "scripts": [ - "lib/browser-polyfill.js" - ] - }, "commands": { "saveSelection": { "description": "Save the selected text into a note", From a2a37a0b54302ebf605adb910ddc4c4d29fbdc68 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 16:45:58 +0200 Subject: [PATCH 32/47] chore(web-clipper): integrate old manifest --- apps/web-clipper/manifest.json | 36 ---------------------------------- apps/web-clipper/package.json | 2 +- apps/web-clipper/wxt.config.ts | 21 ++++++++++++++++++++ 3 files changed, 22 insertions(+), 37 deletions(-) delete mode 100644 apps/web-clipper/manifest.json diff --git a/apps/web-clipper/manifest.json b/apps/web-clipper/manifest.json deleted file mode 100644 index 8c7754a06..000000000 --- a/apps/web-clipper/manifest.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "manifest_version": 2, - "version": "1.0.1", - "homepage_url": "https://github.com/zadam/trilium-web-clipper", - "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'", - "icons": { - "32": "icons/32.png", - "48": "icons/48.png", - "96": "icons/96.png" - }, - "browser_action": { - "default_icon": "icons/32.png", - "default_title": "Trilium Web Clipper", - "default_popup": "popup/popup.html" - }, - "commands": { - "saveSelection": { - "description": "Save the selected text into a note", - "suggested_key": { - "default": "Ctrl+Shift+S" - } - }, - "saveWholePage": { - "description": "Save the current page", - "suggested_key": { - "default": "Alt+Shift+S" - } - }, - "saveCroppedScreenshot": { - "description": "Take a cropped screenshot of the current page", - "suggested_key": { - "default": "Ctrl+Shift+E" - } - } - } -} diff --git a/apps/web-clipper/package.json b/apps/web-clipper/package.json index 8dcc7ec6c..e975e1ac4 100644 --- a/apps/web-clipper/package.json +++ b/apps/web-clipper/package.json @@ -1,6 +1,6 @@ { "name": "@triliumnext/web-clipper", - "version": "1.0.0", + "version": "1.0.1", "description": "", "main": "index.js", "scripts": { diff --git a/apps/web-clipper/wxt.config.ts b/apps/web-clipper/wxt.config.ts index f824eb64c..98932d746 100644 --- a/apps/web-clipper/wxt.config.ts +++ b/apps/web-clipper/wxt.config.ts @@ -5,6 +5,7 @@ export default defineConfig({ manifest: { name: "Trilium Web Clipper", description: "Save web clippings to Trilium Notes.", + homepage_url: "https://docs.triliumnotes.org/user-guide/setup/web-clipper", permissions: [ "activeTab", "tabs", @@ -19,6 +20,26 @@ export default defineConfig({ gecko: { id: "{1410742d-b377-40e7-a9db-63dc9c6ec99c}" } + }, + commands: { + saveSelection: { + description: "Save the selected text into a note", + suggested_key: { + default: "Ctrl+Shift+S" + } + }, + saveWholePage: { + description: "Save the current page", + suggested_key: { + default: "Alt+Shift+S" + } + }, + saveCroppedScreenshot: { + description: "Take a cropped screenshot of the current page", + suggested_key: { + default: "Ctrl+Shift+E" + } + } } } }); From 53e3d65c52b984b664944f7ee9a43058e268835e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 17:01:03 +0200 Subject: [PATCH 33/47] fix(web-clipper): handling of dev port --- apps/web-clipper/entrypoints/background/index.ts | 10 +--------- .../entrypoints/background/trilium_server_facade.ts | 8 +------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/apps/web-clipper/entrypoints/background/index.ts b/apps/web-clipper/entrypoints/background/index.ts index 5aa2642e8..7ca94f21e 100644 --- a/apps/web-clipper/entrypoints/background/index.ts +++ b/apps/web-clipper/entrypoints/background/index.ts @@ -1,6 +1,6 @@ import { randomString, Rect } from "@/utils"; -import TriliumServerFacade, { isDevEnv } from "./trilium_server_facade"; +import TriliumServerFacade from "./trilium_server_facade"; export default defineBackground(() => { const triliumServerFacade = new TriliumServerFacade(); @@ -96,14 +96,6 @@ export default defineBackground(() => { return await browser.tabs.captureVisibleTab({ format: 'png' }); } - browser.runtime.onInstalled.addListener(() => { - if (isDevEnv()) { - browser.browserAction.setIcon({ - path: 'icons/32-dev.png', - }); - } - }); - browser.contextMenus.create({ id: "trilium-save-selection", title: "Save selection to Trilium", diff --git a/apps/web-clipper/entrypoints/background/trilium_server_facade.ts b/apps/web-clipper/entrypoints/background/trilium_server_facade.ts index f9854437f..3e65b849d 100644 --- a/apps/web-clipper/entrypoints/background/trilium_server_facade.ts +++ b/apps/web-clipper/entrypoints/background/trilium_server_facade.ts @@ -1,11 +1,5 @@ const PROTOCOL_VERSION_MAJOR = 1; -export function isDevEnv() { - const manifest = browser.runtime.getManifest(); - - return manifest.name.endsWith('(dev)'); -} - type TriliumSearchStatus = { status: "searching"; } | { @@ -199,7 +193,7 @@ export default class TriliumServerFacade { return parseInt(triliumDesktopPort, 10); } - return isDevEnv() ? 37740 : 37840; + return import.meta.env.DEV ? 37742 : 37840; } async callService(method: string, path: string, body?: string | object) { From bba69e98aef5d1ff73ca60bf102a1a01d00396ec Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 17:07:53 +0200 Subject: [PATCH 34/47] fix(web-clipper): warning about offscreen permission in MV2 --- apps/web-clipper/wxt.config.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/web-clipper/wxt.config.ts b/apps/web-clipper/wxt.config.ts index 98932d746..9c41b9810 100644 --- a/apps/web-clipper/wxt.config.ts +++ b/apps/web-clipper/wxt.config.ts @@ -2,7 +2,7 @@ import { defineConfig } from "wxt"; export default defineConfig({ modules: ['@wxt-dev/auto-icons'], - manifest: { + manifest: ({ manifestVersion }) => ({ name: "Trilium Web Clipper", description: "Save web clippings to Trilium Notes.", homepage_url: "https://docs.triliumnotes.org/user-guide/setup/web-clipper", @@ -14,8 +14,8 @@ export default defineConfig({ "", "storage", "contextMenus", - "offscreen" - ], + manifestVersion === 3 && "offscreen" + ].filter(Boolean), browser_specific_settings: { gecko: { id: "{1410742d-b377-40e7-a9db-63dc9c6ec99c}" @@ -41,5 +41,5 @@ export default defineConfig({ } } } - } + }) }); From c2a758dd4af3f5c81ec07c343546d180f5f18684 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 17:23:06 +0200 Subject: [PATCH 35/47] chore(web-clipper): address requested changes --- .../entrypoints/background/index.ts | 39 +++++++++++-------- .../background/trilium_server_facade.ts | 4 +- apps/web-clipper/entrypoints/content/index.ts | 15 ++++--- 3 files changed, 31 insertions(+), 27 deletions(-) diff --git a/apps/web-clipper/entrypoints/background/index.ts b/apps/web-clipper/entrypoints/background/index.ts index 7ca94f21e..5ac45ba92 100644 --- a/apps/web-clipper/entrypoints/background/index.ts +++ b/apps/web-clipper/entrypoints/background/index.ts @@ -7,18 +7,23 @@ export default defineBackground(() => { // Keyboard shortcuts browser.commands.onCommand.addListener(async (command) => { - if (command == "saveSelection") { - await saveSelection(); - } else if (command == "saveWholePage") { - await saveWholePage(); - } else if (command == "saveTabs") { - await saveTabs(); - } else if (command == "saveCroppedScreenshot") { - const activeTab = await getActiveTab(); - - await saveCroppedScreenshot(activeTab.url); - } else { - console.log("Unrecognized command", command); + switch (command) { + case "saveSelection": + await saveSelection(); + break; + case "saveWholePage": + await saveWholePage(); + break; + case "saveTabs": + await saveTabs(); + break; + case "saveCroppedScreenshot": { + const activeTab = await getActiveTab(); + await saveCroppedScreenshot(activeTab.url); + break; + } + default: + console.log("Unrecognized command", command); } }); @@ -39,6 +44,7 @@ export default defineBackground(() => { ctx.drawImage(img, newArea.x, newArea.y, newArea.width, newArea.height, 0, 0, newArea.width, newArea.height); resolve(canvas.toDataURL()); }; + img.onerror = reject; img.src = dataUrl; }); @@ -188,14 +194,15 @@ export default defineBackground(() => { async function postProcessImage(image: { src: string, dataUrl?: string | null }) { if (image.src.startsWith("data:image/")) { image.dataUrl = image.src; - image.src = `inline.${ image.src.substr(11, 3)}`; // this should extract file type - png/jpg + const mimeSubtype = image.src.match(/data:image\/(.*?);/)?.[1]; + if (!mimeSubtype) return; + image.src = `inline.${mimeSubtype}`; // this should extract file type - png/jpg } else { try { image.dataUrl = await fetchImage(image.src); - } - catch (e) { - console.log(`Cannot fetch image from ${image.src}`); + } catch (e) { + console.error(`Cannot fetch image from ${image.src}`, e); } } } diff --git a/apps/web-clipper/entrypoints/background/trilium_server_facade.ts b/apps/web-clipper/entrypoints/background/trilium_server_facade.ts index 3e65b849d..43247a9e7 100644 --- a/apps/web-clipper/entrypoints/background/trilium_server_facade.ts +++ b/apps/web-clipper/entrypoints/background/trilium_server_facade.ts @@ -154,7 +154,7 @@ export default class TriliumServerFacade { this.setTriliumSearch({ status: 'not-found' }); } - async triggerSearchNoteByUrl(noteUrl) { + async triggerSearchNoteByUrl(noteUrl: string) { const resp = await this.callService('GET', `notes-by-url/${encodeURIComponent(noteUrl)}`); let newStatus: TriliumSearchNoteStatus; if (resp && resp.noteId) { @@ -229,8 +229,6 @@ export default class TriliumServerFacade { catch (e) { console.log("Sending request to trilium failed", e); - window.showToast('Your request failed because we could not contact Trilium instance. Please make sure Trilium is running and is accessible.'); - return null; } } diff --git a/apps/web-clipper/entrypoints/content/index.ts b/apps/web-clipper/entrypoints/content/index.ts index 618ad11e1..b708bd639 100644 --- a/apps/web-clipper/entrypoints/content/index.ts +++ b/apps/web-clipper/entrypoints/content/index.ts @@ -55,12 +55,12 @@ export default defineContentScript({ let modifiedDate: Date | null = null; const articlePublishedTime = document.querySelector("meta[property='article:published_time']")?.getAttribute('content'); - if (articlePublishedTime && articlePublishedTime) { + if (articlePublishedTime) { publishedDate = new Date(articlePublishedTime); } const articleModifiedTime = document.querySelector("meta[property='article:modified_time']")?.getAttribute('content'); - if (articleModifiedTime && articleModifiedTime) { + if (articleModifiedTime) { modifiedDate = new Date(articleModifiedTime); } @@ -129,7 +129,7 @@ export default defineContentScript({ selection.style.height = `${selectionArea.height}px`; } - function setSelectionSizeFromMouse(event) { + function setSelectionSizeFromMouse(event: MouseEvent) { if (!draggingStartPos) return; if (event.clientX < draggingStartPos.x) { @@ -145,14 +145,14 @@ export default defineContentScript({ updateSelection(); } - function selection_mouseDown(event) { + function selection_mouseDown(event: MouseEvent) { selectionArea = {x: event.clientX, y: event.clientY, width: 0, height: 0}; draggingStartPos = {x: event.clientX, y: event.clientY}; isDragging = true; updateSelection(); } - function selection_mouseMove(event) { + function selection_mouseMove(event: MouseEvent) { if (!isDragging) return; setSelectionSizeFromMouse(event); } @@ -169,7 +169,7 @@ export default defineContentScript({ document.body.removeChild(messageComp); } - function selection_mouseUp(event) { + function selection_mouseUp(event: MouseEvent) { setSelectionSizeFromMouse(event); removeOverlay(); @@ -186,7 +186,7 @@ export default defineContentScript({ setTimeout(() => resolve(selectionArea), 100); } - function cancel(event) { + function cancel(event: KeyboardEvent) { if (event.key === "Escape") { removeOverlay(); } @@ -195,7 +195,6 @@ export default defineContentScript({ overlay.addEventListener('mousedown', selection_mouseDown); overlay.addEventListener('mousemove', selection_mouseMove); overlay.addEventListener('mouseup', selection_mouseUp); - overlay.addEventListener('mouseup', selection_mouseUp); messageComp.addEventListener('keydown', cancel); }); } From 9d347ff3d9a2bc856ce462b3dab9a0bf3d6f47d1 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 17:25:09 +0200 Subject: [PATCH 36/47] chore(web-clipper): update help URL --- apps/web-clipper/entrypoints/popup/popup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web-clipper/entrypoints/popup/popup.ts b/apps/web-clipper/entrypoints/popup/popup.ts index 40ca451a8..273fe5c5b 100644 --- a/apps/web-clipper/entrypoints/popup/popup.ts +++ b/apps/web-clipper/entrypoints/popup/popup.ts @@ -101,7 +101,7 @@ async function saveLinkWithNote() { $("#save-button").on("click", saveLinkWithNote); $("#show-help-button").on("click", () => { - window.open("https://github.com/zadam/trilium/wiki/Web-clipper", '_blank'); + window.open("https://docs.triliumnotes.org/user-guide/setup/web-clipper", '_blank'); }); function escapeHtml(string) { From d0f441ec7492b60aa1852f511afebe4c618b7395 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 17:38:36 +0200 Subject: [PATCH 37/47] ci(web-clipper): generate .zip files on change --- .github/workflows/web-clipper.yml | 44 +++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .github/workflows/web-clipper.yml diff --git a/.github/workflows/web-clipper.yml b/.github/workflows/web-clipper.yml new file mode 100644 index 000000000..412b2ad6f --- /dev/null +++ b/.github/workflows/web-clipper.yml @@ -0,0 +1,44 @@ +name: Deploy web clipper extension + +on: + push: + branches: + - main + paths: + - "apps/web-clipper/**" + + pull_request: + paths: + - "apps/web-clipper/**" + +jobs: + build: + runs-on: ubuntu-latest + name: Build web clipper extension + + permissions: + contents: read + deployments: write + + steps: + - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v4 + - name: Set up node & dependencies + uses: actions/setup-node@v6 + with: + node-version: 24 + cache: "pnpm" + + - name: Install dependencies + run: pnpm install --filter web-clipper --frozen-lockfile --ignore-scripts + + - name: Build the web clipper extension + run: | + pnpm --filter web-clipper zip + pnpm --filter web-clipper zip:firefox + + - name: Upload build artifacts + uses: actions/upload-artifact@v6 + with: + name: web-clipper-extension + path: .output/*.zip From 4c978d8622e53f1a208cda79c8cb74fc76c2a639 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 17:41:51 +0200 Subject: [PATCH 38/47] ci(web-clipper): fail if no files found --- .github/workflows/web-clipper.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/web-clipper.yml b/.github/workflows/web-clipper.yml index 412b2ad6f..f12fdb11d 100644 --- a/.github/workflows/web-clipper.yml +++ b/.github/workflows/web-clipper.yml @@ -42,3 +42,4 @@ jobs: with: name: web-clipper-extension path: .output/*.zip + if-no-files-found: error From cb0fabf273f14dfdccf0312c102123e3a0802c08 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 17:43:11 +0200 Subject: [PATCH 39/47] ci(web-clipper): fail if no files found --- .github/workflows/web-clipper.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/web-clipper.yml b/.github/workflows/web-clipper.yml index f12fdb11d..cd9de9a79 100644 --- a/.github/workflows/web-clipper.yml +++ b/.github/workflows/web-clipper.yml @@ -41,5 +41,5 @@ jobs: uses: actions/upload-artifact@v6 with: name: web-clipper-extension - path: .output/*.zip + path: apps/web-clipper/.output/*.zip if-no-files-found: error From 625062a2683324e1d1860ba82f36d538c7a6f3c0 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 17:46:48 +0200 Subject: [PATCH 40/47] ci(web-clipper): no files uploaded --- .github/workflows/web-clipper.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/web-clipper.yml b/.github/workflows/web-clipper.yml index cd9de9a79..ad16ee972 100644 --- a/.github/workflows/web-clipper.yml +++ b/.github/workflows/web-clipper.yml @@ -42,4 +42,5 @@ jobs: with: name: web-clipper-extension path: apps/web-clipper/.output/*.zip + include-hidden-files: true if-no-files-found: error From 743a6f3466c18688e8b05caa3369c8fb94cc9165 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 18:12:02 +0200 Subject: [PATCH 41/47] ci(web-clipper): disable compression level for artifact --- .github/workflows/web-clipper.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/web-clipper.yml b/.github/workflows/web-clipper.yml index ad16ee972..ef360a5a9 100644 --- a/.github/workflows/web-clipper.yml +++ b/.github/workflows/web-clipper.yml @@ -44,3 +44,4 @@ jobs: path: apps/web-clipper/.output/*.zip include-hidden-files: true if-no-files-found: error + compression-level: 0 From 199962233b372838b750b81584c9034a77ccf7da Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 19:04:38 +0200 Subject: [PATCH 42/47] chore(web-clipper): use friendly ID for Firefox --- apps/web-clipper/wxt.config.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/web-clipper/wxt.config.ts b/apps/web-clipper/wxt.config.ts index 9c41b9810..f3389c1ef 100644 --- a/apps/web-clipper/wxt.config.ts +++ b/apps/web-clipper/wxt.config.ts @@ -18,7 +18,8 @@ export default defineConfig({ ].filter(Boolean), browser_specific_settings: { gecko: { - id: "{1410742d-b377-40e7-a9db-63dc9c6ec99c}" + // See https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/browser_specific_settings#id. + id: "web-clipper@triliumnotes.org" } }, commands: { From 1cf93ff0dec89ee1a80654934cb30fad74920043 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 19:25:16 +0200 Subject: [PATCH 43/47] chore(web-clipper): add data_collection_permissions for Firefox --- apps/web-clipper/wxt.config.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/web-clipper/wxt.config.ts b/apps/web-clipper/wxt.config.ts index f3389c1ef..71f7df37a 100644 --- a/apps/web-clipper/wxt.config.ts +++ b/apps/web-clipper/wxt.config.ts @@ -19,7 +19,14 @@ export default defineConfig({ browser_specific_settings: { gecko: { // See https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/browser_specific_settings#id. - id: "web-clipper@triliumnotes.org" + id: "web-clipper@triliumnotes.org", + // Firefox built-in data collection consent + // See https://extensionworkshop.com/documentation/develop/firefox-builtin-data-consent/ + // This extension only communicates with a user-configured Trilium instance + // and does not collect telemetry or send data to remote servers. + data_collection_permissions: { + required: ["none"] + } } }, commands: { From ada22e496679ea366b16704327d67b6577cc6b51 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 21:24:14 +0200 Subject: [PATCH 44/47] docs(user): update web clipper --- .../Installation & Setup/Web Clipper.html | 55 ++++++++++++++----- apps/web-clipper/README.md | 24 -------- .../Developer Guide/Documentation.md | 2 +- .../Installation & Setup/Web Clipper.md | 37 ++++++++++--- 4 files changed, 73 insertions(+), 45 deletions(-) delete mode 100644 apps/web-clipper/README.md diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Installation & Setup/Web Clipper.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Installation & Setup/Web Clipper.html index b406c7b3c..effb6e395 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Installation & Setup/Web Clipper.html +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Installation & Setup/Web Clipper.html @@ -4,17 +4,34 @@

Trilium Web Clipper is a web browser extension which allows user to clip text, screenshots, whole pages and short notes and save them directly to Trilium Notes.

-

Project is hosted here.

-

Firefox and Chrome are supported browsers, but the chrome build should - work on other chromium based browsers as well.

+

Supported browsers

+

Trilium Web Clipper officially supports the following web browsers:

+
    +
  • +

    Mozilla Firefox, using Manifest v2.

    +
  • +
  • +

    Google Chrome, using Manifest v3. Theoretically the extension should work + on other Chromium-based browsers as well, but they are not officially supported.

    +
  • +
+

Obtaining the extension

+

Functionality

  • select text and clip it with the right-click context menu
  • click on an image or link and save it through context menu
  • save whole page from the popup or context menu
  • save screenshot (with crop tool) from either popup or context menu
  • -
  • create short text note from popup
  • +
  • create short text note from popup
+

Location of clippings

Trilium will save these clippings as a new child note under a "clipper inbox" note.

By default, that's the day note but you @@ -23,21 +40,33 @@ spellcheck="false">clipperInbox, on any other note.

If there's multiple clippings from the same page (and on the same day), then they will be added to the same note.

-

Extension is available from: -

+

Keyboard shortcuts

+

Keyboard shortcuts are available for most functions:

    -
  • Project release page - - .xpi for Firefox and .zip for Chromium based browsers.
  • -
  • Chrome Web Store +
  • Save selected text: Ctrl+Shift+S (Mac: ++S)
  • +
  • Save whole page: Alt+Shift+S (Mac: ++S)
  • +
  • Save screenshot: Ctrl+Shift+E (Mac: ++E)
  • +
+

To set custom shortcuts, follow the directions for your browser.

+
    +
  • Firefox: about:addons → + Gear icon ⚙️ → Manage extension shortcuts
  • +
  • Chrome: chrome://extensions/shortcuts
+

Configuration

The extension needs to connect to a running Trilium instance. By default, it scans a port range on the local computer to find a desktop Trilium instance.

It's also possible to configure the server address if you don't run the desktop application, or want it to work without the desktop application running.

-

Username

-

Older versions of Trilium (before 0.50) required username & password - to authenticate, but this is no longer the case. You may enter anything - in that field, it will not have any effect.

\ No newline at end of file +

Credits

+

Some parts of the code are based on the Joplin Notes browser extension.

\ No newline at end of file diff --git a/apps/web-clipper/README.md b/apps/web-clipper/README.md deleted file mode 100644 index a37d0e181..000000000 --- a/apps/web-clipper/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# Trilium Web Clipper - -## This repo is dead - -**Trilium is in maintenance mode and Web Clipper is not likely to get new releases.** - -Trilium Web Clipper is a web browser extension which allows user to clip text, screenshots, whole pages and short notes and save them directly to [Trilium Notes](https://github.com/zadam/trilium). - -For more details, see the [wiki page](https://github.com/zadam/trilium/wiki/Web-clipper). - -## Keyboard shortcuts -Keyboard shortcuts are available for most functions: -* Save selected text: `Ctrl+Shift+S` (Mac: `Cmd+Shift+S`) -* Save whole page: `Alt+Shift+S` (Mac: `Opt+Shift+S`) -* Save screenshot: `Ctrl+Shift+E` (Mac: `Cmd+Shift+E`) - -To set custom shortcuts, follow the directions for your browser. - -**Firefox**: `about:addons` > Gear icon ⚙️ > Manage extension shortcuts - -**Chrome**: `chrome://extensions/shortcuts` - -## Credits -Some parts of the code are based on the [Joplin Notes browser extension](https://github.com/laurent22/joplin/tree/master/Clipper). diff --git a/docs/Developer Guide/Developer Guide/Documentation.md b/docs/Developer Guide/Developer Guide/Documentation.md index ea5d135a2..d840ffd46 100644 --- a/docs/Developer Guide/Developer Guide/Documentation.md +++ b/docs/Developer Guide/Developer Guide/Documentation.md @@ -1,5 +1,5 @@ # Documentation -There are multiple types of documentation for Trilium: +There are multiple types of documentation for Trilium: * The _User Guide_ represents the user-facing documentation. This documentation can be browsed by users directly from within Trilium, by pressing F1. * The _Developer's Guide_ represents a set of Markdown documents that present the internals of Trilium, for developers. diff --git a/docs/User Guide/User Guide/Installation & Setup/Web Clipper.md b/docs/User Guide/User Guide/Installation & Setup/Web Clipper.md index bd5731d34..a74fb72d0 100644 --- a/docs/User Guide/User Guide/Installation & Setup/Web Clipper.md +++ b/docs/User Guide/User Guide/Installation & Setup/Web Clipper.md @@ -3,9 +3,19 @@ Trilium Web Clipper is a web browser extension which allows user to clip text, screenshots, whole pages and short notes and save them directly to Trilium Notes. -Project is hosted [here](https://github.com/TriliumNext/web-clipper). +## Supported browsers -Firefox and Chrome are supported browsers, but the chrome build should work on other chromium based browsers as well. +Trilium Web Clipper officially supports the following web browsers: + +* Mozilla Firefox, using Manifest v2. +* Google Chrome, using Manifest v3. Theoretically the extension should work on other Chromium-based browsers as well, but they are not officially supported. + +## Obtaining the extension + +> [!WARNING] +> The extension is currently under development. A preview with unsigned extensions is available on [GitHub Actions](https://github.com/TriliumNext/Trilium/actions/runs/21318809414). +> +> We have already submitted the extension to both Chrome and Firefox web stores, but they are pending validation. ## Functionality @@ -15,16 +25,29 @@ Firefox and Chrome are supported browsers, but the chrome build should work on o * save screenshot (with crop tool) from either popup or context menu * create short text note from popup +## Location of clippings + Trilium will save these clippings as a new child note under a "clipper inbox" note. By default, that's the [day note](../Advanced%20Usage/Advanced%20Showcases/Day%20Notes.md) but you can override that by setting the [label](../Advanced%20Usage/Attributes.md) `clipperInbox`, on any other note. If there's multiple clippings from the same page (and on the same day), then they will be added to the same note. -**Extension is available from:** +## Keyboard shortcuts -* [Project release page](https://github.com/TriliumNext/web-clipper/releases) - .xpi for Firefox and .zip for Chromium based browsers. -* [Chrome Web Store](https://chromewebstore.google.com/detail/trilium-web-clipper/dfhgmnfclbebfobmblelddiejjcijbjm) +Keyboard shortcuts are available for most functions: + +* Save selected text: Ctrl+Shift+S (Mac: ++S) +* Save whole page: Alt+Shift+S (Mac: ++S) +* Save screenshot: Ctrl+Shift+E (Mac: ++E) + +To set custom shortcuts, follow the directions for your browser. + +* **Firefox**: `about:addons` → Gear icon ⚙️ → Manage extension shortcuts +* **Chrome**: `chrome://extensions/shortcuts` + +> [!NOTE] +> On Firefox, the default shortcuts interfere with some browser features. As such, the keyboard combinations will not trigger the Web Clipper action. To fix this, simply change the keyboard shortcut to something that works. The defaults will be adjusted in future versions. ## Configuration @@ -32,6 +55,6 @@ The extension needs to connect to a running Trilium instance. By default, it sca It's also possible to configure the [server](Server%20Installation.md) address if you don't run the desktop application, or want it to work without the desktop application running. -## Username +## Credits -Older versions of Trilium (before 0.50) required username & password to authenticate, but this is no longer the case. You may enter anything in that field, it will not have any effect. \ No newline at end of file +Some parts of the code are based on the [Joplin Notes browser extension](https://github.com/laurent22/joplin/tree/master/Clipper). \ No newline at end of file From e2e5d485d7281e091fd65b0bddfd760bb99c5c5f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 21:35:54 +0200 Subject: [PATCH 45/47] docs(dev): add some information on web clipper --- docs/Developer Guide/!!!meta.json | 34 ++++++++++++++++++ .../Developer Guide/Concepts/Web Clipper.md | 35 +++++++++++++++++++ .../Developer Guide/Project Structure.md | 1 + 3 files changed, 70 insertions(+) create mode 100644 docs/Developer Guide/Developer Guide/Concepts/Web Clipper.md diff --git a/docs/Developer Guide/!!!meta.json b/docs/Developer Guide/!!!meta.json index d5bdbfd83..3d865230e 100644 --- a/docs/Developer Guide/!!!meta.json +++ b/docs/Developer Guide/!!!meta.json @@ -2839,6 +2839,40 @@ "format": "markdown", "dataFileName": "Themes.md", "attachments": [] + }, + { + "isClone": false, + "noteId": "YTAxJMA3uWwn", + "notePath": [ + "jdjRLhLV3TtI", + "yeqU0zo0ZQ83", + "YTAxJMA3uWwn" + ], + "title": "Web Clipper", + "notePosition": 210, + "prefix": null, + "isExpanded": false, + "type": "text", + "mime": "text/html", + "attributes": [ + { + "type": "label", + "name": "shareAlias", + "value": "web-clipper", + "isInheritable": false, + "position": 20 + }, + { + "type": "label", + "name": "iconClass", + "value": "bx bx-paperclip", + "isInheritable": false, + "position": 30 + } + ], + "format": "markdown", + "dataFileName": "Web Clipper.md", + "attachments": [] } ] }, diff --git a/docs/Developer Guide/Developer Guide/Concepts/Web Clipper.md b/docs/Developer Guide/Developer Guide/Concepts/Web Clipper.md new file mode 100644 index 000000000..b36bfd130 --- /dev/null +++ b/docs/Developer Guide/Developer Guide/Concepts/Web Clipper.md @@ -0,0 +1,35 @@ +# Web Clipper +The Web Clipper is present in the monorepo in `apps/web-clipper`. It's based on [WXT](https://wxt.dev/guide/introduction.html), a framework for building web extensions that allows very easy development and publishing. + +## Manifest version + +Originally the Web Clipper supported only Manifest v2, which made the extension incompatible with Google Chrome. [#8494](https://github.com/TriliumNext/Trilium/pull/8494) introduces Manifest v3 support for Google Chrome, alongside with Manifest v2 for Firefox. + +Although Firefox does support Manifest v3, we are still using Manifest v2 for it because WXT dev mode doesn't work for the Firefox / Manifest v3 combination and there were some mentions about Manifest v3 not being well supported on Firefox Mobile (and we plan to have support for it). + +## Dev mode + +WXT allows easy development of the plugin, with full TypeScript support and live reload. To enter dev mode: + +* Run `pnpm --filter web-clipper dev` to enter dev mode for Chrome (with manifest v3). +* Run `pnpm --filter web-clipper dev:firefox` to enter dev mode for Firefox (with manifest v2). + +This will open a separate browser instance in which the extension is automatically injected. + +## Port + +The default port is: + +* `37742` if in development mode. This makes it possible to use `pnpm desktop:start` to spin up a desktop instance to use the Clipper with. +* `37840` in production, the default Trilium port. + +## Building + +* Run `build` (Chrome) or `build:firefox` to generate the output files, which will be in `.output/[browser]`. +* Run `zip` or `zip:firefox` to generate the ZIP files. + +## CI + +`.github/workflows/web-clipper.yml` handles the building of the web clipper. Whenever the web clipper is modified, it generates the ZIPs and uploads them as artifacts. + +There is currently no automatic publishing to the app stores. \ No newline at end of file diff --git a/docs/Developer Guide/Developer Guide/Project Structure.md b/docs/Developer Guide/Developer Guide/Project Structure.md index 82688cf2a..2e2a5438d 100644 --- a/docs/Developer Guide/Developer Guide/Project Structure.md +++ b/docs/Developer Guide/Developer Guide/Project Structure.md @@ -9,6 +9,7 @@ The mono-repo is mainly structured in: * `client`, representing the front-end that is used both by the server and the desktop application. * `server`, representing the Node.js / server version of the application. * `desktop`, representing the Electron-based desktop application. + * `web-clipper`, representing the browser extension to easily clip web pages into Trilium, with support for both Firefox and Chrome (manifest V3). * `packages`, containing dependencies used by one or more `apps`. * `commons`, containing shared code for all the apps. From 4927b01d9634c7af98e54ec17b9dc341a9c8b437 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 23:13:27 +0200 Subject: [PATCH 46/47] chore(webclipper): fix typecheck --- apps/web-clipper/entrypoints/background/index.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/web-clipper/entrypoints/background/index.ts b/apps/web-clipper/entrypoints/background/index.ts index 5ac45ba92..31ee608f5 100644 --- a/apps/web-clipper/entrypoints/background/index.ts +++ b/apps/web-clipper/entrypoints/background/index.ts @@ -366,7 +366,7 @@ export default defineBackground(() => { toast(`${tabs.length} links have been saved to Trilium.`, resp.noteId, tabIds); } - browser.contextMenus.onClicked.addListener(async (info, tab) => { + browser.contextMenus.onClicked.addListener(async (info: globalThis.Browser.contextMenus.OnClickData & { linkText?: string; }) => { if (info.menuItemId === 'trilium-save-selection') { await saveSelection(); } @@ -381,6 +381,7 @@ export default defineBackground(() => { } else if (info.menuItemId === 'trilium-save-link') { if (!info.linkUrl) return; + // Link text is only available on Firefox. const linkText = info.linkText || info.linkUrl; const content = `${linkText}`; const activeTab = await getActiveTab(); @@ -460,7 +461,9 @@ export default defineBackground(() => { } else if (request.name === 'trigger-trilium-search-note-url') { const activeTab = await getActiveTab(); - triliumServerFacade.triggerSearchNoteByUrl(activeTab.url); + if (activeTab.url) { + triliumServerFacade.triggerSearchNoteByUrl(activeTab.url); + } } }); }); From b7b367b5a3449225f0469d97ea128a351d6e8065 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 23:54:26 +0200 Subject: [PATCH 47/47] chore(webclipper): address requested changes --- .../entrypoints/background/index.ts | 36 +++++++++++++------ .../background/trilium_server_facade.ts | 6 ++-- apps/web-clipper/entrypoints/content/index.ts | 12 ++++--- apps/web-clipper/entrypoints/options/index.ts | 2 +- apps/web-clipper/entrypoints/popup/popup.ts | 2 +- 5 files changed, 37 insertions(+), 21 deletions(-) diff --git a/apps/web-clipper/entrypoints/background/index.ts b/apps/web-clipper/entrypoints/background/index.ts index 31ee608f5..8657ee511 100644 --- a/apps/web-clipper/entrypoints/background/index.ts +++ b/apps/web-clipper/entrypoints/background/index.ts @@ -2,6 +2,19 @@ import { randomString, Rect } from "@/utils"; import TriliumServerFacade from "./trilium_server_facade"; +type BackgroundMessage = { + name: "toast"; + message: string; + noteId: string | null; + tabIds: number[] | null; +} | { + name: "trilium-save-selection"; +} | { + name: "trilium-get-rectangle-for-screenshot"; +} | { + name: "trilium-save-page"; +}; + export default defineBackground(() => { const triliumServerFacade = new TriliumServerFacade(); @@ -155,7 +168,7 @@ export default defineBackground(() => { return tabs; } - async function sendMessageToActiveTab(message) { + async function sendMessageToActiveTab(message: BackgroundMessage) { const activeTab = await getActiveTab(); if (!activeTab?.id) { @@ -229,7 +242,7 @@ export default defineBackground(() => { toast("Selection has been saved to Trilium.", resp.noteId); } - async function getImagePayloadFromSrc(src, pageUrl) { + async function getImagePayloadFromSrc(src: string, pageUrl: string | null | undefined) { const image = { imageId: randomString(20), src @@ -247,7 +260,7 @@ export default defineBackground(() => { }; } - async function saveCroppedScreenshot(pageUrl) { + async function saveCroppedScreenshot(pageUrl: string | null | undefined) { const { rect, devicePixelRatio } = await sendMessageToActiveTab({name: 'trilium-get-rectangle-for-screenshot'}); const src = await takeCroppedScreenshot(rect, devicePixelRatio); @@ -263,7 +276,7 @@ export default defineBackground(() => { toast("Screenshot has been saved to Trilium.", resp.noteId); } - async function saveWholeScreenshot(pageUrl) { + async function saveWholeScreenshot(pageUrl: string | null | undefined) { const src = await takeWholeScreenshot(); const payload = await getImagePayloadFromSrc(src, pageUrl); @@ -277,7 +290,7 @@ export default defineBackground(() => { toast("Screenshot has been saved to Trilium.", resp.noteId); } - async function saveImage(srcUrl, pageUrl) { + async function saveImage(srcUrl: string, pageUrl: string | null | undefined) { const payload = await getImagePayloadFromSrc(srcUrl, pageUrl); const resp = await triliumServerFacade.callService("POST", "clippings", payload); @@ -303,11 +316,11 @@ export default defineBackground(() => { toast("Page has been saved to Trilium.", resp.noteId); } - async function saveLinkWithNote(title, content) { + async function saveLinkWithNote(title: string, content: string) { const activeTab = await getActiveTab(); if (!title.trim()) { - title = activeTab.title; + title = activeTab.title ?? ""; } const resp = await triliumServerFacade.callService('POST', 'notes', { @@ -326,7 +339,7 @@ export default defineBackground(() => { return true; } - async function getTabsPayload(tabs) { + async function getTabsPayload(tabs: Browser.tabs.Tab[]) { let content = '
    '; tabs.forEach(tab => { content += `
  • ${tab.title}
  • `; @@ -335,7 +348,7 @@ export default defineBackground(() => { const domainsCount = tabs.map(tab => tab.url) .reduce((acc, url) => { - const hostname = new URL(url).hostname; + const hostname = new URL(url ?? "").hostname; return acc.set(hostname, (acc.get(hostname) || 0) + 1); }, new Map()); @@ -362,7 +375,7 @@ export default defineBackground(() => { const resp = await triliumServerFacade.callService('POST', 'notes', payload); if (!resp) return; - const tabIds = tabs.map(tab => tab.id!).filter(id => id !== undefined) as number[]; + const tabIds = tabs.map(tab => tab.id).filter(id => id !== undefined) as number[]; toast(`${tabs.length} links have been saved to Trilium.`, resp.noteId, tabIds); } @@ -377,6 +390,7 @@ export default defineBackground(() => { await saveWholeScreenshot(info.pageUrl); } else if (info.menuItemId === 'trilium-save-image') { + if (!info.srcUrl) return; await saveImage(info.srcUrl, info.pageUrl); } else if (info.menuItemId === 'trilium-save-link') { @@ -407,7 +421,7 @@ export default defineBackground(() => { console.log("Received", request); if (request.name === 'openNoteInTrilium') { - const resp = await triliumServerFacade.callService('POST', `open/${ request.noteId}`); + const resp = await triliumServerFacade.callService('POST', `open/${request.noteId}`); if (!resp) { return; diff --git a/apps/web-clipper/entrypoints/background/trilium_server_facade.ts b/apps/web-clipper/entrypoints/background/trilium_server_facade.ts index 43247a9e7..da553cec2 100644 --- a/apps/web-clipper/entrypoints/background/trilium_server_facade.ts +++ b/apps/web-clipper/entrypoints/background/trilium_server_facade.ts @@ -57,7 +57,7 @@ export default class TriliumServerFacade { catch (e) {} // nothing might be listening } - setTriliumSearchNote(st){ + setTriliumSearchNote(st: TriliumSearchNoteStatus){ this.triliumSearchNote = st; this.sendTriliumSearchNoteToPopup(); } @@ -69,7 +69,7 @@ export default class TriliumServerFacade { } setTriliumSearchWithVersionCheck(json: { protocolVersion: string }, resp: TriliumSearchStatus) { - const [major, minor] = json.protocolVersion + const [ major ] = json.protocolVersion .split(".") .map(chunk => parseInt(chunk, 10)); @@ -94,7 +94,7 @@ export default class TriliumServerFacade { try { const port = await this.getPort(); - console.debug(`Trying port ${ port}`); + console.debug(`Trying port ${port}`); const resp = await fetch(`http://127.0.0.1:${port}/api/clipper/handshake`); diff --git a/apps/web-clipper/entrypoints/content/index.ts b/apps/web-clipper/entrypoints/content/index.ts index b708bd639..d35f3d897 100644 --- a/apps/web-clipper/entrypoints/content/index.ts +++ b/apps/web-clipper/entrypoints/content/index.ts @@ -6,7 +6,7 @@ export default defineContentScript({ "" ], main: () => { - function absoluteUrl(url) { + function absoluteUrl(url: string | undefined) { if (!url) { return url; } @@ -89,8 +89,8 @@ export default defineContentScript({ messageComp.style.position = 'fixed'; messageComp.style.opacity = '0.95'; messageComp.style.fontSize = '14px'; - messageComp.style.width = `${messageCompWidth }px`; - messageComp.style.maxWidth = `${messageCompWidth }px`; + messageComp.style.width = `${messageCompWidth}px`; + messageComp.style.maxWidth = `${messageCompWidth}px`; messageComp.style.border = '1px solid black'; messageComp.style.background = 'white'; messageComp.style.color = 'black'; @@ -199,10 +199,12 @@ export default defineContentScript({ }); } - function makeLinksAbsolute(container) { + function makeLinksAbsolute(container: HTMLElement) { for (const link of container.getElementsByTagName('a')) { if (link.href) { - link.href = absoluteUrl(link.href); + const newUrl = absoluteUrl(link.href); + if (!newUrl) continue; + link.href = newUrl; } } } diff --git a/apps/web-clipper/entrypoints/options/index.ts b/apps/web-clipper/entrypoints/options/index.ts index da8fa1850..c7cb3d4bd 100644 --- a/apps/web-clipper/entrypoints/options/index.ts +++ b/apps/web-clipper/entrypoints/options/index.ts @@ -27,7 +27,7 @@ async function saveTriliumServerSetup(e) { let resp; try { - resp = await fetch(`${$triliumServerUrl.val() }/api/login/token`, { + resp = await fetch(`${$triliumServerUrl.val()}/api/login/token`, { method: "POST", headers: { 'Accept': 'application/json', diff --git a/apps/web-clipper/entrypoints/popup/popup.ts b/apps/web-clipper/entrypoints/popup/popup.ts index 273fe5c5b..0e4613b79 100644 --- a/apps/web-clipper/entrypoints/popup/popup.ts +++ b/apps/web-clipper/entrypoints/popup/popup.ts @@ -111,7 +111,7 @@ function escapeHtml(string) { const htmlWithPars = pre.innerHTML.replace(/\n/g, "

    "); - return `

    ${ htmlWithPars }

    `; + return `

    ${htmlWithPars}

    `; } const $connectionStatus = $("#connection-status");