mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-05-13 11:01:16 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e76292aa7 | ||
|
|
4634ab73b1 | ||
|
|
359c10f1d7 | ||
|
|
59ebac3efc | ||
|
|
b4edca3a99 |
23
README.md
23
README.md
@@ -42,15 +42,15 @@ Especially, in some companies, people have to store all data to their fully cont
|
|||||||
### Get your database ready.
|
### Get your database ready.
|
||||||
|
|
||||||
First, get your database ready. IBM Cloudant is preferred for testing. Or you can use your own server with CouchDB. For more information, refer below:
|
First, get your database ready. IBM Cloudant is preferred for testing. Or you can use your own server with CouchDB. For more information, refer below:
|
||||||
1. [Setup IBM Cloudant](docs/setup_cloudant.md)
|
1. [Setup IBM Cloudant](docs/setup_cloudant.md)
|
||||||
2. [Setup your CouchDB](docs/setup_own_server.md)
|
2. [Setup your CouchDB](docs/setup_own_server.md)
|
||||||
|
|
||||||
### First device
|
### First device
|
||||||
|
|
||||||
1. Install the plugin on your device.
|
1. Install the plugin on your device.
|
||||||
2. Configure with the remote database.
|
2. Configure with the remote database.
|
||||||
1. Fill your server's information into the `Remote Database configuration pane`.
|
1. Fill your server's information into the `Remote Database configuration` pane.
|
||||||
2. Enabling `End to End Encryption is recommended. After inputting the passphrase, you have to press `Just apply`.
|
2. Enabling `End to End Encryption` is recommended. After inputting the passphrase, you have to press `Just apply`.
|
||||||
3. Hit `Test Database Connection` and make sure that the plugin says `Connected`.
|
3. Hit `Test Database Connection` and make sure that the plugin says `Connected`.
|
||||||
4. Hit `Check database configuration` and make sure all tests have been passed.
|
4. Hit `Check database configuration` and make sure all tests have been passed.
|
||||||
3. Configure how to synchronize on `Sync setting`. (You can leave these configures later)
|
3. Configure how to synchronize on `Sync setting`. (You can leave these configures later)
|
||||||
@@ -63,32 +63,33 @@ First, get your database ready. IBM Cloudant is preferred for testing. Or you ca
|
|||||||
5. Back to the editor. I hope that initial scan is in the progress or done.
|
5. Back to the editor. I hope that initial scan is in the progress or done.
|
||||||
6. When status became stabilized (All ⏳ and 🧩 disappeared), you are ready to synchronize with the server.
|
6. When status became stabilized (All ⏳ and 🧩 disappeared), you are ready to synchronize with the server.
|
||||||
7. Press the replicate icon on the Ribbon or run `Replicate now` from the Command pallet. You'll send all your data to the server.
|
7. Press the replicate icon on the Ribbon or run `Replicate now` from the Command pallet. You'll send all your data to the server.
|
||||||
8. Open the command palette and run `Copy setup uri`. And share copied URI to your other devices.
|
8. Open the command palette, `Copy setup URI`, and set the passphrase to encrypt the information. Then your configuration will be copied to the clipboard. Please share copied URI with your other devices.
|
||||||
**IMPORTANT NOTICE: DO NOT SHARE THIS URI. THIS CONTAINS YOUR CREDENTIALS.**
|
**IMPORTANT NOTICE: BE CAREFUL TO TREAT THIS URI. THE URI CONTAINS YOUR CREDENTIALS EVEN THOUGH NOBODY COULD READ WITHOUT THE PASSPHRASE.**
|
||||||
|
|
||||||
### Subsequent Devices
|
### Subsequent Devices
|
||||||
|
|
||||||
Strongly recommend using the vault in which all files are completely synchronized including timestamps. Otherwise, some files will be corrupted if failed to resolve conflicts. To simplify, I recommend using a new empty vault.
|
Strongly recommend using the vault in which all files are completely synchronized including timestamps. Otherwise, some files will be corrupted if failed to resolve conflicts. To simplify, I recommend using a new empty vault.
|
||||||
|
|
||||||
1. Install the plug-in.
|
1. Install the plug-in.
|
||||||
2. Open the link that you had been copied to the other device.
|
2. Open the link that you copied from the other device.
|
||||||
|
1. If you are hard to open the link (i.e., in android), you can use `Open setup URI` from the command palette and paste the URI into LiveSync manually.
|
||||||
3. The plug-in asks you that are you sure to apply the configurations. Please answer `Yes` and the following instruction below:
|
3. The plug-in asks you that are you sure to apply the configurations. Please answer `Yes` and the following instruction below:
|
||||||
1. Answer `Yes` to `Keep local DB?`.
|
1. Answer `Yes` to `Keep local DB?`.
|
||||||
*Note: If you started with existed vault, you have to answer `No`. And `No` to `Rebuild the database?`.*
|
*Note: If you started with existed vault, you have to answer `No`. And `No` to `Rebuild the database?`.*
|
||||||
2. Answer `Yes` to `Keep remote DB?`.
|
2. Answer `Yes` to `Keep remote DB?`.
|
||||||
3. Answer `Yes` to `Replicate once?`.
|
3. Answer `Yes` to `Unlock and replicate?`.
|
||||||
Yes, you have to answer `Yes` to everything.
|
Yes, you have to answer `Yes` to everything.
|
||||||
Then, all your settings are copied from the first device.
|
Then, all your settings are copied from the first device.
|
||||||
4. Your notes will arrive soon.
|
4. Your notes will arrive soon.
|
||||||
|
|
||||||
## Something looks corrupted...
|
## Something looks corrupted...
|
||||||
|
|
||||||
Please open the link again and Answer as below:
|
Please open the link again and answer as below:
|
||||||
- If your local database looks corrupted
|
- If your local database looks corrupted
|
||||||
(in the other words, your Obsidian getting werid even off the line)
|
(in other words, when your Obsidian getting weird even standalone.)
|
||||||
- Answer `No` to `Keep local DB?`
|
- Answer `No` to `Keep local DB?`
|
||||||
- If your remote database looks corrupted
|
- If your remote database looks corrupted
|
||||||
(in the other words, something happens while replicating)
|
(in other words, when something happens while replicating)
|
||||||
- Answer `No` to `Keep remote DB?`
|
- Answer `No` to `Keep remote DB?`
|
||||||
|
|
||||||
If you answered `No` to both, your databases will be rebuilt by the content on your device. And the remote database will lock out other devices. You have to synchronize all your devices again. (When this time, almost all your files should be synchronized including a timestamp. So you can use the existed vault).
|
If you answered `No` to both, your databases will be rebuilt by the content on your device. And the remote database will lock out other devices. You have to synchronize all your devices again. (When this time, almost all your files should be synchronized including a timestamp. So you can use the existed vault).
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"id": "obsidian-livesync",
|
"id": "obsidian-livesync",
|
||||||
"name": "Self-hosted LiveSync",
|
"name": "Self-hosted LiveSync",
|
||||||
"version": "0.11.4",
|
"version": "0.11.6",
|
||||||
"minAppVersion": "0.9.12",
|
"minAppVersion": "0.9.12",
|
||||||
"description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
|
"description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
|
||||||
"author": "vorotamoroz",
|
"author": "vorotamoroz",
|
||||||
"authorUrl": "https://github.com/vrtmrz",
|
"authorUrl": "https://github.com/vrtmrz",
|
||||||
"isDesktopOnly": false
|
"isDesktopOnly": false
|
||||||
}
|
}
|
||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "obsidian-livesync",
|
"name": "obsidian-livesync",
|
||||||
"version": "0.11.4",
|
"version": "0.11.6",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "obsidian-livesync",
|
"name": "obsidian-livesync",
|
||||||
"version": "0.11.4",
|
"version": "0.11.6",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"diff-match-patch": "^1.0.5",
|
"diff-match-patch": "^1.0.5",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "obsidian-livesync",
|
"name": "obsidian-livesync",
|
||||||
"version": "0.11.4",
|
"version": "0.11.6",
|
||||||
"description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
|
"description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -41,4 +41,4 @@
|
|||||||
"svelte-preprocess": "^4.10.2",
|
"svelte-preprocess": "^4.10.2",
|
||||||
"xxhash-wasm": "^0.4.2"
|
"xxhash-wasm": "^0.4.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
VER,
|
VER,
|
||||||
MILSTONE_DOCID,
|
MILSTONE_DOCID,
|
||||||
DatabaseConnectingStatus,
|
DatabaseConnectingStatus,
|
||||||
|
ObsidianLiveSyncSettings,
|
||||||
} from "./lib/src/types";
|
} from "./lib/src/types";
|
||||||
import { RemoteDBSettings } from "./lib/src/types";
|
import { RemoteDBSettings } from "./lib/src/types";
|
||||||
import { resolveWithIgnoreKnownError, delay, runWithLock, NewNotice, WrappedNotice, shouldSplitAsPlainText, splitPieces2, enableEncryption } from "./lib/src/utils";
|
import { resolveWithIgnoreKnownError, delay, runWithLock, NewNotice, WrappedNotice, shouldSplitAsPlainText, splitPieces2, enableEncryption } from "./lib/src/utils";
|
||||||
@@ -33,7 +34,7 @@ class LRUCache {
|
|||||||
cache = new Map<string, string>([]);
|
cache = new Map<string, string>([]);
|
||||||
revCache = new Map<string, string>([]);
|
revCache = new Map<string, string>([]);
|
||||||
maxCache = 100;
|
maxCache = 100;
|
||||||
constructor() {}
|
constructor() { }
|
||||||
get(key: string) {
|
get(key: string) {
|
||||||
// debugger
|
// debugger
|
||||||
const v = this.cache.get(key);
|
const v = this.cache.get(key);
|
||||||
@@ -770,7 +771,7 @@ export class LocalPouchDB {
|
|||||||
this.openOneshotReplication(
|
this.openOneshotReplication(
|
||||||
setting,
|
setting,
|
||||||
showingNotice,
|
showingNotice,
|
||||||
async (e) => {},
|
async (e) => { },
|
||||||
false,
|
false,
|
||||||
(e) => {
|
(e) => {
|
||||||
if (e === true) res(e);
|
if (e === true) res(e);
|
||||||
@@ -1222,10 +1223,14 @@ export class LocalPouchDB {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async garbageCollect() {
|
async garbageCollect() {
|
||||||
// if (this.settings.useHistory) {
|
if (this.settings.useHistory) {
|
||||||
// Logger("GC skipped for using history", LOG_LEVEL.VERBOSE);
|
Logger("GC skipped for using history", LOG_LEVEL.VERBOSE);
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
|
if ((this.settings as ObsidianLiveSyncSettings).liveSync) {
|
||||||
|
Logger("GC skipped while live sync.", LOG_LEVEL.VERBOSE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// NOTE:Garbage collection could break old revisions.
|
// NOTE:Garbage collection could break old revisions.
|
||||||
await runWithLock("replicate", true, async () => {
|
await runWithLock("replicate", true, async () => {
|
||||||
if (this.gcRunning) return;
|
if (this.gcRunning) return;
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
);
|
);
|
||||||
new Setting(containerRemoteDatabaseEl)
|
new Setting(containerRemoteDatabaseEl)
|
||||||
.setName("End to End Encryption")
|
.setName("End to End Encryption")
|
||||||
.setDesc("Encrypting contents on the database.")
|
.setDesc("Encrypt contents on the remote database. If you use the plugins synchronizing feature, enabling this is recommend.")
|
||||||
.addToggle((toggle) =>
|
.addToggle((toggle) =>
|
||||||
toggle.setValue(this.plugin.settings.workingEncrypt).onChange(async (value) => {
|
toggle.setValue(this.plugin.settings.workingEncrypt).onChange(async (value) => {
|
||||||
this.plugin.settings.workingEncrypt = value;
|
this.plugin.settings.workingEncrypt = value;
|
||||||
|
|||||||
147
src/main.ts
147
src/main.ts
@@ -1,4 +1,4 @@
|
|||||||
import { debounce, Notice, Plugin, TFile, addIcon, TFolder, normalizePath, TAbstractFile, Editor, MarkdownView, PluginManifest, Modal, App, FuzzySuggestModal } from "obsidian";
|
import { debounce, Notice, Plugin, TFile, addIcon, TFolder, normalizePath, TAbstractFile, Editor, MarkdownView, PluginManifest, Modal, App, FuzzySuggestModal, Setting } from "obsidian";
|
||||||
import { diff_match_patch } from "diff-match-patch";
|
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 } from "./lib/src/types";
|
import { EntryDoc, LoadedEntry, ObsidianLiveSyncSettings, diff_check_result, diff_result_leaf, EntryBody, LOG_LEVEL, VER, DEFAULT_SETTINGS, diff_result, FLAGMD_REDFLAG, SYNCINFO_ID } from "./lib/src/types";
|
||||||
@@ -59,9 +59,65 @@ class PluginDialogModal extends Modal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
class PopoverYesNo extends FuzzySuggestModal<string> {
|
class PopoverYesNo extends FuzzySuggestModal<string> {
|
||||||
app: App;
|
app: App;
|
||||||
callback: (e: string) => void = () => {};
|
callback: (e: string) => void = () => { };
|
||||||
|
|
||||||
constructor(app: App, note: string, callback: (e: string) => void) {
|
constructor(app: App, note: string, callback: (e: string) => void) {
|
||||||
super(app);
|
super(app);
|
||||||
@@ -99,6 +155,13 @@ const askYesNo = (app: App, message: string): Promise<"yes" | "no"> => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const askString = (app: App, title: string, key: string, placeholder: string): Promise<string | false> => {
|
||||||
|
return new Promise((res) => {
|
||||||
|
const dialog = new InputStringDialog(app, title, key, placeholder, (result) => res(result));
|
||||||
|
dialog.open();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export default class ObsidianLiveSyncPlugin extends Plugin {
|
export default class ObsidianLiveSyncPlugin extends Plugin {
|
||||||
settings: ObsidianLiveSyncSettings;
|
settings: ObsidianLiveSyncSettings;
|
||||||
localDatabase: LocalPouchDB;
|
localDatabase: LocalPouchDB;
|
||||||
@@ -241,20 +304,40 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
Logger(ex, LOG_LEVEL.VERBOSE);
|
Logger(ex, LOG_LEVEL.VERBOSE);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const configURIBase = "obsidian://setuplivesync?settings=";
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "livesync-exportconfig",
|
id: "livesync-copysetupuri",
|
||||||
name: "Copy setup uri (beta)",
|
name: "Copy setup URI (beta)",
|
||||||
callback: async () => {
|
callback: async () => {
|
||||||
const encryptedSetting = encodeURIComponent(await encrypt(JSON.stringify(this.settings), "---"));
|
const encryptingPassphrase = await askString(this.app, "Encrypt your settings", "Passphrase", "");
|
||||||
const uri = `obsidian://setuplivesync?settings=${encryptedSetting}`;
|
if (encryptingPassphrase === false) return;
|
||||||
|
const encryptedSetting = encodeURIComponent(await encrypt(JSON.stringify(this.settings), encryptingPassphrase));
|
||||||
|
const uri = `${configURIBase}${encryptedSetting}`;
|
||||||
await navigator.clipboard.writeText(uri);
|
await navigator.clipboard.writeText(uri);
|
||||||
Logger("Setup uri copied to clipboard", LOG_LEVEL.NOTICE);
|
Logger("Setup URI copied to clipboard", LOG_LEVEL.NOTICE);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.registerObsidianProtocolHandler("setuplivesync", async (conf: any) => {
|
this.addCommand({
|
||||||
|
id: "livesync-opensetupuri",
|
||||||
|
name: "Open setup URI (beta)",
|
||||||
|
callback: async () => {
|
||||||
|
const setupURI = await askString(this.app, "Set up manually", "Set up URI", `${configURIBase}aaaaa`);
|
||||||
|
if (setupURI === false) return;
|
||||||
|
if (!setupURI.startsWith(`${configURIBase}`)) {
|
||||||
|
Logger("Set up URI looks wrong.", LOG_LEVEL.NOTICE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const config = decodeURIComponent(setupURI.substring(configURIBase.length));
|
||||||
|
console.dir(config)
|
||||||
|
await setupwizard(config);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const setupwizard = async (confString: string) => {
|
||||||
try {
|
try {
|
||||||
const oldConf = JSON.parse(JSON.stringify(this.settings));
|
const oldConf = JSON.parse(JSON.stringify(this.settings));
|
||||||
const newconf = await JSON.parse(await decrypt(conf.settings, "---"));
|
const encryptingPassphrase = await askString(this.app, "Passphrase", "Passphrase for your settings", "");
|
||||||
|
if (encryptingPassphrase === false) return;
|
||||||
|
const newconf = await JSON.parse(await decrypt(confString, encryptingPassphrase));
|
||||||
if (newconf) {
|
if (newconf) {
|
||||||
const result = await askYesNo(this.app, "Importing LiveSync's conf, OK?");
|
const result = await askYesNo(this.app, "Importing LiveSync's conf, OK?");
|
||||||
if (result == "yes") {
|
if (result == "yes") {
|
||||||
@@ -269,6 +352,11 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
// nothing to do. so peaceful.
|
// nothing to do. so peaceful.
|
||||||
this.settings = newSettingW;
|
this.settings = newSettingW;
|
||||||
await this.saveSettings();
|
await this.saveSettings();
|
||||||
|
const replicate = await askYesNo(this.app, "Unlock and replicate?");
|
||||||
|
if (replicate == "yes") {
|
||||||
|
await this.replicate(true);
|
||||||
|
await this.markRemoteUnlocked();
|
||||||
|
}
|
||||||
Logger("Configuration loaded.", LOG_LEVEL.NOTICE);
|
Logger("Configuration loaded.", LOG_LEVEL.NOTICE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -313,8 +401,11 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
Logger("Cancelled.", LOG_LEVEL.NOTICE);
|
Logger("Cancelled.", LOG_LEVEL.NOTICE);
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger("Couldn't parse configuration uri.", LOG_LEVEL.NOTICE);
|
Logger("Couldn't parse or decrypt configuration uri.", LOG_LEVEL.NOTICE);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
this.registerObsidianProtocolHandler("setuplivesync", async (conf: any) => {
|
||||||
|
await setupwizard(conf.settings);
|
||||||
});
|
});
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "livesync-replicate",
|
id: "livesync-replicate",
|
||||||
@@ -415,15 +506,19 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
|
|
||||||
onunload() {
|
onunload() {
|
||||||
this.hidePluginSyncModal();
|
this.hidePluginSyncModal();
|
||||||
this.localDatabase.onunload();
|
if (this.localDatabase != null) {
|
||||||
|
this.localDatabase.onunload();
|
||||||
|
}
|
||||||
if (this.gcTimerHandler != null) {
|
if (this.gcTimerHandler != null) {
|
||||||
clearTimeout(this.gcTimerHandler);
|
clearTimeout(this.gcTimerHandler);
|
||||||
this.gcTimerHandler = null;
|
this.gcTimerHandler = null;
|
||||||
}
|
}
|
||||||
this.clearPeriodicSync();
|
this.clearPeriodicSync();
|
||||||
this.clearPluginSweep();
|
this.clearPluginSweep();
|
||||||
this.localDatabase.closeReplication();
|
if (this.localDatabase != null) {
|
||||||
this.localDatabase.close();
|
this.localDatabase.closeReplication();
|
||||||
|
this.localDatabase.close();
|
||||||
|
}
|
||||||
window.removeEventListener("visibilitychange", this.watchWindowVisiblity);
|
window.removeEventListener("visibilitychange", this.watchWindowVisiblity);
|
||||||
Logger("unloading plugin");
|
Logger("unloading plugin");
|
||||||
}
|
}
|
||||||
@@ -453,6 +548,10 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
this.settings.workingPassphrase = this.settings.passphrase;
|
this.settings.workingPassphrase = this.settings.passphrase;
|
||||||
// Delete this feature to avoid problems on mobile.
|
// Delete this feature to avoid problems on mobile.
|
||||||
this.settings.disableRequestURI = true;
|
this.settings.disableRequestURI = true;
|
||||||
|
// Temporary disabled
|
||||||
|
// TODO: If a new GC is created, a new default value must be created.
|
||||||
|
this.settings.gcDelay = 0;
|
||||||
|
|
||||||
const lsname = "obsidian-live-sync-vaultanddevicename-" + this.app.vault.getName();
|
const lsname = "obsidian-live-sync-vaultanddevicename-" + this.app.vault.getName();
|
||||||
if (this.settings.deviceAndVaultName != "") {
|
if (this.settings.deviceAndVaultName != "") {
|
||||||
if (!localStorage.getItem(lsname)) {
|
if (!localStorage.getItem(lsname)) {
|
||||||
@@ -618,7 +717,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
// When save is delayed, it should be cancelled.
|
// When save is delayed, it should be cancelled.
|
||||||
this.batchFileChange = this.batchFileChange.filter((e) => e == file.path);
|
this.batchFileChange = this.batchFileChange.filter((e) => e == file.path);
|
||||||
if (this.settings.suspendFileWatching) return;
|
if (this.settings.suspendFileWatching) return;
|
||||||
this.watchVaultDeleteAsync(file).then(() => {});
|
this.watchVaultDeleteAsync(file).then(() => { });
|
||||||
}
|
}
|
||||||
|
|
||||||
async watchVaultDeleteAsync(file: TAbstractFile) {
|
async watchVaultDeleteAsync(file: TAbstractFile) {
|
||||||
@@ -647,7 +746,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
|
|
||||||
watchVaultRename(file: TAbstractFile, oldFile: any) {
|
watchVaultRename(file: TAbstractFile, oldFile: any) {
|
||||||
if (this.settings.suspendFileWatching) return;
|
if (this.settings.suspendFileWatching) return;
|
||||||
this.watchVaultRenameAsync(file, oldFile).then(() => {});
|
this.watchVaultRenameAsync(file, oldFile).then(() => { });
|
||||||
}
|
}
|
||||||
|
|
||||||
getFilePath(file: TAbstractFile): string {
|
getFilePath(file: TAbstractFile): string {
|
||||||
@@ -1209,16 +1308,16 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
const locks = getLocks();
|
const locks = getLocks();
|
||||||
const pendingTask = locks.pending.length
|
const pendingTask = locks.pending.length
|
||||||
? "\nPending: " +
|
? "\nPending: " +
|
||||||
Object.entries([...new Set([...locks.pending])].reduce((p, c) => ({ ...p, [c]: p[c] ?? 0 + 1 }), {} as { [key: string]: number }))
|
Object.entries(locks.pending.reduce((p, c) => ({ ...p, [c]: (p[c] ?? 0) + 1 }), {} as { [key: string]: number }))
|
||||||
.map((e) => `${e[0]}${e[1] == 1 ? "" : `(${e[1]})`}`)
|
.map((e) => `${e[0]}${e[1] == 1 ? "" : `(${e[1]})`}`)
|
||||||
.join(", ")
|
.join(", ")
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
const runningTask = locks.running.length
|
const runningTask = locks.running.length
|
||||||
? "\nRunning: " +
|
? "\nRunning: " +
|
||||||
Object.entries([...new Set([...locks.running])].reduce((p, c) => ({ ...p, [c]: p[c] ?? 0 + 1 }), {} as { [key: string]: number }))
|
Object.entries(locks.running.reduce((p, c) => ({ ...p, [c]: (p[c] ?? 0) + 1 }), {} as { [key: string]: number }))
|
||||||
.map((e) => `${e[0]}${e[1] == 1 ? "" : `(${e[1]})`}`)
|
.map((e) => `${e[0]}${e[1] == 1 ? "" : `(${e[1]})`}`)
|
||||||
.join(", ")
|
.join(", ")
|
||||||
: "";
|
: "";
|
||||||
this.setStatusBarText(message + pendingTask + runningTask);
|
this.setStatusBarText(message + pendingTask + runningTask);
|
||||||
}
|
}
|
||||||
@@ -1246,7 +1345,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
this.lastLog = newLog;
|
this.lastLog = newLog;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateStatusBarText() {}
|
updateStatusBarText() { }
|
||||||
|
|
||||||
async replicate(showMessage?: boolean) {
|
async replicate(showMessage?: boolean) {
|
||||||
if (this.settings.versionUpFlash != "") {
|
if (this.settings.versionUpFlash != "") {
|
||||||
@@ -1757,10 +1856,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
if (!this.localDatabase.isReady) return;
|
if (!this.localDatabase.isReady) return;
|
||||||
await runWithLock("sweepplugin", true, async () => {
|
await runWithLock("sweepplugin", true, async () => {
|
||||||
const logLevel = showMessage ? LOG_LEVEL.NOTICE : LOG_LEVEL.INFO;
|
const logLevel = showMessage ? LOG_LEVEL.NOTICE : LOG_LEVEL.INFO;
|
||||||
if (!this.settings.encrypt) {
|
|
||||||
Logger("You have to encrypt the database to use plugin setting sync.", LOG_LEVEL.NOTICE);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!this.deviceAndVaultName) {
|
if (!this.deviceAndVaultName) {
|
||||||
Logger("You have to set your device and vault name.", LOG_LEVEL.NOTICE);
|
Logger("You have to set your device and vault name.", LOG_LEVEL.NOTICE);
|
||||||
return;
|
return;
|
||||||
|
|||||||
41
styles.css
41
styles.css
@@ -2,28 +2,33 @@
|
|||||||
color: var(--text-on-accent);
|
color: var(--text-on-accent);
|
||||||
background-color: var(--text-accent);
|
background-color: var(--text-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.normal {
|
.normal {
|
||||||
color: var(--text-normal);
|
color: var(--text-normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
.deleted {
|
.deleted {
|
||||||
color: var(--text-on-accent);
|
color: var(--text-on-accent);
|
||||||
background-color: var(--text-muted);
|
background-color: var(--text-muted);
|
||||||
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;
|
||||||
user-select: text;
|
user-select: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
.op-pre {
|
.op-pre {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.op-warn {
|
.op-warn {
|
||||||
border: 1px solid salmon;
|
border: 1px solid salmon;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.op-warn::before {
|
.op-warn::before {
|
||||||
content: "Warning";
|
content: "Warning";
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -31,11 +36,13 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.op-warn-info {
|
.op-warn-info {
|
||||||
border: 1px solid rgb(255, 209, 81);
|
border: 1px solid rgb(255, 209, 81);
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.op-warn-info::before {
|
.op-warn-info::before {
|
||||||
content: "Notice";
|
content: "Notice";
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -43,27 +50,33 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.syncstatusbar {
|
.syncstatusbar {
|
||||||
-webkit-filter: grayscale(100%);
|
-webkit-filter: grayscale(100%);
|
||||||
filter: grayscale(100%);
|
filter: grayscale(100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tcenter {
|
.tcenter {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sls-plugins-wrap {
|
.sls-plugins-wrap {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
max-height: 50vh;
|
max-height: 50vh;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sls-plugins-tbl {
|
.sls-plugins-tbl {
|
||||||
border: 1px solid var(--background-modifier-border);
|
border: 1px solid var(--background-modifier-border);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-height: 80%;
|
max-height: 80%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.divider th {
|
.divider th {
|
||||||
border-top: 1px solid var(--background-modifier-border);
|
border-top: 1px solid var(--background-modifier-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* .sls-table-head{
|
/* .sls-table-head{
|
||||||
width:50%;
|
width:50%;
|
||||||
}
|
}
|
||||||
@@ -75,9 +88,11 @@
|
|||||||
.sls-btn-left {
|
.sls-btn-left {
|
||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sls-btn-right {
|
.sls-btn-right {
|
||||||
padding-left: 4px;
|
padding-left: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sls-hidden {
|
.sls-hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -85,8 +100,9 @@
|
|||||||
:root {
|
:root {
|
||||||
--slsmessage: "";
|
--slsmessage: "";
|
||||||
}
|
}
|
||||||
|
|
||||||
.CodeMirror-wrap::before,
|
.CodeMirror-wrap::before,
|
||||||
.cm-s-obsidian > .cm-editor::before {
|
.cm-s-obsidian>.cm-editor::before {
|
||||||
content: var(--slsmessage);
|
content: var(--slsmessage);
|
||||||
text-align: right;
|
text-align: right;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
@@ -105,12 +121,15 @@
|
|||||||
.CodeMirror-wrap::before {
|
.CodeMirror-wrap::before {
|
||||||
right: 0px;
|
right: 0px;
|
||||||
}
|
}
|
||||||
.cm-s-obsidian > .cm-editor::before {
|
|
||||||
|
.cm-s-obsidian>.cm-editor::before {
|
||||||
right: 16px;
|
right: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sls-setting-tab {
|
.sls-setting-tab {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.sls-setting-menu-btn {
|
div.sls-setting-menu-btn {
|
||||||
color: var(--text-normal);
|
color: var(--text-normal);
|
||||||
background-color: var(--background-secondary-alt);
|
background-color: var(--background-secondary-alt);
|
||||||
@@ -131,8 +150,9 @@ div.sls-setting-menu-btn {
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
/* width: 100%; */
|
/* width: 100%; */
|
||||||
}
|
}
|
||||||
.sls-setting-tab:hover ~ div.sls-setting-menu-btn,
|
|
||||||
.sls-setting-tab:checked ~ div.sls-setting-menu-btn {
|
.sls-setting-tab:hover~div.sls-setting-menu-btn,
|
||||||
|
.sls-setting-tab:checked~div.sls-setting-menu-btn {
|
||||||
background-color: var(--interactive-accent);
|
background-color: var(--interactive-accent);
|
||||||
color: var(--text-on-accent);
|
color: var(--text-on-accent);
|
||||||
}
|
}
|
||||||
@@ -143,14 +163,17 @@ div.sls-setting-menu-btn {
|
|||||||
/* flex-wrap: wrap; */
|
/* flex-wrap: wrap; */
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sls-setting-label {
|
.sls-setting-label {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.setting-collapsed {
|
.setting-collapsed {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sls-plugins-tbl-buttons {
|
.sls-plugins-tbl-buttons {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
@@ -159,13 +182,16 @@ div.sls-setting-menu-btn {
|
|||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sls-plugins-tbl-device-head {
|
.sls-plugins-tbl-device-head {
|
||||||
background-color: var(--background-secondary-alt);
|
background-color: var(--background-secondary-alt);
|
||||||
color: var(--text-accent);
|
color: var(--text-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.op-flex {
|
.op-flex {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.op-flex input {
|
.op-flex input {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
@@ -185,9 +211,11 @@ div.sls-setting-menu-btn {
|
|||||||
color: var(--text-on-accent);
|
color: var(--text-on-accent);
|
||||||
background-color: var(--text-accent);
|
background-color: var(--text-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.history-normal {
|
.history-normal {
|
||||||
color: var(--text-normal);
|
color: var(--text-normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
.history-deleted {
|
.history-deleted {
|
||||||
color: var(--text-on-accent);
|
color: var(--text-on-accent);
|
||||||
background-color: var(--text-muted);
|
background-color: var(--text-muted);
|
||||||
@@ -197,6 +225,7 @@ div.sls-setting-menu-btn {
|
|||||||
.ob-btn-config-fix label {
|
.ob-btn-config-fix label {
|
||||||
margin-right: 40px;
|
margin-right: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ob-btn-config-info {
|
.ob-btn-config-info {
|
||||||
border: 1px solid salmon;
|
border: 1px solid salmon;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
@@ -208,4 +237,4 @@ div.sls-setting-menu-btn {
|
|||||||
padding: 2px;
|
padding: 2px;
|
||||||
margin: 1px;
|
margin: 1px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user