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} + + + + + + + {#if showChunkCorrected} + + {/if} + + + + + {#each history as entry} + + + + + + {#if showChunkCorrected} + + {/if} + + {/each} + + + +
Date Path Rev Stat Chunks
+ {#if loading} +
+ {:else} +
+ {/if} +
+ {entry.mtimeDisp} + +
+ /{entry.dirname.split("/").join(`​/`)} + openFile(entry.path)}>{entry.filename} +
+
+ + {#if entry.isPlain} + showHistory(entry.path, entry.rev)}>{entry.rev} + {:else} + {entry.rev} + {/if} + + + {entry.changes} + + {entry.chunks} +
+ {#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}`; }