From d4e64f4e50e67dca99c61c7266364a940d4212eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Fri, 7 Aug 2026 10:30:11 +0200 Subject: [PATCH] line links in s --- Cargo.lock | 2 +- Cargo.toml | 2 +- static/css/styles.css | 47 ++++++++++++++++++++++++++++++++----------- static/js/public.js | 2 ++ static/public.html | 1 + 5 files changed, 40 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a844209..47c4aa6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2581,7 +2581,7 @@ dependencies = [ [[package]] name = "rustpad" -version = "0.2.51" +version = "0.2.52" dependencies = [ "argon2", "aws-config", diff --git a/Cargo.toml b/Cargo.toml index 839f9eb..3b4c5de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustpad" -version = "0.2.51" +version = "0.2.52" edition = "2024" rust-version = "1.94" description = "Collaborative Markdown notepad built with Axum, WebSockets and SQLite, PostgreSQL and MySQL" diff --git a/static/css/styles.css b/static/css/styles.css index 391e688..32bfe80 100644 --- a/static/css/styles.css +++ b/static/css/styles.css @@ -7350,6 +7350,19 @@ dialog::backdrop { scroll-margin-top: 88px; } +/* + * Line linking is opt-in on published notes. When regular line numbers are + * hidden, enabling links restores the same gutter so link targets stay inside + * the document instead of protruding past its left edge. + */ +.public-page .public-content { + transition: padding-left .18s ease; +} + +.public-page.hide-preview-line-numbers.line-links-enabled .public-content { + padding-left: clamp(24px, 4vw, 52px); +} + /* * The permalink occupies the same gutter slot as the regular source number. * It stays invisible until the row is approached, then behaves like the @@ -7360,7 +7373,7 @@ dialog::backdrop { top: 0; left: var(--preview-line-left, -50px); z-index: 3; - display: block; + display: none; width: 32px; height: 1.32em; padding: 0 2px 0 0; @@ -7378,30 +7391,34 @@ dialog::backdrop { transition: opacity .14s ease, transform .14s ease, background-color .14s ease, color .14s ease; } -.public-content .preview-source-line::before { +.public-page.line-links-enabled .public-content .public-permalink { + display: block; +} + +.public-page.line-links-enabled .public-content .preview-source-line::before { transition: opacity .1s ease; } -.public-content .preview-source-line:hover::before, -.public-content .preview-source-line:focus-within::before { +.public-page.line-links-enabled .public-content .preview-source-line:hover::before, +.public-page.line-links-enabled .public-content .preview-source-line:focus-within::before { opacity: 0; } -.public-content .preview-source-line:hover > .public-permalink, -.public-content .preview-source-line:focus-within > .public-permalink, -.public-content .public-permalink:focus-visible { +.public-page.line-links-enabled .public-content .preview-source-line:hover > .public-permalink, +.public-page.line-links-enabled .public-content .preview-source-line:focus-within > .public-permalink, +.public-page.line-links-enabled .public-content .public-permalink:focus-visible { opacity: 1; transform: translateX(0); } -.public-content .public-permalink:hover, -.public-content .public-permalink:focus-visible { +.public-page.line-links-enabled .public-content .public-permalink:hover, +.public-page.line-links-enabled .public-content .public-permalink:focus-visible { background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--text); outline: none; } -.public-content .public-permalink.is-copied { +.public-page.line-links-enabled .public-content .public-permalink.is-copied { background: color-mix(in srgb, var(--success) 18%, transparent); color: var(--text); opacity: 1; @@ -7415,16 +7432,22 @@ dialog::backdrop { } @media (hover: none), (pointer: coarse) { - .public-content .public-permalink { + .public-page.line-links-enabled .public-content .public-permalink { opacity: .62; transform: none; } - .public-content .preview-source-line::before { + .public-page.line-links-enabled .public-content .preview-source-line::before { opacity: 0; } } +@media (prefers-reduced-motion: reduce) { + .public-page .public-content { + transition: none; + } +} + /* Per-user, per-note navigation-bar focus mode ---------------------------- */ .pad-page .navbar-collapse-toggle { position: fixed; diff --git a/static/js/public.js b/static/js/public.js index e72d8a9..1ac6456 100644 --- a/static/js/public.js +++ b/static/js/public.js @@ -21,6 +21,7 @@ import { loadHighlight, loadMediaPlayer, loadMermaid } from "@rustpad/vendor-lib const token = location.pathname.split("/").filter(Boolean)[1]; const content = document.querySelector("#public-content"); const lineNumbersToggle = document.querySelector("#public-line-numbers-toggle"); +const lineLinksToggle = document.querySelector("#public-line-links-toggle"); const fullWidthToggle = document.querySelector("#public-full-width-toggle"); const fullWidthStorageKey = "rustpad:public-full-width"; const passwordDialog = document.querySelector("#public-password-dialog"), passwordForm = document.querySelector("#public-password-form"), passwordInput = document.querySelector("#public-password"), passwordError = document.querySelector("#public-password-error"); @@ -221,6 +222,7 @@ content.addEventListener("click", async event => { window.addEventListener("hashchange", () => { publicAnchorSettleCleanup?.(); scrollToPublicAnchor(location.hash, "smooth"); }); content.addEventListener("change", async event => { const box = event.target.closest(".task-checkbox"); if (!box || box.disabled) return; const previous = !box.checked; box.disabled = true; try { const page = await api(`/api/public/${encodeURIComponent(token)}/tasks`, { method: "POST", headers: pageHeaders(), body: JSON.stringify({ source_line: Number(box.dataset.sourceLine), checked: box.checked }) }); document.querySelector("#public-meta").textContent = `Updated: ${new Date(page.updated_at).toLocaleString("en-US")} ยท tasks can be updated`; toast("Task saved"); } catch (error) { box.checked = previous; toast(error.message); } finally { box.disabled = false; } }); lineNumbersToggle.addEventListener("change", () => { document.body.classList.toggle("hide-preview-line-numbers", !lineNumbersToggle.checked); }); +lineLinksToggle.addEventListener("change", () => { document.body.classList.toggle("line-links-enabled", lineLinksToggle.checked); }); fullWidthToggle.addEventListener("change", () => setFullWidth(fullWidthToggle.checked)); window.addEventListener("resize", () => requestAnimationFrame(() => alignPreviewLineNumbers(content))); passwordForm.addEventListener("submit", async event => { event.preventDefault(); pagePassword = passwordInput.value; await initialize(); }); diff --git a/static/public.html b/static/public.html index e3d8789..78d19d8 100644 --- a/static/public.html +++ b/static/public.html @@ -17,6 +17,7 @@ RustPad
+