Various AT Protocol integrations with obsidian
20
fork

Configure Feed

Select the types of activity you want to include in your feed.

don't render profile, intiClient outside of onload

+70 -93
+1 -1
manifest.json
··· 1 1 { 2 2 "id": "atmark", 3 3 "name": "ATmark", 4 - "version": "0.1.6", 4 + "version": "0.1.7", 5 5 "minAppVersion": "0.15.0", 6 6 "description": "View and manage AT Protocol bookmarks.", 7 7 "author": "treethought",
+1 -1
package.json
··· 1 1 { 2 2 "name": "obsidian-atmark", 3 - "version": "0.1.6", 3 + "version": "0.1.7", 4 4 "description": "View and manage AT Protocol bookmarks.", 5 5 "main": "main.js", 6 6 "type": "module",
+9 -37
src/main.ts
··· 1 1 import { Notice, Plugin, WorkspaceLeaf } from "obsidian"; 2 2 import type { Client } from "@atcute/client"; 3 3 import { DEFAULT_SETTINGS, AtProtoSettings, SettingTab } from "./settings"; 4 - import { createAuthenticatedClient, createPublicClient } from "./auth"; 5 - import { getProfile } from "./lib"; 4 + import { createAuthenticatedClient} from "./auth"; 6 5 import { ATmarkView, VIEW_TYPE_ATMARK } from "./views/atmark"; 7 - import type { ProfileData } from "./components/profileIcon"; 8 6 9 7 export default class ATmarkPlugin extends Plugin { 10 8 settings: AtProtoSettings = DEFAULT_SETTINGS; 11 9 client: Client | null = null; 12 - profile: ProfileData | null = null; 13 10 14 11 async onload() { 15 12 await this.loadSettings(); 16 - await this.initClient(); 17 13 18 14 this.registerView(VIEW_TYPE_ATMARK, (leaf) => { 19 15 return new ATmarkView(leaf, this); ··· 41 37 new Notice("Connected"); 42 38 } catch (err) { 43 39 const message = err instanceof Error ? err.message : String(err); 44 - new Notice(`Failed to login as ${identifier}: ${message}`); 45 - this.client = createPublicClient(serviceUrl); 46 - this.profile = null; 47 - } 48 - } else { 49 - this.client = createPublicClient(serviceUrl); 50 - this.profile = null; 51 - } 52 - } 53 - 54 - private async fetchProfile() { 55 - if (!this.client || !this.settings.identifier) { 56 - this.profile = null; 57 - return; 58 - } 59 - try { 60 - const resp = await getProfile(this.client, this.settings.identifier); 61 - if (resp.ok) { 62 - this.profile = { 63 - did: resp.data.did, 64 - handle: resp.data.handle, 65 - displayName: resp.data.displayName, 66 - avatar: resp.data.avatar, 67 - }; 68 - } else { 69 - this.profile = null; 40 + console.error("Failed to login:", message); 70 41 } 71 - } catch { 72 - this.profile = null; 73 42 } 74 43 } 75 44 ··· 79 48 80 49 81 50 async activateView(v: string) { 82 - if (!this.profile && this.client && this.settings.identifier) { 83 - await this.fetchProfile(); 51 + const { workspace } = this.app; 52 + if (!this.client) { 53 + await this.initClient(); 54 + } 55 + if (!this.client) { 56 + new Notice("Failed to login. Check your credentials in plugin settings."); 57 + return; 84 58 } 85 - 86 - const { workspace } = this.app; 87 59 88 60 let leaf: WorkspaceLeaf | null = null; 89 61 const leaves = workspace.getLeavesOfType(v);
+12 -6
src/views/atmark.ts
··· 1 1 import { ItemView, WorkspaceLeaf, setIcon } from "obsidian"; 2 2 import type ATmarkPlugin from "../main"; 3 - import { renderProfileIcon } from "../components/profileIcon"; 4 3 import { CardDetailModal } from "../components/cardDetailModal"; 5 4 import type { ATmarkItem, DataSource, SourceFilter } from "../sources/types"; 6 5 import { SembleSource } from "../sources/semble"; ··· 20 19 super(leaf); 21 20 this.plugin = plugin; 22 21 22 + this.initSources(); 23 + 24 + } 25 + 26 + initSources() { 23 27 if (this.plugin.client) { 24 28 const repo = this.plugin.settings.identifier; 25 29 this.sources.set("semble", { ··· 35 39 filters: new Map() 36 40 }); 37 41 } 42 + 38 43 } 39 44 40 45 getViewType() { ··· 70 75 container.addClass("atmark-view"); 71 76 72 77 if (!this.plugin.client) { 73 - container.createEl("p", { text: "Not connected." }); 74 - return; 78 + await this.plugin.refreshClient(); 79 + if (!this.plugin.client) { 80 + container.createEl("p", { text: "Not logged in, check your credentials in settings." }); 81 + return; 82 + } 83 + this.initSources(); 75 84 } 76 85 77 86 const loading = container.createEl("p", { text: "Loading..." }); ··· 105 114 106 115 private renderHeader(container: HTMLElement) { 107 116 const header = container.createEl("div", { cls: "atmark-header" }); 108 - const nav = header.createEl("div", { cls: "atmark-nav" }); 109 - 110 - renderProfileIcon(nav, this.plugin.profile); 111 117 112 118 const sourceSelector = header.createEl("div", { cls: "atmark-source-selector" }); 113 119 const sources: SourceType[] = ["semble", "margin", "bookmark"];
+47 -48
styles.css
··· 9 9 border-bottom: 1px solid var(--background-modifier-border); 10 10 } 11 11 12 - .atmark-nav { 13 - display: flex; 14 - align-items: center; 15 - justify-content: space-between; 16 - margin-bottom: 12px; 17 - gap: 10px; 18 - } 19 - 20 - .atmark-title { 21 - margin: 0; 22 - font-size: var(--h1-size); 23 - font-weight: var(--font-bold); 24 - color: var(--text-accent); 25 - flex-shrink: 1; 26 - min-width: 0; 27 - } 28 - 29 12 .atmark-source-selector { 30 13 display: flex; 31 14 align-items: center; ··· 33 16 gap: 0; 34 17 margin-bottom: 12px; 35 18 border-bottom: 1px solid var(--background-modifier-border); 19 + position: relative; 36 20 } 37 21 38 22 .atmark-source-option { ··· 916 900 color: var(--text-normal); 917 901 } 918 902 919 - /* Semble-specific Profile Icon */ 920 - .semble-profile-icon { 903 + /* Profile Icon */ 904 + .atmark-profile-icon { 921 905 display: flex; 922 906 align-items: center; 923 - gap: 10px; 907 + gap: 6px; 908 + padding: 10px 12px; 909 + margin-bottom: -1px; 924 910 margin-left: auto; 911 + position: absolute; 912 + right: 0; 913 + background: transparent; 914 + transition: background 0.15s ease; 925 915 } 926 916 927 - .semble-avatar-btn { 917 + .atmark-profile-icon:hover { 918 + background: var(--background-modifier-hover); 919 + } 920 + 921 + .atmark-avatar-btn { 928 922 display: flex; 929 923 align-items: center; 930 924 justify-content: center; 931 - width: 36px; 932 - height: 36px; 925 + width: 24px; 926 + height: 24px; 933 927 padding: 0; 934 928 background: var(--background-secondary); 935 - border: 2px solid var(--background-modifier-border); 936 - border-radius: var(--radius-full); 929 + border: 1px solid var(--background-modifier-border); 930 + border-radius: 50%; 937 931 cursor: pointer; 938 932 overflow: hidden; 939 933 transition: opacity 0.15s ease; 940 934 } 941 935 942 - .semble-avatar-btn:hover { 936 + .atmark-avatar-btn:hover { 943 937 opacity: 0.8; 944 938 } 945 939 946 - .semble-avatar-img { 940 + .atmark-avatar-img { 947 941 width: 100%; 948 942 height: 100%; 949 943 object-fit: cover; 950 - border-radius: var(--radius-full); 944 + border-radius: 50%; 951 945 } 952 946 953 - .semble-avatar-initials { 954 - font-size: var(--font-small); 947 + .atmark-avatar-initials { 948 + font-size: var(--font-smallest); 955 949 font-weight: var(--font-semibold); 956 950 color: var(--text-muted); 957 951 } 958 952 959 - .semble-avatar-placeholder { 953 + .atmark-avatar-placeholder { 960 954 display: flex; 961 955 align-items: center; 962 956 justify-content: center; 963 - width: 36px; 964 - height: 36px; 957 + width: 24px; 958 + height: 24px; 965 959 background: var(--background-secondary); 966 - border: 2px solid var(--background-modifier-border); 967 - border-radius: var(--radius-full); 960 + border: 1px solid var(--background-modifier-border); 961 + border-radius: 50%; 968 962 color: var(--text-faint); 969 - font-size: var(--font-small); 963 + font-size: var(--font-smallest); 970 964 } 971 965 972 - .semble-profile-info { 966 + .atmark-profile-info { 973 967 display: flex; 974 968 flex-direction: column; 975 969 align-items: flex-end; 976 - gap: 2px; 970 + gap: 1px; 977 971 } 978 972 979 - .semble-profile-name { 980 - font-size: var(--font-small); 981 - font-weight: var(--font-semibold); 982 - color: var(--text-normal); 973 + .atmark-profile-name { 974 + font-size: var(--font-ui-small); 975 + font-weight: var(--font-medium); 976 + color: var(--text-muted); 983 977 line-height: 1.2; 984 978 } 985 979 986 - .semble-profile-handle { 980 + .atmark-profile-handle { 987 981 font-size: var(--font-smallest); 988 - color: var(--text-muted); 982 + color: var(--text-faint); 989 983 line-height: 1.2; 990 984 } 991 985 ··· 1133 1127 padding-bottom: 12px; 1134 1128 } 1135 1129 1136 - .atmark-title, 1137 - .semble-profile-icon { 1130 + .atmark-profile-icon { 1138 1131 display: none; 1139 1132 } 1140 1133 ··· 1161 1154 } 1162 1155 } 1163 1156 1164 - .is-mobile .atmark-title, 1165 - .is-mobile .semble-profile-icon { 1157 + /* Hide profile in narrow sidebar widths (but not mobile) */ 1158 + @media (max-width: 400px) { 1159 + .atmark-profile-icon { 1160 + display: none; 1161 + } 1162 + } 1163 + 1164 + .is-mobile .atmark-profile-icon { 1166 1165 display: none; 1167 1166 } 1168 1167