Updates peer.paste

This commit is contained in:
Raven Scott
2026-05-28 00:03:52 -04:00
parent 60d17a7b91
commit aac69de4cf
2 changed files with 113 additions and 2 deletions
+100
View File
@@ -664,6 +664,11 @@ function renderPastePage(paste) {
input{flex:1;background:#020617;color:#e2e8f0;border:1px solid #334155;border-radius:8px;padding:.5rem} input{flex:1;background:#020617;color:#e2e8f0;border:1px solid #334155;border-radius:8px;padding:.5rem}
button{background:#2563eb;color:#fff;border:0;border-radius:8px;padding:.5rem .85rem;cursor:pointer} button{background:#2563eb;color:#fff;border:0;border-radius:8px;padding:.5rem .85rem;cursor:pointer}
.preview{white-space:pre-wrap;word-break:break-word} .preview{white-space:pre-wrap;word-break:break-word}
.actions{display:flex;gap:.5rem;margin:.75rem 0 0}
.btn{background:#1d4ed8;color:#fff;border:0;border-radius:8px;padding:.45rem .8rem;cursor:pointer;transition:transform 120ms ease, background-color 120ms ease}
.btn:hover{transform:translateY(-1px);background:#2563eb}
.btn.ok{background:#16a34a;animation:pulse .55s ease}
@keyframes pulse{0%{transform:scale(1)}35%{transform:scale(1.04)}100%{transform:scale(1)}}
</style> </style>
</head> </head>
<body> <body>
@@ -674,6 +679,10 @@ function renderPastePage(paste) {
<input id="manual-key" placeholder="Paste key (base64url) if missing from URL" /> <input id="manual-key" placeholder="Paste key (base64url) if missing from URL" />
<button id="decrypt-btn" type="button">Decrypt</button> <button id="decrypt-btn" type="button">Decrypt</button>
</div> </div>
<div class="actions">
<button id="copy-btn" class="btn" type="button">Copy</button>
<button id="download-btn" class="btn" type="button">Download</button>
</div>
<div class="card"><div id="out" class="preview">Decrypting...</div></div> <div class="card"><div id="out" class="preview">Decrypting...</div></div>
${attachments.length ? `<p class="meta">Attachments: ${attachments.map((a) => a.name).join(', ')}</p>` : ''} ${attachments.length ? `<p class="meta">Attachments: ${attachments.map((a) => a.name).join(', ')}</p>` : ''}
<p><a href="/">Create another paste</a></p> <p><a href="/">Create another paste</a></p>
@@ -683,6 +692,7 @@ function renderPastePage(paste) {
<script> <script>
const payload = ${JSON.stringify(encryptedPayload)}; const payload = ${JSON.stringify(encryptedPayload)};
const pasteFormat = ${JSON.stringify(normalizeFormat(safePaste.format || 'text'))}; const pasteFormat = ${JSON.stringify(normalizeFormat(safePaste.format || 'text'))};
let lastPlaintext = '';
function b64uToBytes(s) { function b64uToBytes(s) {
const p = s.replace(/-/g, '+').replace(/_/g, '/'); const p = s.replace(/-/g, '+').replace(/_/g, '/');
const pad = p.length % 4 ? '='.repeat(4 - (p.length % 4)) : ''; const pad = p.length % 4 ? '='.repeat(4 - (p.length % 4)) : '';
@@ -710,6 +720,7 @@ function renderPastePage(paste) {
const key = await crypto.subtle.importKey('raw', keyBytes, { name: 'AES-GCM' }, false, ['decrypt']); const key = await crypto.subtle.importKey('raw', keyBytes, { name: 'AES-GCM' }, false, ['decrypt']);
const plain = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ciphertext); const plain = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ciphertext);
const text = new TextDecoder().decode(plain); const text = new TextDecoder().decode(plain);
lastPlaintext = text;
if (pasteFormat === 'markdown' && window.marked) { if (pasteFormat === 'markdown' && window.marked) {
const sanitize = (html) => String(html) const sanitize = (html) => String(html)
.replace(/<script[\\s\\S]*?>[\\s\\S]*?<\\/script>/gi, '') .replace(/<script[\\s\\S]*?>[\\s\\S]*?<\\/script>/gi, '')
@@ -739,14 +750,44 @@ function renderPastePage(paste) {
} }
} }
} catch (err) { } catch (err) {
lastPlaintext = '';
out.textContent = 'Failed to decrypt paste: ' + (err && err.message ? err.message : 'unknown error'); out.textContent = 'Failed to decrypt paste: ' + (err && err.message ? err.message : 'unknown error');
} }
} }
async function copyCurrent() {
const btn = document.getElementById('copy-btn');
if (!lastPlaintext) return;
try {
await navigator.clipboard.writeText(lastPlaintext);
btn.textContent = 'Copied';
btn.classList.add('ok');
setTimeout(() => { btn.textContent = 'Copy'; btn.classList.remove('ok'); }, 900);
} catch {}
}
function downloadCurrent() {
const btn = document.getElementById('download-btn');
if (!lastPlaintext) return;
const ext = pasteFormat === 'markdown' ? 'md' : 'txt';
const blob = new Blob([lastPlaintext], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'paste-${paste.id}.' + ext;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
btn.textContent = 'Downloaded';
btn.classList.add('ok');
setTimeout(() => { btn.textContent = 'Download'; btn.classList.remove('ok'); }, 900);
}
function initDecryptView() { function initDecryptView() {
const btn = document.getElementById('decrypt-btn'); const btn = document.getElementById('decrypt-btn');
const input = document.getElementById('manual-key'); const input = document.getElementById('manual-key');
const keyRow = document.getElementById('key-row'); const keyRow = document.getElementById('key-row');
btn.addEventListener('click', () => decryptAndRender(input.value)); btn.addEventListener('click', () => decryptAndRender(input.value));
document.getElementById('copy-btn').addEventListener('click', copyCurrent);
document.getElementById('download-btn').addEventListener('click', downloadCurrent);
input.addEventListener('keydown', (e) => { input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') decryptAndRender(input.value); if (e.key === 'Enter') decryptAndRender(input.value);
}); });
@@ -788,12 +829,21 @@ function renderPastePage(paste) {
pre{white-space:pre-wrap;word-break:break-word;background:#020617;border:1px solid #1e293b;border-radius:8px;padding:1rem} pre{white-space:pre-wrap;word-break:break-word;background:#020617;border:1px solid #1e293b;border-radius:8px;padding:1rem}
a{color:#60a5fa} a{color:#60a5fa}
.meta{font-size:.9rem;color:#94a3b8;margin:.5rem 0 1rem} .meta{font-size:.9rem;color:#94a3b8;margin:.5rem 0 1rem}
.actions{display:flex;gap:.5rem;margin:0 0 .75rem}
.btn{background:#1d4ed8;color:#fff;border:0;border-radius:8px;padding:.45rem .8rem;cursor:pointer;transition:transform 120ms ease, background-color 120ms ease}
.btn:hover{transform:translateY(-1px);background:#2563eb}
.btn.ok{background:#16a34a;animation:pulse .55s ease}
@keyframes pulse{0%{transform:scale(1)}35%{transform:scale(1.04)}100%{transform:scale(1)}}
</style> </style>
</head> </head>
<body> <body>
<div class="wrap"> <div class="wrap">
<h1>paste ${paste.id}</h1> <h1>paste ${paste.id}</h1>
<div class="meta">Language: ${language} | Format: ${safePaste.format} | Expires in: ${hours}h ${minutes}m</div> <div class="meta">Language: ${language} | Format: ${safePaste.format} | Expires in: ${hours}h ${minutes}m</div>
<div class="actions">
<button id="copy-btn" class="btn" type="button">Copy</button>
<button id="download-btn" class="btn" type="button">Download</button>
</div>
<div class="card">${safePaste.format === 'markdown' <div class="card">${safePaste.format === 'markdown'
? `<div id="md-render">${paste.content.replace(/[<>&]/g, (m) => ({ '<': '&lt;', '>': '&gt;', '&': '&amp;' }[m]))}</div>` ? `<div id="md-render">${paste.content.replace(/[<>&]/g, (m) => ({ '<': '&lt;', '>': '&gt;', '&': '&amp;' }[m]))}</div>`
: `<pre><code id="code-render">${paste.content.replace(/[<>&]/g, (m) => ({ '<': '&lt;', '>': '&gt;', '&': '&amp;' }[m]))}</code></pre>`}</div> : `<pre><code id="code-render">${paste.content.replace(/[<>&]/g, (m) => ({ '<': '&lt;', '>': '&gt;', '&': '&amp;' }[m]))}</code></pre>`}</div>
@@ -804,6 +854,31 @@ function renderPastePage(paste) {
? `<script src="/api/vendor/marked.min.js"></script> ? `<script src="/api/vendor/marked.min.js"></script>
<script src="/api/vendor/highlight.min.js"></script> <script src="/api/vendor/highlight.min.js"></script>
<script> <script>
const sourceText = ${JSON.stringify(paste.content)};
const copyBtn = document.getElementById('copy-btn');
const downloadBtn = document.getElementById('download-btn');
copyBtn.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText(sourceText);
copyBtn.textContent = 'Copied';
copyBtn.classList.add('ok');
setTimeout(() => { copyBtn.textContent = 'Copy'; copyBtn.classList.remove('ok'); }, 900);
} catch {}
});
downloadBtn.addEventListener('click', () => {
const blob = new Blob([sourceText], { type: 'text/markdown;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'paste-${paste.id}.md';
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
downloadBtn.textContent = 'Downloaded';
downloadBtn.classList.add('ok');
setTimeout(() => { downloadBtn.textContent = 'Download'; downloadBtn.classList.remove('ok'); }, 900);
});
const el = document.getElementById('md-render'); const el = document.getElementById('md-render');
const src = el.textContent || ''; const src = el.textContent || '';
const sanitize = (html) => String(html) const sanitize = (html) => String(html)
@@ -820,6 +895,31 @@ function renderPastePage(paste) {
</script>` </script>`
: `<script src="/api/vendor/highlight.min.js"></script> : `<script src="/api/vendor/highlight.min.js"></script>
<script> <script>
const sourceText = ${JSON.stringify(paste.content)};
const copyBtn = document.getElementById('copy-btn');
const downloadBtn = document.getElementById('download-btn');
copyBtn.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText(sourceText);
copyBtn.textContent = 'Copied';
copyBtn.classList.add('ok');
setTimeout(() => { copyBtn.textContent = 'Copy'; copyBtn.classList.remove('ok'); }, 900);
} catch {}
});
downloadBtn.addEventListener('click', () => {
const blob = new Blob([sourceText], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'paste-${paste.id}.txt';
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
downloadBtn.textContent = 'Downloaded';
downloadBtn.classList.add('ok');
setTimeout(() => { downloadBtn.textContent = 'Download'; downloadBtn.classList.remove('ok'); }, 900);
});
const block = document.getElementById('code-render'); const block = document.getElementById('code-render');
if (block && window.hljs) { if (block && window.hljs) {
try { try {
+13 -2
View File
@@ -30,6 +30,17 @@ function setCreateState(message, isError = false) {
} }
} }
function flashLinkFeedback(el, okText, fallbackText = null) {
if (!el) return;
const original = el.textContent;
el.textContent = okText;
el.style.opacity = '0.78';
setTimeout(() => {
el.textContent = fallbackText || original;
el.style.opacity = '';
}, 900);
}
function bytesToB64u(bytes) { function bytesToB64u(bytes) {
let bin = ''; let bin = '';
for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]); for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
@@ -386,9 +397,9 @@ document.getElementById('list').addEventListener('click', async (e) => {
try { try {
const full = `${window.location.origin}${link.getAttribute('data-copy-url')}`; const full = `${window.location.origin}${link.getAttribute('data-copy-url')}`;
await navigator.clipboard.writeText(full); await navigator.clipboard.writeText(full);
setCreateState('Recent paste link copied.'); flashLinkFeedback(link, 'Copied');
} catch (err) { } catch (err) {
setCreateState('Could not copy recent paste link.', true); flashLinkFeedback(link, 'Copy failed', 'Copy link');
} }
}); });