Browse Source

修复一些bug,@报错,@后光标异常,提示信息异常,@时无法输入等问题

master
fangxin 2 years ago
parent
commit
1b00213bba
  1. 258
      im-web/src/components/chat/ChatInput.vue

258
im-web/src/components/chat/ChatInput.vue

@ -1,22 +1,22 @@
<template> <template>
<div class="chat-input-area"> <div class="chat-input-area">
<div class="edit-chat-container" contenteditable="true" <div :class="['edit-chat-container',isEmpty?'':'not-empty']" contenteditable="true"
@paste.prevent="onPaste" @paste.prevent="onPaste"
@keydown="onKeydown" @keydown="onKeydown"
@compositionstart="compositionFlag=true" @compositionstart="compositionFlag=true"
@compositionend="compositionFlag=false" @compositionend="compositionFlag=false;updateRange()"
@input="onEditorInput" @input="onEditorInput"
@mousedown="onMousedown" @mousedown="onMousedown"
v-html="contentHtml" v-html="contentHtml"
ref="content" ref="content"
@blur="onBlur" @blur="onBlur"
>
:placeholder="'请输入消息'">
</div> </div>
<chat-at-box @select="onAtSelect" <chat-at-box @select="onAtSelect"
:search-text="atSearchText" :search-text="atSearchText"
ref="atBox" ref="atBox"
:ownerId="ownerId" :members="groupMembers" :ownerId="ownerId"
:members="groupMembers"
></chat-at-box> ></chat-at-box>
</div> </div>
@ -50,21 +50,23 @@ export default {
atSearchText: null, atSearchText: null,
compositionFlag: false, compositionFlag: false,
history: [defaultContentHtml], history: [defaultContentHtml],
focusOffset: 0,
atIng: false, atIng: false,
isEmpty: true,
blurRange: null blurRange: null
} }
}, methods: { }, methods: {
onPaste(e) { onPaste(e) {
let txt = e.clipboardData.getData('Text') let txt = e.clipboardData.getData('Text')
let range = window.getSelection().getRangeAt(0) let range = window.getSelection().getRangeAt(0)
if (range.startContainer !== range.endContainer || range.startOffset !== range.endOffset) { if (range.startContainer !== range.endContainer || range.startOffset !== range.endOffset) {
document.execCommand('delete', false, null); range.deleteContents();
} }
if (typeof (txt) == 'string') { //
if (txt && typeof (txt) == 'string') {
let textNode = document.createTextNode(txt); let textNode = document.createTextNode(txt);
range.insertNode(textNode) range.insertNode(textNode)
// return; return;
} }
let items = (e.clipboardData || window.clipboardData).items let items = (e.clipboardData || window.clipboardData).items
if (items.length) { if (items.length) {
@ -78,14 +80,20 @@ export default {
}; };
this.imageList[imagePush.fileId] = (imagePush); this.imageList[imagePush.fileId] = (imagePush);
let divElement = this.newLine();
let text = document.createTextNode('\u00A0');
divElement.appendChild(text);
let imageElement = document.createElement('img'); let imageElement = document.createElement('img');
imageElement.className = 'chat-image no-text'; imageElement.className = 'chat-image no-text';
imageElement.src = imagePush.url; imageElement.src = imagePush.url;
imageElement.setAttribute("data-img-id", imagePush.fileId); imageElement.dataset.imgId = imagePush.fileId;
// imageElement.width = 200;
// imageElement.height = 100; divElement.appendChild(imageElement);
range.insertNode(imageElement); let after = document.createTextNode('\u00A0');
divElement.appendChild(after);
this.selectElement(after, 1);
// range.insertNode(divElement);
} else { } else {
let asFile = items[i].getAsFile(); let asFile = items[i].getAsFile();
if (!asFile) { if (!asFile) {
@ -93,26 +101,30 @@ export default {
} }
let filePush = {fileId: this.generateId(), file: asFile}; let filePush = {fileId: this.generateId(), file: asFile};
this.fileList[filePush.fileId] = (filePush) this.fileList[filePush.fileId] = (filePush)
let line = this.newLine();
let text = document.createTextNode('\u00A0');
line.appendChild(text);
let fileElement = this.createFile(filePush); let fileElement = this.createFile(filePush);
range.insertNode(fileElement); line.appendChild(fileElement);
// var text = document.createTextNode("\u00A0"); let after = document.createTextNode('\u00A0');
fileElement.insertAdjacentHTML('afterend','\u00A0'); line.appendChild(after);
// range.selectNodeContents(text); this.selectElement(after, 1);
// range.collapse(); // fileElement.insertAdjacentHTML('afterend', '\u00A0');
} }
} }
} }
range.collapse(); range.collapse();
}, },
selectElement(element) { selectElement(element, endOffset) {
let selection = window.getSelection(); let selection = window.getSelection();
// vuedom
setTimeout(() => { setTimeout(() => {
let t1 = document.createRange(); let t1 = document.createRange();
t1.setStart(element, 0); t1.setStart(element, 0);
t1.setEnd(element, 0); t1.setEnd(element, endOffset || 0);
if (element.firstChild) { if (element.firstChild) {
t1.selectNodeContents(element.firstChild); t1.selectNodeContents(element.firstChild);
} }
@ -120,7 +132,10 @@ export default {
selection.removeAllRanges(); selection.removeAllRanges();
selection.addRange(t1); selection.addRange(t1);
//
if (element.focus) {
element.focus(); element.focus();
}
}) })
}, },
onKeydown(e) { onKeydown(e) {
@ -133,34 +148,12 @@ export default {
return; return;
} }
if (e.shiftKey) { if (e.shiftKey) {
let selection = window.getSelection(); let divElement = this.newLine();
let range = selection.getRangeAt(0);
let divElement = document.createElement('div');
let endContainer = range.endContainer;
let parentElement = endContainer.parentElement;
let newText = endContainer.textContent.substring(range.endOffset).trim();
endContainer.textContent = endContainer.textContent.substring(0, range.endOffset);
divElement.innerHTML = newText || '';
if (parentElement === this.$refs.content) {
// range.insertNode(divElement)
this.$refs.content.append(divElement);
} else {
parentElement.insertAdjacentElement('afterend', divElement);
}
// if (range.startContainer !== endContainer || range.startOffset !== range.endOffset) {
// console.log('delete range.', range.startContainer, range.startOffset, range.endContainer, range.endOffset)
// document.execCommand('delete', false, null);
// }
this.selectElement(divElement); this.selectElement(divElement);
// return false;
} else { } else {
//
if (this.compositionFlag) { if (this.compositionFlag) {
console.log('中文输入中')
return; return;
} }
this.submit(); this.submit();
@ -168,7 +161,9 @@ export default {
return; return;
} }
if (e.keyCode === 90) { if (e.keyCode === 90) {
// Ctrl+Zmaccommand+z
if (e.ctrlKey || e.metaKey) { if (e.ctrlKey || e.metaKey) {
// ctrl+zctrl+zlow
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
if (this.history.length <= 1) { if (this.history.length <= 1) {
@ -198,83 +193,103 @@ export default {
}) })
} }
} }
//
if (e.keyCode === 8) { if (e.keyCode === 8) {
// dom
setTimeout(() => { setTimeout(() => {
// console.log(this.$refs.content.innerHTML.trim())
let s = this.$refs.content.innerHTML.trim(); let s = this.$refs.content.innerHTML.trim();
if (s === '' || s === '<br>') { // domdom
if (s === '' || s === '<br>' || s === '<div><br></div>' || s === '<div><br/></div>') {
// dom // dom
this.empty(); this.empty();
this.isEmpty = true;
this.selectElement(this.$refs.content); this.selectElement(this.$refs.content);
} else {
this.isEmpty = false;
} }
// this.
}) })
} }
// console.log(e.keyCode) // at
if (this.atIng) { if (this.atIng) {
console.log('atIng', e.keyCode) if (e.keyCode === 38) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
if (e.keyCode === 38) {
this.$refs.atBox.moveUp(); this.$refs.atBox.moveUp();
} }
if (e.keyCode === 40) { if (e.keyCode === 40) {
e.preventDefault();
e.stopPropagation();
this.$refs.atBox.moveDown(); this.$refs.atBox.moveDown();
} }
} }
}, },
onAtSelect(member) { onAtSelect(member) {
this.atIng = false; this.atIng = false;
let range = window.getSelection().getRangeAt(0)
// @xx // @xx
range.setStart(this.blurRange.startContainer, this.focusOffset - 1 - this.atSearchText.length) let blurRange = this.blurRange;
range.setEnd(this.blurRange.endContainer, this.focusOffset) let startOffset = blurRange.endOffset - this.atSearchText.length - 1;
range.deleteContents() blurRange.setStart(blurRange.endContainer, startOffset);
blurRange.deleteContents()
blurRange.collapse();
this.focus();
// //
let element = document.createElement('SPAN') let element = document.createElement('SPAN')
element.className = "chat-at-user"; element.className = "chat-at-user";
element.dataset.id = member.userId; element.dataset.id = member.userId;
element.contentEditable = 'false' element.contentEditable = 'false'
element.innerText = `@${member.aliasName}` element.innerText = `@${member.aliasName}`
element.setAttribute("data-at-user-id", member.userId) blurRange.insertNode(element)
range.insertNode(element)
// //
range.collapse() blurRange.collapse()
// //
let textNode = document.createTextNode('\u00A0'); let textNode = document.createTextNode('\u00A0');
range.insertNode(textNode) blurRange.insertNode(textNode);
range.selectNodeContents(textNode);
range.collapse() blurRange.collapse()
this.atSearchText = ""; this.atSearchText = "";
this.focus(); this.selectElement(textNode);
// this.selectElement(textNode);
// this.$refs.editBox.focus()
}, },
onEditorInput(e) { onEditorInput(e) {
// @ // timeoutcompositionend
if (this.$props.groupMembers && !this.zhLock) { this.isEmpty = false;
if (e.data === '@') { setTimeout(() => {
// if (this.$props.groupMembers && !this.compositionFlag) {
this.showAtBox(e);
} else {
let selection = window.getSelection() let selection = window.getSelection()
// let range = selection.getRangeAt(0) let range = selection.getRangeAt(0);
let focusNode = selection.focusNode; // @
// @ let endContainer = range.endContainer;
let stIdx = focusNode.textContent.lastIndexOf('@'); let endOffset = range.endOffset;
this.atSearchText = focusNode.textContent.substring(stIdx + 1); let textContent = endContainer.textContent;
let startIndex = -1;
for (let i = endOffset; i >= 0; i--) {
if (textContent[i] === '@') {
startIndex = i;
break;
} }
} }
// at
if (startIndex === -1) {
this.$refs.atBox.close();
return;
}
//
this.showAtBox(e)
let endIndex = endOffset;
for (let i = endOffset; i < textContent.length; i++) {
if (textContent[i] === ' ') {
endIndex = i;
break;
}
}
this.atSearchText = textContent.substring(startIndex + 1, endIndex).trim();
}
})
}, },
onBlur(e) { onBlur(e) {
// setTimeout(() => { this.updateRange();
// console.log(e)
// this.$refs.atBox.close();
// this.atIng = false;
// },100)
this.blurRange = window.getSelection().getRangeAt(0);
}, },
onMousedown() { onMousedown() {
if (this.atIng) { if (this.atIng) {
@ -283,20 +298,30 @@ export default {
} }
}, },
insertEmoji(emojiText) { insertEmoji(emojiText) {
// let selection = window.getSelection();
// let range = selection.getRangeAt(0);
let emojiElement = document.createElement('img'); let emojiElement = document.createElement('img');
emojiElement.className = 'chat-emoji no-text'; emojiElement.className = 'chat-emoji no-text';
emojiElement.setAttribute("data-emoji-code", emojiText); emojiElement.dataset.emojiCode = emojiText;
emojiElement.src = this.$emo.textToUrl(emojiText); emojiElement.src = this.$emo.textToUrl(emojiText);
let blurRange = this.blurRange; let blurRange = this.blurRange;
if (!blurRange) {
this.focus();
this.updateRange();
blurRange = this.blurRange;
}
if (blurRange.startContainer !== blurRange.endContainer || blurRange.startOffset !== blurRange.endOffset) { if (blurRange.startContainer !== blurRange.endContainer || blurRange.startOffset !== blurRange.endOffset) {
blurRange.deleteContents(); blurRange.deleteContents();
} }
blurRange.insertNode(emojiElement); blurRange.insertNode(emojiElement);
blurRange.collapse()
this.focus(); let textNode = document.createTextNode('\u00A0');
blurRange.insertNode(textNode)
blurRange.collapse()
this.selectElement(textNode);
this.updateRange();
this.isEmpty = false;
}, },
generateId() { generateId() {
return this.currentId++; return this.currentId++;
@ -307,7 +332,7 @@ export default {
let container = document.createElement('div'); let container = document.createElement('div');
container.className = 'chat-file-container no-text'; container.className = 'chat-file-container no-text';
container.contentEditable = 'false'; container.contentEditable = 'false';
container.setAttribute("data-file-id", fileId); container.dataset.fileId = fileId;
let left = document.createElement('div'); let left = document.createElement('div');
left.className = 'file-position-left'; left.className = 'file-position-left';
@ -346,6 +371,33 @@ export default {
return (len / 1024 / 1024 / 1024).toFixed(2) + 'GB'; return (len / 1024 / 1024 / 1024).toFixed(2) + 'GB';
} }
}, },
updateRange() {
let selection = window.getSelection();
this.blurRange = selection.getRangeAt(0);
},
newLine() {
let selection = window.getSelection();
let range = selection.getRangeAt(0);
let divElement = document.createElement('div');
let endContainer = range.endContainer;
let parentElement = endContainer.parentElement;
let newText = endContainer.textContent.substring(range.endOffset).trim();
endContainer.textContent = endContainer.textContent.substring(0, range.endOffset);
divElement.innerHTML = newText || '';
// atparentbug
if (parentElement === this.$refs.content) {
this.$refs.content.append(divElement);
} else {
// div
parentElement.insertAdjacentElement('afterend', divElement);
}
this.isEmpty = false;
return divElement;
},
clear() { clear() {
this.empty(); this.empty();
this.imageList = []; this.imageList = [];
@ -361,18 +413,18 @@ export default {
}, },
showAtBox(e) { showAtBox(e) {
this.atIng = true; this.atIng = true;
this.atSearchText = ""; // showtext
// this.atSearchText = "";
let selection = window.getSelection() let selection = window.getSelection()
let range = selection.getRangeAt(0) let range = selection.getRangeAt(0)
//
// this.focusNode = selection.focusNode;
this.focusOffset = selection.focusOffset;
// //
let pos = range.getBoundingClientRect(); let pos = range.getBoundingClientRect();
this.$refs.atBox.open({ this.$refs.atBox.open({
x: pos.x, x: pos.x,
y: pos.y y: pos.y
}) })
//
this.updateRange();
}, },
submit() { submit() {
// console.log(this.content) // console.log(this.content)
@ -401,7 +453,7 @@ export default {
} }
let text = tempText.trim(); let text = tempText.trim();
if (nodeName === 'img') { if (nodeName === 'img') {
let imgId = node.getAttribute('data-img-id'); let imgId = node.dataset.imgId;
if (imgId) { if (imgId) {
if (text) { if (text) {
fullList.push({ fullList.push({
@ -417,11 +469,11 @@ export default {
textList.push(text); textList.push(text);
tempText = ''; tempText = '';
} else { } else {
let emojiCode = node.getAttribute('data-emoji-code'); let emojiCode = node.dataset.emojiCode;
tempText += emojiCode; tempText += emojiCode;
} }
} else if (nodeName === 'div') { } else if (nodeName === 'div') {
let fileId = node.getAttribute('data-file-id'); let fileId = node.dataset.fileId
// //
if (fileId) { if (fileId) {
if (text) { if (text) {
@ -442,7 +494,7 @@ export default {
each(node.childNodes); each(node.childNodes);
} }
} else if (nodeName === 'span') { } else if (nodeName === 'span') {
let userId = node.getAttribute("data-at-user-id"); let userId = node.dataset.id;
if (userId !== null && userId !== undefined) { if (userId !== null && userId !== undefined) {
tempText += node.outerHTML; tempText += node.outerHTML;
} }
@ -476,7 +528,7 @@ export default {
}, },
mounted() { mounted() {
console.log(this.$props.groupMembers) // console.log(this.$props.groupMembers)
// this.$refs.content.firstElementChild.focus(); // this.$refs.content.firstElementChild.focus();
this.selectElement(this.$refs.content.firstElementChild); this.selectElement(this.$refs.content.firstElementChild);
setInterval(() => { setInterval(() => {
@ -520,11 +572,17 @@ export default {
> div { > div {
padding-left: 10px; padding-left: 10px;
//width: 1px;
height: 30px;
} }
// bug
> div:before { > div:before {
content: "\00a0"; content: "\00a0";
font-size: 14px; font-size: 14px;
position: absolute;
top: 0;
left: 0;
} }
.chat-image { .chat-image {
@ -591,12 +649,16 @@ export default {
} }
} }
.edit-chat-container:empty:before { .edit-chat-container > div:nth-of-type(1):empty:after {
content: '请输入消息(按Shift+Enter键换行)'; content: '请输入消息(按Shift+Enter键换行)';
color: gray; color: gray;
} }
.edit-chat-container:focus:before { .edit-chat-container > div:nth-of-type(1):focus:after {
content: none;
}
.edit-chat-container.not-empty > div:nth-of-type(1):after {
content: none; content: none;
} }

Loading…
Cancel
Save