diff --git a/README-cn.md b/README-cn.md index 7ab4dd3f..c53ebd4f 100644 --- a/README-cn.md +++ b/README-cn.md @@ -51,7 +51,7 @@ | 32 | timeline | 时间线 | | | 33 | chat | 聊天 | | | 34 | auto_number | 章节、表格、图片、代码块等自动编号 | | -| 35 | image_viewer | 图片查看器 | | +| 35 | image_see | 双击图片查看器 | | | 36 | cjk_symbol_pairing | 中文符号配对 | | | 37 | resize_table | 调整表格行高列宽 | | | 38 | resize_image | 调整图片显示大小 | | @@ -76,6 +76,7 @@ | 57 | hotkeys | 快捷键注册中心(高级) | | | 58 | action_buttons | 于右下角添加功能按钮(高级) | | | 59 | json_rpc | 外部操纵 Typora(高级) | × | +| 60 | image_viewer | 多图片查看器 | | > 如果有需求或发现 BUG,欢迎 [提 issue](https://github.com/obgnail/typora_plugin/issues/new),欢迎 PR。如果觉得本项目对您有帮助,请不吝点亮一个 Star ⭐! @@ -507,7 +508,24 @@ docker run -d --name plantuml-server -p 8080:8080 plantuml/plantuml-server:jetty -### image_viewer:图片查看器 +### image_see:双击图片查看器 + +功能:鼠标左键双击查看图片,并支持缩放、拖动。 + +使用方式: + +- 查看:鼠标左键双击已被渲染的img图片,即可弹出图片。 +- 缩放:图片弹出后,可通过鼠标滚轮进行缩放。 +- 拖动:图片弹出后,可按住鼠标左键然后拖动图片,即查看各处细节。 +- 关闭:图片弹出后,可点击右上角的X或按Esc,即可关闭查看图片。 + +![image-see-default](./assets/image-see-default.png) +![image-see-scale](./assets/image-see-scale.png) +![image-see-drag](./assets/image-see-drag.png) + + + +### image_viewer:多图片查看器 功能:一站式图片查看,并且提供简单图片编辑。 diff --git a/README.md b/README.md index 77a55a09..f0971059 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ English | [简体中文](https://github.com/obgnail/typora_plugin/blob/master/RE | 32 | timeline | Timeline | | | 33 | chat | Chat | | | 34 | auto_number | Auto Numbering | | -| 35 | image_viewer | Image reviewer | | +| 35 | image_viewer | Multi-image reviewer | | | 36 | cjk_symbol_pairing | Automatic completion of CJK symbols | | | 37 | resize_table | Adjust table row height and column width | | | 38 | resize_image | Adjust image display size | | @@ -76,6 +76,7 @@ English | [简体中文](https://github.com/obgnail/typora_plugin/blob/master/RE | 57 | hotkeys | Hotkey registration center (advanced) | | | 58 | action_buttons | Add function buttons in the lower right corner (advanced) | | | 59 | json_rpc | External control of Typora (advanced) | × | +| 60 | image_see | Double-click Image Viewer | | > If you have other needs or find bugs, feel free to [open an issue](https://github.com/obgnail/typora_plugin/issues/new). PRs are also welcome. If you find this project helpful, please give me a star ⭐ @@ -468,6 +469,21 @@ Usage: ![image-reviewer](./assets/image-reviewer.png) +### image_see: Double-click Image Viewer + +Function: Double-click with left mouse button to view images, supporting zoom and drag. + +Usage: + +- View: Double-click any rendered img image with the left mouse button to pop up the image. +- Zoom: After the image pops up, use the mouse scroll wheel to zoom in/out. +- Drag: After the image pops up, hold the left mouse button and drag to view details. +- Close: After the image pops up, click the X in the top right corner or press Esc to close. + +![image-see-default](./assets/image-see-default.png) +![image-see-scale](./assets/image-see-scale.png) +![image-see-drag](./assets/image-see-drag.png) + ### cjk_symbol_pairing Function: Automatically pair symbols when typing `《 【 ( ‘ “ 「`. diff --git a/assets/image-see-default.png b/assets/image-see-default.png new file mode 100644 index 00000000..40aafed7 Binary files /dev/null and b/assets/image-see-default.png differ diff --git a/assets/image-see-drag.png b/assets/image-see-drag.png new file mode 100644 index 00000000..75e6c521 Binary files /dev/null and b/assets/image-see-drag.png differ diff --git a/assets/image-see-scale.png b/assets/image-see-scale.png new file mode 100644 index 00000000..80a087d3 Binary files /dev/null and b/assets/image-see-scale.png differ diff --git a/plugin/global/settings/settings.default.toml b/plugin/global/settings/settings.default.toml index 6e5f7c17..83586c28 100644 --- a/plugin/global/settings/settings.default.toml +++ b/plugin/global/settings/settings.default.toml @@ -4000,6 +4000,15 @@ DEFAULT_ICON = "\\f075" TEMPLATE = "> [!NOTE]\n> Support Type: TIP、BUG、INFO、NOTE、QUOTE、EXAMPLE、CAUTION、FAILURE、WARNING、SUCCESS、QUESTION、ABSTRACT、IMPORTANT" +[image_see] +# ------------------- 基础 ------------------- +# 启用插件 +ENABLE = true + +# 插件名称(为空则使用默认名) +NAME = "" + + [test] # =========== [ 基础 ] =========== # 启用插件 diff --git a/plugin/image_see.js b/plugin/image_see.js new file mode 100644 index 00000000..b446d436 --- /dev/null +++ b/plugin/image_see.js @@ -0,0 +1,250 @@ +class ImageSeePlugin extends BasePlugin { + // 注册 CSS 样式 + style = () => ` + .image_see_overlay { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: rgba(0, 0, 0, 0.9); + display: flex; + justify-content: center; + align-items: center; + z-index: 9999; + cursor: default; + } + + .image_see_close { + position: absolute; + top: 20px; + right: 20px; + width: 24px; + height: 24px; + background-color: rgba(255, 255, 255, 0.8); + border-radius: 50%; + border: 1px solid #050404ff; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + font-size: 24px; + font-weight: bold; + color: #000; + z-index: 10000; + } + + .image_see_close:hover { + background-color: rgba(255, 255, 255, 1); + } + + .image_see_content { + position: relative; + overflow: visible; + transform-origin: center center; + } + + .image_see_img { + max-width: 100%; + max-height: 100%; + object-fit: contain; + cursor: grab; + } + + .image_see_img:active { + cursor: grabbing; + } + ` + + // 初始化方法 + init = () => { + // 初始化时可以做一些准备工作 + } + + // 处理插件逻辑,为所有图片注册双击事件 + process = () => { + // 为所有已存在的 img 标签注册双击事件 + this.registerDoubleClickEvents(); + + // 监听 DOM 变化,为新添加的图片也注册事件 + this.observeDOMChanges(); + } + + // 为所有图片注册双击事件 + registerDoubleClickEvents = () => { + // 获取所有非插件新增的 img 标签 + const images = document.querySelectorAll('img:not(.image_see_img)'); + images.forEach(img => { + // 移除可能存在的旧事件监听器 + img.removeEventListener('dblclick', this.handleImageDoubleClick); + // 添加新的双击事件监听器 + img.addEventListener('dblclick', this.handleImageDoubleClick); + }); + } + + // 监听 DOM 变化,为新添加的图片注册事件 + observeDOMChanges = () => { + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + mutation.addedNodes.forEach((node) => { + if (node.nodeType === 1) { // 元素节点 + // 检查是否是 img 标签 + if (node.tagName === 'IMG' && !node.classList.contains('image_see_img')) { + node.addEventListener('dblclick', this.handleImageDoubleClick); + } + // 检查是否包含 img 标签 + const images = node.querySelectorAll('img:not(.image_see_img)'); + images.forEach(img => { + img.addEventListener('dblclick', this.handleImageDoubleClick); + }); + } + }); + }); + }); + + // 观察整个文档的变化 + observer.observe(document.body, { + childList: true, + subtree: true + }); + } + + // 处理图片双击事件 + handleImageDoubleClick = (e) => { + // 确保点击的是图片元素 + if (e.target && e.target.tagName === 'IMG' && !e.target.classList.contains('image_see_img')) { + this.showEnlargedImage(e.target); + } + } + + // 显示放大图片 + showEnlargedImage = (imgElement) => { + // 创建覆盖层 + const overlay = document.createElement('div'); + overlay.className = 'image_see_overlay'; + + // 创建关闭按钮 + const closeButton = document.createElement('div'); + closeButton.className = 'image_see_close'; + closeButton.textContent = '×'; + + // 创建内容容器 + const contentContainer = document.createElement('div'); + contentContainer.className = 'image_see_content'; + + // 创建图片 + const enlargedImg = document.createElement('img'); + enlargedImg.className = 'image_see_img'; + enlargedImg.src = imgElement.src; + enlargedImg.alt = imgElement.alt; + + // 组装结构 + contentContainer.appendChild(enlargedImg); + overlay.appendChild(closeButton); + overlay.appendChild(contentContainer); + + // 初始化变量 + let scale = 1; + let translateX = 0; + let translateY = 0; + let isDragging = false; + let startX = 0; + let startY = 0; + let startTranslateX = 0; + let startTranslateY = 0; + + // 更新图片变换 + const updateTransform = () => { + contentContainer.style.transform = `scale(${scale}) translate(${translateX}px, ${translateY}px)`; + }; + + // 计算初始缩放比例,确保图片在可视范围内最大尺寸展示 + const calculateInitialScale = () => { + const viewportWidth = window.innerWidth; //窗口可视宽度 + const viewportHeight = window.innerHeight; //窗口可视高度 + const imgWidth = enlargedImg.naturalWidth; //图片原始宽度 + const imgHeight = enlargedImg.naturalHeight; //图片原始高度 + + // 如果图片小于等于视口,按原图大小展示 + if (imgWidth <= viewportWidth && imgHeight <= viewportHeight) { + scale = 1; + } else { + // 如果图片超过视口,计算缩放比例 + const scaleX = viewportWidth / imgWidth; + const scaleY = viewportHeight / imgHeight; + scale = Math.min(scaleX, scaleY); + } + + updateTransform(); + }; + + // 关闭事件 + closeButton.addEventListener('click', () => { + document.body.removeChild(overlay); + document.removeEventListener('keydown', handleEscKey); + }); + + // 按 ESC 键关闭 + const handleEscKey = (e) => { + if (e.key === 'Escape') { + document.body.removeChild(overlay); + document.removeEventListener('keydown', handleEscKey); + } + }; + + document.addEventListener('keydown', handleEscKey); + + // 鼠标滚轮缩放 + contentContainer.addEventListener('wheel', (e) => { + e.preventDefault(); + + // 计算缩放比例 + const scaleFactor = e.deltaY > 0 ? 0.9 : 1.1; + const newScale = Math.max(0.1, Math.min(5, scale * scaleFactor)); + + // 以图片中心为原点进行缩放,不需要调整位移 + scale = newScale; + updateTransform(); + }); + + // 鼠标拖动 + enlargedImg.addEventListener('mousedown', (e) => { + e.preventDefault(); + isDragging = true; + startX = e.clientX; + startY = e.clientY; + startTranslateX = translateX; + startTranslateY = translateY; + }); + + document.addEventListener('mousemove', (e) => { + if (!isDragging) return; + + const deltaX = e.clientX - startX; + const deltaY = e.clientY - startY; + + translateX = startTranslateX + deltaX; + translateY = startTranslateY + deltaY; + + updateTransform(); + }); + + document.addEventListener('mouseup', () => { + isDragging = false; + }); + + // 添加到文档 + document.body.appendChild(overlay); + + // 等待图片加载完成后计算初始缩放比例 + if (enlargedImg.complete) { + calculateInitialScale(); + } else { + enlargedImg.addEventListener('load', calculateInitialScale); + } + } +} + +module.exports = { + plugin: ImageSeePlugin +}