diff --git a/doc/agent-smoke-cases.md b/doc/agent-smoke-cases.md index e213c3b..49ab309 100644 --- a/doc/agent-smoke-cases.md +++ b/doc/agent-smoke-cases.md @@ -356,6 +356,31 @@ bash scripts/agent-smoke.sh --cdp-port 9222 --cases AGENT-CHROME-001,AGENT-CHROM 2. 对应滑杆 `disabled=true` 3. 提示文案明确说明该设置仅在 `智能预览` 下生效 +### AGENT-CHROME-018:图片预览应全屏覆盖并支持滚轮缩放 + +范围: +正文图片点击预览、全屏覆盖、再次点击关闭、滚轮缩放 + +状态: +当前不在默认批量脚本中;用例内会显式切到 `智能预览` + +建议目标: +优先选列表前几条中正文自带图片的公开主题 + +步骤: + +1. 打开任一主题抽屉 +2. 在 `选项` 中切到 `智能预览` +3. 点击正文中的第一张图片 +4. 在预览层内滚轮向上缩放一次 +5. 再点击预览图片本身 + +断言: + +1. 图片预览层应覆盖整个视口,而不是只局限在右侧抽屉宽度内 +2. 预览打开后再次点击预览图片本身,应立即关闭预览层 +3. 在预览层内滚轮缩放后,预览图片的缩放值应大于默认的 `1` + ## 3. 记录格式 执行完用例后,建议按下面格式留证据: diff --git a/src/content.css b/src/content.css index 30e05fa..1d7beec 100644 --- a/src/content.css +++ b/src/content.css @@ -42,6 +42,13 @@ body.ld-drawer-resizing * { pointer-events: none; } +#ld-image-preview-root { + position: fixed; + inset: 0; + z-index: 2147483647; + pointer-events: none; +} + body.ld-drawer-resizing #ld-drawer-root, body.ld-drawer-resizing { transition: none !important; @@ -82,10 +89,10 @@ body.ld-drawer-page-open #ld-drawer-root { pointer-events: auto; } -#ld-drawer-root .ld-image-preview { - position: absolute; +#ld-image-preview-root .ld-image-preview { + position: fixed; inset: 0; - z-index: 8; + z-index: 1; display: flex; align-items: center; justify-content: center; @@ -94,13 +101,14 @@ body.ld-drawer-page-open #ld-drawer-root { backdrop-filter: blur(6px); opacity: 1; animation: ld-image-preview-fade-in 180ms ease-out; + pointer-events: auto; } -#ld-drawer-root .ld-image-preview[hidden] { +#ld-image-preview-root .ld-image-preview[hidden] { display: none !important; } -#ld-drawer-root .ld-image-preview-close { +#ld-image-preview-root .ld-image-preview-close { position: absolute; top: 14px; right: 18px; @@ -115,13 +123,13 @@ body.ld-drawer-page-open #ld-drawer-root { transition: background-color 0.18s ease, border-color 0.18s ease, transform 0.18s ease; } -#ld-drawer-root .ld-image-preview-close:hover { +#ld-image-preview-root .ld-image-preview-close:hover { background: rgba(15, 23, 42, 0.58); border-color: rgba(255, 255, 255, 0.44); transform: translateY(-1px); } -#ld-drawer-root .ld-image-preview-stage { +#ld-image-preview-root .ld-image-preview-stage { display: flex; align-items: center; justify-content: center; @@ -130,7 +138,7 @@ body.ld-drawer-page-open #ld-drawer-root { overflow: hidden; } -#ld-drawer-root .ld-image-preview-image { +#ld-image-preview-root .ld-image-preview-image { display: block; max-width: 100%; max-height: 100%; @@ -143,18 +151,19 @@ body.ld-drawer-page-open #ld-drawer-root { transition: opacity 0.2s ease, transform 0.24s ease, box-shadow 0.24s ease; transform-origin: center center; will-change: transform; + cursor: zoom-out; } -#ld-drawer-root .ld-image-preview-image.is-ready { +#ld-image-preview-root .ld-image-preview-image.is-ready { opacity: 1; transform: translateY(0) scale(var(--ld-image-preview-scale, 1)); } -#ld-drawer-root .ld-image-preview.is-zoomed { +#ld-image-preview-root .ld-image-preview.is-zoomed { cursor: zoom-out; } -#ld-drawer-root .ld-image-preview.is-zoomed .ld-image-preview-image { +#ld-image-preview-root .ld-image-preview.is-zoomed .ld-image-preview-image { box-shadow: 0 24px 54px rgba(0, 0, 0, 0.44); } @@ -991,14 +1000,14 @@ body.ld-drawer-page-open #ld-drawer-root { width: 100vw; } - #ld-drawer-root .ld-image-preview { + #ld-image-preview-root .ld-image-preview { padding: max(18px, env(safe-area-inset-top, 0px) + 8px) 12px max(12px, env(safe-area-inset-bottom, 0px) + 8px); align-items: stretch; } - #ld-drawer-root .ld-image-preview-close { + #ld-image-preview-root .ld-image-preview-close { top: max(10px, env(safe-area-inset-top, 0px) + 2px); right: 12px; padding: 8px 12px; @@ -1006,13 +1015,13 @@ body.ld-drawer-page-open #ld-drawer-root { backdrop-filter: blur(10px); } - #ld-drawer-root .ld-image-preview-stage { + #ld-image-preview-root .ld-image-preview-stage { align-items: center; padding-top: 40px; padding-bottom: 6px; } - #ld-drawer-root .ld-image-preview-image { + #ld-image-preview-root .ld-image-preview-image { max-width: 100%; max-height: calc(100dvh - 88px); border-radius: 12px; @@ -1068,9 +1077,9 @@ body.ld-drawer-page-open #ld-drawer-root { } @media (prefers-reduced-motion: reduce) { - #ld-drawer-root .ld-image-preview, - #ld-drawer-root .ld-image-preview-close, - #ld-drawer-root .ld-image-preview-image { + #ld-image-preview-root .ld-image-preview, + #ld-image-preview-root .ld-image-preview-close, + #ld-image-preview-root .ld-image-preview-image { animation: none; transition: none; } diff --git a/src/content.js b/src/content.js index 2b9384e..4691310 100644 --- a/src/content.js +++ b/src/content.js @@ -1,5 +1,6 @@ (function () { const ROOT_ID = "ld-drawer-root"; + const IMAGE_PREVIEW_ROOT_ID = "ld-image-preview-root"; const PAGE_OPEN_CLASS = "ld-drawer-page-open"; const PAGE_IFRAME_OPEN_CLASS = "ld-drawer-page-iframe-open"; const ACTIVE_LINK_CLASS = "ld-drawer-topic-link-active"; @@ -102,6 +103,7 @@ replySubmitButton: null, replyCancelButton: null, replyStatus: null, + imagePreviewRoot: null, imagePreview: null, imagePreviewImage: null, imagePreviewCloseButton: null, @@ -296,16 +298,22 @@ - + `; + + const imagePreviewRoot = document.createElement("div"); + imagePreviewRoot.id = IMAGE_PREVIEW_ROOT_ID; + imagePreviewRoot.setAttribute("aria-hidden", "true"); + imagePreviewRoot.innerHTML = ` + `; - document.body.appendChild(root); + document.body.append(root, imagePreviewRoot); state.root = root; state.header = root.querySelector(".ld-drawer-header"); @@ -321,9 +329,10 @@ state.replySubmitButton = root.querySelector('[data-action="submit"]'); state.replyCancelButton = root.querySelector('[data-action="cancel"]'); state.replyStatus = root.querySelector(".ld-reply-status"); - state.imagePreview = root.querySelector(".ld-image-preview"); - state.imagePreviewImage = root.querySelector(".ld-image-preview-image"); - state.imagePreviewCloseButton = root.querySelector(".ld-image-preview-close"); + state.imagePreviewRoot = imagePreviewRoot; + state.imagePreview = imagePreviewRoot.querySelector(".ld-image-preview"); + state.imagePreviewImage = imagePreviewRoot.querySelector(".ld-image-preview-image"); + state.imagePreviewCloseButton = imagePreviewRoot.querySelector(".ld-image-preview-close"); state.openInTab = root.querySelector(".ld-drawer-link"); state.settingsPanel = root.querySelector(".ld-drawer-settings"); state.settingsCard = root.querySelector(".ld-drawer-settings-card"); @@ -352,6 +361,8 @@ state.replyTextarea.addEventListener("paste", handleReplyTextareaPaste); root.addEventListener("click", handleDrawerRootClick); root.addEventListener("wheel", handleDrawerRootWheel, { passive: false }); + imagePreviewRoot.addEventListener("click", handleImagePreviewClick); + imagePreviewRoot.addEventListener("wheel", handleImagePreviewWheel, { passive: false }); state.drawerBody.addEventListener("scroll", handleDrawerBodyScroll, { passive: true }); state.settingsPanel.addEventListener("click", handleSettingsPanelClick); state.settingsPanel.addEventListener("input", handleSettingsInput); @@ -393,6 +404,10 @@ return; } + if (!state.imagePreview?.hidden && target.closest(`#${IMAGE_PREVIEW_ROOT_ID}`)) { + return; + } + if (!state.settingsPanel?.hidden && !target.closest(".ld-drawer-settings-card") && !target.closest(".ld-drawer-settings-toggle")) { setSettingsPanelOpen(false); } @@ -2082,14 +2097,6 @@ return; } - if (!state.imagePreview?.hidden) { - if (target.closest(".ld-image-preview-close") || !target.closest(".ld-image-preview-image")) { - event.preventDefault(); - closeImagePreview(); - } - return; - } - const image = target.closest(".ld-post-body img"); if (!(image instanceof HTMLImageElement)) { return; @@ -2100,8 +2107,23 @@ openImagePreview(image); } + function handleImagePreviewClick(event) { + if (state.imagePreview?.hidden) { + return; + } + + const target = event.target; + if (!(target instanceof Element) || !target.closest(".ld-image-preview")) { + return; + } + + event.preventDefault(); + event.stopPropagation(); + closeImagePreview(); + } + function openImagePreview(image) { - if (!state.imagePreview || !state.imagePreviewImage) { + if (!state.imagePreviewRoot || !state.imagePreview || !state.imagePreviewImage) { return; } @@ -2114,6 +2136,7 @@ state.imagePreviewImage.src = previewSrc; state.imagePreviewImage.alt = image.alt || "图片预览"; state.imagePreviewImage.classList.remove("is-ready"); + state.imagePreviewRoot.setAttribute("aria-hidden", "false"); state.imagePreview.hidden = false; state.imagePreview.setAttribute("aria-hidden", "false"); if (state.imagePreviewImage.complete) { @@ -2126,10 +2149,11 @@ } function closeImagePreview() { - if (!state.imagePreview || !state.imagePreviewImage) { + if (!state.imagePreviewRoot || !state.imagePreview || !state.imagePreviewImage) { return; } + state.imagePreviewRoot.setAttribute("aria-hidden", "true"); state.imagePreview.hidden = true; state.imagePreview.setAttribute("aria-hidden", "true"); resetImagePreviewScale(); @@ -2142,26 +2166,38 @@ state.imagePreviewImage?.classList.add("is-ready"); } - function handleDrawerRootWheel(event) { + function handleImagePreviewWheel(event) { + if (state.imagePreview?.hidden) { + return; + } + const target = event.target; if (!(target instanceof Element)) { return; } - if (!state.imagePreview?.hidden && target.closest(".ld-image-preview-stage")) { - event.preventDefault(); + event.preventDefault(); - const nextScale = clampImagePreviewScale( - state.imagePreviewScale + (event.deltaY < 0 ? IMAGE_PREVIEW_SCALE_STEP : -IMAGE_PREVIEW_SCALE_STEP) - ); + if (!target.closest(".ld-image-preview-stage")) { + return; + } - if (nextScale === state.imagePreviewScale) { - return; - } + const nextScale = clampImagePreviewScale( + state.imagePreviewScale + (event.deltaY < 0 ? IMAGE_PREVIEW_SCALE_STEP : -IMAGE_PREVIEW_SCALE_STEP) + ); + + if (nextScale === state.imagePreviewScale) { + return; + } - updateImagePreviewTransformOrigin(event.clientX, event.clientY); - state.imagePreviewScale = nextScale; - applyImagePreviewScale(); + updateImagePreviewTransformOrigin(event.clientX, event.clientY); + state.imagePreviewScale = nextScale; + applyImagePreviewScale(); + } + + function handleDrawerRootWheel(event) { + const target = event.target; + if (!(target instanceof Element)) { return; }