1 import React, { useState, useRef, useEffect, forwardRef, useImperativeHandle } from 'react';
2 import { SmileOutlined } from '@ant-design/icons';
3 import { Row, Col, Button, Tooltip, message } from 'antd';
4
5 import styles from './index.less';
6
7 import {setCursorPostionEnd} from "./util";
8
9 const emojiPath = '/emojiImages/';
10 const emojiSuffix = '.png';
11 const emojiList = [...Array(15).keys()].map((_, index: number) => {
12 return { id: index + 1, path: emojiPath + (index + 1) + emojiSuffix };
13 });
14
15 type Props = {
16 uniqueId: string; // 唯一键
17 item?: object; // 携带参数
18 okClick: Function; // 发布
19 okText?: string;
20 };
21
22 const InputComment = forwardRef((props: Props, ref) => {
23 const { uniqueId: id, okClick, okText } = props;
24 const inputBoxRef = useRef<any>(null);
25 const [textCount, setTextCount] = useState(0);
26 let rangeOfInputBox: any;
27 const uniqueId = 'uniqueId_' + id;
28
29 const setCaretForEmoji = (target: any) => {
30 if (target?.tagName?.toLowerCase() === 'img') {
31 const range = new Range();
32 range.setStartBefore(target);
33 range.collapse(true);
34 // inputBoxRef?.current?.removeAllRanges();
35 // inputBoxRef?.current?.addRange(range);
36 const sel = window.getSelection();
37 sel?.removeAllRanges();
38 sel?.addRange(range);
39 }
40 };
41
42 /**
43 * 输入框点击
44 */
45 const inputBoxClick = (event: any) => {
46 const target = event.target;
47 setCaretForEmoji(target);
48 };
49
50 /**
51 * emoji点击
52 */
53 const emojiClick = (item: any) => {
54 const emojiEl = document.createElement('img');
55 emojiEl.src = item.path;
56 const dom = document.getElementById(uniqueId);
57 const html = dom?.innerHTML;
58
59 // rangeOfInputBox未定义并且存在内容时,将光标移动到末尾
60 if (!rangeOfInputBox && !!html) {
61 dom.innerHTML = html + `<img src="${item.path}"/>`;
62 setCursorPostionEnd(dom)
63 } else {
64 if (!rangeOfInputBox) {
65 rangeOfInputBox = new Range();
66 rangeOfInputBox.selectNodeContents(inputBoxRef.current);
67 }
68
69 if (rangeOfInputBox.collapsed) {
70 rangeOfInputBox.insertNode(emojiEl);
71 } else {
72 rangeOfInputBox.deleteContents();
73 rangeOfInputBox.insertNode(emojiEl);
74 }
75 rangeOfInputBox.collapse(false);
76
77 const sel = window.getSelection();
78 sel?.removeAllRanges();
79 sel?.addRange(rangeOfInputBox);
80 }
81 };
82
83 /**
84 * 选择变化事件
85 */
86 document.onselectionchange = (e) => {
87 if (inputBoxRef?.current) {
88 const element = inputBoxRef?.current;
89 const doc = element.ownerDocument || element.document;
90 const win = doc.defaultView || doc.parentWindow;
91 const selection = win.getSelection();
92
93 if (selection?.rangeCount > 0) {
94 const range = selection?.getRangeAt(0);
95 if (inputBoxRef?.current?.contains(range?.commonAncestorContainer)) {
96 rangeOfInputBox = range;
97 }
98 }
99 }
100 };
101
102 /**
103 * 获取内容长度
104 */
105 const getContentCount = (content: string) => {
106 return content
107 .replace(/ /g, ' ')
108 .replace(/<br>/g, '')
109 .replace(/<\/?[^>]*>/g, '占位').length;
110 };
111
112 /**
113 * 发送
114 */
115 const okSubmit = () => {
116 const content = inputBoxRef.current.innerHTML;
117 if (!content) {
118 return message.warning('温馨提示:请填写评论内容!');
119 } else if (getContentCount(content) > 1000) {
120 return message.warning(`温馨提示:评论或回复内容小于1000字!`);
121 }
122
123 okClick(content);
124 };
125
126 /**
127 * 清空输入框内容
128 */
129 const clearInputBoxContent = () => {
130 inputBoxRef.current.innerHTML = '';
131 };
132
133 // 将子组件的方法 暴露给父组件
134 useImperativeHandle(ref, () => ({
135 clearInputBoxContent,
136 }));
137
138 // 监听变化
139 useEffect(() => {
140 const dom = document.getElementById(uniqueId);
141 const observer = new MutationObserver(() => {
142 const content = dom?.innerHTML ?? '';
143 // console.log('Content changed:', content);
144 setTextCount(getContentCount(content));
145 });
146
147 if (dom) {
148 observer.observe(dom, {
149 attributes: true,
150 childList: true,
151 characterData: true,
152 subtree: true,
153 });
154 }
155 }, []);
156
157 return (
158 <div style={{ marginTop: 10, marginBottom: 10 }} className={styles.inputComment}>
159 {textCount === 0 ? (
160 <div className="input-placeholder">
161 {okText === '确认' ? '回复' : '发布'}评论,内容小于1000字!
162 </div>
163 ) : null}
164
165 <div
166 ref={inputBoxRef}
167 id={uniqueId}
168 contentEditable={true}
169 placeholder="adsadadsa"
170 className="ant-input input-box"
171 onClick={inputBoxClick}
172 />
173 <div className="input-emojis">
174 <div className="input-count">{textCount}/1000</div>
175
176 <Row wrap={false}>
177 <Col flex="auto">
178 <Row wrap={true} gutter={[0, 10]} align="middle" style={{ userSelect: 'none' }}>
179 {emojiList.map((item, index: number) => {
180 return (
181 <Col
182 flex="none"
183 onClick={() => {
184 emojiClick(item);
185 }}
186
187 <Col flex="none" style={{ marginTop: 5 }}>
188 <Button
189 type="primary"
190 disabled={textCount === 0}
191 onClick={() => {
192 okSubmit();
193 }}
194 >
195 {okText || '发布'}
196 </Button>
197 </Col>
198 </Row>
199 </div>
200 </div>
201 );
202 });
203
204 export default InputComment;
1 /**
2 * 光标放到文字末尾(获取焦点时)
3 * @param el
4 */
5 export function setCursorPostionEnd(el:any) {
6 if (window.getSelection) {
7 // ie11 10 9 ff safari
8 el.focus() // 解决ff不获取焦点无法定位问题
9 const range = window.getSelection() // 创建range
10 range?.selectAllChildren(el) // range 选择obj下所有子内容
11 range?.collapseToEnd() // 光标移至最后
12 } else if (document?.selection) {
13 // ie10 9 8 7 6 5
14 const range = document?.selection?.createRange() // 创建选择对象
15 // var range = document.body.createTextRange();
16 range.moveToElementText(el) // range定位到obj
17 range.collapse(false) // 光标移至最后
18 range.select()
19 }
20 }