Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 193 additions & 1 deletion tools/extra-panels/mdm.js
Original file line number Diff line number Diff line change
Expand Up @@ -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") || "";
Expand Down Expand Up @@ -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 =
`<div class="mdm-lightbox-inner" data-html2canvas-ignore="true">` +
`<div class="mdm-lightbox-head">` +
`<p class="hint tight mdm-lightbox-cap" id="mdm-lightbox-cap"></p>` +
`<button type="button" class="ghost-btn" id="mdm-lightbox-close" aria-label="关闭预览">关闭</button>` +
`</div>` +
`<div class="mdm-lightbox-stage" id="mdm-lightbox-stage">` +
`<img id="mdm-lightbox-img" class="mdm-lightbox-img" alt="" draggable="false" />` +
`</div>` +
`<div class="mdm-lightbox-toolbar">` +
`<button type="button" class="ghost-btn" id="mdm-lightbox-zoom-out" aria-label="缩小">-</button>` +
`<span class="mono" id="mdm-lightbox-zoom-pct">100%</span>` +
`<button type="button" class="ghost-btn" id="mdm-lightbox-zoom-in" aria-label="放大">+</button>` +
`<button type="button" class="ghost-btn" id="mdm-lightbox-zoom-reset">还原</button>` +
`</div></div>`;
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;
Expand Down
36 changes: 18 additions & 18 deletions tools/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@
<link rel="manifest" href="./manifest.webmanifest" />
<title>DevTools · 本地实用小工具合集</title>
<script>try{if(navigator.standalone||matchMedia('(display-mode: standalone)').matches)document.documentElement.classList.add('is-pwa-standalone')}catch(e){}</script>
<script src="./lib/tools-build.js?v=2026.10.23-111328"></script>
<link rel="stylesheet" href="./style.css?v=2026.10.23-111328" />
<link rel="stylesheet" href="./workspace-nav.css?v=2026.10.23-111328" />
<link rel="stylesheet" href="./styles/bridge-shell.css?v=2026.10.23-111328" />
<script src="./lib/theme-presets.js?v=2026.10.23-111328"></script>
<script src="./lib/boot-loader.js?v=2026.10.23-111328"></script>
<script src="./lib/tools-build.js?v=2026.10.23-114229"></script>
<link rel="stylesheet" href="./style.css?v=2026.10.23-114229" />
<link rel="stylesheet" href="./workspace-nav.css?v=2026.10.23-114229" />
<link rel="stylesheet" href="./styles/bridge-shell.css?v=2026.10.23-114229" />
<script src="./lib/theme-presets.js?v=2026.10.23-114229"></script>
<script src="./lib/boot-loader.js?v=2026.10.23-114229"></script>
<script>
(function(){try{var raw=String(location.hash||'').replace(/^#/,'').trim();var head=raw.split(/[/?]/)[0]||'';var last=localStorage.getItem('devtools-tool-last-v1');var id='timestamp';if(head&&head!=='timestamp')id=head;else if(last&&last!=='timestamp')id=last;document.documentElement.dataset.bootPanel=id;}catch(e){document.documentElement.dataset.bootPanel='timestamp'}})();
</script>
Expand All @@ -26,7 +26,7 @@
<div class="header-row">
<div class="header-brand-block">
<a class="brand" href="./"><span class="brand-mark"></span><span class="brand-name">DevTools</span></a>
<span class="tool-ver site-tools-ver mono" id="site-tools-version">v2026.10.23-111328</span>
<span class="tool-ver site-tools-ver mono" id="site-tools-version">v2026.10.23-114229</span>
</div>
<div class="header-actions">
<button type="button" class="ghost-btn header-action-collapse" id="site-force-refresh">强制刷新</button>
Expand Down Expand Up @@ -287,16 +287,16 @@ <h2 class="dateremind-fs-heading">临近的重要日期</h2>
<button type="button" class="ruler-fs-close ghost-btn" id="ruler-fs-close">退出全屏</button>
<p class="ruler-fs-hud mono hint" id="ruler-coord-fs"></p>
</div>
<script src="./lib/bridge-token.js?v=2026.10.23-111328"></script>
<script src="./lib/unified-bridge-bundle.js?v=2026.10.23-111328"></script>
<script src="./lib/bridge-shell.js?v=2026.10.23-111328"></script>
<script src="./lib/giscus-config.js?v=2026.10.23-111328"></script>
<script src="./lib/giscus.js?v=2026.10.23-111328"></script>
<script src="./lib/tool-registry.js?v=2026.10.23-111328"></script>
<script src="./lib/nav-scale.js?v=2026.10.23-111328"></script>
<script src="./lib/lazy-scripts.js?v=2026.10.23-111328"></script>
<script src="./lib/panel-loader.js?v=2026.10.23-111328"></script>
<script src="./theme.js?v=2026.10.23-111328"></script>
<script src="./app.js?v=2026.10.23-111328"></script>
<script src="./lib/bridge-token.js?v=2026.10.23-114229"></script>
<script src="./lib/unified-bridge-bundle.js?v=2026.10.23-114229"></script>
<script src="./lib/bridge-shell.js?v=2026.10.23-114229"></script>
<script src="./lib/giscus-config.js?v=2026.10.23-114229"></script>
<script src="./lib/giscus.js?v=2026.10.23-114229"></script>
<script src="./lib/tool-registry.js?v=2026.10.23-114229"></script>
<script src="./lib/nav-scale.js?v=2026.10.23-114229"></script>
<script src="./lib/lazy-scripts.js?v=2026.10.23-114229"></script>
<script src="./lib/panel-loader.js?v=2026.10.23-114229"></script>
<script src="./theme.js?v=2026.10.23-114229"></script>
<script src="./app.js?v=2026.10.23-114229"></script>
</body>
</html>
2 changes: 1 addition & 1 deletion tools/lib/tools-build.js
Original file line number Diff line number Diff line change
@@ -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;

Expand Down
71 changes: 71 additions & 0 deletions tools/styles/panels/mdm.css
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion tools/sw.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading