#267: jump to top and bottom buttons for long content #270
@@ -884,3 +884,22 @@ button[type="submit"]:focus-visible,
|
|||||||
.created-banner { display: block; }
|
.created-banner { display: block; }
|
||||||
/* #167: gutter rows for wrapped paste view — one row per visual code line */
|
/* #167: gutter rows for wrapped paste view — one row per visual code line */
|
||||||
.gutline { display: block; }
|
.gutline { display: block; }
|
||||||
|
|
||||||
|
/* jump to top/bottom (#267): fixed pills bottom-right, only for tall content */
|
||||||
|
.jump-nav {
|
||||||
|
position: fixed; right: 16px; bottom: 16px;
|
||||||
|
display: flex; flex-direction: column; gap: 8px;
|
||||||
|
opacity: 0; pointer-events: none; transform: translateY(6px);
|
||||||
|
transition: opacity .25s ease, transform .25s ease; z-index: 190;
|
||||||
|
}
|
||||||
|
.jump-nav.show { opacity: 1; pointer-events: auto; transform: translateY(0); }
|
||||||
|
.jump-nav button {
|
||||||
|
background: var(--surface-2); color: var(--fg);
|
||||||
|
border: 1px solid var(--border); border-radius: var(--radius-sm);
|
||||||
|
padding: 6px 14px; font-size: 13px; cursor: pointer;
|
||||||
|
box-shadow: 0 4px 16px rgba(0,0,0,.25);
|
||||||
|
appearance: none; font-family: inherit;
|
||||||
|
}
|
||||||
|
.jump-nav button:hover { border-color: var(--accent); color: var(--accent); }
|
||||||
|
.jump-nav button:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
|
||||||
|
.jump-nav button.hidden { display: none; }
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
// #267: jump to top / bottom for long content.
|
||||||
|
// Works on the paste view (page scroll) and the /new editor (textarea scroll).
|
||||||
|
// Buttons only appear when the content is tall enough to need them.
|
||||||
|
(function () {
|
||||||
|
const nav = document.querySelector('.jump-nav');
|
||||||
|
if (!nav) return;
|
||||||
|
const topBtn = nav.querySelector('.jump-top');
|
||||||
|
const botBtn = nav.querySelector('.jump-bottom');
|
||||||
|
|
||||||
|
// Target: the editor textarea on /new, else the window (paste view).
|
||||||
|
const editor = document.getElementById('content');
|
||||||
|
const target = editor || window;
|
||||||
|
|
||||||
|
function metrics() {
|
||||||
|
if (editor) {
|
||||||
|
return {
|
||||||
|
top: editor.scrollTop,
|
||||||
|
max: editor.scrollHeight - editor.clientHeight,
|
||||||
|
view: editor.clientHeight
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const doc = document.documentElement;
|
||||||
|
return {
|
||||||
|
top: window.scrollY,
|
||||||
|
max: doc.scrollHeight - window.innerHeight,
|
||||||
|
view: window.innerHeight
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function refresh() {
|
||||||
|
const m = metrics();
|
||||||
|
const tall = m.max > m.view * 1.5;
|
||||||
|
nav.classList.toggle('show', tall);
|
||||||
|
if (!tall) return;
|
||||||
|
// Hide the button for the edge you are already at.
|
||||||
|
topBtn.classList.toggle('hidden', m.top < 8);
|
||||||
|
botBtn.classList.toggle('hidden', m.max - m.top < 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
function jump(toTop) {
|
||||||
|
const y = toTop ? 0 : 99999999;
|
||||||
|
if (editor) editor.scrollTo({ top: toTop ? 0 : editor.scrollHeight, behavior: 'smooth' });
|
||||||
|
else window.scrollTo({ top: toTop ? 0 : document.documentElement.scrollHeight, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
|
||||||
|
topBtn.addEventListener('click', () => jump(true));
|
||||||
|
botBtn.addEventListener('click', () => jump(false));
|
||||||
|
|
||||||
|
target.addEventListener('scroll', refresh, { passive: true });
|
||||||
|
if (editor) editor.addEventListener('input', refresh);
|
||||||
|
window.addEventListener('resize', refresh);
|
||||||
|
refresh();
|
||||||
|
})();
|
||||||
@@ -90,4 +90,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="/static/new.js" defer></script>
|
<script src="/static/new.js" defer></script>
|
||||||
|
<script src="/static/jump.js" defer></script>
|
||||||
|
<div class="jump-nav">
|
||||||
|
<button type="button" class="jump-top" aria-label="Jump to top">Top</button>
|
||||||
|
<button type="button" class="jump-bottom" aria-label="Jump to bottom">Bottom</button>
|
||||||
|
</div>
|
||||||
{{template "foot" .}}
|
{{template "foot" .}}
|
||||||
|
|||||||
@@ -56,7 +56,12 @@
|
|||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="jump-nav">
|
||||||
|
<button type="button" class="jump-top" aria-label="Jump to top">Top</button>
|
||||||
|
<button type="button" class="jump-bottom" aria-label="Jump to bottom">Bottom</button>
|
||||||
|
</div>
|
||||||
<input type="hidden" id="raw-content" value="{{.ContentAttr}}">
|
<input type="hidden" id="raw-content" value="{{.ContentAttr}}">
|
||||||
<script src="/static/paste.js" defer data-paste-id="{{.ID}}"></script>
|
<script src="/static/paste.js" defer data-paste-id="{{.ID}}"></script>
|
||||||
<script src="/static/paste-lines.js" defer></script>
|
<script src="/static/paste-lines.js" defer></script>
|
||||||
|
<script src="/static/jump.js" defer></script>
|
||||||
{{template "foot" .}}
|
{{template "foot" .}}
|
||||||
|
|||||||
Reference in New Issue
Block a user