mirror of
https://github.com/hoornet/vega.git
synced 2026-06-08 06:01:57 -07:00
fix: article reader scroll lock + click-to-zoom for body images
WebKitGTK collapses body <img> elements to 0 height during re-decode, shrinking scrollHeight and clamping scrollTop — articles with body images locked partway down. Give every .prose-article img a fixed 16:9 aspect-ratio + object-fit: contain so the layout box survives the collapse and scrollHeight stays constant. Body images now click-to-open the existing ImageLightbox (with cursor: zoom-in affordance), and the lightbox image itself now closes on click instead of swallowing it.
This commit is contained in:
@@ -10,6 +10,7 @@ import { nip19 } from "@nostr-dev-kit/ndk";
|
||||
import { useProfile } from "../../hooks/useProfile";
|
||||
import { profileName } from "../../lib/utils";
|
||||
import { ZapModal } from "../zap/ZapModal";
|
||||
import { ImageLightbox } from "../shared/ImageLightbox";
|
||||
|
||||
// ── Types ────────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -138,6 +139,18 @@ export function ArticleView() {
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [headings, setHeadings] = useState<TocHeading[]>([]);
|
||||
const [activeId, setActiveId] = useState("");
|
||||
const [lightbox, setLightbox] = useState<{ images: string[]; index: number } | null>(null);
|
||||
|
||||
const handleContentClick = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
|
||||
const target = e.target as HTMLElement;
|
||||
if (!(target instanceof HTMLImageElement)) return;
|
||||
const root = contentRef.current;
|
||||
if (!root) return;
|
||||
const imgs = Array.from(root.querySelectorAll<HTMLImageElement>("img"));
|
||||
const index = imgs.indexOf(target);
|
||||
if (index < 0) return;
|
||||
setLightbox({ images: imgs.map((img) => img.src), index });
|
||||
}, []);
|
||||
|
||||
// Extract headings from rendered content and assign IDs
|
||||
useEffect(() => {
|
||||
@@ -398,8 +411,17 @@ export function ArticleView() {
|
||||
<div
|
||||
ref={contentRef}
|
||||
className="prose-article"
|
||||
onClick={handleContentClick}
|
||||
dangerouslySetInnerHTML={{ __html: bodyHtml }}
|
||||
/>
|
||||
{lightbox && (
|
||||
<ImageLightbox
|
||||
images={lightbox.images}
|
||||
index={lightbox.index}
|
||||
onClose={() => setLightbox(null)}
|
||||
onNavigate={(i) => setLightbox({ ...lightbox, index: i })}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Footer */}
|
||||
<div className="mt-10 pt-6 border-t border-border flex items-center justify-between">
|
||||
|
||||
@@ -60,12 +60,12 @@ export function ImageLightbox({ images, index, onClose, onNavigate }: ImageLight
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Image */}
|
||||
{/* Image — click to close (same gesture as clicking the backdrop) */}
|
||||
<img
|
||||
src={images[index]}
|
||||
alt={`Image ${index + 1} of ${images.length}`}
|
||||
className="max-w-[90vw] max-h-[90vh] object-contain select-none"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="max-w-[90vw] max-h-[90vh] object-contain select-none cursor-zoom-out"
|
||||
onClick={onClose}
|
||||
draggable={false}
|
||||
/>
|
||||
|
||||
|
||||
+4
-1
@@ -104,7 +104,10 @@ html.font-readable body {
|
||||
.prose-article a { color: var(--color-accent); text-decoration: underline; text-underline-offset: 3px; }
|
||||
.prose-article hr { border: none; border-top: 1px solid var(--color-border); margin: 2em 0; }
|
||||
.prose-article strong { color: var(--color-text); font-weight: 600; }
|
||||
.prose-article img { max-width: 100%; border-radius: 2px; margin: 1em 0; }
|
||||
/* WebKitGTK scroll-lock workaround: body <img> sometimes collapses to 0 height on re-decode,
|
||||
shrinking scrollHeight and clamping the scroll. A fixed aspect-ratio reserves a layout box
|
||||
that survives the collapse. object-fit: contain preserves the image's own ratio inside it. */
|
||||
.prose-article img { display: block; width: 100%; max-width: 100%; aspect-ratio: 16 / 9; object-fit: contain; background: var(--color-bg-raised); border-radius: 2px; margin: 1em 0; cursor: zoom-in; }
|
||||
|
||||
/* View transition fade-in */
|
||||
@keyframes fade-in {
|
||||
|
||||
Reference in New Issue
Block a user