Compare commits
10
Commits
efddb98a19
...
033ee08b85
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
033ee08b85 | ||
|
|
0e5cfcd403 | ||
|
|
d402113f19 | ||
|
|
9692531a40 | ||
|
|
b801d7fb56 | ||
|
|
362a59690f | ||
|
|
6064404723 | ||
|
|
aa4d017631 | ||
|
|
6ef0de6e19 | ||
|
|
3fa2c93e22 |
@@ -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)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-12
@@ -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; }
|
||||||
@@ -211,13 +217,13 @@ body {
|
|||||||
.paste-title-bar { display: flex; align-items: center; gap: 12px; padding: 12px 18px; flex-wrap: wrap; }
|
.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; word-break: normal; overflow-wrap: anywhere; }
|
.paste-title-bar h1 { font-size: 29.2px; font-weight: 600; margin: 0; word-break: normal; overflow-wrap: anywhere; }
|
||||||
.stats-pill { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
|
.stats-pill { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
|
||||||
.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 { display: flex; align-items: center; gap: 16px; width: 100%; background: none; border: 0; border-bottom: 1px solid var(--border); 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-head:hover { color: var(--fg); background: var(--surface-2); }
|
||||||
.stats-chev { width: 18px; height: 18px; flex: none; transition: transform 0.15s ease; }
|
.stats-chev { width: 18px; height: 18px; flex: none; transition: transform 0.15s ease; }
|
||||||
.stats-pill.open .stats-chev { transform: rotate(180deg); }
|
.stats-pill.open .stats-chev { transform: rotate(180deg); }
|
||||||
.stats-summary { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; letter-spacing: .01em; }
|
.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; } }
|
@media (max-width: 640px) { .stats-summary { white-space: normal; word-break: break-word; } }
|
||||||
.stats-body { border-top: 1px solid var(--border); }
|
.stats-body { /* #169: divider under the dropdown title is the head's own persistent border-bottom */ }
|
||||||
.stats-grid { display: grid; grid-template-columns: max-content 1fr; gap: 6px 18px; padding: 12px 16px; font-size: 20.7px; }
|
.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); }
|
.stats-k { color: var(--muted-fg); }
|
||||||
.stats-v { color: var(--fg); word-break: break-all; }
|
.stats-v { color: var(--fg); word-break: break-all; }
|
||||||
@@ -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; }
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user