mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-10-26 11:03:21 +00:00
refactor: align platform APIs with community review
This commit is contained in:
1 parent
298738fc67
commit
326bf77183
39 files changed
+444
-294
No files matched your search
@@ -17,6 +17,8 @@ on:
|
|||||||
- 'vitest.config*.ts'
|
- 'vitest.config*.ts'
|
||||||
- 'esbuild.config.mjs'
|
- 'esbuild.config.mjs'
|
||||||
- 'eslint.config.mjs'
|
- 'eslint.config.mjs'
|
||||||
|
- 'eslint.config.common.mjs'
|
||||||
|
- 'eslint.community.config.mjs'
|
||||||
- 'update-workspaces.mjs'
|
- 'update-workspaces.mjs'
|
||||||
- 'version-bump.mjs'
|
- 'version-bump.mjs'
|
||||||
- 'utils/release-*.mjs'
|
- 'utils/release-*.mjs'
|
||||||
@@ -36,6 +38,8 @@ on:
|
|||||||
- 'vitest.config*.ts'
|
- 'vitest.config*.ts'
|
||||||
- 'esbuild.config.mjs'
|
- 'esbuild.config.mjs'
|
||||||
- 'eslint.config.mjs'
|
- 'eslint.config.mjs'
|
||||||
|
- 'eslint.config.common.mjs'
|
||||||
|
- 'eslint.community.config.mjs'
|
||||||
- 'update-workspaces.mjs'
|
- 'update-workspaces.mjs'
|
||||||
- 'version-bump.mjs'
|
- 'version-bump.mjs'
|
||||||
- 'utils/release-*.mjs'
|
- 'utils/release-*.mjs'
|
||||||
@@ -66,6 +70,9 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Run source checks
|
||||||
|
run: npm run check
|
||||||
|
|
||||||
- name: Run unit tests suite with coverage
|
- name: Run unit tests suite with coverage
|
||||||
run: npm run test:unit:coverage
|
run: npm run test:unit:coverage
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ Before submitting a pull request, you must run verification scripts locally to e
|
|||||||
```bash
|
```bash
|
||||||
npm run check
|
npm run check
|
||||||
```
|
```
|
||||||
|
This also type-checks the maintained CLI and browser applications, and applies the Community directory blocker rules. Run `npm run lint:community` separately to inspect its non-blocking recommendations.
|
||||||
- Run unit tests:
|
- Run unit tests:
|
||||||
```bash
|
```bash
|
||||||
npm run test:unit
|
npm run test:unit
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import obsidianmd from "eslint-plugin-obsidianmd";
|
||||||
|
import globals from "globals";
|
||||||
|
import { defineConfig, globalIgnores } from "eslint/config";
|
||||||
|
|
||||||
|
export default defineConfig(
|
||||||
|
globalIgnores([
|
||||||
|
"node_modules",
|
||||||
|
"**/dist",
|
||||||
|
"build",
|
||||||
|
"coverage",
|
||||||
|
"main.js",
|
||||||
|
"main_org.js",
|
||||||
|
"pouchdb-browser.js",
|
||||||
|
"package.json",
|
||||||
|
"package-lock.json",
|
||||||
|
"versions.json",
|
||||||
|
// Svelte is covered by the project lint and svelte-check; the directory report currently analyses TypeScript.
|
||||||
|
"**/*.svelte",
|
||||||
|
"**/svelte.config.js",
|
||||||
|
"**/*.unit.spec.ts",
|
||||||
|
"**/test/**",
|
||||||
|
"src/apps/_test/**",
|
||||||
|
"src/apps/cli/testdeno/**",
|
||||||
|
]),
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.node,
|
||||||
|
MANIFEST_VERSION: "readonly",
|
||||||
|
PACKAGE_VERSION: "readonly",
|
||||||
|
UPDATE_INFO: "readonly",
|
||||||
|
hostPlatform: "readonly",
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
project: [
|
||||||
|
"./tsconfig.json",
|
||||||
|
"./src/apps/browser/tsconfig.json",
|
||||||
|
"./src/apps/cli/tsconfig.json",
|
||||||
|
"./src/apps/webapp/tsconfig.json",
|
||||||
|
"./src/apps/webpeer/tsconfig.app.json",
|
||||||
|
"./src/apps/webpeer/tsconfig.node.json",
|
||||||
|
],
|
||||||
|
tsconfigRootDir: import.meta.dirname,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...obsidianmd.configs.recommended,
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
// The directory review reports console usage as guidance rather than a release blocker.
|
||||||
|
"obsidianmd/rule-custom-message": "off",
|
||||||
|
"no-console": "warn",
|
||||||
|
"obsidianmd/no-unsupported-api": "error",
|
||||||
|
// Keep legacy type-safety debt visible while reserving errors for directory-review blockers.
|
||||||
|
"@typescript-eslint/no-unsafe-argument": "warn",
|
||||||
|
"@typescript-eslint/no-unsafe-assignment": "warn",
|
||||||
|
"@typescript-eslint/no-unsafe-call": "warn",
|
||||||
|
"@typescript-eslint/no-unsafe-member-access": "warn",
|
||||||
|
"@typescript-eslint/no-unsafe-return": "warn",
|
||||||
|
"@typescript-eslint/no-base-to-string": "warn",
|
||||||
|
"@typescript-eslint/no-redundant-type-constituents": "warn",
|
||||||
|
"@typescript-eslint/no-unnecessary-type-assertion": "warn",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["src/apps/**/*.{ts,js,mjs}"],
|
||||||
|
rules: {
|
||||||
|
// These applications are inspected by the directory review but accept external command and file data.
|
||||||
|
"@typescript-eslint/restrict-template-expressions": "warn",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["src/apps/browser/**/*.ts", "src/apps/webapp/**/*.ts"],
|
||||||
|
rules: {
|
||||||
|
"obsidianmd/no-static-styles-assignment": "off",
|
||||||
|
"obsidianmd/prefer-create-el": "off",
|
||||||
|
"obsidianmd/prefer-active-doc": "off",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -91,8 +91,8 @@ export const baseRules = {
|
|||||||
*/
|
*/
|
||||||
export const obsidianRules = {
|
export const obsidianRules = {
|
||||||
// -- Obsidian rules
|
// -- 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.
|
// Runtime fallbacks must use requireApiVersion guards recognised by the official rule.
|
||||||
"obsidianmd/no-unsupported-api": warnWhileDev,
|
"obsidianmd/no-unsupported-api": "error",
|
||||||
|
|
||||||
// -- Plugin specific overrides
|
// -- Plugin specific overrides
|
||||||
"obsidianmd/rule-custom-message": "off",
|
"obsidianmd/rule-custom-message": "off",
|
||||||
|
|||||||
+36
-4
@@ -7,10 +7,19 @@ import svelteParser from "svelte-eslint-parser";
|
|||||||
import importAlias from "@dword-design/eslint-plugin-import-alias";
|
import importAlias from "@dword-design/eslint-plugin-import-alias";
|
||||||
import { baseRules, ImportAliasRules, obsidianRules } from "./eslint.config.common.mjs";
|
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.
|
||||||
|
const lintProjects = [
|
||||||
|
"./tsconfig.json",
|
||||||
|
"./src/apps/browser/tsconfig.json",
|
||||||
|
"./src/apps/cli/tsconfig.json",
|
||||||
|
"./src/apps/webapp/tsconfig.json",
|
||||||
|
"./src/apps/webpeer/tsconfig.app.json",
|
||||||
|
"./src/apps/webpeer/tsconfig.node.json",
|
||||||
|
];
|
||||||
export default defineConfig([
|
export default defineConfig([
|
||||||
globalIgnores([
|
globalIgnores([
|
||||||
// Build outputs and legacy files
|
// Build outputs and legacy files
|
||||||
"**/build",
|
"**/build",
|
||||||
|
"**/dist/**",
|
||||||
"coverage",
|
"coverage",
|
||||||
"**/main.js",
|
"**/main.js",
|
||||||
"main_org.js",
|
"main_org.js",
|
||||||
@@ -22,8 +31,7 @@ export default defineConfig([
|
|||||||
// 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-project tooling with its own environment
|
||||||
"src/apps",
|
|
||||||
"utils",
|
"utils",
|
||||||
|
|
||||||
// Config files and build scripts
|
// Config files and build scripts
|
||||||
@@ -38,6 +46,9 @@ export default defineConfig([
|
|||||||
"vitest.*",
|
"vitest.*",
|
||||||
// Testing files (Simplified patterns)
|
// Testing files (Simplified patterns)
|
||||||
"test/**",
|
"test/**",
|
||||||
|
"**/test/**",
|
||||||
|
"src/apps/_test/**",
|
||||||
|
"src/apps/cli/testdeno/**",
|
||||||
"**/*.test.ts",
|
"**/*.test.ts",
|
||||||
"**/*.unit.spec.ts",
|
"**/*.unit.spec.ts",
|
||||||
"**/test.ts",
|
"**/test.ts",
|
||||||
@@ -52,8 +63,8 @@ export default defineConfig([
|
|||||||
globals: { ...globals.browser, PouchDB: "readonly" },
|
globals: { ...globals.browser, PouchDB: "readonly" },
|
||||||
parser: tsParser,
|
parser: tsParser,
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
project: "./tsconfig.json",
|
project: lintProjects,
|
||||||
rootDir: "./",
|
tsconfigRootDir: import.meta.dirname,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
linterOptions: {
|
linterOptions: {
|
||||||
@@ -73,6 +84,8 @@ export default defineConfig([
|
|||||||
parser: svelteParser,
|
parser: svelteParser,
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
parser: tsParser,
|
parser: tsParser,
|
||||||
|
project: lintProjects,
|
||||||
|
tsconfigRootDir: import.meta.dirname,
|
||||||
extraFileExtensions: [".svelte"],
|
extraFileExtensions: [".svelte"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -86,4 +99,23 @@ export default defineConfig([
|
|||||||
...ImportAliasRules("."),
|
...ImportAliasRules("."),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
files: ["src/apps/**/*.ts"],
|
||||||
|
rules: {
|
||||||
|
// Platform adapters implement asynchronous contracts even when a local operation is synchronous.
|
||||||
|
"@typescript-eslint/require-await": "off",
|
||||||
|
// Keep existing application code visible without making gradual type tightening a release blocker.
|
||||||
|
"@typescript-eslint/no-base-to-string": "warn",
|
||||||
|
"@typescript-eslint/no-unnecessary-type-assertion": "warn",
|
||||||
|
"@typescript-eslint/restrict-template-expressions": "warn",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["src/apps/browser/**/*.{ts,svelte}", "src/apps/webapp/**/*.ts"],
|
||||||
|
rules: {
|
||||||
|
// Browser applications use the DOM rather than Obsidian's DOM extensions.
|
||||||
|
"obsidianmd/prefer-create-el": "off",
|
||||||
|
"obsidianmd/prefer-active-doc": "off",
|
||||||
|
},
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
Generated
+37
-6
@@ -65,7 +65,7 @@
|
|||||||
"esbuild-plugin-inline-worker": "^0.1.1",
|
"esbuild-plugin-inline-worker": "^0.1.1",
|
||||||
"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.4.1",
|
||||||
"eslint-plugin-svelte": "^3.19.0",
|
"eslint-plugin-svelte": "^3.19.0",
|
||||||
"events": "^3.3.0",
|
"events": "^3.3.0",
|
||||||
"globals": "^14.0.0",
|
"globals": "^14.0.0",
|
||||||
@@ -1813,6 +1813,36 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@eslint-community/eslint-plugin-eslint-comments": {
|
||||||
|
"version": "4.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-4.7.2.tgz",
|
||||||
|
"integrity": "sha512-LF03qURSwEWm2dz5wtdDCzNk+7Opl0X7q6I3undsaIuNsEiNvRV3BCtqu14Q/6Pzg1tBj44LcxpW2EpSLZStZw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"escape-string-regexp": "^4.0.0",
|
||||||
|
"ignore": "^7.0.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/eslint"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"eslint": "^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@eslint-community/eslint-plugin-eslint-comments/node_modules/ignore": {
|
||||||
|
"version": "7.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz",
|
||||||
|
"integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@eslint-community/eslint-utils": {
|
"node_modules/@eslint-community/eslint-utils": {
|
||||||
"version": "4.9.1",
|
"version": "4.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
|
||||||
@@ -8034,12 +8064,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-plugin-obsidianmd": {
|
"node_modules/eslint-plugin-obsidianmd": {
|
||||||
"version": "0.3.0",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-obsidianmd/-/eslint-plugin-obsidianmd-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-obsidianmd/-/eslint-plugin-obsidianmd-0.4.1.tgz",
|
||||||
"integrity": "sha512-QvGDI6B2nxJBrsZKGTg31da2A/fEJNlnwN+fRZkaoPIu1QL3fYXUdpP7ThyMdr/0iTYQxifb9lt2X9cpydQx1w==",
|
"integrity": "sha512-Nv3593kVsFOS8kir/HWaJ5CR3xcyiPWEPyXst5Pib8mxRYYuLYPpJS2ALMoZXHulHyiGwoYW8AaxvLRc4rhrRg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@eslint-community/eslint-plugin-eslint-comments": "^4.7.2",
|
||||||
"@eslint/config-helpers": "^0.4.2",
|
"@eslint/config-helpers": "^0.4.2",
|
||||||
"@eslint/js": "^9.30.1",
|
"@eslint/js": "^9.30.1",
|
||||||
"@eslint/json": "0.14.0",
|
"@eslint/json": "0.14.0",
|
||||||
@@ -8048,7 +8079,7 @@
|
|||||||
"@types/node": "20.12.12",
|
"@types/node": "20.12.12",
|
||||||
"@typescript-eslint/types": "^8.33.1",
|
"@typescript-eslint/types": "^8.33.1",
|
||||||
"@typescript-eslint/utils": "^8.33.1",
|
"@typescript-eslint/utils": "^8.33.1",
|
||||||
"eslint": ">=9.0.0",
|
"eslint": ">=9.19.0",
|
||||||
"eslint-plugin-depend": "1.3.1",
|
"eslint-plugin-depend": "1.3.1",
|
||||||
"eslint-plugin-import": "^2.31.0",
|
"eslint-plugin-import": "^2.31.0",
|
||||||
"eslint-plugin-json-schema-validator": "5.1.0",
|
"eslint-plugin-json-schema-validator": "5.1.0",
|
||||||
@@ -8069,7 +8100,7 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@eslint/js": "^9.30.1",
|
"@eslint/js": "^9.30.1",
|
||||||
"@eslint/json": "0.14.0",
|
"@eslint/json": "0.14.0",
|
||||||
"eslint": ">=9.0.0",
|
"eslint": ">=9.19.0",
|
||||||
"obsidian": "1.8.7",
|
"obsidian": "1.8.7",
|
||||||
"typescript-eslint": "^8.35.1"
|
"typescript-eslint": "^8.35.1"
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -11,15 +11,17 @@
|
|||||||
"buildViteOriginal": "npx dotenv-cli -e .env -- vite build --mode original",
|
"buildViteOriginal": "npx dotenv-cli -e .env -- vite build --mode original",
|
||||||
"buildDev": "node esbuild.config.mjs dev",
|
"buildDev": "node esbuild.config.mjs dev",
|
||||||
"lint": "eslint --cache --concurrency auto src",
|
"lint": "eslint --cache --concurrency auto src",
|
||||||
|
"lint:community": "eslint --config eslint.community.config.mjs --concurrency auto src",
|
||||||
"svelte-check": "svelte-check --tsconfig ./tsconfig.json",
|
"svelte-check": "svelte-check --tsconfig ./tsconfig.json",
|
||||||
"tsc-check": "tsc --noEmit",
|
"tsc-check": "tsc --noEmit",
|
||||||
|
"tsc-check:apps": "tsc --noEmit -p src/apps/browser/tsconfig.json && tsc --noEmit -p src/apps/cli/tsconfig.json && tsc --noEmit -p src/apps/webapp/tsconfig.json && tsc --noEmit -p src/apps/webpeer/tsconfig.app.json && tsc --noEmit -p src/apps/webpeer/tsconfig.node.json",
|
||||||
"pretty:importpath": "cd utilsdeno && deno run -A ./normalise-imports.ts",
|
"pretty:importpath": "cd utilsdeno && deno run -A ./normalise-imports.ts",
|
||||||
"pretty:json": "prettier --config ./.prettierrc.mjs \"**/*.json\" --write --log-level error",
|
"pretty:json": "prettier --config ./.prettierrc.mjs \"**/*.json\" --write --log-level error",
|
||||||
"pretty": "npm run prettyNoWrite -- --write --log-level error",
|
"pretty": "npm run prettyNoWrite -- --write --log-level error",
|
||||||
"prettyCheck": "npm run prettyNoWrite -- --check",
|
"prettyCheck": "npm run prettyNoWrite -- --check",
|
||||||
"prettyNoWrite": "prettier --config ./.prettierrc.mjs \"**/*.js\" \"**/*.ts\" \"**/*.json\" ",
|
"prettyNoWrite": "prettier --config ./.prettierrc.mjs \"**/*.js\" \"**/*.ts\" \"**/*.json\" ",
|
||||||
"check:compatibility": "node utils/check-compatibility.js --file main.js --ios 15",
|
"check:compatibility": "node utils/check-compatibility.js --file main.js --ios 15",
|
||||||
"check": "npm run tsc-check && npm run lint && npm run svelte-check && npm run check:compatibility",
|
"check": "npm run tsc-check && npm run tsc-check:apps && npm run lint && npm run lint:community -- --quiet && npm run svelte-check && npm run check:compatibility",
|
||||||
"unittest": "deno test -A --no-check --coverage=cov_profile --v8-flags=--expose-gc --trace-leaks ./src/",
|
"unittest": "deno test -A --no-check --coverage=cov_profile --v8-flags=--expose-gc --trace-leaks ./src/",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"test:unit": "vitest run --config vitest.config.unit.ts",
|
"test:unit": "vitest run --config vitest.config.unit.ts",
|
||||||
@@ -108,7 +110,7 @@
|
|||||||
"esbuild-plugin-inline-worker": "^0.1.1",
|
"esbuild-plugin-inline-worker": "^0.1.1",
|
||||||
"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.4.1",
|
||||||
"eslint-plugin-svelte": "^3.19.0",
|
"eslint-plugin-svelte": "^3.19.0",
|
||||||
"events": "^3.3.0",
|
"events": "^3.3.0",
|
||||||
"globals": "^14.0.0",
|
"globals": "^14.0.0",
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["../../*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["**/*.ts", "**/*.svelte"],
|
||||||
|
"exclude": ["**/*.unit.spec.ts"]
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { MenuItem } from "../BrowserMenu";
|
import type { MenuItem } from "@/apps/browser/BrowserMenu";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
item: MenuItem;
|
item: MenuItem;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { MenuSeparator } from "../BrowserMenu";
|
import type { MenuSeparator } from "@/apps/browser/BrowserMenu";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
item: MenuSeparator;
|
item: MenuSeparator;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Menu, MenuItem, MenuSeparator } from "../BrowserMenu";
|
import type { Menu, MenuItem, MenuSeparator } from "@/apps/browser/BrowserMenu";
|
||||||
import MenuItemView from "./MenuItemView.svelte";
|
import MenuItemView from "./MenuItemView.svelte";
|
||||||
import MenuSeparatorView from "./MenuSeparatorView.svelte";
|
import MenuSeparatorView from "./MenuSeparatorView.svelte";
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,3 +91,7 @@ export const VALID_COMMANDS = new Set([
|
|||||||
"remote-status",
|
"remote-status",
|
||||||
"init-settings",
|
"init-settings",
|
||||||
] as const);
|
] as const);
|
||||||
|
|
||||||
|
export function isCLICommand(value: string): value is CLICommand {
|
||||||
|
return (VALID_COMMANDS as ReadonlySet<string>).has(value);
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import { path, readline } from "@vrtmrz/livesync-commonlib/node";
|
import { path, readline } from "@vrtmrz/livesync-commonlib/node";
|
||||||
|
|
||||||
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,17 +1,14 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
// eslint-disable -- This is the entry point for the CLI application.
|
import { RTCPeerConnection } from "werift";
|
||||||
import * as polyfill from "werift";
|
|
||||||
import { main } from "./main";
|
import { main } from "./main";
|
||||||
import { compatGlobal } from "@vrtmrz/livesync-commonlib/compat/common/coreEnvFunctions";
|
import { compatGlobal } from "@vrtmrz/livesync-commonlib/compat/common/coreEnvFunctions";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Polyfill
|
|
||||||
const rtcPolyfillCtor = (polyfill as any).RTCPeerConnection;
|
|
||||||
if (
|
if (
|
||||||
typeof (compatGlobal as unknown as Record<string, unknown>).RTCPeerConnection === "undefined" &&
|
typeof (compatGlobal as unknown as Record<string, unknown>).RTCPeerConnection === "undefined" &&
|
||||||
typeof rtcPolyfillCtor === "function"
|
typeof RTCPeerConnection === "function"
|
||||||
) {
|
) {
|
||||||
// Fill only the standard WebRTC global in Node CLI runtime.
|
// Fill only the standard WebRTC global in Node CLI runtime.
|
||||||
(compatGlobal as unknown as Record<string, unknown>).RTCPeerConnection = rtcPolyfillCtor;
|
(compatGlobal as unknown as Record<string, unknown>).RTCPeerConnection = RTCPeerConnection;
|
||||||
}
|
}
|
||||||
|
|
||||||
main().catch((error) => {
|
main().catch((error) => {
|
||||||
|
|||||||
@@ -24,46 +24,48 @@ type PurgeMultiResult = {
|
|||||||
documentWasRemovedCompletely: boolean;
|
documentWasRemovedCompletely: boolean;
|
||||||
};
|
};
|
||||||
type PurgeMultiParam = [docId: string, rev$$1: string];
|
type PurgeMultiParam = [docId: string, rev$$1: string];
|
||||||
|
type PurgeLogDocument = {
|
||||||
|
purgeSeq: number;
|
||||||
|
purges: Array<{ docId: string; rev: string; purgeSeq: number }>;
|
||||||
|
};
|
||||||
|
|
||||||
function appendPurgeSeqs(db: PouchDB.Database, docs: PurgeMultiParam[]) {
|
function appendPurgeSeqs(db: PouchDB.Database, docs: PurgeMultiParam[]) {
|
||||||
return (
|
return db
|
||||||
db
|
.get<PurgeLogDocument>("_local/purges")
|
||||||
.get("_local/purges")
|
.then(function (doc) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Internal method patching.
|
for (const [docId, rev$$1] of docs) {
|
||||||
.then(function (doc: any) {
|
const purgeSeq = doc.purgeSeq + 1;
|
||||||
for (const [docId, rev$$1] of docs) {
|
doc.purges.push({
|
||||||
const purgeSeq = doc.purgeSeq + 1;
|
docId,
|
||||||
doc.purges.push({
|
rev: rev$$1,
|
||||||
docId,
|
purgeSeq,
|
||||||
rev: rev$$1,
|
});
|
||||||
purgeSeq,
|
//@ts-ignore : missing type def
|
||||||
});
|
if (doc.purges.length > db.purged_infos_limit) {
|
||||||
//@ts-ignore : missing type def
|
//@ts-ignore : missing type def
|
||||||
if (doc.purges.length > db.purged_infos_limit) {
|
doc.purges.splice(0, doc.purges.length - db.purged_infos_limit);
|
||||||
//@ts-ignore : missing type def
|
|
||||||
doc.purges.splice(0, doc.purges.length - db.purged_infos_limit);
|
|
||||||
}
|
|
||||||
doc.purgeSeq = purgeSeq;
|
|
||||||
}
|
}
|
||||||
return doc;
|
doc.purgeSeq = purgeSeq;
|
||||||
})
|
}
|
||||||
.catch(function (err) {
|
return doc;
|
||||||
if (err.status !== 404) {
|
})
|
||||||
throw err;
|
.catch(function (err) {
|
||||||
}
|
if (err.status !== 404) {
|
||||||
return {
|
throw err;
|
||||||
_id: "_local/purges",
|
}
|
||||||
purges: docs.map(([docId, rev$$1], idx) => ({
|
return {
|
||||||
docId,
|
_id: "_local/purges",
|
||||||
rev: rev$$1,
|
purges: docs.map(([docId, rev$$1], idx) => ({
|
||||||
purgeSeq: idx,
|
docId,
|
||||||
})),
|
rev: rev$$1,
|
||||||
purgeSeq: docs.length,
|
purgeSeq: idx,
|
||||||
};
|
})),
|
||||||
})
|
purgeSeq: docs.length,
|
||||||
.then(function (doc) {
|
};
|
||||||
return db.put(doc);
|
})
|
||||||
})
|
.then(function (doc) {
|
||||||
);
|
return db.put(doc);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -88,7 +90,7 @@ PouchDB.prototype.purgeMulti = adapterFun(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
// eslint-disable-next-line @typescript-eslint/no-this-alias -- The adapter task callbacks must retain this PouchDB instance.
|
||||||
const self = this;
|
const self = this;
|
||||||
const tasks = docs.map(
|
const tasks = docs.map(
|
||||||
(param) => () =>
|
(param) => () =>
|
||||||
|
|||||||
+11
-8
@@ -2,7 +2,12 @@ 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 "@vrtmrz/livesync-commonlib/compat/common/types";
|
import {
|
||||||
|
DEFAULT_SETTINGS,
|
||||||
|
LOG_LEVEL_VERBOSE,
|
||||||
|
type LOG_LEVEL,
|
||||||
|
type ObsidianLiveSyncSettings,
|
||||||
|
} from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||||
import type { InjectableServiceHub } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableServiceHub";
|
import type { InjectableServiceHub } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableServiceHub";
|
||||||
import type { InjectableSettingService } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableSettingService";
|
import type { InjectableSettingService } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableSettingService";
|
||||||
import {
|
import {
|
||||||
@@ -14,7 +19,7 @@ import {
|
|||||||
LOG_LEVEL_NOTICE,
|
LOG_LEVEL_NOTICE,
|
||||||
} from "octagonal-wheels/common/logger";
|
} from "octagonal-wheels/common/logger";
|
||||||
import { runCommand } from "./commands/runCommand";
|
import { runCommand } from "./commands/runCommand";
|
||||||
import { VALID_COMMANDS } from "./commands/types";
|
import { isCLICommand } from "./commands/types";
|
||||||
import type { CLICommand, CLIOptions } from "./commands/types";
|
import type { CLICommand, CLIOptions } from "./commands/types";
|
||||||
import { getPathFromUXFileInfo } from "@vrtmrz/livesync-commonlib/compat/common/typeUtils";
|
import { getPathFromUXFileInfo } from "@vrtmrz/livesync-commonlib/compat/common/typeUtils";
|
||||||
import { stripAllPrefixes } from "@vrtmrz/livesync-commonlib/compat/string_and_binary/path";
|
import { stripAllPrefixes } from "@vrtmrz/livesync-commonlib/compat/string_and_binary/path";
|
||||||
@@ -185,9 +190,8 @@ export function parseArgs(): CLIOptions {
|
|||||||
break;
|
break;
|
||||||
default: {
|
default: {
|
||||||
if (!databasePath) {
|
if (!databasePath) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Set checking
|
if (command === "daemon" && isCLICommand(token)) {
|
||||||
if (command === "daemon" && VALID_COMMANDS.has(token as any)) {
|
command = token;
|
||||||
command = token as CLICommand;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (command === "init-settings") {
|
if (command === "init-settings") {
|
||||||
@@ -197,9 +201,8 @@ export function parseArgs(): CLIOptions {
|
|||||||
databasePath = token;
|
databasePath = token;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Set checking
|
if (command === "daemon" && isCLICommand(token)) {
|
||||||
if (command === "daemon" && VALID_COMMANDS.has(token as any)) {
|
command = token;
|
||||||
command = token as CLICommand;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
commandArgs.push(token);
|
commandArgs.push(token);
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import { InjectableTweakValueService } from "@vrtmrz/livesync-commonlib/compat/s
|
|||||||
import { InjectableVaultServiceCompat } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableVaultService";
|
import { InjectableVaultServiceCompat } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableVaultService";
|
||||||
import { ControlService } from "@vrtmrz/livesync-commonlib/compat/services/base/ControlService";
|
import { ControlService } from "@vrtmrz/livesync-commonlib/compat/services/base/ControlService";
|
||||||
import { HeadlessAPIService } from "@vrtmrz/livesync-commonlib/compat/services/implements/headless/HeadlessAPIService";
|
import { HeadlessAPIService } from "@vrtmrz/livesync-commonlib/compat/services/implements/headless/HeadlessAPIService";
|
||||||
import type { ServiceInstances } from "@vrtmrz/livesync-commonlib/compat/services/ServiceHub";
|
|
||||||
import { NodeKeyValueDBService } from "./NodeKeyValueDBService";
|
import { NodeKeyValueDBService } from "./NodeKeyValueDBService";
|
||||||
import { NodeSettingService } from "./NodeSettingService";
|
import { NodeSettingService } from "./NodeSettingService";
|
||||||
import { DatabaseService } from "@vrtmrz/livesync-commonlib/compat/services/base/DatabaseService";
|
import { DatabaseService } from "@vrtmrz/livesync-commonlib/compat/services/base/DatabaseService";
|
||||||
@@ -171,7 +170,7 @@ export class NodeServiceHub<T extends NodeServiceContext> extends InjectableServ
|
|||||||
APIService: API,
|
APIService: API,
|
||||||
});
|
});
|
||||||
|
|
||||||
const serviceInstancesToInit: Required<ServiceInstances<T>> = {
|
const serviceInstancesToInit = {
|
||||||
appLifecycle,
|
appLifecycle,
|
||||||
conflict,
|
conflict,
|
||||||
database,
|
database,
|
||||||
@@ -191,7 +190,6 @@ export class NodeServiceHub<T extends NodeServiceContext> extends InjectableServ
|
|||||||
keyValueDB: keyValueDB as unknown as KeyValueDBService<T>,
|
keyValueDB: keyValueDB as unknown as KeyValueDBService<T>,
|
||||||
control,
|
control,
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- (Forcibly )
|
super(context, serviceInstancesToInit);
|
||||||
super(context, serviceInstancesToInit as any);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,15 @@ import { compatGlobal, _activeDocument } from "@vrtmrz/livesync-commonlib/compat
|
|||||||
const historyStore = new VaultHistoryStore();
|
const historyStore = new VaultHistoryStore();
|
||||||
let app: LiveSyncWebApp | null = null;
|
let app: LiveSyncWebApp | null = null;
|
||||||
|
|
||||||
|
type LiveSyncWebAppDebugApi = {
|
||||||
|
getApp: () => LiveSyncWebApp | null;
|
||||||
|
historyStore: VaultHistoryStore;
|
||||||
|
};
|
||||||
|
|
||||||
|
type LiveSyncWebAppGlobal = typeof compatGlobal & {
|
||||||
|
livesyncApp?: LiveSyncWebAppDebugApi;
|
||||||
|
};
|
||||||
|
|
||||||
function getRequiredElement<T extends HTMLElement>(id: string): T {
|
function getRequiredElement<T extends HTMLElement>(id: string): T {
|
||||||
const element = _activeDocument.getElementById(id);
|
const element = _activeDocument.getElementById(id);
|
||||||
if (!element) {
|
if (!element) {
|
||||||
@@ -131,8 +140,7 @@ compatGlobal.addEventListener("load", () => {
|
|||||||
compatGlobal.addEventListener("beforeunload", () => {
|
compatGlobal.addEventListener("beforeunload", () => {
|
||||||
void app?.shutdown();
|
void app?.shutdown();
|
||||||
});
|
});
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- patching
|
(compatGlobal as LiveSyncWebAppGlobal).livesyncApp = {
|
||||||
(compatGlobal as any).livesyncApp = {
|
|
||||||
getApp: () => app,
|
getApp: () => app,
|
||||||
historyStore,
|
historyStore,
|
||||||
};
|
};
|
||||||
+10
-8
@@ -6,7 +6,7 @@
|
|||||||
import type { BrowserServiceHub } from "@vrtmrz/livesync-commonlib/compat/services/BrowserServices";
|
import type { BrowserServiceHub } from "@vrtmrz/livesync-commonlib/compat/services/BrowserServices";
|
||||||
import { LiveSyncBaseCore } from "@/LiveSyncBaseCore";
|
import { LiveSyncBaseCore } from "@/LiveSyncBaseCore";
|
||||||
import { ServiceContext } from "@vrtmrz/livesync-commonlib/compat/services/base/ServiceBase";
|
import { ServiceContext } from "@vrtmrz/livesync-commonlib/compat/services/base/ServiceBase";
|
||||||
import { initialiseServiceModulesFSAPI } from "./serviceModules/FSAPIServiceModules";
|
import { initialiseServiceModulesFSAPI, type FSAPIServiceModules } from "./serviceModules/FSAPIServiceModules";
|
||||||
import type { ObsidianLiveSyncSettings } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
import type { ObsidianLiveSyncSettings } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||||
import type { BrowserAPIService } from "@vrtmrz/livesync-commonlib/compat/services/implements/browser/BrowserAPIService";
|
import type { BrowserAPIService } from "@vrtmrz/livesync-commonlib/compat/services/implements/browser/BrowserAPIService";
|
||||||
import type { InjectableSettingService } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableSettingService";
|
import type { InjectableSettingService } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableSettingService";
|
||||||
@@ -53,6 +53,7 @@ class LiveSyncWebApp {
|
|||||||
private rootHandle: FileSystemDirectoryHandle;
|
private rootHandle: FileSystemDirectoryHandle;
|
||||||
private core: LiveSyncBaseCore<ServiceContext, never> | null = null;
|
private core: LiveSyncBaseCore<ServiceContext, never> | null = null;
|
||||||
private serviceHub: BrowserServiceHub<ServiceContext> | null = null;
|
private serviceHub: BrowserServiceHub<ServiceContext> | null = null;
|
||||||
|
private platformServiceModules: FSAPIServiceModules | null = null;
|
||||||
|
|
||||||
constructor(rootHandle: FileSystemDirectoryHandle) {
|
constructor(rootHandle: FileSystemDirectoryHandle) {
|
||||||
this.rootHandle = rootHandle;
|
this.rootHandle = rootHandle;
|
||||||
@@ -113,7 +114,9 @@ class LiveSyncWebApp {
|
|||||||
this.core = new LiveSyncBaseCore<ServiceContext, never>(
|
this.core = new LiveSyncBaseCore<ServiceContext, never>(
|
||||||
this.serviceHub,
|
this.serviceHub,
|
||||||
(core, serviceHub) => {
|
(core, serviceHub) => {
|
||||||
return initialiseServiceModulesFSAPI(this.rootHandle, core, serviceHub);
|
const serviceModules = initialiseServiceModulesFSAPI(this.rootHandle, core, serviceHub);
|
||||||
|
this.platformServiceModules = serviceModules;
|
||||||
|
return serviceModules;
|
||||||
},
|
},
|
||||||
(core) => [
|
(core) => [
|
||||||
// new ModuleObsidianEvents(this, core),
|
// new ModuleObsidianEvents(this, core),
|
||||||
@@ -208,9 +211,8 @@ class LiveSyncWebApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Scan the directory to populate file cache
|
// Scan the directory to populate file cache
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Accessing private service modules
|
const fileAccess = this.platformServiceModules?.vaultAccess;
|
||||||
const fileAccess = (this.core as any)._serviceModules?.storageAccess?.vaultAccess;
|
if (fileAccess) {
|
||||||
if (fileAccess?.fsapiAdapter) {
|
|
||||||
console.log("[Scanning] Scanning vault directory...");
|
console.log("[Scanning] Scanning vault directory...");
|
||||||
await fileAccess.fsapiAdapter.scanDirectory();
|
await fileAccess.fsapiAdapter.scanDirectory();
|
||||||
const files = await fileAccess.fsapiAdapter.getFiles();
|
const files = await fileAccess.fsapiAdapter.getFiles();
|
||||||
@@ -227,13 +229,13 @@ class LiveSyncWebApp {
|
|||||||
console.log("[Shutdown] Shutting down...");
|
console.log("[Shutdown] Shutting down...");
|
||||||
|
|
||||||
// Stop file watching
|
// Stop file watching
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Accessing private service modules
|
const storageEventManager = this.platformServiceModules?.storageEventManager;
|
||||||
const storageEventManager = (this.core as any)._serviceModules?.storageAccess?.storageEventManager;
|
if (storageEventManager) {
|
||||||
if (storageEventManager?.cleanup) {
|
|
||||||
await storageEventManager.cleanup();
|
await storageEventManager.cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.core.services.control.onUnload();
|
await this.core.services.control.onUnload();
|
||||||
|
this.platformServiceModules = null;
|
||||||
console.log("[Shutdown] Complete");
|
console.log("[Shutdown] Complete");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,25 @@ import type { FileEventItemSentinel } from "@vrtmrz/livesync-commonlib/compat/ma
|
|||||||
import type { FSAPIFile, FSAPIFolder } from "@/apps/webapp/adapters/FSAPITypes";
|
import type { FSAPIFile, FSAPIFolder } from "@/apps/webapp/adapters/FSAPITypes";
|
||||||
import { compatGlobal } from "@vrtmrz/livesync-commonlib/compat/common/coreEnvFunctions";
|
import { compatGlobal } from "@vrtmrz/livesync-commonlib/compat/common/coreEnvFunctions";
|
||||||
|
|
||||||
|
type FileSystemObserverRecord = {
|
||||||
|
changedHandle?: FileSystemFileHandle | FileSystemDirectoryHandle;
|
||||||
|
relativePathComponents?: readonly string[];
|
||||||
|
type: "appeared" | "disappeared" | "modified" | "moved" | "unknown" | "errored";
|
||||||
|
};
|
||||||
|
|
||||||
|
type FileSystemObserverInstance = {
|
||||||
|
observe(handle: FileSystemDirectoryHandle, options: { recursive: boolean }): Promise<void>;
|
||||||
|
disconnect(): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type FileSystemObserverConstructor = new (
|
||||||
|
callback: (records: readonly FileSystemObserverRecord[]) => void | Promise<void>
|
||||||
|
) => FileSystemObserverInstance;
|
||||||
|
|
||||||
|
type GlobalWithFileSystemObserver = typeof compatGlobal & {
|
||||||
|
FileSystemObserver?: FileSystemObserverConstructor;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FileSystem API-specific type guard adapter
|
* FileSystem API-specific type guard adapter
|
||||||
*/
|
*/
|
||||||
@@ -89,7 +108,8 @@ class FSAPIPersistenceAdapter implements IStorageEventPersistenceAdapter {
|
|||||||
|
|
||||||
const result = await new Promise<(FileEventItem | FileEventItemSentinel)[] | null>((resolve, reject) => {
|
const result = await new Promise<(FileEventItem | FileEventItemSentinel)[] | null>((resolve, reject) => {
|
||||||
const request = store.get(this.snapshotKey);
|
const request = store.get(this.snapshotKey);
|
||||||
request.onsuccess = () => resolve(request.result || null);
|
request.onsuccess = () =>
|
||||||
|
resolve((request.result as (FileEventItem | FileEventItemSentinel)[] | undefined) ?? null);
|
||||||
request.onerror = () => reject(request.error);
|
request.onerror = () => reject(request.error);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -155,26 +175,21 @@ class FSAPIConverterAdapter implements IStorageEventConverterAdapter<FSAPIFile>
|
|||||||
* FileSystem API-specific watch adapter using FileSystemObserver (Chrome only)
|
* FileSystem API-specific watch adapter using FileSystemObserver (Chrome only)
|
||||||
*/
|
*/
|
||||||
class FSAPIWatchAdapter implements IStorageEventWatchAdapter {
|
class FSAPIWatchAdapter implements IStorageEventWatchAdapter {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Accessing private service modules
|
private observer: FileSystemObserverInstance | null = null;
|
||||||
private observer: any = null; // FileSystemObserver type
|
|
||||||
|
|
||||||
constructor(private rootHandle: FileSystemDirectoryHandle) {}
|
constructor(private rootHandle: FileSystemDirectoryHandle) {}
|
||||||
|
|
||||||
async beginWatch(handlers: IStorageEventWatchHandlers): Promise<void> {
|
async beginWatch(handlers: IStorageEventWatchHandlers): Promise<void> {
|
||||||
// Use FileSystemObserver if available (Chrome 124+)
|
// Use FileSystemObserver if available (Chrome 124+)
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Accessing global FileSystemObserver
|
const FileSystemObserver = (compatGlobal as GlobalWithFileSystemObserver).FileSystemObserver;
|
||||||
if (typeof (compatGlobal as any).FileSystemObserver === "undefined") {
|
if (!FileSystemObserver) {
|
||||||
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 {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Accessing private service modules
|
this.observer = new FileSystemObserver(async (records) => {
|
||||||
const FileSystemObserver = (compatGlobal as any).FileSystemObserver;
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Accessing private service modules
|
|
||||||
this.observer = new FileSystemObserver(async (records: any[]) => {
|
|
||||||
for (const record of records) {
|
for (const record of records) {
|
||||||
const changedHandle = record.changedHandle;
|
const changedHandle = record.changedHandle;
|
||||||
const relativePathComponents = record.relativePathComponents;
|
const relativePathComponents = record.relativePathComponents;
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ import { StorageEventManagerFSAPI } from "@/apps/webapp/managers/StorageEventMan
|
|||||||
import type { ServiceModules } from "@vrtmrz/livesync-commonlib/compat/interfaces/ServiceModule";
|
import type { ServiceModules } from "@vrtmrz/livesync-commonlib/compat/interfaces/ServiceModule";
|
||||||
import { ServiceFileHandler } from "@/serviceModules/FileHandler";
|
import { ServiceFileHandler } from "@/serviceModules/FileHandler";
|
||||||
|
|
||||||
|
export interface FSAPIServiceModules extends ServiceModules {
|
||||||
|
vaultAccess: FileAccessFSAPI;
|
||||||
|
storageEventManager: StorageEventManagerFSAPI;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize service modules for FileSystem API webapp version
|
* Initialize service modules for FileSystem API webapp version
|
||||||
* This is the webapp equivalent of ObsidianLiveSyncPlugin.initialiseServiceModules
|
* This is the webapp equivalent of ObsidianLiveSyncPlugin.initialiseServiceModules
|
||||||
@@ -24,7 +29,7 @@ export function initialiseServiceModulesFSAPI(
|
|||||||
rootHandle: FileSystemDirectoryHandle,
|
rootHandle: FileSystemDirectoryHandle,
|
||||||
core: LiveSyncBaseCore<ServiceContext, never>,
|
core: LiveSyncBaseCore<ServiceContext, never>,
|
||||||
services: InjectableServiceHub<ServiceContext>
|
services: InjectableServiceHub<ServiceContext>
|
||||||
): ServiceModules {
|
): FSAPIServiceModules {
|
||||||
const storageAccessManager = new StorageAccessManager();
|
const storageAccessManager = new StorageAccessManager();
|
||||||
|
|
||||||
// FileSystem API-specific file access
|
// FileSystem API-specific file access
|
||||||
@@ -104,5 +109,7 @@ export function initialiseServiceModulesFSAPI(
|
|||||||
fileHandler,
|
fileHandler,
|
||||||
databaseFileAccess,
|
databaseFileAccess,
|
||||||
storageAccess,
|
storageAccess,
|
||||||
|
vaultAccess,
|
||||||
|
storageEventManager,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,6 @@ function redactObject(obj: Record<string, unknown>, dotted: string, redactedValu
|
|||||||
if (!(key in current)) {
|
if (!(key in current)) {
|
||||||
current[key] = {};
|
current[key] = {};
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
||||||
current = current[key] as Record<string, unknown>;
|
current = current[key] as Record<string, unknown>;
|
||||||
}
|
}
|
||||||
const lastKey = keys[keys.length - 1];
|
const lastKey = keys[keys.length - 1];
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export {
|
|||||||
Menu,
|
Menu,
|
||||||
request,
|
request,
|
||||||
getLanguage,
|
getLanguage,
|
||||||
|
requireApiVersion,
|
||||||
ButtonComponent,
|
ButtonComponent,
|
||||||
TextComponent,
|
TextComponent,
|
||||||
ToggleComponent,
|
ToggleComponent,
|
||||||
|
|||||||
@@ -1178,7 +1178,7 @@ export class ConfigSync extends LiveSyncCommands {
|
|||||||
if (this.isThisModuleEnabled() && this.core.settings.notifyPluginOrSettingUpdated) {
|
if (this.isThisModuleEnabled() && this.core.settings.notifyPluginOrSettingUpdated) {
|
||||||
if (!this.pluginDialog || (this.pluginDialog && !this.pluginDialog.isOpened())) {
|
if (!this.pluginDialog || (this.pluginDialog && !this.pluginDialog.isOpened())) {
|
||||||
const fragment = createFragment((doc) => {
|
const fragment = createFragment((doc) => {
|
||||||
doc.createEl("span", undefined, (a) => {
|
doc.createSpan(undefined, (a) => {
|
||||||
a.appendText(`Some configuration has been arrived, Press `);
|
a.appendText(`Some configuration has been arrived, Press `);
|
||||||
a.appendChild(
|
a.appendChild(
|
||||||
a.createEl("a", undefined, (anchor) => {
|
a.createEl("a", undefined, (anchor) => {
|
||||||
|
|||||||
@@ -4,20 +4,16 @@ import { EVENT_PLUGIN_UNLOADED, eventHub } from "@/common/events.ts";
|
|||||||
import { compatGlobal, type CompatIntervalHandle } from "@vrtmrz/livesync-commonlib/compat/common/coreEnvFunctions";
|
import { compatGlobal, type CompatIntervalHandle } from "@vrtmrz/livesync-commonlib/compat/common/coreEnvFunctions";
|
||||||
|
|
||||||
class AutoClosableModal extends Modal {
|
class AutoClosableModal extends Modal {
|
||||||
_closeByUnload() {
|
_closeByUnload = () => {
|
||||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
||||||
eventHub.off(EVENT_PLUGIN_UNLOADED, this._closeByUnload);
|
eventHub.off(EVENT_PLUGIN_UNLOADED, this._closeByUnload);
|
||||||
this.close();
|
this.close();
|
||||||
}
|
};
|
||||||
|
|
||||||
constructor(app: App) {
|
constructor(app: App) {
|
||||||
super(app);
|
super(app);
|
||||||
this._closeByUnload = this._closeByUnload.bind(this);
|
|
||||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
||||||
eventHub.once(EVENT_PLUGIN_UNLOADED, this._closeByUnload);
|
eventHub.once(EVENT_PLUGIN_UNLOADED, this._closeByUnload);
|
||||||
}
|
}
|
||||||
override onClose() {
|
override onClose() {
|
||||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
||||||
eventHub.off(EVENT_PLUGIN_UNLOADED, this._closeByUnload);
|
eventHub.off(EVENT_PLUGIN_UNLOADED, this._closeByUnload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,23 @@ import {
|
|||||||
import type { LiveSyncCore } from "@/main.ts";
|
import type { LiveSyncCore } from "@/main.ts";
|
||||||
import { compatGlobal } from "@vrtmrz/livesync-commonlib/compat/common/coreEnvFunctions";
|
import { compatGlobal } from "@vrtmrz/livesync-commonlib/compat/common/coreEnvFunctions";
|
||||||
|
|
||||||
|
type MutableCommandDefinition = {
|
||||||
|
callback?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type InternalCommandRegistry = {
|
||||||
|
commands?: Record<string, MutableCommandDefinition | undefined>;
|
||||||
|
executeCommandById(commandId: string): unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
type AppWithInternalCommands = {
|
||||||
|
commands?: InternalCommandRegistry;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CodeMirrorAdapter = {
|
||||||
|
commands: { save: () => void };
|
||||||
|
};
|
||||||
|
|
||||||
export class ModuleObsidianEvents extends AbstractObsidianModule {
|
export class ModuleObsidianEvents extends AbstractObsidianModule {
|
||||||
_everyOnloadStart(): Promise<boolean> {
|
_everyOnloadStart(): Promise<boolean> {
|
||||||
// this.registerEvent(this.app.workspace.on("editor-change", ));
|
// this.registerEvent(this.app.workspace.on("editor-change", ));
|
||||||
@@ -40,10 +57,10 @@ export class ModuleObsidianEvents extends AbstractObsidianModule {
|
|||||||
|
|
||||||
swapSaveCommand() {
|
swapSaveCommand() {
|
||||||
this._log("Modifying callback of the save command", LOG_LEVEL_VERBOSE);
|
this._log("Modifying callback of the save command", LOG_LEVEL_VERBOSE);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Editor Tweaking
|
const commandRegistry = (this.app as unknown as AppWithInternalCommands).commands;
|
||||||
const saveCommandDefinition = (this.app as any).commands?.commands?.["editor:save-file"];
|
const saveCommandDefinition = commandRegistry?.commands?.["editor:save-file"];
|
||||||
const save = saveCommandDefinition?.callback;
|
const save = saveCommandDefinition?.callback;
|
||||||
if (typeof save === "function") {
|
if (saveCommandDefinition && typeof save === "function") {
|
||||||
this.initialCallback = save;
|
this.initialCallback = save;
|
||||||
saveCommandDefinition.callback = () => {
|
saveCommandDefinition.callback = () => {
|
||||||
scheduleTask("syncOnEditorSave", 250, () => {
|
scheduleTask("syncOnEditorSave", 250, () => {
|
||||||
@@ -61,17 +78,14 @@ export class ModuleObsidianEvents extends AbstractObsidianModule {
|
|||||||
save();
|
save();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
const codeMirrorAdapter = (compatGlobal as typeof compatGlobal & { CodeMirrorAdapter?: CodeMirrorAdapter })
|
||||||
const _this = this;
|
.CodeMirrorAdapter;
|
||||||
//@ts-ignore
|
if (!codeMirrorAdapter) {
|
||||||
if (!compatGlobal.CodeMirrorAdapter) {
|
|
||||||
this._log("CodeMirrorAdapter is not available");
|
this._log("CodeMirrorAdapter is not available");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//@ts-ignore
|
codeMirrorAdapter.commands.save = () => {
|
||||||
compatGlobal.CodeMirrorAdapter.commands.save = () => {
|
void commandRegistry?.executeCommandById("editor:save-file");
|
||||||
//@ts-ignore
|
|
||||||
void _this.app.commands.executeCommandById("editor:save-file");
|
|
||||||
// _this.app.performCommand('editor:save-file');
|
// _this.app.performCommand('editor:save-file');
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -82,18 +96,18 @@ export class ModuleObsidianEvents extends AbstractObsidianModule {
|
|||||||
this.watchWorkspaceOpen = this.watchWorkspaceOpen.bind(this);
|
this.watchWorkspaceOpen = this.watchWorkspaceOpen.bind(this);
|
||||||
this.watchOnline = this.watchOnline.bind(this);
|
this.watchOnline = this.watchOnline.bind(this);
|
||||||
// Already bound
|
// Already bound
|
||||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
// eslint-disable-next-line @typescript-eslint/unbound-method -- The handler is bound above before registration.
|
||||||
this.plugin.registerEvent(this.app.workspace.on("file-open", this.watchWorkspaceOpen));
|
this.plugin.registerEvent(this.app.workspace.on("file-open", this.watchWorkspaceOpen));
|
||||||
// Already bound
|
// Already bound
|
||||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
// eslint-disable-next-line @typescript-eslint/unbound-method -- The handler is bound above before registration.
|
||||||
this.plugin.registerDomEvent(activeDocument, "visibilitychange", this.watchWindowVisibility);
|
this.plugin.registerDomEvent(activeDocument, "visibilitychange", this.watchWindowVisibility);
|
||||||
this.plugin.registerDomEvent(compatGlobal, "focus", () => this.setHasFocus(true));
|
this.plugin.registerDomEvent(compatGlobal, "focus", () => this.setHasFocus(true));
|
||||||
this.plugin.registerDomEvent(compatGlobal, "blur", () => this.setHasFocus(false));
|
this.plugin.registerDomEvent(compatGlobal, "blur", () => this.setHasFocus(false));
|
||||||
// Already bound
|
// Already bound
|
||||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
// eslint-disable-next-line @typescript-eslint/unbound-method -- The handler is bound above before registration.
|
||||||
this.plugin.registerDomEvent(compatGlobal, "online", this.watchOnline);
|
this.plugin.registerDomEvent(compatGlobal, "online", this.watchOnline);
|
||||||
// Already bound
|
// Already bound
|
||||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
// eslint-disable-next-line @typescript-eslint/unbound-method -- The handler is bound above before registration.
|
||||||
this.plugin.registerDomEvent(compatGlobal, "offline", this.watchOnline);
|
this.plugin.registerDomEvent(compatGlobal, "offline", this.watchOnline);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,7 +331,7 @@ export class ModuleObsidianEvents extends AbstractObsidianModule {
|
|||||||
// const proc = this.core.processingFileEventCount.value;
|
// const proc = this.core.processingFileEventCount.value;
|
||||||
const e = 0;
|
const e = 0;
|
||||||
const proc = 0;
|
const proc = 0;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- Reading the tick establishes the reactive polling dependency.
|
||||||
const __ = __tick.value;
|
const __ = __tick.value;
|
||||||
return (
|
return (
|
||||||
dbCount +
|
dbCount +
|
||||||
|
|||||||
@@ -34,8 +34,7 @@ async function measure(
|
|||||||
return [name, measures.get(name) as MeasureResult];
|
return [name, measures.get(name) as MeasureResult];
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line require-await, @typescript-eslint/require-await
|
function formatPerfResults(items: NamedMeasureResult[]) {
|
||||||
async function formatPerfResults(items: NamedMeasureResult[]) {
|
|
||||||
return (
|
return (
|
||||||
`| Name | Runs | Each | Total |\n| --- | --- | --- | --- | \n` +
|
`| Name | Runs | Each | Total |\n| --- | --- | --- | --- | \n` +
|
||||||
items
|
items
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ import { isPlainText, stripPrefix } from "@vrtmrz/livesync-commonlib/compat/stri
|
|||||||
import { scheduleOnceIfDuplicated } from "octagonal-wheels/concurrency/lock";
|
import { scheduleOnceIfDuplicated } from "octagonal-wheels/concurrency/lock";
|
||||||
import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore.ts";
|
import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore.ts";
|
||||||
import { compatGlobal } from "@vrtmrz/livesync-commonlib/compat/common/coreEnvFunctions";
|
import { compatGlobal } from "@vrtmrz/livesync-commonlib/compat/common/coreEnvFunctions";
|
||||||
|
import {
|
||||||
|
DOCUMENT_HISTORY_PREFERENCE_KEYS,
|
||||||
|
loadDocumentHistoryPreference,
|
||||||
|
saveDocumentHistoryPreference,
|
||||||
|
} from "./documentHistoryPreferences.ts";
|
||||||
|
|
||||||
function isImage(path: string) {
|
function isImage(path: string) {
|
||||||
const ext = path.split(".").splice(-1)[0].toLowerCase();
|
const ext = path.split(".").splice(-1)[0].toLowerCase();
|
||||||
@@ -99,12 +104,10 @@ export class DocumentHistoryModal extends Modal {
|
|||||||
if (!file && id) {
|
if (!file && id) {
|
||||||
this.file = this.services.path.id2path(id);
|
this.file = this.services.path.id2path(id);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line obsidianmd/no-unsupported-api -- loadLocalStorage is supported in Obsidian 1.7.2+
|
if (loadDocumentHistoryPreference(this.app, DOCUMENT_HISTORY_PREFERENCE_KEYS.highlightDiff)) {
|
||||||
if (this.app.loadLocalStorage("ols-history-highlightdiff") == "1") {
|
|
||||||
this.showDiff = true;
|
this.showDiff = true;
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line obsidianmd/no-unsupported-api -- loadLocalStorage is supported in Obsidian 1.7.2+
|
if (loadDocumentHistoryPreference(this.app, DOCUMENT_HISTORY_PREFERENCE_KEYS.diffOnly)) {
|
||||||
if (this.app.loadLocalStorage("ols-history-diffonly") == "1") {
|
|
||||||
this.diffOnly = true;
|
this.diffOnly = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -520,10 +523,10 @@ export class DocumentHistoryModal extends Modal {
|
|||||||
e.addEventListener("click", () => this.navigateSearch("next"));
|
e.addEventListener("click", () => this.navigateSearch("next"));
|
||||||
});
|
});
|
||||||
|
|
||||||
this.searchResultIndicator = searchRow.createEl("span", { text: "" });
|
this.searchResultIndicator = searchRow.createSpan({ text: "" });
|
||||||
this.searchResultIndicator.addClass("history-search-result-indicator");
|
this.searchResultIndicator.addClass("history-search-result-indicator");
|
||||||
|
|
||||||
this.searchProgressIndicator = searchRow.createEl("span", { text: "" });
|
this.searchProgressIndicator = searchRow.createSpan({ text: "" });
|
||||||
this.searchProgressIndicator.addClass("history-search-progress-indicator");
|
this.searchProgressIndicator.addClass("history-search-progress-indicator");
|
||||||
|
|
||||||
const divView = contentEl.createDiv("");
|
const divView = contentEl.createDiv("");
|
||||||
@@ -554,8 +557,11 @@ export class DocumentHistoryModal extends Modal {
|
|||||||
}
|
}
|
||||||
checkbox.addEventListener("input", (evt: Event) => {
|
checkbox.addEventListener("input", (evt: Event) => {
|
||||||
this.showDiff = checkbox.checked;
|
this.showDiff = checkbox.checked;
|
||||||
// eslint-disable-next-line obsidianmd/no-unsupported-api -- saveLocalStorage is supported in Obsidian 1.7.2+
|
saveDocumentHistoryPreference(
|
||||||
this.app.saveLocalStorage("ols-history-highlightdiff", this.showDiff == true ? "1" : null);
|
this.app,
|
||||||
|
DOCUMENT_HISTORY_PREFERENCE_KEYS.highlightDiff,
|
||||||
|
this.showDiff
|
||||||
|
);
|
||||||
this.updateDiffNavVisibility();
|
this.updateDiffNavVisibility();
|
||||||
void scheduleOnceIfDuplicated("loadRevs", () => this.loadRevs());
|
void scheduleOnceIfDuplicated("loadRevs", () => this.loadRevs());
|
||||||
});
|
});
|
||||||
@@ -570,8 +576,7 @@ export class DocumentHistoryModal extends Modal {
|
|||||||
}
|
}
|
||||||
checkbox.addEventListener("input", (evt: Event) => {
|
checkbox.addEventListener("input", (evt: Event) => {
|
||||||
this.diffOnly = checkbox.checked;
|
this.diffOnly = checkbox.checked;
|
||||||
// eslint-disable-next-line obsidianmd/no-unsupported-api -- saveLocalStorage is supported in Obsidian 1.7.2+
|
saveDocumentHistoryPreference(this.app, DOCUMENT_HISTORY_PREFERENCE_KEYS.diffOnly, this.diffOnly);
|
||||||
this.app.saveLocalStorage("ols-history-diffonly", this.diffOnly == true ? "1" : null);
|
|
||||||
void scheduleOnceIfDuplicated("loadRevs", () => this.loadRevs());
|
void scheduleOnceIfDuplicated("loadRevs", () => this.loadRevs());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -597,7 +602,7 @@ export class DocumentHistoryModal extends Modal {
|
|||||||
this.navigateDiff("next");
|
this.navigateDiff("next");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.diffNavIndicator = this.diffNavContainer.createEl("span", { text: "\u2014" });
|
this.diffNavIndicator = this.diffNavContainer.createSpan({ text: "\u2014" });
|
||||||
this.diffNavIndicator.addClass("diff-nav-indicator");
|
this.diffNavIndicator.addClass("diff-nav-indicator");
|
||||||
|
|
||||||
this.info = contentEl.createDiv("");
|
this.info = contentEl.createDiv("");
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { requireApiVersion, type App } from "@/deps.ts";
|
||||||
|
|
||||||
|
export const DOCUMENT_HISTORY_PREFERENCE_KEYS = {
|
||||||
|
diffOnly: "ols-history-diffonly",
|
||||||
|
highlightDiff: "ols-history-highlightdiff",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type DocumentHistoryPreferenceKey =
|
||||||
|
(typeof DOCUMENT_HISTORY_PREFERENCE_KEYS)[keyof typeof DOCUMENT_HISTORY_PREFERENCE_KEYS];
|
||||||
|
|
||||||
|
export function loadDocumentHistoryPreference(app: App, key: DocumentHistoryPreferenceKey): boolean {
|
||||||
|
if (requireApiVersion("1.8.7")) {
|
||||||
|
return app.loadLocalStorage(key) === "1";
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveDocumentHistoryPreference(app: App, key: DocumentHistoryPreferenceKey, enabled: boolean): void {
|
||||||
|
if (requireApiVersion("1.8.7")) {
|
||||||
|
app.saveLocalStorage(key, enabled ? "1" : null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
const requireApiVersionMock = vi.hoisted(() => vi.fn<(version: string) => boolean>());
|
||||||
|
|
||||||
|
vi.mock("@/deps.ts", () => ({
|
||||||
|
requireApiVersion: requireApiVersionMock,
|
||||||
|
}));
|
||||||
|
|
||||||
|
import {
|
||||||
|
DOCUMENT_HISTORY_PREFERENCE_KEYS,
|
||||||
|
loadDocumentHistoryPreference,
|
||||||
|
saveDocumentHistoryPreference,
|
||||||
|
} from "./documentHistoryPreferences.ts";
|
||||||
|
|
||||||
|
function createAppStorage() {
|
||||||
|
return {
|
||||||
|
loadLocalStorage: vi.fn<(key: string) => unknown>(),
|
||||||
|
saveLocalStorage: vi.fn<(key: string, value: unknown | null) => void>(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("document history preferences", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
requireApiVersionMock.mockReset();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back without accessing Vault local storage on older Obsidian versions", () => {
|
||||||
|
requireApiVersionMock.mockReturnValue(false);
|
||||||
|
const app = createAppStorage();
|
||||||
|
|
||||||
|
expect(loadDocumentHistoryPreference(app as never, DOCUMENT_HISTORY_PREFERENCE_KEYS.highlightDiff)).toBe(false);
|
||||||
|
saveDocumentHistoryPreference(app as never, DOCUMENT_HISTORY_PREFERENCE_KEYS.diffOnly, true);
|
||||||
|
|
||||||
|
expect(requireApiVersionMock).toHaveBeenCalledWith("1.8.7");
|
||||||
|
expect(app.loadLocalStorage).not.toHaveBeenCalled();
|
||||||
|
expect(app.saveLocalStorage).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("loads and saves Vault-scoped preferences when the API is available", () => {
|
||||||
|
requireApiVersionMock.mockReturnValue(true);
|
||||||
|
const app = createAppStorage();
|
||||||
|
app.loadLocalStorage.mockReturnValue("1");
|
||||||
|
|
||||||
|
expect(loadDocumentHistoryPreference(app as never, DOCUMENT_HISTORY_PREFERENCE_KEYS.diffOnly)).toBe(true);
|
||||||
|
saveDocumentHistoryPreference(app as never, DOCUMENT_HISTORY_PREFERENCE_KEYS.highlightDiff, true);
|
||||||
|
saveDocumentHistoryPreference(app as never, DOCUMENT_HISTORY_PREFERENCE_KEYS.highlightDiff, false);
|
||||||
|
|
||||||
|
expect(app.loadLocalStorage).toHaveBeenCalledWith("ols-history-diffonly");
|
||||||
|
expect(app.saveLocalStorage).toHaveBeenNthCalledWith(1, "ols-history-highlightdiff", "1");
|
||||||
|
expect(app.saveLocalStorage).toHaveBeenNthCalledWith(2, "ols-history-highlightdiff", null);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -110,7 +110,7 @@ export class ConflictResolveModal extends Modal {
|
|||||||
contentEl.empty();
|
contentEl.empty();
|
||||||
const diffOptionsRow = contentEl.createDiv("");
|
const diffOptionsRow = contentEl.createDiv("");
|
||||||
diffOptionsRow.addClass("diff-options-row");
|
diffOptionsRow.addClass("diff-options-row");
|
||||||
diffOptionsRow.createEl("span", { text: this.filename });
|
diffOptionsRow.createSpan({ text: this.filename });
|
||||||
|
|
||||||
const diffNavContainer = diffOptionsRow.createDiv("");
|
const diffNavContainer = diffOptionsRow.createDiv("");
|
||||||
diffNavContainer.addClass("diff-nav");
|
diffNavContainer.addClass("diff-nav");
|
||||||
@@ -122,7 +122,7 @@ export class ConflictResolveModal extends Modal {
|
|||||||
e.addClass("diff-nav-btn");
|
e.addClass("diff-nav-btn");
|
||||||
e.addEventListener("click", () => this.navigateDiff("next"));
|
e.addEventListener("click", () => this.navigateDiff("next"));
|
||||||
});
|
});
|
||||||
this.diffNavIndicator = diffNavContainer.createEl("span", { text: "\u2014" });
|
this.diffNavIndicator = diffNavContainer.createSpan({ text: "\u2014" });
|
||||||
this.diffNavIndicator.addClass("diff-nav-indicator");
|
this.diffNavIndicator.addClass("diff-nav-indicator");
|
||||||
|
|
||||||
this.diffView = contentEl.createDiv("");
|
this.diffView = contentEl.createDiv("");
|
||||||
|
|||||||
@@ -736,7 +736,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
value: `${order}`,
|
value: `${order}`,
|
||||||
cls: "sls-setting-tab",
|
cls: "sls-setting-tab",
|
||||||
} as DomElementInfo);
|
} as DomElementInfo);
|
||||||
el.createEl("div", {
|
el.createDiv({
|
||||||
cls: "sls-setting-menu-btn",
|
cls: "sls-setting-menu-btn",
|
||||||
text: icon,
|
text: icon,
|
||||||
title: title,
|
title: title,
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ export function panePatches(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElemen
|
|||||||
}
|
}
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
};
|
};
|
||||||
text.inputEl.before((dateEl = activeDocument.createElement("span")));
|
text.inputEl.before((dateEl = activeDocument.createSpan()));
|
||||||
text.inputEl.type = "datetime-local";
|
text.inputEl.type = "datetime-local";
|
||||||
if (this.editingSettings.maxMTimeForReflectEvents > 0) {
|
if (this.editingSettings.maxMTimeForReflectEvents > 0) {
|
||||||
const date = new Date(this.editingSettings.maxMTimeForReflectEvents);
|
const date = new Date(this.editingSettings.maxMTimeForReflectEvents);
|
||||||
|
|||||||
@@ -16,13 +16,7 @@ import type { PageFunctions } from "./SettingPane.ts";
|
|||||||
import InfoPanel from "./InfoPanel.svelte";
|
import InfoPanel from "./InfoPanel.svelte";
|
||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
import { SveltePanel } from "./SveltePanel.ts";
|
import { SveltePanel } from "./SveltePanel.ts";
|
||||||
import {
|
import { getE2EEConfigSummary } from "./settingUtils.ts";
|
||||||
getBucketConfigSummary,
|
|
||||||
getP2PConfigSummary,
|
|
||||||
getCouchDBConfigSummary,
|
|
||||||
getE2EEConfigSummary,
|
|
||||||
} from "./settingUtils.ts";
|
|
||||||
import { SETTING_KEY_P2P_DEVICE_NAME } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
|
||||||
import { SetupManager, UserMode } from "@/modules/features/SetupManager.ts";
|
import { SetupManager, UserMode } from "@/modules/features/SetupManager.ts";
|
||||||
import { OnDialogSettingsDefault, type AllSettings } from "./settingConstants.ts";
|
import { OnDialogSettingsDefault, type AllSettings } from "./settingConstants.ts";
|
||||||
import { activateRemoteConfiguration } from "@vrtmrz/livesync-commonlib/compat/serviceFeatures/remoteConfig";
|
import { activateRemoteConfiguration } from "@vrtmrz/livesync-commonlib/compat/serviceFeatures/remoteConfig";
|
||||||
@@ -43,15 +37,6 @@ function getSettingsFromEditingSettings(editingSettings: AllSettings): ObsidianL
|
|||||||
}
|
}
|
||||||
return workObj;
|
return workObj;
|
||||||
}
|
}
|
||||||
const toggleActiveSyncClass = (el: HTMLElement, isActive: () => boolean) => {
|
|
||||||
if (isActive()) {
|
|
||||||
el.addClass("active-pane");
|
|
||||||
} else {
|
|
||||||
el.removeClass("active-pane");
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
};
|
|
||||||
|
|
||||||
function createRemoteConfigurationId(): string {
|
function createRemoteConfigurationId(): string {
|
||||||
return `remote-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
return `remote-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
}
|
}
|
||||||
@@ -517,123 +502,6 @@ export function paneRemoteConfig(
|
|||||||
refreshList();
|
refreshList();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line no-constant-condition
|
|
||||||
if (false) {
|
|
||||||
const initialProps = {
|
|
||||||
info: getCouchDBConfigSummary(this.editingSettings),
|
|
||||||
};
|
|
||||||
const summaryWritable = writable(initialProps);
|
|
||||||
const updateSummary = () => {
|
|
||||||
summaryWritable.set({
|
|
||||||
info: getCouchDBConfigSummary(this.editingSettings),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
void addPanel(paneEl, $msg("obsidianLiveSyncSettingTab.titleCouchDB"), () => {}).then((paneEl) => {
|
|
||||||
new SveltePanel(InfoPanel, paneEl, summaryWritable);
|
|
||||||
const setupButton = new Setting(paneEl).setName("Configure Remote");
|
|
||||||
setupButton
|
|
||||||
.addButton((button) =>
|
|
||||||
button
|
|
||||||
.setButtonText("Configure")
|
|
||||||
.setCta()
|
|
||||||
.onClick(async () => {
|
|
||||||
const setupManager = this.core.getModule(SetupManager);
|
|
||||||
const originalSettings = getSettingsFromEditingSettings(this.editingSettings);
|
|
||||||
await setupManager.onCouchDBManualSetup(
|
|
||||||
UserMode.Update,
|
|
||||||
originalSettings,
|
|
||||||
this.editingSettings.remoteType === REMOTE_COUCHDB
|
|
||||||
);
|
|
||||||
|
|
||||||
updateSummary();
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.addOnUpdate(() =>
|
|
||||||
toggleActiveSyncClass(paneEl, () => this.editingSettings.remoteType === REMOTE_COUCHDB)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line no-constant-condition
|
|
||||||
if (false) {
|
|
||||||
const initialProps = {
|
|
||||||
info: getBucketConfigSummary(this.editingSettings),
|
|
||||||
};
|
|
||||||
const summaryWritable = writable(initialProps);
|
|
||||||
const updateSummary = () => {
|
|
||||||
summaryWritable.set({
|
|
||||||
info: getBucketConfigSummary(this.editingSettings),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
void addPanel(paneEl, $msg("obsidianLiveSyncSettingTab.titleMinioS3R2"), () => {}).then((paneEl) => {
|
|
||||||
new SveltePanel(InfoPanel, paneEl, summaryWritable);
|
|
||||||
const setupButton = new Setting(paneEl).setName("Configure Remote");
|
|
||||||
setupButton
|
|
||||||
.addButton((button) =>
|
|
||||||
button
|
|
||||||
.setButtonText("Configure")
|
|
||||||
.setCta()
|
|
||||||
.onClick(async () => {
|
|
||||||
const setupManager = this.core.getModule(SetupManager);
|
|
||||||
const originalSettings = getSettingsFromEditingSettings(this.editingSettings);
|
|
||||||
await setupManager.onBucketManualSetup(
|
|
||||||
UserMode.Update,
|
|
||||||
originalSettings,
|
|
||||||
this.editingSettings.remoteType === REMOTE_MINIO
|
|
||||||
);
|
|
||||||
//TODO
|
|
||||||
updateSummary();
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.addOnUpdate(() =>
|
|
||||||
toggleActiveSyncClass(paneEl, () => this.editingSettings.remoteType === REMOTE_MINIO)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line no-constant-condition
|
|
||||||
if (false) {
|
|
||||||
const getDevicePeerId = () => this.services.config.getSmallConfig(SETTING_KEY_P2P_DEVICE_NAME) || "";
|
|
||||||
const initialProps = {
|
|
||||||
info: getP2PConfigSummary(this.editingSettings, {
|
|
||||||
"Device Peer ID": getDevicePeerId(),
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
const summaryWritable = writable(initialProps);
|
|
||||||
const updateSummary = () => {
|
|
||||||
summaryWritable.set({
|
|
||||||
info: getP2PConfigSummary(this.editingSettings, {
|
|
||||||
"Device Peer ID": getDevicePeerId(),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
void addPanel(paneEl, "Peer-to-Peer Synchronisation", () => {}).then((paneEl) => {
|
|
||||||
new SveltePanel(InfoPanel, paneEl, summaryWritable);
|
|
||||||
const setupButton = new Setting(paneEl).setName("Configure Remote");
|
|
||||||
setupButton
|
|
||||||
.addButton((button) =>
|
|
||||||
button
|
|
||||||
.setButtonText("Configure")
|
|
||||||
.setCta()
|
|
||||||
.onClick(async () => {
|
|
||||||
const setupManager = this.core.getModule(SetupManager);
|
|
||||||
const originalSettings = getSettingsFromEditingSettings(this.editingSettings);
|
|
||||||
await setupManager.onP2PManualSetup(
|
|
||||||
UserMode.Update,
|
|
||||||
originalSettings,
|
|
||||||
this.editingSettings.remoteType === REMOTE_P2P
|
|
||||||
);
|
|
||||||
//TODO
|
|
||||||
updateSummary();
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.addOnUpdate(() =>
|
|
||||||
toggleActiveSyncClass(
|
|
||||||
paneEl,
|
|
||||||
() => this.editingSettings.remoteType === REMOTE_P2P || this.editingSettings.P2P_Enabled
|
|
||||||
)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// new Setting(paneEl)
|
// new Setting(paneEl)
|
||||||
// .setDesc("Generate ES256 Keypair for testing")
|
// .setDesc("Generate ES256 Keypair for testing")
|
||||||
// .addButton((button) =>
|
// .addButton((button) =>
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export const enum UserMode {
|
|||||||
/**
|
/**
|
||||||
* Update User Mode - for users who are updating configuration. May be `existing-user` as well, but possibly they want to treat it differently.
|
* Update User Mode - for users who are updating configuration. May be `existing-user` as well, but possibly they want to treat it differently.
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
|
// eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values -- Update is a semantic alias for the unknown setup mode.
|
||||||
Update = "unknown", // Alias for Unknown for better readability
|
Update = "unknown", // Alias for Unknown for better readability
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export class ObsidianConfirm<T extends ObsidianServiceContext = ObsidianServiceC
|
|||||||
askInPopup(key: string, dialogText: string, anchorCallback: (anchor: HTMLAnchorElement) => void) {
|
askInPopup(key: string, dialogText: string, anchorCallback: (anchor: HTMLAnchorElement) => void) {
|
||||||
const fragment = createFragment((doc) => {
|
const fragment = createFragment((doc) => {
|
||||||
const [beforeText, afterText] = dialogText.split("{HERE}", 2);
|
const [beforeText, afterText] = dialogText.split("{HERE}", 2);
|
||||||
doc.createEl("span", undefined, (a) => {
|
doc.createSpan(undefined, (a) => {
|
||||||
a.appendText(beforeText);
|
a.appendText(beforeText);
|
||||||
a.appendChild(
|
a.appendChild(
|
||||||
a.createEl("a", undefined, (anchor) => {
|
a.createEl("a", undefined, (anchor) => {
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import { getLanguage } from "@/deps";
|
import { getLanguage, requireApiVersion } from "@/deps";
|
||||||
import { createServiceFeature } from "@vrtmrz/livesync-commonlib/compat/interfaces/ServiceModule";
|
import { createServiceFeature } from "@vrtmrz/livesync-commonlib/compat/interfaces/ServiceModule";
|
||||||
import { SUPPORTED_I18N_LANGS, type I18N_LANGS } from "@vrtmrz/livesync-commonlib/compat/common/rosetta";
|
import { SUPPORTED_I18N_LANGS, type I18N_LANGS } from "@vrtmrz/livesync-commonlib/compat/common/rosetta";
|
||||||
import { $msg, __onMissingTranslation, setLang } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
|
import { $msg, __onMissingTranslation, setLang } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
|
||||||
|
|
||||||
function tryGetLanguage() {
|
function tryGetLanguage() {
|
||||||
try {
|
if (requireApiVersion("1.8.7")) {
|
||||||
// Note: 1.8.7+ is required. but it is 18, Feb., 2025. we want to fallback on earlier versions, so we catch the error here.
|
try {
|
||||||
// eslint-disable-next-line obsidianmd/no-unsupported-api
|
return getLanguage();
|
||||||
return getLanguage();
|
} catch (e) {
|
||||||
} catch (e) {
|
console.error("Failed to get Obsidian language, defaulting to 'en'", e);
|
||||||
console.error("Failed to get Obsidian language, defaulting to 'def'", e);
|
}
|
||||||
return "en";
|
|
||||||
}
|
}
|
||||||
|
return "en";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enableI18nFeature = createServiceFeature(async ({ services: { setting, API } }) => {
|
export const enableI18nFeature = createServiceFeature(async ({ services: { setting, API } }) => {
|
||||||
|
|||||||
@@ -41,22 +41,12 @@ export class ObsidianVaultAdapter implements IVaultAdapter<TFile, TFolder> {
|
|||||||
return await this.app.vault.rename(file, newPath);
|
return await this.app.vault.rename(file, newPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(file: TFile | TFolder, force = false): Promise<void> {
|
async delete(file: TFile | TFolder): Promise<void> {
|
||||||
if ("trashFile" in this.app.fileManager) {
|
return await this.app.fileManager.trashFile(file);
|
||||||
// eslint-disable-next-line obsidianmd/no-unsupported-api
|
|
||||||
return await this.app.fileManager.trashFile(file);
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line obsidianmd/prefer-file-manager-trash-file -- Fallback for older versions of Obsidian without trashFile support
|
|
||||||
return await this.app.vault.delete(file, force);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async trash(file: TFile | TFolder, force = false): Promise<void> {
|
async trash(file: TFile | TFolder): Promise<void> {
|
||||||
if ("trashFile" in this.app.fileManager) {
|
return await this.app.fileManager.trashFile(file);
|
||||||
// eslint-disable-next-line obsidianmd/no-unsupported-api
|
|
||||||
return await this.app.fileManager.trashFile(file);
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line obsidianmd/prefer-file-manager-trash-file -- Fallback for older versions of Obsidian without trashFile support
|
|
||||||
return await this.app.vault.trash(file, force);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
trigger(name: string, ...data: unknown[]): void {
|
trigger(name: string, ...data: unknown[]): void {
|
||||||
|
|||||||
Reference in new issue
Block a user