UNPKG

kedao

Version:

Rich Text Editor Based On Draft.js

374 lines (373 loc) 15.7 kB
import { classNameParser } from '../../utils/style'; import React, { useState, useRef } from 'react'; import { setMediaData, setMediaPosition } from '../../utils'; import Switch from '../../components/Switch'; import styles from "./style.module.css"; import MeidaToolbar from '../../components/MediaToolbar'; import useLanguage from '../../hooks/use-language'; import { tablerIconProps } from '../../constants'; import AlignCenterIcon from 'tabler-icons-react/dist/icons/align-center'; import AlignLeftIcon from 'tabler-icons-react/dist/icons/align-left'; import AlignRightIcon from 'tabler-icons-react/dist/icons/align-right'; import FloatLeftIcon from 'tabler-icons-react/dist/icons/float-left'; import FloatRightIcon from 'tabler-icons-react/dist/icons/float-right'; import LinkIcon from 'tabler-icons-react/dist/icons/link'; import ResizeIcon from 'tabler-icons-react/dist/icons/resize'; import TrashIcon from 'tabler-icons-react/dist/icons/trash'; const cls = classNameParser(styles); const imageControlItems = { 'float-left': { text: React.createElement(FloatLeftIcon, Object.assign({}, tablerIconProps)), command: 'setImageFloat|left' }, 'float-right': { text: React.createElement(FloatRightIcon, Object.assign({}, tablerIconProps)), command: 'setImageFloat|right' }, 'align-left': { text: React.createElement(AlignLeftIcon, Object.assign({}, tablerIconProps)), command: 'setImageAlignment|left' }, 'align-center': { text: React.createElement(AlignCenterIcon, Object.assign({}, tablerIconProps)), command: 'setImageAlignment|center' }, 'align-right': { text: React.createElement(AlignRightIcon, Object.assign({}, tablerIconProps)), command: 'setImageAlignment|right' }, size: { text: React.createElement(ResizeIcon, Object.assign({}, tablerIconProps)), command: 'toggleSizeEditor' }, link: { text: React.createElement(LinkIcon, Object.assign({}, tablerIconProps)), command: 'toggleLinkEditor' }, remove: { text: React.createElement(TrashIcon, Object.assign({}, tablerIconProps)), command: 'removeImage' } }; const Image = ({ imageEqualRatio, getContainerNode, block, onRemove, entityKey, mediaData, readOnly, lock, imageControls, refresh, value, onChange, imageResizable }) => { const [toolbarVisible, setToolbarVisible] = useState(false); const [toolbarOffset, setToolbarOffset] = useState(0); const [linkEditorVisible, setLinkEditorVisible] = useState(false); const [sizeEditorVisible, setSizeEditorVisible] = useState(false); const [tempLink, setTempLink] = useState(null); const [tempWidth, setTempWidth] = useState(0); const [tempHeight, setTempHeight] = useState(0); const imageElement = useRef(null); const toolbarElement = useRef(null); const initialLeft = useRef(0); const initialTop = useRef(0); const initialWidth = useRef(0); const initialHeight = useRef(0); const reSizeType = useRef(undefined); const zoom = useRef(undefined); const changeSize = (e) => { const type = reSizeType.current; if (!initialLeft.current) { initialLeft.current = e.screenX; initialTop.current = e.screenY; } if (type === 'rightbottom') { initialHeight.current += e.screenY - initialTop.current; initialWidth.current += e.screenX - initialLeft.current; } if (type === 'leftbottom') { initialHeight.current += e.screenY - initialTop.current; initialWidth.current += -e.screenX + initialLeft.current; } initialLeft.current = e.screenX; initialTop.current = e.screenY; }; const moveImage = (e) => { changeSize(e); setTempWidth(Math.abs(initialWidth.current)); setTempHeight(Math.abs(initialHeight.current)); }; const upImage = () => { if (imageEqualRatio) { confirmImageSizeEqualRatio(); } else { confirmImageSize(); } document.removeEventListener('mousemove', moveImage); document.removeEventListener('mouseup', upImage); }; const repareChangeSize = (type) => (e) => { var _a; reSizeType.current = type; const imageRect = (_a = imageElement.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect(); initialTop.current = 0; initialLeft.current = 0; initialWidth.current = imageRect.width; initialHeight.current = imageRect.height; zoom.current = imageRect.width / imageRect.height; e.preventDefault(); document.addEventListener('mousemove', moveImage); document.addEventListener('mouseup', upImage); }; const lockEditor = () => { lock(true); }; const unlockEditor = () => { lock(false); }; const calcToolbarOffset = () => { var _a, _b, _c; const container = getContainerNode(); const viewRect = (_a = container === null || container === void 0 ? void 0 : container.querySelector('.kedao-content')) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect(); const toolbarRect = (_b = toolbarElement.current) === null || _b === void 0 ? void 0 : _b.getBoundingClientRect(); const imageRect = (_c = imageElement.current) === null || _c === void 0 ? void 0 : _c.getBoundingClientRect(); if (!container || !viewRect || !toolbarRect || !imageRect) { return 0; } const right = viewRect.right - (imageRect.right - imageRect.width / 2 + toolbarRect.width / 2); const left = imageRect.left + imageRect.width / 2 - toolbarRect.width / 2 - viewRect.left; if (right < 10) { return right - 10; } else if (left < 10) { return left * -1 + 10; } else { return 0; } }; const handleDragStart = () => { if (readOnly) { return false; } window.__KEDAO_DRAGING__IMAGE__ = { block: block, mediaData: Object.assign({ type: 'IMAGE' }, mediaData) }; setToolbarVisible(false); unlockEditor(); return true; }; const handleDragEnd = () => { window.__KEDAO_DRAGING__IMAGE__ = null; return false; }; const executeCommand = (command) => { var _a; const allCommands = { setImageFloat, setImageAlignment, toggleSizeEditor, toggleLinkEditor, removeImage }; if (typeof command === 'string') { const [method, param] = command.split('|'); (_a = allCommands[method]) === null || _a === void 0 ? void 0 : _a.call(allCommands, param); } else if (typeof command === 'function') { command(block, mediaData, value); } }; const removeImage = () => { onRemove(); unlockEditor(); }; const toggleLinkEditor = () => { setLinkEditorVisible((v) => !v); setSizeEditorVisible(false); }; const toggleSizeEditor = () => { setLinkEditorVisible(false); setSizeEditorVisible((v) => !v); }; const handleLinkInputKeyDown = (e) => { if (e.keyCode === 13) { confirmImageLink(); } }; const setImageLink = (e) => { setTempLink(e.currentTarget.value); }; const setImageLinkTarget = (linkTarget) => { const newLinkTarget = linkTarget === '_blank' ? '' : '_blank'; onChange(setMediaData(value, entityKey, { newLinkTarget })); refresh(); return true; }; const confirmImageLink = () => { const link = tempLink; if (link !== null) { onChange(setMediaData(value, entityKey, { link })); refresh(); } return true; }; const handleSizeInputKeyDown = (e) => { if (e.keyCode === 13) { confirmImageSize(); } }; const setImageWidth = ({ currentTarget }) => { let { value } = currentTarget; if (value && !isNaN(value)) { value += 'px'; } setTempWidth(value); }; const setImageHeight = ({ currentTarget }) => { let { value } = currentTarget; if (value && !isNaN(value)) { value += 'px'; } setTempHeight(value); }; const confirmImageSize = () => { const width = tempWidth; const height = tempHeight; const newImageSize = {}; if (width !== null) { newImageSize.width = width; } if (height !== null) { newImageSize.height = height; } onChange(setMediaData(value, entityKey, newImageSize)); refresh(); return true; }; const confirmImageSizeEqualRatio = () => { const width = tempWidth; const height = tempHeight; let equalWidth; let equalHeight; // 宽度过大 图片等比缩放 if (width / height > zoom.current) { equalWidth = Math.floor(height * zoom.current); setTempWidth(equalWidth); equalHeight = height; } else if (width / height < zoom.current) { equalHeight = Math.floor(width / zoom.current); setTempHeight(equalHeight); equalWidth = width; } const newImageSize = {}; if (equalWidth !== null) { newImageSize.width = equalWidth; } if (equalHeight !== null) { newImageSize.height = equalHeight; } onChange(setMediaData(value, entityKey, newImageSize)); refresh(); return true; }; const setImageFloat = (float) => { onChange(setMediaPosition(value, block, { float })); unlockEditor(); return true; }; const setImageAlignment = (alignment) => { onChange(setMediaPosition(value, block, { alignment })); unlockEditor(); return true; }; const showToolbar = (event) => { if (readOnly) { return false; } event.preventDefault(); if (!toolbarVisible) { setToolbarVisible(true); lockEditor(); setToolbarOffset(calcToolbarOffset()); } return true; }; const hideToolbar = (event) => { event.preventDefault(); setToolbarVisible(false); unlockEditor(); }; const blockData = block.getData(); const float = blockData.get('float'); let alignment = blockData.get('alignment'); const { url, link, linkTarget, width, height, meta } = mediaData; const imageStyles = {}; let clearFix = false; if (float) { alignment = null; } else if (alignment === 'left') { imageStyles.float = 'left'; clearFix = true; } else if (alignment === 'right') { imageStyles.float = 'right'; clearFix = true; } else if (alignment === 'center') { imageStyles.textAlign = 'center'; } else { imageStyles.float = 'left'; clearFix = true; } const renderedControlItems = imageControls.map((item) => { if (typeof item === 'string') { if (imageControlItems[item]) { return (React.createElement("a", { className: cls(item === 'link' && link ? 'active' : ''), role: "presentation", key: item, onClick: () => executeCommand(imageControlItems[item].command) }, imageControlItems[item].text)); } } else if (item && (item.render || item.text)) { return item.render ? (item.render(mediaData, block)) : (React.createElement("a", { key: item.text, role: "presentation", onClick: () => item.onClick && executeCommand(item.onClick) }, item.text)); } return null; }); const language = useLanguage(); return (React.createElement("div", { className: cls('kedao-media') }, React.createElement("div", { style: imageStyles, draggable: true, onMouseEnter: showToolbar, onMouseMove: showToolbar, onMouseLeave: hideToolbar, onDragStart: handleDragStart, onDragEnd: handleDragEnd, className: cls('kedao-image') }, toolbarVisible ? (React.createElement(MeidaToolbar, { style: { marginLeft: toolbarOffset }, ref: toolbarElement, "data-float": float, "data-align": alignment, className: cls('image-toolbar') }, linkEditorVisible ? (React.createElement("div", { className: cls('kedao-image-link-editor') }, React.createElement("div", { className: cls('editor-input-group') }, React.createElement("input", { type: "text", placeholder: language.linkEditor.inputWithEnterPlaceHolder, onKeyDown: handleLinkInputKeyDown, onChange: setImageLink, defaultValue: link }), React.createElement("button", { type: "button", onClick: confirmImageLink }, language.base.confirm)), React.createElement(Switch, { active: linkTarget === '_blank', onClick: () => setImageLinkTarget(linkTarget), label: language.linkEditor.openInNewWindow }))) : null, sizeEditorVisible ? (React.createElement("div", { className: cls('kedao-image-size-editor') }, React.createElement("div", { className: cls('editor-input-group') }, React.createElement("input", { type: "text", placeholder: language.base.width, onKeyDown: handleSizeInputKeyDown, onChange: setImageWidth, defaultValue: width }), React.createElement("input", { type: "text", placeholder: language.base.height, onKeyDown: handleSizeInputKeyDown, onChange: setImageHeight, defaultValue: height }), React.createElement("button", { type: "button", onClick: confirmImageSize }, language.base.confirm)))) : null, renderedControlItems, React.createElement("i", { style: { marginLeft: toolbarOffset * -1 }, className: cls('image-toolbar-arrow') }))) : null, React.createElement("div", { style: { position: 'relative', width: `${width}px`, height: `${height}px`, display: 'inline-block' } }, React.createElement("img", Object.assign({ ref: imageElement, src: url, alt: "Alt", width: width, height: height }, meta)), toolbarVisible && imageResizable ? (React.createElement("div", { role: "presentation", className: cls('kedao-csize-icon right-bottom'), onMouseDown: repareChangeSize('rightbottom') })) : null, toolbarVisible && imageResizable ? (React.createElement("div", { role: "presentation", className: cls('kedao-csize-icon left-bottom'), onMouseDown: repareChangeSize('leftbottom') })) : null, React.createElement("div", { className: cls(`kedao-pre-csize ${reSizeType.current}`), style: { width: `${tempWidth}px`, height: `${tempHeight}px` } }))), clearFix && (React.createElement("div", { className: cls('clearfix'), style: { clear: 'both', height: 0, lineHeight: 0, float: 'none' } })))); }; export default Image;