UNPKG

bibcite

Version:
165 lines (145 loc) 5.38 kB
import { Data } from "csl-json"; import { Bibliography } from "./bibliography"; import { BibReference } from "./bibReference"; import { Citation } from "./citation"; import { CiteStyle } from "./styles/types"; import { styles, fallbackStyle } from "./styles"; export class BibController extends HTMLElement { _scope; // only control citations which are children of this scope _citeStyle: CiteStyle; _bibliography: Promise<Bibliography>; constructor() { super(); } listeners = { // addEventListener used on these in connectedCallback async CitationAdded(event: CustomEvent) { console.log("[BibController] caught CitationAdded event"); const bib = await this.bibliography; const citation: Citation = event.detail.element; // so that the Citation can fire CitationRemoved on me even when disconnected citation.myController = this; event.stopImmediatePropagation(); // monotheism citation.citeStyle = this.citeStyle; bib.registerCitation(event.detail.element); }, async CitationRemoved(event: CustomEvent) { console.log("[BibController] caught CitationRemoved event"); const bib = await this.bibliography; bib.unregisterCitation(event.detail.element); }, async ReferenceAdded(event: CustomEvent) { console.log("[BibController] caught ReferenceAdded event"); const bib: Bibliography = await this.bibliography; const bibReference: BibReference = event.detail.element; // so that ReferenceRemoved can fire on me even when disconnected bibReference.myController = this; event.stopImmediatePropagation(); // monotheism bibReference.citeStyle = this.citeStyle; bib.registerReferenceList(event.detail.element); }, async ReferenceRemoved(event: CustomEvent) { console.log("[BibController] caught ReferenceRemoved event"); const bib = await this.bibliography; bib.unregisterReferenceList(event.detail.element); }, }; connectedCallback() { console.log("[BibController] connectedCallback"); this._scope = this.parentElement === document.head ? document : this.parentElement; for (const [functionName, callback] of Object.entries(this.listeners)) { console.log(`|- addEventListener: ${functionName}`); this._scope.addEventListener(functionName, callback.bind(this)); } } disconnectedCallback() { console.log("[BibController] disconnectedCallback"); for (const [functionName, callback] of Object.entries(this.listeners)) { console.log(`removeEventListener: ${functionName}`); this._scope.removeEventListener(functionName, callback.bind(this)); } } static get observedAttributes() { return ["bib", "citation-style"]; } async attributeChangedCallback( name: string, oldValue: string, newValue: string ) { console.log( `[BibController] attributeChangedCallback(${name}, ${oldValue}, ${newValue})` ); switch (name) { case "bib": if (!this._bibliography) { this.bibliography; // getter for the initialization side-effect } else { (await this.bibliography).bib = await this.csl_from_attribute( newValue ); } break; case "citation-style": this.citeStyle = styles.get(newValue) || fallbackStyle; break; default: console.error( `[BibController] attributeChangedCallback(${name}, ${oldValue}, ${newValue})` + `called with unknown attribute ${name}` ); } } set innerHTML(value: string) { console.log("[BibController] set innerHTML"); super.innerHTML = value; if (!this.getAttribute("bib")) { // attribute has priority this.bibliography.then((res) => (res.bib = JSON.parse(value))); } } async propagateCiteStyle(citeStyle: CiteStyle) { const bib = <Bibliography>await this.bibliography; bib.citations.forEach((cit) => (cit.citeStyle = citeStyle)); bib._reference_lists.forEach((ref) => (ref.citeStyle = citeStyle)); } set citeStyle(value: CiteStyle) { this._citeStyle = value; this.propagateCiteStyle(value); } get citeStyle() { if (!this._citeStyle) { // default: alphabetic this._citeStyle = styles.get(this.getAttribute("citation-style")) || fallbackStyle; } return this._citeStyle; } get bibliography() { if (!this._bibliography) { this._bibliography = this.csl_from_attribute( this.getAttribute("bib") ).then((csl) => new Bibliography(csl, this.citeStyle.order.comparison)); } return this._bibliography; } async csl_from_attribute(bib_attr: string): Promise<Data[]> { // TODO: add option for bib to be callable? if (bib_attr) { console.log("|- bib attribute present -> fetching"); const response = await fetch(bib_attr); return await response.json(); } else if (document.readyState == "loading") { console.log("|- bib attribute missing -> waiting for innerHTML"); return new Promise((resolve, _) => { document.addEventListener("DOMContentLoaded", () => resolve(this.innerHTML) ); }).then(JSON.parse); } else { console.log("|- bib attribute missing -> using innerHTML"); return JSON.parse(this.innerHTML); } } }