diff --git a/src/CmdConfigSync.ts b/src/CmdConfigSync.ts
index 6514cd1..6b7a1e7 100644
--- a/src/CmdConfigSync.ts
+++ b/src/CmdConfigSync.ts
@@ -482,6 +482,10 @@ export class ConfigSync extends LiveSyncCommands {
}
async storeCustomizationFiles(path: FilePath, termOverRide?: string) {
const term = termOverRide || this.plugin.deviceAndVaultName;
+ if (term == "") {
+ Logger("We have to configure the device name", LOG_LEVEL.NOTICE);
+ return;
+ }
const vf = this.filenameToUnifiedKey(path, term);
return await runWithLock(`plugin-${vf}`, false, async () => {
const category = this.getFileCategory(path);
diff --git a/src/DocumentHistoryModal.ts b/src/DocumentHistoryModal.ts
index 980f645..0ac3240 100644
--- a/src/DocumentHistoryModal.ts
+++ b/src/DocumentHistoryModal.ts
@@ -3,7 +3,7 @@ import { getPathFromTFile, isValidPath } from "./utils";
import { base64ToArrayBuffer, base64ToString, escapeStringToHTML } from "./lib/src/strbin";
import ObsidianLiveSyncPlugin from "./main";
import { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch } from "diff-match-patch";
-import { DocumentID, FilePathWithPrefix, LoadedEntry, LOG_LEVEL } from "./lib/src/types";
+import { type DocumentID, type FilePathWithPrefix, type LoadedEntry, LOG_LEVEL } from "./lib/src/types";
import { Logger } from "./lib/src/logger";
import { isErrorOfMissingDoc } from "./lib/src/utils_couchdb";
import { getDocData } from "./lib/src/utils";
@@ -24,12 +24,14 @@ export class DocumentHistoryModal extends Modal {
currentDoc: LoadedEntry;
currentText = "";
currentDeleted = false;
+ initialRev: string;
- constructor(app: App, plugin: ObsidianLiveSyncPlugin, file: TFile | FilePathWithPrefix, id: DocumentID) {
+ constructor(app: App, plugin: ObsidianLiveSyncPlugin, file: TFile | FilePathWithPrefix, id: DocumentID, revision?: string) {
super(app);
this.plugin = plugin;
this.file = (file instanceof TFile) ? getPathFromTFile(file) : file;
this.id = id;
+ this.initialRev = revision;
if (!file) {
this.file = this.plugin.id2path(id, null);
}
@@ -38,7 +40,7 @@ export class DocumentHistoryModal extends Modal {
}
}
- async loadFile() {
+ async loadFile(initialRev: string) {
if (!this.id) {
this.id = await this.plugin.path2id(this.file);
}
@@ -49,7 +51,7 @@ export class DocumentHistoryModal extends Modal {
this.range.max = `${this.revs_info.length - 1}`;
this.range.value = this.range.max;
this.fileInfo.setText(`${this.file} / ${this.revs_info.length} revisions`);
- await this.loadRevs();
+ await this.loadRevs(initialRev);
} catch (ex) {
if (isErrorOfMissingDoc(ex)) {
this.range.max = "0";
@@ -63,18 +65,27 @@ export class DocumentHistoryModal extends Modal {
}
}
}
- async loadRevs() {
+ async loadRevs(initialRev?: string) {
if (this.revs_info.length == 0) return;
- const db = this.plugin.localDatabase;
+ if (initialRev) {
+ const rIndex = this.revs_info.findIndex(e => e.rev == initialRev);
+ if (rIndex >= 0) {
+ this.range.value = `${this.revs_info.length - 1 - rIndex}`;
+ }
+ }
const index = this.revs_info.length - 1 - (this.range.value as any) / 1;
const rev = this.revs_info[index];
- const w = await db.getDBEntry(this.file, { rev: rev.rev }, false, false, true);
+ await this.showExactRev(rev.rev);
+ }
+ async showExactRev(rev: string) {
+ const db = this.plugin.localDatabase;
+ const w = await db.getDBEntry(this.file, { rev: rev }, false, false, true);
this.currentText = "";
this.currentDeleted = false;
if (w === false) {
this.currentDeleted = true;
this.info.innerHTML = "";
- this.contentView.innerHTML = `Could not read this revision
(${rev.rev})`;
+ this.contentView.innerHTML = `Could not read this revision
(${rev})`;
} else {
this.currentDoc = w;
this.info.innerHTML = `Modified:${new Date(w.mtime).toLocaleString()}`;
@@ -158,7 +169,7 @@ export class DocumentHistoryModal extends Modal {
.addClass("op-info");
this.info = contentEl.createDiv("");
this.info.addClass("op-info");
- this.loadFile();
+ this.loadFile(this.initialRev);
const div = contentEl.createDiv({ text: "Loading old revisions..." });
this.contentView = div;
div.addClass("op-scrollable");
diff --git a/src/GlobalHistory.svelte b/src/GlobalHistory.svelte
new file mode 100644
index 0000000..7fa25c3
--- /dev/null
+++ b/src/GlobalHistory.svelte
@@ -0,0 +1,328 @@
+
+
+
+
Vault history
+
+ {#if loading}
+
Gathering information...
+ {/if}
+
+
+ Date |
+ Path |
+ Rev |
+ Stat |
+ {#if showChunkCorrected}
+ Chunks |
+ {/if}
+
+
+
+ {#if loading}
+
+ {:else}
+
+ {/if}
+ |
+
+ {#each history as entry}
+
+
+ {entry.mtimeDisp}
+ |
+
+
+ |
+
+
+ {#if entry.isPlain}
+ showHistory(entry.path, entry.rev)}>{entry.rev}
+ {:else}
+ {entry.rev}
+ {/if}
+
+ |
+
+ {entry.changes}
+ |
+ {#if showChunkCorrected}
+
+ {entry.chunks}
+ |
+ {/if}
+
+ {/each}
+
+
+ {#if loading}
+
+ {:else}
+
+ {/if}
+ |
+
+
+
+
+
diff --git a/src/GlobalHistoryView.ts b/src/GlobalHistoryView.ts
new file mode 100644
index 0000000..4b8edd5
--- /dev/null
+++ b/src/GlobalHistoryView.ts
@@ -0,0 +1,47 @@
+import {
+ ItemView,
+ WorkspaceLeaf
+} from "./deps";
+import GlobalHistoryComponent from "./GlobalHistory.svelte";
+import type ObsidianLiveSyncPlugin from "./main";
+
+export const VIEW_TYPE_GLOBAL_HISTORY = "global-history";
+export class GlobalHistoryView extends ItemView {
+
+ component: GlobalHistoryComponent;
+ plugin: ObsidianLiveSyncPlugin;
+ icon: "clock";
+ title: string;
+ navigation: true;
+
+ getIcon(): string {
+ return "clock";
+ }
+
+ constructor(leaf: WorkspaceLeaf, plugin: ObsidianLiveSyncPlugin) {
+ super(leaf);
+ this.plugin = plugin;
+ }
+
+
+ getViewType() {
+ return VIEW_TYPE_GLOBAL_HISTORY;
+ }
+
+ getDisplayText() {
+ return "Vault history";
+ }
+
+ async onOpen() {
+ this.component = new GlobalHistoryComponent({
+ target: this.contentEl,
+ props: {
+ plugin: this.plugin,
+ },
+ });
+ }
+
+ async onClose() {
+ this.component.$destroy();
+ }
+}
diff --git a/src/LogPane.svelte b/src/LogPane.svelte
new file mode 100644
index 0000000..828841f
--- /dev/null
+++ b/src/LogPane.svelte
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+ {#each messages as line}
+
{line}
+ {/each}
+
+
+
+
diff --git a/src/LogPaneView.ts b/src/LogPaneView.ts
new file mode 100644
index 0000000..c281b05
--- /dev/null
+++ b/src/LogPaneView.ts
@@ -0,0 +1,46 @@
+import {
+ ItemView,
+ WorkspaceLeaf
+} from "obsidian";
+import LogPaneComponent from "./LogPane.svelte";
+import type ObsidianLiveSyncPlugin from "./main";
+export const VIEW_TYPE_LOG = "log-log";
+// Show notes as like scroll.
+export class LogPaneView extends ItemView {
+
+ component: LogPaneComponent;
+ plugin: ObsidianLiveSyncPlugin;
+ icon: "view-log";
+ title: string;
+ navigation: true;
+
+ getIcon(): string {
+ return "view-log";
+ }
+
+ constructor(leaf: WorkspaceLeaf, plugin: ObsidianLiveSyncPlugin) {
+ super(leaf);
+ this.plugin = plugin;
+ }
+
+
+ getViewType() {
+ return VIEW_TYPE_LOG;
+ }
+
+ getDisplayText() {
+ return "Self-hosted LiveSync Log";
+ }
+
+ async onOpen() {
+ this.component = new LogPaneComponent({
+ target: this.contentEl,
+ props: {
+ },
+ });
+ }
+
+ async onClose() {
+ this.component.$destroy();
+ }
+}
diff --git a/src/deps.ts b/src/deps.ts
index c4ef71f..aba6189 100644
--- a/src/deps.ts
+++ b/src/deps.ts
@@ -1,10 +1,10 @@
-import { FilePath } from "./lib/src/types";
+import { type FilePath } from "./lib/src/types";
export {
- addIcon, App, DataWriteOptions, debounce, Editor, FuzzySuggestModal, MarkdownRenderer, MarkdownView, Modal, Notice, Platform, Plugin, PluginManifest,
- PluginSettingTab, Plugin_2, requestUrl, RequestUrlParam, RequestUrlResponse, sanitizeHTMLToDom, Setting, stringifyYaml, TAbstractFile, TextAreaComponent, TFile, TFolder,
- parseYaml
+ addIcon, App, debounce, Editor, FuzzySuggestModal, MarkdownRenderer, MarkdownView, Modal, Notice, Platform, Plugin, PluginSettingTab, Plugin_2, requestUrl, sanitizeHTMLToDom, Setting, stringifyYaml, TAbstractFile, TextAreaComponent, TFile, TFolder,
+ parseYaml, ItemView, WorkspaceLeaf
} from "obsidian";
+export type { DataWriteOptions, PluginManifest, RequestUrlParam, RequestUrlResponse } from "obsidian";
import {
normalizePath as normalizePath_
} from "obsidian";
diff --git a/src/dialogs.ts b/src/dialogs.ts
index b9de157..3a893dd 100644
--- a/src/dialogs.ts
+++ b/src/dialogs.ts
@@ -43,7 +43,7 @@ export class InputStringDialog extends Modal {
key: string;
placeholder: string;
isManuallyClosed = false;
- isPassword: boolean = false;
+ isPassword = false;
constructor(app: App, title: string, key: string, placeholder: string, isPassword: boolean, onSubmit: (result: string | false) => void) {
super(app);
diff --git a/src/main.ts b/src/main.ts
index ef2c895..a9d8569 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -7,7 +7,6 @@ import { type InternalFileInfo, type queueItem, type CacheData, type FileEventIt
import { getDocData, isDocContentSame, Parallels } from "./lib/src/utils";
import { Logger } from "./lib/src/logger";
import { PouchDB } from "./lib/src/pouchdb-browser.js";
-import { LogDisplayModal } from "./LogDisplayModal";
import { ConflictResolveModal } from "./ConflictResolveModal";
import { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab";
import { DocumentHistoryModal } from "./DocumentHistoryModal";
@@ -30,6 +29,8 @@ import { HiddenFileSync } from "./CmdHiddenFileSync";
import { SetupLiveSync } from "./CmdSetupLiveSync";
import { ConfigSync } from "./CmdConfigSync";
import { confirmWithMessage } from "./dialogs";
+import { GlobalHistoryView, VIEW_TYPE_GLOBAL_HISTORY } from "./GlobalHistoryView";
+import { LogPaneView, VIEW_TYPE_LOG } from "./LogPaneView";
setNoticeClass(Notice);
@@ -539,7 +540,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin
});
this.addRibbonIcon("view-log", "Show log", () => {
- new LogDisplayModal(this.app, this).open();
+ this.showView(VIEW_TYPE_LOG);
});
this.addSettingTab(new ObsidianLiveSyncSettingTab(this.app, this));
@@ -650,8 +651,44 @@ export default class ObsidianLiveSyncPlugin extends Plugin
},
})
+ this.registerView(
+ VIEW_TYPE_GLOBAL_HISTORY,
+ (leaf) => new GlobalHistoryView(leaf, this)
+ );
+ this.registerView(
+ VIEW_TYPE_LOG,
+ (leaf) => new LogPaneView(leaf, this)
+ );
+ this.addCommand({
+ id: "livesync-global-history",
+ name: "Show vault history",
+ callback: () => {
+ this.showGlobalHistory()
+ }
+ })
+ }
+ async showView(viewType: string) {
+ const leaves = this.app.workspace.getLeavesOfType(viewType);
+ if (leaves.length == 0) {
+ await this.app.workspace.getLeaf(true).setViewState({
+ type: viewType,
+ active: true,
+ });
+ } else {
+ leaves[0].setViewState({
+ type: viewType,
+ active: true,
+ })
+ }
+ if (leaves.length > 0) {
+ this.app.workspace.revealLeaf(
+ leaves[0]
+ );
+ }
+ }
+ showGlobalHistory() {
+ this.showView(VIEW_TYPE_GLOBAL_HISTORY);
}
-
onunload() {
for (const addOn of this.addOns) {
@@ -1003,7 +1040,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin
}
//--> Basic document Functions
- notifies: { [key: string]: { notice: Notice; timer: NodeJS.Timeout; count: number } } = {};
+ notifies: { [key: string]: { notice: Notice; timer: ReturnType; count: number } } = {};
lastLog = "";
// eslint-disable-next-line require-await
@@ -1382,7 +1419,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin
//---> Sync
async parseReplicationResult(docs: Array>): Promise {
- const docsSorted = docs.sort((a, b) => b.mtime - a.mtime);
+ const docsSorted = docs.sort((a: any, b: any) => b?.mtime ?? 0 - a?.mtime ?? 0);
L1:
for (const change of docsSorted) {
if (isChunk(change._id)) {
@@ -1471,7 +1508,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin
this.statusBar.title = e.syncStatus;
let waiting = "";
if (this.settings.batchSave && !this.settings.liveSync) {
- const len = this.vaultManager.getQueueLength();
+ const len = this.vaultManager?.getQueueLength();
if (len != 0) {
waiting = ` 🛫${len}`;
}