mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-05-24 16:21:34 +00:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c36ec497c | ||
|
|
677895547c | ||
|
|
aec0b2986b | ||
|
|
e6025b92d8 | ||
|
|
fad9fed5ca | ||
|
|
e46246cd63 | ||
|
|
0f3be19dd7 | ||
|
|
bc568ff479 | ||
|
|
2fdc7669f3 | ||
|
|
ec8d9785ed | ||
|
|
71a80cacc3 | ||
|
|
38daeca89f | ||
|
|
7ec64a6a93 | ||
|
|
c5c6deb742 | ||
|
|
ef57fbfdda | ||
|
|
bc158e9f2b |
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "obsidian-livesync",
|
"id": "obsidian-livesync",
|
||||||
"name": "Self-hosted LiveSync",
|
"name": "Self-hosted LiveSync",
|
||||||
"version": "0.17.11",
|
"version": "0.17.20",
|
||||||
"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",
|
||||||
|
|||||||
33
package-lock.json
generated
33
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "obsidian-livesync",
|
"name": "obsidian-livesync",
|
||||||
"version": "0.17.11",
|
"version": "0.17.20",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "obsidian-livesync",
|
"name": "obsidian-livesync",
|
||||||
"version": "0.17.11",
|
"version": "0.17.20",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"diff-match-patch": "^1.0.5",
|
"diff-match-patch": "^1.0.5",
|
||||||
@@ -33,6 +33,7 @@
|
|||||||
"postcss-load-config": "^4.0.1",
|
"postcss-load-config": "^4.0.1",
|
||||||
"pouchdb-adapter-http": "^8.0.0",
|
"pouchdb-adapter-http": "^8.0.0",
|
||||||
"pouchdb-adapter-idb": "^8.0.0",
|
"pouchdb-adapter-idb": "^8.0.0",
|
||||||
|
"pouchdb-adapter-indexeddb": "^8.0.0",
|
||||||
"pouchdb-core": "^8.0.0",
|
"pouchdb-core": "^8.0.0",
|
||||||
"pouchdb-find": "^8.0.0",
|
"pouchdb-find": "^8.0.0",
|
||||||
"pouchdb-mapreduce": "^8.0.0",
|
"pouchdb-mapreduce": "^8.0.0",
|
||||||
@@ -2889,6 +2890,20 @@
|
|||||||
"pouchdb-utils": "8.0.0"
|
"pouchdb-utils": "8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pouchdb-adapter-indexeddb": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pouchdb-adapter-indexeddb/-/pouchdb-adapter-indexeddb-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-h+vMPspVF6s4IKzLSys7iGDlANWkow77hJV/MX6JIftrjj/QS5jShSzhGCAR9HpLtuAVwQQM+k4hQodGnoAGWw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"pouchdb-adapter-utils": "8.0.0",
|
||||||
|
"pouchdb-binary-utils": "8.0.0",
|
||||||
|
"pouchdb-errors": "8.0.0",
|
||||||
|
"pouchdb-md5": "8.0.0",
|
||||||
|
"pouchdb-merge": "8.0.0",
|
||||||
|
"pouchdb-utils": "8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pouchdb-adapter-utils": {
|
"node_modules/pouchdb-adapter-utils": {
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/pouchdb-adapter-utils/-/pouchdb-adapter-utils-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/pouchdb-adapter-utils/-/pouchdb-adapter-utils-8.0.0.tgz",
|
||||||
@@ -5841,6 +5856,20 @@
|
|||||||
"pouchdb-utils": "8.0.0"
|
"pouchdb-utils": "8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"pouchdb-adapter-indexeddb": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pouchdb-adapter-indexeddb/-/pouchdb-adapter-indexeddb-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-h+vMPspVF6s4IKzLSys7iGDlANWkow77hJV/MX6JIftrjj/QS5jShSzhGCAR9HpLtuAVwQQM+k4hQodGnoAGWw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"pouchdb-adapter-utils": "8.0.0",
|
||||||
|
"pouchdb-binary-utils": "8.0.0",
|
||||||
|
"pouchdb-errors": "8.0.0",
|
||||||
|
"pouchdb-md5": "8.0.0",
|
||||||
|
"pouchdb-merge": "8.0.0",
|
||||||
|
"pouchdb-utils": "8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"pouchdb-adapter-utils": {
|
"pouchdb-adapter-utils": {
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/pouchdb-adapter-utils/-/pouchdb-adapter-utils-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/pouchdb-adapter-utils/-/pouchdb-adapter-utils-8.0.0.tgz",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "obsidian-livesync",
|
"name": "obsidian-livesync",
|
||||||
"version": "0.17.11",
|
"version": "0.17.20",
|
||||||
"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",
|
||||||
@@ -30,6 +30,7 @@
|
|||||||
"postcss-load-config": "^4.0.1",
|
"postcss-load-config": "^4.0.1",
|
||||||
"pouchdb-adapter-http": "^8.0.0",
|
"pouchdb-adapter-http": "^8.0.0",
|
||||||
"pouchdb-adapter-idb": "^8.0.0",
|
"pouchdb-adapter-idb": "^8.0.0",
|
||||||
|
"pouchdb-adapter-indexeddb": "^8.0.0",
|
||||||
"pouchdb-core": "^8.0.0",
|
"pouchdb-core": "^8.0.0",
|
||||||
"pouchdb-find": "^8.0.0",
|
"pouchdb-find": "^8.0.0",
|
||||||
"pouchdb-mapreduce": "^8.0.0",
|
"pouchdb-mapreduce": "^8.0.0",
|
||||||
|
|||||||
@@ -6,12 +6,14 @@ 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 = "";
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ 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 { isErrorOfMissingDoc } from "./lib/src/utils_couchdb";
|
||||||
import { getDocData } from "./lib/src/utils";
|
import { getDocData } from "./lib/src/utils";
|
||||||
|
|
||||||
export class DocumentHistoryModal extends Modal {
|
export class DocumentHistoryModal extends Modal {
|
||||||
@@ -35,13 +36,13 @@ export class DocumentHistoryModal extends Modal {
|
|||||||
const db = this.plugin.localDatabase;
|
const db = this.plugin.localDatabase;
|
||||||
try {
|
try {
|
||||||
const w = await db.localDatabase.get(path2id(this.file), { revs_info: true });
|
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.max = `${this.revs_info.length - 1}`;
|
||||||
this.range.value = this.range.max;
|
this.range.value = this.range.max;
|
||||||
this.fileInfo.setText(`${this.file} / ${this.revs_info.length} revisions`);
|
this.fileInfo.setText(`${this.file} / ${this.revs_info.length} revisions`);
|
||||||
await this.loadRevs();
|
await this.loadRevs();
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
if (ex.status && ex.status == 404) {
|
if (isErrorOfMissingDoc(ex)) {
|
||||||
this.range.max = "0";
|
this.range.max = "0";
|
||||||
this.range.value = "";
|
this.range.value = "";
|
||||||
this.range.disabled = true;
|
this.range.disabled = true;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { KeyValueDatabase, OpenKeyValueDatabase } from "./KeyValueDB.js";
|
|||||||
import { LocalPouchDBBase } from "./lib/src/LocalPouchDBBase.js";
|
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, ObsidianLiveSyncSettings } from "./lib/src/types.js";
|
||||||
import { enableEncryption } from "./lib/src/utils_couchdb.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";
|
||||||
@@ -11,6 +11,7 @@ import { id2path, path2id } from "./utils.js";
|
|||||||
export class LocalPouchDB extends LocalPouchDBBase {
|
export class LocalPouchDB extends LocalPouchDBBase {
|
||||||
|
|
||||||
kvDB: KeyValueDatabase;
|
kvDB: KeyValueDatabase;
|
||||||
|
settings: ObsidianLiveSyncSettings;
|
||||||
id2path(filename: string): string {
|
id2path(filename: string): string {
|
||||||
return id2path(filename);
|
return id2path(filename);
|
||||||
}
|
}
|
||||||
@@ -18,6 +19,10 @@ export class LocalPouchDB extends LocalPouchDBBase {
|
|||||||
return path2id(filename);
|
return path2id(filename);
|
||||||
}
|
}
|
||||||
CreatePouchDBInstance<T>(name?: string, options?: PouchDB.Configuration.DatabaseConfiguration): PouchDB.Database<T> {
|
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);
|
return new PouchDB(name, options);
|
||||||
}
|
}
|
||||||
beforeOnUnload(): void {
|
beforeOnUnload(): void {
|
||||||
@@ -52,7 +57,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 +159,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 {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
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 } from "./lib/src/utils";
|
import { delay } from "./lib/src/utils";
|
||||||
import { Semaphore } from "./lib/src/semaphore";
|
import { Semaphore } from "./lib/src/semaphore";
|
||||||
@@ -43,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();
|
||||||
|
|
||||||
@@ -293,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);
|
||||||
@@ -372,19 +385,19 @@ 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;
|
||||||
@@ -392,11 +405,12 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
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.settings.syncAfterMerge = false;
|
||||||
this.plugin.settings.encrypt = this.plugin.settings.workingEncrypt;
|
this.plugin.settings.encrypt = encrypt;
|
||||||
this.plugin.settings.passphrase = this.plugin.settings.workingPassphrase;
|
this.plugin.settings.passphrase = passphrase;
|
||||||
this.plugin.settings.useDynamicIterationCount = this.plugin.settings.workingUseDynamicIterationCount;
|
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();
|
||||||
@@ -803,6 +817,23 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
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);
|
addScreenElement("10", containerLocalDatabaseEl);
|
||||||
const containerGeneralSettingsEl = containerEl.createDiv();
|
const containerGeneralSettingsEl = containerEl.createDiv();
|
||||||
containerGeneralSettingsEl.createEl("h3", { text: "General Settings" });
|
containerGeneralSettingsEl.createEl("h3", { text: "General Settings" });
|
||||||
@@ -852,6 +883,14 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
});
|
});
|
||||||
text.inputEl.setAttribute("type", "number");
|
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);
|
addScreenElement("20", containerGeneralSettingsEl);
|
||||||
@@ -1025,7 +1064,6 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
new Setting(containerSyncSettingEl)
|
new Setting(containerSyncSettingEl)
|
||||||
.setName("Sync hidden files")
|
.setName("Sync hidden files")
|
||||||
.addToggle((toggle) =>
|
.addToggle((toggle) =>
|
||||||
@@ -1034,14 +1072,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
await this.plugin.saveSettings();
|
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)
|
new Setting(containerSyncSettingEl)
|
||||||
.setName("Scan for hidden files before replication")
|
.setName("Scan for hidden files before replication")
|
||||||
.setDesc("This configuration will be ignored if monitoring changes is enabled.")
|
.setDesc("This configuration will be ignored if monitoring changes is enabled.")
|
||||||
@@ -1326,6 +1357,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")
|
||||||
@@ -1367,7 +1437,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)}
|
||||||
@@ -1497,7 +1568,7 @@ ${stringifyYaml(pluginConfig)}`;
|
|||||||
|
|
||||||
new Setting(containerPluginSettings)
|
new Setting(containerPluginSettings)
|
||||||
.setName("Scan plugins periodically")
|
.setName("Scan plugins periodically")
|
||||||
.setDesc("Scan plugins every 1 minute.")
|
.setDesc("Scan plugins every 1 minute. This configuration will be ignored if monitoring changes of hidden files has been enabled.")
|
||||||
.addToggle((toggle) =>
|
.addToggle((toggle) =>
|
||||||
toggle.setValue(this.plugin.settings.autoSweepPluginsPeriodic).onChange(async (value) => {
|
toggle.setValue(this.plugin.settings.autoSweepPluginsPeriodic).onChange(async (value) => {
|
||||||
this.plugin.settings.autoSweepPluginsPeriodic = value;
|
this.plugin.settings.autoSweepPluginsPeriodic = value;
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
2
src/lib
2
src/lib
Submodule src/lib updated: 133bae3607...9e993fd984
946
src/main.ts
946
src/main.ts
File diff suppressed because it is too large
Load Diff
11
src/types.ts
11
src/types.ts
@@ -1,4 +1,4 @@
|
|||||||
import { PluginManifest } from "obsidian";
|
import { PluginManifest, TFile } from "obsidian";
|
||||||
import { DatabaseEntry, EntryBody } from "./lib/src/types";
|
import { DatabaseEntry, EntryBody } from "./lib/src/types";
|
||||||
|
|
||||||
export interface PluginDataEntry extends DatabaseEntry {
|
export interface PluginDataEntry extends DatabaseEntry {
|
||||||
@@ -31,6 +31,15 @@ export interface InternalFileInfo {
|
|||||||
deleted?: boolean;
|
deleted?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FileInfo {
|
||||||
|
path: string;
|
||||||
|
mtime: number;
|
||||||
|
ctime: number;
|
||||||
|
size: number;
|
||||||
|
deleted?: boolean;
|
||||||
|
file: TFile;
|
||||||
|
}
|
||||||
|
|
||||||
export type queueItem = {
|
export type queueItem = {
|
||||||
entry: EntryBody;
|
entry: EntryBody;
|
||||||
missingChildren: string[];
|
missingChildren: string[];
|
||||||
|
|||||||
@@ -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: "✏";
|
||||||
}
|
}
|
||||||
70
updates.md
70
updates.md
@@ -51,37 +51,43 @@
|
|||||||
- Now `Chunk size` can be set to under one hundred.
|
- Now `Chunk size` can be set to under one hundred.
|
||||||
- New feature:
|
- New feature:
|
||||||
- The number of transfers required before replication stabilises is now displayed.
|
- 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.16.0
|
- 0.17.16:
|
||||||
- Now hidden files need not be scanned. Changes will be detected automatically.
|
- Improved:
|
||||||
- If you want it to back to its previous behaviour, please disable `Monitor changes to internal files`.
|
- Plugins and their settings no longer need scanning if changes are monitored.
|
||||||
- Due to using an internal API, this feature may become unusable with a major update. If this happens, please disable this once.
|
- 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.17.20
|
||||||
|
- Improved: Changes of hidden files will be notified to Obsidian.
|
||||||
|
|
||||||
#### Minors
|
... To continue on to `updates_old.md`.
|
||||||
|
|
||||||
- 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.
|
|
||||||
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user