let pendingKey = null; let pendingData = null; const FONT_STACK_SANS = "'Vazirmatn Local', 'Vazirmatn', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif"; const FONT_STACK_MONO = "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace"; const MAX_CUSTOM_EXPIRY_SECONDS = 31536000; const MIN_CUSTOM_EXPIRY_SECONDS = 300; function showError(msg) { document.getElementById('errorDisplay').style.display = 'flex'; document.getElementById('errorMessage').textContent = msg; } function clearError() { document.getElementById('errorDisplay').style.display = 'none'; } function isRTL(text) { const rtlRegex = /[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\u0590-\u05FF\uFB50-\uFDFF\uFE70-\uFEFF]/; return rtlRegex.test(text || ''); } function applyDirectionalStyles(element, text, rtlFont, ltrFont) { if (!element) return; if (isRTL(text)) { element.style.direction = 'rtl'; element.style.fontFamily = rtlFont; } else { element.style.direction = 'ltr'; element.style.fontFamily = ltrFont; } } function updateTextareaDirection(e) { applyDirectionalStyles(e.target, e.target.value, FONT_STACK_SANS, FONT_STACK_MONO); } const contentTextarea = document.getElementById('content'); if (contentTextarea) { contentTextarea.addEventListener('input', updateTextareaDirection); } const subjectInput = document.getElementById('subject'); if (subjectInput) { subjectInput.addEventListener('input', function(e) { applyDirectionalStyles(e.target, e.target.value, FONT_STACK_SANS, FONT_STACK_SANS); }); } const passwordInput = document.getElementById('passwordInput'); if (passwordInput) { passwordInput.addEventListener('input', updatePasswordStrength); } const expiresInSelect = document.getElementById('expiresIn'); if (expiresInSelect) { expiresInSelect.addEventListener('change', toggleCustomExpiry); } const editorToolbar = document.querySelector('.editor-toolbar'); if (editorToolbar && contentTextarea) { editorToolbar.addEventListener('click', handleToolbarClick); } function handleToolbarClick(event) { const button = event.target.closest('[data-action]'); if (!button || !contentTextarea) return; const action = button.dataset.action; applyToolbarAction(action); } function applyToolbarAction(action) { switch (action) { case 'bold': wrapSelection('**', '**', 'bold text'); break; case 'italic': wrapSelection('*', '*', 'italic text'); break; case 'strike': wrapSelection('~~', '~~', 'strikethrough'); break; case 'heading': transformSelectedLines(function(line) { return line ? `# ${line.replace(/^#{1,6}\s+/, '')}` : '# Heading'; }, { fallback: '# Heading' }); break; case 'quote': transformSelectedLines(function(line) { return line ? `> ${line.replace(/^>\s?/, '')}` : '> Quote'; }, { fallback: '> Quote' }); break; case 'bullet': transformSelectedLines(function(line) { return line ? `- ${line.replace(/^[-*+]\s+/, '')}` : '- List item'; }, { fallback: '- List item' }); break; case 'numbered': transformSelectedLines(function(line, index) { return `${index + 1}. ${line.replace(/^\d+\.\s+/, '') || 'List item'}`; }, { fallback: '1. List item' }); break; case 'link': { const selectedText = getSelectedText(contentTextarea) || 'link text'; const url = window.prompt('Enter the URL for this link:', 'https://'); if (url === null) { focusEditor(); return; } const trimmedUrl = url.trim() || 'https://'; replaceSelection(`[${selectedText}](${trimmedUrl})`, { selectInserted: false }); break; } case 'code': wrapSelection('`', '`', 'code'); break; case 'codeblock': wrapSelection('```\n', '\n```', 'your code here'); break; default: return; } updateTextareaDirection({ target: contentTextarea }); } function getSelectedText(textarea) { return textarea.value.slice(textarea.selectionStart, textarea.selectionEnd); } function replaceSelection(replacement, options) { if (!contentTextarea) return; const settings = Object.assign({ selectInserted: true, selectionStartOffset: 0, selectionEndOffset: 0 }, options || {}); const start = contentTextarea.selectionStart; const end = contentTextarea.selectionEnd; const current = contentTextarea.value; contentTextarea.value = current.slice(0, start) + replacement + current.slice(end); if (settings.selectInserted) { contentTextarea.setSelectionRange(start + settings.selectionStartOffset, start + replacement.length - settings.selectionEndOffset); } else { const caret = start + replacement.length; contentTextarea.setSelectionRange(caret, caret); } focusEditor(); } function wrapSelection(prefix, suffix, placeholder) { if (!contentTextarea) return; const selected = getSelectedText(contentTextarea); const inner = selected || placeholder; const replacement = `${prefix}${inner}${suffix}`; const selectionStartOffset = prefix.length; const selectionEndOffset = suffix.length; replaceSelection(replacement, { selectInserted: true, selectionStartOffset, selectionEndOffset }); } function transformSelectedLines(transformer, options) { if (!contentTextarea) return; const settings = Object.assign({ fallback: '' }, options || {}); const value = contentTextarea.value; const start = contentTextarea.selectionStart; const end = contentTextarea.selectionEnd; const lineStart = value.lastIndexOf('\n', Math.max(0, start - 1)) + 1; const nextNewlineIndex = value.indexOf('\n', end); const lineEnd = nextNewlineIndex === -1 ? value.length : nextNewlineIndex; const selectedBlock = value.slice(lineStart, lineEnd); const lines = selectedBlock ? selectedBlock.split('\n') : [settings.fallback]; const transformed = lines.map(function(line, index) { return transformer(line, index); }).join('\n'); contentTextarea.value = value.slice(0, lineStart) + transformed + value.slice(lineEnd); contentTextarea.setSelectionRange(lineStart, lineStart + transformed.length); focusEditor(); } function focusEditor() { if (!contentTextarea) return; contentTextarea.focus(); } function togglePassword() { const enabled = document.getElementById('enablePassword').checked; const wrapper = document.getElementById('passwordWrapper'); wrapper.classList.toggle('show', enabled); if (!enabled) { document.getElementById('passwordInput').value = ''; resetPasswordStrength(); } else { updatePasswordStrength(); } } function toggleCustomExpiry() { const isCustom = document.getElementById('expiresIn').value === 'custom'; const wrapper = document.getElementById('customExpiryWrapper'); wrapper.classList.toggle('show', isCustom); if (isCustom) { setCustomExpiryBounds(); const input = document.getElementById('customExpiry'); if (!input.value) { const defaultDate = new Date(Date.now() + 24 * 60 * 60 * 1000); input.value = toLocalDateTimeValue(defaultDate); } } } function setCustomExpiryBounds() { const input = document.getElementById('customExpiry'); if (!input) return; input.min = toLocalDateTimeValue(new Date(Date.now() + MIN_CUSTOM_EXPIRY_SECONDS * 1000)); input.max = toLocalDateTimeValue(new Date(Date.now() + MAX_CUSTOM_EXPIRY_SECONDS * 1000)); } function toLocalDateTimeValue(date) { const offset = date.getTimezoneOffset(); const local = new Date(date.getTime() - offset * 60000); return local.toISOString().slice(0, 16); } const Base64 = { encode(buf) { const bytes = new Uint8Array(buf); let bin = ''; for (const b of bytes) bin += String.fromCharCode(b); return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); }, decode(str) { str = str.replace(/-/g, '+').replace(/_/g, '/'); while (str.length % 4) str += '='; const bin = atob(str); const bytes = new Uint8Array(bin.length); for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i); return bytes; } }; async function deriveKeyFromPassword(password, salt) { const encoder = new TextEncoder(); const keyMaterial = await crypto.subtle.importKey( 'raw', encoder.encode(password), { name: 'PBKDF2' }, false, ['deriveKey'] ); return crypto.subtle.deriveKey( { name: 'PBKDF2', salt, iterations: 100000, hash: 'SHA-256' }, keyMaterial, { name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt'] ); } function genId() { const arr = new Uint8Array(12); crypto.getRandomValues(arr); return Base64.encode(arr); } function buildEncryptedPayload(subject, content) { return JSON.stringify({ subject: subject || '', content: content }); } function parseDecryptedPayload(text) { try { const parsed = JSON.parse(text); if (parsed && typeof parsed === 'object' && typeof parsed.content === 'string') { return { subject: typeof parsed.subject === 'string' ? parsed.subject : '', content: parsed.content }; } } catch (error) { // Backward compatibility with old plain-text payloads. } return { subject: '', content: text }; } function resetCreateForm() { document.getElementById('content').value = ''; document.getElementById('subject').value = ''; document.getElementById('passwordInput').value = ''; document.getElementById('enablePassword').checked = false; document.getElementById('burnAfterRead').checked = false; document.getElementById('expiresIn').value = '86400'; document.getElementById('customExpiry').value = ''; document.getElementById('customExpiryWrapper').classList.remove('show'); document.getElementById('passwordWrapper').classList.remove('show'); resetPasswordStrength(); applyDirectionalStyles(document.getElementById('content'), '', FONT_STACK_SANS, FONT_STACK_MONO); applyDirectionalStyles(document.getElementById('subject'), '', FONT_STACK_SANS, FONT_STACK_SANS); setCustomExpiryBounds(); } function escapeHtml(text) { return (text || '') .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function escapeAttribute(text) { return escapeHtml(text).replace(/`/g, '`'); } function sanitizeUrl(url) { const trimmed = (url || '').trim(); if (/^(https?:\/\/|mailto:)/i.test(trimmed)) { return trimmed; } return null; } function renderInlineMarkdown(text) { if (!text) return ''; const placeholders = []; let work = text.replace(/\r\n/g, '\n'); work = work.replace(/`([^`\n]+)`/g, function(_, code) { const token = `@@MDTOKEN${placeholders.length}@@`; placeholders.push({ token, html: `${escapeHtml(code)}` }); return token; }); work = work.replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"([^"]+)")?\)/g, function(_, label, url, title) { const safeUrl = sanitizeUrl(url); if (!safeUrl) { return label; } const attrs = [`href="${escapeAttribute(safeUrl)}"`, 'target="_blank"', 'rel="noopener noreferrer"']; if (title) { attrs.push(`title="${escapeAttribute(title)}"`); } const token = `@@MDTOKEN${placeholders.length}@@`; placeholders.push({ token, html: `${escapeHtml(label)}` }); return token; }); let html = escapeHtml(work); html = html.replace(/\*\*([^*]+)\*\*/g, '$1'); html = html.replace(/__([^_]+)__/g, '$1'); html = html.replace(/(^|[^*])\*([^*\n]+)\*(?!\*)/g, '$1$2'); html = html.replace(/(^|[^_])_([^_\n]+)_(?!_)/g, '$1$2'); html = html.replace(/~~([^~]+)~~/g, '$1'); placeholders.forEach(function(entry) { html = html.replace(entry.token, entry.html); }); return html; } function renderMarkdown(content) { const normalized = (content || '').replace(/\r\n/g, '\n'); const blocks = []; const lines = normalized.split('\n'); let i = 0; while (i < lines.length) { if (!lines[i].trim()) { i += 1; continue; } if (lines[i].startsWith('```')) { const lang = lines[i].slice(3).trim(); i += 1; const codeLines = []; while (i < lines.length && !lines[i].startsWith('```')) { codeLines.push(lines[i]); i += 1; } if (i < lines.length && lines[i].startsWith('```')) { i += 1; } const langAttr = lang ? ` data-lang="${escapeAttribute(lang)}"` : ''; blocks.push(`
${escapeHtml(codeLines.join('\n'))}
`); continue; } if (/^#{1,6}\s+/.test(lines[i])) { const match = lines[i].match(/^(#{1,6})\s+(.*)$/); const level = match[1].length; blocks.push(`${renderInlineMarkdown(match[2].trim())}`); i += 1; continue; } if (/^>\s?/.test(lines[i])) { const quoteLines = []; while (i < lines.length && /^>\s?/.test(lines[i])) { quoteLines.push(lines[i].replace(/^>\s?/, '')); i += 1; } blocks.push(`
${quoteLines.map(line => renderInlineMarkdown(line)).join('
')}
`); continue; } if (/^[-*+]\s+/.test(lines[i])) { const items = []; while (i < lines.length && /^[-*+]\s+/.test(lines[i])) { items.push(`
  • ${renderInlineMarkdown(lines[i].replace(/^[-*+]\s+/, ''))}
  • `); i += 1; } blocks.push(``); continue; } if (/^\d+\.\s+/.test(lines[i])) { const items = []; while (i < lines.length && /^\d+\.\s+/.test(lines[i])) { items.push(`
  • ${renderInlineMarkdown(lines[i].replace(/^\d+\.\s+/, ''))}
  • `); i += 1; } blocks.push(`
      ${items.join('')}
    `); continue; } const paragraphLines = []; while ( i < lines.length && lines[i].trim() && !/^#{1,6}\s+/.test(lines[i]) && !/^>\s?/.test(lines[i]) && !/^[-*+]\s+/.test(lines[i]) && !/^\d+\.\s+/.test(lines[i]) && !lines[i].startsWith('```') ) { paragraphLines.push(lines[i]); i += 1; } blocks.push(`

    ${paragraphLines.map(line => renderInlineMarkdown(line)).join('
    ')}

    `); } return blocks.join(''); } function renderContent(content) { const contentBox = document.getElementById('decryptedContent'); contentBox.dataset.rawContent = content || ''; contentBox.innerHTML = renderMarkdown(content || ''); } function showDecryptedContent(payload, burnAfterRead) { const subjectWrapper = document.getElementById('decryptedSubjectWrapper'); const subjectBox = document.getElementById('decryptedSubject'); const contentBox = document.getElementById('decryptedContent'); document.getElementById('passwordPrompt').classList.remove('show'); document.getElementById('decryptView').classList.add('show'); document.getElementById('createView').style.display = 'none'; if (burnAfterRead) { document.getElementById('burnNotice').style.display = 'flex'; } else { document.getElementById('burnNotice').style.display = 'none'; } if (payload.subject && payload.subject.trim()) { subjectWrapper.style.display = 'block'; subjectBox.textContent = payload.subject; applyDirectionalStyles(subjectBox, payload.subject, FONT_STACK_SANS, FONT_STACK_SANS); document.title = `${payload.subject} - Secure Pastebin`; } else { subjectWrapper.style.display = 'none'; subjectBox.textContent = ''; document.title = 'Secure Pastebin - End-to-End Encrypted Message Sharing'; } renderContent(payload.content); applyDirectionalStyles(contentBox, payload.content, FONT_STACK_SANS, FONT_STACK_MONO); } function getExpiryPayload() { const selected = document.getElementById('expiresIn').value; if (selected !== 'custom') { const expiresIn = parseInt(selected, 10); return { expiresIn, expiresAt: null, displayDate: new Date(Date.now() + expiresIn * 1000) }; } const customValue = document.getElementById('customExpiry').value; if (!customValue) { throw new Error('Please choose a custom expiration date and time'); } const customDate = new Date(customValue); if (Number.isNaN(customDate.getTime())) { throw new Error('Custom expiration date is invalid'); } const expiresAt = Math.floor(customDate.getTime() / 1000); const deltaSeconds = expiresAt - Math.floor(Date.now() / 1000); if (deltaSeconds < MIN_CUSTOM_EXPIRY_SECONDS) { throw new Error('Custom expiration must be at least 5 minutes from now'); } if (deltaSeconds > MAX_CUSTOM_EXPIRY_SECONDS) { throw new Error('Custom expiration cannot be more than 365 days from now'); } return { expiresIn: deltaSeconds, expiresAt, displayDate: customDate }; } function buildFullShareUrl(id, keyData, hasPassword) { const suffix = hasPassword ? `${keyData}:pwd` : keyData; return `${location.origin}/p/${encodeURIComponent(id)}#${suffix}`; } function buildShortShareUrl(id, keyData, hasPassword) { const suffix = hasPassword ? `${keyData}:pwd` : keyData; return `${location.origin}/#${encodeURIComponent(id)}:${suffix}`; } async function createPaste() { clearError(); const subject = document.getElementById('subject').value.trim(); const content = document.getElementById('content').value.trim(); if (!content) return showError('Please enter content to encrypt'); const hasPassword = document.getElementById('enablePassword').checked; const password = document.getElementById('passwordInput').value; if (hasPassword && password.length < 4) { return showError('Password must be at least 4 characters'); } const btn = document.getElementById('createBtn'); const btnText = document.getElementById('btnText'); btn.disabled = true; btnText.innerHTML = ' Encrypting...'; try { const expiry = getExpiryPayload(); let key; let keyToExport; if (hasPassword) { const salt = crypto.getRandomValues(new Uint8Array(16)); key = await deriveKeyFromPassword(password, salt); keyToExport = salt; } else { key = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt']); keyToExport = await crypto.subtle.exportKey('raw', key); } const iv = crypto.getRandomValues(new Uint8Array(12)); const encrypted = await crypto.subtle.encrypt( { name: 'AES-GCM', iv }, key, new TextEncoder().encode(buildEncryptedPayload(subject, content)) ); const id = genId(); const payload = { iv: Array.from(iv), data: Array.from(new Uint8Array(encrypted)) }; const res = await fetch('/api/pastes', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id, encryptedData: payload, expiresIn: expiry.expiresIn, customExpiresAt: expiry.expiresAt, burnAfterRead: document.getElementById('burnAfterRead').checked, hasPassword: hasPassword }) }); const result = await res.json(); if (!res.ok) throw new Error(result.error || 'Server error'); const keyFragment = Base64.encode(keyToExport); const fullShareUrl = buildFullShareUrl(id, keyFragment, hasPassword); const shortShareUrl = buildShortShareUrl(id, keyFragment, hasPassword); document.getElementById('shareUrl').value = fullShareUrl; document.getElementById('shareUrl').dataset.shortUrl = shortShareUrl; document.getElementById('expiryTime').textContent = expiry.displayDate.toLocaleString(); document.getElementById('passwordNotice').style.display = hasPassword ? 'flex' : 'none'; document.getElementById('resultBox').classList.add('show'); document.getElementById('resultBox').scrollIntoView({ behavior: 'smooth', block: 'start' }); resetCreateForm(); } catch (err) { showError(err.message); } finally { btn.disabled = false; btnText.textContent = '🔐 Encrypt & Save'; } } function parseLocationForPaste() { const hash = location.hash.slice(1); const pathMatch = location.pathname.match(/^\/p\/([^/]+)\/?$/); if (pathMatch && hash) { const id = decodeURIComponent(pathMatch[1]); const isPasswordProtected = hash.endsWith(':pwd'); const keyData = isPasswordProtected ? hash.slice(0, -4) : hash; if (id && keyData) { return { id, keyData, isPasswordProtected }; } } if (hash.includes(':')) { const parts = hash.split(':'); const id = parts[0]; const keyData = parts[1]; const isPasswordProtected = parts[2] === 'pwd'; if (id && keyData) { return { id, keyData, isPasswordProtected }; } } return null; } async function decryptPaste() { const pasteRef = parseLocationForPaste(); if (!pasteRef) return; try { const res = await fetch(`/api/get/${encodeURIComponent(pasteRef.id)}`); const data = await res.json(); if (!res.ok) throw new Error(data.error); if (data.hasPassword || pasteRef.isPasswordProtected) { pendingKey = pasteRef.keyData; pendingData = data; document.getElementById('createView').style.display = 'none'; document.getElementById('passwordPrompt').classList.add('show'); document.getElementById('passwordPrompt').scrollIntoView({ behavior: 'smooth', block: 'start' }); return; } await performDecryption(pasteRef.keyData, data); } catch (err) { showError('Failed: ' + err.message); setTimeout(() => { location.href = '/'; }, 3000); } } async function decryptWithPassword() { const password = document.getElementById('decryptPassword').value; if (!password) return; const btn = document.querySelector('#passwordPrompt .btn'); const btnText = document.getElementById('decryptBtnText'); btn.disabled = true; btnText.innerHTML = ' Decrypting...'; try { const salt = Base64.decode(pendingKey); const key = await deriveKeyFromPassword(password, salt); await performDecryption(key, pendingData, true); document.getElementById('passwordError').style.display = 'none'; } catch (err) { document.getElementById('passwordError').style.display = 'block'; btn.disabled = false; btnText.textContent = '🔓 Decrypt'; } } async function performDecryption(keyOrData, data, isKeyObject = false) { let key; if (isKeyObject) { key = keyOrData; } else { const keyRaw = Base64.decode(keyOrData); key = await crypto.subtle.importKey( 'raw', keyRaw, { name: 'AES-GCM', length: 256 }, false, ['decrypt'] ); } const decrypted = await crypto.subtle.decrypt( { name: 'AES-GCM', iv: new Uint8Array(data.data.iv) }, key, new Uint8Array(data.data.data) ); const decoded = new TextDecoder().decode(decrypted); const payload = parseDecryptedPayload(decoded); showDecryptedContent(payload, data.burnAfterRead); history.replaceState(null, '', location.pathname + location.search); } async function copyTextToClipboard(text) { if (!text) return; if (navigator.clipboard && window.isSecureContext) { await navigator.clipboard.writeText(text); return; } const temp = document.createElement('textarea'); temp.value = text; document.body.appendChild(temp); temp.select(); document.execCommand('copy'); document.body.removeChild(temp); } async function copyUrl() { const btn = document.getElementById('copyUrlBtn'); try { await copyTextToClipboard(document.getElementById('shareUrl').value); btn.textContent = '✅ Copied!'; setTimeout(() => { btn.textContent = '📋 Copy'; }, 2000); } catch (error) { showError('Unable to copy the full share URL'); } } async function copyShortUrl() { const btn = document.getElementById('copyShortUrlBtn'); const shareUrl = document.getElementById('shareUrl'); const shortUrl = shareUrl ? shareUrl.dataset.shortUrl : ''; if (!shortUrl) { showError('Short link is not available yet'); return; } try { await copyTextToClipboard(shortUrl); btn.textContent = '✅ Copied!'; setTimeout(() => { btn.textContent = '⚡ Copy short'; }, 2000); } catch (error) { showError('Unable to copy the short share URL'); } } async function copyDecryptedContent() { const btn = document.getElementById('copyDecryptedBtn'); const subject = document.getElementById('decryptedSubject').textContent.trim(); const content = document.getElementById('decryptedContent').dataset.rawContent || ''; const textToCopy = subject ? `Subject: ${subject}\n\n${content}` : content; try { await copyTextToClipboard(textToCopy); btn.textContent = '✅ Copied!'; setTimeout(() => { btn.textContent = '📋 Copy Text'; }, 2000); } catch (error) { showError('Unable to copy decrypted content'); } } function resetPasswordStrength() { const meterFill = document.getElementById('passwordStrengthFill'); const meterLabel = document.getElementById('passwordStrengthLabel'); const wrapper = document.getElementById('passwordStrength'); if (!meterFill || !meterLabel || !wrapper) return; wrapper.dataset.strength = 'empty'; meterFill.style.width = '0%'; meterLabel.textContent = 'Password strength will appear here'; } function calculatePasswordStrength(password) { let score = 0; if (password.length >= 8) score += 1; if (password.length >= 12) score += 1; if (password.length >= 16) score += 1; if (/[a-z]/.test(password)) score += 1; if (/[A-Z]/.test(password)) score += 1; if (/\d/.test(password)) score += 1; if (/[^A-Za-z0-9]/.test(password)) score += 1; if (password.length > 0 && password.length < 6) { score = Math.min(score, 1); } if (score <= 1) return { label: 'Weak', width: 25, level: 'weak' }; if (score <= 3) return { label: 'Fair', width: 50, level: 'fair' }; if (score <= 5) return { label: 'Good', width: 75, level: 'good' }; return { label: 'Strong', width: 100, level: 'strong' }; } function updatePasswordStrength() { const wrapper = document.getElementById('passwordStrength'); const input = document.getElementById('passwordInput'); const meterFill = document.getElementById('passwordStrengthFill'); const meterLabel = document.getElementById('passwordStrengthLabel'); if (!wrapper || !input || !meterFill || !meterLabel) return; const password = input.value; if (!password) { resetPasswordStrength(); return; } const result = calculatePasswordStrength(password); wrapper.dataset.strength = result.level; meterFill.style.width = `${result.width}%`; meterLabel.textContent = `${result.label} password`; } document.addEventListener('keydown', function(event) { if (!contentTextarea) return; if (document.activeElement !== contentTextarea) return; const key = event.key.toLowerCase(); if ((event.ctrlKey || event.metaKey) && !event.altKey) { if (key === 'b') { event.preventDefault(); applyToolbarAction('bold'); } else if (key === 'i') { event.preventDefault(); applyToolbarAction('italic'); } } }); window.addEventListener('load', () => { setCustomExpiryBounds(); resetPasswordStrength(); if (location.hash.length > 0 || /^\/p\//.test(location.pathname)) { decryptPaste(); } });