AT protocol bookmarking platforms in obsidian
1import { Modal, Notice, setIcon } from "obsidian";
2import type ATmarkPlugin from "../main";
3import { createNoteCard, deleteRecord } from "../lib";
4import type { ATmarkItem } from "../sources/types";
5
6export class CardDetailModal extends Modal {
7 plugin: ATmarkPlugin;
8 item: ATmarkItem;
9 onSuccess?: () => void;
10 noteInput: HTMLTextAreaElement | null = null;
11
12 constructor(plugin: ATmarkPlugin, item: ATmarkItem, onSuccess?: () => void) {
13 super(plugin.app);
14 this.plugin = plugin;
15 this.item = item;
16 this.onSuccess = onSuccess;
17 }
18
19 onOpen() {
20 const { contentEl } = this;
21 contentEl.empty();
22 contentEl.addClass("atmark-detail-modal");
23
24 const header = contentEl.createEl("div", { cls: "atmark-detail-header" });
25 const source = this.item.getSource();
26 header.createEl("span", {
27 text: source,
28 cls: `atmark-badge atmark-badge-source atmark-badge-${source}`,
29 });
30
31 this.item.renderDetail(contentEl);
32
33 // semble
34 if (this.item.canAddNotes() && this.item.getAttachedNotes) {
35 this.renderNotesSection(contentEl);
36 }
37
38 if (this.item.canAddNotes()) {
39 this.renderAddNoteForm(contentEl);
40 }
41
42 const footer = contentEl.createEl("div", { cls: "atmark-detail-footer" });
43 footer.createEl("span", {
44 text: `Created ${new Date(this.item.getCreatedAt()).toLocaleDateString()}`,
45 cls: "atmark-detail-date",
46 });
47 }
48
49 private renderNotesSection(contentEl: HTMLElement) {
50 const notes = this.item.getAttachedNotes?.();
51 if (!notes || notes.length === 0) return;
52
53 const notesSection = contentEl.createEl("div", { cls: "atmark-semble-detail-notes-section" });
54 notesSection.createEl("h3", { text: "Notes", cls: "atmark-detail-section-title" });
55
56 for (const note of notes) {
57 const noteEl = notesSection.createEl("div", { cls: "atmark-semble-detail-note" });
58
59 const noteContent = noteEl.createEl("div", { cls: "atmark-semble-detail-note-content" });
60 const noteIcon = noteContent.createEl("span", { cls: "atmark-semble-detail-note-icon" });
61 setIcon(noteIcon, "message-square");
62 noteContent.createEl("p", { text: note.text, cls: "atmark-semble-detail-note-text" });
63
64 const deleteBtn = noteEl.createEl("button", { cls: "atmark-semble-note-delete-btn" });
65 setIcon(deleteBtn, "trash-2");
66 deleteBtn.addEventListener("click", () => {
67 void this.handleDeleteNote(note.uri);
68 });
69 }
70 }
71
72 private renderAddNoteForm(contentEl: HTMLElement) {
73 const formSection = contentEl.createEl("div", { cls: "atmark-semble-detail-add-note" });
74 formSection.createEl("h3", { text: "Add a note", cls: "atmark-detail-section-title" });
75
76 const form = formSection.createEl("div", { cls: "atmark-semble-add-note-form" });
77
78 this.noteInput = form.createEl("textarea", {
79 cls: "atmark-textarea atmark-semble-note-input",
80 attr: { placeholder: "Write a note about this item..." },
81 });
82
83 const addBtn = form.createEl("button", { text: "Add note", cls: "atmark-btn atmark-btn-primary" });
84 addBtn.addEventListener("click", () => { void this.handleAddNote(); });
85 }
86
87 private async handleAddNote() {
88 if (!this.plugin.client || !this.noteInput) return;
89
90 const text = this.noteInput.value.trim();
91 if (!text) {
92 new Notice("Please enter a note");
93 return;
94 }
95
96 try {
97 await createNoteCard(
98 this.plugin.client,
99 this.plugin.settings.identifier,
100 text,
101 { uri: this.item.getUri(), cid: this.item.getCid() }
102 );
103
104 new Notice("Note added");
105 this.close();
106 this.onSuccess?.();
107 } catch (err) {
108 const message = err instanceof Error ? err.message : String(err);
109 new Notice(`Failed to add note: ${message}`);
110 }
111 }
112
113 private async handleDeleteNote(noteUri: string) {
114 if (!this.plugin.client) return;
115
116 const rkey = noteUri.split("/").pop();
117 if (!rkey) {
118 new Notice("Invalid note uri");
119 return;
120 }
121
122 try {
123 await deleteRecord(
124 this.plugin.client,
125 this.plugin.settings.identifier,
126 "network.cosmik.card",
127 rkey
128 );
129
130 new Notice("Note deleted");
131 this.close();
132 this.onSuccess?.();
133 } catch (err) {
134 const message = err instanceof Error ? err.message : String(err);
135 new Notice(`Failed to delete note: ${message}`);
136 }
137 }
138
139 onClose() {
140 this.contentEl.empty();
141 }
142}