iaremarkus_wol-reference-vi.../ReferencePopover.ts
Claude 1f4aa8a6da
Fix Obsidian plugin review lint errors and warnings
- Replace document.createElement with createDiv/createSpan/createFragment (Obsidian globals)
- Replace document with activeDocument throughout for popout window compatibility
- Replace setTimeout with activeWindow.setTimeout in ReferenceSidebarView
- Replace inline style.top/style.left with setCssProps + CSS custom properties in ReferencePopover
- Add CSS variables --ref-popover-top/left to styles.css for dynamic popover positioning
- Fix while-loop assignment pattern in ReferenceParser (no-cond-assign)
- Add @codemirror/view and @codemirror/state to dependencies
- Remove unused builtin-modules dev dependency

https://claude.ai/code/session_01NwoSBvufhvXrvTcTenDfGE
2026-05-20 08:01:07 +00:00

91 lines
2.9 KiB
TypeScript

import { App } from 'obsidian';
import { ReferenceData, appendHTML } from './types';
import { fetchReference } from './referenceService';
export class ReferencePopover {
private app: App;
private popoverEl: HTMLElement | null = null;
private showGeneration = 0;
private static instance: ReferencePopover | null = null;
private constructor(app: App) {
this.app = app;
}
public static getInstance(app: App): ReferencePopover {
if (!ReferencePopover.instance) {
ReferencePopover.instance = new ReferencePopover(app);
}
return ReferencePopover.instance;
}
public async show(targetEl: HTMLElement, verseRef: string) {
this.hide();
const generation = ++this.showGeneration;
this.popoverEl = activeDocument.createDiv({ cls: 'ref-popover' });
this.popoverEl.createEl('p', { text: 'Loading…', cls: 'ref-popover-loading' });
activeDocument.body.appendChild(this.popoverEl);
this.positionPopover(targetEl);
const data = await fetchReference(verseRef);
if (generation !== this.showGeneration) return; // stale, another show() ran
this.updateContent(data);
activeDocument.addEventListener('click', this.handleOutsideClick);
this.popoverEl.addEventListener('click', (e) => e.stopPropagation());
}
private updateContent(data: ReferenceData | null) {
if (!this.popoverEl) return;
this.popoverEl.empty();
if (!data || data.results.length === 0) {
this.popoverEl.createEl('p', { text: 'No results found.' });
return;
}
for (const html of data.results) {
appendHTML(this.popoverEl.createDiv({ cls: 'ref-popover-result' }), html);
}
}
private positionPopover(targetEl: HTMLElement) {
if (!this.popoverEl) return;
const targetRect = targetEl.getBoundingClientRect();
const popoverRect = this.popoverEl.getBoundingClientRect();
let top = targetRect.bottom + window.scrollY + 5;
let left = targetRect.left + window.scrollX;
if (left + popoverRect.width > window.innerWidth) {
left = window.innerWidth - popoverRect.width - 20;
}
if (top + popoverRect.height > window.innerHeight && targetRect.top > popoverRect.height) {
top = targetRect.top + window.scrollY - popoverRect.height - 5;
}
this.popoverEl.setCssProps({
'--ref-popover-top': `${top}px`,
'--ref-popover-left': `${left}px`,
});
}
public hide() {
if (this.popoverEl) {
this.popoverEl.remove();
this.popoverEl = null;
activeDocument.removeEventListener('click', this.handleOutsideClick);
}
}
private handleOutsideClick = (event: MouseEvent) => {
if (this.popoverEl && !this.popoverEl.contains(event.target as Node)) {
this.hide();
}
}
}