@我功能。

This commit is contained in:
谢思 2021-11-10 16:47:22 +08:00
parent b1ead191a6
commit f003a99779
4 changed files with 197 additions and 182 deletions

View File

@ -224,7 +224,7 @@ function NoticeContent({ visible, showNotification, resetUserInfo, current_user:
<Badge color="#FA2020" />
</span>
<div className="noticeCont-text">
<span className="content-span atme-cont-span" dangerouslySetInnerHTML={{ __html: "<b>" + (item.sender ? item.sender.name : '') + "</b>&nbsp;&nbsp;&nbsp;" + item.content + " 中@我" }}></span>
<span className="content-span atme-cont-span" dangerouslySetInnerHTML={{ __html: item.content }}></span>
<span className="timeSpan">{item.time_ago}</span>
</div>
</div>

View File

@ -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) {
</div>
</div>}
{messageList && messageList.map(item => {
{/* 系统消息 */}
{messageType === "notification" && messageList && messageList.map(item =>{
return (
<div className="mynotice-content vertical-center-style" key={item.id}>
<div className="mynotice-cont stretch-style">
{item.status === 1 ? <Badge color="#FA2020" /> : <span className="system-notice-blank"></span>}
<i className={"iconfont "+noticeSourceType[item.source]}></i>
<span className={`sysNotice-length ${item.notification_url?'highlightSpan':''}`} onClick={() => {turnToMess(item)}} dangerouslySetInnerHTML={{__html: item.content}}></span>
</div>
<div className="mynotice-cont vertical-center-style float-left-little">
<span className={item.status === 1?"timeSpan":""}>{item.time_ago}</span>
{item.status === 1 && <span className="invisable-read" onClick={()=>readNotice([item.id])}>标记为已读</span>}
</div>
</div>
)
})}
{/* @我消息 */}
{messageType === "atme" && messageList && messageList.map(item =>{
return (
<div className={`mynotice-content vertical-center-style ${isBatchDelete?'batchDel':''}`} key={item.id}>
<div className="mynotice-cont vertical-center-style">
<Checkbox value={item.id} className={isBatchDelete ? 'visible-checkbox' : 'invisible-checkbox'} onChange={onChange} checked={item.checkedBatch}></Checkbox>
{item.sender && <img src={`https://testforgeplus.trustie.net//${item.sender.image_url}`} className="currentImg" onClick={()=>{window.open(`/${item.sender && item.sender.login}`);}}/>}
<div className={`atme-notice-text stretch-style ${item.notification_url && 'highlightSpan'}`} onClick={() => {turnToMess(item)}}>
{item.status === 1 ? <Badge color="#FA2020"/> : <span className="system-notice-blank"></span>}
{item.sender && <span className="atme-length" dangerouslySetInnerHTML={{__html: item.content}}></span>}
</div>
</div>
<div className="mynotice-cont vertical-center-style">
<span className={!isBatchDelete && item.status === 1?"timeSpan":""}>{item.time_ago}</span>
{!isBatchDelete && item.status === 1 && <span className="invisable-read" onClick={()=>readNotice([item.id])}>标记为已读</span>}&nbsp;&nbsp;&nbsp;
{!isBatchDelete && <span className="invisable-read float-left-little" onClick={()=>deleteNotice([item.id])}>删除</span>}
</div>
</div>
)
})}
{false && messageList && messageList.map(item => {
console.log('item',item);
//
if (noticeType === "0") {
//
@ -229,7 +269,7 @@ function MyNotice(props) {
{item.sender && <img src={`https://testforgeplus.trustie.net//${item.sender.image_url}`} className="currentImg" onClick={()=>{window.open(`/${item.sender && item.sender.login}`);}}/>}
<div className={`atme-notice-text stretch-style ${item.notification_url && 'highlightSpan'}`} onClick={() => {turnToMess(item)}}>
{item.status === 1 ? <Badge color="#FA2020"/> : <span className="system-notice-blank"></span>}
{item.sender && <span className="atme-length" dangerouslySetInnerHTML={{__html: "<b class = 'atme-notice-name'>" + item.sender.name+ "</b> "+ item.content +" 中@我"}}></span>}
{item.sender && <span className="atme-length" dangerouslySetInnerHTML={{__html: "<b class = 'atme-notice-name'>" + item.sender.name+ "</b> "+ item.content}}></span>}
</div>
</div>
<div className="mynotice-cont vertical-center-style">

View File

@ -26,7 +26,7 @@
border-bottom: 1px solid rgba(212, 212, 212, 0.5);
padding: 0 4px;
}
.active{
.at_who.active{
background: #F3F4F6;
}
.at_who img{
@ -39,8 +39,4 @@
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.blur_atWho{
position: absolute;
top: -100px;
}

View File

@ -75,30 +75,35 @@ function md_elocalStorage(editor, mdu, id) {
}
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 [search, setSeacrch] = useState(undefined);
//可以@的全部用户
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(()=>{
isCanAtme && axios.get(`/${owner}/${projectsId}/members.json`,{
// params: {
// search: 'admin',
// },
}).then(response=>{
if(response && response.status === 200){
//请求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() {
@ -119,14 +124,16 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
}
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("@")+1;
//替换最后的内容
cm.replaceRange(username+" ",{line,ch},{line,ch});
cm.replaceRange(username+" ",{line,ch:startIndex},{line,ch});
//鼠标聚焦
cm.focus();
//将此user的login存储到atWhoLoginList集合中
@ -136,155 +143,58 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
})
atWhoLoginList.current = Array.from(list);
setAtWhoLoginListState(Array.from(list));
//销毁atWhoKeyDown键盘监听事件
// document.removeEventListener("keydown",atWhoKeyDown);
}
function onMouseOver(key){
document.getElementsByClassName("at_who active")[0].className="at_who";
document.getElementsByClassName("at_who")[key].className="at_who active";
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){
console.log("markdown编辑器--------键盘监听事件");
// document.onkeydown = e=>{
if (e.shiftKey && e.key === "@") {
// 输入@键后在对应的位置显示可选的项目成员
setAtWhoVisible(true);
//获取光标位置
const cssStyle = document.getElementsByClassName("CodeMirror cm-s-default CodeMirror-wrap")[0].firstChild.style;
//设置弹框位置
document.getElementById("at_who_list").style.top = (parseInt(cssStyle.getPropertyValue("top").replace("px",""))+60)+"px";
document.getElementById("at_who_list").style.left = (parseInt(cssStyle.getPropertyValue("left").replace("px",""))+20)+"px";
//将第一个用户默认选中
const at_who_divs = document.getElementsByClassName("at_who");
at_who_divs[0].className = "at_who active";
}
//处理本来@了某人 -> 删掉 -> 撤回 的情况
if(e.ctrlKey && e.code === "KeyZ" && users.length != 0){
const codemirror = editorInstance.cm;
let value = codemirror.getValue();
//处理初始内容就自带@谁的情况
if(initValue){
const del = [];
users.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}`;
}
})
del.length!=0 && del.map(str=>{
value = value.replace(str,"");
})
}
//判断value是否包含@符号
value.indexOf("@") != -1 && users.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));
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}`;
}
})
del.length!=0 && del.map(str=>{
value = value.replace(str,"");
})
}
// const atWhoListDiv = document.getElementById("at_who_list");
// if(atWhoListDiv && (e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "Enter")){
// return false;
// }
// const atWhoDivs = document.getElementsByClassName("at_who");
// let index;
// for(let i = 0; i<atWhoDivs.length;i++){
// atWhoDivs[i].className === "at_who active" && (index = i);
// }
// //当可选@列表div弹出之后监听上下键不能通过判断atWhoVisible因为atWhoVisible还未更新为true
// if(e.key === "ArrowUp" && index > 0){
// e.preventDefault();
// index <=atWhoDivs.length-4 && (atWhoListDiv.scrollTop -=40)
// atWhoDivs[index].className = "at_who";
// atWhoDivs[index-1].className = "at_who active";
// }
// if(e.key === "ArrowDown"){
// e.preventDefault();
// console.log("ArrowDown",atWhoVisible);
// const atWhoListDiv = document.getElementById("at_who_list");
// const atWhoDivs = document.getElementsByClassName("at_who");
// let index;
// for(let i = 0; i<atWhoDivs.length;i++){
// atWhoDivs[i].className === "at_who active" && (index = i);
// }
// index >=3 && (atWhoListDiv.scrollTop +=40)
// atWhoDivs[index].className = "at_who";
// atWhoDivs[index+1].className = "at_who active";
// }
// if(e.key === "Enter"){
// //阻止默认事件
// e.preventDefault();
// //找到classname为at_who active的div执行click事件
// document.getElementsByClassName("at_who active")[0].click();
// }
// editorInstance.addKeyMap({
// 'ArrowUp':()=>{
// index <=atWhoDivs.length-4 && (atWhoListDiv.scrollTop -=40)
// atWhoDivs[index].className = "at_who";
// atWhoDivs[index-1].className = "at_who active";
// },
// 'ArrowDown':()=>{
// index >=3 && (atWhoListDiv.scrollTop +=40)
// atWhoDivs[index].className = "at_who";
// atWhoDivs[index+1].className = "at_who active";
// },
// 'Enter':()=>{
// //找到classname为at_who active的div执行click事件
// document.getElementsByClassName("at_who active")[0].click();
// },
// })
// }
// }
//判断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));
}
})
}
}
//弹出可选@用户列表之后的键盘监听事件
function atWhoKeyDown(e){
//监听上下和enter键
// document.onkeydown = (e) =>{
const atWhoListDiv = document.getElementById("at_who_list");
const atWhoDivs = document.getElementsByClassName("at_who");
let index;
for(let i = 0; i<atWhoDivs.length;i++){
atWhoDivs[i].className === "at_who active" && (index = i);
}
console.log("监听上下和enter键",e,atWhoListDiv,atWhoDivs,index);
if(e.key === "ArrowUp" && index > 0){
index <=atWhoDivs.length-4 && (atWhoListDiv.scrollTop -=40)
atWhoDivs[index].className = "at_who";
atWhoDivs[index-1].className = "at_who active";
return;
}
if(e.key === "ArrowDown" && index < atWhoDivs.length-1){
index >=3 && (atWhoListDiv.scrollTop +=40)
atWhoDivs[index].className = "at_who";
atWhoDivs[index+1].className = "at_who active";
return;
}
if(e.key === "Enter"){
//阻止默认事件
e.preventDefault();
//找到classname为at_who active的div执行click事件
document.getElementsByClassName("at_who active")[0].click();
}
// }
}
//点击其他地方关闭弹框
useEffect(()=>{
document.addEventListener('click',()=>{setAtWhoVisible(false)})
},[])
useEffect(()=>{
console.log('@谁列表发生变化,atWhoLoginList.current',atWhoLoginList.current);
changeAtWhoLoginList && changeAtWhoLoginList(atWhoLoginListState);
},[atWhoLoginListState])
@ -292,8 +202,8 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
<div className="at_who_list" id="at_who_list" >
{users && users.map((item,key)=>{
return(
<div key={key} className="at_who" onClick={()=>{selectAtWho(item.username)}} onMouseOver={()=>{onMouseOver(key)}}>
{item.image_url && <img src={getImageUrl(item.image_url)}></img>}
<div key={key} className={`at_who ${key===0 && `active`}`} onClick={()=>{selectAtWho(item.username)}} onMouseOver={()=>{onMouseOver(key)}}>
{item.image_url && <img src={getImageUrl(`/${item.image_url}`)}></img>}
<span>{item.username}</span>
</div>
)
@ -301,25 +211,6 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
</div>
)
useEffect(()=>{
//当atWhoVisible为true的时候失焦监听上下和enter键
// atWhoVisible && editorInstance.cm.
if(atWhoVisible){
const cm = editorInstance.cm;
//获取鼠标所在行的行数和ch
const line = cm.doc.getCursor().line;//行
console.log('useEffect',cm.getLine(line));
// document.activeElement.id !== "blur_atWho" && document.getElementById("blur_atWho").focus();
// const atWhoDivs = document.getElementsByClassName("at_who");
// let index = 0;
// for(let i = 0; i<atWhoDivs.length;i++){
// atWhoDivs[i].className === "at_who active" && (index = i);
// }
// const atWhoListDiv = document.getElementById("at_who_list");
// document.addEventListener("keydown",atWhoKeyDown);
}
},[atWhoVisible])
useEffect(() => {
if (editorInstance) {
return
@ -402,6 +293,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; i<atWhoDivs.length;i++){
atWhoDivs[i].className === "at_who active" && (index = i);
}
if(index>0){
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<atWhoDivs.length;i++){
atWhoDivs[i].className === "at_who active" && (index = i);
}
if(index<atWhoDivs.length-1){
index >=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
@ -426,7 +380,34 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
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;
@ -434,7 +415,7 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
//处理初始内容就自带@谁的情况
if(initValue){
const del = [];
users.map(item=>{
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}`;
@ -447,7 +428,7 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
//以username为主键login为value的map集合
let atWhoMap = new Map();
Array.from(atWhoLoginList.current).map(item=>{
users.map(i=>{
allUsers.map(i=>{
if(i.login === item){
atWhoMap.set(i.username,i.login);
}
@ -471,8 +452,6 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
return;
}
//处理有名字但是无@符号,有@但是名字对不上的情况
const a = value.indexOf(username)===-1;
const b = value.charAt(value.indexOf(username)-1) !="@";
if(value.indexOf(username)===-1 || value.charAt(value.indexOf(username)-1) !="@"){
//符合任意一种情况->踢掉这个人 不给他发消息
const list = new Set(atWhoLoginList.current);
@ -541,14 +520,14 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
}, [
editorInstance, resizeBarEl
])
return (
<Fragment>
{atWhoVisible && atWhoList}
<div ref={editorEl} className={`df ${className} ${imageExpand && 'editormd-image-click-expand'} `}>
<div className={`edu-back-greyf5 radius4 editormd ${error ? 'error' : ''}`} id={containerId} >
<textarea style={{ display: 'none' }} id={editorBodyId} name="content"></textarea>
<div className="CodeMirror cm-s-defualt"></div>
<input id ="blur_atWho" className="blur_atWho"/>
{atWhoVisible && atWhoList}
</div>
</div>
{showResizeBar ? <a ref={resizeBarEl} className='editor-resize'></a> : null}