10 Commits
Author SHA1 Message Date
fen 033ee08b85 Fix #169: persistent full-width divider under stats dropdown title
CI / test (pull_request) Successful in 43s
CI / docker (pull_request) Skipped
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:38:58 -05:00
fen 0e5cfcd403 Merge pull request 'Revise notification pill (#168)' (#181) from fix-168 into dev
CI / test (push) Successful in 42s
CI / docker (push) Successful in 50s
2026-09-10 16:38:08 +00:00
fen d402113f19 Merge pull request 'Fix #167 rev: gutter numbers realign with wrapped lines (missing .gutline CSS)' (#183) from fix-167-rev into dev
CI / test (push) Successful in 41s
CI / docker (push) Successful in 54s
2026-09-10 16:37:57 +00:00
fen 9692531a40 Fix #167 rev: add missing .gutline CSS so gutter numbers align with wrapped lines
CI / test (pull_request) Successful in 41s
CI / docker (pull_request) Skipped
QA of PR #179 found paste-lines.js creates .gutline spans but no CSS rule
existed, so all gutter numbers rendered inline on one visual row. Stack
them one per row under .code .gutter; blank fill spans inherit it.
2026-09-10 11:35:39 -05:00
fen b801d7fb56 Revise notification pill per reopened #168: short text, smaller, side placement
CI / test (pull_request) Successful in 40s
CI / docker (pull_request) Skipped
- Pill now reads only 'Paste Created' (no combined 'Paste Created - Link Copied')
- Copy fallback toast now says 'Link Copied'
- Pill shrunk (smaller padding/font) and anchored tighter to the right side

Fixes #168
2026-09-10 11:33:45 -05:00
fen 362a59690f Merge pull request 'Clean up notification pill (#168)' (#178) from fix-168 into dev
CI / test (push) Successful in 40s
CI / docker (push) Successful in 50s
2026-09-10 16:25:56 +00:00
fen 6064404723 Merge remote fix-168: keep corner pill, cookie-based token flow
CI / test (pull_request) Successful in 40s
CI / docker (pull_request) Skipped
2026-09-10 11:25:15 -05:00
fen aa4d017631 Fix #168: compact corner notification pill for paste creation 2026-09-10 11:24:24 -05:00
fen 6ef0de6e19 Merge remote-tracking branch 'origin/dev' into qa178
# Conflicts:
#	internal/web/templates/paste.html
2026-09-10 11:23:47 -05:00
fen 3fa2c93e22 clean up notification pill wording, size, and placement (#168)
CI / test (pull_request) Successful in 39s
CI / docker (pull_request) Skipped
2026-09-10 11:18:57 -05:00
6 changed files with 36 additions and 21 deletions
+10 -2
View File
@@ -91,7 +91,15 @@ func TestCreatedBannerViaCookie(t *testing.T) {
if rec2.Code != 200 { if rec2.Code != 200 {
t.Fatalf("paste view: got %d", rec2.Code) t.Fatalf("paste view: got %d", rec2.Code)
} }
if !strings.Contains(rec2.Body.String(), tok) { // #168: the created pill no longer prints the deletion token; the token
t.Error("created banner does not show the deletion token (#143 cookie flow broken)") // 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)")
} }
} }
+16 -10
View File
@@ -197,11 +197,17 @@ body {
border: 1px solid var(--border); border-radius: var(--radius); 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; color: var(--fg); font: inherit; font-size: 21.6px; width: 130px;
} }
.created-banner { /* #168: compact side notification pill (paste-created feedback) */
display: none; padding: 10px 16px; font-size: 22.4px; background: var(--surface-2); .created-pill {
border-bottom: 1px solid var(--border); word-break: break-all; 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 a { color: var(--accent); } .created-pill.show { opacity: 1; transform: translateY(0); }
@media (max-width: 640px) { .created-pill { right: 12px; bottom: 12px; } }
/* paste view */ /* paste view */
.meta-bar { display: flex; align-items: center; gap: 12px; padding: 12px 18px; flex-wrap: wrap; } .meta-bar { display: flex; align-items: center; gap: 12px; padding: 12px 18px; flex-wrap: wrap; }
@@ -293,6 +299,8 @@ html[data-wrap] .float { overflow-x: hidden; }
.codebody { padding: 0 18px; white-space: pre; } .codebody { padding: 0 18px; white-space: pre; }
/* #167: each logical line is its own block so offsetTop identifies its first visual row */ /* #167: each logical line is its own block so offsetTop identifies its first visual row */
.codeline { display: block; } .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) */ /* syntax highlight tokens (#1) */
.tok-kw { color: #c792ea; } .tok-kw { color: #c792ea; }
.tok-str { color: #a5e075; } .tok-str { color: #a5e075; }
@@ -388,15 +396,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; } .seg input, .toggle input { accent-color: var(--accent); width: 16px; height: 16px; margin: 0; }
/* toast (#19) */ /* toast (#19, #168): small pill anchored to the corner, out of content flow */
.toast { .toast {
position: fixed; left: 50%; bottom: 32px; transform: translateX(-50%) translateY(8px); position: fixed; right: 16px; bottom: 16px;
background: var(--surface-2); color: var(--fg); border: 1px solid var(--border); background: var(--surface-2); color: var(--fg); border: 1px solid var(--border);
border-radius: var(--radius-sm); padding: 6px 18px; font-size: 20.7px; border-radius: var(--radius-sm); padding: 6px 12px; font-size: 13px;
opacity: 0; pointer-events: none; transition: opacity .25s ease, transform .25s ease; z-index: 200; 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); box-shadow: 0 4px 16px rgba(0,0,0,.25);
} }
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); } .toast.show { opacity: 1; transform: translateY(0); }
/* status variants (#16) */ /* status variants (#16) */
.toast.success { border-color: var(--ok); color: var(--ok); } .toast.success { border-color: var(--ok); color: var(--ok); }
.toast.error { border-color: var(--err); color: var(--err); } .toast.error { border-color: var(--err); color: var(--err); }
@@ -625,7 +633,6 @@ a.admin-link:hover { color: var(--fg); text-decoration: underline; }
.code { font-size: 13px; } .code { font-size: 13px; }
.codebody { padding: 0 12px; } .codebody { padding: 0 12px; }
.footnote { font-size: 12px; padding: 8px 12px; gap: 10px; } .footnote { font-size: 12px; padding: 8px 12px; gap: 10px; }
.created-banner { font-size: 13px; }
.iconbtn { font-size: 13px; padding: 6px 10px; } .iconbtn { font-size: 13px; padding: 6px 10px; }
/* unlock card */ /* unlock card */
@@ -804,4 +811,3 @@ button[type="submit"]:focus-visible,
.mt18 { margin-top: 18px; } .mt18 { margin-top: 18px; }
.toggle-inline { display: inline-flex; } .toggle-inline { display: inline-flex; }
.wrap-normal { word-break: normal; overflow-wrap: break-word; } .wrap-normal { word-break: normal; overflow-wrap: break-word; }
.created-banner.show { display: block; }
+8 -1
View File
@@ -25,7 +25,7 @@ function copyContent(btn) {
clearTimeout(btn._okh); clearTimeout(btn._okh);
btn._okh = setTimeout(() => { btn.classList.remove('ok'); btn.textContent = 'copy'; }, 2000); btn._okh = setTimeout(() => { btn.classList.remove('ok'); btn.textContent = 'copy'; }, 2000);
} else { } else {
toast('Copied', 'success'); toast('Link Copied', 'success');
} }
} }
function redeem() { function redeem() {
@@ -42,6 +42,13 @@ var PASTE_ID = document.currentScript.getAttribute('data-paste-id');
var copyBtn = document.getElementById('copy-btn'); var copyBtn = document.getElementById('copy-btn');
if (copyBtn) copyBtn.addEventListener('click', function (e) { e.preventDefault(); copyContent(copyBtn); }); if (copyBtn) copyBtn.addEventListener('click', function (e) { e.preventDefault(); copyContent(copyBtn); });
var delBtn = document.getElementById('delete-btn'); 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(); }); if (delBtn) delBtn.addEventListener('click', function (e) { e.preventDefault(); redeem(); });
var statsToggle = document.getElementById('stats-toggle'); var statsToggle = document.getElementById('stats-toggle');
if (statsToggle) statsToggle.addEventListener('click', toggleStats); if (statsToggle) statsToggle.addEventListener('click', toggleStats);
-1
View File
@@ -28,7 +28,6 @@
<textarea class="editor" id="content" placeholder="Paste your code, text, or notes here…" spellcheck="false"></textarea> <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"> <img id="file-preview" class="file-preview hidden" alt="File preview">
</div> </div>
<div class="created-banner" id="created"></div>
<div class="actionbar"> <div class="actionbar">
<span class="hint">Ctrl+Enter to create</span> <span class="hint">Ctrl+Enter to create</span>
<div class="spacer spacer-flex"></div> <div class="spacer spacer-flex"></div>
+1 -6
View File
@@ -34,12 +34,7 @@
</div> </div>
</div> </div>
{{if .JustCreated}} {{if .JustCreated}}
<div class="float"> <div class="created-pill" id="created-pill" role="status">Paste Created</div>
<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}} {{end}}
{{if .Attachment}} {{if .Attachment}}
<div class="float"> <div class="float">
+1 -1
View File
@@ -352,7 +352,7 @@ func (h *Handlers) HandlePasteView(w http.ResponseWriter, r *http.Request) {
token = c.Value token = c.Value
} }
if justCreated && token != "" { if justCreated && token != "" {
// one-time display of the deletion token via the created banner // one-time display of the deletion token via sessionStorage (#143)
http.SetCookie(w, &http.Cookie{Name: "tok_" + row.ID, Value: token, Path: "/", MaxAge: 60, HttpOnly: true, SameSite: http.SameSiteLaxMode}) 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 // Count the view for real page renders, deduped per-viewer within the