function showApplication(toggle) { if (toggle) { document.getElementById('real-body').classList.remove('d-none'); } else { document.getElementById('real-body').classList.add('d-none'); } } function showLoadingIndicator(toggle) { if (toggle) { document.getElementById('loading').classList.remove('d-none'); } else { document.getElementById('loading').classList.add('d-none'); } } function displayError(querySelector, category, ex) { console.error(ex); const errorDiv = document.querySelector(querySelector); const errorHeading = errorDiv.querySelector('.alert-heading'); let message = ex.message; if (ex.hasOwnProperty('cause')) { if (ex.cause.hasOwnProperty('message')) { message += ': ' + ex.cause.message; } else { message += ex.cause; } } //clear all error text, but not heading const errorMessages = errorDiv.querySelectorAll('span'); errorMessages.forEach(message => errorDiv.removeChild(message)); //add new message and show the error alert const errorSpan = document.createElement('span'); errorSpan.innerText = message; errorDiv.appendChild(errorSpan); errorHeading.innerText = category; errorDiv.classList.remove('d-none'); } function displayFatalError(category, ex) { showApplication(false); showLoadingIndicator(false); displayError('#fatal-error', category, ex); } function displayNonFatalError(category, ex) { showApplication(true); showLoadingIndicator(false); displayError('#display-backup-error', category, ex); } function hideErrors() { document .querySelectorAll('#fatal-error, #display-backup-error') .forEach(el => el.classList.add('d-none')); } function importStylesheet(xsl) { //Firefox does not seem to report XML parsing errors into the //exception tree. So we catch here, log, and return some friendly //error. try { const xsltProcessor = new XSLTProcessor(); xsltProcessor.importStylesheet(xsl); return xsltProcessor; } catch (cause) { console.error(cause); throw new Error('Stylesheet parsing failed', { cause }); } } async function createXSLTProcessor() { return fetch(new URL("/MessageLog.xsl")) .then(resp => { if (resp.ok) return resp.text(); else throw new Error(resp.status + ' ' + resp.statusText); }) .then(str => { console.log(str); return new DOMParser().parseFromString(str, "text/xml"); }) .then(importStylesheet) .catch(cause => { throw new Error('Could not load XSL stylesheet', { cause }); }); } function removeChildren(element) { while (element.hasChildNodes()) { element.removeChild(element.lastChild); } } function checkOverflow(elem) { const elemHeight = elem.scrollHeight; const parentHeight = elem.offsetHeight; return elemHeight > parentHeight; } function processFragment() { document .querySelectorAll('[data-bs-toggle="popover"]') .forEach(popover => new bootstrap.Popover(popover)); document.querySelectorAll('.message-content div').forEach(div => { if (checkOverflow(div)) { div.parentElement.classList.add('overflow-icon'); } }); } function parseXML(xmlText) { const xmlDoc = parser.parseFromString(xmlText, "text/xml"); } function displayBackup(xsltProcessor, file) { hideErrors(); //document.getElementById('intro-card').classList.add('d-none'); const chatDisplay = document.getElementById("chat-display"); removeChildren(chatDisplay); file.text().then(xmlText => { document.getElementById('currently-viewing').innerText = file.name; const parser = new DOMParser(); const xmlDoc = parser.parseFromString(xmlText, "text/xml"); const errorNode = xmlDoc.querySelector('parsererror'); if (!errorNode) { try { const fragment = xsltProcessor.transformToFragment(xmlDoc, document); chatDisplay.appendChild(fragment); processFragment(); } catch (e) { displayNonFatalError('Could not load backup', e); } } else { const err = new Error(errorNode.firstChild.nodeValue); displayNonFatalError('Could not load backup', err); } }); } function initEvents(xsltProcessor) { const selectFileButtons = document.querySelectorAll("#backup-file-button, #backup-file-button-card"); const fileSelector = document.getElementById("backup-file"); selectFileButtons.forEach(button => button.addEventListener("click", e => { fileSelector.click(); e.preventDefault(); })); fileSelector.addEventListener("change", async (e) => { const file = e.currentTarget.files[0]; displayBackup(xsltProcessor, file); }); } window.addEventListener('resize', () => { if (window.innerWidth <= 500) { document.querySelector('#chat-display table').classList.add('table-sm'); } else { document.querySelector('#chat-display table').classList.remove('table-sm'); } }); window.addEventListener('DOMContentLoaded', async () => { createXSLTProcessor() .then(initEvents) .then(_ => { showLoadingIndicator(false); showApplication(true); }) .catch(ex => displayFatalError('Initialiation Failed', ex)); });