diff --git a/.eslintrc b/.eslintrc
index 0f74a9b..f55fe98 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -26,6 +26,7 @@
"**/tests.ts",
"**/**test.ts",
"**/**.test.ts",
+ "src/apps/**",
"esbuild.*.mjs",
"terser.*.mjs"
],
diff --git a/esbuild.config.mjs b/esbuild.config.mjs
index 18de237..fe62f52 100644
--- a/esbuild.config.mjs
+++ b/esbuild.config.mjs
@@ -21,14 +21,10 @@ const updateInfo = JSON.stringify(fs.readFileSync("./updates.md") + "");
const PATHS_TEST_INSTALL = process.env?.PATHS_TEST_INSTALL || "";
const PATH_TEST_INSTALL = PATHS_TEST_INSTALL.split(path.delimiter).map(p => p.trim()).filter(p => p.length);
-if (!prod) {
- if (PATH_TEST_INSTALL) {
- console.log(`Built files will be copied to ${PATH_TEST_INSTALL}`);
- } else {
- console.log("Development build: You can install the plug-in to Obsidian for testing by exporting the PATHS_TEST_INSTALL environment variable with the paths to your vault plugins directories separated by your system path delimiter (':' on Unix, ';' on Windows).");
- }
+if (PATH_TEST_INSTALL) {
+ console.log(`Built files will be copied to ${PATH_TEST_INSTALL}`);
} else {
- console.log("Production build");
+ console.log("Development build: You can install the plug-in to Obsidian for testing by exporting the PATHS_TEST_INSTALL environment variable with the paths to your vault plugins directories separated by your system path delimiter (':' on Unix, ';' on Windows).");
}
const moduleAliasPlugin = {
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 7dd29b2..b2fbf76 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -40,7 +40,7 @@ export default [
"src/lib/test",
"src/lib/src/cli",
"**/main.js",
- "src/lib/apps/webpeer/*",
+ "src/apps/**/*",
".prettierrc.*.mjs",
".prettierrc.mjs",
"*.config.mjs"
diff --git a/manifest-beta.json b/manifest-beta.json
deleted file mode 100644
index 26b0d7f..0000000
--- a/manifest-beta.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "id": "obsidian-livesync",
- "name": "Self-hosted LiveSync",
- "version": "0.25.24.beta3",
- "minAppVersion": "0.9.12",
- "description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
- "author": "vorotamoroz",
- "authorUrl": "https://github.com/vrtmrz",
- "isDesktopOnly": false
-}
diff --git a/manifest.json b/manifest.json
index 3ab7955..7f0726f 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,7 +1,7 @@
{
"id": "obsidian-livesync",
"name": "Self-hosted LiveSync",
- "version": "0.25.41",
+ "version": "0.25.42",
"minAppVersion": "0.9.12",
"description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
"author": "vorotamoroz",
diff --git a/package-lock.json b/package-lock.json
index 0cfdf5d..5f463eb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "obsidian-livesync",
- "version": "0.25.41",
+ "version": "0.25.42",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "obsidian-livesync",
- "version": "0.25.41",
+ "version": "0.25.42",
"license": "MIT",
"dependencies": {
"@aws-sdk/client-s3": "^3.808.0",
@@ -19,7 +19,7 @@
"fflate": "^0.8.2",
"idb": "^8.0.3",
"minimatch": "^10.0.2",
- "octagonal-wheels": "^0.1.44",
+ "octagonal-wheels": "^0.1.45",
"qrcode-generator": "^1.4.4",
"trystero": "^0.22.0",
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
@@ -11151,9 +11151,9 @@
"license": "MIT"
},
"node_modules/octagonal-wheels": {
- "version": "0.1.44",
- "resolved": "https://registry.npmjs.org/octagonal-wheels/-/octagonal-wheels-0.1.44.tgz",
- "integrity": "sha512-sUn/dkYQ2AbMB0R8CubVd75BjkcsteW9B14ArO99F6wM5JRwOo/yPIBBoxCUFE7JjBFOfuWG21C9E3NTga6XrA==",
+ "version": "0.1.45",
+ "resolved": "https://registry.npmjs.org/octagonal-wheels/-/octagonal-wheels-0.1.45.tgz",
+ "integrity": "sha512-gXoCrwoUIXhmu57YN4BxAtBe+JaYNJNaXaZuVjqjopwYKpH5p2mn1om6KjA22rgGPiIJFXkse2U28FFXoT3/0Q==",
"license": "MIT",
"dependencies": {
"idb": "^8.0.3"
@@ -23036,9 +23036,9 @@
"dev": true
},
"octagonal-wheels": {
- "version": "0.1.44",
- "resolved": "https://registry.npmjs.org/octagonal-wheels/-/octagonal-wheels-0.1.44.tgz",
- "integrity": "sha512-sUn/dkYQ2AbMB0R8CubVd75BjkcsteW9B14ArO99F6wM5JRwOo/yPIBBoxCUFE7JjBFOfuWG21C9E3NTga6XrA==",
+ "version": "0.1.45",
+ "resolved": "https://registry.npmjs.org/octagonal-wheels/-/octagonal-wheels-0.1.45.tgz",
+ "integrity": "sha512-gXoCrwoUIXhmu57YN4BxAtBe+JaYNJNaXaZuVjqjopwYKpH5p2mn1om6KjA22rgGPiIJFXkse2U28FFXoT3/0Q==",
"requires": {
"idb": "^8.0.3"
}
diff --git a/package.json b/package.json
index 6914468..c31423b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "obsidian-livesync",
- "version": "0.25.41",
+ "version": "0.25.42",
"description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
"main": "main.js",
"type": "module",
@@ -129,7 +129,7 @@
"fflate": "^0.8.2",
"idb": "^8.0.3",
"minimatch": "^10.0.2",
- "octagonal-wheels": "^0.1.44",
+ "octagonal-wheels": "^0.1.45",
"qrcode-generator": "^1.4.4",
"trystero": "^0.22.0",
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
diff --git a/src/apps/webpeer/.gitignore b/src/apps/webpeer/.gitignore
new file mode 100644
index 0000000..a547bf3
--- /dev/null
+++ b/src/apps/webpeer/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/src/apps/webpeer/README.md b/src/apps/webpeer/README.md
new file mode 100644
index 0000000..81a2af5
--- /dev/null
+++ b/src/apps/webpeer/README.md
@@ -0,0 +1,30 @@
+# A pseudo client for Self-hosted LiveSync Peer-to-Peer Sync mode
+
+## What is it for?
+
+This is a pseudo client for the Self-hosted LiveSync Peer-to-Peer Sync mode. It is a simple pure-client-side web-application that can be connected to the Self-hosted LiveSync in peer-to-peer.
+
+As long as you have a browser, it starts up, so if you leave it opened some device, it can replace your existing remote servers such as CouchDB.
+
+> [!IMPORTANT]
+> Of course, it has not been fully tested. Rather, it was created to be tested.
+
+This pseudo client actually receives the data from other devices, and sends if some device requests it. However, it does not store **files** in the local storage. If you want to purge the data, please purge the browser's cache and indexedDB, local storage, etc.
+
+## How to use it?
+
+We can build the application by running the following command:
+
+```bash
+$ deno task build
+```
+
+Then, open the `dist/index.html` in the browser. It can be configured as the same as the Self-hosted LiveSync (Same components are used[^1]).
+
+## Some notes
+
+I will launch this application in the github pages later, so will be able to use it without building it. However, that shares the origin. Hence, the application that your have built and deployed would be more secure.
+
+
+[^1]: Congrats! I made it modular. Finally...
+
diff --git a/src/apps/webpeer/deno.lock b/src/apps/webpeer/deno.lock
new file mode 100644
index 0000000..e908f88
--- /dev/null
+++ b/src/apps/webpeer/deno.lock
@@ -0,0 +1,1101 @@
+{
+ "version": "5",
+ "specifiers": {
+ "npm:@sveltejs/vite-plugin-svelte@^6.2.1": "6.2.4_svelte@5.41.1__acorn@8.15.0_vite@7.3.1__picomatch@4.0.3",
+ "npm:@tsconfig/svelte@^5.0.5": "5.0.6",
+ "npm:eslint-plugin-svelte@^3.12.4": "3.14.0_eslint@9.39.2_svelte@5.41.1__acorn@8.15.0_postcss@8.5.6",
+ "npm:svelte-check@^4.3.3": "4.3.5_svelte@5.41.1__acorn@8.15.0_typescript@5.9.3",
+ "npm:svelte@5.41.1": "5.41.1_acorn@8.15.0",
+ "npm:typescript@5.9.3": "5.9.3",
+ "npm:vite@^7.3.0": "7.3.1_picomatch@4.0.3"
+ },
+ "npm": {
+ "@esbuild/aix-ppc64@0.27.2": {
+ "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==",
+ "os": ["aix"],
+ "cpu": ["ppc64"]
+ },
+ "@esbuild/android-arm64@0.27.2": {
+ "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==",
+ "os": ["android"],
+ "cpu": ["arm64"]
+ },
+ "@esbuild/android-arm@0.27.2": {
+ "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==",
+ "os": ["android"],
+ "cpu": ["arm"]
+ },
+ "@esbuild/android-x64@0.27.2": {
+ "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==",
+ "os": ["android"],
+ "cpu": ["x64"]
+ },
+ "@esbuild/darwin-arm64@0.27.2": {
+ "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
+ "os": ["darwin"],
+ "cpu": ["arm64"]
+ },
+ "@esbuild/darwin-x64@0.27.2": {
+ "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==",
+ "os": ["darwin"],
+ "cpu": ["x64"]
+ },
+ "@esbuild/freebsd-arm64@0.27.2": {
+ "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==",
+ "os": ["freebsd"],
+ "cpu": ["arm64"]
+ },
+ "@esbuild/freebsd-x64@0.27.2": {
+ "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==",
+ "os": ["freebsd"],
+ "cpu": ["x64"]
+ },
+ "@esbuild/linux-arm64@0.27.2": {
+ "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==",
+ "os": ["linux"],
+ "cpu": ["arm64"]
+ },
+ "@esbuild/linux-arm@0.27.2": {
+ "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==",
+ "os": ["linux"],
+ "cpu": ["arm"]
+ },
+ "@esbuild/linux-ia32@0.27.2": {
+ "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==",
+ "os": ["linux"],
+ "cpu": ["ia32"]
+ },
+ "@esbuild/linux-loong64@0.27.2": {
+ "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==",
+ "os": ["linux"],
+ "cpu": ["loong64"]
+ },
+ "@esbuild/linux-mips64el@0.27.2": {
+ "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==",
+ "os": ["linux"],
+ "cpu": ["mips64el"]
+ },
+ "@esbuild/linux-ppc64@0.27.2": {
+ "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==",
+ "os": ["linux"],
+ "cpu": ["ppc64"]
+ },
+ "@esbuild/linux-riscv64@0.27.2": {
+ "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==",
+ "os": ["linux"],
+ "cpu": ["riscv64"]
+ },
+ "@esbuild/linux-s390x@0.27.2": {
+ "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==",
+ "os": ["linux"],
+ "cpu": ["s390x"]
+ },
+ "@esbuild/linux-x64@0.27.2": {
+ "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==",
+ "os": ["linux"],
+ "cpu": ["x64"]
+ },
+ "@esbuild/netbsd-arm64@0.27.2": {
+ "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==",
+ "os": ["netbsd"],
+ "cpu": ["arm64"]
+ },
+ "@esbuild/netbsd-x64@0.27.2": {
+ "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==",
+ "os": ["netbsd"],
+ "cpu": ["x64"]
+ },
+ "@esbuild/openbsd-arm64@0.27.2": {
+ "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==",
+ "os": ["openbsd"],
+ "cpu": ["arm64"]
+ },
+ "@esbuild/openbsd-x64@0.27.2": {
+ "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==",
+ "os": ["openbsd"],
+ "cpu": ["x64"]
+ },
+ "@esbuild/openharmony-arm64@0.27.2": {
+ "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==",
+ "os": ["openharmony"],
+ "cpu": ["arm64"]
+ },
+ "@esbuild/sunos-x64@0.27.2": {
+ "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==",
+ "os": ["sunos"],
+ "cpu": ["x64"]
+ },
+ "@esbuild/win32-arm64@0.27.2": {
+ "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==",
+ "os": ["win32"],
+ "cpu": ["arm64"]
+ },
+ "@esbuild/win32-ia32@0.27.2": {
+ "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==",
+ "os": ["win32"],
+ "cpu": ["ia32"]
+ },
+ "@esbuild/win32-x64@0.27.2": {
+ "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
+ "os": ["win32"],
+ "cpu": ["x64"]
+ },
+ "@eslint-community/eslint-utils@4.9.1_eslint@9.39.2": {
+ "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
+ "dependencies": [
+ "eslint",
+ "eslint-visitor-keys@3.4.3"
+ ]
+ },
+ "@eslint-community/regexpp@4.12.2": {
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="
+ },
+ "@eslint/config-array@0.21.1": {
+ "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
+ "dependencies": [
+ "@eslint/object-schema",
+ "debug",
+ "minimatch"
+ ]
+ },
+ "@eslint/config-helpers@0.4.2": {
+ "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
+ "dependencies": [
+ "@eslint/core"
+ ]
+ },
+ "@eslint/core@0.17.0": {
+ "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
+ "dependencies": [
+ "@types/json-schema"
+ ]
+ },
+ "@eslint/eslintrc@3.3.3": {
+ "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==",
+ "dependencies": [
+ "ajv",
+ "debug",
+ "espree",
+ "globals@14.0.0",
+ "ignore",
+ "import-fresh",
+ "js-yaml",
+ "minimatch",
+ "strip-json-comments"
+ ]
+ },
+ "@eslint/js@9.39.2": {
+ "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA=="
+ },
+ "@eslint/object-schema@2.1.7": {
+ "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="
+ },
+ "@eslint/plugin-kit@0.4.1": {
+ "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
+ "dependencies": [
+ "@eslint/core",
+ "levn"
+ ]
+ },
+ "@humanfs/core@0.19.1": {
+ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="
+ },
+ "@humanfs/node@0.16.7": {
+ "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
+ "dependencies": [
+ "@humanfs/core",
+ "@humanwhocodes/retry"
+ ]
+ },
+ "@humanwhocodes/module-importer@1.0.1": {
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="
+ },
+ "@humanwhocodes/retry@0.4.3": {
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="
+ },
+ "@jridgewell/gen-mapping@0.3.13": {
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dependencies": [
+ "@jridgewell/sourcemap-codec",
+ "@jridgewell/trace-mapping"
+ ]
+ },
+ "@jridgewell/remapping@2.3.5": {
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dependencies": [
+ "@jridgewell/gen-mapping",
+ "@jridgewell/trace-mapping"
+ ]
+ },
+ "@jridgewell/resolve-uri@3.1.2": {
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="
+ },
+ "@jridgewell/sourcemap-codec@1.5.5": {
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="
+ },
+ "@jridgewell/trace-mapping@0.3.31": {
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dependencies": [
+ "@jridgewell/resolve-uri",
+ "@jridgewell/sourcemap-codec"
+ ]
+ },
+ "@rollup/rollup-android-arm-eabi@4.56.0": {
+ "integrity": "sha512-LNKIPA5k8PF1+jAFomGe3qN3bbIgJe/IlpDBwuVjrDKrJhVWywgnJvflMt/zkbVNLFtF1+94SljYQS6e99klnw==",
+ "os": ["android"],
+ "cpu": ["arm"]
+ },
+ "@rollup/rollup-android-arm64@4.56.0": {
+ "integrity": "sha512-lfbVUbelYqXlYiU/HApNMJzT1E87UPGvzveGg2h0ktUNlOCxKlWuJ9jtfvs1sKHdwU4fzY7Pl8sAl49/XaEk6Q==",
+ "os": ["android"],
+ "cpu": ["arm64"]
+ },
+ "@rollup/rollup-darwin-arm64@4.56.0": {
+ "integrity": "sha512-EgxD1ocWfhoD6xSOeEEwyE7tDvwTgZc8Bss7wCWe+uc7wO8G34HHCUH+Q6cHqJubxIAnQzAsyUsClt0yFLu06w==",
+ "os": ["darwin"],
+ "cpu": ["arm64"]
+ },
+ "@rollup/rollup-darwin-x64@4.56.0": {
+ "integrity": "sha512-1vXe1vcMOssb/hOF8iv52A7feWW2xnu+c8BV4t1F//m9QVLTfNVpEdja5ia762j/UEJe2Z1jAmEqZAK42tVW3g==",
+ "os": ["darwin"],
+ "cpu": ["x64"]
+ },
+ "@rollup/rollup-freebsd-arm64@4.56.0": {
+ "integrity": "sha512-bof7fbIlvqsyv/DtaXSck4VYQ9lPtoWNFCB/JY4snlFuJREXfZnm+Ej6yaCHfQvofJDXLDMTVxWscVSuQvVWUQ==",
+ "os": ["freebsd"],
+ "cpu": ["arm64"]
+ },
+ "@rollup/rollup-freebsd-x64@4.56.0": {
+ "integrity": "sha512-KNa6lYHloW+7lTEkYGa37fpvPq+NKG/EHKM8+G/g9WDU7ls4sMqbVRV78J6LdNuVaeeK5WB9/9VAFbKxcbXKYg==",
+ "os": ["freebsd"],
+ "cpu": ["x64"]
+ },
+ "@rollup/rollup-linux-arm-gnueabihf@4.56.0": {
+ "integrity": "sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A==",
+ "os": ["linux"],
+ "cpu": ["arm"]
+ },
+ "@rollup/rollup-linux-arm-musleabihf@4.56.0": {
+ "integrity": "sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw==",
+ "os": ["linux"],
+ "cpu": ["arm"]
+ },
+ "@rollup/rollup-linux-arm64-gnu@4.56.0": {
+ "integrity": "sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ==",
+ "os": ["linux"],
+ "cpu": ["arm64"]
+ },
+ "@rollup/rollup-linux-arm64-musl@4.56.0": {
+ "integrity": "sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA==",
+ "os": ["linux"],
+ "cpu": ["arm64"]
+ },
+ "@rollup/rollup-linux-loong64-gnu@4.56.0": {
+ "integrity": "sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg==",
+ "os": ["linux"],
+ "cpu": ["loong64"]
+ },
+ "@rollup/rollup-linux-loong64-musl@4.56.0": {
+ "integrity": "sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA==",
+ "os": ["linux"],
+ "cpu": ["loong64"]
+ },
+ "@rollup/rollup-linux-ppc64-gnu@4.56.0": {
+ "integrity": "sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw==",
+ "os": ["linux"],
+ "cpu": ["ppc64"]
+ },
+ "@rollup/rollup-linux-ppc64-musl@4.56.0": {
+ "integrity": "sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg==",
+ "os": ["linux"],
+ "cpu": ["ppc64"]
+ },
+ "@rollup/rollup-linux-riscv64-gnu@4.56.0": {
+ "integrity": "sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew==",
+ "os": ["linux"],
+ "cpu": ["riscv64"]
+ },
+ "@rollup/rollup-linux-riscv64-musl@4.56.0": {
+ "integrity": "sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ==",
+ "os": ["linux"],
+ "cpu": ["riscv64"]
+ },
+ "@rollup/rollup-linux-s390x-gnu@4.56.0": {
+ "integrity": "sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ==",
+ "os": ["linux"],
+ "cpu": ["s390x"]
+ },
+ "@rollup/rollup-linux-x64-gnu@4.56.0": {
+ "integrity": "sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw==",
+ "os": ["linux"],
+ "cpu": ["x64"]
+ },
+ "@rollup/rollup-linux-x64-musl@4.56.0": {
+ "integrity": "sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA==",
+ "os": ["linux"],
+ "cpu": ["x64"]
+ },
+ "@rollup/rollup-openbsd-x64@4.56.0": {
+ "integrity": "sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA==",
+ "os": ["openbsd"],
+ "cpu": ["x64"]
+ },
+ "@rollup/rollup-openharmony-arm64@4.56.0": {
+ "integrity": "sha512-LhN/Reh+7F3RCgQIRbgw8ZMwUwyqJM+8pXNT6IIJAqm2IdKkzpCh/V9EdgOMBKuebIrzswqy4ATlrDgiOwbRcQ==",
+ "os": ["openharmony"],
+ "cpu": ["arm64"]
+ },
+ "@rollup/rollup-win32-arm64-msvc@4.56.0": {
+ "integrity": "sha512-kbFsOObXp3LBULg1d3JIUQMa9Kv4UitDmpS+k0tinPBz3watcUiV2/LUDMMucA6pZO3WGE27P7DsfaN54l9ing==",
+ "os": ["win32"],
+ "cpu": ["arm64"]
+ },
+ "@rollup/rollup-win32-ia32-msvc@4.56.0": {
+ "integrity": "sha512-vSSgny54D6P4vf2izbtFm/TcWYedw7f8eBrOiGGecyHyQB9q4Kqentjaj8hToe+995nob/Wv48pDqL5a62EWtg==",
+ "os": ["win32"],
+ "cpu": ["ia32"]
+ },
+ "@rollup/rollup-win32-x64-gnu@4.56.0": {
+ "integrity": "sha512-FeCnkPCTHQJFbiGG49KjV5YGW/8b9rrXAM2Mz2kiIoktq2qsJxRD5giEMEOD2lPdgs72upzefaUvS+nc8E3UzQ==",
+ "os": ["win32"],
+ "cpu": ["x64"]
+ },
+ "@rollup/rollup-win32-x64-msvc@4.56.0": {
+ "integrity": "sha512-H8AE9Ur/t0+1VXujj90w0HrSOuv0Nq9r1vSZF2t5km20NTfosQsGGUXDaKdQZzwuLts7IyL1fYT4hM95TI9c4g==",
+ "os": ["win32"],
+ "cpu": ["x64"]
+ },
+ "@sveltejs/acorn-typescript@1.0.8_acorn@8.15.0": {
+ "integrity": "sha512-esgN+54+q0NjB0Y/4BomT9samII7jGwNy/2a3wNZbT2A2RpmXsXwUt24LvLhx6jUq2gVk4cWEvcRO6MFQbOfNA==",
+ "dependencies": [
+ "acorn"
+ ]
+ },
+ "@sveltejs/vite-plugin-svelte-inspector@5.0.2_@sveltejs+vite-plugin-svelte@6.2.4__svelte@5.41.1___acorn@8.15.0__vite@7.3.1___picomatch@4.0.3_svelte@5.41.1__acorn@8.15.0_vite@7.3.1__picomatch@4.0.3": {
+ "integrity": "sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig==",
+ "dependencies": [
+ "@sveltejs/vite-plugin-svelte",
+ "obug",
+ "svelte",
+ "vite"
+ ]
+ },
+ "@sveltejs/vite-plugin-svelte@6.2.4_svelte@5.41.1__acorn@8.15.0_vite@7.3.1__picomatch@4.0.3": {
+ "integrity": "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==",
+ "dependencies": [
+ "@sveltejs/vite-plugin-svelte-inspector",
+ "deepmerge",
+ "magic-string",
+ "obug",
+ "svelte",
+ "vite",
+ "vitefu"
+ ]
+ },
+ "@tsconfig/svelte@5.0.6": {
+ "integrity": "sha512-yGxYL0I9eETH1/DR9qVJey4DAsCdeau4a9wYPKuXfEhm8lFO8wg+LLYJjIpAm6Fw7HSlhepPhYPDop75485yWQ=="
+ },
+ "@types/estree@1.0.8": {
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="
+ },
+ "@types/json-schema@7.0.15": {
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="
+ },
+ "acorn-jsx@5.3.2_acorn@8.15.0": {
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dependencies": [
+ "acorn"
+ ]
+ },
+ "acorn@8.15.0": {
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "bin": true
+ },
+ "ajv@6.12.6": {
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dependencies": [
+ "fast-deep-equal",
+ "fast-json-stable-stringify",
+ "json-schema-traverse",
+ "uri-js"
+ ]
+ },
+ "ansi-styles@4.3.0": {
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dependencies": [
+ "color-convert"
+ ]
+ },
+ "argparse@2.0.1": {
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
+ },
+ "aria-query@5.3.2": {
+ "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="
+ },
+ "axobject-query@4.1.0": {
+ "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="
+ },
+ "balanced-match@1.0.2": {
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
+ },
+ "brace-expansion@1.1.12": {
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dependencies": [
+ "balanced-match",
+ "concat-map"
+ ]
+ },
+ "callsites@3.1.0": {
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="
+ },
+ "chalk@4.1.2": {
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dependencies": [
+ "ansi-styles",
+ "supports-color"
+ ]
+ },
+ "chokidar@4.0.3": {
+ "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+ "dependencies": [
+ "readdirp"
+ ]
+ },
+ "clsx@2.1.1": {
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="
+ },
+ "color-convert@2.0.1": {
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dependencies": [
+ "color-name"
+ ]
+ },
+ "color-name@1.1.4": {
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+ },
+ "concat-map@0.0.1": {
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
+ },
+ "cross-spawn@7.0.6": {
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dependencies": [
+ "path-key",
+ "shebang-command",
+ "which"
+ ]
+ },
+ "cssesc@3.0.0": {
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "bin": true
+ },
+ "debug@4.4.3": {
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dependencies": [
+ "ms"
+ ]
+ },
+ "deep-is@0.1.4": {
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="
+ },
+ "deepmerge@4.3.1": {
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="
+ },
+ "esbuild@0.27.2": {
+ "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
+ "optionalDependencies": [
+ "@esbuild/aix-ppc64",
+ "@esbuild/android-arm",
+ "@esbuild/android-arm64",
+ "@esbuild/android-x64",
+ "@esbuild/darwin-arm64",
+ "@esbuild/darwin-x64",
+ "@esbuild/freebsd-arm64",
+ "@esbuild/freebsd-x64",
+ "@esbuild/linux-arm",
+ "@esbuild/linux-arm64",
+ "@esbuild/linux-ia32",
+ "@esbuild/linux-loong64",
+ "@esbuild/linux-mips64el",
+ "@esbuild/linux-ppc64",
+ "@esbuild/linux-riscv64",
+ "@esbuild/linux-s390x",
+ "@esbuild/linux-x64",
+ "@esbuild/netbsd-arm64",
+ "@esbuild/netbsd-x64",
+ "@esbuild/openbsd-arm64",
+ "@esbuild/openbsd-x64",
+ "@esbuild/openharmony-arm64",
+ "@esbuild/sunos-x64",
+ "@esbuild/win32-arm64",
+ "@esbuild/win32-ia32",
+ "@esbuild/win32-x64"
+ ],
+ "scripts": true,
+ "bin": true
+ },
+ "escape-string-regexp@4.0.0": {
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
+ },
+ "eslint-plugin-svelte@3.14.0_eslint@9.39.2_svelte@5.41.1__acorn@8.15.0_postcss@8.5.6": {
+ "integrity": "sha512-Isw0GvaMm0yHxAj71edAdGFh28ufYs+6rk2KlbbZphnqZAzrH3Se3t12IFh2H9+1F/jlDhBBL4oiOJmLqmYX0g==",
+ "dependencies": [
+ "@eslint-community/eslint-utils",
+ "@jridgewell/sourcemap-codec",
+ "eslint",
+ "esutils",
+ "globals@16.5.0",
+ "known-css-properties",
+ "postcss",
+ "postcss-load-config",
+ "postcss-safe-parser",
+ "semver",
+ "svelte",
+ "svelte-eslint-parser"
+ ],
+ "optionalPeers": [
+ "svelte"
+ ]
+ },
+ "eslint-scope@8.4.0": {
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "dependencies": [
+ "esrecurse",
+ "estraverse"
+ ]
+ },
+ "eslint-visitor-keys@3.4.3": {
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="
+ },
+ "eslint-visitor-keys@4.2.1": {
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="
+ },
+ "eslint@9.39.2": {
+ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
+ "dependencies": [
+ "@eslint-community/eslint-utils",
+ "@eslint-community/regexpp",
+ "@eslint/config-array",
+ "@eslint/config-helpers",
+ "@eslint/core",
+ "@eslint/eslintrc",
+ "@eslint/js",
+ "@eslint/plugin-kit",
+ "@humanfs/node",
+ "@humanwhocodes/module-importer",
+ "@humanwhocodes/retry",
+ "@types/estree",
+ "ajv",
+ "chalk",
+ "cross-spawn",
+ "debug",
+ "escape-string-regexp",
+ "eslint-scope",
+ "eslint-visitor-keys@4.2.1",
+ "espree",
+ "esquery",
+ "esutils",
+ "fast-deep-equal",
+ "file-entry-cache",
+ "find-up",
+ "glob-parent",
+ "ignore",
+ "imurmurhash",
+ "is-glob",
+ "json-stable-stringify-without-jsonify",
+ "lodash.merge",
+ "minimatch",
+ "natural-compare",
+ "optionator"
+ ],
+ "bin": true
+ },
+ "esm-env@1.2.2": {
+ "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="
+ },
+ "espree@10.4.0_acorn@8.15.0": {
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "dependencies": [
+ "acorn",
+ "acorn-jsx",
+ "eslint-visitor-keys@4.2.1"
+ ]
+ },
+ "esquery@1.7.0": {
+ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
+ "dependencies": [
+ "estraverse"
+ ]
+ },
+ "esrap@2.2.2": {
+ "integrity": "sha512-zA6497ha+qKvoWIK+WM9NAh5ni17sKZKhbS5B3PoYbBvaYHZWoS33zmFybmyqpn07RLUxSmn+RCls2/XF+d0oQ==",
+ "dependencies": [
+ "@jridgewell/sourcemap-codec"
+ ]
+ },
+ "esrecurse@4.3.0": {
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dependencies": [
+ "estraverse"
+ ]
+ },
+ "estraverse@5.3.0": {
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="
+ },
+ "esutils@2.0.3": {
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="
+ },
+ "fast-deep-equal@3.1.3": {
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+ },
+ "fast-json-stable-stringify@2.1.0": {
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
+ },
+ "fast-levenshtein@2.0.6": {
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="
+ },
+ "fdir@6.5.0_picomatch@4.0.3": {
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dependencies": [
+ "picomatch"
+ ],
+ "optionalPeers": [
+ "picomatch"
+ ]
+ },
+ "file-entry-cache@8.0.0": {
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dependencies": [
+ "flat-cache"
+ ]
+ },
+ "find-up@5.0.0": {
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dependencies": [
+ "locate-path",
+ "path-exists"
+ ]
+ },
+ "flat-cache@4.0.1": {
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dependencies": [
+ "flatted",
+ "keyv"
+ ]
+ },
+ "flatted@3.3.3": {
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="
+ },
+ "fsevents@2.3.3": {
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "os": ["darwin"],
+ "scripts": true
+ },
+ "glob-parent@6.0.2": {
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dependencies": [
+ "is-glob"
+ ]
+ },
+ "globals@14.0.0": {
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="
+ },
+ "globals@16.5.0": {
+ "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="
+ },
+ "has-flag@4.0.0": {
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
+ },
+ "ignore@5.3.2": {
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="
+ },
+ "import-fresh@3.3.1": {
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dependencies": [
+ "parent-module",
+ "resolve-from"
+ ]
+ },
+ "imurmurhash@0.1.4": {
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="
+ },
+ "is-extglob@2.1.1": {
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="
+ },
+ "is-glob@4.0.3": {
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dependencies": [
+ "is-extglob"
+ ]
+ },
+ "is-reference@3.0.3": {
+ "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
+ "dependencies": [
+ "@types/estree"
+ ]
+ },
+ "isexe@2.0.0": {
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
+ },
+ "js-yaml@4.1.1": {
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "dependencies": [
+ "argparse"
+ ],
+ "bin": true
+ },
+ "json-buffer@3.0.1": {
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="
+ },
+ "json-schema-traverse@0.4.1": {
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
+ },
+ "json-stable-stringify-without-jsonify@1.0.1": {
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="
+ },
+ "keyv@4.5.4": {
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dependencies": [
+ "json-buffer"
+ ]
+ },
+ "known-css-properties@0.37.0": {
+ "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ=="
+ },
+ "levn@0.4.1": {
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dependencies": [
+ "prelude-ls",
+ "type-check"
+ ]
+ },
+ "lilconfig@2.1.0": {
+ "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ=="
+ },
+ "locate-character@3.0.0": {
+ "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="
+ },
+ "locate-path@6.0.0": {
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dependencies": [
+ "p-locate"
+ ]
+ },
+ "lodash.merge@4.6.2": {
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
+ },
+ "magic-string@0.30.21": {
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dependencies": [
+ "@jridgewell/sourcemap-codec"
+ ]
+ },
+ "minimatch@3.1.2": {
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dependencies": [
+ "brace-expansion"
+ ]
+ },
+ "mri@1.2.0": {
+ "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="
+ },
+ "ms@2.1.3": {
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ },
+ "nanoid@3.3.11": {
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "bin": true
+ },
+ "natural-compare@1.4.0": {
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="
+ },
+ "obug@2.1.1": {
+ "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="
+ },
+ "optionator@0.9.4": {
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dependencies": [
+ "deep-is",
+ "fast-levenshtein",
+ "levn",
+ "prelude-ls",
+ "type-check",
+ "word-wrap"
+ ]
+ },
+ "p-limit@3.1.0": {
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dependencies": [
+ "yocto-queue"
+ ]
+ },
+ "p-locate@5.0.0": {
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dependencies": [
+ "p-limit"
+ ]
+ },
+ "parent-module@1.0.1": {
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dependencies": [
+ "callsites"
+ ]
+ },
+ "path-exists@4.0.0": {
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
+ },
+ "path-key@3.1.1": {
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="
+ },
+ "picocolors@1.1.1": {
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
+ },
+ "picomatch@4.0.3": {
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="
+ },
+ "postcss-load-config@3.1.4_postcss@8.5.6": {
+ "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==",
+ "dependencies": [
+ "lilconfig",
+ "postcss",
+ "yaml"
+ ],
+ "optionalPeers": [
+ "postcss"
+ ]
+ },
+ "postcss-safe-parser@7.0.1_postcss@8.5.6": {
+ "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==",
+ "dependencies": [
+ "postcss"
+ ]
+ },
+ "postcss-scss@4.0.9_postcss@8.5.6": {
+ "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==",
+ "dependencies": [
+ "postcss"
+ ]
+ },
+ "postcss-selector-parser@7.1.1": {
+ "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==",
+ "dependencies": [
+ "cssesc",
+ "util-deprecate"
+ ]
+ },
+ "postcss@8.5.6": {
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "dependencies": [
+ "nanoid",
+ "picocolors",
+ "source-map-js"
+ ]
+ },
+ "prelude-ls@1.2.1": {
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="
+ },
+ "punycode@2.3.1": {
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="
+ },
+ "readdirp@4.1.2": {
+ "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="
+ },
+ "resolve-from@4.0.0": {
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="
+ },
+ "rollup@4.56.0": {
+ "integrity": "sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg==",
+ "dependencies": [
+ "@types/estree"
+ ],
+ "optionalDependencies": [
+ "@rollup/rollup-android-arm-eabi",
+ "@rollup/rollup-android-arm64",
+ "@rollup/rollup-darwin-arm64",
+ "@rollup/rollup-darwin-x64",
+ "@rollup/rollup-freebsd-arm64",
+ "@rollup/rollup-freebsd-x64",
+ "@rollup/rollup-linux-arm-gnueabihf",
+ "@rollup/rollup-linux-arm-musleabihf",
+ "@rollup/rollup-linux-arm64-gnu",
+ "@rollup/rollup-linux-arm64-musl",
+ "@rollup/rollup-linux-loong64-gnu",
+ "@rollup/rollup-linux-loong64-musl",
+ "@rollup/rollup-linux-ppc64-gnu",
+ "@rollup/rollup-linux-ppc64-musl",
+ "@rollup/rollup-linux-riscv64-gnu",
+ "@rollup/rollup-linux-riscv64-musl",
+ "@rollup/rollup-linux-s390x-gnu",
+ "@rollup/rollup-linux-x64-gnu",
+ "@rollup/rollup-linux-x64-musl",
+ "@rollup/rollup-openbsd-x64",
+ "@rollup/rollup-openharmony-arm64",
+ "@rollup/rollup-win32-arm64-msvc",
+ "@rollup/rollup-win32-ia32-msvc",
+ "@rollup/rollup-win32-x64-gnu",
+ "@rollup/rollup-win32-x64-msvc",
+ "fsevents"
+ ],
+ "bin": true
+ },
+ "sade@1.8.1": {
+ "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
+ "dependencies": [
+ "mri"
+ ]
+ },
+ "semver@7.7.3": {
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "bin": true
+ },
+ "shebang-command@2.0.0": {
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dependencies": [
+ "shebang-regex"
+ ]
+ },
+ "shebang-regex@3.0.0": {
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
+ },
+ "source-map-js@1.2.1": {
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
+ },
+ "strip-json-comments@3.1.1": {
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="
+ },
+ "supports-color@7.2.0": {
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dependencies": [
+ "has-flag"
+ ]
+ },
+ "svelte-check@4.3.5_svelte@5.41.1__acorn@8.15.0_typescript@5.9.3": {
+ "integrity": "sha512-e4VWZETyXaKGhpkxOXP+B/d0Fp/zKViZoJmneZWe/05Y2aqSKj3YN2nLfYPJBQ87WEiY4BQCQ9hWGu9mPT1a1Q==",
+ "dependencies": [
+ "@jridgewell/trace-mapping",
+ "chokidar",
+ "fdir",
+ "picocolors",
+ "sade",
+ "svelte",
+ "typescript"
+ ],
+ "bin": true
+ },
+ "svelte-eslint-parser@1.4.1_svelte@5.41.1__acorn@8.15.0_postcss@8.5.6": {
+ "integrity": "sha512-1eqkfQ93goAhjAXxZiu1SaKI9+0/sxp4JIWQwUpsz7ybehRE5L8dNuz7Iry7K22R47p5/+s9EM+38nHV2OlgXA==",
+ "dependencies": [
+ "eslint-scope",
+ "eslint-visitor-keys@4.2.1",
+ "espree",
+ "postcss",
+ "postcss-scss",
+ "postcss-selector-parser",
+ "svelte"
+ ],
+ "optionalPeers": [
+ "svelte"
+ ]
+ },
+ "svelte@5.41.1_acorn@8.15.0": {
+ "integrity": "sha512-0a/huwc8e2es+7KFi70esqsReRfRbrT8h1cJSY/+z1lF0yKM6TT+//HYu28Yxstr50H7ifaqZRDGd0KuKDxP7w==",
+ "dependencies": [
+ "@jridgewell/remapping",
+ "@jridgewell/sourcemap-codec",
+ "@sveltejs/acorn-typescript",
+ "@types/estree",
+ "acorn",
+ "aria-query",
+ "axobject-query",
+ "clsx",
+ "esm-env",
+ "esrap",
+ "is-reference",
+ "locate-character",
+ "magic-string",
+ "zimmerframe"
+ ]
+ },
+ "tinyglobby@0.2.15_picomatch@4.0.3": {
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dependencies": [
+ "fdir",
+ "picomatch"
+ ]
+ },
+ "type-check@0.4.0": {
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dependencies": [
+ "prelude-ls"
+ ]
+ },
+ "typescript@5.9.3": {
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "bin": true
+ },
+ "uri-js@4.4.1": {
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dependencies": [
+ "punycode"
+ ]
+ },
+ "util-deprecate@1.0.2": {
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
+ },
+ "vite@7.3.1_picomatch@4.0.3": {
+ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
+ "dependencies": [
+ "esbuild",
+ "fdir",
+ "picomatch",
+ "postcss",
+ "rollup",
+ "tinyglobby"
+ ],
+ "optionalDependencies": [
+ "fsevents"
+ ],
+ "bin": true
+ },
+ "vitefu@1.1.1_vite@7.3.1__picomatch@4.0.3": {
+ "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==",
+ "dependencies": [
+ "vite"
+ ],
+ "optionalPeers": [
+ "vite"
+ ]
+ },
+ "which@2.0.2": {
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dependencies": [
+ "isexe"
+ ],
+ "bin": true
+ },
+ "word-wrap@1.2.5": {
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="
+ },
+ "yaml@1.10.2": {
+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="
+ },
+ "yocto-queue@0.1.0": {
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="
+ },
+ "zimmerframe@1.1.4": {
+ "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="
+ }
+ },
+ "workspace": {
+ "packageJson": {
+ "dependencies": [
+ "npm:@sveltejs/vite-plugin-svelte@^6.2.1",
+ "npm:@tsconfig/svelte@^5.0.5",
+ "npm:eslint-plugin-svelte@^3.12.4",
+ "npm:svelte-check@^4.3.3",
+ "npm:svelte@5.41.1",
+ "npm:typescript@5.9.3",
+ "npm:vite@^7.3.0"
+ ]
+ }
+ }
+}
diff --git a/src/apps/webpeer/index.html b/src/apps/webpeer/index.html
new file mode 100644
index 0000000..1753155
--- /dev/null
+++ b/src/apps/webpeer/index.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+ Peer-to-Peer Daemon on Browser
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/apps/webpeer/package.json b/src/apps/webpeer/package.json
new file mode 100644
index 0000000..dd12898
--- /dev/null
+++ b/src/apps/webpeer/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "webpeer",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview",
+ "check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json"
+ },
+ "dependencies": {},
+ "devDependencies": {
+ "eslint-plugin-svelte": "^3.12.4",
+ "@sveltejs/vite-plugin-svelte": "^6.2.1",
+ "@tsconfig/svelte": "^5.0.5",
+ "svelte": "5.41.1",
+ "svelte-check": "^4.3.3",
+ "typescript": "5.9.3",
+ "vite": "^7.3.0"
+ },
+ "imports": {
+ "../../src/worker/bgWorker.ts": "../../src/worker/bgWorker.mock.ts"
+ }
+}
diff --git a/src/apps/webpeer/public/icon.svg b/src/apps/webpeer/public/icon.svg
new file mode 100644
index 0000000..a7fd775
--- /dev/null
+++ b/src/apps/webpeer/public/icon.svg
@@ -0,0 +1,52 @@
+
+
+
+
diff --git a/src/apps/webpeer/public/manifest.json b/src/apps/webpeer/public/manifest.json
new file mode 100644
index 0000000..39cb501
--- /dev/null
+++ b/src/apps/webpeer/public/manifest.json
@@ -0,0 +1,26 @@
+{
+ "name": "WebPeer - Pseudo client for Self-hosted LiveSync Peer-to-Peer Replication",
+ "short_name": "WepPeer",
+ "description": "A web-based pseudo peer-to-peer replication client using as like server for background sync.",
+ "start_url": "./",
+ "display": "standalone",
+ "background_color": "#fff",
+ "theme_color": "#fff",
+ "orientation": "any",
+ "icons": [
+ {
+ "src": "./icon.svg",
+ "sizes": "512x512",
+ "type": "image/svg+xml",
+ "maskable": true
+ }
+ ],
+ "additional_icons": [
+ {
+ "src": "./icon.svg",
+ "sizes": "512x512",
+ "type": "image/svg+xml",
+ "maskable": true
+ }
+ ]
+}
diff --git a/src/apps/webpeer/src/App.svelte b/src/apps/webpeer/src/App.svelte
new file mode 100644
index 0000000..bf32f50
--- /dev/null
+++ b/src/apps/webpeer/src/App.svelte
@@ -0,0 +1,5 @@
+
+
+
diff --git a/src/apps/webpeer/src/CommandsShim.ts b/src/apps/webpeer/src/CommandsShim.ts
new file mode 100644
index 0000000..0f53cd1
--- /dev/null
+++ b/src/apps/webpeer/src/CommandsShim.ts
@@ -0,0 +1,23 @@
+import { LOG_LEVEL_VERBOSE } from "@lib/common/types";
+
+import { defaultLoggerEnv, setGlobalLogFunction } from "@lib/common/logger";
+import { writable } from "svelte/store";
+
+export const logs = writable([] as string[]);
+
+let _logs = [] as string[];
+
+const maxLines = 10000;
+setGlobalLogFunction((msg, level) => {
+ console.log(msg);
+ const msgstr = typeof msg === "string" ? msg : JSON.stringify(msg);
+ const strLog = `${new Date().toISOString()}\u2001${msgstr}`;
+ _logs.push(strLog);
+ if (_logs.length > maxLines) {
+ _logs = _logs.slice(_logs.length - maxLines);
+ }
+ logs.set(_logs);
+});
+defaultLoggerEnv.minLogLevel = LOG_LEVEL_VERBOSE;
+
+export const storeP2PStatusLine = writable("");
diff --git a/src/apps/webpeer/src/P2PReplicatorShim.ts b/src/apps/webpeer/src/P2PReplicatorShim.ts
new file mode 100644
index 0000000..c548fd1
--- /dev/null
+++ b/src/apps/webpeer/src/P2PReplicatorShim.ts
@@ -0,0 +1,364 @@
+import { PouchDB } from "@lib/pouchdb/pouchdb-browser";
+import {
+ type EntryDoc,
+ type LOG_LEVEL,
+ type P2PSyncSetting,
+ LOG_LEVEL_NOTICE,
+ LOG_LEVEL_VERBOSE,
+ P2P_DEFAULT_SETTINGS,
+ REMOTE_P2P,
+} from "@lib/common/types";
+import { eventHub } from "@lib/hub/hub";
+
+import type { Confirm } from "@lib/interfaces/Confirm";
+import { LOG_LEVEL_INFO, Logger } from "@lib/common/logger";
+import { storeP2PStatusLine } from "./CommandsShim";
+import {
+ EVENT_P2P_PEER_SHOW_EXTRA_MENU,
+ type CommandShim,
+ type PeerStatus,
+ type PluginShim,
+} from "@lib/replication/trystero/P2PReplicatorPaneCommon";
+import {
+ closeP2PReplicator,
+ openP2PReplicator,
+ P2PLogCollector,
+ type P2PReplicatorBase,
+} from "@lib/replication/trystero/P2PReplicatorCore";
+import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase";
+import { reactiveSource } from "octagonal-wheels/dataobject/reactive_v2";
+import { EVENT_SETTING_SAVED } from "@lib/events/coreEvents";
+import { unique } from "octagonal-wheels/collection";
+import { BrowserServiceHub } from "@lib/services/BrowserServices";
+import { TrysteroReplicator } from "@lib/replication/trystero/TrysteroReplicator";
+import { SETTING_KEY_P2P_DEVICE_NAME } from "@lib/common/types";
+import { ServiceContext } from "@lib/services/base/ServiceBase";
+import type { InjectableServiceHub } from "@lib/services/InjectableServices";
+import { Menu } from "@/lib/src/services/implements/browser/Menu";
+
+function addToList(item: string, list: string) {
+ return unique(
+ list
+ .split(",")
+ .map((e) => e.trim())
+ .concat(item)
+ .filter((p) => p)
+ ).join(",");
+}
+function removeFromList(item: string, list: string) {
+ return list
+ .split(",")
+ .map((e) => e.trim())
+ .filter((p) => p !== item)
+ .filter((p) => p)
+ .join(",");
+}
+
+export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim {
+ storeP2PStatusLine = reactiveSource("");
+ plugin!: PluginShim;
+ // environment!: IEnvironment;
+ confirm!: Confirm;
+ // simpleStoreAPI!: ISimpleStoreAPI;
+ db?: PouchDB.Database;
+ services: InjectableServiceHub;
+
+ getDB() {
+ if (!this.db) {
+ throw new Error("DB not initialized");
+ }
+ return this.db;
+ }
+ _simpleStore!: SimpleStore;
+ async closeDB() {
+ if (this.db) {
+ await this.db.close();
+ this.db = undefined;
+ }
+ }
+ constructor() {
+ const browserServiceHub = new BrowserServiceHub();
+ this.services = browserServiceHub;
+ this.services.vault.getVaultName.setHandler(() => "p2p-livesync-web-peer");
+ }
+ async init() {
+ // const { simpleStoreAPI } = await getWrappedSynchromesh();
+ // this.confirm = confirm;
+ this.confirm = this.services.UI.confirm;
+ // this.environment = environment;
+
+ if (this.db) {
+ try {
+ await this.closeDB();
+ } catch (ex) {
+ Logger("Error closing db", LOG_LEVEL_VERBOSE);
+ Logger(ex, LOG_LEVEL_VERBOSE);
+ }
+ }
+
+ const repStore = this.services.database.openSimpleStore("p2p-livesync-web-peer");
+ this._simpleStore = repStore;
+ let _settings = (await repStore.get("settings")) || ({ ...P2P_DEFAULT_SETTINGS } as P2PSyncSetting);
+
+ this.plugin = {
+ saveSettings: async () => {
+ await repStore.set("settings", _settings);
+ eventHub.emitEvent(EVENT_SETTING_SAVED, _settings);
+ },
+ get settings() {
+ return _settings;
+ },
+ set settings(newSettings: P2PSyncSetting) {
+ _settings = { ..._settings, ...newSettings };
+ },
+ rebuilder: null,
+ services: this.services,
+ // $$scheduleAppReload: () => {},
+ // $$getVaultName: () => "p2p-livesync-web-peer",
+ };
+ // const deviceName = this.getDeviceName();
+ const database_name = this.settings.P2P_AppID + "-" + this.settings.P2P_roomID + "p2p-livesync-web-peer";
+ this.db = new PouchDB(database_name);
+ setTimeout(() => {
+ if (this.settings.P2P_AutoStart && this.settings.P2P_Enabled) {
+ void this.open();
+ }
+ }, 1000);
+ return this;
+ }
+ get settings() {
+ return this.plugin.settings;
+ }
+ _log(msg: any, level?: LOG_LEVEL): void {
+ Logger(msg, level);
+ }
+ _notice(msg: string, key?: string): void {
+ Logger(msg, LOG_LEVEL_NOTICE, key);
+ }
+ getSettings(): P2PSyncSetting {
+ return this.settings;
+ }
+ simpleStore(): SimpleStore {
+ return this._simpleStore;
+ }
+ handleReplicatedDocuments(docs: EntryDoc[]): Promise {
+ // No op. This is a client and does not need to process the docs
+ return Promise.resolve();
+ }
+
+ getPluginShim() {
+ return {};
+ }
+ getConfig(key: string) {
+ const vaultName = this.services.vault.getVaultName();
+ const dbKey = `${vaultName}-${key}`;
+ return localStorage.getItem(dbKey);
+ }
+ setConfig(key: string, value: string) {
+ const vaultName = this.services.vault.getVaultName();
+ const dbKey = `${vaultName}-${key}`;
+ localStorage.setItem(dbKey, value);
+ }
+
+ getDeviceName(): string {
+ return this.getConfig(SETTING_KEY_P2P_DEVICE_NAME) ?? this.plugin.services.vault.getVaultName();
+ }
+ getPlatform(): string {
+ return "pseudo-replicator";
+ }
+ m?: Menu;
+ afterConstructor(): void {
+ eventHub.onEvent(EVENT_P2P_PEER_SHOW_EXTRA_MENU, ({ peer, event }) => {
+ if (this.m) {
+ this.m.hide();
+ }
+ this.m = new Menu()
+ .addItem((item) => item.setTitle("📥 Only Fetch").onClick(() => this.replicateFrom(peer)))
+ .addItem((item) => item.setTitle("📤 Only Send").onClick(() => this.replicateTo(peer)))
+ .addSeparator()
+ // .addItem((item) => {
+ // item.setTitle("🔧 Get Configuration").onClick(async () => {
+ // await this.getRemoteConfig(peer);
+ // });
+ // })
+ // .addSeparator()
+ .addItem((item) => {
+ const mark = peer.syncOnConnect ? "checkmark" : null;
+ item.setTitle("Toggle Sync on connect")
+ .onClick(async () => {
+ await this.toggleProp(peer, "syncOnConnect");
+ })
+ .setIcon(mark);
+ })
+ .addItem((item) => {
+ const mark = peer.watchOnConnect ? "checkmark" : null;
+ item.setTitle("Toggle Watch on connect")
+ .onClick(async () => {
+ await this.toggleProp(peer, "watchOnConnect");
+ })
+ .setIcon(mark);
+ })
+ .addItem((item) => {
+ const mark = peer.syncOnReplicationCommand ? "checkmark" : null;
+ item.setTitle("Toggle Sync on `Replicate now` command")
+ .onClick(async () => {
+ await this.toggleProp(peer, "syncOnReplicationCommand");
+ })
+ .setIcon(mark);
+ });
+ void this.m.showAtPosition({ x: event.x, y: event.y });
+ });
+ this.p2pLogCollector.p2pReplicationLine.onChanged((line) => {
+ storeP2PStatusLine.set(line.value);
+ });
+ }
+
+ _replicatorInstance?: TrysteroReplicator;
+ p2pLogCollector = new P2PLogCollector();
+ async open() {
+ await openP2PReplicator(this);
+ }
+ async close() {
+ await closeP2PReplicator(this);
+ }
+ enableBroadcastCastings() {
+ return this?._replicatorInstance?.enableBroadcastChanges();
+ }
+ disableBroadcastCastings() {
+ return this?._replicatorInstance?.disableBroadcastChanges();
+ }
+
+ async initialiseP2PReplicator(): Promise {
+ await this.init();
+ try {
+ if (this._replicatorInstance) {
+ await this._replicatorInstance.close();
+ this._replicatorInstance = undefined;
+ }
+
+ if (!this.settings.P2P_AppID) {
+ this.settings.P2P_AppID = P2P_DEFAULT_SETTINGS.P2P_AppID;
+ }
+ const getInitialDeviceName = () =>
+ this.getConfig(SETTING_KEY_P2P_DEVICE_NAME) || this.services.vault.getVaultName();
+
+ const getSettings = () => this.settings;
+ const store = () => this.simpleStore();
+ const getDB = () => this.getDB();
+
+ const getConfirm = () => this.confirm;
+ const getPlatform = () => this.getPlatform();
+ const env = {
+ get db() {
+ return getDB();
+ },
+ get confirm() {
+ return getConfirm();
+ },
+ get deviceName() {
+ return getInitialDeviceName();
+ },
+ get platform() {
+ return getPlatform();
+ },
+ get settings() {
+ return getSettings();
+ },
+ processReplicatedDocs: async (docs: EntryDoc[]): Promise => {
+ await this.handleReplicatedDocuments(docs);
+ // No op. This is a client and does not need to process the docs
+ },
+ get simpleStore() {
+ return store();
+ },
+ };
+ this._replicatorInstance = new TrysteroReplicator(env);
+ return this._replicatorInstance;
+ } catch (e) {
+ this._log(
+ e instanceof Error ? e.message : "Something occurred on Initialising P2P Replicator",
+ LOG_LEVEL_INFO
+ );
+ this._log(e, LOG_LEVEL_VERBOSE);
+ throw e;
+ }
+ }
+
+ get replicator() {
+ return this._replicatorInstance!;
+ }
+ async replicateFrom(peer: PeerStatus) {
+ await this.replicator.replicateFrom(peer.peerId);
+ }
+ async replicateTo(peer: PeerStatus) {
+ await this.replicator.requestSynchroniseToPeer(peer.peerId);
+ }
+ async getRemoteConfig(peer: PeerStatus) {
+ Logger(
+ `Requesting remote config for ${peer.name}. Please input the passphrase on the remote device`,
+ LOG_LEVEL_NOTICE
+ );
+ const remoteConfig = await this.replicator.getRemoteConfig(peer.peerId);
+ if (remoteConfig) {
+ Logger(`Remote config for ${peer.name} is retrieved successfully`);
+ const DROP = "Yes, and drop local database";
+ const KEEP = "Yes, but keep local database";
+ const CANCEL = "No, cancel";
+ const yn = await this.confirm.askSelectStringDialogue(
+ `Do you really want to apply the remote config? This will overwrite your current config immediately and restart.
+ And you can also drop the local database to rebuild from the remote device.`,
+ [DROP, KEEP, CANCEL] as const,
+ {
+ defaultAction: CANCEL,
+ title: "Apply Remote Config ",
+ }
+ );
+ if (yn === DROP || yn === KEEP) {
+ if (yn === DROP) {
+ if (remoteConfig.remoteType !== REMOTE_P2P) {
+ const yn2 = await this.confirm.askYesNoDialog(
+ `Do you want to set the remote type to "P2P Sync" to rebuild by "P2P replication"?`,
+ {
+ title: "Rebuild from remote device",
+ }
+ );
+ if (yn2 === "yes") {
+ remoteConfig.remoteType = REMOTE_P2P;
+ remoteConfig.P2P_RebuildFrom = peer.name;
+ }
+ }
+ }
+ this.plugin.settings = remoteConfig;
+ await this.plugin.saveSettings();
+ if (yn === DROP) {
+ await this.plugin.rebuilder.scheduleFetch();
+ } else {
+ await this.plugin.services.appLifecycle.scheduleRestart();
+ }
+ } else {
+ Logger(`Cancelled\nRemote config for ${peer.name} is not applied`, LOG_LEVEL_NOTICE);
+ }
+ } else {
+ Logger(`Cannot retrieve remote config for ${peer.peerId}`);
+ }
+ }
+
+ async toggleProp(peer: PeerStatus, prop: "syncOnConnect" | "watchOnConnect" | "syncOnReplicationCommand") {
+ const settingMap = {
+ syncOnConnect: "P2P_AutoSyncPeers",
+ watchOnConnect: "P2P_AutoWatchPeers",
+ syncOnReplicationCommand: "P2P_SyncOnReplication",
+ } as const;
+
+ const targetSetting = settingMap[prop];
+ if (peer[prop]) {
+ this.plugin.settings[targetSetting] = removeFromList(peer.name, this.plugin.settings[targetSetting]);
+ await this.plugin.saveSettings();
+ } else {
+ this.plugin.settings[targetSetting] = addToList(peer.name, this.plugin.settings[targetSetting]);
+ await this.plugin.saveSettings();
+ }
+ }
+}
+
+export const cmdSyncShim = new P2PReplicatorShim();
diff --git a/src/apps/webpeer/src/SyncMain.svelte b/src/apps/webpeer/src/SyncMain.svelte
new file mode 100644
index 0000000..853ce86
--- /dev/null
+++ b/src/apps/webpeer/src/SyncMain.svelte
@@ -0,0 +1,112 @@
+
+
+
+
+ {#await synchronised then cmdSync}
+
+ {:catch error}
+
{error.message}
+ {/await}
+
+
+
+ {statusLine}
+
+
+ {#each $logs as log}
+
{log}
+ {/each}
+
+
+
+
+
diff --git a/src/apps/webpeer/src/UITest.svelte b/src/apps/webpeer/src/UITest.svelte
new file mode 100644
index 0000000..8cd83fc
--- /dev/null
+++ b/src/apps/webpeer/src/UITest.svelte
@@ -0,0 +1,74 @@
+
+
+
+ UI Test
+
+
+
+ → {result}
+
+
+
+ → {resultPassword}
+
+
+
+
+
+
diff --git a/src/apps/webpeer/src/app.css b/src/apps/webpeer/src/app.css
new file mode 100644
index 0000000..85b8da6
--- /dev/null
+++ b/src/apps/webpeer/src/app.css
@@ -0,0 +1,112 @@
+:root {
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
+ line-height: 1.5;
+ font-weight: 400;
+
+ color-scheme: light dark;
+ color: rgba(255, 255, 255, 0.87);
+ background-color: #242424;
+
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+
+ --background-primary: #ffffff;
+ --background-primary-alt: #e9e9e9;
+ --size-4-1: 0.25em;
+ --tag-background: #f0f0f0;
+ --tag-border-width: 1px;
+ --tag-border-color: #cfffdd;
+ --background-modifier-success: #d4f3e9;
+ --background-secondary: #f0f0f0;
+ --background-modifier-error: #f8d7da;
+ --background-modifier-error-hover: #f5c6cb;
+ --interactive-accent: #007bff;
+ --interactive-accent-hover: #0056b3;
+ --text-normal: #333;
+ --text-warning: #f0ad4e;
+}
+
+a {
+ font-weight: 500;
+ color: #646cff;
+ text-decoration: inherit;
+}
+
+a:hover {
+ color: #535bf2;
+}
+
+body {
+ margin: 0;
+ padding: 0;
+ display: flex;
+ min-width: 320px;
+ min-height: 100vh;
+ box-sizing: border-box;
+}
+
+h1 {
+ font-size: 3.2em;
+ line-height: 1.1;
+}
+
+.card {
+ padding: 2em;
+}
+
+#app {
+ margin: 0;
+ padding: 0;
+ text-align: center;
+ display: flex;
+ flex-grow: 1;
+}
+
+button {
+ border-radius: 8px;
+ border: 1px solid transparent;
+ padding: 0.6em 1.2em;
+ font-size: 1em;
+ font-weight: 500;
+ font-family: inherit;
+ background-color: #1a1a1a;
+ cursor: pointer;
+ transition: border-color 0.25s;
+}
+
+button:hover {
+ border-color: #646cff;
+}
+
+button:focus,
+button:focus-visible {
+ outline: 4px auto -webkit-focus-ring-color;
+}
+
+input,
+select {
+ border-radius: 8px;
+ border: 1px solid #1a1a1a;
+ padding: 0.6em 1.2em;
+ font-size: 1em;
+ font-weight: 500;
+ font-family: inherit;
+ transition: border-color 0.25s;
+}
+
+@media (prefers-color-scheme: light) {
+ :root {
+ color: #213547;
+ background-color: #ffffff;
+ }
+
+ a:hover {
+ color: #747bff;
+ }
+
+ button {
+ background-color: #f9f9f9;
+ }
+}
\ No newline at end of file
diff --git a/src/apps/webpeer/src/assets/svelte.svg b/src/apps/webpeer/src/assets/svelte.svg
new file mode 100644
index 0000000..c5e0848
--- /dev/null
+++ b/src/apps/webpeer/src/assets/svelte.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/apps/webpeer/src/main.ts b/src/apps/webpeer/src/main.ts
new file mode 100644
index 0000000..befae65
--- /dev/null
+++ b/src/apps/webpeer/src/main.ts
@@ -0,0 +1,9 @@
+import { mount } from "svelte";
+import "./app.css";
+import App from "./App.svelte";
+
+const app = mount(App, {
+ target: document.getElementById("app")!,
+});
+
+export default app;
diff --git a/src/apps/webpeer/src/uitest.ts b/src/apps/webpeer/src/uitest.ts
new file mode 100644
index 0000000..04132b0
--- /dev/null
+++ b/src/apps/webpeer/src/uitest.ts
@@ -0,0 +1,9 @@
+import { mount } from "svelte";
+import "./app.css";
+import App from "./UITest.svelte";
+
+const app = mount(App, {
+ target: document.getElementById("app")!,
+});
+
+export default app;
diff --git a/src/apps/webpeer/src/vite-env.d.ts b/src/apps/webpeer/src/vite-env.d.ts
new file mode 100644
index 0000000..4078e74
--- /dev/null
+++ b/src/apps/webpeer/src/vite-env.d.ts
@@ -0,0 +1,2 @@
+///
+///
diff --git a/src/apps/webpeer/svelte.config.js b/src/apps/webpeer/svelte.config.js
new file mode 100644
index 0000000..9a3adfb
--- /dev/null
+++ b/src/apps/webpeer/svelte.config.js
@@ -0,0 +1,7 @@
+import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
+
+export default {
+ // Consult https://svelte.dev/docs#compile-time-svelte-preprocess
+ // for more information about preprocessors
+ preprocess: vitePreprocess(),
+};
diff --git a/src/apps/webpeer/tsconfig.app.json b/src/apps/webpeer/tsconfig.app.json
new file mode 100644
index 0000000..5bd9975
--- /dev/null
+++ b/src/apps/webpeer/tsconfig.app.json
@@ -0,0 +1,25 @@
+{
+ "extends": "@tsconfig/svelte/tsconfig.json",
+ "compilerOptions": {
+ "sourceRoot": "../",
+ "target": "ESNext",
+ "useDefineForClassFields": true,
+ "module": "ESNext",
+ "resolveJsonModule": true,
+ /**
+ * Typecheck JS in `.svelte` and `.js` files by default.
+ * Disable checkJs if you'd like to use dynamic types in JS.
+ * Note that setting allowJs false does not prevent the use
+ * of JS in `.svelte` files.
+ */
+ "allowJs": true,
+ "checkJs": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "paths": {
+ "@/*": ["../../*"],
+ "@lib/*": ["../../lib/src/*"]
+ }
+ },
+ "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"]
+}
diff --git a/src/apps/webpeer/tsconfig.json b/src/apps/webpeer/tsconfig.json
new file mode 100644
index 0000000..eb69b0d
--- /dev/null
+++ b/src/apps/webpeer/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ "files": [],
+ "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }]
+}
diff --git a/src/apps/webpeer/tsconfig.node.json b/src/apps/webpeer/tsconfig.node.json
new file mode 100644
index 0000000..c5f047b
--- /dev/null
+++ b/src/apps/webpeer/tsconfig.node.json
@@ -0,0 +1,28 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "target": "ES2022",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true,
+ "paths": {
+ "@/*": ["../../*"],
+ "@lib/*": ["../../lib/src/*"]
+ }
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/src/apps/webpeer/ui.html b/src/apps/webpeer/ui.html
new file mode 100644
index 0000000..631df86
--- /dev/null
+++ b/src/apps/webpeer/ui.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+ Peer-to-Peer Daemon on Browser
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/apps/webpeer/vite.config.ts b/src/apps/webpeer/vite.config.ts
new file mode 100644
index 0000000..e7ded0b
--- /dev/null
+++ b/src/apps/webpeer/vite.config.ts
@@ -0,0 +1,24 @@
+import { defineConfig } from "vite";
+import { svelte } from "@sveltejs/vite-plugin-svelte";
+import path from "node:path";
+// https://vite.dev/config/
+export default defineConfig({
+ plugins: [svelte()],
+ resolve: {
+ alias: {
+ "@": path.resolve(__dirname, "../../"),
+ "@lib": path.resolve(__dirname, "../../lib/src"),
+ },
+ },
+ base: "./",
+ build: {
+ outDir: "dist",
+ emptyOutDir: true,
+ rollupOptions: {
+ input: {
+ index: "index.html",
+ // uitest: "uitest.html",
+ },
+ },
+ },
+});
diff --git a/src/features/P2PSync/CmdP2PReplicator.ts b/src/features/P2PSync/CmdP2PReplicator.ts
index 7b85b33..211fd3a 100644
--- a/src/features/P2PSync/CmdP2PReplicator.ts
+++ b/src/features/P2PSync/CmdP2PReplicator.ts
@@ -29,7 +29,7 @@ import { reactiveSource } from "octagonal-wheels/dataobject/reactive_v2";
import type { Confirm } from "../../lib/src/interfaces/Confirm.ts";
import type ObsidianLiveSyncPlugin from "../../main.ts";
import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase";
-import { getPlatformName } from "../../lib/src/PlatformAPIs/obsidian/Environment.ts";
+// import { getPlatformName } from "../../lib/src/PlatformAPIs/obsidian/Environment.ts";
import type { LiveSyncCore } from "../../main.ts";
import { TrysteroReplicator } from "../../lib/src/replication/trystero/TrysteroReplicator.ts";
import { SETTING_KEY_P2P_DEVICE_NAME } from "../../lib/src/common/types.ts";
@@ -130,7 +130,7 @@ export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase
const getDB = () => this.getDB();
const getConfirm = () => this.confirm;
- const getPlatform = () => this.getPlatform();
+ const getPlatform = () => this.services.API.getPlatform();
const env = {
get db() {
return getDB();
@@ -166,9 +166,6 @@ export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase
throw e;
}
}
- getPlatform(): string {
- return getPlatformName();
- }
onunload(): void {
removeP2PReplicatorInstance();
diff --git a/src/lib b/src/lib
index cd32d3d..a02102b 160000
--- a/src/lib
+++ b/src/lib
@@ -1 +1 @@
-Subproject commit cd32d3d32635a536266efbf7f974b47f70b878c5
+Subproject commit a02102b13197826f1591448a6bf5ecdee8673d00
diff --git a/src/main.ts b/src/main.ts
index 7be8578..fc65dc1 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -67,9 +67,10 @@ import { ModuleExtraSyncObsidian } from "./modules/extraFeaturesObsidian/ModuleE
import { LocalDatabaseMaintenance } from "./features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts";
import { P2PReplicator } from "./features/P2PSync/CmdP2PReplicator.ts";
import type { LiveSyncManagers } from "./lib/src/managers/LiveSyncManagers.ts";
-import { ObsidianServiceHub } from "./modules/services/ObsidianServices.ts";
-import type { InjectableServiceHub } from "./lib/src/services/InjectableServices.ts";
-import type { ServiceContext } from "./lib/src/services/ServiceHub.ts";
+import type { InjectableServiceHub } from "./lib/src/services/implements/injectable/InjectableServiceHub.ts";
+import { ObsidianServiceHub } from "./modules/services/ObsidianServiceHub.ts";
+import type { ServiceContext } from "./lib/src/services/base/ServiceBase.ts";
+// import type { InjectableServiceHub } from "./lib/src/services/InjectableServices.ts";
export default class ObsidianLiveSyncPlugin
extends Plugin
diff --git a/src/modules/coreFeatures/ModuleRedFlag.ts b/src/modules/coreFeatures/ModuleRedFlag.ts
index 931e39f..5ed6e1e 100644
--- a/src/modules/coreFeatures/ModuleRedFlag.ts
+++ b/src/modules/coreFeatures/ModuleRedFlag.ts
@@ -9,10 +9,11 @@ import {
} from "../../lib/src/common/types.ts";
import { AbstractModule } from "../AbstractModule.ts";
import type { LiveSyncCore } from "../../main.ts";
-import { SvelteDialogManager } from "../features/SetupWizard/ObsidianSvelteDialog.ts";
import FetchEverything from "../features/SetupWizard/dialogs/FetchEverything.svelte";
import RebuildEverything from "../features/SetupWizard/dialogs/RebuildEverything.svelte";
import { extractObject } from "octagonal-wheels/object";
+import { SvelteDialogManagerBase } from "@/lib/src/UI/svelteDialog.ts";
+import type { ServiceContext } from "@/lib/src/services/base/ServiceBase.ts";
export class ModuleRedFlag extends AbstractModule {
async isFlagFileExist(path: string) {
@@ -52,7 +53,10 @@ export class ModuleRedFlag extends AbstractModule {
await this.deleteFlagFile(FlagFilesOriginal.FETCH_ALL);
await this.deleteFlagFile(FlagFilesHumanReadable.FETCH_ALL);
}
- dialogManager = new SvelteDialogManager(this.core);
+ // dialogManager = new SvelteDialogManagerBase(this.core);
+ get dialogManager(): SvelteDialogManagerBase {
+ return this.core.services.UI.dialogManager;
+ }
/**
* Adjust setting to remote if needed.
diff --git a/src/modules/essential/ModuleKeyValueDB.ts b/src/modules/essential/ModuleKeyValueDB.ts
index 3c58edf..6d657e8 100644
--- a/src/modules/essential/ModuleKeyValueDB.ts
+++ b/src/modules/essential/ModuleKeyValueDB.ts
@@ -5,6 +5,8 @@ import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/log
import { AbstractModule } from "../AbstractModule.ts";
import type { LiveSyncCore } from "../../main.ts";
import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase";
+import type { InjectableServiceHub } from "@/lib/src/services/InjectableServices.ts";
+import type { ObsidianDatabaseService } from "../services/ObsidianServices.ts";
export class ModuleKeyValueDB extends AbstractModule {
async tryCloseKvDB() {
@@ -77,6 +79,7 @@ export class ModuleKeyValueDB extends AbstractModule {
.filter((e) => e.startsWith(prefix))
.map((e) => e.substring(prefix.length));
},
+ db: Promise.resolve(getDB()),
} satisfies SimpleStore;
}
_everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise {
@@ -100,12 +103,12 @@ export class ModuleKeyValueDB extends AbstractModule {
}
return true;
}
- onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
+ onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void {
services.databaseEvents.onUnloadDatabase.addHandler(this._onDBUnload.bind(this));
services.databaseEvents.onCloseDatabase.addHandler(this._onDBClose.bind(this));
services.databaseEvents.onDatabaseInitialisation.addHandler(this._everyOnInitializeDatabase.bind(this));
services.databaseEvents.onResetDatabase.addHandler(this._everyOnResetDatabase.bind(this));
- services.database.openSimpleStore.setHandler(this._getSimpleStore.bind(this));
+ (services.database as ObsidianDatabaseService).openSimpleStore.setHandler(this._getSimpleStore.bind(this));
services.appLifecycle.onSettingLoaded.addHandler(this._everyOnloadAfterLoadSettings.bind(this));
}
}
diff --git a/src/modules/essentialObsidian/ModuleObsidianAPI.ts b/src/modules/essentialObsidian/ModuleObsidianAPI.ts
index 1ddc4fd..cdd085b 100644
--- a/src/modules/essentialObsidian/ModuleObsidianAPI.ts
+++ b/src/modules/essentialObsidian/ModuleObsidianAPI.ts
@@ -15,7 +15,6 @@ import { replicationFilter } from "@/lib/src/pouchdb/compress.ts";
import { disableEncryption } from "@/lib/src/pouchdb/encryption.ts";
import { enableEncryption } from "@/lib/src/pouchdb/encryption.ts";
import { setNoticeClass } from "../../lib/src/mock_and_interop/wrapper.ts";
-import { ObsHttpHandler } from "./APILib/ObsHttpHandler.ts";
import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser.ts";
import { AuthorizationHeaderGenerator } from "../../lib/src/replication/httplib.ts";
import type { LiveSyncCore } from "../../main.ts";
@@ -27,10 +26,7 @@ async function fetchByAPI(request: RequestUrlParam, errorAsResult = false): Prom
const ret = await requestUrl({ ...request, throw: !errorAsResult });
return ret;
}
-
export class ModuleObsidianAPI extends AbstractObsidianModule {
- _customHandler!: ObsHttpHandler;
-
_authHeader = new AuthorizationHeaderGenerator();
_previousErrors = new Set();
@@ -47,10 +43,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule {
eventHub.emitEvent(EVENT_ON_UNRESOLVED_ERROR);
}
last_successful_post = false;
- _customFetchHandler(): ObsHttpHandler {
- if (!this._customHandler) this._customHandler = new ObsHttpHandler(undefined, undefined);
- return this._customHandler;
- }
+
_getLastPostFailedBySize(): boolean {
return !this.last_successful_post;
}
@@ -286,11 +279,6 @@ export class ModuleObsidianAPI extends AbstractObsidianModule {
}
}
- _isMobile(): boolean {
- //@ts-ignore : internal API
- return this.app.isMobile;
- }
-
_vaultName(): string {
return this.app.vault.getName();
}
@@ -308,38 +296,16 @@ export class ModuleObsidianAPI extends AbstractObsidianModule {
return undefined;
}
- _anyGetAppId(): string {
- return `${"appId" in this.app ? this.app.appId : ""}`;
- }
-
private _reportUnresolvedMessages(): Promise<(string | Error)[]> {
return Promise.resolve([...this._previousErrors]);
}
- private _getAppVersion(): string {
- const navigatorString = globalThis.navigator?.userAgent ?? "";
- const match = navigatorString.match(/obsidian\/([0-9]+\.[0-9]+\.[0-9]+)/);
- if (match && match.length >= 2) {
- return match[1];
- }
- return "0.0.0";
- }
-
- private _getPluginVersion(): string {
- return this.plugin.manifest.version;
- }
-
onBindFunction(core: LiveSyncCore, services: typeof core.services) {
- services.API.getCustomFetchHandler.setHandler(this._customFetchHandler.bind(this));
services.API.isLastPostFailedDueToPayloadSize.setHandler(this._getLastPostFailedBySize.bind(this));
services.remote.connect.setHandler(this._connectRemoteCouchDB.bind(this));
- services.API.isMobile.setHandler(this._isMobile.bind(this));
services.vault.getVaultName.setHandler(this._getVaultName.bind(this));
services.vault.vaultName.setHandler(this._vaultName.bind(this));
services.vault.getActiveFilePath.setHandler(this._getActiveFilePath.bind(this));
- services.API.getAppID.setHandler(this._anyGetAppId.bind(this));
- services.API.getAppVersion.setHandler(this._getAppVersion.bind(this));
- services.API.getPluginVersion.setHandler(this._getPluginVersion.bind(this));
services.appLifecycle.getUnresolvedMessages.addHandler(this._reportUnresolvedMessages.bind(this));
}
}
diff --git a/src/modules/essentialObsidian/ModuleObsidianMenu.ts b/src/modules/essentialObsidian/ModuleObsidianMenu.ts
index 0186504..736ae10 100644
--- a/src/modules/essentialObsidian/ModuleObsidianMenu.ts
+++ b/src/modules/essentialObsidian/ModuleObsidianMenu.ts
@@ -113,26 +113,8 @@ export class ModuleObsidianMenu extends AbstractObsidianModule {
return Promise.resolve(true);
}
- private async _showView(viewType: string) {
- const leaves = this.app.workspace.getLeavesOfType(viewType);
- if (leaves.length == 0) {
- await this.app.workspace.getLeaf(true).setViewState({
- type: viewType,
- active: true,
- });
- } else {
- await leaves[0].setViewState({
- type: viewType,
- active: true,
- });
- }
- if (leaves.length > 0) {
- await this.app.workspace.revealLeaf(leaves[0]);
- }
- }
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this));
services.appLifecycle.onLoaded.addHandler(this._everyOnload.bind(this));
- services.API.showWindow.setHandler(this._showView.bind(this));
}
}
diff --git a/src/modules/features/SetupManager.ts b/src/modules/features/SetupManager.ts
index 83ebbe1..6691338 100644
--- a/src/modules/features/SetupManager.ts
+++ b/src/modules/features/SetupManager.ts
@@ -9,7 +9,6 @@ import {
} from "../../lib/src/common/types.ts";
import { generatePatchObj, isObjectDifferent } from "../../lib/src/common/utils.ts";
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
-import { SvelteDialogManager } from "./SetupWizard/ObsidianSvelteDialog.ts";
import Intro from "./SetupWizard/dialogs/Intro.svelte";
import SelectMethodNewUser from "./SetupWizard/dialogs/SelectMethodNewUser.svelte";
import SelectMethodExisting from "./SetupWizard/dialogs/SelectMethodExisting.svelte";
@@ -52,10 +51,13 @@ export const enum UserMode {
* Setup Manager to handle onboarding and configuration setup
*/
export class SetupManager extends AbstractObsidianModule {
- /**
- * Dialog manager for handling Svelte dialogs
- */
- private dialogManager: SvelteDialogManager = new SvelteDialogManager(this.plugin);
+ // /**
+ // * Dialog manager for handling Svelte dialogs
+ // */
+ // private dialogManager: SvelteDialogManager = new SvelteDialogManager(this.plugin);
+ get dialogManager() {
+ return this.services.UI.dialogManager;
+ }
/**
* Starts the onboarding process
diff --git a/src/modules/features/SetupWizard/ObsidianSvelteDialog.ts b/src/modules/features/SetupWizard/ObsidianSvelteDialog.ts
deleted file mode 100644
index 51e57ad..0000000
--- a/src/modules/features/SetupWizard/ObsidianSvelteDialog.ts
+++ /dev/null
@@ -1,141 +0,0 @@
-import { eventHub, EVENT_PLUGIN_UNLOADED } from "@/common/events";
-import { Modal } from "@/deps";
-import type ObsidianLiveSyncPlugin from "@/main";
-import { mount, unmount } from "svelte";
-import DialogHost from "@lib/UI/DialogHost.svelte";
-import { fireAndForget, promiseWithResolvers, type PromiseWithResolvers } from "octagonal-wheels/promises";
-import { LOG_LEVEL_NOTICE, Logger } from "octagonal-wheels/common/logger";
-import {
- type DialogControlBase,
- type DialogSvelteComponentBaseProps,
- type ComponentHasResult,
- setupDialogContext,
- getDialogContext,
- type SvelteDialogManagerBase,
-} from "@/lib/src/UI/svelteDialog.ts";
-
-export type DialogSvelteComponentProps = DialogSvelteComponentBaseProps & {
- plugin: ObsidianLiveSyncPlugin;
- services: ObsidianLiveSyncPlugin["services"];
-};
-
-export type DialogControls = DialogControlBase & {
- plugin: ObsidianLiveSyncPlugin;
- services: ObsidianLiveSyncPlugin["services"];
-};
-
-export type DialogMessageProps = Record;
-// type DialogSvelteComponent = Component,any>;
-
-export class SvelteDialog extends Modal {
- plugin: ObsidianLiveSyncPlugin;
- mountedComponent?: ReturnType;
- component: ComponentHasResult;
- result?: T;
- initialData?: U;
- title: string = "Obsidian LiveSync - Setup Wizard";
- constructor(plugin: ObsidianLiveSyncPlugin, component: ComponentHasResult, initialData?: U) {
- super(plugin.app);
- this.plugin = plugin;
- this.component = component;
- this.initialData = initialData;
- }
- resolveResult() {
- this.resultPromiseWithResolvers?.resolve(this.result);
- this.resultPromiseWithResolvers = undefined;
- }
- resultPromiseWithResolvers?: PromiseWithResolvers;
- onOpen() {
- const { contentEl } = this;
- contentEl.empty();
- // eslint-disable-next-line @typescript-eslint/no-this-alias
- const dialog = this;
-
- if (this.resultPromiseWithResolvers) {
- this.resultPromiseWithResolvers.reject("Dialog opened again");
- }
- const pr = promiseWithResolvers();
- eventHub.once(EVENT_PLUGIN_UNLOADED, () => {
- if (this.resultPromiseWithResolvers === pr) {
- pr.reject("Plugin unloaded");
- this.close();
- }
- });
- this.resultPromiseWithResolvers = pr;
- this.mountedComponent = mount(DialogHost, {
- target: contentEl,
- props: {
- onSetupContext: (props: DialogSvelteComponentBaseProps) => {
- setupDialogContext({
- ...props,
- plugin: this.plugin,
- services: this.plugin.services,
- });
- },
- setTitle: (title: string) => {
- dialog.setTitle(title);
- },
- closeDialog: () => {
- dialog.close();
- },
- setResult: (result: T) => {
- this.result = result;
- },
- getInitialData: () => this.initialData,
- mountComponent: this.component,
- },
- });
- }
- waitForClose(): Promise {
- if (!this.resultPromiseWithResolvers) {
- throw new Error("Dialog not opened yet");
- }
- return this.resultPromiseWithResolvers.promise;
- }
- onClose() {
- this.resolveResult();
- fireAndForget(async () => {
- if (this.mountedComponent) {
- await unmount(this.mountedComponent);
- }
- });
- }
-}
-
-export async function openSvelteDialog(
- plugin: ObsidianLiveSyncPlugin,
- component: ComponentHasResult,
- initialData?: U
-): Promise {
- const dialog = new SvelteDialog(plugin, component, initialData);
- dialog.open();
-
- return await dialog.waitForClose();
-}
-
-export class SvelteDialogManager implements SvelteDialogManagerBase {
- plugin: ObsidianLiveSyncPlugin;
- constructor(plugin: ObsidianLiveSyncPlugin) {
- this.plugin = plugin;
- }
- async open(component: ComponentHasResult, initialData?: U): Promise {
- return await openSvelteDialog(this.plugin, component, initialData);
- }
- async openWithExplicitCancel(component: ComponentHasResult, initialData?: U): Promise {
- for (let i = 0; i < 10; i++) {
- const ret = await openSvelteDialog(this.plugin, component, initialData);
- if (ret !== undefined) {
- return ret;
- }
- if (this.plugin.services.appLifecycle.hasUnloaded()) {
- throw new Error("Operation cancelled due to app shutdown.");
- }
- Logger("Please select 'Cancel' explicitly to cancel this operation.", LOG_LEVEL_NOTICE);
- }
- throw new Error("Operation Forcibly cancelled by user.");
- }
-}
-
-export function getObsidianDialogContext(): DialogControls {
- return getDialogContext() as DialogControls;
-}
diff --git a/src/modules/features/SetupWizard/dialogs/SetupRemoteBucket.svelte b/src/modules/features/SetupWizard/dialogs/SetupRemoteBucket.svelte
index d4f905b..f2270dc 100644
--- a/src/modules/features/SetupWizard/dialogs/SetupRemoteBucket.svelte
+++ b/src/modules/features/SetupWizard/dialogs/SetupRemoteBucket.svelte
@@ -16,8 +16,7 @@
} from "../../../../lib/src/common/types";
import { onMount } from "svelte";
- import type { GuestDialogProps } from "../../../../lib/src/UI/svelteDialog";
- import { getObsidianDialogContext } from "../ObsidianSvelteDialog";
+ import { getDialogContext, type GuestDialogProps } from "../../../../lib/src/UI/svelteDialog";
import { copyTo, pickBucketSyncSettings } from "../../../../lib/src/common/utils";
const default_setting = pickBucketSyncSettings(DEFAULT_SETTINGS);
@@ -39,7 +38,7 @@
}
});
let error = $state("");
- const context = getObsidianDialogContext();
+ const context = getDialogContext();
const isEndpointSecure = $derived.by(() => {
return syncSetting.endpoint.trim().toLowerCase().startsWith("https://");
});
diff --git a/src/modules/features/SetupWizard/dialogs/SetupRemoteCouchDB.svelte b/src/modules/features/SetupWizard/dialogs/SetupRemoteCouchDB.svelte
index 2d7134b..671af71 100644
--- a/src/modules/features/SetupWizard/dialogs/SetupRemoteCouchDB.svelte
+++ b/src/modules/features/SetupWizard/dialogs/SetupRemoteCouchDB.svelte
@@ -17,9 +17,8 @@
} from "../../../../lib/src/common/types";
import { isCloudantURI } from "../../../../lib/src/pouchdb/utils_couchdb";
- import { getObsidianDialogContext } from "../ObsidianSvelteDialog";
import { onMount } from "svelte";
- import type { GuestDialogProps } from "../../../../lib/src/UI/svelteDialog";
+ import { getDialogContext, type GuestDialogProps } from "../../../../lib/src/UI/svelteDialog";
import { copyTo, pickCouchDBSyncSettings } from "../../../../lib/src/common/utils";
import PanelCouchDBCheck from "./PanelCouchDBCheck.svelte";
@@ -40,7 +39,7 @@
});
let error = $state("");
- const context = getObsidianDialogContext();
+ const context = getDialogContext();
function generateSetting() {
const connSetting: CouchDBConnection = {
diff --git a/src/modules/features/SetupWizard/dialogs/SetupRemoteP2P.svelte b/src/modules/features/SetupWizard/dialogs/SetupRemoteP2P.svelte
index 3ab053c..b07c157 100644
--- a/src/modules/features/SetupWizard/dialogs/SetupRemoteP2P.svelte
+++ b/src/modules/features/SetupWizard/dialogs/SetupRemoteP2P.svelte
@@ -22,16 +22,15 @@
import { TrysteroReplicator } from "../../../../lib/src/replication/trystero/TrysteroReplicator";
import type { ReplicatorHostEnv } from "../../../../lib/src/replication/trystero/types";
import { copyTo, pickP2PSyncSettings, type SimpleStore } from "../../../../lib/src/common/utils";
- import { getObsidianDialogContext } from "../ObsidianSvelteDialog";
import { onMount } from "svelte";
- import type { GuestDialogProps } from "../../../../lib/src/UI/svelteDialog";
+ import { getDialogContext, type GuestDialogProps } from "../../../../lib/src/UI/svelteDialog";
import { SETTING_KEY_P2P_DEVICE_NAME } from "../../../../lib/src/common/types";
import ExtraItems from "../../../../lib/src/UI/components/ExtraItems.svelte";
const default_setting = pickP2PSyncSettings(DEFAULT_SETTINGS);
let syncSetting = $state({ ...default_setting });
- const context = getObsidianDialogContext();
+ const context = getDialogContext();
let error = $state("");
const TYPE_CANCELLED = "cancelled";
type SettingInfo = P2PConnectionInfo;
@@ -104,7 +103,7 @@
processReplicatedDocs: async (docs: any[]) => {
return;
},
- confirm: context.plugin.confirm,
+ confirm: context.services.confirm,
db: dummyPouch,
simpleStore: store,
deviceName: syncSetting.P2P_DevicePeerName || "unnamed-device",
diff --git a/src/modules/main/ModuleLiveSyncMain.ts b/src/modules/main/ModuleLiveSyncMain.ts
index 5fa27b3..bc7f95a 100644
--- a/src/modules/main/ModuleLiveSyncMain.ts
+++ b/src/modules/main/ModuleLiveSyncMain.ts
@@ -13,8 +13,8 @@ import { versionNumberString2Number } from "../../lib/src/string_and_binary/conv
import { cancelAllPeriodicTask, cancelAllTasks } from "octagonal-wheels/concurrency/task";
import { stopAllRunningProcessors } from "octagonal-wheels/concurrency/processor";
import { AbstractModule } from "../AbstractModule.ts";
-import { EVENT_PLATFORM_UNLOADED } from "../../lib/src/PlatformAPIs/base/APIBase.ts";
-import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
+import { EVENT_PLATFORM_UNLOADED } from "@lib/events/coreEvents";
+import type { InjectableServiceHub } from "@lib/services/implements/injectable/InjectableServiceHub.ts";
import type { LiveSyncCore } from "../../main.ts";
import { initialiseWorkerModule } from "@/lib/src/worker/bgWorker.ts";
diff --git a/src/modules/services/ObsidianConfirm.ts b/src/modules/services/ObsidianConfirm.ts
new file mode 100644
index 0000000..eb4e3b3
--- /dev/null
+++ b/src/modules/services/ObsidianConfirm.ts
@@ -0,0 +1,111 @@
+import { type App, type Plugin, Notice } from "@/deps";
+import { scheduleTask, memoIfNotExist, memoObject, retrieveMemoObject, disposeMemoObject } from "@/common/utils";
+import { $msg } from "@/lib/src/common/i18n";
+import type { Confirm } from "@/lib/src/interfaces/Confirm";
+import type { ObsidianServiceContext } from "@/lib/src/services/implements/obsidian/ObsidianServiceContext";
+import {
+ askYesNo,
+ askString,
+ confirmWithMessageWithWideButton,
+ askSelectString,
+ confirmWithMessage,
+} from "../coreObsidian/UILib/dialogs";
+
+export class ObsidianConfirm implements Confirm {
+ private _context: T;
+ get _app(): App {
+ return this._context.app;
+ }
+ get _plugin(): Plugin {
+ return this._context.plugin;
+ }
+ constructor(context: T) {
+ this._context = context;
+ }
+ askYesNo(message: string): Promise<"yes" | "no"> {
+ return askYesNo(this._app, message);
+ }
+ askString(title: string, key: string, placeholder: string, isPassword: boolean = false): Promise {
+ return askString(this._app, title, key, placeholder, isPassword);
+ }
+
+ async askYesNoDialog(
+ message: string,
+ opt: { title?: string; defaultOption?: "Yes" | "No"; timeout?: number } = { title: "Confirmation" }
+ ): Promise<"yes" | "no"> {
+ const defaultTitle = $msg("moduleInputUIObsidian.defaultTitleConfirmation");
+ const yesLabel = $msg("moduleInputUIObsidian.optionYes");
+ const noLabel = $msg("moduleInputUIObsidian.optionNo");
+ const defaultOption = opt.defaultOption === "Yes" ? yesLabel : noLabel;
+ const ret = await confirmWithMessageWithWideButton(
+ this._plugin,
+ opt.title || defaultTitle,
+ message,
+ [yesLabel, noLabel],
+ defaultOption,
+ opt.timeout
+ );
+ return ret === yesLabel ? "yes" : "no";
+ }
+
+ askSelectString(message: string, items: string[]): Promise {
+ return askSelectString(this._app, message, items);
+ }
+
+ askSelectStringDialogue(
+ message: string,
+ buttons: T,
+ opt: { title?: string; defaultAction: T[number]; timeout?: number }
+ ): Promise {
+ const defaultTitle = $msg("moduleInputUIObsidian.defaultTitleSelect");
+ return confirmWithMessageWithWideButton(
+ this._plugin,
+ opt.title || defaultTitle,
+ message,
+ buttons,
+ opt.defaultAction,
+ opt.timeout
+ );
+ }
+
+ askInPopup(key: string, dialogText: string, anchorCallback: (anchor: HTMLAnchorElement) => void) {
+ const fragment = createFragment((doc) => {
+ const [beforeText, afterText] = dialogText.split("{HERE}", 2);
+ doc.createEl("span", undefined, (a) => {
+ a.appendText(beforeText);
+ a.appendChild(
+ a.createEl("a", undefined, (anchor) => {
+ anchorCallback(anchor);
+ })
+ );
+ a.appendText(afterText);
+ });
+ });
+ const popupKey = "popup-" + key;
+ scheduleTask(popupKey, 1000, async () => {
+ const popup = await memoIfNotExist(popupKey, () => new Notice(fragment, 0));
+ const isShown = popup?.noticeEl?.isShown();
+ if (!isShown) {
+ memoObject(popupKey, new Notice(fragment, 0));
+ }
+ scheduleTask(popupKey + "-close", 20000, () => {
+ const popup = retrieveMemoObject(popupKey);
+ if (!popup) return;
+ if (popup?.noticeEl?.isShown()) {
+ popup.hide();
+ }
+ disposeMemoObject(popupKey);
+ });
+ });
+ }
+
+ confirmWithMessage(
+ title: string,
+ contentMd: string,
+ buttons: string[],
+ defaultAction: (typeof buttons)[number],
+ timeout?: number
+ ): Promise<(typeof buttons)[number] | false> {
+ return confirmWithMessage(this._plugin, title, contentMd, buttons, defaultAction, timeout);
+ }
+}
diff --git a/src/modules/services/ObsidianServiceHub.ts b/src/modules/services/ObsidianServiceHub.ts
new file mode 100644
index 0000000..c01d747
--- /dev/null
+++ b/src/modules/services/ObsidianServiceHub.ts
@@ -0,0 +1,73 @@
+import { InjectableServiceHub } from "@/lib/src/services/implements/injectable/InjectableServiceHub";
+import { ObsidianServiceContext } from "@/lib/src/services/implements/obsidian/ObsidianServiceContext";
+import type { ServiceInstances } from "@/lib/src/services/ServiceHub";
+import type ObsidianLiveSyncPlugin from "@/main";
+import {
+ ObsidianAPIService,
+ ObsidianAppLifecycleService,
+ ObsidianConflictService,
+ ObsidianDatabaseService,
+ ObsidianFileProcessingService,
+ ObsidianReplicationService,
+ ObsidianReplicatorService,
+ ObsidianRemoteService,
+ ObsidianSettingService,
+ ObsidianTweakValueService,
+ ObsidianVaultService,
+ ObsidianTestService,
+ ObsidianDatabaseEventService,
+ ObsidianPathService,
+ ObsidianConfigService,
+} from "./ObsidianServices";
+import { ObsidianUIService } from "./ObsidianUIService";
+
+// InjectableServiceHub
+
+export class ObsidianServiceHub extends InjectableServiceHub {
+ constructor(plugin: ObsidianLiveSyncPlugin) {
+ const context = new ObsidianServiceContext(plugin.app, plugin, plugin);
+
+ const API = new ObsidianAPIService(context);
+ const appLifecycle = new ObsidianAppLifecycleService(context);
+ const conflict = new ObsidianConflictService(context);
+ const database = new ObsidianDatabaseService(context);
+ const fileProcessing = new ObsidianFileProcessingService(context);
+ const replication = new ObsidianReplicationService(context);
+ const replicator = new ObsidianReplicatorService(context);
+ const remote = new ObsidianRemoteService(context);
+ const setting = new ObsidianSettingService(context);
+ const tweakValue = new ObsidianTweakValueService(context);
+ const vault = new ObsidianVaultService(context);
+ const test = new ObsidianTestService(context);
+ const databaseEvents = new ObsidianDatabaseEventService(context);
+ const path = new ObsidianPathService(context);
+ const config = new ObsidianConfigService(context, vault);
+ const ui = new ObsidianUIService(context, {
+ appLifecycle,
+ config,
+ replicator,
+ });
+
+ // Using 'satisfies' to ensure all services are provided
+ const serviceInstancesToInit = {
+ appLifecycle: appLifecycle,
+ conflict: conflict,
+ database: database,
+ databaseEvents: databaseEvents,
+ fileProcessing: fileProcessing,
+ replication: replication,
+ replicator: replicator,
+ remote: remote,
+ setting: setting,
+ tweakValue: tweakValue,
+ vault: vault,
+ test: test,
+ ui: ui,
+ path: path,
+ API: API,
+ config: config,
+ } satisfies Required>;
+
+ super(context, serviceInstancesToInit);
+ }
+}
diff --git a/src/modules/services/ObsidianServices.ts b/src/modules/services/ObsidianServices.ts
index 7acd888..e4b05b7 100644
--- a/src/modules/services/ObsidianServices.ts
+++ b/src/modules/services/ObsidianServices.ts
@@ -1,48 +1,104 @@
-import { ServiceContext, type ServiceInstances } from "@/lib/src/services/ServiceHub.ts";
-import {
- InjectableAPIService,
- InjectableAppLifecycleService,
- InjectableConflictService,
- InjectableDatabaseEventService,
- InjectableDatabaseService,
- InjectableFileProcessingService,
- InjectablePathService,
- InjectableRemoteService,
- InjectableReplicationService,
- InjectableReplicatorService,
- InjectableSettingService,
- InjectableTestService,
- InjectableTweakValueService,
- InjectableVaultService,
-} from "../../lib/src/services/InjectableServices.ts";
-import { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
-import { ConfigServiceBrowserCompat } from "../../lib/src/services/Services.ts";
-import type ObsidianLiveSyncPlugin from "../../main.ts";
-import { ObsidianUIService } from "./ObsidianUIService.ts";
-import type { App, Plugin } from "@/deps";
-
-export class ObsidianServiceContext extends ServiceContext {
- app: App;
- plugin: Plugin;
- liveSyncPlugin: ObsidianLiveSyncPlugin;
- constructor(app: App, plugin: Plugin, liveSyncPlugin: ObsidianLiveSyncPlugin) {
- super();
- this.app = app;
- this.plugin = plugin;
- this.liveSyncPlugin = liveSyncPlugin;
- }
-}
+import { InjectableAPIService } from "@lib/services/implements/injectable/InjectableAPIService";
+import { InjectableAppLifecycleService } from "@lib/services/implements/injectable/InjectableAppLifecycleService";
+import { InjectableConflictService } from "@lib/services/implements/injectable/InjectableConflictService";
+import { InjectableDatabaseEventService } from "@lib/services/implements/injectable/InjectableDatabaseEventService";
+import { InjectableDatabaseService } from "@lib/services/implements/injectable/InjectableDatabaseService";
+import { InjectableFileProcessingService } from "@lib/services/implements/injectable/InjectableFileProcessingService";
+import { InjectablePathService } from "@lib/services/implements/injectable/InjectablePathService";
+import { InjectableRemoteService } from "@lib/services/implements/injectable/InjectableRemoteService";
+import { InjectableReplicationService } from "@lib/services/implements/injectable/InjectableReplicationService";
+import { InjectableReplicatorService } from "@lib/services/implements/injectable/InjectableReplicatorService";
+import { InjectableSettingService } from "@lib/services/implements/injectable/InjectableSettingService";
+import { InjectableTestService } from "@lib/services/implements/injectable/InjectableTestService";
+import { InjectableTweakValueService } from "@lib/services/implements/injectable/InjectableTweakValueService";
+import { InjectableVaultService } from "@lib/services/implements/injectable/InjectableVaultService";
+import { ConfigServiceBrowserCompat } from "@lib/services/implements/browser/ConfigServiceBrowserCompat";
+import type { ObsidianServiceContext } from "@lib/services/implements/obsidian/ObsidianServiceContext.ts";
+import { Platform } from "@/deps";
+import type { SimpleStore } from "@/lib/src/common/utils";
+import type { IDatabaseService } from "@/lib/src/services/base/IService";
+import { handlers } from "@/lib/src/services/lib/HandlerUtils";
+import { ObsHttpHandler } from "../essentialObsidian/APILib/ObsHttpHandler";
// All Services will be migrated to be based on Plain Services, not Injectable Services.
// This is a migration step.
export class ObsidianAPIService extends InjectableAPIService {
+ _customHandler: ObsHttpHandler | undefined;
+ getCustomFetchHandler(): ObsHttpHandler {
+ if (!this._customHandler) this._customHandler = new ObsHttpHandler(undefined, undefined);
+ return this._customHandler;
+ }
+
+ async showWindow(viewType: string): Promise {
+ const leaves = this.app.workspace.getLeavesOfType(viewType);
+ if (leaves.length == 0) {
+ await this.app.workspace.getLeaf(true).setViewState({
+ type: viewType,
+ active: true,
+ });
+ } else {
+ await leaves[0].setViewState({
+ type: viewType,
+ active: true,
+ });
+ }
+ if (leaves.length > 0) {
+ await this.app.workspace.revealLeaf(leaves[0]);
+ }
+ }
+
+ private get app() {
+ return this.context.app;
+ }
+
getPlatform(): string {
- return "obsidian";
+ if (Platform.isAndroidApp) {
+ return "android-app";
+ } else if (Platform.isIosApp) {
+ return "ios";
+ } else if (Platform.isMacOS) {
+ return "macos";
+ } else if (Platform.isMobileApp) {
+ return "mobile-app";
+ } else if (Platform.isMobile) {
+ return "mobile";
+ } else if (Platform.isSafari) {
+ return "safari";
+ } else if (Platform.isDesktop) {
+ return "desktop";
+ } else if (Platform.isDesktopApp) {
+ return "desktop-app";
+ } else {
+ return "unknown-obsidian";
+ }
+ }
+ override isMobile(): boolean {
+ //@ts-ignore : internal API
+ return this.app.isMobile;
+ }
+ override getAppID(): string {
+ return `${"appId" in this.app ? this.app.appId : ""}`;
+ }
+ override getAppVersion(): string {
+ const navigatorString = globalThis.navigator?.userAgent ?? "";
+ const match = navigatorString.match(/obsidian\/([0-9]+\.[0-9]+\.[0-9]+)/);
+ if (match && match.length >= 2) {
+ return match[1];
+ }
+ return "0.0.0";
+ }
+
+ override getPluginVersion(): string {
+ return this.context.plugin.manifest.version;
}
}
export class ObsidianPathService extends InjectablePathService {}
-export class ObsidianDatabaseService extends InjectableDatabaseService {}
+export class ObsidianDatabaseService extends InjectableDatabaseService {
+ openSimpleStore = handlers().binder("openSimpleStore") as ((
+ kind: string
+ ) => SimpleStore) & { setHandler: (handler: IDatabaseService["openSimpleStore"], override?: boolean) => void };
+}
export class ObsidianDatabaseEventService extends InjectableDatabaseEventService {}
// InjectableReplicatorService
@@ -66,49 +122,3 @@ export class ObsidianVaultService extends InjectableVaultService {}
export class ObsidianConfigService extends ConfigServiceBrowserCompat {}
-
-// InjectableServiceHub
-
-export class ObsidianServiceHub extends InjectableServiceHub {
- constructor(plugin: ObsidianLiveSyncPlugin) {
- const context = new ObsidianServiceContext(plugin.app, plugin, plugin);
-
- const API = new ObsidianAPIService(context);
- const appLifecycle = new ObsidianAppLifecycleService(context);
- const conflict = new ObsidianConflictService(context);
- const database = new ObsidianDatabaseService(context);
- const fileProcessing = new ObsidianFileProcessingService(context);
- const replication = new ObsidianReplicationService(context);
- const replicator = new ObsidianReplicatorService(context);
- const remote = new ObsidianRemoteService(context);
- const setting = new ObsidianSettingService(context);
- const tweakValue = new ObsidianTweakValueService(context);
- const vault = new ObsidianVaultService(context);
- const test = new ObsidianTestService(context);
- const databaseEvents = new ObsidianDatabaseEventService(context);
- const path = new ObsidianPathService(context);
- const ui = new ObsidianUIService(context);
- const config = new ObsidianConfigService(context, vault);
- // Using 'satisfies' to ensure all services are provided
- const serviceInstancesToInit = {
- appLifecycle: appLifecycle,
- conflict: conflict,
- database: database,
- databaseEvents: databaseEvents,
- fileProcessing: fileProcessing,
- replication: replication,
- replicator: replicator,
- remote: remote,
- setting: setting,
- tweakValue: tweakValue,
- vault: vault,
- test: test,
- ui: ui,
- path: path,
- API: API,
- config: config,
- } satisfies Required>;
-
- super(context, serviceInstancesToInit);
- }
-}
diff --git a/src/modules/services/ObsidianUIService.ts b/src/modules/services/ObsidianUIService.ts
index e4df15e..7c0bd43 100644
--- a/src/modules/services/ObsidianUIService.ts
+++ b/src/modules/services/ObsidianUIService.ts
@@ -1,156 +1,30 @@
-import { UIService } from "../../lib/src/services/Services";
-import { Notice, type App, type Plugin } from "@/deps";
-import { SvelteDialogManager } from "../features/SetupWizard/ObsidianSvelteDialog";
-import DialogueToCopy from "../../lib/src/UI/dialogues/DialogueToCopy.svelte";
-import type { ObsidianServiceContext } from "./ObsidianServices";
-import type ObsidianLiveSyncPlugin from "@/main";
-import type { Confirm } from "@/lib/src/interfaces/Confirm";
-import {
- askSelectString,
- askString,
- askYesNo,
- confirmWithMessage,
- confirmWithMessageWithWideButton,
-} from "../coreObsidian/UILib/dialogs";
-import { $msg } from "@/lib/src/common/i18n";
-import { disposeMemoObject, memoIfNotExist, memoObject, retrieveMemoObject, scheduleTask } from "@/common/utils";
-export class ObsidianConfirm implements Confirm {
- private _app: App;
- private _plugin: Plugin;
- constructor(app: App, plugin: Plugin) {
- this._app = app;
- this._plugin = plugin;
- }
- askYesNo(message: string): Promise<"yes" | "no"> {
- return askYesNo(this._app, message);
- }
- askString(title: string, key: string, placeholder: string, isPassword: boolean = false): Promise {
- return askString(this._app, title, key, placeholder, isPassword);
- }
+import type { ConfigService } from "@lib/services/base/ConfigService";
+import type { AppLifecycleService } from "@lib/services/base/AppLifecycleService";
+import type { ReplicatorService } from "@lib/services/base/ReplicatorService";
+import { UIService } from "@lib/services//implements/base/UIService";
+import { ObsidianServiceContext } from "@/lib/src/services/implements/obsidian/ObsidianServiceContext";
+import { ObsidianSvelteDialogManager } from "./SvelteDialogObsidian";
+import { ObsidianConfirm } from "./ObsidianConfirm";
- async askYesNoDialog(
- message: string,
- opt: { title?: string; defaultOption?: "Yes" | "No"; timeout?: number } = { title: "Confirmation" }
- ): Promise<"yes" | "no"> {
- const defaultTitle = $msg("moduleInputUIObsidian.defaultTitleConfirmation");
- const yesLabel = $msg("moduleInputUIObsidian.optionYes");
- const noLabel = $msg("moduleInputUIObsidian.optionNo");
- const defaultOption = opt.defaultOption === "Yes" ? yesLabel : noLabel;
- const ret = await confirmWithMessageWithWideButton(
- this._plugin,
- opt.title || defaultTitle,
- message,
- [yesLabel, noLabel],
- defaultOption,
- opt.timeout
- );
- return ret === yesLabel ? "yes" : "no";
- }
+export type ObsidianUIServiceDependencies = {
+ appLifecycle: AppLifecycleService;
+ config: ConfigService;
+ replicator: ReplicatorService;
+};
- askSelectString(message: string, items: string[]): Promise {
- return askSelectString(this._app, message, items);
- }
-
- askSelectStringDialogue(
- message: string,
- buttons: T,
- opt: { title?: string; defaultAction: T[number]; timeout?: number }
- ): Promise {
- const defaultTitle = $msg("moduleInputUIObsidian.defaultTitleSelect");
- return confirmWithMessageWithWideButton(
- this._plugin,
- opt.title || defaultTitle,
- message,
- buttons,
- opt.defaultAction,
- opt.timeout
- );
- }
-
- askInPopup(key: string, dialogText: string, anchorCallback: (anchor: HTMLAnchorElement) => void) {
- const fragment = createFragment((doc) => {
- const [beforeText, afterText] = dialogText.split("{HERE}", 2);
- doc.createEl("span", undefined, (a) => {
- a.appendText(beforeText);
- a.appendChild(
- a.createEl("a", undefined, (anchor) => {
- anchorCallback(anchor);
- })
- );
- a.appendText(afterText);
- });
- });
- const popupKey = "popup-" + key;
- scheduleTask(popupKey, 1000, async () => {
- const popup = await memoIfNotExist(popupKey, () => new Notice(fragment, 0));
- const isShown = popup?.noticeEl?.isShown();
- if (!isShown) {
- memoObject(popupKey, new Notice(fragment, 0));
- }
- scheduleTask(popupKey + "-close", 20000, () => {
- const popup = retrieveMemoObject(popupKey);
- if (!popup) return;
- if (popup?.noticeEl?.isShown()) {
- popup.hide();
- }
- disposeMemoObject(popupKey);
- });
- });
- }
-
- confirmWithMessage(
- title: string,
- contentMd: string,
- buttons: string[],
- defaultAction: (typeof buttons)[number],
- timeout?: number
- ): Promise<(typeof buttons)[number] | false> {
- return confirmWithMessage(this._plugin, title, contentMd, buttons, defaultAction, timeout);
- }
-}
export class ObsidianUIService extends UIService {
- private _dialogManager: SvelteDialogManager;
- private _plugin: Plugin;
- private _liveSyncPlugin: ObsidianLiveSyncPlugin;
- private _confirmInstance: ObsidianConfirm;
- get dialogManager() {
- return this._dialogManager;
- }
- constructor(context: ObsidianServiceContext) {
- super(context);
- this._liveSyncPlugin = context.liveSyncPlugin;
- this._dialogManager = new SvelteDialogManager(this._liveSyncPlugin);
- this._plugin = context.plugin;
- this._confirmInstance = new ObsidianConfirm(this._plugin.app, this._plugin);
- }
-
- async promptCopyToClipboard(title: string, value: string): Promise {
- const param = {
- title: title,
- dataToCopy: value,
- };
- const result = await this._dialogManager.open(DialogueToCopy, param);
- if (result !== "ok") {
- return false;
- }
- return true;
- }
-
- showMarkdownDialog(
- title: string,
- contentMD: string,
- buttons: T,
- defaultAction?: (typeof buttons)[number]
- ): Promise<(typeof buttons)[number] | false> {
- // TODO: implement `confirm` to this service
- return this._liveSyncPlugin.confirm.askSelectStringDialogue(contentMD, buttons, {
- title,
- defaultAction: defaultAction ?? buttons[0],
- timeout: 0,
+ constructor(context: ObsidianServiceContext, dependents: ObsidianUIServiceDependencies) {
+ const obsidianConfirm = new ObsidianConfirm(context);
+ const obsidianSvelteDialogManager = new ObsidianSvelteDialogManager(context, {
+ appLifecycle: dependents.appLifecycle,
+ config: dependents.config,
+ replicator: dependents.replicator,
+ confirm: obsidianConfirm,
+ });
+ super(context, {
+ appLifecycle: dependents.appLifecycle,
+ dialogManager: obsidianSvelteDialogManager,
+ confirm: obsidianConfirm,
});
}
-
- get confirm(): Confirm {
- return this._confirmInstance;
- }
}
diff --git a/src/modules/services/SvelteDialogObsidian.ts b/src/modules/services/SvelteDialogObsidian.ts
new file mode 100644
index 0000000..c57d8f1
--- /dev/null
+++ b/src/modules/services/SvelteDialogObsidian.ts
@@ -0,0 +1,37 @@
+import { Modal } from "@/deps";
+
+import {
+ SvelteDialogManagerBase,
+ SvelteDialogMixIn,
+ type ComponentHasResult,
+ type SvelteDialogManagerDependencies,
+} from "@lib/services/implements/base/SvelteDialog";
+import type { ObsidianServiceContext } from "@lib/services/implements/obsidian/ObsidianServiceContext";
+
+export const SvelteDialogBase = SvelteDialogMixIn(Modal);
+export class SvelteDialogObsidian<
+ T,
+ U,
+ C extends ObsidianServiceContext = ObsidianServiceContext,
+> extends SvelteDialogBase {
+ constructor(
+ context: C,
+ dependents: SvelteDialogManagerDependencies,
+ component: ComponentHasResult,
+ initialData?: U
+ ) {
+ super(context.app);
+ this.initDialog(context, dependents, component, initialData);
+ }
+}
+
+export class ObsidianSvelteDialogManager extends SvelteDialogManagerBase {
+ override async openSvelteDialog(
+ component: ComponentHasResult,
+ initialData?: TU
+ ): Promise {
+ const dialog = new SvelteDialogObsidian(this.context, this.dependents, component, initialData);
+ dialog.open();
+ return await dialog.waitForClose();
+ }
+}
diff --git a/test/harness/harness.ts b/test/harness/harness.ts
index 329e532..390934f 100644
--- a/test/harness/harness.ts
+++ b/test/harness/harness.ts
@@ -3,9 +3,10 @@ import ObsidianLiveSyncPlugin from "@/main";
import { DEFAULT_SETTINGS, type ObsidianLiveSyncSettings } from "@/lib/src/common/types";
import { LOG_LEVEL_VERBOSE, setGlobalLogFunction } from "@lib/common/logger";
import { SettingCache } from "./obsidian-mock";
-import { delay, promiseWithResolvers } from "octagonal-wheels/promises";
+import { delay, fireAndForget, promiseWithResolvers } from "octagonal-wheels/promises";
+import { EVENT_PLATFORM_UNLOADED } from "@lib/events/coreEvents";
import { EVENT_LAYOUT_READY, eventHub } from "@/common/events";
-import { EVENT_PLATFORM_UNLOADED } from "@/lib/src/PlatformAPIs/base/APIBase";
+
import { env } from "../suite/variables";
export type LiveSyncHarness = {
@@ -79,12 +80,14 @@ export async function generateHarness(
await plugin.onload();
let isDisposed = false;
const waitPromise = promiseWithResolvers();
- eventHub.once(EVENT_PLATFORM_UNLOADED, async () => {
- console.log(`Harness for vault '${vaultName}' disposed.`);
- await delay(100);
- eventHub.offAll();
- isDisposed = true;
- waitPromise.resolve();
+ eventHub.once(EVENT_PLATFORM_UNLOADED, () => {
+ fireAndForget(async () => {
+ console.log(`Harness for vault '${vaultName}' disposed.`);
+ await delay(100);
+ eventHub.offAll();
+ isDisposed = true;
+ waitPromise.resolve();
+ });
});
eventHub.once(EVENT_LAYOUT_READY, () => {
plugin.app.vault.trigger("layout-ready");
diff --git a/test/shell/p2p-start.sh b/test/shell/p2p-start.sh
index f875a61..b4218e0 100755
--- a/test/shell/p2p-start.sh
+++ b/test/shell/p2p-start.sh
@@ -1,7 +1,7 @@
#!/bin/bash
set -e
script_dir=$(dirname "$0")
-webpeer_dir=$script_dir/../../src/lib/apps/webpeer
+webpeer_dir=$script_dir/../../src/apps/webpeer
docker run -d --name relay-test -p 4000:8080 scsibug/nostr-rs-relay:latest
npm run --prefix $webpeer_dir build
diff --git a/tsconfig.json b/tsconfig.json
index 15968e6..1ee9b7a 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -24,12 +24,5 @@
}
},
"include": ["**/*.ts", "test/**/*.test.ts"],
- "exclude": [
- "pouchdb-browser-webpack",
- "utils",
- "src/lib/apps",
- "src/**/*.test.ts",
- "**/_test/**",
- "src/**/*.test.ts"
- ]
+ "exclude": ["pouchdb-browser-webpack", "utils", "src/apps", "src/**/*.test.ts", "**/_test/**", "src/**/*.test.ts"]
}
diff --git a/updates.md b/updates.md
index bf640d9..d1024ba 100644
--- a/updates.md
+++ b/updates.md
@@ -3,6 +3,21 @@ Since 19th July, 2025 (beta1 in 0.25.0-beta1, 13th July, 2025)
The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsidian-livesync/blob/main/updates_old.md). Because 0.25 got a lot of updates, thankfully, compatibility is kept and we do not need breaking changes! In other words, when get enough stabled. The next version will be v1.0.0. Even though it my hope.
+## 0.25.42
+
+2nd, February, 2026
+
+This release is identical to 0.25.41-patched-3, except for the version number.
+
+### Refactored
+
+- Now the service context is `protected` instead of `private` in `ServiceBase`.
+ - This change allows derived classes to access the context directly.
+- Some dynamically bound services have been moved to services for better dependency management.
+- `WebPeer` has been moved to the main repository from the sub repository `livesync-commonlib` for correct dependency management.
+- Migrated from the outdated, unstable platform abstraction layer to services.
+ - A bit more services will be added in the future for better maintainability.
+
## 0.25.41
24th January, 2026
@@ -18,16 +33,18 @@ The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsid
### New feature
- We can now set the maximum modified time for reflect events in the settings. (for #754)
- - This setting can be configured from `Patches` -> `Remediation` in the settings dialogue.
+ - This setting can be configured from `Patches` -> `Remediation` in the settings dialogue.
- Enabling this setting will restrict the propagation from the database to storage to only those changes made before the specified date and time.
- This feature is primarily intended for recovery purposes. After placing `redflag.md` in an empty vault and importing the Self-hosted LiveSync configuration, please perform this configuration, and then fetch the local database from the remote.
- This feature is useful when we want to prevent recent unwanted changes from being reflected in the local storage.
### Refactored
+
- Module to service refactoring has been started for better maintainability:
- - UI module has been moved to UI service.
+ - UI module has been moved to UI service.
### Behaviour change
+
- Default chunk splitter version has been changed to `Rabin-Karp` for new installations.
## 0.25.40
diff --git a/vite.config.ts b/vite.config.ts
index 6150f9a..85ea529 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,3 +1,4 @@
-import { defineConfig } from "vite";
+import { defineConfig, mergeConfig } from "vitest/config";
+import viteConfig from "./vitest.config.common";
-export default defineConfig({});
+export default mergeConfig(viteConfig, defineConfig({}));