Compare commits

...

4 Commits

Author SHA1 Message Date
vorotamoroz
61b65b0461 Bumped 2022-07-28 19:20:59 +09:00
vorotamoroz
ac9be937b4 New feature:
- The metadata of the deleted files will be kept on the database by default.
- We can see the history of deleted files.
- Update information became to be shown on the major upgrade.

Fixed:
- `Pick file to show` was renamed to `Pick a file to show.
- Files in the `Pick a file to show` are now ordered by their modified date descent.
2022-07-28 19:19:37 +09:00
vorotamoroz
c610284cab bumped again 2022-07-25 13:50:42 +09:00
vorotamoroz
2e6ed4777c Added postcss for ci 2022-07-25 13:22:37 +09:00
12 changed files with 520 additions and 194 deletions

View File

@@ -13,6 +13,7 @@ if you want to view the source, please visit the github repository of this plugi
const prod = process.argv[2] === "production";
const manifestJson = JSON.parse(fs.readFileSync("./manifest.json"));
const packageJson = JSON.parse(fs.readFileSync("./package.json"));
const updateInfo = JSON.stringify(fs.readFileSync("./updates.md") + "");
esbuild
.build({
banner: {
@@ -23,6 +24,7 @@ esbuild
define: {
"MANIFEST_VERSION": `"${manifestJson.version}"`,
"PACKAGE_VERSION": `"${packageJson.version}"`,
"UPDATE_INFO": `${updateInfo}`,
},
external: ["obsidian", "electron", ...builtins],
format: "cjs",

View File

@@ -1,7 +1,7 @@
{
"id": "obsidian-livesync",
"name": "Self-hosted LiveSync",
"version": "0.12.3",
"version": "0.13.0",
"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",

189
package-lock.json generated
View File

@@ -1,19 +1,18 @@
{
"name": "obsidian-livesync",
"version": "0.12.3",
"version": "0.13.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "obsidian-livesync",
"version": "0.12.3",
"version": "0.13.0",
"license": "MIT",
"dependencies": {
"diff-match-patch": "^1.0.5",
"esbuild": "0.13.12",
"esbuild-svelte": "^0.7.0",
"idb": "^7.0.2",
"svelte-preprocess": "^4.10.5",
"xxhash-wasm": "^0.4.2"
},
"devDependencies": {
@@ -32,8 +31,11 @@
"eslint-config-airbnb-base": "^14.2.1",
"eslint-plugin-import": "^2.25.2",
"obsidian": "^0.15.4",
"postcss": "^8.4.14",
"postcss-load-config": "^3.1.4",
"rollup": "^2.32.1",
"svelte-preprocess": "^4.10.5",
"svelte": "^3.49.0",
"svelte-preprocess": "^4.10.7",
"tslib": "^2.2.0",
"typescript": "^4.2.4"
}
@@ -2452,6 +2454,15 @@
"node": ">= 0.8.0"
}
},
"node_modules/lilconfig": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz",
"integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==",
"dev": true,
"engines": {
"node": ">=10"
}
},
"node_modules/locate-path": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
@@ -2574,6 +2585,18 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"node_modules/nanoid": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"dev": true,
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -2774,6 +2797,12 @@
"node": ">=8"
}
},
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
"dev": true
},
"node_modules/picomatch": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
@@ -2798,6 +2827,59 @@
"node": ">=4"
}
},
"node_modules/postcss": {
"version": "8.4.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
"integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
}
],
"dependencies": {
"nanoid": "^3.3.4",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/postcss-load-config": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz",
"integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==",
"dev": true,
"dependencies": {
"lilconfig": "^2.0.5",
"yaml": "^1.10.2"
},
"engines": {
"node": ">= 10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
"peerDependencies": {
"postcss": ">=8.0.9",
"ts-node": ">=9.0.0"
},
"peerDependenciesMeta": {
"postcss": {
"optional": true
},
"ts-node": {
"optional": true
}
}
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -3066,6 +3148,15 @@
"sorcery": "bin/index.js"
}
},
"node_modules/source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/sourcemap-codec": {
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
@@ -3183,19 +3274,18 @@
}
},
"node_modules/svelte": {
"version": "3.46.4",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.46.4.tgz",
"integrity": "sha512-qKJzw6DpA33CIa+C/rGp4AUdSfii0DOTCzj/2YpSKKayw5WGSS624Et9L1nU1k2OVRS9vaENQXp2CVZNU+xvIg==",
"version": "3.49.0",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.49.0.tgz",
"integrity": "sha512-+lmjic1pApJWDfPCpUUTc1m8azDqYCG1JN9YEngrx/hUyIcFJo6VZhj0A1Ai0wqoHcEIuQy+e9tk+4uDgdtsFA==",
"dev": true,
"peer": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/svelte-preprocess": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-4.10.5.tgz",
"integrity": "sha512-VKXPRScCzAZqeBZOGq4LLwtNrAu++mVn7XvQox3eFDV7Ciq0Lg70Q8QWjH9iXF7J+pMlXhPsSFwpCb2E+hoeyA==",
"version": "4.10.7",
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-4.10.7.tgz",
"integrity": "sha512-sNPBnqYD6FnmdBrUmBCaqS00RyCsCpj2BG58A1JBswNF7b0OKviwxqVrOL/CKyJrLSClrSeqQv5BXNg2RUbPOw==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
@@ -3214,7 +3304,7 @@
"coffeescript": "^2.5.1",
"less": "^3.11.3 || ^4.0.0",
"postcss": "^7 || ^8",
"postcss-load-config": "^2.1.0 || ^3.0.0",
"postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0",
"pug": "^3.0.0",
"sass": "^1.26.8",
"stylus": "^0.55.0",
@@ -3483,6 +3573,15 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/yaml": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
"dev": true,
"engines": {
"node": ">= 6"
}
}
},
"dependencies": {
@@ -5298,6 +5397,12 @@
"type-check": "~0.4.0"
}
},
"lilconfig": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz",
"integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==",
"dev": true
},
"locate-path": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
@@ -5396,6 +5501,12 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"nanoid": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"dev": true
},
"natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -5544,6 +5655,12 @@
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
"dev": true
},
"picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
"dev": true
},
"picomatch": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
@@ -5559,6 +5676,27 @@
"find-up": "^2.1.0"
}
},
"postcss": {
"version": "8.4.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
"integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
"dev": true,
"requires": {
"nanoid": "^3.3.4",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
}
},
"postcss-load-config": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz",
"integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==",
"dev": true,
"requires": {
"lilconfig": "^2.0.5",
"yaml": "^1.10.2"
}
},
"prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -5731,6 +5869,12 @@
"sourcemap-codec": "^1.3.0"
}
},
"source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"dev": true
},
"sourcemap-codec": {
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
@@ -5821,16 +5965,15 @@
}
},
"svelte": {
"version": "3.46.4",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.46.4.tgz",
"integrity": "sha512-qKJzw6DpA33CIa+C/rGp4AUdSfii0DOTCzj/2YpSKKayw5WGSS624Et9L1nU1k2OVRS9vaENQXp2CVZNU+xvIg==",
"dev": true,
"peer": true
"version": "3.49.0",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.49.0.tgz",
"integrity": "sha512-+lmjic1pApJWDfPCpUUTc1m8azDqYCG1JN9YEngrx/hUyIcFJo6VZhj0A1Ai0wqoHcEIuQy+e9tk+4uDgdtsFA==",
"dev": true
},
"svelte-preprocess": {
"version": "4.10.5",
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-4.10.5.tgz",
"integrity": "sha512-VKXPRScCzAZqeBZOGq4LLwtNrAu++mVn7XvQox3eFDV7Ciq0Lg70Q8QWjH9iXF7J+pMlXhPsSFwpCb2E+hoeyA==",
"version": "4.10.7",
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-4.10.7.tgz",
"integrity": "sha512-sNPBnqYD6FnmdBrUmBCaqS00RyCsCpj2BG58A1JBswNF7b0OKviwxqVrOL/CKyJrLSClrSeqQv5BXNg2RUbPOw==",
"dev": true,
"requires": {
"@types/pug": "^2.0.4",
@@ -6023,6 +6166,12 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"yaml": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
"dev": true
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "obsidian-livesync",
"version": "0.12.3",
"version": "0.13.0",
"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",
@@ -28,8 +28,11 @@
"eslint-config-airbnb-base": "^14.2.1",
"eslint-plugin-import": "^2.25.2",
"obsidian": "^0.15.4",
"postcss": "^8.4.14",
"postcss-load-config": "^3.1.4",
"rollup": "^2.32.1",
"svelte-preprocess": "^4.10.5",
"svelte": "^3.49.0",
"svelte-preprocess": "^4.10.7",
"tslib": "^2.2.0",
"typescript": "^4.2.4"
},
@@ -37,7 +40,6 @@
"diff-match-patch": "^1.0.5",
"esbuild": "0.13.12",
"esbuild-svelte": "^0.7.0",
"svelte-preprocess": "^4.10.5",
"idb": "^7.0.2",
"xxhash-wasm": "^0.4.2"
}

View File

@@ -19,6 +19,7 @@ export class DocumentHistoryModal extends Modal {
revs_info: PouchDB.Core.RevisionInfo[] = [];
currentDoc: LoadedEntry;
currentText = "";
currentDeleted = false;
constructor(app: App, plugin: ObsidianLiveSyncPlugin, file: TFile | string) {
super(app);
@@ -41,10 +42,11 @@ export class DocumentHistoryModal extends Modal {
const db = this.plugin.localDatabase;
const index = this.revs_info.length - 1 - (this.range.value as any) / 1;
const rev = this.revs_info[index];
const w = await db.getDBEntry(path2id(this.file), { rev: rev.rev }, false, false);
const w = await db.getDBEntry(path2id(this.file), { rev: rev.rev }, false, false, true);
this.currentText = "";
this.currentDeleted = false;
if (w === false) {
this.currentDeleted = true;
this.info.innerHTML = "";
this.contentView.innerHTML = `Could not read this revision<br>(${rev.rev})`;
} else {
@@ -52,13 +54,13 @@ export class DocumentHistoryModal extends Modal {
this.info.innerHTML = `Modified:${new Date(w.mtime).toLocaleString()}`;
let result = "";
const w1data = w.datatype == "plain" ? w.data : base64ToString(w.data);
this.currentDeleted = w.deleted;
this.currentText = w1data;
if (this.showDiff) {
const prevRevIdx = this.revs_info.length - 1 - ((this.range.value as any) / 1 - 1);
if (prevRevIdx >= 0 && prevRevIdx < this.revs_info.length) {
const oldRev = this.revs_info[prevRevIdx].rev;
const w2 = await db.getDBEntry(path2id(this.file), { rev: oldRev }, false, false);
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" ? w2.data : base64ToString(w2.data);
@@ -86,7 +88,8 @@ export class DocumentHistoryModal extends Modal {
} else {
result = escapeStringToHTML(w1data);
}
this.contentView.innerHTML = result;
this.contentView.innerHTML = (this.currentDeleted ? "(At this revision, the file has been deleted)\n" : "") + result;
}
}

View File

@@ -35,8 +35,8 @@ import { LRUCache } from "./lib/src/LRUCache";
const currentVersionRange: ChunkVersionRange = {
min: 0,
max: 1,
current: 1,
max: 2,
current: 2,
}
type ReplicationCallback = (e: PouchDB.Core.ExistingDocument<EntryDoc>[]) => Promise<void>;
@@ -69,7 +69,9 @@ export class LocalPouchDB {
isMobile = false;
chunkVersion = 0;
chunkVersion = -1;
maxChunkVersion = -1;
minChunkVersion = -1;
cancelHandler<T extends PouchDB.Core.Changes<EntryDoc> | PouchDB.Replication.Sync<EntryDoc> | PouchDB.Replication.Replication<EntryDoc>>(handler: T): T {
if (handler != null) {
@@ -296,7 +298,7 @@ export class LocalPouchDB {
}
}
async getDBEntryMeta(path: string, opt?: PouchDB.Core.GetOptions): Promise<false | LoadedEntry> {
async getDBEntryMeta(path: string, opt?: PouchDB.Core.GetOptions, includeDeleted = false): Promise<false | LoadedEntry> {
const id = path2id(path);
try {
let obj: EntryDocResponse = null;
@@ -306,6 +308,7 @@ export class LocalPouchDB {
obj = await this.localDatabase.get(id);
}
const deleted = "deleted" in obj ? obj.deleted : undefined;
if (!includeDeleted && deleted) return false;
if (obj.type && obj.type == "leaf") {
//do nothing for leaf;
return false;
@@ -326,7 +329,7 @@ export class LocalPouchDB {
ctime: note.ctime,
mtime: note.mtime,
size: note.size,
_deleted: obj._deleted,
// _deleted: obj._deleted,
_rev: obj._rev,
_conflicts: obj._conflicts,
children: children,
@@ -344,7 +347,7 @@ export class LocalPouchDB {
}
return false;
}
async getDBEntry(path: string, opt?: PouchDB.Core.GetOptions, dump = false, waitForReady = true): Promise<false | LoadedEntry> {
async getDBEntry(path: string, opt?: PouchDB.Core.GetOptions, dump = false, waitForReady = true, includeDeleted = false): Promise<false | LoadedEntry> {
const id = path2id(path);
try {
let obj: EntryDocResponse = null;
@@ -354,7 +357,7 @@ export class LocalPouchDB {
obj = await this.localDatabase.get(id);
}
const deleted = "deleted" in obj ? obj.deleted : undefined;
if (!includeDeleted && deleted) return false;
if (obj.type && obj.type == "leaf") {
//do nothing for leaf;
return false;
@@ -369,7 +372,7 @@ export class LocalPouchDB {
ctime: note.ctime,
mtime: note.mtime,
size: note.size,
_deleted: obj._deleted,
// _deleted: obj._deleted,
_rev: obj._rev,
_conflicts: obj._conflicts,
children: [],
@@ -415,7 +418,7 @@ export class LocalPouchDB {
ctime: obj.ctime,
mtime: obj.mtime,
size: obj.size,
_deleted: obj._deleted,
// _deleted: obj._deleted,
_rev: obj._rev,
children: obj.children,
datatype: obj.type,
@@ -476,7 +479,11 @@ export class LocalPouchDB {
// simple note
}
if (obj.type == "newnote" || obj.type == "plain") {
obj._deleted = true;
obj.deleted = true;
if (this.settings.deleteMetadataOfDeletedFiles) {
obj._deleted = true;
}
obj.mtime = Date.now();
const r = await this.localDatabase.put(obj);
Logger(`entry removed:${obj._id}-${r.rev}`);
if (typeof this.corruptedEntries[obj._id] != "undefined") {
@@ -528,7 +535,15 @@ export class LocalPouchDB {
try {
await runWithLock("file:" + v, false, async () => {
const item = await this.localDatabase.get(v);
item._deleted = true;
if (item.type == "newnote" || item.type == "plain") {
item.deleted = true;
if (this.settings.deleteMetadataOfDeletedFiles) {
item._deleted = true;
}
item.mtime = Date.now();
} else {
item._deleted = true;
}
await this.localDatabase.put(item);
});
@@ -777,16 +792,31 @@ export class LocalPouchDB {
remoteMilestone.node_chunk_info = { ...defMilestonePoint.node_chunk_info, ...remoteMilestone.node_chunk_info };
this.remoteLocked = remoteMilestone.locked;
this.remoteLockedAndDeviceNotAccepted = remoteMilestone.locked && remoteMilestone.accepted_nodes.indexOf(this.nodeid) == -1;
const writeMilestone = ((remoteMilestone.node_chunk_info[this.nodeid].min != currentVersionRange.min || remoteMilestone.node_chunk_info[this.nodeid].max != currentVersionRange.max)
const writeMilestone = (
(
remoteMilestone.node_chunk_info[this.nodeid].min != currentVersionRange.min
|| remoteMilestone.node_chunk_info[this.nodeid].max != currentVersionRange.max
)
|| typeof remoteMilestone._rev == "undefined");
if (writeMilestone) {
remoteMilestone.node_chunk_info[this.nodeid].min = currentVersionRange.min;
remoteMilestone.node_chunk_info[this.nodeid].max = currentVersionRange.max;
await dbret.db.put(remoteMilestone);
}
// Check compatibility and make sure available version
//
// v min of A v max of A
// | v min of B | v max of B
// | | | |
// | |<--- We can use --->| |
// | | | |
//If globalMin and globalMax is suitable, we can upgrade.
let globalMin = currentVersionRange.min;
let globalMax = currentVersionRange.max;
for (const nodeid of remoteMilestone.accepted_nodes) {
if (nodeid == this.nodeid) continue;
if (nodeid in remoteMilestone.node_chunk_info) {
const nodeinfo = remoteMilestone.node_chunk_info[nodeid];
globalMin = Math.max(nodeinfo.min, globalMin);
@@ -796,7 +826,15 @@ export class LocalPouchDB {
globalMax = 0;
}
}
//If globalMin and globalMax is suitable, we can upgrade.
this.maxChunkVersion = globalMax;
this.minChunkVersion = globalMin;
if (this.chunkVersion >= 0 && (globalMin > this.chunkVersion || globalMax < this.chunkVersion)) {
if (!setting.ignoreVersionCheck) {
Logger("The remote database has no compatibility with the running version. Please upgrade the plugin.", LOG_LEVEL.NOTICE);
return false;
}
}
if (remoteMilestone.locked && remoteMilestone.accepted_nodes.indexOf(this.nodeid) == -1) {
Logger("The remote database has been rebuilt or corrupted since we have synchronized last time. Fetch rebuilt DB or explicit unlocking is required. See the settings dialog.", LOG_LEVEL.NOTICE);
@@ -1242,4 +1280,12 @@ export class LocalPouchDB {
});
return;
}
isVersionUpgradable(ver: number) {
if (this.maxChunkVersion < 0) return false;
if (this.minChunkVersion < 0) return false;
if (this.maxChunkVersion > 0 && this.maxChunkVersion < ver) return false;
if (this.minChunkVersion > 0 && this.minChunkVersion > ver) return false;
return true;
}
}

View File

@@ -1,7 +1,7 @@
import { App, PluginSettingTab, Setting, sanitizeHTMLToDom, RequestUrlParam, requestUrl, TextAreaComponent } from "obsidian";
import { App, PluginSettingTab, Setting, sanitizeHTMLToDom, RequestUrlParam, requestUrl, TextAreaComponent, MarkdownRenderer } from "obsidian";
import { EntryDoc, LOG_LEVEL, RemoteDBSettings } from "./lib/src/types";
import { path2id, id2path } from "./utils";
import { delay, runWithLock } from "./lib/src/utils";
import { delay, runWithLock, versionNumberString2Number } from "./lib/src/utils";
import { Logger } from "./lib/src/logger";
import { checkSyncInfo, connectRemoteCouchDBWithSetting } from "./utils_couchdb";
import { testCrypt } from "./lib/src/e2ee_v2";
@@ -39,7 +39,8 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
};
w.addClass("sls-setting-menu");
w.innerHTML = `
<label class='sls-setting-label selected'><input type='radio' name='disp' value='0' class='sls-setting-tab' checked><div class='sls-setting-menu-btn'>🛰️</div></label>
<label class='sls-setting-label selected'><input type='radio' name='disp' value='100' class='sls-setting-tab' checked><div class='sls-setting-menu-btn'>💬</div></label>
<label class='sls-setting-label'><input type='radio' name='disp' value='0' class='sls-setting-tab' ><div class='sls-setting-menu-btn'>🛰️</div></label>
<label class='sls-setting-label'><input type='radio' name='disp' value='10' class='sls-setting-tab' ><div class='sls-setting-menu-btn'>📦</div></label>
<label class='sls-setting-label'><input type='radio' name='disp' value='20' class='sls-setting-tab' ><div class='sls-setting-menu-btn'>⚙️</div></label>
<label class='sls-setting-label'><input type='radio' name='disp' value='30' class='sls-setting-tab' ><div class='sls-setting-menu-btn'>🔁</div></label>
@@ -68,6 +69,34 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
});
});
const containerInformationEl = containerEl.createDiv();
const h3El = containerInformationEl.createEl("h3", { text: "Updates" });
const informationDivEl = containerInformationEl.createEl("div", { text: "" });
//@ts-ignore
const manifestVersion: string = MANIFEST_VERSION || "-";
//@ts-ignore
const updateInformation: string = UPDATE_INFO || "";
const lastVersion = ~~(versionNumberString2Number(manifestVersion) / 1000);
const tmpDiv = createSpan();
tmpDiv.addClass("sls-header-button");
tmpDiv.innerHTML = `<button> OK, I read all. </button>`;
if (lastVersion > this.plugin.settings.lastReadUpdates) {
const informationButtonDiv = h3El.appendChild(tmpDiv);
informationButtonDiv.querySelector("button").addEventListener("click", async () => {
this.plugin.settings.lastReadUpdates = lastVersion;
await this.plugin.saveSettings();
informationButtonDiv.remove();
});
}
MarkdownRenderer.renderMarkdown(updateInformation, informationDivEl, "/", null);
addScreenElement("100", containerInformationEl);
const containerRemoteDatabaseEl = containerEl.createDiv();
containerRemoteDatabaseEl.createEl("h3", { text: "Remote Database configuration" });
const syncWarn = containerRemoteDatabaseEl.createEl("div", { text: `These settings are kept locked while automatic synchronization options are enabled. Disable these options in the "Sync Settings" tab to unlock.` });
@@ -639,6 +668,15 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
await this.plugin.saveSettings();
})
);
new Setting(containerGeneralSettingsEl)
.setName("Delete metadata of deleted files.")
.addToggle((toggle) => {
toggle.setValue(this.plugin.settings.deleteMetadataOfDeletedFiles).onChange(async (value) => {
this.plugin.settings.deleteMetadataOfDeletedFiles = value;
await this.plugin.saveSettings();
})
}
);
addScreenElement("20", containerGeneralSettingsEl);
const containerSyncSettingEl = containerEl.createDiv();
@@ -1295,6 +1333,10 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
}
applyDisplayEnabled();
addScreenElement("70", containerCorruptedDataEl);
changeDisplay("0");
if (lastVersion != this.plugin.settings.lastReadUpdates) {
changeDisplay("100");
} else {
changeDisplay("0");
}
}
}

126
src/dialogs.ts Normal file
View File

@@ -0,0 +1,126 @@
import { App, FuzzySuggestModal, Modal, Setting } from "obsidian";
import ObsidianLiveSyncPlugin from "./main";
//@ts-ignore
import PluginPane from "./PluginPane.svelte";
export class PluginDialogModal extends Modal {
plugin: ObsidianLiveSyncPlugin;
logEl: HTMLDivElement;
component: PluginPane = null;
constructor(app: App, plugin: ObsidianLiveSyncPlugin) {
super(app);
this.plugin = plugin;
}
onOpen() {
const { contentEl } = this;
if (this.component == null) {
this.component = new PluginPane({
target: contentEl,
props: { plugin: this.plugin },
});
}
}
onClose() {
if (this.component != null) {
this.component.$destroy();
this.component = null;
}
}
}
export class InputStringDialog extends Modal {
result: string | false = false;
onSubmit: (result: string | boolean) => void;
title: string;
key: string;
placeholder: string;
isManuallyClosed = false;
constructor(app: App, title: string, key: string, placeholder: string, onSubmit: (result: string | false) => void) {
super(app);
this.onSubmit = onSubmit;
this.title = title;
this.placeholder = placeholder;
this.key = key;
}
onOpen() {
const { contentEl } = this;
contentEl.createEl("h1", { text: this.title });
new Setting(contentEl).setName(this.key).addText((text) =>
text.onChange((value) => {
this.result = value;
})
);
new Setting(contentEl).addButton((btn) =>
btn
.setButtonText("Ok")
.setCta()
.onClick(() => {
this.isManuallyClosed = true;
this.close();
})
).addButton((btn) =>
btn
.setButtonText("Cancel")
.setCta()
.onClick(() => {
this.close();
})
);
}
onClose() {
const { contentEl } = this;
contentEl.empty();
if (this.isManuallyClosed) {
this.onSubmit(this.result);
} else {
this.onSubmit(false);
}
}
}
export class PopoverSelectString extends FuzzySuggestModal<string> {
app: App;
callback: (e: string) => void = () => { };
getItemsFun: () => string[] = () => {
return ["yes", "no"];
}
constructor(app: App, note: string, placeholder: string | null, getItemsFun: () => string[], callback: (e: string) => void) {
super(app);
this.app = app;
this.setPlaceholder(placeholder ?? "y/n) " + note);
if (getItemsFun) this.getItemsFun = getItemsFun;
this.callback = callback;
}
getItems(): string[] {
return this.getItemsFun();
}
getItemText(item: string): string {
return item;
}
onChooseItem(item: string, evt: MouseEvent | KeyboardEvent): void {
// debugger;
this.callback(item);
this.callback = null;
}
onClose(): void {
setTimeout(() => {
if (this.callback != null) {
this.callback("");
}
}, 100);
}
}

Submodule src/lib updated: 1133f82732...a49a096a6a

View File

@@ -1,4 +1,4 @@
import { debounce, Notice, Plugin, TFile, addIcon, TFolder, normalizePath, TAbstractFile, Editor, MarkdownView, PluginManifest, Modal, App, FuzzySuggestModal, Setting } from "obsidian";
import { debounce, Notice, Plugin, TFile, addIcon, TFolder, normalizePath, TAbstractFile, Editor, MarkdownView, PluginManifest, App, } from "obsidian";
import { diff_match_patch } from "diff-match-patch";
import { EntryDoc, LoadedEntry, ObsidianLiveSyncSettings, diff_check_result, diff_result_leaf, EntryBody, LOG_LEVEL, VER, DEFAULT_SETTINGS, diff_result, FLAGMD_REDFLAG, SYNCINFO_ID, InternalFileEntry } from "./lib/src/types";
@@ -27,137 +27,51 @@ import { ConflictResolveModal } from "./ConflictResolveModal";
import { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab";
import { DocumentHistoryModal } from "./DocumentHistoryModal";
//@ts-ignore
import PluginPane from "./PluginPane.svelte";
import { clearAllPeriodic, clearAllTriggers, disposeMemoObject, id2path, memoIfNotExist, memoObject, path2id, retriveMemoObject, setTrigger } from "./utils";
import { decrypt, encrypt } from "./lib/src/e2ee_v2";
const isDebug = false;
import { InputStringDialog, PluginDialogModal, PopoverSelectString } from "./dialogs";
setNoticeClass(Notice);
class PluginDialogModal extends Modal {
plugin: ObsidianLiveSyncPlugin;
logEl: HTMLDivElement;
component: PluginPane = null;
const ICHeader = "i:";
const ICHeaderEnd = "i;";
const ICHeaderLength = ICHeader.length;
constructor(app: App, plugin: ObsidianLiveSyncPlugin) {
super(app);
this.plugin = plugin;
}
onOpen() {
const { contentEl } = this;
if (this.component == null) {
this.component = new PluginPane({
target: contentEl,
props: { plugin: this.plugin },
});
}
}
onClose() {
if (this.component != null) {
this.component.$destroy();
this.component = null;
}
}
/**
* returns is internal chunk of file
* @param str ID
* @returns
*/
function isInteralChunk(str: string): boolean {
return str.startsWith(ICHeader);
}
function id2filenameInternalChunk(str: string): string {
return str.substring(ICHeaderLength);
}
function filename2idInternalChunk(str: string): string {
return ICHeader + str;
}
class InputStringDialog extends Modal {
result: string | false = false;
onSubmit: (result: string | boolean) => void;
title: string;
key: string;
placeholder: string;
isManuallyClosed = false;
constructor(app: App, title: string, key: string, placeholder: string, onSubmit: (result: string | false) => void) {
super(app);
this.onSubmit = onSubmit;
this.title = title;
this.placeholder = placeholder;
this.key = key;
}
onOpen() {
const { contentEl } = this;
contentEl.createEl("h1", { text: this.title });
new Setting(contentEl).setName(this.key).addText((text) =>
text.onChange((value) => {
this.result = value;
})
);
new Setting(contentEl).addButton((btn) =>
btn
.setButtonText("Ok")
.setCta()
.onClick(() => {
this.isManuallyClosed = true;
this.close();
})
).addButton((btn) =>
btn
.setButtonText("Cancel")
.setCta()
.onClick(() => {
this.close();
})
);
}
onClose() {
const { contentEl } = this;
contentEl.empty();
if (this.isManuallyClosed) {
this.onSubmit(this.result);
} else {
this.onSubmit(false);
}
}
const CHeader = "h:";
const CHeaderEnd = "h;";
// const CHeaderLength = CHeader.length;
function isChunk(str: string): boolean {
return str.startsWith(CHeader);
}
class PopoverSelectString extends FuzzySuggestModal<string> {
app: App;
callback: (e: string) => void = () => { };
getItemsFun: () => string[] = () => {
return ["yes", "no"];
}
constructor(app: App, note: string, placeholder: string | null, getItemsFun: () => string[], callback: (e: string) => void) {
super(app);
this.app = app;
this.setPlaceholder(placeholder ?? "y/n) " + note);
if (getItemsFun) this.getItemsFun = getItemsFun;
this.callback = callback;
}
getItems(): string[] {
return this.getItemsFun();
}
getItemText(item: string): string {
return item;
}
onChooseItem(item: string, evt: MouseEvent | KeyboardEvent): void {
// debugger;
this.callback(item);
this.callback = null;
}
onClose(): void {
setTimeout(() => {
if (this.callback != null) {
this.callback("");
}
}, 100);
}
const PSCHeader = "ps:";
const PSCHeaderEnd = "ps;";
function isPluginChunk(str: string): boolean {
return str.startsWith(PSCHeader);
}
const askYesNo = (app: App, message: string): Promise<"yes" | "no"> => {
return new Promise((res) => {
const popover = new PopoverSelectString(app, message, null, null, (result) => res(result as "yes" | "no"));
@@ -232,9 +146,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}
async fileHistory() {
const pageLimit = 2500;
const pageLimit = 1000;
let nextKey = "";
const notes = [];
const notes: { path: string, mtime: number }[] = [];
do {
const docs = await this.localDatabase.localDatabase.allDocs({ limit: pageLimit, startkey: nextKey, include_docs: true });
nextKey = "";
@@ -244,11 +158,18 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
if (!("type" in doc)) continue;
if (doc.type == "newnote" || doc.type == "plain") {
// const docId = doc._id.startsWith("i:") ? doc._id.substring("i:".length) : doc._id;
notes.push(id2path(doc._id))
notes.push({ path: id2path(doc._id), mtime: doc.mtime });
}
if (isChunk(nextKey)) {
// skip the chunk zone.
nextKey = CHeaderEnd;
}
}
} while (nextKey != "");
const target = await askSelectString(this.app, "File to view History", notes);
notes.sort((a, b) => b.mtime - a.mtime);
const notesList = notes.map(e => e.path);
const target = await askSelectString(this.app, "File to view History", notesList);
if (target) {
this.showHistory(target);
}
@@ -258,13 +179,19 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
setLogger(this.addLog.bind(this)); // Logger moved to global.
Logger("loading plugin");
//@ts-ignore
const manifestVersion = MANIFEST_VERSION || "-";
const manifestVersion: string = MANIFEST_VERSION || "0.0.0";
//@ts-ignore
const packageVersion = PACKAGE_VERSION || "-";
const packageVersion: string = PACKAGE_VERSION || "0.0.0";
Logger(`Self-hosted LiveSync v${manifestVersion} ${packageVersion} `);
const lsname = "obsidian-live-sync-ver" + this.getVaultName();
const last_version = localStorage.getItem(lsname);
await this.loadSettings();
const lastVersion = ~~(versionNumberString2Number(manifestVersion) / 1000);
if (lastVersion > this.settings.lastReadUpdates) {
Logger("Self-hosted LiveSync has undergone a major upgrade. Please open the setting dialog, and check the information pane.", LOG_LEVEL.NOTICE);
}
//@ts-ignore
if (this.app.isMobile) {
this.isMobile = true;
@@ -276,7 +203,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
this.settings.syncOnStart = false;
this.settings.syncOnFileOpen = false;
this.settings.periodicReplication = false;
this.settings.versionUpFlash = "I changed specifications incompatiblly, so when you enable sync again, be sure to made version up all nother devides.";
this.settings.versionUpFlash = "Self-hosted LiveSync has been upgraded and some behaviors have changed incompatibly. All automatic synchronization is now disabled temporary. Ensure that other devices are also upgraded, and enable synchronization again.";
this.saveSettings();
}
localStorage.setItem(lsname, `${VER}`);
@@ -561,7 +488,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
});
this.addCommand({
id: "livesync-filehistory",
name: "Pick file to show history",
name: "Pick a file to show history",
callback: () => {
this.fileHistory();
},
@@ -1069,7 +996,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
if (shouldBeIgnored(pathSrc)) {
return;
}
if (docEntry._deleted) {
if (docEntry._deleted || docEntry.deleted) {
//basically pass.
//but if there are no docs left, delete file.
const lastDocs = await this.localDatabase.getDBEntry(pathSrc);
@@ -1140,7 +1067,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
async handleDBChanged(change: EntryBody) {
const targetFile = this.app.vault.getAbstractFileByPath(id2path(change._id));
if (targetFile == null) {
if (change._deleted) {
if (change._deleted || change.deleted) {
return;
}
const doc = change;
@@ -1194,9 +1121,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
const now = new Date().getTime();
if (queue.missingChildren.length == 0) {
queue.done = true;
if (queue.entry._id.startsWith("i:")) {
if (isInteralChunk(queue.entry._id)) {
//system file
const filename = id2path(queue.entry._id.substring("i:".length));
const filename = id2path(id2filenameInternalChunk(queue.entry._id));
Logger(`Applying hidden file, ${queue.entry._id} (${queue.entry._rev}) change...`);
await this.syncInternalFilesAndDatabase("pull", false, false, [filename])
Logger(`Applied hidden file, ${queue.entry._id} (${queue.entry._rev}) change...`);
@@ -1239,7 +1166,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}
async parseIncomingDoc(doc: PouchDB.Core.ExistingDocument<EntryBody>) {
const skipOldFile = this.settings.skipOlderFilesOnSync && false; //patched temporary.
if ((!doc._id.startsWith("i:")) && skipOldFile) {
if ((!isInteralChunk(doc._id)) && skipOldFile) {
const info = this.app.vault.getAbstractFileByPath(id2path(doc._id));
if (info && info instanceof TFile) {
@@ -1276,13 +1203,13 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
async parseReplicationResult(docs: Array<PouchDB.Core.ExistingDocument<EntryDoc>>): Promise<void> {
this.refreshStatusText();
for (const change of docs) {
if (change._id.startsWith("ps:")) {
if (isPluginChunk(change._id)) {
if (this.settings.notifyPluginOrSettingUpdated) {
this.triggerCheckPluginUpdate();
}
continue;
}
if (change._id.startsWith("h:")) {
if (isChunk(change._id)) {
await this.parseIncomingChunk(change);
continue;
}
@@ -1542,7 +1469,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
const filesStorage = this.app.vault.getFiles();
const filesStorageName = filesStorage.map((e) => e.path);
const wf = await this.localDatabase.localDatabase.allDocs();
const filesDatabase = wf.rows.filter((e) => !e.id.startsWith("h:") && !e.id.startsWith("ps:") && e.id != "obsydian_livesync_version").filter(e => isValidPath(e.id)).map((e) => id2path(e.id));
const filesDatabase = wf.rows.filter((e) => !isChunk(e.id) && !isPluginChunk(e.id) && e.id != "obsydian_livesync_version").filter(e => isValidPath(e.id)).map((e) => id2path(e.id));
const isInitialized = await (this.localDatabase.kvDB.get<boolean>("initialized")) || false;
// Make chunk bigger if it is the initial scan. There must be non-active docs.
if (filesDatabase.length == 0 && !isInitialized) {
@@ -1603,7 +1530,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
});
if (!initialScan) {
await runAll("UPDATE STORAGE", onlyInDatabase, async (e) => {
Logger(`Pull from db:${e}`);
Logger(`Check or pull from db:${e}`);
await this.pullFile(e, filesStorage, false, null, false);
});
}
@@ -1877,13 +1804,19 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
if (targetFile == null) {
//have to create;
const doc = await this.localDatabase.getDBEntry(filename, rev ? { rev: rev } : null, false, waitForReady);
if (doc === false) return;
if (doc === false) {
Logger(`${filename} Skipped`);
return;
}
await this.doc2storage_create(doc, force);
} else if (targetFile instanceof TFile) {
//normal case
const file = targetFile;
const doc = await this.localDatabase.getDBEntry(filename, rev ? { rev: rev } : null, false, waitForReady);
if (doc === false) return;
if (doc === false) {
Logger(`${filename} Skipped`);
return;
}
await this.doc2storage_modify(doc, file, force);
} else {
Logger(`target files:${filename} is exists as the folder`);
@@ -1927,6 +1860,8 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
const docx = await this.localDatabase.getDBEntry(file.path, null, false, false);
if (docx != false) {
await this.doc2storage_modify(docx, file);
} else {
Logger("STORAGE <- DB :" + file.path + " Skipped");
}
caches[dK] = { storageMtime, docMtime };
return caches;
@@ -1973,10 +1908,10 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}
const old = await this.localDatabase.getDBEntry(fullpath, null, false, false);
if (old !== false) {
const oldData = { data: old.data, deleted: old._deleted };
const newData = { data: d.data, deleted: d._deleted };
const oldData = { data: old.data, deleted: old._deleted || old.deleted, };
const newData = { data: d.data, deleted: d._deleted || d.deleted };
if (JSON.stringify(oldData) == JSON.stringify(newData)) {
Logger(msg + "Skipped (not changed) " + fullpath + (d._deleted ? " (deleted)" : ""), LOG_LEVEL.VERBOSE);
Logger(msg + "Skipped (not changed) " + fullpath + ((d._deleted || d.deleted) ? " (deleted)" : ""), LOG_LEVEL.VERBOSE);
return true;
}
// d._rev = old._rev;
@@ -2029,7 +1964,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
async getPluginList(): Promise<{ plugins: PluginList; allPlugins: DevicePluginList; thisDevicePlugins: DevicePluginList }> {
const db = this.localDatabase.localDatabase;
const docList = await db.allDocs<PluginDataEntry>({ startkey: `ps:`, endkey: `ps;`, include_docs: false });
const docList = await db.allDocs<PluginDataEntry>({ startkey: PSCHeader, endkey: PSCHeaderEnd, include_docs: false });
const oldDocs: PluginDataEntry[] = ((await Promise.all(docList.rows.map(async (e) => await this.localDatabase.getDBEntry(e.id)))).filter((e) => e !== false) as LoadedEntry[]).map((e) => JSON.parse(e.data));
const plugins: { [key: string]: PluginDataEntry[] } = {};
const allPlugins: { [key: string]: PluginDataEntry } = {};
@@ -2125,7 +2060,15 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}
Logger(`Deleting old plugins`, LOG_LEVEL.VERBOSE);
const delDocs = oldDocs.rows.map((e) => {
e.doc._deleted = true;
// e.doc._deleted = true;
if (e.doc.type == "newnote" || e.doc.type == "plain") {
e.doc.deleted = true;
if (this.settings.deleteMetadataOfDeletedFiles) {
e.doc._deleted = true;
}
} else {
e.doc._deleted = true;
}
return e.doc;
});
await db.bulkDocs(delDocs);
@@ -2257,7 +2200,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}
async storeInternaFileToDatabase(file: InternalFileInfo, forceWrite = false) {
const id = "i:" + path2id(file.path);
const id = filename2idInternalChunk(path2id(file.path));
const contentBin = await this.app.vault.adapter.readBinary(file.path);
const content = await arrayBufferToBase64(contentBin);
const mtime = file.mtime;
@@ -2299,7 +2242,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}
async deleteInternaFileOnDatabase(filename: string, forceWrite = false) {
const id = "i:" + path2id(filename);
const id = filename2idInternalChunk(path2id(filename));
const mtime = new Date().getTime();
await runWithLock("file-" + id, false, async () => {
const old = await this.localDatabase.getDBEntry(id, null, false, false) as InternalFileEntry | false;
@@ -2356,7 +2299,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}
async extractInternaFileFromDatabase(filename: string, force = false) {
const isExists = await this.app.vault.adapter.exists(filename);
const id = "i:" + path2id(filename);
const id = filename2idInternalChunk(path2id(filename));
return await runWithLock("file-" + id, false, async () => {
const fileOnDB = await this.localDatabase.getDBEntry(id, null, false, false) as false | LoadedEntry;
@@ -2384,7 +2327,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
const contentBin = await this.app.vault.adapter.readBinary(filename);
const content = await arrayBufferToBase64(contentBin);
if (content == fileOnDB.data && !force) {
Logger(`STORAGE <-- DB:${filename}: skipped (hidden) Not changed`);
// Logger(`STORAGE <-- DB:${filename}: skipped (hidden) Not changed`, LOG_LEVEL.VERBOSE);
return false;
}
await this.app.vault.adapter.writeBinary(filename, base64ToArrayBuffer(fileOnDB.data), { mtime: fileOnDB.mtime, ctime: fileOnDB.ctime });
@@ -2418,8 +2361,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
.replace(/\n| /g, "")
.split(",").filter(e => e).map(e => new RegExp(e));
if (!files) files = await this.scanInternalFiles();
const filesOnDB = (await this.localDatabase.localDatabase.allDocs({ startkey: "i:", endkey: "i;", include_docs: true })).rows.map(e => e.doc) as InternalFileEntry[];
const allFileNamesSrc = [...new Set([...files.map(e => normalizePath(e.path)), ...filesOnDB.map(e => normalizePath(id2path(e._id.substring("i:".length))))])];
const filesOnDB = ((await this.localDatabase.localDatabase.allDocs({ startkey: ICHeader, endkey: ICHeaderEnd, include_docs: true })).rows.map(e => e.doc) as InternalFileEntry[]).filter(e => !e.deleted);
const allFileNamesSrc = [...new Set([...files.map(e => normalizePath(e.path)), ...filesOnDB.map(e => normalizePath(id2path(id2filenameInternalChunk(e._id))))])];
const allFileNames = allFileNamesSrc.filter(filename => !targetFiles || (targetFiles && targetFiles.indexOf(filename) !== -1))
function compareMTime(a: number, b: number) {
const wa = ~~(a / 1000);
@@ -2464,7 +2408,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
if (ignorePatterns.some(e => filename.match(e))) continue;
const fileOnStorage = files.find(e => e.path == filename);
const fileOnDatabase = filesOnDB.find(e => e._id == "i:" + id2path(filename));
const fileOnDatabase = filesOnDB.find(e => e._id == filename2idInternalChunk(id2path(filename)));
// TODO: Fix this somehow smart.
let proc: Promise<void> | null;

View File

@@ -93,6 +93,10 @@
padding-left: 4px;
}
.sls-header-button {
margin-left: 2em;
}
.sls-hidden {
display: none;
}

8
updates.md Normal file
View File

@@ -0,0 +1,8 @@
### 0.13.0
- The metadata of the deleted files will be kept on the database by default. If you want to delete this as the previous version, please turn on `Delete metadata of deleted files.`. And, if you have upgraded from the older version, please ensure every device has been upgraded.
- Please turn on `Delete metadata of deleted files.` if you are using livesync-classroom or filesystem-livesync.
- We can see the history of deleted files.
- `Pick file to show` was renamed to `Pick a file to show.
- Files in the `Pick a file to show` are now ordered by their modified date descent.
- Update information became to be shown on the major upgrade.