|
@@ -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 }}
|
|
|
- />
|
|
|
-}
|