Compare commits

...

18 Commits

Author SHA1 Message Date
vorotamoroz 5cb76bba72 Add annotations and fix some. 2026-06-18 11:57:33 +01:00
vorotamoroz 866a49204c fix lifecycle 2026-06-18 11:15:45 +01:00
vorotamoroz fb93511ae7 Improve typings
Remove `DEV` blocks
2026-06-18 11:09:07 +01:00
vorotamoroz 72033472f3 add dependency explicitly 2026-06-17 10:36:38 +01:00
vorotamoroz 93dc03e86f remove unused dependencies, update some dependencies 2026-06-17 10:05:45 +01:00
vorotamoroz dae8443fe8 update more deps 2026-06-17 06:35:14 +01:00
vorotamoroz 88a8bcbd5a barrel node specific modules to summarise the warnings 2026-06-17 06:15:25 +01:00
vorotamoroz 4a5283543d for automatic review 2026-06-17 06:10:30 +01:00
vorotamoroz 7895336189 revert deno test 2026-06-17 06:08:40 +01:00
vorotamoroz 2d5cdccf7d for automatic review 2026-06-17 05:51:01 +01:00
vorotamoroz 497fd04081 fix global references 2026-06-17 05:29:45 +01:00
vorotamoroz ae9c46f8f0 Fix import paths 2026-06-17 04:39:39 +01:00
vorotamoroz dcd10cd690 Fix dependency management 2026-06-17 04:24:55 +01:00
vorotamoroz 5a35b71339 define workspace 2026-06-17 04:01:45 +01:00
vorotamoroz 18a59219f7 add ignore paths 2026-06-15 12:25:50 +01:00
vorotamoroz c9095738e6 (chore): limit tags 2026-06-15 12:21:58 +01:00
vorotamoroz bed415fc6b (chore): add missing release date 2026-06-15 12:21:11 +01:00
vorotamoroz 4eeb93b9e4 Merge pull request #961 from vrtmrz/0_25_76
Releasing 0.25.76
2026-06-15 20:18:37 +09:00
191 changed files with 4370 additions and 2845 deletions
+11
View File
@@ -12,6 +12,17 @@ on:
- main - main
tags: tags:
- "*.*.*-cli" - "*.*.*-cli"
paths-ignore:
- "docs/**"
- "*.md"
- "images/**"
- "assets/**"
- "instruction_images/**"
- "src/apps/webapp/**"
- "src/apps/webpeer/**"
- ".github/workflows/release.yml"
- ".github/workflows/unit-ci.yml"
- ".github/workflows/harness-ci.yml"
workflow_dispatch: workflow_dispatch:
inputs: inputs:
dry_run: dry_run:
+1
View File
@@ -4,6 +4,7 @@ on:
# Sequence of patterns matched against refs/tags # Sequence of patterns matched against refs/tags
tags: tags:
- '*' # Push events to matching any tag format, i.e. 1.0, 20.15.10 - '*' # Push events to matching any tag format, i.e. 1.0, 20.15.10
- '!*-cli' # Exclude command-line interface tags
workflow_dispatch: workflow_dispatch:
jobs: jobs:
+35 -2
View File
@@ -19,11 +19,15 @@ const packageJson = JSON.parse(fs.readFileSync("./package.json") + "");
const updateInfo = JSON.stringify(fs.readFileSync("./updates.md") + ""); const updateInfo = JSON.stringify(fs.readFileSync("./updates.md") + "");
const PATHS_TEST_INSTALL = process.env?.PATHS_TEST_INSTALL || ""; const PATHS_TEST_INSTALL = process.env?.PATHS_TEST_INSTALL || "";
const PATH_TEST_INSTALL = PATHS_TEST_INSTALL.split(path.delimiter).map(p => p.trim()).filter(p => p.length); const PATH_TEST_INSTALL = PATHS_TEST_INSTALL.split(path.delimiter)
.map((p) => p.trim())
.filter((p) => p.length);
if (PATH_TEST_INSTALL) { if (PATH_TEST_INSTALL) {
console.log(`Built files will be copied to ${PATH_TEST_INSTALL}`); console.log(`Built files will be copied to ${PATH_TEST_INSTALL}`);
} else { } else {
console.log("Development build: You can install the plug-in to Obsidian for testing by exporting the PATHS_TEST_INSTALL environment variable with the paths to your vault plugins directories separated by your system path delimiter (':' on Unix, ';' on Windows)."); console.log(
"Development build: You can install the plug-in to Obsidian for testing by exporting the PATHS_TEST_INSTALL environment variable with the paths to your vault plugins directories separated by your system path delimiter (':' on Unix, ';' on Windows)."
);
} }
const moduleAliasPlugin = { const moduleAliasPlugin = {
@@ -66,6 +70,34 @@ const moduleAliasPlugin = {
}, },
}; };
const removePragmaCommentsPlugin = {
name: "remove-pragma-comments",
setup(build) {
// Filter target extensions (e.g., JavaScript and TypeScript)
build.onLoad({ filter: /\.[jt]s?$/ }, async (args) => {
const source = await fs.promises.readFile(args.path, "utf8");
// Regex targeting both single-line and multi-line comments
// This regex looks for:
// - /* eslint ... */ (multi-line)
// const esLintPragmaRegexBlock = /\/\*[\s\S]*?eslint[\s\S]*?\*\/|([^\\:]|^)\/\/.*eslint.*$/gm;
// - // eslint-disable-next-line
let cleanedSource = source;
const tsIgnoreRegex = /\/\*\s*@ts-ignore\s*\*\/|([^\\:]|^)\/\/.*?@ts-ignore.*$/gm;
const esLintPragmaRegexLine = /([^\\:]|^)\/\/.*?eslint-.*$/gm;
const exps = [tsIgnoreRegex, esLintPragmaRegexLine];
for (const exp of exps) {
cleanedSource = cleanedSource.replace(exp, "$1");
}
return {
contents: cleanedSource,
loader: args.path.endsWith("ts") ? "ts" : "js",
};
});
},
};
/** @type esbuild.Plugin[] */ /** @type esbuild.Plugin[] */
const plugins = [ const plugins = [
{ {
@@ -177,6 +209,7 @@ const context = await esbuild.context({
preprocess: sveltePreprocess(), preprocess: sveltePreprocess(),
compilerOptions: { css: "injected", preserveComments: false }, compilerOptions: { css: "injected", preserveComments: false },
}), }),
removePragmaCommentsPlugin,
...plugins, ...plugins,
], ],
}); });
+143
View File
@@ -0,0 +1,143 @@
const restrictedGlobalsOptions = [
{
name: "app",
message: "Avoid using the global app object. Instead use the reference provided by your plugin instance.",
},
"warn",
{
name: "fetch",
message: "Use the built-in `requestUrl` function instead of `fetch` for network requests in Obsidian.",
},
{
name: "localStorage",
message:
"Prefer `App#saveLocalStorage` / `App#loadLocalStorage` functions to write / read localStorage data that's unique to a vault.",
},
];
const restrictedImportsOptions = [
{
name: "axios",
message: "Use the built-in `requestUrl` function instead of `axios`.",
},
{
name: "superagent",
message: "Use the built-in `requestUrl` function instead of `superagent`.",
},
{
name: "got",
message: "Use the built-in `requestUrl` function instead of `got`.",
},
{
name: "ofetch",
message: "Use the built-in `requestUrl` function instead of `ofetch`.",
},
{
name: "ky",
message: "Use the built-in `requestUrl` function instead of `ky`.",
},
{
name: "node-fetch",
message: "Use the built-in `requestUrl` function instead of `node-fetch`.",
},
{
name: "moment",
message: "The 'moment' package is bundled with Obsidian. Please import it from 'obsidian' instead.",
},
];
const warnWhileDev = "off";
/**
* @type {import("eslint").Linter.RulesRecord}
*/
export const baseRules = {
// -- 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 (Gradual adoption of stricter rules, currently set to 'warn' for a while).
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-redundant-type-constituents": "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-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": "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": 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",
};
/**
* @type {import("eslint").Linter.RulesRecord}
*/
export const obsidianRules = {
// -- 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
"obsidianmd/rule-custom-message": "off",
"obsidianmd/ui/sentence-case": "off",
"obsidianmd/no-plugin-as-component": "warn",
// -- Temporary overrides for migration
"obsidianmd/no-static-styles-assignment": "off",
};
/**
* @type {(base:string) => import("eslint").Linter.RulesRecord}
*/
export const ImportAliasRules = (base) => ({
"@dword-design/import-alias/prefer-alias": [
"error",
{
aliasForSubpaths: true,
alias: {
"@": `${base}/src`,
"@lib": `${base}/src/lib/src`,
},
},
],
});
/**
* @type {import("eslint").Linter.RulesRecord}
*/
export const CommunityReviewRecommendedRules = {
"no-unused-vars": "off",
"no-prototype-bultins": "off",
"no-self-compare": "warn",
"no-eval": "error",
"no-implied-eval": "error",
"prefer-const": "off",
"no-implicit-globals": "error",
"no-console": "off", // overridden by obsidianmd/rule-custom-message
"no-restricted-globals": ["error", ...restrictedGlobalsOptions],
"no-restricted-imports": ["error", ...restrictedImportsOptions],
"no-alert": "error",
"no-undef": "error",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-deprecated": "error",
"@typescript-eslint/no-unused-vars": ["warn", { args: "none" }],
"@typescript-eslint/require-await": "off",
"@typescript-eslint/no-explicit-any": ["error", { fixToUnknown: true }],
// "import/no-nodejs-modules": "off",
// "import/no-extraneous-dependencies": "error",
};
+15 -48
View File
@@ -4,6 +4,8 @@ import globals from "globals";
import { defineConfig, globalIgnores } from "eslint/config"; import { defineConfig, globalIgnores } from "eslint/config";
import * as sveltePlugin from "eslint-plugin-svelte"; import * as sveltePlugin from "eslint-plugin-svelte";
import svelteParser from "svelte-eslint-parser"; import svelteParser from "svelte-eslint-parser";
import importAlias from "@dword-design/eslint-plugin-import-alias";
import { baseRules, ImportAliasRules, obsidianRules } from "./eslint.config.common.mjs";
const warnWhileDev = "off"; // Change to "warn" to enable warnings for rules that are currently disabled. const warnWhileDev = "off"; // Change to "warn" to enable warnings for rules that are currently disabled.
export default defineConfig([ export default defineConfig([
globalIgnores([ globalIgnores([
@@ -18,11 +20,11 @@ export default defineConfig([
"**/*.json", "**/*.json",
"**/.eslintrc.js.bak", "**/.eslintrc.js.bak",
// Files from linked dependencies (those files should not exist for most people). // Files from linked dependencies (those files should not exist for most people).
"modules/octagonal-wheels/dist/**/*", "modules/octagonal-wheels/dist",
// Sub-projects (Exclude from root linting as they have different environments) // Sub-projects (Exclude from root linting as they have different environments)
"src/apps/**/*", "src/apps",
"utils/**/*", "utils",
// Specific exclusions from common library (src/lib) // Specific exclusions from common library (src/lib)
"src/lib/coverage", "src/lib/coverage",
@@ -54,6 +56,7 @@ export default defineConfig([
]), ]),
...sveltePlugin.configs["flat/base"], ...sveltePlugin.configs["flat/base"],
...obsidianmd.configs.recommended, ...obsidianmd.configs.recommended,
importAlias.configs.recommended,
{ {
files: ["**/*.ts"], files: ["**/*.ts"],
// ignores:["src/lib/**/*.ts"], // Exclude library files from root linting (they have different environments and rules). // ignores:["src/lib/**/*.ts"], // Exclude library files from root linting (they have different environments and rules).
@@ -62,60 +65,23 @@ export default defineConfig([
parser: tsParser, parser: tsParser,
parserOptions: { parserOptions: {
project: "./tsconfig.json", project: "./tsconfig.json",
rootDir: "./",
}, },
}, },
linterOptions:{ linterOptions: {
reportUnusedDisableDirectives: false, reportUnusedDisableDirectives: false,
}, },
rules: { rules: {
// -- Base rules (turned off in favour of TS specific versions or explicitly disabled). ...baseRules,
"no-unused-vars": "off", ...obsidianRules,
"no-unused-labels": "off", // -- Project specific rules
"no-prototype-builtins": "off", ...ImportAliasRules("."),
"require-await": "off",
// -- 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": "error",
"@typescript-eslint/no-misused-promises": "error",
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error",
// -- 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,
// -- 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"], files: ["**/*.svelte"],
languageOptions: { languageOptions: {
globals: { ...globals.browser, PouchDB: "readonly" },
parser: svelteParser, parser: svelteParser,
parserOptions: { parserOptions: {
parser: tsParser, parser: tsParser,
@@ -127,8 +93,9 @@ export default defineConfig([
// Svelte template's declarations have a lot of false positives and the rule is not worth the effort to fix at this time. // 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: "^_" }],] // it may improve in the future with some options as like ["error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],]
"no-unused-vars": "off", "no-unused-vars": "off",
...obsidianRules,
"obsidianmd/no-plugin-as-component": "off", "obsidianmd/no-plugin-as-component": "off",
"obsidianmd/ui/sentence-case": "off", ...ImportAliasRules("."),
}, },
}, },
]); ]);
+1463 -1728
View File
File diff suppressed because it is too large Load Diff
+24 -21
View File
@@ -57,15 +57,17 @@
"test:docker-all:stop": "npm run test:docker-all:down", "test:docker-all:stop": "npm run test:docker-all:down",
"test:full": "npm run test:docker-all:start && vitest run --coverage && npm run test:docker-all:stop", "test:full": "npm run test:docker-all:start && vitest run --coverage && npm run test:docker-all:stop",
"test:p2p": "bash test/suitep2p/run-p2p-tests.sh", "test:p2p": "bash test/suitep2p/run-p2p-tests.sh",
"version": "node version-bump.mjs && git add manifest.json versions.json" "update-workspaces": "node update-workspaces.mjs",
"version": "node version-bump.mjs && node update-workspaces.mjs && git add manifest.json versions.json src/apps/cli/package.json src/apps/webpeer/package.json src/apps/webapp/package.json"
}, },
"keywords": [], "keywords": [],
"author": "vorotamoroz", "author": "vorotamoroz",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@chialab/esbuild-plugin-worker": "^0.19.0", "@dword-design/eslint-plugin-import-alias": "^8.1.8",
"@eslint/js": "^9.39.3", "@eslint/js": "^9.39.3",
"@sveltejs/vite-plugin-svelte": "^6.2.4", "@playwright/test": "^1.58.2",
"@sveltejs/vite-plugin-svelte": "^7.1.2",
"@tsconfig/svelte": "^5.0.8", "@tsconfig/svelte": "^5.0.8",
"@types/deno": "^2.5.0", "@types/deno": "^2.5.0",
"@types/diff-match-patch": "^1.0.36", "@types/diff-match-patch": "^1.0.36",
@@ -80,7 +82,6 @@
"@types/pouchdb-mapreduce": "^6.1.10", "@types/pouchdb-mapreduce": "^6.1.10",
"@types/pouchdb-replication": "^6.4.7", "@types/pouchdb-replication": "^6.4.7",
"@types/transform-pouch": "^1.0.6", "@types/transform-pouch": "^1.0.6",
"@typescript-eslint/eslint-plugin": "8.56.1",
"@typescript-eslint/parser": "8.56.1", "@typescript-eslint/parser": "8.56.1",
"@vitest/browser": "^4.1.8", "@vitest/browser": "^4.1.8",
"@vitest/browser-playwright": "^4.1.8", "@vitest/browser-playwright": "^4.1.8",
@@ -91,12 +92,11 @@
"esbuild-svelte": "^0.9.4", "esbuild-svelte": "^0.9.4",
"eslint": "^9.39.3", "eslint": "^9.39.3",
"eslint-plugin-obsidianmd": "^0.3.0", "eslint-plugin-obsidianmd": "^0.3.0",
"eslint-plugin-svelte": "^3.15.0", "eslint-plugin-svelte": "^3.19.0",
"events": "^3.3.0", "events": "^3.3.0",
"globals": "^14.0.0", "globals": "^14.0.0",
"playwright": "^1.58.2", "playwright": "^1.58.2",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"postcss-load-config": "^6.0.1",
"pouchdb-adapter-http": "^9.0.0", "pouchdb-adapter-http": "^9.0.0",
"pouchdb-adapter-idb": "^9.0.0", "pouchdb-adapter-idb": "^9.0.0",
"pouchdb-adapter-indexeddb": "^9.0.0", "pouchdb-adapter-indexeddb": "^9.0.0",
@@ -110,20 +110,22 @@
"pouchdb-utils": "^9.0.0", "pouchdb-utils": "^9.0.0",
"prettier": "3.8.1", "prettier": "3.8.1",
"rollup-plugin-copy": "^3.5.0", "rollup-plugin-copy": "^3.5.0",
"svelte": "5.41.1", "svelte": "5.56.3",
"svelte-check": "^4.4.3", "svelte-check": "^4.6.0",
"svelte-eslint-parser": "^1.8.0",
"svelte-preprocess": "^6.0.3", "svelte-preprocess": "^6.0.3",
"terser": "^5.39.0", "terser": "^5.39.0",
"tinyglobby": "^0.2.15", "tinyglobby": "^0.2.15",
"transform-pouch": "^2.0.0", "transform-pouch": "^2.0.0",
"tslib": "^2.8.1",
"tsx": "^4.21.0", "tsx": "^4.21.0",
"typescript": "5.9.3", "typescript": "5.9.3",
"vite": "^7.3.1", "typescript-eslint": "^8.61.0",
"vite-plugin-istanbul": "^8.0.0", "vite": "^8.0.16",
"vitest": "^4.1.8", "vitest": "^4.1.8",
"webdriverio": "^9.27.0", "webdriverio": "^9.27.0",
"yaml": "^2.8.2" "yaml": "^2.8.2",
"@emnapi/core": "1.11.1",
"@emnapi/runtime": "1.11.1"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.808.0", "@aws-sdk/client-s3": "^3.808.0",
@@ -132,21 +134,22 @@
"@smithy/middleware-apply-body-checksum": "^4.3.9", "@smithy/middleware-apply-body-checksum": "^4.3.9",
"@smithy/protocol-http": "^5.3.9", "@smithy/protocol-http": "^5.3.9",
"@smithy/querystring-builder": "^4.2.9", "@smithy/querystring-builder": "^4.2.9",
"@smithy/types": "^4.14.3",
"@smithy/util-retry": "^4.4.5", "@smithy/util-retry": "^4.4.5",
"@trystero-p2p/nostr": "^0.24.0", "@trystero-p2p/nostr": "^0.24.0",
"chokidar": "^4.0.0",
"commander": "^14.0.3",
"diff-match-patch": "^1.0.5", "diff-match-patch": "^1.0.5",
"fflate": "^0.8.2", "fflate": "^0.8.2",
"idb": "^8.0.3", "idb": "^8.0.3",
"markdown-it": "^14.1.1", "markdown-it": "^14.2.0",
"micromatch": "^4.0.0", "minimatch": "^10.2.5",
"minimatch": "^10.2.2", "obsidian": "^1.13.1",
"obsidian": "^1.12.3",
"octagonal-wheels": "^0.1.46", "octagonal-wheels": "^0.1.46",
"pouchdb-adapter-leveldb": "^9.0.0",
"qrcode-generator": "^1.4.4", "qrcode-generator": "^1.4.4",
"werift": "^0.23.0",
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2" "xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
} },
"workspaces": [
"src/apps/cli",
"src/apps/webpeer",
"src/apps/webapp"
]
} }
+2 -1
View File
@@ -29,6 +29,7 @@ import { ModuleLiveSyncMain } from "./modules/main/ModuleLiveSyncMain";
import type { ServiceModules } from "./lib/src/interfaces/ServiceModule"; import type { ServiceModules } from "./lib/src/interfaces/ServiceModule";
import { ModuleBasicMenu } from "./modules/essential/ModuleBasicMenu"; import { ModuleBasicMenu } from "./modules/essential/ModuleBasicMenu";
import { usePrepareDatabaseForUse } from "./lib/src/serviceFeatures/prepareDatabaseForUse"; import { usePrepareDatabaseForUse } from "./lib/src/serviceFeatures/prepareDatabaseForUse";
import type { Constructor } from "@lib/common/utils.type";
export class LiveSyncBaseCore< export class LiveSyncBaseCore<
T extends ServiceContext = ServiceContext, T extends ServiceContext = ServiceContext,
@@ -120,7 +121,7 @@ export class LiveSyncBaseCore<
* @param constructor * @param constructor
* @returns * @returns
*/ */
getModule<T extends AbstractModule>(constructor: new (...args: any[]) => T): T { getModule<T extends AbstractModule>(constructor: Constructor<T>): T {
for (const module of this.modules) { for (const module of this.modules) {
if (module.constructor === constructor) return module as T; if (module.constructor === constructor) return module as T;
} }
+2 -2
View File
@@ -82,8 +82,8 @@ RUN apt-get update \
WORKDIR /deps WORKDIR /deps
# runtime-package.json lists only the packages that Vite leaves external # package.json lists only the packages that the CLI requires
COPY src/apps/cli/runtime-package.json ./package.json COPY src/apps/cli/package.json ./package.json
RUN npm install --omit=dev RUN npm install --omit=dev
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
+16 -9
View File
@@ -1,17 +1,17 @@
# Self-hosted LiveSync CLI # Self-hosted LiveSync CLI
Command-line version of Self-hosted LiveSync plugin for syncing vaults without Obsidian. Command-line version of Self-hosted LiveSync plug-in for syncing vaults without Obsidian.
## Features ## Features
- ✅ Sync Obsidian vaults using CouchDB without running Obsidian - ✅ Sync Obsidian vaults using CouchDB without running Obsidian
- ✅ Compatible with Self-hosted LiveSync plugin settings - ✅ Compatible with Self-hosted LiveSync plug-in settings
- ✅ Supports all core sync features (encryption, conflict resolution, etc.) - ✅ Supports all core sync features (encryption, conflict resolution, etc.)
- ✅ Lightweight and headless operation - ✅ Lightweight and headless operation
- ✅ Cross-platform (Windows, macOS, Linux) - ✅ Cross-platform (Windows, macOS, Linux)
## Architecture ## Architecture
This CLI version is built using the same core as the Obsidian plugin: This CLI version is built using the same core as the Obsidian plug-in:
``` ```
CLI Main CLI Main
@@ -118,19 +118,26 @@ git submodule update --init --recursive
# Install dependencies from the repository root # Install dependencies from the repository root
npm install npm install
# Build the CLI from its package directory # Build the CLI from the repository root
npm run build -w self-hosted-livesync-cli
# Or from the package directory
cd src/apps/cli cd src/apps/cli
npm run build npm run build
``` ```
If `src/lib` is missing, `npm run build` now stops early with a targeted message If `src/lib` is missing, the build process stops early with a targeted message instead of a low-level Vite `ENOENT` error.
instead of a low-level Vite `ENOENT` error.
Run the CLI: Run the CLI:
```bash ```bash
# Run with npm script (from repository root) # Run with npm workspace script (from repository root)
npm run --silent cli -- [database-path] [command] [args...] npm run cli -w self-hosted-livesync-cli -- [database-path] [command] [args...]
# Or from the package directory
cd src/apps/cli
npm run cli -- [database-path] [command] [args...]
# Run the built executable directly # Run the built executable directly
node src/apps/cli/dist/index.cjs [database-path] [command] [args...] node src/apps/cli/dist/index.cjs [database-path] [command] [args...]
``` ```
@@ -283,7 +290,7 @@ livesync-cli /path/to/your-local-database --settings /path/to/settings.json unlo
### Configuration ### Configuration
The CLI uses the same settings format as the Obsidian plugin. Create a `.livesync/settings.json` file in your vault directory: The CLI uses the same settings format as the Obsidian plug-in. Create a `.livesync/settings.json` file in your vault directory:
```json ```json
{ {
@@ -1,7 +1,7 @@
import * as path from "path";
import type { UXFileInfoStub, UXFolderInfo } from "@lib/common/types"; import type { UXFileInfoStub, UXFolderInfo } from "@lib/common/types";
import type { IConversionAdapter } from "@lib/serviceModules/adapters"; import type { IConversionAdapter } from "@lib/serviceModules/adapters";
import type { NodeFile, NodeFolder } from "./NodeTypes"; import type { NodeFile, NodeFolder } from "./NodeTypes";
import { path } from "../node-compat";
/** /**
* Conversion adapter implementation for Node.js * Conversion adapter implementation for Node.js
@@ -1,5 +1,3 @@
import * as fs from "fs/promises";
import * as path from "path";
import type { FilePath, UXStat } from "@lib/common/types"; import type { FilePath, UXStat } from "@lib/common/types";
import type { IFileSystemAdapter } from "@lib/serviceModules/adapters"; import type { IFileSystemAdapter } from "@lib/serviceModules/adapters";
import { NodePathAdapter } from "./NodePathAdapter"; import { NodePathAdapter } from "./NodePathAdapter";
@@ -8,6 +6,7 @@ import { NodeConversionAdapter } from "./NodeConversionAdapter";
import { NodeStorageAdapter } from "./NodeStorageAdapter"; import { NodeStorageAdapter } from "./NodeStorageAdapter";
import { NodeVaultAdapter } from "./NodeVaultAdapter"; import { NodeVaultAdapter } from "./NodeVaultAdapter";
import type { NodeFile, NodeFolder, NodeStat } from "./NodeTypes"; import type { NodeFile, NodeFolder, NodeStat } from "./NodeTypes";
import { fsPromises as fs, path } from "../node-compat";
/** /**
* Complete file system adapter implementation for Node.js * Complete file system adapter implementation for Node.js
+1 -1
View File
@@ -1,7 +1,7 @@
import * as path from "path";
import type { FilePath } from "@lib/common/types"; import type { FilePath } from "@lib/common/types";
import type { IPathAdapter } from "@lib/serviceModules/adapters"; import type { IPathAdapter } from "@lib/serviceModules/adapters";
import type { NodeFile } from "./NodeTypes"; import type { NodeFile } from "./NodeTypes";
import { path } from "../node-compat";
/** /**
* Path adapter implementation for Node.js * Path adapter implementation for Node.js
+2 -2
View File
@@ -1,8 +1,7 @@
import * as fs from "fs/promises";
import * as path from "path";
import type { UXDataWriteOptions } from "@lib/common/types"; import type { UXDataWriteOptions } from "@lib/common/types";
import type { IStorageAdapter } from "@lib/serviceModules/adapters"; import type { IStorageAdapter } from "@lib/serviceModules/adapters";
import type { NodeStat } from "./NodeTypes"; import type { NodeStat } from "./NodeTypes";
import { fsPromises as fs, path } from "../node-compat";
/** /**
* Storage adapter implementation for Node.js * Storage adapter implementation for Node.js
@@ -61,6 +60,7 @@ export class NodeStorageAdapter implements IStorageAdapter<NodeStat> {
async readBinary(p: string): Promise<ArrayBuffer> { async readBinary(p: string): Promise<ArrayBuffer> {
const buffer = await fs.readFile(this.resolvePath(p)); const buffer = await fs.readFile(this.resolvePath(p));
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- required in environments where Buffer.buffer is ArrayBufferLike
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength) as ArrayBuffer; return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength) as ArrayBuffer;
} }
+3 -3
View File
@@ -1,8 +1,7 @@
import * as fs from "fs/promises";
import * as path from "path";
import type { UXDataWriteOptions } from "@lib/common/types"; import type { UXDataWriteOptions } from "@lib/common/types";
import type { IVaultAdapter } from "@lib/serviceModules/adapters"; import type { IVaultAdapter } from "@lib/serviceModules/adapters";
import type { NodeFile, NodeFolder, NodeStat } from "./NodeTypes"; import type { NodeFile, NodeFolder } from "./NodeTypes";
import { fsPromises as fs, path } from "../node-compat";
/** /**
* Vault adapter implementation for Node.js * Vault adapter implementation for Node.js
@@ -32,6 +31,7 @@ export class NodeVaultAdapter implements IVaultAdapter<NodeFile> {
const buffer = await fs.readFile(this.resolvePath(file.path)); const buffer = await fs.readFile(this.resolvePath(file.path));
// Same correction as read() — ensure stat.size matches actual byte length. // Same correction as read() — ensure stat.size matches actual byte length.
file.stat.size = buffer.length; file.stat.size = buffer.length;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- required in environments where Buffer.buffer is ArrayBufferLike
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength) as ArrayBuffer; return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength) as ArrayBuffer;
} }
+4 -3
View File
@@ -1,15 +1,16 @@
import type { LiveSyncBaseCore } from "../../../LiveSyncBaseCore"; import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore";
import { P2P_DEFAULT_SETTINGS } from "@lib/common/types"; import { P2P_DEFAULT_SETTINGS } from "@lib/common/types";
import type { ServiceContext } from "@lib/services/base/ServiceBase"; import type { ServiceContext } from "@lib/services/base/ServiceBase";
import { LiveSyncTrysteroReplicator } from "@lib/replication/trystero/LiveSyncTrysteroReplicator"; import { LiveSyncTrysteroReplicator } from "@lib/replication/trystero/LiveSyncTrysteroReplicator";
import { addP2PEventHandlers } from "@lib/replication/trystero/addP2PEventHandlers"; import { compatGlobal } from "@lib/common/coreEnvFunctions.ts";
type CLIP2PPeer = { type CLIP2PPeer = {
peerId: string; peerId: string;
name: string; name: string;
}; };
function delay(ms: number): Promise<void> { function delay(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms)); return new Promise((resolve) => compatGlobal.setTimeout(resolve, ms));
} }
export function parseTimeoutSeconds(value: string, commandName: string): number { export function parseTimeoutSeconds(value: string, commandName: string): number {
+5 -5
View File
@@ -1,5 +1,3 @@
import * as fs from "fs/promises";
import * as path from "path";
import { decodeSettingsFromSetupURI } from "@lib/API/processSetting"; import { decodeSettingsFromSetupURI } from "@lib/API/processSetting";
import { configURIBase } from "@lib/common/models/shared.const"; import { configURIBase } from "@lib/common/models/shared.const";
import { import {
@@ -18,6 +16,8 @@ import { promptForPassphrase, readStdinAsUtf8, toArrayBuffer, toDatabaseRelative
import { collectPeers, openP2PHost, parseTimeoutSeconds, syncWithPeer } from "./p2p"; import { collectPeers, openP2PHost, parseTimeoutSeconds, syncWithPeer } from "./p2p";
import { performFullScan } from "@lib/serviceFeatures/offlineScanner"; import { performFullScan } from "@lib/serviceFeatures/offlineScanner";
import { UnresolvedErrorManager } from "@lib/services/base/UnresolvedErrorManager"; import { UnresolvedErrorManager } from "@lib/services/base/UnresolvedErrorManager";
import { compatGlobal } from "@lib/common/coreEnvFunctions.ts";
import { fsPromises as fs, path } from "../node-compat";
function redactConnectionString(uri: string): string { function redactConnectionString(uri: string): string {
return uri.replace(/\/\/([^@/]+)@/u, "//***@"); return uri.replace(/\/\/([^@/]+)@/u, "//***@");
@@ -150,11 +150,11 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext
); );
} }
} }
pollTimer = setTimeout(poll, currentIntervalMs); pollTimer = compatGlobal.setTimeout(poll, currentIntervalMs);
}; };
let pollTimer: ReturnType<typeof setTimeout> = setTimeout(poll, currentIntervalMs); let pollTimer = compatGlobal.setTimeout(poll, currentIntervalMs);
core.services.appLifecycle.onUnload.addHandler(async () => { core.services.appLifecycle.onUnload.addHandler(async () => {
clearTimeout(pollTimer); compatGlobal.clearTimeout(pollTimer);
return true; return true;
}); });
} else { } else {
+1 -1
View File
@@ -1,4 +1,4 @@
import { LiveSyncBaseCore } from "../../../LiveSyncBaseCore"; import { LiveSyncBaseCore } from "@/LiveSyncBaseCore";
import { ServiceContext } from "@lib/services/base/ServiceBase"; import { ServiceContext } from "@lib/services/base/ServiceBase";
import type { ObsidianLiveSyncSettings } from "@lib/common/types"; import type { ObsidianLiveSyncSettings } from "@lib/common/types";
+2 -2
View File
@@ -1,7 +1,7 @@
import * as path from "path"; import { path, readline } from "../node-compat";
import * as readline from "node:readline/promises";
export function toArrayBuffer(data: Buffer): ArrayBuffer { export function toArrayBuffer(data: Buffer): ArrayBuffer {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- required in environments where Buffer.buffer is ArrayBufferLike
return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) as ArrayBuffer; return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) as ArrayBuffer;
} }
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env node #!/usr/bin/env node
// eslint-disable -- This is the entry point for the CLI application.
import * as polyfill from "werift"; import * as polyfill from "werift";
import { main } from "./main"; import { main } from "./main";
+15 -21
View File
@@ -1,17 +1,10 @@
/**
* Self-hosted LiveSync CLI
* Command-line version of Self-hosted LiveSync plugin for syncing vaults without Obsidian
*/
import * as fs from "fs/promises";
import * as path from "path";
import { NodeServiceContext, NodeServiceHub } from "./services/NodeServiceHub"; import { NodeServiceContext, NodeServiceHub } from "./services/NodeServiceHub";
import { configureNodeLocalStorage, ensureGlobalNodeLocalStorage } from "./services/NodeLocalStorage"; import { configureNodeLocalStorage, ensureGlobalNodeLocalStorage } from "./services/NodeLocalStorage";
import { LiveSyncBaseCore } from "../../LiveSyncBaseCore"; import { LiveSyncBaseCore } from "@/LiveSyncBaseCore";
import { initialiseServiceModulesCLI } from "./serviceModules/CLIServiceModules"; import { initialiseServiceModulesCLI } from "./serviceModules/CLIServiceModules";
import { DEFAULT_SETTINGS, LOG_LEVEL_VERBOSE, type LOG_LEVEL, type ObsidianLiveSyncSettings } from "@lib/common/types"; import { DEFAULT_SETTINGS, LOG_LEVEL_VERBOSE, type LOG_LEVEL, type ObsidianLiveSyncSettings } from "@lib/common/types";
import type { InjectableServiceHub } from "@lib/services/implements/injectable/InjectableServiceHub"; import type { InjectableServiceHub } from "@lib/services/implements/injectable/InjectableServiceHub";
import type { InjectableSettingService } from "@/lib/src/services/implements/injectable/InjectableSettingService"; import type { InjectableSettingService } from "@lib/services/implements/injectable/InjectableSettingService";
import { import {
LOG_LEVEL_DEBUG, LOG_LEVEL_DEBUG,
setGlobalLogFunction, setGlobalLogFunction,
@@ -26,7 +19,8 @@ import type { CLICommand, CLIOptions } from "./commands/types";
import { getPathFromUXFileInfo } from "@lib/common/typeUtils"; import { getPathFromUXFileInfo } from "@lib/common/typeUtils";
import { stripAllPrefixes } from "@lib/string_and_binary/path"; import { stripAllPrefixes } from "@lib/string_and_binary/path";
import { IgnoreRules } from "./serviceModules/IgnoreRules"; import { IgnoreRules } from "./serviceModules/IgnoreRules";
import { useP2PReplicatorFeature } from "@/lib/src/replication/trystero/useP2PReplicatorFeature"; import { useP2PReplicatorFeature } from "@lib/replication/trystero/useP2PReplicatorFeature";
import { fsPromises as fs, path, fs as fsSync } from "./node-compat";
const SETTINGS_FILE = ".livesync/settings.json"; const SETTINGS_FILE = ".livesync/settings.json";
ensureGlobalNodeLocalStorage(); ensureGlobalNodeLocalStorage();
@@ -309,7 +303,7 @@ export async function main() {
console.error(`Error: ${databasePath} is not a directory`); console.error(`Error: ${databasePath} is not a directory`);
process.exit(1); process.exit(1);
} }
} catch (error) { } catch {
console.error(`Error: Database directory ${databasePath} does not exist`); console.error(`Error: Database directory ${databasePath} does not exist`);
process.exit(1); process.exit(1);
} }
@@ -330,7 +324,7 @@ export async function main() {
? path.resolve(options.commandArgs[0]) ? path.resolve(options.commandArgs[0])
: options.vaultPath : options.vaultPath
? path.resolve(options.vaultPath) ? path.resolve(options.vaultPath)
: databasePath!; : databasePath;
// Check if vault directory exists // Check if vault directory exists
try { try {
@@ -339,7 +333,7 @@ export async function main() {
console.error(`Error: Vault path ${vaultPath} is not a directory`); console.error(`Error: Vault path ${vaultPath} is not a directory`);
process.exit(1); process.exit(1);
} }
} catch (error) { } catch {
console.error(`Error: Vault directory ${vaultPath} does not exist`); console.error(`Error: Vault directory ${vaultPath} does not exist`);
process.exit(1); process.exit(1);
} }
@@ -421,7 +415,7 @@ export async function main() {
// Force disable IndexedDB adapter in CLI environment // Force disable IndexedDB adapter in CLI environment
data.useIndexedDBAdapter = false; data.useIndexedDBAdapter = false;
return data; return data;
} catch (error) { } catch {
if (options.verbose) { if (options.verbose) {
console.error(`[Settings] File not found, using defaults`); console.error(`[Settings] File not found, using defaults`);
} }
@@ -440,7 +434,7 @@ export async function main() {
() => [], // No add-ons () => [], // No add-ons
(core) => { (core) => {
// Register P2P replicator feature. // Register P2P replicator feature.
const _replicator = useP2PReplicatorFeature(core); useP2PReplicatorFeature(core);
// Add target filter to prevent internal files are handled // Add target filter to prevent internal files are handled
core.services.vault.isTargetFile.addHandler(async (target) => { core.services.vault.isTargetFile.addHandler(async (target) => {
const targetPath = stripAllPrefixes(getPathFromUXFileInfo(target)); const targetPath = stripAllPrefixes(getPathFromUXFileInfo(target));
@@ -464,8 +458,8 @@ export async function main() {
if (rules.shouldIgnore(targetPath)) { if (rules.shouldIgnore(targetPath)) {
return false; return false;
} }
// undefined = pass through to next handler in chain // At least this handler think it is a target file, but other handlers may still veto it.
return undefined; return true;
}, 0); }, 0);
} }
} }
@@ -485,8 +479,8 @@ export async function main() {
} }
}; };
process.on("SIGINT", () => shutdown("SIGINT")); process.on("SIGINT", () => void shutdown("SIGINT"));
process.on("SIGTERM", () => shutdown("SIGTERM")); process.on("SIGTERM", () => void shutdown("SIGTERM"));
// Save the settings file before any lifecycle events can mutate and persist them. // Save the settings file before any lifecycle events can mutate and persist them.
// suspendAllSync and other lifecycle hooks clobber sync settings in memory, and // suspendAllSync and other lifecycle hooks clobber sync settings in memory, and
@@ -499,8 +493,8 @@ export async function main() {
if (settingsBackup) { if (settingsBackup) {
const tmpPath = settingsPath + ".tmp"; const tmpPath = settingsPath + ".tmp";
try { try {
require("fs").writeFileSync(tmpPath, settingsBackup, "utf-8"); fsSync.writeFileSync(tmpPath, settingsBackup, "utf-8");
require("fs").renameSync(tmpPath, settingsPath); fsSync.renameSync(tmpPath, settingsPath);
} catch (err) { } catch (err) {
console.error("[Settings] Failed to restore settings on exit:", err); console.error("[Settings] Failed to restore settings on exit:", err);
} }
@@ -10,12 +10,10 @@ import type {
IStorageEventWatchHandlers, IStorageEventWatchHandlers,
} from "@lib/managers/adapters"; } from "@lib/managers/adapters";
import type { FileEventItemSentinel } from "@lib/managers/StorageEventManager"; import type { FileEventItemSentinel } from "@lib/managers/StorageEventManager";
import type { NodeFile, NodeFolder } from "../adapters/NodeTypes"; import type { NodeFile, NodeFolder } from "@/apps/cli/adapters/NodeTypes";
import type { Stats } from "fs";
import * as fs from "fs/promises";
import * as path from "path";
import { watch as chokidarWatch, type FSWatcher } from "chokidar"; import { watch as chokidarWatch, type FSWatcher } from "chokidar";
import type { IgnoreRules } from "../serviceModules/IgnoreRules"; import type { IgnoreRules } from "@/apps/cli/serviceModules/IgnoreRules";
import { fsPromises as fs, path, type Stats } from "../node-compat";
/** /**
* CLI-specific type guard adapter * CLI-specific type guard adapter
@@ -1,6 +1,6 @@
import { describe, expect, it, vi, beforeEach } from "vitest"; import { describe, expect, it, vi, beforeEach } from "vitest";
import type { IStorageEventWatchHandlers } from "@lib/managers/adapters"; import type { IStorageEventWatchHandlers } from "@lib/managers/adapters";
import type { NodeFile } from "../adapters/NodeTypes"; import type { NodeFile } from "@/apps/cli/adapters/NodeTypes";
// ── chokidar mock ────────────────────────────────────────────────────────────── // ── chokidar mock ──────────────────────────────────────────────────────────────
// Must be hoisted before imports that pull in chokidar. // Must be hoisted before imports that pull in chokidar.
@@ -1,8 +1,8 @@
import { StorageEventManagerBase, type StorageEventManagerBaseDependencies } from "@lib/managers/StorageEventManager"; import { StorageEventManagerBase, type StorageEventManagerBaseDependencies } from "@lib/managers/StorageEventManager";
import { CLIStorageEventManagerAdapter } from "./CLIStorageEventManagerAdapter"; import { CLIStorageEventManagerAdapter } from "./CLIStorageEventManagerAdapter";
import type { IMinimumLiveSyncCommands, LiveSyncBaseCore } from "../../../LiveSyncBaseCore"; import type { IMinimumLiveSyncCommands, LiveSyncBaseCore } from "@/LiveSyncBaseCore";
import type { ServiceContext } from "@lib/services/base/ServiceBase"; import type { ServiceContext } from "@lib/services/base/ServiceBase";
import type { IgnoreRules } from "../serviceModules/IgnoreRules"; import type { IgnoreRules } from "@/apps/cli/serviceModules/IgnoreRules";
// import type { IMinimumLiveSyncCommands } from "@lib/services/base/IService"; // import type { IMinimumLiveSyncCommands } from "@lib/services/base/IService";
export class StorageEventManagerCLI extends StorageEventManagerBase<CLIStorageEventManagerAdapter> { export class StorageEventManagerCLI extends StorageEventManagerBase<CLIStorageEventManagerAdapter> {
+11
View File
@@ -0,0 +1,11 @@
// eslint-disable-next-line obsidianmd/no-nodejs-builtins -- This file is used to provide Node.js built-in modules in the CLI environment, which is not running in a browser context.
import * as nodeFs from "node:fs";
// eslint-disable-next-line obsidianmd/no-nodejs-builtins -- This file is used to provide Node.js built-in modules in the CLI environment, which is not running in a browser context.
import * as nodeFsPromises from "node:fs/promises";
// eslint-disable-next-line obsidianmd/no-nodejs-builtins -- This file is used to provide Node.js built-in modules in the CLI environment, which is not running in a browser context.
import * as nodePath from "node:path";
// eslint-disable-next-line obsidianmd/no-nodejs-builtins -- This file is used to provide Node.js built-in modules in the CLI environment, which is not running in a browser context.
import * as nodeReadlinePromises from "node:readline/promises";
// eslint-disable-next-line obsidianmd/no-nodejs-builtins -- This file is used to provide Node.js built-in modules in the CLI environment, which is not running in a browser context.
import type { Stats } from "node:fs";
export { nodeFs as fs, nodeFsPromises as fsPromises, nodePath as path, nodeReadlinePromises as readline, type Stats };
+23 -3
View File
@@ -1,7 +1,7 @@
{ {
"name": "self-hosted-livesync-cli", "name": "self-hosted-livesync-cli",
"private": true, "private": true,
"version": "0.0.0", "version": "0.25.76-cli",
"main": "dist/index.cjs", "main": "dist/index.cjs",
"type": "module", "type": "module",
"scripts": { "scripts": {
@@ -38,6 +38,26 @@
"test:e2e:docker:p2p-sync": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-p2p-sync-linux.sh", "test:e2e:docker:p2p-sync": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-p2p-sync-linux.sh",
"test:e2e:docker:all": "export RUN_BUILD=0 && npm run test:e2e:docker:setup-put-cat && npm run test:e2e:docker:push-pull && npm run test:e2e:docker:sync-two-local && npm run test:e2e:docker:mirror && npm run test:e2e:docker:remote-commands" "test:e2e:docker:all": "export RUN_BUILD=0 && npm run test:e2e:docker:setup-put-cat && npm run test:e2e:docker:push-pull && npm run test:e2e:docker:sync-two-local && npm run test:e2e:docker:mirror && npm run test:e2e:docker:remote-commands"
}, },
"dependencies": {}, "dependencies": {
"devDependencies": {} "chokidar": "^4.0.0",
"minimatch": "^10.2.5",
"octagonal-wheels": "^0.1.46",
"pouchdb-adapter-http": "^9.0.0",
"pouchdb-adapter-leveldb": "^9.0.0",
"pouchdb-core": "^9.0.0",
"pouchdb-errors": "^9.0.0",
"pouchdb-find": "^9.0.0",
"pouchdb-mapreduce": "^9.0.0",
"pouchdb-merge": "^9.0.0",
"pouchdb-replication": "^9.0.0",
"pouchdb-utils": "^9.0.0",
"transform-pouch": "^2.0.0",
"werift": "^0.23.0"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^7.1.2",
"typescript": "5.9.3",
"vite": "^8.0.16",
"vitest": "^4.1.8"
}
} }
-25
View File
@@ -1,25 +0,0 @@
{
"name": "livesync-cli-runtime",
"private": true,
"version": "0.0.0",
"description": "Runtime dependencies for Self-hosted LiveSync CLI Docker image",
"dependencies": {
"chokidar": "^4.0.0",
"commander": "^14.0.3",
"werift": "^0.22.9",
"pouchdb-adapter-http": "^9.0.0",
"pouchdb-adapter-idb": "^9.0.0",
"pouchdb-adapter-indexeddb": "^9.0.0",
"pouchdb-adapter-leveldb": "^9.0.0",
"pouchdb-adapter-memory": "^9.0.0",
"pouchdb-core": "^9.0.0",
"pouchdb-errors": "^9.0.0",
"pouchdb-find": "^9.0.0",
"pouchdb-mapreduce": "^9.0.0",
"pouchdb-merge": "^9.0.0",
"pouchdb-replication": "^9.0.0",
"pouchdb-utils": "^9.0.0",
"pouchdb-wrappers": "*",
"transform-pouch": "^2.0.0"
}
}
@@ -1,13 +1,13 @@
import type { InjectableServiceHub } from "@lib/services/implements/injectable/InjectableServiceHub"; import type { InjectableServiceHub } from "@lib/services/implements/injectable/InjectableServiceHub";
import { ServiceRebuilder } from "@lib/serviceModules/Rebuilder"; import { ServiceRebuilder } from "@lib/serviceModules/Rebuilder";
import { ServiceFileHandler } from "../../../serviceModules/FileHandler"; import { ServiceFileHandler } from "@/serviceModules/FileHandler";
import { StorageAccessManager } from "@lib/managers/StorageProcessingManager"; import { StorageAccessManager } from "@lib/managers/StorageProcessingManager";
import type { LiveSyncBaseCore } from "../../../LiveSyncBaseCore"; import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore";
import type { ServiceContext } from "@lib/services/base/ServiceBase"; import type { ServiceContext } from "@lib/services/base/ServiceBase";
import { FileAccessCLI } from "./FileAccessCLI"; import { FileAccessCLI } from "./FileAccessCLI";
import { ServiceFileAccessCLI } from "./ServiceFileAccessImpl"; import { ServiceFileAccessCLI } from "./ServiceFileAccessImpl";
import { ServiceDatabaseFileAccessCLI } from "./DatabaseFileAccess"; import { ServiceDatabaseFileAccessCLI } from "./DatabaseFileAccess";
import { StorageEventManagerCLI } from "../managers/StorageEventManagerCLI"; import { StorageEventManagerCLI } from "@/apps/cli/managers/StorageEventManagerCLI";
import type { ServiceModules } from "@lib/interfaces/ServiceModule"; import type { ServiceModules } from "@lib/interfaces/ServiceModule";
import type { IgnoreRules } from "./IgnoreRules"; import type { IgnoreRules } from "./IgnoreRules";
+1 -1
View File
@@ -1,5 +1,5 @@
import { FileAccessBase, type FileAccessBaseDependencies } from "@lib/serviceModules/FileAccessBase"; import { FileAccessBase, type FileAccessBaseDependencies } from "@lib/serviceModules/FileAccessBase";
import { NodeFileSystemAdapter } from "../adapters/NodeFileSystemAdapter"; import { NodeFileSystemAdapter } from "@/apps/cli/adapters/NodeFileSystemAdapter";
/** /**
* CLI-specific implementation of FileAccessBase * CLI-specific implementation of FileAccessBase
+1 -3
View File
@@ -1,7 +1,5 @@
import * as fs from "fs/promises";
import * as path from "path";
import { minimatch } from "minimatch"; import { minimatch } from "minimatch";
import { fsPromises as fs, path } from "../node-compat";
/** /**
* Loads and evaluates ignore rules from `.livesync/ignore` inside the vault. * Loads and evaluates ignore rules from `.livesync/ignore` inside the vault.
@@ -1,5 +1,5 @@
import { ServiceFileAccessBase, type StorageAccessBaseDependencies } from "@lib/serviceModules/ServiceFileAccessBase"; import { ServiceFileAccessBase, type StorageAccessBaseDependencies } from "@lib/serviceModules/ServiceFileAccessBase";
import { NodeFileSystemAdapter } from "../adapters/NodeFileSystemAdapter"; import { NodeFileSystemAdapter } from "@/apps/cli/adapters/NodeFileSystemAdapter";
/** /**
* CLI-specific implementation of ServiceFileAccess * CLI-specific implementation of ServiceFileAccess
@@ -7,8 +7,7 @@ import type { InjectableDatabaseEventService } from "@lib/services/implements/in
import type { IVaultService } from "@lib/services/base/IService"; import type { IVaultService } from "@lib/services/base/IService";
import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase"; import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase";
import { createInstanceLogFunction } from "@lib/services/lib/logUtils"; import { createInstanceLogFunction } from "@lib/services/lib/logUtils";
import * as nodeFs from "node:fs"; import { fs as nodeFs, path as nodePath } from "../node-compat";
import * as nodePath from "node:path";
const NODE_KV_TYPED_KEY = "__nodeKvType"; const NODE_KV_TYPED_KEY = "__nodeKvType";
const NODE_KV_VALUES_KEY = "values"; const NODE_KV_VALUES_KEY = "values";
+1 -2
View File
@@ -1,5 +1,4 @@
import * as nodeFs from "node:fs"; import { fs as nodeFs, path as nodePath } from "../node-compat";
import * as nodePath from "node:path";
type LocalStorageShape = { type LocalStorageShape = {
getItem(key: string): string | null; getItem(key: string): string | null;
+2 -2
View File
@@ -1,6 +1,5 @@
import type { AppLifecycleService, AppLifecycleServiceDependencies } from "@lib/services/base/AppLifecycleService"; import type { AppLifecycleService, AppLifecycleServiceDependencies } from "@lib/services/base/AppLifecycleService";
import { ServiceContext } from "@lib/services/base/ServiceBase"; import { ServiceContext } from "@lib/services/base/ServiceBase";
import * as nodePath from "node:path";
import { ConfigServiceBrowserCompat } from "@lib/services/implements/browser/ConfigServiceBrowserCompat"; import { ConfigServiceBrowserCompat } from "@lib/services/implements/browser/ConfigServiceBrowserCompat";
import { SvelteDialogManagerBase, type ComponentHasResult } from "@lib/services/implements/base/SvelteDialog"; import { SvelteDialogManagerBase, type ComponentHasResult } from "@lib/services/implements/base/SvelteDialog";
import { UIService } from "@lib/services/implements/base/UIService"; import { UIService } from "@lib/services/implements/base/UIService";
@@ -24,7 +23,8 @@ import type { ServiceInstances } from "@lib/services/ServiceHub";
import { NodeKeyValueDBService } from "./NodeKeyValueDBService"; import { NodeKeyValueDBService } from "./NodeKeyValueDBService";
import { NodeSettingService } from "./NodeSettingService"; import { NodeSettingService } from "./NodeSettingService";
import { DatabaseService } from "@lib/services/base/DatabaseService"; import { DatabaseService } from "@lib/services/base/DatabaseService";
import type { ObsidianLiveSyncSettings } from "@/lib/src/common/types"; import type { ObsidianLiveSyncSettings } from "@lib/common/types";
import { path as nodePath } from "../node-compat";
export class NodeServiceContext extends ServiceContext { export class NodeServiceContext extends ServiceContext {
databasePath: string; databasePath: string;
+1 -1
View File
@@ -132,7 +132,7 @@ Deno.test("CLI file operations: push / cat / ls / info / rm / resolve / cat-rev
assertEquals(data.path, REMOTE_PATH, "info .path mismatch"); assertEquals(data.path, REMOTE_PATH, "info .path mismatch");
assertEquals(data.filename, REMOTE_PATH.split("/").at(-1), "info .filename mismatch"); assertEquals(data.filename, REMOTE_PATH.split("/").at(-1), "info .filename mismatch");
assert(typeof data.size === "number" && data.size >= 0, `info .size invalid: ${data.size}`); assert(typeof data.size === "number" && data.size >= 0, `info .size invalid: ${data.size}`);
assert(typeof data.chunks === "number" && (data.chunks as number) >= 1, `info .chunks invalid: ${data.chunks}`); assert(typeof data.chunks === "number" && data.chunks >= 1, `info .chunks invalid: ${data.chunks}`);
assertEquals(data.conflicts, "N/A", "info .conflicts should be N/A"); assertEquals(data.conflicts, "N/A", "info .conflicts should be N/A");
console.log("[PASS] info output format matched"); console.log("[PASS] info output format matched");
}); });
+4 -4
View File
@@ -15,18 +15,18 @@
"noEmit": true, "noEmit": true,
/* Linting */ /* Linting */
"strict": false, "strict": true,
// "noImplicitAny": false,
"noUnusedLocals": false, "noUnusedLocals": false,
"noUnusedParameters": false, "noUnusedParameters": false,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
// "rootDir": "../../../",
/* Path mapping */ /* Path mapping */
"baseUrl": ".",
"paths": { "paths": {
"@/*": ["../../*"], "@/*": ["../../*"],
"@lib/*": ["../../lib/src/*"] "@lib/*": ["../../lib/src/*"]
} }
}, },
"include": ["*.ts", "**/*.ts", "**/*.tsx"], "include": ["*.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules", "dist"] "exclude": ["node_modules", "dist", "test", "testdeno"]
} }
+5 -2
View File
@@ -2,9 +2,12 @@ import { defineConfig } from "vite";
import { svelte } from "@sveltejs/vite-plugin-svelte"; import { svelte } from "@sveltejs/vite-plugin-svelte";
import path from "node:path"; import path from "node:path";
import { readFileSync } from "node:fs"; import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const resolve = (...args: string[]) => path.resolve(...args).replace(/\\/g, "/"); const resolve = (...args: string[]) => path.resolve(...args).replace(/\\/g, "/");
const packageJson = JSON.parse(readFileSync("../../../package.json", "utf-8")); const repoRoot = path.resolve(__dirname, "../../..");
const manifestJson = JSON.parse(readFileSync("../../../manifest.json", "utf-8")); const packageJson = JSON.parse(readFileSync(path.resolve(repoRoot, "package.json"), "utf-8"));
const manifestJson = JSON.parse(readFileSync(path.resolve(repoRoot, "manifest.json"), "utf-8"));
// https://vite.dev/config/ // https://vite.dev/config/
const defaultExternal = [ const defaultExternal = [
"obsidian", "obsidian",
+21 -7
View File
@@ -6,7 +6,7 @@ Note: (I vrtmrz have not tested this so much yet).
- 🌐 Runs entirely in the browser - 🌐 Runs entirely in the browser
- 📁 Uses FileSystem API to access your local vault - 📁 Uses FileSystem API to access your local vault
- 🔄 Syncs with CouchDB, Object Storage server (compatible with Self-hosted LiveSync plugin) - 🔄 Syncs with CouchDB, Object Storage server (compatible with Self-hosted LiveSync plug-in)
- 🚫 No server-side code required!! - 🚫 No server-side code required!!
- 💾 Settings stored in `.livesync/settings.json` within your vault - 💾 Settings stored in `.livesync/settings.json` within your vault
- 👁️ Real-time file watching (Chrome 124+ with FileSystemObserver) - 👁️ Real-time file watching (Chrome 124+ with FileSystemObserver)
@@ -35,8 +35,15 @@ npm install
### Development ### Development
From the repository root:
```bash
npm run dev -w livesync-webapp
```
Or from the package directory:
```bash ```bash
# Build the project (ensure you are in `src/apps/webapp` directory)
cd src/apps/webapp cd src/apps/webapp
npm run dev npm run dev
``` ```
@@ -45,8 +52,15 @@ This will start a development server at `http://localhost:3000`.
### Build ### Build
From the repository root:
```bash
npm run build -w livesync-webapp
```
Or from the package directory:
```bash ```bash
# Build the project (ensure you are in `src/apps/webapp` directory)
cd src/apps/webapp cd src/apps/webapp
npm run build npm run build
``` ```
@@ -113,7 +127,7 @@ webapp/
1. **Adapters**: Implement `IFileSystemAdapter` interface using FileSystem API 1. **Adapters**: Implement `IFileSystemAdapter` interface using FileSystem API
2. **Managers**: Handle storage events and file watching 2. **Managers**: Handle storage events and file watching
3. **Service Modules**: Integrate with LiveSyncBaseCore 3. **Service Modules**: Integrate with LiveSyncBaseCore
4. **Main**: Application initialization and lifecycle management 4. **Main**: Application initialisation and lifecycle management
### Service Hub ### Service Hub
@@ -140,11 +154,11 @@ Uses `BrowserServiceHub` which provides:
- Settings stored in `.livesync/settings.json` in vault - Settings stored in `.livesync/settings.json` in vault
- Real-time file watching only with FileSystemObserver (Chrome 124+) - Real-time file watching only with FileSystemObserver (Chrome 124+)
## Differences from Obsidian Plugin ## Differences from Obsidian Plug-in
- No Obsidian-specific modules (UI, settings dialog, etc.) - No Obsidian-specific modules (UI, settings dialogue, etc.)
- Simplified configuration - Simplified configuration
- No plugin/theme sync features - No plug-in/theme sync features
- No internal file handling (`.obsidian` folder) - No internal file handling (`.obsidian` folder)
## Development Notes ## Development Notes
+11 -10
View File
@@ -1,11 +1,12 @@
import { LiveSyncWebApp } from "./main"; import { LiveSyncWebApp } from "./main";
import { VaultHistoryStore, type VaultHistoryItem } from "./vaultSelector"; import { VaultHistoryStore, type VaultHistoryItem } from "./vaultSelector";
import { compatGlobal, _activeDocument } from "@lib/common/coreEnvFunctions.ts";
const historyStore = new VaultHistoryStore(); const historyStore = new VaultHistoryStore();
let app: LiveSyncWebApp | null = null; let app: LiveSyncWebApp | null = null;
function getRequiredElement<T extends HTMLElement>(id: string): T { function getRequiredElement<T extends HTMLElement>(id: string): T {
const element = document.getElementById(id); const element = _activeDocument.getElementById(id);
if (!element) { if (!element) {
throw new Error(`Missing element: #${id}`); throw new Error(`Missing element: #${id}`);
} }
@@ -22,7 +23,7 @@ function setBusyState(isBusy: boolean): void {
const pickNewBtn = getRequiredElement<HTMLButtonElement>("pick-new-vault"); const pickNewBtn = getRequiredElement<HTMLButtonElement>("pick-new-vault");
pickNewBtn.disabled = isBusy; pickNewBtn.disabled = isBusy;
const historyButtons = document.querySelectorAll<HTMLButtonElement>(".vault-item button"); const historyButtons = _activeDocument.querySelectorAll<HTMLButtonElement>(".vault-item button");
historyButtons.forEach((button) => { historyButtons.forEach((button) => {
button.disabled = isBusy; button.disabled = isBusy;
}); });
@@ -45,24 +46,24 @@ async function renderHistoryList(): Promise<VaultHistoryItem[]> {
emptyEl.classList.toggle("is-hidden", items.length > 0); emptyEl.classList.toggle("is-hidden", items.length > 0);
for (const item of items) { for (const item of items) {
const row = document.createElement("div"); const row = _activeDocument.createElement("div");
row.className = "vault-item"; row.className = "vault-item";
const info = document.createElement("div"); const info = _activeDocument.createElement("div");
info.className = "vault-item-info"; info.className = "vault-item-info";
const name = document.createElement("div"); const name = _activeDocument.createElement("div");
name.className = "vault-item-name"; name.className = "vault-item-name";
name.textContent = item.name; name.textContent = item.name;
const meta = document.createElement("div"); const meta = _activeDocument.createElement("div");
meta.className = "vault-item-meta"; meta.className = "vault-item-meta";
const label = item.id === lastUsedId ? "Last used" : "Used"; const label = item.id === lastUsedId ? "Last used" : "Used";
meta.textContent = `${label}: ${formatLastUsed(item.lastUsedAt)}`; meta.textContent = `${label}: ${formatLastUsed(item.lastUsedAt)}`;
info.append(name, meta); info.append(name, meta);
const useButton = document.createElement("button"); const useButton = _activeDocument.createElement("button");
useButton.type = "button"; useButton.type = "button";
useButton.textContent = "Use this vault"; useButton.textContent = "Use this vault";
useButton.addEventListener("click", () => { useButton.addEventListener("click", () => {
@@ -120,7 +121,7 @@ async function initializeVaultSelector(): Promise<void> {
await renderHistoryList(); await renderHistoryList();
} }
window.addEventListener("load", async () => { compatGlobal.addEventListener("load", async () => {
try { try {
await initializeVaultSelector(); await initializeVaultSelector();
} catch (error) { } catch (error) {
@@ -129,11 +130,11 @@ window.addEventListener("load", async () => {
} }
}); });
window.addEventListener("beforeunload", () => { compatGlobal.addEventListener("beforeunload", () => {
void app?.shutdown(); void app?.shutdown();
}); });
(window as any).livesyncApp = { (compatGlobal as any).livesyncApp = {
getApp: () => app, getApp: () => app,
historyStore, historyStore,
}; };
+10 -11
View File
@@ -17,12 +17,12 @@ import { useSetupURIFeature } from "@lib/serviceFeatures/setupObsidian/setupUri"
import { useRemoteConfiguration } from "@lib/serviceFeatures/remoteConfig"; import { useRemoteConfiguration } from "@lib/serviceFeatures/remoteConfig";
import { SetupManager } from "@/modules/features/SetupManager"; import { SetupManager } from "@/modules/features/SetupManager";
import { useSetupManagerHandlersFeature } from "@/serviceFeatures/setupObsidian/setupManagerHandlers"; import { useSetupManagerHandlersFeature } from "@/serviceFeatures/setupObsidian/setupManagerHandlers";
import { useP2PReplicatorCommands } from "@/lib/src/replication/trystero/useP2PReplicatorCommands"; import { useP2PReplicatorCommands } from "@lib/replication/trystero/useP2PReplicatorCommands";
import { useP2PReplicatorFeature } from "@/lib/src/replication/trystero/useP2PReplicatorFeature"; import { useP2PReplicatorFeature } from "@lib/replication/trystero/useP2PReplicatorFeature";
import { compatGlobal, _activeDocument } from "@lib/common/coreEnvFunctions.ts";
const SETTINGS_DIR = ".livesync"; const SETTINGS_DIR = ".livesync";
const SETTINGS_FILE = "settings.json"; const SETTINGS_FILE = "settings.json";
const DB_NAME = "livesync-webapp";
/** /**
* Default settings for the webapp * Default settings for the webapp
@@ -64,7 +64,6 @@ class LiveSyncWebApp {
console.log(`Vault directory: ${this.rootHandle.name}`); console.log(`Vault directory: ${this.rootHandle.name}`);
// Create service context and hub // Create service context and hub
const context = new ServiceContext();
this.serviceHub = new BrowserServiceHub<ServiceContext>(); this.serviceHub = new BrowserServiceHub<ServiceContext>();
// Setup API service // Setup API service
@@ -91,7 +90,7 @@ class LiveSyncWebApp {
console.log("[Settings] Loaded from .livesync/settings.json"); console.log("[Settings] Loaded from .livesync/settings.json");
return { ...DEFAULT_SETTINGS, ...data } as ObsidianLiveSyncSettings; return { ...DEFAULT_SETTINGS, ...data } as ObsidianLiveSyncSettings;
} }
} catch (error) { } catch {
console.log("[Settings] Failed to load, using defaults"); console.log("[Settings] Failed to load, using defaults");
} }
return DEFAULT_SETTINGS as ObsidianLiveSyncSettings; return DEFAULT_SETTINGS as ObsidianLiveSyncSettings;
@@ -102,8 +101,8 @@ class LiveSyncWebApp {
console.log("[AppLifecycle] Restart requested"); console.log("[AppLifecycle] Restart requested");
await this.shutdown(); await this.shutdown();
await this.initialize(); await this.initialize();
setTimeout(() => { compatGlobal.setTimeout(() => {
window.location.reload(); compatGlobal.location.reload();
}, 1000); }, 1000);
}); });
@@ -169,7 +168,7 @@ class LiveSyncWebApp {
const file = await fileHandle.getFile(); const file = await fileHandle.getFile();
const text = await file.text(); const text = await file.text();
return JSON.parse(text); return JSON.parse(text);
} catch (error) { } catch {
// File doesn't exist yet // File doesn't exist yet
return null; return null;
} }
@@ -235,7 +234,7 @@ class LiveSyncWebApp {
} }
private showError(message: string) { private showError(message: string) {
const statusEl = document.getElementById("status"); const statusEl = _activeDocument.getElementById("status");
if (statusEl) { if (statusEl) {
statusEl.className = "error"; statusEl.className = "error";
statusEl.textContent = `Error: ${message}`; statusEl.textContent = `Error: ${message}`;
@@ -243,7 +242,7 @@ class LiveSyncWebApp {
} }
private showWarning(message: string) { private showWarning(message: string) {
const statusEl = document.getElementById("status"); const statusEl = _activeDocument.getElementById("status");
if (statusEl) { if (statusEl) {
statusEl.className = "warning"; statusEl.className = "warning";
statusEl.textContent = `Warning: ${message}`; statusEl.textContent = `Warning: ${message}`;
@@ -251,7 +250,7 @@ class LiveSyncWebApp {
} }
private showSuccess(message: string) { private showSuccess(message: string) {
const statusEl = document.getElementById("status"); const statusEl = _activeDocument.getElementById("status");
if (statusEl) { if (statusEl) {
statusEl.className = "success"; statusEl.className = "success";
statusEl.textContent = message; statusEl.textContent = message;
@@ -10,7 +10,8 @@ import type {
IStorageEventWatchHandlers, IStorageEventWatchHandlers,
} from "@lib/managers/adapters"; } from "@lib/managers/adapters";
import type { FileEventItemSentinel } from "@lib/managers/StorageEventManager"; import type { FileEventItemSentinel } from "@lib/managers/StorageEventManager";
import type { FSAPIFile, FSAPIFolder } from "../adapters/FSAPITypes"; import type { FSAPIFile, FSAPIFolder } from "@/apps/webapp/adapters/FSAPITypes";
import { compatGlobal } from "@lib/common/coreEnvFunctions.ts";
/** /**
* FileSystem API-specific type guard adapter * FileSystem API-specific type guard adapter
@@ -149,18 +150,17 @@ class FSAPIWatchAdapter implements IStorageEventWatchAdapter {
async beginWatch(handlers: IStorageEventWatchHandlers): Promise<void> { async beginWatch(handlers: IStorageEventWatchHandlers): Promise<void> {
// Use FileSystemObserver if available (Chrome 124+) // Use FileSystemObserver if available (Chrome 124+)
if (typeof (window as any).FileSystemObserver === "undefined") { if (typeof (compatGlobal as any).FileSystemObserver === "undefined") {
console.log("[FSAPIWatchAdapter] FileSystemObserver not available, file watching disabled"); console.log("[FSAPIWatchAdapter] FileSystemObserver not available, file watching disabled");
console.log("[FSAPIWatchAdapter] Consider using Chrome 124+ for real-time file watching"); console.log("[FSAPIWatchAdapter] Consider using Chrome 124+ for real-time file watching");
return Promise.resolve(); return Promise.resolve();
} }
try { try {
const FileSystemObserver = (window as any).FileSystemObserver; const FileSystemObserver = (compatGlobal as any).FileSystemObserver;
this.observer = new FileSystemObserver(async (records: any[]) => { this.observer = new FileSystemObserver(async (records: any[]) => {
for (const record of records) { for (const record of records) {
const handle = record.root;
const changedHandle = record.changedHandle; const changedHandle = record.changedHandle;
const relativePathComponents = record.relativePathComponents; const relativePathComponents = record.relativePathComponents;
const type = record.type; // "appeared", "disappeared", "modified", "moved", "unknown", "errored" const type = record.type; // "appeared", "disappeared", "modified", "moved", "unknown", "errored"
@@ -181,7 +181,7 @@ class FSAPIWatchAdapter implements IStorageEventWatchAdapter {
if (changedHandle && changedHandle.kind === "file") { if (changedHandle && changedHandle.kind === "file") {
const file = await changedHandle.getFile(); const file = await changedHandle.getFile();
const fileInfo = { const fileInfo = {
path: relativePath as any, path: relativePath,
stat: { stat: {
size: file.size, size: file.size,
mtime: file.lastModified, mtime: file.lastModified,
@@ -199,7 +199,7 @@ class FSAPIWatchAdapter implements IStorageEventWatchAdapter {
} }
} else if (type === "disappeared") { } else if (type === "disappeared") {
const fileInfo = { const fileInfo = {
path: relativePath as any, path: relativePath,
stat: { stat: {
size: 0, size: 0,
mtime: Date.now(), mtime: Date.now(),
@@ -216,7 +216,7 @@ class FSAPIWatchAdapter implements IStorageEventWatchAdapter {
if (changedHandle && changedHandle.kind === "file") { if (changedHandle && changedHandle.kind === "file") {
const file = await changedHandle.getFile(); const file = await changedHandle.getFile();
const fileInfo = { const fileInfo = {
path: relativePath as any, path: relativePath,
stat: { stat: {
size: file.size, size: file.size,
mtime: file.lastModified, mtime: file.lastModified,
+10 -3
View File
@@ -1,7 +1,7 @@
{ {
"name": "livesync-webapp", "name": "livesync-webapp",
"private": true, "private": true,
"version": "0.0.1", "version": "0.25.76-webapp",
"type": "module", "type": "module",
"description": "Browser-based Self-hosted LiveSync using FileSystem API", "description": "Browser-based Self-hosted LiveSync using FileSystem API",
"scripts": { "scripts": {
@@ -11,9 +11,16 @@
"run:docker": "docker run -p 8002:80 livesync-webapp", "run:docker": "docker run -p 8002:80 livesync-webapp",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": {}, "dependencies": {
"octagonal-wheels": "^0.1.46"
},
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.58.2",
"@sveltejs/vite-plugin-svelte": "^7.1.2",
"playwright": "^1.58.2",
"svelte": "5.56.3",
"typescript": "5.9.3", "typescript": "5.9.3",
"vite": "^7.3.1" "vite": "^8.0.16",
"vite-plugin-istanbul": "^9.0.1"
} }
} }
@@ -7,7 +7,7 @@ import type { ServiceContext } from "@lib/services/base/ServiceBase";
import { FileAccessFSAPI } from "./FileAccessFSAPI"; import { FileAccessFSAPI } from "./FileAccessFSAPI";
import { ServiceFileAccessFSAPI } from "./ServiceFileAccessImpl"; import { ServiceFileAccessFSAPI } from "./ServiceFileAccessImpl";
import { ServiceDatabaseFileAccessFSAPI } from "./DatabaseFileAccess"; import { ServiceDatabaseFileAccessFSAPI } from "./DatabaseFileAccess";
import { StorageEventManagerFSAPI } from "../managers/StorageEventManagerFSAPI"; import { StorageEventManagerFSAPI } from "@/apps/webapp/managers/StorageEventManagerFSAPI";
import type { ServiceModules } from "@lib/interfaces/ServiceModule"; import type { ServiceModules } from "@lib/interfaces/ServiceModule";
import { ServiceFileHandler } from "@/serviceModules/FileHandler"; import { ServiceFileHandler } from "@/serviceModules/FileHandler";
@@ -1,5 +1,5 @@
import { FileAccessBase, type FileAccessBaseDependencies } from "@lib/serviceModules/FileAccessBase"; import { FileAccessBase, type FileAccessBaseDependencies } from "@lib/serviceModules/FileAccessBase";
import { FSAPIFileSystemAdapter } from "../adapters/FSAPIFileSystemAdapter"; import { FSAPIFileSystemAdapter } from "@/apps/webapp/adapters/FSAPIFileSystemAdapter";
/** /**
* FileSystem API-specific implementation of FileAccessBase * FileSystem API-specific implementation of FileAccessBase
@@ -1,5 +1,5 @@
import { ServiceFileAccessBase, type StorageAccessBaseDependencies } from "@lib/serviceModules/ServiceFileAccessBase"; import { ServiceFileAccessBase, type StorageAccessBaseDependencies } from "@lib/serviceModules/ServiceFileAccessBase";
import { FSAPIFileSystemAdapter } from "../adapters/FSAPIFileSystemAdapter"; import { FSAPIFileSystemAdapter } from "@/apps/webapp/adapters/FSAPIFileSystemAdapter";
/** /**
* FileSystem API-specific implementation of ServiceFileAccess * FileSystem API-specific implementation of ServiceFileAccess
+4 -3
View File
@@ -12,6 +12,7 @@
import { LiveSyncWebApp } from "./main"; import { LiveSyncWebApp } from "./main";
import type { ObsidianLiveSyncSettings } from "@lib/common/types"; import type { ObsidianLiveSyncSettings } from "@lib/common/types";
import type { FilePathWithPrefix } from "@lib/common/types"; import type { FilePathWithPrefix } from "@lib/common/types";
import { compatGlobal } from "@lib/common/coreEnvFunctions.ts";
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
// Internal state one app instance per page / browser context // Internal state one app instance per page / browser context
@@ -41,7 +42,7 @@ async function waitForIdle(core: any, timeoutMs = 60_000): Promise<void> {
(core.services?.fileProcessing?.processing?.value ?? 0) + (core.services?.fileProcessing?.processing?.value ?? 0) +
(core.services?.replication?.storageApplyingCount?.value ?? 0); (core.services?.replication?.storageApplyingCount?.value ?? 0);
if (q === 0) return; if (q === 0) return;
await new Promise<void>((r) => setTimeout(r, 300)); await new Promise<void>((r) => compatGlobal.setTimeout(r, 300));
} }
throw new Error(`waitForIdle timed out after ${timeoutMs} ms`); throw new Error(`waitForIdle timed out after ${timeoutMs} ms`);
} }
@@ -116,7 +117,7 @@ export interface LiveSyncTestAPI {
const livesyncTest: LiveSyncTestAPI = { const livesyncTest: LiveSyncTestAPI = {
async init(vaultName: string, settings: Partial<ObsidianLiveSyncSettings>): Promise<void> { async init(vaultName: string, settings: Partial<ObsidianLiveSyncSettings>): Promise<void> {
// Clean up any stale OPFS data from previous runs. // Clean up any stale OPFS data from previous runs.
const opfsRoot = await navigator.storage.getDirectory(); const opfsRoot = await compatGlobal.navigator.storage.getDirectory();
try { try {
await opfsRoot.removeEntry(vaultName, { recursive: true }); await opfsRoot.removeEntry(vaultName, { recursive: true });
} catch { } catch {
@@ -200,4 +201,4 @@ const livesyncTest: LiveSyncTestAPI = {
}; };
// Expose on window for Playwright page.evaluate() calls. // Expose on window for Playwright page.evaluate() calls.
(window as any).livesyncTest = livesyncTest; (compatGlobal as any).livesyncTest = livesyncTest;
+1 -1
View File
@@ -17,7 +17,7 @@
*/ */
import { test, expect, type BrowserContext, type Page, type TestInfo } from "@playwright/test"; import { test, expect, type BrowserContext, type Page, type TestInfo } from "@playwright/test";
import type { LiveSyncTestAPI } from "../test-entry"; import type { LiveSyncTestAPI } from "@/apps/webapp/test-entry";
import { mkdirSync, writeFileSync } from "node:fs"; import { mkdirSync, writeFileSync } from "node:fs";
import path from "node:path"; import path from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
+2 -2
View File
@@ -21,12 +21,12 @@
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
/* Path mapping */ /* Path mapping */
"baseUrl": ".", // "baseUrl": ".",
"paths": { "paths": {
"@/*": ["../../*"], "@/*": ["../../*"],
"@lib/*": ["../../lib/src/*"] "@lib/*": ["../../lib/src/*"]
} }
}, },
"include": ["*.ts", "**/*.ts", "**/*.tsx"], "include": ["*.ts", "**/*.ts", "**/*.tsx", "**/*.svelte"],
"exclude": ["node_modules", "dist"] "exclude": ["node_modules", "dist"]
} }
+4 -2
View File
@@ -1,3 +1,5 @@
import { compatGlobal } from "@lib/common/coreEnvFunctions.ts";
const HANDLE_DB_NAME = "livesync-webapp-handles"; const HANDLE_DB_NAME = "livesync-webapp-handles";
const HANDLE_STORE_NAME = "handles"; const HANDLE_STORE_NAME = "handles";
const LAST_USED_KEY = "meta:lastUsedVaultId"; const LAST_USED_KEY = "meta:lastUsedVaultId";
@@ -89,7 +91,7 @@ export class VaultHistoryStore {
async getVaultHistory(): Promise<VaultHistoryItem[]> { async getVaultHistory(): Promise<VaultHistoryItem[]> {
return this.withStore("readonly", async (store) => { return this.withStore("readonly", async (store) => {
const keys = (await this.requestAsPromise(store.getAllKeys())) as IDBValidKey[]; const keys = await this.requestAsPromise(store.getAllKeys());
const values = (await this.requestAsPromise(store.getAll())) as unknown[]; const values = (await this.requestAsPromise(store.getAll())) as unknown[];
const items: VaultHistoryItem[] = []; const items: VaultHistoryItem[] = [];
for (let i = 0; i < keys.length; i++) { for (let i = 0; i < keys.length; i++) {
@@ -170,7 +172,7 @@ export class VaultHistoryStore {
} }
async pickNewVault(): Promise<FileSystemDirectoryHandle> { async pickNewVault(): Promise<FileSystemDirectoryHandle> {
const picker = (window as any).showDirectoryPicker; const picker = (compatGlobal as any).showDirectoryPicker;
if (typeof picker !== "function") { if (typeof picker !== "function") {
throw new Error("FileSystem API showDirectoryPicker is not supported in this browser"); throw new Error("FileSystem API showDirectoryPicker is not supported in this browser");
} }
+5 -3
View File
@@ -3,10 +3,12 @@ import { svelte } from "@sveltejs/vite-plugin-svelte";
import istanbul from "vite-plugin-istanbul"; import istanbul from "vite-plugin-istanbul";
import path from "node:path"; import path from "node:path";
import { readFileSync } from "node:fs"; import { readFileSync } from "node:fs";
const packageJson = JSON.parse(readFileSync("../../../package.json", "utf-8")); import { fileURLToPath } from "node:url";
const manifestJson = JSON.parse(readFileSync("../../../manifest.json", "utf-8")); const __dirname = path.dirname(fileURLToPath(import.meta.url));
const enableCoverage = process.env.PW_COVERAGE === "1";
const repoRoot = path.resolve(__dirname, "../../.."); const repoRoot = path.resolve(__dirname, "../../..");
const packageJson = JSON.parse(readFileSync(path.resolve(repoRoot, "package.json"), "utf-8"));
const manifestJson = JSON.parse(readFileSync(path.resolve(repoRoot, "manifest.json"), "utf-8"));
const enableCoverage = process.env.PW_COVERAGE === "1";
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
+10 -3
View File
@@ -13,13 +13,20 @@ This pseudo client actually receives the data from other devices, and sends if s
## How to use it? ## How to use it?
We can build the application by running the following command: We can build the application from the repository root by running the following command:
```bash ```bash
$ deno task build npm run build -w webpeer
``` ```
Then, open the `dist/index.html` in the browser. It can be configured as the same as the Self-hosted LiveSync (Same components are used[^1]). Or from the package directory:
```bash
cd src/apps/webpeer
npm run build
```
Then, open `dist/index.html` in the browser. It can be configured in the same way as Self-hosted LiveSync (the same components are used[^1]).
## Some notes ## Some notes
+9 -7
View File
@@ -1,7 +1,7 @@
{ {
"name": "webpeer", "name": "webpeer",
"private": true, "private": true,
"version": "0.0.0", "version": "0.25.76-webpeer",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
@@ -11,15 +11,17 @@
"preview": "vite preview", "preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json" "check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json"
}, },
"dependencies": {}, "dependencies": {
"octagonal-wheels": "^0.1.46"
},
"devDependencies": { "devDependencies": {
"eslint-plugin-svelte": "^3.15.0", "eslint-plugin-svelte": "^3.19.0",
"@sveltejs/vite-plugin-svelte": "^6.2.4", "@sveltejs/vite-plugin-svelte": "^7.1.2",
"@tsconfig/svelte": "^5.0.8", "@tsconfig/svelte": "^5.0.8",
"svelte": "5.41.1", "svelte": "5.56.3",
"svelte-check": "^443.3", "svelte-check": "^4.6.0",
"typescript": "5.9.3", "typescript": "5.9.3",
"vite": "^7.3.1" "vite": "^8.0.16"
}, },
"imports": { "imports": {
"../../src/worker/bgWorker.ts": "../../src/worker/bgWorker.mock.ts", "../../src/worker/bgWorker.ts": "../../src/worker/bgWorker.mock.ts",
+10 -8
View File
@@ -11,13 +11,14 @@ import { eventHub } from "@lib/hub/hub";
import type { Confirm } from "@lib/interfaces/Confirm"; import type { Confirm } from "@lib/interfaces/Confirm";
import { LOG_LEVEL_NOTICE, Logger } from "@lib/common/logger"; import { LOG_LEVEL_NOTICE, Logger } from "@lib/common/logger";
import { storeP2PStatusLine } from "./CommandsShim";
import { import {
EVENT_P2P_PEER_SHOW_EXTRA_MENU, EVENT_P2P_PEER_SHOW_EXTRA_MENU,
type PeerStatus, type PeerStatus,
type PluginShim, type PluginShim,
} from "@lib/replication/trystero/P2PReplicatorPaneCommon"; } from "@lib/replication/trystero/P2PReplicatorPaneCommon";
import { P2PLogCollector, type P2PReplicatorBase, useP2PReplicator } from "@lib/replication/trystero/P2PReplicatorCore"; import { useP2PReplicator } from "@lib/replication/trystero/P2PReplicatorCore";
import { P2PLogCollector } from "@lib/replication/trystero/P2PLogCollector";
import type { P2PReplicatorBase } from "@lib/replication/trystero/P2PReplicatorBase.ts";
import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase"; import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase";
import { reactiveSource } from "octagonal-wheels/dataobject/reactive_v2"; import { reactiveSource } from "octagonal-wheels/dataobject/reactive_v2";
import { EVENT_SETTING_SAVED } from "@lib/events/coreEvents"; import { EVENT_SETTING_SAVED } from "@lib/events/coreEvents";
@@ -28,9 +29,10 @@ import { ServiceContext } from "@lib/services/base/ServiceBase";
import type { InjectableServiceHub } from "@lib/services/InjectableServices"; import type { InjectableServiceHub } from "@lib/services/InjectableServices";
import { Menu } from "@lib/services/implements/browser/Menu"; import { Menu } from "@lib/services/implements/browser/Menu";
import { SimpleStoreIDBv2 } from "octagonal-wheels/databases/SimpleStoreIDBv2"; import { SimpleStoreIDBv2 } from "octagonal-wheels/databases/SimpleStoreIDBv2";
import type { BrowserAPIService } from "@/lib/src/services/implements/browser/BrowserAPIService"; import type { BrowserAPIService } from "@lib/services/implements/browser/BrowserAPIService";
import type { InjectableSettingService } from "@/lib/src/services/implements/injectable/InjectableSettingService"; import type { InjectableSettingService } from "@lib/services/implements/injectable/InjectableSettingService";
import { LiveSyncTrysteroReplicator } from "@lib/replication/trystero/LiveSyncTrysteroReplicator"; import { LiveSyncTrysteroReplicator } from "@lib/replication/trystero/LiveSyncTrysteroReplicator";
import { compatGlobal } from "@lib/common/coreEnvFunctions.ts";
function addToList(item: string, list: string) { function addToList(item: string, list: string) {
return unique( return unique(
@@ -84,7 +86,7 @@ export class P2PReplicatorShim implements P2PReplicatorBase {
this._liveSyncReplicator = replicator; this._liveSyncReplicator = replicator;
this.p2pLogCollector = p2pLogCollector; this.p2pLogCollector = p2pLogCollector;
p2pLogCollector.p2pReplicationLine.onChanged((line) => { p2pLogCollector.p2pReplicationLine.onChanged((line) => {
storeP2PStatusLine.set(line.value); p2pStatusLine.value = line.value;
}); });
} }
@@ -137,7 +139,7 @@ export class P2PReplicatorShim implements P2PReplicatorBase {
this._initP2PReplicator(); this._initP2PReplicator();
setTimeout(() => { compatGlobal.setTimeout(() => {
if (this.settings.P2P_AutoStart && this.settings.P2P_Enabled) { if (this.settings.P2P_AutoStart && this.settings.P2P_Enabled) {
void this.open(); void this.open();
} }
@@ -164,12 +166,12 @@ export class P2PReplicatorShim implements P2PReplicatorBase {
getConfig(key: string) { getConfig(key: string) {
const vaultName = this.services.vault.getVaultName(); const vaultName = this.services.vault.getVaultName();
const dbKey = `${vaultName}-${key}`; const dbKey = `${vaultName}-${key}`;
return localStorage.getItem(dbKey); return compatGlobal.localStorage.getItem(dbKey);
} }
setConfig(key: string, value: string) { setConfig(key: string, value: string) {
const vaultName = this.services.vault.getVaultName(); const vaultName = this.services.vault.getVaultName();
const dbKey = `${vaultName}-${key}`; const dbKey = `${vaultName}-${key}`;
localStorage.setItem(dbKey, value); compatGlobal.localStorage.setItem(dbKey, value);
} }
getDeviceName(): string { getDeviceName(): string {
+1 -1
View File
@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { Menu } from "@/lib/src/services/implements/browser/Menu"; import { Menu } from "@lib/services/implements/browser/Menu";
import { getDialogContext } from "@lib/services/implements/base/SvelteDialog"; import { getDialogContext } from "@lib/services/implements/base/SvelteDialog";
let result = $state<string | boolean>(""); let result = $state<string | boolean>("");
+2 -1
View File
@@ -1,9 +1,10 @@
import { mount } from "svelte"; import { mount } from "svelte";
import "./app.css"; import "./app.css";
import App from "./App.svelte"; import App from "./App.svelte";
import { _activeDocument } from "@lib/common/coreEnvFunctions.ts";
const app = mount(App, { const app = mount(App, {
target: document.getElementById("app")!, target: _activeDocument.getElementById("app")!,
}); });
export default app; export default app;
+2 -1
View File
@@ -1,9 +1,10 @@
import { mount } from "svelte"; import { mount } from "svelte";
import "./app.css"; import "./app.css";
import App from "./UITest.svelte"; import App from "./UITest.svelte";
import { _activeDocument } from "@lib/common/coreEnvFunctions.ts";
const app = mount(App, { const app = mount(App, {
target: document.getElementById("app")!, target: _activeDocument.getElementById("app")!,
}); });
export default app; export default app;
+5 -3
View File
@@ -1,7 +1,7 @@
{ {
"extends": "@tsconfig/svelte/tsconfig.json", "extends": "../../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
"sourceRoot": "../", // "sourceRoot": "../",
"target": "ESNext", "target": "ESNext",
"useDefineForClassFields": true, "useDefineForClassFields": true,
"module": "ESNext", "module": "ESNext",
@@ -15,11 +15,13 @@
"allowJs": true, "allowJs": true,
"checkJs": true, "checkJs": true,
"isolatedModules": true, "isolatedModules": true,
"allowImportingTsExtensions": true,
"moduleDetection": "force", "moduleDetection": "force",
"paths": { "paths": {
"@/*": ["../../*"], "@/*": ["../../*"],
"@lib/*": ["../../lib/src/*"] "@lib/*": ["../../lib/src/*"]
} }
}, },
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"] "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
"exclude": ["node_modules", "dist"]
} }
+3 -3
View File
@@ -1,8 +1,8 @@
import { deleteDB, type IDBPDatabase, openDB } from "idb"; import { deleteDB, type IDBPDatabase, openDB } from "idb";
import type { KeyValueDatabase } from "../lib/src/interfaces/KeyValueDatabase.ts"; import type { KeyValueDatabase } from "@lib/interfaces/KeyValueDatabase.ts";
import { serialized } from "octagonal-wheels/concurrency/lock"; import { serialized } from "octagonal-wheels/concurrency/lock";
import { Logger } from "octagonal-wheels/common/logger"; import { Logger } from "octagonal-wheels/common/logger";
const databaseCache: { [key: string]: IDBPDatabase<any> } = {}; const databaseCache: { [key: string]: IDBPDatabase<unknown> } = {};
export { OpenKeyValueDatabase } from "./KeyValueDBv2.ts"; export { OpenKeyValueDatabase } from "./KeyValueDBv2.ts";
export const _OpenKeyValueDatabase = async (dbKey: string): Promise<KeyValueDatabase> => { export const _OpenKeyValueDatabase = async (dbKey: string): Promise<KeyValueDatabase> => {
@@ -11,7 +11,7 @@ export const _OpenKeyValueDatabase = async (dbKey: string): Promise<KeyValueData
delete databaseCache[dbKey]; delete databaseCache[dbKey];
} }
const storeKey = dbKey; const storeKey = dbKey;
let db: IDBPDatabase<any> | null = null; let db: IDBPDatabase<unknown> | null = null;
const _openDB = () => { const _openDB = () => {
return serialized("keyvaluedb-" + dbKey, async () => { return serialized("keyvaluedb-" + dbKey, async () => {
const dbInstance = await openDB(dbKey, 1, { const dbInstance = await openDB(dbKey, 1, {
+5 -5
View File
@@ -1,5 +1,5 @@
import { LOG_LEVEL_VERBOSE, Logger } from "@/lib/src/common/logger"; import { LOG_LEVEL_VERBOSE, Logger } from "@lib/common/logger";
import type { KeyValueDatabase } from "@/lib/src/interfaces/KeyValueDatabase"; import type { KeyValueDatabase } from "@lib/interfaces/KeyValueDatabase";
import { deleteDB, openDB, type IDBPDatabase } from "idb"; import { deleteDB, openDB, type IDBPDatabase } from "idb";
import { serialized } from "octagonal-wheels/concurrency/lock"; import { serialized } from "octagonal-wheels/concurrency/lock";
@@ -28,7 +28,7 @@ export async function OpenKeyValueDatabase(dbKey: string): Promise<KeyValueDatab
} }
export class IDBKeyValueDatabase implements KeyValueDatabase { export class IDBKeyValueDatabase implements KeyValueDatabase {
protected _dbPromise: Promise<IDBPDatabase<any>> | null = null; protected _dbPromise: Promise<IDBPDatabase<unknown>> | null = null;
protected dbKey: string; protected dbKey: string;
protected storeKey: string; protected storeKey: string;
protected _isDestroyed: boolean = false; protected _isDestroyed: boolean = false;
@@ -104,7 +104,7 @@ export class IDBKeyValueDatabase implements KeyValueDatabase {
this.destroyedPromise = Promise.resolve(); this.destroyedPromise = Promise.resolve();
} }
} }
get DB(): Promise<IDBPDatabase<any>> { get DB(): Promise<IDBPDatabase<unknown>> {
if (this._isDestroyed) { if (this._isDestroyed) {
return Promise.reject(new Error("Database is destroyed")); return Promise.reject(new Error("Database is destroyed"));
} }
@@ -117,7 +117,7 @@ export class IDBKeyValueDatabase implements KeyValueDatabase {
} }
async get<U>(key: IDBValidKey): Promise<U> { async get<U>(key: IDBValidKey): Promise<U> {
const db = await this.DB; const db = await this.DB;
return await db.get(this.storeKey, key); return (await db.get(this.storeKey, key)) as U;
} }
async set<U>(key: IDBValidKey, value: U): Promise<IDBValidKey> { async set<U>(key: IDBValidKey, value: U): Promise<IDBValidKey> {
const db = await this.DB; const db = await this.DB;
+2 -2
View File
@@ -4,10 +4,10 @@ import { eventHub, EVENT_PLUGIN_UNLOADED } from "./events";
import type { NecessaryServices } from "@lib/interfaces/ServiceModule"; import type { NecessaryServices } from "@lib/interfaces/ServiceModule";
type PeriodicProcessorHost = NecessaryServices<"API" | "control", never>; type PeriodicProcessorHost = NecessaryServices<"API" | "control", never>;
export class PeriodicProcessor { export class PeriodicProcessor {
_process: () => Promise<any>; _process: () => Promise<unknown>;
_timer?: number = undefined; _timer?: number = undefined;
_core: PeriodicProcessorHost; _core: PeriodicProcessorHost;
constructor(core: PeriodicProcessorHost, process: () => Promise<any>) { constructor(core: PeriodicProcessorHost, process: () => Promise<unknown>) {
// this._plugin = plugin; // this._plugin = plugin;
this._core = core; this._core = core;
this._process = process; this._process = process;
+2 -2
View File
@@ -1,4 +1,4 @@
import { eventHub } from "../lib/src/hub/hub"; import { eventHub } from "@lib/hub/hub";
// import type ObsidianLiveSyncPlugin from "../main"; // import type ObsidianLiveSyncPlugin from "../main";
export const EVENT_PLUGIN_LOADED = "plugin-loaded"; export const EVENT_PLUGIN_LOADED = "plugin-loaded";
@@ -43,5 +43,5 @@ declare global {
} }
} }
export * from "../lib/src/events/coreEvents.ts"; export * from "@lib/events/coreEvents.ts";
export { eventHub }; export { eventHub };
+2 -2
View File
@@ -1,5 +1,5 @@
import type { TFile } from "../deps"; import type { TFile } from "@/deps";
import type { FilePathWithPrefix, LoadedEntry } from "../lib/src/common/types"; import type { FilePathWithPrefix, LoadedEntry } from "@lib/common/types";
export const EVENT_REQUEST_SHOW_HISTORY = "show-history"; export const EVENT_REQUEST_SHOW_HISTORY = "show-history";
+6 -6
View File
@@ -9,16 +9,16 @@ import { isCloudantURI } from "@lib/pouchdb/utils_couchdb";
import { compatGlobal } from "@lib/common/coreEnvFunctions"; import { compatGlobal } from "@lib/common/coreEnvFunctions";
import { manifestVersion, packageVersion } from "@lib/common/coreEnvVars"; import { manifestVersion, packageVersion } from "@lib/common/coreEnvVars";
import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore"; import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore";
function redactObject(obj: Record<string, any>, dotted: string, redactedValue = "REDACTED") { function redactObject(obj: Record<string, unknown>, dotted: string, redactedValue = "REDACTED") {
const keys = dotted.split("."); const keys = dotted.split(".");
let current = obj; let current = obj;
for (let i = 0; i < keys.length - 1; i++) { for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i]; const key = keys[i];
if (!(key in current)) { if (!(key in current)) {
current[key] = {} as Record<string, any>; current[key] = {};
} }
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
current = current[key]; current = current[key] as Record<string, unknown>;
} }
const lastKey = keys[keys.length - 1]; const lastKey = keys[keys.length - 1];
if (lastKey in current) { if (lastKey in current) {
@@ -27,7 +27,7 @@ function redactObject(obj: Record<string, any>, dotted: string, redactedValue =
return obj; return obj;
} }
export async function generateReport(settings: ObsidianLiveSyncSettings, core: LiveSyncBaseCore) { export async function generateReport(settings: ObsidianLiveSyncSettings, core: LiveSyncBaseCore) {
let responseConfig: Record<string, any> = {}; let responseConfig: Record<string, unknown> = {};
const REDACTED = "𝑅𝐸𝐷𝐴𝐶𝑇𝐸𝐷"; const REDACTED = "𝑅𝐸𝐷𝐴𝐶𝑇𝐸𝐷";
if (settings.remoteType == REMOTE_COUCHDB) { if (settings.remoteType == REMOTE_COUCHDB) {
try { try {
@@ -36,13 +36,13 @@ export async function generateReport(settings: ObsidianLiveSyncSettings, core: L
const r = await requestToCouchDBWithCredentials( const r = await requestToCouchDBWithCredentials(
settings.couchDB_URI, settings.couchDB_URI,
credential, credential,
window.origin, compatGlobal.origin,
undefined, undefined,
undefined, undefined,
undefined, undefined,
customHeaders customHeaders
); );
responseConfig = r.json as Record<string, any>; responseConfig = r.json as Record<string, unknown>;
redactObject(responseConfig, "couch_httpd_auth.secret"); redactObject(responseConfig, "couch_httpd_auth.secret");
redactObject(responseConfig, "couch_httpd_auth.authentication_db"); redactObject(responseConfig, "couch_httpd_auth.authentication_db");
redactObject(responseConfig, "couch_httpd_auth.authentication_redirect"); redactObject(responseConfig, "couch_httpd_auth.authentication_redirect");
+5 -5
View File
@@ -1,6 +1,6 @@
import { type PluginManifest, TFile } from "../deps.ts"; import { type PluginManifest, TFile } from "@/deps.ts";
import { type DatabaseEntry, type EntryBody, type FilePath } from "../lib/src/common/types.ts"; import { type DatabaseEntry, type EntryBody, type FilePath } from "@lib/common/types.ts";
export type { CacheData, FileEventItem } from "../lib/src/common/types.ts"; export type { CacheData, FileEventItem } from "@lib/common/types.ts";
export interface PluginDataEntry extends DatabaseEntry { export interface PluginDataEntry extends DatabaseEntry {
deviceVaultName: string; deviceVaultName: string;
@@ -51,7 +51,7 @@ export type queueItem = {
export const FileWatchEventQueueMax = 10; export const FileWatchEventQueueMax = 10;
export { configURIBase, configURIBaseQR } from "../lib/src/common/types.ts"; export { configURIBase, configURIBaseQR } from "@lib/common/types.ts";
export { export {
CHeader, CHeader,
@@ -61,4 +61,4 @@ export {
ICHeaderEnd, ICHeaderEnd,
ICHeaderLength, ICHeaderLength,
ICXHeader, ICXHeader,
} from "../lib/src/common/models/fileaccess.const.ts"; } from "@lib/common/models/fileaccess.const.ts";
+22 -22
View File
@@ -1,4 +1,4 @@
import { normalizePath, Platform, TAbstractFile, type RequestUrlParam, requestUrl } from "../deps.ts"; import { normalizePath, Platform, TAbstractFile, type RequestUrlParam, requestUrl } from "@/deps.ts";
import { import {
path2id_base, path2id_base,
id2path_base, id2path_base,
@@ -7,9 +7,9 @@ import {
isValidFilenameInWidows, isValidFilenameInWidows,
isValidFilenameInAndroid, isValidFilenameInAndroid,
stripAllPrefixes, stripAllPrefixes,
} from "../lib/src/string_and_binary/path.ts"; } from "@lib/string_and_binary/path.ts";
import { Logger } from "../lib/src/common/logger.ts"; import { Logger } from "@lib/common/logger.ts";
import { import {
LOG_LEVEL_INFO, LOG_LEVEL_INFO,
LOG_LEVEL_NOTICE, LOG_LEVEL_NOTICE,
@@ -22,14 +22,14 @@ import {
type FilePathWithPrefix, type FilePathWithPrefix,
type UXFileInfo, type UXFileInfo,
type UXFileInfoStub, type UXFileInfoStub,
} from "../lib/src/common/types.ts"; } from "@lib/common/types.ts";
export { ICHeader, ICXHeader } from "./types.ts"; export { ICHeader, ICXHeader } from "./types.ts";
import { writeString } from "../lib/src/string_and_binary/convert.ts"; import { writeString } from "@lib/string_and_binary/convert.ts";
import { sameChangePairs } from "./stores.ts"; import { sameChangePairs } from "./stores.ts";
import { scheduleTask } from "octagonal-wheels/concurrency/task"; import { scheduleTask } from "octagonal-wheels/concurrency/task";
import { AuthorizationHeaderGenerator } from "../lib/src/replication/httplib.ts"; import { AuthorizationHeaderGenerator } from "@lib/replication/httplib.ts";
import type { KeyValueDatabase } from "../lib/src/interfaces/KeyValueDatabase.ts"; import type { KeyValueDatabase } from "@lib/interfaces/KeyValueDatabase.ts";
export { scheduleTask, cancelTask, cancelAllTasks } from "octagonal-wheels/concurrency/task"; export { scheduleTask, cancelTask, cancelAllTasks } from "octagonal-wheels/concurrency/task";
@@ -72,7 +72,7 @@ import {
} from "@lib/common/typeUtils.ts"; } from "@lib/common/typeUtils.ts";
export { isInternalFile, getPathFromUXFileInfo, getStoragePathFromUXFileInfo, getDatabasePathFromUXFileInfo }; export { isInternalFile, getPathFromUXFileInfo, getStoragePathFromUXFileInfo, getDatabasePathFromUXFileInfo };
const memos: { [key: string]: any } = {}; const memos: { [key: string]: unknown } = {};
export function memoObject<T>(key: string, obj: T): T { export function memoObject<T>(key: string, obj: T): T {
memos[key] = obj; memos[key] = obj;
return memos[key] as T; return memos[key] as T;
@@ -87,7 +87,7 @@ export async function memoIfNotExist<T>(key: string, func: () => T | Promise<T>)
} }
export function retrieveMemoObject<T>(key: string): T | false { export function retrieveMemoObject<T>(key: string): T | false {
if (key in memos) { if (key in memos) {
return memos[key]; return memos[key] as T;
} else { } else {
return false; return false;
} }
@@ -128,11 +128,11 @@ export const _requestToCouchDBFetch = async (
username: string, username: string,
password: string, password: string,
path?: string, path?: string,
body?: any, body?: unknown,
method?: string method?: string
) => { ) => {
const utf8str = String.fromCharCode.apply(null, [...writeString(`${username}:${password}`)]); const utf8str = String.fromCharCode.apply(null, [...writeString(`${username}:${password}`)]);
const encoded = window.btoa(utf8str); const encoded = compatGlobal.btoa(utf8str);
const authHeader = "Basic " + encoded; const authHeader = "Basic " + encoded;
const transformedHeaders: Record<string, string> = { const transformedHeaders: Record<string, string> = {
authorization: authHeader, authorization: authHeader,
@@ -154,7 +154,7 @@ export const _requestToCouchDB = async (
credentials: CouchDBCredentials, credentials: CouchDBCredentials,
origin: string, origin: string,
path?: string, path?: string,
body?: any, body?: unknown,
method?: string, method?: string,
customHeaders?: Record<string, string> customHeaders?: Record<string, string>
) => { ) => {
@@ -214,7 +214,7 @@ import { BASE_IS_NEW, EVEN, TARGET_IS_NEW } from "@lib/common/models/shared.cons
export { BASE_IS_NEW, EVEN, TARGET_IS_NEW }; export { BASE_IS_NEW, EVEN, TARGET_IS_NEW };
// Why 2000? : ZIP FILE Does not have enough resolution. // Why 2000? : ZIP FILE Does not have enough resolution.
import { compareMTime } from "@lib/common/utils.ts"; import { compareMTime } from "@lib/common/utils.ts";
import { _fetch } from "@/lib/src/common/coreEnvFunctions.ts"; import { _fetch, compatGlobal } from "@lib/common/coreEnvFunctions.ts";
export { compareMTime }; export { compareMTime };
function getKey(file: AnyEntry | string | UXFileInfoStub) { function getKey(file: AnyEntry | string | UXFileInfoStub) {
const key = typeof file == "string" ? file : stripAllPrefixes(file.path); const key = typeof file == "string" ? file : stripAllPrefixes(file.path);
@@ -263,27 +263,27 @@ export function compareFileFreshness(
const _cached = new Map< const _cached = new Map<
string, string,
{ {
value: any; value: unknown;
context: Map<string, any>; context: Map<string, unknown>;
} }
>(); >();
export type MemoOption = { export type MemoOption = {
key: string; key: string;
forceUpdate?: boolean; forceUpdate?: boolean;
validator?: (context: Map<string, any>) => boolean; validator?: (context: Map<string, unknown>) => boolean;
}; };
export function useMemo<T>( export function useMemo<T>(
{ key, forceUpdate, validator }: MemoOption, { key, forceUpdate, validator }: MemoOption,
updateFunc: (context: Map<string, any>, prev: T) => T updateFunc: (context: Map<string, unknown>, prev: T) => T
): T { ): T {
const cached = _cached.get(key); const cached = _cached.get(key);
const context = cached?.context || new Map<string, any>(); const context = cached?.context || new Map<string, unknown>();
if (cached && !forceUpdate && (!validator || (validator && !validator(context)))) { if (cached && !forceUpdate && (!validator || (validator && !validator(context)))) {
return cached.value; return cached.value as T;
} }
const value = updateFunc(context, cached?.value); const value = updateFunc(context, cached?.value as T);
if (value !== cached?.value) { if (value !== cached?.value) {
_cached.set(key, { value, context }); _cached.set(key, { value, context });
} }
@@ -294,7 +294,7 @@ export function useMemo<T>(
const _staticObj = new Map< const _staticObj = new Map<
string, string,
{ {
value: any; value: unknown;
} }
>(); >();
@@ -390,7 +390,7 @@ export async function autosaveCache<K, V>(db: KeyValueDatabase, mapKey: string):
}; };
} }
export function onlyInNTimes(n: number, proc: (progress: number) => any) { export function onlyInNTimes(n: number, proc: (progress: number) => unknown) {
let counter = 0; let counter = 0;
return function () { return function () {
if (counter++ % n == 0) { if (counter++ % n == 0) {
+3
View File
@@ -31,6 +31,7 @@ export {
TextComponent, TextComponent,
ToggleComponent, ToggleComponent,
DropdownComponent, DropdownComponent,
Component,
} from "obsidian"; } from "obsidian";
export type { export type {
DataWriteOptions, DataWriteOptions,
@@ -41,6 +42,8 @@ export type {
ListedFiles, ListedFiles,
ValueComponent, ValueComponent,
Stat, Stat,
Command,
ViewCreator,
} from "obsidian"; } from "obsidian";
import { normalizePath as normalizePath_ } from "obsidian"; import { normalizePath as normalizePath_ } from "obsidian";
const normalizePath = normalizePath_ as <T extends string | FilePath>(from: T) => T; const normalizePath = normalizePath_ as <T extends string | FilePath>(from: T) => T;
+19 -19
View File
@@ -8,7 +8,7 @@ import {
diff_match_patch, diff_match_patch,
Platform, Platform,
addIcon, addIcon,
} from "../../deps.ts"; } from "@/deps.ts";
import type { import type {
EntryDoc, EntryDoc,
@@ -19,7 +19,7 @@ import type {
AnyEntry, AnyEntry,
SavingEntry, SavingEntry,
diff_result, diff_result,
} from "../../lib/src/common/types.ts"; } from "@lib/common/types.ts";
import { import {
CANCELLED, CANCELLED,
LEAVE_TO_SUBSEQUENT, LEAVE_TO_SUBSEQUENT,
@@ -29,8 +29,8 @@ import {
LOG_LEVEL_VERBOSE, LOG_LEVEL_VERBOSE,
MODE_SELECTIVE, MODE_SELECTIVE,
MODE_SHINY, MODE_SHINY,
} from "../../lib/src/common/types.ts"; } from "@lib/common/types.ts";
import { ICXHeader, PERIODIC_PLUGIN_SWEEP } from "../../common/types.ts"; import { ICXHeader, PERIODIC_PLUGIN_SWEEP } from "@/common/types.ts";
import { import {
createBlob, createBlob,
createSavingEntryFromLoadedEntry, createSavingEntryFromLoadedEntry,
@@ -42,12 +42,12 @@ import {
isDocContentSame, isDocContentSame,
isLoadedEntry, isLoadedEntry,
isObjectDifferent, isObjectDifferent,
} from "../../lib/src/common/utils.ts"; } from "@lib/common/utils.ts";
import { digestHash } from "../../lib/src/string_and_binary/hash.ts"; import { digestHash } from "@lib/string_and_binary/hash.ts";
import { arrayBufferToBase64, decodeBinary, readString } from "../../lib/src/string_and_binary/convert.ts"; import { arrayBufferToBase64, decodeBinary, readString } from "@lib/string_and_binary/convert.ts";
import { serialized, shareRunningResult } from "octagonal-wheels/concurrency/lock"; import { serialized, shareRunningResult } from "octagonal-wheels/concurrency/lock";
import { LiveSyncCommands } from "../LiveSyncCommands.ts"; import { LiveSyncCommands } from "@/features/LiveSyncCommands.ts";
import { stripAllPrefixes } from "../../lib/src/string_and_binary/path.ts"; import { stripAllPrefixes } from "@lib/string_and_binary/path.ts";
import { import {
EVEN, EVEN,
disposeMemoObject, disposeMemoObject,
@@ -57,20 +57,20 @@ import {
memoObject, memoObject,
retrieveMemoObject, retrieveMemoObject,
scheduleTask, scheduleTask,
} from "../../common/utils.ts"; } from "@/common/utils.ts";
import { PeriodicProcessor } from "@/common/PeriodicProcessor.ts"; import { PeriodicProcessor } from "@/common/PeriodicProcessor.ts";
import { JsonResolveModal } from "../HiddenFileCommon/JsonResolveModal.ts"; import { JsonResolveModal } from "@/features/HiddenFileCommon/JsonResolveModal.ts";
import { QueueProcessor } from "octagonal-wheels/concurrency/processor"; import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
import { pluginScanningCount } from "../../lib/src/mock_and_interop/stores.ts"; import { pluginScanningCount } from "@lib/mock_and_interop/stores.ts";
import type ObsidianLiveSyncPlugin from "../../main.ts"; import type ObsidianLiveSyncPlugin from "@/main.ts";
import { base64ToArrayBuffer, base64ToString } from "octagonal-wheels/binary/base64"; import { base64ToArrayBuffer, base64ToString } from "octagonal-wheels/binary/base64";
import { ConflictResolveModal } from "../../modules/features/InteractiveConflictResolving/ConflictResolveModal.ts"; import { ConflictResolveModal } from "@/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts";
import { Semaphore } from "octagonal-wheels/concurrency/semaphore"; import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
import { EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG, eventHub } from "../../common/events.ts"; import { EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG, eventHub } from "@/common/events.ts";
import { PluginDialogModal } from "./PluginDialogModal.ts"; import { PluginDialogModal } from "./PluginDialogModal.ts";
import { $msg } from "@/lib/src/common/i18n.ts"; import { $msg } from "@lib/common/i18n.ts";
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts"; import type { InjectableServiceHub } from "@lib/services/InjectableServices.ts";
import type { LiveSyncCore } from "../../main.ts"; import type { LiveSyncCore } from "@/main.ts";
import { LiveSyncError } from "@lib/common/LSError.ts"; import { LiveSyncError } from "@lib/common/LSError.ts";
const d = "\u200b"; const d = "\u200b";
@@ -1101,7 +1101,7 @@ export class ConfigSync extends LiveSyncCommands {
this._log(`Config ${data.displayName || data.name} has been applied`, LOG_LEVEL_NOTICE); this._log(`Config ${data.displayName || data.name} has been applied`, LOG_LEVEL_NOTICE);
if (data.category == "PLUGIN_DATA" || data.category == "PLUGIN_MAIN") { if (data.category == "PLUGIN_DATA" || data.category == "PLUGIN_MAIN") {
//@ts-ignore //@ts-ignore
const manifests = Object.values(this.app.plugins.manifests) as any as PluginManifest[]; const manifests = Object.values(this.app.plugins.manifests) as unknown as PluginManifest[];
//@ts-ignore //@ts-ignore
const enabledPlugins = this.app.plugins.enabledPlugins as Set<string>; const enabledPlugins = this.app.plugins.enabledPlugins as Set<string>;
const pluginManifest = manifests.find( const pluginManifest = manifests.find(
+4 -4
View File
@@ -5,10 +5,10 @@
type IPluginDataExDisplay, type IPluginDataExDisplay,
type PluginDataExFile, type PluginDataExFile,
} from "./CmdConfigSync.ts"; } from "./CmdConfigSync.ts";
import { Logger } from "../../lib/src/common/logger"; import { Logger } from "@lib/common/logger";
import { type FilePath, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "../../lib/src/common/types"; import { type FilePath, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "@lib/common/types";
import { getDocData, timeDeltaToHumanReadable, unique } from "../../lib/src/common/utils"; import { getDocData, timeDeltaToHumanReadable, unique } from "@lib/common/utils";
import type ObsidianLiveSyncPlugin from "../../main"; import type ObsidianLiveSyncPlugin from "@/main";
// import { askString } from "../../common/utils"; // import { askString } from "../../common/utils";
import { Menu } from "@/deps.ts"; import { Menu } from "@/deps.ts";
+7 -5
View File
@@ -1,6 +1,6 @@
import { mount, unmount } from "svelte"; import { mount, unmount } from "svelte";
import { App, Modal } from "../../deps.ts"; import { App, Modal } from "@/deps.ts";
import ObsidianLiveSyncPlugin from "../../main.ts"; import ObsidianLiveSyncPlugin from "@/main.ts";
import PluginPane from "./PluginPane.svelte"; import PluginPane from "./PluginPane.svelte";
export class PluginDialogModal extends Modal { export class PluginDialogModal extends Modal {
plugin: ObsidianLiveSyncPlugin; plugin: ObsidianLiveSyncPlugin;
@@ -16,9 +16,11 @@ export class PluginDialogModal extends Modal {
override onOpen() { override onOpen() {
const { contentEl } = this; const { contentEl } = this;
this.contentEl.style.overflow = "auto"; this.contentEl.setCssStyles({
this.contentEl.style.display = "flex"; overflow: "auto",
this.contentEl.style.flexDirection = "column"; display: "flex",
flexDirection: "column",
});
this.titleEl.setText("Customization Sync (Beta3)"); this.titleEl.setText("Customization Sync (Beta3)");
if (!this.component) { if (!this.component) {
this.component = mount(PluginPane, { this.component = mount(PluginPane, {
+5 -5
View File
@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { onMount } from "svelte"; import { onMount } from "svelte";
import ObsidianLiveSyncPlugin from "../../main"; import ObsidianLiveSyncPlugin from "@/main";
import { import {
ConfigSync, ConfigSync,
type IPluginDataExDisplay, type IPluginDataExDisplay,
@@ -11,16 +11,16 @@
} from "./CmdConfigSync.ts"; } from "./CmdConfigSync.ts";
import PluginCombo from "./PluginCombo.svelte"; import PluginCombo from "./PluginCombo.svelte";
import { Menu, type PluginManifest } from "@/deps.ts"; import { Menu, type PluginManifest } from "@/deps.ts";
import { unique } from "../../lib/src/common/utils"; import { unique } from "@lib/common/utils";
import { import {
MODE_SELECTIVE, MODE_SELECTIVE,
MODE_AUTOMATIC, MODE_AUTOMATIC,
MODE_PAUSED, MODE_PAUSED,
type SYNC_MODE, type SYNC_MODE,
MODE_SHINY, MODE_SHINY,
} from "../../lib/src/common/types"; } from "@lib/common/types";
import { normalizePath } from "../../deps"; import { normalizePath } from "@/deps";
import { HiddenFileSync } from "../HiddenFileSync/CmdHiddenFileSync.ts"; import { HiddenFileSync } from "@/features/HiddenFileSync/CmdHiddenFileSync.ts";
import { LOG_LEVEL_NOTICE, Logger } from "octagonal-wheels/common/logger"; import { LOG_LEVEL_NOTICE, Logger } from "octagonal-wheels/common/logger";
import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore.ts"; import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore.ts";
export let plugin: ObsidianLiveSyncPlugin; export let plugin: ObsidianLiveSyncPlugin;
@@ -1,7 +1,7 @@
import { App, Modal } from "../../deps.ts"; import { App, Modal } from "@/deps.ts";
import { type FilePath, type LoadedEntry } from "../../lib/src/common/types.ts"; import { type FilePath, type LoadedEntry } from "@lib/common/types.ts";
import JsonResolvePane from "./JsonResolvePane.svelte"; import JsonResolvePane from "./JsonResolvePane.svelte";
import { waitForSignal } from "../../lib/src/common/utils.ts"; import { waitForSignal } from "@lib/common/utils.ts";
import { mount, unmount } from "svelte"; import { mount, unmount } from "svelte";
export class JsonResolveModal extends Modal { export class JsonResolveModal extends Modal {
@@ -1,8 +1,8 @@
<script lang="ts"> <script lang="ts">
import { type Diff, DIFF_DELETE, DIFF_INSERT, diff_match_patch } from "../../deps.ts"; import { type Diff, DIFF_DELETE, DIFF_INSERT, diff_match_patch } from "@/deps.ts";
import type { FilePath, LoadedEntry } from "../../lib/src/common/types.ts"; import type { FilePath, LoadedEntry } from "@lib/common/types.ts";
import { decodeBinary, readString } from "../../lib/src/string_and_binary/convert.ts"; import { decodeBinary, readString } from "@lib/string_and_binary/convert.ts";
import { getDocData, isObjectDifferent, mergeObject } from "../../lib/src/common/utils.ts"; import { getDocData, isObjectDifferent, mergeObject } from "@lib/common/utils.ts";
interface Props { interface Props {
docs?: LoadedEntry[]; docs?: LoadedEntry[];
@@ -1,4 +1,4 @@
import { type PluginManifest, type ListedFiles } from "../../deps.ts"; import { type PluginManifest, type ListedFiles } from "@/deps.ts";
import { import {
type LoadedEntry, type LoadedEntry,
type FilePathWithPrefix, type FilePathWithPrefix,
@@ -15,8 +15,8 @@ import {
LOG_LEVEL_DEBUG, LOG_LEVEL_DEBUG,
type MetaEntry, type MetaEntry,
type UXDataWriteOptions, type UXDataWriteOptions,
} from "../../lib/src/common/types.ts"; } from "@lib/common/types.ts";
import { type InternalFileInfo, ICHeader, ICHeaderEnd } from "../../common/types.ts"; import { type InternalFileInfo, ICHeader, ICHeaderEnd } from "@/common/types.ts";
import { import {
readAsBlob, readAsBlob,
isDocContentSame, isDocContentSame,
@@ -26,7 +26,7 @@ import {
fireAndForget, fireAndForget,
type CustomRegExp, type CustomRegExp,
getFileRegExp, getFileRegExp,
} from "../../lib/src/common/utils.ts"; } from "@lib/common/utils.ts";
import { import {
compareMTime, compareMTime,
isInternalMetadata, isInternalMetadata,
@@ -39,17 +39,17 @@ import {
BASE_IS_NEW, BASE_IS_NEW,
EVEN, EVEN,
displayRev, displayRev,
} from "../../common/utils.ts"; } from "@/common/utils.ts";
import { PeriodicProcessor } from "@/common/PeriodicProcessor.ts"; import { PeriodicProcessor } from "@/common/PeriodicProcessor.ts";
import { serialized, skipIfDuplicated } from "octagonal-wheels/concurrency/lock"; import { serialized, skipIfDuplicated } from "octagonal-wheels/concurrency/lock";
import { JsonResolveModal } from "../HiddenFileCommon/JsonResolveModal.ts"; import { JsonResolveModal } from "@/features/HiddenFileCommon/JsonResolveModal.ts";
import { LiveSyncCommands } from "../LiveSyncCommands.ts"; import { LiveSyncCommands } from "@/features/LiveSyncCommands.ts";
import { addPrefix, stripAllPrefixes } from "../../lib/src/string_and_binary/path.ts"; import { addPrefix, stripAllPrefixes } from "@lib/string_and_binary/path.ts";
import { QueueProcessor } from "octagonal-wheels/concurrency/processor"; import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
import { hiddenFilesEventCount, hiddenFilesProcessingCount } from "../../lib/src/mock_and_interop/stores.ts"; import { hiddenFilesEventCount, hiddenFilesProcessingCount } from "@lib/mock_and_interop/stores.ts";
import { EVENT_SETTING_SAVED, eventHub } from "../../common/events.ts"; import { EVENT_SETTING_SAVED, eventHub } from "@/common/events.ts";
import { Semaphore } from "octagonal-wheels/concurrency/semaphore"; import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
import type { LiveSyncCore } from "../../main.ts"; import type { LiveSyncCore } from "@/main.ts";
import { tryGetFilePath } from "@lib/common/utils.doc.ts"; import { tryGetFilePath } from "@lib/common/utils.doc.ts";
type SyncDirection = "push" | "pull" | "safe" | "pullForce" | "pushForce"; type SyncDirection = "push" | "pull" | "safe" | "pullForce" | "pushForce";
@@ -1225,7 +1225,7 @@ Offline Changed files: ${files.length}`;
this.queuedNotificationFiles.clear(); this.queuedNotificationFiles.clear();
try { try {
//@ts-ignore //@ts-ignore
const manifests = Object.values(this.app.plugins.manifests) as any as PluginManifest[]; const manifests = Object.values(this.app.plugins.manifests) as unknown as PluginManifest[];
//@ts-ignore //@ts-ignore
const enabledPlugins = this.app.plugins.enabledPlugins as Set<string>; const enabledPlugins = this.app.plugins.enabledPlugins as Set<string>;
const enabledPluginManifests = manifests.filter((e) => enabledPlugins.has(e.id)); const enabledPluginManifests = manifests.filter((e) => enabledPlugins.has(e.id));
+13 -13
View File
@@ -7,12 +7,12 @@ import {
type FilePath, type FilePath,
type FilePathWithPrefix, type FilePathWithPrefix,
type LOG_LEVEL, type LOG_LEVEL,
} from "../lib/src/common/types.ts"; } from "@lib/common/types.ts";
import type ObsidianLiveSyncPlugin from "../main.ts"; import type ObsidianLiveSyncPlugin from "@/main.ts";
import { MARK_DONE } from "../modules/features/ModuleLog.ts"; import { MARK_DONE } from "@/modules/features/ModuleLog.ts";
import type { LiveSyncCore } from "../main.ts"; import type { LiveSyncCore } from "@/main.ts";
import { __$checkInstanceBinding } from "../lib/src/dev/checks.ts"; // import { __$checkInstanceBinding } from "@lib/dev/checks.ts";
import { createInstanceLogFunction } from "@/lib/src/services/lib/logUtils.ts"; import { createInstanceLogFunction } from "@lib/services/lib/logUtils.ts";
let noticeIndex = 0; let noticeIndex = 0;
export abstract class LiveSyncCommands { export abstract class LiveSyncCommands {
@@ -50,7 +50,7 @@ export abstract class LiveSyncCommands {
this.core = core; this.core = core;
this.onBindFunction(this.core, this.core.services); this.onBindFunction(this.core, this.core.services);
this._log = createInstanceLogFunction(this.constructor.name, this.services.API); this._log = createInstanceLogFunction(this.constructor.name, this.services.API);
__$checkInstanceBinding(this); // __$checkInstanceBinding(this);
} }
abstract onunload(): void; abstract onunload(): void;
abstract onload(): void | Promise<void>; abstract onload(): void | Promise<void>;
@@ -67,24 +67,24 @@ export abstract class LiveSyncCommands {
_log: ReturnType<typeof createInstanceLogFunction>; _log: ReturnType<typeof createInstanceLogFunction>;
_verbose = (msg: any, key?: string) => { _verbose = (msg: unknown, key?: string) => {
this._log(msg, LOG_LEVEL_VERBOSE, key); this._log(msg, LOG_LEVEL_VERBOSE, key);
}; };
_info = (msg: any, key?: string) => { _info = (msg: unknown, key?: string) => {
this._log(msg, LOG_LEVEL_INFO, key); this._log(msg, LOG_LEVEL_INFO, key);
}; };
_notice = (msg: any, key?: string) => { _notice = (msg: unknown, key?: string) => {
this._log(msg, LOG_LEVEL_NOTICE, key); this._log(msg, LOG_LEVEL_NOTICE, key);
}; };
_progress = (prefix: string = "", level: LOG_LEVEL = LOG_LEVEL_NOTICE) => { _progress = (prefix: string = "", level: LOG_LEVEL = LOG_LEVEL_NOTICE) => {
const key = `keepalive-progress-${noticeIndex++}`; const key = `keepalive-progress-${noticeIndex++}`;
return { return {
log: (msg: any) => { log: (msg: string) => {
this._log(prefix + msg, level, key); this._log(prefix + msg, level, key);
}, },
once: (msg: any) => { once: (msg: string) => {
this._log(prefix + msg, level); this._log(prefix + msg, level);
}, },
done: (msg: string = "Done") => { done: (msg: string = "Done") => {
@@ -93,7 +93,7 @@ export abstract class LiveSyncCommands {
}; };
}; };
_debug = (msg: any, key?: string) => { _debug = (msg: unknown, key?: string) => {
this._log(msg, LOG_LEVEL_VERBOSE, key); this._log(msg, LOG_LEVEL_VERBOSE, key);
}; };
@@ -9,14 +9,15 @@ import {
type EntryLeaf, type EntryLeaf,
type FilePathWithPrefix, type FilePathWithPrefix,
type MetaEntry, type MetaEntry,
} from "../../lib/src/common/types"; } from "@lib/common/types";
import { getNoFromRev } from "../../lib/src/pouchdb/LiveSyncLocalDB"; import { getNoFromRev } from "@lib/pouchdb/LiveSyncLocalDB";
import { LiveSyncCommands } from "../LiveSyncCommands"; import { LiveSyncCommands } from "@/features/LiveSyncCommands";
import { serialized } from "octagonal-wheels/concurrency/lock_v2"; import { serialized } from "octagonal-wheels/concurrency/lock_v2";
import { arrayToChunkedArray } from "octagonal-wheels/collection"; import { arrayToChunkedArray } from "octagonal-wheels/collection";
import { EVENT_ANALYSE_DB_USAGE, EVENT_REQUEST_PERFORM_GC_V3, eventHub } from "@/common/events"; import { EVENT_ANALYSE_DB_USAGE, EVENT_REQUEST_PERFORM_GC_V3, eventHub } from "@/common/events";
import type { LiveSyncCouchDBReplicator } from "@/lib/src/replication/couchdb/LiveSyncReplicator"; import type { LiveSyncCouchDBReplicator } from "@lib/replication/couchdb/LiveSyncReplicator";
import { delay } from "@/lib/src/common/utils"; import { delay } from "@lib/common/utils";
import { isNotFoundError } from "@/lib/src/common/utils.doc";
// import { _requestToCouchDB } from "@/common/utils"; // import { _requestToCouchDB } from "@/common/utils";
const DB_KEY_SEQ = "gc-seq"; const DB_KEY_SEQ = "gc-seq";
const DB_KEY_CHUNK_SET = "chunk-set"; const DB_KEY_CHUNK_SET = "chunk-set";
@@ -394,7 +395,7 @@ Note: **Make sure to synchronise all devices before deletion.**
} }
} }
} catch (ex) { } catch (ex) {
if ((ex as any)?.status == 404) { if (isNotFoundError(ex)) {
this._log(`No revisions found for ${doc._id}`, LOG_LEVEL_VERBOSE); this._log(`No revisions found for ${doc._id}`, LOG_LEVEL_VERBOSE);
} else { } else {
this._log(`Error finding revisions for ${doc._id}`); this._log(`Error finding revisions for ${doc._id}`);
@@ -474,14 +475,14 @@ Are you ready to delete unused chunks?`;
include_docs: true, include_docs: true,
}); });
for (const chunk of deleteChunks.rows) { for (const chunk of deleteChunks.rows) {
if ((chunk as any)?.value?.deleted) { if ((chunk as { value?: { deleted?: boolean } })?.value?.deleted) {
chunkSet.delete(chunk.key as DocumentID); chunkSet.delete(chunk.key as DocumentID);
} }
} }
const deleteDocs = deleteChunks.rows const deleteDocs = deleteChunks.rows
.filter((e) => "doc" in e) .filter((e) => "doc" in e)
.map((e) => ({ .map((e) => ({
...(e as any).doc!, ...(e as { doc?: EntryLeaf }).doc!,
_deleted: true, _deleted: true,
})); }));
@@ -490,7 +491,7 @@ Are you ready to delete unused chunks?`;
let successCount = 0; let successCount = 0;
let errored = 0; let errored = 0;
for (const batch of deleteChunkBatch) { for (const batch of deleteChunkBatch) {
const results = await this.database.bulkDocs(batch as EntryLeaf[]); const results = await this.database.bulkDocs(batch);
for (const result of results) { for (const result of results) {
if ("ok" in result) { if ("ok" in result) {
chunkSet.delete(result.id as DocumentID); chunkSet.delete(result.id as DocumentID);
@@ -698,7 +699,7 @@ Success: ${successCount}, Errored: ${errored}`;
sharedChunkCount: 0, sharedChunkCount: 0,
uniqueChunkSize: orphanChunkSize, uniqueChunkSize: orphanChunkSize,
sharedChunkSize: 0, sharedChunkSize: 0,
} as any); } as const);
const csvSrc = result.map((e) => { const csvSrc = result.map((e) => {
return [ return [
@@ -737,7 +738,7 @@ Success: ${successCount}, Errored: ${errored}`;
}); });
// Probably no need to wait, but just in case. // Probably no need to wait, but just in case.
let timeout = 2 * 60 * 1000; // 2 minutes let timeout = 2 * 60 * 1000; // 2 minutes
do { for (;;) {
const status = await remote.db.info(); const status = await remote.db.info();
if ("compact_running" in status && status?.compact_running) { if ("compact_running" in status && status?.compact_running) {
this._notice("Compaction in progress on remote database...", "gc-compact"); this._notice("Compaction in progress on remote database...", "gc-compact");
@@ -750,7 +751,7 @@ Success: ${successCount}, Errored: ${errored}`;
} else { } else {
break; break;
} }
} while (true); }
if (compactResult && "ok" in compactResult) { if (compactResult && "ok" in compactResult) {
this._notice("Compaction on remote database completed successfully.", "gc-compact"); this._notice("Compaction on remote database completed successfully.", "gc-compact");
} else { } else {
@@ -1,7 +1,7 @@
import { App, Modal } from "@/deps.ts"; import { App, Modal } from "@/deps.ts";
import P2POpenReplicationPane from "./P2POpenReplicationPane.svelte"; import P2POpenReplicationPane from "./P2POpenReplicationPane.svelte";
import { mount, unmount } from "svelte"; import { mount, unmount } from "svelte";
import type { LiveSyncTrysteroReplicator } from "@/lib/src/replication/trystero/LiveSyncTrysteroReplicator"; import type { LiveSyncTrysteroReplicator } from "@lib/replication/trystero/LiveSyncTrysteroReplicator";
export type P2POpenReplicationModalCallback = { export type P2POpenReplicationModalCallback = {
onSync: (peerId: string) => Promise<void>; onSync: (peerId: string) => Promise<void>;
@@ -9,7 +9,7 @@
// import type { TrysteroReplicator } from "@lib/replication/trystero/TrysteroReplicator"; // import type { TrysteroReplicator } from "@lib/replication/trystero/TrysteroReplicator";
import { LOG_LEVEL_NOTICE, LOG_LEVEL_INFO } from "@lib/common/types"; import { LOG_LEVEL_NOTICE, LOG_LEVEL_INFO } from "@lib/common/types";
import { Logger } from "@lib/common/logger"; import { Logger } from "@lib/common/logger";
import type { LiveSyncTrysteroReplicator } from "@/lib/src/replication/trystero/LiveSyncTrysteroReplicator"; import type { LiveSyncTrysteroReplicator } from "@lib/replication/trystero/LiveSyncTrysteroReplicator";
import { delay, fireAndForget } from "octagonal-wheels/promises"; import { delay, fireAndForget } from "octagonal-wheels/promises";
import P2PServerStatusCard from "./P2PServerStatusCard.svelte"; import P2PServerStatusCard from "./P2PServerStatusCard.svelte";
@@ -1,13 +1,13 @@
<script lang="ts"> <script lang="ts">
import { onMount, setContext } from "svelte"; import { onMount, setContext } from "svelte";
import { AutoAccepting, DEFAULT_SETTINGS, type P2PSyncSetting } from "../../../lib/src/common/types"; import { AutoAccepting, DEFAULT_SETTINGS, type P2PSyncSetting } from "@lib/common/types";
import { import {
AcceptedStatus, AcceptedStatus,
ConnectionStatus, ConnectionStatus,
type PeerStatus, type PeerStatus,
} from "@lib/replication/trystero/P2PReplicatorPaneCommon"; } from "@lib/replication/trystero/P2PReplicatorPaneCommon";
import type { LiveSyncTrysteroReplicator } from "@lib/replication/trystero/LiveSyncTrysteroReplicator"; import type { LiveSyncTrysteroReplicator } from "@lib/replication/trystero/LiveSyncTrysteroReplicator";
import PeerStatusRow from "../P2PReplicator/PeerStatusRow.svelte"; import PeerStatusRow from "@/features/P2PSync/P2PReplicator/PeerStatusRow.svelte";
import { EVENT_LAYOUT_READY, eventHub } from "@/common/events"; import { EVENT_LAYOUT_READY, eventHub } from "@/common/events";
import { import {
type PeerInfo, type PeerInfo,
@@ -9,7 +9,7 @@ import { LOG_LEVEL_NOTICE, REMOTE_P2P } from "@lib/common/types.ts";
import { Logger } from "@lib/common/logger.ts"; import { Logger } from "@lib/common/logger.ts";
import { EVENT_P2P_PEER_SHOW_EXTRA_MENU, type PeerStatus } from "@lib/replication/trystero/P2PReplicatorPaneCommon.ts"; import { EVENT_P2P_PEER_SHOW_EXTRA_MENU, type PeerStatus } from "@lib/replication/trystero/P2PReplicatorPaneCommon.ts";
import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore.ts"; import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore.ts";
import type { P2PPaneParams } from "@/lib/src/replication/trystero/UseP2PReplicatorResult"; import type { P2PPaneParams } from "@lib/replication/trystero/UseP2PReplicatorResult";
export const VIEW_TYPE_P2P = "p2p-replicator"; export const VIEW_TYPE_P2P = "p2p-replicator";
function addToList(item: string, list: string) { function addToList(item: string, list: string) {
@@ -9,9 +9,9 @@
EVENT_P2P_REPLICATOR_STATUS, EVENT_P2P_REPLICATOR_STATUS,
} from "@lib/replication/trystero/TrysteroReplicatorP2PServer"; } from "@lib/replication/trystero/TrysteroReplicatorP2PServer";
import { EVENT_SETTING_SAVED } from "@lib/events/coreEvents"; import { EVENT_SETTING_SAVED } from "@lib/events/coreEvents";
import type { LiveSyncTrysteroReplicator } from "@/lib/src/replication/trystero/LiveSyncTrysteroReplicator"; import type { LiveSyncTrysteroReplicator } from "@lib/replication/trystero/LiveSyncTrysteroReplicator";
import type { P2PReplicatorStatus } from "@/lib/src/replication/trystero/TrysteroReplicator"; import type { P2PReplicatorStatus } from "@lib/replication/trystero/TrysteroReplicator";
import { extractP2PRoomSuffix } from "@/lib/src/common/utils"; import { extractP2PRoomSuffix } from "@lib/common/utils";
import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore"; import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore";
interface Props { interface Props {
@@ -2,7 +2,7 @@ import { WorkspaceLeaf } from "@/deps.ts";
import { mount } from "svelte"; import { mount } from "svelte";
import { SvelteItemView } from "@/common/SvelteItemView.ts"; import { SvelteItemView } from "@/common/SvelteItemView.ts";
import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore.ts"; import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore.ts";
import type { P2PPaneParams } from "@/lib/src/replication/trystero/UseP2PReplicatorResult"; import type { P2PPaneParams } from "@lib/replication/trystero/UseP2PReplicatorResult";
import P2PServerStatusPane from "./P2PServerStatusPane.svelte"; import P2PServerStatusPane from "./P2PServerStatusPane.svelte";
export const VIEW_TYPE_P2P_SERVER_STATUS = "p2p-server-status"; export const VIEW_TYPE_P2P_SERVER_STATUS = "p2p-server-status";
@@ -1,9 +1,9 @@
<script lang="ts"> <script lang="ts">
import { getContext } from "svelte"; import { getContext } from "svelte";
import { AcceptedStatus, type PeerStatus } from "../../../lib/src/replication/trystero/P2PReplicatorPaneCommon"; import { AcceptedStatus, type PeerStatus } from "@lib/replication/trystero/P2PReplicatorPaneCommon";
import type { LiveSyncTrysteroReplicator } from "../../../lib/src/replication/trystero/LiveSyncTrysteroReplicator"; import type { LiveSyncTrysteroReplicator } from "@lib/replication/trystero/LiveSyncTrysteroReplicator";
import { eventHub } from "../../../common/events"; import { eventHub } from "@/common/events";
import { EVENT_P2P_PEER_SHOW_EXTRA_MENU } from "../../../lib/src/replication/trystero/P2PReplicatorPaneCommon"; import { EVENT_P2P_PEER_SHOW_EXTRA_MENU } from "@lib/replication/trystero/P2PReplicatorPaneCommon";
interface Props { interface Props {
peerStatus: PeerStatus; peerStatus: PeerStatus;
+1 -1
Submodule src/lib updated: 25d23bf738...9aeab513b0
+2 -2
View File
@@ -4,7 +4,7 @@ setGetLanguage(getLanguage);
import { LiveSyncCommands } from "./features/LiveSyncCommands.ts"; import { LiveSyncCommands } from "./features/LiveSyncCommands.ts";
import { HiddenFileSync } from "./features/HiddenFileSync/CmdHiddenFileSync.ts"; import { HiddenFileSync } from "./features/HiddenFileSync/CmdHiddenFileSync.ts";
import { ConfigSync } from "./features/ConfigSync/CmdConfigSync.ts"; import { ConfigSync } from "./features/ConfigSync/CmdConfigSync.ts";
import { ModuleDev } from "./modules/extras/ModuleDev.ts"; // import { ModuleDev } from "./modules/extras/ModuleDev.ts";
import { ModuleInteractiveConflictResolver } from "./modules/features/ModuleInteractiveConflictResolver.ts"; import { ModuleInteractiveConflictResolver } from "./modules/features/ModuleInteractiveConflictResolver.ts";
import { ModuleLog } from "./modules/features/ModuleLog.ts"; import { ModuleLog } from "./modules/features/ModuleLog.ts";
@@ -153,7 +153,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
new ModuleObsidianDocumentHistory(this, core), new ModuleObsidianDocumentHistory(this, core),
new ModuleInteractiveConflictResolver(this, core), new ModuleInteractiveConflictResolver(this, core),
new ModuleObsidianGlobalHistory(this, core), new ModuleObsidianGlobalHistory(this, core),
new ModuleDev(this, core), // new ModuleDev(this, core),
new SetupManager(core), // this should be moved to core? new SetupManager(core), // this should be moved to core?
new ModuleMigration(core), new ModuleMigration(core),
]; ];
@@ -20,7 +20,7 @@ import { InternalFileToUXFileInfoStub, TFileToUXFileInfoStub } from "@/modules/c
* Obsidian-specific type guard adapter * Obsidian-specific type guard adapter
*/ */
class ObsidianTypeGuardAdapter implements IStorageEventTypeGuardAdapter<TFile, TFolder> { class ObsidianTypeGuardAdapter implements IStorageEventTypeGuardAdapter<TFile, TFolder> {
isFile(file: any): file is TFile { isFile(file: unknown): file is TFile {
if (file instanceof TFile) { if (file instanceof TFile) {
return true; return true;
} }
@@ -30,7 +30,7 @@ class ObsidianTypeGuardAdapter implements IStorageEventTypeGuardAdapter<TFile, T
return false; return false;
} }
isFolder(item: any): item is TFolder { isFolder(item: unknown): item is TFolder {
if (item instanceof TFolder) { if (item instanceof TFolder) {
return true; return true;
} }
+16 -16
View File
@@ -3,7 +3,7 @@ import type { AnyEntry, FilePathWithPrefix } from "@lib/common/types";
import type { IMinimumLiveSyncCommands, LiveSyncBaseCore } from "@/LiveSyncBaseCore"; import type { IMinimumLiveSyncCommands, LiveSyncBaseCore } from "@/LiveSyncBaseCore";
import { stripAllPrefixes } from "@lib/string_and_binary/path"; import { stripAllPrefixes } from "@lib/string_and_binary/path";
import { createInstanceLogFunction } from "@lib/services/lib/logUtils"; import { createInstanceLogFunction } from "@lib/services/lib/logUtils";
import type { ServiceContext } from "@/lib/src/services/base/ServiceBase"; import type { ServiceContext } from "@lib/services/base/ServiceBase";
export abstract class AbstractModule< export abstract class AbstractModule<
T extends LiveSyncBaseCore<ServiceContext, IMinimumLiveSyncCommands> = LiveSyncBaseCore< T extends LiveSyncBaseCore<ServiceContext, IMinimumLiveSyncCommands> = LiveSyncBaseCore<
@@ -61,21 +61,21 @@ export abstract class AbstractModule<
return this.testDone(false); return this.testDone(false);
} }
async _test(key: string, process: () => Promise<any>) { // async _test(key: string, process: () => Promise<any>) {
this._log(`Testing ${key}`, LOG_LEVEL_VERBOSE); // this._log(`Testing ${key}`, LOG_LEVEL_VERBOSE);
try { // try {
const ret = await process(); // const ret = await process();
if (ret !== true) { // if (ret !== true) {
this.addTestResult(key, false, ret.toString()); // this.addTestResult(key, false, ret.toString());
return this.testFail(`${key} failed: ${ret}`); // return this.testFail(`${key} failed: ${ret}`);
} // }
this.addTestResult(key, true, ""); // this.addTestResult(key, true, "");
} catch (ex: any) { // } catch (ex: any) {
this.addTestResult(key, false, "Failed by Exception", ex.toString()); // this.addTestResult(key, false, "Failed by Exception", ex.toString());
return this.testFail(`${key} failed: ${ex}`); // return this.testFail(`${key} failed: ${ex}`);
} // }
return this.testDone(); // return this.testDone();
} // }
isMainReady() { isMainReady() {
return this.services.appLifecycle.isReady(); return this.services.appLifecycle.isReady();
+2 -9
View File
@@ -1,13 +1,6 @@
import { type Prettify } from "../lib/src/common/types"; import type { LiveSyncCore } from "@/main";
import type { LiveSyncCore } from "../main"; import type ObsidianLiveSyncPlugin from "@/main";
import type ObsidianLiveSyncPlugin from "../main";
import { AbstractModule } from "./AbstractModule.ts"; import { AbstractModule } from "./AbstractModule.ts";
import type { ChainableExecuteFunction, OverridableFunctionsKeys } from "./ModuleTypes";
export type IObsidianModuleBase = OverridableFunctionsKeys<ObsidianLiveSyncPlugin>;
export type IObsidianModule = Prettify<Partial<IObsidianModuleBase>>;
export type ModuleKeys = keyof IObsidianModule;
export type ChainableModuleProps = ChainableExecuteFunction<ObsidianLiveSyncPlugin>;
export abstract class AbstractObsidianModule extends AbstractModule { export abstract class AbstractObsidianModule extends AbstractModule {
get app() { get app() {
-55
View File
@@ -1,55 +0,0 @@
import type { Prettify } from "../lib/src/common/types";
import type { LiveSyncCore } from "../main";
export type OverridableFunctionsKeys<T> = {
[K in keyof T as K extends `$${string}` ? K : never]: T[K];
};
export type ChainableExecuteFunction<T> = {
[K in keyof T as K extends `$${string}`
? T[K] extends (...args: any) => ChainableFunctionResult
? K
: never
: never]: T[K];
};
export type ICoreModuleBase = OverridableFunctionsKeys<LiveSyncCore>;
export type ICoreModule = Prettify<Partial<ICoreModuleBase>>;
export type CoreModuleKeys = keyof ICoreModule;
export type ChainableFunctionResult =
| Promise<boolean | undefined | string>
| Promise<boolean | undefined>
| Promise<boolean>
| Promise<void>;
export type ChainableFunctionResultOrAll = Promise<boolean | undefined | string | void>;
type AllExecuteFunction<T> = {
[K in keyof T as K extends `$all${string}`
? T[K] extends (...args: any[]) => ChainableFunctionResultOrAll
? K
: never
: never]: T[K];
};
type EveryExecuteFunction<T> = {
[K in keyof T as K extends `$every${string}`
? T[K] extends (...args: any[]) => ChainableFunctionResult
? K
: never
: never]: T[K];
};
type AnyExecuteFunction<T> = {
[K in keyof T as K extends `$any${string}`
? T[K] extends (...args: any[]) => ChainableFunctionResult
? K
: never
: never]: T[K];
};
type InjectableFunction<T> = {
[K in keyof T as K extends `$$${string}` ? (T[K] extends (...args: any[]) => any ? K : never) : never]: T[K];
};
export type AllExecuteProps = AllExecuteFunction<LiveSyncCore>;
export type EveryExecuteProps = EveryExecuteFunction<LiveSyncCore>;
export type AnyExecuteProps = AnyExecuteFunction<LiveSyncCore>;
export type AllInjectableProps = InjectableFunction<LiveSyncCore>;
+2 -2
View File
@@ -1,6 +1,6 @@
import { PeriodicProcessor } from "@/common/PeriodicProcessor"; import { PeriodicProcessor } from "@/common/PeriodicProcessor";
import type { LiveSyncCore } from "../../main"; import type { LiveSyncCore } from "@/main";
import { AbstractModule } from "../AbstractModule"; import { AbstractModule } from "@/modules/AbstractModule";
export class ModulePeriodicProcess extends AbstractModule { export class ModulePeriodicProcess extends AbstractModule {
periodicSyncProcessor = new PeriodicProcessor(this.core, async () => await this.services.replication.replicate()); periodicSyncProcessor = new PeriodicProcessor(this.core, async () => await this.services.replication.replicate());
+8 -8
View File
@@ -1,18 +1,18 @@
import type PouchDB from "pouchdb-core"; import type PouchDB from "pouchdb-core";
import { fireAndForget } from "octagonal-wheels/promises"; import { fireAndForget } from "octagonal-wheels/promises";
import { AbstractModule } from "../AbstractModule"; import { AbstractModule } from "@/modules/AbstractModule";
import { Logger, LOG_LEVEL_NOTICE, LOG_LEVEL_INFO } from "octagonal-wheels/common/logger"; import { Logger, LOG_LEVEL_NOTICE, LOG_LEVEL_INFO } from "octagonal-wheels/common/logger";
import { skipIfDuplicated } from "octagonal-wheels/concurrency/lock"; import { skipIfDuplicated } from "octagonal-wheels/concurrency/lock";
import { balanceChunkPurgedDBs } from "@lib/pouchdb/chunks"; import { balanceChunkPurgedDBs } from "@lib/pouchdb/chunks";
import { purgeUnreferencedChunks } from "@lib/pouchdb/chunks"; import { purgeUnreferencedChunks } from "@lib/pouchdb/chunks";
import { LiveSyncCouchDBReplicator } from "../../lib/src/replication/couchdb/LiveSyncReplicator"; import { LiveSyncCouchDBReplicator } from "@lib/replication/couchdb/LiveSyncReplicator";
import { type EntryDoc, type RemoteType } from "../../lib/src/common/types"; import { type EntryDoc, type RemoteType } from "@lib/common/types";
import { scheduleTask } from "octagonal-wheels/concurrency/task"; import { scheduleTask } from "octagonal-wheels/concurrency/task";
import { EVENT_FILE_SAVED, EVENT_SETTING_SAVED, eventHub } from "../../common/events"; import { EVENT_FILE_SAVED, EVENT_SETTING_SAVED, eventHub } from "@/common/events";
import { $msg } from "../../lib/src/common/i18n"; import { $msg } from "@lib/common/i18n";
import type { LiveSyncCore } from "../../main"; import type { LiveSyncCore } from "@/main";
import { ReplicateResultProcessor } from "./ReplicateResultProcessor"; import { ReplicateResultProcessor } from "./ReplicateResultProcessor";
import { UnresolvedErrorManager } from "@lib/services/base/UnresolvedErrorManager"; import { UnresolvedErrorManager } from "@lib/services/base/UnresolvedErrorManager";
import { clearHandlers } from "@lib/replication/SyncParamsHandler"; import { clearHandlers } from "@lib/replication/SyncParamsHandler";
@@ -21,7 +21,7 @@ import { MARK_LOG_NETWORK_ERROR } from "@lib/services/lib/logUtils";
function isOnlineAndCanReplicate( function isOnlineAndCanReplicate(
errorManager: UnresolvedErrorManager, errorManager: UnresolvedErrorManager,
host: NecessaryServices<"API", any>, host: NecessaryServices<"API", never>,
showMessage: boolean showMessage: boolean
): Promise<boolean> { ): Promise<boolean> {
const errorMessage = "Network is offline"; const errorMessage = "Network is offline";
@@ -34,7 +34,7 @@ function isOnlineAndCanReplicate(
} }
async function canReplicateWithPBKDF2( async function canReplicateWithPBKDF2(
errorManager: UnresolvedErrorManager, errorManager: UnresolvedErrorManager,
host: NecessaryServices<"replicator" | "setting", any>, host: NecessaryServices<"replicator" | "setting", never>,
showMessage: boolean showMessage: boolean
): Promise<boolean> { ): Promise<boolean> {
const currentSettings = host.services.setting.currentSettings(); const currentSettings = host.services.setting.currentSettings();
+5 -5
View File
@@ -1,9 +1,9 @@
import { fireAndForget } from "octagonal-wheels/promises"; import { fireAndForget } from "octagonal-wheels/promises";
import { REMOTE_MINIO, REMOTE_P2P, type RemoteDBSettings } from "../../lib/src/common/types"; import { REMOTE_MINIO, REMOTE_P2P, type RemoteDBSettings } from "@lib/common/types";
import { LiveSyncCouchDBReplicator } from "../../lib/src/replication/couchdb/LiveSyncReplicator"; import { LiveSyncCouchDBReplicator } from "@lib/replication/couchdb/LiveSyncReplicator";
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator"; import type { LiveSyncAbstractReplicator } from "@lib/replication/LiveSyncAbstractReplicator";
import { AbstractModule } from "../AbstractModule"; import { AbstractModule } from "@/modules/AbstractModule";
import type { LiveSyncCore } from "../../main"; import type { LiveSyncCore } from "@/main";
export class ModuleReplicatorCouchDB extends AbstractModule { export class ModuleReplicatorCouchDB extends AbstractModule {
_anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator | false> { _anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator | false> {
+5 -5
View File
@@ -1,8 +1,8 @@
import { REMOTE_MINIO, type RemoteDBSettings } from "../../lib/src/common/types"; import { REMOTE_MINIO, type RemoteDBSettings } from "@lib/common/types";
import { LiveSyncJournalReplicator } from "../../lib/src/replication/journal/LiveSyncJournalReplicator"; import { LiveSyncJournalReplicator } from "@lib/replication/journal/LiveSyncJournalReplicator";
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator"; import type { LiveSyncAbstractReplicator } from "@lib/replication/LiveSyncAbstractReplicator";
import type { LiveSyncCore } from "../../main"; import type { LiveSyncCore } from "@/main";
import { AbstractModule } from "../AbstractModule"; import { AbstractModule } from "@/modules/AbstractModule";
export class ModuleReplicatorMinIO extends AbstractModule { export class ModuleReplicatorMinIO extends AbstractModule {
_anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator | false> { _anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator | false> {
+5 -4
View File
@@ -8,7 +8,7 @@ import {
type MetaEntry, type MetaEntry,
} from "@lib/common/types"; } from "@lib/common/types";
import type { ModuleReplicator } from "./ModuleReplicator"; import type { ModuleReplicator } from "./ModuleReplicator";
import { isChunk } from "@/lib/src/common/typeUtils"; import { isChunk } from "@lib/common/typeUtils";
import { import {
LOG_LEVEL_DEBUG, LOG_LEVEL_DEBUG,
LOG_LEVEL_INFO, LOG_LEVEL_INFO,
@@ -22,6 +22,7 @@ import { Semaphore } from "octagonal-wheels/concurrency/semaphore_v2";
import { serialized } from "octagonal-wheels/concurrency/lock"; import { serialized } from "octagonal-wheels/concurrency/lock";
import type { ReactiveSource } from "octagonal-wheels/dataobject/reactive_v2"; import type { ReactiveSource } from "octagonal-wheels/dataobject/reactive_v2";
import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore"; import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore";
import { isNotFoundError } from "@lib/common/utils.doc";
const KV_KEY_REPLICATION_RESULT_PROCESSOR_SNAPSHOT = "replicationResultProcessorSnapshot"; const KV_KEY_REPLICATION_RESULT_PROCESSOR_SNAPSHOT = "replicationResultProcessorSnapshot";
type ReplicateResultProcessorState = { type ReplicateResultProcessorState = {
@@ -39,7 +40,7 @@ export class ReplicateResultProcessor {
private log(message: string, level: LOG_LEVEL = LOG_LEVEL_INFO) { private log(message: string, level: LOG_LEVEL = LOG_LEVEL_INFO) {
Logger(`[ReplicateResultProcessor] ${message}`, level); Logger(`[ReplicateResultProcessor] ${message}`, level);
} }
private logError(e: any) { private logError(e: unknown) {
Logger(e, LOG_LEVEL_VERBOSE); Logger(e, LOG_LEVEL_VERBOSE);
} }
private replicator: ModuleReplicator; private replicator: ModuleReplicator;
@@ -466,8 +467,8 @@ export class ReplicateResultProcessor {
return false; // This means that the document already processed (While no conflict existed). return false; // This means that the document already processed (While no conflict existed).
} }
return true; // This mostly should not happen, but we have to process it just in case. return true; // This mostly should not happen, but we have to process it just in case.
} catch (e: any) { } catch (e) {
if ("status" in e && e.status == 404) { if (isNotFoundError(e)) {
// getRaw failed due to not existing, it may not be happened normally especially on replication. // getRaw failed due to not existing, it may not be happened normally especially on replication.
// If the process caused by some other reason, we **probably** have to process it. // If the process caused by some other reason, we **probably** have to process it.
// Note that this is not a common case. // Note that this is not a common case.
@@ -1,9 +1,9 @@
import { AbstractModule } from "../AbstractModule.ts"; import { AbstractModule } from "@/modules/AbstractModule.ts";
import { LOG_LEVEL_NOTICE, type FilePathWithPrefix } from "../../lib/src/common/types"; import { LOG_LEVEL_NOTICE, type FilePathWithPrefix } from "@lib/common/types";
import { QueueProcessor } from "octagonal-wheels/concurrency/processor"; import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
import { sendValue } from "octagonal-wheels/messagepassing/signal"; import { sendValue } from "octagonal-wheels/messagepassing/signal";
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts"; import type { InjectableServiceHub } from "@lib/services/InjectableServices.ts";
import type { LiveSyncCore } from "../../main.ts"; import type { LiveSyncCore } from "@/main.ts";
export class ModuleConflictChecker extends AbstractModule { export class ModuleConflictChecker extends AbstractModule {
async _queueConflictCheckIfOpen(file: FilePathWithPrefix): Promise<void> { async _queueConflictCheckIfOpen(file: FilePathWithPrefix): Promise<void> {
@@ -1,5 +1,5 @@
import { serialized } from "octagonal-wheels/concurrency/lock"; import { serialized } from "octagonal-wheels/concurrency/lock";
import { AbstractModule } from "../AbstractModule.ts"; import { AbstractModule } from "@/modules/AbstractModule.ts";
import { import {
AUTO_MERGED, AUTO_MERGED,
CANCELLED, CANCELLED,
@@ -10,15 +10,15 @@ import {
NOT_CONFLICTED, NOT_CONFLICTED,
type diff_check_result, type diff_check_result,
type FilePathWithPrefix, type FilePathWithPrefix,
} from "../../lib/src/common/types"; } from "@lib/common/types";
import { isCustomisationSyncMetadata, isPluginMetadata } from "@lib/common/typeUtils.ts"; import { isCustomisationSyncMetadata, isPluginMetadata } from "@lib/common/typeUtils.ts";
import { TARGET_IS_NEW } from "@lib/common/models/shared.const.symbols.ts"; import { TARGET_IS_NEW } from "@lib/common/models/shared.const.symbols.ts";
import { compareMTime, displayRev } from "@lib/common/utils.ts"; import { compareMTime, displayRev } from "@lib/common/utils.ts";
import diff_match_patch from "diff-match-patch"; import diff_match_patch from "diff-match-patch";
import { stripAllPrefixes, isPlainText } from "../../lib/src/string_and_binary/path"; import { stripAllPrefixes, isPlainText } from "@lib/string_and_binary/path";
import { eventHub } from "../../common/events.ts"; import { eventHub } from "@/common/events.ts";
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts"; import type { InjectableServiceHub } from "@lib/services/InjectableServices.ts";
import type { LiveSyncCore } from "../../main.ts"; import type { LiveSyncCore } from "@/main.ts";
declare global { declare global {
interface LSEvents { interface LSEvents {
@@ -182,9 +182,9 @@ export class ModuleConflictResolver extends AbstractModule {
revs.map(async (rev) => { revs.map(async (rev) => {
const leaf = await this.core.databaseFileAccess.fetchEntryMeta(filename, rev); const leaf = await this.core.databaseFileAccess.fetchEntryMeta(filename, rev);
if (leaf == false) { if (leaf == false) {
return [0, rev] as [number, string]; return [0, rev];
} }
return [leaf.mtime, rev] as [number, string]; return [leaf.mtime, rev];
}) })
)), )),
] as [number, string][] ] as [number, string][]
@@ -10,15 +10,15 @@ import {
type RemoteDBSettings, type RemoteDBSettings,
IncompatibleChangesInSpecificPattern, IncompatibleChangesInSpecificPattern,
CompatibleButLossyChanges, CompatibleButLossyChanges,
} from "../../lib/src/common/types.ts"; } from "@lib/common/types.ts";
import { escapeMarkdownValue } from "../../lib/src/common/utils.ts"; import { escapeMarkdownValue } from "@lib/common/utils.ts";
import { AbstractModule } from "../AbstractModule.ts"; import { AbstractModule } from "@/modules/AbstractModule.ts";
import { $msg } from "../../lib/src/common/i18n.ts"; import { $msg } from "@lib/common/i18n.ts";
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts"; import type { InjectableServiceHub } from "@lib/services/InjectableServices.ts";
import type { LiveSyncCore } from "../../main.ts"; import type { LiveSyncCore } from "@/main.ts";
import { REMOTE_P2P } from "@lib/common/models/setting.const.ts"; import { REMOTE_P2P } from "@lib/common/models/setting.const.ts";
function valueToString(value: any) { function valueToString(value: string | number | boolean | object | undefined): string {
if (typeof value === "boolean") { if (typeof value === "boolean") {
return value ? "true" : "false"; return value ? "true" : "false";
} }
+24 -15
View File
@@ -1,6 +1,6 @@
import { ButtonComponent } from "@/deps.ts"; import { ButtonComponent } from "@/deps.ts";
import { App, FuzzySuggestModal, MarkdownRenderer, Modal, Plugin, Setting } from "../../../deps.ts"; import { App, FuzzySuggestModal, MarkdownRenderer, Modal, Plugin, Setting, Component } from "@/deps.ts";
import { EVENT_PLUGIN_UNLOADED, eventHub } from "../../../common/events.ts"; import { EVENT_PLUGIN_UNLOADED, eventHub } from "@/common/events.ts";
import { compatGlobal, type CompatIntervalHandle } from "@lib/common/coreEnvFunctions.ts"; import { compatGlobal, type CompatIntervalHandle } from "@lib/common/coreEnvFunctions.ts";
class AutoClosableModal extends Modal { class AutoClosableModal extends Modal {
@@ -148,6 +148,7 @@ export class MessageBox<T extends readonly string[]> extends AutoClosableModal {
wideButton: boolean; wideButton: boolean;
onSubmit: (result: string | false) => void; onSubmit: (result: string | false) => void;
component: Component = new Component();
constructor( constructor(
plugin: Plugin, plugin: Plugin,
@@ -189,12 +190,15 @@ export class MessageBox<T extends readonly string[]> extends AutoClosableModal {
} }
override onOpen() { override onOpen() {
this.component.load();
const { contentEl } = this; const { contentEl } = this;
this.titleEl.setText(this.title); this.titleEl.setText(this.title);
const div = contentEl.createDiv(); const div = contentEl.createDiv();
div.style.userSelect = "text"; div.setCssStyles({
div.style["webkitUserSelect"] = "text"; userSelect: "text",
void MarkdownRenderer.render(this.plugin.app, this.contentMd, div, "/", this.plugin); webkitUserSelect: "text",
});
void MarkdownRenderer.render(this.plugin.app, this.contentMd, div, "/", this.component);
const buttonSetting = new Setting(contentEl); const buttonSetting = new Setting(contentEl);
const labelWrapper = contentEl.createDiv(); const labelWrapper = contentEl.createDiv();
labelWrapper.addClass("sls-dialogue-note-wrapper"); labelWrapper.addClass("sls-dialogue-note-wrapper");
@@ -202,21 +206,23 @@ export class MessageBox<T extends readonly string[]> extends AutoClosableModal {
labelEl.addClass("sls-dialogue-note-countdown"); labelEl.addClass("sls-dialogue-note-countdown");
if (!this.timeout || !this.timer) { if (!this.timeout || !this.timer) {
labelWrapper.empty(); labelWrapper.empty();
labelWrapper.style.display = "none"; labelWrapper.setCssStyles({ display: "none" });
} }
buttonSetting.infoEl.style.display = "none"; buttonSetting.infoEl.setCssStyles({ display: "none" });
buttonSetting.controlEl.style.flexWrap = "wrap"; buttonSetting.controlEl.setCssStyles({ flexWrap: "wrap" });
if (this.wideButton) { if (this.wideButton) {
buttonSetting.controlEl.style.flexDirection = "column"; buttonSetting.controlEl.setCssStyles({
buttonSetting.controlEl.style.alignItems = "center"; flexDirection: "column",
buttonSetting.controlEl.style.justifyContent = "center"; alignItems: "center",
buttonSetting.controlEl.style.flexGrow = "1"; justifyContent: "center",
flexGrow: "1",
});
} }
contentEl.addEventListener("click", () => { contentEl.addEventListener("click", () => {
if (this.timer) { if (this.timer) {
labelWrapper.empty(); labelWrapper.empty();
labelWrapper.style.display = "none"; labelWrapper.setCssStyles({ display: "none" });
compatGlobal.clearInterval(this.timer); compatGlobal.clearInterval(this.timer);
this.timer = undefined; this.timer = undefined;
this.defaultButtonComponent?.setButtonText(`${this.defaultAction}`); this.defaultButtonComponent?.setButtonText(`${this.defaultAction}`);
@@ -238,8 +244,10 @@ export class MessageBox<T extends readonly string[]> extends AutoClosableModal {
btn.setCta(); btn.setCta();
} }
if (this.wideButton) { if (this.wideButton) {
btn.buttonEl.style.flexGrow = "1"; btn.buttonEl.setCssStyles({
btn.buttonEl.style.width = "100%"; flexGrow: "1",
width: "100%",
});
} }
return btn; return btn;
}); });
@@ -248,6 +256,7 @@ export class MessageBox<T extends readonly string[]> extends AutoClosableModal {
override onClose() { override onClose() {
super.onClose(); super.onClose();
this.component.unload();
const { contentEl } = this; const { contentEl } = this;
contentEl.empty(); contentEl.empty();
if (this.timer) { if (this.timer) {
@@ -1,10 +1,10 @@
// Obsidian to LiveSync Utils // Obsidian to LiveSync Utils
import { TFile, type TAbstractFile, type TFolder } from "../../../deps.ts"; import { TFile, type TAbstractFile, type TFolder } from "@/deps.ts";
import { ICHeader } from "../../../common/types.ts"; import { ICHeader } from "@/common/types.ts";
import { addPrefix, isPlainText } from "../../../lib/src/string_and_binary/path.ts"; import { addPrefix, isPlainText } from "@lib/string_and_binary/path.ts";
import { LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger"; import { LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger";
import { createBlob } from "../../../lib/src/common/utils.ts"; import { createBlob } from "@lib/common/utils.ts";
import type { import type {
FilePath, FilePath,
FilePathWithPrefix, FilePathWithPrefix,
@@ -12,8 +12,8 @@ import type {
UXFileInfoStub, UXFileInfoStub,
UXFolderInfo, UXFolderInfo,
UXInternalFileInfoStub, UXInternalFileInfoStub,
} from "../../../lib/src/common/types.ts"; } from "@lib/common/types.ts";
import type { LiveSyncCore } from "../../../main.ts"; import type { LiveSyncCore } from "@/main.ts";
import type { FileAccessObsidian } from "@/serviceModules/FileAccessObsidian.ts"; import type { FileAccessObsidian } from "@/serviceModules/FileAccessObsidian.ts";
export async function TFileToUXFileInfo( export async function TFileToUXFileInfo(
+1 -1
View File
@@ -1,7 +1,7 @@
import type { LiveSyncCore } from "@/main"; import type { LiveSyncCore } from "@/main";
import { LOG_LEVEL_NOTICE } from "octagonal-wheels/common/logger"; import { LOG_LEVEL_NOTICE } from "octagonal-wheels/common/logger";
import { fireAndForget } from "octagonal-wheels/promises"; import { fireAndForget } from "octagonal-wheels/promises";
import { AbstractModule } from "../AbstractModule"; import { AbstractModule } from "@/modules/AbstractModule";
// Separated Module for basic menu commands, which are not related to obsidian specific features. It is expected to be used in other platforms with minimal changes. // Separated Module for basic menu commands, which are not related to obsidian specific features. It is expected to be used in other platforms with minimal changes.
// However, it is odd that it has here at all; it really ought to be in each respective feature. It will likely be moved eventually. Until now, addCommand pointed to Obsidian's version. // However, it is odd that it has here at all; it really ought to be in each respective feature. It will likely be moved eventually. Until now, addCommand pointed to Obsidian's version.
export class ModuleBasicMenu extends AbstractModule { export class ModuleBasicMenu extends AbstractModule {
+10 -10
View File
@@ -1,4 +1,4 @@
import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, Logger } from "../../lib/src/common/logger.ts"; import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, Logger } from "@lib/common/logger.ts";
import { import {
EVENT_REQUEST_OPEN_P2P, EVENT_REQUEST_OPEN_P2P,
EVENT_REQUEST_OPEN_SETTING_WIZARD, EVENT_REQUEST_OPEN_SETTING_WIZARD,
@@ -6,16 +6,16 @@ import {
EVENT_REQUEST_RUN_DOCTOR, EVENT_REQUEST_RUN_DOCTOR,
EVENT_REQUEST_RUN_FIX_INCOMPLETE, EVENT_REQUEST_RUN_FIX_INCOMPLETE,
eventHub, eventHub,
} from "../../common/events.ts"; } from "@/common/events.ts";
import { AbstractModule } from "../AbstractModule.ts"; import { AbstractModule } from "@/modules/AbstractModule.ts";
import { $msg } from "@lib/common/i18n.ts"; import { $msg } from "@lib/common/i18n.ts";
import { performDoctorConsultation, RebuildOptions } from "../../lib/src/common/configForDoc.ts"; import { performDoctorConsultation, RebuildOptions } from "@lib/common/configForDoc.ts";
import { isValidPath } from "../../common/utils.ts"; import { isValidPath } from "@/common/utils.ts";
import { isMetaEntry } from "../../lib/src/common/types.ts"; import { isMetaEntry } from "@lib/common/types.ts";
import { isDeletedEntry, isDocContentSame, isLoadedEntry, readAsBlob } from "../../lib/src/common/utils.ts"; import { isDeletedEntry, isDocContentSame, isLoadedEntry, readAsBlob } from "@lib/common/utils.ts";
import { countCompromisedChunks } from "../../lib/src/pouchdb/negotiation.ts"; import { countCompromisedChunks } from "@lib/pouchdb/negotiation.ts";
import type { LiveSyncCore } from "../../main.ts"; import type { LiveSyncCore } from "@/main.ts";
import { SetupManager } from "../features/SetupManager.ts"; import { SetupManager } from "@/modules/features/SetupManager.ts";
type ErrorInfo = { type ErrorInfo = {
path: string; path: string;

Some files were not shown because too many files have changed in this diff Show More