forked from Gitlink/forgeplus-react
pull 组织首页代码
This commit is contained in:
commit
243d782a0a
|
|
@ -4561,6 +4561,11 @@
|
|||
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
|
||||
"dev": true
|
||||
},
|
||||
"cssfilter": {
|
||||
"version": "0.0.10",
|
||||
"resolved": "https://registry.npmmirror.com/cssfilter/-/cssfilter-0.0.10.tgz",
|
||||
"integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw=="
|
||||
},
|
||||
"cssnano": {
|
||||
"version": "4.1.11",
|
||||
"resolved": "http://173.15.15.82:8081/repository/npm-all/cssnano/-/cssnano-4.1.11.tgz",
|
||||
|
|
@ -21771,6 +21776,15 @@
|
|||
"resolved": "http://173.15.15.82:8081/repository/npm-all/xmlchars/-/xmlchars-2.2.0.tgz",
|
||||
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="
|
||||
},
|
||||
"xss": {
|
||||
"version": "1.0.14",
|
||||
"resolved": "https://registry.npmmirror.com/xss/-/xss-1.0.14.tgz",
|
||||
"integrity": "sha512-og7TEJhXvn1a7kzZGQ7ETjdQVS2UfZyTlsEdDOqvQF7GoxNfY+0YLCzBy1kPdsDDx4QuNAonQPddpsn6Xl/7sw==",
|
||||
"requires": {
|
||||
"commander": "^2.20.3",
|
||||
"cssfilter": "0.0.10"
|
||||
}
|
||||
},
|
||||
"xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "http://173.15.15.82:8081/repository/npm-all/xtend/-/xtend-4.0.2.tgz",
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@
|
|||
"styled-components": "^4.4.1",
|
||||
"whatwg-fetch": "2.0.3",
|
||||
"wrap-md-editor": "^0.2.20",
|
||||
"xss": "^1.0.14",
|
||||
"xterm": "4.8.1",
|
||||
"xterm-addon-fit": "0.4.0"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 2340181 */
|
||||
src: url('iconfont.woff2?t=1682485448664') format('woff2'),
|
||||
url('iconfont.woff?t=1682485448664') format('woff'),
|
||||
url('iconfont.ttf?t=1682485448664') format('truetype');
|
||||
src: url('iconfont.woff2?t=1682492410798') format('woff2'),
|
||||
url('iconfont.woff?t=1682492410798') format('woff'),
|
||||
url('iconfont.ttf?t=1682492410798') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -4,7 +4,7 @@ import { Spin } from 'antd';
|
|||
class Loading extends Component {
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (!prevProps.error && this.props.error) {
|
||||
console.log(this.props.error)
|
||||
// console.log(this.props.error)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
import React, { useEffect, useRef, useMemo } from 'react'
|
||||
import 'katex/dist/katex.min.css'
|
||||
import React, { useEffect, useRef, useMemo , useState } from 'react'
|
||||
import 'katex/dist/katex.min.css';
|
||||
import marked, { getTocContent, cleanToc, getMathExpressions, resetMathExpressions } from '../common/marked';
|
||||
import 'code-prettify';
|
||||
import dompurify from 'dompurify';
|
||||
import { getEmoji } from '../forge/Main/emoji';
|
||||
import axios from 'axios';
|
||||
|
||||
import { renderToString } from 'katex'
|
||||
|
||||
const preRegex = /<pre[^>]*>/g;
|
||||
const strRegexSub = /:([a-zA-Z_]+):/g;
|
||||
const quoteRegex = /\[[#][0-9]{0,}\]\(\/(.*?)\/(.*?)\/issues\/[0-9]{0,}\)/g;
|
||||
function _unescape(str) {
|
||||
let div = document.createElement('div')
|
||||
div.innerHTML = str
|
||||
|
|
@ -21,8 +23,29 @@ export default ({
|
|||
value = '',
|
||||
className,
|
||||
style = {},
|
||||
url
|
||||
url,
|
||||
owner=undefined,
|
||||
projectsId=undefined
|
||||
}) => {
|
||||
const [ issues , setIssues ] = useState([]);
|
||||
|
||||
useEffect(()=>{
|
||||
if(owner&&projectsId){
|
||||
getIssueList();
|
||||
}
|
||||
},[owner,projectsId])
|
||||
|
||||
function getIssueList(){
|
||||
axios.get(`/v1/${owner}/${projectsId}/issues`,{params:{
|
||||
only_name:true,sort_direction:"desc",sort_by:"issues.created_on"
|
||||
}}).then(result=>{
|
||||
if(result){
|
||||
let data = result.data.issues;
|
||||
setIssues(data);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let str = String(value);
|
||||
const html = useMemo(() => {
|
||||
let rs = marked(str);
|
||||
|
|
@ -38,6 +61,26 @@ export default ({
|
|||
rs = rs.replace(matchStr[i],getEmoji(matchStr[i]));
|
||||
}
|
||||
}
|
||||
if(owner && projectsId && issues && issues.length>0){
|
||||
let matchQuote = str.match(quoteRegex);
|
||||
if(matchQuote && matchQuote.length>0){
|
||||
let getIndexReg = /(?<=#)(.+?)(?=\])/g;
|
||||
for(var x=0;x<matchQuote.length;x++){
|
||||
let getIndex = matchQuote[x].match(getIndexReg);
|
||||
if(getIndex && getIndex.length>0 && getIndex[0]){
|
||||
let index = getIndex[0];
|
||||
let filter = issues.filter(f=>f.project_issues_index.toString() === index);
|
||||
if(filter && filter.length === 1){
|
||||
let content = `#${index}:${filter[0].subject}`;
|
||||
rs = rs.replace(`#${index}`,content);
|
||||
}else{
|
||||
let content = `<span>#${index}(已删除)</span>`;
|
||||
rs = rs.replace(`<a href="`+`/${owner}/${projectsId}/issues/${index}`+`">#${index}</a>`,content);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rs = rs.replace(/(__special_katext_id_\d+__)/g, (_match, capture) => {
|
||||
const { type, expression } = math_expressions[capture];
|
||||
|
|
@ -46,7 +89,7 @@ export default ({
|
|||
rs = rs.replace(/▁/g, "▁▁▁")
|
||||
resetMathExpressions()
|
||||
return dompurify.sanitize(rs)
|
||||
}, [str]);
|
||||
}, [str,issues]);
|
||||
|
||||
// 锚点跳转,链接地址里含#对应的id
|
||||
useEffect(()=>{
|
||||
|
|
|
|||
|
|
@ -263,15 +263,15 @@ class Activity extends Component{
|
|||
<div class="normalBox-title">项目演化分析</div>
|
||||
<div className="echartBox">
|
||||
|
||||
<span className="echartTitle" style={{marginTop:0}}>开源项目社群激发熵</span>
|
||||
<span className="echartTitle" style={{marginTop:0}}>开源项目社群激发演化拓扑熵</span>
|
||||
<BranchLine url={ai_shang_v1_url}/>
|
||||
<p>基于信息熵围绕疑修任务的社群群智激发演化度量</p>
|
||||
|
||||
<span className="echartTitle">开源项目代码变更熵</span>
|
||||
<span className="echartTitle">开源项目代码变更演化拓扑熵</span>
|
||||
<IssueLine url={ai_shang_v3_url}/>
|
||||
<p>基于信息熵围绕代码提交的软件代码变更演化度量</p>
|
||||
|
||||
<span className="echartTitle">开源项目社区演化熵</span>
|
||||
<span className="echartTitle">开源项目社区演化拓扑熵</span>
|
||||
<SmoothLineTwo url={ai_shang_v4_url}/>
|
||||
<p>基于信息熵围绕项目社群的社区分裂、缩减、合并和扩大演化行为度量</p>
|
||||
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ function Line({url}) {
|
|||
yAxis: {
|
||||
type: 'value',
|
||||
scale: true,
|
||||
name: '社\n群\n激\n发\n熵',
|
||||
name: '社\n群\n激\n发\n演\n化\n拓\n扑\n熵',
|
||||
min:0,
|
||||
splitLine : {
|
||||
show : true,
|
||||
|
|
@ -49,7 +49,7 @@ function Line({url}) {
|
|||
nameLocation:'end',
|
||||
nameTextStyle:{
|
||||
fontSize:14,
|
||||
padding:[0,70,-80,0]
|
||||
padding:[0,70,-120,0]
|
||||
}
|
||||
},
|
||||
dataZoom: [
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ function Line({url}) {
|
|||
yAxis: {
|
||||
type: 'value',
|
||||
scale: true,
|
||||
name: '代\n码\n变\n更\n熵',
|
||||
name: '代\n码\n变\n更\n演\n化\n拓\n扑\n熵',
|
||||
min:0,
|
||||
splitLine : {
|
||||
show : true,
|
||||
|
|
@ -51,7 +51,7 @@ function Line({url}) {
|
|||
nameLocation:'end',
|
||||
nameTextStyle:{
|
||||
fontSize:14,
|
||||
padding:[0,70,-80,0]
|
||||
padding:[0,70,-120,0]
|
||||
}
|
||||
},
|
||||
dataZoom: [
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ function SmoothLineTwo({url}) {
|
|||
yAxis: {
|
||||
type: 'value',
|
||||
scale: true,
|
||||
name: '社\n区\n演\n化\n熵',
|
||||
name: '社\n区\n演\n化\n拓\n扑\n熵',
|
||||
min:0,
|
||||
splitLine : {
|
||||
show : true,
|
||||
|
|
@ -61,7 +61,7 @@ function SmoothLineTwo({url}) {
|
|||
nameLocation:'end',
|
||||
nameTextStyle:{
|
||||
fontSize:14,
|
||||
padding:[0,70,-80,0]
|
||||
padding:[0,70,-120,0]
|
||||
}
|
||||
},
|
||||
dataZoom: [
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ function EditComment(props){
|
|||
initValue={content}
|
||||
onChange={(value)=>{setQuillFlag(false);setContent(value);}}
|
||||
isCanAtme = {true}
|
||||
isQuoteIssue={true}
|
||||
changeAtWhoLoginList = {(loginList)=>{setAtWhoLoginList(loginList); setAttachmentClean(true)}}
|
||||
owner = {owner}
|
||||
projectsId = {projectsId}
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ function IssueCommentList(props){
|
|||
}, [reload, category, page])
|
||||
|
||||
function commentCtx(v){
|
||||
return <RenderHtml className="break_word_comments imageLayerParent commentRenderHtml" value={v} url={location}/>;
|
||||
return <RenderHtml owner={owner} projectsId={projectsId} className="break_word_comments imageLayerParent commentRenderHtml" value={v} url={location}/>;
|
||||
};
|
||||
|
||||
// 删除评论内容
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import Attachments from "../../Upload/attachment";
|
|||
import UploadImg from '../Img/UploadImg.png';
|
||||
|
||||
function NewPanel(props,ref){
|
||||
const [ description , setDescription ] = useState("");
|
||||
const [ description , setDescription ] = useState(undefined);
|
||||
const [ fileList , setFileList] = useState(undefined);
|
||||
const [ attachments , setAttachments ] = useState([]);
|
||||
const [ save , setSave ] = useState([]);
|
||||
|
|
@ -15,12 +15,11 @@ function NewPanel(props,ref){
|
|||
const { createFunc , title , desc , files , onCancel , owner , projectsId } = props;
|
||||
|
||||
const { form: { getFieldDecorator, validateFields , setFieldsValue } } = props;
|
||||
|
||||
useState(()=>{
|
||||
title && setTimeout(()=>{
|
||||
setFieldsValue({subject:title});
|
||||
desc && onContentChange(desc);
|
||||
},100)
|
||||
desc && setDescription(desc);
|
||||
files && setAttachments(files);
|
||||
files && setSave(files);
|
||||
},[title , desc , files])
|
||||
|
|
@ -84,11 +83,12 @@ function NewPanel(props,ref){
|
|||
<MDEditor
|
||||
placeholder={"请输入描述信息"}
|
||||
height={392}
|
||||
mdID={"order-new-description"}
|
||||
mdID={"issue-edit-description"}
|
||||
initValue={description}
|
||||
onChange={onContentChange}
|
||||
className="mt20"
|
||||
isCanAtme = {true}
|
||||
isQuoteIssue={true}
|
||||
owner = {owner}
|
||||
projectsId = {projectsId}
|
||||
changeAtWhoLoginList = {changeAtWhoLoginList}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ function Details(props){
|
|||
const [ tag , setTag ] = useState(undefined);
|
||||
const [ branchList , setBranchList ] = useState(undefined);
|
||||
const [ branch , setBranch ] = useState(undefined);
|
||||
const [ desc , setDesc ] = useState(undefined);
|
||||
|
||||
const [ orderId , setOrderId ] = useState(undefined);
|
||||
|
||||
|
|
@ -91,6 +92,8 @@ function Details(props){
|
|||
let per = data && !data.user_permission;
|
||||
setEditFlag(per);
|
||||
|
||||
setDesc(data && data.description);
|
||||
|
||||
const aut = current_user && data.author && (data.author.login === current_user.login);
|
||||
const con = data.pull_fixed === false && aut;
|
||||
setAmountEditFlag(con);
|
||||
|
|
@ -277,10 +280,10 @@ function Details(props){
|
|||
if(result){
|
||||
window.scrollTo(0,0);
|
||||
props.showNotification("疑修更新成功!");
|
||||
// 刷新操作记录
|
||||
setCommentReload(Math.random());
|
||||
Init();
|
||||
setEdit(false);
|
||||
// 刷新操作记录
|
||||
setCommentReload(Math.random());
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}else{
|
||||
|
|
@ -318,6 +321,7 @@ function Details(props){
|
|||
props.history.push(`/${owner}/${projectsId}/issues/${index}`);
|
||||
}else{
|
||||
setEdit(false);
|
||||
window.scrollTo(0,0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -367,7 +371,7 @@ function Details(props){
|
|||
{...props}
|
||||
onCancel={onCancel}
|
||||
title={details.subject}
|
||||
desc={details.description}
|
||||
desc={edit ? desc : undefined}
|
||||
files={copy ? undefined : details.attachments}
|
||||
createFunc={createFunc}
|
||||
owner = {owner} projectsId = {projectsId}
|
||||
|
|
@ -405,8 +409,8 @@ function Details(props){
|
|||
}
|
||||
</div>
|
||||
<div class="descPanel">
|
||||
{details.description ?
|
||||
<RenderHtml className="break_word_comments imageLayerParent" value={details.description} url={props.history.location} />
|
||||
{desc ?
|
||||
<RenderHtml owner={owner} projectsId={projectsId} className="break_word_comments imageLayerParent" value={desc} url={props.history.location} />
|
||||
:
|
||||
<span className="color-grey-9 ml3 mr3">暂无描述</span>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -519,7 +519,6 @@ class Detail extends Component {
|
|||
}
|
||||
|
||||
textFunc = (forked_from_project_id, fork_info) => {
|
||||
let type = fork_info && fork_info.fork_project_user_type;
|
||||
return forked_from_project_id && fork_info ?
|
||||
<div className="color-grey-9 df">
|
||||
<span>复刻自</span>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Collapse , Tooltip , Spin } from 'antd';
|
||||
import { Button, Collapse , Tooltip , Spin , Pagination } from 'antd';
|
||||
import axios from 'axios';
|
||||
import Content from './historyContent';
|
||||
import Fault from '../images/fault.png';
|
||||
|
|
@ -8,21 +8,30 @@ const { Panel } = Collapse;
|
|||
function PushHistory({id,owner,projectsId,showNotification}) {
|
||||
const [ list , setList ] = useState(undefined);
|
||||
const [ isSpin , setIsSpin ] = useState(false);
|
||||
const [ page , setPage ] = useState(1);
|
||||
const [ total , setTotal ] = useState(0);
|
||||
const pageSize = 10;
|
||||
|
||||
useEffect(()=>{
|
||||
if(id && owner && projectsId){
|
||||
Init();
|
||||
Init(1);
|
||||
}
|
||||
},[id,owner,projectsId])
|
||||
|
||||
function Init() {
|
||||
useEffect(()=>{
|
||||
setList(undefined)
|
||||
Init(page);
|
||||
},[page])
|
||||
|
||||
function Init(page) {
|
||||
const url = `/${owner}/${projectsId}/webhooks/${id}/tasks.json`;
|
||||
axios.get(url,{
|
||||
params:{page:1,limit:10}
|
||||
params:{page:page,limit:pageSize}
|
||||
}).then(result=>{
|
||||
if(result && result.data){
|
||||
setList(result.data.tasks);
|
||||
setIsSpin(false);
|
||||
setTotal(result.data.total_count);
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
|
|
@ -49,7 +58,7 @@ function PushHistory({id,owner,projectsId,showNotification}) {
|
|||
</span>
|
||||
</div>
|
||||
{
|
||||
list && list.length>0 &&
|
||||
list && list.length>0 ?
|
||||
<Collapse accordion bordered={false} className="historyColl">
|
||||
{
|
||||
list.map((i,k)=>{
|
||||
|
|
@ -75,6 +84,15 @@ function PushHistory({id,owner,projectsId,showNotification}) {
|
|||
})
|
||||
}
|
||||
</Collapse>
|
||||
:
|
||||
<div style={{height:"470px",display:"flex",alignItems:"center",justifyContent:"center"}}>
|
||||
<Spin />
|
||||
</div>
|
||||
}
|
||||
{
|
||||
total > pageSize ?
|
||||
<div style={{padding:"15px 0px",textAlign:"right"}}><Pagination size="small" showQuickJumper pageSize={pageSize} current={page} total={total} onChange={(p)=>setPage(p)}/></div>
|
||||
:""
|
||||
}
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export default (({ name , count , bottom , children , url })=>{
|
||||
export default (({ name , icon , count , bottom , children , url })=>{
|
||||
return(
|
||||
<div className="box">
|
||||
<div className="head">
|
||||
<span className="font-16">{name}</span>
|
||||
<Link to={url}>{count}<i className="iconfont icon-youjiantou font-12 ml3"></i></Link>
|
||||
<span className="font-18">{icon}{name}{count && `(${count})`}</span>
|
||||
{ url && <Link to={url} style={{color:"rgba(31, 35, 41, 1)"}}>更多<i className="iconfont icon-youjiantou font-12 ml3"></i></Link>}
|
||||
</div>
|
||||
<div className="content">
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,10 @@ const New = Loadable({
|
|||
loading: Loading,
|
||||
});
|
||||
const DetailIndex = Loadable({
|
||||
loader: () => import("./List"),
|
||||
loading: Loading,
|
||||
});
|
||||
const TeamDetailIndex = Loadable({
|
||||
loader: () => import("./Sub/Detail"),
|
||||
loading: Loading,
|
||||
});
|
||||
|
|
@ -61,14 +65,14 @@ export default withRouter(CNotificationHOC()(SnackbarHOC()(TPMIndexHOC(
|
|||
<Route
|
||||
path="/:OIdentifier/teams"
|
||||
render={(p) => {
|
||||
return <DetailIndex {...props} {...p}/>
|
||||
return <TeamDetailIndex {...props} {...p}/>
|
||||
}}
|
||||
></Route>
|
||||
{/* 组织成员 */}
|
||||
<Route
|
||||
path="/:OIdentifier/members"
|
||||
render={(p) => {
|
||||
return <DetailIndex {...props} {...p}/>
|
||||
return <TeamDetailIndex {...props} {...p}/>
|
||||
}}
|
||||
></Route>
|
||||
|
||||
|
|
@ -77,17 +81,17 @@ export default withRouter(CNotificationHOC()(SnackbarHOC()(TPMIndexHOC(
|
|||
<Route
|
||||
path="/:OIdentifier/setting"
|
||||
render={(p) => (
|
||||
<DetailIndex {...props} {...p}/>
|
||||
<TeamDetailIndex {...props} {...p}/>
|
||||
)}
|
||||
></Route>
|
||||
|
||||
{/* 组织下的项目详情 */}
|
||||
<Route
|
||||
path="/:owner/:projectsId"
|
||||
render={(p) => (
|
||||
<ProjectDetail {...props} {...p} />
|
||||
)}
|
||||
></Route>
|
||||
path="/:owner/:projectsId"
|
||||
render={(p) => (
|
||||
<ProjectDetail {...props} {...p} />
|
||||
)}
|
||||
></Route>
|
||||
|
||||
{/* 组织详情(包含组织设置) */}
|
||||
<Route
|
||||
|
|
|
|||
|
|
@ -1,3 +1,46 @@
|
|||
.top-orz{
|
||||
background-color:#f9fbfe;
|
||||
box-shadow:0px 8px 20px rgba(17, 35, 146, 0.06);
|
||||
.box{
|
||||
width: 1200px;
|
||||
display: flex;
|
||||
margin:0px auto;
|
||||
align-items: center;
|
||||
padding:25px 0px 35px;
|
||||
&>img{
|
||||
margin-right: 27px;
|
||||
// max-width: 140px;
|
||||
height: 112px;
|
||||
border-radius:4px;
|
||||
}
|
||||
.df{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.info-orz{
|
||||
flex: 1;
|
||||
.orz-main{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
margin-bottom: 14px!important;
|
||||
height:30px;
|
||||
}
|
||||
.orz-name{
|
||||
font-weight:700;
|
||||
color:#333333;
|
||||
font-size:22px;
|
||||
}
|
||||
.orz-desc{
|
||||
color:#4c5b76;
|
||||
font-size:15px;
|
||||
line-height:26px;
|
||||
height: 52px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.teamBox{
|
||||
margin:10px 0px;
|
||||
border:1px solid #eee;
|
||||
|
|
@ -40,17 +83,79 @@
|
|||
margin:0px auto;
|
||||
padding-top:18px;
|
||||
}
|
||||
.list{
|
||||
.content-orz{
|
||||
background-color:#f3f5f8;
|
||||
}
|
||||
.content-orz .list{
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
width: 1200px;
|
||||
margin:0px auto;
|
||||
padding-top: 40px;
|
||||
.list-l{
|
||||
background-color: #fff;
|
||||
background-image: url('./images/left-bc.png');
|
||||
background-size: 100%;
|
||||
max-width: 860px;
|
||||
width: 72%;
|
||||
margin-bottom: 30px;
|
||||
border:1px solid #eee;
|
||||
padding:30px;
|
||||
.newstatus{
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px dashed rgba(170, 175, 190, 1);
|
||||
.desc{
|
||||
color:#4c5876;
|
||||
font-size:14px;
|
||||
line-height:26px;
|
||||
}
|
||||
.title{
|
||||
height:22px;
|
||||
line-height:22px;
|
||||
color:#1f2329;
|
||||
font-size:16px;
|
||||
margin:20px auto 14px!important;
|
||||
}
|
||||
.name{
|
||||
height:24px;
|
||||
span{
|
||||
font-weight:700;
|
||||
color:#1f2329;
|
||||
font-size:17px;
|
||||
}
|
||||
i{
|
||||
margin-right: 4px;
|
||||
color:rgba(31, 35, 41, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
.sepment{
|
||||
text-align: center;
|
||||
margin:30px auto 20px;
|
||||
.ant-radio-group{
|
||||
box-shadow:0px 0px 10px rgba(30, 47, 162, 0.09);
|
||||
.ant-radio-button-wrapper{
|
||||
border:none;
|
||||
background-color: #fff;
|
||||
line-height: 34px;
|
||||
&:first-child{
|
||||
padding:3px 0px 3px 3px;
|
||||
}
|
||||
&:last-child{
|
||||
padding:3px 3px 3px 0px;
|
||||
}
|
||||
span{
|
||||
background-color:rgba(30, 47, 162, 0.09) ;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.ant-radio-button-wrapper-checked{
|
||||
background-color:#466aff;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
.head{
|
||||
padding:16px 32px;
|
||||
padding:16px 0px;
|
||||
border-bottom: 1px solid #eee;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
|
@ -70,7 +175,6 @@
|
|||
}
|
||||
}
|
||||
.team{
|
||||
padding:0px 32px;
|
||||
min-height: 450px;
|
||||
.team_project{
|
||||
padding:22px 0px;
|
||||
|
|
@ -106,31 +210,53 @@
|
|||
}
|
||||
.list-r{
|
||||
width: 28%;
|
||||
max-width: 340px;
|
||||
padding-left: 20px;
|
||||
box-sizing: border-box;
|
||||
max-width: 338px;
|
||||
padding-left:30px;
|
||||
margin-bottom: 30px;
|
||||
& > div{
|
||||
border:1px solid #eee;
|
||||
}
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.box{
|
||||
background:rgba(255,255,255,1);
|
||||
border-radius:5px;
|
||||
margin-bottom: 20px;
|
||||
.head{
|
||||
padding:25px 20px;
|
||||
padding:25px 0px 15px;
|
||||
display: flex;
|
||||
border-bottom: 1px solid #eee;
|
||||
border-bottom: 1px dashed rgba(170, 175, 190, 1);
|
||||
justify-content: space-between;
|
||||
color: #333;
|
||||
align-items: center;
|
||||
margin:0px 20px;
|
||||
a:hover{
|
||||
color: #466aff!important;
|
||||
}
|
||||
}
|
||||
.content{
|
||||
padding:13px 0px;
|
||||
padding:5px 0px;
|
||||
.progress{
|
||||
display: flex;
|
||||
padding:10px 35px 10px 20px;
|
||||
align-items: center;
|
||||
&:hover{
|
||||
background-color: rgba(175, 183, 194, 0.13);
|
||||
}
|
||||
}
|
||||
.teammembers{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding:13px 20px;
|
||||
align-items: flex-start;
|
||||
padding:10px 20px 7px 20px;
|
||||
&:hover{
|
||||
background-color: rgba(175, 183, 194, 0.13);
|
||||
}
|
||||
.memberIndex{
|
||||
display: block;
|
||||
width:22px;
|
||||
height:22px;
|
||||
line-height:22px;
|
||||
background-color:#466aff;
|
||||
border-radius:2px;
|
||||
margin-right: 10px;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
}
|
||||
.m-img{
|
||||
border-radius: 50%;
|
||||
width:45px;
|
||||
|
|
@ -141,7 +267,14 @@
|
|||
}
|
||||
.foot{
|
||||
padding:15px 20px;
|
||||
border-top: 1px solid #eee;
|
||||
text-align: center;
|
||||
.newBtn{
|
||||
border-color:#466aff;
|
||||
border-radius:8px;
|
||||
color: #466aff;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -413,4 +546,13 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.overviewcontent{
|
||||
.ConcentrateTip{
|
||||
margin:20px 0px 0px 0px!important;
|
||||
}
|
||||
.concentrateUl li{
|
||||
border-color:#cddbeb!important;
|
||||
border-radius:4px;
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,9 @@ import Item from './ListItem';
|
|||
import Right from './RightBox';
|
||||
import NoData from '../Nodata';
|
||||
import CheckProfile from '../Component/ProfileModal/Profile';
|
||||
import { Menu , Pagination , Dropdown , Spin } from 'antd';
|
||||
import { Menu , Pagination , Dropdown , Spin , Tabs , Radio } from 'antd';
|
||||
import ConcentrateProject from '../users/GeneralView/ConcentrateProject';
|
||||
import { getImageUrl } from 'educoder';
|
||||
import axios from 'axios';
|
||||
|
||||
const limit = 15;
|
||||
|
|
@ -18,9 +20,27 @@ function List(props){
|
|||
const [ search , setSearch ] = useState(undefined);
|
||||
const [ page , setPage ] = useState(1);
|
||||
const [ sortBy , setSortBy ] = useState("updated_on");
|
||||
const [ organizeDetail , setOrganizeDetail ] = useState(undefined);
|
||||
const [ mode , setMode ] = useState("overview");
|
||||
|
||||
const OIdentifier = props.match.params.OIdentifier;
|
||||
const organizeDetail = props.organizeDetail;
|
||||
const { current_user } = props;
|
||||
console.log(props);
|
||||
|
||||
useEffect(()=>{
|
||||
if(OIdentifier){
|
||||
getDetail(OIdentifier);
|
||||
}
|
||||
},[OIdentifier]);
|
||||
|
||||
function getDetail(id) {
|
||||
const url = `/organizations/${id}.json`;
|
||||
axios.get(url).then(result=>{
|
||||
if(result && result.data){
|
||||
setOrganizeDetail(result.data);
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
|
||||
useEffect(()=>{
|
||||
if(organizeDetail){
|
||||
|
|
@ -31,11 +51,11 @@ function List(props){
|
|||
},[organizeDetail])
|
||||
|
||||
useEffect(()=>{
|
||||
if(OIdentifier){
|
||||
if(OIdentifier && mode==="subject"){
|
||||
setIsSpin(true);
|
||||
getProject();
|
||||
}
|
||||
},[OIdentifier,sortBy,page,search])
|
||||
},[OIdentifier,sortBy,page,search,mode])
|
||||
|
||||
function getProject(){
|
||||
const url = `/organizations/${OIdentifier}/projects.json`;
|
||||
|
|
@ -76,55 +96,129 @@ function List(props){
|
|||
</li>
|
||||
</ul>
|
||||
)
|
||||
|
||||
function changeRadio(e){
|
||||
setMode(e.target.value);
|
||||
}
|
||||
|
||||
|
||||
return(
|
||||
<div className="list">
|
||||
<div className="list-l">
|
||||
<div>
|
||||
<div className="head">
|
||||
<div style={{width:"370px"}}>
|
||||
<Search placeholder="输入仓库名称进行搜索" onSearch={onSearch}/>
|
||||
</div>
|
||||
<p>
|
||||
{ organizeDetail && organizeDetail.can_create_project ?
|
||||
<Sort menu={menu_new} overlayClassName={"newPopUl"}>
|
||||
<a className="addBtn mr30">+ 新建项目</a>
|
||||
</Sort>
|
||||
:""}
|
||||
<Dropdown overlay={menu}>
|
||||
<a className="color-blue">排序<i className="iconfont icon-sanjiaoxing-down ml3 font-14"></i></a>
|
||||
</Dropdown>
|
||||
</p>
|
||||
</div>
|
||||
<Spin spinning={isSpin}>
|
||||
<div className="team">
|
||||
{
|
||||
list && list.length>0 ? list.map((item,key)=>{
|
||||
return(
|
||||
<Item item={item} keu={key} OIdentifier={OIdentifier}/>
|
||||
)
|
||||
})
|
||||
:
|
||||
<NoData _html="暂无数据"/>
|
||||
}
|
||||
</div>
|
||||
</Spin>
|
||||
organizeDetail ?
|
||||
<div className="content-orz">
|
||||
<div className="top-orz">
|
||||
<div className="box">
|
||||
<img src={getImageUrl(organizeDetail && organizeDetail.avatar_url)} alt=""/>
|
||||
<div className="info-orz">
|
||||
<p className="orz-main">
|
||||
<span className="orz-name">{organizeDetail && organizeDetail.nickname}</span>
|
||||
{organizeDetail && organizeDetail.is_admin ?
|
||||
<Link to={`/${OIdentifier}/setting`} className="color-blue ml10 font-14 df"><i className="iconfont icon-shezhi2"></i>设置</Link>
|
||||
:""}
|
||||
</p>
|
||||
<div className="orz-desc task-hide-2">{organizeDetail && organizeDetail.description}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="list">
|
||||
<div className="list-l">
|
||||
{
|
||||
totalCount > limit &&
|
||||
<div className="mb20 mt20" style={{textAlign:"center"}}>
|
||||
<Pagination simple current={page} total={totalCount} pageSize={limit} onChange={(page)=>setPage(page)}/>
|
||||
organizeDetail.news_title &&
|
||||
<div className="newstatus">
|
||||
<p className="name"><i className="iconfont icon-dongtaiicon1" /><span>新闻动态</span></p>
|
||||
<p className="title">
|
||||
{organizeDetail.news_title}
|
||||
</p>
|
||||
<div className="desc">
|
||||
{organizeDetail.news_content}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div className="sepment" onChange={changeRadio} >
|
||||
<Radio.Group size="large" value={mode}>
|
||||
<Radio.Button value="overview" style={{width:"130px"}}>概览</Radio.Button>
|
||||
<Radio.Button value="subject" style={{width:"130px"}}>仓库</Radio.Button>
|
||||
</Radio.Group>
|
||||
</div>
|
||||
{
|
||||
mode === "overview" &&
|
||||
<div className="overviewcontent">
|
||||
<ConcentrateProject
|
||||
{...props}
|
||||
title={
|
||||
<p className="names">
|
||||
<i className="iconfont icon-jingxuanxiangmu font-17 mr8"></i>
|
||||
<span className="font-17">精选项目</span>
|
||||
</p>
|
||||
}
|
||||
btn={
|
||||
<span><i className="iconfont icon-zidingyi font-14 mr5"></i>自定义</span>
|
||||
}
|
||||
userLogin={OIdentifier}
|
||||
type="organizations"
|
||||
current={organizeDetail.is_admin}
|
||||
/>
|
||||
<div>
|
||||
<p className="mt30 mb10">
|
||||
<i className="iconfont icon-zuzhijieshao mr4"></i>
|
||||
<span className="font-17">组织介绍</span>
|
||||
</p>
|
||||
<div style={{color:"#4c5876",lineHeight:"30px",wordBreak:"break-all",textAlign:"justify"}}>{organizeDetail.description}</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{
|
||||
mode === "subject" &&
|
||||
<div>
|
||||
<div>
|
||||
<div className="head">
|
||||
<div style={{width:"370px"}}>
|
||||
<Search placeholder="输入仓库名称进行搜索" onSearch={onSearch}/>
|
||||
</div>
|
||||
<p>
|
||||
{ organizeDetail && organizeDetail.can_create_project ?
|
||||
<Sort menu={menu_new} overlayClassName={"newPopUl"}>
|
||||
<a className="addBtn mr30">+ 新建项目</a>
|
||||
</Sort>
|
||||
:""}
|
||||
<Dropdown overlay={menu}>
|
||||
<a className="color-blue">排序<i className="iconfont icon-sanjiaoxing-down ml3 font-14"></i></a>
|
||||
</Dropdown>
|
||||
</p>
|
||||
</div>
|
||||
<Spin spinning={isSpin}>
|
||||
<div className="team">
|
||||
{
|
||||
list && list.length>0 ? list.map((item,key)=>{
|
||||
return(
|
||||
<Item item={item} keu={key} OIdentifier={OIdentifier}/>
|
||||
)
|
||||
})
|
||||
:
|
||||
<NoData _html="暂无数据"/>
|
||||
}
|
||||
</div>
|
||||
</Spin>
|
||||
</div>
|
||||
{
|
||||
totalCount > limit &&
|
||||
<div className="mb20 mt20" style={{textAlign:"center"}}>
|
||||
<Pagination simple current={page} total={totalCount} pageSize={limit} onChange={(page)=>setPage(page)}/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
<Right
|
||||
admin={organizeDetail && organizeDetail.is_admin}
|
||||
OIdentifier={OIdentifier}
|
||||
showCompeleteDialog={props.showCompeleteDialog}
|
||||
completeProfile={props.completeProfile}
|
||||
history={props.history}
|
||||
/>
|
||||
<Right
|
||||
admin={organizeDetail && organizeDetail.is_admin}
|
||||
OIdentifier={OIdentifier}
|
||||
showCompeleteDialog={props.showCompeleteDialog}
|
||||
completeProfile={props.completeProfile}
|
||||
history={props.history}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
:""
|
||||
)
|
||||
}
|
||||
export default List;
|
||||
|
|
@ -5,6 +5,8 @@ import axios from 'axios';
|
|||
import { getImageUrl } from 'educoder';
|
||||
import { Link } from 'react-router-dom';
|
||||
import Nodata from '../Nodata';
|
||||
import { Progress } from 'antd';
|
||||
import Right from './images/right.png';
|
||||
|
||||
import CheckProfile from '../Component/ProfileModal/Profile';
|
||||
|
||||
|
|
@ -25,7 +27,7 @@ const ListName = styled.div`{
|
|||
line-height:18px;
|
||||
}`;
|
||||
const ColorListName = styled.div`{
|
||||
color:#5091FF;
|
||||
color:rgba(31, 35, 41, 1);
|
||||
font-size:14px;
|
||||
margin-bottom:8px;
|
||||
height:18px;
|
||||
|
|
@ -38,6 +40,7 @@ const Img = styled.img`{
|
|||
margin-right:12px;
|
||||
}`
|
||||
function RightBox({ OIdentifier , history , admin , showCompeleteDialog ,completeProfile }) {
|
||||
const [ languageData, setLanguageData ] = useState(undefined);
|
||||
const [ memberData, setMemberData ] = useState(undefined);
|
||||
const [ groupData, setGroupData ] = useState(undefined);
|
||||
|
||||
|
|
@ -45,8 +48,32 @@ function RightBox({ OIdentifier , history , admin , showCompeleteDialog ,complet
|
|||
if(OIdentifier){
|
||||
getMember(OIdentifier);
|
||||
getGroup(OIdentifier);
|
||||
getLanguage(OIdentifier);
|
||||
}
|
||||
},[OIdentifier])
|
||||
function getLanguage(iden){
|
||||
const url = `/organizations/${iden}/languages.json`;
|
||||
axios.get(url,{params:{limit:10}}).then(result=>{
|
||||
if(result && result.data){
|
||||
let languages = result.data;
|
||||
let arr = [];
|
||||
Object.keys(languages).map((item,key)=>{
|
||||
arr.push({name:item,percent:languages[item].replace("%",""),color:getColor()});
|
||||
})
|
||||
setLanguageData(arr);
|
||||
console.log(result.data);
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
function getColor(){
|
||||
let str = "#";
|
||||
let arr = ["1","2","3","4","4","5","6","7","8","9","a","b","c","d","e","f"];
|
||||
for(var i=0;i<6;i++){
|
||||
let num = parseInt(Math.random() * 16);
|
||||
str+=arr[num];
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
function getMember(iden){
|
||||
const url = `/organizations/${iden}/organization_users.json`;
|
||||
|
|
@ -66,17 +93,43 @@ function RightBox({ OIdentifier , history , admin , showCompeleteDialog ,complet
|
|||
}
|
||||
return(
|
||||
<div className="list-r">
|
||||
{
|
||||
languageData ?
|
||||
<Box
|
||||
name="仓库语言"
|
||||
// count={languageData && languageData.length}
|
||||
icon={<i className="iconfont icon-cangkuyuyanicon font-17 mr8"></i>}
|
||||
// url={`/${OIdentifier}/members`}
|
||||
>
|
||||
{
|
||||
languageData.map((item,key)=>{
|
||||
return(
|
||||
<div className="progress">
|
||||
<span className="mr10">{item.name}</span>
|
||||
<Progress percent={item.percent} strokeColor={item.color}/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
</Box>
|
||||
:""
|
||||
}
|
||||
{
|
||||
memberData && memberData.organization_users && memberData.organization_users.length>0 ?
|
||||
<Box name="组织成员" count={memberData && memberData.total_count} url={`/${OIdentifier}/members`}>
|
||||
<Box
|
||||
name="组织成员"
|
||||
count={memberData && memberData.total_count}
|
||||
icon={<i className="iconfont icon-zuzhichengyuan1 font-16 mr8"></i>}
|
||||
url={`/${OIdentifier}/members`}>
|
||||
{
|
||||
memberData.organization_users.map((item,key)=>{
|
||||
return(
|
||||
<div className="teammembers" key={key}>
|
||||
key<5 && <div className="teammembers" key={key}>
|
||||
<Link to={`/${item.user && item.user.login}`}><Img src={getImageUrl(`/${item.user && item.user.image_url}`)} alt="" className="m-img"/></Link>
|
||||
<div>
|
||||
<Link to={`/${item.user && item.user.login}`}><ListName>{item.user && item.user.name}</ListName></Link>
|
||||
<Align><i className="iconfont icon-shijian color-green mr3 font-13"></i><Span>加入时间:{item.created_at}</Span></Align>
|
||||
<Align><Span>加入时间:{item.created_at}</Span></Align>
|
||||
{/* <i className="iconfont icon-shijian color-green mr3 font-13"></i> */}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
@ -88,14 +141,15 @@ function RightBox({ OIdentifier , history , admin , showCompeleteDialog ,complet
|
|||
<Box
|
||||
name="组织团队"
|
||||
count={groupData && groupData.total_count}
|
||||
icon={<i className="iconfont icon-zuzhituandui font-17 mr8"></i>}
|
||||
bottom={
|
||||
admin &&
|
||||
<CheckProfile
|
||||
showCompeleteDialog={showCompeleteDialog}
|
||||
completeProfile={completeProfile}
|
||||
sureFunc={()=>history.push(`/${OIdentifier}/teams/new`)}
|
||||
className={"ant-btn ant-btn-primary"}
|
||||
>新建团队</CheckProfile>
|
||||
className={"ant-btn newBtn"}
|
||||
>新建团队<img src={Right} alt="" width="9" className="ml5" /></CheckProfile>
|
||||
}
|
||||
url={`/${OIdentifier}/teams`}
|
||||
>
|
||||
|
|
@ -104,7 +158,8 @@ function RightBox({ OIdentifier , history , admin , showCompeleteDialog ,complet
|
|||
<React.Fragment>
|
||||
{groupData.teams.map((item,key)=>{
|
||||
return(
|
||||
<div className="teammembers" key={key}>
|
||||
key < 5 &&<div className="teammembers" key={key}>
|
||||
<span className="memberIndex">{key+1}</span>
|
||||
<div>
|
||||
{
|
||||
(item.is_admin || item.is_member) ?
|
||||
|
|
@ -113,8 +168,8 @@ function RightBox({ OIdentifier , history , admin , showCompeleteDialog ,complet
|
|||
<ColorListName>{item.name}</ColorListName>
|
||||
}
|
||||
<Align>
|
||||
<Span>{item.num_users}名成员</Span>
|
||||
<Span>{item.num_projects}个仓库</Span>
|
||||
<Span><span style={{color:"rgba(31, 35, 41, 1)"}}>{item.num_users}</span>名成员</Span>
|
||||
<Span><span style={{color:"rgba(31, 35, 41, 1)"}}>{item.num_projects}</span>个仓库</Span>
|
||||
</Align>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 125 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 736 B |
|
|
@ -5,7 +5,7 @@ import CheckProfile from '../../Component/ProfileModal/Profile';
|
|||
|
||||
const { Search } = Input;
|
||||
const limit = 20;
|
||||
function ConcentrateBox({ visible , onCancel , onSure , username , choosed , history , showCompeleteDialog , completeProfile }) {
|
||||
function ConcentrateBox({ visible , onCancel , onSure , type , username , choosed , history , showCompeleteDialog , completeProfile }) {
|
||||
const [ page , setPage ]= useState(1);
|
||||
const [ total , setTotal ]= useState(0);
|
||||
const [ pageSize , setPageSize ] = useState(false);
|
||||
|
|
@ -54,7 +54,7 @@ function ConcentrateBox({ visible , onCancel , onSure , username , choosed , his
|
|||
},[value])
|
||||
|
||||
function getProjectList(p,s) {
|
||||
const url = `/users/${username}/projects.json`;
|
||||
const url = `/${type || `users`}/${username}/projects.json`;
|
||||
Axios.get(url,{
|
||||
params:{
|
||||
page:p,limit,is_public: "public",search:s,choosed
|
||||
|
|
@ -152,15 +152,16 @@ function ConcentrateBox({ visible , onCancel , onSure , username , choosed , his
|
|||
{
|
||||
copyList && copyList.length >0 && copyList.map((i,k)=>{
|
||||
return(
|
||||
<Checkbox value={i.id} disabled={disable && (value.filter(j=>j === i.id).length===0)}>{i.author && i.author.name}/{i.name}</Checkbox>
|
||||
<Checkbox value={i.id} disabled={disable && (value.filter(j=>j === i.id).length===0)}>{i.author && `${i.author.name}/`}{i.name}</Checkbox>
|
||||
)
|
||||
})
|
||||
}
|
||||
{
|
||||
list && list.length > 0 && list.map((i,k)=>{
|
||||
console.log(i);
|
||||
let c = copyList && copyList.length >0 && copyList.filter(j=>j.id === i.id).length !== 0;
|
||||
return(
|
||||
!c && <Checkbox value={i.id} disabled={disable && (value.filter(j=>j === i.id).length===0)}>{i.author && i.author.name}/{i.name}</Checkbox>
|
||||
!c && <Checkbox value={i.id} disabled={disable && (value.filter(j=>j === i.id).length===0)}>{i.author && `${i.author.name}/`}{i.name}</Checkbox>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,18 @@ import { FlexAJ , AlignCenter } from '../../Component/layout';
|
|||
import { Link } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
import Box from './ConcentrateBox';
|
||||
import './Index.scss';
|
||||
|
||||
function ConcentrateProject({userLogin,current,showCompeleteDialog,completeProfile,history}) {
|
||||
function ConcentrateProject({
|
||||
userLogin,
|
||||
current,
|
||||
showCompeleteDialog,
|
||||
completeProfile,
|
||||
history,
|
||||
type,
|
||||
title,
|
||||
btn
|
||||
}) {
|
||||
const [ list , setList ] = useState(undefined);
|
||||
const [ visible , setVisible ] = useState(false);
|
||||
const [ value , setValue ] = useState([]);
|
||||
|
|
@ -14,7 +24,7 @@ function ConcentrateProject({userLogin,current,showCompeleteDialog,completeProfi
|
|||
},[])
|
||||
|
||||
function getList() {
|
||||
const url = `/users/${userLogin}/is_pinned_projects.json`;
|
||||
const url = `/${type || `users`}/${userLogin}/is_pinned_projects.json`;
|
||||
axios.get(url).then(result=>{
|
||||
if(result && result.data){
|
||||
let p = result.data.projects;
|
||||
|
|
@ -33,7 +43,7 @@ function ConcentrateProject({userLogin,current,showCompeleteDialog,completeProfi
|
|||
if(is_pinned_project_ids && is_pinned_project_ids.length===0){
|
||||
setValue([]);
|
||||
}
|
||||
const url = `/users/${userLogin}/is_pinned_projects/pin.json`;
|
||||
const url = `/${type || `users`}/${userLogin}/is_pinned_projects/pin.json`;
|
||||
axios.post(url,{
|
||||
is_pinned_project_ids
|
||||
}).then(result=>{
|
||||
|
|
@ -54,14 +64,21 @@ function ConcentrateProject({userLogin,current,showCompeleteDialog,completeProfi
|
|||
completeProfile={completeProfile}
|
||||
showCompeleteDialog={showCompeleteDialog}
|
||||
history={history}
|
||||
type={type}
|
||||
/>
|
||||
{
|
||||
list && list.length>0 &&
|
||||
<div className="concentrate">
|
||||
<FlexAJ>
|
||||
<span className="font-18">精选项目</span>
|
||||
{ current && <a className="color-blue" onClick={()=>setVisible(true)}>自定义精选项目</a> }
|
||||
{
|
||||
title || <span className="font-18">精选项目</span>
|
||||
}
|
||||
{ current &&
|
||||
<a className="color-blue" onClick={()=>setVisible(true)}>
|
||||
{btn || "自定义精选项目"}
|
||||
</a>
|
||||
}
|
||||
</FlexAJ>
|
||||
{
|
||||
list && list.length>0 &&
|
||||
<div>
|
||||
<ul className="concentrateUl">
|
||||
{
|
||||
|
|
@ -71,12 +88,12 @@ function ConcentrateProject({userLogin,current,showCompeleteDialog,completeProfi
|
|||
<Link to={`/${i.author && i.author.login}/${i.identifier}`} className="name">{i.name}</Link>
|
||||
<p className="task-hide desc">{i.description}</p>
|
||||
{/* 项目标签 */}
|
||||
{i.topics && <div className='viewProListTopics'>
|
||||
{i.topics && <div className='viewProListTopics mt10'>
|
||||
{i.topics.map(item=><Link to={`/explore/topic/${item.id}/${encodeURIComponent(item.name)}`} className='viewProListTopic mr15 font-13 task-hide'>{item.name}</Link>)}
|
||||
</div>}
|
||||
<AlignCenter>
|
||||
{ i.category && <span className="tagName">{i.category.name}</span> }
|
||||
<span className="pariseCount"><i className="iconfont icon-xingzhuang"></i>{i.watchers_count}</span>
|
||||
<span className="pariseCount ml2"><i className="iconfont icon-xingzhuang"></i>{i.watchers_count}</span>
|
||||
<span className="forkCount"><i className="iconfont icon-morenfuke_icon1"></i>{i.forked_count}</span>
|
||||
</AlignCenter>
|
||||
</li>
|
||||
|
|
@ -85,8 +102,8 @@ function ConcentrateProject({userLogin,current,showCompeleteDialog,completeProfi
|
|||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
{
|
||||
list && list.length === 0 && current && <div className="ConcentrateTip"><i className="iconfont icon-tishi2"></i>你还没有设置精选项目,<a onClick={()=>setVisible(true)}>点击设置</a></div>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,14 +18,16 @@
|
|||
}
|
||||
.name{
|
||||
font-size: 16px;
|
||||
// color: #4CACFF;
|
||||
color: $primary-color;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
&:hover{
|
||||
color: $primary-color-hover;
|
||||
}
|
||||
}
|
||||
.desc{
|
||||
color: #999;
|
||||
line-height: 21px;
|
||||
}
|
||||
.tagName{
|
||||
display: block;
|
||||
|
|
@ -36,6 +38,7 @@
|
|||
height: 22px;
|
||||
line-height: 22px;
|
||||
font-size: 13px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
.pariseCount,.forkCount{
|
||||
i{
|
||||
|
|
@ -43,7 +46,7 @@
|
|||
margin-right: 4px;
|
||||
}
|
||||
color: #999;
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -152,13 +155,13 @@
|
|||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
.viewProListTopic{
|
||||
background-color:#f3f8ff;
|
||||
background-color:rgba(70, 106, 255, 0.12);
|
||||
border-radius: 4px;
|
||||
max-width: 158px;
|
||||
padding: 0 10px;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
color: #4c5b76;
|
||||
color:#466aff;
|
||||
margin-bottom: 5px !important;
|
||||
}
|
||||
}
|
||||
|
|
@ -14,14 +14,21 @@ function LoginRegisterPage(props){
|
|||
return(
|
||||
<div className="loginRegister">
|
||||
<div className="login_register_left">
|
||||
<img src={logo} className="logo" onClick={()=>{window.location.href='/'}}></img>
|
||||
<img src={ball} className="ball"></img>
|
||||
<img src={banner} className="banner"></img>
|
||||
<img src={logo} className="logo" alt="" onClick={()=>{window.location.href='/'}} />
|
||||
<img src={ball} className="ball" alt="" />
|
||||
<img src={banner} className="banner" alt="" />
|
||||
</div>
|
||||
<div className="login_register_right">
|
||||
{props.location.pathname === "/login" ? <Login {...props}/> : props.location.pathname === "/register" ? <Register mygetHelmetapi={mygetHelmetapi}/> : <ResetPassword mygetHelmetapi={mygetHelmetapi}/>}
|
||||
<img src={img1} className="img1"></img>
|
||||
<img src={img2} className="img2"></img>
|
||||
{props.location.pathname === "/login" ?
|
||||
<Login {...props}/>
|
||||
:
|
||||
props.location.pathname === "/register" ?
|
||||
<Register mygetHelmetapi={mygetHelmetapi}/>
|
||||
:
|
||||
<ResetPassword mygetHelmetapi={mygetHelmetapi}/>
|
||||
}
|
||||
<img src={img1} className="img1" alt="" />
|
||||
<img src={img2} className="img2" alt="" />
|
||||
</div>
|
||||
<div className="clear"></div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,9 +5,41 @@
|
|||
color:#5091FF !important;
|
||||
border-color: #5091FF !important;
|
||||
}
|
||||
|
||||
.quoteDiv{
|
||||
width: 360px!important;
|
||||
max-height: 280px!important;
|
||||
}
|
||||
.quoteDiv > li{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 32px;
|
||||
padding:0px 10px;
|
||||
border-bottom: 1px solid rgba(212, 212, 212, 0.5);
|
||||
}
|
||||
.quoteDiv > li.active{
|
||||
background-color: #F3F4F6;
|
||||
}
|
||||
.quoteDiv > li:last-child{
|
||||
border-bottom: none;
|
||||
}
|
||||
.issueIndex{
|
||||
background-color: rgba(213, 220, 246, 0.36);
|
||||
border-radius: 4px;
|
||||
height: 19px;
|
||||
line-height: 19px;
|
||||
color: #666666;
|
||||
text-align: center;
|
||||
float: left;
|
||||
padding: 0px 2px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
margin-right: 7px;
|
||||
}
|
||||
.issueName{
|
||||
flex:1
|
||||
}
|
||||
/*md编辑器中输入@弹出可选人列表样式*/
|
||||
.at_who_list{
|
||||
.at_who_list,.quoteDiv{
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
width: 180px;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
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';
|
||||
|
|
@ -74,8 +76,8 @@ function md_elocalStorage(editor, mdu, id) {
|
|||
return tid
|
||||
}
|
||||
|
||||
// isFocus:是否聚焦到md编译器
|
||||
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, isFocus = true }) => {
|
||||
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();
|
||||
|
|
@ -91,6 +93,13 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
|
|||
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=>{
|
||||
|
|
@ -103,9 +112,108 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
|
|||
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) {
|
||||
|
|
@ -153,23 +261,22 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
|
|||
}
|
||||
|
||||
//markdown编辑器中输入的键盘监听事件
|
||||
function mdKeyDown(e){
|
||||
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);
|
||||
//获取光标位置
|
||||
const cssStyle = document.getElementsByClassName("CodeMirror cm-s-default CodeMirror-wrap")[0].firstChild.style;
|
||||
//设置弹框位置
|
||||
const newTop = 62
|
||||
const newLeft = 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(e.ctrlKey && e.code === "KeyZ" && allUsers.length !== 0){
|
||||
//处理初始内容就自带@谁的情况
|
||||
if(initValue){
|
||||
const del = [];
|
||||
|
|
@ -194,6 +301,13 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
|
|||
}
|
||||
})
|
||||
}
|
||||
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(()=>{
|
||||
|
|
@ -233,8 +347,7 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
|
|||
htmlDecode: "style,script,iframe",
|
||||
sequenceDiagram: true,
|
||||
autoFocus: false,
|
||||
watch: watch === undefined ? true : watch,
|
||||
|
||||
watch: watch,
|
||||
saveHTMLToTextarea: true,
|
||||
dialogMaskOpacity: 0.6,
|
||||
placeholder: placeholder,
|
||||
|
|
@ -265,7 +378,6 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
|
|||
},
|
||||
"fullScreen":function(cm,icon,cursor,selection){
|
||||
icon.addClass("none");
|
||||
console.log(cm,icon)
|
||||
},
|
||||
"inline-latex": function (cm, icon, cursor, selection) {
|
||||
cm.replaceSelection("$$" + selection + "$$");
|
||||
|
|
@ -298,7 +410,7 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
|
|||
if(atWhoVisibleRef.current){
|
||||
// 添加上下键、enter键监听事件
|
||||
cmEl.addKeyMap({
|
||||
'Up':()=>{
|
||||
'Up':()=>{
|
||||
const atWhoListDiv = document.getElementById("at_who_list");
|
||||
const atWhoDivs = document.getElementsByClassName("at_who");
|
||||
let index;
|
||||
|
|
@ -357,10 +469,11 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
|
|||
}
|
||||
},[users])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (cmEl) {
|
||||
let tid = null
|
||||
let ro
|
||||
let ro;
|
||||
if (onCMBlur) {
|
||||
editorInstance.cm.on('blur', () => { onCMBlur(editorInstance.getValue()) })
|
||||
}
|
||||
|
|
@ -374,15 +487,45 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
|
|||
}
|
||||
//isCanAtme:只有issue和合并请求以及评论部分可以@他人操作
|
||||
//绑定@事件
|
||||
isCanAtme && editorInstance.cm.on("focus", () => {
|
||||
(isCanAtme || isQuoteIssue) && editorInstance.cm.on("focus", () => {
|
||||
document.addEventListener("keydown", mdKeyDown);
|
||||
});
|
||||
isCanAtme && editorInstance.cm.on("blur", () => {
|
||||
(isCanAtme || isQuoteIssue) && editorInstance.cm.on("blur", () => {
|
||||
document.removeEventListener("keydown",mdKeyDown);
|
||||
});
|
||||
editorInstance.cm.on("change", (cm) => {
|
||||
//调用父组件的onchange方法,将输入内容传入父级组件
|
||||
onChange && onChange(cm.getValue());
|
||||
// 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(xss(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();
|
||||
|
|
@ -409,11 +552,12 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
|
|||
}
|
||||
}
|
||||
|
||||
changeValueTime("#",issues,chooseIssuesList.current);
|
||||
|
||||
//当内容发生改变并且有已@列表时
|
||||
if(atWhoLoginList.current.length != 0){
|
||||
const codemirror = editorInstance.cm;
|
||||
//startValue:触发change方法时的内容,value:处理了初始内容带@用户的情况
|
||||
let startValue = codemirror.getValue();
|
||||
let value = codemirror.getValue();
|
||||
//处理初始内容就自带@谁的情况
|
||||
if(initValue){
|
||||
|
|
@ -442,13 +586,6 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
|
|||
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([]);
|
||||
}
|
||||
|
|
@ -467,21 +604,6 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
|
|||
}
|
||||
//处理已经有@列表但是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));
|
||||
|
|
@ -516,6 +638,86 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
|
|||
}
|
||||
}, [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()) {
|
||||
|
|
@ -558,14 +760,13 @@ export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, cla
|
|||
document.removeEventListener('mouseup', onMouseUp)
|
||||
}
|
||||
}
|
||||
}, [
|
||||
editorInstance, resizeBarEl
|
||||
])
|
||||
}, [ 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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue