1import { ItemView, Notice, WorkspaceLeaf } from "obsidian";2import type SublimatedPlugin from "./main";3import { SublimatedApi, type Version } from "./api";4
5export const VERSION_VIEW_TYPE = "sublimated-versions";6
7export class VersionView extends ItemView {8 plugin: SublimatedPlugin;9
10 constructor(leaf: WorkspaceLeaf, plugin: SublimatedPlugin) {11 super(leaf);12 this.plugin = plugin;13 }14
15 getViewType(): string {16 return VERSION_VIEW_TYPE;17 }18
19 getDisplayText(): string {20 return "sublimated versions";21 }22
23 getIcon(): string {24 return "history";25 }26
27 async onOpen(): Promise<void> {28 await this.refresh();29 // refresh when active file changes30 this.registerEvent(31 this.app.workspace.on("active-leaf-change", () => {32 this.refresh();33 })34 );35 }36
37 async refresh(): Promise<void> {38 const { contentEl } = this;39 contentEl.empty();40 contentEl.addClass("sbl-version-view");41
42 const activeFile = this.app.workspace.getActiveFile();43 if (!activeFile) {44 contentEl.createEl("p", {45 text: "No file open.",46 cls: "sbl-version-empty",47 });48 return;49 }50
51 if (!this.plugin.settings.apiToken) {52 contentEl.createEl("p", {53 text: "API token required for version history.",54 cls: "sbl-version-empty",55 });56 contentEl.createEl("p", {57 text: "Configure in Settings → sublimated",58 cls: "sbl-version-hint",59 });60 return;61 }62
63 // read xattrs to get space + item ID64 const meta = this.plugin.lastMetadata;65 if (!meta?.space || !meta?.id) {66 contentEl.createEl("p", {67 text: "Not a sublimated file.",68 cls: "sbl-version-empty",69 });70 return;71 }72
73 contentEl.createEl("h4", {74 text: activeFile.name,75 cls: "sbl-version-filename",76 });77
78 const loading = contentEl.createEl("p", {79 text: "Loading versions...",80 cls: "sbl-version-loading",81 });82
83 try {84 const api = new SublimatedApi(this.plugin.settings.apiToken);85 const versions = await api.getVersions(meta.space, meta.id);86 loading.remove();87
88 if (versions.length === 0) {89 contentEl.createEl("p", {90 text: "No versions found.",91 cls: "sbl-version-empty",92 });93 return;94 }95
96 const list = contentEl.createEl("div", {97 cls: "sbl-version-list",98 });99 const groups = groupByDay(versions);100 for (const group of groups) {101 list.createEl("div", {102 text: group.label,103 cls: "sbl-version-day",104 });105 for (const v of group.versions) {106 this.renderVersion(list, v, meta.url);107 }108 }109 } catch (e) {110 loading.remove();111 contentEl.createEl("p", {112 text: `Error: ${e instanceof Error ? e.message : "Unknown"}`,113 cls: "sbl-version-error",114 });115 }116 }117
118 private renderVersion(119 container: HTMLElement,120 v: Version,121 fileUrl: string | null122 ): void {123 const item = container.createDiv({ cls: "sbl-version-item" });124
125 const header = item.createDiv({ cls: "sbl-version-header" });126 header.createEl("span", {127 text: `v${v.number}`,128 cls: "sbl-version-number",129 });130 header.createEl("span", {131 text: v.authorName,132 cls: "sbl-version-author",133 });134
135 const time = item.createDiv({ cls: "sbl-version-time" });136 time.createEl("span", {137 text: formatRelativeTime(v.createdAt),138 attr: { title: new Date(v.createdAt).toLocaleString() },139 });140
141 if (v.message) {142 item.createDiv({143 text: v.message,144 cls: "sbl-version-message",145 });146 }147
148 if (fileUrl) {149 const webUrl = `https://${fileUrl}?v=${v.number}`;150 const link = item.createEl("a", {151 text: "view on web",152 cls: "sbl-version-web-link",153 href: webUrl,154 });155 link.addEventListener("click", (e) => {156 e.preventDefault();157 window.open(webUrl);158 });159 }160 }161
162 async onClose(): Promise<void> {163 // cleanup handled by obsidian164 }165}166
167function formatRelativeTime(iso: string): string {168 const now = Date.now();169 const then = new Date(iso).getTime();170 const diff = now - then;171 const seconds = Math.floor(diff / 1000);172 const minutes = Math.floor(seconds / 60);173 const hours = Math.floor(minutes / 60);174 const days = Math.floor(hours / 24);175
176 if (days > 30) return new Date(iso).toLocaleDateString();177 if (days > 0) return `${days}d ago`;178 if (hours > 0) return `${hours}h ago`;179 if (minutes > 0) return `${minutes}m ago`;180 return "just now";181} v2 of v8
commit 6370cc6
message edit version-view.ts via macOS
author anton
date Apr 8, 2026
size 4.1 KB