import { getAppState, initAppState } from '../../main'; import { renderPage } from './pageRenderer'; // eslint-disable-next-line @typescript-eslint/no-unused-vars import { getCookie, setCookie } from '../cookieManager'; import { Reactive } from '../ReactiveObject'; export let ctrlPressed = false; export function setCtrl(ctrl: boolean) { ctrlPressed = ctrl; } // function to turn the template into reactive content "hydating" a page export async function hydratePage(reduceJavascript?: boolean){ if (import.meta.env.SSR) return; if (reduceJavascript === undefined) reduceJavascript = false; await initAppState(); const appState = getAppState(); const documentBody = document.getElementById('app'); if (!documentBody) { throw new Error('Fatal Error: element with id app not found'); } ReactifyTemplate(appState); // here we look for elements with the d-on:click attribute and on click run the function in the attribute hydrateElement('*[d-on:click]', appState, 'click'); // here we determine if an element should be deleted form the DOM via the d-if directive hydrateIfAttributes(appState); hydrateElement('*[d-on:pointerEnter]', appState, 'pointerenter'); hydrateElement('*[d-on:pointerExit]', appState, 'pointerleave'); hydrateElement('*[d-on:mouseDown]', appState, 'mousedown'); hydrateElement('*[d-on:mouseUp]', appState, 'mouseup'); // here we look for elements with the d-model attribute and if there is any input in the element then we update the appState item with the name // of the attribute value // example: if the user types "hello" into a text field with the d-model attribute of "text" then we update the appState item with the name "text" // to "hello" // similar to vue.js v-model attribute hydrateModelAttributes(appState); hydrateHeadElements(appState); hydrateAnchorElements(reduceJavascript); hydrateKeyDown(appState); } export function ReactifyTemplate(appState: Reactive) { // for every item in the appState lets check for any element with the "checksum", a hex code equivalent of the item name Object.keys(appState.contents).forEach((e: string) => { if (e === undefined) return; // here we check for elements with the name of "data-token-" const uuid = e.split('').map((c: string) => c.charCodeAt(0).toString(16).padStart(2, '0')).join(''); const listeningElements = document.querySelectorAll(`[${'data-token-' + uuid}]`); listeningElements.forEach((elm) => { if (elm.parentElement?.getAttribute('d-once') !== null) { elm.parentElement?.removeAttribute('d-once'); return; } if (elm.parentElement?.getAttribute('d-html') !== null) { appState.listen(e, (change: string) => elm.innerHTML = change); elm.parentElement?.removeAttribute('d-html'); } else { appState.listen(e, (change: string) => elm.textContent = change); } }); }); } // eslint-disable-next-line @typescript-eslint/no-unused-vars export function hydrateIfAttributes(appState: Reactive) { const conditionalElms = Array.from(document.querySelectorAll('*[d-if]')); conditionalElms.forEach(async (e: Element) => { const condition = e.getAttribute('d-if'); const siblingConditionalElms: Array = []; // recursively check for subsequent elements with the d-else of d-else-if attribute function checkForConditionSibling(elm: Element) { if (!elm.nextElementSibling || typeof elm.nextElementSibling == 'undefined') return; if (elm.nextElementSibling?.getAttribute('d-else-if') !== null) { siblingConditionalElms.push(elm.nextElementSibling); if (!elm.nextElementSibling) return; checkForConditionSibling(elm.nextElementSibling); } if (elm.nextElementSibling?.getAttribute('d-else') !== null) { siblingConditionalElms.push(elm.nextElementSibling); } } checkForConditionSibling(e); if (siblingConditionalElms == undefined) return; function resetHTML() { e.innerHTML = ''; for (let i = 0; i < siblingConditionalElms.length; i++) { const element = siblingConditionalElms[i]; if (!element) return; element.innerHTML = ''; } } let ifStatement = `if (!!eval(condition)) { e.innerHTML = originalHTML } `; for (let i = 0; i < siblingConditionalElms.length; i++) { const element = siblingConditionalElms[i]; if (!element) return; const originHTML = element.innerHTML; element.innerHTML = ''; let statementDirective = 'else'; if (element.getAttribute('d-else-if') !== null) { statementDirective = 'else if'; } const condition = eval('element.getAttribute(\'d-\' + statementDirective.split(\' \').join(\'-\'))'); if (statementDirective == 'else if') { statementDirective = 'else if (' + condition + ')'; } ifStatement = ifStatement + statementDirective + `{ siblingConditionalElms[${i}].innerHTML = "${originHTML}" }`; } e.removeAttribute('d-if'); const originalHTML = e.innerHTML; if (!condition || originalHTML == undefined) return; resetHTML(); if (condition.includes('appState.contents.')) { let reactiveProp: Array | string | null | undefined = /appState\.contents\.[a-zA-Z]+/.exec(condition); if (!reactiveProp || !reactiveProp[0]) return; reactiveProp = reactiveProp[0].split('.')[2]; if (!reactiveProp) return; appState.listen(reactiveProp, () => { resetHTML(); eval(ifStatement); }); } eval(ifStatement); }); } export function hydrateElement(querySelector: string, appState: Reactive, eventListenerName: string, removeAttribute?: boolean) { const queryName: Array | null = /(?<=\[).+?(?=\])/.exec(querySelector); if (!queryName || !queryName[0]) return; const querySelectorAll = (querySelector.replace(':', '\\\\3A ')); querySelector = queryName[0]; const elms = eval(`Array.from(document.querySelectorAll('${querySelectorAll.toString()}'));`); if (Array.from(elms).length === 0) return; elms.forEach((e: Element) => { const hydrationFunction = e.getAttribute(`${querySelector}`); if (removeAttribute === undefined || removeAttribute === true) { e.removeAttribute(`${querySelector}`); } if (!hydrationFunction) return; e.addEventListener(eventListenerName, () => { eval(hydrationFunction); }); }); } export function hydrateModelAttributes(appState: Reactive) { const modelElms = Array.from(document.querySelectorAll('input[d-model], textarea[d-model]')); modelElms.forEach((e: Element) => { const modelName = e.getAttribute('d-model'); if (!modelName) return; e.addEventListener('input', (input: Event) => { const target = input.target as HTMLInputElement; appState.contents[modelName] = target.value; }); }); } // eslint-disable-next-line @typescript-eslint/no-unused-vars export function hydrateHeadElements(appState: Reactive) { const headContent = document.head.innerHTML; const headElements = Array.from(document.querySelectorAll('devto\\3A head')); headElements.forEach((e: Element) => { if (e.childNodes.length == 0) return; e.childNodes.forEach((elm) => { const child = elm as Element; if (child.nodeType !== 1) return; if (!document.head.querySelectorAll(child.tagName)) return; const uniqueAttributes = ['content', 'href']; document.head.querySelectorAll(child.tagName).forEach((headEl: Element) => { if (headEl.attributes.length == 0) { headEl.remove(); } for (let i = 0; i < headEl.attributes.length; i++) { if (!headEl.attributes.item(i)) return; const itemName = headEl.attributes.item(i)?.name; if (!itemName) return; if (uniqueAttributes.indexOf(itemName) == -1) { if (child.attributes.getNamedItem(itemName)?.nodeValue == headEl.attributes.item(i)?.nodeValue) { headEl.remove(); } } } }); document.head.appendChild(child); }); e.remove(); }); document.addEventListener('router:naviagte', () => { document.head.innerHTML = headContent; }, { once: true }); } export function hydrateAnchorElements(reduceJavascript: boolean) { const anchorElms = Array.from(document.querySelectorAll('a')); anchorElms.forEach((e: HTMLAnchorElement) => { if (!reduceJavascript && e.href === window.location.href) { e.setAttribute('link:active', ''); e.setAttribute('tabindex', '-1'); } e.addEventListener('click', async (click: MouseEvent) => { if (!event || click.ctrlKey) return; const target = click.target as HTMLElement; event.preventDefault(); if (!target) return; const url: string | null = target.getAttribute('href'); if (!url) return; await renderPage(url); }); }); } // eslint-disable-next-line @typescript-eslint/no-unused-vars export function hydrateKeyDown(appState: Reactive) { Array.from(document.body.querySelectorAll('*')).forEach((e) => { for (let i = 0; i < e.attributes.length; i++) { const item = e.attributes.item(i)?.name; if (!item) return; if (item.startsWith('d-on:keydown')) { const key = item.split('.')[1]?.toLowerCase(); let correctedKey = ''; if (key && key?.length > 0) { key.split('').forEach((e, i, arr) => { if (i === 0) { arr[i] = e.toUpperCase(); } correctedKey += arr[i]; }); } e.addEventListener('keydown', (keydown) => { const keyboardEvent = keydown; let keyName = keyboardEvent.key; if (keyName === ' ') { keyName = 'Space'; } const firstLetter = keyName.split('')[0]; keyName = firstLetter?.toUpperCase() + keyName.slice(1); if (keyName == correctedKey) { const itemCode = e.getAttribute(item); if (!itemCode) return; eval(itemCode); } }); } } }); }