Compare commits

..

2 Commits

Author SHA1 Message Date
vorotamoroz
5ee0d482f8 bump 2022-12-23 21:54:20 +09:00
vorotamoroz
7ef43f747e Fixed:
- Now our renamed/deleted files have been surely deleted again.
2022-12-23 21:52:42 +09:00
19 changed files with 631 additions and 1932 deletions

View File

@@ -1,4 +1,3 @@
node_modules
npm node_modules
build
.eslintrc.js.bak
src/lib/src/patches/pouchdb-utils
.eslintrc.js.bak

View File

@@ -25,16 +25,14 @@ esbuild
"MANIFEST_VERSION": `"${manifestJson.version}"`,
"PACKAGE_VERSION": `"${packageJson.version}"`,
"UPDATE_INFO": `${updateInfo}`,
"global":"window",
},
external: ["obsidian", "electron", "crypto"],
external: ["obsidian", "electron", ...builtins],
format: "cjs",
watch: !prod,
target: "es2018",
logLevel: "info",
sourcemap: prod ? false : "inline",
treeShaking: true,
platform: "browser",
plugins: [
sveltePlugin({
preprocess: sveltePreprocess(),

View File

@@ -1,7 +1,7 @@
{
"id": "obsidian-livesync",
"name": "Self-hosted LiveSync",
"version": "0.17.19",
"version": "0.17.6",
"minAppVersion": "0.9.12",
"description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
"author": "vorotamoroz",

887
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "obsidian-livesync",
"version": "0.17.19",
"version": "0.17.6",
"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",
@@ -24,21 +24,11 @@
"eslint": "^8.28.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.26.0",
"events": "^3.3.0",
"obsidian": "^0.16.3",
"postcss": "^8.4.19",
"postcss-load-config": "^4.0.1",
"pouchdb-adapter-http": "^8.0.0",
"pouchdb-adapter-idb": "^8.0.0",
"pouchdb-adapter-indexeddb": "^8.0.0",
"pouchdb-core": "^8.0.0",
"pouchdb-find": "^8.0.0",
"pouchdb-mapreduce": "^8.0.0",
"pouchdb-replication": "^8.0.0",
"pouchdb-utils": "file:src/lib/src/patches/pouchdb-utils",
"svelte": "^3.53.1",
"svelte-preprocess": "^4.10.7",
"transform-pouch": "^2.0.0",
"tslib": "^2.4.1",
"typescript": "^4.9.3"
},

View File

@@ -1,19 +1,17 @@
import { App, Modal } from "obsidian";
import { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT } from "diff-match-patch";
import { diff_result } from "./lib/src/types";
import { escapeStringToHTML } from "./lib/src/strbin";
import { escapeStringToHTML } from "./lib/src/utils";
export class ConflictResolveModal extends Modal {
// result: Array<[number, string]>;
result: diff_result;
filename: string;
callback: (remove_rev: string) => Promise<void>;
constructor(app: App, filename: string, diff: diff_result, callback: (remove_rev: string) => Promise<void>) {
constructor(app: App, diff: diff_result, callback: (remove_rev: string) => Promise<void>) {
super(app);
this.result = diff;
this.callback = callback;
this.filename = filename;
}
onOpen() {
@@ -22,7 +20,6 @@ export class ConflictResolveModal extends Modal {
contentEl.empty();
contentEl.createEl("h2", { text: "This document has conflicted changes." });
contentEl.createEl("span", this.filename);
const div = contentEl.createDiv("");
div.addClass("op-scrollable");
let diff = "";

View File

@@ -1,13 +1,10 @@
import { TFile, Modal, App } from "obsidian";
import { path2id } from "./utils";
import { base64ToArrayBuffer, base64ToString, escapeStringToHTML } from "./lib/src/strbin";
import { isValidPath } from "./lib/src/path";
import { base64ToArrayBuffer, base64ToString, escapeStringToHTML, isValidPath } from "./lib/src/utils";
import ObsidianLiveSyncPlugin from "./main";
import { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch } from "diff-match-patch";
import { LoadedEntry, LOG_LEVEL } from "./lib/src/types";
import { Logger } from "./lib/src/logger";
import { isErrorOfMissingDoc } from "./lib/src/utils_couchdb";
import { getDocData } from "./lib/src/utils";
export class DocumentHistoryModal extends Modal {
plugin: ObsidianLiveSyncPlugin;
@@ -36,13 +33,13 @@ export class DocumentHistoryModal extends Modal {
const db = this.plugin.localDatabase;
try {
const w = await db.localDatabase.get(path2id(this.file), { revs_info: true });
this.revs_info = w._revs_info.filter((e) => e?.status == "available");
this.revs_info = w._revs_info.filter((e) => e.status == "available");
this.range.max = `${this.revs_info.length - 1}`;
this.range.value = this.range.max;
this.fileInfo.setText(`${this.file} / ${this.revs_info.length} revisions`);
await this.loadRevs();
} catch (ex) {
if (isErrorOfMissingDoc(ex)) {
if (ex.status && ex.status == 404) {
this.range.max = "0";
this.range.value = "";
this.range.disabled = true;
@@ -67,7 +64,7 @@ export class DocumentHistoryModal extends Modal {
this.currentDoc = w;
this.info.innerHTML = `Modified:${new Date(w.mtime).toLocaleString()}`;
let result = "";
const w1data = w.datatype == "plain" ? getDocData(w.data) : base64ToString(w.data);
const w1data = w.datatype == "plain" ? w.data : base64ToString(w.data);
this.currentDeleted = w.deleted;
this.currentText = w1data;
if (this.showDiff) {
@@ -77,7 +74,7 @@ export class DocumentHistoryModal extends Modal {
const w2 = await db.getDBEntry(path2id(this.file), { rev: oldRev }, false, false, true);
if (w2 != false) {
const dmp = new diff_match_patch();
const w2data = w2.datatype == "plain" ? getDocData(w2.data) : base64ToString(w2.data);
const w2data = w2.datatype == "plain" ? w2.data : base64ToString(w2.data);
const diff = dmp.diff_main(w2data, w1data);
dmp.diff_cleanupSemantic(diff);
for (const v of diff) {
@@ -179,7 +176,7 @@ export class DocumentHistoryModal extends Modal {
Logger("Path is not valid to write content.", LOG_LEVEL.INFO);
}
if (this.currentDoc?.datatype == "plain") {
await this.app.vault.adapter.write(pathToWrite, getDocData(this.currentDoc.data));
await this.app.vault.adapter.write(pathToWrite, this.currentDoc.data);
await focusFile(pathToWrite);
this.close();
} else if (this.currentDoc?.datatype == "newnote") {

View File

@@ -3,15 +3,14 @@ import { KeyValueDatabase, OpenKeyValueDatabase } from "./KeyValueDB.js";
import { LocalPouchDBBase } from "./lib/src/LocalPouchDBBase.js";
import { Logger } from "./lib/src/logger.js";
import { PouchDB } from "./lib/src/pouchdb-browser.js";
import { EntryDoc, LOG_LEVEL, ObsidianLiveSyncSettings } from "./lib/src/types.js";
import { enableEncryption } from "./lib/src/utils_couchdb.js";
import { EntryDoc, LOG_LEVEL } from "./lib/src/types.js";
import { enableEncryption } from "./lib/src/utils.js";
import { isCloudantURI, isValidRemoteCouchDBURI } from "./lib/src/utils_couchdb.js";
import { id2path, path2id } from "./utils.js";
export class LocalPouchDB extends LocalPouchDBBase {
kvDB: KeyValueDatabase;
settings: ObsidianLiveSyncSettings;
id2path(filename: string): string {
return id2path(filename);
}
@@ -19,10 +18,6 @@ export class LocalPouchDB extends LocalPouchDBBase {
return path2id(filename);
}
CreatePouchDBInstance<T>(name?: string, options?: PouchDB.Configuration.DatabaseConfiguration): PouchDB.Database<T> {
if (this.settings.useIndexedDBAdapter) {
options.adapter = "indexeddb";
return new PouchDB(name + "-indexeddb", options);
}
return new PouchDB(name, options);
}
beforeOnUnload(): void {
@@ -57,7 +52,7 @@ export class LocalPouchDB extends LocalPouchDBBase {
}
async connectRemoteCouchDB(uri: string, auth: { username: string; password: string }, disableRequestURI: boolean, passphrase: string | false, useDynamicIterationCount: boolean): Promise<string | { db: PouchDB.Database<EntryDoc>; info: PouchDB.Core.DatabaseInfo }> {
async connectRemoteCouchDB(uri: string, auth: { username: string; password: string }, disableRequestURI: boolean, passphrase: string | boolean, useDynamicIterationCount: boolean): Promise<string | { db: PouchDB.Database<EntryDoc>; info: PouchDB.Core.DatabaseInfo }> {
if (!isValidRemoteCouchDBURI(uri)) return "Remote URI is not valid";
if (uri.toLowerCase() != uri) return "Remote URI and database name could not contain capital letters.";
if (uri.indexOf(" ") !== -1) return "Remote URI and database name could not contain spaces.";
@@ -159,7 +154,7 @@ export class LocalPouchDB extends LocalPouchDBBase {
};
const db: PouchDB.Database<EntryDoc> = new PouchDB<EntryDoc>(uri, conf);
if (passphrase !== "false" && typeof passphrase === "string") {
if (passphrase && typeof passphrase === "string") {
enableEncryption(db, passphrase, useDynamicIterationCount);
}
try {

View File

@@ -1,17 +1,21 @@
import { App, Modal } from "obsidian";
import { logMessageStore } from "./lib/src/stores";
import { escapeStringToHTML } from "./lib/src/strbin";
import { escapeStringToHTML } from "./lib/src/utils";
import ObsidianLiveSyncPlugin from "./main";
export class LogDisplayModal extends Modal {
plugin: ObsidianLiveSyncPlugin;
logEl: HTMLDivElement;
unsubscribe: () => void;
constructor(app: App, plugin: ObsidianLiveSyncPlugin) {
super(app);
this.plugin = plugin;
}
updateLog() {
let msg = "";
for (const v of this.plugin.logMessage) {
msg += escapeStringToHTML(v) + "<br>";
}
this.logEl.innerHTML = msg;
}
onOpen() {
const { contentEl } = this;
@@ -21,18 +25,13 @@ export class LogDisplayModal extends Modal {
div.addClass("op-scrollable");
div.addClass("op-pre");
this.logEl = div;
this.unsubscribe = logMessageStore.observe((e) => {
let msg = "";
for (const v of e) {
msg += escapeStringToHTML(v) + "<br>";
}
this.logEl.innerHTML = msg;
})
logMessageStore.invalidate();
this.updateLog = this.updateLog.bind(this);
this.plugin.addLogHook = this.updateLog;
this.updateLog();
}
onClose() {
const { contentEl } = this;
contentEl.empty();
if (this.unsubscribe) this.unsubscribe();
this.plugin.addLogHook = null;
}
}

View File

@@ -1,9 +1,7 @@
import { App, PluginSettingTab, Setting, sanitizeHTMLToDom, RequestUrlParam, requestUrl, TextAreaComponent, MarkdownRenderer, stringifyYaml } from "obsidian";
import { DEFAULT_SETTINGS, LOG_LEVEL, ObsidianLiveSyncSettings, ConfigPassphraseStore, RemoteDBSettings } from "./lib/src/types";
import { DEFAULT_SETTINGS, LOG_LEVEL, ObsidianLiveSyncSettings, RemoteDBSettings } from "./lib/src/types";
import { path2id, id2path } from "./utils";
import { delay } from "./lib/src/utils";
import { Semaphore } from "./lib/src/semaphore";
import { versionNumberString2Number } from "./lib/src/strbin";
import { delay, Semaphore, versionNumberString2Number } from "./lib/src/utils";
import { Logger } from "./lib/src/logger";
import { checkSyncInfo, isCloudantURI } from "./lib/src/utils_couchdb.js";
import { testCrypt } from "./lib/src/e2ee_v2";
@@ -43,9 +41,6 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
}
display(): void {
const { containerEl } = this;
let encrypt = this.plugin.settings.encrypt;
let passphrase = this.plugin.settings.passphrase;
let useDynamicIterationCount = this.plugin.settings.useDynamicIterationCount;
containerEl.empty();
@@ -138,7 +133,6 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
if (this.plugin.settings.syncOnFileOpen) return true;
if (this.plugin.settings.syncOnSave) return true;
if (this.plugin.settings.syncOnStart) return true;
if (this.plugin.settings.syncAfterMerge) return true;
if (this.plugin.localDatabase.syncStatus == "CONNECTED") return true;
if (this.plugin.localDatabase.syncStatus == "PAUSED") return true;
return false;
@@ -150,7 +144,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
new Setting(setupWizardEl)
.setName("Discard the existing configuration and set up")
.addButton((text) => {
text.setButtonText("Next").onClick(() => {
text.setButtonText("Next").onClick(async () => {
if (JSON.stringify(this.plugin.settings) != JSON.stringify(DEFAULT_SETTINGS)) {
this.plugin.localDatabase.closeReplication();
this.plugin.settings = { ...DEFAULT_SETTINGS };
@@ -176,7 +170,6 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
this.plugin.settings.syncOnSave = false;
this.plugin.settings.syncOnStart = false;
this.plugin.settings.syncOnFileOpen = false;
this.plugin.settings.syncAfterMerge = false;
this.plugin.localDatabase.closeReplication();
await this.plugin.saveSettings();
containerEl.addClass("isWizard");
@@ -233,7 +226,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
syncLive.forEach((e) => {
e.setDisabled(false).setTooltip("");
});
} else if (this.plugin.settings.syncOnFileOpen || this.plugin.settings.syncOnSave || this.plugin.settings.syncOnStart || this.plugin.settings.periodicReplication || this.plugin.settings.syncAfterMerge) {
} else if (this.plugin.settings.syncOnFileOpen || this.plugin.settings.syncOnSave || this.plugin.settings.syncOnStart || this.plugin.settings.periodicReplication) {
syncNonLive.forEach((e) => {
e.setDisabled(false).setTooltip("");
});
@@ -296,78 +289,68 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
)
);
const e2e = new Setting(containerRemoteDatabaseEl)
new Setting(containerRemoteDatabaseEl)
.setName("End to End Encryption")
.setDesc("Encrypt contents on the remote database. If you use the plugin's synchronization feature, enabling this is recommend.")
.addToggle((toggle) =>
toggle.setValue(encrypt).onChange(async (value) => {
toggle.setValue(this.plugin.settings.workingEncrypt).onChange(async (value) => {
if (inWizard) {
this.plugin.settings.encrypt = value;
passphraseSetting.setDisabled(!value);
passphrase.setDisabled(!value);
dynamicIteration.setDisabled(!value);
await this.plugin.saveSettings();
} else {
encrypt = value;
passphraseSetting.setDisabled(!value);
this.plugin.settings.workingEncrypt = value;
passphrase.setDisabled(!value);
dynamicIteration.setDisabled(!value);
await this.plugin.saveSettings();
markDirtyControl();
}
})
);
const markDirtyControl = () => {
passphraseSetting.controlEl.toggleClass("sls-item-dirty", passphrase != this.plugin.settings.passphrase);
e2e.controlEl.toggleClass("sls-item-dirty", encrypt != this.plugin.settings.encrypt);
dynamicIteration.controlEl.toggleClass("sls-item-dirty", useDynamicIterationCount != this.plugin.settings.useDynamicIterationCount)
}
const passphraseSetting = new Setting(containerRemoteDatabaseEl)
const passphrase = new Setting(containerRemoteDatabaseEl)
.setName("Passphrase")
.setDesc("Encrypting passphrase. If you change the passphrase of a existing database, overwriting the remote database is strongly recommended.")
.addText((text) => {
text.setPlaceholder("")
.setValue(passphrase)
.setValue(this.plugin.settings.workingPassphrase)
.onChange(async (value) => {
if (inWizard) {
this.plugin.settings.passphrase = value;
await this.plugin.saveSettings();
} else {
passphrase = value;
this.plugin.settings.workingPassphrase = value;
await this.plugin.saveSettings();
markDirtyControl();
}
});
text.inputEl.setAttribute("type", "password");
});
passphraseSetting.setDisabled(!encrypt);
passphrase.setDisabled(!this.plugin.settings.workingEncrypt);
const dynamicIteration = new Setting(containerRemoteDatabaseEl)
.setName("Use dynamic iteration count (experimental)")
.setDesc("Balancing the encryption/decryption load against the length of the passphrase if toggled. (v0.17.5 or higher required)")
.addToggle((toggle) => {
toggle.setValue(useDynamicIterationCount)
toggle.setValue(this.plugin.settings.workingUseDynamicIterationCount)
.onChange(async (value) => {
if (inWizard) {
this.plugin.settings.useDynamicIterationCount = value;
await this.plugin.saveSettings();
} else {
useDynamicIterationCount = value;
this.plugin.settings.workingUseDynamicIterationCount = value;
await this.plugin.saveSettings();
markDirtyControl();
}
});
})
.setClass("wizardHidden");
dynamicIteration.setDisabled(!encrypt);
dynamicIteration.setDisabled(!this.plugin.settings.workingEncrypt);
const checkWorkingPassphrase = async (): Promise<boolean> => {
const settingForCheck: RemoteDBSettings = {
...this.plugin.settings,
encrypt: encrypt,
passphrase: passphrase,
useDynamicIterationCount: useDynamicIterationCount,
encrypt: this.plugin.settings.workingEncrypt,
passphrase: this.plugin.settings.workingPassphrase,
useDynamicIterationCount: this.plugin.settings.workingUseDynamicIterationCount,
};
console.dir(settingForCheck);
const db = await this.plugin.localDatabase.connectRemoteCouchDBWithSetting(settingForCheck, this.plugin.localDatabase.isMobile);
@@ -385,32 +368,30 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
}
};
const applyEncryption = async (sendToServer: boolean) => {
if (encrypt && passphrase == "") {
if (this.plugin.settings.workingEncrypt && this.plugin.settings.workingPassphrase == "") {
Logger("If you enable encryption, you have to set the passphrase", LOG_LEVEL.NOTICE);
return;
}
if (encrypt && !(await testCrypt())) {
if (this.plugin.settings.workingEncrypt && !(await testCrypt())) {
Logger("WARNING! Your device would not support encryption.", LOG_LEVEL.NOTICE);
return;
}
if (!(await checkWorkingPassphrase()) && !sendToServer) {
return;
}
if (!encrypt) {
passphrase = "";
if (!this.plugin.settings.workingEncrypt) {
this.plugin.settings.workingPassphrase = "";
}
this.plugin.settings.liveSync = false;
this.plugin.settings.periodicReplication = false;
this.plugin.settings.syncOnSave = false;
this.plugin.settings.syncOnStart = false;
this.plugin.settings.syncOnFileOpen = false;
this.plugin.settings.syncAfterMerge = false;
this.plugin.settings.encrypt = encrypt;
this.plugin.settings.passphrase = passphrase;
this.plugin.settings.useDynamicIterationCount = useDynamicIterationCount;
this.plugin.settings.encrypt = this.plugin.settings.workingEncrypt;
this.plugin.settings.passphrase = this.plugin.settings.workingPassphrase;
this.plugin.settings.useDynamicIterationCount = this.plugin.settings.workingUseDynamicIterationCount;
await this.plugin.saveSettings();
markDirtyControl();
if (sendToServer) {
await this.plugin.initializeDatabase(true);
await this.plugin.markRemoteLocked();
@@ -431,6 +412,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
.setButtonText("Apply")
.setWarning()
.setDisabled(false)
.setClass("sls-btn-right")
.onClick(async () => {
await applyEncryption(true);
})
@@ -440,6 +422,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
.setButtonText("Apply w/o rebuilding")
.setWarning()
.setDisabled(false)
.setClass("sls-btn-right")
.onClick(async () => {
await applyEncryption(false);
})
@@ -452,7 +435,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
this.plugin.settings.syncOnSave = false;
this.plugin.settings.syncOnStart = false;
this.plugin.settings.syncOnFileOpen = false;
this.plugin.settings.syncAfterMerge = false;
await this.plugin.saveSettings();
applyDisplayEnabled();
@@ -487,6 +470,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
.setButtonText("Send")
.setWarning()
.setDisabled(false)
.setClass("sls-btn-left")
.onClick(async () => {
await rebuildDB("remoteOnly");
})
@@ -501,6 +485,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
.setButtonText("Rebuild")
.setWarning()
.setDisabled(false)
.setClass("sls-btn-left")
.onClick(async () => {
await rebuildDB("rebuildBothByThisDevice");
})
@@ -712,7 +697,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
.setButtonText("Next")
.setClass("mod-cta")
.setDisabled(false)
.onClick(() => {
.onClick(async () => {
if (!this.plugin.settings.encrypt) {
this.plugin.settings.passphrase = "";
}
@@ -733,7 +718,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
.setButtonText("Discard exist database and proceed")
.setDisabled(false)
.setWarning()
.onClick(() => {
.onClick(async () => {
if (!this.plugin.settings.encrypt) {
this.plugin.settings.passphrase = "";
}
@@ -775,6 +760,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
.setButtonText("Fetch")
.setWarning()
.setDisabled(false)
.setClass("sls-btn-left")
.onClick(async () => {
await rebuildDB("localOnly");
})
@@ -812,28 +798,11 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
button
.setButtonText("Next")
.setDisabled(false)
.onClick(() => {
.onClick(async () => {
changeDisplay("40");
})
);
containerLocalDatabaseEl.createEl("h3", {
text: sanitizeHTMLToDom(`Experimental`),
cls: "wizardHidden"
});
new Setting(containerLocalDatabaseEl)
.setName("Use new adapter")
.setDesc("This option is not compatible with a database made by older versions. Changing this configuration will fetch the remote database again.")
.setClass("wizardHidden")
.addToggle((toggle) =>
toggle.setValue(this.plugin.settings.useIndexedDBAdapter).onChange(async (value) => {
this.plugin.settings.useIndexedDBAdapter = value;
await this.plugin.saveSettings();
await rebuildDB("localOnly");
})
);
addScreenElement("10", containerLocalDatabaseEl);
const containerGeneralSettingsEl = containerEl.createDiv();
containerGeneralSettingsEl.createEl("h3", { text: "General Settings" });
@@ -883,14 +852,6 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
});
text.inputEl.setAttribute("type", "number");
});
new Setting(containerGeneralSettingsEl)
.setName("Monitor changes to hidden files and plugin")
.addToggle((toggle) =>
toggle.setValue(this.plugin.settings.watchInternalFileChanges).onChange(async (value) => {
this.plugin.settings.watchInternalFileChanges = value;
await this.plugin.saveSettings();
})
);
addScreenElement("20", containerGeneralSettingsEl);
@@ -993,17 +954,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
await this.plugin.saveSettings();
applyDisplayEnabled();
})
),
new Setting(containerSyncSettingEl)
.setName("Sync after merging file")
.setDesc("Sync automatically after merging files")
.addToggle((toggle) =>
toggle.setValue(this.plugin.settings.syncAfterMerge).onChange(async (value) => {
this.plugin.settings.syncAfterMerge = value;
await this.plugin.saveSettings();
applyDisplayEnabled();
})
),
)
);
new Setting(containerSyncSettingEl)
@@ -1064,6 +1015,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
})
);
new Setting(containerSyncSettingEl)
.setName("Sync hidden files")
.addToggle((toggle) =>
@@ -1072,7 +1024,14 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
await this.plugin.saveSettings();
})
);
new Setting(containerSyncSettingEl)
.setName("Monitor changes to internal files")
.addToggle((toggle) =>
toggle.setValue(this.plugin.settings.watchInternalFileChanges).onChange(async (value) => {
this.plugin.settings.watchInternalFileChanges = value;
await this.plugin.saveSettings();
})
);
new Setting(containerSyncSettingEl)
.setName("Scan for hidden files before replication")
.setDesc("This configuration will be ignored if monitoring changes is enabled.")
@@ -1144,6 +1103,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
.setButtonText("Touch")
.setWarning()
.setDisabled(false)
.setClass("sls-btn-left")
.onClick(async () => {
const filesAll = await this.plugin.scanInternalFiles();
const targetFiles = await this.plugin.filterTargetFiles(filesAll);
@@ -1217,8 +1177,8 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
.setValue(this.plugin.settings.customChunkSize + "")
.onChange(async (value) => {
let v = Number(value);
if (isNaN(v) || v < 1) {
v = 1;
if (isNaN(v) || v < 100) {
v = 100;
}
this.plugin.settings.customChunkSize = v;
await this.plugin.saveSettings();
@@ -1318,7 +1278,6 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
this.plugin.settings.syncOnSave = false;
this.plugin.settings.syncOnStart = false;
this.plugin.settings.syncOnFileOpen = false;
this.plugin.settings.syncAfterMerge = false;
if (currentPreset == "LIVESYNC") {
this.plugin.settings.liveSync = true;
Logger("Synchronization setting configured as LiveSync.", LOG_LEVEL.NOTICE);
@@ -1328,7 +1287,6 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
this.plugin.settings.syncOnSave = false;
this.plugin.settings.syncOnStart = true;
this.plugin.settings.syncOnFileOpen = true;
this.plugin.settings.syncAfterMerge = true;
Logger("Synchronization setting configured as Periodic sync with batch database update.", LOG_LEVEL.NOTICE);
} else {
Logger("All synchronization disabled.", LOG_LEVEL.NOTICE);
@@ -1357,45 +1315,6 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
})
);
const passphrase_options: Record<ConfigPassphraseStore, string> = {
"": "Default",
LOCALSTORAGE: "Use a custom passphrase",
ASK_AT_LAUNCH: "Ask an passphrase at every launch",
}
new Setting(containerMiscellaneousEl)
.setName("Encrypting sensitive configuration items")
.addDropdown((dropdown) =>
dropdown
.addOptions(passphrase_options)
.setValue(this.plugin.settings.configPassphraseStore)
.onChange(async (value) => {
this.plugin.settings.configPassphraseStore = value as ConfigPassphraseStore;
this.plugin.usedPassphrase = "";
confPassphraseSetting.setDisabled(this.plugin.settings.configPassphraseStore != "LOCALSTORAGE");
await this.plugin.saveSettings();
})
)
.setClass("wizardHidden");
const confPassphrase = localStorage.getItem("ls-setting-passphrase") || "";
const confPassphraseSetting = new Setting(containerMiscellaneousEl)
.setName("Passphrase of sensitive configuration items")
.setDesc("This passphrase will not be copied to another device. It will be set to `Default` until you configure it again.")
.addText((text) => {
text.setPlaceholder("")
.setValue(confPassphrase)
.onChange(async (value) => {
this.plugin.usedPassphrase = "";
localStorage.setItem("ls-setting-passphrase", value);
await this.plugin.saveSettings();
markDirtyControl();
});
text.inputEl.setAttribute("type", "password");
})
.setClass("wizardHidden");
confPassphraseSetting.setDisabled(this.plugin.settings.configPassphraseStore != "LOCALSTORAGE");
const infoApply = containerMiscellaneousEl.createEl("div", { text: `To finish setup, please select one of the presets` });
infoApply.addClass("op-warn-info");
infoApply.addClass("wizardOnly")
@@ -1437,8 +1356,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
pluginConfig.couchDB_URI = isCloudantURI(pluginConfig.couchDB_URI) ? "cloudant" : "self-hosted";
pluginConfig.couchDB_USER = REDACTED;
pluginConfig.passphrase = REDACTED;
pluginConfig.encryptedPassphrase = REDACTED;
pluginConfig.encryptedCouchDBConnection = REDACTED;
pluginConfig.workingPassphrase = REDACTED;
const msgConfig = `----remote config----
${stringifyYaml(responseConfig)}
@@ -1496,7 +1414,7 @@ ${stringifyYaml(pluginConfig)}`;
const releaser = await semaphore.acquire(1, "verifyAndRepair");
try {
Logger(`UPDATE DATABASE ${file.path}`);
Logger(`Update into ${file.path}`);
await this.plugin.updateIntoDB(file, false, null, true);
i++;
Logger(`${i}/${files.length}\n${file.path}`, LOG_LEVEL.NOTICE, "verify");
@@ -1568,7 +1486,7 @@ ${stringifyYaml(pluginConfig)}`;
new Setting(containerPluginSettings)
.setName("Scan plugins periodically")
.setDesc("Scan plugins every 1 minute. This configuration will be ignored if monitoring changes of hidden files has been enabled.")
.setDesc("Scan plugins every 1 minute.")
.addToggle((toggle) =>
toggle.setValue(this.plugin.settings.autoSweepPluginsPeriodic).onChange(async (value) => {
this.plugin.settings.autoSweepPluginsPeriodic = value;

View File

@@ -2,7 +2,7 @@
import ObsidianLiveSyncPlugin from "./main";
import { onMount } from "svelte";
import { DevicePluginList, PluginDataEntry } from "./types";
import { versionNumberString2Number } from "./lib/src/strbin";
import { versionNumberString2Number } from "./lib/src/utils";
type JudgeResult = "" | "NEWER" | "EVEN" | "EVEN_BUT_DIFFERENT" | "OLDER" | "REMOTE_ONLY";

View File

@@ -52,14 +52,14 @@ export class InputStringDialog extends Modal {
const { contentEl } = this;
contentEl.createEl("h1", { text: this.title });
// For enter to submit
const formEl = contentEl.createEl("form");
new Setting(formEl).setName(this.key).addText((text) =>
new Setting(contentEl).setName(this.key).addText((text) =>
text.onChange((value) => {
this.result = value;
})
);
new Setting(formEl).addButton((btn) =>
new Setting(contentEl).addButton((btn) =>
btn
.setButtonText("Ok")
.setCta()

Submodule src/lib updated: 9e993fd984...9fe5ce421f

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
import { PluginManifest, TFile } from "obsidian";
import { DatabaseEntry, EntryBody } from "./lib/src/types";
import { PluginManifest } from "obsidian";
import { DatabaseEntry } from "./lib/src/types";
export interface PluginDataEntry extends DatabaseEntry {
deviceVaultName: string;
@@ -30,20 +30,3 @@ export interface InternalFileInfo {
size: number;
deleted?: boolean;
}
export interface FileInfo {
path: string;
mtime: number;
ctime: number;
size: number;
deleted?: boolean;
file: TFile;
}
export type queueItem = {
entry: EntryBody;
missingChildren: string[];
timeout?: number;
done?: boolean;
warned?: boolean;
};

View File

@@ -1,6 +1,6 @@
import { normalizePath } from "obsidian";
import { path2id_base, id2path_base } from "./lib/src/path";
import { path2id_base, id2path_base } from "./lib/src/utils";
// For backward compatibility, using the path for determining id.
// Only CouchDB unacceptable ID (that starts with an underscore) has been prefixed with "/".

View File

@@ -85,6 +85,13 @@
} */
.sls-btn-left {
padding-right: 4px;
}
.sls-btn-right {
padding-left: 4px;
}
.sls-header-button {
margin-left: 2em;
@@ -101,7 +108,7 @@
.CodeMirror-wrap::before,
.cm-s-obsidian>.cm-editor::before,
.canvas-wrapper::before {
content: attr(data-log);
content: var(--slsmessage);
text-align: right;
white-space: pre-wrap;
position: absolute;
@@ -247,8 +254,4 @@ div.sls-setting-menu-btn {
.sls-setting:not(.isWizard) .wizardOnly {
display: none;
}
.sls-item-dirty::before {
content: "✏";
}

View File

@@ -29,63 +29,37 @@
- 0.17.5 Now `read chunks online` had been fixed, and a new feature: `Use dynamic iteration count` to reduce the load on encryption/decryption.
Note: `Use dynamic iteration count` is not compatible with earlier versions.
- 0.17.6 Now our renamed/deleted files have been surely deleted again.
- 0.17.7
- Fixed:
- Fixed merging issues.
- Fixed button styling.
- Changed:
- Conflict checking on synchronising has been enabled for every note in default.
- 0.17.8
- Improved: Performance improved. Prebuilt PouchDB is no longer used.
- Fixed: Merging hidden files is also fixed.
- New Feature: Now we can synchronise automatically after merging conflicts.
- 0.17.9
- Fixed: Conflict merge of internal files is no longer broken.
- Improved: Smoother status display inside the editor.
- 0.17.10
- Fixed: Large file synchronising has been now addressed!
Note: When synchronising large files, we have to set `Chunk size` to lower than 50, disable `Read chunks online`, `Batch size` should be set 50-100, and `Batch limit` could be around 20.
- 0.17.11
- Fixed:
- Performance improvement
- Now `Chunk size` can be set to under one hundred.
- New feature:
- The number of transfers required before replication stabilises is now displayed.
- 0.17.12: Skipped.
- 0.17.13
- Fixed: Document history is now displayed again.
- Reorganised: Many files have been refactored.
- 0.17.14: Skipped.
- 0.17.15
- Improved:
- Confidential information has no longer stored in data.json as is.
- Synchronising progress has been shown in the notification.
- We can commit passphrases with a keyboard.
- Configuration which had not been saved yet is marked now.
- Now the filename is shown on the Conflict resolving dialog
- Fixed:
- Hidden files have been synchronised again.
- Rename of files has been fixed again.
And, minor changes have been included.
- 0.17.16:
- Improved:
- Plugins and their settings no longer need scanning if changes are monitored.
- Now synchronising plugins and their settings are performed parallelly and faster.
- We can place `redflag2.md` to rebuild the database automatically while the boot sequence.
- Experimental:
- We can use a new adapter on PouchDB. This will make us smoother.
- Note: Not compatible with the older version.
- Fixed:
- The default batch size is smaller again.
- Plugins and their setting can be synchronised again.
- Hidden files and plugins are correctly scanned while rebuilding.
- Files with the name started `_` are also being performed conflict-checking.
- 0.17.17
- Fixed: Now we can merge JSON files even if we failed to compare items like null.
- 0.17.18
- Fixed: Fixed lack of error handling.
- 0.17.19
- Fixed: Error reporting has been ensured.
### 0.16.0
- Now hidden files need not be scanned. Changes will be detected automatically.
- If you want it to back to its previous behaviour, please disable `Monitor changes to internal files`.
- Due to using an internal API, this feature may become unusable with a major update. If this happens, please disable this once.
... To continue on to `updates_old.md`.
#### Minors
- 0.16.1 Added missing log updates.
- 0.16.2 Fixed many problems caused by combinations of `Sync On Save` and the tracking logic that changed at 0.15.6.
- 0.16.3
- Fixed detection of IBM Cloudant (And if there are some issues, be fixed automatically).
- A configuration information reporting tool has been implemented.
- 0.16.4 Fixed detection failure. Please set the `Chunk size` again when using a self-hosted database.
- 0.16.5
- Fixed
- Conflict detection and merging now be able to treat deleted files.
- Logs while the boot-up sequence has been tidied up.
- Fixed incorrect log entries.
- New Feature
- The feature of automatically deleting old expired metadata has been implemented.
We can configure it in `Delete old metadata of deleted files on start-up` in the `General Settings` pane.
- 0.16.6
- Fixed
- Automatic (temporary) batch size adjustment has been restored to work correctly.
- Chunk splitting has been backed to the previous behaviour for saving them correctly.
- Improved
- Corrupted chunks will be detected automatically.
- Now on the case-insensitive system, `aaa.md` and `AAA.md` will be treated as the same file or path at applying changesets.
- 0.16.7 Nothing has been changed except toolsets, framework library, and as like them. Please inform me if something had been getting strange!
- 0.16.8 Now we can synchronise without `bad_request:invalid UTF-8 JSON` even while end-to-end encryption has been disabled.
Note:
Before 0.16.5, LiveSync had some issues making chunks. In this case, synchronisation had became been always failing after a corrupted one should be made. After 0.16.6, the corrupted chunk is automatically detected. Sorry for troubling you but please do `rebuild everything` when this plug-in notified so.

View File

@@ -1,58 +1,3 @@
### 0.16.0
- Now hidden files need not be scanned. Changes will be detected automatically.
- If you want it to back to its previous behaviour, please disable `Monitor changes to internal files`.
- Due to using an internal API, this feature may become unusable with a major update. If this happens, please disable this once.
#### Minors
- 0.16.1 Added missing log updates.
- 0.16.2 Fixed many problems caused by combinations of `Sync On Save` and the tracking logic that changed at 0.15.6.
- 0.16.3
- Fixed detection of IBM Cloudant (And if there are some issues, be fixed automatically).
- A configuration information reporting tool has been implemented.
- 0.16.4 Fixed detection failure. Please set the `Chunk size` again when using a self-hosted database.
- 0.16.5
- Fixed
- Conflict detection and merging now be able to treat deleted files.
- Logs while the boot-up sequence has been tidied up.
- Fixed incorrect log entries.
- New Feature
- The feature of automatically deleting old expired metadata has been implemented.
We can configure it in `Delete old metadata of deleted files on start-up` in the `General Settings` pane.
- 0.16.6
- Fixed
- Automatic (temporary) batch size adjustment has been restored to work correctly.
- Chunk splitting has been backed to the previous behaviour for saving them correctly.
- Improved
- Corrupted chunks will be detected automatically.
- Now on the case-insensitive system, `aaa.md` and `AAA.md` will be treated as the same file or path at applying changesets.
- 0.16.7 Nothing has been changed except toolsets, framework library, and as like them. Please inform me if something had been getting strange!
- 0.16.8 Now we can synchronise without `bad_request:invalid UTF-8 JSON` even while end-to-end encryption has been disabled.
Note:
Before 0.16.5, LiveSync had some issues making chunks. In this case, synchronisation had became been always failing after a corrupted one should be made. After 0.16.6, the corrupted chunk is automatically detected. Sorry for troubling you but please do `rebuild everything` when this plug-in notified so.
### 0.15.0
- Outdated configuration items have been removed.
- Setup wizard has been implemented!
I appreciate for reviewing and giving me advice @Pouhon158!
#### Minors
- 0.15.1 Missed the stylesheet.
- 0.15.2 The wizard has been improved and documented!
- 0.15.3 Fixed the issue about locking/unlocking remote database while rebuilding in the wizard.
- 0.15.4 Fixed issues about asynchronous processing (e.g., Conflict check or hidden file detection)
- 0.15.5 Add new features for setting Self-hosted LiveSync up more easier.
- 0.15.6 File tracking logic has been refined.
- 0.15.7 Fixed bug about renaming file.
- 0.15.8 Fixed bug about deleting empty directory, weird behaviour on boot-sequence on mobile devices.
- 0.15.9 Improved chunk retrieving, now chunks are retrieved in batch on continuous requests.
- 0.15.10 Fixed:
- The boot sequence has been corrected and now boots smoothly.
- Auto applying of batch save will be processed earlier than before.
### 0.14.1
- The target selecting filter was implemented.
Now we can set what files are synchronised by regular expression.