|  | @@ -1,232 +0,0 @@
 | 
											
												
													
														|  | -import { Button, List, Space, Typography } from "antd"
 |  | 
 | 
											
												
													
														|  | -import { useCallback, useEffect, useRef, useState } from "react"
 |  | 
 | 
											
												
													
														|  | -import Expression, { emoList } from "./Expression";
 |  | 
 | 
											
												
													
														|  | -const { Text } = Typography
 |  | 
 | 
											
												
													
														|  | -import style from './index.less'
 |  | 
 | 
											
												
													
														|  | -import AddLink from "@/pages/MiniApp/EntWeChat/Welcome/components/link";
 |  | 
 | 
											
												
													
														|  | -
 |  | 
 | 
											
												
													
														|  | -type Props = {
 |  | 
 | 
											
												
													
														|  | -    /**是否展示底部*/
 |  | 
 | 
											
												
													
														|  | -    footer: null | string | JSX.Element,
 |  | 
 | 
											
												
													
														|  | -    /**插入变量按钮的按钮名称*/
 |  | 
 | 
											
												
													
														|  | -    btnNames: string[],
 |  | 
 | 
											
												
													
														|  | -    /**初始默认插入的按钮*/
 |  | 
 | 
											
												
													
														|  | -    initBtnNames?: string[],
 |  | 
 | 
											
												
													
														|  | -    /**是否开启表情 */
 |  | 
 | 
											
												
													
														|  | -    emo?: boolean,
 |  | 
 | 
											
												
													
														|  | -    value?: any
 |  | 
 | 
											
												
													
														|  | -    onChange?: (value: any) => void,
 |  | 
 | 
											
												
													
														|  | -    maxStr?: number,//最大字数
 |  | 
 | 
											
												
													
														|  | -    isOnInput?: boolean,
 |  | 
 | 
											
												
													
														|  | -}
 |  | 
 | 
											
												
													
														|  | -export function TextEditor(props: Props) {
 |  | 
 | 
											
												
													
														|  | -    const { footer, btnNames, initBtnNames, emo = true, onChange, value = '', maxStr, isOnInput } = props
 |  | 
 | 
											
												
													
														|  | -    const ref: { current: any } = useRef(null)
 |  | 
 | 
											
												
													
														|  | -    const [text, setText] = useState('')
 |  | 
 | 
											
												
													
														|  | -    const [range, setRange] = useState<Range>()//丢失焦点存放焦点位置
 |  | 
 | 
											
												
													
														|  | -    const [strLength, setStrLength] = useState(0)
 |  | 
 | 
											
												
													
														|  | -    /** 回填 */
 |  | 
 | 
											
												
													
														|  | -    useEffect(() => {
 |  | 
 | 
											
												
													
														|  | -        backfill()
 |  | 
 | 
											
												
													
														|  | -    }, [value])
 |  | 
 | 
											
												
													
														|  | -    //回填处理
 |  | 
 | 
											
												
													
														|  | -    const backfill = useCallback(() => {
 |  | 
 | 
											
												
													
														|  | -        if (value) {
 |  | 
 | 
											
												
													
														|  | -            if (typeof value === 'string') {
 |  | 
 | 
											
												
													
														|  | -                setStrLength(value.length || 0)
 |  | 
 | 
											
												
													
														|  | -                let newValue = value || ''
 |  | 
 | 
											
												
													
														|  | -                let newEmo = emoList.reduce((prev, cur) => {
 |  | 
 | 
											
												
													
														|  | -                    return [...prev, ...cur]
 |  | 
 | 
											
												
													
														|  | -                }, [])
 |  | 
 | 
											
												
													
														|  | -                let emos = newValue.match(/\[[\u4e00-\u9fa5]+\]/g)
 |  | 
 | 
											
												
													
														|  | -                if (emos && emos?.length) {
 |  | 
 | 
											
												
													
														|  | -                    emos.forEach(emo => {
 |  | 
 | 
											
												
													
														|  | -                        let emoData = newEmo.find(o => `[${o.name}]` === emo)
 |  | 
 | 
											
												
													
														|  | -                        if (emoData) {
 |  | 
 | 
											
												
													
														|  | -                            newValue = newValue.replace(emo, `<img src="${emoData.url}" alt="${emoData.name}" style="width:20px;height:20px"/>`)
 |  | 
 | 
											
												
													
														|  | -                        }
 |  | 
 | 
											
												
													
														|  | -                    })
 |  | 
 | 
											
												
													
														|  | -                }
 |  | 
 | 
											
												
													
														|  | -                setText(newValue)//?.replaceAll('\n', '<br/>')
 |  | 
 | 
											
												
													
														|  | -            } else {
 |  | 
 | 
											
												
													
														|  | -                setText(value?.innerHTML)
 |  | 
 | 
											
												
													
														|  | -            }
 |  | 
 | 
											
												
													
														|  | -        } else {//不存在内容设置焦点
 |  | 
 | 
											
												
													
														|  | -            ref?.current?.focus()
 |  | 
 | 
											
												
													
														|  | -        }
 |  | 
 | 
											
												
													
														|  | -    }, [value])
 |  | 
 | 
											
												
													
														|  | -
 |  | 
 | 
											
												
													
														|  | -    // 焦点丢失记录
 |  | 
 | 
											
												
													
														|  | -    const onBlur = (e: any) => {
 |  | 
 | 
											
												
													
														|  | -        try {
 |  | 
 | 
											
												
													
														|  | -            let selection = window.getSelection()
 |  | 
 | 
											
												
													
														|  | -            let range = selection?.getRangeAt(0)
 |  | 
 | 
											
												
													
														|  | -            setRange(range)
 |  | 
 | 
											
												
													
														|  | -            //点击表情不更新
 |  | 
 | 
											
												
													
														|  | -            if (!e?.relatedTarget?.className?.match(/(myEmo)|(textEditor_btn)/g)) {
 |  | 
 | 
											
												
													
														|  | -                onChange?.(ref.current?.innerHTML)
 |  | 
 | 
											
												
													
														|  | -            }
 |  | 
 | 
											
												
													
														|  | -        } catch (err) {
 |  | 
 | 
											
												
													
														|  | -        }
 |  | 
 | 
											
												
													
														|  | -    }
 |  | 
 | 
											
												
													
														|  | -    // 自定义文本插入按钮
 |  | 
 | 
											
												
													
														|  | -    const btnClick = useCallback((btnName: any) => {
 |  | 
 | 
											
												
													
														|  | -        if (range) {
 |  | 
 | 
											
												
													
														|  | -            let str = btnName //` <span style="display: inline-block;margin:0 1px;position: relative; border: 1px solid ${token.colorBorder}; padding: ${token.paddingXS}px; border-radius: ${token.borderRadius}px;color: ${token.colorTextBase}; background: ${token.colorSuccess};color:${token.colorTextLightSolid}" contenteditable="false">${btnName}<strong data-name="${btnName}" style="padding: 0 6px;cursor:pointer" onclick="let html =document.querySelector('[data-name=${btnName}]').parentElement.parentElement.innerHTML;let span = ' '+document.querySelector('[data-name=${btnName}]').parentElement.outerHTML+' ';console.log('=',html,'=');console.log('=',span,'=');document.execCommand('selectAll');document.execCommand('delete'); document.execCommand('insertHTML', true, html.replace(span,''));">X</strong></span> `
 |  | 
 | 
											
												
													
														|  | -            let selection = window.getSelection()
 |  | 
 | 
											
												
													
														|  | -            selection?.empty()//清空选择range
 |  | 
 | 
											
												
													
														|  | -            selection?.addRange(range as Range)//插入新的range
 |  | 
 | 
											
												
													
														|  | -            document.execCommand('insertHTML', false, btnName);//插入内容
 |  | 
 | 
											
												
													
														|  | -            range.collapse()
 |  | 
 | 
											
												
													
														|  | -            selection?.collapseToEnd()//光标插入到末尾
 |  | 
 | 
											
												
													
														|  | -        } else {
 |  | 
 | 
											
												
													
														|  | -            ref.current.focus()
 |  | 
 | 
											
												
													
														|  | -        }
 |  | 
 | 
											
												
													
														|  | -    }, [range, ref])
 |  | 
 | 
											
												
													
														|  | -    /**
 |  | 
 | 
											
												
													
														|  | -     * 插入表情
 |  | 
 | 
											
												
													
														|  | -     */
 |  | 
 | 
											
												
													
														|  | -    const addEmo = useCallback((str: string, range: Range) => {
 |  | 
 | 
											
												
													
														|  | -        if (range) {
 |  | 
 | 
											
												
													
														|  | -            let selection = window.getSelection()
 |  | 
 | 
											
												
													
														|  | -            selection?.empty()//清空选择range
 |  | 
 | 
											
												
													
														|  | -            selection?.addRange(range)//插入新的range
 |  | 
 | 
											
												
													
														|  | -            document.execCommand('insertHTML', false, str);
 |  | 
 | 
											
												
													
														|  | -        }
 |  | 
 | 
											
												
													
														|  | -    }, [])
 |  | 
 | 
											
												
													
														|  | -    const addLink = useCallback((str: string, range: Range) => {
 |  | 
 | 
											
												
													
														|  | -        if (range) {
 |  | 
 | 
											
												
													
														|  | -            let selection = window.getSelection()
 |  | 
 | 
											
												
													
														|  | -            selection?.empty()//清空选择range
 |  | 
 | 
											
												
													
														|  | -            selection?.addRange(range)//插入新的range
 |  | 
 | 
											
												
													
														|  | -            document.execCommand('insertHTML', false, str);
 |  | 
 | 
											
												
													
														|  | -        }
 |  | 
 | 
											
												
													
														|  | -    }, [])
 |  | 
 | 
											
												
													
														|  | -    //初始化设置
 |  | 
 | 
											
												
													
														|  | -    useEffect(() => {
 |  | 
 | 
											
												
													
														|  | -        if (initBtnNames && initBtnNames?.length > 0) {
 |  | 
 | 
											
												
													
														|  | -            let newText = ''
 |  | 
 | 
											
												
													
														|  | -            initBtnNames?.map(btnName => {
 |  | 
 | 
											
												
													
														|  | -                newText += "" //` <span style="display: inline-block;margin:0 1px;position: relative; border: 1px solid ${token.colorBorder}; padding: ${token.paddingXS}px; border-radius: ${token.borderRadius}px;color: ${token.colorTextBase}; background: ${token.colorSuccess};color:${token.colorTextLightSolid}" contenteditable="false">${btnName}<strong data-name="${btnName}" style="padding: 0 6px;cursor:pointer" onclick="let html =document.querySelector('[data-name=${btnName}]').parentElement.parentElement.innerHTML;let span = ' '+document.querySelector('[data-name=${btnName}]').parentElement.outerHTML+' ';console.log('=',html,'=');console.log('=',span,'=');document.execCommand('selectAll');document.execCommand('delete'); document.execCommand('insertHTML', true, html.replace(span,''));">X</strong></span> `
 |  | 
 | 
											
												
													
														|  | -            })
 |  | 
 | 
											
												
													
														|  | -            setText(newText)
 |  | 
 | 
											
												
													
														|  | -        }
 |  | 
 | 
											
												
													
														|  | -    }, [initBtnNames])
 |  | 
 | 
											
												
													
														|  | -    //复制只取纯文本
 |  | 
 | 
											
												
													
														|  | -    function textPaste(event: any) {
 |  | 
 | 
											
												
													
														|  | -        event.preventDefault();
 |  | 
 | 
											
												
													
														|  | -        let text;
 |  | 
 | 
											
												
													
														|  | -        let clp = (event.originalEvent || event).clipboardData;
 |  | 
 | 
											
												
													
														|  | -        // 兼容chorme或hotfire
 |  | 
 | 
											
												
													
														|  | -        text = (clp.getData('text/plain') || clp.getData('text'))
 |  | 
 | 
											
												
													
														|  | -            .replace(/"/ig, '"')
 |  | 
 | 
											
												
													
														|  | -            .replace(/&/ig, '&')
 |  | 
 | 
											
												
													
														|  | -            .replace(/</ig, '<')
 |  | 
 | 
											
												
													
														|  | -            .replace(/>/ig, '>')
 |  | 
 | 
											
												
													
														|  | -            .replace(/<\s+/g, '<')
 |  | 
 | 
											
												
													
														|  | -            .replace(/href="weixin/ig, 'href=weixin')
 |  | 
 | 
											
												
													
														|  | -        if (text !== "") {
 |  | 
 | 
											
												
													
														|  | -            document.execCommand('insertHTML', false, text);
 |  | 
 | 
											
												
													
														|  | -        }
 |  | 
 | 
											
												
													
														|  | -    }
 |  | 
 | 
											
												
													
														|  | -    const insertHtmlAtCursor = (html: string) => {
 |  | 
 | 
											
												
													
														|  | -        const selection = window.getSelection();
 |  | 
 | 
											
												
													
														|  | -        if (!selection || selection.rangeCount === 0) return;
 |  | 
 | 
											
												
													
														|  | -
 |  | 
 | 
											
												
													
														|  | -        const range = selection.getRangeAt(0);
 |  | 
 | 
											
												
													
														|  | -        range.deleteContents(); // 删除光标处的内容
 |  | 
 | 
											
												
													
														|  | -
 |  | 
 | 
											
												
													
														|  | -        const tempDiv = document.createElement('div');
 |  | 
 | 
											
												
													
														|  | -        tempDiv.innerHTML = html; // 创建包含 HTML 的临时元素
 |  | 
 | 
											
												
													
														|  | -        const frag = document.createDocumentFragment();
 |  | 
 | 
											
												
													
														|  | -        
 |  | 
 | 
											
												
													
														|  | -        // 将临时元素的子元素添加到文档片段中
 |  | 
 | 
											
												
													
														|  | -        while (tempDiv.firstChild) {
 |  | 
 | 
											
												
													
														|  | -            frag.appendChild(tempDiv.firstChild);
 |  | 
 | 
											
												
													
														|  | -        }
 |  | 
 | 
											
												
													
														|  | -        range.insertNode(frag); // 将内容插入光标位置
 |  | 
 | 
											
												
													
														|  | -        // 重新设置光标位置
 |  | 
 | 
											
												
													
														|  | -        range.collapse(false);
 |  | 
 | 
											
												
													
														|  | -        selection.removeAllRanges();
 |  | 
 | 
											
												
													
														|  | -        selection.addRange(range);
 |  | 
 | 
											
												
													
														|  | -    };
 |  | 
 | 
											
												
													
														|  | -    return <List
 |  | 
 | 
											
												
													
														|  | -        size="small"
 |  | 
 | 
											
												
													
														|  | -        header={<div style={{ display: 'flex', justifyContent: 'space-between' }}>
 |  | 
 | 
											
												
													
														|  | -            <Space wrap size={[20, 0]}>
 |  | 
 | 
											
												
													
														|  | -                {/* {emo && <Expression addEmo={addEmo} range={range as Range} />} */}
 |  | 
 | 
											
												
													
														|  | -                {btnNames?.map(name => {
 |  | 
 | 
											
												
													
														|  | -                    return <Button className="textEditor_btn" size="small" onClick={() => { btnClick(name) }} key={name}>{name}</Button>
 |  | 
 | 
											
												
													
														|  | -                })}
 |  | 
 | 
											
												
													
														|  | -                {/* <AddLink onChange={(value) => {
 |  | 
 | 
											
												
													
														|  | -                    if (value) {
 |  | 
 | 
											
												
													
														|  | -                        addEmo("1", range as any)
 |  | 
 | 
											
												
													
														|  | -                    }
 |  | 
 | 
											
												
													
														|  | -                }} /> */}
 |  | 
 | 
											
												
													
														|  | -                <button onClick={() => insertHtmlAtCursor('<a href="https://example.com" target="_blank">https://example.com</a>')}>插入链接</button>
 |  | 
 | 
											
												
													
														|  | -            </Space>
 |  | 
 | 
											
												
													
														|  | -            {maxStr && <Typography.Text type='secondary'><Typography.Text type={strLength > maxStr ? 'danger' : 'secondary'} >{strLength}</Typography.Text>/{maxStr}</Typography.Text>}
 |  | 
 | 
											
												
													
														|  | -            {/* <Button type="link">复制</Button> */}
 |  | 
 | 
											
												
													
														|  | -        </div>}
 |  | 
 | 
											
												
													
														|  | -        footer={typeof footer === 'string' ? <Text >{footer}</Text> : footer}
 |  | 
 | 
											
												
													
														|  | -        bordered
 |  | 
 | 
											
												
													
														|  | -        dataSource={['']}
 |  | 
 | 
											
												
													
														|  | -        renderItem={(item) => (
 |  | 
 | 
											
												
													
														|  | -            <List.Item style={{ padding: 5 }}>
 |  | 
 | 
											
												
													
														|  | -                <pre
 |  | 
 | 
											
												
													
														|  | -                    className={style.myPre}
 |  | 
 | 
											
												
													
														|  | -                    style={{
 |  | 
 | 
											
												
													
														|  | -                        fontFamily: ' sans-serif',
 |  | 
 | 
											
												
													
														|  | -                        width: '100%',
 |  | 
 | 
											
												
													
														|  | -                        minHeight: '40px',
 |  | 
 | 
											
												
													
														|  | -                        padding: 5,
 |  | 
 | 
											
												
													
														|  | -                        resize: 'none',
 |  | 
 | 
											
												
													
														|  | -                        whiteSpace: 'pre-wrap',
 |  | 
 | 
											
												
													
														|  | -                        margin: 0,
 |  | 
 | 
											
												
													
														|  | -                        textAlign: 'left',
 |  | 
 | 
											
												
													
														|  | -                        wordBreak: 'break-all'
 |  | 
 | 
											
												
													
														|  | -                    }}
 |  | 
 | 
											
												
													
														|  | -                    contentEditable="true"
 |  | 
 | 
											
												
													
														|  | -                    dangerouslySetInnerHTML={{
 |  | 
 | 
											
												
													
														|  | -                        __html: text
 |  | 
 | 
											
												
													
														|  | -                    }}
 |  | 
 | 
											
												
													
														|  | -                    ref={ref}
 |  | 
 | 
											
												
													
														|  | -                    // onKeyDown={(e: React.KeyboardEvent<HTMLPreElement>) => {
 |  | 
 | 
											
												
													
														|  | -                    //     if (e.key === 'Enter') {
 |  | 
 | 
											
												
													
														|  | -                    //         e.preventDefault()
 |  | 
 | 
											
												
													
														|  | -                    //         document.execCommand('insertHTML', false, '\n')
 |  | 
 | 
											
												
													
														|  | -                    //     }
 |  | 
 | 
											
												
													
														|  | -                    // }}
 |  | 
 | 
											
												
													
														|  | -                    onInput={(e: any) => {
 |  | 
 | 
											
												
													
														|  | -
 |  | 
 | 
											
												
													
														|  | -                    }}
 |  | 
 | 
											
												
													
														|  | -                    onKeyUp={(e: React.KeyboardEvent<HTMLPreElement>) => {
 |  | 
 | 
											
												
													
														|  | -                        if (e.key === 'Enter') {
 |  | 
 | 
											
												
													
														|  | -                        } else {
 |  | 
 | 
											
												
													
														|  | -                            if (maxStr) {
 |  | 
 | 
											
												
													
														|  | -                                let childrens = (e.target as any).childNodes
 |  | 
 | 
											
												
													
														|  | -                                let newText: string = ''
 |  | 
 | 
											
												
													
														|  | -                                for (const children of childrens) {
 |  | 
 | 
											
												
													
														|  | -                                    if (children.tagName === 'IMG') {
 |  | 
 | 
											
												
													
														|  | -                                        newText += `[${children.alt}]`
 |  | 
 | 
											
												
													
														|  | -                                    } else if (children.tagName === 'BR') {
 |  | 
 | 
											
												
													
														|  | -                                        newText += `\n`
 |  | 
 | 
											
												
													
														|  | -                                    } else if (children.tagName === 'SPAN') {
 |  | 
 | 
											
												
													
														|  | -                                        let name = children.children[0].dataset.name
 |  | 
 | 
											
												
													
														|  | -                                        newText += `%${name}%`
 |  | 
 | 
											
												
													
														|  | -                                    } else {
 |  | 
 | 
											
												
													
														|  | -                                        newText += children.textContent
 |  | 
 | 
											
												
													
														|  | -                                    }
 |  | 
 | 
											
												
													
														|  | -                                }
 |  | 
 | 
											
												
													
														|  | -                                setStrLength(newText.length)
 |  | 
 | 
											
												
													
														|  | -                            }
 |  | 
 | 
											
												
													
														|  | -                        }
 |  | 
 | 
											
												
													
														|  | -                    }}
 |  | 
 | 
											
												
													
														|  | -                    onBlur={onBlur}//焦点丢失
 |  | 
 | 
											
												
													
														|  | -                    onPaste={textPaste}
 |  | 
 | 
											
												
													
														|  | -                />
 |  | 
 | 
											
												
													
														|  | -            </List.Item>
 |  | 
 | 
											
												
													
														|  | -        )}
 |  | 
 | 
											
												
													
														|  | -        style={{ marginTop: 10 }}
 |  | 
 | 
											
												
													
														|  | -    />
 |  | 
 | 
											
												
													
														|  | -}
 |  | 
 |