/* Paste area */ const source = document.body.dataset.name || location.host; const pasteArea = document.getElementById('paste-text'); const pastePlaceholder = document.querySelector('.paste-placeholder'); const pasteSaveButtons = document.querySelectorAll('.paste-save'); const fileSelectButton = document.getElementById('paste-select-file'); const pasteClipboardButton = document.getElementById('paste-clipboard'); const pasteError = document.getElementById('paste-error'); pasteArea.focus(); pasteArea.addEventListener('input', reevaluateContentStatus); pasteArea.addEventListener('paste', handlePasteEvent); pasteSaveButtons.forEach(button => button.addEventListener('click', sendLog)); fileSelectButton.addEventListener('click', selectLogFile); pasteClipboardButton.addEventListener('click', pasteFromClipboard); reevaluateContentStatus(); document.addEventListener('keydown', event => { if (event.key.toLowerCase() === 's' && (event.ctrlKey || event.metaKey)) { void sendLog(); event.preventDefault(); return false; } return true; }); /** * Save the log to the API * @returns {Promise} */ async function sendLog() { if (pasteArea.value === "") { return; } clearError(); pasteSaveButtons.forEach(button => button.classList.add("btn-working")); try { let log = pasteArea.value; log = applyFilters(log); const bodyData = { "content": log, "source": source, "metadata": Array.isArray(self.METADATA) ? self.METADATA : [] }; let headers = { "Content-Type": "application/json" } let body = JSON.stringify(bodyData); if (isGzSupported()) { headers["Content-Encoding"] = "gzip"; body = await packGz(body); } const response = await fetch(`/new`, { method: "POST", credentials: "include", headers: { "Content-Type": "application/json", "Content-Encoding": "gzip" }, body }); if (!response.ok) { showError(`${response.status} (${response.statusText})`); return; } let data = null; try { data = await response.json(); } catch (e) { console.error("Failed to parse JSON returned by API", e); showError("API returned invalid JSON"); return; } if (typeof data === 'object' && !data.success && data.error) { console.error(new Error("API returned an error"), data.error); showError(data.error); return; } if (typeof data !== 'object' || !data.success || !data.id) { console.error(new Error("API returned an invalid response"), data); showError("API returned an invalid response"); return; } location.href = data.url; } catch (e) { showError("Network error"); } } /* filters */ function applyFilters(text) { if (typeof FILTERS === "undefined" || !Array.isArray(FILTERS)) { return text; } for (let filter of FILTERS) { text = applyFilter(text, filter); } return text; } function applyFilter(text, filter) { switch (filter.type) { case 'trim': return text.trim(); case 'limit-bytes': return text.substring(0, filter.data.limit); case 'limit-lines': return text.split('\n').slice(0, filter.data.limit).join('\n'); case 'regex': try { for (const pattern of filter.data.patterns) { const regex = new RegExp(pattern.pattern, 'g' + pattern.modifiers.join()); text = text.replace(regex, (match) => { for (const exemption of filter.data.exemptions) { if (new RegExp(exemption.pattern, exemption.modifiers.join()).test(match)) { return match; } } return pattern.replacement; }); } } catch (e) { console.error('Error applying regex filter', e); } return text; default: console.error('Unknown filter type', filter.type); return text; } } async function pasteFromClipboard() { try { let content = await navigator.clipboard.readText(); if (!content || content.trim().length === 0) { showError("Clipboard is empty."); return; } pasteArea.value = content; reevaluateContentStatus(); } catch (err) { showError("Clipboard is empty or not accessible."); } } function reevaluateContentStatus() { clearError(); if (pasteArea.value.length > 0) { pastePlaceholder.style.display = 'none'; pasteSaveButtons.forEach(button => button.removeAttribute("disabled")); } else { pastePlaceholder.style.display = 'flex'; pasteSaveButtons.forEach(button => button.setAttribute("disabled", "disabled")); } } function showError(message) { pasteSaveButtons.forEach(button => button.classList.remove("btn-working")); pasteError.innerText = message; pasteError.style.display = 'block'; } function clearError() { pasteSaveButtons.forEach(button => button.classList.remove("btn-working")); pasteError.innerText = ''; pasteError.style.display = 'none'; } /* File handling */ async function handlePasteEvent(e) { if (e.clipboardData?.files?.length > 0) { e.preventDefault(); await loadFileContents(e.clipboardData.files[0]); } } /** * @param {Blob} file * @return {Promise} */ function readFile(file) { return new Promise((resolve, reject) => { let reader = new FileReader(); // noinspection JSCheckFunctionSignatures reader.onload = () => resolve(new Uint8Array(reader.result)); reader.onerror = e => reject(e); reader.readAsArrayBuffer(file); }); } async function loadFileContents(file) { if (file.size > 1024 * 1024 * 100) { showError(`File is too large.`); return; } let content = await readFile(file); if (file.name.endsWith('.gz')) { if (!isGzSupported()) { showError(`Gzip files are not supported in this browser.`); return; } content = await unpackGz(content); } if (content.includes(0)) { showError(`This file is not supported.`); return; } pasteArea.value = new TextDecoder().decode(content); reevaluateContentStatus(); } function selectLogFile() { let input = document.createElement('input'); input.type = 'file'; input.style.display = 'none'; document.body.appendChild(input); input.onchange = async () => { if (input.files.length) { await loadFileContents(input.files[0]); } } input.click(); document.body.removeChild(input); } /* Gzip compression */ function isGzSupported() { return (typeof CompressionStream !== 'undefined') && (typeof DecompressionStream !== 'undefined'); } /** * @param {string} raw * @returns {Promise} */ async function packGz(raw) { let data = new TextEncoder().encode(raw); let inputStream = new ReadableStream({ start: (controller) => { controller.enqueue(data); controller.close(); } }); const cs = new CompressionStream('gzip'); const compressedStream = inputStream.pipeThrough(cs); return new Uint8Array(await new Response(compressedStream).arrayBuffer()); } /** * @param {Uint8Array} data * @return {Promise} */ async function unpackGz(data) { let inputStream = new ReadableStream({ start: (controller) => { controller.enqueue(data); controller.close(); } }); const ds = new DecompressionStream('gzip'); const decompressedStream = inputStream.pipeThrough(ds); return new Uint8Array(await new Response(decompressedStream).arrayBuffer()); } function isDragEventValid(e) { if (!e.dataTransfer) { return false; } let types = Array.from(e.dataTransfer.types); if (types.includes('text/uri-list')) { return false; } return types.includes('Files') || types.includes('text/plain'); } /* Drag and drop */ const dropZone = document.getElementById('dropzone'); let windowDragCount = 0; let dropZoneDragCount = 0; window.addEventListener('dragover', e => e.preventDefault()); window.addEventListener('dragenter', e => { e.preventDefault(); if (isDragEventValid(e)) { updateWindowDragCount(1); } }); window.addEventListener('dragleave', e => { e.preventDefault(); if (isDragEventValid(e)) { updateWindowDragCount(-1); } }); window.addEventListener('drop', e => { e.preventDefault(); if (isDragEventValid(e)) { updateWindowDragCount(-1); } }); dropZone.addEventListener('dragenter', e => { e.preventDefault(); if (isDragEventValid(e)) { updateDropZoneDragCount(1); } }); dropZone.addEventListener('dragleave', e => { e.preventDefault(); if (isDragEventValid(e)) { updateDropZoneDragCount(-1); } }); dropZone.addEventListener('drop', async e => { e.preventDefault(); if (isDragEventValid(e)) { updateDropZoneDragCount(-1); } await handleDropEvent(e); }); function updateWindowDragCount(amount) { windowDragCount = Math.max(0, windowDragCount + amount); if (windowDragCount > 0) { dropZone.classList.add('window-dragover'); } else { dropZone.classList.remove('window-dragover'); } } function updateDropZoneDragCount(amount) { dropZoneDragCount = Math.max(0, dropZoneDragCount + amount); if (dropZoneDragCount > 0) { dropZone.classList.add('dragover'); } else { dropZone.classList.remove('dragover'); } } async function handleDropEvent(e) { console.log(e.dataTransfer?.types); let files = e.dataTransfer.files; if (files.length !== 1) { if (Array.from(e.dataTransfer.types).includes('text/plain')) { pasteArea.value = e.dataTransfer.getData('text/plain'); reevaluateContentStatus(); } return; } await loadFileContents(files[0]); }