diff --git a/tools/extra-panels/mdm.js b/tools/extra-panels/mdm.js index dc804e9f..80ee45e8 100644 --- a/tools/extra-panels/mdm.js +++ b/tools/extra-panels/mdm.js @@ -6876,7 +6876,7 @@ a{color:${v.accent}} return true; } - /** 点预览里的图片 → 小工具条改尺寸/对齐 */ + /** 点预览里的图片 → 小工具条改尺寸/对齐(单击;双击放大见 bindPreviewImgLightbox) */ function openImageTools(img) { const rel = img.getAttribute("data-mdm-asset") || ""; const alt = img.getAttribute("alt") || ""; @@ -6926,6 +6926,198 @@ a{color:${v.accent}} }, 0); } + /** + * 双击预览图片 → 全屏放大(与单击尺寸条互不干扰)。 + * lightbox 挂 body + html2canvas-ignore,不影响导出 PNG / 复制预览。 + */ + function bindPreviewImgLightbox() { + if (!els.preview || els.preview.dataset.mdmLightboxBound === "1") return; + els.preview.dataset.mdmLightboxBound = "1"; + + let dlg = document.getElementById("mdm-lightbox"); + if (!dlg) { + dlg = document.createElement("dialog"); + dlg.id = "mdm-lightbox"; + dlg.className = "mdm-lightbox"; + dlg.setAttribute("data-html2canvas-ignore", "true"); + dlg.setAttribute("aria-label", "图片放大预览"); + dlg.innerHTML = + `
` + + `
` + + `

` + + `` + + `
` + + `
` + + `` + + `
` + + `
` + + `` + + `100%` + + `` + + `` + + `
`; + document.body.appendChild(dlg); + } + + const stage = dlg.querySelector("#mdm-lightbox-stage"); + const imgEl = dlg.querySelector("#mdm-lightbox-img"); + const cap = dlg.querySelector("#mdm-lightbox-cap"); + const pctEl = dlg.querySelector("#mdm-lightbox-zoom-pct"); + const closeBtn = dlg.querySelector("#mdm-lightbox-close"); + const zoomInBtn = dlg.querySelector("#mdm-lightbox-zoom-in"); + const zoomOutBtn = dlg.querySelector("#mdm-lightbox-zoom-out"); + const zoomResetBtn = dlg.querySelector("#mdm-lightbox-zoom-reset"); + if (!stage || !imgEl) return; + + const view = { scale: 1, x: 0, y: 0, dragging: false, lastX: 0, lastY: 0 }; + + function syncTransform() { + imgEl.style.transform = `translate(calc(-50% + ${view.x}px), calc(-50% + ${view.y}px)) scale(${view.scale})`; + if (pctEl) pctEl.textContent = `${Math.round(view.scale * 100)}%`; + } + + function fitImage() { + const sw = stage.clientWidth || 1; + const sh = stage.clientHeight || 1; + const nw = imgEl.naturalWidth || 1; + const nh = imgEl.naturalHeight || 1; + view.scale = Math.min(1, sw / nw, sh / nh); + view.x = 0; + view.y = 0; + syncTransform(); + } + + function clampScale(next) { + return Math.min(8, Math.max(0.2, next)); + } + + function zoomAt(clientX, clientY, nextScale) { + const rect = stage.getBoundingClientRect(); + const cx = clientX - rect.left - rect.width / 2; + const cy = clientY - rect.top - rect.height / 2; + const ratio = nextScale / view.scale; + view.x = cx - (cx - view.x) * ratio; + view.y = cy - (cy - view.y) * ratio; + view.scale = nextScale; + syncTransform(); + } + + function closePreview() { + try { + if (dlg.open) dlg.close(); + else dlg.removeAttribute("open"); + } catch (_) { + dlg.removeAttribute("open"); + } + imgEl.removeAttribute("src"); + imgEl.alt = ""; + if (cap) cap.textContent = ""; + view.scale = 1; + view.x = 0; + view.y = 0; + view.dragging = false; + syncTransform(); + } + + function openPreview(src, alt) { + if (!src) return; + // 关掉尺寸条,避免叠在 lightbox 上 + document.querySelectorAll(".mdm-imgtools").forEach((n) => n.remove()); + view.scale = 1; + view.x = 0; + view.y = 0; + view.dragging = false; + imgEl.onload = () => fitImage(); + imgEl.onerror = () => { + toast("图片无法放大预览"); + closePreview(); + }; + imgEl.src = src; + imgEl.alt = alt || ""; + if (cap) cap.textContent = alt || "图片预览 · 滚轮缩放 · 拖拽平移"; + try { + if (typeof dlg.showModal === "function") dlg.showModal(); + else dlg.setAttribute("open", ""); + } catch (_) { + dlg.setAttribute("open", ""); + } + if (imgEl.complete && imgEl.naturalWidth) fitImage(); + } + + els.preview.addEventListener("dblclick", (e) => { + const img = e.target.closest?.("img"); + if (!img || !els.preview.contains(img)) return; + // 缺图占位 / 无可用 src 不打开 + if (img.classList.contains("mdm-img-missing")) return; + const src = img.currentSrc || img.src || ""; + if (!src || src === window.location.href) return; + e.preventDefault(); + e.stopPropagation(); + openPreview(src, img.getAttribute("alt") || ""); + }); + + closeBtn?.addEventListener("click", (e) => { + e.preventDefault(); + closePreview(); + }); + dlg.addEventListener("click", (e) => { + if (e.target === dlg) closePreview(); + }); + dlg.addEventListener("cancel", (e) => { + e.preventDefault(); + closePreview(); + }); + + zoomInBtn?.addEventListener("click", () => { + const rect = stage.getBoundingClientRect(); + zoomAt(rect.left + rect.width / 2, rect.top + rect.height / 2, clampScale(view.scale * 1.2)); + }); + zoomOutBtn?.addEventListener("click", () => { + const rect = stage.getBoundingClientRect(); + zoomAt(rect.left + rect.width / 2, rect.top + rect.height / 2, clampScale(view.scale / 1.2)); + }); + zoomResetBtn?.addEventListener("click", fitImage); + + stage.addEventListener( + "wheel", + (e) => { + if (!dlg.open) return; + e.preventDefault(); + const factor = Math.exp(-e.deltaY * 0.0015); + zoomAt(e.clientX, e.clientY, clampScale(view.scale * factor)); + }, + { passive: false } + ); + + stage.addEventListener("pointerdown", (e) => { + if (e.button !== 0) return; + view.dragging = true; + view.lastX = e.clientX; + view.lastY = e.clientY; + stage.setPointerCapture?.(e.pointerId); + }); + stage.addEventListener("pointermove", (e) => { + if (!view.dragging) return; + view.x += e.clientX - view.lastX; + view.y += e.clientY - view.lastY; + view.lastX = e.clientX; + view.lastY = e.clientY; + syncTransform(); + }); + const endDrag = () => { + view.dragging = false; + }; + stage.addEventListener("pointerup", endDrag); + stage.addEventListener("pointercancel", endDrag); + stage.addEventListener("dblclick", (e) => { + e.preventDefault(); + if (view.scale > 1.05) fitImage(); + else zoomAt(e.clientX, e.clientY, clampScale(Math.max(view.scale * 1.8, 1.5))); + }); + } + + bindPreviewImgLightbox(); + /** 复制预览到剪贴板:HTML(带格式,媒体内联为 data URL)+ 纯文本(Markdown 源码) */ async function copyPreviewToClipboard() { if (!els.preview) return; diff --git a/tools/index.html b/tools/index.html index d09bd937..4794a187 100644 --- a/tools/index.html +++ b/tools/index.html @@ -11,12 +11,12 @@ DevTools · 本地实用小工具合集 - - - - - - + + + + + + @@ -26,7 +26,7 @@
DevTools - v2026.10.23-111328 + v2026.10.23-114229
@@ -287,16 +287,16 @@

临近的重要日期

- - - - - - - - - - - + + + + + + + + + + + diff --git a/tools/lib/tools-build.js b/tools/lib/tools-build.js index 06c61157..21213f9e 100644 --- a/tools/lib/tools-build.js +++ b/tools/lib/tools-build.js @@ -1,6 +1,6 @@ (() => { "use strict"; - const BUILD = "2026.10.23-111328"; + const BUILD = "2026.10.23-114229"; window.TOOLS_BUILD = BUILD; window.TOOLS_VERSION = BUILD; diff --git a/tools/styles/panels/mdm.css b/tools/styles/panels/mdm.css index dc8d4f7c..64ae770e 100644 --- a/tools/styles/panels/mdm.css +++ b/tools/styles/panels/mdm.css @@ -237,6 +237,77 @@ .mdm-imgtools button:hover { color: var(--accent); border-color: var(--accent); } .mdm-imgtools-label { margin: 0 0.15rem 0 0.35rem; color: var(--muted); font-size: 0.72rem; } +/* 预览双击图片放大(挂 body;不改单击尺寸条) */ +.mdm-lightbox { + border: 0; + padding: 0; + max-width: min(94vw, 1400px); + width: min(94vw, 1400px); + height: min(92vh, 960px); + max-height: min(92vh, 960px); + background: var(--panel-strong, var(--bg-0)); + color: var(--ink); + border-radius: 14px; + box-shadow: 0 22px 64px rgba(0, 0, 0, 0.45); +} +.mdm-lightbox::backdrop { + background: color-mix(in srgb, var(--bg-0) 72%, transparent); +} +.mdm-lightbox-inner { + display: grid; + grid-template-rows: auto minmax(0, 1fr) auto; + gap: 0.55rem; + padding: 0.75rem; + height: 100%; + min-height: 0; + box-sizing: border-box; +} +.mdm-lightbox-head { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 0.75rem; +} +.mdm-lightbox-cap { + margin: 0; + flex: 1 1 auto; +} +.mdm-lightbox-stage { + position: relative; + overflow: hidden; + min-height: 0; + height: 100%; + border: 1px solid var(--line); + border-radius: 10px; + background: color-mix(in srgb, var(--ink) 4%, transparent); + touch-action: none; + cursor: grab; +} +.mdm-lightbox-stage:active { cursor: grabbing; } +.mdm-lightbox-img { + position: absolute; + left: 50%; + top: 50%; + max-width: none; + max-height: none; + width: auto; + height: auto; + transform: translate(-50%, -50%); + transform-origin: center center; + user-select: none; + -webkit-user-drag: none; +} +.mdm-lightbox-toolbar { + display: flex; + align-items: center; + justify-content: center; + gap: 0.45rem; + flex-wrap: wrap; +} +#mdm .markdown-body img:not(.mdm-img-missing) { + cursor: zoom-in; +} + /* 沉浸(全屏):隐藏站点外壳,让工具铺满屏幕;只有编辑器 / 预览自己滚 */ .mdm-max-btn { padding: 0.15rem 0.5rem; line-height: 1.2; } body.mdm-immersive .site-header, diff --git a/tools/sw.js b/tools/sw.js index f0a90230..84c4a10a 100644 --- a/tools/sw.js +++ b/tools/sw.js @@ -2,7 +2,7 @@ /* eslint-disable no-restricted-globals */ "use strict"; -const SHELL_CACHE = "devtools-shell-20261023-111328"; +const SHELL_CACHE = "devtools-shell-20261023-114229"; const PRECACHE = [ "./", "./index.html",