//import * as katex from 'katex' import katex from "katex"; // uncomment this line instead of the above before compiling, use the above for intellisense type support //const katex = require('katex'); import katex_css_ulr from "../node_modules/katex/dist/katex.min.css" import basic_css_url from "./basic.css" // add a basic stylesheet to make i-math display as inline block and tex-math display as block let css = document.createElement('link'); css.rel = 'stylesheet'; css.href = basic_css_url; //document.head.appendChild(css); //console.log("css:", basic_css_url) // lc-ref compatibility /** * The [lc-ref](https://www.npmjs.com/package/lc-ref) package. * * This variable provides the compatibility with the package when it is loaded as a js file. * * We use `declare var` to tell the compiler that this variable is defined in the global scope. */ declare var lc_ref : any; export abstract class TexMathBase extends HTMLElement { private m_tex : string = ""; private blockDisplay : boolean = false; private m_slot? : HTMLSlotElement; private m_display? : HTMLElement; private m_number? : HTMLElement; private m_container? : HTMLElement; // TODO remove? private number : number = -1; private static triggering_attributes : string[] = ['id', 'number', 'n']; //static equation_counter = 0; public lc_number : string | Function | null = null; /** * Get the tex content of the element */ get tex() : string { return this.m_tex; } /** * Set the text content and updates the display */ set tex(_tex : string) { this.textContent = _tex; } constructor(blockDisplay : boolean) { super(); // store display mode this.blockDisplay = blockDisplay; // Create a shadowRoot to display the content const shadowRoot = this.attachShadow({ mode: 'open' }); shadowRoot.innerHTML = // style (katex css), we have to insert the stylesheet here since the shadowRoot cannot inherit styles `` + // hidden slot, just to receive the content '' + // katex math display here ''; // find the slot this.m_slot = shadowRoot.querySelectorAll('#src')[0] as HTMLSlotElement; // find the display element this.m_display = shadowRoot.querySelectorAll('#display')[0] as HTMLElement; // find the container element let container = shadowRoot.querySelectorAll('#container')[0] as HTMLElement; this.m_container = container; // find the number element this.m_number = shadowRoot.querySelectorAll('#number')[0] as HTMLElement; this.m_number.style.display = "none"; { container.style.display = "inline"; container.style.alignContent = "center"; this.m_display.style.flexGrow = "1"; this.m_number.style.display = "none"; this.m_number.style.flexGrow = "0"; this.m_number.style.width = "4em"; this.m_number.style.position = "relative"; } } connectedCallback() { // add an event listener to the slot in order to change the // see https://stackoverflow.com/a/54355963 let _this_ = this; let slot = this.m_slot!; this.m_slot!.addEventListener('slotchange', function({/* unused event parameter */} : Event) { _this_.render(); // listen for nodes changes // https://stackoverflow.com/questions/47378194/fire-a-function-when-innerhtml-of-element-changes let nodes = slot.assignedNodes(); for (let node of nodes) { node.addEventListener('DOMSubtreeModified', function() { _this_.render(); }); } }); let obs = new MutationObserver(function(mutations : MutationRecord[]) { for (let mutation of mutations) { if (mutation.type == "attributes") { if (TexMathBase.triggering_attributes.includes(mutation.attributeName!)) { _this_.update_number(); } } } }); obs.observe(this, { attributes: true }); /*if (this.number < 0 && (this.hasAttribute('id') || this.hasAttribute('number') || this.hasAttribute('n'))) { this.number = ++TexMathBase.equation_counter; }*/ this.render(); this.update_number(); } /** * Update the equation display. * * Updates the display by parsing the tex content and rendering it, * it also updates the stored tex that can be queryed by `get tex()` * and the number if required. * * TODO: update the number if required */ render() : void { // In this function we will get all the tex content and then store it // for parsing that will be done in the last section of this function // if the receiving slot is not defined, we cannot render if (!this.m_slot) return; // we take al the text that is in the slot (if no child with slot attribute // is defined, this is equivalent of calling `this.innerText`) let nodes = this.m_slot.assignedNodes(); let tex = ""; for (let node of nodes) { tex += node.textContent; } tex = TexMathBase.fix_indentation(tex); // save tex this.m_tex = tex; let my_tag = "texMathInnerSlot"; // render using katex if (this.m_display) { try { katex.render(tex, this.m_display, { displayMode: this.blockDisplay, //display: this.blockDisplay, output: "html", //trust: true, macros : { "\\slot" : "\\text{" + my_tag + "\\{#1\\}}", } }); while (true) { let html = this.m_display.innerHTML; let my_tag_index = html.indexOf(my_tag); // if not found, finished if (my_tag_index < 0) { break; } let open_paren_index = html.indexOf("{", my_tag_index); let close_paren_index = html.indexOf("}", open_paren_index); if (open_paren_index < 0 || close_paren_index < 0) { console.error(my_tag + ": missing { or }"); break; } if (open_paren_index > close_paren_index) { console.error(my_tag + ": { is after }"); break; } let slot_id = html.substring(open_paren_index + 1, close_paren_index); let slot = document.createElement("slot"); slot.name = slot_id; let span = document.createElement("span"); slot.appendChild(span); span.style.color = "red"; span.innerText = "#" + slot_id; this.m_display.innerHTML = html.substring(0, my_tag_index) + slot.outerHTML + html.substring(close_paren_index + 1); } } catch (error) { this.m_display.innerHTML = '' + error + ""; } } else { console.warn("tex-math internal error, no display element defined, cannot render"); } } /** * Builds a peview of the equation. * * This funtion allows the [lc-ref](https://www.npmjs.com/package/lc-ref) package to create the equation preview on link hover. * * The preview will be rendered simply as another tex-math element with the same content. * * @param preview the element to fill with the preview */ public lc_build_ref_preview(preview : HTMLElement) : void { // create a new tex-math element and fill it with our tex content let eq = document.createElement('tex-math') as TexMathBase; eq.tex = this.tex; // add the element to the preview preview.appendChild(eq); } /** * updates the equation number, it also adds/removes the number if necessary */ private update_number() : void { // in any case we ensure the number is shown as required. // To do that we check if a triggering attribute is set and if the display is block /** * Tells if the number should be shown or not. */ let numerated : boolean = false; // check for triggering attributes for (let attr of TexMathBase.triggering_attributes) { if (this.hasAttribute(attr)) { numerated = true; break; } } // disable the number if the display is not block since inline equations cannot // have a number, it could be confused with the equation content itself. // Maybe in the future we could display the number as a link or with another style or position // and therefore inline equations could have a number, too. if (!this.blockDisplay) { numerated = false; } // TODO do nothing if not necessary, this is a bit of a hack, we should check if the number is already // there and if it is not we should add it. if (numerated) { this.add_number(); } else { this.remove_number(); } // actual renumeration this.renumerate(); } /** * this function does not actually add the number, but it schedules for it to be added when the enumeration is done */ private add_number() : void { // we add the `lc_number` attribute to the element, this will be used by the [lc-ref](https://www.npmjs.com/package/lc-ref) // package to know that this element should be numerated. It will set the number to the value of the `number` attribute // and display it as a link. this.lc_number = function(n : number) : Number { // TODO remove? this.number = n; //this.m_number!.innerHTML = "(" + n.toString() + ")"; this.m_number!.innerHTML = "