Compare commits

...

15 Commits

Author SHA1 Message Date
vorotamoroz
7ec64a6a93 bump 2023-01-19 18:55:18 +09:00
vorotamoroz
c5c6deb742 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.

Fixed:
- Hidden files have been synchronised again.

And, minor changes have been included.
2023-01-19 18:50:06 +09:00
vorotamoroz
ef57fbfdda Fixed:
- Now the filename is shown on the Conflict resolving dialog
- Rename of files has been improved again.
2023-01-19 13:11:30 +09:00
vorotamoroz
bc158e9f2b bump 2023-01-17 17:46:06 +09:00
vorotamoroz
6513c53c7e Fixed:
- Document history is now displayed again.

Reorganised:
- Many files have been refactored.
2023-01-17 17:39:26 +09:00
vorotamoroz
5d1074065c bump 2023-01-16 17:33:31 +09:00
vorotamoroz
b444082b0c 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.
2023-01-16 17:31:37 +09:00
vorotamoroz
d5e6419504 bump 2023-01-15 11:17:08 +09:00
vorotamoroz
1bf1e1540d Fix diff check. 2023-01-15 11:09:23 +09:00
vorotamoroz
be1e6b11ac Fixed
- Large files addressed.
2023-01-13 19:43:39 +09:00
vorotamoroz
a486788572 bump 2023-01-06 16:33:54 +09:00
vorotamoroz
e5784a1da6 Fixed:
- Conflict merge of internal files is no longer broken.
Improved:
- Smoother status display inside the editor.
2023-01-06 16:27:39 +09:00
vorotamoroz
2100e22276 bump 2022-12-27 18:12:00 +09:00
vorotamoroz
ec08dc5fe8 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.
2022-12-27 18:09:51 +09:00
vorotamoroz
c92e94e552 fix eslint configuration 2022-12-27 17:58:42 +09:00
19 changed files with 1535 additions and 388 deletions

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
{ {
"id": "obsidian-livesync", "id": "obsidian-livesync",
"name": "Self-hosted LiveSync", "name": "Self-hosted LiveSync",
"version": "0.17.7", "version": "0.17.14",
"minAppVersion": "0.9.12", "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.", "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", "author": "vorotamoroz",

858
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "obsidian-livesync", "name": "obsidian-livesync",
"version": "0.17.7", "version": "0.17.14",
"description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.", "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", "main": "main.js",
"type": "module", "type": "module",
@@ -24,11 +24,20 @@
"eslint": "^8.28.0", "eslint": "^8.28.0",
"eslint-config-airbnb-base": "^15.0.0", "eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.26.0",
"events": "^3.3.0",
"obsidian": "^0.16.3", "obsidian": "^0.16.3",
"postcss": "^8.4.19", "postcss": "^8.4.19",
"postcss-load-config": "^4.0.1", "postcss-load-config": "^4.0.1",
"pouchdb-adapter-http": "^8.0.0",
"pouchdb-adapter-idb": "^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": "^3.53.1",
"svelte-preprocess": "^4.10.7", "svelte-preprocess": "^4.10.7",
"transform-pouch": "^2.0.0",
"tslib": "^2.4.1", "tslib": "^2.4.1",
"typescript": "^4.9.3" "typescript": "^4.9.3"
}, },

View File

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

View File

@@ -1,10 +1,12 @@
import { TFile, Modal, App } from "obsidian"; import { TFile, Modal, App } from "obsidian";
import { path2id } from "./utils"; import { path2id } from "./utils";
import { base64ToArrayBuffer, base64ToString, escapeStringToHTML, isValidPath } from "./lib/src/utils"; import { base64ToArrayBuffer, base64ToString, escapeStringToHTML } from "./lib/src/strbin";
import { isValidPath } from "./lib/src/path";
import ObsidianLiveSyncPlugin from "./main"; import ObsidianLiveSyncPlugin from "./main";
import { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch } from "diff-match-patch"; import { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch } from "diff-match-patch";
import { LoadedEntry, LOG_LEVEL } from "./lib/src/types"; import { LoadedEntry, LOG_LEVEL } from "./lib/src/types";
import { Logger } from "./lib/src/logger"; import { Logger } from "./lib/src/logger";
import { getDocData } from "./lib/src/utils";
export class DocumentHistoryModal extends Modal { export class DocumentHistoryModal extends Modal {
plugin: ObsidianLiveSyncPlugin; plugin: ObsidianLiveSyncPlugin;
@@ -64,7 +66,7 @@ export class DocumentHistoryModal extends Modal {
this.currentDoc = w; this.currentDoc = w;
this.info.innerHTML = `Modified:${new Date(w.mtime).toLocaleString()}`; this.info.innerHTML = `Modified:${new Date(w.mtime).toLocaleString()}`;
let result = ""; let result = "";
const w1data = w.datatype == "plain" ? w.data : base64ToString(w.data); const w1data = w.datatype == "plain" ? getDocData(w.data) : base64ToString(w.data);
this.currentDeleted = w.deleted; this.currentDeleted = w.deleted;
this.currentText = w1data; this.currentText = w1data;
if (this.showDiff) { if (this.showDiff) {
@@ -74,7 +76,7 @@ export class DocumentHistoryModal extends Modal {
const w2 = await db.getDBEntry(path2id(this.file), { rev: oldRev }, false, false, true); const w2 = await db.getDBEntry(path2id(this.file), { rev: oldRev }, false, false, true);
if (w2 != false) { if (w2 != false) {
const dmp = new diff_match_patch(); const dmp = new diff_match_patch();
const w2data = w2.datatype == "plain" ? w2.data : base64ToString(w2.data); const w2data = w2.datatype == "plain" ? getDocData(w2.data) : base64ToString(w2.data);
const diff = dmp.diff_main(w2data, w1data); const diff = dmp.diff_main(w2data, w1data);
dmp.diff_cleanupSemantic(diff); dmp.diff_cleanupSemantic(diff);
for (const v of diff) { for (const v of diff) {
@@ -176,7 +178,7 @@ export class DocumentHistoryModal extends Modal {
Logger("Path is not valid to write content.", LOG_LEVEL.INFO); Logger("Path is not valid to write content.", LOG_LEVEL.INFO);
} }
if (this.currentDoc?.datatype == "plain") { if (this.currentDoc?.datatype == "plain") {
await this.app.vault.adapter.write(pathToWrite, this.currentDoc.data); await this.app.vault.adapter.write(pathToWrite, getDocData(this.currentDoc.data));
await focusFile(pathToWrite); await focusFile(pathToWrite);
this.close(); this.close();
} else if (this.currentDoc?.datatype == "newnote") { } else if (this.currentDoc?.datatype == "newnote") {

View File

@@ -4,7 +4,7 @@ import { LocalPouchDBBase } from "./lib/src/LocalPouchDBBase.js";
import { Logger } from "./lib/src/logger.js"; import { Logger } from "./lib/src/logger.js";
import { PouchDB } from "./lib/src/pouchdb-browser.js"; import { PouchDB } from "./lib/src/pouchdb-browser.js";
import { EntryDoc, LOG_LEVEL } from "./lib/src/types.js"; import { EntryDoc, LOG_LEVEL } from "./lib/src/types.js";
import { enableEncryption } from "./lib/src/utils.js"; import { enableEncryption } from "./lib/src/utils_couchdb.js";
import { isCloudantURI, isValidRemoteCouchDBURI } from "./lib/src/utils_couchdb.js"; import { isCloudantURI, isValidRemoteCouchDBURI } from "./lib/src/utils_couchdb.js";
import { id2path, path2id } from "./utils.js"; import { id2path, path2id } from "./utils.js";
@@ -52,7 +52,7 @@ export class LocalPouchDB extends LocalPouchDBBase {
} }
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 }> { 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 }> {
if (!isValidRemoteCouchDBURI(uri)) return "Remote URI is not valid"; 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.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."; if (uri.indexOf(" ") !== -1) return "Remote URI and database name could not contain spaces.";
@@ -154,7 +154,7 @@ export class LocalPouchDB extends LocalPouchDBBase {
}; };
const db: PouchDB.Database<EntryDoc> = new PouchDB<EntryDoc>(uri, conf); const db: PouchDB.Database<EntryDoc> = new PouchDB<EntryDoc>(uri, conf);
if (passphrase && typeof passphrase === "string") { if (passphrase !== "false" && typeof passphrase === "string") {
enableEncryption(db, passphrase, useDynamicIterationCount); enableEncryption(db, passphrase, useDynamicIterationCount);
} }
try { try {

View File

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

View File

@@ -1,7 +1,9 @@
import { App, PluginSettingTab, Setting, sanitizeHTMLToDom, RequestUrlParam, requestUrl, TextAreaComponent, MarkdownRenderer, stringifyYaml } from "obsidian"; import { App, PluginSettingTab, Setting, sanitizeHTMLToDom, RequestUrlParam, requestUrl, TextAreaComponent, MarkdownRenderer, stringifyYaml } from "obsidian";
import { DEFAULT_SETTINGS, LOG_LEVEL, ObsidianLiveSyncSettings, RemoteDBSettings } from "./lib/src/types"; import { DEFAULT_SETTINGS, LOG_LEVEL, ObsidianLiveSyncSettings, ConfigPassphraseStore, RemoteDBSettings } from "./lib/src/types";
import { path2id, id2path } from "./utils"; import { path2id, id2path } from "./utils";
import { delay, Semaphore, versionNumberString2Number } from "./lib/src/utils"; import { delay } from "./lib/src/utils";
import { Semaphore } from "./lib/src/semaphore";
import { versionNumberString2Number } from "./lib/src/strbin";
import { Logger } from "./lib/src/logger"; import { Logger } from "./lib/src/logger";
import { checkSyncInfo, isCloudantURI } from "./lib/src/utils_couchdb.js"; import { checkSyncInfo, isCloudantURI } from "./lib/src/utils_couchdb.js";
import { testCrypt } from "./lib/src/e2ee_v2"; import { testCrypt } from "./lib/src/e2ee_v2";
@@ -41,6 +43,9 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
} }
display(): void { display(): void {
const { containerEl } = this; const { containerEl } = this;
let encrypt = this.plugin.settings.encrypt;
let passphrase = this.plugin.settings.passphrase;
let useDynamicIterationCount = this.plugin.settings.useDynamicIterationCount;
containerEl.empty(); containerEl.empty();
@@ -133,6 +138,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
if (this.plugin.settings.syncOnFileOpen) return true; if (this.plugin.settings.syncOnFileOpen) return true;
if (this.plugin.settings.syncOnSave) return true; if (this.plugin.settings.syncOnSave) return true;
if (this.plugin.settings.syncOnStart) 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 == "CONNECTED") return true;
if (this.plugin.localDatabase.syncStatus == "PAUSED") return true; if (this.plugin.localDatabase.syncStatus == "PAUSED") return true;
return false; return false;
@@ -144,7 +150,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
new Setting(setupWizardEl) new Setting(setupWizardEl)
.setName("Discard the existing configuration and set up") .setName("Discard the existing configuration and set up")
.addButton((text) => { .addButton((text) => {
text.setButtonText("Next").onClick(async () => { text.setButtonText("Next").onClick(() => {
if (JSON.stringify(this.plugin.settings) != JSON.stringify(DEFAULT_SETTINGS)) { if (JSON.stringify(this.plugin.settings) != JSON.stringify(DEFAULT_SETTINGS)) {
this.plugin.localDatabase.closeReplication(); this.plugin.localDatabase.closeReplication();
this.plugin.settings = { ...DEFAULT_SETTINGS }; this.plugin.settings = { ...DEFAULT_SETTINGS };
@@ -170,6 +176,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
this.plugin.settings.syncOnSave = false; this.plugin.settings.syncOnSave = false;
this.plugin.settings.syncOnStart = false; this.plugin.settings.syncOnStart = false;
this.plugin.settings.syncOnFileOpen = false; this.plugin.settings.syncOnFileOpen = false;
this.plugin.settings.syncAfterMerge = false;
this.plugin.localDatabase.closeReplication(); this.plugin.localDatabase.closeReplication();
await this.plugin.saveSettings(); await this.plugin.saveSettings();
containerEl.addClass("isWizard"); containerEl.addClass("isWizard");
@@ -226,7 +233,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
syncLive.forEach((e) => { syncLive.forEach((e) => {
e.setDisabled(false).setTooltip(""); e.setDisabled(false).setTooltip("");
}); });
} else if (this.plugin.settings.syncOnFileOpen || this.plugin.settings.syncOnSave || this.plugin.settings.syncOnStart || this.plugin.settings.periodicReplication) { } else if (this.plugin.settings.syncOnFileOpen || this.plugin.settings.syncOnSave || this.plugin.settings.syncOnStart || this.plugin.settings.periodicReplication || this.plugin.settings.syncAfterMerge) {
syncNonLive.forEach((e) => { syncNonLive.forEach((e) => {
e.setDisabled(false).setTooltip(""); e.setDisabled(false).setTooltip("");
}); });
@@ -289,68 +296,78 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
) )
); );
new Setting(containerRemoteDatabaseEl) const e2e = new Setting(containerRemoteDatabaseEl)
.setName("End to End Encryption") .setName("End to End Encryption")
.setDesc("Encrypt contents on the remote database. If you use the plugin's synchronization feature, enabling this is recommend.") .setDesc("Encrypt contents on the remote database. If you use the plugin's synchronization feature, enabling this is recommend.")
.addToggle((toggle) => .addToggle((toggle) =>
toggle.setValue(this.plugin.settings.workingEncrypt).onChange(async (value) => { toggle.setValue(encrypt).onChange(async (value) => {
if (inWizard) { if (inWizard) {
this.plugin.settings.encrypt = value; this.plugin.settings.encrypt = value;
passphrase.setDisabled(!value); passphraseSetting.setDisabled(!value);
dynamicIteration.setDisabled(!value); dynamicIteration.setDisabled(!value);
await this.plugin.saveSettings(); await this.plugin.saveSettings();
} else { } else {
this.plugin.settings.workingEncrypt = value; encrypt = value;
passphrase.setDisabled(!value); passphraseSetting.setDisabled(!value);
dynamicIteration.setDisabled(!value); dynamicIteration.setDisabled(!value);
await this.plugin.saveSettings(); await this.plugin.saveSettings();
markDirtyControl();
} }
}) })
); );
const passphrase = new Setting(containerRemoteDatabaseEl)
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)
.setName("Passphrase") .setName("Passphrase")
.setDesc("Encrypting passphrase. If you change the passphrase of a existing database, overwriting the remote database is strongly recommended.") .setDesc("Encrypting passphrase. If you change the passphrase of a existing database, overwriting the remote database is strongly recommended.")
.addText((text) => { .addText((text) => {
text.setPlaceholder("") text.setPlaceholder("")
.setValue(this.plugin.settings.workingPassphrase) .setValue(passphrase)
.onChange(async (value) => { .onChange(async (value) => {
if (inWizard) { if (inWizard) {
this.plugin.settings.passphrase = value; this.plugin.settings.passphrase = value;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
} else { } else {
this.plugin.settings.workingPassphrase = value; passphrase = value;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
markDirtyControl();
} }
}); });
text.inputEl.setAttribute("type", "password"); text.inputEl.setAttribute("type", "password");
}); });
passphrase.setDisabled(!this.plugin.settings.workingEncrypt); passphraseSetting.setDisabled(!encrypt);
const dynamicIteration = new Setting(containerRemoteDatabaseEl) const dynamicIteration = new Setting(containerRemoteDatabaseEl)
.setName("Use dynamic iteration count (experimental)") .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)") .setDesc("Balancing the encryption/decryption load against the length of the passphrase if toggled. (v0.17.5 or higher required)")
.addToggle((toggle) => { .addToggle((toggle) => {
toggle.setValue(this.plugin.settings.workingUseDynamicIterationCount) toggle.setValue(useDynamicIterationCount)
.onChange(async (value) => { .onChange(async (value) => {
if (inWizard) { if (inWizard) {
this.plugin.settings.useDynamicIterationCount = value; this.plugin.settings.useDynamicIterationCount = value;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
} else { } else {
this.plugin.settings.workingUseDynamicIterationCount = value; useDynamicIterationCount = value;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
markDirtyControl();
} }
}); });
}) })
.setClass("wizardHidden"); .setClass("wizardHidden");
dynamicIteration.setDisabled(!this.plugin.settings.workingEncrypt); dynamicIteration.setDisabled(!encrypt);
const checkWorkingPassphrase = async (): Promise<boolean> => { const checkWorkingPassphrase = async (): Promise<boolean> => {
const settingForCheck: RemoteDBSettings = { const settingForCheck: RemoteDBSettings = {
...this.plugin.settings, ...this.plugin.settings,
encrypt: this.plugin.settings.workingEncrypt, encrypt: encrypt,
passphrase: this.plugin.settings.workingPassphrase, passphrase: passphrase,
useDynamicIterationCount: this.plugin.settings.workingUseDynamicIterationCount, useDynamicIterationCount: useDynamicIterationCount,
}; };
console.dir(settingForCheck); console.dir(settingForCheck);
const db = await this.plugin.localDatabase.connectRemoteCouchDBWithSetting(settingForCheck, this.plugin.localDatabase.isMobile); const db = await this.plugin.localDatabase.connectRemoteCouchDBWithSetting(settingForCheck, this.plugin.localDatabase.isMobile);
@@ -368,30 +385,32 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
} }
}; };
const applyEncryption = async (sendToServer: boolean) => { const applyEncryption = async (sendToServer: boolean) => {
if (this.plugin.settings.workingEncrypt && this.plugin.settings.workingPassphrase == "") { if (encrypt && passphrase == "") {
Logger("If you enable encryption, you have to set the passphrase", LOG_LEVEL.NOTICE); Logger("If you enable encryption, you have to set the passphrase", LOG_LEVEL.NOTICE);
return; return;
} }
if (this.plugin.settings.workingEncrypt && !(await testCrypt())) { if (encrypt && !(await testCrypt())) {
Logger("WARNING! Your device would not support encryption.", LOG_LEVEL.NOTICE); Logger("WARNING! Your device would not support encryption.", LOG_LEVEL.NOTICE);
return; return;
} }
if (!(await checkWorkingPassphrase()) && !sendToServer) { if (!(await checkWorkingPassphrase()) && !sendToServer) {
return; return;
} }
if (!this.plugin.settings.workingEncrypt) { if (!encrypt) {
this.plugin.settings.workingPassphrase = ""; passphrase = "";
} }
this.plugin.settings.liveSync = false; this.plugin.settings.liveSync = false;
this.plugin.settings.periodicReplication = false; this.plugin.settings.periodicReplication = false;
this.plugin.settings.syncOnSave = false; this.plugin.settings.syncOnSave = false;
this.plugin.settings.syncOnStart = false; this.plugin.settings.syncOnStart = false;
this.plugin.settings.syncOnFileOpen = false; this.plugin.settings.syncOnFileOpen = false;
this.plugin.settings.encrypt = this.plugin.settings.workingEncrypt; this.plugin.settings.syncAfterMerge = false;
this.plugin.settings.passphrase = this.plugin.settings.workingPassphrase; this.plugin.settings.encrypt = encrypt;
this.plugin.settings.useDynamicIterationCount = this.plugin.settings.workingUseDynamicIterationCount; this.plugin.settings.passphrase = passphrase;
this.plugin.settings.useDynamicIterationCount = useDynamicIterationCount;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
markDirtyControl();
if (sendToServer) { if (sendToServer) {
await this.plugin.initializeDatabase(true); await this.plugin.initializeDatabase(true);
await this.plugin.markRemoteLocked(); await this.plugin.markRemoteLocked();
@@ -433,7 +452,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
this.plugin.settings.syncOnSave = false; this.plugin.settings.syncOnSave = false;
this.plugin.settings.syncOnStart = false; this.plugin.settings.syncOnStart = false;
this.plugin.settings.syncOnFileOpen = false; this.plugin.settings.syncOnFileOpen = false;
this.plugin.settings.syncAfterMerge = false;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
applyDisplayEnabled(); applyDisplayEnabled();
@@ -693,7 +712,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
.setButtonText("Next") .setButtonText("Next")
.setClass("mod-cta") .setClass("mod-cta")
.setDisabled(false) .setDisabled(false)
.onClick(async () => { .onClick(() => {
if (!this.plugin.settings.encrypt) { if (!this.plugin.settings.encrypt) {
this.plugin.settings.passphrase = ""; this.plugin.settings.passphrase = "";
} }
@@ -714,7 +733,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
.setButtonText("Discard exist database and proceed") .setButtonText("Discard exist database and proceed")
.setDisabled(false) .setDisabled(false)
.setWarning() .setWarning()
.onClick(async () => { .onClick(() => {
if (!this.plugin.settings.encrypt) { if (!this.plugin.settings.encrypt) {
this.plugin.settings.passphrase = ""; this.plugin.settings.passphrase = "";
} }
@@ -793,7 +812,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
button button
.setButtonText("Next") .setButtonText("Next")
.setDisabled(false) .setDisabled(false)
.onClick(async () => { .onClick(() => {
changeDisplay("40"); changeDisplay("40");
}) })
); );
@@ -949,7 +968,17 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
await this.plugin.saveSettings(); await this.plugin.saveSettings();
applyDisplayEnabled(); 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) new Setting(containerSyncSettingEl)
@@ -1171,8 +1200,8 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
.setValue(this.plugin.settings.customChunkSize + "") .setValue(this.plugin.settings.customChunkSize + "")
.onChange(async (value) => { .onChange(async (value) => {
let v = Number(value); let v = Number(value);
if (isNaN(v) || v < 100) { if (isNaN(v) || v < 1) {
v = 100; v = 1;
} }
this.plugin.settings.customChunkSize = v; this.plugin.settings.customChunkSize = v;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
@@ -1272,6 +1301,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
this.plugin.settings.syncOnSave = false; this.plugin.settings.syncOnSave = false;
this.plugin.settings.syncOnStart = false; this.plugin.settings.syncOnStart = false;
this.plugin.settings.syncOnFileOpen = false; this.plugin.settings.syncOnFileOpen = false;
this.plugin.settings.syncAfterMerge = false;
if (currentPreset == "LIVESYNC") { if (currentPreset == "LIVESYNC") {
this.plugin.settings.liveSync = true; this.plugin.settings.liveSync = true;
Logger("Synchronization setting configured as LiveSync.", LOG_LEVEL.NOTICE); Logger("Synchronization setting configured as LiveSync.", LOG_LEVEL.NOTICE);
@@ -1281,6 +1311,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
this.plugin.settings.syncOnSave = false; this.plugin.settings.syncOnSave = false;
this.plugin.settings.syncOnStart = true; this.plugin.settings.syncOnStart = true;
this.plugin.settings.syncOnFileOpen = 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); Logger("Synchronization setting configured as Periodic sync with batch database update.", LOG_LEVEL.NOTICE);
} else { } else {
Logger("All synchronization disabled.", LOG_LEVEL.NOTICE); Logger("All synchronization disabled.", LOG_LEVEL.NOTICE);
@@ -1309,6 +1340,45 @@ 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` }); const infoApply = containerMiscellaneousEl.createEl("div", { text: `To finish setup, please select one of the presets` });
infoApply.addClass("op-warn-info"); infoApply.addClass("op-warn-info");
infoApply.addClass("wizardOnly") infoApply.addClass("wizardOnly")
@@ -1350,7 +1420,8 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
pluginConfig.couchDB_URI = isCloudantURI(pluginConfig.couchDB_URI) ? "cloudant" : "self-hosted"; pluginConfig.couchDB_URI = isCloudantURI(pluginConfig.couchDB_URI) ? "cloudant" : "self-hosted";
pluginConfig.couchDB_USER = REDACTED; pluginConfig.couchDB_USER = REDACTED;
pluginConfig.passphrase = REDACTED; pluginConfig.passphrase = REDACTED;
pluginConfig.workingPassphrase = REDACTED; pluginConfig.encryptedPassphrase = REDACTED;
pluginConfig.encryptedCouchDBConnection = REDACTED;
const msgConfig = `----remote config---- const msgConfig = `----remote config----
${stringifyYaml(responseConfig)} ${stringifyYaml(responseConfig)}
@@ -1408,7 +1479,7 @@ ${stringifyYaml(pluginConfig)}`;
const releaser = await semaphore.acquire(1, "verifyAndRepair"); const releaser = await semaphore.acquire(1, "verifyAndRepair");
try { try {
Logger(`Update into ${file.path}`); Logger(`UPDATE DATABASE ${file.path}`);
await this.plugin.updateIntoDB(file, false, null, true); await this.plugin.updateIntoDB(file, false, null, true);
i++; i++;
Logger(`${i}/${files.length}\n${file.path}`, LOG_LEVEL.NOTICE, "verify"); Logger(`${i}/${files.length}\n${file.path}`, LOG_LEVEL.NOTICE, "verify");

View File

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

View File

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

Submodule src/lib updated: bfad1f86d3...14fecd2411

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
import { PluginManifest } from "obsidian"; import { PluginManifest, TFile } from "obsidian";
import { DatabaseEntry } from "./lib/src/types"; import { DatabaseEntry, EntryBody } from "./lib/src/types";
export interface PluginDataEntry extends DatabaseEntry { export interface PluginDataEntry extends DatabaseEntry {
deviceVaultName: string; deviceVaultName: string;
@@ -30,3 +30,20 @@ export interface InternalFileInfo {
size: number; size: number;
deleted?: boolean; 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 { normalizePath } from "obsidian";
import { path2id_base, id2path_base } from "./lib/src/utils"; import { path2id_base, id2path_base } from "./lib/src/path";
// For backward compatibility, using the path for determining id. // For backward compatibility, using the path for determining id.
// Only CouchDB unacceptable ID (that starts with an underscore) has been prefixed with "/". // Only CouchDB unacceptable ID (that starts with an underscore) has been prefixed with "/".

View File

@@ -101,7 +101,7 @@
.CodeMirror-wrap::before, .CodeMirror-wrap::before,
.cm-s-obsidian>.cm-editor::before, .cm-s-obsidian>.cm-editor::before,
.canvas-wrapper::before { .canvas-wrapper::before {
content: var(--slsmessage); content: attr(data-log);
text-align: right; text-align: right;
white-space: pre-wrap; white-space: pre-wrap;
position: absolute; position: absolute;
@@ -247,4 +247,8 @@ div.sls-setting-menu-btn {
.sls-setting:not(.isWizard) .wizardOnly { .sls-setting:not(.isWizard) .wizardOnly {
display: none; display: none;
}
.sls-item-dirty::before {
content: "✏";
} }

View File

@@ -35,37 +35,37 @@
- Fixed button styling. - Fixed button styling.
- Changed: - Changed:
- Conflict checking on synchronising has been enabled for every note in default. - 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
- 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.16.0 ... To continue on to `updates_old.md`.
- 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.

View File

@@ -1,3 +1,58 @@
### 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 ### 0.14.1
- The target selecting filter was implemented. - The target selecting filter was implemented.
Now we can set what files are synchronised by regular expression. Now we can set what files are synchronised by regular expression.