/* line numbers */ updateLineNumber(location.hash); for (let line of document.querySelectorAll('.line-number')) { line.addEventListener("click", () => updateLineNumber(line.attributes.getNamedItem("id").value)); } function updateLineNumber(id) { if (id && id.startsWith('#')) { id = id.substring(1); } if (!id) { return; } let element = document.getElementById(id); if (element.classList.contains("line-number")) { for (const line of document.querySelectorAll(".line-active")) { line.classList.remove("line-active"); } element.closest('.entry').classList.add('line-active'); } } /* Scroll to top/bottom buttons */ const downButton = document.getElementById("down-button"); if (downButton) { downButton.addEventListener("click", () => scrollToHeight(document.body.scrollHeight)); } const upButton = document.getElementById("up-button"); if (upButton) { upButton.addEventListener("click", () => scrollToHeight(0)); } /** * Scroll to a specific height * Disable smooth scrolling for large pages * @param {number} top height to scroll to * @param {number} [smoothScrollLimit] only use smooth scrolling if the distance is less than this value */ function scrollToHeight(top, smoothScrollLimit = 10000) { const distance = Math.abs(document.documentElement.scrollTop - top); const behavior = (distance < smoothScrollLimit) ? "smooth" : "instant"; window.scrollTo({left: 0, top, behavior}); } /* error collapse toggle */ const toggleErrorsButton = document.getElementById("error-toggle"); if (toggleErrorsButton) { toggleErrorsButton.addEventListener("click", toggleErrors); } function toggleErrors() { if (toggleErrorsButton.classList.contains("toggled")) { toggleErrorsButton.classList.remove("toggled"); uncollapseAllErrors(); } else { toggleErrorsButton.classList.add("toggled"); collapseAllErrors(); } } function collapseAllErrors() { let firstNoErrorLine = false; let lines = document.querySelectorAll('.log-inner > .entry'); let totalLines = lines.length; for (const [i, line] of lines.entries()) { let lineNumber = line.querySelector(".line-number").innerHTML; if (line.classList.contains("entry-no-error")) { line.style.display = "none"; if (firstNoErrorLine === false) { firstNoErrorLine = lineNumber; } if (i + 1 === totalLines && firstNoErrorLine) { line.insertAdjacentElement("afterend", generateCollapsedLines(firstNoErrorLine, lineNumber)); } } else { if (firstNoErrorLine) { line.insertAdjacentElement("beforebegin", generateCollapsedLines(firstNoErrorLine, lineNumber - 1)); firstNoErrorLine = false; } } } } function uncollapseAllErrors() { document.querySelectorAll('.entry-no-error').forEach(line => line.style.removeProperty("display")); document.querySelectorAll('.collapsed-lines').forEach(collapsed => collapsed.remove()); } function handleCollapsedClick(e) { let collapsed = e.currentTarget; let positionElement = document.getElementById(`L${parseInt(collapsed.dataset.end) + 1}`); let position; if (positionElement) { position = positionElement.getBoundingClientRect().top - window.scrollY; } for (let i = parseInt(collapsed.dataset.start); i <= parseInt(collapsed.dataset.end); i++) { document.getElementById(`L${i}`).parentElement.parentElement.style.removeProperty("display"); } if (positionElement) { window.scrollTo({ left: 0, top: positionElement.getBoundingClientRect().top - position - collapsed.offsetHeight, behavior: "instant" }); } collapsed.remove(); } function generateCollapsedLines(start, end) { let count = end - start + 1; let string = count === 1 ? "line" : "lines"; let collapsedRow = document.createElement("div"); collapsedRow.classList.add("collapsed-lines"); collapsedRow.dataset.start = start; collapsedRow.dataset.end = end; collapsedRow.appendChild(document.createElement("div")); collapsedRow.addEventListener("click", handleCollapsedClick); let collapsedLinesCount = document.createElement("div"); collapsedLinesCount.classList.add("collapsed-lines-count"); let icon = document.createElement("i"); icon.classList.add("fa-solid", "fa-angle-up"); collapsedLinesCount.appendChild(icon); collapsedLinesCount.append(` ${count} ${string} `); collapsedLinesCount.append(icon.cloneNode()); collapsedRow.appendChild(collapsedLinesCount); return collapsedRow; } /* convert timestamps */ let timeElements = document.querySelectorAll('[data-time]'); for (const element of timeElements) { const timestamp = parseInt(element.dataset.time); if (isNaN(timestamp)) { continue; } const date = new Date(timestamp * 1000); element.innerHTML = date.toLocaleString(); } /* settings */ const settingCheckboxes = document.querySelectorAll(".setting-checkbox"); settingCheckboxes.forEach(checkbox => checkbox.addEventListener("change", handleSettingChange)); let settingsChannel = null; if (typeof BroadcastChannel !== "undefined") { settingsChannel = new BroadcastChannel("mc-logs-settings"); settingsChannel.onmessage = (e) => { if (e.data.type === "settings-updated") { for (const checkbox of settingCheckboxes) { checkbox.checked = !!e.data.settings[checkbox.dataset.key]; applySetting(checkbox); } } }; } function handleSettingChange(e) { let checkbox = e.target; applySetting(checkbox); saveSettings(); if (settingsChannel) { settingsChannel.postMessage({ type: "settings-updated", settings: getCurrentSettings() }); } } function applySetting(checkbox) { let bodyClass = checkbox.dataset.bodyClass; if (checkbox.checked) { document.body.classList.add(bodyClass); } else { document.body.classList.remove(bodyClass); } switch (checkbox.dataset.key) { case "floatingScrollbar": initFloatingScrollbar(); break; } } function getCurrentSettings() { const data = {}; for (const checkbox of settingCheckboxes) { data[checkbox.dataset.key] = checkbox.checked; } return data; } function saveSettings() { const data = {}; for (const checkbox of settingCheckboxes) { data[checkbox.dataset.key] = checkbox.checked; } document.cookie = "IBLOGS_SETTINGS=" + encodeURIComponent(JSON.stringify(data)) + ";path=/;expires=" + new Date(new Date().getTime() + 100 * 365 * 24 * 60 * 60 * 1000).toUTCString(); } /* copy to clipboard */ const copyButtons = document.querySelectorAll("[data-clipboard]"); copyButtons.forEach(button => button.addEventListener("click", handleCopyButtonClick)); const doneClassName = "fa-solid fa-check"; async function handleCopyButtonClick(e) { const button = e.currentTarget; const data = button.dataset.clipboard; await navigator.clipboard.writeText(data); const iconElement = button.querySelector("i"); if (!iconElement) { return; } const originalClassName = iconElement.className; if (originalClassName === doneClassName) { return; } iconElement.className = doneClassName; setTimeout(() => { iconElement.className = originalClassName; }, 2000); } /* delete button */ const deleteButton = document.querySelector(".delete-log-button"); const deleteErrorElement = document.querySelector(".delete-overlay .popover-error"); if (deleteButton) { deleteButton.addEventListener("click", handleDeleteButtonClick); } async function handleDeleteButtonClick() { deleteErrorElement.style.display = "none"; const response = await fetch(window.location.href, { method: "DELETE", credentials: "include" }); if (!response.ok) { deleteErrorElement.style.display = "block"; deleteErrorElement.textContent = `${response.status} (${response.statusText})`; return; } window.location.href = "/"; } /* floating scroll bar */ const browser = getComputedStyle(document.body) .getPropertyValue("--browser") .replaceAll(/['"]/g, '') .trim() .toLowerCase(); const floatingScrollbar = document.querySelector(".floating-scrollbar"); let logContainer = null; if (browser === "firefox") { logContainer = document.querySelector(".log"); } else { logContainer = document.querySelector(".log-inner"); } if (floatingScrollbar && logContainer) { updateFloatingScrollbarWidths(); floatingScrollbar.addEventListener("scroll", () => { syncScroll(floatingScrollbar, logContainer); }); logContainer.addEventListener("scroll", () => { syncScroll(logContainer, floatingScrollbar); }); const observer = new ResizeObserver(() => { updateFloatingScrollbarWidths(); }); observer.observe(logContainer); } function syncScroll(source, target) { if (Math.abs(source.scrollLeft - target.scrollLeft) > 1) { target.scrollLeft = source.scrollLeft; } } function initFloatingScrollbar() { if (!floatingScrollbar || !logContainer) { return; } updateFloatingScrollbarWidths(); syncScroll(logContainer, floatingScrollbar); } function updateFloatingScrollbarWidths() { floatingScrollbar.style.setProperty( "--floating-scrollbar-width", `${logContainer.clientWidth}px` ); floatingScrollbar.style.setProperty( "--floating-scrollbar-content-width", `${logContainer.scrollWidth}px` ); }