forgeplus-react/src/modules/tpm/challengesnew/tpm-md-editor.js

781 lines
29 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { Fragment, useEffect, useRef, useState } from 'react';
import { getUploadActionUrl, getUrl } from 'educoder';
import ResizeObserver from 'resize-observer-polyfill';
// import myxss from '../../../common/filterXss';
import xss from 'xss';
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","fullscreen"];
const NULL_CH = '▁';
function md_add_data(k, mdu, d) {
window.sessionStorage.setItem(k + mdu, d);
}
// 清空保存的数据
function md_clear_data(k, mdu, id) {
window.sessionStorage.removeItem(k + mdu);
var id1 = "#e_tip_" + id;
var id2 = "#e_tips_" + id;
if (k == 'content') {
$(id2).html(" ");
} else {
$(id1).html(" ");
}
}
window.md_clear_data = md_clear_data
function md_rec_data(k, mdu, id) {
if (window.sessionStorage.getItem(k + mdu) !== null) {
var editor = $("#e_tips_" + id).data('editor');
editor.setValue(window.sessionStorage.getItem(k + mdu));
// /shixuns/b5hjq9zm/challenges/3977/tab=3 setValue可能导致editor样式问题
md_clear_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()) {
$("#e_tips_" + id).data('editor', editor);
let h = '您上次有已保存的数据,是否<a style="cursor: pointer;" class="link-color-blue" onclick="md_rec_data(\'content\',\'' + mdu + '\',\'' + id + '\')">恢复</a> ? / <a style="cursor: pointer;" class="link-color-blue" onclick="md_clear_data(\'content\',\'' + mdu + '\',\'' + id + '\')">不恢复</a>';
$("#e_tips_" + id).html(h)
}
let tid = setInterval(function () {
let d = new Date();
let h = d.getHours();
let m = d.getMinutes();
let s = d.getSeconds();
h = h < 10 ? '0' + h : h;
m = m < 10 ? '0' + m : m;
s = s < 10 ? '0' + s : s;
if (editor.getValue().trim() !== "") {
md_add_data("content", mdu, editor.getValue());
let id2 = "#e_tips_" + id;
let textStart = " 数据已于 ";
let text = textStart + h + ':' + m + ':' + s + " 保存 ";
// 占位符
let oldHtml = $(id2).html();
if (oldHtml && oldHtml !== ' ' && oldHtml.startsWith(textStart) === false) {
$(id2).html(oldHtml.split(' (')[0] + ` (${text})`);
} else {
$(id2).html(text);
}
}
}, 10000)
return tid
}
export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, className = '', noStorage = false, imageExpand = true, placeholder = '', width = '100%', height = 400, initValue = '', emoji, watch=true, showNullButton = false, showResizeBar = false, startInit = true , forMember = true , isCanAtme = false , isQuoteIssue = false , changeAtWhoLoginList, owner, projectsId , isFocus = true}) => {
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}`;
// quote相关
const [issues, setIssues ] = useState([]);
const [quoteVisible, setQuoteVisible] = useState(false);
const [screenIssues, setScreenIssues ] = useState([]);
const quoteVisibleRef = useRef(false);
const chooseIssuesList = useRef([]);
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);
quoteVisibleRef.current = false;
setQuoteVisible(false);
})
isQuoteIssue && getIssueList();
},[])
// 请求issues接口获取全部可quote的issue列表
function getIssueList(keyword){
axios.get(`/v1/${owner}/${projectsId}/issues`,{params:{
only_name:true,keyword,sort_direction:"desc",sort_by:"issues.created_on"
}}).then(result=>{
if(result){
!keyword && setIssues(result.data.issues);
let data = result.data.issues;
setScreenIssues(data && data.length > 0 ? data:undefined);
}
})
}
// # 引用的弹框div
const QuoteDiv = (
<div id="quoteDiv" className="quoteDiv">
{
screenIssues && screenIssues.map((i,k)=>{
return(
<li className={`quote ${k === 0 && "active"}`} onClick={()=>selectQuoteIssue(i)} onMouseOver={()=>onMouseOverQuote(k)}>
<span style={{minWidth:"40px"}}><span className="issueIndex" title={i.project_issues_index}>#{i.project_issues_index}</span></span>
<span className="issueName task-hide">{i.subject}</span>
</li>
)
})
}
</div>
)
// 点击选择要引用的issue
function selectQuoteIssue(i){
quoteVisibleRef.current = false;
setQuoteVisible(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("#");
//替换内容
cm.replaceRange(`[#${i.project_issues_index}](/${owner}/${projectsId}/issues/${i.project_issues_index}) `,{line,ch:startIndex},{line,ch});
//鼠标聚焦
cm.focus();
// 保存引用的issue
const list = new Set(chooseIssuesList.current);
list.add(i.project_issues_index);
chooseIssuesList.current = Array.from(list);
}
function onMouseOverQuote(key){
document.getElementsByClassName("quote active")[0] && (document.getElementsByClassName("quote active")[0].className="quote");
document.getElementsByClassName("quote")[key] && (document.getElementsByClassName("quote")[key].className="quote active");
}
useEffect(()=>{
if(cmEl){
if(quoteVisibleRef.current){
const atWhoListDiv = document.getElementById("quoteDiv");
const atWhoDivs = document.getElementsByClassName("quote");
cmEl.addKeyMap({
'Up':()=>{
let index;
for(let i = 0; i<atWhoDivs.length;i++){
atWhoDivs[i].className === "quote active" && (index = i);
}
if(index>0){
index <=atWhoDivs.length-4 && (atWhoListDiv.scrollTop -=40)
atWhoDivs[index].className = "quote";
atWhoDivs[index-1].className = "quote active";
}
},
'Down':()=>{
let index;
for(let i = 0; i<atWhoDivs.length;i++){
atWhoDivs[i].className === "quote active" && (index = i);
}
if(index<atWhoDivs.length-1){
index >=3 && (atWhoListDiv.scrollTop +=40)
atWhoDivs[index].className = "quote";
atWhoDivs[index+1].className = "quote active";
}
},
'Enter':()=>{
//找到classname为quote active的div执行click事件
if(document.getElementsByClassName("quote active")[0]){
document.getElementsByClassName("quote active")[0].click();
}
}
})
} else {
//移除上下、enter键监听
cmEl.removeKeyMap();
}
}
},[quoteVisible])
function onLayout() {
let ro;
if (editorEl.current) {
ro = new ResizeObserver(entries => {
for (let entry of entries) {
if (entry.target.offsetHeight > 0 || entry.target.offsetWidth > 0) {
editorInstance.resize();
editorInstance.cm.refresh();
isFocus && editorInstance.cm.focus();
}
}
})
ro.observe(editorEl.current);
}
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){ //获取光标位置
const cssStyle = document.getElementsByClassName("CodeMirror cm-s-default CodeMirror-wrap")[0].firstChild.style;
//设置弹框位置
const newTop = 62
const newLeft = 20;
const codemirror = editorInstance.cm;
let value = codemirror.getValue();
if (e.shiftKey && e.code === "Digit2") {
// 输入@键后在对应的位置显示可选的项目成员
atWhoVisibleRef.current = true;
setAtWhoVisible(true);
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){
//处理初始内容就自带@谁的情况
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));
}
})
}
if(e.shiftKey && e.code === "Digit3" && isQuoteIssue){
// 输入#键后在对应的位置显示可选的项目issue
quoteVisibleRef.current = true;
setQuoteVisible(true);
document.getElementById("quoteDiv").style.top = parseInt(cssStyle.getPropertyValue("top").replace("px","")) + newTop +"px";
document.getElementById("quoteDiv").style.left = parseInt(cssStyle.getPropertyValue("left").replace("px",""))+newLeft+"px";
}
}
useEffect(()=>{
changeAtWhoLoginList && changeAtWhoLoginList(atWhoLoginListState);
},[atWhoLoginListState])
const atWhoList = (
<div className="at_who_list" id="at_who_list" >
{users && users.map((item,key)=>{
return(
<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>
)
})}
</div>
)
useEffect(() => {
if (editorInstance) {
return
}
if (!startInit) { return }
const editor_instance = window.editormd(containerId, {
width,
height,
path: getUrl("/editormd/lib/"),
markdown: initValue,
syncScrolling: "single",
tex: true,
tocm: true,
emoji: !!emoji,
taskList: true,
codeFold: true,
searchReplace: true,
htmlDecode: "style,script,iframe",
sequenceDiagram: true,
autoFocus: false,
watch: watch,
saveHTMLToTextarea: true,
dialogMaskOpacity: 0.6,
placeholder: placeholder,
imageUpload: true,
imageFormats: ["jpg", "jpeg", "gif", "png", "bmp", "webp", "JPG", "JPEG", "GIF", "PNG", "BMP", "WEBP"],
imageUploadURL: getUploadActionUrl(),
toolbarIcons: function () {
return showNullButton ? [...mdIcons, 'null-button'] : mdIcons
},
toolbarIconsClass: {
"line-break": "fa-minus",
"fullScreen":"iconfont icon-fangdaicon font-14"
},
toolbarCustomIcons: {
"inline-latex": "<a title='行内公式' class='latex' ><i name='inline-latex' class='fa iconfont icon-hangneigongshi font-14'></i></a>",
"latex": "<a title='多行公式' class='latex' ><i name='latex' class='fa iconfont icon-duohanggongshi font-16'></i></a>",
"null-button": "<a class='pr' title='增加填空'><i class='border-left'><span></span></i><span name='null-button' class='fa fillTip'>点击插入填空项</span><i class='iconfont fa icon-edit font-16' name='null-button'></i></a>",
},
toolbarHandlers: {
"line-break": function (cm, icon, cursor, selection) {
cm.replaceSelection("<br/>")
},
"null-button": function (cm, icon, cursor, selection) {
if (selection === "") {
cm.setCursor(cursor.line, cursor.ch + 1)
}
cm.replaceSelection(NULL_CH)
},
"fullScreen":function(cm,icon,cursor,selection){
icon.addClass("none");
},
"inline-latex": function (cm, icon, cursor, selection) {
cm.replaceSelection("$$" + selection + "$$");
cm.setCursor(cursor.line, cursor.ch + 2);
cm.focus()
},
"latex": function (cm, icon, cursor, selection) {
cm.replaceSelection("```latex\n\n" + selection + "```");
cm.setCursor(cursor.line + 1, 0);
cm.focus()
},
},
lang: {
toolbar: {
"latex": "多行公式",
"line-break": "换行",
"fullScreen":"开启全屏"
}
},
onload: function () {
setEditorInstance(this)
}
})
}, [containerId, editorInstance, startInit])
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
let ro;
if (onCMBlur) {
editorInstance.cm.on('blur', () => { onCMBlur(editorInstance.getValue()) })
}
if (onCMBeforeChange) {
editorInstance.cm.on('beforeChange', (cm, change) => {
onCMBeforeChange(cm, change)
})
}
if (!noStorage) {
tid = md_elocalStorage(editorInstance, `MDEditor__${containerId}`, containerId)
}
//isCanAtme:只有issue和合并请求以及评论部分可以@他人操作
//绑定@事件
(isCanAtme || isQuoteIssue) && editorInstance.cm.on("focus", () => {
document.addEventListener("keydown", mdKeyDown);
});
(isCanAtme || isQuoteIssue) && editorInstance.cm.on("blur", () => {
document.removeEventListener("keydown",mdKeyDown);
});
editorInstance.cm.on("change", (cm) => {
//调用父组件的onchange方法将输入内容传入父级组件
let reg = /alert\((.*?)\)/g;
let v = cm.getValue();
if(v){
let matchvalue = v.match(reg);
if(matchvalue && matchvalue.length>0){
for(var x=0;x<matchvalue.length;x++){
v = v.replace(matchvalue[x],"");
}
}
}
// xss()防止恶意代码如alert等
onChange && onChange(v);
if(quoteVisibleRef.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){
setQuoteVisible(false);
quoteVisibleRef.current = false;
}else{
rangeCont = rangeCont.substring(rangeCont.lastIndexOf("#")+1);
if(rangeCont === " "){
setQuoteVisible(false);
quoteVisibleRef.current = false;
}else{
getIssueList(rangeCont);
}
}
}
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)
}
}
changeValueTime("#",issues,chooseIssuesList.current);
//当内容发生改变并且有已@列表时
if(atWhoLoginList.current.length != 0){
const codemirror = editorInstance.cm;
//startValue触发change方法时的内容value处理了初始内容带@用户的情况
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){
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){
//符合情况->踢掉这个人 不给他发消息
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) {
clearInterval(tid)
}
if (ro) {
ro.unobserve(editorEl.current)
}
}
}
}, [cmEl])
function changeValueTime(str,alllist,chooselsit){
if(chooselsit.length != 0){
const codemirror = editorInstance.cm;
//startValue触发change方法时的内容value处理了初始内容带@用户的情况
let value = codemirror.getValue();
//处理初始内容就自带@谁的情况
if(initValue){
const del = [];
alllist.map(item=>{
let strings = item.username;
if(str==="#"){
strings = item.project_issues_index;
}
if(initValue.indexOf(strings)!=-1 && initValue.charAt(initValue.indexOf(strings)-1) === str && initValue.indexOf(`${str}${strings}`)===value.indexOf(`${str}${strings}`)){
//初始内容中有符合@+名字的格式并且当前内容未删除初始内容
del[del.length] = `[${str}${strings}](/${strings})`;
if(str==="#"){
del[del.length] = `[${str}${strings}](${owner}/${projectsId}/issues/${strings})`;
}
}
})
del.length!=0 && del.map(str=>{
value = value.replace(str,"");
})
}
//以project_issues_index为主键和value的map集合
let atWhoMap = new Map();
Array.from(chooselsit).map(item=>{
alllist.map(i=>{
if(i.project_issues_index === item){
atWhoMap.set(i.project_issues_index,i.project_issues_index);
}
})
});
const cursor = codemirror.doc.getCursor();
const line = cursor.line;
const ch = cursor.ch;
//处理全部内容中不包含“@”的情况
if(value.indexOf(str) === -1){
if(str==="#"){
chooseIssuesList.current = [];
}
}
//截取第一个字符到光标的内容
const curAfterCont = codemirror.getRange({line,ch:0},{line,ch});
const content = codemirror.getLine(line);
//处理光标所在行 有“@”的情况
if(content && content.indexOf(str) !== -1){
Array.from(atWhoMap.keys()).map(index=>{
//判断content是不是以列表中的某个username结尾
const userCont = `[${str}${index}](/${owner}/${projectsId}/issues/${atWhoMap.get(index)})`;
//删除空格->选中@用户区域
if(curAfterCont.endsWith(userCont)){
codemirror.setSelection({line,ch:curAfterCont.lastIndexOf(str)-1},{line,ch});
}
if(value.indexOf(userCont)===-1){
//符合情况->从数组中删除
if(str==="#"){
const list = new Set(chooseIssuesList.current);
list.delete(atWhoMap.get(index));
chooseIssuesList.current = Array.from(list);
}
}
})
}else{
//处理所在行没有“@”的情况
Array.from(atWhoMap.keys()).map(index=>{
const userCont = `[${str}${index}](/${owner}/${projectsId}/issues/${atWhoMap.get(index)})`;
if(value.indexOf(userCont)===-1){
if(str==="#"){
const list = new Set(chooseIssuesList.current);
list.delete(atWhoMap.get(index));
chooseIssuesList.current = Array.from(list);
}
}
})
}
}
}
useEffect(() => {
if (editorInstance && initValue !== undefined) {
if (initValue !== null && initValue !== editorInstance.getValue()) {
editorInstance.setValue(initValue.toString())
}
}
}, [editorInstance, initValue, containerId])
useEffect(() => {
if (resizeBarEl.current) {
let el = resizeBarEl.current
let dragging = false
let startY = 0
function onMouseDown(e) {
dragging = true
startY = e.pageY
}
function onMouseUp() {
dragging = false
}
function onMouseMove(e) {
if (dragging) {
let delta = e.pageY - startY
if (delta < 0) {
delta = 0
}
if (delta > 300) {
delta = 300
}
let resizeH = height + delta + 'px'
editorInstance.resize('', resizeH)
}
}
el.addEventListener('mousedown', onMouseDown)
document.addEventListener('mousemove', onMouseMove)
document.addEventListener('mouseup', onMouseUp)
return () => {
el.removeEventListener('mousedown', onMouseDown)
document.removeEventListener('mousemove', onMouseMove)
document.removeEventListener('mouseup', onMouseUp)
}
}
}, [ editorInstance, resizeBarEl ])
return (
<Fragment>
{atWhoVisible && atWhoList}
<div ref={editorEl} className={`df editormd-editing ${className} ${imageExpand && 'editormd-image-click-expand'} `}>
{quoteVisible && QuoteDiv }
<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>
</div>
</div>
{showResizeBar ? <a ref={resizeBarEl} className='editor-resize'></a> : null}
<div className={"fr rememberTip"}>
{noStorage === true ? null : <div id={tipId} className="edu-txt-right color-grey-cd font-12"></div>}
</div>
</Fragment>
)
}