From effe0bbd968a9a6737af2d660f908f68ee705910 Mon Sep 17 00:00:00 2001 From: huaqo Date: Thu, 23 Apr 2026 13:46:09 +0200 Subject: [PATCH] feat: add open modal support, refactor client, change setting naming --- src/clients.ts | 30 +++++++------ src/main.ts | 28 ++++++++++++- src/modals.ts | 109 ++++++++++++++++++++++++++++++++++++++++++++---- src/settings.ts | 3 +- 4 files changed, 146 insertions(+), 24 deletions(-) diff --git a/src/clients.ts b/src/clients.ts index 4835b77..2feb473 100644 --- a/src/clients.ts +++ b/src/clients.ts @@ -1,5 +1,4 @@ import { Notice, requestUrl } from 'obsidian'; -import Cite from "./main"; type ZoteroItemResponse = { key: string; @@ -19,16 +18,25 @@ type ZoteroItemResponse = { }; }; -export class CiteClient { +export class ZoteroClient { - private baseURL: string; private userID: number; - private style: string; + private baseURL: string; + private style?: string; + private baseParams: URLSearchParams; - constructor(port: string, style: string){ - this.baseURL = `http://127.0.0.1:${port}/api`; + constructor(port: string); + constructor(port: string, style: string); + constructor(port: string, style?: string) { this.userID = 0; + this.baseURL = `http://127.0.0.1:${port}/api/users/${this.userID}`; this.style = style; + this.baseParams = new URLSearchParams(); + if (this.style) { + this.baseParams.set("style", this.style); + } + this.baseParams.set("format", "json"); + this.baseParams.set("include", "citation,bib,data"); } private async request(url: string): Promise { @@ -58,15 +66,13 @@ export class CiteClient { } - async getAllCitations(): Promise { - const url = `${this.baseURL}/users/${this.userID}/items?format=json&include=citation,data&style=${this.style}` - console.log(url) + async getItems(): Promise { + const url = `${this.baseURL}/items?${this.baseParams.toString()}`; return this.request(url); } - async getAllBibliographies(): Promise { - const url = `${this.baseURL}/users/${this.userID}/items?format=json&include=bib,data&style=${this.style}` - console.log(url) + async getItemsTop(): Promise { + const url = `${this.baseURL}/items/top?${this.baseParams.toString()}`; return this.request(url); } diff --git a/src/main.ts b/src/main.ts index 04e5307..3204044 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,6 @@ import { App, Editor, MarkdownView, Modal, Plugin, Notice } from 'obsidian'; import { DEFAULT_SETTINGS, CiteSettings, CiteSettingTab } from "./settings"; -import { CiteModal } from "./modals" +import { CiteModal, OpenModal } from "./modals" export default class Cite extends Plugin { settings: CiteSettings; @@ -25,6 +25,22 @@ export default class Cite extends Plugin { }).open(); }); + this.addRibbonIcon('scroll-text', 'insert bibliography', (evt: MouseEvent) => { + const view = this.app.workspace.getActiveViewOfType(MarkdownView); + + if (!view) { + new Notice("No active Markdown view"); + console.log("No active Markdown view"); + return; + } + + const editor = view.editor; + + new CiteModal(this.app, this.settings.portSetting, this.settings.linkSetting, this.settings.styleSetting, "bibliography", (citation: string) => { + editor.replaceSelection(citation); + }).open(); + }); + this.addCommand({ id: 'citation-command', name: 'insert citation', @@ -41,12 +57,20 @@ export default class Cite extends Plugin { name: 'insert bibliography', editorCallback: (editor: Editor, view: MarkdownView) => { const selection = editor.getSelection(); - new CiteModal(this.app, this.settings.portSetting, this.settings.linkSetting, this.settings.styleSetting, "bibliography",(bibliography: string) => { + new CiteModal(this.app, this.settings.portSetting, this.settings.linkSetting, this.settings.styleSetting, "bibliography", (bibliography: string) => { editor.replaceSelection(bibliography); }).open(); } }); + this.addCommand({ + id: 'open-command', + name: 'open', + callback: () => { + new OpenModal(this.app, this.settings.portSetting).open(); + }, + }); + this.addSettingTab(new CiteSettingTab(this.app, this)); this.registerInterval(window.setInterval(() => console.log('setInterval'), 5 * 60 * 1000)); diff --git a/src/modals.ts b/src/modals.ts index 15b1633..ad998bf 100644 --- a/src/modals.ts +++ b/src/modals.ts @@ -1,10 +1,104 @@ +import { shell } from "electron"; import { App, FuzzyMatch, FuzzySuggestModal, renderResults, Notice } from 'obsidian'; -import {CiteClient, ZoteroItemResponse} from "./clients" +import { ZoteroClient } from "./clients"; +import type { ZoteroItemResponse } from "./clients"; + +export class OpenModal extends FuzzySuggestModal { + private client: ZoteroClient; + private items: ZoteroItemResponse[] = []; + private zoteroBaseUrl; + + constructor( + app: App, + port: string, + ) { + super(app); + this.client = new ZoteroClient(port); + this.setPlaceholder("Loading references..."); + this.zoteroBaseUrl = "zotero://select/library/items/"; + } + + async onOpen(){ + super.onOpen(); + + try { + this.items = await this.client.getItemsTop(); + this.setPlaceholder("Search Zotero..."); + this.inputEl.disabled = false; + this.inputEl.focus(); + this.updateSuggestions(); + } catch (error) { + new Notice("Failed to load citations/bibliographies from Zotero. Maybe you forgot to turn on or configure Zotero."); + console.error(error); + this.close(); + } + } + + + private getTitle(item: ZoteroItemResponse): string { + return item.data?.title ?? "Untitled"; + } + + private getYear(item: ZoteroItemResponse): string { + const date = item.data?.date; + if (!date) return "n.d."; + + const match = date.match(/\b\d{4}\b/); + return match ? match[0] : "n.d."; + } + + private getAuthor(item: ZoteroItemResponse): string { + const creators = item.data?.creators; + if (!creators || creators.length === 0) return "Unknown author"; + + const first = creators[0]; + + if (first.name) return first.name; + if (first.firstName && first.lastName) return `${first.firstName} ${first.lastName}`; + if (first.lastName) return first.lastName; + + return "Unknown author"; + } + + private getKey(item: ZoteroItemResponse): string { + return item.key ?? ""; + } + + getItems(): ZoteroItemResponse[] { + return this.items; + } + + getItemText(item: ZoteroItemResponse): string { + return `${this.getTitle(item)} ${this.getAuthor(item)} ${this.getYear(item)}`; + } + + renderSuggestion(match: FuzzyMatch, el: HTMLElement) { + const { item } = match; + + const title = this.getTitle(item); + const author = this.getAuthor(item); + const year = this.getYear(item); + + const titleEl = el.createDiv(); + renderResults(titleEl, title, match.match); + + const subtitleEl = el.createEl("small"); + const offset = -(title.length + 1); + renderResults(subtitleEl, `${author}, ${year}`, match.match, offset); + } + + onChooseItem(item: ZoteroItemResponse) { + if (!this.getKey(item)) return; + const uri = `${this.zoteroBaseUrl}${this.getKey(item)}`; + shell.openExternal(uri); + } + +} export class CiteModal extends FuzzySuggestModal { - private onChoose: (citation: string) => void; - private client: CiteClient; + private onChoose: (reference: string) => void; + private client: ZoteroClient; private items: ZoteroItemResponse[] = []; private zoteroBaseUrl: string; private createLink: boolean; @@ -16,10 +110,10 @@ export class CiteModal extends FuzzySuggestModal { createLink: boolean, style: string, mode: string, - onChoose: (citation: string) => void, + onChoose: (reference: string) => void, ) { super(app); - this.client = new CiteClient(port, style); + this.client = new ZoteroClient(port, style); this.onChoose = onChoose; this.setPlaceholder("Loading citations..."); this.zoteroBaseUrl = "zotero://select/library/items/"; @@ -32,9 +126,9 @@ export class CiteModal extends FuzzySuggestModal { try { if (this.mode === "citation"){ - this.items = await this.client.getAllCitations(); + this.items = await this.client.getItemsTop(); } else if (this.mode === "bibliography"){ - this.items = await this.client.getAllBibliographies(); + this.items = await this.client.getItemsTop(); } else { throw new Error(`Unknown mode: ${this.mode}`); } @@ -109,7 +203,6 @@ export class CiteModal extends FuzzySuggestModal { renderResults(subtitleEl, `${author}, ${year}`, match.match, offset); } - onChooseItem(item: ZoteroItemResponse) { let output = ""; diff --git a/src/settings.ts b/src/settings.ts index 8a1061f..aa50450 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -1,5 +1,4 @@ import {App, PluginSettingTab, Setting } from "obsidian"; -import Cite from "./main"; export interface CiteSettings { portSetting: string; @@ -49,7 +48,7 @@ export class CiteSettingTab extends PluginSettingTab { })); new Setting(containerEl) - .setName('Add link to library') + .setName('URI') .setDesc('zotero://select/library/items/...') .addToggle(toggle => toggle .setValue(this.plugin.settings.linkSetting)