Clean up notification pill (#168) #178

Merged
fen merged 4 commits from fix-168 into dev 2026-09-10 16:25:56 +00:00
6 changed files with 33 additions and 20 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)")
} }
} }
+14 -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 corner 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: 16px; bottom: 16px; z-index: 200;
background: var(--surface-2); color: var(--ok); border: 1px solid var(--ok);
border-radius: var(--radius-sm); padding: 4px 12px; font-size: 13px;
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; }
@@ -386,15 +392,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); }
@@ -623,7 +629,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 */
@@ -802,4 +807,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; }
+7
View File
@@ -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 · Link Copied</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