Small fixes: gutter alignment, paste column, sort arrows, copy feedback, stats bar, title slug (#50-#55)
- #50 gutter/code line misalignment: shared --code-lh/--code-fs tokens; .code .gutter drops its own vertical padding (was 14px vs 0, 14px offset) - #51 history Paste column shows only the paste name, or slug pill when untitled - #52 sort indicator arrow sits LEFT of the column label for all sortable columns - #53 copy buttons give in-place success feedback ('Success!' in --ok for 2s): paste view copy button and new-page result copy button - #54 collapsed stats summary bar: roomier padding (14px 18px) and item gap (16px); wraps gracefully on mobile - #55 paste view title bar: slug pill only shown for custom slugs, id not repeated Closes #50, closes #51, closes #52, closes #53, closes #54, closes #55
This commit is contained in:
+16
-4
@@ -95,10 +95,13 @@ body {
|
||||
border: 1px solid var(--border); background: var(--surface-2); color: var(--muted-fg);
|
||||
border-radius: var(--radius); padding: 4px 10px; font: inherit; font-size: 21.6px; cursor: pointer;
|
||||
}
|
||||
/* shared code line metrics (#50): gutter + code must share one line box */
|
||||
:root { --code-lh: 1.7; --code-fs: 21.6px; }
|
||||
|
||||
.editor-wrap { flex: 1; display: flex; min-height: 0; }
|
||||
.gutter {
|
||||
padding: 14px 10px; text-align: right; color: var(--muted); font-family: var(--font-mono);
|
||||
font-size: 21.6px; line-height: 1.7; user-select: none; white-space: pre; overflow: hidden;
|
||||
font-size: var(--code-fs); line-height: var(--code-lh); user-select: none; white-space: pre; overflow: hidden;
|
||||
border-right: 1px solid var(--border);
|
||||
}
|
||||
.editor {
|
||||
@@ -145,11 +148,12 @@ body {
|
||||
.paste-title-bar { display: flex; align-items: center; gap: 12px; padding: 12px 18px; flex-wrap: wrap; }
|
||||
.paste-title-bar h1 { font-size: 29.2px; font-weight: 600; margin: 0; }
|
||||
.stats-pill { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
|
||||
.stats-head { display: flex; align-items: center; gap: 10px; width: 100%; background: none; border: 0; color: var(--muted-fg); font: inherit; font-size: 21.6px; padding: 10px 14px; cursor: pointer; text-align: left; }
|
||||
.stats-head { display: flex; align-items: center; gap: 16px; width: 100%; background: none; border: 0; color: var(--muted-fg); font: inherit; font-size: 21.6px; padding: 14px 18px; cursor: pointer; text-align: left; }
|
||||
.stats-head:hover { color: var(--fg); background: var(--surface-2); }
|
||||
.stats-chev { width: 18px; height: 18px; flex: none; transition: transform 0.15s ease; }
|
||||
.stats-pill.open .stats-chev { transform: rotate(180deg); }
|
||||
.stats-summary { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.stats-summary { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; letter-spacing: .01em; }
|
||||
@media (max-width: 640px) { .stats-summary { white-space: normal; word-break: break-word; } }
|
||||
.stats-body { border-top: 1px solid var(--border); }
|
||||
.stats-grid { display: grid; grid-template-columns: max-content 1fr; gap: 6px 18px; padding: 12px 16px; font-size: 20.7px; }
|
||||
.stats-k { color: var(--muted-fg); }
|
||||
@@ -169,10 +173,12 @@ body {
|
||||
}
|
||||
.code-head .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); }
|
||||
.code {
|
||||
font-family: var(--font-mono); font-size: 22.4px; line-height: 1.7;
|
||||
font-family: var(--font-mono); font-size: var(--code-fs); line-height: var(--code-lh);
|
||||
padding: 14px 0; display: flex; overflow-x: auto;
|
||||
}
|
||||
.code .gutter { flex-shrink: 0; }
|
||||
/* gutter/code share line metrics; the editor gutter keeps its own padding (#50) */
|
||||
.code .gutter { padding-top: 0; padding-bottom: 0; }
|
||||
.codebody { padding: 0 18px; white-space: pre; }
|
||||
/* syntax highlight tokens (#1) */
|
||||
.tok-kw { color: #c792ea; }
|
||||
@@ -370,6 +376,12 @@ th.sorted.desc .sort-ind { border-top: 6px solid var(--accent); }
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
/* in-place copy success feedback (#53) */
|
||||
.iconbtn.ok, .btn.ok {
|
||||
color: var(--ok);
|
||||
border-color: var(--ok);
|
||||
}
|
||||
|
||||
/* headings: unified treatment (mirrors .side-section h3) */
|
||||
.settings-head h1, .paste-title-bar h1, .head-row h1, .inner h1 {
|
||||
letter-spacing: -0.01em;
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
<table>
|
||||
<colgroup><col style="width:260px"><col style="width:140px"><col style="width:120px"><col style="width:96px"><col style="width:140px"><col style="width:190px"><col style="width:100px"></colgroup>
|
||||
<thead><tr>
|
||||
<th data-sort="title" class="sortable">Paste<span class="sort-ind"></span></th>
|
||||
<th data-sort="language" class="sortable">Language<span class="sort-ind"></span></th>
|
||||
<th data-sort="size" class="sortable">Size<span class="sort-ind"></span></th>
|
||||
<th data-sort="view_count" class="sortable">Views<span class="sort-ind"></span></th>
|
||||
<th data-sort="created_at" class="sortable">Created<span class="sort-ind"></span></th>
|
||||
<th data-sort="custom_slug" class="sortable">URL<span class="sort-ind"></span></th>
|
||||
<th data-sort="id" class="sortable">ID<span class="sort-ind"></span></th>
|
||||
<th data-sort="title" class="sortable"><span class="sort-ind"></span>Paste</th>
|
||||
<th data-sort="language" class="sortable"><span class="sort-ind"></span>Language</th>
|
||||
<th data-sort="size" class="sortable"><span class="sort-ind"></span>Size</th>
|
||||
<th data-sort="view_count" class="sortable"><span class="sort-ind"></span>Views</th>
|
||||
<th data-sort="created_at" class="sortable"><span class="sort-ind"></span>Created</th>
|
||||
<th data-sort="custom_slug" class="sortable"><span class="sort-ind"></span>URL</th>
|
||||
<th data-sort="id" class="sortable"><span class="sort-ind"></span>ID</th>
|
||||
</tr></thead>
|
||||
<tbody id="rows"></tbody>
|
||||
</table>
|
||||
@@ -115,8 +115,11 @@ async function load() {
|
||||
} else {
|
||||
$('empty').style.display = 'none';
|
||||
rows.innerHTML = items.map(it =>
|
||||
`<tr class="row" data-href="/${esc(it.id)}"><td><a class="slug" href="/${esc(it.id)}">${esc(it.id)}</a>` +
|
||||
(it.title ? `<div class="paste-sub">${esc(it.title)}</div>` : `<div class="paste-sub dim">none</div>`) + `</td>` +
|
||||
`<tr class="row" data-href="/${esc(it.id)}"><td>` +
|
||||
(it.title
|
||||
? `${esc(it.title)}`
|
||||
: `<a class="slug" href="/${esc(it.id)}">${esc(it.id)}</a>`) +
|
||||
`</td>` +
|
||||
`<td><span class="badge">${esc(it.language || 'text')}</span></td>` +
|
||||
`<td class="dim">${fmtSize(it.size)}</td><td class="dim">${it.view_count}</td><td class="dim" data-ts="${it.created_at}">${ago(it.created_at)}</td>` +
|
||||
(it.custom_slug ? `<td><a class="slug url-link" href="/${esc(it.custom_slug)}">/${esc(it.custom_slug)}</a></td>` : `<td class="dim">none</td>`) +
|
||||
|
||||
@@ -57,8 +57,8 @@ async function load() {
|
||||
}
|
||||
$('empty').style.display = 'none';
|
||||
rows.innerHTML = data.items.map(it =>
|
||||
`<tr class="row" data-href="/${esc(it.id)}"><td><a class="slug" href="/${esc(it.id)}">${esc(it.id)}</a>` +
|
||||
(it.title ? `<div class="paste-sub">${esc(it.title)}</div>` : `<div class="paste-sub dim">none</div>`) + `</td>` +
|
||||
`<tr class="row" data-href="/${esc(it.id)}"><td>` +
|
||||
(it.title ? `${esc(it.title)}` : `<a class="slug" href="/${esc(it.id)}">${esc(it.id)}</a>`) + `</td>` +
|
||||
`<td><span class="badge">${esc(it.language || 'text')}</span></td>` +
|
||||
`<td class="dim">${fmtSize(it.size)}</td><td class="dim" data-ts="${it.created_at}">${ago(it.created_at)}</td>` +
|
||||
(it.custom_slug ? `<td><a class="slug url-link" href="/${esc(it.custom_slug)}">/${esc(it.custom_slug)}</a></td>` : `<td class="dim">none</td>`) +
|
||||
|
||||
+10
-2
@@ -169,9 +169,17 @@ async function create() {
|
||||
return;
|
||||
}
|
||||
const url = location.origin + '/' + (data.custom_slug || data.id);
|
||||
showResult('<a href="' + url + '">' + url + '</a>', false);
|
||||
showResult('<a href="' + url + '">' + url + '</a> <button class="btn btn-icon" id="result-copy" title="Copy URL" type="button">⧉</button>', false);
|
||||
$('result').dataset.token = data.deletion_token || '';
|
||||
try { navigator.clipboard.writeText(url); toast('Copied', 'success'); } catch(e) {}
|
||||
const copyBtn = document.getElementById('result-copy');
|
||||
copyBtn.addEventListener('click', () => {
|
||||
try {
|
||||
navigator.clipboard.writeText(url);
|
||||
copyBtn.classList.add('ok'); // in-place success feedback (#53)
|
||||
copyBtn.textContent = 'Success!';
|
||||
setTimeout(() => { copyBtn.classList.remove('ok'); copyBtn.textContent = '⧉'; }, 2000);
|
||||
} catch(e) { toast('Copy failed', 'error'); }
|
||||
});
|
||||
const dest = '/' + data.id + '?created=1&token=' + encodeURIComponent(data.deletion_token || '');
|
||||
// password-protected: unlock now with the password we already have (#26)
|
||||
if ($('haspw').checked && data.id) {
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
<div class="float">
|
||||
<div class="paste-title-bar">
|
||||
<h1>{{if .Title}}{{.Title}}{{else}}Untitled paste{{end}}</h1>
|
||||
<span class="slug">/{{if .CustomSlug}}{{.CustomSlug}}{{else}}{{.ID}}{{end}}</span>
|
||||
{{if .CustomSlug}}<span class="slug">/{{.CustomSlug}}</span>{{end}}
|
||||
<div class="spacer"></div>
|
||||
<a class="iconbtn" href="/raw/{{.ID}}">raw</a>
|
||||
<a class="iconbtn" href="#" onclick="copyContent(); return false;">copy</a>
|
||||
<a class="iconbtn" href="#" id="copy-btn" onclick="copyContent(this); return false;">copy</a>
|
||||
{{if .DeletionToken}}<a class="iconbtn danger" href="#" onclick="redeem('{{.DeletionToken}}'); return false;">delete</a>{{end}}
|
||||
</div>
|
||||
</div>
|
||||
@@ -63,9 +63,17 @@ function toggleStats() {
|
||||
pill.classList.toggle('open', open);
|
||||
btn.setAttribute('aria-expanded', open ? 'true' : 'false');
|
||||
}
|
||||
function copyContent() {
|
||||
function copyContent(btn) {
|
||||
navigator.clipboard.writeText(document.getElementById('raw-content').value);
|
||||
toast('Copied');
|
||||
// in-place success feedback (#53)
|
||||
if (btn) {
|
||||
btn.classList.add('ok');
|
||||
btn.textContent = 'Success!';
|
||||
clearTimeout(btn._okh);
|
||||
btn._okh = setTimeout(() => { btn.classList.remove('ok'); btn.textContent = 'copy'; }, 2000);
|
||||
} else {
|
||||
toast('Copied', 'success');
|
||||
}
|
||||
}
|
||||
function redeem(token) {
|
||||
if (!confirm('Hard delete this paste immediately?')) return;
|
||||
|
||||
Reference in New Issue
Block a user