mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-05-14 11:31:17 +00:00
Compare commits
30 Commits
update_lib
...
0.25.61
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1859f5d2e | ||
|
|
785af8cb8f | ||
|
|
06e1f4aa4a | ||
|
|
767f22ce9c | ||
|
|
6a9bba702c | ||
|
|
de2397dc3f | ||
|
|
daaad9212e | ||
|
|
a6891374a1 | ||
|
|
b1cadf0549 | ||
|
|
95f40cc954 | ||
|
|
8deaf123d6 | ||
|
|
053813bffb | ||
|
|
cc7af03618 | ||
|
|
a130e3700e | ||
|
|
0549e901b2 | ||
|
|
e9afe06968 | ||
|
|
37715d4c9f | ||
|
|
106367fa41 | ||
|
|
538130aa91 | ||
|
|
c9d0357fec | ||
|
|
d05c76da36 | ||
|
|
d2eb6ecbaf | ||
|
|
25a6fde212 | ||
|
|
e8f8b680ef | ||
|
|
6c30f2b863 | ||
|
|
770d4af4a0 | ||
|
|
3b311248cb | ||
|
|
5772811a45 | ||
|
|
55529cd71e | ||
|
|
2e9b8b7b62 |
@@ -2,7 +2,6 @@
|
||||
|
||||
import esbuild from "esbuild";
|
||||
import process from "process";
|
||||
import builtins from "builtin-modules";
|
||||
import sveltePlugin from "esbuild-svelte";
|
||||
import { sveltePreprocess } from "svelte-preprocess";
|
||||
import fs from "node:fs";
|
||||
|
||||
@@ -1,103 +1,83 @@
|
||||
import typescriptEslint from "@typescript-eslint/eslint-plugin";
|
||||
import svelte from "eslint-plugin-svelte";
|
||||
import _import from "eslint-plugin-import";
|
||||
import { fixupPluginRules } from "@eslint/compat";
|
||||
import tsParser from "@typescript-eslint/parser";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import js from "@eslint/js";
|
||||
import { FlatCompat } from "@eslint/eslintrc";
|
||||
import obsidianmd from "eslint-plugin-obsidianmd";
|
||||
import globals from "globals";
|
||||
import { defineConfig, globalIgnores } from "eslint/config";
|
||||
import * as sveltePlugin from "eslint-plugin-svelte";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
recommendedConfig: js.configs.recommended,
|
||||
allConfig: js.configs.all,
|
||||
});
|
||||
|
||||
export default [
|
||||
export default defineConfig([
|
||||
globalIgnores([
|
||||
"**/node_modules/*",
|
||||
"**/jest.config.js",
|
||||
"src/lib/coverage",
|
||||
"src/lib/browsertest",
|
||||
"**/test.ts",
|
||||
"**/tests.ts",
|
||||
"**/**test.ts",
|
||||
"**/**.test.ts",
|
||||
"**/*.unit.spec.ts",
|
||||
"**/esbuild.*.mjs",
|
||||
"**/terser.*.mjs",
|
||||
"**/node_modules",
|
||||
"**/build",
|
||||
"**/.eslintrc.js.bak",
|
||||
"src/lib/src/patches/pouchdb-utils",
|
||||
"**/esbuild.config.mjs",
|
||||
"**/rollup.config.js",
|
||||
"modules/octagonal-wheels/rollup.config.js",
|
||||
"modules/octagonal-wheels/dist/**/*",
|
||||
"src/lib/test",
|
||||
"src/lib/_tools",
|
||||
"src/lib/src/cli",
|
||||
"**/main.js",
|
||||
"src/apps/**/*",
|
||||
".prettierrc.*.mjs",
|
||||
".prettierrc.mjs",
|
||||
"*.config.mjs",
|
||||
"src/apps/**/*",
|
||||
"src/lib/src/services/implements/browser/**",
|
||||
"src/lib/src/services/implements/headless/**",
|
||||
"src/lib/src/API",
|
||||
]),
|
||||
...sveltePlugin.configs["flat/base"],
|
||||
...obsidianmd.configs.recommended,
|
||||
{
|
||||
ignores: [
|
||||
"**/node_modules/*",
|
||||
"**/jest.config.js",
|
||||
"src/lib/coverage",
|
||||
"src/lib/browsertest",
|
||||
"**/test.ts",
|
||||
"**/tests.ts",
|
||||
"**/**test.ts",
|
||||
"**/**.test.ts",
|
||||
"**/esbuild.*.mjs",
|
||||
"**/terser.*.mjs",
|
||||
"**/node_modules",
|
||||
"**/build",
|
||||
"**/.eslintrc.js.bak",
|
||||
"src/lib/src/patches/pouchdb-utils",
|
||||
"**/esbuild.config.mjs",
|
||||
"**/rollup.config.js",
|
||||
"modules/octagonal-wheels/rollup.config.js",
|
||||
"modules/octagonal-wheels/dist/**/*",
|
||||
"src/lib/test",
|
||||
"src/lib/_tools",
|
||||
"src/lib/src/cli",
|
||||
"**/main.js",
|
||||
"src/apps/**/*",
|
||||
".prettierrc.*.mjs",
|
||||
".prettierrc.mjs",
|
||||
"*.config.mjs"
|
||||
],
|
||||
},
|
||||
...compat.extends(
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
),
|
||||
{
|
||||
plugins: {
|
||||
"@typescript-eslint": typescriptEslint,
|
||||
svelte,
|
||||
import: fixupPluginRules(_import),
|
||||
},
|
||||
|
||||
files: ["**/*.ts"],
|
||||
languageOptions: {
|
||||
globals: { ...globals.browser },
|
||||
parser: tsParser,
|
||||
ecmaVersion: 5,
|
||||
sourceType: "module",
|
||||
|
||||
parserOptions: {
|
||||
project: ["tsconfig.json"],
|
||||
project: "./tsconfig.json",
|
||||
},
|
||||
},
|
||||
|
||||
rules: {
|
||||
"no-unused-vars": "off",
|
||||
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
args: "none",
|
||||
},
|
||||
],
|
||||
|
||||
"@typescript-eslint/no-unused-vars": ["error", { args: "none" }],
|
||||
"no-unused-labels": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"no-prototype-builtins": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"require-await": "error",
|
||||
"obsidianmd/rule-custom-message": "off", // Temporary
|
||||
"obsidianmd/ui/sentence-case": "off", // Temporary
|
||||
"@typescript-eslint/require-await": "warn",
|
||||
"@typescript-eslint/no-misused-promises": "warn",
|
||||
"@typescript-eslint/no-floating-promises": "warn",
|
||||
"no-async-promise-executor": "warn",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
||||
|
||||
"no-constant-condition": [
|
||||
"error",
|
||||
{
|
||||
checkLoops: false,
|
||||
},
|
||||
],
|
||||
"no-constant-condition": ["error", { checkLoops: false }],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
{
|
||||
files: ["**/*.svelte"],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
parser: tsParser,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
"no-unused-vars": ["error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],
|
||||
"obsidianmd/no-plugin-as-component": "off", // Temporary
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"id": "obsidian-livesync",
|
||||
"name": "Self-hosted LiveSync",
|
||||
"version": "0.25.60",
|
||||
"minAppVersion": "0.9.12",
|
||||
"version": "0.25.61",
|
||||
"minAppVersion": "1.7.2",
|
||||
"description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
|
||||
"author": "vorotamoroz",
|
||||
"authorUrl": "https://github.com/vrtmrz",
|
||||
|
||||
1355
package-lock.json
generated
1355
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
13
package.json
13
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "obsidian-livesync",
|
||||
"version": "0.25.60",
|
||||
"version": "0.25.61",
|
||||
"description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
|
||||
"main": "main.js",
|
||||
"type": "module",
|
||||
@@ -61,8 +61,6 @@
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@chialab/esbuild-plugin-worker": "^0.19.0",
|
||||
"@eslint/compat": "^2.0.2",
|
||||
"@eslint/eslintrc": "^3.3.4",
|
||||
"@eslint/js": "^9.39.3",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
||||
"@tsconfig/svelte": "^5.0.8",
|
||||
@@ -84,18 +82,15 @@
|
||||
"@vitest/browser": "^4.1.1",
|
||||
"@vitest/browser-playwright": "^4.1.1",
|
||||
"@vitest/coverage-v8": "^4.1.1",
|
||||
"builtin-modules": "5.0.0",
|
||||
"dotenv": "^17.3.1",
|
||||
"dotenv-cli": "^11.0.0",
|
||||
"esbuild": "0.25.0",
|
||||
"esbuild-plugin-inline-worker": "^0.1.1",
|
||||
"esbuild-svelte": "^0.9.4",
|
||||
"eslint": "^9.39.3",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-plugin-obsidianmd": "^0.3.0",
|
||||
"eslint-plugin-svelte": "^3.15.0",
|
||||
"events": "^3.3.0",
|
||||
"glob": "^13.0.6",
|
||||
"obsidian": "^1.12.3",
|
||||
"globals": "^14.0.0",
|
||||
"playwright": "^1.58.2",
|
||||
"postcss": "^8.5.6",
|
||||
"postcss-load-config": "^6.0.1",
|
||||
@@ -116,6 +111,7 @@
|
||||
"svelte-check": "^4.4.3",
|
||||
"svelte-preprocess": "^6.0.3",
|
||||
"terser": "^5.39.0",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"transform-pouch": "^2.0.0",
|
||||
"tslib": "^2.8.1",
|
||||
"tsx": "^4.21.0",
|
||||
@@ -136,6 +132,7 @@
|
||||
"@trystero-p2p/nostr": "^0.23.0",
|
||||
"chokidar": "^4.0.0",
|
||||
"commander": "^14.0.3",
|
||||
"obsidian": "^1.12.3",
|
||||
"diff-match-patch": "^1.0.5",
|
||||
"fflate": "^0.8.2",
|
||||
"idb": "^8.0.3",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { LOG_LEVEL_INFO } from "octagonal-wheels/common/logger";
|
||||
import type PouchDB from "pouchdb-core";
|
||||
import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase";
|
||||
import type { HasSettings, ObsidianLiveSyncSettings, EntryDoc } from "./lib/src/common/types";
|
||||
import { __$checkInstanceBinding } from "./lib/src/dev/checks";
|
||||
@@ -123,7 +124,7 @@ export class LiveSyncBaseCore<
|
||||
for (const module of this.modules) {
|
||||
if (module.constructor === constructor) return module as T;
|
||||
}
|
||||
throw new Error(`Module ${constructor} not found or not loaded.`);
|
||||
throw new Error(`Module ${constructor.name} not found or not loaded.`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,8 +161,10 @@ export class LiveSyncBaseCore<
|
||||
module.onBindFunction(this, this.services);
|
||||
__$checkInstanceBinding(module); // Check if all functions are properly bound, and log warnings if not.
|
||||
} else {
|
||||
// module should not be never.
|
||||
const moduleName = (module as unknown)?.constructor?.name ?? "unknown";
|
||||
this.services.API.addLog(
|
||||
`Module ${(module as any)?.constructor?.name ?? "unknown"} does not have onBindFunction, skipping binding.`,
|
||||
`Module ${moduleName} does not have onBindFunction, skipping binding.`,
|
||||
LOG_LEVEL_INFO
|
||||
);
|
||||
}
|
||||
|
||||
@@ -257,12 +257,12 @@ describe("daemon command", () => {
|
||||
// failure 1: 30000*2=60000, failure 2: 30000*4=120000,
|
||||
// failure 3: 30000*8=240000, failure 4: 30000*16=480000→capped, 5→cap, 6→cap
|
||||
const expectedIntervals = [
|
||||
baseMs * 2, // after failure 1: 60000
|
||||
baseMs * 4, // after failure 2: 120000
|
||||
baseMs * 8, // after failure 3: 240000
|
||||
300_000, // after failure 4 (would be 480000, capped)
|
||||
300_000, // after failure 5 (cap)
|
||||
300_000, // after failure 6 (cap)
|
||||
baseMs * 2, // after failure 1: 60000
|
||||
baseMs * 4, // after failure 2: 120000
|
||||
baseMs * 8, // after failure 3: 240000
|
||||
300_000, // after failure 4 (would be 480000, capped)
|
||||
300_000, // after failure 5 (cap)
|
||||
300_000, // after failure 6 (cap)
|
||||
];
|
||||
|
||||
for (const expected of expectedIntervals) {
|
||||
|
||||
@@ -43,10 +43,13 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext
|
||||
|
||||
// 3. Re-enable sync.
|
||||
const restoreSyncSettings = async () => {
|
||||
await core.services.setting.applyPartial({
|
||||
...context.originalSyncSettings,
|
||||
suspendFileWatching: false,
|
||||
}, true);
|
||||
await core.services.setting.applyPartial(
|
||||
{
|
||||
...context.originalSyncSettings,
|
||||
suspendFileWatching: false,
|
||||
},
|
||||
true
|
||||
);
|
||||
// applySettings fires the full lifecycle: onSuspending → onResumed.
|
||||
// ModuleReplicatorCouchDB starts continuous replication on onResumed
|
||||
// via fireAndForget.
|
||||
@@ -54,10 +57,13 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext
|
||||
// Lifecycle events (onSuspending) may re-enable suspension flags.
|
||||
// Clear them explicitly after the lifecycle completes. applyPartial
|
||||
// with true is a direct store write — it does not re-trigger lifecycle.
|
||||
await core.services.setting.applyPartial({
|
||||
suspendFileWatching: false,
|
||||
suspendParseReplicationResult: false,
|
||||
}, true);
|
||||
await core.services.setting.applyPartial(
|
||||
{
|
||||
suspendFileWatching: false,
|
||||
suspendParseReplicationResult: false,
|
||||
},
|
||||
true
|
||||
);
|
||||
};
|
||||
if (options.interval) {
|
||||
log(`Polling mode: syncing every ${options.interval}s`);
|
||||
@@ -80,7 +86,9 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext
|
||||
currentIntervalMs = Math.min(baseIntervalMs * Math.pow(2, consecutiveFailures), maxIntervalMs);
|
||||
console.error(`[Daemon] Poll error (${consecutiveFailures} consecutive):`, err);
|
||||
if (consecutiveFailures >= 5) {
|
||||
console.error(`[Daemon] Warning: ${consecutiveFailures} consecutive failures, backing off to ${Math.round(currentIntervalMs / 1000)}s`);
|
||||
console.error(
|
||||
`[Daemon] Warning: ${consecutiveFailures} consecutive failures, backing off to ${Math.round(currentIntervalMs / 1000)}s`
|
||||
);
|
||||
}
|
||||
}
|
||||
pollTimer = setTimeout(poll, currentIntervalMs);
|
||||
@@ -99,9 +107,11 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext
|
||||
log("LiveSync active");
|
||||
const currentSettings = core.services.setting.currentSettings();
|
||||
if (!currentSettings.liveSync && !currentSettings.syncOnStart) {
|
||||
console.error("[Daemon] Warning: liveSync and syncOnStart are both disabled in settings. " +
|
||||
"No sync will occur. Set liveSync=true in your settings file for continuous sync, " +
|
||||
"or use --interval for polling mode.");
|
||||
console.error(
|
||||
"[Daemon] Warning: liveSync and syncOnStart are both disabled in settings. " +
|
||||
"No sync will occur. Set liveSync=true in your settings file for continuous sync, " +
|
||||
"or use --interval for polling mode."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,16 @@ export interface CLICommandContext {
|
||||
databasePath: string;
|
||||
core: LiveSyncBaseCore<ServiceContext, any>;
|
||||
settingsPath: string;
|
||||
originalSyncSettings: Pick<ObsidianLiveSyncSettings, "liveSync" | "syncOnStart" | "periodicReplication" | "syncOnSave" | "syncOnEditorSave" | "syncOnFileOpen" | "syncAfterMerge">;
|
||||
originalSyncSettings: Pick<
|
||||
ObsidianLiveSyncSettings,
|
||||
| "liveSync"
|
||||
| "syncOnStart"
|
||||
| "periodicReplication"
|
||||
| "syncOnSave"
|
||||
| "syncOnEditorSave"
|
||||
| "syncOnFileOpen"
|
||||
| "syncAfterMerge"
|
||||
>;
|
||||
}
|
||||
|
||||
export const VALID_COMMANDS = new Set([
|
||||
|
||||
@@ -280,16 +280,13 @@ export async function main() {
|
||||
// chokidar's ignored option is populated when beginWatch() fires during onLoad().
|
||||
const watchEnabled = options.command === "daemon";
|
||||
const vaultPath =
|
||||
options.command === "mirror" && options.commandArgs[0]
|
||||
? path.resolve(options.commandArgs[0])
|
||||
: databasePath;
|
||||
options.command === "mirror" && options.commandArgs[0] ? path.resolve(options.commandArgs[0]) : databasePath;
|
||||
let ignoreRules: IgnoreRules | undefined;
|
||||
if (options.command === "daemon" || options.command === "mirror") {
|
||||
ignoreRules = new IgnoreRules(vaultPath);
|
||||
await ignoreRules.load();
|
||||
}
|
||||
|
||||
|
||||
// Create service context and hub
|
||||
const context = new NodeServiceContext(databasePath);
|
||||
const serviceHubInstance = new NodeServiceHub<NodeServiceContext>(databasePath, context);
|
||||
|
||||
@@ -97,7 +97,11 @@ class CLIConverterAdapter implements IStorageEventConverterAdapter<NodeFile> {
|
||||
class CLIWatchAdapter implements IStorageEventWatchAdapter {
|
||||
private _watcher: FSWatcher | undefined;
|
||||
|
||||
constructor(private basePath: string, private ignoreRules?: IgnoreRules, private watchEnabled: boolean = false) {}
|
||||
constructor(
|
||||
private basePath: string,
|
||||
private ignoreRules?: IgnoreRules,
|
||||
private watchEnabled: boolean = false
|
||||
) {}
|
||||
|
||||
private _toNodeFile(filePath: string, stats: Stats | undefined): NodeFile {
|
||||
return {
|
||||
|
||||
@@ -60,10 +60,7 @@ describe("CLIStorageEventManagerAdapter", () => {
|
||||
await adapter.watch.beginWatch(handlers);
|
||||
|
||||
expect(chokidar.watch).toHaveBeenCalledTimes(1);
|
||||
expect(chokidar.watch).toHaveBeenCalledWith(
|
||||
"/base",
|
||||
expect.objectContaining({ ignoreInitial: true })
|
||||
);
|
||||
expect(chokidar.watch).toHaveBeenCalledWith("/base", expect.objectContaining({ ignoreInitial: true }));
|
||||
});
|
||||
|
||||
it("add event produces NodeFile with correct relative path via onCreate", async () => {
|
||||
|
||||
@@ -25,7 +25,7 @@ export function initialiseServiceModulesCLI(
|
||||
core: LiveSyncBaseCore<ServiceContext, any>,
|
||||
services: InjectableServiceHub<ServiceContext>,
|
||||
ignoreRules?: IgnoreRules,
|
||||
watchEnabled: boolean = false,
|
||||
watchEnabled: boolean = false
|
||||
): ServiceModules {
|
||||
const storageAccessManager = new StorageAccessManager();
|
||||
|
||||
@@ -39,13 +39,19 @@ export function initialiseServiceModulesCLI(
|
||||
});
|
||||
|
||||
// CLI-specific storage event manager
|
||||
const storageEventManager = new StorageEventManagerCLI(basePath, core, {
|
||||
fileProcessing: services.fileProcessing,
|
||||
setting: services.setting,
|
||||
vaultService: services.vault,
|
||||
storageAccessManager: storageAccessManager,
|
||||
APIService: services.API,
|
||||
}, ignoreRules, watchEnabled);
|
||||
const storageEventManager = new StorageEventManagerCLI(
|
||||
basePath,
|
||||
core,
|
||||
{
|
||||
fileProcessing: services.fileProcessing,
|
||||
setting: services.setting,
|
||||
vaultService: services.vault,
|
||||
storageAccessManager: storageAccessManager,
|
||||
APIService: services.API,
|
||||
},
|
||||
ignoreRules,
|
||||
watchEnabled
|
||||
);
|
||||
|
||||
// Close the file watcher during graceful shutdown so the process can exit cleanly.
|
||||
services.appLifecycle.onUnload.addHandler(async () => {
|
||||
|
||||
@@ -55,7 +55,9 @@ export class IgnoreRules {
|
||||
continue;
|
||||
}
|
||||
if (trimmed.startsWith("import:")) {
|
||||
console.error(`[IgnoreRules] Warning: unrecognised directive '${trimmed}' — only 'import: .gitignore' is supported`);
|
||||
console.error(
|
||||
`[IgnoreRules] Warning: unrecognised directive '${trimmed}' — only 'import: .gitignore' is supported`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
this._addPattern(trimmed);
|
||||
@@ -105,7 +107,7 @@ export class IgnoreRules {
|
||||
if (raw.startsWith("!")) {
|
||||
throw new Error(
|
||||
`[IgnoreRules] Negation pattern '${raw}' is not supported. ` +
|
||||
`Remove it from .livesync/ignore or use a separate include/exclude file.`
|
||||
`Remove it from .livesync/ignore or use a separate include/exclude file.`
|
||||
);
|
||||
}
|
||||
this.patterns.push(this._normalisePattern(raw));
|
||||
|
||||
@@ -122,10 +122,7 @@ describe("IgnoreRules", () => {
|
||||
describe("load() with comments and blank lines", () => {
|
||||
it("skips # comment lines and blank lines", async () => {
|
||||
const vaultPath = await createVault();
|
||||
await writeIgnoreFile(
|
||||
vaultPath,
|
||||
"# This is a comment\n\n \n*.tmp\n# another comment\nbuild/\n"
|
||||
);
|
||||
await writeIgnoreFile(vaultPath, "# This is a comment\n\n \n*.tmp\n# another comment\nbuild/\n");
|
||||
const rules = new IgnoreRules(vaultPath);
|
||||
await rules.load();
|
||||
expect(rules.shouldIgnore("scratch.tmp")).toBe(true);
|
||||
|
||||
@@ -29,7 +29,8 @@ export async function runScenario(remoteType: RemoteType, encrypt: boolean): Pro
|
||||
const dbPrefix = remoteType === "COUCHDB" ? requireEnv("COUCHDB_DBNAME", "dbname") : "";
|
||||
const dbname = remoteType === "COUCHDB" ? `${dbPrefix}-${dbSuffix}` : "";
|
||||
|
||||
const minioEndpoint = remoteType === "MINIO" ? requireEnv("MINIO_ENDPOINT", "minioEndpoint").replace(/\/$/, "") : "";
|
||||
const minioEndpoint =
|
||||
remoteType === "MINIO" ? requireEnv("MINIO_ENDPOINT", "minioEndpoint").replace(/\/$/, "") : "";
|
||||
const minioAccessKey = remoteType === "MINIO" ? requireEnv("MINIO_ACCESS_KEY", "accessKey") : "";
|
||||
const minioSecretKey = remoteType === "MINIO" ? requireEnv("MINIO_SECRET_KEY", "secretKey") : "";
|
||||
const minioBucketBase = remoteType === "MINIO" ? requireEnv("MINIO_BUCKET_NAME", "bucketName") : "";
|
||||
|
||||
@@ -47,7 +47,8 @@ function injectBanner(): import("vite").Plugin {
|
||||
// Insert after the shebang line if present, otherwise at the top.
|
||||
if (chunk.code.startsWith("#!")) {
|
||||
const newline = chunk.code.indexOf("\n");
|
||||
chunk.code = chunk.code.slice(0, newline + 1) + fileReaderPolyfillBanner + chunk.code.slice(newline + 1);
|
||||
chunk.code =
|
||||
chunk.code.slice(0, newline + 1) + fileReaderPolyfillBanner + chunk.code.slice(newline + 1);
|
||||
} else {
|
||||
chunk.code = fileReaderPolyfillBanner + chunk.code;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ async function renderHistoryList(): Promise<VaultHistoryItem[]> {
|
||||
|
||||
const [items, lastUsedId] = await Promise.all([historyStore.getVaultHistory(), historyStore.getLastUsedVaultId()]);
|
||||
|
||||
listEl.innerHTML = "";
|
||||
listEl.replaceChildren();
|
||||
emptyEl.classList.toggle("is-hidden", items.length > 0);
|
||||
|
||||
for (const item of items) {
|
||||
|
||||
2
src/lib
2
src/lib
Submodule src/lib updated: adcfe42522...f6a6c2dff0
@@ -1,5 +1,6 @@
|
||||
import { Notice, Plugin, type App, type PluginManifest } from "./deps";
|
||||
|
||||
import { getLanguage, Notice, Plugin, type App, type PluginManifest } from "./deps";
|
||||
import { setGetLanguage } from "./lib/src/common/coreEnvFunctions.ts";
|
||||
setGetLanguage(getLanguage);
|
||||
import { LiveSyncCommands } from "./features/LiveSyncCommands.ts";
|
||||
import { HiddenFileSync } from "./features/HiddenFileSync/CmdHiddenFileSync.ts";
|
||||
import { ConfigSync } from "./features/ConfigSync/CmdConfigSync.ts";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { TFile, Modal, App, DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch } from "../../../deps.ts";
|
||||
import { getPathFromTFile, isValidPath } from "../../../common/utils.ts";
|
||||
import { decodeBinary, escapeStringToHTML, readString } from "../../../lib/src/string_and_binary/convert.ts";
|
||||
import { decodeBinary, readString } from "../../../lib/src/string_and_binary/convert.ts";
|
||||
import ObsidianLiveSyncPlugin from "../../../main.ts";
|
||||
import {
|
||||
type DocumentID,
|
||||
@@ -145,22 +145,66 @@ export class DocumentHistoryModal extends Modal {
|
||||
return v;
|
||||
}
|
||||
|
||||
prepareContentView(usePreformatted = true) {
|
||||
this.contentView.empty();
|
||||
this.contentView.toggleClass("op-pre", usePreformatted);
|
||||
}
|
||||
|
||||
appendTextDiff(diff: [number, string][]) {
|
||||
for (const [operation, text] of diff) {
|
||||
if (operation == DIFF_DELETE) {
|
||||
this.contentView.createSpan({ text, cls: "history-deleted" });
|
||||
} else if (operation == DIFF_EQUAL) {
|
||||
this.contentView.createSpan({ text, cls: "history-normal" });
|
||||
} else if (operation == DIFF_INSERT) {
|
||||
this.contentView.createSpan({ text, cls: "history-added" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
appendImageDiff(baseSrc: string, overlaySrc?: string) {
|
||||
const wrap = this.contentView.createDiv({ cls: "ls-imgdiff-wrap" });
|
||||
const overlay = wrap.createDiv({ cls: "overlay" });
|
||||
overlay.createEl("img", { cls: "img-base" }, (img) => {
|
||||
img.src = baseSrc;
|
||||
});
|
||||
if (overlaySrc) {
|
||||
overlay.createEl("img", { cls: "img-overlay" }, (img) => {
|
||||
img.src = overlaySrc;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
appendDeletedNotice(usePreformatted = true) {
|
||||
const notice = "(At this revision, the file has been deleted)";
|
||||
if (usePreformatted) {
|
||||
this.contentView.appendText(`${notice}\n`);
|
||||
} else {
|
||||
this.contentView.createDiv({ text: notice });
|
||||
}
|
||||
}
|
||||
|
||||
async showExactRev(rev: string) {
|
||||
const db = this.core.localDatabase;
|
||||
const w = await db.getDBEntry(this.file, { rev: rev }, false, false, true);
|
||||
this.currentText = "";
|
||||
this.currentDeleted = false;
|
||||
this.prepareContentView();
|
||||
if (w === false) {
|
||||
this.currentDeleted = true;
|
||||
this.info.innerHTML = "";
|
||||
this.contentView.innerHTML = `Could not read this revision<br>(${rev})`;
|
||||
this.info.empty();
|
||||
this.contentView.appendText("Could not read this revision");
|
||||
this.contentView.createEl("br");
|
||||
this.contentView.appendText(`(${rev})`);
|
||||
} else {
|
||||
this.currentDoc = w;
|
||||
this.info.innerHTML = `Modified:${new Date(w.mtime).toLocaleString()}`;
|
||||
let result = undefined;
|
||||
this.info.setText(`Modified:${new Date(w.mtime).toLocaleString()}`);
|
||||
const w1data = readDocument(w);
|
||||
this.currentDeleted = !!w.deleted;
|
||||
// this.currentText = w1data;
|
||||
if (typeof w1data == "string") {
|
||||
this.currentText = w1data;
|
||||
}
|
||||
let rendered = false;
|
||||
if (this.showDiff) {
|
||||
const prevRevIdx = this.revs_info.length - 1 - ((this.range.value as any) / 1 - 1);
|
||||
if (prevRevIdx >= 0 && prevRevIdx < this.revs_info.length) {
|
||||
@@ -168,58 +212,55 @@ export class DocumentHistoryModal extends Modal {
|
||||
const w2 = await db.getDBEntry(this.file, { rev: oldRev }, false, false, true);
|
||||
if (w2 != false) {
|
||||
if (typeof w1data == "string") {
|
||||
result = "";
|
||||
const dmp = new diff_match_patch();
|
||||
const w2data = readDocument(w2) as string;
|
||||
const diff = dmp.diff_main(w2data, w1data);
|
||||
dmp.diff_cleanupSemantic(diff);
|
||||
for (const v of diff) {
|
||||
const x1 = v[0];
|
||||
const x2 = v[1];
|
||||
if (x1 == DIFF_DELETE) {
|
||||
result += "<span class='history-deleted'>" + escapeStringToHTML(x2) + "</span>";
|
||||
} else if (x1 == DIFF_EQUAL) {
|
||||
result += "<span class='history-normal'>" + escapeStringToHTML(x2) + "</span>";
|
||||
} else if (x1 == DIFF_INSERT) {
|
||||
result += "<span class='history-added'>" + escapeStringToHTML(x2) + "</span>";
|
||||
const w2data = readDocument(w2);
|
||||
if (typeof w2data == "string") {
|
||||
const dmp = new diff_match_patch();
|
||||
const diff = dmp.diff_main(w2data, w1data);
|
||||
dmp.diff_cleanupSemantic(diff);
|
||||
if (this.currentDeleted) {
|
||||
this.appendDeletedNotice();
|
||||
}
|
||||
this.appendTextDiff(diff);
|
||||
rendered = true;
|
||||
}
|
||||
result = result.replace(/\n/g, "<br>");
|
||||
} else if (isImage(this.file)) {
|
||||
const src = this.generateBlobURL("base", w1data);
|
||||
const overlay = this.generateBlobURL(
|
||||
"overlay",
|
||||
readDocument(w2) as Uint8Array<ArrayBuffer>
|
||||
);
|
||||
result = `<div class='ls-imgdiff-wrap'>
|
||||
<div class='overlay'>
|
||||
<img class='img-base' src="${src}">
|
||||
<img class='img-overlay' src='${overlay}'>
|
||||
</div>
|
||||
</div>`;
|
||||
this.contentView.removeClass("op-pre");
|
||||
this.prepareContentView(false);
|
||||
if (this.currentDeleted) {
|
||||
this.appendDeletedNotice(false);
|
||||
}
|
||||
this.appendImageDiff(src, overlay);
|
||||
rendered = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result == undefined) {
|
||||
if (!rendered) {
|
||||
if (typeof w1data != "string") {
|
||||
if (isImage(this.file)) {
|
||||
const src = this.generateBlobURL("base", w1data);
|
||||
result = `<div class='ls-imgdiff-wrap'>
|
||||
<div class='overlay'>
|
||||
<img class='img-base' src="${src}">
|
||||
</div>
|
||||
</div>`;
|
||||
this.contentView.removeClass("op-pre");
|
||||
this.prepareContentView(false);
|
||||
if (this.currentDeleted) {
|
||||
this.appendDeletedNotice(false);
|
||||
}
|
||||
this.appendImageDiff(src);
|
||||
} else {
|
||||
if (this.currentDeleted) {
|
||||
this.appendDeletedNotice();
|
||||
}
|
||||
this.contentView.appendText("Binary file");
|
||||
}
|
||||
} else {
|
||||
result = escapeStringToHTML(w1data);
|
||||
if (this.currentDeleted) {
|
||||
this.appendDeletedNotice();
|
||||
}
|
||||
this.contentView.appendText(w1data);
|
||||
}
|
||||
}
|
||||
if (result == undefined) result = typeof w1data == "string" ? escapeStringToHTML(w1data) : "Binary file";
|
||||
this.contentView.innerHTML =
|
||||
(this.currentDeleted ? "(At this revision, the file has been deleted)\n" : "") + result;
|
||||
}
|
||||
// Reset diff navigation after content changes
|
||||
this.resetDiffNavigation();
|
||||
@@ -245,8 +286,7 @@ export class DocumentHistoryModal extends Modal {
|
||||
if (direction === "next") {
|
||||
this.currentDiffIndex = (this.currentDiffIndex + 1) % diffElements.length;
|
||||
} else {
|
||||
this.currentDiffIndex =
|
||||
this.currentDiffIndex <= 0 ? diffElements.length - 1 : this.currentDiffIndex - 1;
|
||||
this.currentDiffIndex = this.currentDiffIndex <= 0 ? diffElements.length - 1 : this.currentDiffIndex - 1;
|
||||
}
|
||||
|
||||
const target = diffElements[this.currentDiffIndex];
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { App, Modal } from "../../../deps.ts";
|
||||
import { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT } from "diff-match-patch";
|
||||
import { CANCELLED, LEAVE_TO_SUBSEQUENT, type diff_result } from "../../../lib/src/common/types.ts";
|
||||
import { escapeStringToHTML } from "../../../lib/src/string_and_binary/convert.ts";
|
||||
import { delay } from "../../../lib/src/common/utils.ts";
|
||||
import { eventHub } from "../../../common/events.ts";
|
||||
import { globalSlipBoard } from "../../../lib/src/bureau/bureau.ts";
|
||||
@@ -44,6 +43,25 @@ export class ConflictResolveModal extends Modal {
|
||||
// sendValue("close-resolve-conflict:" + this.filename, false);
|
||||
}
|
||||
|
||||
appendDiffFragment(container: HTMLDivElement, text: string, cls: string) {
|
||||
const lines = text.split("\n");
|
||||
lines.forEach((line, index) => {
|
||||
const span = container.createSpan({ cls });
|
||||
span.textContent = line;
|
||||
if (index < lines.length - 1) {
|
||||
container.createSpan({ cls: "ls-mark-cr" });
|
||||
container.createEl("br");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
appendVersionInfo(container: HTMLDivElement, cls: string, name: string, date: string) {
|
||||
const line = container.createSpan({ cls });
|
||||
line.createSpan({ text: name, cls: "conflict-dev-name" });
|
||||
line.appendText(`: ${date}`);
|
||||
container.createEl("br");
|
||||
}
|
||||
|
||||
override onOpen() {
|
||||
const { contentEl } = this;
|
||||
// Send cancel signal for the previous merge dialogue
|
||||
@@ -64,25 +82,21 @@ export class ConflictResolveModal extends Modal {
|
||||
const div = contentEl.createDiv("");
|
||||
div.addClass("op-scrollable");
|
||||
div.addClass("ls-dialog");
|
||||
let diff = "";
|
||||
let diffLength = 0;
|
||||
for (const v of this.result.diff) {
|
||||
const x1 = v[0];
|
||||
const x2 = v[1];
|
||||
diffLength += x2.length;
|
||||
if (diffLength > 100 * 1024) {
|
||||
continue;
|
||||
}
|
||||
if (x1 == DIFF_DELETE) {
|
||||
diff +=
|
||||
"<span class='deleted'>" +
|
||||
escapeStringToHTML(x2).replace(/\n/g, "<span class='ls-mark-cr'></span>\n") +
|
||||
"</span>";
|
||||
this.appendDiffFragment(div, x2, "deleted");
|
||||
div.createEl("span", { text: x2, cls: "deleted normal conflict-dev-name" });
|
||||
} else if (x1 == DIFF_EQUAL) {
|
||||
diff +=
|
||||
"<span class='normal'>" +
|
||||
escapeStringToHTML(x2).replace(/\n/g, "<span class='ls-mark-cr'></span>\n") +
|
||||
"</span>";
|
||||
this.appendDiffFragment(div, x2, "normal");
|
||||
} else if (x1 == DIFF_INSERT) {
|
||||
diff +=
|
||||
"<span class='added'>" +
|
||||
escapeStringToHTML(x2).replace(/\n/g, "<span class='ls-mark-cr'></span>\n") +
|
||||
"</span>";
|
||||
this.appendDiffFragment(div, x2, "added");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,8 +106,8 @@ export class ConflictResolveModal extends Modal {
|
||||
new Date(this.result.left.mtime).toLocaleString() + (this.result.left.deleted ? " (Deleted)" : "");
|
||||
const date2 =
|
||||
new Date(this.result.right.mtime).toLocaleString() + (this.result.right.deleted ? " (Deleted)" : "");
|
||||
div2.innerHTML = `<span class='deleted'><span class='conflict-dev-name'>${this.localName}</span>: ${date1}</span><br>
|
||||
<span class='added'><span class='conflict-dev-name'>${this.remoteName}</span>: ${date2}</span><br>`;
|
||||
this.appendVersionInfo(div2, "deleted", this.localName, date1);
|
||||
this.appendVersionInfo(div2, "added", this.remoteName, date2);
|
||||
contentEl.createEl("button", { text: `Use ${this.localName}` }, (e) =>
|
||||
e.addEventListener("click", () => this.sendResponse(this.result.right.rev))
|
||||
).style.marginRight = "4px";
|
||||
@@ -108,11 +122,9 @@ export class ConflictResolveModal extends Modal {
|
||||
contentEl.createEl("button", { text: !this.pluginPickMode ? "Not now" : "Cancel" }, (e) =>
|
||||
e.addEventListener("click", () => this.sendResponse(CANCELLED))
|
||||
).style.marginRight = "4px";
|
||||
diff = diff.replace(/\n/g, "<br>");
|
||||
if (diff.length > 100 * 1024) {
|
||||
if (diffLength > 100 * 1024) {
|
||||
div.empty();
|
||||
div.innerText = "(Too large diff to display)";
|
||||
} else {
|
||||
div.innerHTML = diff;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,10 +43,13 @@ export function paneChangeLog(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElem
|
||||
// tmpDiv.addClass("sls-header-button");
|
||||
tmpDiv.addClass("op-warn-info");
|
||||
|
||||
tmpDiv.innerHTML = `<p>${$msg("obsidianLiveSyncSettingTab.msgNewVersionNote")}</p><button>${$msg("obsidianLiveSyncSettingTab.optionOkReadEverything")}</button>`;
|
||||
tmpDiv.createEl("p", { text: $msg("obsidianLiveSyncSettingTab.msgNewVersionNote") });
|
||||
const readEverythingButton = tmpDiv.createEl("button", {
|
||||
text: $msg("obsidianLiveSyncSettingTab.optionOkReadEverything"),
|
||||
});
|
||||
if (lastVersion > (this.editingSettings?.lastReadUpdates || 0)) {
|
||||
const informationButtonDiv = informationDivEl.appendChild(tmpDiv);
|
||||
informationButtonDiv.querySelector("button")?.addEventListener("click", () => {
|
||||
readEverythingButton.addEventListener("click", () => {
|
||||
fireAndForget(async () => {
|
||||
this.editingSettings.lastReadUpdates = lastVersion;
|
||||
await this.saveAllDirtySettings();
|
||||
|
||||
@@ -121,13 +121,13 @@ export function paneSetup(
|
||||
const repo = "vrtmrz/obsidian-livesync";
|
||||
const topPath = $msg("obsidianLiveSyncSettingTab.linkTroubleshooting");
|
||||
const rawRepoURI = `https://raw.githubusercontent.com/${repo}/main`;
|
||||
this.createEl(
|
||||
paneEl,
|
||||
"div",
|
||||
"",
|
||||
(el) =>
|
||||
(el.innerHTML = `<a href='https://github.com/${repo}/blob/main${topPath}' target="_blank">${$msg("obsidianLiveSyncSettingTab.linkOpenInBrowser")}</a>`)
|
||||
);
|
||||
this.createEl(paneEl, "div", "", (el) => {
|
||||
el.createEl("a", { text: $msg("obsidianLiveSyncSettingTab.linkOpenInBrowser") }, (anchor) => {
|
||||
anchor.href = `https://github.com/${repo}/blob/main${topPath}`;
|
||||
anchor.target = "_blank";
|
||||
anchor.rel = "noopener";
|
||||
});
|
||||
});
|
||||
const troubleShootEl = this.createEl(paneEl, "div", {
|
||||
text: "",
|
||||
cls: "sls-troubleshoot-preview",
|
||||
|
||||
@@ -13,7 +13,7 @@ export const checkConfig = async (
|
||||
Logger($msg("obsidianLiveSyncSettingTab.logCheckingDbConfig"), LOG_LEVEL_INFO);
|
||||
let isSuccessful = true;
|
||||
const emptyDiv = createDiv();
|
||||
emptyDiv.innerHTML = "<span></span>";
|
||||
emptyDiv.createSpan();
|
||||
checkResultDiv?.replaceChildren(...[emptyDiv]);
|
||||
const addResult = (msg: string, classes?: string[]) => {
|
||||
const tmpDiv = createDiv();
|
||||
@@ -21,7 +21,7 @@ export const checkConfig = async (
|
||||
if (classes) {
|
||||
tmpDiv.addClasses(classes);
|
||||
}
|
||||
tmpDiv.innerHTML = `${msg}`;
|
||||
tmpDiv.textContent = msg;
|
||||
checkResultDiv?.appendChild(tmpDiv);
|
||||
};
|
||||
try {
|
||||
@@ -47,9 +47,10 @@ export const checkConfig = async (
|
||||
if (!checkResultDiv) return;
|
||||
const tmpDiv = createDiv();
|
||||
tmpDiv.addClass("ob-btn-config-fix");
|
||||
tmpDiv.innerHTML = `<label>${title}</label><button>${$msg("obsidianLiveSyncSettingTab.btnFix")}</button>`;
|
||||
tmpDiv.createEl("label", { text: title });
|
||||
const fixButton = tmpDiv.createEl("button", { text: $msg("obsidianLiveSyncSettingTab.btnFix") });
|
||||
const x = checkResultDiv.appendChild(tmpDiv);
|
||||
x.querySelector("button")?.addEventListener("click", () => {
|
||||
fixButton.addEventListener("click", () => {
|
||||
fireAndForget(async () => {
|
||||
Logger($msg("obsidianLiveSyncSettingTab.logCouchDbConfigSet", { title, key, value }));
|
||||
const res = await requestToCouchDBWithCredentials(
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
import Decision from "@/lib/src/UI/components/Decision.svelte";
|
||||
import Instruction from "@/lib/src/UI/components/Instruction.svelte";
|
||||
import UserDecisions from "@/lib/src/UI/components/UserDecisions.svelte";
|
||||
const TYPE_CLOSE = "close";
|
||||
const TYPE_CLOSE = "close";
|
||||
type ResultType = typeof TYPE_CLOSE;
|
||||
type Props = {
|
||||
setResult: (result: ResultType) => void;
|
||||
setResult: (_result: ResultType) => void;
|
||||
};
|
||||
const { setResult }: Props = $props();
|
||||
</script>
|
||||
|
||||
@@ -3,11 +3,22 @@ import { createServiceFeature } from "@lib/interfaces/ServiceModule";
|
||||
import { SUPPORTED_I18N_LANGS, type I18N_LANGS } from "@lib/common/rosetta";
|
||||
import { $msg, setLang } from "@lib/common/i18n";
|
||||
|
||||
function tryGetLanguage() {
|
||||
try {
|
||||
// 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.
|
||||
// eslint-disable-next-line obsidianmd/no-unsupported-api
|
||||
return getLanguage();
|
||||
} catch (e) {
|
||||
console.error("Failed to get Obsidian language, defaulting to 'def'", e);
|
||||
return "en";
|
||||
}
|
||||
}
|
||||
|
||||
export const enableI18nFeature = createServiceFeature(async ({ services: { setting, API } }) => {
|
||||
let isChanged = false;
|
||||
const settings = setting.currentSettings();
|
||||
if (settings.displayLanguage == "") {
|
||||
const obsidianLanguage = getLanguage();
|
||||
const obsidianLanguage = tryGetLanguage();
|
||||
if (
|
||||
SUPPORTED_I18N_LANGS.indexOf(obsidianLanguage) !== -1 && // Check if the language is supported
|
||||
obsidianLanguage != settings.displayLanguage // Check if the language is different from the current setting
|
||||
|
||||
@@ -5,6 +5,7 @@ import { type UseP2PReplicatorResult } from "@/lib/src/replication/trystero/UseP
|
||||
import { P2PLogCollector } from "@/lib/src/replication/trystero/P2PLogCollector";
|
||||
import { P2PReplicatorPaneView, VIEW_TYPE_P2P } from "@/features/P2PSync/P2PReplicator/P2PReplicatorPaneView";
|
||||
import type { LiveSyncCore } from "@/main";
|
||||
import type { WorkspaceLeaf } from "@/deps";
|
||||
|
||||
/**
|
||||
* ServiceFeature: P2P Replicator lifecycle management.
|
||||
@@ -43,7 +44,7 @@ export function useP2PReplicatorUI(
|
||||
|
||||
// Register view, commands and ribbon if a view factory is provided
|
||||
const viewType = VIEW_TYPE_P2P;
|
||||
const factory = (leaf: any) => {
|
||||
const factory = (leaf: WorkspaceLeaf) => {
|
||||
return new P2PReplicatorPaneView(leaf, core, {
|
||||
replicator: getReplicator(),
|
||||
p2pLogCollector,
|
||||
|
||||
@@ -38,10 +38,20 @@ export class ObsidianVaultAdapter implements IVaultAdapter<TFile> {
|
||||
}
|
||||
|
||||
async delete(file: TFile | TFolder, force = false): Promise<void> {
|
||||
// if ("trashFile" in this.app.fileManager) {
|
||||
// // eslint-disable-next-line obsidianmd/no-unsupported-api
|
||||
// return await this.app.fileManager.trashFile(file);
|
||||
// }
|
||||
//TODO: need fix
|
||||
return await this.app.vault.delete(file, force);
|
||||
}
|
||||
|
||||
async trash(file: TFile | TFolder, force = false): Promise<void> {
|
||||
// if ("trashFile" in this.app.fileManager) {
|
||||
// // eslint-disable-next-line obsidianmd/no-unsupported-api
|
||||
// return await this.app.fileManager.trashFile(file);
|
||||
// }
|
||||
//TODO: need fix
|
||||
return await this.app.vault.trash(file, force);
|
||||
}
|
||||
|
||||
|
||||
33
updates.md
33
updates.md
@@ -3,7 +3,15 @@ Since 19th July, 2025 (beta1 in 0.25.0-beta1, 13th July, 2025)
|
||||
|
||||
The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsidian-livesync/blob/main/updates_old.md). Because 0.25 got a lot of updates, thankfully, compatibility is kept and we do not need breaking changes! In other words, when get enough stabled. The next version will be v1.0.0. Even though it my hope.
|
||||
|
||||
## Unreleased
|
||||
## 0.25.61
|
||||
|
||||
13th May, 2026
|
||||
|
||||
Reviews have started on the Obsidian Community, haven't they? It was quite a struggle, what with having to fix the outdated ESLint.
|
||||
I am a bit nervous, but it is far better than just plodding along aimlessly, so let us get on with it. If you spot any issues, please let me know straight away.
|
||||
|
||||
From now on, I am avoiding committing directly to the main branch. This is because you lots have all been sending so much PRs. I wanted to keep things harmonious.
|
||||
That said, I am still not used to rebasing, so there are some parts where the commit history is a right mess. I will work on improving that.
|
||||
|
||||
### Improved
|
||||
|
||||
@@ -14,6 +22,29 @@ This P2P synchronisation is not compatible with previous versions in terms of co
|
||||
### Fixed
|
||||
|
||||
- No longer baffling errors occur when setting-update is triggered during the early stage of initialisation.
|
||||
- Network error notice pop-ups are now suppressed when 'NetworkWarningStyle' is set to 'Hidden'. (Thank you so much @SeleiXi!)
|
||||
|
||||
### New features
|
||||
|
||||
- Diff navigation buttons have been added to the diff view, making it easier to move between differences. (Thank you so much @SeleiXi! #871)
|
||||
|
||||
### Translations
|
||||
|
||||
- Chinese (Simplified) translations for settings and the Setup Wizard have been added. (Thank you so much @zombiek731!)
|
||||
- Common UI controls and signal words are now localised into Chinese (Simplified). (Thank you so much @zombiek731!)
|
||||
- i18n runtime behaviour and locale coverage have been improved. (Thank you so much @52sanmao!)
|
||||
|
||||
### CLI
|
||||
|
||||
#### New features
|
||||
|
||||
- Daemon synchronisation is now supported. (Thank you so much @andrewleech! #843)
|
||||
- `HeadlessConfirm` has been implemented with sensible defaults, enabling unattended operation in headless environments. (Thank you so much @andrewleech!)
|
||||
- The CLI onboarding experience has been improved. (Thank you so much @OriBoharon! #872)
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Sub-millisecond CLI mtimes are now truncated to prevent mobile crash. (Thank you so much @brian-spackman! #893)
|
||||
|
||||
## 0.25.60
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
{
|
||||
"0.25.61": "1.7.2",
|
||||
"0.25.60": "1.7.2",
|
||||
"1.0.1": "0.9.12",
|
||||
"1.0.0": "0.9.7"
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ import { defineConfig, mergeConfig } from "vitest/config";
|
||||
import { playwright } from "@vitest/browser-playwright";
|
||||
import viteConfig from "./vitest.config.common";
|
||||
import path from "path";
|
||||
import dotenv from "dotenv";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { parseEnv } from "node:util";
|
||||
import { grantClipboardPermissions, writeHandoffFile, readHandoffFile } from "./test/lib/commands";
|
||||
|
||||
// P2P test environment variables
|
||||
@@ -22,8 +23,9 @@ import { grantClipboardPermissions, writeHandoffFile, readHandoffFile } from "./
|
||||
// General test options (also read from env):
|
||||
// ENABLE_DEBUGGER - Set to "true" to attach a debugger and pause before tests
|
||||
// ENABLE_UI - Set to "true" to open a visible browser window during tests
|
||||
const defEnv = dotenv.config({ path: ".env" }).parsed;
|
||||
const testEnv = dotenv.config({ path: ".test.env" }).parsed;
|
||||
const loadEnvFile = (path: string) => (existsSync(path) ? parseEnv(readFileSync(path, "utf-8")) : undefined);
|
||||
const defEnv = loadEnvFile(".env");
|
||||
const testEnv = loadEnvFile(".test.env");
|
||||
// Merge: dotenv files < process.env (so shell-injected vars like P2P_TEST_* take precedence)
|
||||
const p2pEnv: Record<string, string> = {};
|
||||
if (process.env.P2P_TEST_ROOM_ID) p2pEnv.P2P_TEST_ROOM_ID = process.env.P2P_TEST_ROOM_ID;
|
||||
|
||||
@@ -2,10 +2,13 @@ import { defineConfig, mergeConfig } from "vitest/config";
|
||||
import { playwright } from "@vitest/browser-playwright";
|
||||
import viteConfig from "./vitest.config.common";
|
||||
import path from "path";
|
||||
import dotenv from "dotenv";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { parseEnv } from "node:util";
|
||||
import { grantClipboardPermissions, openWebPeer, closeWebPeer, acceptWebPeer } from "./test/lib/commands";
|
||||
const defEnv = dotenv.config({ path: ".env" }).parsed;
|
||||
const testEnv = dotenv.config({ path: ".test.env" }).parsed;
|
||||
|
||||
const loadEnvFile = (path: string) => (existsSync(path) ? parseEnv(readFileSync(path, "utf-8")) : undefined);
|
||||
const defEnv = loadEnvFile(".env");
|
||||
const testEnv = loadEnvFile(".test.env");
|
||||
const env = Object.assign({}, defEnv, testEnv);
|
||||
const debuggerEnabled = env?.ENABLE_DEBUGGER === "true";
|
||||
const enableUI = env?.ENABLE_UI === "true";
|
||||
|
||||
Reference in New Issue
Block a user