diff --git a/palette b/palette index e22f3c0..e032107 100755 Binary files a/palette and b/palette differ diff --git a/palette.db-shm b/palette.db-shm index e1bfd1d..f27a97f 100644 Binary files a/palette.db-shm and b/palette.db-shm differ diff --git a/palette.db-wal b/palette.db-wal index ed5c6cd..6538049 100644 Binary files a/palette.db-wal and b/palette.db-wal differ diff --git a/web/static/app.css b/web/static/app.css index 27d20d4..0a3b0c7 100644 --- a/web/static/app.css +++ b/web/static/app.css @@ -3,7 +3,7 @@ --bg: #241B30; --surface: #2D2340; --surface-2: #3A2D52; --muted: #7A6A9E; --muted-fg: #C0B2DE; --fg: #F2EDF8; --accent: #C4A8F0; --border: #42355C; - --radius: 20px; + --radius-lg: 20px; --radius: 10px; --radius-sm: 999px; --font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; --font-mono: ui-monospace, "JetBrains Mono", "Fira Code", monospace; } @@ -31,15 +31,18 @@ body { .logo { font-weight: 700; font-size: 27.6px; letter-spacing: -0.02em; text-decoration: none; color: var(--fg); } .logo em { font-style: normal; color: var(--muted-fg); font-weight: 400; } .topbar nav { display: flex; gap: 4px; } -.topbar nav a { color: var(--muted-fg); text-decoration: none; padding: 6px 12px; border-radius: 8px; font-size: 23.2px; } +.topbar nav a { color: var(--muted-fg); text-decoration: none; padding: 6px 12px; border-radius: var(--radius); font-size: 23.2px; } .topbar nav a:hover { background: var(--surface-2); color: var(--fg); } .topbar nav a.on { background: var(--accent); color: var(--bg); } .topbar .spacer { flex: 1; } -.kbd { font-family: var(--font-mono); font-size: 18.9px; border: 1px solid var(--border); border-radius: 5px; padding: 2px 6px; color: var(--muted-fg); } +.kbd { font-family: var(--font-mono); font-size: 18.9px; border: 1px solid var(--border); border-radius: var(--radius); padding: 2px 6px; color: var(--muted-fg); } .float { - background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); - box-shadow: 0 2px 6px rgba(20,14,32,.25), 0 12px 32px rgba(20,14,32,.3); + background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); + box-shadow: + 0 1px 2px rgba(0, 0, 0, .10), + 0 2px 6px rgba(0, 0, 0, .08), + 0 8px 24px rgba(0, 0, 0, .07); overflow: hidden; } @@ -62,7 +65,7 @@ body { } .editor-head select { border: 1px solid var(--border); background: var(--surface-2); color: var(--muted-fg); - border-radius: 7px; padding: 4px 10px; font: inherit; font-size: 21.6px; cursor: pointer; + border-radius: var(--radius); padding: 4px 10px; font: inherit; font-size: 21.6px; cursor: pointer; } .editor-wrap { flex: 1; display: flex; min-height: 0; } .gutter { @@ -83,21 +86,21 @@ body { .actionbar .btn { padding: 10px 26px; } .btn { background: var(--accent); color: var(--bg); border: none; cursor: pointer; - padding: 8px 18px; border-radius: 8px; font: inherit; font-size: 22.4px; font-weight: 600; + padding: 8px 18px; border-radius: var(--radius); font: inherit; font-size: 22.4px; font-weight: 600; } .btn:hover { filter: brightness(1.08); } .hint { font-size: 20.7px; color: var(--muted-fg); } .hint b { color: var(--fg); font-weight: 550; } .seg { display: flex; flex-direction: column; gap: 2px; } -.seg label { display: flex; align-items: center; gap: 8px; padding: 5px 8px; border-radius: 7px; cursor: pointer; font-size: 22.4px; } +.seg label { display: flex; align-items: center; gap: 8px; padding: 5px 8px; border-radius: var(--radius); cursor: pointer; font-size: 22.4px; } .seg label:hover { background: var(--surface-2); } .seg input { accent-color: var(--accent); } -.toggle { display: flex; align-items: center; gap: 8px; font-size: 22.4px; cursor: pointer; padding: 5px 8px; border-radius: 7px; } +.toggle { display: flex; align-items: center; gap: 8px; font-size: 22.4px; cursor: pointer; padding: 5px 8px; border-radius: var(--radius); } .toggle:hover { background: var(--surface-2); } .toggle input { accent-color: var(--accent); } .deck .row { display: flex; justify-content: space-between; align-items: center; font-size: 22.4px; padding: 4px 0; } .row input[type="text"] { - border: 1px solid var(--border); border-radius: 7px; padding: 5px 8px; background: var(--bg); + border: 1px solid var(--border); border-radius: var(--radius); padding: 5px 8px; background: var(--bg); color: var(--fg); font: inherit; font-size: 21.6px; width: 130px; } .created-banner { @@ -109,10 +112,10 @@ body { /* paste view */ .meta-bar { display: flex; align-items: center; gap: 12px; padding: 12px 18px; flex-wrap: wrap; } .meta-bar h1 { font-size: 29.2px; font-weight: 600; } -.slug { font-family: var(--font-mono); font-size: 21.6px; color: var(--muted-fg); background: var(--surface-2); padding: 3px 9px; border-radius: 7px; } -.tag { font-size: 19.8px; color: var(--muted-fg); border: 1px solid var(--border); border-radius: 999px; padding: 2px 9px; } +.slug { font-family: var(--font-mono); font-size: 21.6px; color: var(--muted-fg); background: var(--surface-2); padding: 3px 9px; border-radius: var(--radius); } +.tag { font-size: 19.8px; color: var(--muted-fg); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 2px 9px; } .meta-bar .spacer { flex: 1; } -.iconbtn { border: 1px solid var(--border); background: var(--surface-2); color: var(--muted-fg); border-radius: 8px; padding: 5px 12px; font: inherit; font-size: 21.6px; cursor: pointer; text-decoration: none; } +.iconbtn { border: 1px solid var(--border); background: var(--surface-2); color: var(--muted-fg); border-radius: var(--radius); padding: 5px 12px; font: inherit; font-size: 21.6px; cursor: pointer; text-decoration: none; } .iconbtn:hover { color: var(--fg); border-color: var(--muted); } .iconbtn.danger:hover { color: #ff8fa3; border-color: #ff8fa3; } .code-head { @@ -134,7 +137,7 @@ body { .head-row h1 { font-size: 34.5px; font-weight: 600; } .search { display: flex; align-items: center; gap: 8px; background: var(--surface); - border: 1px solid var(--border); border-radius: 10px; padding: 8px 14px; width: 260px; + border: 1px solid var(--border); border-radius: var(--radius); padding: 8px 14px; width: 260px; } .search input { border: none; outline: none; background: transparent; color: var(--fg); font: inherit; font-size: 22.4px; width: 100%; } table { width: 100%; border-collapse: collapse; font-size: 22.4px; table-layout: fixed; } @@ -149,12 +152,12 @@ tr.row:hover td { background: var(--surface-2); } tr.row:hover td a.slug { color: var(--accent); } td a.slug { font-family: var(--font-mono); font-size: 21.6px; color: var(--fg); text-decoration: none; } td a.slug:hover { color: var(--accent); } -.badge { font-size: 18.9px; border: 1px solid var(--border); color: var(--muted-fg); border-radius: 999px; padding: 1px 8px; } +.badge { font-size: 18.9px; border: 1px solid var(--border); color: var(--muted-fg); border-radius: var(--radius-sm); padding: 1px 8px; } .badge.lock { color: var(--accent); border-color: var(--accent); } .dim { color: var(--muted-fg); white-space: nowrap; } .pager { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; font-size: 21.6px; color: var(--muted-fg); } .pager .pg { display: flex; gap: 6px; } -.pager button { border: 1px solid var(--border); background: var(--surface-2); color: var(--muted-fg); border-radius: 7px; padding: 4px 11px; font: inherit; font-size: 21.6px; cursor: pointer; } +.pager button { border: 1px solid var(--border); background: var(--surface-2); color: var(--muted-fg); border-radius: var(--radius); padding: 4px 11px; font: inherit; font-size: 21.6px; cursor: pointer; } .pager button:hover:not(:disabled) { color: var(--fg); border-color: var(--muted); } .pager button.on { background: var(--accent); color: var(--bg); border-color: var(--accent); } .pager button:disabled { opacity: .4; cursor: default; } @@ -171,7 +174,7 @@ td a.slug:hover { color: var(--accent); } .inner h1 { font-size: 29.2px; font-weight: 600; margin-bottom: 6px; } .inner .sub { font-size: 22.4px; color: var(--muted-fg); margin-bottom: 20px; } .pwinput { - width: 100%; padding: 10px 14px; border: 1px solid var(--border); border-radius: 9px; + width: 100%; padding: 10px 14px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg); color: var(--fg); font: inherit; font-size: 23.2px; outline: none; text-align: center; letter-spacing: .12em; } @@ -180,4 +183,31 @@ td a.slug:hover { color: var(--accent); } .err { display: none; margin-top: 12px; font-size: 21.6px; color: #ff8fa3; } .center .foot { font-size: 20.7px; color: var(--muted-fg); padding: 14px; border-top: 1px solid var(--border); } -.btn-icon { padding: 6px 10px; font-size: 24.2px; line-height: 1; } +.btn-icon { + padding: 4px 8px; font-size: 24.2px; line-height: 1; + display: inline-flex; align-items: center; justify-content: center; + min-width: 40px; height: 36px; +} + +/* selection controls: pill-style selected states (#14) */ +.seg label, .toggle { + border: 1px solid transparent; + transition: background .12s ease, border-color .12s ease, color .12s ease; +} +.seg label:hover, .toggle:hover { + background: var(--surface-2); + color: var(--fg); +} +.seg label:has(input:checked), +.toggle:has(input:checked) { + background: var(--surface-2); + border-color: var(--accent); + color: var(--fg); + border-radius: var(--radius-sm); +} +.seg label:has(input:focus-visible), +.toggle:has(input:focus-visible) { + outline: 2px solid var(--accent); + outline-offset: 1px; +} +.seg input, .toggle input { accent-color: var(--accent); width: 16px; height: 16px; margin: 0; } diff --git a/web/templates/history.html b/web/templates/history.html index 1e42cb6..c0aaf33 100644 --- a/web/templates/history.html +++ b/web/templates/history.html @@ -25,7 +25,7 @@ let page = 1, total = 0; const $ = id => document.getElementById(id); function esc(s) { const d = document.createElement('div'); d.textContent = s == null ? '' : s; return d.innerHTML; } -function fmtSize(n) { if (n == null) return '—'; if (n < 1024) return n + ' B'; if (n < 1048576) return (n/1024).toFixed(1) + ' KB'; return (n/1048576).toFixed(1) + ' MB'; } +function fmtSize(n) { if (n == null) return 'none'; if (n < 1024) return n + ' B'; if (n < 1048576) return (n/1024).toFixed(1) + ' KB'; return (n/1048576).toFixed(1) + ' MB'; } function ago(ts) { const s = Math.floor(Date.now()/1000) - ts; if (s < 60) return s + 's ago'; @@ -34,45 +34,67 @@ function ago(ts) { return Math.floor(s/86400) + 'd ago'; } +let filter = ''; + +function matchesFilter(it) { + if (!filter) return true; + const f = filter.toLowerCase(); + return (it.title || '').toLowerCase().includes(f) || (it.id || '').toLowerCase().includes(f); +} + async function load() { + const filtered = filter.length > 0; const off = (page - 1) * PER; - const res = await fetch('/api/public?limit=' + PER + '&offset=' + off); + const url = filtered + ? '/api/public?limit=200&offset=0' + : '/api/public?limit=' + PER + '&offset=' + off; + const res = await fetch(url); const data = await res.json(); total = data.total; - $('count').textContent = total.toLocaleString() + ' total'; + const items = filtered ? data.items.filter(matchesFilter) : data.items; + + $('count').textContent = filtered + ? items.length.toLocaleString() + ' matches (of ' + total.toLocaleString() + ' total)' + : total.toLocaleString() + ' total'; const rows = $('rows'); - if (data.items.length === 0) { + if (items.length === 0) { rows.innerHTML = ''; $('empty').style.display = 'block'; + $('empty').textContent = filtered ? 'No pastes match your search.' : 'No pastes yet. Create the first one.'; } else { $('empty').style.display = 'none'; - rows.innerHTML = data.items.map(it => + rows.innerHTML = items.map(it => `${esc(it.id)}` + - `${it.title ? esc(it.title) : ''}` + + `${it.title ? esc(it.title) : 'none'}` + `${esc(it.language || 'text')}` + `${fmtSize(it.size)}${it.view_count}${ago(it.created_at)}` ).join(''); } const pages = Math.max(1, Math.ceil(total / PER)); - $('showing').textContent = total === 0 ? 'Nothing here yet' : - `Showing ${off+1}–${Math.min(off+PER, total)} of ${total.toLocaleString()} · page ${page} of ${pages}`; + if (filtered) { + $('showing').textContent = 'Showing ' + items.length.toLocaleString() + ' matches for "' + filter + '"'; + $('pg').innerHTML = ''; + } else { + $('showing').textContent = total === 0 ? 'Nothing here yet' : + `Showing ${off+1}–${Math.min(off+PER, total)} of ${total.toLocaleString()} · page ${page} of ${pages}`; - const btns = []; - const add = (label, target, opts={}) => btns.push(``); - add('‹', page-1, {dis: page===1}); - const win = new Set([1, 2, page-1, page, page+1, pages]); - let last = 0; - for (let i = 1; i <= pages; i++) { - if (win.has(i)) { - if (last && i - last > 1) btns.push(''); - add(String(i), i, {on: i===page}); - last = i; + const btns = []; + const add = (label, target, opts={}) => btns.push(``); + add('‹', page-1, {dis: page===1}); + const win = new Set([1, 2, page-1, page, page+1, pages]); + let last = 0; + for (let i = 1; i <= pages; i++) { + if (win.has(i)) { + if (last && i - last > 1) btns.push(''); + add(String(i), i, {on: i===page}); + last = i; + } } + add('›', page+1, {dis: page===pages}); + $('pg').innerHTML = btns.join(''); } - add('›', page+1, {dis: page===pages}); - $('pg').innerHTML = btns.join(''); } $('pg').addEventListener('click', e => { @@ -87,7 +109,15 @@ $('rows').addEventListener('click', e => { if (!tr || e.target.closest('a')) return; // let real links (e.g. middle-click) work window.location.href = tr.dataset.href; }); -$('filter').addEventListener('input', () => { page = 1; load(); }); +let filterTimer = null; +$('filter').addEventListener('input', e => { + clearTimeout(filterTimer); + filterTimer = setTimeout(() => { + filter = e.target.value.trim(); + page = 1; + load(); + }, 200); +}); load(); setInterval(load, 30000); // auto-refresh history every 30s diff --git a/web/templates/new.html b/web/templates/new.html index e9a5325..ab9e9f5 100644 --- a/web/templates/new.html +++ b/web/templates/new.html @@ -47,11 +47,11 @@

Custom URL

-
/
+
/
-
+
@@ -72,6 +72,29 @@ $('haspw').addEventListener('change', e => { $('password').style.display = e.tar let guessed = ''; // last auto-detected language, '' = user override +function showResult(html, isError) { + $('result').innerHTML = html; + $('result').dataset.token = isError ? '' : ($('result').dataset.token || ''); + $('result-card').style.display = 'block'; +} +function defaultFilename(lang) { + const names = { + python: 'Python.py', go: 'main.go', javascript: 'script.js', rust: 'main.rs', + c: 'main.c', cpp: 'main.cpp', java: 'Main.java', bash: 'script.sh', + sql: 'query.sql', yaml: 'config.yaml', json: 'data.json', + markdown: 'notes.md', text: 'Text.txt', + }; + return names[lang] || ''; +} +// fill default filename when title is still blank +function maybeSetDefaultTitle(lang) { + const title = $('title'); + if (lang && !title.value.trim()) { + const fn = defaultFilename(lang); + if (fn) title.value = fn; + } +} + async function guessLang() { if (!content.value.trim()) return; try { @@ -84,6 +107,7 @@ async function guessLang() { if (res.ok && data.language) { guessed = data.language; $('language').value = data.language; + maybeSetDefaultTitle(data.language); } } catch(e) {} } @@ -113,11 +137,11 @@ async function create() { }); const data = await res.json(); if (!res.ok) { - $('result').textContent = 'Error: ' + (data.error || res.status); + showResult('Error: ' + (data.error || res.status), true); return; } const url = location.origin + '/' + (data.custom_slug || data.id); - $('result').innerHTML = '' + url + ''; + showResult('' + url + '', false); $('result').dataset.token = data.deletion_token || ''; try { navigator.clipboard.writeText(url); } catch(e) {} // show the paste