Compare commits
8
Commits
v0.3.0
..
f1b5dd0364
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1b5dd0364 | ||
|
|
8eabeb4839 | ||
|
|
7ed50087a6 | ||
|
|
8104cdba4c | ||
|
|
be3ff81e5c | ||
|
|
1e317d11da | ||
|
|
2ce50a6393 | ||
|
|
70c284b15d |
@@ -40,6 +40,47 @@
|
||||
--ok: #3E6B3E; --warn: #7A5E1B; --err: #9E4054;
|
||||
--on-accent: #274a6b;
|
||||
}
|
||||
/* matching light/dark counterparts: same hue family, flipped lightness roles */
|
||||
/* Midnight Light: light counterpart of midnight (violet family) */
|
||||
[data-preset="midnight-light"] {
|
||||
--bg: #F3EFF9; --surface: #FAF8FD; --surface-2: #E1D9EF;
|
||||
--muted: #A99CC9; --muted-fg: #5F5390; --fg: #2B2140;
|
||||
--accent: #7B5FC0; --border: #D8CEEC;
|
||||
--ok: #3E6B3E; --warn: #7A5E1B; --err: #9E4054;
|
||||
--on-accent: #FAF8FD;
|
||||
}
|
||||
/* Smooth Dark: dark counterpart of smooth (muted slate-violet family) */
|
||||
[data-preset="smooth-dark"] {
|
||||
--bg: #23222E; --surface: #2C2B3A; --surface-2: #3B3A4D;
|
||||
--muted: #7A7796; --muted-fg: #B5B1C9; --fg: #E8E7F0;
|
||||
--accent: #A9A6C4; --border: #45435A;
|
||||
--ok: #9CD49C; --warn: #E8C77B; --err: #F2A3B3;
|
||||
--on-accent: #23222E;
|
||||
}
|
||||
/* Pastel Lavender Dark: dark counterpart (lavender family) */
|
||||
[data-preset="pastel-lavender-dark"] {
|
||||
--bg: #2A2340; --surface: #352C50; --surface-2: #4A3E6B;
|
||||
--muted: #8F7FB8; --muted-fg: #CBBEE8; --fg: #EFEAF8;
|
||||
--accent: #B39DE4; --border: #4E4272;
|
||||
--ok: #9CD49C; --warn: #E8C77B; --err: #F2A3B3;
|
||||
--on-accent: #2A2340;
|
||||
}
|
||||
/* Pastel Peach Dark: dark counterpart (peach family) */
|
||||
[data-preset="pastel-peach-dark"] {
|
||||
--bg: #3A241C; --surface: #4A2F24; --surface-2: #654234;
|
||||
--muted: #B07A64; --muted-fg: #F0C4B0; --fg: #FBEBE2;
|
||||
--accent: #F9826C; --border: #75503F;
|
||||
--ok: #9CD49C; --warn: #E8C77B; --err: #F2A3B3;
|
||||
--on-accent: #3A241C;
|
||||
}
|
||||
/* Pastel Cloud Dark: dark counterpart (pink cloud family, blue accent) */
|
||||
[data-preset="pastel-cloud-dark"] {
|
||||
--bg: #33222E; --surface: #402C3A; --surface-2: #583D50;
|
||||
--muted: #A87E97; --muted-fg: #E5C2D6; --fg: #FAEEF5;
|
||||
--accent: #A2D2FF; --border: #644759;
|
||||
--ok: #9CD49C; --warn: #E8C77B; --err: #F2A3B3;
|
||||
--on-accent: #33222E;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body {
|
||||
@@ -503,3 +544,86 @@ a.admin-link:hover { color: var(--fg); text-decoration: underline; }
|
||||
.can-item-row { margin-top: 8px; }
|
||||
.can-item-row .can-item-title { width: 100%; margin-bottom: 6px; }
|
||||
.can-item-row .can-item-content { width: 100%; font-family: var(--mono, monospace); resize: vertical; }
|
||||
|
||||
/* ============================================================
|
||||
Styled controls (#123) — buttons, selects, checkboxes, radios
|
||||
match the custom URL box house pattern: 1px var(--border) on
|
||||
var(--bg), var(--radius) corners, accent border on focus, no
|
||||
default browser chrome (appearance: none).
|
||||
============================================================ */
|
||||
|
||||
/* selects: replace native chrome with a house-styled pill */
|
||||
select {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
background: var(--bg) url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%23888' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") no-repeat right 10px center;
|
||||
color: var(--fg);
|
||||
font: inherit;
|
||||
padding: 4px 30px 4px 12px;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
transition: border-color .12s ease, background-color .12s ease;
|
||||
}
|
||||
select:hover { border-color: var(--muted-fg); background-color: var(--surface-2); }
|
||||
select:focus-visible { border-color: var(--accent); outline: 2px solid var(--accent); outline-offset: 1px; }
|
||||
.editor-head select { padding: 4px 30px 4px 10px; }
|
||||
|
||||
/* checkboxes and radios: custom drawn, accent when checked */
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
width: 16px; height: 16px; margin: 0; flex-shrink: 0;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--bg);
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition: border-color .12s ease, background-color .12s ease;
|
||||
}
|
||||
input[type="checkbox"] { border-radius: 4px; }
|
||||
input[type="radio"] { border-radius: 50%; }
|
||||
input[type="checkbox"]:hover,
|
||||
input[type="radio"]:hover { border-color: var(--muted-fg); }
|
||||
input[type="checkbox"]:checked,
|
||||
input[type="radio"]:checked { background: var(--accent); border-color: var(--accent); }
|
||||
input[type="checkbox"]:checked::after {
|
||||
content: "";
|
||||
position: absolute; inset: 0;
|
||||
background: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='10' viewBox='0 0 12 10'%3E%3Cpath d='M1 5l4 4 6-8' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 11px no-repeat;
|
||||
}
|
||||
input[type="radio"]:checked::after {
|
||||
content: "";
|
||||
position: absolute; inset: 3px;
|
||||
border-radius: 50%;
|
||||
background: var(--bg);
|
||||
}
|
||||
input[type="checkbox"]:focus-visible,
|
||||
input[type="radio"]:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
/* buttons: every submit-style button gets the .btn treatment,
|
||||
and .btn itself gains the house surface/border/hover pattern */
|
||||
button[type="submit"] {
|
||||
background: var(--accent); color: var(--bg); border: 1px solid var(--accent);
|
||||
border-radius: var(--radius); font: inherit; font-weight: 600;
|
||||
padding: 8px 18px; cursor: pointer;
|
||||
transition: filter .12s ease;
|
||||
}
|
||||
button[type="submit"]:hover { filter: brightness(1.08); }
|
||||
button[type="submit"]:focus-visible,
|
||||
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
|
||||
.btn {
|
||||
border: 1px solid var(--accent);
|
||||
}
|
||||
.btn:hover { filter: brightness(1.08); }
|
||||
|
||||
/* settings: give the Admin link clear separation from the theme grid */
|
||||
.admin-link-row {
|
||||
margin-top: 32px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
@@ -65,17 +65,7 @@
|
||||
</div>
|
||||
<div class="float side-section">
|
||||
<h3>Custom URL</h3>
|
||||
<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>
|
||||
<input type="text" id="custom" class="custom-input" placeholder="my-snippet">
|
||||
</div>
|
||||
<div class="float side-section" id="result-card" style="display:none">
|
||||
<h3>Result</h3>
|
||||
@@ -154,21 +144,6 @@ $('pwreveal').addEventListener('click', () => {
|
||||
$('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
|
||||
|
||||
// #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));
|
||||
|
||||
async function create() {
|
||||
// #4: can mode — bundle the editor + extra items into a can via multipart
|
||||
if ($('iscan').checked) return createCan();
|
||||
|
||||
const body = {
|
||||
content: content.value,
|
||||
@@ -307,61 +280,6 @@ async function 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)
|
||||
window.addEventListener('pageshow', e => {
|
||||
if (!e.persisted) return;
|
||||
|
||||
@@ -21,7 +21,12 @@
|
||||
{ id: 'smooth', name: 'Smooth' },
|
||||
{ id: 'pastel-lavender', name: 'Pastel Lavender' },
|
||||
{ id: 'pastel-peach', name: 'Pastel Peach' },
|
||||
{ id: 'pastel-cloud', name: 'Pastel Cloud' }
|
||||
{ id: 'pastel-cloud', name: 'Pastel Cloud' },
|
||||
{ id: 'midnight-light', name: 'Midnight Light' },
|
||||
{ id: 'smooth-dark', name: 'Smooth Dark' },
|
||||
{ id: 'pastel-lavender-dark', name: 'Pastel Lavender Dark' },
|
||||
{ id: 'pastel-peach-dark', name: 'Pastel Peach Dark' },
|
||||
{ id: 'pastel-cloud-dark', name: 'Pastel Cloud Dark' }
|
||||
];
|
||||
var SWATCH_VARS = ['--bg', '--surface', '--surface-2', '--muted', '--accent'];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user