forked from ComputerTech/bastebin
216 lines
9.4 KiB
JavaScript
216 lines
9.4 KiB
JavaScript
'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';
|
|
}
|
|
}
|
|
});
|