diff --git a/src/forge/Head/NoticeContent.jsx b/src/forge/Head/NoticeContent.jsx index 634a7adc..39546707 100644 --- a/src/forge/Head/NoticeContent.jsx +++ b/src/forge/Head/NoticeContent.jsx @@ -224,7 +224,7 @@ function NoticeContent({ visible, showNotification, resetUserInfo, current_user:
- " + (item.sender ? item.sender.name : '') + "   " + item.content + " 中@我" }}> + {item.time_ago}
diff --git a/src/forge/Merge/Files.jsx b/src/forge/Merge/Files.jsx index b4cf5b48..6661c1d3 100644 --- a/src/forge/Merge/Files.jsx +++ b/src/forge/Merge/Files.jsx @@ -18,7 +18,7 @@ function Files({ data,history,owner,projectsId , parentsSha }){ useEffect(()=>{ document.addEventListener('click',()=>{setIsOpen(false)}) - }) + },[]) function showDown(flag,index,isBin){ if(!isBin){ diff --git a/src/forge/Merge/UpdateMerge.js b/src/forge/Merge/UpdateMerge.js index 8918db6e..8af84941 100644 --- a/src/forge/Merge/UpdateMerge.js +++ b/src/forge/Merge/UpdateMerge.js @@ -54,7 +54,7 @@ class UpdateMerge extends Component { const { data, isSpin, pull, merge } = this.state; return (
-
+
{" "} {data ? ( diff --git a/src/forge/Merge/merge.css b/src/forge/Merge/merge.css index d61543b4..0ac43c7d 100644 --- a/src/forge/Merge/merge.css +++ b/src/forge/Merge/merge.css @@ -222,4 +222,8 @@ form .ant-cascader-picker, form .ant-select { .overlihide li{ max-width: 450px; + } + /* 距离底部加大 @列表被遮挡 */ + .updateMerge{ + margin: 30px auto 60px; } \ No newline at end of file diff --git a/src/forge/Merge/merge_form.js b/src/forge/Merge/merge_form.js index e6378139..d050116f 100644 --- a/src/forge/Merge/merge_form.js +++ b/src/forge/Merge/merge_form.js @@ -25,6 +25,7 @@ class MergeForm extends Component { issue_tags: undefined, issue_versions: undefined, issue_priories: undefined, + atWhoLoginList:undefined }; } @@ -146,7 +147,7 @@ class MergeForm extends Component { } else { values.issue_tag_ids = []; } - const { desc } = this.state; + const { desc , atWhoLoginList } = this.state; if (merge_type === "new") { let url = `/${owner}/${projectsId}/pulls.json`; axios.post(url, { @@ -158,7 +159,8 @@ class MergeForm extends Component { fork_project_id: data && data.fork_project_id, merge_user_login: data && data.merge_user_login, files_count, - commits_count + commits_count, + receivers_login:atWhoLoginList, }) .then((result) => { if (result) { @@ -189,6 +191,7 @@ class MergeForm extends Component { body: desc, head: pull, base: merge, + receivers_login:atWhoLoginList, }) .then((result) => { if (result) { @@ -225,6 +228,13 @@ class MergeForm extends Component { }); }; + //合并请求中at谁列表(存储:login) + changeAtWhoLoginList = (loginList) =>{ + this.setState({ + atWhoLoginList:loginList, + }); + }; + render() { const { merge_type } = this.props; const { getFieldDecorator } = this.props.form; @@ -273,6 +283,10 @@ class MergeForm extends Component { mdID={"merge-new-description"} initValue={desc} onChange={this.onContentChange} + isCanAtme = {true} + changeAtWhoLoginList = {this.changeAtWhoLoginList} + owner = {owner} + projectsId = {projectsId} >

{get_attachments && get_attachments.length > 0 ? ( diff --git a/src/forge/SecuritySetting/notice/myNotice/Index.jsx b/src/forge/SecuritySetting/notice/myNotice/Index.jsx index 22660691..3d495722 100644 --- a/src/forge/SecuritySetting/notice/myNotice/Index.jsx +++ b/src/forge/SecuritySetting/notice/myNotice/Index.jsx @@ -17,7 +17,7 @@ function MyNotice(props) { const [selectedNum, setSelectedNum] = useState(0);//@我批量删除选择消息条数 const [isBatchDelete, setIsBatchDelete] = useState(false);//@我是否批量删除 const [batchDeleteCheckedAll, setBatchDeleteCheckAll] = useState(false);//@我批量删除--全选 - + const [messageType, setMessageType] = useState(undefined); const [noticeUnreadCount, setNoticeUnreadCount] = useState();//未读系统通知数量 // const [letterUnreadCount, setLetterUnreadCount] = useState(0);//未读私信数量 const [atUnreadCount, setAtUnreadCount] = useState();//未读@我数量 @@ -65,6 +65,7 @@ function MyNotice(props) { setAtUnreadCount(response.data.unread_atme); setMessageList(response.data.messages); setMessTotalCount(response.data.total_count); + setMessageType(response.data.type); } }); } @@ -203,7 +204,46 @@ function MyNotice(props) {
} - {messageList && messageList.map(item => { + {/* 系统消息 */} + {messageType === "notification" && messageList && messageList.map(item =>{ + return ( +
+
+ {item.status === 1 ? : } + + {turnToMess(item)}} dangerouslySetInnerHTML={{__html: item.content}}> +
+
+ {item.time_ago} + {item.status === 1 && readNotice([item.id])}>标记为已读} +
+
+ ) + })} + + {/* @我消息 */} + {messageType === "atme" && messageList && messageList.map(item =>{ + return ( +
+
+ + {item.sender && {window.open(`/${item.sender && item.sender.login}`);}}/>} +
{turnToMess(item)}}> + {item.status === 1 ? : } + {item.sender && } +
+
+
+ {item.time_ago} + {!isBatchDelete && item.status === 1 && readNotice([item.id])}>标记为已读}    + {!isBatchDelete && deleteNotice([item.id])}>删除} +
+
+ ) + })} + + {false && messageList && messageList.map(item => { + console.log('item',item); // 系统消息 if (noticeType === "0") { // 消息类别 @@ -229,7 +269,7 @@ function MyNotice(props) { {item.sender && {window.open(`/${item.sender && item.sender.login}`);}}/>}
{turnToMess(item)}}> {item.status === 1 ? : } - {item.sender && " + item.sender.name+ " "+ item.content +" 中@我"}}>} + {item.sender && " + item.sender.name+ " "+ item.content}}>}
diff --git a/src/forge/UsersList/fork_users.js b/src/forge/UsersList/fork_users.js index 23cd5bea..a3d9c2a2 100644 --- a/src/forge/UsersList/fork_users.js +++ b/src/forge/UsersList/fork_users.js @@ -27,7 +27,7 @@ class ForkUsers extends Component { }); const { projectsId , owner } = this.props.match.params; - const url = `/${owner}/${projectsId}/members.json`; + const url = `/${owner}/${projectsId}/forks.json`; axios .get(url, { params: { diff --git a/src/forge/comments/comments.js b/src/forge/comments/comments.js index dca7581f..fb19e1da 100644 --- a/src/forge/comments/comments.js +++ b/src/forge/comments/comments.js @@ -30,6 +30,7 @@ class comments extends Component { reply_id: undefined, reply_content: undefined, new_journal_id: undefined, + atWhoLoginList:undefined }; } @@ -51,6 +52,7 @@ class comments extends Component { }); return; } + this.props.form.validateFieldsAndScroll((err, values) => { if (!err) { const { @@ -60,10 +62,12 @@ class comments extends Component { orderId, reply_id, is_reply, + atWhoLoginList, } = this.state; - const url = `/issues/${orderId}/journals.json`; + + const url = `/issues/${orderId}/journals.json`; axios .post(url, { ...values, @@ -71,6 +75,7 @@ class comments extends Component { issue_id: orderId, attachment_ids: fileList, parent_id: reply_id, + receivers_login:atWhoLoginList, }) .then((result) => { if (result && result.data.status === 0) { @@ -248,18 +253,29 @@ class comments extends Component { onContentChange = (value) => { if (value) { this.setState({ - content: value, quillFlag: false, }); } + this.setState({ + content: value, + }); }; replyContentChange = (value) => { if (value) { this.setState({ - reply_content: value, quillFlag: false, }); } + this.setState({ + reply_content: value, + }); + }; + + //评论中at谁列表(存储:login) + changeAtWhoLoginList = (loginList) =>{ + this.setState({ + atWhoLoginList:loginList, + }); }; onRef = (ref) => { @@ -308,6 +324,7 @@ class comments extends Component { new_journal_id, } = this.state; const { current_user, only_show_content } = this.props; + const { projectsId ,owner } = this.props.match.params; const new_comment = (is_reply, item_id) => { return ( @@ -339,6 +356,10 @@ class comments extends Component { onChange={ is_reply ? this.replyContentChange : this.onContentChange } + isCanAtme = {true} + changeAtWhoLoginList = {this.changeAtWhoLoginList} + owner = {owner} + projectsId = {projectsId} >

{quillFlag && 请输入评论内容} diff --git a/src/modules/tpm/challengesnew/css/newquestion.css b/src/modules/tpm/challengesnew/css/newquestion.css index 444ca8d1..eb1a67eb 100644 --- a/src/modules/tpm/challengesnew/css/newquestion.css +++ b/src/modules/tpm/challengesnew/css/newquestion.css @@ -4,4 +4,39 @@ .Permanentban{ color:#5091FF !important; border-color: #5091FF !important; +} + +/*md编辑器中输入@弹出可选人列表样式*/ +.at_who_list{ + position: absolute; + z-index: 100; + width: 180px; + max-height: 160px; + background: #FFFFFF; + box-shadow: 0px 4px 8px 2px rgba(212, 212, 212, 0.5); + border-radius: 4px; + overflow-y: scroll; + cursor: pointer; +} +.at_who{ + height: 40px; + display: flex; + flex-direction: row; + align-items: center; + border-bottom: 1px solid rgba(212, 212, 212, 0.5); + padding: 0 4px; +} +.at_who.active{ + background: #F3F4F6; +} +.at_who img{ + width:30px; + height:30px; + border-radius:50%; + margin-right: 10px; +} +.at_who span{ + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } \ No newline at end of file diff --git a/src/modules/tpm/challengesnew/tpm-md-editor.js b/src/modules/tpm/challengesnew/tpm-md-editor.js index e1c06eda..46c01ec3 100644 --- a/src/modules/tpm/challengesnew/tpm-md-editor.js +++ b/src/modules/tpm/challengesnew/tpm-md-editor.js @@ -1,11 +1,12 @@ - import React, { Fragment, useEffect, useRef, useState } from 'react'; import { getUploadActionUrl, getUrl } from 'educoder'; import ResizeObserver from 'resize-observer-polyfill'; - +import { getImageUrl } from 'educoder'; +import axios from 'axios'; import '../../courses/css/Courses.css'; import './css/TPMchallengesnew.css'; import 'codemirror/lib/codemirror.css'; +import './css/newquestion.css'; const $ = window.$ const mdIcons = ["bold", "italic", "|", "list-ul", "list-ol", "|", "code", "code-block", "link", "|", "inline-latex", "latex", '|', "image", "table", '|', "line-break", "watch", "clear"]; @@ -39,7 +40,6 @@ function md_rec_data(k, mdu, id) { } window.md_rec_data = md_rec_data; - function md_elocalStorage(editor, mdu, id) { let oc = window.sessionStorage.getItem('content' + mdu) if (oc !== null && oc !== editor.getValue()) { @@ -74,16 +74,38 @@ function md_elocalStorage(editor, mdu, id) { return tid } - -export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, className = '', noStorage = false, imageExpand = true, placeholder = '', width = '100%', height = 400, initValue = '', emoji, watch, showNullButton = false, showResizeBar = false, startInit = true , forMember = true }) => { - +export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, className = '', noStorage = false, imageExpand = true, placeholder = '', width = '100%', height = 400, initValue = '', emoji, watch, showNullButton = false, showResizeBar = false, startInit = true , forMember = true , isCanAtme = false , changeAtWhoLoginList, owner, projectsId }) => { + const editorEl = useRef(); const resizeBarEl = useRef(); const [editorInstance, setEditorInstance] = useState(); + const [atWhoVisible, setAtWhoVisible] = useState(false); + const [atWhoLoginListState, setAtWhoLoginListState] = useState([]); + //调用member.json接口获取到的用户列表 + const [users, setUsers] = useState([]); + //可以@的全部用户 + const [allUsers, setAllUsers] = useState([]); + const atWhoLoginList = useRef([]); + const atWhoVisibleRef = useRef(false); const containerId = `mdEditor_${mdID}`; const editorBodyId = `mdEditors_${mdID}`; const tipId = `e_tips_mdEditor_${mdID}`; + useEffect(()=>{ + //请求members接口获取全部可@列表 + isCanAtme && axios.get(`/${owner}/${projectsId}/members.json`).then(response=>{ + if(response.data.total_count !== 0){ + setAllUsers(response.data.users); + setUsers(response.data.users); + } + }) + //点击其他地方关闭弹框 + document.addEventListener('click',()=>{ + atWhoVisibleRef.current = false; + setAtWhoVisible(false); + }) + },[]) + function onLayout() { let ro; if (editorEl.current) { @@ -101,6 +123,96 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla return ro; } + function selectAtWho(username){ + atWhoVisibleRef.current = false; + setAtWhoVisible(false); + const cm = editorInstance.cm; + //获取鼠标所在行的行数和ch + const cursor = cm.doc.getCursor(); + const line = cursor.line;//行 + const ch = cursor.ch;//列 + const startIndex = cm.getRange({line,ch:0},{line,ch}).lastIndexOf("@"); + let selectUserLogin = undefined; + users.map((item)=>{ + item.username === username && (selectUserLogin = item.login); + }) + //替换内容 + cm.replaceRange("[@"+username+"]"+`(/${selectUserLogin}) `,{line,ch:startIndex},{line,ch}); + //鼠标聚焦 + cm.focus(); + //将此user的login存储到atWhoLoginList集合中 + const list = new Set(atWhoLoginList.current); + list.add(selectUserLogin); + atWhoLoginList.current = Array.from(list); + setAtWhoLoginListState(Array.from(list)); + } + + function onMouseOver(key){ + document.getElementsByClassName("at_who active")[0] && (document.getElementsByClassName("at_who active")[0].className="at_who"); + document.getElementsByClassName("at_who")[key] && (document.getElementsByClassName("at_who")[key].className="at_who active"); + } + + //markdown编辑器中输入的键盘监听事件 + function mdKeyDown(e){ + if (e.shiftKey && e.code === "Digit2") { + // 输入@键后在对应的位置显示可选的项目成员 + atWhoVisibleRef.current = true; + setAtWhoVisible(true); + //获取光标位置 + const cssStyle = document.getElementsByClassName("CodeMirror cm-s-default CodeMirror-wrap")[0].firstChild.style; + //设置弹框位置 + const newTop = placeholder === "添加评论..." ? 159: placeholder === "请输入合并请求的描述..." ? 172:62; + const newLeft = placeholder === "添加评论..." ? 80: 20; + document.getElementById("at_who_list").style.top = parseInt(cssStyle.getPropertyValue("top").replace("px","")) + newTop +"px"; + document.getElementById("at_who_list").style.left = parseInt(cssStyle.getPropertyValue("left").replace("px",""))+newLeft+"px"; + } + //处理本来@了某人 -> 删掉 -> 撤回 的情况 + if(e.ctrlKey && e.code === "KeyZ" && allUsers.length != 0){ + const codemirror = editorInstance.cm; + let value = codemirror.getValue(); + //处理初始内容就自带@谁的情况 + if(initValue){ + const del = []; + allUsers.map(item=>{ + if(initValue.indexOf(item.username)!=-1 && initValue.charAt(initValue.indexOf(item.username)-1) === "@" && initValue.indexOf(`@${item.username}`)===value.indexOf(`@${item.username}`)){ + //初始内容中有符合@+名字的格式并且当前内容未删除初始内容 + del[del.length] = `[@${item.username}](/${item.login})`; + } + }) + del.length!=0 && del.map(str=>{ + value = value.replace(str,""); + }) + } + //判断value是否包含@符号 + value.indexOf("@") != -1 && allUsers.map(item =>{ + if(value.indexOf(item.username)!=-1 && value.charAt(value.indexOf(item.username)-1) ==="@"){ + //将此user的login存储到atWhoLoginList集合中 + const list = new Set(atWhoLoginList.current); + list.add(item.login); + atWhoLoginList.current = Array.from(list); + setAtWhoLoginListState(Array.from(list)); + } + }) + } + } + + useEffect(()=>{ + changeAtWhoLoginList && changeAtWhoLoginList(atWhoLoginListState); + },[atWhoLoginListState]) + + const atWhoList = ( +

+ {users && users.map((item,key)=>{ + return( +
{selectAtWho(item.username)}} onMouseOver={()=>{onMouseOver(key)}}> + {item.image_url && } + {item.username} +
+ ) + })} +
+ ) + useEffect(() => { if (editorInstance) { return @@ -183,6 +295,69 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla const cmEl = editorInstance && editorInstance.cm + useEffect(()=>{ + if(atWhoVisibleRef.current){ + // 添加上下键、enter键监听事件 + cmEl.addKeyMap({ + 'Up':()=>{ + const atWhoListDiv = document.getElementById("at_who_list"); + const atWhoDivs = document.getElementsByClassName("at_who"); + let index; + for(let i = 0; i0){ + index <=atWhoDivs.length-4 && (atWhoListDiv.scrollTop -=40) + atWhoDivs[index].className = "at_who"; + atWhoDivs[index-1].className = "at_who active"; + } + }, + 'Down':()=>{ + const atWhoListDiv = document.getElementById("at_who_list"); + const atWhoDivs = document.getElementsByClassName("at_who"); + let index; + for(let i = 0; i=3 && (atWhoListDiv.scrollTop +=40) + atWhoDivs[index].className = "at_who"; + atWhoDivs[index+1].className = "at_who active"; + } + }, + 'Enter':()=>{ + //找到classname为at_who active的div,执行click事件 + if(document.getElementsByClassName("at_who active")[0]){ + document.getElementsByClassName("at_who active")[0].click() + }else{ + const cm = editorInstance.cm; + const cursor = cm.doc.getCursor(); + const line = cursor.line;//行 + const ch = cursor.ch;//列 + //添加换行 + cm.replaceRange("\n",{line,ch},{line,ch}); + setAtWhoVisible(false); + atWhoVisibleRef.current = false; + } + } + }) + } else { + //移除上下、enter键监听 + cmEl && cmEl.removeKeyMap(); + } + },[atWhoVisible]) + + useEffect(()=>{ + //当users数组发生变化时改变框的位置 + if(atWhoVisibleRef.current && users){ + //获取光标位置 + const cssStyle = document.getElementsByClassName("CodeMirror cm-s-default CodeMirror-wrap")[0].firstChild.style; + //设置弹框位置 + const newLeft = placeholder === "添加评论..."? 80: 10; + document.getElementById("at_who_list").style.left = (parseInt(cssStyle.getPropertyValue("left").replace("px",""))+newLeft)+"px"; + } + },[users]) + useEffect(() => { if (cmEl) { let tid = null @@ -198,19 +373,138 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla if (!noStorage) { tid = md_elocalStorage(editorInstance, `MDEditor__${containerId}`, containerId) } - if (onChange) { - editorInstance.cm.on('change', (cm) => { - // if(forMember){ - // document.onkeydown = (e) => { - // if (e.key === "@") { - // // 输入@键后在对应的位置显示可选的项目成员 - - // } - // }; - // } - onChange(cm.getValue()) - }) - } + //isCanAtme:只有issue和合并请求以及评论部分可以@他人操作 + //绑定@事件 + isCanAtme && editorInstance.cm.on("focus", () => { + document.addEventListener("keydown", mdKeyDown); + }); + isCanAtme && editorInstance.cm.on("blur", () => { + document.removeEventListener("keydown",mdKeyDown); + }); + editorInstance.cm.on("change", (cm) => { + //调用父组件的onchange方法,将输入内容传入父级组件 + onChange && onChange(cm.getValue()); + if(atWhoVisibleRef.current){ + //搜索用户(弹框之后用户输入用户名信息) + const cur = cm.doc.getCursor(); + const line = cur.line; + const ch = cur.ch; + let rangeCont = cmEl.getRange({line,ch:0},{line,ch}); + //处理已经弹出列表框,但用户删除@符号 + if(rangeCont.indexOf("@")===-1){ + setAtWhoVisible(false); + atWhoVisibleRef.current = false; + }else{ + rangeCont = rangeCont.substring(rangeCont.lastIndexOf("@")+1); + rangeCont ? axios.get(`/${owner}/${projectsId}/members.json`,{ + params: { + search: rangeCont, + }, + }).then(response=>{ + if(response && response.data && response.data.total_count !== 0){ + setUsers(response.data.users); + }else{ + setUsers(undefined); + } + }):setUsers(allUsers) + } + } + + //当内容发生改变并且有已@列表时 + if(atWhoLoginList.current.length != 0){ + const codemirror = editorInstance.cm; + //startValue:触发change方法时的内容,value:处理了初始内容带@用户的情况 + let startValue = codemirror.getValue(); + let value = codemirror.getValue(); + //处理初始内容就自带@谁的情况 + if(initValue){ + const del = []; + allUsers.map(item=>{ + if(initValue.indexOf(item.username)!=-1 && initValue.charAt(initValue.indexOf(item.username)-1) === "@" && initValue.indexOf(`@${item.username}`)===value.indexOf(`@${item.username}`)){ + //初始内容中有符合@+名字的格式并且当前内容未删除初始内容 + del[del.length] = `[@${item.username}](/${item.login})`; + } + }) + del.length!=0 && del.map(str=>{ + value = value.replace(str,""); + }) + } + //以username为主键,login为value的map集合 + let atWhoMap = new Map(); + Array.from(atWhoLoginList.current).map(item=>{ + allUsers.map(i=>{ + if(i.login === item){ + atWhoMap.set(i.username,i.login); + } + }) + }); + const cursor = codemirror.doc.getCursor(); + const line = cursor.line; + const ch = cursor.ch; + //处理全部内容中不包含“@”的情况 + if(value.indexOf("@") === -1){ + //markdown嵌套的链接删掉 + // Array.from(atWhoMap.keys()).map(username=>{ + // startValue = startValue.replaceAll(`[${username}](/${atWhoMap.get(username)}) `,username); + // }) + //替换全部内容 + // codemirror.setValue(startValue); + //全部内容已经有要@的列表,但是没有@符号 -> 清空@集合 + atWhoLoginList.current = []; + setAtWhoLoginListState([]); + } + + //截取第一个字符到光标的内容 + const curAfterCont = codemirror.getRange({line,ch:0},{line,ch}); + const content = codemirror.getLine(line); + //处理光标所在行 有“@”的情况 + if(content && content.indexOf("@") !== -1){ + Array.from(atWhoMap.keys()).map(username=>{ + //判断content是不是以列表中的某个username结尾 + const userCont = `[@${username}](/${atWhoMap.get(username)})`; + //删除空格->选中@用户区域 + if(curAfterCont.endsWith(userCont)){ + codemirror.setSelection({line,ch:curAfterCont.lastIndexOf("@")-1},{line,ch}); + } + //处理已经有@列表但是value中不包含完整[@用户名](/login)的情况 + if(value.indexOf(userCont)===-1){ + // //markdown嵌套的链接删掉,删[]、()的情况不用处理,markdown会自动认为不是链接 + // //找到[和)的index,将区域内容替换成[]包裹的内容 + // //光标之后的内容 + // const curLeterCont = codemirror.getRange({line,ch},{line,ch:content.length}); + // console.log('光标之后的内容curLeterCont',curLeterCont); + // //删除用户名 -> ]在curLeterCont中 + // //删除login -> ]在curAfterCont中 + // const a = curAfterCont.lastIndexOf('['); + // const b = curLeterCont.indexOf(')') + // const c = curLeterCont.indexOf(']') === -1 ? curAfterCont.lastIndexOf(']') : curLeterCont.indexOf(']')+curAfterCont.length; + // console.log('[',a,')',b,']',c); + // const newCont = codemirror.getRange({line,ch:a+1},{line,ch:c}); + // console.log('newCont',newCont); + // codemirror.replaceRange(newCont,{line,ch:a-1},{line,ch:b+curAfterCont.length+1}) + + //符合情况->踢掉这个人 不给他发消息 + const list = new Set(atWhoLoginList.current); + list.delete(atWhoMap.get(username)); + atWhoLoginList.current = Array.from(list); + setAtWhoLoginListState(Array.from(list)); + } + }) + }else{ + //处理所在行没有“@”的情况 + Array.from(atWhoMap.keys()).map(username=>{ + const userCont = `[@${username}](/${atWhoMap.get(username)})`; + if(value.indexOf(userCont)===-1){ + //符合情况->踢掉这个人 不给他发消息 + const list = new Set(atWhoLoginList.current); + list.delete(atWhoMap.get(username)); + atWhoLoginList.current = Array.from(list); + setAtWhoLoginListState(Array.from(list)); + } + }) + } + } + }); ro = onLayout() return () => { if (!noStorage) { @@ -271,7 +565,8 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla return ( -
+ {atWhoVisible && atWhoList} +