import { compress, decompress } from "./compress.js"; import { outputAlphabetASCII, outputAlphabetQR, outputAlphabetEmoji } from "./alphabets.js"; var settings = { emoji: false, qr: false }; const settingsElements = { emoji: "#settings-emoji", qr: "#settings-qr" }; for (const setting in settingsElements) { const element = document.querySelector(settingsElements[setting]); settings[setting] = element.checked; element.addEventListener("change", (event) => { settings[setting] = element.checked; updateOutput(); }); } function countSymbols (string, alphabet) { let count = 0; while (string) { const symbol = alphabet.find(c => string.endsWith(c)); string = string.slice(0, symbol ? -symbol.length : -1); count ++; } return count; } const inputLinkElement = document.querySelector("#input-link"); const outputLinkElement = document.querySelector("#output-link"); const outputRatioElement = document.querySelector("#output-ratio"); const queryWarningElement = document.querySelector("#query-warning"); const qrCodeImage = document.querySelector("#qrcode"); const qrCodeCorrectionLevelContainer = document.querySelector("#qr-correct-level-container"); const qrCodeCorrectionLevelElement = document.querySelector("#qr-correct-level"); qrCodeCorrectionLevelElement.addEventListener("change", updateOutput); function updateOutput () { const input = inputLinkElement.value.trim(); try { const alphabet = settings.emoji ? outputAlphabetEmoji : outputAlphabetASCII; const output = compress(input, alphabet); let inputNormalized = input; if (input.startsWith("https://")) { inputNormalized = input.slice(8); } else if (input.startsWith("http://")) { inputNormalized = input.slice(7); } let excessiveParams = false; if (URL.canParse("http://" + inputNormalized)) { const url = new URL("http://" + inputNormalized); if (url.searchParams.size > 1) { excessiveParams = true; } } if (excessiveParams) { queryWarningElement.style.display = "inline"; } else { queryWarningElement.style.display = "none"; } const ratio = (1 - (countSymbols(output, alphabet) + 6) / inputNormalized.length) * 100; if (ratio < -300) { outputRatioElement.textContent = `Output is much larger than the input`; outputRatioElement.style.color = "rgb(255, 50, 50)"; } else if (ratio < 0) { outputRatioElement.textContent = `Output is ${Math.floor(-ratio)}% larger than the input`; outputRatioElement.style.color = "rgb(255, 50, 50)"; } else if (ratio > 0) { outputRatioElement.textContent = `Output is ${Math.ceil(ratio)}% smaller than the input`; outputRatioElement.style.color = "rgb(15, 190, 15)"; } else { outputRatioElement.textContent = "Output is the same length as the input"; outputRatioElement.style.color = "gray"; } outputLinkElement.textContent = `http://ha.mr#${output}`; outputLinkElement.href = `http://ha.mr#${output}`; outputLinkElement.style.color = ""; if (settings.qr) { const errorCorrection = ["L", "M", "Q", "H"][qrCodeCorrectionLevelElement.value]; qrCodeImage.style.display = "inline"; qrCodeCorrectionLevelContainer.style.display = "inline"; let qrCodeLink = `HTTP://HA.MR/${compress(input, outputAlphabetQR)}`; QRCode.toDataURL(qrCodeLink, { errorCorrectionLevel: errorCorrection, scale: 8 }, (err, url) => { if (err) { qrCodeImage.style.display = "none"; qrCodeCorrectionLevelContainer.style.display = "none"; return; } qrCodeImage.src = url; qrCodeImage.title = qrCodeLink; }); } else { qrCodeImage.style.display = "none"; qrCodeCorrectionLevelContainer.style.display = "none"; } } catch (e) { if (!input.trim()) { outputLinkElement.textContent = "Enter a link above to compress"; } else { outputLinkElement.textContent = "Invalid link"; outputLinkElement.style.color = "rgb(255, 50, 50)"; console.error(e); } qrCodeImage.style.display = "none"; qrCodeCorrectionLevelContainer.style.display = "none"; outputRatioElement.style.color = "rgba(255, 255, 255, 0)"; outputLinkElement.removeAttribute("href"); queryWarningElement.style.display = "none"; } } inputLinkElement.addEventListener("input", updateOutput); (() => { let payload = null; let alphabet = outputAlphabetASCII; // Get hash value of current address bar if (window.location.hash) { // Decode hash value in case it's non-ASCII payload = decodeURIComponent(window.location.hash.slice(1)); // Remove all whitespace - we never use whitespace when encoding hash values payload = payload.replaceAll(" ", ""); // Check if input is pure ASCII - potentially unreliable? const useEmoji = Array.from(payload).some(c => !outputAlphabetASCII.includes(c)); alphabet = useEmoji ? outputAlphabetEmoji : outputAlphabetASCII; } else { // If no hash value, we're likely reading a QR code // For that, use the path instead payload = decodeURIComponent(window.location.pathname.slice(1)); alphabet = outputAlphabetQR; } if (payload && payload.trim()) { try { const target = decompress(payload, alphabet); window.location.href = target; return; } catch (e) { console.warn(`Redirect failed. Could not decode input.`); console.error(e); } } updateOutput(); document.querySelector("#loader").style.opacity = 0; document.querySelector("#content").style.opacity = 1; document.querySelector("#content").style.pointerEvents = "auto"; })();