vault-link/frontend/obsidian-plugin/src/views/history-view.ts
2025-02-22 17:25:26 +00:00

163 lines
3.6 KiB
TypeScript

import type { IconName, WorkspaceLeaf } from "obsidian";
import { ItemView, setIcon } from "obsidian";
import { intlFormatDistance } from "date-fns";
import type { HistoryEntry, SyncClient } from "sync-client";
import { SyncType, SyncSource, SyncStatus } from "sync-client";
export class HistoryView extends ItemView {
public static readonly TYPE = "history-view";
public static readonly ICON = "square-stack";
private timer: NodeJS.Timeout | null = null;
public constructor(
leaf: WorkspaceLeaf,
private readonly client: SyncClient
) {
super(leaf);
this.icon = HistoryView.ICON;
this.client.history.addSyncHistoryUpdateListener(() => {
this.updateView().catch((_error: unknown) => {
this.client.logger.error("Failed to update history view");
});
});
}
private static getSyncTypeIcon(type: SyncType | undefined): IconName {
switch (type) {
case SyncType.CREATE:
return "file-plus";
case SyncType.DELETE:
return "trash-2";
case SyncType.UPDATE:
return "file-pen-line";
case undefined:
default:
return "";
}
}
private static getSyncSourceIcon(source: SyncSource | undefined): IconName {
switch (source) {
case SyncSource.PUSH:
return "upload";
case SyncSource.PULL:
return "download";
case undefined:
default:
return "";
}
}
private static renderSyncItemTitle(
element: HTMLElement,
entry: HistoryEntry
): void {
const syncTypeIcon = HistoryView.getSyncTypeIcon(entry.type);
if (syncTypeIcon) {
setIcon(element.createDiv(), syncTypeIcon);
}
element.createEl("span", {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
text: entry.relativePath
});
const syncSourceIcon = HistoryView.getSyncSourceIcon(entry.source);
if (syncSourceIcon) {
setIcon(element.createDiv(), syncSourceIcon);
}
}
public getViewType(): string {
return HistoryView.TYPE;
}
public getDisplayText(): string {
return "VaultLink history";
}
public async onOpen(): Promise<void> {
await this.updateView();
this.timer = setInterval(() => void this.updateView(), 1000);
}
public async onClose(): Promise<void> {
if (this.timer) {
clearInterval(this.timer);
}
}
private async updateView(): Promise<void> {
const container = this.containerEl.children[1];
container.empty();
container.createEl("h4", { text: "VaultLink History" });
const entries = this.client.history
.getEntries()
.reverse()
.filter(
(entry) =>
entry.status !== SyncStatus.NO_OP ||
this.client.settings.getSettings().displayNoopSyncEvents
);
entries.forEach((entry) => {
container.createDiv(
{
cls: ["history-card", entry.status.toLocaleLowerCase()]
},
(card) => {
if (
this.app.vault.getFileByPath(entry.relativePath) !==
null
) {
card.addEventListener("click", () => {
void this.app.workspace.openLinkText(
entry.relativePath,
entry.relativePath,
false
);
});
card.addClass("clickable");
}
card.createDiv(
{
cls: "history-card-header"
},
(header) => {
header.createEl(
"h5",
{
cls: "history-card-title"
},
(title) => {
HistoryView.renderSyncItemTitle(
title,
entry
);
}
);
header.createSpan({
text: intlFormatDistance(
entry.timestamp,
new Date()
),
cls: "history-card-timestamp"
});
}
);
card.createEl("p", {
text: `${entry.message}.`,
cls: "history-card-message"
});
}
);
});
}
}