AT protocol bookmarking platforms in obsidian
at margin 116 lines 3.1 kB view raw
1import { Notice, Plugin, WorkspaceLeaf } from "obsidian"; 2import type { Client } from "@atcute/client"; 3import { DEFAULT_SETTINGS, AtProtoSettings, SettingTab } from "./settings"; 4import { createAuthenticatedClient, createPublicClient } from "./auth"; 5import { getProfile } from "./lib"; 6import { ATmarkView, VIEW_TYPE_ATMARK } from "views/atmark"; 7import type { ProfileData } from "components/profileIcon"; 8 9export default class ATmarkPlugin extends Plugin { 10 settings: AtProtoSettings = DEFAULT_SETTINGS; 11 client: Client | null = null; 12 profile: ProfileData | null = null; 13 14 async onload() { 15 await this.loadSettings(); 16 await this.initClient(); 17 18 this.registerView(VIEW_TYPE_ATMARK, (leaf) => { 19 return new ATmarkView(leaf, this); 20 }); 21 22 // eslint-disable-next-line obsidianmd/ui/sentence-case 23 this.addRibbonIcon("layers", "Open ATmark", () => { 24 void this.activateView(VIEW_TYPE_ATMARK); 25 }); 26 27 this.addCommand({ 28 id: "view", 29 name: "Open view", 30 callback: () => { void this.activateView(VIEW_TYPE_ATMARK); }, 31 }); 32 33 this.addSettingTab(new SettingTab(this.app, this)); 34 } 35 36 37 private async initClient() { 38 const { identifier, appPassword } = this.settings; 39 if (identifier && appPassword) { 40 try { 41 this.client = await createAuthenticatedClient({ identifier, password: appPassword }); 42 await this.fetchProfile(); 43 new Notice("Connected"); 44 } catch (err) { 45 const message = err instanceof Error ? err.message : String(err); 46 new Notice(`Auth failed: ${message}`); 47 this.client = createPublicClient(); 48 this.profile = null; 49 } 50 } else { 51 this.client = createPublicClient(); 52 this.profile = null; 53 } 54 } 55 56 private async fetchProfile() { 57 if (!this.client || !this.settings.identifier) { 58 this.profile = null; 59 return; 60 } 61 try { 62 const resp = await getProfile(this.client, this.settings.identifier); 63 if (resp.ok) { 64 this.profile = { 65 did: resp.data.did, 66 handle: resp.data.handle, 67 displayName: resp.data.displayName, 68 avatar: resp.data.avatar, 69 }; 70 } else { 71 this.profile = null; 72 } 73 } catch (e) { 74 console.error("Failed to fetch profile:", e); 75 this.profile = null; 76 } 77 } 78 79 async refreshClient() { 80 await this.initClient(); 81 } 82 83 84 async activateView(v: string) { 85 const { workspace } = this.app; 86 87 let leaf: WorkspaceLeaf | null = null; 88 const leaves = workspace.getLeavesOfType(v); 89 90 if (leaves.length > 0) { 91 // A leaf with our view already exists, use that 92 leaf = leaves[0] as WorkspaceLeaf; 93 void workspace.revealLeaf(leaf); 94 return; 95 } 96 97 // Our view could not be found in the workspace, create a new leaf 98 leaf = workspace.getMostRecentLeaf() 99 await leaf?.setViewState({ type: v, active: true }); 100 101 // "Reveal" the leaf in case it is in a collapsed sidebar 102 if (leaf) { 103 void workspace.revealLeaf(leaf); 104 } 105 } 106 107 async loadSettings() { 108 this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData() as Partial<AtProtoSettings>); 109 } 110 111 async saveSettings() { 112 await this.saveData(this.settings); 113 } 114 115 onunload() { } 116}