mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-05-08 08:41:50 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3f0639d95 | ||
|
|
531cf0d8a4 | ||
|
|
e4f62cefb9 |
13
.gitignore
vendored
13
.gitignore
vendored
@@ -1 +1,14 @@
|
|||||||
|
# Intellij
|
||||||
|
*.iml
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# npm
|
||||||
node_modules
|
node_modules
|
||||||
|
package-lock.json
|
||||||
|
|
||||||
|
# build
|
||||||
|
# main.js
|
||||||
|
*.js.map
|
||||||
|
|
||||||
|
# obsidian
|
||||||
|
data.json
|
||||||
@@ -16,6 +16,7 @@ Limitations: File deletion handling is not completed.
|
|||||||
|
|
||||||
- Live sync
|
- Live sync
|
||||||
- Self-Hosted data synchronization with conflict detection and resolving in Obsidian.
|
- Self-Hosted data synchronization with conflict detection and resolving in Obsidian.
|
||||||
|
- Off line sync is also available.
|
||||||
|
|
||||||
## How to use the beta build
|
## How to use the beta build
|
||||||
|
|
||||||
@@ -106,4 +107,4 @@ example values.
|
|||||||
| CouchDB Password | (\*4) | c2c11651d75497fa3d3c486e4c8bdf27 |
|
| CouchDB Password | (\*4) | c2c11651d75497fa3d3c486e4c8bdf27 |
|
||||||
|
|
||||||
# License
|
# License
|
||||||
The source code is licensed MIT.
|
The source code is licensed MIT.
|
||||||
|
|||||||
418
main.ts
418
main.ts
@@ -1,11 +1,13 @@
|
|||||||
import { App, debounce, Modal, Notice, Plugin, PluginSettingTab, Setting, TFile, addIcon, TFolder } from "obsidian";
|
import { App, debounce, Modal, Notice, Plugin, PluginSettingTab, Setting, TFile, addIcon, TFolder } from "obsidian";
|
||||||
import { PouchDB } from "./pouchdb-browser-webpack/dist/pouchdb-browser";
|
import { PouchDB } from "./pouchdb-browser-webpack/dist/pouchdb-browser";
|
||||||
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 xxhash from "xxhash-wasm";
|
||||||
|
|
||||||
// docs should be encoded as base64, so 1 char -> 1 bytes
|
// docs should be encoded as base64, so 1 char -> 1 bytes
|
||||||
// and cloudant limitation is 1MB , we use 900kb;
|
// and cloudant limitation is 1MB , we use 900kb;
|
||||||
// const MAX_DOC_SIZE = 921600;
|
// const MAX_DOC_SIZE = 921600;
|
||||||
const MAX_DOC_SIZE = 921600;
|
const MAX_DOC_SIZE = 200; // for .md file
|
||||||
|
const MAX_DOC_SIZE_BIN = 102400; // 100kb
|
||||||
|
|
||||||
interface ObsidianLiveSyncSettings {
|
interface ObsidianLiveSyncSettings {
|
||||||
couchDB_URI: string;
|
couchDB_URI: string;
|
||||||
@@ -14,6 +16,9 @@ interface ObsidianLiveSyncSettings {
|
|||||||
liveSync: boolean;
|
liveSync: boolean;
|
||||||
syncOnSave: boolean;
|
syncOnSave: boolean;
|
||||||
syncOnStart: boolean;
|
syncOnStart: boolean;
|
||||||
|
savingDelay: number;
|
||||||
|
lessInformationInLog: boolean;
|
||||||
|
gcDelay: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_SETTINGS: ObsidianLiveSyncSettings = {
|
const DEFAULT_SETTINGS: ObsidianLiveSyncSettings = {
|
||||||
@@ -23,6 +28,9 @@ const DEFAULT_SETTINGS: ObsidianLiveSyncSettings = {
|
|||||||
liveSync: false,
|
liveSync: false,
|
||||||
syncOnSave: false,
|
syncOnSave: false,
|
||||||
syncOnStart: false,
|
syncOnStart: false,
|
||||||
|
savingDelay: 200,
|
||||||
|
lessInformationInLog: false,
|
||||||
|
gcDelay: 30,
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Entry {
|
interface Entry {
|
||||||
@@ -52,13 +60,11 @@ type LoadedEntry = Entry & {
|
|||||||
|
|
||||||
interface EntryLeaf {
|
interface EntryLeaf {
|
||||||
_id: string;
|
_id: string;
|
||||||
parent: string;
|
|
||||||
seq: number;
|
|
||||||
data: string;
|
data: string;
|
||||||
_rev?: string;
|
|
||||||
_deleted?: boolean;
|
_deleted?: boolean;
|
||||||
type: "leaf";
|
type: "leaf";
|
||||||
}
|
}
|
||||||
|
|
||||||
type EntryDoc = Entry | NewEntry | LoadedEntry | EntryLeaf;
|
type EntryDoc = Entry | NewEntry | LoadedEntry | EntryLeaf;
|
||||||
type diff_result_leaf = {
|
type diff_result_leaf = {
|
||||||
rev: string;
|
rev: string;
|
||||||
@@ -160,6 +166,8 @@ class LocalPouchDB {
|
|||||||
addLog: (message: any, isNotify?: boolean) => Promise<void>;
|
addLog: (message: any, isNotify?: boolean) => Promise<void>;
|
||||||
localDatabase: PouchDB.Database<EntryDoc>;
|
localDatabase: PouchDB.Database<EntryDoc>;
|
||||||
|
|
||||||
|
h32: (input: string, seed?: number) => string;
|
||||||
|
h64: (input: string, seedHigh?: number, seedLow?: number) => string;
|
||||||
constructor(app: App, plugin: ObsidianLiveSyncPlugin, dbname: string) {
|
constructor(app: App, plugin: ObsidianLiveSyncPlugin, dbname: string) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.app = app;
|
this.app = app;
|
||||||
@@ -181,7 +189,7 @@ class LocalPouchDB {
|
|||||||
}
|
}
|
||||||
return "disabled";
|
return "disabled";
|
||||||
}
|
}
|
||||||
initializeDatabase() {
|
async initializeDatabase() {
|
||||||
if (this.localDatabase != null) this.localDatabase.close();
|
if (this.localDatabase != null) this.localDatabase.close();
|
||||||
this.localDatabase = null;
|
this.localDatabase = null;
|
||||||
this.localDatabase = new PouchDB<EntryDoc>(this.dbname + "-livesync", {
|
this.localDatabase = new PouchDB<EntryDoc>(this.dbname + "-livesync", {
|
||||||
@@ -189,8 +197,14 @@ class LocalPouchDB {
|
|||||||
revs_limit: 100,
|
revs_limit: 100,
|
||||||
deterministic_revs: true,
|
deterministic_revs: true,
|
||||||
});
|
});
|
||||||
|
await this.prepareHashArg();
|
||||||
|
}
|
||||||
|
async prepareHashArg() {
|
||||||
|
if (this.h32 != null) return;
|
||||||
|
const { h32, h64 } = await xxhash();
|
||||||
|
this.h32 = h32;
|
||||||
|
this.h64 = h64;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDatabaseDoc(id: string, opt?: any): Promise<false | LoadedEntry> {
|
async getDatabaseDoc(id: string, opt?: any): Promise<false | LoadedEntry> {
|
||||||
try {
|
try {
|
||||||
let obj: EntryDoc & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta = null;
|
let obj: EntryDoc & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta = null;
|
||||||
@@ -225,23 +239,16 @@ class LocalPouchDB {
|
|||||||
// search childrens
|
// search childrens
|
||||||
try {
|
try {
|
||||||
let childrens = [];
|
let childrens = [];
|
||||||
// let childPromise = [];
|
|
||||||
for (var v of obj.children) {
|
for (var v of obj.children) {
|
||||||
// childPromise.push(this.localDatabase.get(v));
|
// childPromise.push(this.localDatabase.get(v));
|
||||||
let elem = await this.localDatabase.get(v);
|
let elem = await this.localDatabase.get(v);
|
||||||
if (elem.type && elem.type == "leaf") {
|
if (elem.type && elem.type == "leaf") {
|
||||||
childrens.push(elem);
|
childrens.push(elem.data);
|
||||||
} else {
|
} else {
|
||||||
throw new Error("linked document is not leaf");
|
throw new Error("linked document is not leaf");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// let childrens = await Promise.all(childPromise);
|
let data = childrens.join("");
|
||||||
let data = childrens
|
|
||||||
// .filter((e) => e.type == "leaf")
|
|
||||||
// .map((e) => e as NoteLeaf)
|
|
||||||
.sort((e) => e.seq)
|
|
||||||
.map((e) => e.data)
|
|
||||||
.join("");
|
|
||||||
let doc: LoadedEntry = {
|
let doc: LoadedEntry = {
|
||||||
data: data,
|
data: data,
|
||||||
_id: obj._id,
|
_id: obj._id,
|
||||||
@@ -257,6 +264,7 @@ class LocalPouchDB {
|
|||||||
if (ex.status && ex.status == 404) {
|
if (ex.status && ex.status == 404) {
|
||||||
this.addLog(`Missing document content!, could not read ${obj._id} from database.`, true);
|
this.addLog(`Missing document content!, could not read ${obj._id} from database.`, true);
|
||||||
// this.addLog(ex);
|
// this.addLog(ex);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
this.addLog(`Something went wrong on reading ${obj._id} from database.`, true);
|
this.addLog(`Something went wrong on reading ${obj._id} from database.`, true);
|
||||||
this.addLog(ex);
|
this.addLog(ex);
|
||||||
@@ -285,24 +293,12 @@ class LocalPouchDB {
|
|||||||
}
|
}
|
||||||
//Check it out and fix docs to regular case
|
//Check it out and fix docs to regular case
|
||||||
if (!obj.type || (obj.type && obj.type == "notes")) {
|
if (!obj.type || (obj.type && obj.type == "notes")) {
|
||||||
// let note = obj as Notes;
|
obj._deleted = true;
|
||||||
// note._deleted=true;
|
|
||||||
obj._deleted=true;
|
|
||||||
let r = await this.localDatabase.put(obj);
|
let r = await this.localDatabase.put(obj);
|
||||||
return true;
|
return true;
|
||||||
// simple note
|
// simple note
|
||||||
}
|
}
|
||||||
if (obj.type == "newnote") {
|
if (obj.type == "newnote") {
|
||||||
// search childrens
|
|
||||||
for (var v of obj.children) {
|
|
||||||
let d = await this.localDatabase.get(v);
|
|
||||||
if (d.type != "leaf") {
|
|
||||||
this.addLog(`structure went wrong:${id}-${v}`);
|
|
||||||
}
|
|
||||||
d._deleted = true;
|
|
||||||
await this.localDatabase.put(d);
|
|
||||||
this.addLog(`content removed:${(d as EntryLeaf).seq}`);
|
|
||||||
}
|
|
||||||
obj._deleted = true;
|
obj._deleted = true;
|
||||||
await this.localDatabase.put(obj);
|
await this.localDatabase.put(obj);
|
||||||
this.addLog(`entry removed:${obj._id}`);
|
this.addLog(`entry removed:${obj._id}`);
|
||||||
@@ -318,25 +314,79 @@ class LocalPouchDB {
|
|||||||
|
|
||||||
async putDBEntry(note: LoadedEntry) {
|
async putDBEntry(note: LoadedEntry) {
|
||||||
let leftData = note.data;
|
let leftData = note.data;
|
||||||
let savenNotes = []; // something occured, kill this .
|
let savenNotes = [];
|
||||||
let seq = 0;
|
let processed = 0;
|
||||||
let now = Date.now();
|
let made = 0;
|
||||||
|
let skiped = 0;
|
||||||
|
let pieceSize = MAX_DOC_SIZE;
|
||||||
|
if (!note._id.endsWith(".md")) {
|
||||||
|
pieceSize = MAX_DOC_SIZE_BIN;
|
||||||
|
}
|
||||||
do {
|
do {
|
||||||
let piece = leftData.substring(0, MAX_DOC_SIZE);
|
// To keep low bandwith and database size,
|
||||||
leftData = leftData.substring(MAX_DOC_SIZE);
|
// Dedup pieces on database.
|
||||||
seq++;
|
let piece = leftData.substring(0, pieceSize);
|
||||||
let leafid = note._id + "-" + now + "-" + seq;
|
leftData = leftData.substring(pieceSize);
|
||||||
let d: EntryLeaf = {
|
processed++;
|
||||||
_id: leafid,
|
// Get has of piece.
|
||||||
parent: note._id,
|
let hashedPiece = this.h32(piece);
|
||||||
data: piece,
|
let leafid = "h:" + hashedPiece;
|
||||||
seq: seq,
|
let hashQ: number = 0; // if hash collided, **IF**, count it up.
|
||||||
type: "leaf",
|
let tryNextHash = false;
|
||||||
};
|
let needMake = true;
|
||||||
let result = await this.localDatabase.put(d);
|
|
||||||
|
do {
|
||||||
|
let nleafid = leafid;
|
||||||
|
try {
|
||||||
|
nleafid = `${leafid}${hashQ}`;
|
||||||
|
// console.log(nleafid);
|
||||||
|
let pieceData = await this.localDatabase.get<EntryLeaf>(nleafid);
|
||||||
|
if (pieceData.type == "leaf" && pieceData.data == piece) {
|
||||||
|
this.addLog("hashe:data exists.");
|
||||||
|
leafid = nleafid;
|
||||||
|
needMake = false;
|
||||||
|
tryNextHash = false;
|
||||||
|
} else if (pieceData.type == "leaf") {
|
||||||
|
this.addLog("hash:collision!!");
|
||||||
|
hashQ++;
|
||||||
|
tryNextHash = true;
|
||||||
|
} else {
|
||||||
|
this.addLog("hash:no collision, it's not leaf. what's going on..");
|
||||||
|
leafid = nleafid;
|
||||||
|
tryNextHash = false;
|
||||||
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
if (ex.status && ex.status == 404) {
|
||||||
|
//not found, we can use it.
|
||||||
|
this.addLog(`hash:not found.`);
|
||||||
|
leafid = nleafid;
|
||||||
|
needMake = true;
|
||||||
|
} else {
|
||||||
|
needMake = false;
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (tryNextHash);
|
||||||
|
if (needMake) {
|
||||||
|
//have to make
|
||||||
|
let d: EntryLeaf = {
|
||||||
|
_id: leafid,
|
||||||
|
data: piece,
|
||||||
|
type: "leaf",
|
||||||
|
};
|
||||||
|
let result = await this.localDatabase.put(d);
|
||||||
|
if (result.ok) {
|
||||||
|
this.addLog(`ok:saven`);
|
||||||
|
made++;
|
||||||
|
} else {
|
||||||
|
this.addLog("save faild");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
skiped++;
|
||||||
|
}
|
||||||
savenNotes.push(leafid);
|
savenNotes.push(leafid);
|
||||||
} while (leftData != "");
|
} while (leftData != "");
|
||||||
this.addLog(`note content saven, pieces:${seq}`);
|
this.addLog(`note content saven, pieces:${processed} new:${made}, skip:${skiped}`);
|
||||||
let newDoc: NewEntry = {
|
let newDoc: NewEntry = {
|
||||||
NewNote: true,
|
NewNote: true,
|
||||||
children: savenNotes,
|
children: savenNotes,
|
||||||
@@ -351,15 +401,10 @@ class LocalPouchDB {
|
|||||||
// Here for upsert logic,
|
// Here for upsert logic,
|
||||||
try {
|
try {
|
||||||
let old = await this.localDatabase.get(newDoc._id);
|
let old = await this.localDatabase.get(newDoc._id);
|
||||||
if (!old.type || old.type == "notes") {
|
if (!old.type || old.type == "notes" || old.type == "newnote") {
|
||||||
// simple use rev for new doc
|
// simple use rev for new doc
|
||||||
newDoc._rev = old._rev;
|
newDoc._rev = old._rev;
|
||||||
}
|
}
|
||||||
if (old.type == "newnote") {
|
|
||||||
//when save finished, we have to garbage collect.
|
|
||||||
deldocs = old.children;
|
|
||||||
newDoc._rev = old._rev;
|
|
||||||
}
|
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
if (ex.status && ex.status == 404) {
|
if (ex.status && ex.status == 404) {
|
||||||
// NO OP/
|
// NO OP/
|
||||||
@@ -369,20 +414,11 @@ class LocalPouchDB {
|
|||||||
}
|
}
|
||||||
await this.localDatabase.put(newDoc);
|
await this.localDatabase.put(newDoc);
|
||||||
this.addLog(`note saven:${newDoc._id}`);
|
this.addLog(`note saven:${newDoc._id}`);
|
||||||
let items = 0;
|
|
||||||
for (var v of deldocs) {
|
|
||||||
items++;
|
|
||||||
//TODO: Check for missing link
|
|
||||||
let d = await this.localDatabase.get(v);
|
|
||||||
d._deleted = true;
|
|
||||||
await this.localDatabase.put(d);
|
|
||||||
}
|
|
||||||
this.addLog(`old content deleted, pieces:${items}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
syncHandler: PouchDB.Replication.Sync<{}> = null;
|
syncHandler: PouchDB.Replication.Sync<{}> = null;
|
||||||
|
|
||||||
async openReplication(setting: ObsidianLiveSyncSettings, keepAlive: boolean, callback: (e: PouchDB.Replication.SyncResult<{}>) => Promise<void>) {
|
async openReplication(setting: ObsidianLiveSyncSettings, keepAlive: boolean, showResult: boolean, callback: (e: PouchDB.Core.ExistingDocument<{}>[]) => Promise<void>) {
|
||||||
let uri = setting.couchDB_URI;
|
let uri = setting.couchDB_URI;
|
||||||
let auth: Credential = {
|
let auth: Credential = {
|
||||||
username: setting.couchDB_USER,
|
username: setting.couchDB_USER,
|
||||||
@@ -401,14 +437,26 @@ class LocalPouchDB {
|
|||||||
let db = dbret.db;
|
let db = dbret.db;
|
||||||
|
|
||||||
//replicate once
|
//replicate once
|
||||||
this.localDatabase.replicate
|
let replicate = this.localDatabase.replicate.from(db);
|
||||||
.from(db)
|
replicate
|
||||||
|
.on("change", async (e) => {
|
||||||
|
try {
|
||||||
|
callback(e.docs);
|
||||||
|
this.addLog(`pulled ${e.docs.length} doc(s)`);
|
||||||
|
} catch (ex) {
|
||||||
|
this.addLog("Replication callback error");
|
||||||
|
this.addLog(ex);
|
||||||
|
}
|
||||||
|
})
|
||||||
.on("complete", async (info) => {
|
.on("complete", async (info) => {
|
||||||
|
replicate.removeAllListeners();
|
||||||
|
replicate.cancel();
|
||||||
|
// this.syncHandler = null;
|
||||||
this.syncHandler = this.localDatabase.sync(db, syncOption);
|
this.syncHandler = this.localDatabase.sync(db, syncOption);
|
||||||
this.syncHandler
|
this.syncHandler
|
||||||
.on("change", async (e) => {
|
.on("change", async (e) => {
|
||||||
try {
|
try {
|
||||||
callback(e);
|
callback(e.change.docs);
|
||||||
this.addLog(`replicated ${e.change.docs.length} doc(s)`);
|
this.addLog(`replicated ${e.change.docs.length} doc(s)`);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
this.addLog("Replication callback error");
|
this.addLog("Replication callback error");
|
||||||
@@ -419,8 +467,9 @@ class LocalPouchDB {
|
|||||||
this.addLog("Replication activated");
|
this.addLog("Replication activated");
|
||||||
})
|
})
|
||||||
.on("complete", (e) => {
|
.on("complete", (e) => {
|
||||||
this.addLog("Replication completed", true);
|
this.addLog("Replication completed", showResult);
|
||||||
// this.addLog(e);
|
// this.addLog(e);
|
||||||
|
console.dir(this.syncHandler);
|
||||||
this.syncHandler = null;
|
this.syncHandler = null;
|
||||||
})
|
})
|
||||||
.on("denied", (e) => {
|
.on("denied", (e) => {
|
||||||
@@ -433,7 +482,10 @@ class LocalPouchDB {
|
|||||||
})
|
})
|
||||||
.on("paused", (e) => {
|
.on("paused", (e) => {
|
||||||
this.addLog("replication paused");
|
this.addLog("replication paused");
|
||||||
|
// console.dir(this.syncHandler);
|
||||||
|
// this.addLog(e);
|
||||||
});
|
});
|
||||||
|
// console.dir();
|
||||||
})
|
})
|
||||||
.on("error", () => {
|
.on("error", () => {
|
||||||
this.addLog("Pulling Replication error", true);
|
this.addLog("Pulling Replication error", true);
|
||||||
@@ -453,6 +505,7 @@ class LocalPouchDB {
|
|||||||
async resetDatabase() {
|
async resetDatabase() {
|
||||||
await this.closeReplication();
|
await this.closeReplication();
|
||||||
await this.localDatabase.destroy();
|
await this.localDatabase.destroy();
|
||||||
|
this.localDatabase = null;
|
||||||
await this.initializeDatabase();
|
await this.initializeDatabase();
|
||||||
this.addLog("Local Database Reset", true);
|
this.addLog("Local Database Reset", true);
|
||||||
}
|
}
|
||||||
@@ -485,6 +538,53 @@ class LocalPouchDB {
|
|||||||
if (con2 === false) return;
|
if (con2 === false) return;
|
||||||
this.addLog("Remote Database Created or Connected", true);
|
this.addLog("Remote Database Created or Connected", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async garbageCollect() {
|
||||||
|
// get all documents of NewEntry2
|
||||||
|
// we don't use queries , just use allDocs();
|
||||||
|
let c = 0;
|
||||||
|
let readCount = 0;
|
||||||
|
let hashPieces: string[] = [];
|
||||||
|
let usedPieces: string[] = [];
|
||||||
|
do {
|
||||||
|
let result = await this.localDatabase.allDocs({ include_docs: true, skip: c, limit: 100 });
|
||||||
|
readCount = result.rows.length;
|
||||||
|
if (readCount > 0) {
|
||||||
|
//there are some result
|
||||||
|
for (let v of result.rows) {
|
||||||
|
let doc = v.doc;
|
||||||
|
if (doc.type == "newnote") {
|
||||||
|
// used pieces memo.
|
||||||
|
usedPieces = Array.from(new Set([...usedPieces, ...doc.children]));
|
||||||
|
}
|
||||||
|
if (doc.type == "leaf") {
|
||||||
|
// all pieces.
|
||||||
|
hashPieces = Array.from(new Set([...hashPieces, doc._id]));
|
||||||
|
}
|
||||||
|
// this.addLog(`GC:processed:${v.doc._id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c += readCount;
|
||||||
|
} while (readCount != 0);
|
||||||
|
// items collected.
|
||||||
|
const garbages = hashPieces.filter((e) => usedPieces.indexOf(e) == -1);
|
||||||
|
let deleteCount = 0;
|
||||||
|
for (let v of garbages) {
|
||||||
|
try {
|
||||||
|
let item = await this.localDatabase.get(v);
|
||||||
|
item._deleted = true;
|
||||||
|
await this.localDatabase.put(item);
|
||||||
|
deleteCount++;
|
||||||
|
} catch (ex) {
|
||||||
|
if (ex.status && ex.status == 404) {
|
||||||
|
// NO OP. It should be timing problem.
|
||||||
|
} else {
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.addLog(`GC:deleted ${deleteCount} items.`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ObsidianLiveSyncPlugin extends Plugin {
|
export default class ObsidianLiveSyncPlugin extends Plugin {
|
||||||
@@ -492,11 +592,12 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
//localDatabase: PouchDB.Database<EntryDoc>;
|
//localDatabase: PouchDB.Database<EntryDoc>;
|
||||||
localDatabase: LocalPouchDB;
|
localDatabase: LocalPouchDB;
|
||||||
logMessage: string[] = [];
|
logMessage: string[] = [];
|
||||||
onLogChanged: () => void;
|
// onLogChanged: () => void;
|
||||||
statusBar: HTMLElement;
|
statusBar: HTMLElement;
|
||||||
statusBar2: HTMLElement;
|
statusBar2: HTMLElement;
|
||||||
|
|
||||||
async onload() {
|
async onload() {
|
||||||
|
this.addLog = this.addLog.bind(this);
|
||||||
this.addLog("loading plugin");
|
this.addLog("loading plugin");
|
||||||
await this.openDatabase();
|
await this.openDatabase();
|
||||||
await this.loadSettings();
|
await this.loadSettings();
|
||||||
@@ -517,20 +618,23 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
</g>`
|
</g>`
|
||||||
);
|
);
|
||||||
this.addRibbonIcon("replicate", "Replicate", async () => {
|
this.addRibbonIcon("replicate", "Replicate", async () => {
|
||||||
await this.replicate();
|
await this.replicate(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addRibbonIcon("view-log", "Show log", () => {
|
let x = this.addRibbonIcon("view-log", "Show log", () => {
|
||||||
new LogDisplayModal(this.app, this).open();
|
new LogDisplayModal(this.app, this).open();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.statusBar = this.addStatusBarItem();
|
this.statusBar = this.addStatusBarItem();
|
||||||
|
|
||||||
this.statusBar2 = this.addStatusBarItem();
|
this.statusBar2 = this.addStatusBarItem();
|
||||||
|
let delay = this.settings.savingDelay;
|
||||||
this.watchVaultChange = debounce(this.watchVaultChange.bind(this), 200, false);
|
if (delay < 200) delay = 200;
|
||||||
this.watchVaultDelete = debounce(this.watchVaultDelete.bind(this), 200, false);
|
if (delay > 5000) delay = 5000;
|
||||||
this.watchVaultRename = debounce(this.watchVaultRename.bind(this), 200, false);
|
this.watchVaultChange = debounce(this.watchVaultChange.bind(this), delay, false);
|
||||||
this.watchWorkspaceOpen = debounce(this.watchWorkspaceOpen.bind(this), 200, false);
|
this.watchVaultDelete = debounce(this.watchVaultDelete.bind(this), delay, false);
|
||||||
|
this.watchVaultRename = debounce(this.watchVaultRename.bind(this), delay, false);
|
||||||
|
this.watchWorkspaceOpen = debounce(this.watchWorkspaceOpen.bind(this), delay, false);
|
||||||
this.registerWatchEvents();
|
this.registerWatchEvents();
|
||||||
this.parseReplicationResult = this.parseReplicationResult.bind(this);
|
this.parseReplicationResult = this.parseReplicationResult.bind(this);
|
||||||
|
|
||||||
@@ -540,7 +644,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
await this.initializeDatabase();
|
await this.initializeDatabase();
|
||||||
this.realizeSettingSyncMode();
|
this.realizeSettingSyncMode();
|
||||||
if (this.settings.syncOnStart) {
|
if (this.settings.syncOnStart) {
|
||||||
await this.replicate();
|
await this.replicate(false);
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
@@ -550,16 +654,23 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
if (this.settings.liveSync) {
|
if (this.settings.liveSync) {
|
||||||
await this.localDatabase.closeReplication();
|
await this.localDatabase.closeReplication();
|
||||||
if (this.settings.liveSync) {
|
if (this.settings.liveSync) {
|
||||||
this.localDatabase.openReplication(this.settings, true, this.parseReplicationResult);
|
this.localDatabase.openReplication(this.settings, true, false, this.parseReplicationResult);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 60 * 1000)
|
}, 60 * 1000)
|
||||||
);
|
);
|
||||||
|
this.watchWindowVisiblity = this.watchWindowVisiblity.bind(this);
|
||||||
|
window.addEventListener("visibilitychange", this.watchWindowVisiblity);
|
||||||
}
|
}
|
||||||
|
|
||||||
onunload() {
|
onunload() {
|
||||||
|
if (this.gcTimerHandler != null) {
|
||||||
|
clearTimeout(this.gcTimerHandler);
|
||||||
|
this.gcTimerHandler = null;
|
||||||
|
}
|
||||||
this.localDatabase.closeReplication();
|
this.localDatabase.closeReplication();
|
||||||
this.localDatabase.close();
|
this.localDatabase.close();
|
||||||
|
window.removeEventListener("visibilitychange", this.watchWindowVisiblity);
|
||||||
this.addLog("unloading plugin");
|
this.addLog("unloading plugin");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -569,7 +680,10 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
let vaultName = this.app.vault.getName();
|
let vaultName = this.app.vault.getName();
|
||||||
this.localDatabase = new LocalPouchDB(this.app, this, vaultName);
|
this.localDatabase = new LocalPouchDB(this.app, this, vaultName);
|
||||||
this.localDatabase.initializeDatabase();
|
await this.localDatabase.initializeDatabase();
|
||||||
|
}
|
||||||
|
async garbageCollect() {
|
||||||
|
await this.localDatabase.garbageCollect();
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadSettings() {
|
async loadSettings() {
|
||||||
@@ -579,7 +693,19 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
async saveSettings() {
|
async saveSettings() {
|
||||||
await this.saveData(this.settings);
|
await this.saveData(this.settings);
|
||||||
}
|
}
|
||||||
|
gcTimerHandler: any = null;
|
||||||
|
gcHook() {
|
||||||
|
if (this.settings.gcDelay == 0) return;
|
||||||
|
const GC_DELAY = this.settings.gcDelay * 1000; // if leaving opening window, try GC,
|
||||||
|
if (this.gcTimerHandler != null) {
|
||||||
|
clearTimeout(this.gcTimerHandler);
|
||||||
|
this.gcTimerHandler = null;
|
||||||
|
}
|
||||||
|
this.gcTimerHandler = setTimeout(() => {
|
||||||
|
this.gcTimerHandler = null;
|
||||||
|
this.garbageCollect();
|
||||||
|
}, GC_DELAY);
|
||||||
|
}
|
||||||
registerWatchEvents() {
|
registerWatchEvents() {
|
||||||
this.registerEvent(this.app.vault.on("modify", this.watchVaultChange));
|
this.registerEvent(this.app.vault.on("modify", this.watchVaultChange));
|
||||||
this.registerEvent(this.app.vault.on("delete", this.watchVaultDelete));
|
this.registerEvent(this.app.vault.on("delete", this.watchVaultDelete));
|
||||||
@@ -587,12 +713,32 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
this.registerEvent(this.app.vault.on("create", this.watchVaultChange));
|
this.registerEvent(this.app.vault.on("create", this.watchVaultChange));
|
||||||
this.registerEvent(this.app.workspace.on("file-open", this.watchWorkspaceOpen));
|
this.registerEvent(this.app.workspace.on("file-open", this.watchWorkspaceOpen));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watchWindowVisiblity() {
|
||||||
|
this.addLog("visiblity changed");
|
||||||
|
let isHidden = document.hidden;
|
||||||
|
// this.addLog(isHidden);
|
||||||
|
if (isHidden) {
|
||||||
|
this.localDatabase.closeReplication();
|
||||||
|
} else {
|
||||||
|
if (this.settings.liveSync) {
|
||||||
|
this.localDatabase.openReplication(this.settings, true, false, this.parseReplicationResult);
|
||||||
|
}
|
||||||
|
if (this.settings.syncOnStart) {
|
||||||
|
this.localDatabase.openReplication(this.settings, false, false, this.parseReplicationResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.gcHook();
|
||||||
|
}
|
||||||
|
|
||||||
watchWorkspaceOpen(file: TFile) {
|
watchWorkspaceOpen(file: TFile) {
|
||||||
if (file == null) return;
|
if (file == null) return;
|
||||||
this.showIfConflicted(file);
|
this.showIfConflicted(file);
|
||||||
|
this.gcHook();
|
||||||
}
|
}
|
||||||
watchVaultChange(file: TFile, ...args: any[]) {
|
watchVaultChange(file: TFile, ...args: any[]) {
|
||||||
this.updateIntoDB(file);
|
this.updateIntoDB(file);
|
||||||
|
this.gcHook();
|
||||||
}
|
}
|
||||||
watchVaultDelete(file: TFile & TFolder) {
|
watchVaultDelete(file: TFile & TFolder) {
|
||||||
if (file.children) {
|
if (file.children) {
|
||||||
@@ -602,6 +748,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
} else {
|
} else {
|
||||||
this.deleteFromDB(file);
|
this.deleteFromDB(file);
|
||||||
}
|
}
|
||||||
|
this.gcHook();
|
||||||
}
|
}
|
||||||
watchVaultRename(file: TFile & TFolder, oldFile: any) {
|
watchVaultRename(file: TFile & TFolder, oldFile: any) {
|
||||||
if (file.children) {
|
if (file.children) {
|
||||||
@@ -611,10 +758,17 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
this.updateIntoDB(file);
|
this.updateIntoDB(file);
|
||||||
this.deleteFromDBbyPath(oldFile);
|
this.deleteFromDBbyPath(oldFile);
|
||||||
}
|
}
|
||||||
|
this.gcHook();
|
||||||
}
|
}
|
||||||
|
|
||||||
//--> Basic document Functions
|
//--> Basic document Functions
|
||||||
async addLog(message: any, isNotify?: boolean) {
|
async addLog(message: any, isNotify?: boolean) {
|
||||||
|
// debugger;
|
||||||
|
|
||||||
|
if (!isNotify && this.settings && this.settings.lessInformationInLog) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// console.log(this.settings);
|
||||||
let timestamp = new Date().toLocaleString();
|
let timestamp = new Date().toLocaleString();
|
||||||
let messagecontent = typeof message == "string" ? message : JSON.stringify(message, null, 2);
|
let messagecontent = typeof message == "string" ? message : JSON.stringify(message, null, 2);
|
||||||
let newmessage = timestamp + "->" + messagecontent;
|
let newmessage = timestamp + "->" + messagecontent;
|
||||||
@@ -625,9 +779,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
if (this.statusBar2 != null) {
|
if (this.statusBar2 != null) {
|
||||||
this.statusBar2.setText(newmessage.substring(0, 60));
|
this.statusBar2.setText(newmessage.substring(0, 60));
|
||||||
}
|
}
|
||||||
if (this.onLogChanged != null) {
|
// if (this.onLogChanged != null) {
|
||||||
this.onLogChanged();
|
// this.onLogChanged();
|
||||||
}
|
// }
|
||||||
if (isNotify) {
|
if (isNotify) {
|
||||||
new Notice(messagecontent);
|
new Notice(messagecontent);
|
||||||
}
|
}
|
||||||
@@ -725,18 +879,16 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//---> Sync
|
//---> Sync
|
||||||
async parseReplicationResult(e: PouchDB.Replication.SyncResult<{}>): Promise<void> {
|
async parseReplicationResult(docs: Array<PouchDB.Core.ExistingDocument<Entry>>): Promise<void> {
|
||||||
let docs = e.change.docs;
|
|
||||||
for (var change of docs) {
|
for (var change of docs) {
|
||||||
this.addLog("replication change arrived");
|
this.addLog("replication change arrived");
|
||||||
// this.addLog(change);
|
await this.pouchdbChanged(change);
|
||||||
await this.pouchdbChanged(change as Entry);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async realizeSettingSyncMode() {
|
async realizeSettingSyncMode() {
|
||||||
await this.localDatabase.closeReplication();
|
await this.localDatabase.closeReplication();
|
||||||
if (this.settings.liveSync) {
|
if (this.settings.liveSync) {
|
||||||
this.localDatabase.openReplication(this.settings, true, this.parseReplicationResult);
|
this.localDatabase.openReplication(this.settings, true, false, this.parseReplicationResult);
|
||||||
this.refreshStatusText();
|
this.refreshStatusText();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -744,8 +896,8 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
let statusStr = this.localDatabase.status();
|
let statusStr = this.localDatabase.status();
|
||||||
this.statusBar.setText("Sync:" + statusStr);
|
this.statusBar.setText("Sync:" + statusStr);
|
||||||
}
|
}
|
||||||
async replicate() {
|
async replicate(showMessage?: boolean) {
|
||||||
this.localDatabase.openReplication(this.settings, false, this.parseReplicationResult);
|
this.localDatabase.openReplication(this.settings, false, showMessage, this.parseReplicationResult);
|
||||||
}
|
}
|
||||||
//<-- Sync
|
//<-- Sync
|
||||||
|
|
||||||
@@ -787,13 +939,31 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
if (entry.children) {
|
if (entry.children) {
|
||||||
this.addLog(`->is dir`);
|
this.addLog(`->is dir`);
|
||||||
await this.deleteFolderOnDB(entry);
|
await this.deleteFolderOnDB(entry);
|
||||||
await this.app.vault.delete(entry);
|
try {
|
||||||
|
await this.app.vault.delete(entry);
|
||||||
|
} catch (ex) {
|
||||||
|
if (ex.code && ex.code == "ENOENT") {
|
||||||
|
//NO OP.
|
||||||
|
} else {
|
||||||
|
this.addLog(`error while delete filder:${entry.path}`);
|
||||||
|
this.addLog(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.addLog(`->is file`);
|
this.addLog(`->is file`);
|
||||||
await this.deleteFromDB(entry);
|
await this.deleteFromDB(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await this.app.vault.delete(folder);
|
try {
|
||||||
|
await this.app.vault.delete(folder);
|
||||||
|
} catch (ex) {
|
||||||
|
if (ex.code && ex.code == "ENOENT") {
|
||||||
|
//NO OP.
|
||||||
|
} else {
|
||||||
|
this.addLog(`error while delete filder:${folder.path}`);
|
||||||
|
this.addLog(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async renameFolder(folder: TFolder, oldFile: any) {
|
async renameFolder(folder: TFolder, oldFile: any) {
|
||||||
@@ -956,7 +1126,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
let oldData = { data: old.data, deleted: old._deleted };
|
let oldData = { data: old.data, deleted: old._deleted };
|
||||||
let newData = { data: d.data, deleted: d._deleted };
|
let newData = { data: d.data, deleted: d._deleted };
|
||||||
if (JSON.stringify(oldData) == JSON.stringify(newData)) {
|
if (JSON.stringify(oldData) == JSON.stringify(newData)) {
|
||||||
this.addLog("no changed" + fullpath + d._deleted);
|
this.addLog("no changed" + fullpath + (d._deleted ? " (deleted)" : ""));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// d._rev = old._rev;
|
// d._rev = old._rev;
|
||||||
@@ -1001,9 +1171,8 @@ class LogDisplayModal extends Modal {
|
|||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
}
|
}
|
||||||
updateLog() {
|
updateLog() {
|
||||||
let logs = [...this.plugin.logMessage];
|
|
||||||
let msg = "";
|
let msg = "";
|
||||||
for (var v of logs) {
|
for (var v of this.plugin.logMessage) {
|
||||||
msg += escapeStringToHTML(v) + "<br>";
|
msg += escapeStringToHTML(v) + "<br>";
|
||||||
}
|
}
|
||||||
this.logEl.innerHTML = msg;
|
this.logEl.innerHTML = msg;
|
||||||
@@ -1017,13 +1186,14 @@ 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.plugin.onLogChanged = this.updateLog;
|
this.updateLog = this.updateLog.bind(this);
|
||||||
|
// this.plugin.onLogChanged = this.updateLog;
|
||||||
this.updateLog();
|
this.updateLog();
|
||||||
}
|
}
|
||||||
onClose() {
|
onClose() {
|
||||||
let { contentEl } = this;
|
let { contentEl } = this;
|
||||||
contentEl.empty();
|
contentEl.empty();
|
||||||
this.plugin.onLogChanged = null;
|
// this.plugin.onLogChanged = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
class ConflictResolveModal extends Modal {
|
class ConflictResolveModal extends Modal {
|
||||||
@@ -1158,6 +1328,49 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName("File to Database saving delay")
|
||||||
|
.setDesc("ms, between 200 and 5000, restart required.")
|
||||||
|
.addText((text) => {
|
||||||
|
text.setPlaceholder("")
|
||||||
|
.setValue(this.plugin.settings.savingDelay + "")
|
||||||
|
.onChange(async (value) => {
|
||||||
|
let v = Number(value);
|
||||||
|
if (isNaN(v) || v < 200 || v > 5000) {
|
||||||
|
return 200;
|
||||||
|
//text.inputEl.va;
|
||||||
|
}
|
||||||
|
this.plugin.settings.savingDelay = v;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
});
|
||||||
|
text.inputEl.setAttribute("type", "number");
|
||||||
|
});
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName("Auto GC delay")
|
||||||
|
.setDesc("(seconds), if you set zero, you have to run manually.")
|
||||||
|
.addText((text) => {
|
||||||
|
text.setPlaceholder("")
|
||||||
|
.setValue(this.plugin.settings.gcDelay + "")
|
||||||
|
.onChange(async (value) => {
|
||||||
|
let v = Number(value);
|
||||||
|
if (isNaN(v) || v < 200 || v > 5000) {
|
||||||
|
return 30;
|
||||||
|
//text.inputEl.va;
|
||||||
|
}
|
||||||
|
this.plugin.settings.gcDelay = v;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
});
|
||||||
|
text.inputEl.setAttribute("type", "number");
|
||||||
|
});
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName("Log")
|
||||||
|
.setDesc("Reduce log infomations")
|
||||||
|
.addToggle((toggle) =>
|
||||||
|
toggle.setValue(this.plugin.settings.lessInformationInLog).onChange(async (value) => {
|
||||||
|
this.plugin.settings.lessInformationInLog = value;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
})
|
||||||
|
);
|
||||||
new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
.setName("LiveSync")
|
.setName("LiveSync")
|
||||||
.setDesc("Sync realtime")
|
.setDesc("Sync realtime")
|
||||||
@@ -1205,6 +1418,25 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
//await this.test();
|
//await this.test();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName("Garbage Collect")
|
||||||
|
.addButton((button) =>
|
||||||
|
button
|
||||||
|
.setButtonText("Garbage Collection")
|
||||||
|
.setDisabled(false)
|
||||||
|
.onClick(async () => {
|
||||||
|
await this.plugin.garbageCollect();
|
||||||
|
//await this.test();
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.addButton((button) =>
|
||||||
|
button
|
||||||
|
.setButtonText("Reset local files")
|
||||||
|
.setDisabled(false)
|
||||||
|
.onClick(async () => {
|
||||||
|
//await this.test();
|
||||||
|
})
|
||||||
|
);
|
||||||
new Setting(containerEl).setName("Remote Database Operations").addButton((button) =>
|
new Setting(containerEl).setName("Remote Database Operations").addButton((button) =>
|
||||||
button
|
button
|
||||||
.setButtonText("Reset remote database")
|
.setButtonText("Reset remote database")
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "obsidian-livesync",
|
"id": "obsidian-livesync",
|
||||||
"name": "Obsidian Live sync",
|
"name": "Obsidian Live sync",
|
||||||
"version": "0.0.6",
|
"version": "0.0.8",
|
||||||
"minAppVersion": "0.9.12",
|
"minAppVersion": "0.9.12",
|
||||||
"description": "obsidian Live synchronization plugin.",
|
"description": "obsidian Live synchronization plugin.",
|
||||||
"author": "vorotamoroz",
|
"author": "vorotamoroz",
|
||||||
|
|||||||
21
package-lock.json
generated
21
package-lock.json
generated
@@ -1,15 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "obsidian-pouch",
|
"name": "obsidian-livesync",
|
||||||
"version": "0.12.0",
|
"version": "0.0.8",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "obsidian-pouch",
|
"name": "obsidian-livesync",
|
||||||
"version": "0.12.0",
|
"version": "0.0.8",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"diff-match-patch": "^1.0.5"
|
"diff-match-patch": "^1.0.5",
|
||||||
|
"xxhash-wasm": "^0.4.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-commonjs": "^18.0.0",
|
"@rollup/plugin-commonjs": "^18.0.0",
|
||||||
@@ -539,6 +540,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
||||||
"dev": true
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/xxhash-wasm": {
|
||||||
|
"version": "0.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-0.4.2.tgz",
|
||||||
|
"integrity": "sha512-/eyHVRJQCirEkSZ1agRSCwriMhwlyUcFkXD5TPVSLP+IPzjsqMVzZwdoczLp1SoQU0R3dxz1RpIK+4YNQbCVOA=="
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -975,6 +981,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
||||||
"dev": true
|
"dev": true
|
||||||
|
},
|
||||||
|
"xxhash-wasm": {
|
||||||
|
"version": "0.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-0.4.2.tgz",
|
||||||
|
"integrity": "sha512-/eyHVRJQCirEkSZ1agRSCwriMhwlyUcFkXD5TPVSLP+IPzjsqMVzZwdoczLp1SoQU0R3dxz1RpIK+4YNQbCVOA=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "obsidian-livesync",
|
"name": "obsidian-livesync",
|
||||||
"version": "0.0.6",
|
"version": "0.0.8",
|
||||||
"description": "obsidian Live synchronization plugin.",
|
"description": "obsidian Live synchronization plugin.",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
"typescript": "^4.2.4"
|
"typescript": "^4.2.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"diff-match-patch": "^1.0.5"
|
"diff-match-patch": "^1.0.5",
|
||||||
|
"xxhash-wasm": "^0.4.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ export default {
|
|||||||
exports: "default",
|
exports: "default",
|
||||||
banner,
|
banner,
|
||||||
},
|
},
|
||||||
// treeshake: "safest",
|
|
||||||
external: ["obsidian"],
|
external: ["obsidian"],
|
||||||
plugins: [
|
plugins: [
|
||||||
typescript({ exclude: ["pouchdb-browser.js", "pouchdb-browser-webpack"] }),
|
typescript({ exclude: ["pouchdb-browser.js", "pouchdb-browser-webpack"] }),
|
||||||
@@ -28,9 +27,5 @@ export default {
|
|||||||
browser: true,
|
browser: true,
|
||||||
}),
|
}),
|
||||||
commonjs(),
|
commonjs(),
|
||||||
// nodePolyfills(
|
|
||||||
// // // {crypto:true}
|
|
||||||
// { include: "pouchdb-browser" }
|
|
||||||
// ),
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user