mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2025-02-07 19:30:08 +02:00
Implemented:
- using Obsidian API to synchronize. - Copy button on history dialog. Documented: - Document improved.
This commit is contained in:
parent
255e7bf828
commit
3545ae9690
@ -18,7 +18,14 @@ Note: This password is saved into your Obsidian's vault in plain text.
|
||||
The Database name to synchronize.
|
||||
⚠️If not exist, created automatically.
|
||||
|
||||
|
||||
### Use the old connecting method
|
||||
Since v0.8.0, Self-hosted LiveSync uses Obsidian's API to connect to the CouchDB instead of the browser API.
|
||||
This method will increase the performance and avoid troubles with the CORS.
|
||||
But it doesn't been well tested yet. If you are troubled, please disable this option once.
|
||||
|
||||
### Test Database connection
|
||||
You can check the connection by clicking this button.
|
||||
|
||||
## Local Database Configurations
|
||||
"Local Database" is created inside your obsidian.
|
||||
@ -44,6 +51,8 @@ As a result, Obsidian's behavior is temporarily slowed down.
|
||||
Default is 300 seconds.
|
||||
If you are an early adopter, maybe this value is left as 30 seconds. Please change this value to larger values.
|
||||
|
||||
Note: If you want to use "Use history", this vault must be set to 0.
|
||||
|
||||
### Manual Garbage Collect
|
||||
Run "Garbage Collection" manually.
|
||||
|
||||
@ -52,6 +61,8 @@ Encrypt your database. It affects only the database, your files are left as plai
|
||||
|
||||
The encryption algorithm is AES-GCM.
|
||||
|
||||
Note: If you want to use "Plugins and their settings", you have to enable this.
|
||||
|
||||
### Passphrase
|
||||
The passphrase to used as the key of encryption. Please use the long text.
|
||||
|
||||
@ -195,6 +206,29 @@ You can set synchronization method at once as these pattern:
|
||||
- Sync on File Open : disabled
|
||||
- Sync on Start : disabled
|
||||
|
||||
### Use history
|
||||
If you enable this option, you can keep document histories in your database.
|
||||
(Not all intermediate changes are synchronized.)
|
||||
You can check the changes caused by your edit and/or replication.
|
||||
|
||||
### Enable plugin synchronization
|
||||
If you want to use this feature, you have to activate this feature by this switch.
|
||||
|
||||
### Sweep plugins automatically
|
||||
Plugin sweep will run before replication automatically.
|
||||
|
||||
### Sweep plugins periodically
|
||||
Plugin sweep will run each 1 minute.
|
||||
|
||||
### Notify updates
|
||||
When replication is complete, a message will be notified if a newer version of the plugin applied to this device is configured on another device.
|
||||
|
||||
### Device and Vault name
|
||||
To save the plugins, you have to set a unique name every each device.
|
||||
|
||||
### Open
|
||||
Open the "Plugins and their settings" dialog.
|
||||
|
||||
## Hatch
|
||||
From here, everything is under the hood. Please handle it with care.
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-livesync",
|
||||
"name": "Self-hosted LiveSync",
|
||||
"version": "0.7.2",
|
||||
"version": "0.8.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",
|
||||
|
18
package-lock.json
generated
18
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "obsidian-livesync",
|
||||
"version": "0.7.2",
|
||||
"version": "0.8.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "obsidian-livesync",
|
||||
"version": "0.7.2",
|
||||
"version": "0.8.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"diff-match-patch": "^1.0.5",
|
||||
@ -27,7 +27,7 @@
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-airbnb-base": "^14.2.1",
|
||||
"eslint-plugin-import": "^2.25.2",
|
||||
"obsidian": "^0.13.11",
|
||||
"obsidian": "^0.13.30",
|
||||
"rollup": "^2.32.1",
|
||||
"svelte-preprocess": "^4.10.2",
|
||||
"tslib": "^2.2.0",
|
||||
@ -2659,9 +2659,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/obsidian": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-0.13.11.tgz",
|
||||
"integrity": "sha512-KxOvAh4CG5vzcukmHvyuK9hUIr6ZFlM9FQfGZEwrrEV8VG2/W2Tk5cWrg0VM7EkGE3QBmjX6owjIDIO8QDXVUQ==",
|
||||
"version": "0.13.30",
|
||||
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-0.13.30.tgz",
|
||||
"integrity": "sha512-uAOrIyeHE9qYzg1Qjfpy/qlyLUFX9oyKWeHYO8NVDoI+pm5VUTMe7XWcsXPwb9iVsVmggVJcdV15Vqm9bljhxQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^0.19.6",
|
||||
@ -5470,9 +5470,9 @@
|
||||
}
|
||||
},
|
||||
"obsidian": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-0.13.11.tgz",
|
||||
"integrity": "sha512-KxOvAh4CG5vzcukmHvyuK9hUIr6ZFlM9FQfGZEwrrEV8VG2/W2Tk5cWrg0VM7EkGE3QBmjX6owjIDIO8QDXVUQ==",
|
||||
"version": "0.13.30",
|
||||
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-0.13.30.tgz",
|
||||
"integrity": "sha512-uAOrIyeHE9qYzg1Qjfpy/qlyLUFX9oyKWeHYO8NVDoI+pm5VUTMe7XWcsXPwb9iVsVmggVJcdV15Vqm9bljhxQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@codemirror/state": "^0.19.6",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "obsidian-livesync",
|
||||
"version": "0.7.2",
|
||||
"version": "0.8.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",
|
||||
@ -24,7 +24,7 @@
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-airbnb-base": "^14.2.1",
|
||||
"eslint-plugin-import": "^2.25.2",
|
||||
"obsidian": "^0.13.11",
|
||||
"obsidian": "^0.13.30",
|
||||
"rollup": "^2.32.1",
|
||||
"tslib": "^2.2.0",
|
||||
"typescript": "^4.2.4",
|
||||
|
@ -2,6 +2,8 @@ import { TFile, Modal, App } from "obsidian";
|
||||
import { path2id, escapeStringToHTML } from "./utils";
|
||||
import ObsidianLiveSyncPlugin from "./main";
|
||||
import { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch } from "diff-match-patch";
|
||||
import { LOG_LEVEL } from "./types";
|
||||
import { Logger } from "./logger";
|
||||
|
||||
export class DocumentHistoryModal extends Modal {
|
||||
plugin: ObsidianLiveSyncPlugin;
|
||||
@ -14,6 +16,7 @@ export class DocumentHistoryModal extends Modal {
|
||||
file: string;
|
||||
|
||||
revs_info: PouchDB.Core.RevisionInfo[] = [];
|
||||
currentText = "";
|
||||
|
||||
constructor(app: App, plugin: ObsidianLiveSyncPlugin, file: TFile) {
|
||||
super(app);
|
||||
@ -37,6 +40,7 @@ export class DocumentHistoryModal extends Modal {
|
||||
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);
|
||||
this.currentText = "";
|
||||
|
||||
if (w === false) {
|
||||
this.info.innerHTML = "";
|
||||
@ -44,6 +48,7 @@ export class DocumentHistoryModal extends Modal {
|
||||
} else {
|
||||
this.info.innerHTML = `Modified:${new Date(w.mtime).toLocaleString()}`;
|
||||
let result = "";
|
||||
this.currentText = w.data;
|
||||
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) {
|
||||
@ -124,6 +129,14 @@ export class DocumentHistoryModal extends Modal {
|
||||
this.contentView = div;
|
||||
div.addClass("op-scrollable");
|
||||
div.addClass("op-pre");
|
||||
const buttons = contentEl.createDiv("");
|
||||
buttons.createEl("button", { text: "Copy to clipboard" }, (e) => {
|
||||
e.addClass("mod-cta");
|
||||
e.addEventListener("click", async () => {
|
||||
await navigator.clipboard.writeText(this.currentText);
|
||||
Logger(`Old content copied to clipboard`, LOG_LEVEL.NOTICE);
|
||||
});
|
||||
});
|
||||
}
|
||||
onClose() {
|
||||
const { contentEl } = this;
|
||||
|
@ -743,7 +743,7 @@ export class LocalPouchDB {
|
||||
username: setting.couchDB_USER,
|
||||
password: setting.couchDB_PASSWORD,
|
||||
};
|
||||
const dbret = await connectRemoteCouchDB(uri, auth);
|
||||
const dbret = await connectRemoteCouchDB(uri, auth, setting.disableRequestURI);
|
||||
if (typeof dbret === "string") {
|
||||
Logger(`could not connect to ${uri}:${dbret}`, LOG_LEVEL.NOTICE);
|
||||
if (notice != null) notice.hide();
|
||||
@ -820,7 +820,7 @@ export class LocalPouchDB {
|
||||
Logger("Another replication running.");
|
||||
return false;
|
||||
}
|
||||
const dbret = await connectRemoteCouchDB(uri, auth);
|
||||
const dbret = await connectRemoteCouchDB(uri, auth, setting.disableRequestURI);
|
||||
if (typeof dbret === "string") {
|
||||
Logger(`could not connect to ${uri}: ${dbret}`, LOG_LEVEL.NOTICE);
|
||||
return false;
|
||||
@ -1081,7 +1081,7 @@ export class LocalPouchDB {
|
||||
username: setting.couchDB_USER,
|
||||
password: setting.couchDB_PASSWORD,
|
||||
};
|
||||
const con = await connectRemoteCouchDB(uri, auth);
|
||||
const con = await connectRemoteCouchDB(uri, auth, setting.disableRequestURI);
|
||||
if (typeof con == "string") return;
|
||||
try {
|
||||
await con.db.destroy();
|
||||
@ -1099,7 +1099,7 @@ export class LocalPouchDB {
|
||||
username: setting.couchDB_USER,
|
||||
password: setting.couchDB_PASSWORD,
|
||||
};
|
||||
const con2 = await connectRemoteCouchDB(uri, auth);
|
||||
const con2 = await connectRemoteCouchDB(uri, auth, setting.disableRequestURI);
|
||||
if (typeof con2 === "string") return;
|
||||
Logger("Remote Database Created or Connected", LOG_LEVEL.NOTICE);
|
||||
}
|
||||
@ -1109,7 +1109,7 @@ export class LocalPouchDB {
|
||||
username: setting.couchDB_USER,
|
||||
password: setting.couchDB_PASSWORD,
|
||||
};
|
||||
const dbret = await connectRemoteCouchDB(uri, auth);
|
||||
const dbret = await connectRemoteCouchDB(uri, auth, setting.disableRequestURI);
|
||||
if (typeof dbret === "string") {
|
||||
Logger(`could not connect to ${uri}:${dbret}`, LOG_LEVEL.NOTICE);
|
||||
return;
|
||||
@ -1143,7 +1143,7 @@ export class LocalPouchDB {
|
||||
username: setting.couchDB_USER,
|
||||
password: setting.couchDB_PASSWORD,
|
||||
};
|
||||
const dbret = await connectRemoteCouchDB(uri, auth);
|
||||
const dbret = await connectRemoteCouchDB(uri, auth, setting.disableRequestURI);
|
||||
if (typeof dbret === "string") {
|
||||
Logger(`could not connect to ${uri}:${dbret}`, LOG_LEVEL.NOTICE);
|
||||
return;
|
||||
|
@ -14,10 +14,14 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
async testConnection(): Promise<void> {
|
||||
const db = await connectRemoteCouchDB(this.plugin.settings.couchDB_URI + (this.plugin.settings.couchDB_DBNAME == "" ? "" : "/" + this.plugin.settings.couchDB_DBNAME), {
|
||||
username: this.plugin.settings.couchDB_USER,
|
||||
password: this.plugin.settings.couchDB_PASSWORD,
|
||||
});
|
||||
const db = await connectRemoteCouchDB(
|
||||
this.plugin.settings.couchDB_URI + (this.plugin.settings.couchDB_DBNAME == "" ? "" : "/" + this.plugin.settings.couchDB_DBNAME),
|
||||
{
|
||||
username: this.plugin.settings.couchDB_USER,
|
||||
password: this.plugin.settings.couchDB_PASSWORD,
|
||||
},
|
||||
this.plugin.settings.disableRequestURI
|
||||
);
|
||||
if (typeof db === "string") {
|
||||
this.plugin.addLog(`could not connect to ${this.plugin.settings.couchDB_URI} : ${this.plugin.settings.couchDB_DBNAME} \n(${db})`, LOG_LEVEL.NOTICE);
|
||||
return;
|
||||
@ -165,6 +169,12 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
this.plugin.settings.couchDB_DBNAME = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
),
|
||||
new Setting(containerRemoteDatabaseEl).setName("Use the old connecting method").addToggle((toggle) =>
|
||||
toggle.setValue(this.plugin.settings.disableRequestURI).onChange(async (value) => {
|
||||
this.plugin.settings.disableRequestURI = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
@ -603,7 +613,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
);
|
||||
|
||||
new Setting(containerMiscellaneousEl)
|
||||
.setName("Use history (beta)")
|
||||
.setName("Use history")
|
||||
.setDesc("Use history dialog (Restart required, auto compaction would be disabled, and more storage will be consumed)")
|
||||
.addToggle((toggle) =>
|
||||
toggle.setValue(this.plugin.settings.useHistory).onChange(async (value) => {
|
||||
@ -832,12 +842,6 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
new Setting(containerPluginSettings).setName("Show own plugins and settings").addToggle((toggle) =>
|
||||
toggle.setValue(this.plugin.settings.showOwnPlugins).onChange(async (value) => {
|
||||
this.plugin.settings.showOwnPlugins = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerPluginSettings)
|
||||
.setName("Sweep plugins automatically")
|
||||
|
@ -60,6 +60,7 @@ export interface ObsidianLiveSyncSettings {
|
||||
batch_size: number;
|
||||
batches_limit: number;
|
||||
useHistory: boolean;
|
||||
disableRequestURI: boolean;
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: ObsidianLiveSyncSettings = {
|
||||
@ -101,6 +102,7 @@ export const DEFAULT_SETTINGS: ObsidianLiveSyncSettings = {
|
||||
batch_size: 250,
|
||||
batches_limit: 40,
|
||||
useHistory: false,
|
||||
disableRequestURI: false,
|
||||
};
|
||||
|
||||
export const PERIODIC_PLUGIN_SWEEP = 60;
|
||||
|
@ -2,6 +2,7 @@ import { Logger } from "./logger";
|
||||
import { LOG_LEVEL, VER, VERSIONINFO_DOCID, EntryVersionInfo, EntryDoc } from "./types";
|
||||
import { resolveWithIgnoreKnownError } from "./utils";
|
||||
import { PouchDB } from "../pouchdb-browser-webpack/dist/pouchdb-browser.js";
|
||||
import { requestUrl, RequestUrlParam } from "obsidian";
|
||||
|
||||
export const isValidRemoteCouchDBURI = (uri: string): boolean => {
|
||||
if (uri.startsWith("https://")) return true;
|
||||
@ -12,8 +13,17 @@ let last_post_successed = false;
|
||||
export const getLastPostFailedBySize = () => {
|
||||
return !last_post_successed;
|
||||
};
|
||||
export const connectRemoteCouchDB = async (uri: string, auth: { username: string; password: string }): Promise<string | { db: PouchDB.Database<EntryDoc>; info: PouchDB.Core.DatabaseInfo }> => {
|
||||
|
||||
export const connectRemoteCouchDB = async (uri: string, auth: { username: string; password: string }, disableRequestURI: boolean): Promise<string | { db: PouchDB.Database<EntryDoc>; info: PouchDB.Core.DatabaseInfo }> => {
|
||||
if (!isValidRemoteCouchDBURI(uri)) return "Remote URI is not valid";
|
||||
let authHeader = "";
|
||||
if (auth.username && auth.password) {
|
||||
const utf8str = String.fromCharCode.apply(null, new TextEncoder().encode(`${auth.username}:${auth.password}`));
|
||||
const encoded = window.btoa(utf8str);
|
||||
authHeader = "Basic " + encoded;
|
||||
} else {
|
||||
authHeader = "";
|
||||
}
|
||||
const conf: PouchDB.HttpAdapter.HttpAdapterConfiguration = {
|
||||
adapter: "http",
|
||||
auth,
|
||||
@ -35,6 +45,54 @@ export const connectRemoteCouchDB = async (uri: string, auth: { username: string
|
||||
}
|
||||
size = ` (${opts_length})`;
|
||||
}
|
||||
|
||||
if (!disableRequestURI && typeof url == "string" && typeof (opts.body ?? "") == "string") {
|
||||
const body = opts.body as string;
|
||||
|
||||
const transformedHeaders = { ...(opts.headers as Record<string, string>) };
|
||||
if (authHeader != "") transformedHeaders["authorization"] = authHeader;
|
||||
delete transformedHeaders["host"];
|
||||
delete transformedHeaders["Host"];
|
||||
delete transformedHeaders["content-length"];
|
||||
delete transformedHeaders["Content-Length"];
|
||||
const requestParam: RequestUrlParam = {
|
||||
url: url as string,
|
||||
method: opts.method,
|
||||
body: body,
|
||||
headers: transformedHeaders,
|
||||
contentType: "application/json",
|
||||
// contentType: opts.headers,
|
||||
};
|
||||
|
||||
try {
|
||||
const r = await requestUrl(requestParam);
|
||||
if (method == "POST" || method == "PUT") {
|
||||
last_post_successed = r.status - (r.status % 100) == 200;
|
||||
} else {
|
||||
last_post_successed = true;
|
||||
}
|
||||
if (r.status - (r.status % 100) !== 200) {
|
||||
throw new Error(`Request Error:${r.status}`);
|
||||
}
|
||||
Logger(`HTTP:${method}${size} to:${localURL} -> ${r.status}`, LOG_LEVEL.VERBOSE);
|
||||
|
||||
return new Response(r.arrayBuffer, {
|
||||
headers: r.headers,
|
||||
status: r.status,
|
||||
statusText: `${r.status}`,
|
||||
});
|
||||
} catch (ex) {
|
||||
Logger(`HTTP:${method}${size} to:${localURL} -> failed`, LOG_LEVEL.VERBOSE);
|
||||
if (!size_ok && (method == "POST" || method == "PUT")) {
|
||||
last_post_successed = false;
|
||||
}
|
||||
Logger(ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
// -old implementation
|
||||
|
||||
try {
|
||||
const responce: Response = await fetch(url, opts);
|
||||
if (method == "POST" || method == "PUT") {
|
||||
|
Loading…
x
Reference in New Issue
Block a user