1 Commits
Author SHA1 Message Date
fen efddb98a19 Fix #169: persistent full-width divider under stats dropdown title
CI / docker (pull_request) Skipped
CI / test (pull_request) Successful in 41s
The divider between the stats dropdown head and body lived on
.stats-body's border-top, so it vanished when collapsed and only the
hover background hinted at the boundary (flicker). Move it to
.stats-head border-bottom so it is always present and spans the full
pill width.
2026-09-10 11:34:33 -05:00
6 changed files with 21 additions and 36 deletions
+2 -10
View File
@@ -91,15 +91,7 @@ func TestCreatedBannerViaCookie(t *testing.T) {
if rec2.Code != 200 {
t.Fatalf("paste view: got %d", rec2.Code)
}
// #168: the created pill no longer prints the deletion token; the token
// still arrives via the one-time cookie flow (re-set on the view response).
var got bool
for _, c := range rec2.Result().Cookies() {
if c.Name == "tok_"+id && c.Value == tok {
got = true
}
}
if !got {
t.Error("created view did not re-set the deletion token cookie (#143 cookie flow broken)")
if !strings.Contains(rec2.Body.String(), tok) {
t.Error("created banner does not show the deletion token (#143 cookie flow broken)")
}
}
+10 -16
View File
@@ -197,17 +197,11 @@ body {
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;
}
/* #168: compact side notification pill (paste-created feedback) */
.created-pill {
position: fixed; right: 12px; bottom: 12px; z-index: 200;
background: var(--surface-2); color: var(--ok); border: 1px solid var(--ok);
border-radius: var(--radius-sm); padding: 2px 8px; font-size: 11.5px;
opacity: 0; pointer-events: none; transform: translateY(6px);
transition: opacity .25s ease, transform .25s ease;
box-shadow: 0 4px 16px rgba(0,0,0,.25);
.created-banner {
display: none; padding: 10px 16px; font-size: 22.4px; background: var(--surface-2);
border-bottom: 1px solid var(--border); word-break: break-all;
}
.created-pill.show { opacity: 1; transform: translateY(0); }
@media (max-width: 640px) { .created-pill { right: 12px; bottom: 12px; } }
.created-banner a { color: var(--accent); }
/* paste view */
.meta-bar { display: flex; align-items: center; gap: 12px; padding: 12px 18px; flex-wrap: wrap; }
@@ -299,8 +293,6 @@ html[data-wrap] .float { overflow-x: hidden; }
.codebody { padding: 0 18px; white-space: pre; }
/* #167: each logical line is its own block so offsetTop identifies its first visual row */
.codeline { display: block; }
/* #167 rev: gutter number spans must stack one per visual row (wrap on) */
.code .gutter .gutline { display: block; }
/* syntax highlight tokens (#1) */
.tok-kw { color: #c792ea; }
.tok-str { color: #a5e075; }
@@ -396,15 +388,15 @@ td a.slug.paste-name { background: none; padding: 0; border-radius: 0; font-fami
}
.seg input, .toggle input { accent-color: var(--accent); width: 16px; height: 16px; margin: 0; }
/* toast (#19, #168): small pill anchored to the corner, out of content flow */
/* toast (#19) */
.toast {
position: fixed; right: 16px; bottom: 16px;
position: fixed; left: 50%; bottom: 32px; transform: translateX(-50%) translateY(8px);
background: var(--surface-2); color: var(--fg); border: 1px solid var(--border);
border-radius: var(--radius-sm); padding: 6px 12px; font-size: 13px;
border-radius: var(--radius-sm); padding: 6px 18px; font-size: 20.7px;
opacity: 0; pointer-events: none; transition: opacity .25s ease, transform .25s ease; z-index: 200;
box-shadow: 0 4px 16px rgba(0,0,0,.25);
}
.toast.show { opacity: 1; transform: translateY(0); }
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
/* status variants (#16) */
.toast.success { border-color: var(--ok); color: var(--ok); }
.toast.error { border-color: var(--err); color: var(--err); }
@@ -633,6 +625,7 @@ a.admin-link:hover { color: var(--fg); text-decoration: underline; }
.code { font-size: 13px; }
.codebody { padding: 0 12px; }
.footnote { font-size: 12px; padding: 8px 12px; gap: 10px; }
.created-banner { font-size: 13px; }
.iconbtn { font-size: 13px; padding: 6px 10px; }
/* unlock card */
@@ -811,3 +804,4 @@ button[type="submit"]:focus-visible,
.mt18 { margin-top: 18px; }
.toggle-inline { display: inline-flex; }
.wrap-normal { word-break: normal; overflow-wrap: break-word; }
.created-banner.show { display: block; }
+1 -8
View File
@@ -25,7 +25,7 @@ function copyContent(btn) {
clearTimeout(btn._okh);
btn._okh = setTimeout(() => { btn.classList.remove('ok'); btn.textContent = 'copy'; }, 2000);
} else {
toast('Link Copied', 'success');
toast('Copied', 'success');
}
}
function redeem() {
@@ -42,13 +42,6 @@ var PASTE_ID = document.currentScript.getAttribute('data-paste-id');
var copyBtn = document.getElementById('copy-btn');
if (copyBtn) copyBtn.addEventListener('click', function (e) { e.preventDefault(); copyContent(copyBtn); });
var delBtn = document.getElementById('delete-btn');
// #168: show the compact paste-created pill in the bottom corner, then fade it out
const createdPill = document.getElementById('created-pill');
if (createdPill) {
requestAnimationFrame(() => createdPill.classList.add('show'));
setTimeout(() => createdPill.classList.remove('show'), 4000);
}
if (delBtn) delBtn.addEventListener('click', function (e) { e.preventDefault(); redeem(); });
var statsToggle = document.getElementById('stats-toggle');
if (statsToggle) statsToggle.addEventListener('click', toggleStats);
+1
View File
@@ -28,6 +28,7 @@
<textarea class="editor" id="content" placeholder="Paste your code, text, or notes here…" spellcheck="false"></textarea>
<img id="file-preview" class="file-preview hidden" alt="File preview">
</div>
<div class="created-banner" id="created"></div>
<div class="actionbar">
<span class="hint">Ctrl+Enter to create</span>
<div class="spacer spacer-flex"></div>
+6 -1
View File
@@ -34,7 +34,12 @@
</div>
</div>
{{if .JustCreated}}
<div class="created-pill" id="created-pill" role="status">Paste Created</div>
<div class="float">
<div class="created-banner show">
Paste created. Link copied to clipboard: <a href="/{{.ID}}">{{.Host}}/{{.ID}}</a>
{{if .DeletionToken}} · deletion token: <code>{{.DeletionToken}}</code>{{end}}
</div>
</div>
{{end}}
{{if .Attachment}}
<div class="float">
+1 -1
View File
@@ -352,7 +352,7 @@ func (h *Handlers) HandlePasteView(w http.ResponseWriter, r *http.Request) {
token = c.Value
}
if justCreated && token != "" {
// one-time display of the deletion token via sessionStorage (#143)
// one-time display of the deletion token via the created banner
http.SetCookie(w, &http.Cookie{Name: "tok_" + row.ID, Value: token, Path: "/", MaxAge: 60, HttpOnly: true, SameSite: http.SameSiteLaxMode})
}
// Count the view for real page renders, deduped per-viewer within the