Release v0.4.0: dev -> main #251
@@ -66,16 +66,6 @@
|
|||||||
<div class="float side-section">
|
<div class="float side-section">
|
||||||
<h3>Custom URL</h3>
|
<h3>Custom URL</h3>
|
||||||
<input type="text" id="custom" class="custom-input" placeholder="/my-snippet">
|
<input type="text" id="custom" class="custom-input" placeholder="/my-snippet">
|
||||||
<div class="hint" style="margin-top:6px; font-size:19px;">Stays reserved while the paste exists</div>
|
|
||||||
</div>
|
|
||||||
<div class="float side-section">
|
|
||||||
<h3>Can contents</h3>
|
|
||||||
<label class="toggle"><input type="checkbox" id="iscan"> Bundle as a can (multiple text items)</label>
|
|
||||||
<div class="pw-row" id="canrow" style="display:none">
|
|
||||||
<div id="can-items"></div>
|
|
||||||
<button class="btn btn-icon" id="can-add" type="button" title="Add item" style="margin-top:6px">+ Add item</button>
|
|
||||||
<div class="hint" style="margin-top:6px; font-size:19px;">Each item gets its own card on the can page. File uploads in cans come later.</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="float side-section" id="result-card" style="display:none">
|
<div class="float side-section" id="result-card" style="display:none">
|
||||||
<h3>Result</h3>
|
<h3>Result</h3>
|
||||||
@@ -154,21 +144,6 @@ $('pwreveal').addEventListener('click', () => {
|
|||||||
$('pwreveal').title = show ? 'Hide password' : 'Show password';
|
$('pwreveal').title = show ? 'Hide password' : 'Show password';
|
||||||
});
|
});
|
||||||
|
|
||||||
// #4: can builder — multiple text items bundled into one shareable page.
|
|
||||||
// The main editor becomes the first item; extra items are added below.
|
|
||||||
$('iscan').addEventListener('change', e => {
|
|
||||||
$('canrow').style.display = e.target.checked ? 'block' : 'none';
|
|
||||||
if (e.target.checked && !$('can-items').children.length) addCanItem();
|
|
||||||
});
|
|
||||||
function addCanItem() {
|
|
||||||
const row = document.createElement('div');
|
|
||||||
row.className = 'can-item-row';
|
|
||||||
row.innerHTML = '<input class="can-item-title" placeholder="Item title">' +
|
|
||||||
'<textarea class="can-item-content" placeholder="Item content" rows="3" spellcheck="false"></textarea>';
|
|
||||||
$('can-items').appendChild(row);
|
|
||||||
}
|
|
||||||
$('can-add').addEventListener('click', addCanItem);
|
|
||||||
|
|
||||||
let guessed = ''; // last auto-detected language, '' = user override
|
let guessed = ''; // last auto-detected language, '' = user override
|
||||||
|
|
||||||
// #105: map backend machine-readable error codes to plain-language guidance.
|
// #105: map backend machine-readable error codes to plain-language guidance.
|
||||||
@@ -248,8 +223,6 @@ $('reguess').addEventListener('click', guessLang);
|
|||||||
content.addEventListener('paste', () => setTimeout(guessLang, 0));
|
content.addEventListener('paste', () => setTimeout(guessLang, 0));
|
||||||
|
|
||||||
async function create() {
|
async function create() {
|
||||||
// #4: can mode — bundle the editor + extra items into a can via multipart
|
|
||||||
if ($('iscan').checked) return createCan();
|
|
||||||
|
|
||||||
const body = {
|
const body = {
|
||||||
content: content.value,
|
content: content.value,
|
||||||
@@ -307,61 +280,6 @@ async function create() {
|
|||||||
}
|
}
|
||||||
$('create').addEventListener('click', create);
|
$('create').addEventListener('click', create);
|
||||||
|
|
||||||
// #4: can creation — POST multipart to /api/pastes/can. The main editor is
|
|
||||||
// the first item; each extra can-item row is another text item.
|
|
||||||
async function createCan() {
|
|
||||||
const items = [];
|
|
||||||
if (content.value.trim()) {
|
|
||||||
items.push({title: $('title').value || 'main', content: content.value, language: $('language').value || ''});
|
|
||||||
}
|
|
||||||
document.querySelectorAll('#can-items .can-item-row').forEach(row => {
|
|
||||||
const t = row.querySelector('.can-item-title').value.trim();
|
|
||||||
const c = row.querySelector('.can-item-content').value;
|
|
||||||
if (c.trim()) items.push({title: t || ('item-' + (items.length + 1)), content: c});
|
|
||||||
});
|
|
||||||
if (!items.length) { toast('Nothing to put in the can', 'error'); return; }
|
|
||||||
|
|
||||||
const fd = new FormData();
|
|
||||||
fd.append('title', $('title').value || 'Untitled can');
|
|
||||||
fd.append('json_items', JSON.stringify(items));
|
|
||||||
if ($('haspw').checked) fd.append('password', $('password').value);
|
|
||||||
if ($('unlisted').checked) fd.append('visibility', 'unlisted');
|
|
||||||
const exp = document.querySelector('input[name="exp"]:checked').value;
|
|
||||||
if (exp === 'custom') {
|
|
||||||
const dur = composeCustomExpiry();
|
|
||||||
if (dur === null) { toast('Check the custom expiry', 'error'); return; }
|
|
||||||
if (dur) fd.append('expires_in', dur);
|
|
||||||
} else if (exp) {
|
|
||||||
fd.append('expires_in', exp);
|
|
||||||
}
|
|
||||||
if ($('custom').value.trim()) fd.append('custom_slug', $('custom').value.trim());
|
|
||||||
|
|
||||||
const res = await fetch('/api/pastes/can', {method: 'POST', body: fd});
|
|
||||||
const data = await res.json();
|
|
||||||
if (!res.ok) {
|
|
||||||
showResult(friendlyError(data), 'err');
|
|
||||||
toast('Can create failed', 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const url = location.origin + data.url;
|
|
||||||
showResult('<a href="' + url + '">' + url + '</a> <button class="btn btn-icon" id="result-copy" title="Copy URL" type="button">⧉</button>', 'ok');
|
|
||||||
const copyBtn = document.getElementById('result-copy');
|
|
||||||
copyBtn.addEventListener('click', () => {
|
|
||||||
try {
|
|
||||||
navigator.clipboard.writeText(url);
|
|
||||||
copyBtn.classList.add('ok');
|
|
||||||
copyBtn.textContent = 'Success!';
|
|
||||||
setTimeout(() => { copyBtn.classList.remove('ok'); copyBtn.textContent = '⧉'; }, 2000);
|
|
||||||
} catch(e) { toast('Copy failed', 'error'); }
|
|
||||||
});
|
|
||||||
// password-protected can: unlock now with the password we already have (#26 parity)
|
|
||||||
if ($('haspw').checked && data.id) {
|
|
||||||
const pd = new FormData();
|
|
||||||
pd.append('password', $('password').value);
|
|
||||||
try { await fetch('/can/' + data.id, {method: 'POST', body: pd}); } catch(e) {}
|
|
||||||
}
|
|
||||||
location.href = data.url;
|
|
||||||
}
|
|
||||||
// reset stale result state when returning via Back (bfcache) (#28)
|
// reset stale result state when returning via Back (bfcache) (#28)
|
||||||
window.addEventListener('pageshow', e => {
|
window.addEventListener('pageshow', e => {
|
||||||
if (!e.persisted) return;
|
if (!e.persisted) return;
|
||||||
|
|||||||
Reference in New Issue
Block a user