Compare commits

..

No commits in common. "1ae317dbb32ba0a271484553811c7d2f023747b7" and "3b6d998ce1552392e0e0b5b9d5279004c145c94c" have entirely different histories.

4 changed files with 99 additions and 189 deletions

View File

@ -26,7 +26,6 @@
"@vueuse/core": "^10.7.0", "@vueuse/core": "^10.7.0",
"ant-design-vue": "^4.2.6", "ant-design-vue": "^4.2.6",
"axios": "^1.6.2", "axios": "^1.6.2",
"dayjs": "^1.11.13",
"highlight.js": "^11.5.0", "highlight.js": "^11.5.0",
"js-audio-recorder": "^1.0.7", "js-audio-recorder": "^1.0.7",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",

View File

@ -44,9 +44,6 @@ importers:
axios: axios:
specifier: ^1.6.2 specifier: ^1.6.2
version: 1.9.0 version: 1.9.0
dayjs:
specifier: ^1.11.13
version: 1.11.13
highlight.js: highlight.js:
specifier: ^11.5.0 specifier: ^11.5.0
version: 11.11.1 version: 11.11.1

View File

@ -76,9 +76,7 @@ const navs = ref([
const mentionList = ref([]) const mentionList = ref([])
const currentMentionQuery = ref('') const currentMentionQuery = ref('')
setTimeout(() => {
console.log('props.members',props.members)
}, 1000)
// //
const editorContent = ref('') const editorContent = ref('')
const editorHtml = ref('') const editorHtml = ref('')
@ -102,7 +100,6 @@ const handleInput = (event) => {
const editorClone = target.cloneNode(true) const editorClone = target.cloneNode(true)
const quoteElements = editorClone.querySelectorAll('.editor-quote') const quoteElements = editorClone.querySelectorAll('.editor-quote')
quoteElements.forEach(quote => quote.remove()) quoteElements.forEach(quote => quote.remove())
quoteElements.forEach(quote => quote.remove())
// alt // alt
const emojiImages = editorClone.querySelectorAll('img.editor-emoji') const emojiImages = editorClone.querySelectorAll('img.editor-emoji')
@ -122,8 +119,7 @@ const handleInput = (event) => {
editorContent.value = textContent editorContent.value = textContent
// placeholder // placeholder
// const isEmpty = textContent.trim() === '' &&
const isEmpty = textContent === '' &&
!target.querySelector('img, .editor-file, .mention') !target.querySelector('img, .editor-file, .mention')
if (isEmpty && target.innerHTML !== '') { if (isEmpty && target.innerHTML !== '') {
@ -167,30 +163,11 @@ const showMentionList = () => {
mentionList.value = props.members.filter(member => { mentionList.value = props.members.filter(member => {
return member.value.toLowerCase().startsWith(query) return member.value.toLowerCase().startsWith(query)
}) })
if(dialogueStore.groupInfo.is_manager){
mentionList.value.unshift({ id: 0, nickname: '全体成员', avatar: defAvatar, value: '全体成员' })
}
showMention.value = mentionList.value.length > 0 showMention.value = mentionList.value.length > 0
selectedMentionIndex.value = 0 selectedMentionIndex.value = 0
} }
// mention
const handleMentionSelectByMouse = (member) => {
const selection = window.getSelection();
if (selection && selection.rangeCount > 0) {
insertMention(member, selection.getRangeAt(0).cloneRange());
} else {
//
editorRef.value?.focus();
nextTick(() => {
const newSelection = window.getSelection();
if (newSelection && newSelection.rangeCount > 0) {
insertMention(member, newSelection.getRangeAt(0).cloneRange());
}
});
}
};
// mention // mention
const hideMentionList = () => { const hideMentionList = () => {
showMention.value = false showMention.value = false
@ -210,73 +187,65 @@ const updateMentionPosition = (range) => {
} }
// mention // mention
const insertMention = (member, clonedRange) => { const insertMention = (member) => {
console.log('插入mention', member); const selection = window.getSelection()
const selection = window.getSelection(); if (!selection.rangeCount) return
if (!clonedRange || !selection) return;
const range = selection.getRangeAt(0)
const range = clonedRange; // 使 range const textNode = range.startContainer
const offset = range.startOffset
const textNode = range.startContainer;
const offset = range.startOffset; // @
const textContent = textNode.nodeType === Node.TEXT_NODE ? textNode.textContent || '' : ''; const textContent = textNode.textContent || ''
// @ const atIndex = textContent.lastIndexOf('@', offset - 1)
const atIndex = (textNode.nodeType === Node.TEXT_NODE && offset > 0) ? textContent.lastIndexOf('@', offset - 1) : -1;
// mention
const mentionSpan = document.createElement('span'); const mentionSpan = document.createElement('span')
mentionSpan.className = 'mention'; mentionSpan.className = 'mention'
mentionSpan.setAttribute('data-user-id', String(member.id)); mentionSpan.setAttribute('data-user-id', member.id || member.user_id)
mentionSpan.textContent = `@${member.value || member.nickname}`; mentionSpan.textContent = `@${member.value || member.nickname}`
mentionSpan.contentEditable = 'false'; mentionSpan.contentEditable = 'false'
if (atIndex !== -1 && textNode.nodeType === Node.TEXT_NODE) { if (atIndex !== -1) {
const parent = textNode.parentNode; // @
if (!parent) return; // Sanity check const beforeText = textContent.substring(0, atIndex)
const afterText = textContent.substring(offset)
// @
range.setStart(textNode, atIndex); //
range.setEnd(textNode, offset); const beforeNode = document.createTextNode(beforeText)
range.deleteContents(); const afterNode = document.createTextNode(' ' + afterText)
// mention //
range.insertNode(mentionSpan); const parent = textNode.parentNode
parent.insertBefore(beforeNode, textNode)
parent.insertBefore(mentionSpan, textNode)
parent.insertBefore(afterNode, textNode)
parent.removeChild(textNode)
} else { } else {
// @ // @
if (!range.collapsed) { range.deleteContents()
range.deleteContents();
} // @
range.insertNode(mentionSpan); range.insertNode(mentionSpan)
// @
const spaceNode = document.createTextNode(' ')
range.setStartAfter(mentionSpan)
range.insertNode(spaceNode)
//
range.setStartAfter(spaceNode)
range.collapse(true)
selection.removeAllRanges()
selection.addRange(range)
} }
// mention //
const spaceNode = document.createTextNode('\u00A0'); // 使 handleInput({ target: editorRef.value })
const currentParent = mentionSpan.parentNode;
if (currentParent) { // mention
// mentionSpan hideMentionList()
if (mentionSpan.nextSibling) { }
currentParent.insertBefore(spaceNode, mentionSpan.nextSibling);
} else {
currentParent.appendChild(spaceNode);
}
//
range.setStartAfter(spaceNode);
range.collapse(true);
} else {
// Fallback: mentionSpan mentionSpan
range.setStartAfter(mentionSpan);
range.collapse(true);
}
selection.removeAllRanges();
selection.addRange(range);
editorRef.value?.focus(); //
nextTick(() => {
handleInput({ target: editorRef.value });
hideMentionList();
});
};
// //
const handlePaste = (event) => { const handlePaste = (event) => {
@ -374,15 +343,12 @@ const handleKeydown = (event) => {
break break
case 'Enter': case 'Enter':
case 'Tab': case 'Tab':
event.preventDefault(); event.preventDefault()
const selectedMember = mentionList.value[selectedMentionIndex.value]; const selectedMember = mentionList.value[selectedMentionIndex.value]
if (selectedMember) { if (selectedMember) {
const selection = window.getSelection(); insertMention(selectedMember)
if (selection && selection.rangeCount > 0) {
insertMention(selectedMember, selection.getRangeAt(0).cloneRange());
}
} }
break; break
case 'Escape': case 'Escape':
hideMentionList() hideMentionList()
break break
@ -425,25 +391,6 @@ const handleKeydown = (event) => {
} }
prevSibling = prevSibling.previousSibling prevSibling = prevSibling.previousSibling
} }
} else {
//
// mention
if (!container.textContent.trim()) {
let prevSibling = container.previousSibling
while (prevSibling) {
if (prevSibling.nodeType === Node.ELEMENT_NODE &&
prevSibling.classList &&
prevSibling.classList.contains('mention')) {
targetMention = prevSibling
break
}
//
if (prevSibling.nodeType === Node.TEXT_NODE && prevSibling.textContent.trim()) {
break
}
prevSibling = prevSibling.previousSibling
}
}
} }
} else if (container.nodeType === Node.ELEMENT_NODE) { } else if (container.nodeType === Node.ELEMENT_NODE) {
// //
@ -578,29 +525,24 @@ const sendMessage = () => {
if (messageData.items.length === 0 || if (messageData.items.length === 0 ||
(messageData.items.length === 1 && (messageData.items.length === 1 &&
messageData.items[0].type === 1 && messageData.items[0].type === 1 &&
!messageData.items[0].content.trimEnd())) { !messageData.items[0].content.trim())) {
return // return //
} }
// //
messageData.items.forEach(item => { messageData.items.forEach(item => {
// //
if (item.type === 1 && item.content.trimEnd()) { if (item.type === 1 && item.content.trim()) {
const data = { const data = {
items: [{ items: [{
content: item.content, content: item.content,
type: 1 type: 1
}], }],
mentionUids: messageData.mentionUids, mentionUids: messageData.mentionUids,
mentions: messageData.mentionUids.map(uid => { mentions: [],
return {
atid: uid,
name: mentionList.value.find(member => member.id === uid)?.nickname || ''
}
}),
quoteId: messageData.quoteId, quoteId: messageData.quoteId,
} }
console.log('data',data)
emit( emit(
'editor-event', 'editor-event',
emitCall('text_event', data) emitCall('text_event', data)
@ -660,7 +602,7 @@ const parseEditorContent = () => {
// @ // @
const userId = node.getAttribute('data-user-id') const userId = node.getAttribute('data-user-id')
if (userId) { if (userId) {
mentionUids.push(Number(userId)) mentionUids.push(parseInt(userId))
} }
textContent += node.textContent textContent += node.textContent
} else if (node.tagName === 'IMG') { } else if (node.tagName === 'IMG') {
@ -692,7 +634,7 @@ const parseEditorContent = () => {
if (textContent.trim()) { if (textContent.trim()) {
items.push({ items.push({
type: 1, type: 1,
content: textContent.trimEnd() content: textContent.trim()
}) })
textContent = '' textContent = ''
} }
@ -732,7 +674,7 @@ const parseEditorContent = () => {
if (textContent.trim()) { if (textContent.trim()) {
items.push({ items.push({
type: 1, type: 1,
content: textContent.trimEnd() content: textContent.trim()
}) })
textContent = '' textContent = ''
} }
@ -754,7 +696,7 @@ const parseEditorContent = () => {
if (textContent.trim()) { if (textContent.trim()) {
items.push({ items.push({
type: 1, type: 1,
content: textContent.trimEnd() content: textContent.trim()
}) })
} }
@ -976,44 +918,27 @@ const insertImageEmoji = (imgSrc, altText) => {
} }
// //
const onSubscribeMention = async (data) => { const onSubscribeMention = (data) => {
const editorNode = editorRef.value; //
if (!editorNode) return; editorRef.value?.focus()
editorNode.focus(); //
await nextTick(); // const selection = window.getSelection()
if (!selection.rangeCount || !editorRef.value.contains(selection.anchorNode)) {
let selection = window.getSelection(); const range = document.createRange()
if (!selection || selection.rangeCount === 0) { if (editorRef.value.lastChild) {
const range = document.createRange(); range.setStartAfter(editorRef.value.lastChild)
if (editorNode.lastChild) {
range.setStartAfter(editorNode.lastChild);
} else { } else {
range.setStart(editorNode, 0); range.setStart(editorRef.value, 0)
} }
range.collapse(true); range.collapse(true)
if (selection) selection.removeAllRanges(); selection.removeAllRanges()
selection?.addRange(range); selection.addRange(range)
await nextTick();
selection = window.getSelection();
} else if (!editorNode.contains(selection.anchorNode)) {
const range = document.createRange();
if (editorNode.lastChild) {
range.setStartAfter(editorNode.lastChild);
} else {
range.setStart(editorNode, 0);
}
range.collapse(true);
selection.removeAllRanges();
selection.addRange(range);
await nextTick();
selection = window.getSelection();
} }
if (selection && selection.rangeCount > 0) { // @
insertMention(data, selection.getRangeAt(0).cloneRange()); insertMention(data)
} }
};
const onSubscribeQuote = (data) => { const onSubscribeQuote = (data) => {
// 稿 // 稿
@ -1506,7 +1431,7 @@ const handleEditorClick = (event) => {
<n-icon size="18" class="icon" :component="nav.icon" /> <n-icon size="18" class="icon" :component="nav.icon" />
<p class="tip-title">{{ nav.title }}</p> <p class="tip-title">{{ nav.title }}</p>
</div> </div>
<n-button class="w-80px h-30px ml-auto" type="primary" @click="sendMessage"> <n-button class="w-80px h-30px ml-auto" type="primary">
<template #icon> <template #icon>
<n-icon> <n-icon>
<IosSend /> <IosSend />
@ -1558,7 +1483,7 @@ const handleEditorClick = (event) => {
:key="member.user_id || member.id" :key="member.user_id || member.id"
class="cursor-pointer px-14px h-42px" class="cursor-pointer px-14px h-42px"
:class="{ 'bg-#EEE9F9': index === selectedMentionIndex }" :class="{ 'bg-#EEE9F9': index === selectedMentionIndex }"
@mousedown.prevent="handleMentionSelectByMouse(member)" @mousedown.prevent="insertMention(member)"
@mouseover="selectedMentionIndex = index" @mouseover="selectedMentionIndex = index"
> >
<div class="flex items-center border-b-1px border-b-solid border-b-#F8F8F8 h-full"> <div class="flex items-center border-b-1px border-b-solid border-b-#F8F8F8 h-full">

View File

@ -1,5 +1,4 @@
import { reactive } from 'vue' import { reactive } from 'vue'
import dayjs from 'dayjs'
import { useDialogueStore } from '@/store/modules/dialogue.js' import { useDialogueStore } from '@/store/modules/dialogue.js'
interface IDropdown { interface IDropdown {
@ -15,10 +14,11 @@ const isRevoke = (uid: any, item: any): boolean => {
return false return false
} }
const messageTime = dayjs(item.created_at) const datetime = item.created_at.replace(/-/g, '/')
const now = dayjs()
const diffInMinutes = now.diff(messageTime, 'minute') const time = new Date().getTime() - Date.parse(datetime)
return diffInMinutes <= 5
return Math.floor(time / 1000 / 60) <= 2
} }
const dialogueStore = useDialogueStore() const dialogueStore = useDialogueStore()
export function useMenu() { export function useMenu() {
@ -48,20 +48,9 @@ export function useMenu() {
dropdown.options.push({ label: '多选', key: 'multiSelect' }) dropdown.options.push({ label: '多选', key: 'multiSelect' })
dropdown.options.push({ label: '引用', key: 'quote' }) dropdown.options.push({ label: '引用', key: 'quote' })
//如果是单聊 if (isRevoke(uid, item)|| (dialogueStore.groupInfo as any).is_manager) {
if(item.talk_type===1){ dropdown.options.push({ label: `撤回`, key: 'revoke' })
//撤回时间限制内,并且是自己发的
if(isRevoke(uid, item)&&item.float==='right'){
dropdown.options.push({ label: `撤回`, key: 'revoke' })
}
//群聊
}else if(item.talk_type===2){
//管理员可以强制撤回所有成员信息
if ((dialogueStore.groupInfo as any).is_manager) {
dropdown.options.push({ label: `撤回`, key: 'revoke' })
}
} }
dropdown.options.push({ label: '删除', key: 'delete' }) dropdown.options.push({ label: '删除', key: 'delete' })