'use strict'; /** * paste_create.js — Logic for the paste creation page. * Depends on: crypto.js (PasteCrypto), app.js (clearDraft, window.PBCFG) */ document.addEventListener('DOMContentLoaded', function () { const textarea = document.getElementById('content'); const submitBtn = document.getElementById('submitBtn'); const langSelect = document.getElementById('language'); // ── Load languages from API ────────────────────────────────────────────── fetch('/api/languages') .then(r => r.json()) .then(langs => { langSelect.innerHTML = ''; langs.forEach(l => { const o = document.createElement('option'); o.value = l.value; o.textContent = l.name; langSelect.appendChild(o); }); // Restore last-used language preference const saved = localStorage.getItem('preferred_language'); if (saved) langSelect.value = saved; }) .catch(() => {}); langSelect.addEventListener('change', () => localStorage.setItem('preferred_language', langSelect.value)); // ── Restore expiry preference ──────────────────────────────────────────── const expirySelect = document.getElementById('expires_in'); const savedExpiry = localStorage.getItem('preferred_expiry'); if (savedExpiry) expirySelect.value = savedExpiry; expirySelect.addEventListener('change', () => localStorage.setItem('preferred_expiry', expirySelect.value)); // ── Indent controls (Tabs / Spaces + size) ─────────────────────────────── const indentType = document.getElementById('indent_type'); const indentSize = document.getElementById('indent_size'); // Restore saved preferences const savedIndentType = localStorage.getItem('preferred_indent_type'); const savedIndentSize = localStorage.getItem('preferred_indent_size'); if (savedIndentType) indentType.value = savedIndentType; if (savedIndentSize) indentSize.value = savedIndentSize; // Hide size selector when Tabs is chosen (tabs have no width setting in the input) function syncIndentUI() { indentSize.style.display = indentType.value === 'tabs' ? 'none' : ''; } syncIndentUI(); indentType.addEventListener('change', () => { localStorage.setItem('preferred_indent_type', indentType.value); syncIndentUI(); }); indentSize.addEventListener('change', () => localStorage.setItem('preferred_indent_size', indentSize.value)); // ── Tab key handler ────────────────────────────────────────────────────── textarea.addEventListener('keydown', function (e) { if (e.key !== 'Tab') return; e.preventDefault(); const useTab = indentType.value === 'tabs'; const indent = useTab ? '\t' : ' '.repeat(parseInt(indentSize.value, 10)); const start = this.selectionStart; const end = this.selectionEnd; const value = this.value; if (start === end) { // No selection — insert indent at cursor this.value = value.slice(0, start) + indent + value.slice(end); this.selectionStart = this.selectionEnd = start + indent.length; } else { // Multi-line selection — indent / unindent each line const lineStart = value.lastIndexOf('\n', start - 1) + 1; const selectedText = value.slice(lineStart, end); const lines = selectedText.split('\n'); let newText; if (e.shiftKey) { // Unindent: remove one level of indent from the start of each line newText = lines.map(line => { if (useTab) return line.startsWith('\t') ? line.slice(1) : line; const spaces = parseInt(indentSize.value, 10); return line.startsWith(' '.repeat(spaces)) ? line.slice(spaces) : line.replace(/^ +/, m => m.slice(Math.min(m.length, spaces))); }).join('\n'); } else { // Indent: prepend indent to each line newText = lines.map(line => indent + line).join('\n'); } const removed = selectedText.length - newText.length; // negative when indenting this.value = value.slice(0, lineStart) + newText + value.slice(end); this.selectionStart = lineStart; this.selectionEnd = lineStart + newText.length; } }); // ── Enter key: auto-indent to match previous line ──────────────────────── textarea.addEventListener('keydown', function (e) { if (e.key !== 'Enter') return; e.preventDefault(); const start = this.selectionStart; const value = this.value; // Find the beginning of the current line const lineStart = value.lastIndexOf('\n', start - 1) + 1; const currentLine = value.slice(lineStart, start); // Grab the leading whitespace (spaces or tabs) of the current line const leadingWhitespace = currentLine.match(/^(\s*)/)[1]; const insert = '\n' + leadingWhitespace; this.value = value.slice(0, start) + insert + value.slice(this.selectionEnd); this.selectionStart = this.selectionEnd = start + insert.length; }); // ── Ctrl/Cmd+S shortcut ────────────────────────────────────────────────── document.addEventListener('keydown', e => { if ((e.ctrlKey || e.metaKey) && e.key === 's') { e.preventDefault(); submitPaste(); } }); submitBtn.addEventListener('click', submitPaste); // ── Clear Draft ────────────────────────────────────────────────────────── const clearBtn = document.getElementById('clearBtn'); if (clearBtn) { clearBtn.addEventListener('click', () => { console.log('[Clear] Manual clear requested.'); // Clear the editor and title textarea.value = ''; const title = document.getElementById('title'); if (title) title.value = ''; // Wipe the localStorage draft via the global helper in app.js if (typeof window.clearDraft === 'function') { window.clearDraft(); } else { localStorage.removeItem('paste_draft'); } textarea.focus(); console.log('[Clear] Draft wiped.'); }); } // ── Submit ─────────────────────────────────────────────────────────────── async function submitPaste() { const content = textarea.value; const title = document.getElementById('title').value || 'Untitled'; const language = langSelect.value; const expires_in = expirySelect.value; if (!content.trim()) { textarea.focus(); return; } // Read E2E flag from the already-loaded config (fetched by app.js at startup). // By the time the user clicks Save, window.PBCFG is guaranteed to be populated. const E2E = window.PBCFG?.features?.encrypt_pastes ?? true; submitBtn.disabled = true; submitBtn.textContent = '…'; try { let postBody, keyBase64 = null; if (E2E) { const keyLen = window.PBCFG?.pastes?.encryption_key_bits ?? 128; const key = await PasteCrypto.generateKey(keyLen); keyBase64 = await PasteCrypto.exportKey(key); const plain = JSON.stringify({ title, content, language }); postBody = { encrypted_data: await PasteCrypto.encrypt(plain, key), expires_in }; } else { postBody = { title, content, language, expires_in }; } const resp = await fetch('/create', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(postBody), }); const result = await resp.json(); if (result.error) { alert('Error: ' + result.error); submitBtn.disabled = false; submitBtn.textContent = 'Save'; } else { if (typeof window.clearDraft === 'function') { window.clearDraft(); } if (result.deletion_token) { localStorage.setItem('del_' + result.paste_id, result.deletion_token); } window.location.href = result.url + (keyBase64 ? '#' + keyBase64 : ''); } } catch (err) { console.error(err); alert('Failed to create paste. Try again.'); submitBtn.disabled = false; submitBtn.textContent = 'Save'; } } });