'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(); }); 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() { if (!_decryptedPaste) return; const blob = new Blob([_decryptedPaste.content], { type: 'text/plain; charset=utf-8' }); const url = URL.createObjectURL(blob); const a = Object.assign(document.createElement('a'), { href: url, target: '_blank', rel: 'noopener noreferrer' }); document.body.appendChild(a); a.click(); document.body.removeChild(a); setTimeout(() => URL.revokeObjectURL(url), 10000); } 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); }