@opentiny/fluent-editor 4.1.0-alpha.2 → 4.1.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/es/index.es.js +3 -0
- package/es/index.es.js.map +1 -1
- package/es/modules/custom-image/actions/image-toolbar-buttons.es.js +14 -1
- package/es/modules/custom-image/actions/image-toolbar-buttons.es.js.map +1 -1
- package/es/modules/custom-image/index.es.js +1 -0
- package/es/modules/custom-image/index.es.js.map +1 -1
- package/es/modules/custom-image/options.es.js +4 -1
- package/es/modules/custom-image/options.es.js.map +1 -1
- package/es/modules/custom-image/preview/index.es.js +6 -0
- package/es/modules/custom-image/preview/index.es.js.map +1 -0
- package/es/modules/custom-image/preview/preview-modal.es.js +230 -0
- package/es/modules/custom-image/preview/preview-modal.es.js.map +1 -0
- package/es/modules/custom-image/specs/custom-image-spec.es.js +14 -0
- package/es/modules/custom-image/specs/custom-image-spec.es.js.map +1 -1
- package/lib/index.cjs.js +3 -0
- package/lib/index.cjs.js.map +1 -1
- package/lib/modules/custom-image/actions/image-toolbar-buttons.cjs.js +13 -0
- package/lib/modules/custom-image/actions/image-toolbar-buttons.cjs.js.map +1 -1
- package/lib/modules/custom-image/index.cjs.js +1 -0
- package/lib/modules/custom-image/index.cjs.js.map +1 -1
- package/lib/modules/custom-image/options.cjs.js +4 -1
- package/lib/modules/custom-image/options.cjs.js.map +1 -1
- package/lib/modules/custom-image/preview/index.cjs.js +6 -0
- package/lib/modules/custom-image/preview/index.cjs.js.map +1 -0
- package/lib/modules/custom-image/preview/preview-modal.cjs.js +230 -0
- package/lib/modules/custom-image/preview/preview-modal.cjs.js.map +1 -0
- package/lib/modules/custom-image/specs/custom-image-spec.cjs.js +14 -0
- package/lib/modules/custom-image/specs/custom-image-spec.cjs.js.map +1 -1
- package/package.json +1 -1
- package/types/modules/custom-image/index.d.ts +1 -0
- package/types/modules/custom-image/options.d.ts +1 -0
- package/types/modules/custom-image/preview/index.d.ts +1 -0
- package/types/modules/custom-image/preview/preview-modal.d.ts +54 -0
- package/types/modules/custom-image/specs/custom-image-spec.d.ts +4 -0
package/es/index.es.js
CHANGED
|
@@ -29,6 +29,7 @@ import { CustomImage } from "./modules/custom-image/image.es.js";
|
|
|
29
29
|
import { BlotSpec } from "./modules/custom-image/specs/blot-spec.es.js";
|
|
30
30
|
import { CustomImageSpec } from "./modules/custom-image/specs/custom-image-spec.es.js";
|
|
31
31
|
import { ImageSpec } from "./modules/custom-image/specs/image-spec.es.js";
|
|
32
|
+
import { ImagePreviewModal, getImagePreviewModal } from "./modules/custom-image/preview/preview-modal.es.js";
|
|
32
33
|
import { FileUploader } from "./modules/custom-uploader.es.js";
|
|
33
34
|
import { DividerBlot } from "./modules/divider.es.js";
|
|
34
35
|
import { EmojiModule } from "./modules/emoji.es.js";
|
|
@@ -84,6 +85,7 @@ export {
|
|
|
84
85
|
FontStyle,
|
|
85
86
|
I18N,
|
|
86
87
|
IMAGE_UPLOADER_MIME_TYPES,
|
|
88
|
+
ImagePreviewModal,
|
|
87
89
|
ImageSpec,
|
|
88
90
|
ImageToolbar,
|
|
89
91
|
ImageToolbarAction,
|
|
@@ -118,6 +120,7 @@ export {
|
|
|
118
120
|
generateTableUpShortKeyMenu,
|
|
119
121
|
generateToolbarTip,
|
|
120
122
|
getEventComposedPath,
|
|
123
|
+
getImagePreviewModal,
|
|
121
124
|
getListValue,
|
|
122
125
|
hadProtocol,
|
|
123
126
|
hexToRgbA,
|
package/es/index.es.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.es.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.es.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -2,7 +2,9 @@ var __defProp = Object.defineProperty;
|
|
|
2
2
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
4
|
import { isBoolean, isObject } from "../../../utils/is.es.js";
|
|
5
|
-
import { COPY, DOWNLOAD, RIGHT_ALIGN, CENTER_ALIGN, LEFT_ALIGN } from "../options.es.js";
|
|
5
|
+
import { PREVIEW, COPY, DOWNLOAD, RIGHT_ALIGN, CENTER_ALIGN, LEFT_ALIGN } from "../options.es.js";
|
|
6
|
+
import "../preview/index.es.js";
|
|
7
|
+
import { getImagePreviewModal } from "../preview/preview-modal.es.js";
|
|
6
8
|
const ALIGN_ATTR = "data-align";
|
|
7
9
|
function setAlignStyle(el, display, float, margin) {
|
|
8
10
|
el.style.setProperty("display", display);
|
|
@@ -106,6 +108,17 @@ const defaultButtons = {
|
|
|
106
108
|
apply: (el, toolbarButtons) => {
|
|
107
109
|
alignmentHandler.copy(el, toolbarButtons);
|
|
108
110
|
}
|
|
111
|
+
},
|
|
112
|
+
[PREVIEW]: {
|
|
113
|
+
name: PREVIEW,
|
|
114
|
+
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path class="ql-fill" d="M16 7c-4.96 0-9.23 3.13-11 7.5 1.77 4.37 6.04 7.5 11 7.5s9.23-3.13 11-7.5c-1.77-4.37-6.04-7.5-11-7.5zm0 12c-2.49 0-4.5-2.01-4.5-4.5S13.51 10 16 10s4.5 2.01 4.5 4.5-2.01 4.5-4.5 4.5zm0-7c-1.38 0-2.5 1.12-2.5 2.5s1.12 2.5 2.5 2.5 2.5-1.12 2.5-2.5-1.12-2.5-2.5-2.5z"/></svg>`,
|
|
115
|
+
apply: (el, toolbarButtons) => {
|
|
116
|
+
const imageSrc = el.getAttribute("src") || el.getAttribute("data-src");
|
|
117
|
+
if (imageSrc) {
|
|
118
|
+
const modal = getImagePreviewModal();
|
|
119
|
+
modal.show(imageSrc);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
109
122
|
}
|
|
110
123
|
};
|
|
111
124
|
class ImageToolbarButtons {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-toolbar-buttons.es.js","sources":["../../../../../src/modules/custom-image/actions/image-toolbar-buttons.ts"],"sourcesContent":["import type { ToolbarButtonOptions, ToolButtonOption } from '../options'\r\nimport { isBoolean, isObject } from '../../../utils/is'\r\nimport { CENTER_ALIGN, COPY, DOWNLOAD, LEFT_ALIGN, RIGHT_ALIGN } from '../options'\r\n\r\nexport const ALIGN_ATTR = 'data-align'\r\n\r\nexport function setAlignStyle(el: HTMLElement, display: string | null, float: string | null, margin: string | null) {\r\n el.style.setProperty('display', display)\r\n el.style.setProperty('float', float)\r\n el.style.setProperty('margin', margin)\r\n}\r\nexport const alignmentHandler = {\r\n left: (el: HTMLElement, toolbarButtons: ImageToolbarButtons) => {\r\n setAlignStyle(el, 'inline', 'left', '0 1em 1em 0')\r\n },\r\n center: (el: HTMLElement, toolbarButtons: ImageToolbarButtons) => {\r\n setAlignStyle(el, 'block', null, 'auto')\r\n },\r\n right: (el: HTMLElement, toolbarButtons: ImageToolbarButtons) => {\r\n setAlignStyle(el, 'inline', 'right', '0 0 1em 1em')\r\n },\r\n download: (el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => {\r\n const imageName = el.dataset.title || 'image'\r\n const url = el.src || ''\r\n if (!url) return\r\n const a = document.createElement('a')\r\n a.href = url\r\n a.target = '_blank'\r\n a.download = imageName\r\n a.style.display = 'none'\r\n document.body.appendChild(a)\r\n a.click()\r\n a.parentNode.removeChild(a)\r\n },\r\n copy: async (el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => {\r\n if (!el.src) return\r\n const imageUrl = el.src\r\n try {\r\n const response = await fetch(imageUrl)\r\n if (!response.ok) {\r\n throw new Error('Copy image failed')\r\n }\r\n const blob = await response.blob()\r\n await navigator.clipboard.write([new ClipboardItem({ [blob.type]: blob })])\r\n }\r\n catch (e) {\r\n throw new Error('Copy image failed')\r\n }\r\n },\r\n}\r\nconst defaultButtons: Record<string, ToolButtonOption> = {\r\n [LEFT_ALIGN]: {\r\n name: LEFT_ALIGN,\r\n icon: `\r\n <svg viewbox=\"0 0 18 18\">\r\n <line class=\"ql-stroke\" x1=\"3\" x2=\"15\" y1=\"9\" y2=\"9\"></line>\r\n <line class=\"ql-stroke\" x1=\"3\" x2=\"13\" y1=\"14\" y2=\"14\"></line>\r\n <line class=\"ql-stroke\" x1=\"3\" x2=\"9\" y1=\"4\" y2=\"4\"></line>\r\n </svg>\r\n `,\r\n isActive: el => el.getAttribute(ALIGN_ATTR) === 'left',\r\n apply: (el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => {\r\n el.setAttribute(ALIGN_ATTR, 'left')\r\n alignmentHandler.left(el, toolbarButtons)\r\n },\r\n },\r\n [CENTER_ALIGN]: {\r\n name: CENTER_ALIGN,\r\n icon: `\r\n <svg viewbox=\"0 0 18 18\">\r\n <line class=\"ql-stroke\" x1=\"15\" x2=\"3\" y1=\"9\" y2=\"9\"></line>\r\n <line class=\"ql-stroke\" x1=\"14\" x2=\"4\" y1=\"14\" y2=\"14\"></line>\r\n <line class=\"ql-stroke\" x1=\"12\" x2=\"6\" y1=\"4\" y2=\"4\"></line>\r\n </svg>\r\n `,\r\n isActive: el => el.getAttribute(ALIGN_ATTR) === 'center',\r\n apply: (el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => {\r\n el.setAttribute(ALIGN_ATTR, 'center')\r\n alignmentHandler.center(el, toolbarButtons)\r\n },\r\n },\r\n [RIGHT_ALIGN]: {\r\n name: RIGHT_ALIGN,\r\n icon: `\r\n <svg viewbox=\"0 0 18 18\">\r\n <line class=\"ql-stroke\" x1=\"15\" x2=\"3\" y1=\"9\" y2=\"9\"></line>\r\n <line class=\"ql-stroke\" x1=\"15\" x2=\"5\" y1=\"14\" y2=\"14\"></line>\r\n <line class=\"ql-stroke\" x1=\"15\" x2=\"9\" y1=\"4\" y2=\"4\"></line>\r\n </svg>\r\n `,\r\n isActive: el => el.getAttribute(ALIGN_ATTR) === 'right',\r\n apply: (el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => {\r\n el.setAttribute(ALIGN_ATTR, 'right')\r\n alignmentHandler.right(el, toolbarButtons)\r\n },\r\n },\r\n [DOWNLOAD]: {\r\n name: DOWNLOAD,\r\n icon: `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" viewBox=\"0 0 32 32\"><path class=\"ql-fill\" d=\"M26 24v4H6v-4H4v4a2 2 0 0 0 2 2h20a2 2 0 0 0 2-2v-4zm0-10l-1.41-1.41L17 20.17V2h-2v18.17l-7.59-7.58L6 14l10 10z\"/></svg>`,\r\n apply: (el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => {\r\n alignmentHandler.download(el, toolbarButtons)\r\n },\r\n },\r\n [COPY]: {\r\n name: COPY,\r\n icon: `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" viewBox=\"0 0 32 32\"><path class=\"ql-fill\" d=\"M28 10v18H10V10zm0-2H10a2 2 0 0 0-2 2v18a2 2 0 0 0 2 2h18a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2\"/><path class=\"ql-fill\" d=\"M4 18H2V4a2 2 0 0 1 2-2h14v2H4Z\"/></svg>`,\r\n apply: (el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => {\r\n alignmentHandler.copy(el, toolbarButtons)\r\n },\r\n },\r\n}\r\nexport class ImageToolbarButtons {\r\n buttons: Record<string, ToolButtonOption>\r\n\r\n constructor(options: ToolbarButtonOptions) {\r\n this.buttons = Object.entries(options.buttons).reduce((acc, [name, button]) => {\r\n if (isBoolean(button) && button && defaultButtons[name]) {\r\n acc[name] = defaultButtons[name]\r\n }\r\n else if (isObject(button)) {\r\n acc[button.name] = button\r\n }\r\n return acc\r\n }, {})\r\n }\r\n\r\n getItems(): ToolButtonOption[] {\r\n return Object.keys(this.buttons).map(k => this.buttons[k])\r\n }\r\n\r\n clear(el: HTMLElement): void {\r\n el.removeAttribute(ALIGN_ATTR)\r\n setAlignStyle(el, null, null, null)\r\n }\r\n}\r\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"image-toolbar-buttons.es.js","sources":["../../../../../src/modules/custom-image/actions/image-toolbar-buttons.ts"],"sourcesContent":["import type { ToolbarButtonOptions, ToolButtonOption } from '../options'\r\nimport { isBoolean, isObject } from '../../../utils/is'\r\nimport { CENTER_ALIGN, COPY, DOWNLOAD, LEFT_ALIGN, PREVIEW, RIGHT_ALIGN } from '../options'\r\nimport { getImagePreviewModal } from '../preview'\r\n\r\nexport const ALIGN_ATTR = 'data-align'\r\n\r\nexport function setAlignStyle(el: HTMLElement, display: string | null, float: string | null, margin: string | null) {\r\n el.style.setProperty('display', display)\r\n el.style.setProperty('float', float)\r\n el.style.setProperty('margin', margin)\r\n}\r\nexport const alignmentHandler = {\r\n left: (el: HTMLElement, toolbarButtons: ImageToolbarButtons) => {\r\n setAlignStyle(el, 'inline', 'left', '0 1em 1em 0')\r\n },\r\n center: (el: HTMLElement, toolbarButtons: ImageToolbarButtons) => {\r\n setAlignStyle(el, 'block', null, 'auto')\r\n },\r\n right: (el: HTMLElement, toolbarButtons: ImageToolbarButtons) => {\r\n setAlignStyle(el, 'inline', 'right', '0 0 1em 1em')\r\n },\r\n download: (el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => {\r\n const imageName = el.dataset.title || 'image'\r\n const url = el.src || ''\r\n if (!url) return\r\n const a = document.createElement('a')\r\n a.href = url\r\n a.target = '_blank'\r\n a.download = imageName\r\n a.style.display = 'none'\r\n document.body.appendChild(a)\r\n a.click()\r\n a.parentNode.removeChild(a)\r\n },\r\n copy: async (el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => {\r\n if (!el.src) return\r\n const imageUrl = el.src\r\n try {\r\n const response = await fetch(imageUrl)\r\n if (!response.ok) {\r\n throw new Error('Copy image failed')\r\n }\r\n const blob = await response.blob()\r\n await navigator.clipboard.write([new ClipboardItem({ [blob.type]: blob })])\r\n }\r\n catch (e) {\r\n throw new Error('Copy image failed')\r\n }\r\n },\r\n}\r\nconst defaultButtons: Record<string, ToolButtonOption> = {\r\n [LEFT_ALIGN]: {\r\n name: LEFT_ALIGN,\r\n icon: `\r\n <svg viewbox=\"0 0 18 18\">\r\n <line class=\"ql-stroke\" x1=\"3\" x2=\"15\" y1=\"9\" y2=\"9\"></line>\r\n <line class=\"ql-stroke\" x1=\"3\" x2=\"13\" y1=\"14\" y2=\"14\"></line>\r\n <line class=\"ql-stroke\" x1=\"3\" x2=\"9\" y1=\"4\" y2=\"4\"></line>\r\n </svg>\r\n `,\r\n isActive: el => el.getAttribute(ALIGN_ATTR) === 'left',\r\n apply: (el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => {\r\n el.setAttribute(ALIGN_ATTR, 'left')\r\n alignmentHandler.left(el, toolbarButtons)\r\n },\r\n },\r\n [CENTER_ALIGN]: {\r\n name: CENTER_ALIGN,\r\n icon: `\r\n <svg viewbox=\"0 0 18 18\">\r\n <line class=\"ql-stroke\" x1=\"15\" x2=\"3\" y1=\"9\" y2=\"9\"></line>\r\n <line class=\"ql-stroke\" x1=\"14\" x2=\"4\" y1=\"14\" y2=\"14\"></line>\r\n <line class=\"ql-stroke\" x1=\"12\" x2=\"6\" y1=\"4\" y2=\"4\"></line>\r\n </svg>\r\n `,\r\n isActive: el => el.getAttribute(ALIGN_ATTR) === 'center',\r\n apply: (el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => {\r\n el.setAttribute(ALIGN_ATTR, 'center')\r\n alignmentHandler.center(el, toolbarButtons)\r\n },\r\n },\r\n [RIGHT_ALIGN]: {\r\n name: RIGHT_ALIGN,\r\n icon: `\r\n <svg viewbox=\"0 0 18 18\">\r\n <line class=\"ql-stroke\" x1=\"15\" x2=\"3\" y1=\"9\" y2=\"9\"></line>\r\n <line class=\"ql-stroke\" x1=\"15\" x2=\"5\" y1=\"14\" y2=\"14\"></line>\r\n <line class=\"ql-stroke\" x1=\"15\" x2=\"9\" y1=\"4\" y2=\"4\"></line>\r\n </svg>\r\n `,\r\n isActive: el => el.getAttribute(ALIGN_ATTR) === 'right',\r\n apply: (el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => {\r\n el.setAttribute(ALIGN_ATTR, 'right')\r\n alignmentHandler.right(el, toolbarButtons)\r\n },\r\n },\r\n [DOWNLOAD]: {\r\n name: DOWNLOAD,\r\n icon: `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" viewBox=\"0 0 32 32\"><path class=\"ql-fill\" d=\"M26 24v4H6v-4H4v4a2 2 0 0 0 2 2h20a2 2 0 0 0 2-2v-4zm0-10l-1.41-1.41L17 20.17V2h-2v18.17l-7.59-7.58L6 14l10 10z\"/></svg>`,\r\n apply: (el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => {\r\n alignmentHandler.download(el, toolbarButtons)\r\n },\r\n },\r\n [COPY]: {\r\n name: COPY,\r\n icon: `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" viewBox=\"0 0 32 32\"><path class=\"ql-fill\" d=\"M28 10v18H10V10zm0-2H10a2 2 0 0 0-2 2v18a2 2 0 0 0 2 2h18a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2\"/><path class=\"ql-fill\" d=\"M4 18H2V4a2 2 0 0 1 2-2h14v2H4Z\"/></svg>`,\r\n apply: (el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => {\r\n alignmentHandler.copy(el, toolbarButtons)\r\n },\r\n },\r\n [PREVIEW]: {\r\n name: PREVIEW,\r\n icon: `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" viewBox=\"0 0 32 32\"><path class=\"ql-fill\" d=\"M16 7c-4.96 0-9.23 3.13-11 7.5 1.77 4.37 6.04 7.5 11 7.5s9.23-3.13 11-7.5c-1.77-4.37-6.04-7.5-11-7.5zm0 12c-2.49 0-4.5-2.01-4.5-4.5S13.51 10 16 10s4.5 2.01 4.5 4.5-2.01 4.5-4.5 4.5zm0-7c-1.38 0-2.5 1.12-2.5 2.5s1.12 2.5 2.5 2.5 2.5-1.12 2.5-2.5-1.12-2.5-2.5-2.5z\"/></svg>`,\r\n apply: (el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => {\r\n const imageSrc = el.getAttribute('src') || el.getAttribute('data-src')\r\n if (imageSrc) {\r\n const modal = getImagePreviewModal()\r\n modal.show(imageSrc)\r\n }\r\n },\r\n },\r\n}\r\nexport class ImageToolbarButtons {\r\n buttons: Record<string, ToolButtonOption>\r\n\r\n constructor(options: ToolbarButtonOptions) {\r\n this.buttons = Object.entries(options.buttons).reduce((acc, [name, button]) => {\r\n if (isBoolean(button) && button && defaultButtons[name]) {\r\n acc[name] = defaultButtons[name]\r\n }\r\n else if (isObject(button)) {\r\n acc[button.name] = button\r\n }\r\n return acc\r\n }, {})\r\n }\r\n\r\n getItems(): ToolButtonOption[] {\r\n return Object.keys(this.buttons).map(k => this.buttons[k])\r\n }\r\n\r\n clear(el: HTMLElement): void {\r\n el.removeAttribute(ALIGN_ATTR)\r\n setAlignStyle(el, null, null, null)\r\n }\r\n}\r\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.es.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.es.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;"}
|
|
@@ -5,6 +5,7 @@ const CENTER_ALIGN = "align-center";
|
|
|
5
5
|
const RIGHT_ALIGN = "align-right";
|
|
6
6
|
const COPY = "copy";
|
|
7
7
|
const DOWNLOAD = "download";
|
|
8
|
+
const PREVIEW = "preview";
|
|
8
9
|
const DefaultOptions = {
|
|
9
10
|
// 默认情况下,`file://` 格式的本地文件路径在浏览器环境无法读取,因此会被转换成 `//:0`,但是在一些特殊的场景下(比如:Electron),需要获取到图片的原始路径,进行后续的上传处理
|
|
10
11
|
// 注意:该选项一旦设置为 true,本地磁盘路径会暴露出去,这可能会带来安全风险,请确保你了解相关的安全隐患
|
|
@@ -61,7 +62,8 @@ const DefaultOptions = {
|
|
|
61
62
|
[CENTER_ALIGN]: true,
|
|
62
63
|
[RIGHT_ALIGN]: true,
|
|
63
64
|
[COPY]: true,
|
|
64
|
-
[DOWNLOAD]: true
|
|
65
|
+
[DOWNLOAD]: true,
|
|
66
|
+
[PREVIEW]: true
|
|
65
67
|
}
|
|
66
68
|
},
|
|
67
69
|
resize: {
|
|
@@ -82,6 +84,7 @@ export {
|
|
|
82
84
|
COPY,
|
|
83
85
|
DOWNLOAD,
|
|
84
86
|
LEFT_ALIGN,
|
|
87
|
+
PREVIEW,
|
|
85
88
|
RIGHT_ALIGN,
|
|
86
89
|
DefaultOptions as default
|
|
87
90
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"options.es.js","sources":["../../../../src/modules/custom-image/options.ts"],"sourcesContent":["import type { ImageToolbar, ImageToolbarButtons } from './actions'\nimport type { BlotSpec } from './specs'\nimport { ImageSpec } from './specs'\n\nexport interface OverlayOptions {\n // classname applied to the overlay element\n className: string\n // style applied to overlay element, or null to prevent styles\n style: Record<string, string>\n}\n\nexport interface ResizeOptions {\n // class name applied to the resize handles\n handleClassName: string\n // style applied to resize handles, or null to prevent styles\n handleStyle: Record<string, string>\n}\n\nexport interface ToolButtonOption {\n name: string\n icon: string\n isActive?: (el: HTMLElement) => boolean\n apply: (this: ImageToolbar, el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => void\n}\n\nexport interface ToolbarButtonOptions {\n buttons: Record<string, ToolButtonOption | boolean>\n}\n\nexport interface ToolbarOptions extends ToolbarButtonOptions {\n // class name applied to the root toolbar element\n mainClassName: string\n // style applied to root toolbar element, or null to prevent styles\n mainStyle: Record<string, unknown>\n // class name applied to each button in the toolbar\n buttonClassName: string\n /* whether or not to add the selected style to the buttons.\n they'll always get the is-selected class */\n addButtonSelectStyle: boolean\n // style applied to buttons, or null to prevent styles\n buttonStyle: Record<string, unknown>\n // style applied to the svgs in the buttons\n svgStyle: Record<string, unknown>\n}\n\nexport interface BlotFormatterOptionsInput {\n specs: typeof BlotSpec[]\n overlay: Partial<OverlayOptions>\n resize: Partial<ResizeOptions>\n toolbar: Partial<ToolbarOptions>\n}\nexport interface BlotFormatterOptions {\n specs: typeof BlotSpec[]\n overlay: OverlayOptions\n resize: ResizeOptions\n toolbar: ToolbarOptions\n allowInvalidUrl: boolean\n}\n\nexport const LEFT_ALIGN = 'align-left'\nexport const CENTER_ALIGN = 'align-center'\nexport const RIGHT_ALIGN = 'align-right'\nexport const COPY = 'copy'\nexport const DOWNLOAD = 'download'\nconst DefaultOptions: BlotFormatterOptions = {\n // 默认情况下,`file://` 格式的本地文件路径在浏览器环境无法读取,因此会被转换成 `//:0`,但是在一些特殊的场景下(比如:Electron),需要获取到图片的原始路径,进行后续的上传处理\n // 注意:该选项一旦设置为 true,本地磁盘路径会暴露出去,这可能会带来安全风险,请确保你了解相关的安全隐患\n allowInvalidUrl: false,\n\n specs: [\n ImageSpec,\n ],\n overlay: {\n className: 'blot-formatter__overlay',\n style: {\n position: 'absolute',\n boxSizing: 'border-box',\n border: '1px dashed #444',\n },\n },\n toolbar: {\n mainClassName: 'blot-formatter__toolbar',\n mainStyle: {\n position: 'absolute',\n top: '-12px',\n right: '0',\n left: '0',\n height: '0',\n minWidth: '120px',\n font: '12px/1.0 Arial, Helvetica, sans-serif',\n textAlign: 'center',\n color: '#333',\n boxSizing: 'border-box',\n cursor: 'default',\n zIndex: '1',\n },\n buttonClassName: 'blot-formatter__toolbar-button',\n addButtonSelectStyle: true,\n buttonStyle: {\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: '24px',\n height: '24px',\n background: 'white',\n border: '1px solid #999',\n verticalAlign: 'middle',\n cursor: 'pointer',\n },\n svgStyle: {\n display: 'inline-block',\n width: '16px',\n height: '16px',\n background: 'white',\n verticalAlign: 'middle',\n },\n buttons: {\n [LEFT_ALIGN]: true,\n [CENTER_ALIGN]: true,\n [RIGHT_ALIGN]: true,\n [COPY]: true,\n [DOWNLOAD]: true,\n },\n },\n resize: {\n handleClassName: 'blot-formatter__resize-handle',\n handleStyle: {\n position: 'absolute',\n height: '12px',\n width: '12px',\n backgroundColor: 'white',\n border: '1px solid #777',\n boxSizing: 'border-box',\n opacity: '0.80',\n },\n },\n}\n\nexport default DefaultOptions\n"],"names":[],"mappings":";;AA2DO,MAAM,aAAa;AACnB,MAAM,eAAe;AACrB,MAAM,cAAc;AACpB,MAAM,OAAO;AACb,MAAM,WAAW;
|
|
1
|
+
{"version":3,"file":"options.es.js","sources":["../../../../src/modules/custom-image/options.ts"],"sourcesContent":["import type { ImageToolbar, ImageToolbarButtons } from './actions'\nimport type { BlotSpec } from './specs'\nimport { ImageSpec } from './specs'\n\nexport interface OverlayOptions {\n // classname applied to the overlay element\n className: string\n // style applied to overlay element, or null to prevent styles\n style: Record<string, string>\n}\n\nexport interface ResizeOptions {\n // class name applied to the resize handles\n handleClassName: string\n // style applied to resize handles, or null to prevent styles\n handleStyle: Record<string, string>\n}\n\nexport interface ToolButtonOption {\n name: string\n icon: string\n isActive?: (el: HTMLElement) => boolean\n apply: (this: ImageToolbar, el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => void\n}\n\nexport interface ToolbarButtonOptions {\n buttons: Record<string, ToolButtonOption | boolean>\n}\n\nexport interface ToolbarOptions extends ToolbarButtonOptions {\n // class name applied to the root toolbar element\n mainClassName: string\n // style applied to root toolbar element, or null to prevent styles\n mainStyle: Record<string, unknown>\n // class name applied to each button in the toolbar\n buttonClassName: string\n /* whether or not to add the selected style to the buttons.\n they'll always get the is-selected class */\n addButtonSelectStyle: boolean\n // style applied to buttons, or null to prevent styles\n buttonStyle: Record<string, unknown>\n // style applied to the svgs in the buttons\n svgStyle: Record<string, unknown>\n}\n\nexport interface BlotFormatterOptionsInput {\n specs: typeof BlotSpec[]\n overlay: Partial<OverlayOptions>\n resize: Partial<ResizeOptions>\n toolbar: Partial<ToolbarOptions>\n}\nexport interface BlotFormatterOptions {\n specs: typeof BlotSpec[]\n overlay: OverlayOptions\n resize: ResizeOptions\n toolbar: ToolbarOptions\n allowInvalidUrl: boolean\n}\n\nexport const LEFT_ALIGN = 'align-left'\nexport const CENTER_ALIGN = 'align-center'\nexport const RIGHT_ALIGN = 'align-right'\nexport const COPY = 'copy'\nexport const DOWNLOAD = 'download'\nexport const PREVIEW = 'preview'\nconst DefaultOptions: BlotFormatterOptions = {\n // 默认情况下,`file://` 格式的本地文件路径在浏览器环境无法读取,因此会被转换成 `//:0`,但是在一些特殊的场景下(比如:Electron),需要获取到图片的原始路径,进行后续的上传处理\n // 注意:该选项一旦设置为 true,本地磁盘路径会暴露出去,这可能会带来安全风险,请确保你了解相关的安全隐患\n allowInvalidUrl: false,\n\n specs: [\n ImageSpec,\n ],\n overlay: {\n className: 'blot-formatter__overlay',\n style: {\n position: 'absolute',\n boxSizing: 'border-box',\n border: '1px dashed #444',\n },\n },\n toolbar: {\n mainClassName: 'blot-formatter__toolbar',\n mainStyle: {\n position: 'absolute',\n top: '-12px',\n right: '0',\n left: '0',\n height: '0',\n minWidth: '120px',\n font: '12px/1.0 Arial, Helvetica, sans-serif',\n textAlign: 'center',\n color: '#333',\n boxSizing: 'border-box',\n cursor: 'default',\n zIndex: '1',\n },\n buttonClassName: 'blot-formatter__toolbar-button',\n addButtonSelectStyle: true,\n buttonStyle: {\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: '24px',\n height: '24px',\n background: 'white',\n border: '1px solid #999',\n verticalAlign: 'middle',\n cursor: 'pointer',\n },\n svgStyle: {\n display: 'inline-block',\n width: '16px',\n height: '16px',\n background: 'white',\n verticalAlign: 'middle',\n },\n buttons: {\n [LEFT_ALIGN]: true,\n [CENTER_ALIGN]: true,\n [RIGHT_ALIGN]: true,\n [COPY]: true,\n [DOWNLOAD]: true,\n [PREVIEW]: true,\n },\n },\n resize: {\n handleClassName: 'blot-formatter__resize-handle',\n handleStyle: {\n position: 'absolute',\n height: '12px',\n width: '12px',\n backgroundColor: 'white',\n border: '1px solid #777',\n boxSizing: 'border-box',\n opacity: '0.80',\n },\n },\n}\n\nexport default DefaultOptions\n"],"names":[],"mappings":";;AA2DO,MAAM,aAAa;AACnB,MAAM,eAAe;AACrB,MAAM,cAAc;AACpB,MAAM,OAAO;AACb,MAAM,WAAW;AACjB,MAAM,UAAU;AACvB,MAAM,iBAAuC;AAAA;AAAA;AAAA,EAG3C,iBAAiB;AAAA,EAEjB,OAAO;AAAA,IACL;AAAA,EAAA;AAAA,EAEF,SAAS;AAAA,IACP,WAAW;AAAA,IACX,OAAO;AAAA,MACL,UAAU;AAAA,MACV,WAAW;AAAA,MACX,QAAQ;AAAA,IAAA;AAAA,EACV;AAAA,EAEF,SAAS;AAAA,IACP,eAAe;AAAA,IACf,WAAW;AAAA,MACT,UAAU;AAAA,MACV,KAAK;AAAA,MACL,OAAO;AAAA,MACP,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,MAAM;AAAA,MACN,WAAW;AAAA,MACX,OAAO;AAAA,MACP,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,QAAQ;AAAA,IAAA;AAAA,IAEV,iBAAiB;AAAA,IACjB,sBAAsB;AAAA,IACtB,aAAa;AAAA,MACX,SAAS;AAAA,MACT,YAAY;AAAA,MACZ,gBAAgB;AAAA,MAChB,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,eAAe;AAAA,MACf,QAAQ;AAAA,IAAA;AAAA,IAEV,UAAU;AAAA,MACR,SAAS;AAAA,MACT,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ,eAAe;AAAA,IAAA;AAAA,IAEjB,SAAS;AAAA,MACP,CAAC,UAAU,GAAG;AAAA,MACd,CAAC,YAAY,GAAG;AAAA,MAChB,CAAC,WAAW,GAAG;AAAA,MACf,CAAC,IAAI,GAAG;AAAA,MACR,CAAC,QAAQ,GAAG;AAAA,MACZ,CAAC,OAAO,GAAG;AAAA,IAAA;AAAA,EACb;AAAA,EAEF,QAAQ;AAAA,IACN,iBAAiB;AAAA,IACjB,aAAa;AAAA,MACX,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,iBAAiB;AAAA,MACjB,QAAQ;AAAA,MACR,WAAW;AAAA,MACX,SAAS;AAAA,IAAA;AAAA,EACX;AAEJ;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.es.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
class ImagePreviewModal {
|
|
5
|
+
constructor() {
|
|
6
|
+
__publicField(this, "modal", null);
|
|
7
|
+
__publicField(this, "overlay", null);
|
|
8
|
+
__publicField(this, "previewImage", null);
|
|
9
|
+
__publicField(this, "scaleTooltip", null);
|
|
10
|
+
__publicField(this, "currentScale", 1);
|
|
11
|
+
__publicField(this, "minScale", 0.5);
|
|
12
|
+
__publicField(this, "maxScale", 3);
|
|
13
|
+
__publicField(this, "scaleStep", 0.1);
|
|
14
|
+
__publicField(this, "tooltipHideTimer", null);
|
|
15
|
+
/**
|
|
16
|
+
* 处理鼠标滚轮事件 - 缩放图片
|
|
17
|
+
*/
|
|
18
|
+
__publicField(this, "onMouseWheel", (event) => {
|
|
19
|
+
if (!this.modal || this.modal.style.display === "none") {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
event.preventDefault();
|
|
23
|
+
const delta = event.deltaY > 0 ? -this.scaleStep : this.scaleStep;
|
|
24
|
+
this.setScale(this.currentScale + delta);
|
|
25
|
+
this.showScaleTooltip();
|
|
26
|
+
});
|
|
27
|
+
this.initModal();
|
|
28
|
+
}
|
|
29
|
+
initModal() {
|
|
30
|
+
this.overlay = document.createElement("div");
|
|
31
|
+
this.overlay.className = "image-preview-overlay";
|
|
32
|
+
this.overlay.style.cssText = `
|
|
33
|
+
position: fixed;
|
|
34
|
+
top: 0;
|
|
35
|
+
left: 0;
|
|
36
|
+
width: 100%;
|
|
37
|
+
height: 100%;
|
|
38
|
+
background-color: rgba(0, 0, 0, 0.8);
|
|
39
|
+
display: none;
|
|
40
|
+
z-index: 9999;
|
|
41
|
+
cursor: pointer;
|
|
42
|
+
`;
|
|
43
|
+
this.modal = document.createElement("div");
|
|
44
|
+
this.modal.className = "image-preview-modal";
|
|
45
|
+
this.modal.style.cssText = `
|
|
46
|
+
position: fixed;
|
|
47
|
+
top: 50%;
|
|
48
|
+
left: 50%;
|
|
49
|
+
transform: translate(-50%, -50%);
|
|
50
|
+
background-color: transparent;
|
|
51
|
+
display: none;
|
|
52
|
+
z-index: 10000;
|
|
53
|
+
max-width: 90vw;
|
|
54
|
+
max-height: 90vh;
|
|
55
|
+
cursor: auto;
|
|
56
|
+
`;
|
|
57
|
+
this.previewImage = document.createElement("img");
|
|
58
|
+
this.previewImage.className = "image-preview-img";
|
|
59
|
+
this.previewImage.style.cssText = `
|
|
60
|
+
max-width: 100%;
|
|
61
|
+
max-height: 100%;
|
|
62
|
+
object-fit: contain;
|
|
63
|
+
border-radius: 4px;
|
|
64
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
65
|
+
transition: transform 0.2s ease-out;
|
|
66
|
+
cursor: grab;
|
|
67
|
+
`;
|
|
68
|
+
const closeBtn = document.createElement("button");
|
|
69
|
+
closeBtn.className = "image-preview-close";
|
|
70
|
+
closeBtn.innerHTML = "×";
|
|
71
|
+
closeBtn.style.cssText = `
|
|
72
|
+
position: absolute;
|
|
73
|
+
top: -40px;
|
|
74
|
+
right: 0;
|
|
75
|
+
width: 40px;
|
|
76
|
+
height: 40px;
|
|
77
|
+
border: none;
|
|
78
|
+
background-color: transparent;
|
|
79
|
+
color: white;
|
|
80
|
+
font-size: 32px;
|
|
81
|
+
cursor: pointer;
|
|
82
|
+
z-index: 10001;
|
|
83
|
+
line-height: 1;
|
|
84
|
+
padding: 0;
|
|
85
|
+
`;
|
|
86
|
+
closeBtn.addEventListener("click", () => this.hide());
|
|
87
|
+
this.modal.appendChild(this.previewImage);
|
|
88
|
+
this.modal.appendChild(closeBtn);
|
|
89
|
+
this.scaleTooltip = document.createElement("div");
|
|
90
|
+
this.scaleTooltip.className = "image-preview-scale-tooltip";
|
|
91
|
+
this.scaleTooltip.style.cssText = `
|
|
92
|
+
position: fixed;
|
|
93
|
+
top: 50%;
|
|
94
|
+
left: 50%;
|
|
95
|
+
transform: translate(-50%, -50%);
|
|
96
|
+
background-color: rgba(0, 0, 0, 0.7);
|
|
97
|
+
color: white;
|
|
98
|
+
padding: 12px 20px;
|
|
99
|
+
border-radius: 6px;
|
|
100
|
+
font-size: 14px;
|
|
101
|
+
z-index: 10002;
|
|
102
|
+
display: none;
|
|
103
|
+
pointer-events: none;
|
|
104
|
+
white-space: nowrap;
|
|
105
|
+
font-weight: 500;
|
|
106
|
+
`;
|
|
107
|
+
document.body.appendChild(this.scaleTooltip);
|
|
108
|
+
this.overlay.addEventListener("click", () => this.hide());
|
|
109
|
+
document.addEventListener("keydown", (e) => {
|
|
110
|
+
if (e.key === "Escape") {
|
|
111
|
+
this.hide();
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
document.addEventListener("wheel", (e) => this.onMouseWheel(e), { passive: false });
|
|
115
|
+
this.modal.addEventListener("click", (e) => {
|
|
116
|
+
e.stopPropagation();
|
|
117
|
+
});
|
|
118
|
+
document.body.appendChild(this.overlay);
|
|
119
|
+
document.body.appendChild(this.modal);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* 设置缩放比例
|
|
123
|
+
*/
|
|
124
|
+
setScale(scale) {
|
|
125
|
+
this.currentScale = Math.max(this.minScale, Math.min(scale, this.maxScale));
|
|
126
|
+
if (this.previewImage) {
|
|
127
|
+
this.previewImage.style.transform = `scale(${this.currentScale})`;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* 显示缩放百分比提示
|
|
132
|
+
*/
|
|
133
|
+
showScaleTooltip() {
|
|
134
|
+
if (!this.scaleTooltip) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (this.tooltipHideTimer !== null) {
|
|
138
|
+
clearTimeout(this.tooltipHideTimer);
|
|
139
|
+
}
|
|
140
|
+
const percentage = Math.round(this.currentScale * 100);
|
|
141
|
+
this.scaleTooltip.textContent = `${percentage}%`;
|
|
142
|
+
this.scaleTooltip.style.display = "block";
|
|
143
|
+
this.tooltipHideTimer = window.setTimeout(() => {
|
|
144
|
+
if (this.scaleTooltip) {
|
|
145
|
+
this.scaleTooltip.style.display = "none";
|
|
146
|
+
}
|
|
147
|
+
this.tooltipHideTimer = null;
|
|
148
|
+
}, 1500);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* 隐藏缩放提示
|
|
152
|
+
*/
|
|
153
|
+
hideScaleTooltip() {
|
|
154
|
+
if (this.tooltipHideTimer !== null) {
|
|
155
|
+
clearTimeout(this.tooltipHideTimer);
|
|
156
|
+
this.tooltipHideTimer = null;
|
|
157
|
+
}
|
|
158
|
+
if (this.scaleTooltip) {
|
|
159
|
+
this.scaleTooltip.style.display = "none";
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* 重置缩放比例
|
|
164
|
+
*/
|
|
165
|
+
resetScale() {
|
|
166
|
+
this.currentScale = 1;
|
|
167
|
+
if (this.previewImage) {
|
|
168
|
+
this.previewImage.style.transform = "scale(1)";
|
|
169
|
+
}
|
|
170
|
+
this.hideScaleTooltip();
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* 显示预览
|
|
174
|
+
* @param imageUrl 图片URL
|
|
175
|
+
*/
|
|
176
|
+
show(imageUrl) {
|
|
177
|
+
if (!this.previewImage || !this.modal || !this.overlay) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
this.resetScale();
|
|
181
|
+
this.previewImage.src = imageUrl;
|
|
182
|
+
this.modal.style.display = "flex";
|
|
183
|
+
this.modal.style.alignItems = "center";
|
|
184
|
+
this.modal.style.justifyContent = "center";
|
|
185
|
+
this.overlay.style.display = "block";
|
|
186
|
+
document.body.style.overflow = "hidden";
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* 隐藏预览
|
|
190
|
+
*/
|
|
191
|
+
hide() {
|
|
192
|
+
if (this.modal && this.overlay) {
|
|
193
|
+
this.modal.style.display = "none";
|
|
194
|
+
this.overlay.style.display = "none";
|
|
195
|
+
document.body.style.overflow = "";
|
|
196
|
+
this.resetScale();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* 销毁预览模态框
|
|
201
|
+
*/
|
|
202
|
+
destroy() {
|
|
203
|
+
this.hideScaleTooltip();
|
|
204
|
+
if (this.overlay && this.overlay.parentNode) {
|
|
205
|
+
this.overlay.parentNode.removeChild(this.overlay);
|
|
206
|
+
}
|
|
207
|
+
if (this.modal && this.modal.parentNode) {
|
|
208
|
+
this.modal.parentNode.removeChild(this.modal);
|
|
209
|
+
}
|
|
210
|
+
if (this.scaleTooltip && this.scaleTooltip.parentNode) {
|
|
211
|
+
this.scaleTooltip.parentNode.removeChild(this.scaleTooltip);
|
|
212
|
+
}
|
|
213
|
+
this.modal = null;
|
|
214
|
+
this.overlay = null;
|
|
215
|
+
this.previewImage = null;
|
|
216
|
+
this.scaleTooltip = null;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
let globalPreviewModal = null;
|
|
220
|
+
function getImagePreviewModal() {
|
|
221
|
+
if (!globalPreviewModal) {
|
|
222
|
+
globalPreviewModal = new ImagePreviewModal();
|
|
223
|
+
}
|
|
224
|
+
return globalPreviewModal;
|
|
225
|
+
}
|
|
226
|
+
export {
|
|
227
|
+
ImagePreviewModal,
|
|
228
|
+
getImagePreviewModal
|
|
229
|
+
};
|
|
230
|
+
//# sourceMappingURL=preview-modal.es.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview-modal.es.js","sources":["../../../../../src/modules/custom-image/preview/preview-modal.ts"],"sourcesContent":["/**\n * 图片预览模态框\n * 提供图片双击时的预览功能,包括遮罩层和全屏预览\n */\n\nexport class ImagePreviewModal {\n private modal: HTMLElement | null = null\n private overlay: HTMLElement | null = null\n private previewImage: HTMLImageElement | null = null\n private scaleTooltip: HTMLElement | null = null\n private currentScale: number = 1\n private minScale: number = 0.5\n private maxScale: number = 3\n private scaleStep: number = 0.1\n private tooltipHideTimer: number | null = null\n\n constructor() {\n this.initModal()\n }\n\n private initModal() {\n // 创建遮罩层\n this.overlay = document.createElement('div')\n this.overlay.className = 'image-preview-overlay'\n this.overlay.style.cssText = `\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-color: rgba(0, 0, 0, 0.8);\n display: none;\n z-index: 9999;\n cursor: pointer;\n `\n\n // 创建预览容器\n this.modal = document.createElement('div')\n this.modal.className = 'image-preview-modal'\n this.modal.style.cssText = `\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background-color: transparent;\n display: none;\n z-index: 10000;\n max-width: 90vw;\n max-height: 90vh;\n cursor: auto;\n `\n\n // 创建预览图片\n this.previewImage = document.createElement('img')\n this.previewImage.className = 'image-preview-img'\n this.previewImage.style.cssText = `\n max-width: 100%;\n max-height: 100%;\n object-fit: contain;\n border-radius: 4px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);\n transition: transform 0.2s ease-out;\n cursor: grab;\n `\n\n // 创建关闭按钮\n const closeBtn = document.createElement('button')\n closeBtn.className = 'image-preview-close'\n closeBtn.innerHTML = '×'\n closeBtn.style.cssText = `\n position: absolute;\n top: -40px;\n right: 0;\n width: 40px;\n height: 40px;\n border: none;\n background-color: transparent;\n color: white;\n font-size: 32px;\n cursor: pointer;\n z-index: 10001;\n line-height: 1;\n padding: 0;\n `\n closeBtn.addEventListener('click', () => this.hide())\n\n this.modal.appendChild(this.previewImage)\n this.modal.appendChild(closeBtn)\n\n // 创建缩放提示窗口\n this.scaleTooltip = document.createElement('div')\n this.scaleTooltip.className = 'image-preview-scale-tooltip'\n this.scaleTooltip.style.cssText = `\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background-color: rgba(0, 0, 0, 0.7);\n color: white;\n padding: 12px 20px;\n border-radius: 6px;\n font-size: 14px;\n z-index: 10002;\n display: none;\n pointer-events: none;\n white-space: nowrap;\n font-weight: 500;\n `\n document.body.appendChild(this.scaleTooltip)\n\n // 绑定事件\n this.overlay.addEventListener('click', () => this.hide())\n document.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') {\n this.hide()\n }\n })\n\n // 绑定滚轮缩放事件\n document.addEventListener('wheel', (e) => this.onMouseWheel(e), { passive: false })\n\n // 阻止模态框内的点击事件冒泡到遮罩层\n this.modal.addEventListener('click', (e) => {\n e.stopPropagation()\n })\n\n document.body.appendChild(this.overlay)\n document.body.appendChild(this.modal)\n }\n\n /**\n * 处理鼠标滚轮事件 - 缩放图片\n */\n private onMouseWheel = (event: WheelEvent) => {\n // 只在预览打开时处理\n if (!this.modal || this.modal.style.display === 'none') {\n return\n }\n\n event.preventDefault()\n\n // 根据滚轮方向调整缩放比例\n const delta = event.deltaY > 0 ? -this.scaleStep : this.scaleStep\n this.setScale(this.currentScale + delta)\n\n // 显示缩放提示\n this.showScaleTooltip()\n }\n\n /**\n * 设置缩放比例\n */\n private setScale(scale: number) {\n // 限制缩放范围\n this.currentScale = Math.max(this.minScale, Math.min(scale, this.maxScale))\n\n if (this.previewImage) {\n this.previewImage.style.transform = `scale(${this.currentScale})`\n }\n }\n\n /**\n * 显示缩放百分比提示\n */\n private showScaleTooltip() {\n if (!this.scaleTooltip) {\n return\n }\n\n // 清除之前的隐藏计时器\n if (this.tooltipHideTimer !== null) {\n clearTimeout(this.tooltipHideTimer)\n }\n\n // 更新提示文本\n const percentage = Math.round(this.currentScale * 100)\n this.scaleTooltip.textContent = `${percentage}%`\n this.scaleTooltip.style.display = 'block'\n\n // 1.5秒后隐藏提示\n this.tooltipHideTimer = window.setTimeout(() => {\n if (this.scaleTooltip) {\n this.scaleTooltip.style.display = 'none'\n }\n this.tooltipHideTimer = null\n }, 1500)\n }\n\n /**\n * 隐藏缩放提示\n */\n private hideScaleTooltip() {\n if (this.tooltipHideTimer !== null) {\n clearTimeout(this.tooltipHideTimer)\n this.tooltipHideTimer = null\n }\n if (this.scaleTooltip) {\n this.scaleTooltip.style.display = 'none'\n }\n }\n\n /**\n * 重置缩放比例\n */\n private resetScale() {\n this.currentScale = 1\n if (this.previewImage) {\n this.previewImage.style.transform = 'scale(1)'\n }\n this.hideScaleTooltip()\n }\n\n /**\n * 显示预览\n * @param imageUrl 图片URL\n */\n show(imageUrl: string) {\n if (!this.previewImage || !this.modal || !this.overlay) {\n return\n }\n\n this.resetScale()\n this.previewImage.src = imageUrl\n this.modal.style.display = 'flex'\n this.modal.style.alignItems = 'center'\n this.modal.style.justifyContent = 'center'\n this.overlay.style.display = 'block'\n\n // 防止页面滚动\n document.body.style.overflow = 'hidden'\n }\n\n /**\n * 隐藏预览\n */\n hide() {\n if (this.modal && this.overlay) {\n this.modal.style.display = 'none'\n this.overlay.style.display = 'none'\n document.body.style.overflow = ''\n this.resetScale()\n }\n }\n\n /**\n * 销毁预览模态框\n */\n destroy() {\n this.hideScaleTooltip()\n if (this.overlay && this.overlay.parentNode) {\n this.overlay.parentNode.removeChild(this.overlay)\n }\n if (this.modal && this.modal.parentNode) {\n this.modal.parentNode.removeChild(this.modal)\n }\n if (this.scaleTooltip && this.scaleTooltip.parentNode) {\n this.scaleTooltip.parentNode.removeChild(this.scaleTooltip)\n }\n this.modal = null\n this.overlay = null\n this.previewImage = null\n this.scaleTooltip = null\n }\n}\n\n// 全局单例实例\nlet globalPreviewModal: ImagePreviewModal | null = null\n\n/**\n * 获取或创建全局预览模态框实例\n */\nexport function getImagePreviewModal(): ImagePreviewModal {\n if (!globalPreviewModal) {\n globalPreviewModal = new ImagePreviewModal()\n }\n return globalPreviewModal\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -2,7 +2,9 @@ var __defProp = Object.defineProperty;
|
|
|
2
2
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
4
|
import { isInside } from "../../../config/editor.utils.es.js";
|
|
5
|
+
import "../preview/index.es.js";
|
|
5
6
|
import { ImageSpec } from "./image-spec.es.js";
|
|
7
|
+
import { getImagePreviewModal } from "../preview/preview-modal.es.js";
|
|
6
8
|
class CustomImageSpec extends ImageSpec {
|
|
7
9
|
constructor(formatter) {
|
|
8
10
|
super(formatter);
|
|
@@ -38,6 +40,17 @@ class CustomImageSpec extends ImageSpec {
|
|
|
38
40
|
quill.setSelection(index, len);
|
|
39
41
|
}
|
|
40
42
|
});
|
|
43
|
+
/**
|
|
44
|
+
* 处理图片双击事件 - 显示预览
|
|
45
|
+
*/
|
|
46
|
+
__publicField(this, "onImageDoubleClick", (event) => {
|
|
47
|
+
const target = event.target;
|
|
48
|
+
const imageSrc = target.getAttribute("src") || target.getAttribute("data-image");
|
|
49
|
+
if (imageSrc) {
|
|
50
|
+
const modal = getImagePreviewModal();
|
|
51
|
+
modal.show(imageSrc);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
41
54
|
this.formatter = formatter;
|
|
42
55
|
this.oldRootScrollTop = this.formatter.quill.root.scrollTop;
|
|
43
56
|
this.editorElem = this.formatter.quill.container;
|
|
@@ -51,6 +64,7 @@ class CustomImageSpec extends ImageSpec {
|
|
|
51
64
|
init() {
|
|
52
65
|
this.editorElem.addEventListener("mouseover", this.imageMouseOver.bind(this));
|
|
53
66
|
this.editorElem.addEventListener("mouseout", this.imageMouseout);
|
|
67
|
+
this.editorElem.addEventListener("dblclick", this.onImageDoubleClick.bind(this));
|
|
54
68
|
super.init();
|
|
55
69
|
}
|
|
56
70
|
imageMouseOver(event) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"custom-image-spec.es.js","sources":["../../../../../src/modules/custom-image/specs/custom-image-spec.ts"],"sourcesContent":["import type { BlotFormatter } from '../blot-formatter'\nimport { isInside } from '../../../config/editor.utils'\nimport { ImageSpec } from './image-spec'\n\nexport class CustomImageSpec extends ImageSpec {\n editorElem: HTMLElement | undefined\n observer: any\n oldRootScrollTop: number\n\n constructor(formatter: BlotFormatter) {\n super(formatter)\n this.formatter = formatter\n this.oldRootScrollTop = this.formatter.quill.root.scrollTop\n this.editorElem = this.formatter.quill.container\n this.formatter.quill.root.addEventListener('scroll', this.handleQuillRootScroll.bind(this))\n }\n\n handleQuillRootScroll() {\n if (this.formatter.overlay) {\n this.formatter.overlay.style.marginTop = `${this.oldRootScrollTop - this.formatter.quill.root.scrollTop}px`\n }\n }\n\n init(): void {\n this.editorElem.addEventListener('mouseover', this.imageMouseOver.bind(this))\n this.editorElem.addEventListener('mouseout', this.imageMouseout)\n\n super.init()\n }\n\n imageMouseOver(event) {\n const target = event.target\n const isBlotFormatter = target?.classList?.contains('blot-formatter__overlay')\n if (target.nodeName === 'IMG' || isBlotFormatter) {\n // this.addImagePreviewOverlay(event);\n }\n }\n\n imageMouseout = (event) => {\n if (event.target.nodeName === 'IMG'\n || event.target.classList.contains('blot-formatter__overlay')) {\n const imgDom = event.target\n if (!isInside(event, imgDom)) {\n this.removeImagePreviewOverlay()\n }\n }\n }\n\n addImagePreviewOverlay(event) {\n const target = event.target\n const {\n left: imgLeft,\n width: imgWidth,\n } = target.getBoundingClientRect()\n // fix: 解决 ql-container 容器设置 calc(100vh - 180px) 这样的视窗相对单位时,滚动视窗导致图片相对视窗的 top 相应改变,从而导致图片预览按钮的位置显示错误\n const imgTop = target.getBoundingClientRect().top + this.formatter.quill.container.scrollTop\n\n const {\n left: editorLeft,\n top: editorTop,\n } = event.currentTarget.getBoundingClientRect()\n\n const imgRelativeLeft = imgLeft - editorLeft\n const imgRelativeTop = imgTop - editorTop\n\n const maxmizeWidth = 24\n const maxmizePadding = 15\n const previewLeft = imgRelativeLeft + imgWidth - maxmizeWidth - maxmizePadding\n const previewTop = imgRelativeTop + maxmizePadding\n\n const previewStyle = `\n left: ${previewLeft}px;\n top: ${previewTop}px;\n width: ${maxmizeWidth}px;\n `\n const imageSrc = target.src || target.getAttribute('data-image')\n const imageId = target.getAttribute('data-image-id')\n\n const previewDom = event.currentTarget.querySelector('.image-preview__overlay')\n if (!previewDom) {\n event.currentTarget.insertAdjacentHTML('beforeend', `\n <div class=\"image-preview__overlay\" style=\"${previewStyle}\">\n <i class=\"icon-maxmize\" id=\"btn-image-preview\" data-image-id=\"${imageId}\"\n data-image=\"${imageSrc}\"></i>\n </div>\n `)\n }\n }\n\n removeImagePreviewOverlay() {\n const previewDom = this.editorElem.querySelector('.image-preview__overlay')\n if (previewDom) {\n previewDom.parentNode.removeChild(previewDom)\n }\n }\n\n onHide() {\n this.removeImagePreviewOverlay()\n super.onHide()\n }\n\n resetOverlayMarginTop() {\n if (this.formatter.overlay) {\n this.formatter.overlay.style.marginTop = '0px'\n }\n }\n\n onClick = (event: MouseEvent) => {\n const el = event.target\n const isReadonly = this.formatter.quill.options.readOnly\n if (!(el instanceof HTMLElement) || el.tagName !== 'IMG' || isReadonly) {\n return\n }\n\n this.img = el as HTMLImageElement\n this.oldRootScrollTop = this.formatter.quill.root.scrollTop\n this.resetOverlayMarginTop()\n this.formatter.show(this)\n\n // 通过图片dom获取图片选区用以复制,设置 current-select-img::selection 取消选区背景\n const imageDom = this.formatter.currentSpec?.getTargetElement()\n if (imageDom) {\n imageDom.classList.add('current-select-img')\n const quill = this.formatter.quill\n const imgBlot = quill.scroll.find(this.img)\n const index = quill.getIndex(imgBlot)\n const len = imgBlot.length()\n quill.setSelection(index, len)\n }\n }\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"custom-image-spec.es.js","sources":["../../../../../src/modules/custom-image/specs/custom-image-spec.ts"],"sourcesContent":["import type { BlotFormatter } from '../blot-formatter'\nimport { isInside } from '../../../config/editor.utils'\nimport { getImagePreviewModal } from '../preview'\nimport { ImageSpec } from './image-spec'\n\nexport class CustomImageSpec extends ImageSpec {\n editorElem: HTMLElement | undefined\n observer: any\n oldRootScrollTop: number\n\n constructor(formatter: BlotFormatter) {\n super(formatter)\n this.formatter = formatter\n this.oldRootScrollTop = this.formatter.quill.root.scrollTop\n this.editorElem = this.formatter.quill.container\n this.formatter.quill.root.addEventListener('scroll', this.handleQuillRootScroll.bind(this))\n }\n\n handleQuillRootScroll() {\n if (this.formatter.overlay) {\n this.formatter.overlay.style.marginTop = `${this.oldRootScrollTop - this.formatter.quill.root.scrollTop}px`\n }\n }\n\n init(): void {\n this.editorElem.addEventListener('mouseover', this.imageMouseOver.bind(this))\n this.editorElem.addEventListener('mouseout', this.imageMouseout)\n this.editorElem.addEventListener('dblclick', this.onImageDoubleClick.bind(this))\n\n super.init()\n }\n\n imageMouseOver(event) {\n const target = event.target\n const isBlotFormatter = target?.classList?.contains('blot-formatter__overlay')\n if (target.nodeName === 'IMG' || isBlotFormatter) {\n // this.addImagePreviewOverlay(event);\n }\n }\n\n imageMouseout = (event) => {\n if (event.target.nodeName === 'IMG'\n || event.target.classList.contains('blot-formatter__overlay')) {\n const imgDom = event.target\n if (!isInside(event, imgDom)) {\n this.removeImagePreviewOverlay()\n }\n }\n }\n\n addImagePreviewOverlay(event) {\n const target = event.target\n const {\n left: imgLeft,\n width: imgWidth,\n } = target.getBoundingClientRect()\n // fix: 解决 ql-container 容器设置 calc(100vh - 180px) 这样的视窗相对单位时,滚动视窗导致图片相对视窗的 top 相应改变,从而导致图片预览按钮的位置显示错误\n const imgTop = target.getBoundingClientRect().top + this.formatter.quill.container.scrollTop\n\n const {\n left: editorLeft,\n top: editorTop,\n } = event.currentTarget.getBoundingClientRect()\n\n const imgRelativeLeft = imgLeft - editorLeft\n const imgRelativeTop = imgTop - editorTop\n\n const maxmizeWidth = 24\n const maxmizePadding = 15\n const previewLeft = imgRelativeLeft + imgWidth - maxmizeWidth - maxmizePadding\n const previewTop = imgRelativeTop + maxmizePadding\n\n const previewStyle = `\n left: ${previewLeft}px;\n top: ${previewTop}px;\n width: ${maxmizeWidth}px;\n `\n const imageSrc = target.src || target.getAttribute('data-image')\n const imageId = target.getAttribute('data-image-id')\n\n const previewDom = event.currentTarget.querySelector('.image-preview__overlay')\n if (!previewDom) {\n event.currentTarget.insertAdjacentHTML('beforeend', `\n <div class=\"image-preview__overlay\" style=\"${previewStyle}\">\n <i class=\"icon-maxmize\" id=\"btn-image-preview\" data-image-id=\"${imageId}\"\n data-image=\"${imageSrc}\"></i>\n </div>\n `)\n }\n }\n\n removeImagePreviewOverlay() {\n const previewDom = this.editorElem.querySelector('.image-preview__overlay')\n if (previewDom) {\n previewDom.parentNode.removeChild(previewDom)\n }\n }\n\n onHide() {\n this.removeImagePreviewOverlay()\n super.onHide()\n }\n\n resetOverlayMarginTop() {\n if (this.formatter.overlay) {\n this.formatter.overlay.style.marginTop = '0px'\n }\n }\n\n onClick = (event: MouseEvent) => {\n const el = event.target\n const isReadonly = this.formatter.quill.options.readOnly\n if (!(el instanceof HTMLElement) || el.tagName !== 'IMG' || isReadonly) {\n return\n }\n\n this.img = el as HTMLImageElement\n this.oldRootScrollTop = this.formatter.quill.root.scrollTop\n this.resetOverlayMarginTop()\n this.formatter.show(this)\n\n // 通过图片dom获取图片选区用以复制,设置 current-select-img::selection 取消选区背景\n const imageDom = this.formatter.currentSpec?.getTargetElement()\n if (imageDom) {\n imageDom.classList.add('current-select-img')\n const quill = this.formatter.quill\n const imgBlot = quill.scroll.find(this.img)\n const index = quill.getIndex(imgBlot)\n const len = imgBlot.length()\n quill.setSelection(index, len)\n }\n }\n\n /**\n * 处理图片双击事件 - 显示预览\n */\n onImageDoubleClick = (event: MouseEvent) => {\n const target = event.target\n const imageSrc = target.getAttribute('src') || target.getAttribute('data-image')\n\n if (imageSrc) {\n const modal = getImagePreviewModal()\n modal.show(imageSrc)\n }\n }\n}\n"],"names":[],"mappings":";;;;;;;AAKO,MAAM,wBAAwB,UAAU;AAAA,EAK7C,YAAY,WAA0B;AACpC,UAAM,SAAS;AALjB;AACA;AACA;AAgCA,yCAAgB,CAAC,UAAU;AACzB,UAAI,MAAM,OAAO,aAAa,SACzB,MAAM,OAAO,UAAU,SAAS,yBAAyB,GAAG;AAC/D,cAAM,SAAS,MAAM;AACrB,YAAI,CAAC,SAAS,OAAO,MAAM,GAAG;AAC5B,eAAK,0BAAA;AAAA,QACP;AAAA,MACF;AAAA,IACF;AA6DA,mCAAU,CAAC,UAAsB;;AAC/B,YAAM,KAAK,MAAM;AACjB,YAAM,aAAa,KAAK,UAAU,MAAM,QAAQ;AAChD,UAAI,EAAE,cAAc,gBAAgB,GAAG,YAAY,SAAS,YAAY;AACtE;AAAA,MACF;AAEA,WAAK,MAAM;AACX,WAAK,mBAAmB,KAAK,UAAU,MAAM,KAAK;AAClD,WAAK,sBAAA;AACL,WAAK,UAAU,KAAK,IAAI;AAGxB,YAAM,YAAW,UAAK,UAAU,gBAAf,mBAA4B;AAC7C,UAAI,UAAU;AACZ,iBAAS,UAAU,IAAI,oBAAoB;AAC3C,cAAM,QAAQ,KAAK,UAAU;AAC7B,cAAM,UAAU,MAAM,OAAO,KAAK,KAAK,GAAG;AAC1C,cAAM,QAAQ,MAAM,SAAS,OAAO;AACpC,cAAM,MAAM,QAAQ,OAAA;AACpB,cAAM,aAAa,OAAO,GAAG;AAAA,MAC/B;AAAA,IACF;AAKA;AAAA;AAAA;AAAA,8CAAqB,CAAC,UAAsB;AAC1C,YAAM,SAAS,MAAM;AACrB,YAAM,WAAW,OAAO,aAAa,KAAK,KAAK,OAAO,aAAa,YAAY;AAE/E,UAAI,UAAU;AACZ,cAAM,QAAQ,qBAAA;AACd,cAAM,KAAK,QAAQ;AAAA,MACrB;AAAA,IACF;AApIE,SAAK,YAAY;AACjB,SAAK,mBAAmB,KAAK,UAAU,MAAM,KAAK;AAClD,SAAK,aAAa,KAAK,UAAU,MAAM;AACvC,SAAK,UAAU,MAAM,KAAK,iBAAiB,UAAU,KAAK,sBAAsB,KAAK,IAAI,CAAC;AAAA,EAC5F;AAAA,EAEA,wBAAwB;AACtB,QAAI,KAAK,UAAU,SAAS;AAC1B,WAAK,UAAU,QAAQ,MAAM,YAAY,GAAG,KAAK,mBAAmB,KAAK,UAAU,MAAM,KAAK,SAAS;AAAA,IACzG;AAAA,EACF;AAAA,EAEA,OAAa;AACX,SAAK,WAAW,iBAAiB,aAAa,KAAK,eAAe,KAAK,IAAI,CAAC;AAC5E,SAAK,WAAW,iBAAiB,YAAY,KAAK,aAAa;AAC/D,SAAK,WAAW,iBAAiB,YAAY,KAAK,mBAAmB,KAAK,IAAI,CAAC;AAE/E,UAAM,KAAA;AAAA,EACR;AAAA,EAEA,eAAe,OAAO;;AACpB,UAAM,SAAS,MAAM;AACrB,UAAM,mBAAkB,sCAAQ,cAAR,mBAAmB,SAAS;AACpD,QAAI,OAAO,aAAa,SAAS,iBAAiB;AAAA,IAElD;AAAA,EACF;AAAA,EAYA,uBAAuB,OAAO;AAC5B,UAAM,SAAS,MAAM;AACrB,UAAM;AAAA,MACJ,MAAM;AAAA,MACN,OAAO;AAAA,IAAA,IACL,OAAO,sBAAA;AAEX,UAAM,SAAS,OAAO,wBAAwB,MAAM,KAAK,UAAU,MAAM,UAAU;AAEnF,UAAM;AAAA,MACJ,MAAM;AAAA,MACN,KAAK;AAAA,IAAA,IACH,MAAM,cAAc,sBAAA;AAExB,UAAM,kBAAkB,UAAU;AAClC,UAAM,iBAAiB,SAAS;AAEhC,UAAM,eAAe;AACrB,UAAM,iBAAiB;AACvB,UAAM,cAAc,kBAAkB,WAAW,eAAe;AAChE,UAAM,aAAa,iBAAiB;AAEpC,UAAM,eAAe;AAAA,gBACT,WAAW;AAAA,eACZ,UAAU;AAAA,iBACR,YAAY;AAAA;AAEzB,UAAM,WAAW,OAAO,OAAO,OAAO,aAAa,YAAY;AAC/D,UAAM,UAAU,OAAO,aAAa,eAAe;AAEnD,UAAM,aAAa,MAAM,cAAc,cAAc,yBAAyB;AAC9E,QAAI,CAAC,YAAY;AACf,YAAM,cAAc,mBAAmB,aAAa;AAAA,uDACH,YAAY;AAAA,4EACS,OAAO;AAAA,4BACvD,QAAQ;AAAA;AAAA,SAE3B;AAAA,IACL;AAAA,EACF;AAAA,EAEA,4BAA4B;AAC1B,UAAM,aAAa,KAAK,WAAW,cAAc,yBAAyB;AAC1E,QAAI,YAAY;AACd,iBAAW,WAAW,YAAY,UAAU;AAAA,IAC9C;AAAA,EACF;AAAA,EAEA,SAAS;AACP,SAAK,0BAAA;AACL,UAAM,OAAA;AAAA,EACR;AAAA,EAEA,wBAAwB;AACtB,QAAI,KAAK,UAAU,SAAS;AAC1B,WAAK,UAAU,QAAQ,MAAM,YAAY;AAAA,IAC3C;AAAA,EACF;AAsCF;"}
|
package/lib/index.cjs.js
CHANGED
|
@@ -31,6 +31,7 @@ const image = require("./modules/custom-image/image.cjs.js");
|
|
|
31
31
|
const blotSpec = require("./modules/custom-image/specs/blot-spec.cjs.js");
|
|
32
32
|
const customImageSpec = require("./modules/custom-image/specs/custom-image-spec.cjs.js");
|
|
33
33
|
const imageSpec = require("./modules/custom-image/specs/image-spec.cjs.js");
|
|
34
|
+
const previewModal = require("./modules/custom-image/preview/preview-modal.cjs.js");
|
|
34
35
|
const customUploader = require("./modules/custom-uploader.cjs.js");
|
|
35
36
|
const divider = require("./modules/divider.cjs.js");
|
|
36
37
|
const emoji$1 = require("./modules/emoji.cjs.js");
|
|
@@ -132,6 +133,8 @@ exports.CustomImage = image.CustomImage;
|
|
|
132
133
|
exports.BlotSpec = blotSpec.BlotSpec;
|
|
133
134
|
exports.CustomImageSpec = customImageSpec.CustomImageSpec;
|
|
134
135
|
exports.ImageSpec = imageSpec.ImageSpec;
|
|
136
|
+
exports.ImagePreviewModal = previewModal.ImagePreviewModal;
|
|
137
|
+
exports.getImagePreviewModal = previewModal.getImagePreviewModal;
|
|
135
138
|
exports.FileUploader = customUploader.FileUploader;
|
|
136
139
|
exports.DividerBlot = divider.DividerBlot;
|
|
137
140
|
exports.EmojiModule = emoji$1.EmojiModule;
|
package/lib/index.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -5,6 +5,8 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
5
5
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
6
6
|
const is = require("../../../utils/is.cjs.js");
|
|
7
7
|
const options = require("../options.cjs.js");
|
|
8
|
+
require("../preview/index.cjs.js");
|
|
9
|
+
const previewModal = require("../preview/preview-modal.cjs.js");
|
|
8
10
|
const ALIGN_ATTR = "data-align";
|
|
9
11
|
function setAlignStyle(el, display, float, margin) {
|
|
10
12
|
el.style.setProperty("display", display);
|
|
@@ -108,6 +110,17 @@ const defaultButtons = {
|
|
|
108
110
|
apply: (el, toolbarButtons) => {
|
|
109
111
|
alignmentHandler.copy(el, toolbarButtons);
|
|
110
112
|
}
|
|
113
|
+
},
|
|
114
|
+
[options.PREVIEW]: {
|
|
115
|
+
name: options.PREVIEW,
|
|
116
|
+
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path class="ql-fill" d="M16 7c-4.96 0-9.23 3.13-11 7.5 1.77 4.37 6.04 7.5 11 7.5s9.23-3.13 11-7.5c-1.77-4.37-6.04-7.5-11-7.5zm0 12c-2.49 0-4.5-2.01-4.5-4.5S13.51 10 16 10s4.5 2.01 4.5 4.5-2.01 4.5-4.5 4.5zm0-7c-1.38 0-2.5 1.12-2.5 2.5s1.12 2.5 2.5 2.5 2.5-1.12 2.5-2.5-1.12-2.5-2.5-2.5z"/></svg>`,
|
|
117
|
+
apply: (el, toolbarButtons) => {
|
|
118
|
+
const imageSrc = el.getAttribute("src") || el.getAttribute("data-src");
|
|
119
|
+
if (imageSrc) {
|
|
120
|
+
const modal = previewModal.getImagePreviewModal();
|
|
121
|
+
modal.show(imageSrc);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
111
124
|
}
|
|
112
125
|
};
|
|
113
126
|
class ImageToolbarButtons {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-toolbar-buttons.cjs.js","sources":["../../../../../src/modules/custom-image/actions/image-toolbar-buttons.ts"],"sourcesContent":["import type { ToolbarButtonOptions, ToolButtonOption } from '../options'\r\nimport { isBoolean, isObject } from '../../../utils/is'\r\nimport { CENTER_ALIGN, COPY, DOWNLOAD, LEFT_ALIGN, RIGHT_ALIGN } from '../options'\r\n\r\nexport const ALIGN_ATTR = 'data-align'\r\n\r\nexport function setAlignStyle(el: HTMLElement, display: string | null, float: string | null, margin: string | null) {\r\n el.style.setProperty('display', display)\r\n el.style.setProperty('float', float)\r\n el.style.setProperty('margin', margin)\r\n}\r\nexport const alignmentHandler = {\r\n left: (el: HTMLElement, toolbarButtons: ImageToolbarButtons) => {\r\n setAlignStyle(el, 'inline', 'left', '0 1em 1em 0')\r\n },\r\n center: (el: HTMLElement, toolbarButtons: ImageToolbarButtons) => {\r\n setAlignStyle(el, 'block', null, 'auto')\r\n },\r\n right: (el: HTMLElement, toolbarButtons: ImageToolbarButtons) => {\r\n setAlignStyle(el, 'inline', 'right', '0 0 1em 1em')\r\n },\r\n download: (el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => {\r\n const imageName = el.dataset.title || 'image'\r\n const url = el.src || ''\r\n if (!url) return\r\n const a = document.createElement('a')\r\n a.href = url\r\n a.target = '_blank'\r\n a.download = imageName\r\n a.style.display = 'none'\r\n document.body.appendChild(a)\r\n a.click()\r\n a.parentNode.removeChild(a)\r\n },\r\n copy: async (el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => {\r\n if (!el.src) return\r\n const imageUrl = el.src\r\n try {\r\n const response = await fetch(imageUrl)\r\n if (!response.ok) {\r\n throw new Error('Copy image failed')\r\n }\r\n const blob = await response.blob()\r\n await navigator.clipboard.write([new ClipboardItem({ [blob.type]: blob })])\r\n }\r\n catch (e) {\r\n throw new Error('Copy image failed')\r\n }\r\n },\r\n}\r\nconst defaultButtons: Record<string, ToolButtonOption> = {\r\n [LEFT_ALIGN]: {\r\n name: LEFT_ALIGN,\r\n icon: `\r\n <svg viewbox=\"0 0 18 18\">\r\n <line class=\"ql-stroke\" x1=\"3\" x2=\"15\" y1=\"9\" y2=\"9\"></line>\r\n <line class=\"ql-stroke\" x1=\"3\" x2=\"13\" y1=\"14\" y2=\"14\"></line>\r\n <line class=\"ql-stroke\" x1=\"3\" x2=\"9\" y1=\"4\" y2=\"4\"></line>\r\n </svg>\r\n `,\r\n isActive: el => el.getAttribute(ALIGN_ATTR) === 'left',\r\n apply: (el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => {\r\n el.setAttribute(ALIGN_ATTR, 'left')\r\n alignmentHandler.left(el, toolbarButtons)\r\n },\r\n },\r\n [CENTER_ALIGN]: {\r\n name: CENTER_ALIGN,\r\n icon: `\r\n <svg viewbox=\"0 0 18 18\">\r\n <line class=\"ql-stroke\" x1=\"15\" x2=\"3\" y1=\"9\" y2=\"9\"></line>\r\n <line class=\"ql-stroke\" x1=\"14\" x2=\"4\" y1=\"14\" y2=\"14\"></line>\r\n <line class=\"ql-stroke\" x1=\"12\" x2=\"6\" y1=\"4\" y2=\"4\"></line>\r\n </svg>\r\n `,\r\n isActive: el => el.getAttribute(ALIGN_ATTR) === 'center',\r\n apply: (el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => {\r\n el.setAttribute(ALIGN_ATTR, 'center')\r\n alignmentHandler.center(el, toolbarButtons)\r\n },\r\n },\r\n [RIGHT_ALIGN]: {\r\n name: RIGHT_ALIGN,\r\n icon: `\r\n <svg viewbox=\"0 0 18 18\">\r\n <line class=\"ql-stroke\" x1=\"15\" x2=\"3\" y1=\"9\" y2=\"9\"></line>\r\n <line class=\"ql-stroke\" x1=\"15\" x2=\"5\" y1=\"14\" y2=\"14\"></line>\r\n <line class=\"ql-stroke\" x1=\"15\" x2=\"9\" y1=\"4\" y2=\"4\"></line>\r\n </svg>\r\n `,\r\n isActive: el => el.getAttribute(ALIGN_ATTR) === 'right',\r\n apply: (el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => {\r\n el.setAttribute(ALIGN_ATTR, 'right')\r\n alignmentHandler.right(el, toolbarButtons)\r\n },\r\n },\r\n [DOWNLOAD]: {\r\n name: DOWNLOAD,\r\n icon: `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" viewBox=\"0 0 32 32\"><path class=\"ql-fill\" d=\"M26 24v4H6v-4H4v4a2 2 0 0 0 2 2h20a2 2 0 0 0 2-2v-4zm0-10l-1.41-1.41L17 20.17V2h-2v18.17l-7.59-7.58L6 14l10 10z\"/></svg>`,\r\n apply: (el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => {\r\n alignmentHandler.download(el, toolbarButtons)\r\n },\r\n },\r\n [COPY]: {\r\n name: COPY,\r\n icon: `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" viewBox=\"0 0 32 32\"><path class=\"ql-fill\" d=\"M28 10v18H10V10zm0-2H10a2 2 0 0 0-2 2v18a2 2 0 0 0 2 2h18a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2\"/><path class=\"ql-fill\" d=\"M4 18H2V4a2 2 0 0 1 2-2h14v2H4Z\"/></svg>`,\r\n apply: (el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => {\r\n alignmentHandler.copy(el, toolbarButtons)\r\n },\r\n },\r\n}\r\nexport class ImageToolbarButtons {\r\n buttons: Record<string, ToolButtonOption>\r\n\r\n constructor(options: ToolbarButtonOptions) {\r\n this.buttons = Object.entries(options.buttons).reduce((acc, [name, button]) => {\r\n if (isBoolean(button) && button && defaultButtons[name]) {\r\n acc[name] = defaultButtons[name]\r\n }\r\n else if (isObject(button)) {\r\n acc[button.name] = button\r\n }\r\n return acc\r\n }, {})\r\n }\r\n\r\n getItems(): ToolButtonOption[] {\r\n return Object.keys(this.buttons).map(k => this.buttons[k])\r\n }\r\n\r\n clear(el: HTMLElement): void {\r\n el.removeAttribute(ALIGN_ATTR)\r\n setAlignStyle(el, null, null, null)\r\n }\r\n}\r\n"],"names":["LEFT_ALIGN","CENTER_ALIGN","RIGHT_ALIGN","DOWNLOAD","COPY","options","isBoolean","isObject"],"mappings":"
|
|
1
|
+
{"version":3,"file":"image-toolbar-buttons.cjs.js","sources":["../../../../../src/modules/custom-image/actions/image-toolbar-buttons.ts"],"sourcesContent":["import type { ToolbarButtonOptions, ToolButtonOption } from '../options'\r\nimport { isBoolean, isObject } from '../../../utils/is'\r\nimport { CENTER_ALIGN, COPY, DOWNLOAD, LEFT_ALIGN, PREVIEW, RIGHT_ALIGN } from '../options'\r\nimport { getImagePreviewModal } from '../preview'\r\n\r\nexport const ALIGN_ATTR = 'data-align'\r\n\r\nexport function setAlignStyle(el: HTMLElement, display: string | null, float: string | null, margin: string | null) {\r\n el.style.setProperty('display', display)\r\n el.style.setProperty('float', float)\r\n el.style.setProperty('margin', margin)\r\n}\r\nexport const alignmentHandler = {\r\n left: (el: HTMLElement, toolbarButtons: ImageToolbarButtons) => {\r\n setAlignStyle(el, 'inline', 'left', '0 1em 1em 0')\r\n },\r\n center: (el: HTMLElement, toolbarButtons: ImageToolbarButtons) => {\r\n setAlignStyle(el, 'block', null, 'auto')\r\n },\r\n right: (el: HTMLElement, toolbarButtons: ImageToolbarButtons) => {\r\n setAlignStyle(el, 'inline', 'right', '0 0 1em 1em')\r\n },\r\n download: (el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => {\r\n const imageName = el.dataset.title || 'image'\r\n const url = el.src || ''\r\n if (!url) return\r\n const a = document.createElement('a')\r\n a.href = url\r\n a.target = '_blank'\r\n a.download = imageName\r\n a.style.display = 'none'\r\n document.body.appendChild(a)\r\n a.click()\r\n a.parentNode.removeChild(a)\r\n },\r\n copy: async (el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => {\r\n if (!el.src) return\r\n const imageUrl = el.src\r\n try {\r\n const response = await fetch(imageUrl)\r\n if (!response.ok) {\r\n throw new Error('Copy image failed')\r\n }\r\n const blob = await response.blob()\r\n await navigator.clipboard.write([new ClipboardItem({ [blob.type]: blob })])\r\n }\r\n catch (e) {\r\n throw new Error('Copy image failed')\r\n }\r\n },\r\n}\r\nconst defaultButtons: Record<string, ToolButtonOption> = {\r\n [LEFT_ALIGN]: {\r\n name: LEFT_ALIGN,\r\n icon: `\r\n <svg viewbox=\"0 0 18 18\">\r\n <line class=\"ql-stroke\" x1=\"3\" x2=\"15\" y1=\"9\" y2=\"9\"></line>\r\n <line class=\"ql-stroke\" x1=\"3\" x2=\"13\" y1=\"14\" y2=\"14\"></line>\r\n <line class=\"ql-stroke\" x1=\"3\" x2=\"9\" y1=\"4\" y2=\"4\"></line>\r\n </svg>\r\n `,\r\n isActive: el => el.getAttribute(ALIGN_ATTR) === 'left',\r\n apply: (el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => {\r\n el.setAttribute(ALIGN_ATTR, 'left')\r\n alignmentHandler.left(el, toolbarButtons)\r\n },\r\n },\r\n [CENTER_ALIGN]: {\r\n name: CENTER_ALIGN,\r\n icon: `\r\n <svg viewbox=\"0 0 18 18\">\r\n <line class=\"ql-stroke\" x1=\"15\" x2=\"3\" y1=\"9\" y2=\"9\"></line>\r\n <line class=\"ql-stroke\" x1=\"14\" x2=\"4\" y1=\"14\" y2=\"14\"></line>\r\n <line class=\"ql-stroke\" x1=\"12\" x2=\"6\" y1=\"4\" y2=\"4\"></line>\r\n </svg>\r\n `,\r\n isActive: el => el.getAttribute(ALIGN_ATTR) === 'center',\r\n apply: (el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => {\r\n el.setAttribute(ALIGN_ATTR, 'center')\r\n alignmentHandler.center(el, toolbarButtons)\r\n },\r\n },\r\n [RIGHT_ALIGN]: {\r\n name: RIGHT_ALIGN,\r\n icon: `\r\n <svg viewbox=\"0 0 18 18\">\r\n <line class=\"ql-stroke\" x1=\"15\" x2=\"3\" y1=\"9\" y2=\"9\"></line>\r\n <line class=\"ql-stroke\" x1=\"15\" x2=\"5\" y1=\"14\" y2=\"14\"></line>\r\n <line class=\"ql-stroke\" x1=\"15\" x2=\"9\" y1=\"4\" y2=\"4\"></line>\r\n </svg>\r\n `,\r\n isActive: el => el.getAttribute(ALIGN_ATTR) === 'right',\r\n apply: (el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => {\r\n el.setAttribute(ALIGN_ATTR, 'right')\r\n alignmentHandler.right(el, toolbarButtons)\r\n },\r\n },\r\n [DOWNLOAD]: {\r\n name: DOWNLOAD,\r\n icon: `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" viewBox=\"0 0 32 32\"><path class=\"ql-fill\" d=\"M26 24v4H6v-4H4v4a2 2 0 0 0 2 2h20a2 2 0 0 0 2-2v-4zm0-10l-1.41-1.41L17 20.17V2h-2v18.17l-7.59-7.58L6 14l10 10z\"/></svg>`,\r\n apply: (el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => {\r\n alignmentHandler.download(el, toolbarButtons)\r\n },\r\n },\r\n [COPY]: {\r\n name: COPY,\r\n icon: `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" viewBox=\"0 0 32 32\"><path class=\"ql-fill\" d=\"M28 10v18H10V10zm0-2H10a2 2 0 0 0-2 2v18a2 2 0 0 0 2 2h18a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2\"/><path class=\"ql-fill\" d=\"M4 18H2V4a2 2 0 0 1 2-2h14v2H4Z\"/></svg>`,\r\n apply: (el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => {\r\n alignmentHandler.copy(el, toolbarButtons)\r\n },\r\n },\r\n [PREVIEW]: {\r\n name: PREVIEW,\r\n icon: `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" viewBox=\"0 0 32 32\"><path class=\"ql-fill\" d=\"M16 7c-4.96 0-9.23 3.13-11 7.5 1.77 4.37 6.04 7.5 11 7.5s9.23-3.13 11-7.5c-1.77-4.37-6.04-7.5-11-7.5zm0 12c-2.49 0-4.5-2.01-4.5-4.5S13.51 10 16 10s4.5 2.01 4.5 4.5-2.01 4.5-4.5 4.5zm0-7c-1.38 0-2.5 1.12-2.5 2.5s1.12 2.5 2.5 2.5 2.5-1.12 2.5-2.5-1.12-2.5-2.5-2.5z\"/></svg>`,\r\n apply: (el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => {\r\n const imageSrc = el.getAttribute('src') || el.getAttribute('data-src')\r\n if (imageSrc) {\r\n const modal = getImagePreviewModal()\r\n modal.show(imageSrc)\r\n }\r\n },\r\n },\r\n}\r\nexport class ImageToolbarButtons {\r\n buttons: Record<string, ToolButtonOption>\r\n\r\n constructor(options: ToolbarButtonOptions) {\r\n this.buttons = Object.entries(options.buttons).reduce((acc, [name, button]) => {\r\n if (isBoolean(button) && button && defaultButtons[name]) {\r\n acc[name] = defaultButtons[name]\r\n }\r\n else if (isObject(button)) {\r\n acc[button.name] = button\r\n }\r\n return acc\r\n }, {})\r\n }\r\n\r\n getItems(): ToolButtonOption[] {\r\n return Object.keys(this.buttons).map(k => this.buttons[k])\r\n }\r\n\r\n clear(el: HTMLElement): void {\r\n el.removeAttribute(ALIGN_ATTR)\r\n setAlignStyle(el, null, null, null)\r\n }\r\n}\r\n"],"names":["LEFT_ALIGN","CENTER_ALIGN","RIGHT_ALIGN","DOWNLOAD","COPY","PREVIEW","getImagePreviewModal","options","isBoolean","isObject"],"mappings":"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"}
|
|
@@ -4,6 +4,7 @@ require("./actions/index.cjs.js");
|
|
|
4
4
|
const blotFormatter = require("./blot-formatter.cjs.js");
|
|
5
5
|
const image = require("./image.cjs.js");
|
|
6
6
|
require("./specs/index.cjs.js");
|
|
7
|
+
require("./preview/index.cjs.js");
|
|
7
8
|
exports.BlotFormatter = blotFormatter.BlotFormatter;
|
|
8
9
|
exports.CustomImage = image.CustomImage;
|
|
9
10
|
//# sourceMappingURL=index.cjs.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;"}
|
|
@@ -7,6 +7,7 @@ const CENTER_ALIGN = "align-center";
|
|
|
7
7
|
const RIGHT_ALIGN = "align-right";
|
|
8
8
|
const COPY = "copy";
|
|
9
9
|
const DOWNLOAD = "download";
|
|
10
|
+
const PREVIEW = "preview";
|
|
10
11
|
const DefaultOptions = {
|
|
11
12
|
// 默认情况下,`file://` 格式的本地文件路径在浏览器环境无法读取,因此会被转换成 `//:0`,但是在一些特殊的场景下(比如:Electron),需要获取到图片的原始路径,进行后续的上传处理
|
|
12
13
|
// 注意:该选项一旦设置为 true,本地磁盘路径会暴露出去,这可能会带来安全风险,请确保你了解相关的安全隐患
|
|
@@ -63,7 +64,8 @@ const DefaultOptions = {
|
|
|
63
64
|
[CENTER_ALIGN]: true,
|
|
64
65
|
[RIGHT_ALIGN]: true,
|
|
65
66
|
[COPY]: true,
|
|
66
|
-
[DOWNLOAD]: true
|
|
67
|
+
[DOWNLOAD]: true,
|
|
68
|
+
[PREVIEW]: true
|
|
67
69
|
}
|
|
68
70
|
},
|
|
69
71
|
resize: {
|
|
@@ -83,6 +85,7 @@ exports.CENTER_ALIGN = CENTER_ALIGN;
|
|
|
83
85
|
exports.COPY = COPY;
|
|
84
86
|
exports.DOWNLOAD = DOWNLOAD;
|
|
85
87
|
exports.LEFT_ALIGN = LEFT_ALIGN;
|
|
88
|
+
exports.PREVIEW = PREVIEW;
|
|
86
89
|
exports.RIGHT_ALIGN = RIGHT_ALIGN;
|
|
87
90
|
exports.default = DefaultOptions;
|
|
88
91
|
//# sourceMappingURL=options.cjs.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"options.cjs.js","sources":["../../../../src/modules/custom-image/options.ts"],"sourcesContent":["import type { ImageToolbar, ImageToolbarButtons } from './actions'\nimport type { BlotSpec } from './specs'\nimport { ImageSpec } from './specs'\n\nexport interface OverlayOptions {\n // classname applied to the overlay element\n className: string\n // style applied to overlay element, or null to prevent styles\n style: Record<string, string>\n}\n\nexport interface ResizeOptions {\n // class name applied to the resize handles\n handleClassName: string\n // style applied to resize handles, or null to prevent styles\n handleStyle: Record<string, string>\n}\n\nexport interface ToolButtonOption {\n name: string\n icon: string\n isActive?: (el: HTMLElement) => boolean\n apply: (this: ImageToolbar, el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => void\n}\n\nexport interface ToolbarButtonOptions {\n buttons: Record<string, ToolButtonOption | boolean>\n}\n\nexport interface ToolbarOptions extends ToolbarButtonOptions {\n // class name applied to the root toolbar element\n mainClassName: string\n // style applied to root toolbar element, or null to prevent styles\n mainStyle: Record<string, unknown>\n // class name applied to each button in the toolbar\n buttonClassName: string\n /* whether or not to add the selected style to the buttons.\n they'll always get the is-selected class */\n addButtonSelectStyle: boolean\n // style applied to buttons, or null to prevent styles\n buttonStyle: Record<string, unknown>\n // style applied to the svgs in the buttons\n svgStyle: Record<string, unknown>\n}\n\nexport interface BlotFormatterOptionsInput {\n specs: typeof BlotSpec[]\n overlay: Partial<OverlayOptions>\n resize: Partial<ResizeOptions>\n toolbar: Partial<ToolbarOptions>\n}\nexport interface BlotFormatterOptions {\n specs: typeof BlotSpec[]\n overlay: OverlayOptions\n resize: ResizeOptions\n toolbar: ToolbarOptions\n allowInvalidUrl: boolean\n}\n\nexport const LEFT_ALIGN = 'align-left'\nexport const CENTER_ALIGN = 'align-center'\nexport const RIGHT_ALIGN = 'align-right'\nexport const COPY = 'copy'\nexport const DOWNLOAD = 'download'\nconst DefaultOptions: BlotFormatterOptions = {\n // 默认情况下,`file://` 格式的本地文件路径在浏览器环境无法读取,因此会被转换成 `//:0`,但是在一些特殊的场景下(比如:Electron),需要获取到图片的原始路径,进行后续的上传处理\n // 注意:该选项一旦设置为 true,本地磁盘路径会暴露出去,这可能会带来安全风险,请确保你了解相关的安全隐患\n allowInvalidUrl: false,\n\n specs: [\n ImageSpec,\n ],\n overlay: {\n className: 'blot-formatter__overlay',\n style: {\n position: 'absolute',\n boxSizing: 'border-box',\n border: '1px dashed #444',\n },\n },\n toolbar: {\n mainClassName: 'blot-formatter__toolbar',\n mainStyle: {\n position: 'absolute',\n top: '-12px',\n right: '0',\n left: '0',\n height: '0',\n minWidth: '120px',\n font: '12px/1.0 Arial, Helvetica, sans-serif',\n textAlign: 'center',\n color: '#333',\n boxSizing: 'border-box',\n cursor: 'default',\n zIndex: '1',\n },\n buttonClassName: 'blot-formatter__toolbar-button',\n addButtonSelectStyle: true,\n buttonStyle: {\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: '24px',\n height: '24px',\n background: 'white',\n border: '1px solid #999',\n verticalAlign: 'middle',\n cursor: 'pointer',\n },\n svgStyle: {\n display: 'inline-block',\n width: '16px',\n height: '16px',\n background: 'white',\n verticalAlign: 'middle',\n },\n buttons: {\n [LEFT_ALIGN]: true,\n [CENTER_ALIGN]: true,\n [RIGHT_ALIGN]: true,\n [COPY]: true,\n [DOWNLOAD]: true,\n },\n },\n resize: {\n handleClassName: 'blot-formatter__resize-handle',\n handleStyle: {\n position: 'absolute',\n height: '12px',\n width: '12px',\n backgroundColor: 'white',\n border: '1px solid #777',\n boxSizing: 'border-box',\n opacity: '0.80',\n },\n },\n}\n\nexport default DefaultOptions\n"],"names":["ImageSpec"],"mappings":";;;;AA2DO,MAAM,aAAa;AACnB,MAAM,eAAe;AACrB,MAAM,cAAc;AACpB,MAAM,OAAO;AACb,MAAM,WAAW;
|
|
1
|
+
{"version":3,"file":"options.cjs.js","sources":["../../../../src/modules/custom-image/options.ts"],"sourcesContent":["import type { ImageToolbar, ImageToolbarButtons } from './actions'\nimport type { BlotSpec } from './specs'\nimport { ImageSpec } from './specs'\n\nexport interface OverlayOptions {\n // classname applied to the overlay element\n className: string\n // style applied to overlay element, or null to prevent styles\n style: Record<string, string>\n}\n\nexport interface ResizeOptions {\n // class name applied to the resize handles\n handleClassName: string\n // style applied to resize handles, or null to prevent styles\n handleStyle: Record<string, string>\n}\n\nexport interface ToolButtonOption {\n name: string\n icon: string\n isActive?: (el: HTMLElement) => boolean\n apply: (this: ImageToolbar, el: HTMLImageElement, toolbarButtons: ImageToolbarButtons) => void\n}\n\nexport interface ToolbarButtonOptions {\n buttons: Record<string, ToolButtonOption | boolean>\n}\n\nexport interface ToolbarOptions extends ToolbarButtonOptions {\n // class name applied to the root toolbar element\n mainClassName: string\n // style applied to root toolbar element, or null to prevent styles\n mainStyle: Record<string, unknown>\n // class name applied to each button in the toolbar\n buttonClassName: string\n /* whether or not to add the selected style to the buttons.\n they'll always get the is-selected class */\n addButtonSelectStyle: boolean\n // style applied to buttons, or null to prevent styles\n buttonStyle: Record<string, unknown>\n // style applied to the svgs in the buttons\n svgStyle: Record<string, unknown>\n}\n\nexport interface BlotFormatterOptionsInput {\n specs: typeof BlotSpec[]\n overlay: Partial<OverlayOptions>\n resize: Partial<ResizeOptions>\n toolbar: Partial<ToolbarOptions>\n}\nexport interface BlotFormatterOptions {\n specs: typeof BlotSpec[]\n overlay: OverlayOptions\n resize: ResizeOptions\n toolbar: ToolbarOptions\n allowInvalidUrl: boolean\n}\n\nexport const LEFT_ALIGN = 'align-left'\nexport const CENTER_ALIGN = 'align-center'\nexport const RIGHT_ALIGN = 'align-right'\nexport const COPY = 'copy'\nexport const DOWNLOAD = 'download'\nexport const PREVIEW = 'preview'\nconst DefaultOptions: BlotFormatterOptions = {\n // 默认情况下,`file://` 格式的本地文件路径在浏览器环境无法读取,因此会被转换成 `//:0`,但是在一些特殊的场景下(比如:Electron),需要获取到图片的原始路径,进行后续的上传处理\n // 注意:该选项一旦设置为 true,本地磁盘路径会暴露出去,这可能会带来安全风险,请确保你了解相关的安全隐患\n allowInvalidUrl: false,\n\n specs: [\n ImageSpec,\n ],\n overlay: {\n className: 'blot-formatter__overlay',\n style: {\n position: 'absolute',\n boxSizing: 'border-box',\n border: '1px dashed #444',\n },\n },\n toolbar: {\n mainClassName: 'blot-formatter__toolbar',\n mainStyle: {\n position: 'absolute',\n top: '-12px',\n right: '0',\n left: '0',\n height: '0',\n minWidth: '120px',\n font: '12px/1.0 Arial, Helvetica, sans-serif',\n textAlign: 'center',\n color: '#333',\n boxSizing: 'border-box',\n cursor: 'default',\n zIndex: '1',\n },\n buttonClassName: 'blot-formatter__toolbar-button',\n addButtonSelectStyle: true,\n buttonStyle: {\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: '24px',\n height: '24px',\n background: 'white',\n border: '1px solid #999',\n verticalAlign: 'middle',\n cursor: 'pointer',\n },\n svgStyle: {\n display: 'inline-block',\n width: '16px',\n height: '16px',\n background: 'white',\n verticalAlign: 'middle',\n },\n buttons: {\n [LEFT_ALIGN]: true,\n [CENTER_ALIGN]: true,\n [RIGHT_ALIGN]: true,\n [COPY]: true,\n [DOWNLOAD]: true,\n [PREVIEW]: true,\n },\n },\n resize: {\n handleClassName: 'blot-formatter__resize-handle',\n handleStyle: {\n position: 'absolute',\n height: '12px',\n width: '12px',\n backgroundColor: 'white',\n border: '1px solid #777',\n boxSizing: 'border-box',\n opacity: '0.80',\n },\n },\n}\n\nexport default DefaultOptions\n"],"names":["ImageSpec"],"mappings":";;;;AA2DO,MAAM,aAAa;AACnB,MAAM,eAAe;AACrB,MAAM,cAAc;AACpB,MAAM,OAAO;AACb,MAAM,WAAW;AACjB,MAAM,UAAU;AACvB,MAAM,iBAAuC;AAAA;AAAA;AAAA,EAG3C,iBAAiB;AAAA,EAEjB,OAAO;AAAA,IACLA,UAAAA;AAAAA,EAAA;AAAA,EAEF,SAAS;AAAA,IACP,WAAW;AAAA,IACX,OAAO;AAAA,MACL,UAAU;AAAA,MACV,WAAW;AAAA,MACX,QAAQ;AAAA,IAAA;AAAA,EACV;AAAA,EAEF,SAAS;AAAA,IACP,eAAe;AAAA,IACf,WAAW;AAAA,MACT,UAAU;AAAA,MACV,KAAK;AAAA,MACL,OAAO;AAAA,MACP,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,MAAM;AAAA,MACN,WAAW;AAAA,MACX,OAAO;AAAA,MACP,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,QAAQ;AAAA,IAAA;AAAA,IAEV,iBAAiB;AAAA,IACjB,sBAAsB;AAAA,IACtB,aAAa;AAAA,MACX,SAAS;AAAA,MACT,YAAY;AAAA,MACZ,gBAAgB;AAAA,MAChB,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,eAAe;AAAA,MACf,QAAQ;AAAA,IAAA;AAAA,IAEV,UAAU;AAAA,MACR,SAAS;AAAA,MACT,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ,eAAe;AAAA,IAAA;AAAA,IAEjB,SAAS;AAAA,MACP,CAAC,UAAU,GAAG;AAAA,MACd,CAAC,YAAY,GAAG;AAAA,MAChB,CAAC,WAAW,GAAG;AAAA,MACf,CAAC,IAAI,GAAG;AAAA,MACR,CAAC,QAAQ,GAAG;AAAA,MACZ,CAAC,OAAO,GAAG;AAAA,IAAA;AAAA,EACb;AAAA,EAEF,QAAQ;AAAA,IACN,iBAAiB;AAAA,IACjB,aAAa;AAAA,MACX,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,iBAAiB;AAAA,MACjB,QAAQ;AAAA,MACR,WAAW;AAAA,MACX,SAAS;AAAA,IAAA;AAAA,EACX;AAEJ;;;;;;;;"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const previewModal = require("./preview-modal.cjs.js");
|
|
4
|
+
exports.ImagePreviewModal = previewModal.ImagePreviewModal;
|
|
5
|
+
exports.getImagePreviewModal = previewModal.getImagePreviewModal;
|
|
6
|
+
//# sourceMappingURL=index.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;"}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
4
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
5
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
6
|
+
class ImagePreviewModal {
|
|
7
|
+
constructor() {
|
|
8
|
+
__publicField(this, "modal", null);
|
|
9
|
+
__publicField(this, "overlay", null);
|
|
10
|
+
__publicField(this, "previewImage", null);
|
|
11
|
+
__publicField(this, "scaleTooltip", null);
|
|
12
|
+
__publicField(this, "currentScale", 1);
|
|
13
|
+
__publicField(this, "minScale", 0.5);
|
|
14
|
+
__publicField(this, "maxScale", 3);
|
|
15
|
+
__publicField(this, "scaleStep", 0.1);
|
|
16
|
+
__publicField(this, "tooltipHideTimer", null);
|
|
17
|
+
/**
|
|
18
|
+
* 处理鼠标滚轮事件 - 缩放图片
|
|
19
|
+
*/
|
|
20
|
+
__publicField(this, "onMouseWheel", (event) => {
|
|
21
|
+
if (!this.modal || this.modal.style.display === "none") {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
event.preventDefault();
|
|
25
|
+
const delta = event.deltaY > 0 ? -this.scaleStep : this.scaleStep;
|
|
26
|
+
this.setScale(this.currentScale + delta);
|
|
27
|
+
this.showScaleTooltip();
|
|
28
|
+
});
|
|
29
|
+
this.initModal();
|
|
30
|
+
}
|
|
31
|
+
initModal() {
|
|
32
|
+
this.overlay = document.createElement("div");
|
|
33
|
+
this.overlay.className = "image-preview-overlay";
|
|
34
|
+
this.overlay.style.cssText = `
|
|
35
|
+
position: fixed;
|
|
36
|
+
top: 0;
|
|
37
|
+
left: 0;
|
|
38
|
+
width: 100%;
|
|
39
|
+
height: 100%;
|
|
40
|
+
background-color: rgba(0, 0, 0, 0.8);
|
|
41
|
+
display: none;
|
|
42
|
+
z-index: 9999;
|
|
43
|
+
cursor: pointer;
|
|
44
|
+
`;
|
|
45
|
+
this.modal = document.createElement("div");
|
|
46
|
+
this.modal.className = "image-preview-modal";
|
|
47
|
+
this.modal.style.cssText = `
|
|
48
|
+
position: fixed;
|
|
49
|
+
top: 50%;
|
|
50
|
+
left: 50%;
|
|
51
|
+
transform: translate(-50%, -50%);
|
|
52
|
+
background-color: transparent;
|
|
53
|
+
display: none;
|
|
54
|
+
z-index: 10000;
|
|
55
|
+
max-width: 90vw;
|
|
56
|
+
max-height: 90vh;
|
|
57
|
+
cursor: auto;
|
|
58
|
+
`;
|
|
59
|
+
this.previewImage = document.createElement("img");
|
|
60
|
+
this.previewImage.className = "image-preview-img";
|
|
61
|
+
this.previewImage.style.cssText = `
|
|
62
|
+
max-width: 100%;
|
|
63
|
+
max-height: 100%;
|
|
64
|
+
object-fit: contain;
|
|
65
|
+
border-radius: 4px;
|
|
66
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
67
|
+
transition: transform 0.2s ease-out;
|
|
68
|
+
cursor: grab;
|
|
69
|
+
`;
|
|
70
|
+
const closeBtn = document.createElement("button");
|
|
71
|
+
closeBtn.className = "image-preview-close";
|
|
72
|
+
closeBtn.innerHTML = "×";
|
|
73
|
+
closeBtn.style.cssText = `
|
|
74
|
+
position: absolute;
|
|
75
|
+
top: -40px;
|
|
76
|
+
right: 0;
|
|
77
|
+
width: 40px;
|
|
78
|
+
height: 40px;
|
|
79
|
+
border: none;
|
|
80
|
+
background-color: transparent;
|
|
81
|
+
color: white;
|
|
82
|
+
font-size: 32px;
|
|
83
|
+
cursor: pointer;
|
|
84
|
+
z-index: 10001;
|
|
85
|
+
line-height: 1;
|
|
86
|
+
padding: 0;
|
|
87
|
+
`;
|
|
88
|
+
closeBtn.addEventListener("click", () => this.hide());
|
|
89
|
+
this.modal.appendChild(this.previewImage);
|
|
90
|
+
this.modal.appendChild(closeBtn);
|
|
91
|
+
this.scaleTooltip = document.createElement("div");
|
|
92
|
+
this.scaleTooltip.className = "image-preview-scale-tooltip";
|
|
93
|
+
this.scaleTooltip.style.cssText = `
|
|
94
|
+
position: fixed;
|
|
95
|
+
top: 50%;
|
|
96
|
+
left: 50%;
|
|
97
|
+
transform: translate(-50%, -50%);
|
|
98
|
+
background-color: rgba(0, 0, 0, 0.7);
|
|
99
|
+
color: white;
|
|
100
|
+
padding: 12px 20px;
|
|
101
|
+
border-radius: 6px;
|
|
102
|
+
font-size: 14px;
|
|
103
|
+
z-index: 10002;
|
|
104
|
+
display: none;
|
|
105
|
+
pointer-events: none;
|
|
106
|
+
white-space: nowrap;
|
|
107
|
+
font-weight: 500;
|
|
108
|
+
`;
|
|
109
|
+
document.body.appendChild(this.scaleTooltip);
|
|
110
|
+
this.overlay.addEventListener("click", () => this.hide());
|
|
111
|
+
document.addEventListener("keydown", (e) => {
|
|
112
|
+
if (e.key === "Escape") {
|
|
113
|
+
this.hide();
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
document.addEventListener("wheel", (e) => this.onMouseWheel(e), { passive: false });
|
|
117
|
+
this.modal.addEventListener("click", (e) => {
|
|
118
|
+
e.stopPropagation();
|
|
119
|
+
});
|
|
120
|
+
document.body.appendChild(this.overlay);
|
|
121
|
+
document.body.appendChild(this.modal);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* 设置缩放比例
|
|
125
|
+
*/
|
|
126
|
+
setScale(scale) {
|
|
127
|
+
this.currentScale = Math.max(this.minScale, Math.min(scale, this.maxScale));
|
|
128
|
+
if (this.previewImage) {
|
|
129
|
+
this.previewImage.style.transform = `scale(${this.currentScale})`;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* 显示缩放百分比提示
|
|
134
|
+
*/
|
|
135
|
+
showScaleTooltip() {
|
|
136
|
+
if (!this.scaleTooltip) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (this.tooltipHideTimer !== null) {
|
|
140
|
+
clearTimeout(this.tooltipHideTimer);
|
|
141
|
+
}
|
|
142
|
+
const percentage = Math.round(this.currentScale * 100);
|
|
143
|
+
this.scaleTooltip.textContent = `${percentage}%`;
|
|
144
|
+
this.scaleTooltip.style.display = "block";
|
|
145
|
+
this.tooltipHideTimer = window.setTimeout(() => {
|
|
146
|
+
if (this.scaleTooltip) {
|
|
147
|
+
this.scaleTooltip.style.display = "none";
|
|
148
|
+
}
|
|
149
|
+
this.tooltipHideTimer = null;
|
|
150
|
+
}, 1500);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* 隐藏缩放提示
|
|
154
|
+
*/
|
|
155
|
+
hideScaleTooltip() {
|
|
156
|
+
if (this.tooltipHideTimer !== null) {
|
|
157
|
+
clearTimeout(this.tooltipHideTimer);
|
|
158
|
+
this.tooltipHideTimer = null;
|
|
159
|
+
}
|
|
160
|
+
if (this.scaleTooltip) {
|
|
161
|
+
this.scaleTooltip.style.display = "none";
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* 重置缩放比例
|
|
166
|
+
*/
|
|
167
|
+
resetScale() {
|
|
168
|
+
this.currentScale = 1;
|
|
169
|
+
if (this.previewImage) {
|
|
170
|
+
this.previewImage.style.transform = "scale(1)";
|
|
171
|
+
}
|
|
172
|
+
this.hideScaleTooltip();
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* 显示预览
|
|
176
|
+
* @param imageUrl 图片URL
|
|
177
|
+
*/
|
|
178
|
+
show(imageUrl) {
|
|
179
|
+
if (!this.previewImage || !this.modal || !this.overlay) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
this.resetScale();
|
|
183
|
+
this.previewImage.src = imageUrl;
|
|
184
|
+
this.modal.style.display = "flex";
|
|
185
|
+
this.modal.style.alignItems = "center";
|
|
186
|
+
this.modal.style.justifyContent = "center";
|
|
187
|
+
this.overlay.style.display = "block";
|
|
188
|
+
document.body.style.overflow = "hidden";
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* 隐藏预览
|
|
192
|
+
*/
|
|
193
|
+
hide() {
|
|
194
|
+
if (this.modal && this.overlay) {
|
|
195
|
+
this.modal.style.display = "none";
|
|
196
|
+
this.overlay.style.display = "none";
|
|
197
|
+
document.body.style.overflow = "";
|
|
198
|
+
this.resetScale();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* 销毁预览模态框
|
|
203
|
+
*/
|
|
204
|
+
destroy() {
|
|
205
|
+
this.hideScaleTooltip();
|
|
206
|
+
if (this.overlay && this.overlay.parentNode) {
|
|
207
|
+
this.overlay.parentNode.removeChild(this.overlay);
|
|
208
|
+
}
|
|
209
|
+
if (this.modal && this.modal.parentNode) {
|
|
210
|
+
this.modal.parentNode.removeChild(this.modal);
|
|
211
|
+
}
|
|
212
|
+
if (this.scaleTooltip && this.scaleTooltip.parentNode) {
|
|
213
|
+
this.scaleTooltip.parentNode.removeChild(this.scaleTooltip);
|
|
214
|
+
}
|
|
215
|
+
this.modal = null;
|
|
216
|
+
this.overlay = null;
|
|
217
|
+
this.previewImage = null;
|
|
218
|
+
this.scaleTooltip = null;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
let globalPreviewModal = null;
|
|
222
|
+
function getImagePreviewModal() {
|
|
223
|
+
if (!globalPreviewModal) {
|
|
224
|
+
globalPreviewModal = new ImagePreviewModal();
|
|
225
|
+
}
|
|
226
|
+
return globalPreviewModal;
|
|
227
|
+
}
|
|
228
|
+
exports.ImagePreviewModal = ImagePreviewModal;
|
|
229
|
+
exports.getImagePreviewModal = getImagePreviewModal;
|
|
230
|
+
//# sourceMappingURL=preview-modal.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview-modal.cjs.js","sources":["../../../../../src/modules/custom-image/preview/preview-modal.ts"],"sourcesContent":["/**\n * 图片预览模态框\n * 提供图片双击时的预览功能,包括遮罩层和全屏预览\n */\n\nexport class ImagePreviewModal {\n private modal: HTMLElement | null = null\n private overlay: HTMLElement | null = null\n private previewImage: HTMLImageElement | null = null\n private scaleTooltip: HTMLElement | null = null\n private currentScale: number = 1\n private minScale: number = 0.5\n private maxScale: number = 3\n private scaleStep: number = 0.1\n private tooltipHideTimer: number | null = null\n\n constructor() {\n this.initModal()\n }\n\n private initModal() {\n // 创建遮罩层\n this.overlay = document.createElement('div')\n this.overlay.className = 'image-preview-overlay'\n this.overlay.style.cssText = `\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-color: rgba(0, 0, 0, 0.8);\n display: none;\n z-index: 9999;\n cursor: pointer;\n `\n\n // 创建预览容器\n this.modal = document.createElement('div')\n this.modal.className = 'image-preview-modal'\n this.modal.style.cssText = `\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background-color: transparent;\n display: none;\n z-index: 10000;\n max-width: 90vw;\n max-height: 90vh;\n cursor: auto;\n `\n\n // 创建预览图片\n this.previewImage = document.createElement('img')\n this.previewImage.className = 'image-preview-img'\n this.previewImage.style.cssText = `\n max-width: 100%;\n max-height: 100%;\n object-fit: contain;\n border-radius: 4px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);\n transition: transform 0.2s ease-out;\n cursor: grab;\n `\n\n // 创建关闭按钮\n const closeBtn = document.createElement('button')\n closeBtn.className = 'image-preview-close'\n closeBtn.innerHTML = '×'\n closeBtn.style.cssText = `\n position: absolute;\n top: -40px;\n right: 0;\n width: 40px;\n height: 40px;\n border: none;\n background-color: transparent;\n color: white;\n font-size: 32px;\n cursor: pointer;\n z-index: 10001;\n line-height: 1;\n padding: 0;\n `\n closeBtn.addEventListener('click', () => this.hide())\n\n this.modal.appendChild(this.previewImage)\n this.modal.appendChild(closeBtn)\n\n // 创建缩放提示窗口\n this.scaleTooltip = document.createElement('div')\n this.scaleTooltip.className = 'image-preview-scale-tooltip'\n this.scaleTooltip.style.cssText = `\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background-color: rgba(0, 0, 0, 0.7);\n color: white;\n padding: 12px 20px;\n border-radius: 6px;\n font-size: 14px;\n z-index: 10002;\n display: none;\n pointer-events: none;\n white-space: nowrap;\n font-weight: 500;\n `\n document.body.appendChild(this.scaleTooltip)\n\n // 绑定事件\n this.overlay.addEventListener('click', () => this.hide())\n document.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') {\n this.hide()\n }\n })\n\n // 绑定滚轮缩放事件\n document.addEventListener('wheel', (e) => this.onMouseWheel(e), { passive: false })\n\n // 阻止模态框内的点击事件冒泡到遮罩层\n this.modal.addEventListener('click', (e) => {\n e.stopPropagation()\n })\n\n document.body.appendChild(this.overlay)\n document.body.appendChild(this.modal)\n }\n\n /**\n * 处理鼠标滚轮事件 - 缩放图片\n */\n private onMouseWheel = (event: WheelEvent) => {\n // 只在预览打开时处理\n if (!this.modal || this.modal.style.display === 'none') {\n return\n }\n\n event.preventDefault()\n\n // 根据滚轮方向调整缩放比例\n const delta = event.deltaY > 0 ? -this.scaleStep : this.scaleStep\n this.setScale(this.currentScale + delta)\n\n // 显示缩放提示\n this.showScaleTooltip()\n }\n\n /**\n * 设置缩放比例\n */\n private setScale(scale: number) {\n // 限制缩放范围\n this.currentScale = Math.max(this.minScale, Math.min(scale, this.maxScale))\n\n if (this.previewImage) {\n this.previewImage.style.transform = `scale(${this.currentScale})`\n }\n }\n\n /**\n * 显示缩放百分比提示\n */\n private showScaleTooltip() {\n if (!this.scaleTooltip) {\n return\n }\n\n // 清除之前的隐藏计时器\n if (this.tooltipHideTimer !== null) {\n clearTimeout(this.tooltipHideTimer)\n }\n\n // 更新提示文本\n const percentage = Math.round(this.currentScale * 100)\n this.scaleTooltip.textContent = `${percentage}%`\n this.scaleTooltip.style.display = 'block'\n\n // 1.5秒后隐藏提示\n this.tooltipHideTimer = window.setTimeout(() => {\n if (this.scaleTooltip) {\n this.scaleTooltip.style.display = 'none'\n }\n this.tooltipHideTimer = null\n }, 1500)\n }\n\n /**\n * 隐藏缩放提示\n */\n private hideScaleTooltip() {\n if (this.tooltipHideTimer !== null) {\n clearTimeout(this.tooltipHideTimer)\n this.tooltipHideTimer = null\n }\n if (this.scaleTooltip) {\n this.scaleTooltip.style.display = 'none'\n }\n }\n\n /**\n * 重置缩放比例\n */\n private resetScale() {\n this.currentScale = 1\n if (this.previewImage) {\n this.previewImage.style.transform = 'scale(1)'\n }\n this.hideScaleTooltip()\n }\n\n /**\n * 显示预览\n * @param imageUrl 图片URL\n */\n show(imageUrl: string) {\n if (!this.previewImage || !this.modal || !this.overlay) {\n return\n }\n\n this.resetScale()\n this.previewImage.src = imageUrl\n this.modal.style.display = 'flex'\n this.modal.style.alignItems = 'center'\n this.modal.style.justifyContent = 'center'\n this.overlay.style.display = 'block'\n\n // 防止页面滚动\n document.body.style.overflow = 'hidden'\n }\n\n /**\n * 隐藏预览\n */\n hide() {\n if (this.modal && this.overlay) {\n this.modal.style.display = 'none'\n this.overlay.style.display = 'none'\n document.body.style.overflow = ''\n this.resetScale()\n }\n }\n\n /**\n * 销毁预览模态框\n */\n destroy() {\n this.hideScaleTooltip()\n if (this.overlay && this.overlay.parentNode) {\n this.overlay.parentNode.removeChild(this.overlay)\n }\n if (this.modal && this.modal.parentNode) {\n this.modal.parentNode.removeChild(this.modal)\n }\n if (this.scaleTooltip && this.scaleTooltip.parentNode) {\n this.scaleTooltip.parentNode.removeChild(this.scaleTooltip)\n }\n this.modal = null\n this.overlay = null\n this.previewImage = null\n this.scaleTooltip = null\n }\n}\n\n// 全局单例实例\nlet globalPreviewModal: ImagePreviewModal | null = null\n\n/**\n * 获取或创建全局预览模态框实例\n */\nexport function getImagePreviewModal(): ImagePreviewModal {\n if (!globalPreviewModal) {\n globalPreviewModal = new ImagePreviewModal()\n }\n return globalPreviewModal\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -4,7 +4,9 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
|
|
|
4
4
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
5
5
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
6
6
|
const editor_utils = require("../../../config/editor.utils.cjs.js");
|
|
7
|
+
require("../preview/index.cjs.js");
|
|
7
8
|
const imageSpec = require("./image-spec.cjs.js");
|
|
9
|
+
const previewModal = require("../preview/preview-modal.cjs.js");
|
|
8
10
|
class CustomImageSpec extends imageSpec.ImageSpec {
|
|
9
11
|
constructor(formatter) {
|
|
10
12
|
super(formatter);
|
|
@@ -40,6 +42,17 @@ class CustomImageSpec extends imageSpec.ImageSpec {
|
|
|
40
42
|
quill.setSelection(index, len);
|
|
41
43
|
}
|
|
42
44
|
});
|
|
45
|
+
/**
|
|
46
|
+
* 处理图片双击事件 - 显示预览
|
|
47
|
+
*/
|
|
48
|
+
__publicField(this, "onImageDoubleClick", (event) => {
|
|
49
|
+
const target = event.target;
|
|
50
|
+
const imageSrc = target.getAttribute("src") || target.getAttribute("data-image");
|
|
51
|
+
if (imageSrc) {
|
|
52
|
+
const modal = previewModal.getImagePreviewModal();
|
|
53
|
+
modal.show(imageSrc);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
43
56
|
this.formatter = formatter;
|
|
44
57
|
this.oldRootScrollTop = this.formatter.quill.root.scrollTop;
|
|
45
58
|
this.editorElem = this.formatter.quill.container;
|
|
@@ -53,6 +66,7 @@ class CustomImageSpec extends imageSpec.ImageSpec {
|
|
|
53
66
|
init() {
|
|
54
67
|
this.editorElem.addEventListener("mouseover", this.imageMouseOver.bind(this));
|
|
55
68
|
this.editorElem.addEventListener("mouseout", this.imageMouseout);
|
|
69
|
+
this.editorElem.addEventListener("dblclick", this.onImageDoubleClick.bind(this));
|
|
56
70
|
super.init();
|
|
57
71
|
}
|
|
58
72
|
imageMouseOver(event) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"custom-image-spec.cjs.js","sources":["../../../../../src/modules/custom-image/specs/custom-image-spec.ts"],"sourcesContent":["import type { BlotFormatter } from '../blot-formatter'\nimport { isInside } from '../../../config/editor.utils'\nimport { ImageSpec } from './image-spec'\n\nexport class CustomImageSpec extends ImageSpec {\n editorElem: HTMLElement | undefined\n observer: any\n oldRootScrollTop: number\n\n constructor(formatter: BlotFormatter) {\n super(formatter)\n this.formatter = formatter\n this.oldRootScrollTop = this.formatter.quill.root.scrollTop\n this.editorElem = this.formatter.quill.container\n this.formatter.quill.root.addEventListener('scroll', this.handleQuillRootScroll.bind(this))\n }\n\n handleQuillRootScroll() {\n if (this.formatter.overlay) {\n this.formatter.overlay.style.marginTop = `${this.oldRootScrollTop - this.formatter.quill.root.scrollTop}px`\n }\n }\n\n init(): void {\n this.editorElem.addEventListener('mouseover', this.imageMouseOver.bind(this))\n this.editorElem.addEventListener('mouseout', this.imageMouseout)\n\n super.init()\n }\n\n imageMouseOver(event) {\n const target = event.target\n const isBlotFormatter = target?.classList?.contains('blot-formatter__overlay')\n if (target.nodeName === 'IMG' || isBlotFormatter) {\n // this.addImagePreviewOverlay(event);\n }\n }\n\n imageMouseout = (event) => {\n if (event.target.nodeName === 'IMG'\n || event.target.classList.contains('blot-formatter__overlay')) {\n const imgDom = event.target\n if (!isInside(event, imgDom)) {\n this.removeImagePreviewOverlay()\n }\n }\n }\n\n addImagePreviewOverlay(event) {\n const target = event.target\n const {\n left: imgLeft,\n width: imgWidth,\n } = target.getBoundingClientRect()\n // fix: 解决 ql-container 容器设置 calc(100vh - 180px) 这样的视窗相对单位时,滚动视窗导致图片相对视窗的 top 相应改变,从而导致图片预览按钮的位置显示错误\n const imgTop = target.getBoundingClientRect().top + this.formatter.quill.container.scrollTop\n\n const {\n left: editorLeft,\n top: editorTop,\n } = event.currentTarget.getBoundingClientRect()\n\n const imgRelativeLeft = imgLeft - editorLeft\n const imgRelativeTop = imgTop - editorTop\n\n const maxmizeWidth = 24\n const maxmizePadding = 15\n const previewLeft = imgRelativeLeft + imgWidth - maxmizeWidth - maxmizePadding\n const previewTop = imgRelativeTop + maxmizePadding\n\n const previewStyle = `\n left: ${previewLeft}px;\n top: ${previewTop}px;\n width: ${maxmizeWidth}px;\n `\n const imageSrc = target.src || target.getAttribute('data-image')\n const imageId = target.getAttribute('data-image-id')\n\n const previewDom = event.currentTarget.querySelector('.image-preview__overlay')\n if (!previewDom) {\n event.currentTarget.insertAdjacentHTML('beforeend', `\n <div class=\"image-preview__overlay\" style=\"${previewStyle}\">\n <i class=\"icon-maxmize\" id=\"btn-image-preview\" data-image-id=\"${imageId}\"\n data-image=\"${imageSrc}\"></i>\n </div>\n `)\n }\n }\n\n removeImagePreviewOverlay() {\n const previewDom = this.editorElem.querySelector('.image-preview__overlay')\n if (previewDom) {\n previewDom.parentNode.removeChild(previewDom)\n }\n }\n\n onHide() {\n this.removeImagePreviewOverlay()\n super.onHide()\n }\n\n resetOverlayMarginTop() {\n if (this.formatter.overlay) {\n this.formatter.overlay.style.marginTop = '0px'\n }\n }\n\n onClick = (event: MouseEvent) => {\n const el = event.target\n const isReadonly = this.formatter.quill.options.readOnly\n if (!(el instanceof HTMLElement) || el.tagName !== 'IMG' || isReadonly) {\n return\n }\n\n this.img = el as HTMLImageElement\n this.oldRootScrollTop = this.formatter.quill.root.scrollTop\n this.resetOverlayMarginTop()\n this.formatter.show(this)\n\n // 通过图片dom获取图片选区用以复制,设置 current-select-img::selection 取消选区背景\n const imageDom = this.formatter.currentSpec?.getTargetElement()\n if (imageDom) {\n imageDom.classList.add('current-select-img')\n const quill = this.formatter.quill\n const imgBlot = quill.scroll.find(this.img)\n const index = quill.getIndex(imgBlot)\n const len = imgBlot.length()\n quill.setSelection(index, len)\n }\n }\n}\n"],"names":["ImageSpec","isInside"],"mappings":"
|
|
1
|
+
{"version":3,"file":"custom-image-spec.cjs.js","sources":["../../../../../src/modules/custom-image/specs/custom-image-spec.ts"],"sourcesContent":["import type { BlotFormatter } from '../blot-formatter'\nimport { isInside } from '../../../config/editor.utils'\nimport { getImagePreviewModal } from '../preview'\nimport { ImageSpec } from './image-spec'\n\nexport class CustomImageSpec extends ImageSpec {\n editorElem: HTMLElement | undefined\n observer: any\n oldRootScrollTop: number\n\n constructor(formatter: BlotFormatter) {\n super(formatter)\n this.formatter = formatter\n this.oldRootScrollTop = this.formatter.quill.root.scrollTop\n this.editorElem = this.formatter.quill.container\n this.formatter.quill.root.addEventListener('scroll', this.handleQuillRootScroll.bind(this))\n }\n\n handleQuillRootScroll() {\n if (this.formatter.overlay) {\n this.formatter.overlay.style.marginTop = `${this.oldRootScrollTop - this.formatter.quill.root.scrollTop}px`\n }\n }\n\n init(): void {\n this.editorElem.addEventListener('mouseover', this.imageMouseOver.bind(this))\n this.editorElem.addEventListener('mouseout', this.imageMouseout)\n this.editorElem.addEventListener('dblclick', this.onImageDoubleClick.bind(this))\n\n super.init()\n }\n\n imageMouseOver(event) {\n const target = event.target\n const isBlotFormatter = target?.classList?.contains('blot-formatter__overlay')\n if (target.nodeName === 'IMG' || isBlotFormatter) {\n // this.addImagePreviewOverlay(event);\n }\n }\n\n imageMouseout = (event) => {\n if (event.target.nodeName === 'IMG'\n || event.target.classList.contains('blot-formatter__overlay')) {\n const imgDom = event.target\n if (!isInside(event, imgDom)) {\n this.removeImagePreviewOverlay()\n }\n }\n }\n\n addImagePreviewOverlay(event) {\n const target = event.target\n const {\n left: imgLeft,\n width: imgWidth,\n } = target.getBoundingClientRect()\n // fix: 解决 ql-container 容器设置 calc(100vh - 180px) 这样的视窗相对单位时,滚动视窗导致图片相对视窗的 top 相应改变,从而导致图片预览按钮的位置显示错误\n const imgTop = target.getBoundingClientRect().top + this.formatter.quill.container.scrollTop\n\n const {\n left: editorLeft,\n top: editorTop,\n } = event.currentTarget.getBoundingClientRect()\n\n const imgRelativeLeft = imgLeft - editorLeft\n const imgRelativeTop = imgTop - editorTop\n\n const maxmizeWidth = 24\n const maxmizePadding = 15\n const previewLeft = imgRelativeLeft + imgWidth - maxmizeWidth - maxmizePadding\n const previewTop = imgRelativeTop + maxmizePadding\n\n const previewStyle = `\n left: ${previewLeft}px;\n top: ${previewTop}px;\n width: ${maxmizeWidth}px;\n `\n const imageSrc = target.src || target.getAttribute('data-image')\n const imageId = target.getAttribute('data-image-id')\n\n const previewDom = event.currentTarget.querySelector('.image-preview__overlay')\n if (!previewDom) {\n event.currentTarget.insertAdjacentHTML('beforeend', `\n <div class=\"image-preview__overlay\" style=\"${previewStyle}\">\n <i class=\"icon-maxmize\" id=\"btn-image-preview\" data-image-id=\"${imageId}\"\n data-image=\"${imageSrc}\"></i>\n </div>\n `)\n }\n }\n\n removeImagePreviewOverlay() {\n const previewDom = this.editorElem.querySelector('.image-preview__overlay')\n if (previewDom) {\n previewDom.parentNode.removeChild(previewDom)\n }\n }\n\n onHide() {\n this.removeImagePreviewOverlay()\n super.onHide()\n }\n\n resetOverlayMarginTop() {\n if (this.formatter.overlay) {\n this.formatter.overlay.style.marginTop = '0px'\n }\n }\n\n onClick = (event: MouseEvent) => {\n const el = event.target\n const isReadonly = this.formatter.quill.options.readOnly\n if (!(el instanceof HTMLElement) || el.tagName !== 'IMG' || isReadonly) {\n return\n }\n\n this.img = el as HTMLImageElement\n this.oldRootScrollTop = this.formatter.quill.root.scrollTop\n this.resetOverlayMarginTop()\n this.formatter.show(this)\n\n // 通过图片dom获取图片选区用以复制,设置 current-select-img::selection 取消选区背景\n const imageDom = this.formatter.currentSpec?.getTargetElement()\n if (imageDom) {\n imageDom.classList.add('current-select-img')\n const quill = this.formatter.quill\n const imgBlot = quill.scroll.find(this.img)\n const index = quill.getIndex(imgBlot)\n const len = imgBlot.length()\n quill.setSelection(index, len)\n }\n }\n\n /**\n * 处理图片双击事件 - 显示预览\n */\n onImageDoubleClick = (event: MouseEvent) => {\n const target = event.target\n const imageSrc = target.getAttribute('src') || target.getAttribute('data-image')\n\n if (imageSrc) {\n const modal = getImagePreviewModal()\n modal.show(imageSrc)\n }\n }\n}\n"],"names":["ImageSpec","isInside","getImagePreviewModal"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opentiny/fluent-editor",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "4.1.0-alpha.
|
|
4
|
+
"version": "4.1.0-alpha.3",
|
|
5
5
|
"description": "A rich text editor based on Quill 2.0, which extends rich modules and formats on the basis of Quill. It's powerful and out-of-the-box.",
|
|
6
6
|
"author": "OpenTiny Team",
|
|
7
7
|
"license": "MIT",
|
|
@@ -43,5 +43,6 @@ export declare const CENTER_ALIGN = "align-center";
|
|
|
43
43
|
export declare const RIGHT_ALIGN = "align-right";
|
|
44
44
|
export declare const COPY = "copy";
|
|
45
45
|
export declare const DOWNLOAD = "download";
|
|
46
|
+
export declare const PREVIEW = "preview";
|
|
46
47
|
declare const DefaultOptions: BlotFormatterOptions;
|
|
47
48
|
export default DefaultOptions;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './preview-modal';
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 图片预览模态框
|
|
3
|
+
* 提供图片双击时的预览功能,包括遮罩层和全屏预览
|
|
4
|
+
*/
|
|
5
|
+
export declare class ImagePreviewModal {
|
|
6
|
+
private modal;
|
|
7
|
+
private overlay;
|
|
8
|
+
private previewImage;
|
|
9
|
+
private scaleTooltip;
|
|
10
|
+
private currentScale;
|
|
11
|
+
private minScale;
|
|
12
|
+
private maxScale;
|
|
13
|
+
private scaleStep;
|
|
14
|
+
private tooltipHideTimer;
|
|
15
|
+
constructor();
|
|
16
|
+
private initModal;
|
|
17
|
+
/**
|
|
18
|
+
* 处理鼠标滚轮事件 - 缩放图片
|
|
19
|
+
*/
|
|
20
|
+
private onMouseWheel;
|
|
21
|
+
/**
|
|
22
|
+
* 设置缩放比例
|
|
23
|
+
*/
|
|
24
|
+
private setScale;
|
|
25
|
+
/**
|
|
26
|
+
* 显示缩放百分比提示
|
|
27
|
+
*/
|
|
28
|
+
private showScaleTooltip;
|
|
29
|
+
/**
|
|
30
|
+
* 隐藏缩放提示
|
|
31
|
+
*/
|
|
32
|
+
private hideScaleTooltip;
|
|
33
|
+
/**
|
|
34
|
+
* 重置缩放比例
|
|
35
|
+
*/
|
|
36
|
+
private resetScale;
|
|
37
|
+
/**
|
|
38
|
+
* 显示预览
|
|
39
|
+
* @param imageUrl 图片URL
|
|
40
|
+
*/
|
|
41
|
+
show(imageUrl: string): void;
|
|
42
|
+
/**
|
|
43
|
+
* 隐藏预览
|
|
44
|
+
*/
|
|
45
|
+
hide(): void;
|
|
46
|
+
/**
|
|
47
|
+
* 销毁预览模态框
|
|
48
|
+
*/
|
|
49
|
+
destroy(): void;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 获取或创建全局预览模态框实例
|
|
53
|
+
*/
|
|
54
|
+
export declare function getImagePreviewModal(): ImagePreviewModal;
|