mirror of
https://github.com/iaremarkus/wol-reference-viewer.git
synced 2026-07-22 06:14:20 +00:00
- 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
142 lines
4.6 KiB
TypeScript
142 lines
4.6 KiB
TypeScript
import { ItemView, TFile, WorkspaceLeaf, setIcon } from 'obsidian';
|
|
import { ReferenceData, appendHTML } from './types';
|
|
import { fetchReference, isCached } from './referenceService';
|
|
|
|
export const VIEW_TYPE_REFERENCE_SIDEBAR = 'reference-sidebar-view';
|
|
|
|
const REF_REGEX = /!!(.+?)!!/g;
|
|
|
|
export class ReferenceSidebarView extends ItemView {
|
|
private updateGeneration = 0;
|
|
private abortController: AbortController | null = null;
|
|
|
|
constructor(leaf: WorkspaceLeaf) {
|
|
super(leaf);
|
|
}
|
|
|
|
getViewType(): string {
|
|
return VIEW_TYPE_REFERENCE_SIDEBAR;
|
|
}
|
|
|
|
getDisplayText(): string {
|
|
return 'References';
|
|
}
|
|
|
|
getIcon(): string {
|
|
return 'book-open';
|
|
}
|
|
|
|
async onOpen() {
|
|
this.renderEmpty('Open a note with !!reference!! syntax to see results here.');
|
|
const file = this.app.workspace.getActiveFile();
|
|
if (file) await this.update(file);
|
|
}
|
|
|
|
onClose(): Promise<void> {
|
|
this.abortController?.abort();
|
|
this.updateGeneration++;
|
|
return Promise.resolve();
|
|
}
|
|
|
|
async update(file: TFile | null) {
|
|
this.abortController?.abort();
|
|
const controller = new AbortController();
|
|
this.abortController = controller;
|
|
const { signal } = controller;
|
|
|
|
if (!file) {
|
|
this.renderEmpty('No file open.');
|
|
return;
|
|
}
|
|
|
|
const content = await this.app.vault.read(file);
|
|
const refs = this.extractRefs(content);
|
|
|
|
if (refs.length === 0) {
|
|
this.renderEmpty('No references in this note.');
|
|
return;
|
|
}
|
|
|
|
this.renderPlaceholders(refs);
|
|
|
|
const generation = ++this.updateGeneration;
|
|
|
|
for (let i = 0; i < refs.length; i++) {
|
|
if (signal.aborted || generation !== this.updateGeneration) return;
|
|
if (i > 0 && !isCached(refs[i])) await new Promise(resolve => activeWindow.setTimeout(resolve, 1000));
|
|
if (signal.aborted || generation !== this.updateGeneration) return;
|
|
const data = await fetchReference(refs[i], signal);
|
|
if (signal.aborted || generation !== this.updateGeneration) return;
|
|
this.fillPlaceholder(refs[i], data);
|
|
}
|
|
}
|
|
|
|
private extractRefs(content: string): string[] {
|
|
const seen = new Set<string>();
|
|
const refs: string[] = [];
|
|
const regex = new RegExp(REF_REGEX.source, 'g');
|
|
let match;
|
|
|
|
while ((match = regex.exec(content)) !== null) {
|
|
for (const part of match[1].split(';')) {
|
|
const ref = part.trim();
|
|
if (ref && !seen.has(ref)) {
|
|
seen.add(ref);
|
|
refs.push(ref);
|
|
}
|
|
}
|
|
}
|
|
return refs;
|
|
}
|
|
|
|
private renderEmpty(message: string) {
|
|
this.contentEl.empty();
|
|
this.contentEl.addClass('ref-sidebar');
|
|
this.contentEl.createEl('p', { text: message, cls: 'ref-sidebar-empty' });
|
|
}
|
|
|
|
private renderPlaceholders(refs: string[]) {
|
|
this.contentEl.empty();
|
|
this.contentEl.addClass('ref-sidebar');
|
|
|
|
for (const ref of refs) {
|
|
const item = this.contentEl.createDiv({ cls: 'ref-sidebar-item' });
|
|
item.dataset.ref = ref;
|
|
|
|
const header = item.createDiv({ cls: 'ref-sidebar-header' });
|
|
header.createDiv({ text: ref, cls: 'ref-sidebar-ref' });
|
|
const wolLink = header.createEl('a', {
|
|
href: `https://wol.jw.org/en/wol/l/r1/lp-e?q=${encodeURIComponent(ref)}`,
|
|
cls: 'ref-sidebar-wol-icon',
|
|
});
|
|
wolLink.setAttr('target', '_blank');
|
|
wolLink.setAttr('aria-label', 'View on WOL');
|
|
setIcon(wolLink, 'external-link');
|
|
|
|
const body = item.createDiv({ cls: 'ref-sidebar-body' });
|
|
body.createEl('p', { text: 'Loading\u2026', cls: 'ref-sidebar-loading' });
|
|
}
|
|
}
|
|
|
|
private fillPlaceholder(ref: string, data: ReferenceData | null) {
|
|
const item = this.contentEl.querySelector<HTMLElement>(
|
|
`.ref-sidebar-item[data-ref="${CSS.escape(ref)}"]`
|
|
);
|
|
if (!item) return;
|
|
|
|
const body = item.querySelector<HTMLElement>('.ref-sidebar-body');
|
|
if (!body) return;
|
|
|
|
body.empty();
|
|
|
|
if (!data) {
|
|
body.createEl('p', { text: 'Could not load reference.', cls: 'ref-sidebar-error' });
|
|
} else if (data.results.length === 0) {
|
|
body.createEl('p', { text: 'No results found.', cls: 'ref-sidebar-error' });
|
|
} else {
|
|
for (const html of data.results) {
|
|
appendHTML(body.createDiv({ cls: 'ref-sidebar-wol-result' }), html);
|
|
}
|
|
}
|
|
}
|
|
}
|