'use strict'; /** * paste_view.js — Logic for the paste view page. * Depends on: crypto.js (PasteCrypto), app.js (copyToClipboard) * * Reads the site name from so no Jinja injection * is needed in this external file. */ let _decryptedPaste = null; document.addEventListener('DOMContentLoaded', async () => { let rawPayload; try { const island = document.getElementById('encryptedPayload'); if (!island) return; // Not a paste view page rawPayload = JSON.parse(island.textContent); } catch (e) { showError('Bad Data', 'Could not read the paste payload.'); return; } // Detect format from the data itself. // Encrypted pastes: "base64url:base64url" string. // Plaintext pastes: a JSON object. const isEncrypted = typeof rawPayload === 'string' && /^[A-Za-z0-9_-]+:[A-Za-z0-9_-]+$/.test(rawPayload); if (isEncrypted) { const keyBase64 = window.location.hash.slice(1); if (!keyBase64) { showError('No Key', 'The decryption key is missing from the URL. ' + 'Use the full link including the # part.'); return; } try { const key = await PasteCrypto.importKey(keyBase64); const plaintext = await PasteCrypto.decrypt(rawPayload, key); _decryptedPaste = JSON.parse(plaintext); } catch (e) { showError('Decryption Failed', 'Wrong key or tampered data.'); return; } } else { // Plaintext paste — rawPayload is already the parsed JSON object. try { _decryptedPaste = (typeof rawPayload === 'object') ? rawPayload : JSON.parse(rawPayload); } catch (e) { showError('Bad Data', 'Could not parse paste data.'); return; } } renderPaste(_decryptedPaste); initPasteActions(); initLineNumbers(); initDeletion(); }); function initPasteActions() { const rawBtn = document.getElementById('rawBtn'); const copyBtn = document.getElementById('copyBtn'); const downloadBtn = document.getElementById('downloadBtn'); if (rawBtn) rawBtn.addEventListener('click', rawView); if (copyBtn) copyBtn.addEventListener('click', copyPaste); if (downloadBtn) downloadBtn.addEventListener('click', downloadPaste); } function initLineNumbers() { const toggle = document.getElementById('lineNoToggle'); const viewPre = document.getElementById('viewPre'); if (!toggle || !viewPre) return; // Load preference from localStorage (default to checked) const stored = localStorage.getItem('show_line_numbers'); const isEnabled = (stored === null) ? true : (stored === 'true'); toggle.checked = isEnabled; const updateLines = () => { const checked = toggle.checked; if (checked) { viewPre.classList.add('line-numbers'); } else { viewPre.classList.remove('line-numbers'); } localStorage.setItem('show_line_numbers', checked); // Re-highlight if a language is selected to force Prism to update the numbers span const code = document.getElementById('codeBlock'); if (code && (code.className.includes('language-') || viewPre.className.includes('language-'))) { // Prism's line-numbers plugin needs to clean up if turning off if (!checked) { const existing = viewPre.querySelector('.line-numbers-rows'); if (existing) existing.remove(); } Prism.highlightElement(code); } }; toggle.addEventListener('change', updateLines); // Initial state if (isEnabled) { viewPre.classList.add('line-numbers'); } } function renderPaste(paste) { const siteName = document.querySelector('meta[name="site-name"]')?.content || ''; const title = paste.title || 'Untitled'; document.title = title + (siteName ? ' \u2014 ' + siteName : ''); document.getElementById('navPasteTitle').textContent = title; const lang = paste.language || 'text'; const prismLangMap = { text: false, html: 'markup', xml: 'markup', docker: 'docker' }; const prismLang = (lang in prismLangMap) ? prismLangMap[lang] : lang; const codeBlock = document.getElementById('codeBlock'); const viewPre = document.getElementById('viewPre'); codeBlock.textContent = paste.content || ''; if (prismLang) { codeBlock.className = 'language-' + prismLang; viewPre.className = 'language-' + prismLang; Prism.highlightElement(codeBlock); } document.getElementById('viewFull').style.display = 'block'; } function showError(title, detail) { document.getElementById('errorTitle').textContent = title; document.getElementById('errorDetail').textContent = detail; document.getElementById('errorState').style.display = 'block'; } function rawView() { const key = window.location.hash; const url = window.location.href.split('?')[0].split('#')[0] + '/raw' + key; window.open(url, '_blank'); } async function copyPaste() { if (!_decryptedPaste) return; const ok = await copyToClipboard(_decryptedPaste.content); const btn = document.getElementById('copyBtn'); if (btn) { const t = btn.textContent; btn.textContent = ok ? 'Copied!' : 'Failed'; setTimeout(() => btn.textContent = t, 1500); } } function downloadPaste() { if (!_decryptedPaste) return; const { title = 'untitled', content = '', language = 'text' } = _decryptedPaste; const extMap = { javascript: '.js', typescript: '.ts', python: '.py', java: '.java', c: '.c', cpp: '.cpp', csharp: '.cs', html: '.html', css: '.css', scss: '.scss', sql: '.sql', json: '.json', yaml: '.yaml',xml: '.xml', bash: '.sh', powershell: '.ps1', php: '.php', ruby: '.rb', go: '.go', rust: '.rs', swift: '.swift',kotlin: '.kt', markdown:'.md', diff: '.diff', docker: '', nginx: '.conf', toml: '.toml', ini: '.ini', }; const filename = title.replace(/[^a-z0-9.\-]/gi, '_') + (extMap[language] ?? '.txt'); const blob = new Blob([content], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = Object.assign(document.createElement('a'), { href: url, download: filename }); document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } function initDeletion() { const pasteId = window.location.pathname.split('/').pop(); const token = localStorage.getItem('del_' + pasteId); const deleteBtn = document.getElementById('deleteBtn'); if (token && deleteBtn) { deleteBtn.style.display = 'inline-block'; deleteBtn.addEventListener('click', async () => { if (!confirm('Are you sure you want to delete this paste? This action cannot be undone.')) return; deleteBtn.disabled = true; deleteBtn.textContent = '...'; try { const resp = await fetch('/api/delete/' + pasteId, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token }) }); const result = await resp.json(); if (result.success) { localStorage.removeItem('del_' + pasteId); window.location.href = '/'; } else { alert('Error: ' + (result.error || 'Unknown error')); deleteBtn.disabled = false; deleteBtn.textContent = 'Delete'; } } catch (err) { console.error(err); alert('Failed to delete paste. Connection error.'); deleteBtn.disabled = false; deleteBtn.textContent = 'Delete'; } }); } }