brushup readme

add delete handling
icon fix
This commit is contained in:
vorotamoroz
2021-10-13 21:38:44 +09:00
parent ed0d8372cb
commit 44efde7762
23 changed files with 563 additions and 217 deletions

106
README.md
View File

@@ -1,24 +1,100 @@
# obsidian-livesync # obsidian-livesync
This is the obsidian plugin that enables livesync between multi terminals. This is the obsidian plugin that enables livesync between multi terminals.
<!-- screenshot/movie will coming. --> <!-- screenshot/movie will coming. -->
**It's on the bleeding edge. Do not use for your "precious" Vault.** **It's on the bleeding edge. Do not use for your "precious" Vault.**
## This plugin enables.. ## This plugin enables..
- Live sync
- Self-Hosted data synchronization with conflict detection and resolving in Obsidian.
## how to use the experimental build - Live sync
- Self-Hosted data synchronization with conflict detection and resolving in Obsidian.
1. download this repo and expand `[your-vault]/.obsidian/plugins/` (PC, Mac and Android will work) ## how to use the experimental build
1. enable obsidian livesync in the settings dialog.
1. If you use your self-hosted CouchDB, set your server's info.
1. or Use [IBM Cloudant](https://www.ibm.com/cloud/cloudant), take an account and enable **Cloudant** in [Catalog](https://cloud.ibm.com/catalog#services)
Note please choose "IAM and legacy credentials" for the Authentication method
Setup details are in Couldant Setup Section.
1. Setup LiveSync or SyncOnSave or SyncOnStart as you like.
### Cloudant Setup 1. download this repo and expand `[your-vault]/.obsidian/plugins/` (PC, Mac and Android will work)
**WIP** 1. enable obsidian livesync in the settings dialog.
1. If you use your self-hosted CouchDB, set your server's info.
1. or Use [IBM Cloudant](https://www.ibm.com/cloud/cloudant), take an account and enable **Cloudant** in [Catalog](https://cloud.ibm.com/catalog#services)
Note please choose "IAM and legacy credentials" for the Authentication method
Setup details are in Couldant Setup Section.
1. Setup LiveSync or SyncOnSave or SyncOnStart as you like.
## Cloudant Setup
### Creating an Instance
1. Hit the "Create Resource" button.
![step 1](./instruction_images/cloudant_1.png)
1. In IBM Cloud Catalog, search "Cloudant".
![step 2](instruction_images/cloudant_2.png)
1. You can choise "Lite plan" in free.
![step 3](instruction_images/cloudant_3.png)
Select Multitenant(it's the default) and the region as you like.
![step 4](instruction_images/cloudant_4.png) 3. Be sure to select "IAM and Legacy credentials" for "Authentication Method".
![step 5](instruction_images/cloudant_5.png)
4. Select Lite and be sure to check the capacity.
![step 6](instruction_images/cloudant_6.png)
5. And hit "Create" on the right panel.
![step 7](instruction_images/cloudant_7.png)
6. When all of the above steps have been done, Open "Resource list" on the left pane. you can see the Cloudant instance in the "Service and software". Click it.
![step 8](instruction_images/cloudant_8.png)
7. In resource details, there's information to connect from obsidian-livesync.
Copy the "External Endpoint(preferred)" address. <sup>(\*1)</sup>
![step 9](instruction_images/cloudant_9.png)
### CouchDB setup
1. Hit the "Launch Dashboard" button, Cloudant dashboard will be shown.
Yes, it's almost CouchDB's fauxton.
![step 1](instruction_images/couchdb_1.png)
1. First, you have to enable the CORS option.
Hit the Account menu and open the "CORS" tab.
Initially, "Origin Domains" is set to "Restrict to specific domains"., so set to "All domains(\*)"
_NOTE: of course We want to set "app://obsidian.md" but it's not acceptable on Cloudant._
![step 2](instruction_images/couchdb_2.png)
1. And open the "Databases" tab and hit the "Create Database" button.
Enter the name as you like <sup>(\*2)</sup> and Hit the "Create" button below.
![step 3](instruction_images/couchdb_3.png)
1. If the database was shown with joyful messages, then you can close this browser tab now.
![step 4](instruction_images/couchdb_4.png)
### Credentials Setup
1. Back into IBM Cloud, Open the "Service credentials". You'll get an empty list, hit the "New credential" button.
![step 1](instruction_images/credentials_1.png)
1. The dialog to create a credential will be shown.
type any name or leave it default, hit the "Add" button.
![step 2](instruction_images/credentials_2.png)
_NOTE: This "name" is not related to your username that uses in Obsidian-livesync._
1. Back to "Service credentials", the new credential should be created.
open details.
![step 3](instruction_images/credentials_3.png)
The username and password pair is inside this JSON.
"username" and "password" are so.
follow the figure, it's
"apikey-v2-2unu15184f7o8emr90xlqgkm2ncwhbltml6tgnjl9sd5"<sup>(\*3)</sup> and "c2c11651d75497fa3d3c486e4c8bdf27"<sup>(\*4)</sup>
### obsidian-livesync setting
![xx](instruction_images/obsidian_sync_1.png)
example values.
| Items | Value | example |
| ------------------- | ----------- | --------------------------------------------------------------------------- |
| CouchDB Remote URI: | (\*1)/(\*2) | https://xxxxxxxxxxxxxxxxx-bluemix.cloudantnosqldb.appdomain.cloud/sync-test |
| CouchDB Username | (\*3) | apikey-v2-2unu15184f7o8emr90xlqgkm2ncwhbltml6tgnjl9sd5 |
| CouchDB Password | (\*4) | c2c11651d75497fa3d3c486e4c8bdf27 |

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

290
main.js

File diff suppressed because one or more lines are too long

300
main.ts
View File

@@ -1,4 +1,4 @@
import { App, debounce, Modal, Notice, Plugin, PluginSettingTab, Setting, TFile, Vault, DataWriteOptions, View } 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";
@@ -104,22 +104,47 @@ const escapeStringToHTML = (str: string) => {
return escape[match]; return escape[match];
}); });
}; };
const isValidRemoteCouchDBURI = (uri: string): boolean => {
if (uri.startsWith("https://")) return true;
if (uri.startsWith("http://")) return true;
return false;
};
const connectRemoteCouchDB = async (uri: string, auth: { username: string; password: string }): Promise<false | { db: PouchDB.Database; info: any }> => {
if (!isValidRemoteCouchDBURI(uri)) false;
let db = new PouchDB(uri, {
auth,
});
try {
let info = await db.info();
return { db: db, info: info };
} catch (ex) {
return;
}
};
//<--Functions //<--Functions
export default class ObsidianLiveSyncPlugin extends Plugin { export default class ObsidianLiveSyncPlugin extends Plugin {
settings: ObsidianLiveSyncSettings; settings: ObsidianLiveSyncSettings;
localDatabase: PouchDB.Database<Notes>; localDatabase: PouchDB.Database<Notes>;
logMessage: string[] = []; logMessage: string[] = [];
onLogChanged: () => void;
async addLog(message: any) { async addLog(message: any, isNotify?: boolean) {
let timestamp = new Date().toLocaleString(); let timestamp = new Date().toLocaleString();
let newmessage = timestamp + "->" + (typeof message == "string" ? message : JSON.stringify(message, null, 2)); let messagecontent = typeof message == "string" ? message : JSON.stringify(message, null, 2);
let newmessage = timestamp + "->" + messagecontent;
this.logMessage = [].concat(this.logMessage).concat([newmessage]).slice(-100); this.logMessage = [].concat(this.logMessage).concat([newmessage]).slice(-100);
// this.logMessage = [...this.logMessage, timestamp + ":" + newmessage].slice(-100); // this.logMessage = [...this.logMessage, timestamp + ":" + newmessage].slice(-100);
console.log(newmessage); console.log(newmessage);
if (this.statusBar2 != null) { if (this.statusBar2 != null) {
this.statusBar2.setText(newmessage); this.statusBar2.setText(newmessage.substring(0, 60));
}
if (this.onLogChanged != null) {
this.onLogChanged();
}
if (isNotify) {
new Notice(messagecontent);
} }
} }
@@ -132,8 +157,13 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
try { try {
await this.app.vault.createFolder(c); await this.app.vault.createFolder(c);
} catch (ex) { } catch (ex) {
this.addLog("ensure excep"); // basically skip exceptions.
this.addLog(ex); if (ex.message && ex.message == "Folder already exists.") {
// especialy this message is.
} else {
this.addLog("Folder Create Error");
this.addLog(ex);
}
} }
c += "/"; c += "/";
} }
@@ -148,17 +178,27 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
await this.app.vault.trigger("create", newfile); await this.app.vault.trigger("create", newfile);
} }
} }
async deleteVaultItem(file: TFile | TFolder) {
let dir = file.parent;
await this.app.vault.delete(file);
this.addLog(`deleted:${file.path}`);
this.addLog(`other items:${dir.children.length}`);
if (dir.children.length == 0) {
this.addLog(`all files deleted by replication, so delete dir`);
await this.deleteVaultItem(dir);
}
}
async doc2storate_modify(doc: Notes, file: TFile, force?: boolean) { async doc2storate_modify(doc: Notes, file: TFile, force?: boolean) {
if (doc._deleted) { if (doc._deleted) {
//basically pass. //basically pass.
//but if there're no docs left, delete file. //but if there're no docs left, delete file.
try { try {
let lastDocs = this.localDatabase.get(doc._id); let lastDocs = await this.localDatabase.get(doc._id);
this.addLog(`delete skipped:${doc._id}`); this.addLog(`delete skipped:${lastDocs._id}`);
} catch (ex) { } catch (ex) {
if (ex.status || ex.status == 404) { if (ex.status || ex.status == 404) {
//no op. //no op.
await this.app.vault.delete(file); await this.deleteVaultItem(file);
} }
} }
return; return;
@@ -203,17 +243,17 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
if (this.syncHandler != null) { if (this.syncHandler != null) {
return false; return false;
} }
let db = new PouchDB(this.settings.couchDB_URI, {
auth: { let dbret = await connectRemoteCouchDB(this.settings.couchDB_URI, {
username: this.settings.couchDB_USER, username: this.settings.couchDB_USER,
password: this.settings.couchDB_PASSWORD, password: this.settings.couchDB_PASSWORD,
},
}); });
try { if (dbret === false) {
let info = await db.info(); this.addLog(`could not connect to ${this.settings.couchDB_URI}`, true);
} catch (ex) {
return; return;
} }
let db = dbret.db;
this.syncHandler = this.localDatabase.sync(db, { live: true, retry: true }); this.syncHandler = this.localDatabase.sync(db, { live: true, retry: true });
this.syncHandler this.syncHandler
@@ -222,20 +262,22 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
for (var change of docs) { for (var change of docs) {
await this.pouchdbChanged(change as Notes); await this.pouchdbChanged(change as Notes);
} }
this.addLog(`replicated ${docs.length} doc(s)`);
localStorage.setItem("last-sync-no", e.change.last_seq + ""); localStorage.setItem("last-sync-no", e.change.last_seq + "");
// new Notice(`changed:${JSON.stringify(e)}`); // new Notice(`changed:${JSON.stringify(e)}`);
}) })
.on("active", () => { .on("active", () => {
// new Notice(`Replication activated`); // new Notice(`Replication activated`);
this.addLog("Replication activated");
}) })
.on("complete", (e) => { .on("complete", (e) => {
new Notice(`Replication completed`); this.addLog("Replication completed", true);
}) })
.on("denied", (e) => { .on("denied", (e) => {
new Notice(`Replication denied`); this.addLog("Replication denied", true);
}) })
.on("error", (e) => { .on("error", (e) => {
new Notice(`Replication error`); this.addLog("Replication error", true);
}) })
.on("paused", (e) => { .on("paused", (e) => {
// new Notice(`Replication paused`); // new Notice(`Replication paused`);
@@ -274,7 +316,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
this.statusBar.setText("Sync:" + statusStr); this.statusBar.setText("Sync:" + statusStr);
} }
async initializeDatabase() { async initializeDatabase() {
// debugger;
let vaultName = this.app.vault.getName(); let vaultName = this.app.vault.getName();
this.localDatabase = new PouchDB<Notes>(vaultName + "-livesync", { this.localDatabase = new PouchDB<Notes>(vaultName + "-livesync", {
@@ -289,14 +330,17 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
statusBar2: HTMLElement; statusBar2: HTMLElement;
//<-- Sync //<-- Sync
async replicate() { async replicate() {
let db = new PouchDB(this.settings.couchDB_URI, { let dbret = await connectRemoteCouchDB(this.settings.couchDB_URI, {
auth: { username: this.settings.couchDB_USER,
username: this.settings.couchDB_USER, password: this.settings.couchDB_PASSWORD,
password: this.settings.couchDB_PASSWORD,
},
}); });
if (dbret === false) {
this.addLog("could not connect to database");
return;
}
try { try {
var info = await db.info(); let db = dbret.db;
var info = dbret.info;
new Notice(`Connected to ${info.db_name}`); new Notice(`Connected to ${info.db_name}`);
this.localDatabase this.localDatabase
.sync(db, { .sync(db, {
@@ -304,8 +348,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
// since: "0", // since: "0",
}) })
.on("change", async (e) => { .on("change", async (e) => {
// debugger;
let docs = e.change.docs; 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");
@@ -313,8 +355,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
await this.pouchdbChanged(change as Notes); await this.pouchdbChanged(change as Notes);
} }
localStorage.setItem("last-sync-no", e.change.last_seq + ""); localStorage.setItem("last-sync-no", e.change.last_seq + "");
// this.addLog(e.change.last_seq);
// new Notice(`changed:${JSON.stringify(e)}`);
}) })
.on("active", () => { .on("active", () => {
new Notice(`Replication activated`); new Notice(`Replication activated`);
@@ -339,31 +379,47 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
this.addLog("loading plugin"); this.addLog("loading plugin");
await this.loadSettings(); await this.loadSettings();
addIcon(
this.addRibbonIcon("dice", "Replicate", async () => { "replicate",
`<g transform="matrix(1.15 0 0 1.15 -8.31 -9.52)" fill="currentColor" fill-rule="evenodd">
<path d="m85 22.2c-0.799-4.74-4.99-8.37-9.88-8.37-0.499 0-1.1 0.101-1.6 0.101-2.4-3.03-6.09-4.94-10.3-4.94-6.09 0-11.2 4.14-12.8 9.79-5.59 1.11-9.78 6.05-9.78 12 0 6.76 5.39 12.2 12 12.2h29.9c5.79 0 10.1-4.74 10.1-10.6 0-4.84-3.29-8.88-7.68-10.2zm-2.99 14.7h-29.5c-2.3-0.202-4.29-1.51-5.29-3.53-0.899-2.12-0.699-4.54 0.698-6.46 1.2-1.61 2.99-2.52 4.89-2.52 0.299 0 0.698 0 0.998 0.101l1.8 0.303v-2.02c0-3.63 2.4-6.76 5.89-7.57 0.599-0.101 1.2-0.202 1.8-0.202 2.89 0 5.49 1.62 6.79 4.24l0.598 1.21 1.3-0.504c0.599-0.202 1.3-0.303 2-0.303 1.3 0 2.5 0.404 3.59 1.11 1.6 1.21 2.6 3.13 2.6 5.15v1.61h2c2.6 0 4.69 2.12 4.69 4.74-0.099 2.52-2.2 4.64-4.79 4.64z"/>
<path d="m53.2 49.2h-41.6c-1.8 0-3.2 1.4-3.2 3.2v28.6c0 1.8 1.4 3.2 3.2 3.2h15.8v4h-7v6h24v-6h-7v-4h15.8c1.8 0 3.2-1.4 3.2-3.2v-28.6c0-1.8-1.4-3.2-3.2-3.2zm-2.8 29h-36v-23h36z"/>
<path d="m73 49.2c1.02 1.29 1.53 2.97 1.53 4.56 0 2.97-1.74 5.65-4.39 7.04v-4.06l-7.46 7.33 7.46 7.14v-4.06c7.66-1.98 12.2-9.61 10-17-0.102-0.297-0.205-0.595-0.307-0.892z"/>
<path d="m24.1 43c-0.817-0.991-1.53-2.97-1.53-4.56 0-2.97 1.74-5.65 4.39-7.04v4.06l7.46-7.33-7.46-7.14v4.06c-7.66 1.98-12.2 9.61-10 17 0.102 0.297 0.205 0.595 0.307 0.892z"/>
</g>`
);
addIcon(
"view-log",
`<g transform="matrix(1.28 0 0 1.28 -131 -411)" fill="currentColor" fill-rule="evenodd">
<path d="m103 330h76v12h-76z"/>
<path d="m106 346v44h70v-44zm45 16h-20v-8h20z"/>
</g>`
);
this.addRibbonIcon("replicate", "Replicate", async () => {
await this.replicate(); await this.replicate();
}); });
this.addRibbonIcon("dice", "pull file force", async () => { // this.addRibbonIcon("dice", "pull file force", async () => {
this.localDatabase // this.localDatabase
.changes({ // .changes({
since: 0, // since: 0,
include_docs: true, // include_docs: true,
}) // })
.on("change", async (change) => { // .on("change", async (change) => {
await this.pouchdbChanged(change.doc); // await this.pouchdbChanged(change.doc);
localStorage.setItem("last-sync-no", change.seq + ""); // localStorage.setItem("last-sync-no", change.seq + "");
// received a change // // received a change
}) // })
.on("error", function (err) { // .on("error", function (err) {
// handle errors // // handle errors
}); // });
}); // });
this.addRibbonIcon("dice", "Toggle Sync", () => {
this.toggleSync(); this.addRibbonIcon("view-log", "Show log", () => {
// Vault.recurseChildren(); new LogDisplayModal(this.app, this).open();
}); });
this.statusBar = this.addStatusBarItem(); this.statusBar = this.addStatusBarItem();
this.statusBar2 = this.addStatusBarItem(); this.statusBar2 = this.addStatusBarItem();
this.watchVaultChange = debounce(this.watchVaultChange.bind(this), 200, false); this.watchVaultChange = debounce(this.watchVaultChange.bind(this), 200, false);
@@ -423,17 +479,57 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
this.registerEvent(this.app.workspace.on("file-open", this.watchWorkspaceOpen)); this.registerEvent(this.app.workspace.on("file-open", this.watchWorkspaceOpen));
} }
watchWorkspaceOpen(file: TFile) { watchWorkspaceOpen(file: TFile) {
if (file == null) return;
this.showIfConflicted(file); this.showIfConflicted(file);
} }
watchVaultChange(file: TFile, ...args: any[]) { watchVaultChange(file: TFile, ...args: any[]) {
this.updateDB(file); this.updateDB(file);
} }
watchVaultDelete(file: TFile) { watchVaultDelete(file: TFile & TFolder) {
this.deleteDB(file); // debugger;
if (file.children) {
//folder
this.deleteFolderOnDB(file);
// this.app.vault.delete(file);
} else {
this.deleteDB(file);
}
} }
watchVaultRename(file: any, oldFile: any) { async deleteFolderOnDB(folder: TFolder) {
this.updateDB(file); this.addLog(`delete folder:${folder.path}`);
this.deleteDBbyPath(oldFile); for (var v of folder.children) {
let entry = v as TFile & TFolder;
this.addLog(`->entry:${entry.path}`);
if (entry.children) {
this.addLog(`->is dir`);
await this.deleteFolderOnDB(entry);
await this.app.vault.delete(entry);
} else {
this.addLog(`->is file`);
await this.deleteDB(entry);
}
}
await this.app.vault.delete(folder);
}
watchVaultRename(file: TFile & TFolder, oldFile: any) {
if (file.children) {
// this.renameFolder(file,oldFile);
this.addLog(`folder name changed:(this operation is not supported) ${file.path}`);
} else {
this.updateDB(file);
this.deleteDBbyPath(oldFile);
}
}
async renameFolder(folder: TFolder, oldFile: any) {
for (var v of folder.children) {
let entry = v as TFile & TFolder;
if (entry.children) {
await this.deleteFolderOnDB(entry);
this.app.vault.delete(entry);
} else {
await this.deleteDB(entry);
}
}
} }
// --> conflict resolving // --> conflict resolving
@@ -454,7 +550,18 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
return false; return false;
} }
async getConflictedStatus(path: string): Promise<diff_check_result> { async getConflictedStatus(path: string): Promise<diff_check_result> {
let test = await this.localDatabase.get(path, { conflicts: true }); let test: Notes & PouchDB.Core.GetMeta = null;
try {
test = await this.localDatabase.get(path, { conflicts: true });
} catch (ex) {
if (ex.status && ex.status == 404) {
this.addLog(`Getting conflicted status, but there was not ${path}`);
// NO OP.
} else {
throw ex;
}
}
if (test == null) return false;
if (!test._conflicts) return false; if (!test._conflicts) return false;
if (test._conflicts.length == 0) return false; if (test._conflicts.length == 0) return false;
// should be two or more conflicts; // should be two or more conflicts;
@@ -527,6 +634,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
let doc = await this.localDatabase.get(filename); let doc = await this.localDatabase.get(filename);
await this.doc2storate_modify(doc, file, force); await this.doc2storate_modify(doc, file, force);
} else { } else {
this.addLog(`target files:${filename} is two or more files in your vault`);
//something went wrong.. //something went wrong..
} }
//when to opened file; //when to opened file;
@@ -559,8 +667,8 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}; };
try { try {
let old = await this.localDatabase.get(fullpath); let old = await this.localDatabase.get(fullpath);
let oldData = { data: old.data }; let oldData = { data: old.data, deleted: old._deleted };
let newData = { data: d.data }; 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); this.addLog("no changed" + fullpath);
return; return;
@@ -575,7 +683,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
} }
let ret = await this.localDatabase.put(d); let ret = await this.localDatabase.put(d);
this.addLog("put database " + fullpath); this.addLog("put database:" + fullpath);
this.addLog(ret); this.addLog(ret);
if (this.settings.syncOnSave) { if (this.settings.syncOnSave) {
await this.replicate(); await this.replicate();
@@ -623,8 +731,36 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
} }
} }
class LogDisplayModal extends Modal { class LogDisplayModal extends Modal {
constructor(app: App) { plugin: ObsidianLiveSyncPlugin;
logEl: HTMLDivElement;
constructor(app: App, plugin: ObsidianLiveSyncPlugin) {
super(app); super(app);
this.plugin = plugin;
}
updateLog() {
let logs = [...this.plugin.logMessage];
let msg = "";
for (var v of logs) {
msg += escapeStringToHTML(v) + "<br>";
}
this.logEl.innerHTML = msg;
}
onOpen() {
let { contentEl } = this;
contentEl.empty();
contentEl.createEl("h2", { text: "Sync Status" });
let div = contentEl.createDiv("");
div.addClass("op-scrollable");
div.addClass("op-pre");
this.logEl = div;
this.plugin.onLogChanged = this.updateLog;
this.updateLog();
}
onClose() {
let { contentEl } = this;
contentEl.empty();
this.plugin.onLogChanged = null;
} }
} }
class ConflictResolveModal extends Modal { class ConflictResolveModal extends Modal {
@@ -700,18 +836,17 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
this.plugin = plugin; this.plugin = plugin;
} }
async test(): Promise<void> { async test(): Promise<void> {
let db = new PouchDB(this.plugin.settings.couchDB_URI, { let db = await connectRemoteCouchDB(this.plugin.settings.couchDB_URI, {
auth: { username: this.plugin.settings.couchDB_USER,
username: this.plugin.settings.couchDB_USER, password: this.plugin.settings.couchDB_PASSWORD,
password: this.plugin.settings.couchDB_PASSWORD,
},
}); });
try { if (db === false) {
var info = await db.info(); this.plugin.addLog(`could not connect to ${this.plugin.settings.couchDB_URI}`);
new Notice(`Connected to ${info.db_name}`); new Notice(`could not connect to ${this.plugin.settings.couchDB_URI}`);
} catch (ex) { return;
new Notice("Could not connect to db:" + ex);
} }
new Notice(`Connected to ${db.info.db_name}`);
this.plugin.addLog(`Connected to ${db.info.db_name}`);
} }
display(): void { display(): void {
let { containerEl } = this; let { containerEl } = this;
@@ -720,18 +855,15 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
containerEl.createEl("h2", { text: "Settings for obsidian-livesync." }); containerEl.createEl("h2", { text: "Settings for obsidian-livesync." });
new Setting(containerEl) new Setting(containerEl).setName("CouchDB Remote URI").addText((text) =>
.setName("CouchDB Remote URI") text
.setDesc("It's a secret") .setPlaceholder("https://........")
.addText((text) => .setValue(this.plugin.settings.couchDB_URI)
text .onChange(async (value) => {
.setPlaceholder("https://........") this.plugin.settings.couchDB_URI = value;
.setValue(this.plugin.settings.couchDB_URI) await this.plugin.saveSettings();
.onChange(async (value) => { })
this.plugin.settings.couchDB_URI = value; );
await this.plugin.saveSettings();
})
);
new Setting(containerEl) new Setting(containerEl)
.setName("CouchDB Username") .setName("CouchDB Username")
.setDesc("username") .setDesc("username")
@@ -794,7 +926,7 @@ class ObsidianLiveSyncSettingTab extends PluginSettingTab {
}) })
); );
new Setting(containerEl) new Setting(containerEl)
.setName("Database Operations") .setName("Local Database Operations")
.addButton((button) => .addButton((button) =>
button button
.setButtonText("Reset local database") .setButtonText("Reset local database")

View File

@@ -1,40 +1,37 @@
import typescript from '@rollup/plugin-typescript'; import typescript from "@rollup/plugin-typescript";
import { nodeResolve } from '@rollup/plugin-node-resolve'; import { nodeResolve } from "@rollup/plugin-node-resolve";
import commonjs from '@rollup/plugin-commonjs'; import commonjs from "@rollup/plugin-commonjs";
import nodePolyfills from 'rollup-plugin-polyfill-node'; import nodePolyfills from "rollup-plugin-polyfill-node";
const isProd = (process.env.BUILD === 'production'); const isProd = process.env.BUILD === "production";
const banner = const banner = `/*
`/*
THIS IS A GENERATED/BUNDLED FILE BY ROLLUP THIS IS A GENERATED/BUNDLED FILE BY ROLLUP
if you want to view the source visit the plugins github repository if you want to view the source visit the plugins github repository
*/ */
`; `;
export default { export default {
input: 'main.ts', input: "main.ts",
output: { output: {
dir: '.', dir: ".",
sourcemap: 'inline', sourcemap: "inline",
sourcemapExcludeSources: isProd, sourcemapExcludeSources: isProd,
format: 'cjs', format: "cjs",
exports: 'default', exports: "default",
banner, banner,
}, },
// treeshake: "safest", // treeshake: "safest",
external: ['obsidian'], external: ["obsidian"],
plugins: [ plugins: [
typescript({exclude:["pouchdb-browser.js"]}), typescript({ exclude: ["pouchdb-browser.js", "pouchdb-browser-webpack"] }),
nodePolyfills( nodeResolve({
// // {crypto:true} browser: true,
{ include: "pouchdb-browser" } }),
), commonjs(),
nodeResolve({ // nodePolyfills(
browser: true, // // // {crypto:true}
// preferBuiltins: true // { include: "pouchdb-browser" }
}), // ),
commonjs(), ],
]
}; };

View File

@@ -1,22 +1,20 @@
/* Sets all the text color to red! */
/* body {
color: red;
} */
.added { .added {
color:black; color: black;
background-color: white; background-color: white;
} }
.normal { .normal {
color:lightgray; color: lightgray;
} }
.deleted { .deleted {
color:white; color: white;
background-color: black; background-color: black;
/* text-decoration: line-through; */ /* text-decoration: line-through; */
} }
.op-scrollable{ .op-scrollable {
overflow-y:scroll; overflow-y: scroll;
/* min-height: 280px; */ /* min-height: 280px; */
max-height: 280px; max-height: 280px;
} }
.op-pre {
white-space: pre-wrap;
}

View File

@@ -12,5 +12,6 @@
"lib": ["dom", "es5", "scripthost", "es2015"] "lib": ["dom", "es5", "scripthost", "es2015"]
}, },
"include": ["**/*.ts"], "include": ["**/*.ts"],
"exclude": ["pouchdb-browser.js"] "files": ["./main.ts"],
"exclude": ["pouchdb-browser-webpack"]
} }