1import {2 Notice,3 Plugin,4 WorkspaceLeaf,5 normalizePath,6} from "obsidian";7import {8 type SublimatedSettings,9 DEFAULT_SETTINGS,10 SublimatedSettingTab,11} from "./settings";12import { type FileMetadata, readFileMetadata, isMacOS } from "./xattr";13import { VersionView, VERSION_VIEW_TYPE } from "./version-view";14import { WelcomeModal } from "./welcome-modal";15
16export default class SublimatedPlugin extends Plugin {17 settings: SublimatedSettings = DEFAULT_SETTINGS;18 lastMetadata: FileMetadata | null = null;19
20 private statusBarEl: HTMLElement | null = null;21 private isSublimatedVault = false;22
23 async onload(): Promise<void> {24 await this.loadSettings();25
26 // detect if this vault is a sublimated workspace27 this.isSublimatedVault = await this.checkSublimatedVault();28
29 // status bar30 this.statusBarEl = this.addStatusBarItem();31 this.statusBarEl.addClass("sbl-status-bar");32 this.statusBarEl.addEventListener("click", () =>33 this.openCurrentFileOnWeb()34 );35 this.updateStatusBar(null);36
37 // register version history view38 this.registerView(39 VERSION_VIEW_TYPE,40 (leaf) => new VersionView(leaf, this)41 );42
43 // commands44 this.addCommand({45 id: "open-on-web",46 name: "Open on sublimated.com",47 callback: () => this.openCurrentFileOnWeb(),48 });49
50 this.addCommand({51 id: "show-versions",52 name: "Show versions",53 callback: () => this.activateVersionView(),54 });55
56 this.addCommand({57 id: "copy-web-link",58 name: "Copy web link",59 callback: () => this.copyWebLink(),60 });61
62 // ribbon icon for version sidebar63 this.addRibbonIcon("history", "sublimated versions", () => {64 this.activateVersionView();65 });66
67 // URI handler: obsidian://sublimated?action=open&file=path/to/note.md68 this.registerObsidianProtocolHandler(69 "sublimated",70 (params) => this.handleUri(params)71 );72
73 // settings tab74 this.addSettingTab(new SublimatedSettingTab(this.app, this));75
76 // update status bar when active file changes77 this.registerEvent(78 this.app.workspace.on("active-leaf-change", () => {79 this.refreshMetadata();80 })81 );82
83 // initial metadata read84 this.refreshMetadata();85
86 // welcome modal on first install87 if (!this.settings.hasSeenWelcome) {88 new WelcomeModal(this.app).open();89 this.settings.hasSeenWelcome = true;90 await this.saveSettings();91 }92 }93
94 onunload(): void {95 this.app.workspace.detachLeavesOfType(VERSION_VIEW_TYPE);96 }97
98 async loadSettings(): Promise<void> {99 this.settings = Object.assign(100 {},101 DEFAULT_SETTINGS,102 await this.loadData()103 );104 }105
106 async saveSettings(): Promise<void> {107 await this.saveData(this.settings);108 }109
110 // check for .sbl/ folder in vault root111 private async checkSublimatedVault(): Promise<boolean> {112 const adapter = this.app.vault.adapter;113 return adapter.exists(normalizePath(".sbl"));114 }115
116 detectWorkspace(): { space: string | null } | null {117 if (!this.isSublimatedVault) return null;118 return {119 space: this.lastMetadata?.space ?? null,120 };121 }122
123 private async refreshMetadata(): Promise<void> {124 const file = this.app.workspace.getActiveFile();125 if (!file) {126 this.lastMetadata = null;127 this.updateStatusBar(null);128 return;129 }130
131 if (!isMacOS()) {132 this.lastMetadata = null;133 this.updateStatusBar(null);134 return;135 }136
137 // get absolute path via vault adapter138 const adapter = this.app.vault.adapter;139 const basePath = (adapter as { getBasePath?: () => string })140 .getBasePath?.();141 if (!basePath) {142 this.lastMetadata = null;143 this.updateStatusBar(null);144 return;145 }146
147 const absPath = `${basePath}/${file.path}`;148 this.lastMetadata = await readFileMetadata(absPath);149 this.updateStatusBar(this.lastMetadata);150 }151
152 private updateStatusBar(meta: FileMetadata | null): void {153 if (!this.statusBarEl) return;154
155 if (!this.isSublimatedVault) {156 this.statusBarEl.setText("");157 return;158 }159
160 if (!meta) {161 this.statusBarEl.setText("sublimated");162 return;163 }164
165 const parts: string[] = [];166 if (meta.version) parts.push(`v${meta.version}`);167 if (meta.syncStatus) parts.push(meta.syncStatus);168 if (meta.modifiedBy) parts.push(`by ${meta.modifiedBy}`);169
170 if (parts.length > 0) {171 this.statusBarEl.setText(`sublimated: ${parts.join(" · ")}`);172 } else {173 this.statusBarEl.setText("sublimated");174 }175
176 // show pointer cursor when URL available177 if (meta.url) {178 this.statusBarEl.addClass("sbl-clickable");179 } else {180 this.statusBarEl.removeClass("sbl-clickable");181 }182 }183
184 private openCurrentFileOnWeb(): void {185 const url = this.lastMetadata?.url;186 if (!url) {187 new Notice("No sublimated URL for this file");188 return;189 }190 window.open(`https://${url}`);191 }192
193 private async copyWebLink(): Promise<void> {194 const url = this.lastMetadata?.url;195 if (!url) {196 new Notice("No sublimated URL for this file");197 return;198 }199 await navigator.clipboard.writeText(`https://${url}`);200 new Notice("Link copied to clipboard");201 }202
203 private async activateVersionView(): Promise<void> {204 const existing =205 this.app.workspace.getLeavesOfType(VERSION_VIEW_TYPE);206 if (existing.length > 0) {207 this.app.workspace.revealLeaf(existing[0]);208 return;209 }210
211 const leaf = this.app.workspace.getRightLeaf(false);212 if (!leaf) return;213 await leaf.setViewState({214 type: VERSION_VIEW_TYPE,215 active: true,216 });217 this.app.workspace.revealLeaf(leaf);218 }219
220 private handleUri(params: Record<string, string>): void {221 const { action, file } = params;222
223 if (action === "open" && file) {224 const normalized = normalizePath(file);225 const existing = this.app.vault.getAbstractFileByPath(normalized);226 if (existing) {227 this.app.workspace.openLinkText(normalized, "", false);228 } else {229 new Notice(`File not found: ${file}`);230 }231 return;232 }233
234 if (action === "auth" && params.token) {235 // future: OAuth flow stores token from redirect236 this.settings.apiToken = params.token;237 this.saveSettings();238 new Notice("sublimated: signed in successfully");239 return;240 }241
242 new Notice(`Unknown sublimated action: ${action}`);243 }244} v1 of v8
commit 9755c30
message create main.ts via macOS
author anton
date Apr 8, 2026
size 5.9 KB