@opentiny/fluent-editor 4.1.0 → 4.1.2
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/config/editor.config.es.js +1 -1
- package/es/config/editor.config.es.js.map +1 -1
- package/es/config/editor.utils.es.js +33 -0
- package/es/config/editor.utils.es.js.map +1 -1
- package/es/config/i18n/en-us.es.js +4 -1
- package/es/config/i18n/en-us.es.js.map +1 -1
- package/es/config/i18n/zh-cn.es.js +4 -1
- package/es/config/i18n/zh-cn.es.js.map +1 -1
- package/es/config/index.es.js +3 -1
- package/es/core/fluent-editor.es.js +29 -4
- package/es/core/fluent-editor.es.js.map +1 -1
- package/es/fluent-editor.es.js +2 -11
- package/es/fluent-editor.es.js.map +1 -1
- package/es/formats/strike.es.js +1 -1
- package/es/formats/strike.es.js.map +1 -1
- package/es/index.es.js +6 -4
- package/es/index.es.js.map +1 -1
- package/es/modules/counter.es.js +2 -3
- package/es/modules/counter.es.js.map +1 -1
- package/es/modules/custom-clipboard.es.js +14 -16
- package/es/modules/custom-clipboard.es.js.map +1 -1
- package/es/modules/custom-image/blot-formatter.es.js +2 -2
- package/es/modules/custom-image/blot-formatter.es.js.map +1 -1
- package/es/modules/custom-image/index.es.js +1 -1
- package/es/modules/custom-image/preview/preview-modal.es.js +17 -6
- package/es/modules/custom-image/preview/preview-modal.es.js.map +1 -1
- package/es/modules/custom-image/specs/custom-image-spec.es.js.map +1 -1
- package/es/modules/custom-uploader.es.js.map +1 -1
- package/es/modules/file/modules/file-module.es.js.map +1 -1
- package/es/modules/flow-chart/i18n/index.es.js +3 -5
- package/es/modules/flow-chart/i18n/index.es.js.map +1 -1
- package/es/modules/flow-chart/modules/context-menu.es.js +10 -13
- package/es/modules/flow-chart/modules/context-menu.es.js.map +1 -1
- package/es/modules/flow-chart/modules/control-panel.es.js +13 -16
- package/es/modules/flow-chart/modules/control-panel.es.js.map +1 -1
- package/es/modules/index.es.js +2 -2
- package/es/modules/index.es.js.map +1 -1
- package/es/modules/link/modules/tooltip.es.js +42 -27
- package/es/modules/link/modules/tooltip.es.js.map +1 -1
- package/es/modules/mind-map/i18n/index.es.js +3 -5
- package/es/modules/mind-map/i18n/index.es.js.map +1 -1
- package/es/modules/mind-map/modules/context-menu.es.js +11 -11
- package/es/modules/mind-map/modules/context-menu.es.js.map +1 -1
- package/es/modules/mind-map/modules/control-panel.es.js +7 -9
- package/es/modules/mind-map/modules/control-panel.es.js.map +1 -1
- package/es/modules/shortcut-key/index.es.js +6 -6
- package/es/modules/shortcut-key/index.es.js.map +1 -1
- package/es/modules/table-up/index.es.js +23 -33
- package/es/modules/table-up/index.es.js.map +1 -1
- package/es/modules/toolbar/toolbar-tip.es.js +27 -5
- package/es/modules/toolbar/toolbar-tip.es.js.map +1 -1
- package/es/themes/snow.es.js +21 -4
- package/es/themes/snow.es.js.map +1 -1
- package/lib/config/editor.config.cjs.js +1 -1
- package/lib/config/editor.config.cjs.js.map +1 -1
- package/lib/config/editor.utils.cjs.js +33 -0
- package/lib/config/editor.utils.cjs.js.map +1 -1
- package/lib/config/i18n/en-us.cjs.js +4 -1
- package/lib/config/i18n/en-us.cjs.js.map +1 -1
- package/lib/config/i18n/zh-cn.cjs.js +4 -1
- package/lib/config/i18n/zh-cn.cjs.js.map +1 -1
- package/lib/config/index.cjs.js +2 -0
- package/lib/config/index.cjs.js.map +1 -1
- package/lib/core/fluent-editor.cjs.js +29 -4
- package/lib/core/fluent-editor.cjs.js.map +1 -1
- package/lib/fluent-editor.cjs.js +2 -11
- package/lib/fluent-editor.cjs.js.map +1 -1
- package/lib/formats/strike.cjs.js +1 -1
- package/lib/formats/strike.cjs.js.map +1 -1
- package/lib/index.cjs.js +18 -5
- package/lib/index.cjs.js.map +1 -1
- package/lib/modules/counter.cjs.js +2 -3
- package/lib/modules/counter.cjs.js.map +1 -1
- package/lib/modules/custom-clipboard.cjs.js +14 -16
- package/lib/modules/custom-clipboard.cjs.js.map +1 -1
- package/lib/modules/custom-image/blot-formatter.cjs.js +2 -2
- package/lib/modules/custom-image/blot-formatter.cjs.js.map +1 -1
- package/lib/modules/custom-image/index.cjs.js +1 -1
- package/lib/modules/custom-image/preview/preview-modal.cjs.js +17 -6
- package/lib/modules/custom-image/preview/preview-modal.cjs.js.map +1 -1
- package/lib/modules/custom-image/specs/custom-image-spec.cjs.js.map +1 -1
- package/lib/modules/custom-uploader.cjs.js.map +1 -1
- package/lib/modules/file/modules/file-module.cjs.js.map +1 -1
- package/lib/modules/flow-chart/i18n/index.cjs.js +3 -5
- package/lib/modules/flow-chart/i18n/index.cjs.js.map +1 -1
- package/lib/modules/flow-chart/modules/context-menu.cjs.js +10 -13
- package/lib/modules/flow-chart/modules/context-menu.cjs.js.map +1 -1
- package/lib/modules/flow-chart/modules/control-panel.cjs.js +13 -16
- package/lib/modules/flow-chart/modules/control-panel.cjs.js.map +1 -1
- package/lib/modules/index.cjs.js +14 -2
- package/lib/modules/index.cjs.js.map +1 -1
- package/lib/modules/link/modules/tooltip.cjs.js +42 -27
- package/lib/modules/link/modules/tooltip.cjs.js.map +1 -1
- package/lib/modules/mind-map/i18n/index.cjs.js +3 -5
- package/lib/modules/mind-map/i18n/index.cjs.js.map +1 -1
- package/lib/modules/mind-map/modules/context-menu.cjs.js +11 -11
- package/lib/modules/mind-map/modules/context-menu.cjs.js.map +1 -1
- package/lib/modules/mind-map/modules/control-panel.cjs.js +7 -9
- package/lib/modules/mind-map/modules/control-panel.cjs.js.map +1 -1
- package/lib/modules/shortcut-key/index.cjs.js +4 -4
- package/lib/modules/shortcut-key/index.cjs.js.map +1 -1
- package/lib/modules/table-up/index.cjs.js +23 -33
- package/lib/modules/table-up/index.cjs.js.map +1 -1
- package/lib/modules/toolbar/toolbar-tip.cjs.js +26 -4
- package/lib/modules/toolbar/toolbar-tip.cjs.js.map +1 -1
- package/lib/themes/snow.cjs.js +20 -3
- package/lib/themes/snow.cjs.js.map +1 -1
- package/package.json +4 -2
- package/style.css +176 -144
- package/types/config/editor.config.d.ts +4 -2
- package/types/config/editor.utils.d.ts +8 -0
- package/types/config/i18n/en-us.d.ts +4 -1
- package/types/config/i18n/zh-cn.d.ts +4 -1
- package/types/config/types/editor-modules.interface.d.ts +3 -2
- package/types/core/fluent-editor.d.ts +1 -1
- package/types/modules/custom-clipboard.d.ts +2 -0
- package/types/modules/custom-image/index.d.ts +1 -1
- package/types/modules/custom-image/preview/preview-modal.d.ts +1 -0
- package/types/modules/flow-chart/i18n/index.d.ts +1 -1
- package/types/modules/index.d.ts +2 -1
- package/types/modules/link/modules/tooltip.d.ts +1 -0
- package/types/modules/mind-map/i18n/index.d.ts +1 -1
- package/types/modules/table-up/index.d.ts +3 -26
- package/es/modules/i18n.es.js +0 -62
- package/es/modules/i18n.es.js.map +0 -1
- package/lib/modules/i18n.cjs.js +0 -62
- package/lib/modules/i18n.cjs.js.map +0 -1
- package/types/modules/i18n.d.ts +0 -14
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"custom-clipboard.es.js","sources":["../../../src/modules/custom-clipboard.ts"],"sourcesContent":["import type TypeClipboard from 'quill/modules/clipboard'\nimport type FluentEditor from '../fluent-editor'\nimport Quill from 'quill'\nimport {\n ERROR_IMAGE_PLACEHOLDER_CN,\n ERROR_IMAGE_PLACEHOLDER_EN,\n} from '../config/base64-image'\nimport { BIG_DELTA_LIMIT } from '../config/editor.config'\nimport {\n hexToRgbA,\n imageUrlToFile,\n isNullOrUndefined,\n omit,\n replaceDeltaImage,\n splitWithBreak,\n} from '../config/editor.utils'\nimport { isString } from '../utils/is'\n\nconst Clipboard = Quill.import('modules/clipboard') as typeof TypeClipboard\nconst Delta = Quill.import('delta')\n\nexport class CustomClipboard extends Clipboard {\n declare quill: FluentEditor\n\n prepareMatching(container: HTMLElement, nodeMatches) {\n const elementMatchers = []\n const textMatchers = []\n this.matchers.forEach((pair) => {\n const [selector, matcher] = pair\n if (selector === Node.TEXT_NODE) {\n textMatchers.push(matcher)\n }\n else if (selector === Node.ELEMENT_NODE) {\n elementMatchers.push(matcher)\n }\n else if (isString(selector)) {\n // word 的 v:shape 系列标签只能通过 getElementsByTagName 获取\n const vRegex = /v:(.+)/\n const nodeList = Array.from(\n vRegex.test(selector)\n ? container.getElementsByTagName(selector)\n : container.querySelectorAll(selector),\n )\n nodeList.forEach((node) => {\n if (nodeMatches.has(node)) {\n const matches = nodeMatches.get(node)\n matches.push(matcher)\n }\n else {\n nodeMatches.set(node, [matcher])\n }\n })\n }\n })\n return [elementMatchers, textMatchers]\n }\n\n onCaptureCopy(e, isCut = false) {\n if (e.defaultPrevented) {\n return\n }\n e.preventDefault()\n const [range] = this.quill.selection.getRange()\n if (isNullOrUndefined(range)) {\n return\n }\n const { html, text } = this.onCopy(range, isCut)\n\n // 兼容IE11浏览器`\n if (!e.clipboardData) {\n e.clipboardData = {\n types: 'text/plain',\n setData: (_type, value) => {\n // @ts-ignore\n return window.clipboardData.setData('Text', value)\n },\n }\n }\n\n // 复制代码时移除utf8中产生的不间断空格\\u00A0\n let plainText = text\n if (html.startsWith('<pre>')) {\n plainText = text.replace(/\\u00A0/g, ' ')\n }\n\n e.clipboardData.setData('text/html', html)\n e.clipboardData.setData('text/plain', plainText)\n if (isCut) {\n this.quill.deleteText(range, Quill.sources.USER)\n }\n }\n\n onCapturePaste(e: ClipboardEvent) {\n if (e.defaultPrevented || !this.quill.isEnabled()) {\n return\n }\n e.preventDefault()\n const range = this.quill.getSelection(true)\n if (isNullOrUndefined(range)) {\n return\n }\n\n // 兼容IE11浏览器\n if (!e.clipboardData) {\n // @ts-ignore\n e.clipboardData = {\n types: 'text/plain',\n getData: () => {\n // @ts-ignore\n return window.clipboardData.getData('Text')\n },\n }\n }\n\n const html = e.clipboardData.getData('text/html')\n const text = e.clipboardData.getData('text/plain')\n const files = Array.from(e.clipboardData.files || [])\n const msExcelCheck = /<meta.*?Microsoft Excel\\s[\\d].*?>/\n\n if (html.search(msExcelCheck) === -1 && files.length > 0) {\n this.quill.uploader.upload(range, files)\n }\n else {\n const msWordCheck1\n = /<meta\\s*name=\"?generator\"?\\s*content=\"?microsoft\\s*word\\s*\\d+\"?\\/?>/i\n const msWordCheck2 = /xmlns:o=\"urn:schemas-microsoft-com/i\n const result = { html, text, files, rtf: null }\n if (html.search(msExcelCheck) !== -1) {\n result.html = renderStyles(html)\n }\n if (msWordCheck1.test(html) || msWordCheck2.test(html)) {\n // TODO: 当word文档包含heading时text/rtf读取为空,无法获取hex图片,待修复。可参考ckeditor5/issues/2493\n result.rtf = e.clipboardData.getData('text/rtf')\n }\n this.onPaste(range, result)\n }\n }\n\n onPaste(range, { html, text, files: clipboardFiles, rtf }) {\n const hexImages = this.extractImageDataFromRtf(rtf)\n const rootBgColor = getComputedStyle(this.quill.root).backgroundColor\n const formats = this.quill.getFormat(range.index)\n let pastedDelta = this.convert({ text, html }, formats)\n pastedDelta = replaceDeltaWhiteSpace(pastedDelta, rootBgColor)\n const deltaLength = pastedDelta.ops.length\n\n let loadingTipsContainer\n if (deltaLength > BIG_DELTA_LIMIT) {\n loadingTipsContainer = this.quill.addContainer('ql-loading-tips')\n loadingTipsContainer.innerHTML = this.quill.getLangText('pasting')\n }\n\n const linePos = { index: range.index, length: range.length, fix: 0 }\n const [line, offset] = this.quill.getLine(range.index)\n\n const handlePasteContent = (content: any) => {\n const pastedContent = content\n\n const oldDelta = new Delta().retain(linePos.index).delete(linePos.length)\n const delta = oldDelta.concat(pastedContent)\n\n setTimeout(() => {\n this.quill.updateContents(delta, Quill.sources.USER)\n // 光标位置应该在粘贴内容之后:原光标位置 + 粘贴内容长度\n const newSelectionIndex = linePos.index + (pastedContent.length ? pastedContent.length() : 0)\n this.quill.setSelection(\n newSelectionIndex,\n Quill.sources.SILENT,\n )\n this.quill.scrollSelectionIntoView()\n if (loadingTipsContainer) {\n loadingTipsContainer.remove()\n }\n })\n }\n\n ;(async () => {\n try {\n const [files, placeholders, originalUrls, imageIndexs] = this.flipFilesArray(\n await this.extractFilesFromDelta(\n pastedDelta,\n clipboardFiles,\n hexImages,\n ),\n )\n\n if (files.length === 0) {\n handlePasteContent(pastedDelta)\n }\n else {\n if (this.quill.options.editorPaste && this.quill.options.editorPaste.observers.length !== 0) {\n // 设置editorPaste回调的情况\n this.quill.options.editorPaste.emit({\n files,\n callback: ({ code, message, data }) => {\n if (code === 0) {\n const { imageUrls } = data\n pastedDelta = replaceDeltaImage(\n pastedDelta,\n imageUrls,\n placeholders,\n )\n handlePasteContent(pastedDelta)\n }\n else {\n console.error('error message:', message)\n }\n },\n })\n }\n else {\n // 没有originalUrls 也没有文件粘贴\n if (files[0] !== undefined || originalUrls.length === 0) {\n // 没有设置editorPaste回调的情况下,File格式的占位图需要手动转换成url格式,插入到编辑器中\n const imageUrls = await this.files2urls(\n files,\n placeholders,\n originalUrls,\n pastedDelta,\n imageIndexs,\n )\n pastedDelta = replaceDeltaImage(\n pastedDelta,\n imageUrls,\n placeholders,\n )\n }\n handlePasteContent(pastedDelta)\n }\n }\n }\n catch (_e) {\n throw new Error('Paste failed.')\n }\n })()\n }\n\n files2urls(files: File[], placeholders, originalUrls, pastedDelta, imageIndexs) {\n return Promise.all(\n files.map(async (imageFile, index) => {\n const netImgExp = /^((http|https)\\:)?\\/\\/([\\s\\S]+)$/\n if (\n !placeholders[index]\n && originalUrls[index]\n && netImgExp.test(originalUrls[index])\n ) {\n // 不是占位图的普通url图片直接返回url\n return new Promise((resolve) => {\n resolve(originalUrls[index])\n })\n }\n else {\n const range = this.getImgSelection(pastedDelta, imageIndexs[index])\n this.quill.uploader.upload(range, [imageFile])\n }\n }),\n )\n }\n\n flipFilesArray(filesArr) {\n const files = []\n const placeholders = []\n const originalUrls = []\n const imageIndexs = []\n filesArr.forEach((item: any) => {\n if (item) {\n const [file, placeholder, originalUrl, imageIndex] = item\n files.push(file)\n placeholders.push(placeholder)\n originalUrls.push(originalUrl)\n if (imageIndex === 0 || imageIndex) {\n imageIndexs.push(imageIndex)\n }\n }\n })\n return [files, placeholders, originalUrls, imageIndexs]\n }\n\n // 将图片从hex转为base64\n convertHexToBase64(hexString) {\n return btoa(\n hexString\n .match(/\\w{2}/g)\n .map((char) => {\n return String.fromCharCode(Number.parseInt(char, 16))\n })\n .join(''),\n )\n }\n\n // 匹配rtf中的图片,存储为{hex, type}对象数组\n extractImageDataFromRtf(rtfData) {\n if (!rtfData) {\n return []\n }\n\n const regexPictureHeader\n = /{\\\\pict[\\s\\S]+?\\\\bliptag-?\\d+(\\\\blipupi-?\\d+)?({\\\\\\*\\\\blipuid\\s?[\\da-fA-F]+)?[\\s}]*?/\n const regexPicture = new RegExp(\n `(?:(${regexPictureHeader.source}))([\\\\da-fA-F\\\\s]+)\\\\}`,\n 'g',\n )\n const images = rtfData.match(regexPicture)\n const result = []\n\n if (images) {\n for (const image of images) {\n let imageType = ''\n\n if (image.includes('\\\\pngblip')) {\n imageType = 'image/png'\n }\n else if (image.includes('\\\\jpegblip')) {\n imageType = 'image/jpeg'\n }\n\n if (imageType) {\n result.push({\n hex: image\n .replace(regexPictureHeader, '')\n .replace(/[^\\da-fA-F]/g, ''),\n type: imageType,\n })\n }\n }\n }\n\n return result\n }\n\n extractFilesFromDelta(delta, clipboardFiles, hexImages?) {\n let index = -1\n return Promise.all(\n delta.map(async (op) => {\n index++\n const image = op.insert.image\n if (!image || image.hasExisted) {\n return\n }\n\n let file\n let isPlaceholderImage = false\n let imageIndex\n try {\n // hex 图片存在则为 file:/// 协议本地图片,使用 hex 图片转为 base64 读取\n const hexImage = hexImages.length && hexImages.shift()\n const newImage\n = hexImage\n && `data:${hexImage.type};base64,${this.convertHexToBase64(\n hexImage.hex,\n )}`\n imageIndex = index\n file = await imageUrlToFile(newImage || image.src || image)\n }\n catch (_err) {\n if (clipboardFiles.length !== 0) {\n // 跨域获取图片失败时从剪切板获取图片\n const clipboardFile = clipboardFiles[0]\n const imageType\n = clipboardFile.type?.indexOf('image') === -1\n ? 'image/png'\n : clipboardFile.type\n const blob = clipboardFile.slice(0, clipboardFile.size, imageType)\n file = new File([blob], `image-CORS-${new Date().getTime()}.png`, {\n type: imageType,\n })\n }\n else if (image.src.startsWith('http')) {\n // 什么都不做\n }\n else {\n // 剪切板中无图片,用失败占位图替换\n const errorImagePlaceholderJpg\n = this.quill.getLangText('img-error') === 'Image Copy Error'\n ? ERROR_IMAGE_PLACEHOLDER_EN\n : ERROR_IMAGE_PLACEHOLDER_CN\n file = await imageUrlToFile(errorImagePlaceholderJpg, true)\n isPlaceholderImage = true\n }\n }\n\n return [file, isPlaceholderImage, image, imageIndex]\n }),\n )\n }\n\n getImgSelection(delta, imageIndex) {\n let length = 0\n delta.ops.every((op, index) => {\n if (index === imageIndex) {\n return false\n }\n if (typeof op.insert === 'string') {\n length += op.insert.length\n }\n else if (typeof op.insert === 'object') {\n // 对于图片、提及等对象类型的 insert,长度为 1\n length += 1\n }\n return true\n })\n const range = {\n index: length,\n length: 0,\n }\n return range\n }\n}\n\nfunction rebuildDelta(delta, cellLine) {\n const { cell: cellId, colspan, row: rowId, rowspan } = cellLine\n const buildedDelta = delta.reduce((newDelta, op) => {\n if (op.insert && typeof op.insert === 'string') {\n const lines = splitWithBreak(op.insert)\n\n lines.forEach((text) => {\n if (text === '\\n') {\n // 对换行增加 table-cell-line 格式,以避免表格断开\n newDelta.insert('\\n', {\n ...op.attributes,\n 'table-cell-line': { row: rowId, cell: cellId, rowspan, colspan },\n })\n }\n else {\n text = text.endsWith('\\r') ? text.slice(0, -1) : text\n newDelta.insert(\n text,\n omit(op.attributes, ['table', 'table-cell-line']),\n )\n }\n })\n }\n else {\n newDelta.insert(op.insert, op.attributes)\n }\n\n return newDelta\n }, new Delta())\n\n return buildedDelta\n}\n\nfunction replaceStrWhiteSpace(str) {\n const isWhiteSpace = value => /^(\\u3000|\\u0020){1}$/.test(value) // 空白字符\n let textWithWhiteSpace = ''\n let beginHasChar = false\n for (const char of str) {\n if (isWhiteSpace(char) && !beginHasChar) {\n textWithWhiteSpace += '\\u00A0'\n }\n else {\n textWithWhiteSpace += char\n beginHasChar = true\n }\n }\n return textWithWhiteSpace\n}\n\nfunction replaceDeltaWhiteSpace(delta, rootBgColor?) {\n return delta.reduce((newDelta, op) => {\n // fix: 当粘贴文字颜色和编辑器背景色一致且自身无背景色的情况下移除文字颜色样式,避免误导用户粘贴无效\n if (\n rootBgColor\n && op.attributes\n && op.attributes.color\n && !op.attributes.background\n ) {\n const originColor = op.attributes.color\n const fontColor\n = originColor.indexOf('#') === 0 ? hexToRgbA(originColor) : originColor\n if (\n fontColor === rootBgColor\n || (fontColor === 'rgba(255,255,255,1)'\n && rootBgColor === 'rgba(0, 0, 0, 0)')\n ) {\n delete op.attributes.color\n }\n }\n if (op.insert && typeof op.insert === 'string') {\n const lines = splitWithBreak(op.insert)\n let insertWithWhiteSpace = ''\n lines.forEach((text) => {\n insertWithWhiteSpace += replaceStrWhiteSpace(text)\n })\n newDelta.insert(insertWithWhiteSpace, op.attributes)\n }\n else {\n newDelta.insert(op.insert, op.attributes)\n }\n return newDelta\n }, new Delta())\n}\n\nfunction renderStyles(html) {\n let htmlString = html\n // Trim unnecessary parts.\n htmlString = htmlString.substring(\n htmlString.indexOf('<html '),\n htmlString.length,\n )\n htmlString = htmlString.substring(\n 0,\n htmlString.lastIndexOf('</html>') + '</html>'.length,\n )\n\n // Add temporary iframe.\n const iframe = document.createElement('iframe')\n iframe.style.display = 'none'\n document.body.appendChild(iframe)\n\n const iframeDoc = iframe.contentDocument || iframe.contentWindow.document\n iframeDoc.open()\n iframeDoc.write(htmlString)\n iframeDoc.close()\n\n let collection\n let pointer\n const rules\n = iframeDoc.styleSheets[iframeDoc.styleSheets.length - 1].cssRules\n\n // Convert internal styles to inline style of respective node.\n for (let idx = 0; idx < rules.length; idx++) {\n if ((rules[idx] as CSSStyleRule).selectorText === '') {\n continue\n }\n collection = iframeDoc.body.querySelectorAll(\n (rules[idx] as CSSStyleRule).selectorText,\n )\n\n for (pointer = 0; pointer < collection.length; pointer++) {\n collection[pointer].style.cssText += (\n rules[idx] as CSSStyleRule\n ).style.cssText\n }\n }\n\n // @ts-ignore\n const convertedString = iframeDoc.firstChild.outerHTML\n // Remove temporary iframe.\n iframe.parentNode.removeChild(iframe)\n\n return convertedString\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"custom-clipboard.es.js","sources":["../../../src/modules/custom-clipboard.ts"],"sourcesContent":["import type TypeClipboard from 'quill/modules/clipboard'\nimport type FluentEditor from '../fluent-editor'\nimport Quill from 'quill'\nimport {\n ERROR_IMAGE_PLACEHOLDER_CN,\n ERROR_IMAGE_PLACEHOLDER_EN,\n} from '../config/base64-image'\nimport { BIG_DELTA_LIMIT } from '../config/editor.config'\nimport {\n hexToRgbA,\n imageUrlToFile,\n isNullOrUndefined,\n omit,\n replaceDeltaImage,\n replaceStrWhiteSpace,\n splitWithBreak,\n} from '../config/editor.utils'\nimport { isString } from '../utils/is'\n\nconst Clipboard = Quill.import('modules/clipboard') as typeof TypeClipboard\nconst Delta = Quill.import('delta')\n\nexport class CustomClipboard extends Clipboard {\n declare quill: FluentEditor\n\n constructor(public quill: FluentEditor) {\n super(quill)\n // 解决 quill 组件在中文输入法的情景下开始输入placeholder不消失的问题\n this.quill.root.addEventListener('input', () => {\n if (this.quill.root.innerText !== '\\\\n' && this.quill.root.classList.contains('ql-blank')) {\n this.quill.root.classList.toggle('ql-blank', false)\n }\n else {\n this.quill.options.placeholder = this.quill.getLangText('editor-placeholder')\n }\n })\n }\n\n prepareMatching(container: HTMLElement, nodeMatches) {\n const elementMatchers = []\n const textMatchers = []\n this.matchers.forEach((pair) => {\n const [selector, matcher] = pair\n if (selector === Node.TEXT_NODE) {\n textMatchers.push(matcher)\n }\n else if (selector === Node.ELEMENT_NODE) {\n elementMatchers.push(matcher)\n }\n else if (isString(selector)) {\n // word 的 v:shape 系列标签只能通过 getElementsByTagName 获取\n const vRegex = /v:(.+)/\n const nodeList = Array.from(\n vRegex.test(selector)\n ? container.getElementsByTagName(selector)\n : container.querySelectorAll(selector),\n )\n nodeList.forEach((node) => {\n if (nodeMatches.has(node)) {\n const matches = nodeMatches.get(node)\n matches.push(matcher)\n }\n else {\n nodeMatches.set(node, [matcher])\n }\n })\n }\n })\n return [elementMatchers, textMatchers]\n }\n\n onCaptureCopy(e, isCut = false) {\n if (e.defaultPrevented) {\n return\n }\n e.preventDefault()\n const [range] = this.quill.selection.getRange()\n if (isNullOrUndefined(range)) {\n return\n }\n const { html, text } = this.onCopy(range, isCut)\n\n // 兼容IE11浏览器`\n if (!e.clipboardData) {\n e.clipboardData = {\n types: 'text/plain',\n setData: (_type, value) => {\n // @ts-ignore\n return window.clipboardData.setData('Text', value)\n },\n }\n }\n\n // 复制代码时移除utf8中产生的不间断空格\\u00A0\n let plainText = text\n if (html.startsWith('<pre>')) {\n plainText = text.replace(/\\u00A0/g, ' ')\n }\n\n e.clipboardData.setData('text/html', html)\n e.clipboardData.setData('text/plain', plainText)\n if (isCut) {\n this.quill.deleteText(range, Quill.sources.USER)\n }\n }\n\n onCapturePaste(e: ClipboardEvent) {\n if (e.defaultPrevented || !this.quill.isEnabled()) {\n return\n }\n e.preventDefault()\n const range = this.quill.getSelection(true)\n if (isNullOrUndefined(range)) {\n return\n }\n\n // 兼容IE11浏览器\n if (!e.clipboardData) {\n // @ts-ignore\n e.clipboardData = {\n types: 'text/plain',\n getData: () => {\n // @ts-ignore\n return window.clipboardData.getData('Text')\n },\n }\n }\n\n const html = e.clipboardData.getData('text/html')\n const text = e.clipboardData.getData('text/plain')\n const files = Array.from(e.clipboardData.files || [])\n const msExcelCheck = /<meta.*?Microsoft Excel\\s[\\d].*?>/\n\n if (html.search(msExcelCheck) === -1 && files.length > 0) {\n this.quill.uploader.upload(range, files)\n }\n else {\n const msWordCheck1\n = /<meta\\s*name=\"?generator\"?\\s*content=\"?microsoft\\s*word\\s*\\d+\"?\\/?>/i\n const msWordCheck2 = /xmlns:o=\"urn:schemas-microsoft-com/i\n const result = { html, text, files, rtf: null }\n if (html.search(msExcelCheck) !== -1) {\n result.html = renderStyles(html)\n }\n if (msWordCheck1.test(html) || msWordCheck2.test(html)) {\n // TODO: 当word文档包含heading时text/rtf读取为空,无法获取hex图片,待修复。可参考ckeditor5/issues/2493\n result.rtf = e.clipboardData.getData('text/rtf')\n }\n this.onPaste(range, result)\n }\n }\n\n onPaste(range, { html, text, files: clipboardFiles, rtf }) {\n const hexImages = this.extractImageDataFromRtf(rtf)\n const rootBgColor = getComputedStyle(this.quill.root).backgroundColor\n const formats = this.quill.getFormat(range.index)\n let pastedDelta = this.convert({ text, html }, formats)\n pastedDelta = replaceDeltaWhiteSpace(pastedDelta, rootBgColor)\n const deltaLength = pastedDelta.ops.length\n\n let loadingTipsContainer\n if (deltaLength > BIG_DELTA_LIMIT) {\n loadingTipsContainer = this.quill.addContainer('ql-loading-tips')\n loadingTipsContainer.innerHTML = this.quill.getLangText('pasting')\n }\n\n const linePos = { index: range.index, length: range.length, fix: 0 }\n const [line, offset] = this.quill.getLine(range.index)\n\n const handlePasteContent = (content: any) => {\n const pastedContent = content\n\n const oldDelta = new Delta().retain(linePos.index).delete(linePos.length)\n const delta = oldDelta.concat(pastedContent)\n\n setTimeout(() => {\n this.quill.updateContents(delta, Quill.sources.USER)\n // 光标位置应该在粘贴内容之后:原光标位置 + 粘贴内容长度\n const newSelectionIndex = linePos.index + (pastedContent.length ? pastedContent.length() : 0)\n this.quill.setSelection(\n newSelectionIndex,\n Quill.sources.SILENT,\n )\n this.quill.scrollSelectionIntoView()\n if (loadingTipsContainer) {\n loadingTipsContainer.remove()\n }\n })\n }\n\n ;(async () => {\n try {\n const [files, placeholders, originalUrls, imageIndexs] = this.flipFilesArray(\n await this.extractFilesFromDelta(\n pastedDelta,\n clipboardFiles,\n hexImages,\n ),\n )\n\n if (files.length === 0) {\n handlePasteContent(pastedDelta)\n }\n else {\n if (this.quill.options.editorPaste && this.quill.options.editorPaste.observers.length !== 0) {\n // 设置editorPaste回调的情况\n this.quill.options.editorPaste.emit({\n files,\n callback: ({ code, message, data }) => {\n if (code === 0) {\n const { imageUrls } = data\n pastedDelta = replaceDeltaImage(\n pastedDelta,\n imageUrls,\n placeholders,\n )\n handlePasteContent(pastedDelta)\n }\n else {\n console.error('error message:', message)\n }\n },\n })\n }\n else {\n // 没有originalUrls 也没有文件粘贴\n if (files[0] !== undefined || originalUrls.length === 0) {\n // 没有设置editorPaste回调的情况下,File格式的占位图需要手动转换成url格式,插入到编辑器中\n const imageUrls = await this.files2urls(\n files,\n placeholders,\n originalUrls,\n pastedDelta,\n imageIndexs,\n )\n pastedDelta = replaceDeltaImage(\n pastedDelta,\n imageUrls,\n placeholders,\n )\n }\n handlePasteContent(pastedDelta)\n }\n }\n }\n catch (_e) {\n throw new Error('Paste failed.')\n }\n })()\n }\n\n files2urls(files: File[], placeholders, originalUrls, pastedDelta, imageIndexs) {\n return Promise.all(\n files.map(async (imageFile, index) => {\n const netImgExp = /^((http|https)\\:)?\\/\\/([\\s\\S]+)$/\n if (\n !placeholders[index]\n && originalUrls[index]\n && netImgExp.test(originalUrls[index])\n ) {\n // 不是占位图的普通url图片直接返回url\n return new Promise((resolve) => {\n resolve(originalUrls[index])\n })\n }\n else {\n const range = this.getImgSelection(pastedDelta, imageIndexs[index])\n const urls = await this.quill.uploader.getFileUrls([imageFile], range)\n return urls[0] || undefined\n }\n }),\n )\n }\n\n flipFilesArray(filesArr) {\n const files = []\n const placeholders = []\n const originalUrls = []\n const imageIndexs = []\n filesArr.forEach((item: any) => {\n if (item) {\n const [file, placeholder, originalUrl, imageIndex] = item\n files.push(file)\n placeholders.push(placeholder)\n originalUrls.push(originalUrl)\n if (imageIndex === 0 || imageIndex) {\n imageIndexs.push(imageIndex)\n }\n }\n })\n return [files, placeholders, originalUrls, imageIndexs]\n }\n\n // 将图片从hex转为base64\n convertHexToBase64(hexString) {\n return btoa(\n hexString\n .match(/\\w{2}/g)\n .map((char) => {\n return String.fromCharCode(Number.parseInt(char, 16))\n })\n .join(''),\n )\n }\n\n // 匹配rtf中的图片,存储为{hex, type}对象数组\n extractImageDataFromRtf(rtfData) {\n if (!rtfData) {\n return []\n }\n\n const regexPictureHeader\n = /{\\\\pict[\\s\\S]+?\\\\bliptag-?\\d+(\\\\blipupi-?\\d+)?({\\\\\\*\\\\blipuid\\s?[\\da-fA-F]+)?[\\s}]*?/\n const regexPicture = new RegExp(\n `(?:(${regexPictureHeader.source}))([\\\\da-fA-F\\\\s]+)\\\\}`,\n 'g',\n )\n const images = rtfData.match(regexPicture)\n const result = []\n\n if (images) {\n for (const image of images) {\n let imageType = ''\n\n if (image.includes('\\\\pngblip')) {\n imageType = 'image/png'\n }\n else if (image.includes('\\\\jpegblip')) {\n imageType = 'image/jpeg'\n }\n\n if (imageType) {\n result.push({\n hex: image\n .replace(regexPictureHeader, '')\n .replace(/[^\\da-fA-F]/g, ''),\n type: imageType,\n })\n }\n }\n }\n\n return result\n }\n\n extractFilesFromDelta(delta, clipboardFiles, hexImages?) {\n let index = -1\n return Promise.all(\n delta.map(async (op) => {\n index++\n const image = op.insert.image\n if (!image || image.hasExisted) {\n return\n }\n\n let file\n let isPlaceholderImage = false\n let imageIndex\n try {\n // hex 图片存在则为 file:/// 协议本地图片,使用 hex 图片转为 base64 读取\n const hexImage = hexImages.length && hexImages.shift()\n const newImage\n = hexImage\n && `data:${hexImage.type};base64,${this.convertHexToBase64(\n hexImage.hex,\n )}`\n imageIndex = index\n file = await imageUrlToFile(newImage || image.src || image)\n }\n catch (_err) {\n if (clipboardFiles.length !== 0) {\n // 跨域获取图片失败时从剪切板获取图片\n const clipboardFile = clipboardFiles[0]\n const imageType\n = clipboardFile.type?.indexOf('image') === -1\n ? 'image/png'\n : clipboardFile.type\n const blob = clipboardFile.slice(0, clipboardFile.size, imageType)\n file = new File([blob], `image-CORS-${new Date().getTime()}.png`, {\n type: imageType,\n })\n }\n else if (image.src.startsWith('http')) {\n // 什么都不做\n }\n else {\n // 剪切板中无图片,用失败占位图替换\n const errorImagePlaceholderJpg\n = this.quill.getLangText('img-error') === 'Image Copy Error'\n ? ERROR_IMAGE_PLACEHOLDER_EN\n : ERROR_IMAGE_PLACEHOLDER_CN\n file = await imageUrlToFile(errorImagePlaceholderJpg, true)\n isPlaceholderImage = true\n }\n }\n\n return [file, isPlaceholderImage, image, imageIndex]\n }),\n )\n }\n\n getImgSelection(delta, imageIndex) {\n let length = 0\n delta.ops.every((op, index) => {\n if (index === imageIndex) {\n return false\n }\n if (typeof op.insert === 'string') {\n length += op.insert.length\n }\n else if (typeof op.insert === 'object') {\n // 对于图片、提及等对象类型的 insert,长度为 1\n length += 1\n }\n return true\n })\n const range = {\n index: length,\n length: 0,\n }\n return range\n }\n}\n\nfunction rebuildDelta(delta, cellLine) {\n const { cell: cellId, colspan, row: rowId, rowspan } = cellLine\n const buildedDelta = delta.reduce((newDelta, op) => {\n if (op.insert && typeof op.insert === 'string') {\n const lines = splitWithBreak(op.insert)\n\n lines.forEach((text) => {\n if (text === '\\n') {\n // 对换行增加 table-cell-line 格式,以避免表格断开\n newDelta.insert('\\n', {\n ...op.attributes,\n 'table-cell-line': { row: rowId, cell: cellId, rowspan, colspan },\n })\n }\n else {\n text = text.endsWith('\\r') ? text.slice(0, -1) : text\n newDelta.insert(\n text,\n omit(op.attributes, ['table', 'table-cell-line']),\n )\n }\n })\n }\n else {\n newDelta.insert(op.insert, op.attributes)\n }\n\n return newDelta\n }, new Delta())\n\n return buildedDelta\n}\n\nfunction replaceDeltaWhiteSpace(delta, rootBgColor?) {\n return delta.reduce((newDelta, op) => {\n // fix: 当粘贴文字颜色和编辑器背景色一致且自身无背景色的情况下移除文字颜色样式,避免误导用户粘贴无效\n if (\n rootBgColor\n && op.attributes\n && op.attributes.color\n && !op.attributes.background\n ) {\n const originColor = op.attributes.color\n const fontColor\n = originColor.indexOf('#') === 0 ? hexToRgbA(originColor) : originColor\n if (\n fontColor === rootBgColor\n || (fontColor === 'rgba(255,255,255,1)'\n && rootBgColor === 'rgba(0, 0, 0, 0)')\n ) {\n delete op.attributes.color\n }\n }\n if (op.insert && typeof op.insert === 'string') {\n const lines = splitWithBreak(op.insert)\n let insertWithWhiteSpace = ''\n lines.forEach((text) => {\n insertWithWhiteSpace += replaceStrWhiteSpace(text)\n })\n newDelta.insert(insertWithWhiteSpace, op.attributes)\n }\n else {\n newDelta.insert(op.insert, op.attributes)\n }\n return newDelta\n }, new Delta())\n}\n\nfunction renderStyles(html) {\n let htmlString = html\n // Trim unnecessary parts.\n htmlString = htmlString.substring(\n htmlString.indexOf('<html '),\n htmlString.length,\n )\n htmlString = htmlString.substring(\n 0,\n htmlString.lastIndexOf('</html>') + '</html>'.length,\n )\n\n // Add temporary iframe.\n const iframe = document.createElement('iframe')\n iframe.style.display = 'none'\n document.body.appendChild(iframe)\n\n const iframeDoc = iframe.contentDocument || iframe.contentWindow.document\n iframeDoc.open()\n iframeDoc.write(htmlString)\n iframeDoc.close()\n\n let collection\n let pointer\n const rules\n = iframeDoc.styleSheets[iframeDoc.styleSheets.length - 1].cssRules\n\n // Convert internal styles to inline style of respective node.\n for (let idx = 0; idx < rules.length; idx++) {\n if ((rules[idx] as CSSStyleRule).selectorText === '') {\n continue\n }\n collection = iframeDoc.body.querySelectorAll(\n (rules[idx] as CSSStyleRule).selectorText,\n )\n\n for (pointer = 0; pointer < collection.length; pointer++) {\n collection[pointer].style.cssText += (\n rules[idx] as CSSStyleRule\n ).style.cssText\n }\n }\n\n // @ts-ignore\n const convertedString = iframeDoc.firstChild.outerHTML\n // Remove temporary iframe.\n iframe.parentNode.removeChild(iframe)\n\n return convertedString\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -115,8 +115,8 @@ class BlotFormatter {
|
|
|
115
115
|
display: "block",
|
|
116
116
|
left: `${specRect.left - parentRect.left - 1 + parent.scrollLeft}px`,
|
|
117
117
|
top: `${specRect.top - parentRect.top + parent.scrollTop}px`,
|
|
118
|
-
width: `${specRect.width}px`,
|
|
119
|
-
height: `${specRect.height}px`
|
|
118
|
+
width: `${Math.min(specRect.width, parentRect.width)}px`,
|
|
119
|
+
height: `${Math.min(specRect.height, parentRect.height)}px`
|
|
120
120
|
});
|
|
121
121
|
}
|
|
122
122
|
setUserSelect(value) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"blot-formatter.es.js","sources":["../../../../src/modules/custom-image/blot-formatter.ts"],"sourcesContent":["import type FluentEditor from '../../core/fluent-editor'\nimport type { Action } from './actions'\nimport type { BlotFormatterOptions } from './options'\nimport type { BlotSpec } from './specs'\nimport Quill from 'quill'\nimport { merge as deepmerge } from '../../utils/merge'\nimport { CustomImage } from './image'\nimport DefaultOptions from './options'\nimport { CustomImageSpec } from './specs'\n\nconst dontMerge = (_destination: Array<any>, source: Array<any>) => source\n\nexport class BlotFormatter {\n options: BlotFormatterOptions\n currentSpec: BlotSpec\n specs: BlotSpec[]\n overlay: HTMLElement\n actions: Action[]\n observer: MutationObserver\n\n static register() {\n Quill.register({\n 'formats/image': CustomImage,\n 'modules/image-spec': CustomImageSpec,\n }, true)\n }\n\n constructor(public quill: FluentEditor, options: Partial<BlotFormatterOptions> = {}) {\n this.options = deepmerge({}, DefaultOptions, options, { arrayMerge: dontMerge })\n if (options.allowInvalidUrl !== undefined) {\n this.options.allowInvalidUrl = options.allowInvalidUrl\n }\n CustomImage.setOptions(this.options.allowInvalidUrl)\n this.currentSpec = null\n this.actions = []\n this.overlay = document.createElement('div')\n this.overlay.classList.add(this.options.overlay.className)\n if (this.options.overlay.style) {\n Object.assign(this.overlay.style, this.options.overlay.style)\n }\n\n // disable native image resizing on firefox\n document.execCommand('enableObjectResizing', false, 'false') // eslint-disable-next-line-line no-undef\n this.quill.root.addEventListener('click', this.onClick)\n this.specs = this.options.specs.map((SpecClass: any) => new SpecClass(this))\n this.specs.forEach(spec => spec.init())\n }\n\n show(spec: BlotSpec) {\n this.currentSpec = spec\n this.currentSpec.setSelection()\n this.setUserSelect('none')\n this.quill.root.parentNode.appendChild(this.overlay)\n this.repositionOverlay()\n this.createActions(spec)\n\n // fix: 图片对齐之后,虚线外框应该跟随移动\n const imageDom = spec.getTargetElement()\n const win: any = window\n const MutationObserver: typeof window.MutationObserver = win.MutationObserver || win.WebKitMutationObserver || win.MozMutationObserver\n const element = imageDom.parentNode\n this.observer = new MutationObserver((mutationList) => {\n for (const mutation of mutationList) {\n const target = mutation.target as HTMLElement\n const image = target.querySelector('img')\n if (image) {\n this.repositionOverlay()\n }\n }\n })\n this.observer.observe(element, {\n attributes: true,\n attributeFilter: ['class'],\n attributeOldValue: true,\n subtree: true,\n })\n }\n\n hide() {\n if (!this.currentSpec) {\n return\n }\n\n const imgDom = this.currentSpec.getTargetElement()\n if (imgDom) {\n imgDom.classList.remove('current-select-img')\n }\n\n this.currentSpec.onHide()\n this.currentSpec = null\n this.quill.root.parentNode.removeChild(this.overlay)\n this.overlay.style.setProperty('display', 'none')\n this.setUserSelect('')\n this.destroyActions()\n }\n\n update() {\n this.repositionOverlay()\n this.actions.forEach(action => action.onUpdate())\n }\n\n createActions(spec: BlotSpec) {\n this.actions = spec.getActions().map((ActionClass: any) => {\n const action: Action = new ActionClass(this)\n action.onCreate()\n return action\n })\n }\n\n destroyActions() {\n this.actions.forEach((action: Action) => action.onDestroy())\n this.actions = []\n }\n\n repositionOverlay() {\n if (!this.currentSpec) {\n return\n }\n\n const overlayTarget = this.currentSpec.getOverlayElement()\n if (!overlayTarget) {\n return\n }\n\n const parent = this.quill.root.parentElement\n const specRect = overlayTarget.getBoundingClientRect()\n const parentRect = parent.getBoundingClientRect()\n\n Object.assign(this.overlay.style, {\n display: 'block',\n left: `${specRect.left - parentRect.left - 1 + parent.scrollLeft}px`,\n top: `${specRect.top - parentRect.top + parent.scrollTop}px`,\n width: `${specRect.width}px`,\n height: `${specRect.height}px`,\n })\n }\n\n setUserSelect(value: string) {\n const props: string[] = [\n 'userSelect',\n 'mozUserSelect',\n 'webkitUserSelect',\n 'msUserSelect',\n ]\n\n props.forEach((prop: string) => {\n // set on contenteditable element and <html>\n this.quill.root.style.setProperty(prop, value)\n if (document.documentElement) {\n document.documentElement.style.setProperty(prop, value)\n }\n })\n }\n\n onClick = () => {\n this.hide()\n }\n}\n"],"names":["deepmerge"],"mappings":"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
|
|
1
|
+
{"version":3,"file":"blot-formatter.es.js","sources":["../../../../src/modules/custom-image/blot-formatter.ts"],"sourcesContent":["import type FluentEditor from '../../core/fluent-editor'\nimport type { Action } from './actions'\nimport type { BlotFormatterOptions } from './options'\nimport type { BlotSpec } from './specs'\nimport Quill from 'quill'\nimport { merge as deepmerge } from '../../utils/merge'\nimport { CustomImage } from './image'\nimport DefaultOptions from './options'\nimport { CustomImageSpec } from './specs'\n\nconst dontMerge = (_destination: Array<any>, source: Array<any>) => source\n\nexport class BlotFormatter {\n options: BlotFormatterOptions\n currentSpec: BlotSpec\n specs: BlotSpec[]\n overlay: HTMLElement\n actions: Action[]\n observer: MutationObserver\n\n static register() {\n Quill.register({\n 'formats/image': CustomImage,\n 'modules/image-spec': CustomImageSpec,\n }, true)\n }\n\n constructor(public quill: FluentEditor, options: Partial<BlotFormatterOptions> = {}) {\n this.options = deepmerge({}, DefaultOptions, options, { arrayMerge: dontMerge })\n if (options.allowInvalidUrl !== undefined) {\n this.options.allowInvalidUrl = options.allowInvalidUrl\n }\n CustomImage.setOptions(this.options.allowInvalidUrl)\n this.currentSpec = null\n this.actions = []\n this.overlay = document.createElement('div')\n this.overlay.classList.add(this.options.overlay.className)\n if (this.options.overlay.style) {\n Object.assign(this.overlay.style, this.options.overlay.style)\n }\n\n // disable native image resizing on firefox\n document.execCommand('enableObjectResizing', false, 'false') // eslint-disable-next-line-line no-undef\n this.quill.root.addEventListener('click', this.onClick)\n this.specs = this.options.specs.map((SpecClass: any) => new SpecClass(this))\n this.specs.forEach(spec => spec.init())\n }\n\n show(spec: BlotSpec) {\n this.currentSpec = spec\n this.currentSpec.setSelection()\n this.setUserSelect('none')\n this.quill.root.parentNode.appendChild(this.overlay)\n this.repositionOverlay()\n this.createActions(spec)\n\n // fix: 图片对齐之后,虚线外框应该跟随移动\n const imageDom = spec.getTargetElement()\n const win: any = window\n const MutationObserver: typeof window.MutationObserver = win.MutationObserver || win.WebKitMutationObserver || win.MozMutationObserver\n const element = imageDom.parentNode\n this.observer = new MutationObserver((mutationList) => {\n for (const mutation of mutationList) {\n const target = mutation.target as HTMLElement\n const image = target.querySelector('img')\n if (image) {\n this.repositionOverlay()\n }\n }\n })\n this.observer.observe(element, {\n attributes: true,\n attributeFilter: ['class'],\n attributeOldValue: true,\n subtree: true,\n })\n }\n\n hide() {\n if (!this.currentSpec) {\n return\n }\n\n const imgDom = this.currentSpec.getTargetElement()\n if (imgDom) {\n imgDom.classList.remove('current-select-img')\n }\n\n this.currentSpec.onHide()\n this.currentSpec = null\n this.quill.root.parentNode.removeChild(this.overlay)\n this.overlay.style.setProperty('display', 'none')\n this.setUserSelect('')\n this.destroyActions()\n }\n\n update() {\n this.repositionOverlay()\n this.actions.forEach(action => action.onUpdate())\n }\n\n createActions(spec: BlotSpec) {\n this.actions = spec.getActions().map((ActionClass: any) => {\n const action: Action = new ActionClass(this)\n action.onCreate()\n return action\n })\n }\n\n destroyActions() {\n this.actions.forEach((action: Action) => action.onDestroy())\n this.actions = []\n }\n\n repositionOverlay() {\n if (!this.currentSpec) {\n return\n }\n\n const overlayTarget = this.currentSpec.getOverlayElement()\n if (!overlayTarget) {\n return\n }\n\n const parent = this.quill.root.parentElement\n const specRect = overlayTarget.getBoundingClientRect()\n const parentRect = parent.getBoundingClientRect()\n\n Object.assign(this.overlay.style, {\n display: 'block',\n left: `${specRect.left - parentRect.left - 1 + parent.scrollLeft}px`,\n top: `${specRect.top - parentRect.top + parent.scrollTop}px`,\n width: `${Math.min(specRect.width, parentRect.width)}px`,\n height: `${Math.min(specRect.height, parentRect.height)}px`,\n })\n }\n\n setUserSelect(value: string) {\n const props: string[] = [\n 'userSelect',\n 'mozUserSelect',\n 'webkitUserSelect',\n 'msUserSelect',\n ]\n\n props.forEach((prop: string) => {\n // set on contenteditable element and <html>\n this.quill.root.style.setProperty(prop, value)\n if (document.documentElement) {\n document.documentElement.style.setProperty(prop, value)\n }\n })\n }\n\n onClick = () => {\n this.hide()\n }\n}\n"],"names":["deepmerge"],"mappings":";;;;;;;;;AAUA,MAAM,YAAY,CAAC,cAA0B,WAAuB;AAE7D,MAAM,cAAc;AAAA,EAezB,YAAmB,OAAqB,UAAyC,IAAI;AAdrF;AACA;AACA;AACA;AACA;AACA;AAwIA,mCAAU,MAAM;AACd,WAAK,KAAA;AAAA,IACP;AAjImB,SAAA,QAAA;AACjB,SAAK,UAAUA,MAAU,CAAA,GAAI,gBAAgB,SAAS,EAAE,YAAY,WAAW;AAC/E,QAAI,QAAQ,oBAAoB,QAAW;AACzC,WAAK,QAAQ,kBAAkB,QAAQ;AAAA,IACzC;AACA,gBAAY,WAAW,KAAK,QAAQ,eAAe;AACnD,SAAK,cAAc;AACnB,SAAK,UAAU,CAAA;AACf,SAAK,UAAU,SAAS,cAAc,KAAK;AAC3C,SAAK,QAAQ,UAAU,IAAI,KAAK,QAAQ,QAAQ,SAAS;AACzD,QAAI,KAAK,QAAQ,QAAQ,OAAO;AAC9B,aAAO,OAAO,KAAK,QAAQ,OAAO,KAAK,QAAQ,QAAQ,KAAK;AAAA,IAC9D;AAGA,aAAS,YAAY,wBAAwB,OAAO,OAAO;AAC3D,SAAK,MAAM,KAAK,iBAAiB,SAAS,KAAK,OAAO;AACtD,SAAK,QAAQ,KAAK,QAAQ,MAAM,IAAI,CAAC,cAAmB,IAAI,UAAU,IAAI,CAAC;AAC3E,SAAK,MAAM,QAAQ,CAAA,SAAQ,KAAK,MAAM;AAAA,EACxC;AAAA,EA1BA,OAAO,WAAW;AAChB,UAAM,SAAS;AAAA,MACb,iBAAiB;AAAA,MACjB,sBAAsB;AAAA,IAAA,GACrB,IAAI;AAAA,EACT;AAAA,EAuBA,KAAK,MAAgB;AACnB,SAAK,cAAc;AACnB,SAAK,YAAY,aAAA;AACjB,SAAK,cAAc,MAAM;AACzB,SAAK,MAAM,KAAK,WAAW,YAAY,KAAK,OAAO;AACnD,SAAK,kBAAA;AACL,SAAK,cAAc,IAAI;AAGvB,UAAM,WAAW,KAAK,iBAAA;AACtB,UAAM,MAAW;AACjB,UAAM,mBAAmD,IAAI,oBAAoB,IAAI,0BAA0B,IAAI;AACnH,UAAM,UAAU,SAAS;AACzB,SAAK,WAAW,IAAI,iBAAiB,CAAC,iBAAiB;AACrD,iBAAW,YAAY,cAAc;AACnC,cAAM,SAAS,SAAS;AACxB,cAAM,QAAQ,OAAO,cAAc,KAAK;AACxC,YAAI,OAAO;AACT,eAAK,kBAAA;AAAA,QACP;AAAA,MACF;AAAA,IACF,CAAC;AACD,SAAK,SAAS,QAAQ,SAAS;AAAA,MAC7B,YAAY;AAAA,MACZ,iBAAiB,CAAC,OAAO;AAAA,MACzB,mBAAmB;AAAA,MACnB,SAAS;AAAA,IAAA,CACV;AAAA,EACH;AAAA,EAEA,OAAO;AACL,QAAI,CAAC,KAAK,aAAa;AACrB;AAAA,IACF;AAEA,UAAM,SAAS,KAAK,YAAY,iBAAA;AAChC,QAAI,QAAQ;AACV,aAAO,UAAU,OAAO,oBAAoB;AAAA,IAC9C;AAEA,SAAK,YAAY,OAAA;AACjB,SAAK,cAAc;AACnB,SAAK,MAAM,KAAK,WAAW,YAAY,KAAK,OAAO;AACnD,SAAK,QAAQ,MAAM,YAAY,WAAW,MAAM;AAChD,SAAK,cAAc,EAAE;AACrB,SAAK,eAAA;AAAA,EACP;AAAA,EAEA,SAAS;AACP,SAAK,kBAAA;AACL,SAAK,QAAQ,QAAQ,CAAA,WAAU,OAAO,UAAU;AAAA,EAClD;AAAA,EAEA,cAAc,MAAgB;AAC5B,SAAK,UAAU,KAAK,WAAA,EAAa,IAAI,CAAC,gBAAqB;AACzD,YAAM,SAAiB,IAAI,YAAY,IAAI;AAC3C,aAAO,SAAA;AACP,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EAEA,iBAAiB;AACf,SAAK,QAAQ,QAAQ,CAAC,WAAmB,OAAO,WAAW;AAC3D,SAAK,UAAU,CAAA;AAAA,EACjB;AAAA,EAEA,oBAAoB;AAClB,QAAI,CAAC,KAAK,aAAa;AACrB;AAAA,IACF;AAEA,UAAM,gBAAgB,KAAK,YAAY,kBAAA;AACvC,QAAI,CAAC,eAAe;AAClB;AAAA,IACF;AAEA,UAAM,SAAS,KAAK,MAAM,KAAK;AAC/B,UAAM,WAAW,cAAc,sBAAA;AAC/B,UAAM,aAAa,OAAO,sBAAA;AAE1B,WAAO,OAAO,KAAK,QAAQ,OAAO;AAAA,MAChC,SAAS;AAAA,MACT,MAAM,GAAG,SAAS,OAAO,WAAW,OAAO,IAAI,OAAO,UAAU;AAAA,MAChE,KAAK,GAAG,SAAS,MAAM,WAAW,MAAM,OAAO,SAAS;AAAA,MACxD,OAAO,GAAG,KAAK,IAAI,SAAS,OAAO,WAAW,KAAK,CAAC;AAAA,MACpD,QAAQ,GAAG,KAAK,IAAI,SAAS,QAAQ,WAAW,MAAM,CAAC;AAAA,IAAA,CACxD;AAAA,EACH;AAAA,EAEA,cAAc,OAAe;AAC3B,UAAM,QAAkB;AAAA,MACtB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAGF,UAAM,QAAQ,CAAC,SAAiB;AAE9B,WAAK,MAAM,KAAK,MAAM,YAAY,MAAM,KAAK;AAC7C,UAAI,SAAS,iBAAiB;AAC5B,iBAAS,gBAAgB,MAAM,YAAY,MAAM,KAAK;AAAA,MACxD;AAAA,IACF,CAAC;AAAA,EACH;AAKF;"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import "./actions/index.es.js";
|
|
2
2
|
import { BlotFormatter } from "./blot-formatter.es.js";
|
|
3
3
|
import { CustomImage } from "./image.es.js";
|
|
4
|
-
import "./specs/index.es.js";
|
|
5
4
|
import "./preview/index.es.js";
|
|
5
|
+
import "./specs/index.es.js";
|
|
6
6
|
export {
|
|
7
7
|
BlotFormatter,
|
|
8
8
|
CustomImage
|
|
@@ -7,6 +7,7 @@ class ImagePreviewModal {
|
|
|
7
7
|
__publicField(this, "overlay", null);
|
|
8
8
|
__publicField(this, "previewImage", null);
|
|
9
9
|
__publicField(this, "scaleTooltip", null);
|
|
10
|
+
__publicField(this, "closeBtn", null);
|
|
10
11
|
__publicField(this, "currentScale", 1);
|
|
11
12
|
__publicField(this, "minScale", 0.5);
|
|
12
13
|
__publicField(this, "maxScale", 3);
|
|
@@ -65,10 +66,10 @@ class ImagePreviewModal {
|
|
|
65
66
|
transition: transform 0.2s ease-out;
|
|
66
67
|
cursor: grab;
|
|
67
68
|
`;
|
|
68
|
-
|
|
69
|
-
closeBtn.className = "tiny-editor-image-preview-close";
|
|
70
|
-
closeBtn.innerHTML = "×";
|
|
71
|
-
closeBtn.style.cssText = `
|
|
69
|
+
this.closeBtn = document.createElement("button");
|
|
70
|
+
this.closeBtn.className = "tiny-editor-image-preview-close";
|
|
71
|
+
this.closeBtn.innerHTML = "×";
|
|
72
|
+
this.closeBtn.style.cssText = `
|
|
72
73
|
position: fixed;
|
|
73
74
|
top: 20px;
|
|
74
75
|
right: 20px;
|
|
@@ -83,9 +84,9 @@ class ImagePreviewModal {
|
|
|
83
84
|
line-height: 1;
|
|
84
85
|
padding: 0;
|
|
85
86
|
`;
|
|
86
|
-
closeBtn.addEventListener("click", () => this.hide());
|
|
87
|
+
this.closeBtn.addEventListener("click", () => this.hide());
|
|
87
88
|
this.modal.appendChild(this.previewImage);
|
|
88
|
-
document.body.appendChild(closeBtn);
|
|
89
|
+
document.body.appendChild(this.closeBtn);
|
|
89
90
|
this.scaleTooltip = document.createElement("div");
|
|
90
91
|
this.scaleTooltip.className = "image-preview-scale-tooltip";
|
|
91
92
|
this.scaleTooltip.style.cssText = `
|
|
@@ -183,6 +184,9 @@ class ImagePreviewModal {
|
|
|
183
184
|
this.modal.style.alignItems = "center";
|
|
184
185
|
this.modal.style.justifyContent = "center";
|
|
185
186
|
this.overlay.style.display = "block";
|
|
187
|
+
if (this.closeBtn) {
|
|
188
|
+
this.closeBtn.style.display = "block";
|
|
189
|
+
}
|
|
186
190
|
document.body.style.overflow = "hidden";
|
|
187
191
|
}
|
|
188
192
|
/**
|
|
@@ -194,6 +198,9 @@ class ImagePreviewModal {
|
|
|
194
198
|
this.overlay.style.display = "none";
|
|
195
199
|
document.body.style.overflow = "";
|
|
196
200
|
this.resetScale();
|
|
201
|
+
if (this.closeBtn) {
|
|
202
|
+
this.closeBtn.style.display = "none";
|
|
203
|
+
}
|
|
197
204
|
}
|
|
198
205
|
}
|
|
199
206
|
/**
|
|
@@ -207,12 +214,16 @@ class ImagePreviewModal {
|
|
|
207
214
|
if (this.modal && this.modal.parentNode) {
|
|
208
215
|
this.modal.parentNode.removeChild(this.modal);
|
|
209
216
|
}
|
|
217
|
+
if (this.closeBtn && this.closeBtn.parentNode) {
|
|
218
|
+
this.closeBtn.parentNode.removeChild(this.closeBtn);
|
|
219
|
+
}
|
|
210
220
|
if (this.scaleTooltip && this.scaleTooltip.parentNode) {
|
|
211
221
|
this.scaleTooltip.parentNode.removeChild(this.scaleTooltip);
|
|
212
222
|
}
|
|
213
223
|
this.modal = null;
|
|
214
224
|
this.overlay = null;
|
|
215
225
|
this.previewImage = null;
|
|
226
|
+
this.closeBtn = null;
|
|
216
227
|
this.scaleTooltip = null;
|
|
217
228
|
}
|
|
218
229
|
}
|
|
@@ -1 +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 = 'tiny-editor-image-preview-close'\n closeBtn.innerHTML = '×'\n closeBtn.style.cssText = `\n position: fixed;\n top: 20px;\n right: 20px;\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 document.body.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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"}
|
|
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 closeBtn: HTMLButtonElement | 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 this.closeBtn = document.createElement('button')\n this.closeBtn.className = 'tiny-editor-image-preview-close'\n this.closeBtn.innerHTML = '×'\n this.closeBtn.style.cssText = `\n position: fixed;\n top: 20px;\n right: 20px;\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 this.closeBtn.addEventListener('click', () => this.hide())\n\n this.modal.appendChild(this.previewImage)\n document.body.appendChild(this.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 if (this.closeBtn) {\n this.closeBtn.style.display = 'block'\n }\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 if (this.closeBtn) {\n this.closeBtn.style.display = 'none'\n }\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.closeBtn && this.closeBtn.parentNode) {\n this.closeBtn.parentNode.removeChild(this.closeBtn)\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.closeBtn = 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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"}
|
|
@@ -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 { 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,
|
|
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 as HTMLElement).getAttribute('src') || (target as HTMLElement).getAttribute('data-image')\n\n if (imageSrc) {\n const modal = getImagePreviewModal()\n modal.show(imageSrc)\n }\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"custom-uploader.es.js","sources":["../../../src/modules/custom-uploader.ts"],"sourcesContent":["import type { Range } from 'quill'\r\nimport type TypeUploader from 'quill/modules/uploader'\r\nimport type FluentEditor from '../core/fluent-editor'\r\nimport Quill from 'quill'\r\nimport { isString } from '../utils/is'\r\n\r\nconst Uploader = Quill.import('modules/uploader') as typeof TypeUploader\r\nconst Delta = Quill.import('delta')\r\n\r\nexport type UploadKind = 'image' | 'video' | 'file'\r\nexport type MimeTypesConfig = string[] | Partial<Record<UploadKind, string[]>>\r\nexport type MaxSizeConfig = number | Partial<Record<UploadKind, number>>\r\nexport type MultipleConfig = boolean | Partial<Record<UploadKind, boolean>>\r\n\r\ninterface UploaderOptions {\r\n mimetypes: string[]\r\n handler: (this: { quill: Quill }, range: Range, files: File[]) => void\r\n}\r\nexport interface FileUploaderOptions {\r\n /**\r\n * 支持单个 MIME type、模糊匹配子类型和后缀名三种格式\r\n * - 全局配置:`string[]`\r\n * - 根据文件/图片/视频单独配置:`{ file?: string[]; image?: string[]; video?: string[] }`\r\n */\r\n mimetypes: MimeTypesConfig\r\n /**\r\n * 最大文件大小限制(字节)\r\n * - 全局配置:`number`\r\n * - 根据文件/图片/视频单独配置:`{ file?: number; image?: number; video?: number }`\r\n */\r\n maxSize: MaxSizeConfig\r\n /**\r\n * 是否允许多选文件\r\n * - 全局配置:`boolean`\r\n * - 根据文件/图片/视频单独配置:`{ file?: boolean; image?: boolean; video?: boolean }`\r\n */\r\n multiple: MultipleConfig\r\n handler: (this: { quill: FluentEditor }, range: Range, files: File[]) => Promise<(string | false)[]> | (string | false)[]\r\n success: (this: { quill: FluentEditor }, file: File, range: Range) => void\r\n fail: (this: { quill: FluentEditor }, file: File, range: Range) => void\r\n}\r\nexport class FileUploader extends Uploader {\r\n static DEFAULTS = {} as any\r\n // Partial<UploaderOptions> for ts type\r\n declare options: Partial<UploaderOptions> & FileUploaderOptions\r\n constructor(public quill: FluentEditor, options: Partial<FileUploaderOptions>) {\r\n super(quill, options as any)\r\n this.options = this.resolveOptions(options)\r\n // paste handle in clipboard\r\n }\r\n\r\n resolveOptions(options: Partial<FileUploaderOptions> = {}) {\r\n return Object.assign({\r\n mimetypes: ['*'],\r\n maxSize: Number.POSITIVE_INFINITY,\r\n multiple: true,\r\n handler(range: Range, files: File[]) {\r\n return files.map(file => URL.createObjectURL(file))\r\n },\r\n success() {},\r\n fail() {},\r\n }, options)\r\n }\r\n\r\n private inferKind(file: File): UploadKind {\r\n const type = file.type || ''\r\n if (type.startsWith('image/')) return 'image'\r\n if (type.startsWith('video/')) return 'video'\r\n return 'file'\r\n }\r\n\r\n private filterFromArray(types: string[], kind: UploadKind): string[] {\r\n if (kind === 'file') return types\r\n const prefix = `${kind}/`\r\n return types.filter((type) => {\r\n if (type === '*') return true\r\n if (type.includes('/')) return type.startsWith(prefix)\r\n return true\r\n })\r\n }\r\n\r\n getAccept(kind: UploadKind): string[] {\r\n const mimetypes = this.options.mimetypes\r\n if (Array.isArray(mimetypes)) {\r\n return this.filterFromArray(mimetypes, kind)\r\n }\r\n const map = mimetypes || {}\r\n const fromKind = map[kind]\r\n if (fromKind?.length) return fromKind\r\n if (map.file?.length && kind !== 'file') return map.file\r\n return []\r\n }\r\n\r\n getMaxSize(kind: UploadKind): number {\r\n const maxSize = this.options.maxSize\r\n if (typeof maxSize === 'number') {\r\n return maxSize\r\n }\r\n const map = maxSize || {}\r\n const fromKind = map[kind]\r\n if (typeof fromKind === 'number') return fromKind\r\n if (typeof map.file === 'number' && kind !== 'file') return map.file\r\n return Number.POSITIVE_INFINITY\r\n }\r\n\r\n getMultiple(kind: UploadKind): boolean {\r\n const multiple = this.options.multiple\r\n if (typeof multiple === 'boolean') {\r\n return multiple\r\n }\r\n const map = multiple || {}\r\n const fromKind = map[kind]\r\n if (typeof fromKind === 'boolean') return fromKind\r\n if (typeof map.file === 'boolean' && kind !== 'file') return map.file\r\n return true\r\n }\r\n\r\n validateFile(file: File, kind?: UploadKind) {\r\n const inferredKind = kind ?? this.inferKind(file)\r\n const accept = this.getAccept(inferredKind)\r\n const maxSize = this.getMaxSize(inferredKind)\r\n const mimeOk = accept.some((type) => {\r\n // 简单区分:带 '/' 的按 MIME,其他按后缀\r\n if (type.includes('/')) {\r\n return (file.type || 'text/plain').match(type.replaceAll('*', '.*'))\r\n }\r\n else {\r\n // 按文件名后缀匹配,例如 'png' / '.png'\r\n const ext = type.startsWith('.') ? type.toLowerCase() : `.${type.toLowerCase()}`\r\n return file.name.toLowerCase().endsWith(ext)\r\n }\r\n })\r\n return mimeOk && file.size < maxSize\r\n }\r\n\r\n async getFileUrls(files: File[], range: Range, kind?: UploadKind) {\r\n const uploads = files.filter(file => this.validateFile(file, kind))\r\n return this.options.handler.call(this, range, uploads)\r\n }\r\n\r\n async upload(range: Range, files: FileList | File[], kind?: UploadKind) {\r\n const uploads = []\r\n const fails = []\r\n\r\n for (const file of Array.from(files)) {\r\n if (this.validateFile(file, kind)) {\r\n uploads.push(file)\r\n }\r\n else {\r\n fails.push(file)\r\n }\r\n }\r\n\r\n const result = await this.options.handler.call(this, range, uploads)\r\n const updateDelta = result.reduce((delta, url, i) => {\r\n if (isString(url)) {\r\n const type = uploads[i].type\r\n if (type.startsWith('image/')) {\r\n delta.insert({ image: url })\r\n }\r\n else if (type.startsWith('video/')) {\r\n delta.insert({ video: { src: url } })\r\n }\r\n else {\r\n delta.insert({ file: { size: uploads[i].size, title: uploads[i].name, src: url } })\r\n }\r\n }\r\n else {\r\n delta.insert(' ')\r\n }\r\n return delta\r\n }, new Delta().retain(range.index).delete(range.length))\r\n\r\n this.quill.updateContents(updateDelta, Quill.sources.USER)\r\n this.quill.setSelection(range.index + result.length, Quill.sources.SILENT)\r\n\r\n for (const file of fails) {\r\n this.options.fail.call(this, file, range)\r\n }\r\n\r\n for (const [i, res] of result.entries()) {\r\n if (isString(res)) {\r\n this.options.success.call(this, files[i], { index: range.index + i, length: 0 })\r\n }\r\n else {\r\n this.options.fail.call(this, files[i], { index: range.index + i, length: 0 })\r\n }\r\n }\r\n }\r\n}\r\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"custom-uploader.es.js","sources":["../../../src/modules/custom-uploader.ts"],"sourcesContent":["import type { Range } from 'quill'\r\nimport type TypeUploader from 'quill/modules/uploader'\r\nimport type FluentEditor from '../core/fluent-editor'\r\nimport Quill from 'quill'\r\nimport { isString } from '../utils/is'\r\n\r\nconst Uploader = Quill.import('modules/uploader') as typeof TypeUploader\r\nconst Delta = Quill.import('delta')\r\n\r\nexport type UploadKind = 'image' | 'video' | 'file'\r\nexport type MimeTypesConfig = string[] | Partial<Record<UploadKind, string[]>>\r\nexport type MaxSizeConfig = number | Partial<Record<UploadKind, number>>\r\nexport type MultipleConfig = boolean | Partial<Record<UploadKind, boolean>>\r\n\r\ninterface UploaderOptions {\r\n mimetypes: string[]\r\n handler: (this: { quill: Quill }, range: Range, files: File[]) => void\r\n}\r\nexport interface FileUploaderOptions {\r\n /**\r\n * 支持单个 MIME type、模糊匹配子类型和后缀名三种格式\r\n * - 全局配置:`string[]`\r\n * - 根据文件/图片/视频单独配置:`{ file?: string[]; image?: string[]; video?: string[] }`\r\n */\r\n mimetypes: MimeTypesConfig\r\n /**\r\n * 最大文件大小限制(字节)\r\n * - 全局配置:`number`\r\n * - 根据文件/图片/视频单独配置:`{ file?: number; image?: number; video?: number }`\r\n */\r\n maxSize: MaxSizeConfig\r\n /**\r\n * 是否允许多选文件\r\n * - 全局配置:`boolean`\r\n * - 根据文件/图片/视频单独配置:`{ file?: boolean; image?: boolean; video?: boolean }`\r\n */\r\n multiple: MultipleConfig\r\n handler: (this: { quill: FluentEditor }, range: Range, files: File[]) => Promise<(string | false)[]> | (string | false)[]\r\n success: (this: { quill: FluentEditor }, file: File, range: Range) => void\r\n fail: (this: { quill: FluentEditor }, file: File, range: Range) => void\r\n}\r\nexport class FileUploader extends Uploader {\r\n static DEFAULTS = {} as any\r\n // Partial<UploaderOptions> for ts type\r\n declare options: Partial<UploaderOptions> & FileUploaderOptions\r\n constructor(public quill: FluentEditor, options: Partial<FileUploaderOptions>) {\r\n super(quill, options as any)\r\n this.options = this.resolveOptions(options)\r\n // paste handle in clipboard\r\n }\r\n\r\n resolveOptions(options: Partial<FileUploaderOptions> = {}) {\r\n return Object.assign({\r\n mimetypes: ['*'],\r\n maxSize: Number.POSITIVE_INFINITY,\r\n multiple: true,\r\n handler(range: Range, files: File[]) {\r\n return files.map(file => URL.createObjectURL(file))\r\n },\r\n success() {},\r\n fail() {},\r\n }, options)\r\n }\r\n\r\n private inferKind(file: File): UploadKind {\r\n const type = file.type || ''\r\n if (type.startsWith('image/')) return 'image'\r\n if (type.startsWith('video/')) return 'video'\r\n return 'file'\r\n }\r\n\r\n private filterFromArray(types: string[], kind: UploadKind): string[] {\r\n if (kind === 'file') return types\r\n const prefix = `${kind}/`\r\n return types.filter((type) => {\r\n if (type === '*') return true\r\n if (type.includes('/')) return type.startsWith(prefix)\r\n return true\r\n })\r\n }\r\n\r\n getAccept(kind: UploadKind): string[] {\r\n const mimetypes = this.options.mimetypes\r\n if (Array.isArray(mimetypes)) {\r\n return this.filterFromArray(mimetypes, kind)\r\n }\r\n const map = (mimetypes || {}) as any\r\n const fromKind = map[kind]\r\n if (fromKind?.length) return fromKind as string[]\r\n if (map.file?.length && kind !== 'file') return map.file as string[]\r\n return []\r\n }\r\n\r\n getMaxSize(kind: UploadKind): number {\r\n const maxSize = this.options.maxSize\r\n if (typeof maxSize === 'number') {\r\n return maxSize\r\n }\r\n const map = (maxSize || {}) as any\r\n const fromKind = map[kind]\r\n if (typeof fromKind === 'number') return fromKind\r\n if (typeof map.file === 'number' && kind !== 'file') return map.file\r\n return Number.POSITIVE_INFINITY\r\n }\r\n\r\n getMultiple(kind: UploadKind): boolean {\r\n const multiple = this.options.multiple\r\n if (typeof multiple === 'boolean') {\r\n return multiple\r\n }\r\n const map = (multiple || {}) as any\r\n const fromKind = map[kind]\r\n if (typeof fromKind === 'boolean') return fromKind\r\n if (typeof map.file === 'boolean' && kind !== 'file') return map.file\r\n return true\r\n }\r\n\r\n validateFile(file: File, kind?: UploadKind) {\r\n const inferredKind = kind ?? this.inferKind(file)\r\n const accept = this.getAccept(inferredKind)\r\n const maxSize = this.getMaxSize(inferredKind)\r\n const mimeOk = accept.some((type) => {\r\n // 简单区分:带 '/' 的按 MIME,其他按后缀\r\n if (type.includes('/')) {\r\n return (file.type || 'text/plain').match(type.replaceAll('*', '.*'))\r\n }\r\n else {\r\n // 按文件名后缀匹配,例如 'png' / '.png'\r\n const ext = type.startsWith('.') ? type.toLowerCase() : `.${type.toLowerCase()}`\r\n return file.name.toLowerCase().endsWith(ext)\r\n }\r\n })\r\n return mimeOk && file.size < maxSize\r\n }\r\n\r\n async getFileUrls(files: File[], range: Range, kind?: UploadKind) {\r\n const uploads = files.filter(file => this.validateFile(file, kind))\r\n return this.options.handler.call(this, range, uploads)\r\n }\r\n\r\n async upload(range: Range, files: FileList | File[], kind?: UploadKind) {\r\n const uploads = []\r\n const fails = []\r\n\r\n for (const file of Array.from(files)) {\r\n if (this.validateFile(file, kind)) {\r\n uploads.push(file)\r\n }\r\n else {\r\n fails.push(file)\r\n }\r\n }\r\n\r\n const result = await this.options.handler.call(this, range, uploads)\r\n const updateDelta = result.reduce((delta, url, i) => {\r\n if (isString(url)) {\r\n const type = uploads[i].type\r\n if (type.startsWith('image/')) {\r\n delta.insert({ image: url })\r\n }\r\n else if (type.startsWith('video/')) {\r\n delta.insert({ video: { src: url } })\r\n }\r\n else {\r\n delta.insert({ file: { size: uploads[i].size, title: uploads[i].name, src: url } })\r\n }\r\n }\r\n else {\r\n delta.insert(' ')\r\n }\r\n return delta\r\n }, new Delta().retain(range.index).delete(range.length))\r\n\r\n this.quill.updateContents(updateDelta, Quill.sources.USER)\r\n this.quill.setSelection(range.index + result.length, Quill.sources.SILENT)\r\n\r\n for (const file of fails) {\r\n this.options.fail.call(this, file, range)\r\n }\r\n\r\n for (const [i, res] of result.entries()) {\r\n if (isString(res)) {\r\n this.options.success.call(this, files[i], { index: range.index + i, length: 0 })\r\n }\r\n else {\r\n this.options.fail.call(this, files[i], { index: range.index + i, length: 0 })\r\n }\r\n }\r\n }\r\n}\r\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-module.es.js","sources":["../../../../../src/modules/file/modules/file-module.ts"],"sourcesContent":["import type FluentEditor from '../../../core/fluent-editor'\nimport Quill from 'quill'\nimport { File } from '../formats/file'\nimport { FileBar } from './file-bar'\n\nexport class FileModule {\n fileBar: FileBar\n\n static register() {\n Quill.register('formats/file', File, true)\n }\n\n constructor(public quill: FluentEditor) {\n this.quill = quill\n quill.root.addEventListener('click', event => this.clickEvent(event), false)\n }\n\n clickEvent(event: MouseEvent) {\n const target = event.target as HTMLElement\n const fileDom = target.closest('a.ql-file-item')\n const fileBar = target.closest('.ql-file-bar')\n\n if (fileDom) {\n event.preventDefault()\n // 在只读模式下直接下载文件\n if (!this.quill.isEnabled()) {\n this.downloadFile(fileDom)\n return\n }\n if (this.fileBar) {\n this.fileBar.destroy()\n }\n this.fileBar = new FileBar(this.quill, fileDom)\n }\n else if (this.fileBar && !fileBar) {\n event.preventDefault()\n this.fileBar.destroy()\n this.fileBar = null\n }\n }\n\n downloadFile(fileDom: HTMLElement) {\n const fileName = fileDom.dataset.title || ''\n const fileDownloadUrl = fileDom.getAttribute('href') || ''\n if (fileDownloadUrl) {\n const a = document.createElement('a')\n a.href = fileDownloadUrl\n a.target = '_blank'\n a.id = 'exppub'\n a.download = fileName\n document.body.appendChild(a)\n const alink = document.getElementById('exppub')\n alink?.click()\n alink?.parentNode?.removeChild(a)\n }\n }\n}\n"],"names":[],"mappings":";;;;;;AAKO,MAAM,WAAW;AAAA,EAOtB,YAAmB,OAAqB;AANxC;AAMmB,SAAA,QAAA;AACjB,SAAK,QAAQ;AACb,UAAM,KAAK,iBAAiB,SAAS,CAAA,UAAS,KAAK,WAAW,KAAK,GAAG,KAAK;AAAA,EAC7E;AAAA,EAPA,OAAO,WAAW;AAChB,UAAM,SAAS,gBAAgB,MAAM,IAAI;AAAA,EAC3C;AAAA,EAOA,WAAW,OAAmB;AAC5B,UAAM,SAAS,MAAM;AACrB,UAAM,UAAU,OAAO,QAAQ,gBAAgB;AAC/C,UAAM,UAAU,OAAO,QAAQ,cAAc;AAE7C,QAAI,SAAS;AACX,YAAM,eAAA;AAEN,UAAI,CAAC,KAAK,MAAM,aAAa;AAC3B,aAAK,aAAa,
|
|
1
|
+
{"version":3,"file":"file-module.es.js","sources":["../../../../../src/modules/file/modules/file-module.ts"],"sourcesContent":["import type FluentEditor from '../../../core/fluent-editor'\nimport Quill from 'quill'\nimport { File } from '../formats/file'\nimport { FileBar } from './file-bar'\n\nexport class FileModule {\n fileBar: FileBar\n\n static register() {\n Quill.register('formats/file', File, true)\n }\n\n constructor(public quill: FluentEditor) {\n this.quill = quill\n quill.root.addEventListener('click', event => this.clickEvent(event), false)\n }\n\n clickEvent(event: MouseEvent) {\n const target = event.target as HTMLElement\n const fileDom = target.closest('a.ql-file-item')\n const fileBar = target.closest('.ql-file-bar')\n\n if (fileDom) {\n event.preventDefault()\n // 在只读模式下直接下载文件\n if (!this.quill.isEnabled()) {\n this.downloadFile(fileDom as HTMLElement)\n return\n }\n if (this.fileBar) {\n this.fileBar.destroy()\n }\n this.fileBar = new FileBar(this.quill, fileDom)\n }\n else if (this.fileBar && !fileBar) {\n event.preventDefault()\n this.fileBar.destroy()\n this.fileBar = null\n }\n }\n\n downloadFile(fileDom: HTMLElement) {\n const fileName = fileDom.dataset.title || ''\n const fileDownloadUrl = fileDom.getAttribute('href') || ''\n if (fileDownloadUrl) {\n const a = document.createElement('a')\n a.href = fileDownloadUrl\n a.target = '_blank'\n a.id = 'exppub'\n a.download = fileName\n document.body.appendChild(a)\n const alink = document.getElementById('exppub')\n alink?.click()\n alink?.parentNode?.removeChild(a)\n }\n }\n}\n"],"names":[],"mappings":";;;;;;AAKO,MAAM,WAAW;AAAA,EAOtB,YAAmB,OAAqB;AANxC;AAMmB,SAAA,QAAA;AACjB,SAAK,QAAQ;AACb,UAAM,KAAK,iBAAiB,SAAS,CAAA,UAAS,KAAK,WAAW,KAAK,GAAG,KAAK;AAAA,EAC7E;AAAA,EAPA,OAAO,WAAW;AAChB,UAAM,SAAS,gBAAgB,MAAM,IAAI;AAAA,EAC3C;AAAA,EAOA,WAAW,OAAmB;AAC5B,UAAM,SAAS,MAAM;AACrB,UAAM,UAAU,OAAO,QAAQ,gBAAgB;AAC/C,UAAM,UAAU,OAAO,QAAQ,cAAc;AAE7C,QAAI,SAAS;AACX,YAAM,eAAA;AAEN,UAAI,CAAC,KAAK,MAAM,aAAa;AAC3B,aAAK,aAAa,OAAsB;AACxC;AAAA,MACF;AACA,UAAI,KAAK,SAAS;AAChB,aAAK,QAAQ,QAAA;AAAA,MACf;AACA,WAAK,UAAU,IAAI,QAAQ,KAAK,OAAO,OAAO;AAAA,IAChD,WACS,KAAK,WAAW,CAAC,SAAS;AACjC,YAAM,eAAA;AACN,WAAK,QAAQ,QAAA;AACb,WAAK,UAAU;AAAA,IACjB;AAAA,EACF;AAAA,EAEA,aAAa,SAAsB;;AACjC,UAAM,WAAW,QAAQ,QAAQ,SAAS;AAC1C,UAAM,kBAAkB,QAAQ,aAAa,MAAM,KAAK;AACxD,QAAI,iBAAiB;AACnB,YAAM,IAAI,SAAS,cAAc,GAAG;AACpC,QAAE,OAAO;AACT,QAAE,SAAS;AACX,QAAE,KAAK;AACP,QAAE,WAAW;AACb,eAAS,KAAK,YAAY,CAAC;AAC3B,YAAM,QAAQ,SAAS,eAAe,QAAQ;AAC9C,qCAAO;AACP,2CAAO,eAAP,mBAAmB,YAAY;AAAA,IACjC;AAAA,EACF;AACF;"}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { FLOW_CHART_EN_US } from "./en-us.es.js";
|
|
2
2
|
import { FLOW_CHART_ZH_CN } from "./zh-cn.es.js";
|
|
3
|
-
function registerFlowChartI18N(
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
"zh-CN": FLOW_CHART_ZH_CN
|
|
7
|
-
}, false);
|
|
3
|
+
function registerFlowChartI18N(i18nModule) {
|
|
4
|
+
i18nModule.addMessages("en-US", FLOW_CHART_EN_US);
|
|
5
|
+
i18nModule.addMessages("zh-CN", FLOW_CHART_ZH_CN);
|
|
8
6
|
}
|
|
9
7
|
export {
|
|
10
8
|
registerFlowChartI18N
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.es.js","sources":["../../../../../src/modules/flow-chart/i18n/index.ts"],"sourcesContent":["import { FLOW_CHART_EN_US } from './en-us'\nimport { FLOW_CHART_ZH_CN } from './zh-cn'\n\nexport function registerFlowChartI18N(
|
|
1
|
+
{"version":3,"file":"index.es.js","sources":["../../../../../src/modules/flow-chart/i18n/index.ts"],"sourcesContent":["import { FLOW_CHART_EN_US } from './en-us'\nimport { FLOW_CHART_ZH_CN } from './zh-cn'\n\nexport function registerFlowChartI18N(i18nModule: any) {\n i18nModule.addMessages('en-US', FLOW_CHART_EN_US)\n i18nModule.addMessages('zh-CN', FLOW_CHART_ZH_CN)\n}\n"],"names":[],"mappings":";;AAGO,SAAS,sBAAsB,YAAiB;AACrD,aAAW,YAAY,SAAS,gBAAgB;AAChD,aAAW,YAAY,SAAS,gBAAgB;AAClD;"}
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
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
|
-
import "../../../config/index.es.js";
|
|
5
|
-
import { I18N } from "../../i18n.es.js";
|
|
6
4
|
import { registerFlowChartI18N } from "../i18n/index.es.js";
|
|
7
|
-
import { CHANGE_LANGUAGE_EVENT } from "../../../config/editor.config.es.js";
|
|
8
5
|
class FlowChartContextMenuHandler {
|
|
9
6
|
constructor(quill, blot) {
|
|
10
7
|
__publicField(this, "texts");
|
|
@@ -12,11 +9,11 @@ class FlowChartContextMenuHandler {
|
|
|
12
9
|
this.quill = quill;
|
|
13
10
|
this.blot = blot;
|
|
14
11
|
const i18nModule = this.quill.getModule("i18n");
|
|
15
|
-
registerFlowChartI18N(
|
|
16
|
-
this.lang = i18nModule.
|
|
12
|
+
registerFlowChartI18N(i18nModule);
|
|
13
|
+
this.lang = i18nModule.getLocale();
|
|
17
14
|
this.texts = this.resolveTexts();
|
|
18
|
-
this.quill.
|
|
19
|
-
this.lang =
|
|
15
|
+
this.quill.on("i18n-locale-change", (event) => {
|
|
16
|
+
this.lang = event.locale;
|
|
20
17
|
this.texts = this.resolveTexts();
|
|
21
18
|
this.updateContextMenuItems();
|
|
22
19
|
});
|
|
@@ -26,12 +23,12 @@ class FlowChartContextMenuHandler {
|
|
|
26
23
|
}
|
|
27
24
|
resolveTexts() {
|
|
28
25
|
return {
|
|
29
|
-
copy:
|
|
30
|
-
cut:
|
|
31
|
-
paste:
|
|
32
|
-
deleteContent:
|
|
33
|
-
deleteNode:
|
|
34
|
-
deleteEdge:
|
|
26
|
+
copy: this.quill.getLangText("flowChart.contextMenu.copy"),
|
|
27
|
+
cut: this.quill.getLangText("flowChart.contextMenu.cut"),
|
|
28
|
+
paste: this.quill.getLangText("flowChart.contextMenu.paste"),
|
|
29
|
+
deleteContent: this.quill.getLangText("flowChart.contextMenu.deleteContent"),
|
|
30
|
+
deleteNode: this.quill.getLangText("flowChart.contextMenu.deleteNode"),
|
|
31
|
+
deleteEdge: this.quill.getLangText("flowChart.contextMenu.deleteEdge")
|
|
35
32
|
};
|
|
36
33
|
}
|
|
37
34
|
updateContextMenuItems() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-menu.es.js","sources":["../../../../../src/modules/flow-chart/modules/context-menu.ts"],"sourcesContent":["import type FluentEditor from '../../../core/fluent-editor'\nimport type FlowChartPlaceholderBlot from '../formats/flow-chart-blot'\nimport { CHANGE_LANGUAGE_EVENT } from '../../../config'\nimport { I18N } from '../../../modules/i18n'\nimport { registerFlowChartI18N } from '../i18n/index'\n\nclass FlowChartContextMenuHandler {\n private texts: Record<string, string>\n private lang: string\n getText(key: keyof Record<string, string>): string {\n return this.texts[key]\n }\n\n constructor(private quill: FluentEditor, private blot: FlowChartPlaceholderBlot) {\n const i18nModule = this.quill.getModule('i18n') as I18N\n registerFlowChartI18N(I18N)\n this.lang = i18nModule.options.lang\n this.texts = this.resolveTexts()\n this.quill.emitter.on(CHANGE_LANGUAGE_EVENT, (lang: string) => {\n this.lang = lang\n this.texts = this.resolveTexts()\n this.updateContextMenuItems()\n })\n }\n\n resolveTexts() {\n return {\n copy: I18N.parserText('flowChart.contextMenu.copy', this.lang),\n cut: I18N.parserText('flowChart.contextMenu.cut', this.lang),\n paste: I18N.parserText('flowChart.contextMenu.paste', this.lang),\n deleteContent: I18N.parserText('flowChart.contextMenu.deleteContent', this.lang),\n deleteNode: I18N.parserText('flowChart.contextMenu.deleteNode', this.lang),\n deleteEdge: I18N.parserText('flowChart.contextMenu.deleteEdge', this.lang),\n }\n }\n\n updateContextMenuItems() {\n if (!this.blot.contextMenu) return\n\n const menuItems = this.blot.contextMenu.querySelectorAll('.ql-flow-chart-context-menu-item')\n if (menuItems.length > 0) {\n Array.from(menuItems).forEach((item) => {\n const text = item.getAttribute('data-text-key')\n if (text && this.texts[text as keyof Record<string, string>]) {\n item.textContent = this.texts[text as keyof Record<string, string>]\n }\n })\n }\n }\n}\n\nconst contextMenuHandlers = new WeakMap<FlowChartPlaceholderBlot, FlowChartContextMenuHandler>()\n\nexport function initContextMenu(blot: FlowChartPlaceholderBlot, quill: FluentEditor): void {\n blot.contextMenu = document.createElement('div')\n blot.contextMenu.className = 'ql-flow-chart-context-menu'\n blot.contextMenu.style.position = 'fixed'\n blot.contextMenu.style.background = 'white'\n blot.contextMenu.style.borderRadius = '4px'\n blot.contextMenu.style.boxShadow = '0 2px 10px rgba(0,0,0,0.1)'\n blot.contextMenu.style.padding = '5px 0'\n blot.contextMenu.style.zIndex = '1000'\n blot.contextMenu.style.display = 'block'\n blot.contextMenu.style.visibility = 'visible'\n blot.contextMenu.style.opacity = '1'\n blot.contextMenu.style.width = '120px'\n blot.contextMenu.style.height = 'auto'\n blot.domNode.appendChild(blot.contextMenu)\n\n const handler = new FlowChartContextMenuHandler(quill, blot)\n contextMenuHandlers.set(blot, handler)\n\n if (blot.flowChart) {\n blot.flowChart.on('node:contextmenu', (event: any) => {\n const { data, e } = event\n e.preventDefault()\n e.stopPropagation()\n blot.currentElement = { type: 'node', data }\n showNodeContextMenu(blot, handler, e.clientX, e.clientY)\n })\n\n blot.flowChart.on('edge:contextmenu', (event: any) => {\n const { data, e } = event\n e.preventDefault()\n e.stopPropagation()\n blot.currentElement = { type: 'edge', data }\n showEdgeContextMenu(blot, handler, e.clientX, e.clientY)\n })\n }\n\n const hideMenu = () => {\n if (blot.contextMenu) {\n blot.contextMenu.style.display = 'none'\n blot.currentElement = null\n }\n }\n\n blot.flowChart.on('blank:click', hideMenu)\n document.addEventListener('click', (e) => {\n if (blot.contextMenu && !blot.contextMenu.contains(e.target as Node)) {\n hideMenu()\n }\n })\n}\n\nfunction showNodeContextMenu(blot: FlowChartPlaceholderBlot, handler: FlowChartContextMenuHandler, x: number, y: number): void {\n clearContextMenu(blot)\n\n addContextMenuItem(blot, handler.getText('copy'), 'copy', () => handleCopy(blot))\n addContextMenuItem(blot, handler.getText('deleteContent'), 'deleteContent', () => handleDeleteContent(blot))\n addContextMenuItem(blot, handler.getText('deleteNode'), 'deleteNode', () => handleDeleteNode(blot))\n\n showContextMenu(blot, x, y)\n}\n\nfunction showEdgeContextMenu(blot: FlowChartPlaceholderBlot, handler: FlowChartContextMenuHandler, x: number, y: number): void {\n clearContextMenu(blot)\n\n addContextMenuItem(blot, handler.getText('deleteContent'), 'deleteContent', () => handleDeleteContent(blot))\n addContextMenuItem(blot, handler.getText('deleteEdge'), 'deleteEdge', () => handleDeleteEdge(blot))\n\n showContextMenu(blot, x, y)\n}\n\nfunction clearContextMenu(blot: FlowChartPlaceholderBlot): void {\n if (blot.contextMenu) {\n blot.contextMenu.innerHTML = ''\n }\n}\n\nfunction showContextMenu(blot: FlowChartPlaceholderBlot, x: number, y: number): void {\n if (blot.contextMenu) {\n blot.contextMenu.style.display = 'block'\n blot.contextMenu.style.left = `${x}px`\n blot.contextMenu.style.top = `${y}px`\n }\n}\n\nfunction addContextMenuItem(blot: FlowChartPlaceholderBlot, text: string, textKey: string, onClick: () => void): void {\n const item = document.createElement('div')\n item.className = 'ql-flow-chart-context-menu-item'\n item.textContent = text\n item.setAttribute('data-text-key', textKey)\n item.style.padding = '5px 15px'\n item.style.cursor = 'pointer'\n item.style.whiteSpace = 'nowrap'\n item.addEventListener('click', onClick)\n item.addEventListener('mouseenter', () => {\n item.style.background = '#f5f5f5'\n })\n item.addEventListener('mouseleave', () => {\n item.style.background = 'white'\n })\n blot.contextMenu!.appendChild(item)\n}\n\nfunction handleCopy(blot: FlowChartPlaceholderBlot): void {\n if (blot.currentElement && blot.currentElement.type === 'node') {\n const nodeId = blot.currentElement.data.id\n blot.flowChart.cloneNode(nodeId)\n }\n hideContextMenu(blot)\n}\n\nfunction handleDeleteContent(blot: FlowChartPlaceholderBlot): void {\n if (blot.currentElement) {\n if (blot.currentElement.type === 'node') {\n const node = blot.currentElement.data\n blot.flowChart.updateText(node.id, '')\n }\n else if (blot.currentElement.type === 'edge') {\n const edge = blot.currentElement.data\n blot.flowChart.updateText(edge.id, '')\n }\n blot.data = blot.flowChart.getGraphData()\n blot.domNode.setAttribute('data-flow-chart', JSON.stringify(blot.data))\n }\n hideContextMenu(blot)\n}\n\nfunction handleDeleteNode(blot: FlowChartPlaceholderBlot): void {\n if (blot.currentElement && blot.currentElement.type === 'node') {\n blot.flowChart.deleteNode(blot.currentElement.data.id)\n blot.data = blot.flowChart.getGraphData()\n blot.domNode.setAttribute('data-flow-chart', JSON.stringify(blot.data))\n }\n hideContextMenu(blot)\n}\n\nfunction handleDeleteEdge(blot: FlowChartPlaceholderBlot): void {\n if (blot.currentElement && blot.currentElement.type === 'edge') {\n blot.flowChart.deleteEdge(blot.currentElement.data.id)\n blot.data = blot.flowChart.getGraphData()\n blot.domNode.setAttribute('data-flow-chart', JSON.stringify(blot.data))\n }\n hideContextMenu(blot)\n}\n\nfunction hideContextMenu(blot: FlowChartPlaceholderBlot): void {\n if (blot.contextMenu) {\n blot.contextMenu.style.display = 'none'\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"context-menu.es.js","sources":["../../../../../src/modules/flow-chart/modules/context-menu.ts"],"sourcesContent":["import type { I18n } from 'quill-i18n'\nimport type FluentEditor from '../../../core/fluent-editor'\nimport type FlowChartPlaceholderBlot from '../formats/flow-chart-blot'\nimport { registerFlowChartI18N } from '../i18n/index'\n\nclass FlowChartContextMenuHandler {\n private texts: Record<string, string>\n private lang: string\n getText(key: keyof Record<string, string>): string {\n return this.texts[key]\n }\n\n constructor(private quill: FluentEditor, private blot: FlowChartPlaceholderBlot) {\n const i18nModule = this.quill.getModule('i18n') as I18n\n registerFlowChartI18N(i18nModule)\n this.lang = i18nModule.getLocale()\n this.texts = this.resolveTexts()\n this.quill.on('i18n-locale-change', (event: { locale: string, oldLocale: string }) => {\n this.lang = event.locale\n this.texts = this.resolveTexts()\n this.updateContextMenuItems()\n })\n }\n\n resolveTexts() {\n return {\n copy: this.quill.getLangText('flowChart.contextMenu.copy'),\n cut: this.quill.getLangText('flowChart.contextMenu.cut'),\n paste: this.quill.getLangText('flowChart.contextMenu.paste'),\n deleteContent: this.quill.getLangText('flowChart.contextMenu.deleteContent'),\n deleteNode: this.quill.getLangText('flowChart.contextMenu.deleteNode'),\n deleteEdge: this.quill.getLangText('flowChart.contextMenu.deleteEdge'),\n }\n }\n\n updateContextMenuItems() {\n if (!this.blot.contextMenu) return\n\n const menuItems = this.blot.contextMenu.querySelectorAll('.ql-flow-chart-context-menu-item')\n if (menuItems.length > 0) {\n Array.from(menuItems).forEach((item) => {\n const text = item.getAttribute('data-text-key')\n if (text && this.texts[text as keyof Record<string, string>]) {\n item.textContent = this.texts[text as keyof Record<string, string>]\n }\n })\n }\n }\n}\n\nconst contextMenuHandlers = new WeakMap<FlowChartPlaceholderBlot, FlowChartContextMenuHandler>()\n\nexport function initContextMenu(blot: FlowChartPlaceholderBlot, quill: FluentEditor): void {\n blot.contextMenu = document.createElement('div')\n blot.contextMenu.className = 'ql-flow-chart-context-menu'\n blot.contextMenu.style.position = 'fixed'\n blot.contextMenu.style.background = 'white'\n blot.contextMenu.style.borderRadius = '4px'\n blot.contextMenu.style.boxShadow = '0 2px 10px rgba(0,0,0,0.1)'\n blot.contextMenu.style.padding = '5px 0'\n blot.contextMenu.style.zIndex = '1000'\n blot.contextMenu.style.display = 'block'\n blot.contextMenu.style.visibility = 'visible'\n blot.contextMenu.style.opacity = '1'\n blot.contextMenu.style.width = '120px'\n blot.contextMenu.style.height = 'auto'\n blot.domNode.appendChild(blot.contextMenu)\n\n const handler = new FlowChartContextMenuHandler(quill, blot)\n contextMenuHandlers.set(blot, handler)\n\n if (blot.flowChart) {\n blot.flowChart.on('node:contextmenu', (event: any) => {\n const { data, e } = event\n e.preventDefault()\n e.stopPropagation()\n blot.currentElement = { type: 'node', data }\n showNodeContextMenu(blot, handler, e.clientX, e.clientY)\n })\n\n blot.flowChart.on('edge:contextmenu', (event: any) => {\n const { data, e } = event\n e.preventDefault()\n e.stopPropagation()\n blot.currentElement = { type: 'edge', data }\n showEdgeContextMenu(blot, handler, e.clientX, e.clientY)\n })\n }\n\n const hideMenu = () => {\n if (blot.contextMenu) {\n blot.contextMenu.style.display = 'none'\n blot.currentElement = null\n }\n }\n\n blot.flowChart.on('blank:click', hideMenu)\n document.addEventListener('click', (e) => {\n if (blot.contextMenu && !blot.contextMenu.contains(e.target as Node)) {\n hideMenu()\n }\n })\n}\n\nfunction showNodeContextMenu(blot: FlowChartPlaceholderBlot, handler: FlowChartContextMenuHandler, x: number, y: number): void {\n clearContextMenu(blot)\n\n addContextMenuItem(blot, handler.getText('copy'), 'copy', () => handleCopy(blot))\n addContextMenuItem(blot, handler.getText('deleteContent'), 'deleteContent', () => handleDeleteContent(blot))\n addContextMenuItem(blot, handler.getText('deleteNode'), 'deleteNode', () => handleDeleteNode(blot))\n\n showContextMenu(blot, x, y)\n}\n\nfunction showEdgeContextMenu(blot: FlowChartPlaceholderBlot, handler: FlowChartContextMenuHandler, x: number, y: number): void {\n clearContextMenu(blot)\n\n addContextMenuItem(blot, handler.getText('deleteContent'), 'deleteContent', () => handleDeleteContent(blot))\n addContextMenuItem(blot, handler.getText('deleteEdge'), 'deleteEdge', () => handleDeleteEdge(blot))\n\n showContextMenu(blot, x, y)\n}\n\nfunction clearContextMenu(blot: FlowChartPlaceholderBlot): void {\n if (blot.contextMenu) {\n blot.contextMenu.innerHTML = ''\n }\n}\n\nfunction showContextMenu(blot: FlowChartPlaceholderBlot, x: number, y: number): void {\n if (blot.contextMenu) {\n blot.contextMenu.style.display = 'block'\n blot.contextMenu.style.left = `${x}px`\n blot.contextMenu.style.top = `${y}px`\n }\n}\n\nfunction addContextMenuItem(blot: FlowChartPlaceholderBlot, text: string, textKey: string, onClick: () => void): void {\n const item = document.createElement('div')\n item.className = 'ql-flow-chart-context-menu-item'\n item.textContent = text\n item.setAttribute('data-text-key', textKey)\n item.style.padding = '5px 15px'\n item.style.cursor = 'pointer'\n item.style.whiteSpace = 'nowrap'\n item.addEventListener('click', onClick)\n item.addEventListener('mouseenter', () => {\n item.style.background = '#f5f5f5'\n })\n item.addEventListener('mouseleave', () => {\n item.style.background = 'white'\n })\n blot.contextMenu!.appendChild(item)\n}\n\nfunction handleCopy(blot: FlowChartPlaceholderBlot): void {\n if (blot.currentElement && blot.currentElement.type === 'node') {\n const nodeId = blot.currentElement.data.id\n blot.flowChart.cloneNode(nodeId)\n }\n hideContextMenu(blot)\n}\n\nfunction handleDeleteContent(blot: FlowChartPlaceholderBlot): void {\n if (blot.currentElement) {\n if (blot.currentElement.type === 'node') {\n const node = blot.currentElement.data\n blot.flowChart.updateText(node.id, '')\n }\n else if (blot.currentElement.type === 'edge') {\n const edge = blot.currentElement.data\n blot.flowChart.updateText(edge.id, '')\n }\n blot.data = blot.flowChart.getGraphData()\n blot.domNode.setAttribute('data-flow-chart', JSON.stringify(blot.data))\n }\n hideContextMenu(blot)\n}\n\nfunction handleDeleteNode(blot: FlowChartPlaceholderBlot): void {\n if (blot.currentElement && blot.currentElement.type === 'node') {\n blot.flowChart.deleteNode(blot.currentElement.data.id)\n blot.data = blot.flowChart.getGraphData()\n blot.domNode.setAttribute('data-flow-chart', JSON.stringify(blot.data))\n }\n hideContextMenu(blot)\n}\n\nfunction handleDeleteEdge(blot: FlowChartPlaceholderBlot): void {\n if (blot.currentElement && blot.currentElement.type === 'edge') {\n blot.flowChart.deleteEdge(blot.currentElement.data.id)\n blot.data = blot.flowChart.getGraphData()\n blot.domNode.setAttribute('data-flow-chart', JSON.stringify(blot.data))\n }\n hideContextMenu(blot)\n}\n\nfunction hideContextMenu(blot: FlowChartPlaceholderBlot): void {\n if (blot.contextMenu) {\n blot.contextMenu.style.display = 'none'\n }\n}\n"],"names":[],"mappings":"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"}
|