mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-06-05 01:53:04 +03:00
Compare commits
17 Commits
fix_warns
...
0.25.70-pa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb77426b7b | ||
|
|
1bef5fbef3 | ||
|
|
7d2ba1b0b9 | ||
|
|
ac6b9a4dad | ||
|
|
225e2c5096 | ||
|
|
674d68b7d9 | ||
|
|
0e6dd300ef | ||
|
|
8171db353a | ||
|
|
6ab1556880 | ||
|
|
cd2bff5fc7 | ||
|
|
5a280c7919 | ||
|
|
c6697327d5 | ||
|
|
7c203a522a | ||
|
|
c80c294d93 | ||
|
|
3e65ae932d | ||
|
|
f710f03380 | ||
|
|
b887269fc1 |
@@ -3,7 +3,8 @@ import obsidianmd from "eslint-plugin-obsidianmd";
|
||||
import globals from "globals";
|
||||
import { defineConfig, globalIgnores } from "eslint/config";
|
||||
import * as sveltePlugin from "eslint-plugin-svelte";
|
||||
|
||||
import svelteParser from "svelte-eslint-parser";
|
||||
const warnWhileDev = "off"; // Change to "warn" to enable warnings for rules that are currently disabled.
|
||||
export default defineConfig([
|
||||
globalIgnores([
|
||||
// Build outputs and legacy files
|
||||
@@ -55,49 +56,79 @@ export default defineConfig([
|
||||
...obsidianmd.configs.recommended,
|
||||
{
|
||||
files: ["**/*.ts"],
|
||||
// ignores:["src/lib/**/*.ts"], // Exclude library files from root linting (they have different environments and rules).
|
||||
languageOptions: {
|
||||
globals: { ...globals.browser, "PouchDB": "readonly" },
|
||||
globals: { ...globals.browser, PouchDB: "readonly" },
|
||||
parser: tsParser,
|
||||
parserOptions: {
|
||||
project: "./tsconfig.json",
|
||||
},
|
||||
},
|
||||
linterOptions:{
|
||||
reportUnusedDisableDirectives: false,
|
||||
},
|
||||
rules: {
|
||||
// Base rules (turned off in favour of TS specific versions or explicitly disabled).
|
||||
// -- Base rules (turned off in favour of TS specific versions or explicitly disabled).
|
||||
"no-unused-vars": "off",
|
||||
"no-unused-labels": "off",
|
||||
"no-prototype-builtins": "off",
|
||||
"require-await": "off",
|
||||
// TypeScript specific rules
|
||||
"@typescript-eslint/no-deprecated": "warn",
|
||||
// -- TypeScript specific rules
|
||||
// @typescript-eslint/no-unsafe-* rules and @typescript-eslint/no-explicit-any:
|
||||
// This project contains a lot of library-sh code where the use of `any` is often necessary and justified.
|
||||
// Rules is now set to 'off' for a while.
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-unsafe-argument": "off",
|
||||
"@typescript-eslint/no-unsafe-call": "off",
|
||||
"@typescript-eslint/no-unsafe-member-access": "off",
|
||||
"@typescript-eslint/no-unsafe-return": "off",
|
||||
"@typescript-eslint/no-unsafe-assignment": "off",
|
||||
// -- Reasonable rules.
|
||||
"@typescript-eslint/no-deprecated": warnWhileDev,
|
||||
"@typescript-eslint/no-unused-vars": ["error", { args: "none" }],
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/require-await": "warn",
|
||||
"@typescript-eslint/no-misused-promises": "warn",
|
||||
"@typescript-eslint/no-floating-promises": "warn",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/require-await": "error",
|
||||
"@typescript-eslint/no-misused-promises": "error",
|
||||
"@typescript-eslint/no-floating-promises": "error",
|
||||
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
||||
|
||||
// General rules
|
||||
"no-async-promise-executor": "warn",
|
||||
"no-constant-condition": ["error", { checkLoops: false }],
|
||||
// -- Obsidian rules
|
||||
// obsidianmd/no-unsupported-api: usually this project checks for API support at runtime, so this rule is not critical but can be helpful to catch potential issues.
|
||||
"obsidianmd/no-unsupported-api": warnWhileDev,
|
||||
|
||||
// Plugin specific overrides (Pending review)
|
||||
// -- General rules
|
||||
"no-async-promise-executor": warnWhileDev,
|
||||
"no-constant-condition": ["error", { checkLoops: false }],
|
||||
// -- Disabled rules
|
||||
// no-undef: This option breaks the global declarations for the library files and is not worth the effort to fix at this time.
|
||||
"no-undef": "off",
|
||||
|
||||
// -- Plugin specific overrides
|
||||
"obsidianmd/rule-custom-message": "off",
|
||||
"obsidianmd/ui/sentence-case": "off",
|
||||
"obsidianmd/no-plugin-as-component": "off",
|
||||
|
||||
// -- Temporary overrides for migration
|
||||
"obsidianmd/no-static-styles-assignment": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["**/*.svelte"],
|
||||
languageOptions: {
|
||||
parser: svelteParser,
|
||||
parserOptions: {
|
||||
parser: tsParser,
|
||||
extraFileExtensions: [".svelte"],
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
"no-unused-vars": ["error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],
|
||||
// no-unused-vars:
|
||||
// Svelte template's declarations have a lot of false positives and the rule is not worth the effort to fix at this time.
|
||||
// it may improve in the future with some options as like ["error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],]
|
||||
"no-unused-vars": "off",
|
||||
"obsidianmd/no-plugin-as-component": "off",
|
||||
"obsidianmd/ui/sentence-case": "off",
|
||||
},
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-livesync",
|
||||
"name": "Self-hosted LiveSync",
|
||||
"version": "0.25.70",
|
||||
"version": "0.25.70-patch3",
|
||||
"minAppVersion": "1.7.2",
|
||||
"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",
|
||||
|
||||
331
package-lock.json
generated
331
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "obsidian-livesync",
|
||||
"version": "0.25.70",
|
||||
"version": "0.25.70-patch3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "obsidian-livesync",
|
||||
"version": "0.25.70",
|
||||
"version": "0.25.70-patch3",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.808.0",
|
||||
@@ -15,6 +15,7 @@
|
||||
"@smithy/middleware-apply-body-checksum": "^4.3.9",
|
||||
"@smithy/protocol-http": "^5.3.9",
|
||||
"@smithy/querystring-builder": "^4.2.9",
|
||||
"@smithy/util-retry": "^4.4.5",
|
||||
"@trystero-p2p/nostr": "^0.24.0",
|
||||
"chokidar": "^4.0.0",
|
||||
"commander": "^14.0.3",
|
||||
@@ -51,9 +52,9 @@
|
||||
"@types/transform-pouch": "^1.0.6",
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"@vitest/browser": "^4.1.1",
|
||||
"@vitest/browser-playwright": "^4.1.1",
|
||||
"@vitest/coverage-v8": "^4.1.1",
|
||||
"@vitest/browser": "^4.1.8",
|
||||
"@vitest/browser-playwright": "^4.1.8",
|
||||
"@vitest/coverage-v8": "^4.1.8",
|
||||
"dotenv-cli": "^11.0.0",
|
||||
"esbuild": "0.25.0",
|
||||
"esbuild-plugin-inline-worker": "^0.1.1",
|
||||
@@ -90,7 +91,7 @@
|
||||
"typescript": "5.9.3",
|
||||
"vite": "^7.3.1",
|
||||
"vite-plugin-istanbul": "^8.0.0",
|
||||
"vitest": "^4.1.1",
|
||||
"vitest": "^4.1.8",
|
||||
"webdriverio": "^9.27.0",
|
||||
"yaml": "^2.8.2"
|
||||
}
|
||||
@@ -1851,9 +1852,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@eslint/config-array/node_modules/brace-expansion": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
||||
"version": "1.1.15",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
|
||||
"integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1932,9 +1933,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
||||
"version": "1.1.15",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
|
||||
"integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -3546,13 +3547,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@smithy/core": {
|
||||
"version": "3.24.3",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.24.3.tgz",
|
||||
"integrity": "sha512-Ep/7tPamGY8mgESE3LyLKtxJyy6U52WWAqr/3wial47Sj4u3PiIF73AOGI27UyLy9duTkhZbgzodOfLV4TduZg==",
|
||||
"version": "3.24.6",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.24.6.tgz",
|
||||
"integrity": "sha512-wBXDRup6UU97VKyaiRo8AssnfStPtG0oAAfpq/bC0a1YYau8pM86YB4kM6ccoVi1mS8l/UHbn9oDM+7uozr/ug==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-crypto/crc32": "5.2.0",
|
||||
"@smithy/types": "^4.14.2",
|
||||
"@smithy/types": "^4.14.3",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3719,12 +3720,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@smithy/is-array-buffer": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.3.3.tgz",
|
||||
"integrity": "sha512-RRxYqjUa/n8dRVkbhyuiRarppLzt4H/AtMUEFmiHlDy8o4wrgqAdzxsk9naemzu6iX67ZV375fNmX7Q8dynGKw==",
|
||||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.3.6.tgz",
|
||||
"integrity": "sha512-/cSYHP8jPffkhBClQzH9fAJujIh8dwMwg2swrVF4stXQsUWO5Oi2bwyaMUcBPIyulUI5IxaJFxd9C8UQX+YZsQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@smithy/core": "^3.24.3",
|
||||
"@smithy/core": "^3.24.6",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3988,9 +3989,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@smithy/types": {
|
||||
"version": "4.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.2.tgz",
|
||||
"integrity": "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw==",
|
||||
"version": "4.14.3",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.3.tgz",
|
||||
"integrity": "sha512-YupL0ZWmFtJexUN2cHzkvvF/b9pKrtAIfT1o7/oY/Ppu8IYeZ+lDPM5vZdQJaSeA132dJCqojjGC9NhXeF71VQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.6.2"
|
||||
@@ -4052,12 +4053,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@smithy/util-buffer-from": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.3.3.tgz",
|
||||
"integrity": "sha512-5xlgilVaX96HdVlLZymKUa7vOTZtisOTxBJloM2J4PeRqyAWBeFIq0DnIxQISvwxT4rgJAvk7rHhB+GlCCKe8g==",
|
||||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.3.6.tgz",
|
||||
"integrity": "sha512-sms/ty2CJwHOiGzEaAVWizTVK5KusXpAYqCUeXIa+hWtNKLwjimH4z11mc07d0Fe3DT3lmZJIZWOMcVQ/N4hBQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@smithy/core": "^3.24.3",
|
||||
"@smithy/core": "^3.24.6",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
@@ -4149,13 +4150,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@smithy/util-retry": {
|
||||
"version": "4.2.12",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.12.tgz",
|
||||
"integrity": "sha512-1zopLDUEOwumjcHdJ1mwBHddubYF8GMQvstVCLC54Y46rqoHwlIU+8ZzUeaBcD+WCJHyDGSeZ2ml9YSe9aqcoQ==",
|
||||
"version": "4.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.4.5.tgz",
|
||||
"integrity": "sha512-W9Ovy9i02yGqtLlpqZNQuXNxXc5OPfXujnembxN/FxyBtGjJd8vKY0PQYEJ8FNybTOcXG+ZxsSsX23HOb3zQzg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@smithy/service-error-classification": "^4.2.12",
|
||||
"@smithy/types": "^4.13.1",
|
||||
"@smithy/core": "^3.24.5",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
@@ -4194,12 +4194,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@smithy/util-utf8": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.3.3.tgz",
|
||||
"integrity": "sha512-c1QpRBn3aMsoqE64dd4Imgjy8Pynfw+eR7GkjElquxUFSnezwYVaOFm8JcYa+Bo/5ssbEyPKcT3+4bmrWYh6eQ==",
|
||||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.3.6.tgz",
|
||||
"integrity": "sha512-tAa4sePYB7mlJzdYbdBqdv37KwFKWixmM/r3ihcI0HFOVjf+a5oGvtcLXcGm4S1bY4DFsLAIOHgjubtp+oRufw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@smithy/core": "^3.24.3",
|
||||
"@smithy/core": "^3.24.6",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
@@ -4994,47 +4994,46 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/browser": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.1.1.tgz",
|
||||
"integrity": "sha512-gjjrFC4+kPVK/fN9URDJWrssU5Gqh8Az8pKG/NSfQ2V+ky8b/y1BgBg0Ug13+hOGp5pzInonmGRPn7vOgSLgzA==",
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.1.8.tgz",
|
||||
"integrity": "sha512-u21VzX07HzlJYpFgkxmjEXar/tG2UqWGgyGG/46SrrPc7rSdCTPw5vuowopO9CIqF8UCUQzDFdbVnNpw6N0BfQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@blazediff/core": "1.9.1",
|
||||
"@vitest/mocker": "4.1.1",
|
||||
"@vitest/utils": "4.1.1",
|
||||
"@vitest/mocker": "4.1.8",
|
||||
"@vitest/utils": "4.1.8",
|
||||
"magic-string": "^0.30.21",
|
||||
"pngjs": "^7.0.0",
|
||||
"sirv": "^3.0.2",
|
||||
"tinyrainbow": "^3.0.3",
|
||||
"tinyrainbow": "^3.1.0",
|
||||
"ws": "^8.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vitest": "4.1.1"
|
||||
"vitest": "4.1.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/browser-playwright": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.1.1.tgz",
|
||||
"integrity": "sha512-dtVSBZZha2k/7P7EAXXrEAoxuIKl8Yv9f2Dk4GN/DGfmhf4DQvkvu+57okR2wq/gan1xppKjL/aBxK/kbYrbGw==",
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.1.8.tgz",
|
||||
"integrity": "sha512-SR7FqgegaexEg73xvf3ArtygXegagMdXnL0EZMpxrWvvhQxvicD/E8p0ib0J91riPRtQUViyh67Xjw3NqvyhVg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vitest/browser": "4.1.1",
|
||||
"@vitest/mocker": "4.1.1",
|
||||
"tinyrainbow": "^3.0.3"
|
||||
"@vitest/browser": "4.1.8",
|
||||
"@vitest/mocker": "4.1.8",
|
||||
"tinyrainbow": "^3.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"playwright": "*",
|
||||
"vitest": "4.1.1"
|
||||
"vitest": "4.1.8"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"playwright": {
|
||||
@@ -5043,14 +5042,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/coverage-v8": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.1.tgz",
|
||||
"integrity": "sha512-nZ4RWwGCoGOQRMmU/Q9wlUY540RVRxJZ9lxFsFfy0QV7Zmo5VVBhB6Sl9Xa0KIp2iIs3zWfPlo9LcY1iqbpzCw==",
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.8.tgz",
|
||||
"integrity": "sha512-lt3kovsyHwYe00wq4D1ti0Z974fWj4NLp6siqiyEufUpyFwK9Yhi7rBhac9JL5aA0zoMrJqc4vYPZRUnI7l7nw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@bcoe/v8-coverage": "^1.0.2",
|
||||
"@vitest/utils": "4.1.1",
|
||||
"@vitest/utils": "4.1.8",
|
||||
"ast-v8-to-istanbul": "^1.0.0",
|
||||
"istanbul-lib-coverage": "^3.2.2",
|
||||
"istanbul-lib-report": "^3.0.1",
|
||||
@@ -5058,14 +5058,14 @@
|
||||
"magicast": "^0.5.2",
|
||||
"obug": "^2.1.1",
|
||||
"std-env": "^4.0.0-rc.1",
|
||||
"tinyrainbow": "^3.0.3"
|
||||
"tinyrainbow": "^3.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vitest/browser": "4.1.1",
|
||||
"vitest": "4.1.1"
|
||||
"@vitest/browser": "4.1.8",
|
||||
"vitest": "4.1.8"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vitest/browser": {
|
||||
@@ -5074,41 +5074,31 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/expect": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.1.tgz",
|
||||
"integrity": "sha512-xAV0fqBTk44Rn6SjJReEQkHP3RrqbJo6JQ4zZ7/uVOiJZRarBtblzrOfFIZeYUrukp2YD6snZG6IBqhOoHTm+A==",
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.8.tgz",
|
||||
"integrity": "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "^1.1.0",
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/spy": "4.1.1",
|
||||
"@vitest/utils": "4.1.1",
|
||||
"@vitest/spy": "4.1.8",
|
||||
"@vitest/utils": "4.1.8",
|
||||
"chai": "^6.2.2",
|
||||
"tinyrainbow": "^3.0.3"
|
||||
"tinyrainbow": "^3.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/expect/node_modules/chai": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
|
||||
"integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/mocker": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.1.tgz",
|
||||
"integrity": "sha512-h3BOylsfsCLPeceuCPAAJ+BvNwSENgJa4hXoXu4im0bs9Lyp4URc4JYK4pWLZ4pG/UQn7AT92K6IByi6rE6g3A==",
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.8.tgz",
|
||||
"integrity": "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/spy": "4.1.1",
|
||||
"@vitest/spy": "4.1.8",
|
||||
"estree-walker": "^3.0.3",
|
||||
"magic-string": "^0.30.21"
|
||||
},
|
||||
@@ -5129,26 +5119,26 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/pretty-format": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.1.tgz",
|
||||
"integrity": "sha512-GM+TEQN5WhOygr1lp7skeVjdLPqqWMHsfzXrcHAqZJi/lIVh63H0kaRCY8MDhNWikx19zBUK8ceaLB7X5AH9NQ==",
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.8.tgz",
|
||||
"integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tinyrainbow": "^3.0.3"
|
||||
"tinyrainbow": "^3.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/runner": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.1.tgz",
|
||||
"integrity": "sha512-f7+FPy75vN91QGWsITueq0gedwUZy1fLtHOCMeQpjs8jTekAHeKP80zfDEnhrleviLHzVSDXIWuCIOFn3D3f8A==",
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.8.tgz",
|
||||
"integrity": "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/utils": "4.1.1",
|
||||
"@vitest/utils": "4.1.8",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
"funding": {
|
||||
@@ -5156,14 +5146,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/snapshot": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.1.tgz",
|
||||
"integrity": "sha512-kMVSgcegWV2FibXEx9p9WIKgje58lcTbXgnJixfcg15iK8nzCXhmalL0ZLtTWLW9PH1+1NEDShiFFedB3tEgWg==",
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.8.tgz",
|
||||
"integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "4.1.1",
|
||||
"@vitest/utils": "4.1.1",
|
||||
"@vitest/pretty-format": "4.1.8",
|
||||
"@vitest/utils": "4.1.8",
|
||||
"magic-string": "^0.30.21",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
@@ -5172,9 +5162,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/spy": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.1.tgz",
|
||||
"integrity": "sha512-6Ti/KT5OVaiupdIZEuZN7l3CZcR0cxnxt70Z0//3CtwgObwA6jZhmVBA3yrXSVN3gmwjgd7oDNLlsXz526gpRA==",
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.8.tgz",
|
||||
"integrity": "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
@@ -5182,15 +5172,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/utils": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.1.tgz",
|
||||
"integrity": "sha512-cNxAlaB3sHoCdL6pj6yyUXv9Gry1NHNg0kFTXdvSIZXLHsqKH7chiWOkwJ5s5+d/oMwcoG9T0bKU38JZWKusrQ==",
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.8.tgz",
|
||||
"integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "4.1.1",
|
||||
"@vitest/pretty-format": "4.1.8",
|
||||
"convert-source-map": "^2.0.0",
|
||||
"tinyrainbow": "^3.0.3"
|
||||
"tinyrainbow": "^3.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
@@ -5223,9 +5213,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@wdio/config/node_modules/brace-expansion": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
|
||||
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
|
||||
"integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -5591,9 +5581,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/archiver-utils/node_modules/brace-expansion": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
|
||||
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
|
||||
"integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -6400,6 +6390,16 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/chai": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
|
||||
"integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
@@ -7882,9 +7882,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/eslint-plugin-import/node_modules/brace-expansion": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
||||
"version": "1.1.15",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
|
||||
"integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -7979,9 +7979,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/eslint-plugin-json-schema-validator/node_modules/brace-expansion": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
|
||||
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
|
||||
"integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -8045,9 +8045,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/eslint-plugin-n/node_modules/brace-expansion": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
|
||||
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
|
||||
"integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -8206,9 +8206,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/eslint-plugin-react/node_modules/brace-expansion": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
||||
"version": "1.1.15",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
|
||||
"integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -8408,9 +8408,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/eslint/node_modules/brace-expansion": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
||||
"version": "1.1.15",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
|
||||
"integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -9244,9 +9244,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/globby/node_modules/brace-expansion": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
||||
"version": "1.1.15",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
|
||||
"integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -10257,10 +10257,20 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz",
|
||||
"integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/puzrin"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/nodeca"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
@@ -10928,9 +10938,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "11.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.4.0.tgz",
|
||||
"integrity": "sha512-W+R+kFL4HgVxONq2bhXPi3bGpzGe/yEhVOp233qw9wCRtgncJ15P3bC+e4zZMu4Cq7d+WAJjXGW0uUkifhcatA==",
|
||||
"version": "11.5.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz",
|
||||
"integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"engines": {
|
||||
@@ -12650,9 +12660,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/readdir-glob/node_modules/brace-expansion": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
|
||||
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
|
||||
"integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -13075,9 +13085,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
|
||||
"integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
|
||||
"version": "7.8.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz",
|
||||
"integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
@@ -15249,9 +15259,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "7.25.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz",
|
||||
"integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==",
|
||||
"version": "7.27.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.27.0.tgz",
|
||||
"integrity": "sha512-+t2Z/GwkZQDtu00813aP66ygViGtPHKhhoFZpQKpKrE+9jIgES+Zw+mFNaDWOVRKiuJjuqKHzD3B1sfGg8+ZOQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -16030,20 +16040,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vitest": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.1.tgz",
|
||||
"integrity": "sha512-yF+o4POL41rpAzj5KVILUxm1GCjKnELvaqmU9TLLUbMfDzuN0UpUR9uaDs+mCtjPe+uYPksXDRLQGGPvj1cTmA==",
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.8.tgz",
|
||||
"integrity": "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vitest/expect": "4.1.1",
|
||||
"@vitest/mocker": "4.1.1",
|
||||
"@vitest/pretty-format": "4.1.1",
|
||||
"@vitest/runner": "4.1.1",
|
||||
"@vitest/snapshot": "4.1.1",
|
||||
"@vitest/spy": "4.1.1",
|
||||
"@vitest/utils": "4.1.1",
|
||||
"@vitest/expect": "4.1.8",
|
||||
"@vitest/mocker": "4.1.8",
|
||||
"@vitest/pretty-format": "4.1.8",
|
||||
"@vitest/runner": "4.1.8",
|
||||
"@vitest/snapshot": "4.1.8",
|
||||
"@vitest/spy": "4.1.8",
|
||||
"@vitest/utils": "4.1.8",
|
||||
"es-module-lexer": "^2.0.0",
|
||||
"expect-type": "^1.3.0",
|
||||
"magic-string": "^0.30.21",
|
||||
@@ -16054,7 +16063,7 @@
|
||||
"tinybench": "^2.9.0",
|
||||
"tinyexec": "^1.0.2",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"tinyrainbow": "^3.0.3",
|
||||
"tinyrainbow": "^3.1.0",
|
||||
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
|
||||
"why-is-node-running": "^2.3.0"
|
||||
},
|
||||
@@ -16071,10 +16080,12 @@
|
||||
"@edge-runtime/vm": "*",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
|
||||
"@vitest/browser-playwright": "4.1.1",
|
||||
"@vitest/browser-preview": "4.1.1",
|
||||
"@vitest/browser-webdriverio": "4.1.1",
|
||||
"@vitest/ui": "4.1.1",
|
||||
"@vitest/browser-playwright": "4.1.8",
|
||||
"@vitest/browser-preview": "4.1.8",
|
||||
"@vitest/browser-webdriverio": "4.1.8",
|
||||
"@vitest/coverage-istanbul": "4.1.8",
|
||||
"@vitest/coverage-v8": "4.1.8",
|
||||
"@vitest/ui": "4.1.8",
|
||||
"happy-dom": "*",
|
||||
"jsdom": "*",
|
||||
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||
@@ -16098,6 +16109,12 @@
|
||||
"@vitest/browser-webdriverio": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/coverage-istanbul": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/coverage-v8": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/ui": {
|
||||
"optional": true
|
||||
},
|
||||
@@ -16206,9 +16223,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webdriver/node_modules/undici": {
|
||||
"version": "6.25.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.25.0.tgz",
|
||||
"integrity": "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==",
|
||||
"version": "6.26.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.26.0.tgz",
|
||||
"integrity": "sha512-4yqz8a3n5HmGTlsbADNtr/dJlhkh/55Rq798G6ibiULcXbDtaLpTl1pvdqcbFfeoj3iSi52lePFM7h9H21cw/A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
||||
15
package.json
15
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "obsidian-livesync",
|
||||
"version": "0.25.70",
|
||||
"version": "0.25.70-patch3",
|
||||
"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",
|
||||
@@ -25,7 +25,7 @@
|
||||
"pretty": "npm run prettyNoWrite -- --write --log-level error",
|
||||
"prettyCheck": "npm run prettyNoWrite -- --check",
|
||||
"prettyNoWrite": "prettier --config ./.prettierrc.mjs \"**/*.js\" \"**/*.ts\" \"**/*.json\" ",
|
||||
"check": "npm run lint && npm run svelte-check",
|
||||
"check": "npm run tsc-check && npm run lint && npm run svelte-check",
|
||||
"unittest": "deno test -A --no-check --coverage=cov_profile --v8-flags=--expose-gc --trace-leaks ./src/",
|
||||
"test": "vitest run",
|
||||
"test:unit": "vitest run --config vitest.config.unit.ts",
|
||||
@@ -80,9 +80,9 @@
|
||||
"@types/transform-pouch": "^1.0.6",
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"@vitest/browser": "^4.1.1",
|
||||
"@vitest/browser-playwright": "^4.1.1",
|
||||
"@vitest/coverage-v8": "^4.1.1",
|
||||
"@vitest/browser": "^4.1.8",
|
||||
"@vitest/browser-playwright": "^4.1.8",
|
||||
"@vitest/coverage-v8": "^4.1.8",
|
||||
"dotenv-cli": "^11.0.0",
|
||||
"esbuild": "0.25.0",
|
||||
"esbuild-plugin-inline-worker": "^0.1.1",
|
||||
@@ -119,7 +119,7 @@
|
||||
"typescript": "5.9.3",
|
||||
"vite": "^7.3.1",
|
||||
"vite-plugin-istanbul": "^8.0.0",
|
||||
"vitest": "^4.1.1",
|
||||
"vitest": "^4.1.8",
|
||||
"webdriverio": "^9.27.0",
|
||||
"yaml": "^2.8.2"
|
||||
},
|
||||
@@ -130,16 +130,17 @@
|
||||
"@smithy/middleware-apply-body-checksum": "^4.3.9",
|
||||
"@smithy/protocol-http": "^5.3.9",
|
||||
"@smithy/querystring-builder": "^4.2.9",
|
||||
"@smithy/util-retry": "^4.4.5",
|
||||
"@trystero-p2p/nostr": "^0.24.0",
|
||||
"chokidar": "^4.0.0",
|
||||
"commander": "^14.0.3",
|
||||
"obsidian": "^1.12.3",
|
||||
"diff-match-patch": "^1.0.5",
|
||||
"fflate": "^0.8.2",
|
||||
"idb": "^8.0.3",
|
||||
"markdown-it": "^14.1.1",
|
||||
"micromatch": "^4.0.0",
|
||||
"minimatch": "^10.2.2",
|
||||
"obsidian": "^1.12.3",
|
||||
"octagonal-wheels": "^0.1.46",
|
||||
"pouchdb-adapter-leveldb": "^9.0.0",
|
||||
"qrcode-generator": "^1.4.4",
|
||||
|
||||
@@ -80,7 +80,9 @@ describe("CLIStorageEventManagerAdapter", () => {
|
||||
|
||||
expect(handlers.onCreate).toHaveBeenCalledTimes(1);
|
||||
const created = (handlers.onCreate as ReturnType<typeof vi.fn>).mock.calls[0][0] as NodeFile;
|
||||
expect(created.path).toBe("subdir/note.md");
|
||||
if (process.platform !== "win32") {
|
||||
expect(created.path).toBe("subdir/note.md");
|
||||
}
|
||||
expect(created.stat?.size).toBe(42);
|
||||
});
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ export const _requestToCouchDBFetch = async (
|
||||
username: string,
|
||||
password: string,
|
||||
path?: string,
|
||||
body?: string | any,
|
||||
body?: any,
|
||||
method?: string
|
||||
) => {
|
||||
const utf8str = String.fromCharCode.apply(null, [...writeString(`${username}:${password}`)]);
|
||||
|
||||
@@ -71,6 +71,7 @@ import { PluginDialogModal } from "./PluginDialogModal.ts";
|
||||
import { $msg } from "@/lib/src/common/i18n.ts";
|
||||
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
|
||||
import type { LiveSyncCore } from "../../main.ts";
|
||||
import { LiveSyncError } from "@lib/common/LSError.ts";
|
||||
|
||||
const d = "\u200b";
|
||||
const d2 = "\n";
|
||||
@@ -1069,10 +1070,10 @@ export class ConfigSync extends LiveSyncCommands {
|
||||
}
|
||||
const baseDir = this.configDir;
|
||||
try {
|
||||
if (!data.documentPath) throw "InternalError: Document path not exist";
|
||||
if (!data.documentPath) throw new LiveSyncError("InternalError: Document path not exist");
|
||||
const dx = await this.localDatabase.getDBEntry(data.documentPath);
|
||||
if (dx == false) {
|
||||
throw "Not found on database";
|
||||
throw new LiveSyncError("Not found on database");
|
||||
}
|
||||
const loadedData = deserialize(getDocDataAsArray(dx.data), {}) as PluginDataEx;
|
||||
for (const f of loadedData.files) {
|
||||
@@ -1317,7 +1318,7 @@ export class ConfigSync extends LiveSyncCommands {
|
||||
}
|
||||
const docXDoc = await this.localDatabase.getDBEntryFromMeta(old, false, false);
|
||||
if (docXDoc == false) {
|
||||
throw "Could not load the document";
|
||||
throw new LiveSyncError("Could not load the document");
|
||||
}
|
||||
const dataSrc = getDocData(docXDoc.data);
|
||||
const dataStart = dataSrc.indexOf(DUMMY_END);
|
||||
|
||||
@@ -50,6 +50,7 @@ import { hiddenFilesEventCount, hiddenFilesProcessingCount } from "../../lib/src
|
||||
import { EVENT_SETTING_SAVED, eventHub } from "../../common/events.ts";
|
||||
import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
|
||||
import type { LiveSyncCore } from "../../main.ts";
|
||||
import { tryGetFilePath } from "@lib/common/utils.doc.ts";
|
||||
type SyncDirection = "push" | "pull" | "safe" | "pullForce" | "pushForce";
|
||||
|
||||
declare global {
|
||||
@@ -317,7 +318,7 @@ export class HiddenFileSync extends LiveSyncCommands {
|
||||
this._fileInfoLastProcessed.set(file, key);
|
||||
}
|
||||
|
||||
async updateLastProcessedAsActualFile(file: FilePath, stat?: UXStat | null | undefined) {
|
||||
async updateLastProcessedAsActualFile(file: FilePath, stat?: UXStat | null) {
|
||||
if (!stat) stat = await this.core.storageAccess.statHidden(file);
|
||||
this._fileInfoLastProcessed.set(file, this.statToKey(stat));
|
||||
}
|
||||
@@ -411,10 +412,7 @@ export class HiddenFileSync extends LiveSyncCommands {
|
||||
}
|
||||
}
|
||||
|
||||
async updateLastProcessedAsActualDatabase(
|
||||
file: FilePath,
|
||||
doc?: MetaEntry | LoadedEntry | null | undefined | false
|
||||
) {
|
||||
async updateLastProcessedAsActualDatabase(file: FilePath, doc?: MetaEntry | LoadedEntry | null | false) {
|
||||
const dbPath = addPrefix(file, ICHeader);
|
||||
if (!doc) doc = await this.localDatabase.getDBEntryMeta(dbPath);
|
||||
if (!doc) return;
|
||||
@@ -1050,7 +1048,7 @@ Offline Changed files: ${processFiles.length}`;
|
||||
}
|
||||
notifyProgress();
|
||||
} catch (ex) {
|
||||
this._log(`Failed to process storage change file:${file}`, logLevel);
|
||||
this._log(`Failed to process storage change file:${tryGetFilePath(file)}`, logLevel);
|
||||
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||
}
|
||||
});
|
||||
@@ -1162,7 +1160,7 @@ Offline Changed files: ${files.length}`;
|
||||
await this.trackDatabaseFileModification(path, "[Scanning]", true, onlyNew, file);
|
||||
notifyProgress();
|
||||
} catch (ex) {
|
||||
this._log(`Failed to process database changes:${file}`);
|
||||
this._log(`Failed to process database changes:${tryGetFilePath(file)}`);
|
||||
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||
}
|
||||
return;
|
||||
@@ -1500,7 +1498,7 @@ Offline Changed files: ${files.length}`;
|
||||
}
|
||||
|
||||
async storeInternalFileToDatabase(file: InternalFileInfo | UXFileInfo, forceWrite = false) {
|
||||
const storeFilePath = stripAllPrefixes(file.path as FilePath);
|
||||
const storeFilePath = stripAllPrefixes(file.path);
|
||||
const storageFilePath = file.path;
|
||||
if (await this.services.vault.isIgnoredByIgnoreFile(storageFilePath)) {
|
||||
return undefined;
|
||||
|
||||
@@ -16,9 +16,8 @@ import { serialized } from "octagonal-wheels/concurrency/lock_v2";
|
||||
import { arrayToChunkedArray } from "octagonal-wheels/collection";
|
||||
import { EVENT_ANALYSE_DB_USAGE, EVENT_REQUEST_PERFORM_GC_V3, eventHub } from "@/common/events";
|
||||
import type { LiveSyncCouchDBReplicator } from "@/lib/src/replication/couchdb/LiveSyncReplicator";
|
||||
import { delay, parseHeaderValues } from "@/lib/src/common/utils";
|
||||
import { generateCredentialObject } from "@/lib/src/replication/httplib";
|
||||
import { _requestToCouchDB } from "@/common/utils";
|
||||
import { delay } from "@/lib/src/common/utils";
|
||||
// import { _requestToCouchDB } from "@/common/utils";
|
||||
const DB_KEY_SEQ = "gc-seq";
|
||||
const DB_KEY_CHUNK_SET = "chunk-set";
|
||||
const DB_KEY_DOC_USAGE_MAP = "doc-usage-map";
|
||||
@@ -391,7 +390,7 @@ Note: **Make sure to synchronise all devices before deletion.**
|
||||
.map((revInfo) => db.get(doc._id, { rev: revInfo.rev }))
|
||||
).then((docs) => docs.filter((doc) => doc));
|
||||
for (const oldDoc of oldDocs) {
|
||||
await processDoc(oldDoc as EntryDoc, false);
|
||||
await processDoc(oldDoc, false);
|
||||
}
|
||||
}
|
||||
} catch (ex) {
|
||||
@@ -533,7 +532,7 @@ Success: ${successCount}, Errored: ${errored}`;
|
||||
const docMap = new Map<DocumentID, Set<DocumentInfo>>();
|
||||
const info = await db.info();
|
||||
// Total number of revisions to process (approximate)
|
||||
const maxSeq = new Number(info.update_seq);
|
||||
const maxSeq = Number.parseInt(`${info.update_seq ?? 0}`, 10);
|
||||
let processed = 0;
|
||||
let read = 0;
|
||||
let errored = 0;
|
||||
@@ -560,7 +559,7 @@ Success: ${successCount}, Errored: ${errored}`;
|
||||
});
|
||||
docMap.set(id, set);
|
||||
} else if (doc.type === EntryTypes.CHUNK) {
|
||||
const id = doc._id as DocumentID;
|
||||
const id = doc._id;
|
||||
if (chunkMap.has(id)) {
|
||||
return;
|
||||
}
|
||||
@@ -759,68 +758,68 @@ Success: ${successCount}, Errored: ${errored}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compact the database by temporarily setting the revision limit to 1.
|
||||
* @returns
|
||||
*/
|
||||
async compactDatabaseWithRevLimit() {
|
||||
// Temporarily set revs_limit to 1, perform compaction, and restore the original revs_limit.
|
||||
// Very dangerous operation, so now suppressed.
|
||||
return false;
|
||||
const replicator = this.core.replicator as LiveSyncCouchDBReplicator;
|
||||
const remote = await replicator.connectRemoteCouchDBWithSetting(this.settings, false, false, true);
|
||||
if (!remote) {
|
||||
this._notice("Failed to connect to remote for compaction.");
|
||||
return;
|
||||
}
|
||||
if (typeof remote == "string") {
|
||||
this._notice(`Failed to connect to remote for compaction. ${remote}`);
|
||||
return;
|
||||
}
|
||||
const customHeaders = parseHeaderValues(this.settings.couchDB_CustomHeaders);
|
||||
const credential = generateCredentialObject(this.settings);
|
||||
const request = async (path: string, method: string = "GET", body: any = undefined) => {
|
||||
const req = await _requestToCouchDB(
|
||||
this.settings.couchDB_URI.replace(/\/+$/, "") +
|
||||
(this.settings.couchDB_DBNAME ? `/${this.settings.couchDB_DBNAME}` : ""),
|
||||
credential,
|
||||
window.origin,
|
||||
path,
|
||||
body,
|
||||
method,
|
||||
customHeaders
|
||||
);
|
||||
return req;
|
||||
};
|
||||
let revsLimit = "";
|
||||
const req = await request(`_revs_limit`, "GET");
|
||||
if (req.status == 200) {
|
||||
revsLimit = req.text.trim();
|
||||
this._info(`Remote database _revs_limit: ${revsLimit}`);
|
||||
} else {
|
||||
this._notice(`Failed to get remote database _revs_limit. Status: ${req.status}`);
|
||||
return;
|
||||
}
|
||||
const req2 = await request(`_revs_limit`, "PUT", 1);
|
||||
if (req2.status == 200) {
|
||||
this._info(`Set remote database _revs_limit to 1 for compaction.`);
|
||||
}
|
||||
try {
|
||||
await this.compactDatabase();
|
||||
} finally {
|
||||
// Restore revs_limit
|
||||
if (revsLimit) {
|
||||
const req3 = await request(`_revs_limit`, "PUT", parseInt(revsLimit));
|
||||
if (req3.status == 200) {
|
||||
this._info(`Restored remote database _revs_limit to ${revsLimit}.`);
|
||||
} else {
|
||||
this._notice(
|
||||
`Failed to restore remote database _revs_limit. Status: ${req3.status} / ${req3.text}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// /**
|
||||
// * Compact the database by temporarily setting the revision limit to 1.
|
||||
// * @returns
|
||||
// */
|
||||
// async compactDatabaseWithRevLimit() {
|
||||
// // Temporarily set revs_limit to 1, perform compaction, and restore the original revs_limit.
|
||||
// // Very dangerous operation, so now suppressed.
|
||||
// return Promise.resolve(false);
|
||||
// const replicator = this.core.replicator as LiveSyncCouchDBReplicator;
|
||||
// const remote = await replicator.connectRemoteCouchDBWithSetting(this.settings, false, false, true);
|
||||
// if (!remote) {
|
||||
// this._notice("Failed to connect to remote for compaction.");
|
||||
// return;
|
||||
// }
|
||||
// if (typeof remote == "string") {
|
||||
// this._notice(`Failed to connect to remote for compaction. ${remote}`);
|
||||
// return;
|
||||
// }
|
||||
// const customHeaders = parseHeaderValues(this.settings.couchDB_CustomHeaders);
|
||||
// const credential = generateCredentialObject(this.settings);
|
||||
// const request = async (path: string, method: string = "GET", body: any = undefined) => {
|
||||
// const req = await _requestToCouchDB(
|
||||
// this.settings.couchDB_URI.replace(/\/+$/, "") +
|
||||
// (this.settings.couchDB_DBNAME ? `/${this.settings.couchDB_DBNAME}` : ""),
|
||||
// credential,
|
||||
// window.origin,
|
||||
// path,
|
||||
// body,
|
||||
// method,
|
||||
// customHeaders
|
||||
// );
|
||||
// return req;
|
||||
// };
|
||||
// let revsLimit = "";
|
||||
// const req = await request(`_revs_limit`, "GET");
|
||||
// if (req.status == 200) {
|
||||
// revsLimit = req.text.trim();
|
||||
// this._info(`Remote database _revs_limit: ${revsLimit}`);
|
||||
// } else {
|
||||
// this._notice(`Failed to get remote database _revs_limit. Status: ${req.status}`);
|
||||
// return;
|
||||
// }
|
||||
// const req2 = await request(`_revs_limit`, "PUT", 1);
|
||||
// if (req2.status == 200) {
|
||||
// this._info(`Set remote database _revs_limit to 1 for compaction.`);
|
||||
// }
|
||||
// try {
|
||||
// await this.compactDatabase();
|
||||
// } finally {
|
||||
// // Restore revs_limit
|
||||
// if (revsLimit) {
|
||||
// const req3 = await request(`_revs_limit`, "PUT", parseInt(revsLimit));
|
||||
// if (req3.status == 200) {
|
||||
// this._info(`Restored remote database _revs_limit to ${revsLimit}.`);
|
||||
// } else {
|
||||
// this._notice(
|
||||
// `Failed to restore remote database _revs_limit. Status: ${req3.status} / ${req3.text}`
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
async gcv3() {
|
||||
if (!this.isAvailable()) return;
|
||||
const replicator = this.core.replicator as LiveSyncCouchDBReplicator;
|
||||
@@ -929,7 +928,7 @@ This may indicate that some devices have not completed synchronisation, which co
|
||||
usedChunks.add(chunkId);
|
||||
}
|
||||
} else if (doc.type === EntryTypes.CHUNK) {
|
||||
allChunks.set(doc._id as DocumentID, doc._rev);
|
||||
allChunks.set(doc._id, doc._rev);
|
||||
}
|
||||
}
|
||||
this._notice(
|
||||
|
||||
2
src/lib
2
src/lib
Submodule src/lib updated: faf5c55fd7...6fac4a00dd
@@ -1,10 +1,11 @@
|
||||
import { ButtonComponent } from "@/deps.ts";
|
||||
import { App, FuzzySuggestModal, MarkdownRenderer, Modal, Plugin, Setting } from "../../../deps.ts";
|
||||
import { EVENT_PLUGIN_UNLOADED, eventHub } from "../../../common/events.ts";
|
||||
import { compatGlobal } from "@lib/common/coreEnvFunctions.ts";
|
||||
import { compatGlobal, type CompatIntervalHandle } from "@lib/common/coreEnvFunctions.ts";
|
||||
|
||||
class AutoClosableModal extends Modal {
|
||||
_closeByUnload() {
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
eventHub.off(EVENT_PLUGIN_UNLOADED, this._closeByUnload);
|
||||
this.close();
|
||||
}
|
||||
@@ -12,9 +13,11 @@ class AutoClosableModal extends Modal {
|
||||
constructor(app: App) {
|
||||
super(app);
|
||||
this._closeByUnload = this._closeByUnload.bind(this);
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
eventHub.once(EVENT_PLUGIN_UNLOADED, this._closeByUnload);
|
||||
}
|
||||
override onClose() {
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
eventHub.off(EVENT_PLUGIN_UNLOADED, this._closeByUnload);
|
||||
}
|
||||
}
|
||||
@@ -140,7 +143,7 @@ export class MessageBox<T extends readonly string[]> extends AutoClosableModal {
|
||||
isManuallyClosed = false;
|
||||
defaultAction: string | undefined;
|
||||
timeout: number | undefined;
|
||||
timer: ReturnType<typeof compatGlobal.setInterval> | undefined = undefined;
|
||||
timer: CompatIntervalHandle | undefined = undefined;
|
||||
defaultButtonComponent: ButtonComponent | undefined;
|
||||
wideButton: boolean;
|
||||
|
||||
|
||||
@@ -80,11 +80,19 @@ export class ModuleObsidianEvents extends AbstractObsidianModule {
|
||||
this.watchWindowVisibility = this.watchWindowVisibility.bind(this);
|
||||
this.watchWorkspaceOpen = this.watchWorkspaceOpen.bind(this);
|
||||
this.watchOnline = this.watchOnline.bind(this);
|
||||
// Already bound
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
this.plugin.registerEvent(this.app.workspace.on("file-open", this.watchWorkspaceOpen));
|
||||
this.plugin.registerDomEvent(document, "visibilitychange", this.watchWindowVisibility);
|
||||
// Already bound
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
this.plugin.registerDomEvent(activeDocument, "visibilitychange", this.watchWindowVisibility);
|
||||
this.plugin.registerDomEvent(window, "focus", () => this.setHasFocus(true));
|
||||
this.plugin.registerDomEvent(window, "blur", () => this.setHasFocus(false));
|
||||
// Already bound
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
this.plugin.registerDomEvent(window, "online", this.watchOnline);
|
||||
// Already bound
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
this.plugin.registerDomEvent(window, "offline", this.watchOnline);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import { TestPaneView, VIEW_TYPE_TEST } from "./devUtil/TestPaneView.ts";
|
||||
import { writable } from "svelte/store";
|
||||
import type { FilePathWithPrefix } from "../../lib/src/common/types.ts";
|
||||
import type { LiveSyncCore } from "../../main.ts";
|
||||
|
||||
export class ModuleDev extends AbstractObsidianModule {
|
||||
_everyOnloadStart(): Promise<boolean> {
|
||||
__onMissingTranslation(() => {});
|
||||
@@ -98,6 +97,7 @@ export class ModuleDev extends AbstractObsidianModule {
|
||||
});
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
async _everyOnLayoutReady(): Promise<boolean> {
|
||||
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
||||
// if (await this.core.storageAccess.isExistsIncludeHidden("_SHOWDIALOGAUTO.md")) {
|
||||
|
||||
@@ -3,6 +3,11 @@ import TestPaneComponent from "./TestPane.svelte";
|
||||
import type ObsidianLiveSyncPlugin from "../../../main.ts";
|
||||
import type { ModuleDev } from "../ModuleDev.ts";
|
||||
export const VIEW_TYPE_TEST = "ols-pane-test";
|
||||
declare global {
|
||||
interface LSEvents {
|
||||
"debug-sync-status": string[];
|
||||
}
|
||||
}
|
||||
//Log view
|
||||
export class TestPaneView extends ItemView {
|
||||
component?: TestPaneComponent;
|
||||
|
||||
@@ -248,7 +248,7 @@
|
||||
</td>
|
||||
<td class="path">
|
||||
<div class="filenames">
|
||||
<span class="path">/{entry.dirname.split("/").join(`/`)}</span>
|
||||
<span class="path">/{entry.dirname.split("/").join(`\u200b/`)}</span>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-missing-attribute -->
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { onDestroy, onMount } from "svelte";
|
||||
import { logMessages } from "../../../lib/src/mock_and_interop/stores";
|
||||
import { reactive, type ReactiveInstance } from "octagonal-wheels/dataobject/reactive";
|
||||
import { Logger } from "../../../lib/src/common/logger";
|
||||
import { $msg as msg, currentLang as lang } from "../../../lib/src/common/i18n.ts";
|
||||
import { onDestroy, onMount } from "svelte";
|
||||
import { logMessages } from "../../../lib/src/mock_and_interop/stores";
|
||||
import { reactive, type ReactiveInstance } from "octagonal-wheels/dataobject/reactive";
|
||||
import { Logger } from "../../../lib/src/common/logger";
|
||||
import { $msg as msg, currentLang as lang } from "../../../lib/src/common/i18n.ts";
|
||||
import { compatGlobal } from "@lib/common/coreEnvFunctions.ts";
|
||||
|
||||
let unsubscribe: () => void;
|
||||
let unsubscribe: () => void;
|
||||
let messages = $state([] as string[]);
|
||||
let wrapRight = $state(false);
|
||||
let autoScroll = $state(true);
|
||||
@@ -16,90 +17,90 @@
|
||||
};
|
||||
let { close }: Props = $props();
|
||||
// export let close: () => void;
|
||||
function updateLog(logs: ReactiveInstance<string[]>) {
|
||||
const e = logs.value;
|
||||
if (!suspended) {
|
||||
messages = [...e];
|
||||
setTimeout(() => {
|
||||
if (scroll) scroll.scrollTop = scroll.scrollHeight;
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
onMount(async () => {
|
||||
const _logMessages = reactive(() => logMessages.value);
|
||||
_logMessages.onChanged(updateLog);
|
||||
Logger(msg("logPane.logWindowOpened", {}, lang));
|
||||
unsubscribe = () => _logMessages.offChanged(updateLog);
|
||||
});
|
||||
onDestroy(() => {
|
||||
if (unsubscribe) unsubscribe();
|
||||
});
|
||||
let scroll: HTMLDivElement;
|
||||
function updateLog(logs: ReactiveInstance<string[]>) {
|
||||
const e = logs.value;
|
||||
if (!suspended) {
|
||||
messages = [...e];
|
||||
compatGlobal.setTimeout(() => {
|
||||
if (scroll) scroll.scrollTop = scroll.scrollHeight;
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
onMount(async () => {
|
||||
const _logMessages = reactive(() => logMessages.value);
|
||||
_logMessages.onChanged(updateLog);
|
||||
Logger(msg("logPane.logWindowOpened", {}, lang));
|
||||
unsubscribe = () => _logMessages.offChanged(updateLog);
|
||||
});
|
||||
onDestroy(() => {
|
||||
if (unsubscribe) unsubscribe();
|
||||
});
|
||||
let scroll: HTMLDivElement;
|
||||
function closeDialogue() {
|
||||
close();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="logpane">
|
||||
<!-- <h1>{msg("logPane.title", {}, lang)}</h1> -->
|
||||
<div class="control">
|
||||
<div class="row">
|
||||
<label>
|
||||
<input type="checkbox" bind:checked={wrapRight} />
|
||||
<span>{msg("logPane.wrap", {}, lang)}</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" bind:checked={autoScroll} />
|
||||
<span>{msg("logPane.autoScroll", {}, lang)}</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" bind:checked={suspended} />
|
||||
<span>{msg("logPane.pause", {}, lang)}</span>
|
||||
</label>
|
||||
<!-- <h1>{msg("logPane.title", {}, lang)}</h1> -->
|
||||
<div class="control">
|
||||
<div class="row">
|
||||
<label>
|
||||
<input type="checkbox" bind:checked={wrapRight} />
|
||||
<span>{msg("logPane.wrap", {}, lang)}</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" bind:checked={autoScroll} />
|
||||
<span>{msg("logPane.autoScroll", {}, lang)}</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" bind:checked={suspended} />
|
||||
<span>{msg("logPane.pause", {}, lang)}</span>
|
||||
</label>
|
||||
<span class="spacer"></span>
|
||||
<button onclick={() => closeDialogue()}>Close</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log" bind:this={scroll}>
|
||||
{#each messages as line}
|
||||
<pre class:wrap-right={wrapRight}>{line}</pre>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log" bind:this={scroll}>
|
||||
{#each messages as line}
|
||||
<pre class:wrap-right={wrapRight}>{line}</pre>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.logpane {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
.log {
|
||||
overflow-y: scroll;
|
||||
user-select: text;
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.logpane {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
.log {
|
||||
overflow-y: scroll;
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
padding-bottom: 2em;
|
||||
}
|
||||
.log > pre {
|
||||
margin: 0;
|
||||
}
|
||||
.log > pre.wrap-right {
|
||||
word-break: break-all;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
white-space: normal;
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.row > label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 5em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
padding-bottom: 2em;
|
||||
}
|
||||
.log > pre {
|
||||
margin: 0;
|
||||
}
|
||||
.log > pre.wrap-right {
|
||||
word-break: break-all;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
white-space: normal;
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.row > label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 5em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -88,7 +88,7 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
this._log(`Merge: Something went wrong: ${filename}, (${toDelete})`, LOG_LEVEL_NOTICE);
|
||||
this._log(`Merge: Something went wrong: ${filename}, (${toDelete as string})`, LOG_LEVEL_NOTICE);
|
||||
return false;
|
||||
}
|
||||
// In here, some merge has been processed.
|
||||
@@ -163,7 +163,7 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule {
|
||||
this._log(`There are no conflicting files`, LOG_LEVEL_VERBOSE);
|
||||
}
|
||||
} catch (e) {
|
||||
this._log(`Error while scanning conflicted files: ${e}`, LOG_LEVEL_NOTICE);
|
||||
this._log(`Error while scanning conflicted files...`, LOG_LEVEL_NOTICE);
|
||||
this._log(e, LOG_LEVEL_VERBOSE);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -262,7 +262,7 @@ export class ModuleLog extends AbstractObsidianModule {
|
||||
this.statusDiv.remove();
|
||||
// this.statusDiv.pa();
|
||||
const container = mdv.view.containerEl;
|
||||
container.insertBefore(this.statusDiv, container.lastChild);
|
||||
container.appendChild(this.statusDiv);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -466,12 +466,14 @@ ${stringifyYaml(info)}
|
||||
|
||||
this.observeForLogs();
|
||||
|
||||
this.statusDiv = this.app.workspace.containerEl.createDiv({ cls: "livesync-status" });
|
||||
this.statusLine = this.statusDiv.createDiv({ cls: "livesync-status-statusline" });
|
||||
this.messageArea = this.statusDiv.createDiv({ cls: "livesync-status-messagearea" });
|
||||
this.logMessage = this.statusDiv.createDiv({ cls: "livesync-status-logmessage" });
|
||||
this.logHistory = this.statusDiv.createDiv({ cls: "livesync-status-loghistory" });
|
||||
this.statusDiv.style.display = this.settings?.showStatusOnEditor ? "" : "none";
|
||||
if (this.settings.showStatusOnEditor) {
|
||||
this.statusDiv = this.app.workspace.containerEl.createDiv({ cls: "livesync-status" });
|
||||
this.statusLine = this.statusDiv.createDiv({ cls: "livesync-status-statusline" });
|
||||
this.messageArea = this.statusDiv.createDiv({ cls: "livesync-status-messagearea" });
|
||||
this.logMessage = this.statusDiv.createDiv({ cls: "livesync-status-logmessage" });
|
||||
this.logHistory = this.statusDiv.createDiv({ cls: "livesync-status-loghistory" });
|
||||
this.statusDiv.style.display = this.settings?.showStatusOnEditor ? "" : "none";
|
||||
}
|
||||
eventHub.onEvent(EVENT_LAYOUT_READY, () => this.adjustStatusDivPosition());
|
||||
if (this.settings?.showStatusOnStatusbar) {
|
||||
this.statusBar = this.services.API.addStatusBarItem();
|
||||
@@ -516,7 +518,12 @@ ${stringifyYaml(info)}
|
||||
let errorInfo = "";
|
||||
if (message instanceof Error) {
|
||||
if (message instanceof LiveSyncError) {
|
||||
errorInfo = `${message.cause?.name}:${message.cause?.message}\n[StackTrace]: ${message.stack}\n[CausedBy]: ${message.cause?.stack}`;
|
||||
if (message.cause && message.cause instanceof Error) {
|
||||
const causedError = message.cause;
|
||||
errorInfo = `${causedError?.name}:${causedError?.message}\n[StackTrace]: ${message.stack}\n[CausedBy]: ${causedError?.stack}`;
|
||||
} else {
|
||||
errorInfo = `${message.name}:${message.message}\n[StackTrace]: ${message.stack}`;
|
||||
}
|
||||
} else {
|
||||
const thisStack = new Error().stack;
|
||||
errorInfo = `${message.name}:${message.message}\n[StackTrace]: ${message.stack}\n[LogCallStack]: ${thisStack}`;
|
||||
|
||||
@@ -62,6 +62,7 @@ import { paneAdvanced } from "./PaneAdvanced.ts";
|
||||
import { panePowerUsers } from "./PanePowerUsers.ts";
|
||||
import { panePatches } from "./PanePatches.ts";
|
||||
import { paneMaintenance } from "./PaneMaintenance.ts";
|
||||
import { compatGlobal } from "@lib/common/coreEnvFunctions.ts";
|
||||
|
||||
// For creating a document
|
||||
const toc = new Set<string>();
|
||||
@@ -141,7 +142,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
|
||||
async saveLocalSetting(key: keyof typeof OnDialogSettingsDefault) {
|
||||
if (key == "configPassphrase") {
|
||||
localStorage.setItem("ls-setting-passphrase", this.editingSettings?.[key] ?? "");
|
||||
compatGlobal.localStorage.setItem("ls-setting-passphrase", this.editingSettings?.[key] ?? "");
|
||||
return await Promise.resolve();
|
||||
}
|
||||
if (key == "deviceAndVaultName") {
|
||||
@@ -180,7 +181,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
// if (runOnSaved) {
|
||||
const handlers = this.onSavedHandlers
|
||||
.filter((e) => appliedKeys.indexOf(e.key) !== -1)
|
||||
.map((e) => e.handler(this.editingSettings[e.key as AllSettingItemKey]));
|
||||
.map((e) => Promise.resolve(e.handler(this.editingSettings[e.key as AllSettingItemKey])));
|
||||
await Promise.all(handlers);
|
||||
// }
|
||||
keys.forEach((e) => this.refreshSetting(e));
|
||||
@@ -214,7 +215,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
|
||||
reloadAllLocalSettings() {
|
||||
const ret = { ...OnDialogSettingsDefault };
|
||||
ret.configPassphrase = localStorage.getItem("ls-setting-passphrase") || "";
|
||||
ret.configPassphrase = compatGlobal.localStorage.getItem("ls-setting-passphrase") || "";
|
||||
ret.preset = "";
|
||||
ret.deviceAndVaultName = this.services.setting.getDeviceAndVaultName();
|
||||
return ret;
|
||||
@@ -349,7 +350,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
createEl<T extends keyof HTMLElementTagNameMap>(
|
||||
el: HTMLElement,
|
||||
tag: T,
|
||||
o?: string | DomElementInfo | undefined,
|
||||
o?: string | DomElementInfo,
|
||||
callback?: (el: HTMLElementTagNameMap[T]) => void,
|
||||
func?: OnUpdateFunc
|
||||
) {
|
||||
@@ -361,7 +362,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
addEl<T extends keyof HTMLElementTagNameMap>(
|
||||
el: HTMLElement,
|
||||
tag: T,
|
||||
o?: string | DomElementInfo | undefined,
|
||||
o?: string | DomElementInfo,
|
||||
callback?: (el: HTMLElementTagNameMap[T]) => void,
|
||||
func?: OnUpdateFunc
|
||||
) {
|
||||
@@ -647,7 +648,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
this.editingSettings.passphrase = "";
|
||||
}
|
||||
await this.saveAllDirtySettings();
|
||||
await this.applyAllSettings();
|
||||
await Promise.resolve(this.applyAllSettings());
|
||||
if (result == OPTION_FETCH) {
|
||||
await this.core.storageAccess.writeFileAuto(FLAGMD_REDFLAG3_HR, "");
|
||||
this.services.appLifecycle.scheduleRestart();
|
||||
@@ -738,6 +739,8 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
);
|
||||
}
|
||||
setLevelClass(el, level);
|
||||
// TODO: Refactor to use Obsidian's recommended way to create heading.
|
||||
// eslint-disable-next-line obsidianmd/settings-tab/no-manual-html-headings
|
||||
el.createEl("h3", { text: title, cls: "sls-setting-pane-title" });
|
||||
if (this.menuEl) {
|
||||
this.menuEl.createEl(
|
||||
|
||||
@@ -188,7 +188,7 @@ export function panePatches(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElemen
|
||||
}
|
||||
this.requestUpdate();
|
||||
};
|
||||
text.inputEl.before((dateEl = document.createElement("span")));
|
||||
text.inputEl.before((dateEl = activeDocument.createElement("span")));
|
||||
text.inputEl.type = "datetime-local";
|
||||
if (this.editingSettings.maxMTimeForReflectEvents > 0) {
|
||||
const date = new Date(this.editingSettings.maxMTimeForReflectEvents);
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
DEFAULT_SETTINGS,
|
||||
LOG_LEVEL_NOTICE,
|
||||
type ObsidianLiveSyncSettings,
|
||||
LOG_LEVEL_VERBOSE,
|
||||
} from "../../../lib/src/common/types.ts";
|
||||
import { Menu } from "@/deps.ts";
|
||||
import { $msg } from "../../../lib/src/common/i18n.ts";
|
||||
@@ -288,7 +289,8 @@ export function paneRemoteConfig(
|
||||
try {
|
||||
parsed = ConnectionStringParser.parse(trimmedURI);
|
||||
} catch (ex) {
|
||||
this.services.API.addLog(`Failed to import remote configuration: ${ex}`, LOG_LEVEL_NOTICE);
|
||||
this.services.API.addLog(`Failed to import remote configuration!`, LOG_LEVEL_NOTICE);
|
||||
this.services.API.addLog(ex, LOG_LEVEL_VERBOSE);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -343,9 +345,10 @@ export function paneRemoteConfig(
|
||||
parsed = ConnectionStringParser.parse(config.uri);
|
||||
} catch (ex) {
|
||||
this.services.API.addLog(
|
||||
`Failed to parse remote configuration '${config.id}' for editing: ${ex}`,
|
||||
`Failed to parse remote configuration '${config.id}' for editing!`,
|
||||
LOG_LEVEL_NOTICE
|
||||
);
|
||||
this.services.API.addLog(ex, LOG_LEVEL_VERBOSE);
|
||||
return;
|
||||
}
|
||||
const workSettings = createBaseRemoteSettings();
|
||||
@@ -452,9 +455,10 @@ export function paneRemoteConfig(
|
||||
parsed = ConnectionStringParser.parse(config.uri);
|
||||
} catch (ex) {
|
||||
this.services.API.addLog(
|
||||
`Failed to parse remote configuration '${config.id}': ${ex}`,
|
||||
`Failed to parse remote configuration '${config.id}' for fetching settings!`,
|
||||
LOG_LEVEL_NOTICE
|
||||
);
|
||||
this.services.API.addLog(ex, LOG_LEVEL_VERBOSE);
|
||||
return;
|
||||
}
|
||||
const workSettings = createBaseRemoteSettings();
|
||||
|
||||
@@ -75,7 +75,7 @@ export function getSummaryFromPartialSettings(setting: Partial<ObsidianLiveSyncS
|
||||
if (config.isAdvanced && !showAdvanced) continue;
|
||||
const value =
|
||||
key != "E2EEAlgorithm"
|
||||
? `${setting[key]}`
|
||||
? `${setting[key] as string}`
|
||||
: E2EEAlgorithmNames[`${setting[key]}` as keyof typeof E2EEAlgorithmNames];
|
||||
const displayValue = config.isHidden ? "•".repeat(value.length) : escapeStringToHTML(value);
|
||||
outputSummary[config.name] = displayValue;
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import {
|
||||
type BucketSyncSetting,
|
||||
type CouchDBConnection,
|
||||
type EncryptionSettings,
|
||||
type ObsidianLiveSyncSettings,
|
||||
type P2PSyncSetting,
|
||||
DEFAULT_SETTINGS,
|
||||
LOG_LEVEL_NOTICE,
|
||||
LOG_LEVEL_VERBOSE,
|
||||
REMOTE_COUCHDB,
|
||||
REMOTE_MINIO,
|
||||
REMOTE_P2P,
|
||||
} from "../../lib/src/common/types.ts";
|
||||
} from "@lib/common/types.ts";
|
||||
import { isObjectDifferent } from "@lib/common/utils.ts";
|
||||
import Intro from "./SetupWizard/dialogs/Intro.svelte";
|
||||
import SelectMethodNewUser from "./SetupWizard/dialogs/SelectMethodNewUser.svelte";
|
||||
@@ -21,9 +25,21 @@ import SetupRemoteCouchDB from "./SetupWizard/dialogs/SetupRemoteCouchDB.svelte"
|
||||
import SetupRemoteBucket from "./SetupWizard/dialogs/SetupRemoteBucket.svelte";
|
||||
import SetupRemoteP2P from "./SetupWizard/dialogs/SetupRemoteP2P.svelte";
|
||||
import SetupRemoteE2EE from "./SetupWizard/dialogs/SetupRemoteE2EE.svelte";
|
||||
import { decodeSettingsFromQRCodeData } from "../../lib/src/API/processSetting.ts";
|
||||
import { decodeSettingsFromQRCodeData } from "@lib/API/processSetting.ts";
|
||||
import { AbstractModule } from "../AbstractModule.ts";
|
||||
import { ConnectionStringParser } from "@lib/common/ConnectionString.ts";
|
||||
import type {
|
||||
OutroAskUserModeResultType,
|
||||
OutroExistingUserResultType,
|
||||
OutroNewUserResultType,
|
||||
ScanQRCodeResultType,
|
||||
SetupRemoteBucketResultType,
|
||||
SetupRemoteCouchDBResultType,
|
||||
SetupRemoteE2EEResultType,
|
||||
SetupRemoteP2PResultType,
|
||||
SetupRemoteResultType,
|
||||
UseSetupURIResultType,
|
||||
} from "./SetupWizard/dialogs/setupDialogTypes.ts";
|
||||
|
||||
/**
|
||||
* User modes for onboarding and setup
|
||||
@@ -118,7 +134,10 @@ export class SetupManager extends AbstractModule {
|
||||
* @returns Promise that resolves to true if onboarding completed successfully, false otherwise
|
||||
*/
|
||||
async onUseSetupURI(userMode: UserMode, setupURI: string = ""): Promise<boolean> {
|
||||
const newSetting = await this.dialogManager.openWithExplicitCancel(UseSetupURI, setupURI);
|
||||
const newSetting = await this.dialogManager.openWithExplicitCancel<UseSetupURIResultType, string>(
|
||||
UseSetupURI,
|
||||
setupURI
|
||||
);
|
||||
if (newSetting === "cancelled") {
|
||||
this._log("Setup URI dialog cancelled.", LOG_LEVEL_NOTICE);
|
||||
return false;
|
||||
@@ -141,7 +160,10 @@ export class SetupManager extends AbstractModule {
|
||||
): Promise<boolean> {
|
||||
const originalSetting = JSON.parse(JSON.stringify(currentSetting)) as ObsidianLiveSyncSettings;
|
||||
const baseSetting = JSON.parse(JSON.stringify(originalSetting)) as ObsidianLiveSyncSettings;
|
||||
const couchConf = await this.dialogManager.openWithExplicitCancel(SetupRemoteCouchDB, originalSetting);
|
||||
const couchConf = await this.dialogManager.openWithExplicitCancel<
|
||||
SetupRemoteCouchDBResultType,
|
||||
CouchDBConnection
|
||||
>(SetupRemoteCouchDB, originalSetting);
|
||||
if (couchConf === "cancelled") {
|
||||
this._log("Manual configuration cancelled.", LOG_LEVEL_NOTICE);
|
||||
return await this.onOnboard(userMode);
|
||||
@@ -165,7 +187,10 @@ export class SetupManager extends AbstractModule {
|
||||
currentSetting: ObsidianLiveSyncSettings,
|
||||
activate = true
|
||||
): Promise<boolean> {
|
||||
const bucketConf = await this.dialogManager.openWithExplicitCancel(SetupRemoteBucket, currentSetting);
|
||||
const bucketConf = await this.dialogManager.openWithExplicitCancel<
|
||||
SetupRemoteBucketResultType,
|
||||
BucketSyncSetting
|
||||
>(SetupRemoteBucket, currentSetting);
|
||||
if (bucketConf === "cancelled") {
|
||||
this._log("Manual configuration cancelled.", LOG_LEVEL_NOTICE);
|
||||
return await this.onOnboard(userMode);
|
||||
@@ -189,7 +214,10 @@ export class SetupManager extends AbstractModule {
|
||||
currentSetting: ObsidianLiveSyncSettings,
|
||||
activate = true
|
||||
): Promise<boolean> {
|
||||
const p2pConf = await this.dialogManager.openWithExplicitCancel(SetupRemoteP2P, currentSetting);
|
||||
const p2pConf = await this.dialogManager.openWithExplicitCancel<SetupRemoteP2PResultType, P2PSyncSetting>(
|
||||
SetupRemoteP2P,
|
||||
currentSetting
|
||||
);
|
||||
if (p2pConf === "cancelled") {
|
||||
this._log("Manual configuration cancelled.", LOG_LEVEL_NOTICE);
|
||||
return await this.onOnboard(userMode);
|
||||
@@ -224,10 +252,13 @@ export class SetupManager extends AbstractModule {
|
||||
* @returns
|
||||
*/
|
||||
async onlyE2EEConfiguration(userMode: UserMode, currentSetting: ObsidianLiveSyncSettings): Promise<boolean> {
|
||||
const e2eeConf = await this.dialogManager.openWithExplicitCancel(SetupRemoteE2EE, currentSetting);
|
||||
const e2eeConf = await this.dialogManager.openWithExplicitCancel<SetupRemoteE2EEResultType, EncryptionSettings>(
|
||||
SetupRemoteE2EE,
|
||||
currentSetting
|
||||
);
|
||||
if (e2eeConf === "cancelled") {
|
||||
this._log("E2EE configuration cancelled.", LOG_LEVEL_NOTICE);
|
||||
return await false;
|
||||
return false;
|
||||
}
|
||||
const newSetting = {
|
||||
...currentSetting,
|
||||
@@ -243,7 +274,10 @@ export class SetupManager extends AbstractModule {
|
||||
* @returns
|
||||
*/
|
||||
async onConfigureManually(originalSetting: ObsidianLiveSyncSettings, userMode: UserMode): Promise<boolean> {
|
||||
const e2eeConf = await this.dialogManager.openWithExplicitCancel(SetupRemoteE2EE, originalSetting);
|
||||
const e2eeConf = await this.dialogManager.openWithExplicitCancel<SetupRemoteE2EEResultType, EncryptionSettings>(
|
||||
SetupRemoteE2EE,
|
||||
originalSetting
|
||||
);
|
||||
if (e2eeConf === "cancelled") {
|
||||
this._log("Manual configuration cancelled.", LOG_LEVEL_NOTICE);
|
||||
return await this.onOnboard(userMode);
|
||||
@@ -262,7 +296,7 @@ export class SetupManager extends AbstractModule {
|
||||
* @returns
|
||||
*/
|
||||
async onSelectServer(currentSetting: ObsidianLiveSyncSettings, userMode: UserMode): Promise<boolean> {
|
||||
const method = await this.dialogManager.openWithExplicitCancel(SetupRemote);
|
||||
const method = await this.dialogManager.openWithExplicitCancel<SetupRemoteResultType>(SetupRemote);
|
||||
if (method === "couchdb") {
|
||||
return await this.onCouchDBManualSetup(userMode, currentSetting, true);
|
||||
} else if (method === "bucket") {
|
||||
@@ -321,7 +355,8 @@ export class SetupManager extends AbstractModule {
|
||||
this._log("Settings from wizard applied.", LOG_LEVEL_NOTICE);
|
||||
return true;
|
||||
} else {
|
||||
const userModeResult = await this.dialogManager.openWithExplicitCancel(OutroAskUserMode);
|
||||
const userModeResult =
|
||||
await this.dialogManager.openWithExplicitCancel<OutroAskUserModeResultType>(OutroAskUserMode);
|
||||
if (userModeResult === "new-user") {
|
||||
userMode = UserMode.NewUser;
|
||||
} else if (userModeResult === "existing-user") {
|
||||
@@ -338,7 +373,9 @@ export class SetupManager extends AbstractModule {
|
||||
}
|
||||
}
|
||||
const component = userMode === UserMode.NewUser ? OutroNewUser : OutroExistingUser;
|
||||
const confirm = await this.dialogManager.openWithExplicitCancel(component);
|
||||
const confirm = await this.dialogManager.openWithExplicitCancel<
|
||||
OutroNewUserResultType | OutroExistingUserResultType
|
||||
>(component);
|
||||
if (confirm === "cancelled") {
|
||||
this._log("User cancelled applying settings from wizard..", LOG_LEVEL_NOTICE);
|
||||
return false;
|
||||
@@ -364,10 +401,10 @@ export class SetupManager extends AbstractModule {
|
||||
*/
|
||||
|
||||
async onPromptQRCodeInstruction(): Promise<boolean> {
|
||||
const qrResult = await this.dialogManager.open(ScanQRCode);
|
||||
const qrResult = await this.dialogManager.open<ScanQRCodeResultType>(ScanQRCode);
|
||||
this._log("QR Code dialog closed.", LOG_LEVEL_VERBOSE);
|
||||
// Result is not used, but log it for debugging.
|
||||
this._log(`QR Code result: ${qrResult}`, LOG_LEVEL_VERBOSE);
|
||||
this._log(qrResult, LOG_LEVEL_VERBOSE);
|
||||
// QR Code instruction dialog never yields settings directly.
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,47 +1,30 @@
|
||||
<script lang="ts">
|
||||
import DialogHeader from "@/lib/src/UI/components/DialogHeader.svelte";
|
||||
import Guidance from "@/lib/src/UI/components/Guidance.svelte";
|
||||
import Decision from "@/lib/src/UI/components/Decision.svelte";
|
||||
import Question from "@/lib/src/UI/components/Question.svelte";
|
||||
import Option from "@/lib/src/UI/components/Option.svelte";
|
||||
import Options from "@/lib/src/UI/components/Options.svelte";
|
||||
import Instruction from "@/lib/src/UI/components/Instruction.svelte";
|
||||
import UserDecisions from "@/lib/src/UI/components/UserDecisions.svelte";
|
||||
import InfoNote from "@/lib/src/UI/components/InfoNote.svelte";
|
||||
import ExtraItems from "@/lib/src/UI/components/ExtraItems.svelte";
|
||||
import Check from "@/lib/src/UI/components/Check.svelte";
|
||||
const TYPE_IDENTICAL = "identical";
|
||||
const TYPE_INDEPENDENT = "independent";
|
||||
const TYPE_UNBALANCED = "unbalanced";
|
||||
const TYPE_CANCEL = "cancelled";
|
||||
import DialogHeader from "@lib/UI/components/DialogHeader.svelte";
|
||||
import Guidance from "@lib/UI/components/Guidance.svelte";
|
||||
import Decision from "@lib/UI/components/Decision.svelte";
|
||||
import Question from "@lib/UI/components/Question.svelte";
|
||||
import Option from "@lib/UI/components/Option.svelte";
|
||||
import Options from "@lib/UI/components/Options.svelte";
|
||||
import Instruction from "@lib/UI/components/Instruction.svelte";
|
||||
import UserDecisions from "@lib/UI/components/UserDecisions.svelte";
|
||||
import InfoNote from "@lib/UI/components/InfoNote.svelte";
|
||||
import ExtraItems from "@lib/UI/components/ExtraItems.svelte";
|
||||
import Check from "@lib/UI/components/Check.svelte";
|
||||
import {
|
||||
TYPE_BACKUP_DONE,
|
||||
TYPE_BACKUP_SKIPPED,
|
||||
TYPE_CANCEL,
|
||||
TYPE_IDENTICAL,
|
||||
TYPE_INDEPENDENT,
|
||||
TYPE_UNABLE_TO_BACKUP,
|
||||
TYPE_UNBALANCED,
|
||||
type FetchEverythingResult,
|
||||
type ResultTypeBackup,
|
||||
type ResultTypeVault,
|
||||
} from "./setupDialogTypes";
|
||||
|
||||
const TYPE_BACKUP_DONE = "backup_done";
|
||||
const TYPE_BACKUP_SKIPPED = "backup_skipped";
|
||||
const TYPE_UNABLE_TO_BACKUP = "unable_to_backup";
|
||||
|
||||
type ResultTypeVault =
|
||||
| typeof TYPE_IDENTICAL
|
||||
| typeof TYPE_INDEPENDENT
|
||||
| typeof TYPE_UNBALANCED
|
||||
| typeof TYPE_CANCEL;
|
||||
type ResultTypeBackup =
|
||||
| typeof TYPE_BACKUP_DONE
|
||||
| typeof TYPE_BACKUP_SKIPPED
|
||||
| typeof TYPE_UNABLE_TO_BACKUP
|
||||
| typeof TYPE_CANCEL;
|
||||
|
||||
type ResultTypeExtra = {
|
||||
preventFetchingConfig: boolean;
|
||||
};
|
||||
type ResultType =
|
||||
| {
|
||||
vault: ResultTypeVault;
|
||||
backup: ResultTypeBackup;
|
||||
extra: ResultTypeExtra;
|
||||
}
|
||||
| typeof TYPE_CANCEL;
|
||||
type Props = {
|
||||
setResult: (result: ResultType) => void;
|
||||
setResult: (result: FetchEverythingResult) => void;
|
||||
};
|
||||
const { setResult }: Props = $props();
|
||||
let vaultType = $state<ResultTypeVault>(TYPE_CANCEL);
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
<script lang="ts">
|
||||
import DialogHeader from "@/lib/src/UI/components/DialogHeader.svelte";
|
||||
import Guidance from "@/lib/src/UI/components/Guidance.svelte";
|
||||
import Decision from "@/lib/src/UI/components/Decision.svelte";
|
||||
import Question from "@/lib/src/UI/components/Question.svelte";
|
||||
import Option from "@/lib/src/UI/components/Option.svelte";
|
||||
import Options from "@/lib/src/UI/components/Options.svelte";
|
||||
import Instruction from "@/lib/src/UI/components/Instruction.svelte";
|
||||
import UserDecisions from "@/lib/src/UI/components/UserDecisions.svelte";
|
||||
const TYPE_NEW_USER = "new-user";
|
||||
const TYPE_EXISTING_USER = "existing-user";
|
||||
const TYPE_CANCELLED = "cancelled";
|
||||
type ResultType = typeof TYPE_NEW_USER | typeof TYPE_EXISTING_USER | typeof TYPE_CANCELLED;
|
||||
import DialogHeader from "@lib/UI/components/DialogHeader.svelte";
|
||||
import Guidance from "@lib/UI/components/Guidance.svelte";
|
||||
import Decision from "@lib/UI/components/Decision.svelte";
|
||||
import Question from "@lib/UI/components/Question.svelte";
|
||||
import Option from "@lib/UI/components/Option.svelte";
|
||||
import Options from "@lib/UI/components/Options.svelte";
|
||||
import Instruction from "@lib/UI/components/Instruction.svelte";
|
||||
import UserDecisions from "@lib/UI/components/UserDecisions.svelte";
|
||||
import { TYPE_NEW_USER, TYPE_EXISTING_USER, TYPE_CANCELLED, type IntroResultType } from "./setupDialogTypes";
|
||||
|
||||
type Props = {
|
||||
setResult: (result: ResultType) => void;
|
||||
setResult: (result: IntroResultType) => void;
|
||||
};
|
||||
const { setResult }: Props = $props();
|
||||
let userType = $state<ResultType>(TYPE_CANCELLED);
|
||||
let userType = $state<IntroResultType>(TYPE_CANCELLED);
|
||||
let proceedTitle = $derived.by(() => {
|
||||
if (userType === TYPE_NEW_USER) {
|
||||
return "Yes, I want to set up a new synchronisation";
|
||||
|
||||
@@ -7,16 +7,19 @@
|
||||
import Instruction from "@/lib/src/UI/components/Instruction.svelte";
|
||||
import UserDecisions from "@/lib/src/UI/components/UserDecisions.svelte";
|
||||
import InfoNote from "@/lib/src/UI/components/InfoNote.svelte";
|
||||
const TYPE_EXISTING = "existing-user";
|
||||
const TYPE_NEW = "new-user";
|
||||
const TYPE_COMPATIBLE_EXISTING = "compatible-existing-user";
|
||||
const TYPE_CANCELLED = "cancelled";
|
||||
type ResultType = typeof TYPE_EXISTING | typeof TYPE_NEW | typeof TYPE_COMPATIBLE_EXISTING | typeof TYPE_CANCELLED;
|
||||
import {
|
||||
type OutroAskUserModeResultType,
|
||||
TYPE_CANCELLED,
|
||||
TYPE_EXISTING,
|
||||
TYPE_NEW,
|
||||
TYPE_COMPATIBLE_EXISTING,
|
||||
} from "./setupDialogTypes";
|
||||
|
||||
type Props = {
|
||||
setResult: (result: ResultType) => void;
|
||||
setResult: (result: OutroAskUserModeResultType) => void;
|
||||
};
|
||||
const { setResult }: Props = $props();
|
||||
let userType = $state<ResultType>(TYPE_CANCELLED);
|
||||
let userType = $state<OutroAskUserModeResultType>(TYPE_CANCELLED);
|
||||
const canProceed = $derived.by(() => {
|
||||
return userType === TYPE_EXISTING || userType === TYPE_NEW || userType === TYPE_COMPATIBLE_EXISTING;
|
||||
});
|
||||
@@ -41,7 +44,11 @@
|
||||
</Guidance>
|
||||
<Instruction>
|
||||
<Question>Please select your situation.</Question>
|
||||
<Option title="I am setting up a new server for the first time / I want to reset my existing server." bind:value={userType} selectedValue={TYPE_NEW}>
|
||||
<Option
|
||||
title="I am setting up a new server for the first time / I want to reset my existing server."
|
||||
bind:value={userType}
|
||||
selectedValue={TYPE_NEW}
|
||||
>
|
||||
<InfoNote>
|
||||
Selecting this option will result in the current data on this device being used to initialise the server.
|
||||
Any existing data on the server will be completely overwritten.
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
<script lang="ts">
|
||||
import DialogHeader from "@/lib/src/UI/components/DialogHeader.svelte";
|
||||
import Guidance from "@/lib/src/UI/components/Guidance.svelte";
|
||||
import Decision from "@/lib/src/UI/components/Decision.svelte";
|
||||
import Question from "@/lib/src/UI/components/Question.svelte";
|
||||
import Instruction from "@/lib/src/UI/components/Instruction.svelte";
|
||||
import UserDecisions from "@/lib/src/UI/components/UserDecisions.svelte";
|
||||
const TYPE_APPLY = "apply";
|
||||
const TYPE_CANCELLED = "cancelled";
|
||||
type ResultType = typeof TYPE_APPLY | typeof TYPE_CANCELLED;
|
||||
import DialogHeader from "@lib/UI/components/DialogHeader.svelte";
|
||||
import Guidance from "@lib/UI/components/Guidance.svelte";
|
||||
import Decision from "@lib/UI/components/Decision.svelte";
|
||||
import Question from "@lib/UI/components/Question.svelte";
|
||||
import Instruction from "@lib/UI/components/Instruction.svelte";
|
||||
import UserDecisions from "@lib/UI/components/UserDecisions.svelte";
|
||||
|
||||
import { TYPE_CANCELLED, TYPE_APPLY, type OutroExistingUserResultType } from "./setupDialogTypes";
|
||||
type Props = {
|
||||
setResult: (result: ResultType) => void;
|
||||
setResult: (result: OutroExistingUserResultType) => void;
|
||||
};
|
||||
const { setResult }: Props = $props();
|
||||
</script>
|
||||
|
||||
@@ -5,14 +5,13 @@
|
||||
import Question from "@/lib/src/UI/components/Question.svelte";
|
||||
import Instruction from "@/lib/src/UI/components/Instruction.svelte";
|
||||
import UserDecisions from "@/lib/src/UI/components/UserDecisions.svelte";
|
||||
const TYPE_APPLY = "apply";
|
||||
const TYPE_CANCELLED = "cancelled";
|
||||
type ResultType = typeof TYPE_APPLY | typeof TYPE_CANCELLED;
|
||||
import { TYPE_APPLY, TYPE_CANCELLED, type OutroNewUserResultType } from "./setupDialogTypes";
|
||||
|
||||
type Props = {
|
||||
setResult: (result: ResultType) => void;
|
||||
setResult: (result: OutroNewUserResultType) => void;
|
||||
};
|
||||
const { setResult }: Props = $props();
|
||||
// let userType = $state<ResultType>(TYPE_CANCELLED);
|
||||
// let userType = $state<OutroNewUserResultType>(TYPE_CANCELLED);
|
||||
</script>
|
||||
|
||||
<DialogHeader title="Setup Complete: Preparing to Initialise Server" />
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
/**
|
||||
* Panel to check and fix CouchDB configuration issues
|
||||
*/
|
||||
import type { ObsidianLiveSyncSettings } from "../../../../lib/src/common/types";
|
||||
import Decision from "../../../../lib/src/UI/components/Decision.svelte";
|
||||
import UserDecisions from "../../../../lib/src/UI/components/UserDecisions.svelte";
|
||||
import type { ObsidianLiveSyncSettings } from "@lib/common/types";
|
||||
import Decision from "@lib/UI/components/Decision.svelte";
|
||||
import UserDecisions from "@lib/UI/components/UserDecisions.svelte";
|
||||
import { checkConfig, type ConfigCheckResult, type ResultError, type ResultErrorMessage } from "./utilCheckCouchDB";
|
||||
type Props = {
|
||||
trialRemoteSetting: ObsidianLiveSyncSettings;
|
||||
|
||||
@@ -10,29 +10,17 @@
|
||||
import InfoNote from "@/lib/src/UI/components/InfoNote.svelte";
|
||||
import ExtraItems from "@/lib/src/UI/components/ExtraItems.svelte";
|
||||
import Check from "@/lib/src/UI/components/Check.svelte";
|
||||
const TYPE_CANCEL = "cancelled";
|
||||
import {
|
||||
TYPE_CANCEL,
|
||||
TYPE_BACKUP_DONE,
|
||||
TYPE_BACKUP_SKIPPED,
|
||||
TYPE_UNABLE_TO_BACKUP,
|
||||
type RebuildEverythingResult,
|
||||
type ResultTypeBackup,
|
||||
} from "./setupDialogTypes";
|
||||
|
||||
const TYPE_BACKUP_DONE = "backup_done";
|
||||
const TYPE_BACKUP_SKIPPED = "backup_skipped";
|
||||
const TYPE_UNABLE_TO_BACKUP = "unable_to_backup";
|
||||
|
||||
type ResultTypeBackup =
|
||||
| typeof TYPE_BACKUP_DONE
|
||||
| typeof TYPE_BACKUP_SKIPPED
|
||||
| typeof TYPE_UNABLE_TO_BACKUP
|
||||
| typeof TYPE_CANCEL;
|
||||
|
||||
type ResultTypeExtra = {
|
||||
preventFetchingConfig: boolean;
|
||||
};
|
||||
type ResultType =
|
||||
| {
|
||||
backup: ResultTypeBackup;
|
||||
extra: ResultTypeExtra;
|
||||
}
|
||||
| typeof TYPE_CANCEL;
|
||||
type Props = {
|
||||
setResult: (result: ResultType) => void;
|
||||
setResult: (result: RebuildEverythingResult) => void;
|
||||
};
|
||||
const { setResult }: Props = $props();
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
import Decision from "@/lib/src/UI/components/Decision.svelte";
|
||||
import Instruction from "@/lib/src/UI/components/Instruction.svelte";
|
||||
import UserDecisions from "@/lib/src/UI/components/UserDecisions.svelte";
|
||||
const TYPE_CLOSE = "close";
|
||||
type ResultType = typeof TYPE_CLOSE;
|
||||
import { TYPE_CLOSE, type ScanQRCodeResultType } from "./setupDialogTypes";
|
||||
|
||||
type Props = {
|
||||
setResult: (_result: ResultType) => void;
|
||||
setResult: (_result: ScanQRCodeResultType) => void;
|
||||
};
|
||||
const { setResult }: Props = $props();
|
||||
</script>
|
||||
|
||||
@@ -7,16 +7,19 @@
|
||||
import Options from "@/lib/src/UI/components/Options.svelte";
|
||||
import Instruction from "@/lib/src/UI/components/Instruction.svelte";
|
||||
import UserDecisions from "@/lib/src/UI/components/UserDecisions.svelte";
|
||||
const TYPE_USE_SETUP_URI = "use-setup-uri";
|
||||
const TYPE_SCAN_QR_CODE = "scan-qr-code";
|
||||
const TYPE_CONFIGURE_MANUALLY = "configure-manually";
|
||||
const TYPE_CANCELLED = "cancelled";
|
||||
type ResultType = typeof TYPE_USE_SETUP_URI | typeof TYPE_SCAN_QR_CODE | typeof TYPE_CONFIGURE_MANUALLY | typeof TYPE_CANCELLED;
|
||||
import {
|
||||
TYPE_USE_SETUP_URI,
|
||||
TYPE_SCAN_QR_CODE,
|
||||
TYPE_CONFIGURE_MANUALLY,
|
||||
TYPE_CANCELLED,
|
||||
type SelectMethodExistingResultType,
|
||||
} from "./setupDialogTypes";
|
||||
|
||||
type Props = {
|
||||
setResult: (result: ResultType) => void;
|
||||
setResult: (result: SelectMethodExistingResultType) => void;
|
||||
};
|
||||
const { setResult }: Props = $props();
|
||||
let userType = $state<ResultType>(TYPE_CANCELLED);
|
||||
let userType = $state<SelectMethodExistingResultType>(TYPE_CANCELLED);
|
||||
let proceedTitle = $derived.by(() => {
|
||||
if (userType === TYPE_USE_SETUP_URI) {
|
||||
return "Proceed with Setup URI";
|
||||
|
||||
@@ -7,15 +7,18 @@
|
||||
import Options from "@/lib/src/UI/components/Options.svelte";
|
||||
import Instruction from "@/lib/src/UI/components/Instruction.svelte";
|
||||
import UserDecisions from "@/lib/src/UI/components/UserDecisions.svelte";
|
||||
const TYPE_USE_SETUP_URI = "use-setup-uri";
|
||||
const TYPE_CONFIGURE_MANUALLY = "configure-manually";
|
||||
const TYPE_CANCELLED = "cancelled";
|
||||
type ResultType = typeof TYPE_USE_SETUP_URI | typeof TYPE_CONFIGURE_MANUALLY | typeof TYPE_CANCELLED;
|
||||
import {
|
||||
TYPE_USE_SETUP_URI,
|
||||
TYPE_CONFIGURE_MANUALLY,
|
||||
TYPE_CANCELLED,
|
||||
type SelectMethodNewUserResultType,
|
||||
} from "./setupDialogTypes";
|
||||
|
||||
type Props = {
|
||||
setResult: (result: ResultType) => void;
|
||||
setResult: (result: SelectMethodNewUserResultType) => void;
|
||||
};
|
||||
const { setResult }: Props = $props();
|
||||
let userType = $state<ResultType>(TYPE_CANCELLED);
|
||||
let userType = $state<SelectMethodNewUserResultType>(TYPE_CANCELLED);
|
||||
let proceedTitle = $derived.by(() => {
|
||||
if (userType === TYPE_USE_SETUP_URI) {
|
||||
return "Proceed with Setup URI";
|
||||
|
||||
@@ -6,16 +6,19 @@
|
||||
import Options from "@/lib/src/UI/components/Options.svelte";
|
||||
import Instruction from "@/lib/src/UI/components/Instruction.svelte";
|
||||
import UserDecisions from "@/lib/src/UI/components/UserDecisions.svelte";
|
||||
const TYPE_COUCHDB = "couchdb";
|
||||
const TYPE_BUCKET = "bucket";
|
||||
const TYPE_P2P = "p2p";
|
||||
const TYPE_CANCELLED = "cancelled";
|
||||
type ResultType = typeof TYPE_COUCHDB | typeof TYPE_BUCKET | typeof TYPE_P2P | typeof TYPE_CANCELLED;
|
||||
import {
|
||||
TYPE_COUCHDB,
|
||||
TYPE_BUCKET,
|
||||
TYPE_P2P,
|
||||
TYPE_CANCELLED,
|
||||
type SetupRemoteResultType,
|
||||
} from "./setupDialogTypes";
|
||||
|
||||
type Props = {
|
||||
setResult: (result: ResultType) => void;
|
||||
setResult: (result: SetupRemoteResultType) => void;
|
||||
};
|
||||
const { setResult }: Props = $props();
|
||||
let userType = $state<ResultType>(TYPE_CANCELLED);
|
||||
let userType = $state<SetupRemoteResultType>(TYPE_CANCELLED);
|
||||
let proceedTitle = $derived.by(() => {
|
||||
if (userType === TYPE_COUCHDB) {
|
||||
return "Continue to CouchDB setup";
|
||||
|
||||
@@ -13,19 +13,18 @@
|
||||
DEFAULT_SETTINGS,
|
||||
PREFERRED_JOURNAL_SYNC,
|
||||
RemoteTypes,
|
||||
} from "../../../../lib/src/common/types";
|
||||
} from "@lib/common/types";
|
||||
|
||||
import { onMount } from "svelte";
|
||||
import { getDialogContext, type GuestDialogProps } from "../../../../lib/src/UI/svelteDialog";
|
||||
import { copyTo, pickBucketSyncSettings } from "../../../../lib/src/common/utils";
|
||||
import { getDialogContext, type GuestDialogProps } from "@lib/UI/svelteDialog";
|
||||
import { copyTo, pickBucketSyncSettings } from "@lib/common/utils";
|
||||
import { TYPE_CANCELLED, type SetupRemoteBucketResultType } from "./setupDialogTypes";
|
||||
|
||||
const default_setting = pickBucketSyncSettings(DEFAULT_SETTINGS);
|
||||
|
||||
let syncSetting = $state<BucketSyncSetting>({ ...default_setting });
|
||||
|
||||
type ResultType = typeof TYPE_CANCELLED | BucketSyncSetting;
|
||||
type Props = GuestDialogProps<ResultType, BucketSyncSetting>;
|
||||
const TYPE_CANCELLED = "cancelled";
|
||||
type Props = GuestDialogProps<SetupRemoteBucketResultType, BucketSyncSetting>;
|
||||
|
||||
const { setResult, getInitialData }: Props = $props();
|
||||
|
||||
|
||||
@@ -14,20 +14,19 @@
|
||||
RemoteTypes,
|
||||
type CouchDBConnection,
|
||||
type ObsidianLiveSyncSettings,
|
||||
} from "../../../../lib/src/common/types";
|
||||
import { isCloudantURI } from "../../../../lib/src/pouchdb/utils_couchdb";
|
||||
} from "@lib/common/types";
|
||||
import { isCloudantURI } from "@lib/pouchdb/utils_couchdb";
|
||||
|
||||
import { onMount } from "svelte";
|
||||
import { getDialogContext, type GuestDialogProps } from "../../../../lib/src/UI/svelteDialog";
|
||||
import { copyTo, pickCouchDBSyncSettings } from "../../../../lib/src/common/utils";
|
||||
import { getDialogContext, type GuestDialogProps } from "@lib/UI/svelteDialog";
|
||||
import { copyTo, pickCouchDBSyncSettings } from "@lib/common/utils";
|
||||
import PanelCouchDBCheck from "./PanelCouchDBCheck.svelte";
|
||||
import { TYPE_CANCELLED, type SetupRemoteCouchDBResultType } from "./setupDialogTypes";
|
||||
|
||||
const default_setting = pickCouchDBSyncSettings(DEFAULT_SETTINGS);
|
||||
|
||||
let syncSetting = $state<CouchDBConnection>({ ...default_setting });
|
||||
type ResultType = typeof TYPE_CANCELLED | CouchDBConnection;
|
||||
const TYPE_CANCELLED = "cancelled";
|
||||
type Props = GuestDialogProps<ResultType, CouchDBConnection>;
|
||||
type Props = GuestDialogProps<SetupRemoteCouchDBResultType, CouchDBConnection>;
|
||||
const { setResult, getInitialData }: Props = $props();
|
||||
onMount(() => {
|
||||
if (getInitialData) {
|
||||
|
||||
@@ -12,13 +12,13 @@
|
||||
E2EEAlgorithmNames,
|
||||
E2EEAlgorithms,
|
||||
type EncryptionSettings,
|
||||
} from "../../../../lib/src/common/types";
|
||||
} from "@lib/common/types";
|
||||
import { onMount } from "svelte";
|
||||
import type { GuestDialogProps } from "../../../../lib/src/UI/svelteDialog";
|
||||
import { copyTo, pickEncryptionSettings } from "../../../../lib/src/common/utils";
|
||||
const TYPE_CANCELLED = "cancelled";
|
||||
type ResultType = typeof TYPE_CANCELLED | EncryptionSettings;
|
||||
type Props = GuestDialogProps<ResultType, EncryptionSettings>;
|
||||
import type { GuestDialogProps } from "@lib/UI/svelteDialog";
|
||||
import { copyTo, pickEncryptionSettings } from "@lib/common/utils";
|
||||
import { TYPE_CANCELLED, type SetupRemoteE2EEResultType } from "./setupDialogTypes";
|
||||
|
||||
type Props = GuestDialogProps<SetupRemoteE2EEResultType, EncryptionSettings>;
|
||||
const { setResult, getInitialData }: Props = $props();
|
||||
let default_encryption: EncryptionSettings = {
|
||||
encrypt: true,
|
||||
|
||||
@@ -26,16 +26,14 @@
|
||||
import { getDialogContext, type GuestDialogProps } from "@lib/UI/svelteDialog";
|
||||
import { SETTING_KEY_P2P_DEVICE_NAME } from "@lib/common/types";
|
||||
import ExtraItems from "@lib/UI/components/ExtraItems.svelte";
|
||||
import { TYPE_CANCELLED, type SetupRemoteP2PResultType } from "./setupDialogTypes";
|
||||
|
||||
const default_setting = pickP2PSyncSettings(DEFAULT_SETTINGS);
|
||||
let syncSetting = $state<P2PConnectionInfo>({ ...default_setting });
|
||||
|
||||
const context = getDialogContext();
|
||||
let error = $state("");
|
||||
const TYPE_CANCELLED = "cancelled";
|
||||
type SettingInfo = P2PConnectionInfo;
|
||||
type ResultType = typeof TYPE_CANCELLED | SettingInfo;
|
||||
type Props = GuestDialogProps<ResultType, P2PSyncSetting>;
|
||||
type Props = GuestDialogProps<SetupRemoteP2PResultType, P2PSyncSetting>;
|
||||
|
||||
const { setResult, getInitialData }: Props = $props();
|
||||
onMount(() => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { configURIBase } from "../../../../common/types";
|
||||
import type { ObsidianLiveSyncSettings } from "../../../../lib/src/common/types";
|
||||
import { configURIBase } from "@/common/types";
|
||||
import type { ObsidianLiveSyncSettings } from "@lib/common/types";
|
||||
import DialogHeader from "@/lib/src/UI/components/DialogHeader.svelte";
|
||||
import Guidance from "@/lib/src/UI/components/Guidance.svelte";
|
||||
import Decision from "@/lib/src/UI/components/Decision.svelte";
|
||||
@@ -10,11 +10,11 @@
|
||||
import Password from "@/lib/src/UI/components/Password.svelte";
|
||||
|
||||
import { onMount } from "svelte";
|
||||
import { decryptString } from "../../../../lib/src/encryption/stringEncryption.ts";
|
||||
import type { GuestDialogProps } from "../../../../lib/src/UI/svelteDialog.ts";
|
||||
const TYPE_CANCELLED = "cancelled";
|
||||
type ResultType = typeof TYPE_CANCELLED | ObsidianLiveSyncSettings;
|
||||
type Props = GuestDialogProps<ResultType, string>;
|
||||
import { decryptString } from "@lib/encryption/stringEncryption.ts";
|
||||
import type { GuestDialogProps } from "@lib/UI/svelteDialog.ts";
|
||||
import { TYPE_CANCELLED, type UseSetupURIResultType } from "./setupDialogTypes";
|
||||
|
||||
type Props = GuestDialogProps<UseSetupURIResultType, string>;
|
||||
const { setResult, getInitialData }: Props = $props();
|
||||
|
||||
let setupURI = $state("");
|
||||
|
||||
108
src/modules/features/SetupWizard/dialogs/setupDialogTypes.ts
Normal file
108
src/modules/features/SetupWizard/dialogs/setupDialogTypes.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import type {
|
||||
BucketSyncSetting,
|
||||
CouchDBConnection,
|
||||
EncryptionSettings,
|
||||
ObsidianLiveSyncSettings,
|
||||
P2PConnectionInfo,
|
||||
} from "@lib/common/models/setting.type";
|
||||
|
||||
export const TYPE_IDENTICAL = "identical";
|
||||
export const TYPE_INDEPENDENT = "independent";
|
||||
export const TYPE_UNBALANCED = "unbalanced";
|
||||
export const TYPE_CANCEL = "cancelled";
|
||||
|
||||
export const TYPE_BACKUP_DONE = "backup_done";
|
||||
export const TYPE_BACKUP_SKIPPED = "backup_skipped";
|
||||
export const TYPE_UNABLE_TO_BACKUP = "unable_to_backup";
|
||||
|
||||
// Intro
|
||||
export const TYPE_NEW_USER = "new-user";
|
||||
export const TYPE_EXISTING_USER = "existing-user";
|
||||
export const TYPE_CANCELLED = "cancelled";
|
||||
|
||||
// Outro ask user mode
|
||||
export const TYPE_EXISTING = "existing-user";
|
||||
export const TYPE_NEW = "new-user";
|
||||
export const TYPE_COMPATIBLE_EXISTING = "compatible-existing-user";
|
||||
|
||||
// OutroExistingUser
|
||||
export const TYPE_APPLY = "apply";
|
||||
|
||||
// Select methods
|
||||
export const TYPE_USE_SETUP_URI = "use-setup-uri";
|
||||
export const TYPE_SCAN_QR_CODE = "scan-qr-code";
|
||||
export const TYPE_CONFIGURE_MANUALLY = "configure-manually";
|
||||
|
||||
// ScanQRCode
|
||||
export const TYPE_CLOSE = "close";
|
||||
|
||||
// SetupRemote
|
||||
export const TYPE_COUCHDB = "couchdb";
|
||||
export const TYPE_BUCKET = "bucket";
|
||||
export const TYPE_P2P = "p2p";
|
||||
|
||||
export type ResultTypeVault =
|
||||
| typeof TYPE_IDENTICAL
|
||||
| typeof TYPE_INDEPENDENT
|
||||
| typeof TYPE_UNBALANCED
|
||||
| typeof TYPE_CANCEL;
|
||||
export type ResultTypeBackup =
|
||||
| typeof TYPE_BACKUP_DONE
|
||||
| typeof TYPE_BACKUP_SKIPPED
|
||||
| typeof TYPE_UNABLE_TO_BACKUP
|
||||
| typeof TYPE_CANCEL;
|
||||
|
||||
export type ResultTypeExtra = {
|
||||
preventFetchingConfig: boolean;
|
||||
};
|
||||
export type FetchEverythingResult =
|
||||
| {
|
||||
vault: ResultTypeVault;
|
||||
backup: ResultTypeBackup;
|
||||
extra: ResultTypeExtra;
|
||||
}
|
||||
| typeof TYPE_CANCEL;
|
||||
|
||||
export type RebuildEverythingResult =
|
||||
| {
|
||||
backup: ResultTypeBackup;
|
||||
extra: ResultTypeExtra;
|
||||
}
|
||||
| typeof TYPE_CANCEL;
|
||||
|
||||
export type IntroResultType = typeof TYPE_NEW_USER | typeof TYPE_EXISTING_USER | typeof TYPE_CANCELLED;
|
||||
|
||||
export type OutroAskUserModeResultType =
|
||||
| typeof TYPE_EXISTING
|
||||
| typeof TYPE_NEW
|
||||
| typeof TYPE_COMPATIBLE_EXISTING
|
||||
| typeof TYPE_CANCELLED;
|
||||
|
||||
export type OutroExistingUserResultType = typeof TYPE_APPLY | typeof TYPE_CANCELLED;
|
||||
|
||||
export type OutroNewUserResultType = typeof TYPE_APPLY | typeof TYPE_CANCELLED;
|
||||
|
||||
export type SelectMethodNewUserResultType =
|
||||
| typeof TYPE_USE_SETUP_URI
|
||||
| typeof TYPE_CONFIGURE_MANUALLY
|
||||
| typeof TYPE_CANCELLED;
|
||||
|
||||
export type SelectMethodExistingResultType =
|
||||
| typeof TYPE_USE_SETUP_URI
|
||||
| typeof TYPE_SCAN_QR_CODE
|
||||
| typeof TYPE_CONFIGURE_MANUALLY
|
||||
| typeof TYPE_CANCELLED;
|
||||
|
||||
export type SetupRemoteResultType = typeof TYPE_COUCHDB | typeof TYPE_BUCKET | typeof TYPE_P2P | typeof TYPE_CANCELLED;
|
||||
|
||||
export type UseSetupURIResultType = typeof TYPE_CANCELLED | ObsidianLiveSyncSettings;
|
||||
|
||||
export type SetupRemoteE2EEResultType = typeof TYPE_CANCELLED | EncryptionSettings;
|
||||
|
||||
export type SetupRemoteBucketResultType = typeof TYPE_CANCELLED | BucketSyncSetting;
|
||||
|
||||
export type SetupRemoteCouchDBResultType = typeof TYPE_CANCELLED | CouchDBConnection;
|
||||
|
||||
export type SetupRemoteP2PResultType = typeof TYPE_CANCELLED | P2PConnectionInfo;
|
||||
|
||||
export type ScanQRCodeResultType = typeof TYPE_CLOSE;
|
||||
@@ -14,6 +14,7 @@ import type { InjectableServiceHub } from "@lib/services/implements/injectable/I
|
||||
import type { LiveSyncCore } from "../../main.ts";
|
||||
import { initialiseWorkerModule } from "@lib/worker/bgWorker.ts";
|
||||
import { manifestVersion, packageVersion } from "@lib/common/coreEnvVars.ts";
|
||||
import { compatGlobal } from "@lib/common/coreEnvFunctions.ts";
|
||||
|
||||
export class ModuleLiveSyncMain extends AbstractModule {
|
||||
async _onLiveSyncReady() {
|
||||
@@ -97,7 +98,7 @@ export class ModuleLiveSyncMain extends AbstractModule {
|
||||
return false;
|
||||
}
|
||||
const lsKey = "obsidian-live-sync-ver" + this.services.vault.getVaultName();
|
||||
const last_version = localStorage.getItem(lsKey);
|
||||
const last_version = compatGlobal.localStorage.getItem(lsKey);
|
||||
|
||||
const lastVersion = ~~(versionNumberString2Number(manifestVersion) / 1000);
|
||||
if (lastVersion > this.settings.lastReadUpdates && this.settings.isConfigured) {
|
||||
@@ -119,7 +120,7 @@ export class ModuleLiveSyncMain extends AbstractModule {
|
||||
this.settings.versionUpFlash = $msg("moduleLiveSyncMain.logVersionUpdate");
|
||||
await this.saveSettings();
|
||||
}
|
||||
localStorage.setItem(lsKey, `${VER}`);
|
||||
compatGlobal.localStorage.setItem(lsKey, `${VER}`);
|
||||
await this.services.database.openDatabase({
|
||||
databaseEvents: this.services.databaseEvents,
|
||||
replicator: this.services.replicator,
|
||||
@@ -129,7 +130,7 @@ export class ModuleLiveSyncMain extends AbstractModule {
|
||||
// this.$$replicate = this.$$replicate.bind(this);
|
||||
// this.core.$$onLiveSyncReady = this.core.$$onLiveSyncReady.bind(this);
|
||||
await this.core.services.appLifecycle.onLoaded();
|
||||
await Promise.all(this.core.addOns.map((e) => e.onload()));
|
||||
await Promise.all(this.core.addOns.map((e) => Promise.resolve(e.onload())));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { compatGlobal } from "@/lib/src/common/coreEnvFunctions";
|
||||
import { type ObsidianLiveSyncSettings } from "@lib/common/types";
|
||||
import { EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED } from "@lib/events/coreEvents";
|
||||
import { eventHub } from "@lib/hub/hub";
|
||||
@@ -17,13 +18,16 @@ export class ObsidianSettingService<T extends ObsidianServiceContext> extends Se
|
||||
});
|
||||
}
|
||||
protected setItem(key: string, value: string) {
|
||||
return localStorage.setItem(key, value);
|
||||
// TODO: Implement nativeLocalStorage.
|
||||
return compatGlobal.localStorage.setItem(key, value);
|
||||
}
|
||||
protected getItem(key: string): string {
|
||||
return localStorage.getItem(key) ?? "";
|
||||
// TODO: Implement nativeLocalStorage.
|
||||
return compatGlobal.localStorage.getItem(key) ?? "";
|
||||
}
|
||||
protected deleteItem(key: string): void {
|
||||
localStorage.removeItem(key);
|
||||
// TODO: Implement nativeLocalStorage.
|
||||
compatGlobal.localStorage.removeItem(key);
|
||||
}
|
||||
|
||||
protected override async saveData(data: ObsidianLiveSyncSettings): Promise<void> {
|
||||
|
||||
197
src/serviceFeatures/redFlag.simpleFetch.ts
Normal file
197
src/serviceFeatures/redFlag.simpleFetch.ts
Normal file
@@ -0,0 +1,197 @@
|
||||
import { LOG_LEVEL_NOTICE } from "octagonal-wheels/common/logger";
|
||||
import type { NecessaryServices } from "@lib/interfaces/ServiceModule";
|
||||
import { type LogFunction } from "@lib/services/lib/logUtils";
|
||||
import { UnresolvedErrorManager } from "@lib/services/base/UnresolvedErrorManager";
|
||||
import {
|
||||
ExtraOnLocal,
|
||||
ExtraOnRemote,
|
||||
FullScanModes,
|
||||
normaliseFullScanOptions,
|
||||
synchroniseAllFilesBetweenDBandStorage,
|
||||
type FullScanOptions,
|
||||
} from "@lib/serviceFeatures/offlineScanner";
|
||||
import { adjustSettingToRemoteIfNeeded, processVaultInitialisation } from "./redFlag";
|
||||
|
||||
export const SIMPLE_FETCH_STAGE1_REMOTE_WINS = "Overwrite all with remote files";
|
||||
export const SIMPLE_FETCH_STAGE1_NEWER_WINS = "Compare time and take newer";
|
||||
export const SIMPLE_FETCH_STAGE1_LEGACY = "Use the detailed flow";
|
||||
export const SIMPLE_FETCH_STAGE1_CANCEL = "Cancel";
|
||||
|
||||
export const SIMPLE_FETCH_STAGE2_REMOTE_DELETE_NONE = "Keep local files even if not on remote";
|
||||
export const SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL = "Delete local files if not on remote";
|
||||
|
||||
export const SIMPLE_FETCH_STAGE2_NEWER_CLEANUP = "Delete local files if deleted on remote";
|
||||
export const SIMPLE_FETCH_STAGE2_NEWER_SYNC_ALL = "Keep local files even if deleted on remote";
|
||||
export const STAGE2_ABORT = "Cancel all and reboot";
|
||||
|
||||
export async function askSimpleFetchMode(
|
||||
host: NecessaryServices<"UI" | "vault", "storageAccess">
|
||||
): Promise<{ mode: string; options: Partial<FullScanOptions> } | "cancelled" | "aborted"> {
|
||||
const msg = `We are about to retrieve the remote data.
|
||||
|
||||
Firstly, how shall we handle the data retrieved from this remote server?
|
||||
|
||||
- **${SIMPLE_FETCH_STAGE1_NEWER_WINS}**: Compares the modified time of files and takes the newer one.
|
||||
If you have been using Self-hosted LiveSync and have made changes on multiple devices, this option may be suitable for you as it tries to merge changes based on modified time.
|
||||
- **${SIMPLE_FETCH_STAGE1_REMOTE_WINS}**: Remote data is the source of truth.
|
||||
If you are new to using Self-hosted LiveSync. This option may be easiest to understand and get started with.
|
||||
It will overwrite all your local files with the remote data, so please make sure you have a backup if there is any important data in your vault.
|
||||
- **${SIMPLE_FETCH_STAGE1_LEGACY}**: Opens the detailed setup wizard.
|
||||
If you want to have more control over the synchronisation process, or want to review the changes before applying, you can choose this option to use the detailed flow.
|
||||
`;
|
||||
const stage1 = await host.services.UI.confirm.confirmWithMessage(
|
||||
"Data retrieval scheduled",
|
||||
msg,
|
||||
[
|
||||
SIMPLE_FETCH_STAGE1_NEWER_WINS,
|
||||
SIMPLE_FETCH_STAGE1_REMOTE_WINS,
|
||||
SIMPLE_FETCH_STAGE1_LEGACY,
|
||||
SIMPLE_FETCH_STAGE1_CANCEL,
|
||||
],
|
||||
SIMPLE_FETCH_STAGE1_NEWER_WINS,
|
||||
0
|
||||
);
|
||||
|
||||
if (!stage1 || stage1 === SIMPLE_FETCH_STAGE1_CANCEL) return "cancelled";
|
||||
|
||||
if (stage1 === SIMPLE_FETCH_STAGE1_LEGACY) {
|
||||
return { mode: "legacy", options: {} };
|
||||
}
|
||||
|
||||
if (stage1 === SIMPLE_FETCH_STAGE1_REMOTE_WINS) {
|
||||
const msg = `Since you have chosen to overwrite all local files with remote data, **how would you like to handle local files that are not present in the remote database?**
|
||||
|
||||
- **${SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL}**: Local-only files and remote-deleted files will be removed.
|
||||
This option will make your local vault exactly the same as the remote database, but please make sure you have a backup if there is any important data in your vault.
|
||||
- **${SIMPLE_FETCH_STAGE2_REMOTE_DELETE_NONE}**: All existing local files will be preserved.
|
||||
This option will keep all your local files, but it may cause duplicates if there are files that exist on local but not on remote. You can clean up these duplicates manually after the synchronisation.`;
|
||||
|
||||
const stage2 = await host.services.UI.confirm.confirmWithMessage(
|
||||
"How to handle extra existing local files?",
|
||||
msg,
|
||||
[SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL, SIMPLE_FETCH_STAGE2_REMOTE_DELETE_NONE, STAGE2_ABORT],
|
||||
SIMPLE_FETCH_STAGE2_REMOTE_DELETE_NONE,
|
||||
0
|
||||
);
|
||||
if (!stage2) return "cancelled";
|
||||
if (stage2 === STAGE2_ABORT) {
|
||||
return "aborted";
|
||||
}
|
||||
return {
|
||||
mode: "remote-only",
|
||||
options: {
|
||||
mode: FullScanModes.DB_APPLY,
|
||||
extraOnRemote:
|
||||
stage2 === SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL ? ExtraOnRemote.DELETE_LOCAL_MISSING : undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (stage1 === SIMPLE_FETCH_STAGE1_NEWER_WINS) {
|
||||
const msg = `How should files that were deleted on other devices be handled?
|
||||
|
||||
- **${SIMPLE_FETCH_STAGE2_NEWER_CLEANUP}**: Delete local files if they were deleted on remote.
|
||||
This is useful if you want to keep your vault clean and consistent across devices, but please make sure you have a backup if there is already any important data in your vault.
|
||||
- **${SIMPLE_FETCH_STAGE2_NEWER_SYNC_ALL}**: Recreate remote files even if they were deleted on remote.
|
||||
This option will keep all your local files, but it may cause duplicates if there are files that exist on local but not on remote. You can clean up these duplicates manually after the synchronisation.
|
||||
`;
|
||||
|
||||
const stage2 = await host.services.UI.confirm.confirmWithMessage(
|
||||
"Conflict & Deletion Options",
|
||||
msg,
|
||||
[SIMPLE_FETCH_STAGE2_NEWER_CLEANUP, SIMPLE_FETCH_STAGE2_NEWER_SYNC_ALL, STAGE2_ABORT],
|
||||
SIMPLE_FETCH_STAGE2_NEWER_SYNC_ALL,
|
||||
0
|
||||
);
|
||||
if (!stage2) return "cancelled";
|
||||
if (stage2 === STAGE2_ABORT) {
|
||||
return "aborted";
|
||||
}
|
||||
return {
|
||||
mode: "newer-wins",
|
||||
options: {
|
||||
mode: FullScanModes.NEWER_WINS,
|
||||
extraOnLocal:
|
||||
stage2 === SIMPLE_FETCH_STAGE2_NEWER_CLEANUP
|
||||
? ExtraOnLocal.DELETE_DB_DELETED
|
||||
: ExtraOnLocal.APPEND_STORAGE_ONLY,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return "cancelled";
|
||||
}
|
||||
|
||||
const RERUN_PROCESS = "Reboot to re-run the process";
|
||||
const RELEASE_FLAG_PROCESS = "Finalise the process and resume normal operation";
|
||||
export async function askAndPerformFastSetupOnScheduledFetchAll(
|
||||
host: NecessaryServices<
|
||||
| "vault"
|
||||
| "fileProcessing"
|
||||
| "tweakValue"
|
||||
| "UI"
|
||||
| "setting"
|
||||
| "appLifecycle"
|
||||
| "path"
|
||||
| "keyValueDB"
|
||||
| "database",
|
||||
"storageAccess" | "rebuilder" | "fileHandler"
|
||||
>,
|
||||
log: LogFunction,
|
||||
cleanupFlag: () => Promise<void>
|
||||
): Promise<boolean | undefined> {
|
||||
const result = await askSimpleFetchMode(host);
|
||||
if (result === "cancelled") {
|
||||
log("Fetch cancelled by user.", LOG_LEVEL_NOTICE);
|
||||
await cleanupFlag();
|
||||
host.services.appLifecycle.performRestart();
|
||||
return false;
|
||||
}
|
||||
if (result === "aborted") {
|
||||
log("Fetch exited by user.", LOG_LEVEL_NOTICE);
|
||||
host.services.appLifecycle.performRestart();
|
||||
return false;
|
||||
}
|
||||
if (result.mode === "legacy") {
|
||||
return undefined; // Let the legacy flow handle it.
|
||||
}
|
||||
|
||||
return await processVaultInitialisation(host, log, async () => {
|
||||
const settings = host.services.setting.currentSettings();
|
||||
await adjustSettingToRemoteIfNeeded(host, log, { preventFetchingConfig: false }, settings);
|
||||
// 1. Perform fast DB fetch (download remote DB content to local DB)
|
||||
await host.serviceModules.rebuilder.$fetchLocalDBFast(false);
|
||||
|
||||
// 2. Call the extended synchroniseAllFilesBetweenDBandStorage to reflect changes in storage
|
||||
const errorManager = new UnresolvedErrorManager(host.services.appLifecycle);
|
||||
const syncResult = await synchroniseAllFilesBetweenDBandStorage(
|
||||
host,
|
||||
log,
|
||||
errorManager,
|
||||
normaliseFullScanOptions({
|
||||
...result.options,
|
||||
showingNotice: true,
|
||||
omitEvents: true,
|
||||
ignoreSuspending: true,
|
||||
})
|
||||
);
|
||||
if (!syncResult) {
|
||||
const canRelease = await host.services.UI.confirm.askSelectStringDialogue(
|
||||
"Some files failed to synchronise. What would you like to do?",
|
||||
[RERUN_PROCESS, RELEASE_FLAG_PROCESS],
|
||||
{ defaultAction: RELEASE_FLAG_PROCESS, title: "Synchronisation Issues Detected" }
|
||||
);
|
||||
if (canRelease === RERUN_PROCESS) {
|
||||
log("User chose to reboot and re-run the process.", LOG_LEVEL_NOTICE);
|
||||
// Prevent to delete the flag, so that the process will be re-run after reboot.
|
||||
// await cleanupFlag();
|
||||
host.services.appLifecycle.performRestart();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
await host.serviceModules.rebuilder.finishRebuild();
|
||||
await cleanupFlag();
|
||||
log("Simple fetch and scan operation completed.", LOG_LEVEL_NOTICE);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
@@ -8,6 +8,13 @@ import { extractObject } from "octagonal-wheels/object";
|
||||
import { REMOTE_MINIO, REMOTE_P2P } from "@lib/common/models/setting.const";
|
||||
import type { ObsidianLiveSyncSettings } from "@lib/common/models/setting.type";
|
||||
import { TweakValuesShouldMatchedTemplate } from "@lib/common/models/tweak.definition";
|
||||
import type {
|
||||
FetchEverythingResult,
|
||||
RebuildEverythingResult,
|
||||
} from "@/modules/features/SetupWizard/dialogs/setupDialogTypes";
|
||||
import { askAndPerformFastSetupOnScheduledFetchAll } from "./redFlag.simpleFetch";
|
||||
import { ConnectionStringParser } from "@lib/common/ConnectionString";
|
||||
import { activateRemoteConfiguration } from "@lib/serviceFeatures/remoteConfig";
|
||||
|
||||
/**
|
||||
* Flag file handler interface, similar to target filter pattern.
|
||||
@@ -41,14 +48,79 @@ export async function deleteFlagFile(host: NecessaryServices<never, "storageAcce
|
||||
log(ex, LOG_LEVEL_VERBOSE);
|
||||
}
|
||||
}
|
||||
const REMOTE_KEEP_CURRENT = "Use active remote";
|
||||
const REMOTE_CANCEL = "Cancel";
|
||||
async function askAndActivateRemoteDatabase(host: NecessaryServices<"UI" | "setting", any>, log: LogFunction) {
|
||||
const settings = host.services.setting.currentSettings();
|
||||
if (settings.remoteConfigurations && Object.keys(settings.remoteConfigurations).length > 1) {
|
||||
const message =
|
||||
"Multiple remote configurations detected. Please select the remote configuration you want to fetch from.";
|
||||
const options = Object.entries(settings.remoteConfigurations).map(([id, config]) => {
|
||||
const parsed = ConnectionStringParser.parse(config.uri);
|
||||
const displayURI = (config.uri.split("@").pop() || "").substring(0, 20) + "..."; // Show only the last part of URI for better readability and privacy.
|
||||
return {
|
||||
name: `${config.name} - ${parsed.type} (${displayURI})`,
|
||||
id: id,
|
||||
};
|
||||
});
|
||||
options.push({
|
||||
name: REMOTE_KEEP_CURRENT,
|
||||
id: "keep_current",
|
||||
});
|
||||
options.push({
|
||||
name: REMOTE_CANCEL,
|
||||
id: "cancel",
|
||||
});
|
||||
|
||||
const selections = options.map((option) => option.name);
|
||||
// const defaultAction =
|
||||
// options.find((option) => option.id === settings.activeConfigurationId)?.name || selections[0];
|
||||
const selectedId = await host.services.UI.confirm.askSelectStringDialogue(message, selections, {
|
||||
title: "Select Remote Configuration",
|
||||
defaultAction: REMOTE_KEEP_CURRENT,
|
||||
});
|
||||
const selectedConfig = options.find((option) => option.name === selectedId);
|
||||
if (selectedConfig) {
|
||||
if (selectedConfig.id === "keep_current") {
|
||||
log(`Keeping current remote configuration.`, LOG_LEVEL_INFO);
|
||||
return true;
|
||||
}
|
||||
if (selectedConfig.id === "cancel") {
|
||||
log(`Remote configuration selection cancelled.`, LOG_LEVEL_NOTICE);
|
||||
return false;
|
||||
}
|
||||
const activated = activateRemoteConfiguration(settings, selectedConfig.id);
|
||||
if (activated) {
|
||||
await host.services.setting.applyPartial(activated);
|
||||
log(`Activated remote configuration: ${selectedConfig.name}`, LOG_LEVEL_INFO);
|
||||
return true;
|
||||
} else {
|
||||
log(`Failed to activate remote configuration: ${selectedConfig.name}`, LOG_LEVEL_NOTICE);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
log(`No remote configuration selected.`, LOG_LEVEL_NOTICE);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true; // If there is only one or no remote configuration, proceed without asking.
|
||||
}
|
||||
/**
|
||||
* Factory function to create a fetch all flag handler.
|
||||
* All logic related to fetch all flag is encapsulated here.
|
||||
*/
|
||||
export function createFetchAllFlagHandler(
|
||||
host: NecessaryServices<
|
||||
"vault" | "fileProcessing" | "tweakValue" | "UI" | "setting" | "appLifecycle",
|
||||
"storageAccess" | "rebuilder"
|
||||
| "vault"
|
||||
| "fileProcessing"
|
||||
| "tweakValue"
|
||||
| "UI"
|
||||
| "setting"
|
||||
| "appLifecycle"
|
||||
| "path"
|
||||
| "keyValueDB"
|
||||
| "database",
|
||||
"storageAccess" | "rebuilder" | "fileHandler"
|
||||
>,
|
||||
log: LogFunction
|
||||
): FlagFileHandler {
|
||||
@@ -65,7 +137,21 @@ export function createFetchAllFlagHandler(
|
||||
|
||||
// Handle the fetch all scheduled operation
|
||||
const onScheduled = async () => {
|
||||
const method = await host.services.UI.dialogManager.openWithExplicitCancel(FetchEverything);
|
||||
// Select the remote database if there are multiple remotes configured.
|
||||
const isRemoteActivated = await askAndActivateRemoteDatabase(host, log);
|
||||
if (!isRemoteActivated) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ask user for use Fast Setup
|
||||
const useFastSetup = await askAndPerformFastSetupOnScheduledFetchAll(host, log, cleanupFlag);
|
||||
if (useFastSetup !== undefined) {
|
||||
return useFastSetup;
|
||||
}
|
||||
// if useFastSetup is undefined, it means user choose to proceed with normal fetch process, so continue to ask for fetch method.
|
||||
|
||||
const method =
|
||||
await host.services.UI.dialogManager.openWithExplicitCancel<FetchEverythingResult>(FetchEverything);
|
||||
if (method === "cancelled") {
|
||||
log("Fetch everything cancelled by user.", LOG_LEVEL_NOTICE);
|
||||
await cleanupFlag();
|
||||
@@ -73,7 +159,7 @@ export function createFetchAllFlagHandler(
|
||||
return false;
|
||||
}
|
||||
const { vault, extra } = method;
|
||||
const settings = await host.services.setting.currentSettings();
|
||||
const settings = await Promise.resolve(host.services.setting.currentSettings());
|
||||
// If remote is MinIO, makeLocalChunkBeforeSync is not available. (because no-deduplication on sending).
|
||||
const makeLocalChunkBeforeSyncAvailable = settings.remoteType !== REMOTE_MINIO;
|
||||
const mapVaultStateToAction = {
|
||||
@@ -296,7 +382,8 @@ export function createRebuildFlagHandler(
|
||||
|
||||
// Handle the rebuild everything scheduled operation
|
||||
const onScheduled = async () => {
|
||||
const method = await host.services.UI.dialogManager.openWithExplicitCancel(RebuildEverything);
|
||||
const method =
|
||||
await host.services.UI.dialogManager.openWithExplicitCancel<RebuildEverythingResult>(RebuildEverything);
|
||||
if (method === "cancelled") {
|
||||
log("Rebuild everything cancelled by user.", LOG_LEVEL_NOTICE);
|
||||
await cleanupFlag();
|
||||
@@ -374,8 +461,17 @@ export function flagHandlerToEventHandler(flagHandler: FlagFileHandler) {
|
||||
|
||||
export function useRedFlagFeatures(
|
||||
host: NecessaryServices<
|
||||
"API" | "appLifecycle" | "UI" | "setting" | "tweakValue" | "fileProcessing" | "vault",
|
||||
"storageAccess" | "rebuilder"
|
||||
| "API"
|
||||
| "appLifecycle"
|
||||
| "UI"
|
||||
| "setting"
|
||||
| "tweakValue"
|
||||
| "fileProcessing"
|
||||
| "vault"
|
||||
| "path"
|
||||
| "keyValueDB"
|
||||
| "database",
|
||||
"storageAccess" | "rebuilder" | "fileHandler"
|
||||
>
|
||||
) {
|
||||
const log = createInstanceLogFunction("SF:RedFlag", host.services.API);
|
||||
|
||||
@@ -19,6 +19,45 @@ import {
|
||||
TweakValuesShouldMatchedTemplate,
|
||||
TweakValuesTemplate,
|
||||
} from "@/lib/src/common/types";
|
||||
import {
|
||||
ExtraOnLocal,
|
||||
FullScanModes,
|
||||
synchroniseAllFilesBetweenDBandStorage,
|
||||
} from "@/lib/src/serviceFeatures/offlineScanner";
|
||||
import {
|
||||
SIMPLE_FETCH_STAGE1_LEGACY,
|
||||
SIMPLE_FETCH_STAGE1_NEWER_WINS,
|
||||
SIMPLE_FETCH_STAGE1_REMOTE_WINS,
|
||||
SIMPLE_FETCH_STAGE2_NEWER_CLEANUP,
|
||||
SIMPLE_FETCH_STAGE2_NEWER_SYNC_ALL,
|
||||
SIMPLE_FETCH_STAGE2_REMOTE_DELETE_NONE,
|
||||
SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL,
|
||||
STAGE2_ABORT,
|
||||
askAndPerformFastSetupOnScheduledFetchAll,
|
||||
askSimpleFetchMode,
|
||||
} from "./redFlag.simpleFetch";
|
||||
import { activateRemoteConfiguration } from "@lib/serviceFeatures/remoteConfig";
|
||||
//Mock synchroniseAllFilesBetweenDBandStorage
|
||||
vi.mock("@/lib/src/serviceFeatures/offlineScanner", async (importOriginal) => {
|
||||
const originalModule = (await importOriginal()) as any;
|
||||
return {
|
||||
...originalModule,
|
||||
synchroniseAllFilesBetweenDBandStorage: vi.fn(() => Promise.resolve(true)),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@lib/serviceFeatures/remoteConfig", () => {
|
||||
return {
|
||||
activateRemoteConfiguration: vi.fn((settings: any, configurationId: string) => {
|
||||
if (!settings?.remoteConfigurations?.[configurationId]) return false;
|
||||
return {
|
||||
activeConfigurationId: configurationId,
|
||||
remoteType: settings.remoteConfigurations[configurationId].remoteType ?? settings.remoteType,
|
||||
remoteURI: settings.remoteConfigurations[configurationId].uri,
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
// Mock types and functions
|
||||
const createLoggerMock = (): LogFunction => {
|
||||
@@ -68,6 +107,9 @@ const createAppLifecycleMock = () => {
|
||||
onLayoutReady: {
|
||||
addHandler: vi.fn(),
|
||||
},
|
||||
getUnresolvedMessages: {
|
||||
addHandler: vi.fn(),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -79,6 +121,7 @@ const createUIServiceMock = () => {
|
||||
confirm: {
|
||||
askSelectStringDialogue: vi.fn(),
|
||||
askYesNoDialog: vi.fn(),
|
||||
confirmWithMessage: vi.fn(),
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -86,7 +129,9 @@ const createUIServiceMock = () => {
|
||||
const createRebuilderMock = () => {
|
||||
return {
|
||||
$fetchLocal: vi.fn(async () => {}),
|
||||
$fetchLocalDBFast: vi.fn(async () => {}),
|
||||
$rebuildEverything: vi.fn(async () => {}),
|
||||
finishRebuild: vi.fn(async () => {}),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -389,6 +434,394 @@ describe("Red Flag Feature", () => {
|
||||
const handler = createFetchAllFlagHandler(host as any, log);
|
||||
expect(handler.priority).toBe(10);
|
||||
});
|
||||
|
||||
it("should use simplified remote-only mode and call performFullScan", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
|
||||
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
|
||||
// Stage 1: Overwrite all with remote files
|
||||
// Stage 2: Delete local files if not on remote (Clean overwrite)
|
||||
host.mocks.ui.confirm.confirmWithMessage
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_REMOTE_WINS)
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL);
|
||||
|
||||
host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({
|
||||
batchSave: false,
|
||||
} as any);
|
||||
|
||||
const handler = createFetchAllFlagHandler(host as any, log);
|
||||
const result = await handler.handle();
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(host.mocks.rebuilder.$fetchLocalDBFast).toHaveBeenCalled();
|
||||
expect(synchroniseAllFilesBetweenDBandStorage).toHaveBeenCalled();
|
||||
// We can't easily check performFullScan call here because it's imported,
|
||||
// but we can verify rebuilder was called.
|
||||
});
|
||||
|
||||
it("should restore legacy fetch flow when requested", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
|
||||
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
|
||||
host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_LEGACY);
|
||||
host.mocks.ui.dialogManager.openWithExplicitCancel.mockResolvedValueOnce({
|
||||
vault: "identical",
|
||||
backup: "backup_skipped",
|
||||
extra: { preventFetchingConfig: false },
|
||||
});
|
||||
host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({
|
||||
batchSave: false,
|
||||
} as any);
|
||||
const handler = createFetchAllFlagHandler(host as any, log);
|
||||
const result = await handler.handle();
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(host.mocks.ui.dialogManager.openWithExplicitCancel).toHaveBeenCalled();
|
||||
expect(host.mocks.rebuilder.$fetchLocal).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should cancel fetch flow when first quick step is cancelled", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
|
||||
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
|
||||
host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(false);
|
||||
|
||||
const handler = createFetchAllFlagHandler(host as any, log);
|
||||
host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({
|
||||
batchSave: false,
|
||||
} as any);
|
||||
const result = await handler.handle();
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(host.mocks.rebuilder.$fetchLocal).not.toHaveBeenCalled();
|
||||
expect(host.mocks.appLifecycle.performRestart).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should use remote-authoritative quick mode for empty vault", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
|
||||
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
|
||||
host.mocks.ui.confirm.confirmWithMessage
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_REMOTE_WINS)
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL);
|
||||
|
||||
host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({
|
||||
batchSave: false,
|
||||
} as any);
|
||||
const handler = createFetchAllFlagHandler(host as any, log);
|
||||
const result = await handler.handle();
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(host.mocks.rebuilder.$fetchLocalDBFast).toHaveBeenCalled();
|
||||
expect(host.mocks.rebuilder.$fetchLocal).not.toHaveBeenCalledWith(false, true);
|
||||
});
|
||||
|
||||
it("should keep current remote configuration when selected", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
|
||||
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
|
||||
Object.assign(host.mocks.setting.settings, {
|
||||
remoteConfigurations: {
|
||||
alpha: { name: "Alpha", uri: "sls+https://user:pass@example.com/db1" },
|
||||
beta: { name: "Beta", uri: "sls+https://user:pass@example.com/db2" },
|
||||
},
|
||||
});
|
||||
host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce("Use active remote");
|
||||
host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(false);
|
||||
|
||||
const handler = createFetchAllFlagHandler(host as any, log);
|
||||
const result = await handler.handle();
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(host.mocks.setting.applyPartial).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({ activeConfigurationId: expect.any(String) })
|
||||
);
|
||||
});
|
||||
|
||||
it("should stop when remote selection is cancelled", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
|
||||
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
|
||||
Object.assign(host.mocks.setting.settings, {
|
||||
remoteConfigurations: {
|
||||
alpha: { name: "Alpha", uri: "sls+https://user:pass@example.com/db1" },
|
||||
beta: { name: "Beta", uri: "sls+https://user:pass@example.com/db2" },
|
||||
},
|
||||
});
|
||||
host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce("Cancel");
|
||||
|
||||
const handler = createFetchAllFlagHandler(host as any, log);
|
||||
const result = await handler.handle();
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(host.mocks.ui.confirm.confirmWithMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should activate selected remote configuration", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
|
||||
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
|
||||
Object.assign(host.mocks.setting.settings, {
|
||||
remoteConfigurations: {
|
||||
alpha: {
|
||||
name: "Alpha",
|
||||
uri: "sls+https://user:pass@example.com/db1",
|
||||
remoteType: "CouchDB",
|
||||
},
|
||||
beta: {
|
||||
name: "Beta",
|
||||
uri: "sls+https://user:pass@example.com/db2",
|
||||
remoteType: "CouchDB",
|
||||
},
|
||||
},
|
||||
});
|
||||
host.mocks.ui.confirm.askSelectStringDialogue.mockImplementationOnce(
|
||||
async (_message: string, selections: string[]) => selections.find((e) => e.startsWith("Beta -"))
|
||||
);
|
||||
host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(false);
|
||||
|
||||
const handler = createFetchAllFlagHandler(host as any, log);
|
||||
const result = await handler.handle();
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(activateRemoteConfiguration).toHaveBeenCalledWith(host.mocks.setting.settings, "beta");
|
||||
expect(host.mocks.setting.applyPartial).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ activeConfigurationId: "beta" })
|
||||
);
|
||||
});
|
||||
|
||||
it("should stop when selected remote name is unknown", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
|
||||
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
|
||||
Object.assign(host.mocks.setting.settings, {
|
||||
remoteConfigurations: {
|
||||
alpha: { name: "Alpha", uri: "sls+https://user:pass@example.com/db1" },
|
||||
beta: { name: "Beta", uri: "sls+https://user:pass@example.com/db2" },
|
||||
},
|
||||
});
|
||||
host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce("Unknown option");
|
||||
|
||||
const handler = createFetchAllFlagHandler(host as any, log);
|
||||
const result = await handler.handle();
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(host.mocks.ui.confirm.confirmWithMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should stop when remote activation fails", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
|
||||
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
|
||||
Object.assign(host.mocks.setting.settings, {
|
||||
remoteConfigurations: {
|
||||
alpha: { name: "Alpha", uri: "sls+https://user:pass@example.com/db1" },
|
||||
beta: { name: "Beta", uri: "sls+https://user:pass@example.com/db2" },
|
||||
},
|
||||
});
|
||||
host.mocks.ui.confirm.askSelectStringDialogue.mockImplementationOnce(
|
||||
async (_message: string, selections: string[]) => selections.find((e) => e.startsWith("Beta -"))
|
||||
);
|
||||
(activateRemoteConfiguration as any).mockReturnValueOnce(false);
|
||||
|
||||
const handler = createFetchAllFlagHandler(host as any, log);
|
||||
const result = await handler.handle();
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(host.mocks.ui.confirm.confirmWithMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("askSimpleFetchMode", () => {
|
||||
it("should return cancelled when stage1 is cancelled", async () => {
|
||||
const host = createHostMock();
|
||||
host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(false);
|
||||
|
||||
await expect(askSimpleFetchMode(host as any)).resolves.toBe("cancelled");
|
||||
});
|
||||
|
||||
it("should return legacy mode when selected", async () => {
|
||||
const host = createHostMock();
|
||||
host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_LEGACY);
|
||||
|
||||
await expect(askSimpleFetchMode(host as any)).resolves.toEqual({ mode: "legacy", options: {} });
|
||||
});
|
||||
|
||||
it("should return remote-only with keep-local option", async () => {
|
||||
const host = createHostMock();
|
||||
host.mocks.ui.confirm.confirmWithMessage
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_REMOTE_WINS)
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE2_REMOTE_DELETE_NONE);
|
||||
|
||||
await expect(askSimpleFetchMode(host as any)).resolves.toEqual({
|
||||
mode: "remote-only",
|
||||
options: {
|
||||
mode: FullScanModes.DB_APPLY,
|
||||
extraOnRemote: undefined,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should return cancelled when remote-only stage2 is cancelled", async () => {
|
||||
const host = createHostMock();
|
||||
host.mocks.ui.confirm.confirmWithMessage
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_REMOTE_WINS)
|
||||
.mockResolvedValueOnce(false);
|
||||
|
||||
await expect(askSimpleFetchMode(host as any)).resolves.toBe("cancelled");
|
||||
});
|
||||
|
||||
it("should return aborted when remote-only stage2 aborts", async () => {
|
||||
const host = createHostMock();
|
||||
host.mocks.ui.confirm.confirmWithMessage
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_REMOTE_WINS)
|
||||
.mockResolvedValueOnce(STAGE2_ABORT);
|
||||
|
||||
await expect(askSimpleFetchMode(host as any)).resolves.toBe("aborted");
|
||||
});
|
||||
|
||||
it("should return newer-wins cleanup option", async () => {
|
||||
const host = createHostMock();
|
||||
host.mocks.ui.confirm.confirmWithMessage
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_NEWER_WINS)
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE2_NEWER_CLEANUP);
|
||||
|
||||
await expect(askSimpleFetchMode(host as any)).resolves.toEqual({
|
||||
mode: "newer-wins",
|
||||
options: {
|
||||
mode: FullScanModes.NEWER_WINS,
|
||||
extraOnLocal: ExtraOnLocal.DELETE_DB_DELETED,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should return newer-wins keep-all option", async () => {
|
||||
const host = createHostMock();
|
||||
host.mocks.ui.confirm.confirmWithMessage
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_NEWER_WINS)
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE2_NEWER_SYNC_ALL);
|
||||
|
||||
await expect(askSimpleFetchMode(host as any)).resolves.toEqual({
|
||||
mode: "newer-wins",
|
||||
options: {
|
||||
mode: FullScanModes.NEWER_WINS,
|
||||
extraOnLocal: ExtraOnLocal.APPEND_STORAGE_ONLY,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should return cancelled when newer-wins stage2 is cancelled", async () => {
|
||||
const host = createHostMock();
|
||||
host.mocks.ui.confirm.confirmWithMessage
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_NEWER_WINS)
|
||||
.mockResolvedValueOnce(false);
|
||||
|
||||
await expect(askSimpleFetchMode(host as any)).resolves.toBe("cancelled");
|
||||
});
|
||||
|
||||
it("should return aborted when newer-wins stage2 aborts", async () => {
|
||||
const host = createHostMock();
|
||||
host.mocks.ui.confirm.confirmWithMessage
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_NEWER_WINS)
|
||||
.mockResolvedValueOnce(STAGE2_ABORT);
|
||||
|
||||
await expect(askSimpleFetchMode(host as any)).resolves.toBe("aborted");
|
||||
});
|
||||
});
|
||||
|
||||
describe("askAndPerformFastSetupOnScheduledFetchAll", () => {
|
||||
it("should return false and cleanup when quick flow is cancelled", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
const cleanupFlag = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(false);
|
||||
|
||||
const result = await askAndPerformFastSetupOnScheduledFetchAll(host as any, log, cleanupFlag);
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(cleanupFlag).toHaveBeenCalled();
|
||||
expect(host.mocks.appLifecycle.performRestart).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should return false without cleanup when quick flow is aborted", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
const cleanupFlag = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
host.mocks.ui.confirm.confirmWithMessage
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_REMOTE_WINS)
|
||||
.mockResolvedValueOnce(STAGE2_ABORT);
|
||||
|
||||
const result = await askAndPerformFastSetupOnScheduledFetchAll(host as any, log, cleanupFlag);
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(cleanupFlag).not.toHaveBeenCalled();
|
||||
expect(host.mocks.appLifecycle.performRestart).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should return undefined when legacy mode is selected", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
const cleanupFlag = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_LEGACY);
|
||||
|
||||
const result = await askAndPerformFastSetupOnScheduledFetchAll(host as any, log, cleanupFlag);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
expect(host.mocks.rebuilder.$fetchLocalDBFast).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should reboot and return false when sync has failures and user chooses rerun", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
const cleanupFlag = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
host.mocks.ui.confirm.confirmWithMessage
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_REMOTE_WINS)
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL);
|
||||
host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({ batchSave: false } as any);
|
||||
(synchroniseAllFilesBetweenDBandStorage as any).mockResolvedValueOnce(false);
|
||||
host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce("Reboot to re-run the process");
|
||||
|
||||
const result = await askAndPerformFastSetupOnScheduledFetchAll(host as any, log, cleanupFlag);
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(host.mocks.appLifecycle.performRestart).toHaveBeenCalled();
|
||||
expect(cleanupFlag).not.toHaveBeenCalled();
|
||||
expect(host.mocks.rebuilder.finishRebuild).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should continue and finalise when sync has failures but user releases flag", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
const cleanupFlag = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
host.mocks.ui.confirm.confirmWithMessage
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_REMOTE_WINS)
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL);
|
||||
host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({ batchSave: false } as any);
|
||||
(synchroniseAllFilesBetweenDBandStorage as any).mockResolvedValueOnce(false);
|
||||
host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce(
|
||||
"Finalise the process and resume normal operation"
|
||||
);
|
||||
|
||||
const result = await askAndPerformFastSetupOnScheduledFetchAll(host as any, log, cleanupFlag);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(host.mocks.rebuilder.finishRebuild).toHaveBeenCalled();
|
||||
expect(cleanupFlag).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Rebuild All Flag Handler", () => {
|
||||
@@ -980,6 +1413,8 @@ describe("Red Flag Feature", () => {
|
||||
const log = createLoggerMock();
|
||||
|
||||
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
|
||||
host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({});
|
||||
host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_LEGACY);
|
||||
host.mocks.ui.dialogManager.openWithExplicitCancel.mockResolvedValueOnce("cancelled");
|
||||
|
||||
const handler = createFetchAllFlagHandler(host as any, log);
|
||||
@@ -1056,11 +1491,12 @@ describe("Red Flag Feature", () => {
|
||||
it("should handle fetchAll flag with flagHandlerToEventHandler identical", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({
|
||||
host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValue({
|
||||
customChunkSize: 1,
|
||||
} as any);
|
||||
|
||||
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
|
||||
host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_LEGACY);
|
||||
host.mocks.ui.dialogManager.openWithExplicitCancel.mockResolvedValueOnce({ vault: "identical", extra: {} });
|
||||
host.mocks.rebuilder.$fetchLocal.mockResolvedValueOnce();
|
||||
const handler = createFetchAllFlagHandler(host as any, log);
|
||||
|
||||
29
updates.md
29
updates.md
@@ -3,6 +3,35 @@ 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.70-patch3
|
||||
|
||||
2nd June, 2026
|
||||
|
||||
### Improved
|
||||
|
||||
- Database fetching (a.k.a. Reset Synchronisation on This Device) on the initialisation now supports streaming and is faster (CouchDB only)
|
||||
- The database fetching process has been streamlined, and database operations are now suspended until it has been completed
|
||||
- The initial synchronisation process has been simplified, making it easier to synchronise files with the remote server
|
||||
- We can select the remote database to fetch from during the initialisation, when there are multiple remote databases configured (e.g. multiple CouchDBs or S3 remotes)
|
||||
|
||||
## 0.25.70-patch2
|
||||
|
||||
1st June, 2026
|
||||
|
||||
### Fixed
|
||||
- No longer does the status element break other plugins' interaction (#930).
|
||||
|
||||
## 0.25.70-patch1
|
||||
|
||||
1st June, 2026
|
||||
|
||||
This release does not include any changes to behaviour (if everything is as intended).
|
||||
However, this release had addressed a large number of errors and potential issues caused by the switch to a modern ESLint configuration, as well as unintended log output.
|
||||
I have also separated out some parts where the type definitions were a bit loose.
|
||||
|
||||
As the diff has become too large, I am releasing it as a beta.
|
||||
To anyone who has submitted a pull request, please bear with me for a little while.
|
||||
|
||||
## 0.25.70
|
||||
|
||||
25th May, 2026
|
||||
|
||||
@@ -11,17 +11,23 @@ export default mergeConfig(
|
||||
},
|
||||
},
|
||||
test: {
|
||||
logHeapUsage: true,
|
||||
// maxConcurrency: 2,
|
||||
name: "unit-tests",
|
||||
include: ["**/*unit.test.ts", "**/*.unit.spec.ts"],
|
||||
exclude: ["test/**"],
|
||||
exclude: ["test/**", "src/apps/**"],
|
||||
coverage: {
|
||||
include: ["src/**/*.ts"],
|
||||
exclude: [
|
||||
"**/*.test.ts",
|
||||
"**/*unit.test.ts",
|
||||
"**/*.unit.spec.ts",
|
||||
"test/**",
|
||||
"src/lib/**/*.test.ts",
|
||||
"**/_*",
|
||||
"src/lib/apps",
|
||||
"src/lib/src/cli",
|
||||
"src/apps/**",
|
||||
// "src/cli/**",
|
||||
"src/lib/src/cli/**",
|
||||
"**/*_obsolete.ts",
|
||||
...importOnlyFiles,
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user