This commit is contained in:
caishi 2023-04-07 17:52:04 +08:00
commit 0d17abc4b0
141 changed files with 5923 additions and 807 deletions

10
package-lock.json generated
View File

@ -7847,7 +7847,7 @@
},
"fsevents": {
"version": "1.2.13",
"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-1.2.13.tgz",
"resolved": "http://173.15.15.82:8081/repository/npm-all/fsevents/-/fsevents-1.2.13.tgz",
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
"optional": true,
"requires": {
@ -11299,7 +11299,7 @@
"dependencies": {
"yallist": {
"version": "4.0.0",
"resolved": "http://173.15.15.82:8081/repository/npm-all/yallist/-/yallist-4.0.0.tgz",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
}
@ -15441,7 +15441,7 @@
},
"react-copy-to-clipboard": {
"version": "5.1.0",
"resolved": "https://registry.npmmirror.com/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz",
"resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz",
"integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==",
"requires": {
"copy-to-clipboard": "^3.3.1",
@ -15450,7 +15450,7 @@
"dependencies": {
"prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"requires": {
"loose-envify": "^1.4.0",
@ -20220,7 +20220,7 @@
},
"fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz",
"resolved": "http://173.15.15.82:8081/repository/npm-all/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"optional": true

View File

@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 2340181 */
src: url('iconfont.woff2?t=1675409079100') format('woff2'),
url('iconfont.woff?t=1675409079100') format('woff'),
url('iconfont.ttf?t=1675409079100') format('truetype');
src: url('iconfont.woff2?t=1680498859493') format('woff2'),
url('iconfont.woff?t=1680498859493') format('woff'),
url('iconfont.ttf?t=1680498859493') format('truetype');
}
.iconfont {
@ -13,6 +13,38 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-a-zu37:before {
content: "\e961";
}
.icon-guanzhuicon:before {
content: "\e95b";
}
.icon-yidianzan_icon:before {
content: "\e95c";
}
.icon-morenfuke_icon1:before {
content: "\e95d";
}
.icon-yiguanzhuicon:before {
content: "\e95e";
}
.icon-yifuke_icon:before {
content: "\e95f";
}
.icon-xingzhuang:before {
content: "\e960";
}
.icon-kaiqi:before {
content: "\e95a";
}
.icon-a-31shijian:before {
content: "\e944";
}

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,62 @@
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "34875122",
"name": "组 37",
"font_class": "a-zu37",
"unicode": "e961",
"unicode_decimal": 59745
},
{
"icon_id": "34819652",
"name": "关注icon",
"font_class": "guanzhuicon",
"unicode": "e95b",
"unicode_decimal": 59739
},
{
"icon_id": "34819653",
"name": "已点赞_icon",
"font_class": "yidianzan_icon",
"unicode": "e95c",
"unicode_decimal": 59740
},
{
"icon_id": "34819654",
"name": "默认复刻_icon",
"font_class": "morenfuke_icon1",
"unicode": "e95d",
"unicode_decimal": 59741
},
{
"icon_id": "34819655",
"name": "已关注icon",
"font_class": "yiguanzhuicon",
"unicode": "e95e",
"unicode_decimal": 59742
},
{
"icon_id": "34819656",
"name": "已复刻_icon",
"font_class": "yifuke_icon",
"unicode": "e95f",
"unicode_decimal": 59743
},
{
"icon_id": "34820092",
"name": "形状",
"font_class": "xingzhuang",
"unicode": "e960",
"unicode_decimal": 59744
},
{
"icon_id": "34197190",
"name": "开启",
"font_class": "kaiqi",
"unicode": "e95a",
"unicode_decimal": 59738
},
{
"icon_id": "33949760",
"name": "3.1 时间",

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -68,11 +68,11 @@
window.onload=function(){
$(".newContainer").delegate("a.anchors","click",function(){
let h = $(this).offset().top - 180;
console.log($(this).offset().top,document.body.scrollTop);
$("html,body").animate({scrollTop:h},10);
window.location.hash = $(this).attr("name");
return false;
});
}
</script>

View File

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
<!-- <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests"> -->
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<!--

View File

@ -110,7 +110,7 @@ body {
/* 图片点击放大的场景,隐藏图片链接 */
.editormd-image-click-expand .editormd-image-dialog {
height: 234px !important;
height: 244px !important;
}
.editormd-image-click-expand .editormd-image-dialog .image-link {

View File

@ -87,6 +87,7 @@ const Search = Loadable({
loader: () => import('./modules/search/'),
loading: Loading,
})
const WikiPreview = Loadable({
loader: () => import('./forge/Wiki/Preview'),
loading: Loading,
@ -126,17 +127,31 @@ const Glcc = Loadable({
loader : () => import("./glcc"),
loading: Loading,
})
const ConfigurateBot = Loadable({
loader: () => import("./forge/SecuritySetting/bot/exploit/configuration"),
loading: Loading,
});
const SoftBot = Loadable({
loader : () => import("./softbot"),
loading: Loading,
})
const WebIDE = Loadable({
loader: () => import('./forge/Newfile/webIDE'),
loading: Loading,
});
const Badge = Loadable({
loader: () => import('./forge/Badge/Index'),
loading: Loading,
})
// const CreateMerge = Loadable({
// loader: () => import('./forge/Merge/NewMerge'),
// loading: Loading,
// })
// 此处仅维护前端可能的一级路由,不用进行项目或者组织判断的字段。
const keyWord = ["explore", "settings", "setting", "mulan", "wiki", "issues", "setting", "trending", "code", "projects", "pulls", "mine", "login", "register", "email", "export", "nopage", "404", "403", "500", "501", "search", "organize", "login", "register", "resetPassword", "aboutus","educoder", "glcc","bindlogin"];
const keyWord = ["explore", "settings", "setting", "mulan", "wiki", "issues", "setting", "trending", "code", "projects", "pulls", "mine", "login", "register", "email", "export", "nopage", "404", "403", "500", "501", "search", "organize", "login", "register", "resetPassword", "aboutus","educoder", "glcc","bindlogin", "softbot"];
class App extends Component {
constructor(props) {
@ -319,6 +334,13 @@ class App extends Component {
}
} />
{/* svg */}
<Route path="/:owner/:projectsId/badge/:type" render={
(props) => {
return (<Badge {...this.props} {...props} {...this.state} />)
}
} />
{/* webIDE */}
<Route path="/:owner/:projectsId/webIDE/tree/:branchName"
render={
@ -342,6 +364,13 @@ class App extends Component {
}
}>
</Route>
{/* 上架bot配置 */}
<Route
path="/settings/mybot/configuration/:id"
render={(p) => (
<ConfigurateBot {...this.props} {...p}/>
)}
></Route>
<Route
path={"/settings"}
render={
@ -350,7 +379,6 @@ class App extends Component {
}
}>
</Route>
{/*403*/}
<Route path="/403" component={Shixunauthority} />
@ -418,6 +446,14 @@ class App extends Component {
render={(props) => <Glcc {...this.props} {...props} />}
></Route>
{/* soft市场页 */}
<Route
path="/softbot"
render={(props) => (
<SoftBot {...this.props} {...props} />
)}
></Route>
{/* 组织 */}
<Route path={"/organize"}
render={

View File

@ -82,6 +82,13 @@ export function initAxiosInterceptors(props) {
}
throw new axios.Cancel('Operation canceled by the user.');
}
if (response.data.status === 403 || response.data.status === "403" || response.data.status === 404) {
if(!window.location.pathname.includes('/releases')){
locationurl(`/${response.data.status}`);
}else{
console.log("releases 404了");
}
}
if (response.data.status === 403 || response.data.status === "403") {
// locationurl('/403');

View File

@ -68,13 +68,14 @@ function Index(props){
return(
<div className="aboutPanels">
<div className="aboutContent">
<AlignCenterBetween style={{padding:"14px 20px"}}>
<span className="font-16"><i className="iconfont icon-xiangmujianjie mr5 font-16 color-blue"></i>项目概览</span>
{ editOpration && !edit && <a onClick={editContent} className="color-blue">编辑</a> }
</AlignCenterBetween>
<div className="aboutTitle">
<span className="font-16">项目概览</span>
{ editOpration && !edit && <a onClick={editContent} className="color-blue">
<i className="iconfont icon-a-bianji12 mr5 font-13 color-blue"></i>编辑</a> }
</div>
{
edit ?
<div className="padding20">
<div className="padding20 boies">
<MDEditor
placeholder={"请输入描述信息"}
height={500}
@ -113,7 +114,7 @@ function Index(props){
</div>
</div>
:
<div className="padding20">
<div className="padding20 boies">
{content ?
<RenderHtml className="break_word_comments imageLayerParent" value={content} url={props.history.location}/>
:

View File

@ -3,10 +3,25 @@
margin:0px auto;
.aboutContent{
border-radius: 2px;
border: 1px solid #EEEEEE;
// border: 1px solid #EEEEEE;
width:100%;
background-color: #fff;
margin-top:20px;
margin-bottom: 30px;
.aboutTitle{
height: 50px;
background-color: #fafcff;
border: 1px solid;
border-color: rgba(42, 97, 255, 0.23);
border-radius: 4px 4px 0px 0px;
display: flex;
align-items: center;
justify-content: space-between;
padding:0px 20px;
}
.boies{
border: 1px solid #d0d0d0;
border-top: none;
}
}
}

View File

@ -9,6 +9,7 @@ import IssueLine from './issueLine';
import BranchLine from './branchLine';
import SmoothLine from './smoothLine';
import SmoothLineTwo from './smoothLineTwo';
import Demo from './demo';
import ProjectScale from './ProjectScale';
import Pie from './pie';
@ -185,74 +186,76 @@ class Activity extends Component{
<div className="contentBox mt20">
<div className="normalBox">
<div class="normalBox-title">概览</div>
<div className="orderInfo">
<div>
<div className="percentLine prPercent">
<p className="percent_purple" style={{width:first_per}}></p>
<p className="percent_green resetStyle" style={{width:`${second_per}`}}></p>
<div className="boxpart">
<div className="orderInfo">
<div>
<div className="percentLine prPercent">
<p className="percent_purple" style={{width:first_per}}></p>
<p className="percent_green resetStyle" style={{width:`${second_per}`}}></p>
</div>
<span>{data && data.pr_all_count}合并请求</span>
</div>
<span>{data && data.pr_all_count}合并请求</span>
</div>
<div>
<div className="percentLine">
<p className="percent_red" style={{width:`${third_per}`}}></p>
<p className="percent_green" style={{width:`${fourth_per}`}}></p>
<div>
<div className="percentLine">
<p className="percent_red" style={{width:`${third_per}`}}></p>
<p className="percent_green" style={{width:`${fourth_per}`}}></p>
</div>
<span>{data && data.issues_count}疑修</span>
</div>
<span>{data && data.issues_count}疑修</span>
</div>
<ul className="percentBox">
<li>
<span className="purple">{data && data.pr_count}</span>
<span className={type==="PullRequest" && status==="delay" ?`change active`:"change"} onClick={()=>this.changeTrends("PullRequest","delay")}>已处理的合并请求</span>
</li>
<li>
<span className="green">{data && data.new_pr_count}</span>
<span className={type==="PullRequest"&& status==="not_delay" ?`change active`:"change"} onClick={()=>this.changeTrends("PullRequest","not_delay")}>未处理的合并请求</span>
</li>
<li>
<span className="red">{data && data.close_issues_count}</span>
<span className={type==="Issue"&& status==="delay" ?`change active`:"change"} onClick={()=>this.changeTrends("Issue","delay")}>已关闭的疑修</span>
</li>
<li>
<span className="green">{data && data.open_issues_count}</span>
<span className={type==="Issue"&& status==="not_delay" ?`change active`:"change"} onClick={()=>this.changeTrends("Issue","not_delay")}>未处理的疑修</span>
</li>
</ul>
{
codeStatus &&
<div className="prMsg">
<div><span className="fontbold">{codeStatus.author_count}位作者</span> <span className="fontbold">{codeStatus.commit_count}</span>
{codeStatus.commit_count_in_all_branches && codeStatus.commit_count_in_all_branches>0?<span> {projectDetail && projectDetail.default_branch}分支 <span>{codeStatus.commit_count_in_all_branches}次提交</span> </span>:""} {projectDetail && projectDetail.default_branch}
{codeStatus.change_files && codeStatus.change_files >0 ? <span><span className="fontbold">{codeStatus.change_files} 个文件</span> </span>:""}
{codeStatus.additions && codeStatus.additions >0 ?<span><span className="fontbold greencount">新增 {codeStatus.additions} 行代码</span></span>:""}
{(codeStatus.additions && codeStatus.additions >0) &&(codeStatus.deletions && codeStatus.deletions >0) ?<span> </span>:""}
{codeStatus.deletions && codeStatus.deletions >0 ?<span><span className="fontbold redcount"> 删除 {codeStatus.deletions} 行代码</span></span>:""}.</div>
{/* {
codeStatus.authors && codeStatus.authors.length > 0 &&
<ul className="prAuthor">
{
codeStatus.authors.map((i,v)=>{
return(
i.author ?
!i.author.id ?
<span>
<img src={getImageUrl(i.author && i.author.image_url)} width="38px" height="38px" alt=""/>
<Tooltip title={i.author && i.author.name} placement="bottom"><span>{i.author && i.author.name}</span></Tooltip>
</span>
:
<a href={i.author.login ? `/${i.author.login}` : `mailto:${i.author.email}`}>
<img src={getImageUrl(i.author && i.author.image_url)} width="38px" height="38px" alt=""/>
<Tooltip title={i.author && i.author.name} placement="bottom"><span>{i.author && i.author.name}</span></Tooltip>
</a>
:""
)
})
}
</ul>
} */}
</div>
}
</div>
<ul className="percentBox">
<li>
<span className="purple">{data && data.pr_count}</span>
<span className={type==="PullRequest" && status==="delay" ?`change active`:"change"} onClick={()=>this.changeTrends("PullRequest","delay")}>已处理的合并请求</span>
</li>
<li>
<span className="green">{data && data.new_pr_count}</span>
<span className={type==="PullRequest"&& status==="not_delay" ?`change active`:"change"} onClick={()=>this.changeTrends("PullRequest","not_delay")}>未处理的合并请求</span>
</li>
<li>
<span className="red">{data && data.close_issues_count}</span>
<span className={type==="Issue"&& status==="delay" ?`change active`:"change"} onClick={()=>this.changeTrends("Issue","delay")}>已关闭的疑修</span>
</li>
<li>
<span className="green">{data && data.open_issues_count}</span>
<span className={type==="Issue"&& status==="not_delay" ?`change active`:"change"} onClick={()=>this.changeTrends("Issue","not_delay")}>未处理的疑修</span>
</li>
</ul>
{
codeStatus &&
<div className="prMsg">
<div><span className="fontbold">{codeStatus.author_count}位作者</span> <span className="fontbold">{codeStatus.commit_count}</span>
{codeStatus.commit_count_in_all_branches && codeStatus.commit_count_in_all_branches>0?<span> {projectDetail && projectDetail.default_branch}分支 <span>{codeStatus.commit_count_in_all_branches}次提交</span> </span>:""} {projectDetail && projectDetail.default_branch}
{codeStatus.change_files && codeStatus.change_files >0 ? <span><span className="fontbold">{codeStatus.change_files} 个文件</span> </span>:""}
{codeStatus.additions && codeStatus.additions >0 ?<span><span className="fontbold greencount">新增 {codeStatus.additions} 行代码</span></span>:""}
{(codeStatus.additions && codeStatus.additions >0) &&(codeStatus.deletions && codeStatus.deletions >0) ?<span> </span>:""}
{codeStatus.deletions && codeStatus.deletions >0 ?<span><span className="fontbold redcount"> 删除 {codeStatus.deletions} 行代码</span></span>:""}.</div>
{/* {
codeStatus.authors && codeStatus.authors.length > 0 &&
<ul className="prAuthor">
{
codeStatus.authors.map((i,v)=>{
return(
i.author ?
!i.author.id ?
<span>
<img src={getImageUrl(i.author && i.author.image_url)} width="38px" height="38px" alt=""/>
<Tooltip title={i.author && i.author.name} placement="bottom"><span>{i.author && i.author.name}</span></Tooltip>
</span>
:
<a href={i.author.login ? `/${i.author.login}` : `mailto:${i.author.email}`}>
<img src={getImageUrl(i.author && i.author.image_url)} width="38px" height="38px" alt=""/>
<Tooltip title={i.author && i.author.name} placement="bottom"><span>{i.author && i.author.name}</span></Tooltip>
</a>
:""
)
})
}
</ul>
} */}
</div>
}
</div>
{
ai_shang_v1_url &&
@ -272,12 +275,13 @@ class Activity extends Component{
<SmoothLineTwo url={ai_shang_v4_url}/>
<p>基于信息熵围绕项目社群的社区分裂缩减合并和扩大演化行为度量</p>
</div>
</div>
}
<div className="commentsBox">
<div className="trendsTop">
<span>疑修/合并请求记录</span>
<span className="font-16">疑修/合并请求记录</span>
<div className="branchDropdown f-wrap-alignCenter">
<Dropdown overlay={this.menu()} trigger={['click']} placement="bottomLeft">
<a className="ant-dropdown-link">

View File

@ -1,7 +1,9 @@
.trendsTop{
padding:15px;
border-bottom:1px solid #ddd;
background-color: #f4f4f4;
background-color: #fafcff;
border: 1px solid;
border-color: rgba(42, 97, 255, 0.23);
border-radius: 4px 4px 0px 0px;
padding: 10px 20px;
display: flex;
align-items: center;
justify-content: space-between;
@ -29,6 +31,10 @@
display: flex;
justify-content: space-between;
}
.boxpart{
border: 1px solid #d0d0d0;
border-top: none;
}
.orderInfo > div{
width: 45%;
}
@ -95,7 +101,7 @@
}
.commentsBox{
border-radius: 4px;
border: 1px solid #ddd;
/* border: 1px solid #ddd; */
margin: 20px 0px 20px 0px;
}
.prMsg{
@ -140,6 +146,10 @@
white-space: nowrap;
text-overflow: ellipsis;
}
.activity_list{
border:1px solid #d0d0d0;
border-top: none;
}
.activity_list .activity_item{
border-bottom: 1px solid #f4f4f4;
padding:15px;

File diff suppressed because one or more lines are too long

165
src/forge/Activity/demo.jsx Normal file
View File

@ -0,0 +1,165 @@
import React ,{ useEffect } from 'react';
// import echarts from 'echarts/lib/echarts';
import 'echarts/lib/chart/line';
import * as echarts from 'echarts';
import axios from 'axios';
import { getdata } from './data'
function Demo({url}) {
useEffect(()=>{
Init();
},[])
function Init() {
let data = getdata();
console.log(data);
var huan_val = document.getElementById("Demo");
var myEcharts = echarts.init(huan_val);
let date = [];
let array = [0];
let array1 = [0];
let array2 = [0];
let option = {
xAxis: {
type: 'time',
data:date,
boundaryGap: false,
axisLabel:{
month:`{M}月`
}
},
tooltip: {
trigger: 'axis',
formatter: function (params) {
params = params[0];
var date = new Date(params.name);
return (
date.getDate() +
'/' +
(date.getMonth() + 1) +
'/' +
date.getFullYear() +
' : ' +
params.value
);
},
axisPointer: {
animation: false
}
},
grid: {
left: '5%',
right: '1%',
top: '8%',
},
legend: {
data: ['issue', 'fork' ,'commit']
},
color: ['red','#36bba6',"skyblue",'#a491d7','#e840f7',"#36bba6","#7b32b2"],
yAxis: {
type: 'value',
scale: true,
boundaryGap: [0, '100%'],
name: '',
min:0,
splitLine : {
show : true,
},
nameLocation:'end',
nameTextStyle:{
fontSize:14,
padding:[0,70,-80,0]
}
},series:[
{
data: array,
type: 'line',
name:"issue",
smooth: true
},
{
data: array1,
type: 'line',
name:"fork",
smooth: true
},
{
data: array2,
type: 'line',
name:"commit",
smooth: true
},
]
};
myEcharts.setOption(option);
setTimeout(()=>{
let count = data.length>15?15:data.length;
for(var i = 0;i<count;i++){
date.push(data[count][0]);
array.push(data[count][1]);
array1.push(data[count][2]);
array2.push(data[count][3]);
}
let interval = setInterval(function () {
count ++ ;
if(count < data.length ){
setValue(count)
}else{
clearInterval(interval);
}
}, 2000);
},1000);
function setValue(count){
for (var i = 0; i < 2; i++) {
array.shift();
array1.shift();
array2.shift();
date.shift();
date.push(data[count][0]);
array.push(data[count][1]);
array1.push(data[count][2]);
array2.push(data[count][3]);
}
myEcharts.setOption({
xAxis: {
type: 'category',
data:date
},
legend: {
data: ['issue', 'fork' ,'commit']
},
series: [
{
data: array,
type: 'line',
name:"issue",
showSymbol: false,
smooth: true
},
{
data: array1,
type: 'line',
name:"fork",
showSymbol: false,
smooth: true
},
{
data: array2,
type: 'line',
name:"commit",
showSymbol: false,
smooth: true
},
]
});
}
}
return(
<div id="Demo" style={{height:"400px"}}></div>
)
}
export default Demo;

108
src/forge/Badge/Index.jsx Normal file
View File

@ -0,0 +1,108 @@
import React, { useEffect, useState } from "react";
import axios from 'axios';
import { setSeoMeta } from 'educoder';
function Badge(props) {
const { match, history } = props;
const { projectsId, owner, type } = match.params;
if (!["watch.svg", "star.svg", "fork.svg"].includes(type)) {
history.push('/nopage');
}
// 使mock
// {
// watchers_count: 10,
// praises_count: 0,
// forked_count: 1200,
// }
const [projectDetail, setProjectDetail] = useState();
const [text, setText] = useState("");
//
useEffect(() => {
const url = `/${owner}/${projectsId}/detail.json`;
axios.get(url).then((result) => {
if (result && result.data) {
if (result.data.status === 404) {
this.props.history.push('/nopage');
} else {
setProjectDetail(result.data);
// seo
let keyWords = `${owner},${projectsId},${result.data.author.name},`;
let title = `${owner}/${projectsId}${result.data.description ? '' + result.data.description : ''}`;
setSeoMeta(keyWords, title, result.data.description, `/${owner}/${projectsId}`, owner, projectsId);
}
}
}).catch((error) => { })
}, [projectsId, owner]);
useEffect(() => {
if (projectDetail) {
if (type === "watch.svg") {
setText(`${coutTranslate(projectDetail.watchers_count)} 关注数`);
} else if (type === "star.svg") {
setText(`${coutTranslate(projectDetail.praises_count)} 点赞数`);
} else if (type === "fork.svg") {
setText(`${coutTranslate(projectDetail.forked_count)} Fork数`);
}
}
}, [type, projectDetail]);
function coutTranslate(num) {
if (num < 1000) {
return num;
} else if (num >= 1000 & num < 10000) {
return (num / 1000).toFixed(1) + 'k';
} else {
return (num / 1000).toFixed(0) + 'k';
}
}
return <svg xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" width="148" height="44" viewBox="0 0 148 44">
<defs>
<filter id="矩形_234" x="0" y="0" width="148" height="44" filterUnits="userSpaceOnUse">
<feOffset input="SourceAlpha" />
<feGaussianBlur stdDeviation="3" result="blur" />
<feFlood floodColor="#0c1a75" floodOpacity="0.161" />
<feComposite operator="in" in2="blur" />
<feComposite in="SourceGraphic" />
</filter>
<filter id="矩形_235" x="0" y="0" width="148" height="44" filterUnits="userSpaceOnUse">
<feOffset input="SourceAlpha" />
<feGaussianBlur stdDeviation="3" result="blur-2" />
<feFlood floodColor="#445a64" floodOpacity="0.161" />
<feComposite operator="in" in2="blur-2" />
<feComposite in="SourceGraphic" />
</filter>
<linearGradient id="linear-gradient" x1="0.494" y1="-0.055" x2="1.494" y2="-0.055" gradientUnits="objectBoundingBox">
<stop offset="0" stopColor="#6dffff" />
<stop offset="1" stopColor="#0080ff" />
</linearGradient>
</defs>
<g id="组_1758" data-name="组 1758" transform="translate(-1468 -507)">
<g transform="matrix(1, 0, 0, 1, 1468, 507)" filter="url(#矩形_234)">
<path id="矩形_234-2" data-name="矩形 234" d="M0,0H126a4,4,0,0,1,4,4V22a4,4,0,0,1-4,4H0a0,0,0,0,1,0,0V0A0,0,0,0,1,0,0Z" transform="translate(9 9)" fill="#fff" />
</g>
<g transform="matrix(1, 0, 0, 1, 1468, 507)" filter="url(#矩形_235)">
<path id="矩形_235-2" data-name="矩形 235" d="M4,0H42a0,0,0,0,1,0,0V26a0,0,0,0,1,0,0H4a4,4,0,0,1-4-4V4A4,4,0,0,1,4,0Z" transform="translate(9 9)" fill="#2b2b2b" />
<text xmlns="http://www.w3.org/2000/svg" fill="#000"
// svgfork1.50.5
textLength={80 - 10 * (7.5 - (text.indexOf("Fork") > -1 ? (text.length - 1.5) : text.length) + (text.includes('k ') && 0.5))}
x={64 + 6 * (7.5 - (text.indexOf("Fork") > -1 ? (text.length - 1.5) : text.length) + (text.includes('k ') && 0.5))}
y="30" lengthAdjust="spacing" transform="scale(.9)" weight="bold">
{text}
</text>
</g>
<g id="图层_2" data-name="图层 2" transform="translate(1484 520)">
<g id="图层_1" data-name="图层 1">
<path id="路径_3357" data-name="路径 3357" d="M20.6,5.477l-.75-1.023-.658.329v8.211l.546.288v-.911l.862,1.395.7.109V3.9l-.7.27Zm-15.462,8.4,1.559-.78V4.676L5.139,3.9Zm10.7,1.063,1.4.836V1.559l-1.4.836ZM13.827,0H11.878V17.764h2.984V16.008H13.827ZM7.51,3.683l.771.285V15.756l1.41-.729V4.578l.755.271V3.258L7.51,1.559ZM0,13.271l3.39-1.717V8.32L1.78,8.155V9.292h.645v1.1l-1.109.5V7.4L3.4,7.779V6.451L0,5.329ZM25.1,9.257l2.5-3.928-1.864.2L24.5,7.177V6.086l-1.37.329V12.32l1.37.286V11.475l1.329,1.684,2.138.107Z" fillRule="evenodd" fill="url(#linear-gradient)" />
</g>
</g>
</g>
</svg>
}
export default Badge;

View File

@ -166,7 +166,7 @@ function Contributors({owner,projectsId,currentLogin}){
return(
total > 0 ?
<div className="halfs">
<Link to={`/${owner}/${projectsId}/contribute`} className="font-16 color-ooo hoverA">
<Link to={`/${owner}/${projectsId}/contribute`} onClick={()=>{window.scrollTo(0,0)}} className="font-16 color-ooo hoverA">
<span>贡献者</span>
{ total > 0 && <span className="infoCount">{total}</span>}
</Link>
@ -174,11 +174,16 @@ function Contributors({owner,projectsId,currentLogin}){
{
list && list.length>0 && list.map((item,key)=>{
return(
item.login ?
<Popover content={menu} visible={item.visible} overlayClassName="menuPanels" placement="top">
<Link key={key} to={`/${item.login}`}>
<a key={key} href={`/${item.login}`}>
<img src={getImageUrl(`/${item.image_url}`)} alt="" onMouseOver={()=>setVisibleFunc(true,item.login,key)}/>
</Link>
</a>
</Popover>
:
<a key={key} href={`mailto:${item.email}`}>
<img src={getImageUrl(`/${item.image_url}`)} alt="" onMouseOver={()=>setVisibleFunc(true,item.login,key)}/>
</a>
)
})
}

View File

@ -42,7 +42,7 @@ function CopyTool({ beforeText, afterText, className , inputId , timeOut }) {
title={title}
onVisibleChange={() => { setTitle(beforeText) }}
>
<i className={`iconfont icon-fuzhiicon ${className}`} style={{ color: '#466aff' }} onClick={copyUrl}></i>
<i className={`iconfont icon-fuzhiicon cursor ${className}`} style={{ color: '#466aff' }} onClick={copyUrl}></i>
</Tooltip>
);
}

View File

@ -0,0 +1,123 @@
import React , { useState , useEffect } from 'react';
import { Input } from 'antd';
import './index.scss';
function FileName({
onChangeFile,addonBefore , name , empty , onEmpty
}){
const [ defaultUrl , setDefaultUrl ] = useState(undefined);
const [ newUrl , setNewUrl ] = useState([]);
const [ value , setValue ] = useState(undefined);
useEffect(()=>{
if(name){
let v ="";
if(name.indexOf("/")>-1){
let s = name.split("/");
v = s[s.length-1];
console.log(v);
setValue(v);
}else{
v=name;
setValue(name);
}
let a = newUrl && newUrl.length > 0 ? newUrl.join("/")+"/" +v : v ;
onChangeFile(a);
}else{
setValue(undefined);
}
},[name])
useEffect(()=>{
if(addonBefore){
setDefaultUrl(addonBefore.split('/'));
}
},[addonBefore])
function keyUpFun(e){
const key = e.keyCode;
if(key === 8){
//input
if(value === undefined && (newUrl && newUrl.length>0)){
const last = newUrl[newUrl.length-1];
let n = newUrl;
n.splice(n.length-1,1);
setNewUrl(n);
setValue(last);
}
}
}
function changeValue(e){
const value = e.target.value;
console.log(value);
if(value){
if(value === "/"){
setValue(undefined);
}
else if(value.indexOf("/")>-1){
let a = value.split("/");
let b = newUrl;
let c = b.concat(a[0]);
setNewUrl(c);
let v = a[a.length-1];
setValue(v);
if(v){
onChangeFile(c && c.length > 0?c.join("/")+"/"+v:v);
onEmpty && onEmpty(false);
}else{
clearvalue();
}
}else{
let a = newUrl && newUrl.length > 0 ? newUrl.join("/")+"/" +value : value ;
onChangeFile(a);
setValue(value);
onEmpty && onEmpty(false);
}
}else{
clearvalue();
}
}
function clearvalue(){
if(newUrl && newUrl.length>0){
setTimeout(()=>{setValue(undefined)},200);
}else{
setValue(undefined);
}
onEmpty && onEmpty(true);
}
const renderList=(list)=>{
return(
<div>
{
list.map((i,k)=>{
return(
i && <span>{i}<span className="ml2 mr3">/</span></span>
)
})
}
</div>
)
}
return(
<div className="fileName">
{
defaultUrl && defaultUrl.length>0 ? renderList(defaultUrl) : ""
}
{
newUrl && newUrl.length>0 ? renderList(newUrl) : ""
}
<Input
placeholder="命名文件名称或文件路径"
value={value}
onKeyUp={keyUpFun}
onChange={changeValue}
style={{width:"220px",borderColor:empty===true ? "red":"grey"}}
/>
{empty && <p className="color-red ml20">请输入文件名称</p>}
</div>
)
}
export default FileName;

View File

@ -0,0 +1,4 @@
.fileName{
display: flex;
align-items: center;
}

View File

@ -31,10 +31,10 @@ export default (({fork,parise})=>{
return(
<DivStyle>
{
fork || fork ===0 ? <SpanStylefork><i className="iconfont icon-fork font-15 mr3" style={{color:"#1B8FFF"}}></i>fork({fork})</SpanStylefork>:""
fork || fork ===0 ? <SpanStylefork><i className="iconfont icon-morenfuke_icon1 font-15 mr3" style={{color:"#1B8FFF"}}></i>fork({fork})</SpanStylefork>:""
}
{
parise || parise ===0?<SpanStyleparise><i className="iconfont icon-guanzhu font-14 mr3" style={{color:"#FFA802"}}></i>点赞({parise})</SpanStyleparise>:""
parise || parise ===0?<SpanStyleparise><i className="iconfont icon-xingzhuang font-14 mr3" style={{color:"#FFA802"}}></i>点赞({parise})</SpanStyleparise>:""
}
</DivStyle>
)

View File

@ -5,7 +5,7 @@ import { Link } from 'react-router-dom';
function Releases({ owner, projectsId, releaseVersions, distribution }) {
return(
<div>
<Link to={`/${owner}/${projectsId}/releases`} className="font-16 color-ooo hoverA">
<Link to={`/${owner}/${projectsId}/releases`} onClick={()=>{window.scrollTo(0,0)}} className="font-16 color-ooo hoverA">
<span>发行版</span>
{ releaseVersions && releaseVersions.total_count > 0 && <span className="infoCount">{releaseVersions.total_count}</span>}
</Link>

BIN
src/forge/Images/emp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View File

@ -44,7 +44,6 @@ function AllMenus({owner,projectsId,chooseFunc,update,defaultNames,defaultIds,op
useEffect(()=>{
if(defaultIds){
console.log("defaule:",defaultIds);
setIds(defaultIds);
}
},[defaultIds])
@ -206,6 +205,7 @@ function AllMenus({owner,projectsId,chooseFunc,update,defaultNames,defaultIds,op
chooseFunc={(id,name)=>{let copy = {...ids,status_id:id && id.length>0 ? id.join(","):undefined};let copyname = { ...names,status_name:name};setNames(copyname);setIds(copy);chooseFunc(copy,copyname)}}
/>
{ !update && <Menus
className="minwidth"
ids={ids && ids.sort_by}
name={"排序"} size={"small"}
lists={open_blockchain ? array : array1}

View File

@ -1,8 +1,9 @@
import React from 'react';
import { Input, Button, message } from 'antd';
import { getImageUrl, timeAgo } from 'educoder';
import { Button, message } from 'antd';
import { getImageUrl } from 'educoder';
import MDEditor from "../../../../modules/tpm/challengesnew/tpm-md-editor";
import Upload from "../../../../forge/Upload/Index";
import Attachments from "../../../../forge/Upload/attachment";
import UploadImg from "../../../../forge/Images/upload.png";
import './list.scss';
import '../../../../forge/Order/order.scss';
@ -20,6 +21,11 @@ function EditComment(props){
const [atWhoLoginList, setAtWhoLoginList] = useState(undefined);
const [fileList, setFileList] = useState(undefined);
const [attachmentClean, setAttachmentClean] = useState(true);
const [ save , setSave ] = useState(undefined);
useState(()=>{
setSave(props.defaultFileList);
},[props.defaultFileList])
//
function addJournals(){
@ -65,6 +71,24 @@ function EditComment(props){
})
}
};
function deleteLoad(id){
let arr = [];
let list = save;
if(list && list.length>0 ){
arr = list.filter(i=>i.id !== id);
}
setFileList(arr.map(i=>{return i.id}));
setSave(arr);
}
function UploadFunc(f){
let list = [];
let arr = [];
if(save && save.length>0 ){
arr = save.map(i=>{return i.id});
}
list = arr && arr.length>0 ? f.concat(arr) : f ;
setFileList(list);
};
return(
<div className="grid-item-top pb10">
@ -101,7 +125,7 @@ function EditComment(props){
<Upload
className="commentStyle"
isComplete={attachmentClean}
load={(fileList)=>{setFileList(fileList)}}
load={UploadFunc}
icon={
<img
src={UploadImg}
@ -112,8 +136,16 @@ function EditComment(props){
}
size={100}
showNotification={showNotification}
defaultFileList={props.defaultFileList}
// defaultFileList={props.defaultFileList}
/>
{props.defaultFileList && props.defaultFileList.length > 0 &&
<Attachments
attachments={props.defaultFileList}
showNotification={props.showNotification}
canDelete={true}
deleteLoad={deleteLoad}
></Attachments>
}
<p className="clearfix mt20">
<Button
type="primary"

View File

@ -110,27 +110,17 @@ function IssueCommentList(props){
return(
<div className="commentListBox">
{/* 全部 / 评论 / 操作日志 */}
<div className='typeActionBox mt30 mb20'>
<Radio.Group onChange={(e)=>{setSpin(true);setPage(1);setCategory(e.target.value)}} value={category}>
<Radio value={'comment'} className='typeActionRadio font-14'>评论<span className='journalsCount font-13 ml5'>{journalsCount}</span></Radio>
<Radio value={'operate'} className='typeActionRadio font-14'>操作日志</Radio>
<Radio value={'all'} className='typeActionRadio font-14'>全部</Radio>
</Radio.Group>
<div className={`${!login && journalsCount > 0 && 'pb15'}`}>
<div className='typeActionBox mt30 mb20'>
<Radio.Group onChange={(e)=>{setSpin(true);setPage(1);setCategory(e.target.value);history.push(history.pathname)}} value={category}>
<Radio value={'comment'} className='typeActionRadio font-14'>评论<span className='journalsCount font-13 ml5'>{journalsCount}</span></Radio>
<Radio value={'operate'} className='typeActionRadio font-14'>操作日志</Radio>
<Radio value={'all'} className='typeActionRadio font-14'>全部</Radio>
</Radio.Group>
</div>
</div>
{/* 添加评论 */}
{category !== 'operate' && <div className="pt5 pb30">
{login ? showEdit === 1 ? <EditComment {...props} cancelMd={cancelMd}/> : <div className="addComments">
<img src={getImageUrl(image_url)} alt="" />
<div style={{flex:1}}>
<CheckProfile {...props} sureFunc={()=>{setShowEdit(1)}}>
<Input className='addCommentBox' placeholder="添加评论"/>
</CheckProfile>
</div>
</div> : <div className='unLoginComment font-15 pl20'>
{/* 未登录用户 */}
<a className='mr5 loginBtn' onClick={()=>{showLoginDialog()}}>登录</a>并参与评论与回复
</div>}
</div>}
{/* 评论快速入口-仅有评论且登录时展示 */}
{login && category !== 'operate' && journalsCount > 0 && <div className={`pb30 ${journalsCount > 0 && 'pt15'}`}><div className='gotoComment'>点击<a className='ml5' href='#addComments'>添加评论</a></div></div>}
{/* 评论/操作日志 展示列表 */}
<Spin spinning={spin}>
<div className={`issueCommentsBox ${category === 'comment' && !spin && 'justComment'}`}>
@ -200,7 +190,7 @@ function IssueCommentList(props){
</div>
{login && <div>
{/* 平台管理员/仓库管理员/发布评论者/回复评论者/issue创建者 */}
{(admin || isManager || login === i.user.login || login === i.reply_user.login || user_id === author.id) && <Popconfirm
{(admin || isManager || login === i.user.login || (i.reply_user && login === i.reply_user.login) || user_id === author.id) && <Popconfirm
placement="bottom"
title={"确定要删除当前回复吗?"}
okText="是"
@ -232,8 +222,20 @@ function IssueCommentList(props){
{totalCount>limit && <div className='mt20 paginationIssueComment mb20'>
<Pagination simple current={page} pageSize={limit} total={totalCount} onChange={(page)=>setPage(page)}/>
</div>}
{/* 无数据 新建第一条评论时暂无数据隐藏 用户未登录隐藏*/}
{journals && !journals.length && showEdit !== 1 && login && <Nodata _html="暂无数据"/>}
{/* 添加评论 */}
{category !== 'operate' && <div className="pb30" style={{marginTop: journalsCount ? '-5px' : '30px'}}>
{login ? showEdit === 1 ? <EditComment {...props} cancelMd={cancelMd}/> : <div className="addComments" id='addComments'>
<img src={getImageUrl(image_url)} alt="" />
<div style={{flex:1}}>
<CheckProfile {...props} sureFunc={()=>{setShowEdit(1)}}>
<Input className='addCommentBox' placeholder="添加评论"/>
</CheckProfile>
</div>
</div> : <div className='unLoginComment font-15 pl20'>
{/* 未登录用户 */}
<a className='mr5 loginBtn' onClick={()=>{showLoginDialog()}}>登录</a>并参与评论与回复
</div>}
</div>}
</div>
)
}

View File

@ -115,6 +115,7 @@
position: relative;
top: -6px;
padding: 10px 15px 16px;
width: 0;
&::before{
content: '';
display: block;
@ -192,4 +193,12 @@
}
.paginationIssueComment{
text-align: center;
}
.gotoComment{
background-color:rgba(241, 243, 252, 0.55);
border-radius:4px;
color: rgba(172, 176, 191, 1);
padding: 8px 20px;
margin-top: -10px;
&>a{color: rgba(70, 106, 255, 1);}
}

View File

@ -58,7 +58,12 @@ function Datas({checkbox ,item , projectsId,owner}){
<span className="mr12">{item.created_at} 发布</span>
<span className="mr20">{item.updated_at}更新</span>
{item.blockchain_token_num && <span className="mr30"><img src={gold} alt="" width="13px" className="mr3"/>{item.blockchain_token_num}</span>}
{item.milestone_name && <span style={{maxWidth:item.blockchain_token_num ? "261px":"340px"}} title={item.milestone_name} className="task-hide"><i className="iconfont icon-lichengbeiicon1 font-12 mr3"></i>{item.milestone_name}</span> }
{item.milestone_name &&
<Link to={`/${owner}/${projectsId}/milestones/${item.milestone_id}`} onClick={()=>{window.scrollTo(0,0)}} style={{maxWidth:item.blockchain_token_num ? "261px":"340px",color:"#898d9d"}} title={item.milestone_name} className="task-hide">
<i className="iconfont icon-lichengbeiicon1 font-12 mr3"></i>
{item.milestone_name}
</Link>
}
</div>
</div>
</div>

View File

@ -31,19 +31,17 @@ function Drop({overlay , children , placement, overlayClassName},ref){
}
}
return(
<li>
<Dropdown
placement={placement}
visible={visible}
overlay={<div ref={refFa}>{overlay}</div>}
trigger={['click']}
overlayClassName={overlayClassName}
>
<span className="dropspan" ref={refBox} onClick={()=>setVisible(visible ? false : true)}>
{children}
</span>
</Dropdown>
</li>
<Dropdown
placement={placement}
visible={visible}
overlay={<div ref={refFa}>{overlay}</div>}
trigger={['click']}
overlayClassName={overlayClassName}
>
<span className="dropspan" ref={refBox} onClick={()=>setVisible(visible ? false : true)}>
{children}
</span>
</Dropdown>
)
}
export default forwardRef(Drop);

View File

@ -5,6 +5,7 @@ import Drop from './drop';
const { Search } = Input;
/**
* @param {className} <li>的样式名称
* @param {name} 未选择时显示的内容
* @param {lists} 下拉列表从接口获取需一一填充
* @param {imgControl} 控制是否显示头像
@ -15,13 +16,13 @@ const { Search } = Input;
* @param {update} 编辑状态
* @param {double} 是否可以多选
*/
function Menus({name, ids , lists , size , imgControl , searchFunc , chooseFunc , update , double ,names },ref){
function Menus({className,name, ids , lists , size , imgControl , searchFunc , chooseFunc , update , double ,names },ref){
const dropRef = useRef(null);
const [ chooseValue , setChooseValue ] = useState([]);
const [ showValue , setShowValue ] = useState(undefined);
useEffect(()=>{
setChooseValue(ids);
ids ? setChooseValue(ids.split(",")) : setChooseValue([]);
setShowValue(names);
},[ids,names])
@ -81,10 +82,12 @@ function Menus({name, ids , lists , size , imgControl , searchFunc , chooseFunc
</div>
}
return(
<Drop ref={dropRef} overlay={menu(lists)} placement={"bottomRight"} >
<span className="task-hide">{showValue || (update ? `更换${name}` : name) }</span>
<Icon type="caret-down" className="ml5 color-grey-6" />
</Drop>
<li className={className || ""}>
<Drop className={className} ref={dropRef} overlay={menu(lists)} placement={"bottomRight"} >
<span className="task-hide">{showValue || (update ? `更换${name}` : name) }</span>
<Icon type="caret-down" className="color-grey-6" />
</Drop>
</li>
)
}
export default Menus;

View File

@ -9,6 +9,7 @@ function NewPanel(props,ref){
const [ description , setDescription ] = useState("");
const [ fileList , setFileList] = useState(undefined);
const [ attachments , setAttachments ] = useState([]);
const [ save , setSave ] = useState([]);
const [ receivers_login , setReceiversLogin ] = useState(undefined);
const { createFunc , title , desc , files , onCancel , owner , projectsId } = props;
@ -21,6 +22,7 @@ function NewPanel(props,ref){
},100)
desc && setDescription(desc);
files && setAttachments(files);
files && setSave(files);
},[title , desc , files])
function onContentChange(value){
@ -29,8 +31,8 @@ function NewPanel(props,ref){
function UploadFunc(f){
let list = [];
let arr = [];
if(files && files.length>0 ){
arr = files.map(i=>{return i.id});
if(save && save.length>0 ){
arr = save.map(i=>{return i.id});
}
list = arr && arr.length>0 ? f.concat(arr) : f ;
setFileList(list);
@ -38,11 +40,12 @@ function NewPanel(props,ref){
function deleteLoad(id){
let arr = [];
let list = files
let list = save;
if(list && list.length>0 ){
arr = list.filter(i=>i.id !== id);
}
setFileList(arr.map(i=>{return i.id}));
setSave(arr);
}
function changeAtWhoLoginList(loginList){

View File

@ -275,6 +275,7 @@ function Details(props){
...params
}).then(result=>{
if(result){
window.scrollTo(0,0);
props.showNotification("疑修更新成功!");
//
setCommentReload(Math.random());
@ -303,6 +304,7 @@ function Details(props){
due_date
}).then(result=>{
if(result && result.data && result.data.project_issues_index){
window.scrollTo(0,0);
props.showNotification("任务复制成功!");
props.history.push(`/${owner}/${projectsId}/issues/${result.data.project_issues_index}`);
}
@ -434,7 +436,7 @@ function Details(props){
editFlag={editFlag}
chooseFunc={(list)=>{setAssigner_choose(list);let l = list && list.length>0 ?list.map(i=>{return i.id || i.name}):[];saveForeach(l);}}
double={5}
removeFlag
removeFlag={!editFlag}
/>
<DropMenu
placeholder="状态"
@ -461,7 +463,7 @@ function Details(props){
chooseFunc={(list)=>{setTag_choose(list);let l = list && list.length>0 ?list.map(i=>{return i.id || i.name}):[];saveForeach(undefined,undefined,undefined,l);}}
double={3}
colorFlag="2"
removeFlag
removeFlag={!editFlag}
editFlag={editFlag}
onAdd={permission && permission !== "Reporter" ? ()=>{getSign();} :false}
owner={owner}

View File

@ -63,7 +63,6 @@ function List(props){
useEffect(()=>{
const datas = cookie.load('issuestates');
let states = datas === "undefined" ? undefined : datas;
console.log(states);
if(states){
setAboutMe(states.participant_category);
setCategory(states.category);
@ -82,12 +81,12 @@ function List(props){
useEffect(()=>{
let v = allValue && allValue > 0;
if((updateIds && !v && (updateIds.author_id || updateIds.issue_priorities_id || updateIds.issue_tag_ids || updateIds.milestone_id || updateIds.sort_by || updateIds.status_id ||updateIds.assigner_id)) || value || end_date || begin_date){
if((updateIds && !v && (updateIds.author_id || updateIds.issue_priorities_id || updateIds.issue_tag_ids || updateIds.milestone_id || updateIds.sort_by || updateIds.status_id ||updateIds.assigner_id)) || value || end_date || begin_date || aboutMe !=="all"){
setClearFlag(true);
}else{
setClearFlag(false);
}
},[updateIds,value,allValue,begin_date,end_date])
},[updateIds,value,allValue,begin_date,end_date,aboutMe])
// hook
const useUpdateEffect = (fn, inputs) => {
const didMountRef = useRef(false);

View File

@ -197,6 +197,7 @@ function New(props){
blockchain_token_num:rewardAmount
}).then(result=>{
if(result && result.data && result.data.project_issues_index){
window.scrollTo(0,0);
props.showNotification("任务创建成功!");
props.history.push(`/${owner}/${projectsId}/issues/${result.data.project_issues_index}`);
}

View File

@ -47,22 +47,6 @@
background-color: #f3f4f6;
}
}
// 操作按钮-蓝色
.operateButton{
padding:0px 15px;
height:32px;
color: #fff!important;
cursor: pointer;
line-height: 30px;
background-color:rgba(70, 106, 255, 1);
border:1px solid rgba(70, 106, 255, 1);
border-radius: 4px;
display: block;
&:hover{
background-color:#708cff;
border-color:#708cff;
}
}
// flex-左右上下居中
.between{
display: flex;
@ -187,11 +171,15 @@
}
.dropboxul{
padding-right: 20px;
li.minwidth{
min-width: 70px;
}
li{
min-width: 106px;
min-width:88px;
display: flex;
justify-content: flex-end;
cursor: default;
margin-left: 12px;
.dropspan{
display: flex;
align-items: center;
@ -804,7 +792,7 @@
}
}
li{
width: 100px;
width: 98px;
height:32px;
line-height:30px;
text-align: center;

View File

@ -49,6 +49,7 @@ function CoderDepot(props){
const [ desc , setDesc ] = useState(undefined);
const [ website , setWebsite ] = useState(undefined);
const [ lesson_url , setLessonUrl ] = useState(undefined);
const [ topics, setTopics] = useState(undefined);
const [ readme , setReadme ] = useState(undefined);
const [ defaultBranch , setDefaultBranch ] = useState(undefined);
const [ editReadme , setEditReadme ] = useState(false);
@ -62,8 +63,9 @@ function CoderDepot(props){
branchName = returnbar(branchName);
const details = props.projectDetail;
let pathname = props.history.location.pathname;
const { platform } = props ;
//distribution
const distribution = details && details.type != 2 && (details.permission === "Admin" || details.permission === "Owner" || details.permission === "Manager");
const distribution = details && details.type !== 2 && ( details.permission && details.permission !== "Reporter");
const { bannerList } = props;
useEffect(()=>{
if(bannerList && bannerList.length>0){
@ -93,6 +95,7 @@ function CoderDepot(props){
useEffect(()=>{
if(details){
setProjectDetail(details);
setTopics(details.topics);
setDesc(details.description);
setWebsite(details.website);
setLessonUrl(details.lesson_url);
@ -139,17 +142,19 @@ function CoderDepot(props){
},[projectsId,owner,pathname,defaultBranch])
useEffect(()=>{
setReadOnly(props.history.location.search.indexOf('edit') !== -1);
axios.get(`/${owner}/${projectsId}/releases.json`).then((result)=>{
if(result && result.data){
const release = {
"list":result.data.releases,
"total_count":result.data.releases && result.data.releases.length
if(platform){
setReadOnly(props.history.location.search.indexOf('edit') !== -1);
axios.get(`/${owner}/${projectsId}/releases.json`).then((result)=>{
if(result && result.data){
const release = {
"list":result.data.releases,
"total_count":result.data.releases && result.data.releases.length
}
setReleaseVersions(release);
}
setReleaseVersions(release);
}
})
},[])
})
}
},[platform])
// readme
function getReadmeInfo(path, ref) {
@ -185,7 +190,7 @@ function CoderDepot(props){
setZip_url(result.data.zip_url);
let c = result.data.last_commit
setLastCommit(c && c.commit);
setLastCommitAuthor(c && c.committer);
setLastCommitAuthor(c && c.author);
setMainFlag(true);
setReadOnly(true);
// setReadme(result.data.readme);
@ -233,7 +238,7 @@ function CoderDepot(props){
}
let c = result.data.last_commit
setLastCommit(c && c.commit);
setLastCommitAuthor(c && c.committer);
setLastCommitAuthor(c && c.author);
setMainFlag(false);
setReadOnly(true);
setReadOnly(!editReadme);
@ -295,6 +300,7 @@ function CoderDepot(props){
let checkvalue = turnbar(enBranch);
let enPath = turnbar(path);
setType(type);
window.scrollTo(0,0)
props.history.push(`/${owner}/${projectsId}${`/tree/${checkvalue}`}${enPath?`/${enPath}`:""}`);
}
}
@ -339,15 +345,17 @@ function CoderDepot(props){
showNotification={props.showNotification}/>
)
// website
function okUpdate(d,w,l){
function okUpdate(d,w,l, projectTopicNames){
const url = `/${owner}/${projectsId}.json`;
axios.put(url,{
description:d,website:w || "",lesson_url:l||""
description:d,website:w || "",lesson_url:l||"",
project_topic_names: projectTopicNames
}).then(result=>{
if(result && result.data && result.data.id){
setDesc(result.data.description);
setWebsite(result.data.website);
setLessonUrl(result.data.lesson_url);
setTopics(result.data.topics);
}
})
}
@ -378,7 +386,7 @@ function CoderDepot(props){
const fileOperate = type === "dir" && projectDetail && projectDetail.type !== 2 && ((projectDetail.permission && projectDetail.permission !=="Reporter") || (current_user && current_user.admin));
return(
<WhiteBack>
<UpdateDescModal desc={desc} website={website} lesson_url={lesson_url} visible={openModal} onCancel={()=>setOpenModal(false)} onOk={okUpdate}/>
<UpdateDescModal desc={desc} website={website} lesson_url={lesson_url} topicsInfo={topics} visible={openModal} onCancel={()=>setOpenModal(false)} onOk={okUpdate}/>
<Spin spinning={isSpin}>
{
(dirInfo || fileInfo) &&
@ -582,6 +590,10 @@ function CoderDepot(props){
}
</FlexAJ>
{desc && <p className="font-14 color-grey-3 mb15 task-hide-2" style={{lineHeight:"24px",WebkitLineClamp:"4",textAlign:"justify",wordBreak:"break-all"}} title={desc}>{desc}</p>}
{/* 项目标签 */}
{topics && <div className='projectHomeTopics'>
{topics.map(item=><span className='proHomeTopic mr15 mb10 font-13 task-hide'>{item.name}</span>)}
</div>}
{
website &&
<div className="color-grey-6 df pinfos mb5">
@ -622,7 +634,7 @@ function CoderDepot(props){
}
{/* 发布 */}
{
releaseVersions && props.platform &&
releaseVersions &&
<React.Fragment>
<Divider />
<Releases

View File

@ -29,12 +29,22 @@ function CoderDepotCatalogue({item , goToSubRoot , owner , projectsId , platform
<span><i className={`iconfont ${typeIco[`${item.type}`]} mr8`}></i>{item.name}</span>
:
(item.type === "submodule" ?
<React.Fragment>
<a href={`${item.submodule_git_url.slice(0,item.submodule_git_url.length-4)}`} target="_self">
<i className={`iconfont ${typeIco[`${item.type}`]} mr8`}></i>{item.name}
</a>
<span className="ml2">@</span><a href={`${item.submodule_git_url.slice(0,item.submodule_git_url.length-4)}/commits/${truncateCommitId(item.sha)}`} title={item.sha}>{truncateCommitId(item.sha)}</a>
</React.Fragment>
(
item.submodule_git_url ?
<React.Fragment>
<a href={`${item.submodule_git_url.slice(0,item.submodule_git_url.length-4)}`} onClick={()=>{window.scrollTo(0,0)}} target="_self">
<i className={`iconfont ${typeIco[`${item.type}`]} mr8`}></i>{item.name}
</a>
<span className="ml2">@</span>
<a href={`${item.submodule_git_url.slice(0,item.submodule_git_url.length-4)}/commits/${truncateCommitId(item.sha)}`} onClick={()=>{window.scrollTo(0,0)}} title={item.sha}>{truncateCommitId(item.sha)}</a>
</React.Fragment>
:
<React.Fragment>
<i className={`iconfont ${typeIco[`${item.type}`]} mr8`}></i>{item.name}
<span className="ml2">@</span>
<span title={item.sha}>{truncateCommitId(item.sha)}</span>
</React.Fragment>
)
:
<a onClick={()=>goToSubRoot(item.path,item.type,item.name)}>
<i className={`iconfont ${typeIco[`${item.type}`]} mr8`}></i>{item.name}

View File

@ -51,7 +51,7 @@ function CoderDepotReadme({ operate , history , readme , ChangeFile }){
</span>
</Dropdown>
<span className="commonBox-title-read"><a href="#readme ">README.md</a></span>
<span className="commonBox-title-read"><a href="#readme ">{readme.name}</a></span>
</AlignCenter>
{

View File

@ -5,6 +5,7 @@ import axios from "axios";
import Meditor from "../Newfile/m_editor";
import RenderHtml from "../../components/render-html";
import ReadmeCatelogue from "./sub/ReadmeCatelogue";
import { Base64 } from "js-base64";
const $ = window.$;
function bytesToSize(bytes) {
@ -158,6 +159,7 @@ class CoderRootFileDetail extends Component {
axios.delete(url, {
params: {
filepath: detail.path,
base64_filepath:Base64.encode(detail.path),
branch:branchName,
sha: detail.sha,
},

View File

@ -1,6 +1,7 @@
import React, { Component } from 'react';
import { Spin, Tooltip } from 'antd';
import { Link, Route, Switch } from 'react-router-dom';
import { withRouter } from "react-router";
import { Content, AlignTop } from '../Component/layout';
import DetailBanner from './sub/DetailBanner';
import cookie from 'react-cookies';
@ -21,18 +22,6 @@ const Setting = Loadable({
loader: () => import('../Settings/Index'),
loading: Loading,
})
const TagList = Loadable({
loader: () => import('../Order/Tags'),
loading: Loading,
})
const OrderNew = Loadable({
loader: () => import('../Order/New'),
loading: Loading,
})
const OrderDetail = Loadable({
loader: () => import('../Order/Detail'),
loading: Loading,
})
const OrderIndex = Loadable({
loader: () => import('../Issues/index'),
loading: Loading,
@ -55,18 +44,6 @@ const OrderupdateMilepost = Loadable({
loader: () => import('../Order/UpdateMilepost'),
loading: Loading,
})
const OrderupdateDetail = Loadable({
// loader: () => import('../Order/UpdateDetail'),
loader: () => import('../Order/order_form'),
loading: Loading,
})
const OrdercopyDetail = Loadable({
loader: () => import('../Order/CopyDetail'),
loading: Loading,
})
//合并请求
const MergeIndexDetail = Loadable({
loader: () => import('../Merge/merge'),
@ -155,10 +132,6 @@ const Review = Loadable({
loader: () => import('../Newfile/codeReview'),
loading: Loading,
});
// const WebIDE = Loadable({
// loader: () => import('../Newfile/webIDE'),
// loading: Loading,
// });
/**
* permissionManager:管理员Reporter报告人员(只有读取权限)Developer开发人员除不能设置仓库信息外
*/
@ -222,6 +195,7 @@ class Detail extends Component {
}
}
componentDidMount = () => {
this.getProject();
let history = this.props.location;
@ -604,7 +578,7 @@ class Detail extends Component {
}
<span className="detail_tag_btn">
<a className="detail_tag_btn_name" style={{ cursor: platform ? "pointer" : "default" }} onClick={() => this.focusFunc(watched)}>
<i className={watched ? "iconfont icon-shixing color-orange font-16 mr3" : "iconfont icon-kongxing color-grey-9 font-16 mr3"}></i>
<i className={watched ? "iconfont icon-yiguanzhuicon color-orange font-16 mr3" : "iconfont icon-guanzhuicon color-grey-9 font-16 mr3"}></i>
<span>{watched ? '取消关注' : '关注'}</span>
</a>
{
@ -620,7 +594,7 @@ class Detail extends Component {
</span>
<span className="detail_tag_btn">
<a className="detail_tag_btn_name" style={{ cursor: platform ? "pointer" : "default" }} onClick={() => this.pariseFunc(praised)}>
<i className={praised ? "iconfont icon-weibiaoti105 color-orange font-14 mr3" : "iconfont icon-guanzhu color-grey-9 font-14 mr3"}></i>
<i className={praised ? "iconfont icon-yidianzan_icon color-orange font-15 mr3" : "iconfont icon-xingzhuang color-grey-9 font-14 mr3"}></i>
<span>{praised ? '取消点赞' : '点赞'}</span>
</a>
{
@ -636,7 +610,7 @@ class Detail extends Component {
<span className="detail_tag_btn" loading={forkSpin}>
<Tooltip title="复刻是fork的中文名即复制代码仓库" placement="bottom">
<a className="detail_tag_btn_name" style={{ cursor: platform ? "pointer" : "default" }} onClick={this.forkFunc}>
<i className="iconfont icon-fork color-grey-9 mr3 font-16"></i><span>(Fork)</span>
<i className="iconfont icon-morenfuke_icon1 color-grey-9 mr3 font-16"></i><span>(Fork)</span>
</a>
</Tooltip>
{
@ -719,13 +693,13 @@ class Detail extends Component {
}
></Route>
{/* wiki编辑文件 */}
<Route path="/:owner/:projectsId/wiki/edit/:wikiName"
<Route path="/:owner/:projectsId/wiki/:wikiName/:key/edit"
render={
() => (<WikiEdit {...this.props} {...this.state} {...common} />)
}
></Route>
{/* wiki具体某一个地址 */}
<Route path="/:owner/:projectsId/wiki/:wikiName"
<Route path="/:owner/:projectsId/wiki/:wikiName/:key"
render={
(props) => (<Wiki {...this.props} {...props} {...this.state} {...common} />)
}
@ -934,7 +908,7 @@ class Detail extends Component {
}
}
export default ImageLayerOfCommentHOC({
export default withRouter(ImageLayerOfCommentHOC({
imgSelector: ".imageLayerParent img, .imageLayerParent .imageTarget",
parentSelector: ".newContainer",
})(Detail);
})(Detail));

View File

@ -263,6 +263,9 @@
color: #466AFF;
}
}
li{
line-height: 15px;
}
margin-top: 2px;
// cursor: pointer;
#ptxt{
@ -526,4 +529,22 @@
}
.readmeFile p[align=center]{
white-space: normal;
}
// 项目标签
.projectHomeTopics{
display: flex;
flex-wrap: wrap;
&.myProList .proHomeTopic{
background-color:#f3f8ff;
color:#4c5b76;
}
.proHomeTopic{
background-color: #eff2ff;
border-radius: 4px;
max-width: 158px;
padding: 0 10px;
height: 24px;
line-height: 24px;
color: $primary-color;
}
}

View File

@ -6,7 +6,7 @@ import { Link } from 'react-router-dom';
import '../css/index.scss';
import Nodata from '../Nodata';
import './list.scss';
import img_parise from '../Images/parise.png';
import './Index.scss';
class IndexItem extends Component {
TurnToDetail = (login, url) => {
@ -35,7 +35,7 @@ class IndexItem extends Component {
<div className="p-r-Infos">
<div className="p-r-name">
<AlignCenter>
<Link to={`/${item.author.login}/${item.identifier}`} target="_blank" title={`${item.author.name}/${item.name}`} className="color-grey-3 font-18 task-hide " style={{maxWidth: 470 }}>
<Link to={`/${item.author.login}/${item.identifier}`} title={`${item.author.name}/${item.name}`} className="color-grey-3 font-18 task-hide " style={{maxWidth: 470 }}>
{item.author.name}/{item.name}
</Link>
{ !item.is_public && <span className="privateTag">私有</span> }
@ -57,19 +57,22 @@ class IndexItem extends Component {
{
item.praises_count && item.praises_count>0 ?
<span className="pariseTag">
<img src={img_parise} alt="" className="pariseImg" /> {item.praises_count}
<i className="iconfont icon-xingzhuang font-12 mr3" style={{color:"#ffb830"}} /> {item.praises_count}
</span>:""
}
{
item.forked_count && item.forked_count>0 ?
<span>
<i className="iconfont icon-fork mr3 font-16 color-blue"/>fork {item.forked_count}
<i className="iconfont icon-morenfuke_icon1 mr3 font-14 color-blue"/>fork {item.forked_count}
</span>:""
}
</span>
</div>
<p className="break_word task-hide-2 mt10" style={{ maxHeight: "44px",lineHeight:"22px" }}>{item.description}</p>
{/* 项目标签 */}
{item.topics && <div className='projectHomeTopics myProList'>
{item.topics.map(topic=><span className='proHomeTopic mr15 font-13 task-hide'>{topic.name}</span>)}
</div>}
<div className="p-r-about">
<span className="p-r-detail">
{item.last_update_time ? <span><label>更新于</label>{item.time_ago}</span> : ""}

View File

@ -779,6 +779,8 @@ a.color-grey-ccc:hover{
}
.treecopy-cont{
padding: 4px 15px;
display: flex;
align-items: center;
}
.btn-83{
margin-left: 20px;

View File

@ -93,7 +93,7 @@ function Index() {
style={{width:"300px"}}
allowClear
/>
<Link to={`/explore/all${search ? '?search='+search : ''}`} target="_blank">更多<i className="iconfont icon-triangle font-12"></i></Link>
<Link to={`/explore/all${search ? '?search='+search : ''}`}>更多<i className="iconfont icon-triangle font-12"></i></Link>
</div>
<Spin spinning={isSpin}>
<div style={{minHeight:"400px"}}>
@ -110,20 +110,24 @@ function Index() {
<img className="p-r-photo" alt="" src={i.author && i.author.image_url} ></img>
</a>
:
<Link to={`/${i.author && i.author.login}`} target="_blank"><img src={getImageUrl(`/${i.author && i.author.image_url}`)} alt="" /></Link>
<Link to={`/${i.author && i.author.login}`}><img src={getImageUrl(`/${i.author && i.author.image_url}`)} alt="" /></Link>
}
<div className="itemTitle">
<div className="item-title-infos">
<Link to={`/${i.author && i.author.login}/${i.identifier}`} target="_blank" className="infotitle task-hide">{i.author && i.author.name}/{i.name}</Link>
{i.praises_count > 0 ? <span><i className="iconfont icon-dianzan11 mr3 font-16"></i>{i.praises_count}</span> :"" }
{i.forked_count > 0 ? <span><i className="iconfont icon-fork2 mr3 font-13"></i>{i.forked_count}</span>:""}
<Link to={`/${i.author && i.author.login}/${i.identifier}`} className="infotitle task-hide">{i.author && i.author.name}/{i.name}</Link>
{i.praises_count > 0 ? <span><i className="iconfont icon-xingzhuang mr3 font-14"></i>{i.praises_count}</span> :"" }
{i.forked_count > 0 ? <span><i className="iconfont icon-yifuke_icon mr3 font-14"></i>{i.forked_count}</span>:""}
</div>
<div className="item-desc task-hide-2">
{i.description}
</div>
{/* 项目标签 */}
{i.topics && <div className='proListTopics'>
{i.topics.map(item=><span className='proListTopic mr15 font-13 task-hide'>{item.name}</span>)}
</div>}
<div className="item-data">
{i.category && i.category.id ? <span className="category">{i.category.name}</span> :"" }
{i.language && i.language.id ? <span className="language mr30">{i.language.name}</span> :""}
{i.language && i.language.id ? <span className={`language mr30 ${i.category && i.category.id ? 'hasCate' : ''}`}>{i.language.name}</span> :""}
<span style={{lineHeight:"15px",display:"flex"}}><i className="iconfont icon-shijian font-15 mr5"></i>更新于{i.time_ago}</span>
</div>
</div>

View File

@ -439,8 +439,10 @@
}
.language{
position: relative;
padding-left: 11px;
&::before{
&.hasCate{
padding-left: 11px;
}
&.hasCate::before{
position: absolute;
content: "";
width: 1px;
@ -558,4 +560,18 @@
}
}
}
}
.proListTopics{
display: flex;
flex-wrap: wrap;
.proListTopic{
background-color:#f3f8ff;
border-radius: 4px;
max-width: 158px;
padding: 0 10px;
height: 24px;
line-height: 24px;
color: #4c5b76;
margin-bottom: 5px !important;
}
}

View File

@ -63,7 +63,7 @@ function Contribute(props){
<AlignCenter className='mb15'>
<img alt="" style={{borderRadius:"50%",marginRight:"10px"}} src={getImageUrl(`/${item.image_url}`)} width="50px" height="50px"/>
<div>
<Link to={`/${item.login}`} className="font-16">{item.name}</Link>
<a href={item.login ? `/${item.login}` : `mailto:${item.email}`} className="font-16">{item.name}</a>
<p className="font-12 color-grey-9">提交{item.contributions}{item.contribution_perc ? ` I 贡献占比${item.contribution_perc}` : ''}</p>
</div>
</AlignCenter>

View File

@ -1,9 +1,21 @@
import React , { forwardRef, useEffect } from 'react';
import {Form , Modal , Input } from 'antd';
import {Form , Modal , Input, Select } from 'antd';
import "./sub.scss";
import axios from 'axios';
import { useState } from 'react';
const { TextArea } = Input;
function UpdateDescModal({form , visible , onCancel , onOk,desc,website,lesson_url}){
function UpdateDescModal({form , visible , onCancel , onOk,desc,website,lesson_url, topicsInfo}){
const { getFieldDecorator, validateFields , setFieldsValue } = form;
const [topics, setTopics] = useState(undefined);
useEffect(()=>{
//
axios.get(`/v1/project_topics`).then(res=>{
if(res && res.data){
setTopics(res.data.project_topics);
}
})
}, [])
useEffect(()=>{
if(desc || website){
@ -17,10 +29,22 @@ function UpdateDescModal({form , visible , onCancel , onOk,desc,website,lesson_u
validateFields((err,values)=>{
if(!err){
onCancel();
onOk(values.desc,values.website,values.lesson_url)
onOk(values.desc,values.website,values.lesson_url, values.project_topic_names)
}
})
}
//
function checkTopics(rule, value, callback){
const boolLength = value.filter(item=>item.length > 20);
if(value.length > 5){
callback("最多创建5个标签");
}else if(boolLength.length){
callback("每个标签最多支持20个汉字");
}
callback();
}
return(
<Modal
title={"修改信息"}
@ -31,8 +55,9 @@ function UpdateDescModal({form , visible , onCancel , onOk,desc,website,lesson_u
onOk={onSure}
okText="确定"
cancelText="取消"
width="400px"
width="520px"
className={"descmodal"}
maskClosable={false}
>
<Form>
<Form.Item label="项目简介">
@ -42,6 +67,16 @@ function UpdateDescModal({form , visible , onCancel , onOk,desc,website,lesson_u
<TextArea placeholder="请输入项目简介" rows={4} maxLength={200}/>
)}
</Form.Item>
<Form.Item label="项目标签">
{getFieldDecorator("project_topic_names",{
rules:[{validator:checkTopics}],
initialValue: topicsInfo && topicsInfo.map(item=>item.name)
})(
<Select mode='tags' placeholder="请输入标签名称" getPopupContainer={trigger => trigger.parentNode} maxLength={2}>
{topics && topics.map(item=> <Select.Option key={item.name}>{item.name}</Select.Option>)}
</Select>
)}
</Form.Item>
<Form.Item label="website">
{getFieldDecorator("website",{
rules:[]

View File

@ -22,6 +22,16 @@
height: 20px;
line-height: 20px;
}
.ant-select-selection--multiple{
padding: 0 0 5px !important;
}
.ant-select-selection--multiple .ant-select-selection__choice{
color: $primary-color;
background-color:#eff2ff;
.ant-select-selection__choice__remove{
color: $primary-color;
}
}
}
.menuslist{

View File

@ -22,7 +22,8 @@ function Tags(props) {
//
const [visible, setVisible] = useState(false);
const { projectsId, owner } = props.match.params;
const { projectDetail} = props;
const { projectDetail, current_user} = props;
const permission = projectDetail && projectDetail.permission;
useEffect(()=>{
if(projectDetail && document.title.indexOf('所有标签-') === -1){
@ -60,18 +61,18 @@ function Tags(props) {
render: (txt, item) => {
return (
<div className="tagBranch">
<Link className="hover tagClass font-16" to={`/${owner}/${projectsId}/tree/${item.name}`}>{item.name}</Link>
<Link className="hover tagClass font-16" to={`/${owner}/${projectsId}/tree/${item.name}`} onClick={()=>{window.scrollTo(0,0)}}>{item.name}</Link>
<div className="tagModel">
{
item.tagger &&
<Tooltip placement="top" title={item.tagger.name}>
{
item.tagger.id ?
<Link className="mr3 tagModelImg" to={`/${item.tagger.login}`} >
<Link className="mr3 tagModelImg" to={`/${item.tagger.login}`} onClick={()=>{window.scrollTo(0,0)}}>
<img src={getImageUrl(`/${item.tagger && item.tagger.image_url}`)} alt="" />
</Link>
:
<span className="mr3 tagModelImg" style={{ cursor: "default" }}>
<span className="mr3 tagModelImg" style={{ cursor: "default" }} onClick={()=>{window.scrollTo(0,0)}}>
<img src={getImageUrl(`/${item.tagger && item.tagger.image_url}`)} alt="" />
</span>
}
@ -96,7 +97,7 @@ function Tags(props) {
<span style={{lineHeight: '30px'}}>
<img src={Tree} alt="sha" width={"16px"}/>
<Tooltip placement="top" title={`最后提交日期:${item.created_at_unix ? moment(item.created_at_unix * 1000).format('YYYY-MM-DD') : ''}`}>
<Link className="hover color-blue" to={`/${owner}/${projectsId}/commits/${truncateCommitId(`${item.id}`)}`}>{truncateCommitId(item.id)}</Link>
<Link className="hover color-blue" to={`/${owner}/${projectsId}/commits/${truncateCommitId(`${item.id}`)}`} onClick={()=>{window.scrollTo(0,0)}}>{truncateCommitId(item.id)}</Link>
</Tooltip>
<input type="text" id={`value${truncateCommitId(`${item.id}`)}`} value={`${truncateCommitId(item.id)}`}/>
</span>
@ -130,8 +131,24 @@ function Tags(props) {
<a href={`${item.zipball_url}`} download className="btn-83" style={{width: '76px'}}>
<i className="iconfont icon-xiazai-icon font-16 mr5"></i>ZIP
</a>
{item.has_release ? <Tooltip title={`该标签已绑定发行版,不支持创建`}><a className="btn-83 disabledBut">创建发行版</a></Tooltip> : <Link to={`/${owner}/${projectsId}/releases/new?tag=${item.name}`} className="btn-83">创建发行版</Link>}
{item.has_release ? <Tooltip title={`该标签已绑定发行版,不支持删除操作`}><Button style={{width: '76px'}} disabled className='ml8'>删除</Button></Tooltip> : <Button className='f6_bor_but ml8' style={{width: '76px'}} onClick={()=>{setVisible(item)}}>删除</Button>}
{
(current_user && current_user.login) && (permission && permission!=="Reporter") &&
<React.Fragment>
{item.has_release ?
<Tooltip title={`该标签已绑定发行版,不支持创建`}>
<a className="btn-83 disabledBut">创建发行版</a>
</Tooltip>
:
<Link to={`/${owner}/${projectsId}/releases/new?tag=${item.name}`} className="btn-83">创建发行版</Link>
}
{item.has_release ?
<Tooltip title={`该标签已绑定发行版,不支持删除操作`}>
<Button style={{width: '76px'}} disabled className='ml8'>删除</Button>
</Tooltip> :
<Button className='f6_bor_but ml8' style={{width: '76px'}} onClick={()=>{setVisible(item)}}>删除</Button>
}
</React.Fragment>
}
</React.Fragment>
)
}

View File

@ -31,7 +31,6 @@ function Index(props) {
const { projectsId , owner } = props.match.params;
const { isManager , isDeveloper , projectDetail , defaultBranch, getDetail } = props;
useEffect(()=>{
// getList();
const url = `/v1/${owner}/${projectsId}/branches.json`;
@ -76,7 +75,7 @@ function Index(props) {
className: "branchNameColumn",
render: (txt, item) => {
return (
<Link to={`/${owner}/${projectsId}/tree/${turnbar(txt)}`} className="task-hide">{txt}</Link>
<Link to={`/${owner}/${projectsId}/tree/${turnbar(txt)}`} onClick={()=>{window.scrollTo(0,0)}} className="task-hide">{txt}</Link>
)
}
},
@ -241,6 +240,7 @@ function Index(props) {
name: info.name
}).then(res=>{
if(res && res.data){
getDetail();
setReload(Math.random());
!res.data.status && message.success('操作成功');
}

View File

@ -161,7 +161,7 @@
padding:0px 15px;
border-right: 1px solid rgba(153, 153, 153, 0.4);
height: 100%;
width: 135px;
width: 140px;
img{
margin-right: 4px;
}

View File

@ -71,16 +71,16 @@ function version(props) {
<span className={`${item.draft === "稳定" ?"versionTag green":"versionTag orange"}`}>{item.draft}</span>
<span className="color-grey-3 mt15 font-12">
<i className="iconfont icon-biaoqianicon mr3 font-14"></i>
<Link className="hover" to={`/${owner}/${projectsId}/tree/${item.tag_name}`} >{item.tag_name}</Link>
<Link className="hover" to={`/${owner}/${projectsId}/tree/${item.tag_name}`} onClick={()=>{window.scrollTo(0,0)}}>{item.tag_name}</Link>
</span>
<span className="color-grey-3 font-12">
<img src={Tree} width="16px" color="#333333" className="mr3"/>
<Link className="hover" to={`/${owner}/${projectsId}/commits/${truncateCommitId(`${item.sha}`)}`}>{truncateCommitId(item.sha)}</Link>
<img src={Tree} width="16px" color="#333333" className="mr3" alt=""/>
<Link onClick={()=>{window.scrollTo(0,0)}} className="hover" to={`/${owner}/${projectsId}/commits/${truncateCommitId(`${item.sha}`)}`}>{truncateCommitId(item.sha)}</Link>
</span>
</span>
<div className="versionInfo_right">
<div className="versionName">
<Link to={`/${owner}/${projectsId}/tree/${item.tag_name}`} className="task-hide color-blue hover font-18">{item.name}</Link>
<Link onClick={()=>{window.scrollTo(0,0)}} to={`/${owner}/${projectsId}/tree/${item.tag_name}`} className="task-hide color-blue hover font-18">{item.name}</Link>
<span>
{data && data.user_admin_permission && type !== 2 && <Link to={{pathname:`/${owner}/${projectsId}/releases/${item.version_id}/update`,state:{"stable":item.draft==="稳定"}}} className="ml15"><i className="iconfont icon-a-bianji1 font-16 color-grey-6"></i></Link>}
{data && data.user_admin_permission && type !== 2 &&
@ -131,7 +131,7 @@ function version(props) {
return (
<React.Fragment>
{
data && data.user_admin_permission && type !== 2 &&
data && data.user_permission && type !== 2 &&
<div className="addReleaseBtn">
<Button type={"primary"} onClick={addFunc} className="btnblue" style={{height:"36px"}}>发布新版本</Button>
</div>
@ -144,7 +144,7 @@ function version(props) {
} else if (releases && releases.length === 0) {
return (
<Empty
operation={data && data.user_admin_permission && type !== 2}
operation={data && data.user_permission && type !== 2}
addFunc={addFunc}
/>
)

View File

@ -109,7 +109,7 @@
margin-top: 5px;
padding-top: 20px;
border-top: 1px solid #eee;
// width: 100%;
width: 100%;
a{
display: block;
color: #333;

View File

@ -72,8 +72,8 @@ function Files({ data,history,owner,projectsId , parentsSha }){
<div onClick={()=>{setIsOpen(!isOpen)}}>
<i className={`iconfont mr5 ${isOpen? "font-18 icon-sanjiaoxing-down":"font-16 icon-triangle"}`}></i>
<span className="color-grey-6 update-file-count">
共有<span className="color-grey-3"> {data && data.files_count} 个文件 </span>被更改包括
{ data && data.total_addition ? <span className="color-green"> {data && data.total_addition} 次插入</span>:"" }
共有<span className="color-grey-3"> {data && data.files_count} 个文件 </span>被更改
{ data && data.total_addition ? <span>包括 <span className="color-green">{data && data.total_addition} 次插入</span></span>:"" }
{ data && data.total_addition && data.total_deletion ? " 和 ":""}
{ data && data.total_deletion ? <span className="color-red"> {data && data.total_deletion} 次删除</span>:""}
</span>
@ -90,8 +90,12 @@ function Files({ data,history,owner,projectsId , parentsSha }){
<a id= {`value${key}`} className="anchorPoint"></a>
<FlexAJ className="filesInfo">
<AlignCenter>
{!item.isBin ? <i className={!item.flag?"iconfont icon-sanjiaoxing-down color-grey-9 mt4":"iconfont icon-triangle font-15 color-grey-9"} onClick={()=>showDown(item.flag,key,item.isBin)}></i>:""}
<span className="cursor-pointer" data-clipboard-text={item.name} onClick={()=>showDown(item.flag,key,item.isBin)}>{item.name}</span>
{!item.isBin ? <i className={!item.flag?"iconfont icon-sanjiaoxing-down color-grey-9":"iconfont icon-triangle font-15 color-grey-9"} onClick={()=>showDown(item.flag,key,item.isBin)}></i>:""}
<span className="cursor-pointer" data-clipboard-text={item.name} onClick={()=>showDown(item.flag,key,item.isBin)}>
{ item.isRenamed && item.old_name}
{ item.isRenamed && <i className="iconfont icon-youjiang font-12 color-grey-8 ml5 mr5"></i> }
{item.name}
</span>
<Tooltip
title={copyfileTipTitle}
onVisibleChange={()=>setCopyfileTipTitle("复制文件路径")}

View File

@ -100,4 +100,16 @@
}
}
}
}
.empBtn{
display: flex;
align-items: center;
justify-content: center;
margin-top: 60px;
padding-top: 20px;
border-top: 1px solid #F3F4F6;
}
.mergeBox{
margin:0px auto;
width: 1200px;
}

View File

@ -56,6 +56,7 @@ class MergeItem extends Component {
<i className={`iconfont icon-hebingqingqiu1 font-14 mr3 i_${status}`}></i>
<Link
to={`/${owner}/${projectsId}/pulls/${item.pull_request_number || item.pull_request_id}`}
onClick={()=>{window.scrollTo(0,0)}}
className="hide-1 font-15 color-grey-3 fwb lineh-30 mr10"
style={{ maxWidth: "600px" }}
>

View File

@ -11,7 +11,7 @@ import { turnbar } from "educoder";
import axios from "axios";
import CheckProfile from '../Component/ProfileModal/Profile';
const Search = Input.Search;
const { Search } = Input;
/**
* issue_chosen:下拉的筛选列表,
* data:列表接口返回的所有数据,
@ -49,6 +49,7 @@ class merge extends Component {
assigned_to_ids: "审查人员",
paix: "排序",
priority_ids: "优先级",
has_created_pull_requests:false,
select_params: {
status_type: "1", //开启中和关闭中,默认为开启中的
assigned_to_id: undefined, // 指派人
@ -113,6 +114,7 @@ class merge extends Component {
issues: result.data.issues,
search_count: result.data.search_count,
isSpin: false,
has_created_pull_requests:result.data.has_created_pull_requests
});
}
})
@ -247,7 +249,7 @@ class merge extends Component {
data,
isSpin,
status_type,
select_params,
select_params,has_created_pull_requests
} = this.state;
const menu = (
<Menu onClick={(e) => this.getMenu(e)}>
@ -266,19 +268,16 @@ class merge extends Component {
</Menu>
);
return (
<div className="main mt20" style={{padding:"0px"}}>
<div className="topWrapper" style={{borderBottom:"none",padding:"20px"}}>
<div className="target-detail-search">
<Search
placeholder="输入关键字搜索合并请求"
enterButton
onSearch={this.searchFunc}
style={{ width: 300 }}
/>
</div>
<div className="mt30 mergeBox">
<div className="topWrapper">
<Search
placeholder="输入关键字搜索合并请求"
style={{ width: 354 , height : 32 }}
onSearch={this.searchFunc}
/>
{
data && data.user_admin_or_developer &&
<CheckProfile {...this.props} className="topWrapper_btn ml10" sureFunc={() => this.checkOperation()}>
<CheckProfile {...this.props} className="operateButton ml10" sureFunc={() => this.checkOperation()}>
+&nbsp;新建合并请求
</CheckProfile>
}
@ -409,7 +408,7 @@ class merge extends Component {
<div style={{minHeight:"470px"}}>
<Spin spinning={isSpin}>
{data && data.search_count && data.search_count > 0 ? (
<div>
<div className="mergeData">
<MergeItem
issues={issues}
search_count={search_count}
@ -437,7 +436,17 @@ class merge extends Component {
) : (
""
)}
{ data && data.issues && data.issues.length === 0 ? <NoneData _html="暂时还没有相关数据!" user_admin_or_developer={data.user_admin_or_developer} projectsId={projectsId} owner={owner} defaultBranch={this.props && turnbar(this.props.defaultBranch) }/> :""}
{ data && data.issues && data.issues.length === 0 ?
<div style={{border:"1px solid rgba(42, 97, 255, 0.23)",borderTop:" none",borderRadius: "0px 0px 2px 2px;"}}>
<NoneData _html="暂时还没有相关数据!"
user_admin_or_developer={data.user_admin_or_developer}
has_created_pull_requests={has_created_pull_requests}
projectsId={projectsId} owner={owner}
defaultBranch={this.props && turnbar(this.props.defaultBranch) }/>
</div>
:""
}
</Spin>
</div>
</div>

View File

@ -1,19 +1,30 @@
import React , { Component } from 'react';
import { Link } from "react-router-dom";
import None from '../Images/emp.png'
class Nodata extends Component{
render(){
const { _html, projectsId , owner , user_admin_or_developer , defaultBranch} = this.props;
const { has_created_pull_requests , projectsId , owner , user_admin_or_developer , defaultBranch} = this.props;
return(
<div className="none_panels">
<div>
<div className="mb15">
<i className="iconfont icon-hebingqingqiu font-80 ver-middle color-grey-b"></i>
</div>
<h3>欢迎使用合并请求</h3>
<div className="color-grey-8">
合并请求可以帮助您与他人协作编写代码{ user_admin_or_developer && <span>在使用之前请先创建一个 <Link className="color-blue" to={`/${owner}/${projectsId}/compare/${defaultBranch}...${defaultBranch}`}>合并请求</Link></span> }
{/* <i className="iconfont icon-hebingqingqiu font-80 ver-middle color-grey-b"></i> */}
<img src={None} alt=""/>
</div>
{
!has_created_pull_requests &&
<div>
<p className="font-20">欢迎使用合并请求</p>
<div className="color-grey-8 mt10">
合并请求可以帮助您与他人协作编写代码请先创建一个合并请求
</div>
{ user_admin_or_developer &&
<div className="empBtn">
<span><Link className="operateButton" to={`/${owner}/${projectsId}/compare/${defaultBranch}...${defaultBranch}`}>去创建合并请求</Link></span>
</div>
}
</div>
}
</div>
</div>
)

View File

@ -1,134 +1,82 @@
import React, { Component } from "react";
import React, { useEffect , useState } from "react";
import Meditor from "./m_editor";
import "./index.css";
import { Input } from "antd";
import { returnbar } from 'educoder';
import FileLanguage from '../Component/FileLanguage';
import FP from '../Component/FileName/index';
class Index extends Component {
constructor(props) {
super(props);
this.state = {
editorValue: "",
filename: "",
language: undefined,
checkName:false
};
}
componentDidUpdate(){
this.updateDocumentTitle();
}
function Index(props){
const [ filename , setFileName ] = useState("");
const [ language , setLanguage ] = useState("");
const [ checkName , setCheckName ] = useState(false);
const [ empty , setEmpty ] = useState(false);
const {projectDetail} = props;
const { pathname } = props && props.location;
const urlroot = pathname.split("newfile/")[1];
const file_path = urlroot ? `/${urlroot}/` :"/";
useEffect(()=>{
updateDocumentTitle();
},[projectDetail])
componentDidMount(){
this.updateDocumentTitle();
}
// 更新网页标题
updateDocumentTitle(){
const {projectDetail} = this.props;
function updateDocumentTitle(){
if(projectDetail && document.title.indexOf('新建文件-') === -1){
const { author, name} = projectDetail;
const { author, name } = projectDetail;
document.title = `新建文件-${author.name}/${name}`;
}
}
// 命名文件
changeFileName = (e) => {
this.setState({
filename: e.target.value,
});
};
// 取消,弹框询问
CancelAddFile = () => {
this.props.history.goBack();
};
select_language = (e) => {
this.setState({
language: e
})
function select_language(e){
setLanguage(e);
}
checkFileName=(e)=>{
let value = (e.target.value);
//\需要单独判断
const str = '!@/:"“”<《》>|%';
let reg = /\\/g;
if(reg.test(value)){
this.setState({
checkName:true
})
}else{
for(var i=0;i<str.length;i++){
let s = str[i];
if(value.indexOf(s) > -1){
this.setState({
checkName:true
})
return;
}else{
this.setState({
checkName:false
})
continue;
}
}
}
function onChangeFile(value){
setFileName(value);
}
render() {
const { pathname } = this.props.location;
const { filename, language , checkName } = this.state;
const urlroot = pathname.split("newfile")[1];
const file_path = `${urlroot}/${filename}`;
const { projectDetail } = this.props;
return (
<React.Fragment>
<div className="main">
<p className="pb15 bor-bottom-greyE font-16 color-grey-3 mb20">
新建文件
</p>
<div>
<div className="grid-item mb30">
<div className="grid-item">
<div className={checkName?"setInputAddon red":"setInputAddon"}>
<Input
addonBefore={`/${ projectDetail && projectDetail.identifier }${urlroot}/`}
value={filename}
onChange={this.changeFileName}
placeholder="命名文件..."
onBlur={this.checkFileName}
/>
{checkName ? <p style={{color:"red",position:'absolute'}}>文件名不能包含下列任何字符\{`@/:"<>|%`}</p>:"" }
</div>
<a onClick={this.CancelAddFile} className="color-blue">
取消
</a>
</div>
<div className="text-right">
<FileLanguage language={language} select_language={this.select_language}></FileLanguage>
</div>
return(
<React.Fragment>
<div className="main">
<p className="pb15 bor-bottom-greyE font-16 color-grey-3 mb20">
新建文件
</p>
<div>
<div className="grid-item mb30">
<div className="grid-item">
<FP
addonBefore={`${ projectDetail && projectDetail.identifier }${returnbar(file_path)}`}
onChangeFile={onChangeFile}
empty={empty}
onEmpty={(e)=>setEmpty(e)}
/>
</div>
<div className="editorBorder">
<Meditor
{...this.props}
{...this.state}
filepath={`${file_path}`}
language = {language}
content={undefined}
readOnly={false}
editor_type="new"
descName={filename ? `Add ${filename}`:""}
checkName={checkName}
></Meditor>
<div className="text-right">
<FileLanguage language={language} select_language={select_language}></FileLanguage>
</div>
</div>
<div className="editorBorder">
<Meditor
{...props}
filepath={`${file_path}`}
language = {language}
content={undefined}
readOnly={false}
editor_type="new"
descName={filename ? `Add ${filename}`:""}
checkName={checkName}
onEmpty={(e)=>setEmpty(e)}
empty={empty}
filename={filename}
></Meditor>
</div>
</div>
</React.Fragment>
);
}
</div>
</React.Fragment>
)
}
export default Index;
export default Index;

View File

@ -5,6 +5,7 @@ import { Link } from "react-router-dom";
import "./index.css";
import { turnbar , returnbar } from 'educoder';
import { Base64 } from 'js-base64';
import FP from '../Component/FileName/index';
import axios from "axios";
const TextArea = Input.TextArea;
@ -16,25 +17,42 @@ class UserSubmitComponent extends Component {
submitType: "0",
filename: "",
isSpin: false,
empty:false,
};
}
componentDidMount=()=>{
const { descName } = this.props;
const { descName , empty , filename} = this.props;
if(descName){
this.props.form.setFieldsValue({
desc:descName
})
}
this.setState({
empty,filename
})
}
componentDidUpdate=(preProps)=>{
const { descName } = this.props;
const { descName ,empty,filename} = this.props;
const nowempty= this.state.empty;
const nowfilename = this.state.filename;
if(preProps && descName && preProps.descName !== descName ){
this.props.form.setFieldsValue({
desc:descName
})
}
if(empty !== undefined && empty !== nowempty){
this.setState({
empty
})
}
if(filename !== "" && filename!==nowfilename){
this.setState({
filename
})
}
}
changeSubmittype = (e) => {
@ -43,25 +61,31 @@ class UserSubmitComponent extends Component {
});
};
// 命名文件
changeFileName = (e) => {
onEmpty=(value)=>{
this.setState({
filename: e.target.value,
});
};
empty:value
})
}
// 提交变更
subMitFrom = () => {
const { filepath, content, editor_type , checkName} = this.props;
const { filepath, content, editor_type , checkName , onEmpty} = this.props;
const { branch, projectsId , owner } = this.props.match.params;
const { submitType, filename } = this.state;
const { submitType, filename , empty } = this.state;
if(!filename || empty){
onEmpty ? onEmpty(true) : this.onEmpty(true);
onEmpty && window.scrollTo(0,0);
return;
}
let path = filepath + filename;
this.setState({ isSpin: true });
let path = editor_type === "upload" ? filepath : filepath.substr(1);
this.props.form.validateFieldsAndScroll((err, values) => {
if (!err && !checkName) {
const url = `/${owner}/${projectsId}/create_file.json`;
axios.post(url, {
filepath: filename ? filename : path,
filepath: path.substr(1),
base64_filepath:Base64.encode(path.substr(1)),
branch: returnbar(branch),
new_branch: submitType === "1" ? values.branchname : undefined,
content:editor_type === "upload" ? content : Base64.encode(content),
@ -75,6 +99,7 @@ class UserSubmitComponent extends Component {
const { getDetail } = this.props;
getDetail && getDetail();
}
window.scrollTo(0,0);
let url = `/${owner}/${projectsId}${values.branchname ? `/tree/${turnbar(values.branchname)}`: (branch ? `/tree/${turnbar(branch)}` : "")}`;
this.props.history.push(url);
}
@ -102,6 +127,7 @@ class UserSubmitComponent extends Component {
axios
.put(url, {
filepath: detail.path,
base64_filepath:Base64.encode(detail.path),
branch: submitType === "1" ? undefined : returnbar(b),
new_branch: submitType === "1" ? values.branchname : undefined,
content: content,
@ -112,6 +138,7 @@ class UserSubmitComponent extends Component {
this.setState({ isSpin: false });
if (result.data && result.data.status === 1) {
let b = currentBranch || branch;
window.scrollTo(0,0);
let url = `/${owner}/${projectsId}${(values.branchname ? `/tree/${turnbar(values.branchname)}` : (b ? `/tree/${turnbar(b)}`:""))}`;
this.props.history.push(url);
this.props.showNotification("文件修改成功!");
@ -151,13 +178,21 @@ class UserSubmitComponent extends Component {
callback();
}
onChangeFile=(value)=>{
const { changeName } = this.props;
this.setState({
filename:value
})
changeName && changeName(value);
}
render() {
const { submitType, filename, isSpin } = this.state;
const { submitType, filename, isSpin , empty } = this.state;
const { getFieldDecorator } = this.props.form;
const { branch, projectsId , owner } = this.props.match.params;
const { current_user, filepath, projectDetail , currentBranch } = this.props;
const { current_user, filepath, projectDetail , currentBranch , loadName } = this.props;
const { editor_type } = this.props;
let b = currentBranch || branch;
@ -180,24 +215,17 @@ class UserSubmitComponent extends Component {
<div className="userScrew">
<div className="screwPanel">
<Form>
<Form.Item
style={{
<div className="mb20" style={{
display: editor_type === "upload" ? "block" : "none",
}}
>
{getFieldDecorator("path", {
rules: [],
})(
<div className="setInputAddon">
<Input
addonBefore={`/${ projectDetail && projectDetail.identifier }/`}
value={filename || filepath}
onChange={this.changeFileName}
placeholder="文件路径..."
/>
</div>
)}
</Form.Item>
}}>
<FP
addonBefore={`${ projectDetail && projectDetail.identifier }${returnbar(filepath)}`}
onChangeFile={this.onChangeFile}
empty={empty}
name={loadName}
onEmpty={(v)=>this.onEmpty(v)}
/>
</div>
<Form.Item>
{getFieldDecorator("desc", {
rules: [
@ -267,6 +295,7 @@ class UserSubmitComponent extends Component {
type="primary grey"
onClick={() => {
this.props.history.push(`/${owner}/${projectsId}`);
window.scrollTo(0,0);
}}
className="mr20"
>

View File

@ -34,7 +34,7 @@ class m_editor extends Component {
render() {
const { editorValue, changeValue } = this.state;
const { readOnly, editorType, currentBranch, descName, checkName, detail, match: { params },filepath,ideTheme } = this.props;
const { readOnly, editorType,onEmpty,filename,empty, currentBranch, descName, checkName, detail, match: { params },filepath,ideTheme } = this.props;
const editor_options = {
lineNumbers: "on",
lineWrapping: true, //强制换行
@ -82,6 +82,9 @@ class m_editor extends Component {
currentBranch={currentBranch}
descName={descName}
checkName={checkName}
onEmpty={onEmpty}
empty={empty}
filename={filename}
></UserSubmitComponent>
</div>
)}

View File

@ -40,12 +40,17 @@ class UploadFile extends Component {
});
};
changeName=(value)=>{
this.setState({
filename:value
})
}
render() {
const { pathname } = this.props.location;
const { filename, editorValue, attachment_clean } = this.state;
const urlroot = pathname.split("uploadfile/")[1];
const file_path = !urlroot ? filename : `${urlroot}/${filename}`;
const file_path = urlroot ? `/${urlroot}/` :"/";
return (
<React.Fragment>
<div className="main">
@ -75,9 +80,11 @@ class UploadFile extends Component {
{...this.props}
{...this.state}
filepath={file_path}
loadName={filename}
content={editorValue}
editor_type={"upload"}
descName={`ADD file via upload`}
changeName={this.changeName}
></UserSubmitComponent>
</div>
</div>

View File

@ -20,7 +20,7 @@ function MilepostDetail(props){
const[ aboutMe , setAboutMe ] = useState("aboutme");
const[ keyword , setKeyword ] = useState(undefined);
const [ category , setCategory] = useState("all");
const [ category , setCategory] = useState("opened");
const [ allValue , setAllValue ] = useState([]);
const [ allIds , setAllIds ] = useState([]);

View File

@ -92,6 +92,7 @@ class UpdateMilepost extends Component {
}).then(result => {
if (result) {
this.setState({ isSpin: false })
window.scrollTo(0,0);
this.props.history.push(`/${owner}/${projectsId}/milestones/${meilid}`);
}

View File

@ -1,9 +1,14 @@
.screenWrap{
background:rgba(250,250,250,1);
border-top:1px solid rgba(221,221,221,1);
border-bottom:1px solid rgba(221,221,221,1);
background:#fafcff;
border:1px solid rgba(42, 97, 255, 0.23);
padding-left: 20px;
box-sizing: border-box;
border-radius: 4px 4px 0px 0px;
}
.mergeData{
border:1px solid #d0d0d0;
border-top: none;
border-radius: 0px 0px 2px 2px;
}
.attachment-list-div:hover {
background-color: #e6f7ff;

View File

@ -77,6 +77,7 @@ class NewMilepost extends Component {
}).then(result => {
if (result) {
this.setState({ isSpin: false })
window.scrollTo(0,0);
this.props.history.push(`/${owner}/${projectsId}/milestones`);
const { getDetail } = this.props;
getDetail && getDetail();

View File

@ -47,6 +47,22 @@ const PrivateLetter = Loadable({
loader: () => import("./notice/privateLetter/Index"),
loading: Loading,
});
const ExploitBot = Loadable({
loader: () => import("./bot/exploit"),
loading: Loading,
});
const CreateBot = Loadable({
loader: () => import("./bot/exploit/new"),
loading: Loading,
});
const DispositionDetail = Loadable({
loader: () => import("./bot/disposition/detail"),
loading: Loading,
});
const Disposition = Loadable({
loader: () => import("./bot/disposition"),
loading: Loading,
});
function Index(props){
const { current_user,mygetHelmetapi , checkIfLogin } = props;
@ -54,7 +70,7 @@ function Index(props){
const notice_url = mygetHelmetapi && mygetHelmetapi.common && mygetHelmetapi.common.notice;
const [ avatarVisible , setAvatarVisible ] = useState(false);
useEffect(()=>{
if(checkIfLogin() === false){
props.history.push('/login');
@ -70,8 +86,8 @@ function Index(props){
return(
<div className="newMain clearfix whiteBack">
{
avatarVisible &&
<Avatar
avatarVisible &&
<Avatar
onCancel={onCancelAvatar}
avatarImg={getImageUrl(`/${current_user && current_user.image_url}`)}
login={current_user && current_user.login}
@ -94,13 +110,18 @@ function Index(props){
</ul>
{notice_url && <ul className="securityUl ul-border-buttom">
<li>消息通知</li>
<li className={(pathname.indexOf("/settings/notice")>-1 && pathname.indexOf("/settings/notice/config") == -1) || pathname.indexOf("/settings/notice/privateLetter")>-1 ?"active":""}><Link to={"/settings/notice"}><i className="iconfont icon-wodetongzhi"></i><span className="text-shodow-bold">我的通知</span></Link></li>
<li className={pathname.indexOf("/settings/notice/config")>-1 ?"active":""}><Link to={'/settings/notice/config'}><i className="iconfont icon-tongzhiguanli"></i><span className="text-shodow-bold">通知管理</span></Link></li>
<li className={(pathname.indexOf("/settings/notice")>-1 && pathname.indexOf("/settings/notice/config") == -1) || pathname.indexOf("/settings/notice/privateLetter")>-1 ?"active":""}><Link to={"/settings/notice"}><i className="iconfont icon-wodetongzhi font-14"></i><span className="text-shodow-bold">我的通知</span></Link></li>
<li className={pathname.indexOf("/settings/notice/config")>-1 ?"active":""}><Link to={'/settings/notice/config'}><i className="iconfont icon-tongzhiguanli font-14"></i><span className="text-shodow-bold">通知管理</span></Link></li>
</ul>}
<ul className="securityUl">
<ul className="securityUl ul-border-buttom">
<li>安全设置</li>
<li className={pathname.indexOf("/settings/SSH")>-1 ?"active":""}><Link to={`/settings/SSH`}><i className="iconfont icon-xuanzhongssh_icon mr5 font-14"></i><span className="text-shodow-bold">SSH密钥</span></Link></li>
</ul>
<ul className="securityUl">
<li>Bot开发</li>
<li className={pathname.indexOf("/settings/mybot")>-1 ?"active":""}><Link to={`/settings/mybot`}><i className="iconfont icon-kaifabot mr5 font-18"></i><span className="text-shodow-bold">我的Bot</span></Link></li>
<li className={pathname.indexOf("/settings/installbot")>-1 ?"active":""}><Link to={`/settings/installbot`}><i className="iconfont icon-BOTpeizhi mr5 font-17"></i><span className="text-shodow-bold">Bot安装</span></Link></li>
</ul>
</div>
<LongWidth>
<Gap>
@ -165,6 +186,34 @@ function Index(props){
<SSHIndex {...props} {...p}/>
)}
></Route>
{/* 创建一个新的bot */}
<Route
path="/settings/mybot/new"
render={(p) => (
<CreateBot {...props} {...p}/>
)}
></Route>
{/* 开发bot */}
<Route
path="/settings/mybot"
render={(p) => (
<ExploitBot {...props} {...p}/>
)}
></Route>
{/* bot配置页面 */}
<Route
path="/settings/installbot/:id"
render={(p) => (
<DispositionDetail {...props} {...p}/>
)}
></Route>
{/* bot配置 */}
<Route
path="/settings/installbot"
render={(p) => (
<Disposition {...props} {...p}/>
)}
></Route>
</Switch>
</Gap>
</LongWidth>

View File

@ -6,12 +6,12 @@
padding:30px 0px 10px;
.shortW{
width: 198px;
border: 1px solid rgba(153, 153, 153, 0.22);
border: 1px solid rgba(79, 108, 188, 0.21);
border-radius: 4px;
// min-height: 400px;
margin-bottom: 30px;
.userDetail{
background: rgba(153, 153, 153, 0.05);
background: rgba(124, 146, 230, 0.05);
border-radius: 4px 4px 0px 0px;
padding:20px 25px;
text-align: center;
@ -32,52 +32,40 @@
}
}
.securityUl{
padding-left: 17px;
color: #333;
margin-bottom: 0px;
padding-bottom: 12px;
padding-top: 5px;
li{
font-size: 14px;
margin-top: 10px;
margin-bottom: 10px;
height: 27px;
line-height: 27px;
height: 36px;
line-height: 36px;
position: relative;
cursor: default;
padding-left: 17px;
font-weight:500;
color:#142a56;
font-size:16px;
a{
color: #666;
&:hover{
color: #333;
}
color:#202d40;
font-size: 14px;
}
&.active a{
color: #333;
.text-shodow-bold{
font-weight: 400;
text-shadow: 0.5px 0 #333;
}
&:hover{
background-color:rgba(70, 106, 255, 0.05);
}
&.active::before{
position: absolute;
left: -18px;
top:6px;
height: 15px;
width: 3px;
border-radius: 2px;
content: "";
background-color: #466AFF;
&:first-child:hover{
background-color: white;
}
&.active{
background-color:rgba(70, 106, 255, 0.05);
a{color: $primary-color;}
}
i{
font-size: 14px !important;
// font-size: 14px !important;
margin-right: 8px;
}
}
}
.ul-border-buttom{
border-bottom: 1px solid rgba(153, 153, 153, 0.22);
border-bottom: 1px solid #eef3ff;
padding-bottom: 5px;
}
}
@ -220,4 +208,96 @@
display: block!important;
}
}
}
.dangerBorBut, .dangerBorBut:focus{
background-color:rgba(196, 0, 14, 0.09);
border:1px solid #f60011;
border-radius:5px;
color:#f60011;
&::after{display: none;}
&:hover{
background-color:rgba(196, 0, 14, 0.18);
border:1px solid #ff727c;
color:#f60011;
}
&:active{
background-color:rgba(196, 0, 14, 0.22);
border:1px solid #f60011;
color:#f60011;
}
}
.themeCorBorBut, .themeCorBorBut:focus{
border:1px solid $primary-color;
border-radius:5px;
color: $primary-color;
&::after{display: none;}
&:hover{
border:1px solid $primary-color-hover;
}
&:active{
border:1px solid $primary-color-active;
}
}
.grayBorBut, .grayBorBut:focus{
background-color:#fbfbfb;
border:1px solid #d5dbec;
border-radius:5px;
color:#333333;
&::after{display: none;}
&:hover{
background-color:#edf0f6;
border:1px solid #adb8d6;
color:#333333;
}
&:active{
background-color:#e7edfb;
border:1px solid #adb8d6;
color:#333333;
}
&.redFontBut, &.redFontBut:hover, &.redFontBut:active, &.redFontBut:focus{
color:#f60011;
}
&.whiteBackBut, &.whiteBackBut:hover, &.whiteBackBut:active, &.whiteBackBut:focus{
background-color:#ffffff;
}
}
.f8HeadModal{
color:#333333;
.ant-modal-content{border-radius: 0;}
.ant-modal-header{
height: 56px;
padding: 10px 30px;
background-color: #f8f8f8;
}
.ant-modal-title{
text-align: left;
font-weight: normal !important;
}
.ant-modal-close-x{
font-size: 25px;
line-height: 45px;
}
.titleTip{margin-left: 45px;}
.deleteTip{
color: #666666;
margin-top: 20px;
margin-left: 92px;
}
.ant-modal-footer{
border-top: none;
padding: 40px 0 60px;
text-align: center;
}
}
.circleRed{
display: inline-block;
width:36px;
height:36px;
line-height: 36px;
text-align: center;
border-radius: 50%;
background-color:#ca0002;
color: white;
margin-right: 15px;
}

View File

@ -0,0 +1,199 @@
import React, { useEffect, useState } from "react";
import { Button, Checkbox, message, Radio, Select, Switch, Spin, Tooltip } from "antd";
import axios from "axios";
import { Link } from "react-router-dom";
import { getInstallBot, updateInstallBot } from "../../../../softbot/api";
import { getImageUrl } from 'educoder';
import './index.scss';
function DispositionDetail(props){
const {match:{params:{id}}, current_user, current_user:{login, user_id}} = props;
const [checkValue, setCheckValue] = useState(0);
const [selectValue, setSelectValue] = useState([]);
const [botDetail, setBotDetail] = useState(undefined);
const [options, setOptions] = useState(undefined);
const [filterOption, setFilterOption] = useState([]);
// bot
const [botState, setBotState] = useState(true);
const [error, setError] = useState(false);
// bot
useEffect(()=>{
//
current_user && axios.get(`/users/${login}/projects.json`,{
params:{
limit: 1000,
page: 1,
category:"admin"
}
}).then(result=>{
if(result && result.data){
const {projects} = result.data;
setOptions(projects);
setFilterOption(projects);
// getInstallBot
const repoIds = new Set();
projects.map(item=>{
const {id} = item;
id && repoIds.add(id);
})
getInsBotDetail(repoIds);
}
}).catch(error=>{
console.log(error);
})
}, [])
// selectValue
useEffect(()=>{
const ids = selectValue.map(item=>{return item.id});
options && setFilterOption(options.filter(item=>!ids.includes(item.id)));
}, [selectValue])
//
useEffect(()=>{
if(botDetail && options){
if(botDetail.store_id_list.length === options.length){
setCheckValue(0);
}else{
setCheckValue(1);
//
const {store_id_list} = botDetail;
const allStoreId = options.map((item)=>{return item.id});
let newMap = selectValue;
store_id_list.map(item=>{
if(allStoreId.includes(item)){
newMap = newMap.concat(options.filter(i=>{return i.id === item})[0]);
}
})
setSelectValue(newMap);
}
}
}, [options, botDetail])
function getInsBotDetail(repoIds){
getInstallBot({
login: login,
user_id: user_id,
bot_id: id,
repoIds: [...repoIds].toString()
}).then(res=>{
if(res && res.code === 200){
const {state, market_name} = res.data;
document.title = `${market_name}-Bot配置`
setBotState(state);
setBotDetail(res.data);
}
})
}
function selectChange(e){
let newMap = selectValue;
newMap = newMap.concat(options.filter(i=>{return i.id === e})[0]);
setSelectValue(newMap);
}
function deleteSelect(e){
let newMap = selectValue;
newMap = newMap.filter(item=>{return item.id !== e.id});
setSelectValue(newMap);
}
function updateInsBot(){
setError(false);
if(checkValue && !selectValue.length){
setError(true);
return;
}
// id -> identifier
const repo_map = {};
// id -> login
const repo_owner_map = {};
if(checkValue){
selectValue.map(item=>{
const {author:{login}} = item;
repo_map[item.id+""] = item.identifier;
repo_owner_map[item.id+""] = login;
});
}else{
options.map(item=>{
const {author:{login}} = item;
repo_map[item.id+""] = item.identifier;
repo_owner_map[item.id+""] = login;
});
}
current_user && updateInstallBot({
bot_id: id,
password: "",
state: botState ? 1 : 0,
store_list: checkValue ? selectValue.map(item=>{return item.id}) : options.map(item=>{return item.id}),
login: login,
user_id: user_id,
repo_map,
repo_owner_map,
installed_store_list: botDetail.store_id_list
}).then(res=>{
if(res && res.code === 200){
setSelectValue([]);
const repoIds = new Set();
options.map(item=>{
const {id} = item;
id && repoIds.add(id);
})
getInsBotDetail(repoIds);
message.success('更改成功');
}else{
message.error(res.message);
}
})
}
return <Spin spinning={!Boolean(botDetail)}>
<div className="dispositionDetailBox">
<div className="oneLine mb30">
{botDetail && <div className="oneLine">
<img src={getImageUrl(botDetail.logo)} alt="" className="botImg imgBox"/>
<div className="ml40 font-16">
<div className="font-22"><a href={`/softbot/${id}`}>{botDetail && botDetail.market_name}</a></div>
<a className="mr30 color88" href={`/${botDetail.register_login}`}>{botDetail && botDetail.register_name}</a>
<span className="color88">{botDetail && botDetail.create_time}</span>
</div>
</div>}
<Button style={{width: '53px', padding: '0'}} className="grayBorBut"><Link to={`/settings/installbot`}>返回</Link></Button>
</div>
{botDetail && <div className="mb10 greenCol font-16"><i className={`mr8 iconfont ${botDetail.limit_vo.juris_diction_code !== 2 ? 'icon-wanchenggouxuan' : 'icon-tishi1 font-16'}`}></i>代码库{botDetail.limit_vo.juris_diction_code === 2 ? '无' : botDetail.limit_vo.juris_diction_code === 1 ? '读写' : '只读'}权限</div>}
{botDetail && <div className="mb20 greenCol font-16"><i className={`mr8 iconfont ${botDetail.limit_vo.juris_diction_pr !== 2 ? 'icon-wanchenggouxuan' : 'icon-tishi1 font-16'}`}></i>合并请求(PR){botDetail.limit_vo.juris_diction_pr === 2 ? '无' : botDetail.limit_vo.juris_diction_pr === 1 ? '读写' : '只读'}权限</div>}
<div className="detailTil font-16">更改安装位置</div>
<div>
<Radio.Group onChange={(e)=>{setCheckValue(e.target.value)}} value={checkValue} disabled={botDetail && !botDetail.is_public && login !== botDetail.register_login}>
<Radio value={0} className="radioBox mt10 font-16">安装到所有仓库<br/><span className="font-15 radioBoxTip">安装到用户拥有的所有仓库中</span></Radio>
<Radio value={1} className="radioBox mt15 font-16">安装到指定仓库<br/><span className={`font-15 radioBoxTip ${error ? 'errorBot' : ''}`}>请至少选择一个仓库</span></Radio>
</Radio.Group>
{checkValue === 1 && <Select
placeholder="请选择仓库名称"
value={selectValue.map(item=>{return item.id})}
onChange={selectChange}
className="radioBox selectBox"
disabled={botDetail && !botDetail.is_public && login !== botDetail.register_login}
>
{filterOption.map(item=>(<Select.Option key={item.id} value={item.id}>{`${item.author && item.author.name}/${item.name}`}</Select.Option>))}
</Select>}
{checkValue === 1 && selectValue.map((item, index)=>{return <div key={index} className="selectValueBox font-14">
<i className="iconfont icon-daimakuicon1 font-14 mr5"></i>
<Link className="selectValue" to={`/${item.author && item.author.login}/${item.identifier}`}>{`${item.author && item.author.name}/${item.name}`}</Link>
{(botDetail && botDetail.is_public === 1 || (botDetail.is_public === 0 && login === botDetail.register_login)) && <i className="iconfont icon-guanbi font-12 close ml30" onClick={()=>{deleteSelect(item)}}></i>}
</div>})}
</div>
<div className="detailTil font-16 mt30 mb10">更改Bot状态</div>
<div className="ml25">
{botDetail && <Switch defaultChecked = {botDetail.state} onChange={(checked)=>{setBotState(checked)}} disabled={botDetail && !botDetail.is_public && login !== botDetail.register_login}/>}
<div className="mt10">
<div className="font-15">{botState ? '启用' : '挂起'}</div>
<span className='changeStatusTip'>{botState ? '开始使用该Bot授予该Bot所需的仓库访问权限' : '暂停停止使用该bot阻塞该bot拥有的仓库访问权限'}</span>
</div>
</div>
{botDetail && !botDetail.is_public && login !== botDetail.register_login ? <Tooltip title="该bot为私有bot仅bot开发者有权限更改"><Button type="primary" style={{width: '100px', height: '36px'}} className="mt30" disabled>保存</Button></Tooltip> : <Button type="primary" style={{width: '100px', height: '36px'}} className="mt30" onClick={updateInsBot}>保存</Button>}
</div>
</Spin>
}
export default DispositionDetail;

View File

@ -0,0 +1,121 @@
import React, { useEffect, useState } from "react";
import { Button, message, Modal } from "antd";
import { Link } from "react-router-dom";
import { getImageUrl } from 'educoder';
import nullBot from '../../../../softbot/image/notBot.png';
import { getAllInstallBots, deleteInstallBot } from "../../../../softbot/api";
import './index.scss';
import axios from "axios";
// bot
function DispositionBot(props){
const {current_user} = props;
const [botList, setBotList] = useState(undefined);
const [delBotId, setDelBotId] = useState(undefined);
const [visible, setVisible] = useState(false);
const [options, setOptions] = useState(undefined);
useEffect(()=>{
window.scrollTo(0,0);
document.title='Bot安装';
//
current_user && axios.get(`/users/${current_user.login}/projects.json`,{
params:{
limit: 1000,
page: 1,
category:"admin"
}
}).then(result=>{
if(result && result.data){
const {projects} = result.data;
setOptions(projects);
// getAllInstallBots
const repoIds = new Set();
projects.map(item=>{
const {id} = item;
id && repoIds.add(id);
})
getAllInsBots(repoIds)
}
}).catch(error=>{
console.log(error);
})
}, [])
function getAllInsBots(repoIds){
getAllInstallBots({
user_id: current_user && current_user.user_id,
repoIds: [...repoIds].toString()
}).then(res=>{
if(res && res.code === 200){
setBotList(res.data.install_bots);
}
})
}
function delInstallBot(){
if(current_user && delBotId){
const repoIds = new Set();
options.map(item=>{
const {id} = item;
id && repoIds.add(id);
})
deleteInstallBot({
login: current_user.login,
user_id: current_user.user_id,
bot_id: delBotId.bot_id,
store_repo_ids: delBotId.store_repo_ids,
password: ""
}).then(res=>{
if(res && res.code === 200){
message.success('卸载成功');
setDelBotId(undefined);
setVisible(false);
getAllInsBots(repoIds);
}else{
message.error(res.message);
}
})
}
}
return <div>
<div className="dispositionHead font-18">Bot安装</div>
{/* bot列表显示区域 */}
{botList && (botList.length > 0 ? <div className="setBotListBox">
{botList.map((item, index)=>{return <div className="disBotItem" key={index}>
<div className="botNameLeft">
<img src={getImageUrl(item.logo)} alt="" className="imgBox mr20"/>
<Link className="font-15 botName" to={`/settings/installbot/${item.bot_id}`}>{item.bot_name}</Link>
<span className={`statusBox font-12 ml10 ${item.state === 0 ? '' : 'active'}`}>{item.state === 0 ? '挂起' : '启用'}</span>
</div>
<div>
<Button className="themeCorBorBut" style={{width: '68px', height: '36px'}}><Link to={`/settings/installbot/${item.bot_id}`}>配置</Link></Button>
<Button className="dangerBorBut ml20" style={{width: '68px', height: '36px'}} onClick={()=>{setDelBotId(item);setVisible(true)}}>卸载</Button>
</div>
</div>})}
</div> : <div className="nullBotsBox mt80">
<img src={nullBot} alt="" width={62}/>
<p className="font-18 showBigTip">您当前暂未安装任何Bot</p>
<div className="showTip font-14">您可以将Bot配置到仓库来处理一些繁杂的项目任务比如自动化关闭疑修合并请求等以节约时间精力提升开发效率快去<Link to="/softbot"> Bot市场 </Link>中挑选一个适合您项目的Bot吧!</div>
<div className="borTip"></div>
<Button type="primary" style={{width: '112px', height: '36px', padding: 0}}><Link to={'/softbot'}>去Bot市场逛逛</Link></Button>
</div>)}
<Modal
className="f8HeadModal"
width="485px"
title="卸载Bot"
visible={visible}
maskClosable={false}
footer={[
<Button onClick={()=>{setVisible(false)}} className="mr30 grayBorBut whiteBackBut" style={{width:"104px", height: '36px'}}>取消</Button>,
<Button className="redFontBut grayBorBut whiteBackBut" style={{width:"104px", height: '36px'}} onClick={delInstallBot}>确认卸载</Button>
]}
onCancel={()=>{setVisible(false)}}
>
<div className="font-16 titleTip mt20"><span className="circleRed font-18">!</span>您确定要卸载此Bot吗</div>
<div className="deleteTip">卸载后将回收所有分配给该bot的权限</div>
</Modal>
</div>
}
export default DispositionBot;

View File

@ -0,0 +1,125 @@
.disBotItem, .oneLine{
display: flex;
justify-content: space-between;
align-items: center;
}
.dispositionHead{
height: 65px;
line-height: 65px;
border-bottom: 1px solid #e0e6f5;
color:#151d40;
}
.disBotItem{
border-bottom: 1px dashed #e0e6f5;
padding: 20px 0 15px;
.botNameLeft{
display: flex;
align-items: center;
width: 80%;
}
.botName{
display: inline-block;
max-width: 80%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.setBotListBox{
margin-top: 16px;
color:#202d40;
.statusBox{
background-color:rgba(255, 81, 81, 0.08);
border:1px solid#f23737;
color:#f92d2d;
border-radius:4px;
padding: 4px 6px;
line-height: 11px;
&.active{
background-color:rgba(58, 213, 115, 0.08);
border-color:#25c589;
color:#25c589;
}
}
}
.imgBox{
width: 44px;
height: 44px;
border-radius: 50%;
object-fit: cover;
}
.dispositionDetailBox{
color:#202d40;
.color88{
color:#8893af;
}
a.color88:hover{color: $primary-color;}
.botImg{
width: 86px;
height: 86px;
}
.greenCol{
color:#202d40;
&>.icon-tishi1{
color:#f60011;
}
&>.icon-wanchenggouxuan{
color:#00bb75;
}
}
.detailTil{
font-weight: 700;
color:#151d40;
padding-bottom: 15px;
border-bottom: 1px solid #e0e6f5;
}
.radioBox{
display: block;
color:#202d40;
.radioBoxTip{
display: inline-block;
margin-top: 5px;
margin-left: 24px;
color:#99a2af;
&.errorBot{color: #f60011;}
}
}
.selectBox{
width: 360px;
margin: 10px 0 0 24px;
.ant-select-selection{padding-top: 0;}
.ant-select-selection__placeholder{
display: block !important;
}
.ant-select-selection-selected-value{
display: none !important;
}
}
.selectValueBox{
margin: 13px 0 0px 23px;
color:#333333;
border-bottom: 1px dashed #e0e6f5;
padding-bottom: 10px;
.close{
cursor: pointer;
color: rgba(0, 0, 0, 0.45);
}
}
.changeStatusTip{color:#99a2af;}
}
.nullBotsBox{
border-radius: 4px 4px 0px 0px;
text-align: center;
padding: 25px 158px 65px;
.showBigTip{
color:#333333;
margin-top: 16px;
margin-bottom: 22px !important;
}
.showTip{color: #666;}
.borTip{
width: 82%;
margin: 20px auto 18px;
border-top: 1px solid rgba(90, 117, 193, 0.23)
}
}

View File

@ -0,0 +1,261 @@
import { AutoComplete, Button, message, Modal } from "antd";
import React from "react";
import { useState, useEffect } from "react";
import { getImageUrl } from 'educoder';
import './index.scss';
import axios from "axios";
import { Link } from "react-router-dom";
import { deleteBot, transferBot, registerUpdateBot, downMarket, getMarketBotById, cancelTransferBot } from "../../../../../../softbot/api";
const { Option } = AutoComplete;
// Bot
function AdvancedInformation(props){
const {match, current_user, match:{params:{id}}, botDetail, setReload} = props;
const [ visibleMakeOver, setVisibleMakeOver] = useState(false);
const [ newId , setNewId] = useState(undefined);
const [ source , setSource ] = useState(undefined);
const [ searchKey , setSearchKey ] = useState(undefined);
// bot012
const [ type, setType] = useState(undefined);
const [ visibleDelete, setVisibleDelete] = useState(false);
// bot
const [makeOverErrortTip, setMakeOverErrortTip] = useState(undefined);
//
const [isMarket, setIsMarket] = useState(false);
//
const [marketReload, setMarketReload] = useState(undefined);
useEffect(()=>{
// bot
getMarketBotById({bot_id: id}).then(res=>{
res && setIsMarket(res.code === 200);
})
}, [marketReload])
useEffect(()=>{
getUserList();
},[searchKey])
function getUserList(e){
const url = `/users/list.json`;
axios.get(url, {
params: {
search: searchKey,
},
}).then((result) => {
if (result) {
sourceOptions(result.data.users);
}
})
.catch((error) => {
console.log(error);
});
};
function changeInputUser(e){
setSearchKey(e);
};
//
function selectInputUser(e, option){
setNewId(option.props);
setSearchKey(option.props.name);
};
function sourceOptions(userDataSource){
const s = userDataSource && userDataSource.map((item, key) => {
return (
<Option
key={key}
value={`${item.user_id}`}
login={`${item.login}`}
name={item.username}
>
<img
className="user_img radius"
width="28"
height="28"
src={getImageUrl(`/${item && item.image_url}`)}
alt=""
/>
<span className="ml10" style={{ verticalAlign: "middle" }}>
{item.username}
<span className="color-grey ml10">({item.login})</span>
</span>
</Option>
);
});
setSource(s);
}
//
function makeOver(){
setMakeOverErrortTip(undefined);
if(newId){
const {value, login} = newId
transferBot({
login: current_user && current_user.login,
user_id: current_user && current_user.user_id,
bot_id: parseInt(id),
transfer_to_user_id: parseInt(value),
to_logn: login
}).then(res=>{
if(res && res.code === 200){
message.success('操作成功');
setSearchKey(undefined);
setNewId(undefined);
setVisibleMakeOver(false);
setReload(Math.random());
}
})
}else{
setMakeOverErrortTip("请选择被转让者");
}
}
// ///Bot
function submitModal(){
if(type === '3'){
//
downMarket(parseInt(id), {}).then(res=>{
if(res && res.code === 200){
message.success('下架成功');
setVisibleDelete(false);
setMarketReload(Math.random());
}
})
}else if(type === '0'){
// bot
const params = {
login: current_user && current_user.login,
user_id: current_user && current_user.user_id,
bot_id: parseInt(id)
}
deleteBot(params).then(res=>{
if(res && res.code === 200){
message.success('删除成功');
setVisibleDelete(false);
props.history.push("/settings/mybot/configuration");
}
})
}else{
// /bot
const params = {
...botDetail,
bot_id: parseInt(id),
is_public: type === '1' ? 1 : 0,
user_id: current_user && current_user.user_id,
}
registerUpdateBot(params).then(res=>{
if(res && res.code === 200){
setVisibleDelete(false);
message.success("更改成功");
setReload(Math.random());
}else{
setVisibleDelete(false);
message.error(res.message);
}
})
}
}
function cancelTra(){
current_user && cancelTransferBot({
bot_id: parseInt(id),
login: current_user.login,
user_id: current_user.user_id
}).then(res=>{
if(res && res.code === 200){
message.success("操作成功");
setReload(Math.random());
}
})
}
return <div className="advancedInformationBox">
<div className="informationHead pb15">高级选项</div>
{/* 提示:转让中状态 */}
{botDetail && botDetail.is_transfer_success === 2 && <div className="transferBotStatus mt15"><i className="iconfont icon-yuyue-lishi-shijian font-18 mr5 ml10"></i>此Bot正在等待转移至<a href={`/${botDetail.transfer_to_login}`}>{botDetail.transfer_to_name +' (' + botDetail.transfer_to_login +')'}</a></div>}
<div className="disFlex itemBox">
<div className="font-15">
转让该Bot的所有权至其他用户
<p className="grayBox font-14">转让可能会推迟直到新的所有者批准转让</p>
</div>
{botDetail && (botDetail.is_transfer_success !== 2 ? <Button className="themeCorBorBut" style={{width:"88px", height: '36px', padding: '0'}} onClick={()=>{setVisibleMakeOver(true)}}>转让所有权</Button> :
<Button className="themeCorBorBut" style={{width:"88px", height: '36px', padding: '0'}} onClick={cancelTra}>取消转让</Button>)}
</div>
<div className="disFlex itemBox">
<div className="font-15">
删除Bot
<p className="grayBox font-14">该操作无法撤销请慎重</p>
</div>
<Button className="dangerBorBut" style={{width:"88px", height: '36px', padding: '0'}} onClick={()=>{setType('0');setVisibleDelete(true)}}>删除Bot</Button>
</div>
{botDetail && !botDetail.is_public && <div className="disFlex itemBox">
<div className="font-15">
私有Bot
<p className="grayBox font-14">目前您的bot将不能被其他用户安装</p>
</div>
<Button className="themeCorBorBut" style={{width:"88px", height: '36px', padding: '0'}} onClick={()=>{setType('1');setVisibleDelete(true)}}>公开此Bot</Button>
</div>}
{botDetail && !!botDetail.is_public && !isMarket && <div className="disFlex itemBox">
<div className="font-15">
公开Bot
<p className="grayBox font-14">目前您的Bot所有用户都能安装</p>
</div>
<div>
<Button type="primary" style={{width:"88px", height: '36px', padding: '0'}} className="mr20"><Link to={`/settings/mybot/configuration/${id}/advanced/putaway`}>上架市场</Link></Button>
<Button className="themeCorBorBut" style={{width:"88px", height: '36px', padding: '0'}} onClick={()=>{setType('2');setVisibleDelete(true)}}>私有此Bot</Button>
</div>
</div>}
{botDetail && !!botDetail.is_public && isMarket && <div className="disFlex itemBox">
<div className="font-15">
公开Bot
<p className="grayBox font-14">目前您的Bot所有用户都能安装</p>
</div>
<div>
<Button type="primary" style={{width:"88px", height: '36px', padding: '0'}} className="mr20"><Link to={`/settings/mybot/configuration/${id}/advanced/putaway/edit`}>编辑</Link></Button>
<Button className="themeCorBorBut" style={{width:"88px", height: '36px', padding: '0'}} onClick={()=>{setType('3');setVisibleDelete(true)}}>下架市场</Button>
</div>
</div>}
{/* 转让bot弹框 */}
<Modal
className="themeHeadModal"
width="430px"
title="转让可能会推迟,直到新的所有者批准转让"
visible={visibleMakeOver}
maskClosable={false}
footer={null}
onCancel={()=>{setVisibleMakeOver(false);setMakeOverErrortTip(undefined)}}>
<div className="font-15 mt5 mb10 makeOverTip">新的所有者账号</div>
{makeOverErrortTip && <div className="font-15 mt5 mb10 makeOverErrorTip">{makeOverErrortTip}</div>}
<AutoComplete
dataSource={source}
value={searchKey}
style={{ width: '100%' }}
onChange={changeInputUser}
onSelect={selectInputUser}
placeholder="请输入账户名称"
allowClear
/>
<Button type="primary" onClick={makeOver} className="makeOverSubmit" style={{width:"104px", height: '36px'}}>确认转让</Button>
</Modal>
{/* 删除/公开/私有Bot弹框 */}
<Modal
className="f8HeadModal"
width="550px"
title={`${type === '0' ? '删除' : type === '1' ? '公开' : type === '2' ? '私有' : '下架'}Bot`}
visible={visibleDelete}
maskClosable={false}
footer={[
<Button onClick={()=>{setVisibleDelete(false)}} className="mr30 grayBorBut whiteBackBut" style={{width:"104px", height: '36px'}}>取消</Button>,
<Button className="redFontBut grayBorBut whiteBackBut" style={{width:"104px", height: '36px'}} onClick={submitModal}>确认{type === '0' ? '删除' : type === '1' ? '公开' : type === '2' ? '私有' : '下架'}</Button>
]}
onCancel={()=>{setVisibleDelete(false)}}
>
<div className="font-16 titleTip mt20"><span className="circleRed font-18">!</span>您确定要{type === '0' ? '删除此Bot' : type === '1' ? `公开` : type === '2' ? '私有' : '下架'}{botDetail && botDetail.bot_name}</div>
<div className="deleteTip">{type === '0' ? `该操作无法撤销,将会永久删除${botDetail && botDetail.bot_name}` : type === '1' ? `公开后任何用户都可以安装此Bot` : type === '2' ? '私有后此Bot将只能被Bot开发者账户安装' : '下架后社区用户将无法在市场搜索到此Bot'}</div>
</Modal>
</div>
}
export default AdvancedInformation;

View File

@ -0,0 +1,183 @@
import React, { useEffect, useState } from "react";
import { Button, Form, Input, message } from "antd";
import MDEditor from '../../../../../../modules/tpm/challengesnew/tpm-md-editor';
import { Box } from "../../../../../Component/layout";
import miyao from '../../../../img/miyao.png';
import './index.scss';
import { Link } from "react-router-dom";
import { getImageUrl } from 'educoder';
import { registerUpdateBot } from "../../../../../../softbot/api";
import UploadImage from '../../../../../../forge/Team/Component/UploadImage.jsx';
import {CopyToClipboard} from 'react-copy-to-clipboard';
// Bot
function BasicInformation(props){
const {form, botDetail, match:{params:{id}}, setReload, current_user} = props;
const { getFieldDecorator, validateFields , setFieldsValue } = form;
const [ content , setContent ] = useState(undefined);
const [ avatarVisible , setAvatarVisible ] = useState(false);
const [ image , setImage ] = useState(undefined);
const [ imageFlag , setImageFlag ] = useState(false);
const [ imageId, setImageId] = useState(undefined);
function getImage(image){
setImageFlag(true);
setImage(image);
}
function getImageId(id){
setImageId(id);
}
function onContentChange(value){
setContent(value);
};
useEffect(()=>{
if(botDetail){
setContent(botDetail.bot_des);
setImage(botDetail.logo)
}
},[botDetail])
// Bot
function submit(e){
e.preventDefault();
validateFields((err, fieldsValue)=>{
if(err){
return;
}
const params = {
...botDetail,
...fieldsValue,
bot_id: parseInt(id),
user_id: current_user && current_user.user_id,
}
registerUpdateBot(params).then(res=>{
if(res && res.code === 200){
message.success("更改成功");
setReload(Math.random());
}else{
message.error(res.message);
}
})
})
}
//
function saveUpdateImage(){
if(imageId){
const params = {
...botDetail,
bot_id: parseInt(id),
logo: '/api/attachments/'+imageId,
user_id: current_user && current_user.user_id,
}
registerUpdateBot(params).then(res=>{
if(res && res.code === 200){
message.success("成功修改头像");
setReload(Math.random());
}
})
}else{
message.error('请先上传图片');
}
}
// webhooks
function checkAddr(rule, value, callback) {
let reg = /(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?/;
if(!value){
callback();
}
if (!reg.test(value)){
callback("请输入有效的URL");
}
callback();
}
return <Box>
{botDetail && <div className="basicInformationBox">
<p className="informationHead pb15 mb10">注册信息</p>
<div className="labelBox"><span className="label">Bot所有者</span><Link to={`/${current_user.login}`}>{current_user.username}</Link></div>
<div className="labelBox"><span className="label">Bot ID</span>{id}</div>
<div className="labelBox"><span className="label">客户端ID</span>{botDetail.client_id}</div>
<div className="labelBox"><span className="label">主页URL</span><Link to={`/softbot/${id}`} className="blueSpan">{window.location.origin + `/softbot/${id}`}</Link></div>
<div className="informationHead disFlex pb15 mt20 mb10">
客户端密钥
</div>
<div className="disFlex miYaoBoxItem">
<div className="disFlex">
<img src={miyao} alt="" className="imgBox"/>
<div className="copyBotInfo">
<span className="font-13 oneLineSpan">{botDetail.client_secret}</span>
<CopyToClipboard text={botDetail.client_secret} onCopy={()=>{message.success('复制成功')}}>
<i className="iconfont icon-fuzhiicon ml5 font-16" style={{color: '#466aff'}}></i>
</CopyToClipboard>
</div>
</div>
</div>
<div className="informationHead disFlex pb15 mt20 mb10">
私钥
</div>
<div className="disFlex miYaoBoxItem">
<div className="disFlex">
<img src={miyao} alt="" className="imgBox"/>
<div className="copyBotInfo">
<span className="font-13 oneLineSpan">{botDetail.private_key}</span>
<CopyToClipboard text={botDetail.private_key} onCopy={()=>{message.success('复制成功')}}>
<i className="iconfont icon-fuzhiicon ml5 font-16" style={{color: '#466aff'}}></i>
</CopyToClipboard>
</div>
</div>
</div>
<p className="informationHead mt30 pb15">Bot信息</p>
<Form className="basicInformationForm mt10" onSubmit={submit}>
<Form.Item label="Bot名称" className="width80 botItem">
{getFieldDecorator("bot_name",{
rules:[{required:true,message:"请输入Bot名称"}, {min: 3, max: 50, message: "Bot名称3-50个字符"}],
initialValue: botDetail.bot_name
})(
<Input.TextArea placeholder="请输入Bot名称" maxLength={51} className="height36" autoSize={true}/>
)}
</Form.Item>
<Form.Item label="详细介绍" className="botItem introduce">
{getFieldDecorator("bot_des",{
rules:[]
})(
<MDEditor
placeholder={"请输入详细介绍"}
height={500}
mdID={"order-new-description"}
initValue={content}
onChange={onContentChange}
className="mt20"
isFocus={false}
></MDEditor>
)}
</Form.Item>
<Form.Item label="Webhook地址" className="width80 botItem">
{getFieldDecorator("webhook",{
rules:[{required:true,message:"请输入Webhook地址"},{validator:checkAddr}],
initialValue: botDetail.webhook
})(
<Input.TextArea placeholder="请输入Webhook地址" maxLength={200} className="height36" autoSize={true}/>
)}
</Form.Item>
<Form.Item label="回调地址" className="width80 botItem">
{getFieldDecorator("oauth_callback_url",{
rules:[{required:true,message:"请输入回调地址"},{validator:checkAddr}],
initialValue: botDetail.oauth_callback_url
})(
<Input.TextArea placeholder="请输入回调地址" maxLength={200} className="height36" autoSize={true}/>
)}
</Form.Item>
<Form.Item className="mt30"><Button style={{width:"129px", height: '36px'}} type="primary" htmlType="submit">保存修改</Button></Form.Item>
</Form>
</div>}
<div className="headPortraitBox">
<UploadImage url={image && image.startsWith('/api/attachments/') ? getImageUrl(`${image}`) : image} getImage={getImage} getImageId = {getImageId}/>
<Button className="themeCorBorBut ml5" onClick={saveUpdateImage}>保存头像</Button>
</div>
</Box>
}
export default Form.create()(BasicInformation);

View File

@ -0,0 +1,136 @@
.informationHead{
border-bottom: 1px solid #e0e6f5;
font-weight:700;
color:#151d40;
font-size:16px;
}
.basicInformationBox{
width: 800px;
margin-right: 55px;
padding-bottom: 50px;
.labelBox{
line-height: 40px;
}
.blueSpan{
color: $primary-color;
}
.label{
display: inline-block;
width: 76px;
}
.tipBox{
background-color:rgba(70, 106, 255, 0.07);
border-radius:4px;
color:#151d40;
padding: 8px 13px;
}
.imgBox{
width: 30px;
margin-right: 25px;
}
.grayCol{
color:rgba(13, 22, 71, 0.65);
}
.miYaoBoxItem{
padding: 20px 0;
border-bottom: 1px dashed #e0e6f5;
&:hover{background-color:rgba(125, 148, 246, 0.05);}
.copyBotInfo{
display: flex;
align-items: center;
}
.oneLineSpan{
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 680px;
}
}
.basicInformationForm{
.width80{
width: 80%;
}
.botItem{
margin-bottom: 5px;
.ant-form-item-label{
line-height: 30px;
}
&.introduce .ant-form-item-control-wrapper{
margin-top: -15px;
margin-bottom: 0;
}
}
}
}
.jurisdictionBox, .advancedInformationBox{
width: 85%;
padding-bottom: 45px;
}
.advancedInformationBox{
.grayBox{
color:#99a2af;
}
.itemBox{
padding: 15px 0;
border-bottom: 1px dashed #e0e6f5;
}
.transferBotStatus{
background-color:rgba(70, 106, 255, 0.09);
border:1px solid $primary-color;
border-radius:4px;
color:rgba(0, 0, 0, 0.65);
display: flex;
align-items: center;
}
.icon-yuyue-lishi-shijian{
color: $primary-color;
}
}
.themeHeadModal{
.ant-modal-header{
height: 55px;
padding: 8px 25px;
background-color:$primary-color;
}
.ant-modal-title{
text-align: left;
color:#ffffff;
font-weight: normal !important;
}
.ant-modal-close-x{
line-height: 38px;
font-size: 18px;
color:#ffffff;
}
.makeOverTip{color:#202d40;}
.makeOverSubmit{margin: 70px 145px;}
.ant-modal-body{
background-image:linear-gradient(42.46deg,#ffffff 0%,#ffffff 48.54%,#ebf0ff 100%);
}
.makeOverErrorTip{color:#f60011;}
}
.putawayBox{
width: 100%;
.putaHead{
color:#151d40;
border-bottom: 1px solid #e0e6f5;
}
.oneItem.ant-form-item{
margin-bottom: 6px;
&.introduce .ant-form-item-control-wrapper{
margin-top: -15px;
}
}
.selectBox{
display: flex;
justify-content: space-between;
.selectOne{
width: 48%;
}
}
.putawayBut[disabled]{
background-color:rgba(70, 106, 255, 0.56);
border: none;
color:#ffffff;
}
}

View File

@ -0,0 +1,141 @@
import React from "react";
import { Button, Checkbox, Form, Input, message, Select } from "antd";
import { useState } from "react";
import '../../new/index.scss';
import './index.scss';
import { registerUpdateBot } from "../../../../../../softbot/api";
import { useEffect } from "react";
const {Option} = Select;
// bot
function Jurisdiction(props){
const {form, botDetail, setReload, match:{params:{id}}, current_user } = props;
const {getFieldDecorator, validateFields , setFieldsValue, getFieldsValue } = form;
const resource = [{value: 2, name: '无权限'}, {value: 0, name: '只读'}, {value: 1, name: '读写'}];
const [codeSelectValue , setCodeSelectValue ] = useState(undefined);
const [prSelectValue , setPrSelectValue ] = useState(undefined);
useEffect(()=>{
if(botDetail){
const {limit_and_events:{juris_diction_code, juris_diction_pr}} = botDetail
setCodeSelectValue(juris_diction_code);
setPrSelectValue(juris_diction_pr);
}
}, [botDetail])
function codeControl(e){
setCodeSelectValue(e);
return e;
}
function prControl(e){
setPrSelectValue(e);
return e;
}
// bot
function submit(e){
e.preventDefault();
validateFields((err, fieldsValue)=>{
if(err){
return;
}
const {juris_diction_code, juris_diction_pr, event_code, event_pr} = fieldsValue;
const params = {
...botDetail,
bot_id: parseInt(id),
limit_and_events: {
event_code: event_code ? event_code.toString() : "",
event_pr: event_pr ? event_pr.toString() : "",
juris_diction_code,
juris_diction_pr
},
user_id: current_user && current_user.user_id,
}
registerUpdateBot(params).then(res=>{
if(res && res.code === 200){
message.success("更改成功");
setReload(Math.random());
}else{
message.error(res.data)
}
})
})
}
// 访
function jurisDictionPrValidator(rule, value, callback){
const {juris_diction_code} = getFieldsValue();
if(juris_diction_code === 2 && value === 2){
callback('请至少选择一个权限');
return;
}
callback();
}
//
function eventValidator(rule, value, callback, type){
const {event_code, event_pr} = getFieldsValue();
const resourceValue = resource[0].value;
const boolPr = (event_code === undefined || (event_code && event_code.length === 0)) && prSelectValue === resourceValue;
const boolCode = (event_pr === undefined || (event_pr && event_pr.length === 0)) && codeSelectValue === resourceValue;
const bool = prSelectValue !== resourceValue && codeSelectValue !== resourceValue && (event_pr === undefined || (event_pr && event_pr.length === 0)) && (event_code === undefined || (event_code && event_code.length === 0))
if(boolPr || boolCode){
callback("请至少选择一个订阅");
return;
}
if(bool && type === "pr"){
callback("请至少选择一个订阅");
return;
}
callback();
}
return <Form className="createExploitForm jurisdictionBox" onSubmit={submit}>
{botDetail && <div>
<div className="resourceTitle font-16 pb15 mb10">仓库访问权限</div>
<Form.Item label="代码库权限" className="resourceBox botItem dashedBorder">
<div className="color-99">代码库git推送分支的创建与删除</div>
{getFieldDecorator("juris_diction_code",{initialValue: botDetail.limit_and_events.juris_diction_code, getValueFromEvent: codeControl})(
<Select>
{resource.map(item=>{return <Option value={item.value} key={item.value}>{item.name}</Option>})}
</Select>
)}
</Form.Item>
<div className="dashedBor"></div>
<Form.Item label="合并请求(PR)权限" className="resourceBox botItem">
<div className="color-99">合并请求的打开关闭编辑分配</div>
{getFieldDecorator("juris_diction_pr",{rules:[{validator: jurisDictionPrValidator}],initialValue: botDetail.limit_and_events.juris_diction_pr, getValueFromEvent: prControl})(
<Select>
{resource.map(item=>{return <Option value={item.value} key={item.value}>{item.name}</Option>})}
</Select>
)}
</Form.Item>
{(codeSelectValue !== resource[0].value || prSelectValue !== resource[0].value) && <div className="resourceTitle font-16 pb15 mb10">订阅事件</div>}
{codeSelectValue !== resource[0].value && <Form.Item label="代码库事件" className="botItem width50 checkBox three dashedBorder">
{getFieldDecorator("event_code", {
initialValue: botDetail.limit_and_events.event_code ? botDetail.limit_and_events.event_code.split(',').map(item=>item = parseInt(item)) : [],
rules:[{validator: (rule, value, callback)=>{eventValidator(rule, value, callback, "code")}}]
})(
<Checkbox.Group>
<Checkbox value={0}>推送<br/><span className="color-99">git推送到存储库</span></Checkbox>
<Checkbox value={1}>创建<br/><span className="color-99">创建分支或标签</span></Checkbox>
<Checkbox value={2}>删除<br/><span className="color-99">删除分支或标签</span></Checkbox>
</Checkbox.Group>
)}
</Form.Item>}
{(codeSelectValue !== resource[0].value && prSelectValue !== resource[0].value) && <div className="dashedBor"></div>}
{prSelectValue !== resource[0].value && <Form.Item label="合并请求事件" className="botItem width50 checkBox">
{getFieldDecorator("event_pr", {
initialValue: botDetail.limit_and_events.event_pr ? botDetail.limit_and_events.event_pr.split(',').map(item=>item = parseInt(item)) : [],
rules:[{validator: (rule, value, callback)=>{eventValidator(rule, value, callback, "pr")}}]
})(
<Checkbox.Group>
<Checkbox value={3}>合并请求<br/><span className="color-99">合并请求被打开被关闭被重新打开或被编辑</span></Checkbox>
<Checkbox value={4}>合并请求分配<br/><span className="color-99">合并请求被分配或取消分配</span></Checkbox>
</Checkbox.Group>
)}
</Form.Item>}
<Form.Item className="mt30"><Button style={{width:"129px", height: '36px'}} type="primary" htmlType="submit">保存修改</Button></Form.Item></div>}
</Form>
}
export default Form.create()(Jurisdiction);

View File

@ -0,0 +1,195 @@
import React, {forwardRef, useState, useEffect} from "react";
import { Button, Checkbox, Form, Input, message, Select } from "antd";
import { Link } from "react-router-dom";
import './index.scss';
import MdEditor from "../../../../../../modules/tpm/challengesnew/tpm-md-editor";
import { marketBot, getAllBotCategory, getMarketBotById, updateMarketBot } from "../../../../../../softbot/api";
import moment from "moment";
const {Option} = Select;
function Putaway(props){
const {form, current_user, match:{params:{id}}, botDetail, location:{pathname}} = props;
const { getFieldDecorator, validateFields , setFieldsValue } = form;
const [ resource, setResource] = useState([]);
const [ resourceFirst, setResourceFirst] = useState([]);
const [ resourceSecond, setResourceSecond] = useState([]);
const [ content , setContent ] = useState(undefined);
const [ disabled , setDisabled ] = useState(undefined);
const [ detail, setDetail] = useState(undefined);
const [ isEdit, setIsEdit] = useState(undefined);
useEffect(()=>{
//
getAllBotCategory().then(res=>{
if(res && res.code === 200){
setResource(res.data.category_list);
setResourceFirst(res.data.category_list);
setResourceSecond(res.data.category_list);
}
})
if(pathname && pathname.endsWith("/putaway/edit")){
setIsEdit(true);
setDisabled(true);
getMarketBotById({bot_id: id}).then(res=>{
if(res && res.code === 200){
setDetail({
...res.data
});
setContent(res.data.market_intro);
}
})
}else{
setIsEdit(false);
if(botDetail){
const {bot_des, bot_name, webhook} = botDetail;
setDetail({
bot_id: id,
category: "",
first_func: undefined,
logo: "",
market_desc: undefined,
market_name: bot_name,
market_time: undefined,
second_func: undefined,
webhook: webhook
})
setContent(bot_des);
}
}
}, [pathname, botDetail])
//
function onChange(e){
setDisabled(e.target.checked);
}
function onContentChange(value){
setContent(value);
};
// bot
function submit(e){
e.preventDefault();
validateFields((err, fieldsValue)=>{
if(err){
return;
}
const {logo} = botDetail;
if(isEdit){
// bot
updateMarketBot({
...fieldsValue,
category: "",
is_receive_ag1: 1,
is_receive_ag2: 0,
logo: logo,
market_time: "",
login: current_user && current_user.login,
user_id: current_user && current_user.user_id,
bot_id: parseInt(id),
}).then(res=>{
if(res && res.code === 200){
message.success("修改成功");
}else{
message.error(res.data);
}
})
}else{
// bot
marketBot({
...fieldsValue,
login: current_user && current_user.login,
user_id: current_user && current_user.user_id,
bot_id: parseInt(id),
category: "",
is_receive_ag1: disabled ? 1 : 0,
is_receive_ag2: 0,
logo: logo,
market_time: "",
}).then(res=>{
if(res && res.code === -1){
message.error(res.data);
}else{
message.success("成功上架市场");
props.history.push('/softbot/'+id);
}
})
}
})
}
return <div className="putawayBox">
<div className="putaHead pb10 font-18 mb5">
<a href="#" className="font-16">高级选项&gt;</a><b>上架Bot市场</b>
</div>
{detail && <Form className="putawayForm" onSubmit={submit}>
<Form.Item label="上架Bot市场名称" className="width50 oneItem">
{getFieldDecorator("market_name",{
rules:[{required:true,message:"请输入上架Bot市场名称"}],
initialValue: detail.market_name
})(
<Input.TextArea placeholder="请输入上架Bot市场名称" maxLength={50} className="height36" autoSize={true}/>
)}
</Form.Item>
<Form.Item label="简要介绍" className="oneItem">
{getFieldDecorator("market_desc",{
rules:[{required:true,message:"请输入简要介绍"}],
initialValue: detail.market_desc
})(
<Input.TextArea placeholder="请输入简要介绍的内容" maxLength={200} className="height36" autoSize={true}/>
)}
</Form.Item>
<div className="selectBox">
<Form.Item label="主要功能" className="selectOne oneItem">
{getFieldDecorator("first_func",{
rules:[{required:true,message:"请选择主要功能"}],
initialValue: detail.first_func
})(
<Select placeholder="请选择" onChange={(value)=>{setResourceSecond(resource.filter(item => item !== value))}}>
{resourceFirst.map(item=>{return <Option value={item} key={item}>{item}</Option>})}
</Select>
)}
</Form.Item>
<Form.Item label="次要功能" className="selectOne oneItem">
{getFieldDecorator("second_func",{
initialValue: detail.second_func
})(
<Select placeholder="请选择" onChange={(value)=>{setResourceFirst(resource.filter(item => item !== value))}}>
{resourceSecond.map(item=>{return <Option value={item} key={item}>{item}</Option>})}
</Select>
)}
</Form.Item>
</div>
<Form.Item label="详细介绍" className="oneItem introduce">
{getFieldDecorator("market_intro",{
rules:[{required:true,message:"请输入详细介绍"}],
initialValue: content
})(
<MdEditor
placeholder={"请输入详细介绍"}
height={500}
mdID={"order-new-description"}
initValue={content}
onChange={onContentChange}
className="mt20"
imageExpand={false}
></MdEditor>
)}
</Form.Item>
{/* 此webhook用于bot上架之后接收bot安装者的操作记录由于此功能未上线所以暂时屏蔽编辑入口 */}
{/* <Form.Item label="Webhook" className="width50 oneItem">
{getFieldDecorator("webhook",{
rules:[{required:true,message:"请输入Webhook地址"}],
initialValue: detail.webhook
})(
<Input.TextArea placeholder="请输入Webhook地址" maxLength={200} className="height36" autoSize={true}/>
)}
</Form.Item> */}
{!isEdit && <Form.Item>
<Checkbox onChange={onChange}>我已阅读并接受<a className="login-form-forgot" href="https://forum.trustie.net/forums/5029/detail" target="_blank">GitLink服务协议条款</a></Checkbox>
</Form.Item>}
<Form.Item><Button style={{width:"129px", height: '36px'}} type="primary" htmlType="submit" disabled={!disabled} className="putawayBut mt20">{isEdit ? '保存修改' : '申请上架'}</Button></Form.Item>
</Form>}
</div>
}
export default Form.create()(forwardRef(Putaway));

View File

@ -0,0 +1,96 @@
import React, {useEffect, useState} from "react";
import { Route, Switch } from "react-router-dom";
import Loadable from "react-loadable";
import Loading from "../../../../../Loading";
import { Link } from "react-router-dom";
import { TPMIndexHOC } from "../../../../../modules/tpm/TPMIndexHOC";
import { Box } from "../../../../Component/layout";
import './index.scss';
import { Button, message } from "antd";
import { getRegisterBot } from "../../../../../softbot/api";
const BasicInformation = Loadable({
loader: () => import("./compenent/basicInformation"),
loading: Loading,
});
const Jurisdiction = Loadable({
loader: () => import("./compenent/jurisdiction"),
loading: Loading,
});
const AdvancedInformation = Loadable({
loader: () => import("./compenent/advancedInformation"),
loading: Loading,
});
const Putaway = Loadable({
loader: () => import("./compenent/putaway"),
loading: Loading,
});
// bot
function ConfigurateBot(props){
const {match:{params:{id}}, current_user, location:{pathname}, history} = props;
const [reload, setReload] = useState(undefined);
const [botDetail, setBotDetail] = useState(undefined);
useEffect(()=>{
getRegisterBot({
user_id: current_user && current_user.user_id,
bot_id: id
}).then(res=>{
if(res && res.code === 200){
const {bot_name} = res.data.bot_output_vo;
document.title = `${bot_name}-Bot开发者配置`
setBotDetail(res.data.bot_output_vo);
}else if(res && res.code === 500){
message.error("您没有权限访问该bot");
history.push('/settings/mybot/configuration');
}
})
}, [reload])
return <div className="configurateBotBox">
<div className="configurateBotHead mb30">
<span className="font-18 configurateBotTitle">Bot开发者配置</span>
<Button style={{width: '53px', padding: '0'}} className="grayBorBut"><Link to={`/settings/mybot/configuration`}>返回</Link></Button>
</div>
<Box>
<div className="leftBox mr30">
<Link to={`/settings/mybot/configuration/${id}/basic`} className={pathname.lastIndexOf("/jurisdiction") === -1 && pathname.lastIndexOf("/advanced") === -1 ?"active itemBox":"itemBox"}>基本信息</Link>
<Link to={`/settings/mybot/configuration/${id}/jurisdiction`} className={pathname.lastIndexOf("/jurisdiction") > -1 ?"active itemBox":"itemBox"}>权限&订阅</Link>
<Link to={`/settings/mybot/configuration/${id}/advanced`} className={pathname.lastIndexOf("/advanced") > -1 ?"active itemBox":"itemBox"}>高级选项</Link>
</div>
<Switch>
{/* bot上架市场 */}
<Route
path="/settings/mybot/configuration/:id/advanced/putaway"
render={(p) => (
<Putaway {...props} {...p} botDetail={botDetail} setReload={setReload}/>
)}
></Route>
<Route
path="/settings/mybot/configuration/:id/advanced/putaway/edit"
render={(p) => (
<Putaway {...props} {...p} botDetail={botDetail} setReload={setReload}/>
)}
></Route>
<Route
path="/settings/mybot/configuration/:id/basic"
render={(p)=>(<BasicInformation {...props} {...p} botDetail={botDetail} setReload={setReload}/>)}
></Route>
<Route
path="/settings/mybot/configuration/:id/jurisdiction"
render={(p)=>(<Jurisdiction {...props} {...p} botDetail={botDetail} setReload={setReload}/>)}
></Route>
<Route
path="/settings/mybot/configuration/:id/advanced"
render={(p)=>(<AdvancedInformation {...props} {...p} botDetail={botDetail} setReload={setReload}/>)}
></Route>
<Route
path="/settings/mybot/configuration/:id"
render={(p)=>(<BasicInformation {...props} {...p} botDetail={botDetail} setReload={setReload}/>)}
></Route>
</Switch>
</Box>
</div>
}
export default TPMIndexHOC(ConfigurateBot);

View File

@ -0,0 +1,30 @@
.configurateBotBox{
width: 1200px;
margin: 0 auto;
color:#202d40;
}
.configurateBotHead, .disFlex{
display: flex;
justify-content: space-between;
align-items: center;
}
.configurateBotHead{
padding: 25px 0 15px;
border-bottom: 1px solid #e0e6f5;
.configurateBotTitle{font-weight: bold;}
}
.leftBox{
width: 150px;
min-height: 317px;
background-color: #f8faff;
border-radius: 4px;
.itemBox{
display: block;
color:#202d40;
padding: 4px 0 4px 15px;
&.active{
background-color:rgba(70, 106, 255, 0.06);
color: $primary-color;
}
}
}

View File

@ -0,0 +1,128 @@
import React, { useState, useEffect } from "react";
import { Button, Icon, message, Modal } from "antd";
import './index.scss';
import { Link } from "react-router-dom";
import { getImageUrl } from 'educoder';
import { getMyBot, getTransferToBot, receiveTransferBot, refuseTransferBot } from "../../../../softbot/api";
import nullBot from '../../../../softbot/image/notBot1.png';
import moment from "moment";
function ExploitBot(props){
const {current_user} = props;
const [makeOverList, setMakeOverList] = useState([]);
const [botList, setBotList] = useState(undefined);
const [visible, setVisible] = useState(false);
const [refuseBotDetail, setRefuseBotDetail] = useState(undefined);
const [reload, setReload] = useState(undefined);
useEffect(()=>{
window.scrollTo(0,0);
document.title='我的Bot';
}, [])
useEffect(()=>{
getMyBot({
user_id: current_user && current_user.user_id,
page_no: 1,
page_size: 1000
}).then(res=>{
if(res && res.code === 200){
setBotList(res.data.list);
}
})
getTransferToBot({
state: "2",
user_id: current_user && current_user.user_id,
login: current_user && current_user.login
}).then(res=>{
if(res && res.code === 200){
setMakeOverList(res.data.bot_list);
}
})
}, [reload])
// bot
function receiveBot(info){
receiveTransferBot({
bot_id: info.bot_id,
transfer_from_id: info.transfer_id,
transfer_to_login: current_user && current_user.login,
transfer_to_id: current_user && current_user.user_id
}).then(res=>{
if(res && res.code === 200){
message.success('操作成功');
setReload(Math.random());
}
})
}
// bot
function refuseBot(){
refuseTransferBot({
bot_id: refuseBotDetail.bot_id,
transfer_from_bot: refuseBotDetail.transfer_id,
transfer_to_bot: current_user && current_user.user_id
}).then(res=>{
if(res && res.code === 200){
setVisible(false);
setRefuseBotDetail(undefined);
message.success('操作成功');
setReload(Math.random());
}
})
}
return <div>
<div className="exploitHead font-18">
<span>我的Bot</span>
<Button type="primary" style={{width: '100px', height: '36px'}}><Link to={`/settings/mybot/new`}>Bot注册</Link></Button>
</div>
{/* bot转让接收按钮显示区域 */}
{makeOverList.length > 0 && <div className="makeOverBox">
{makeOverList.map((item, index)=>{return <div className="makeOverItem" key={index}>
<div>
<p className="botName font-15">{item.bot_name}</p>
<p className="userName"><a href={`/${item.fromlogin}`} className="fromUserNameBot">{item.fromname}</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{moment(item.transfer_date).format('YYYY-MM-DD HH:mm')}</p>
</div>
<div>
<Button className="themeCorBorBut" style={{width: '68px', height: '36px'}} onClick={()=>{receiveBot(item)}}>接收</Button>
<Button className="dangerBorBut ml20" style={{width: '68px', height: '36px'}} onClick={()=>{setRefuseBotDetail(item); setVisible(true)}}>拒绝</Button>
</div>
</div>})}
</div>}
{/* bot列表显示区域 */}
{botList && (botList.length > 0 ? <div className="softBotListBox">
{botList.map((item, index)=>{return <div className="softBotItem" key={index}>
<div className="botOneLine botNameWarpBox">
<img src={getImageUrl(item.logo)} alt="" className="imgBox mr20" object-fit="fill"/>
<Link className="font-15 botOneLine botNameBox" to={`/settings/mybot/configuration/${item.bot_id}`}>{item.bot_name}</Link>
<span className={`font-12 ml10 statusBot ${item.is_public ? item.is_market ? 'market' : 'public' : 'private'}`}>{item.is_public ? item.is_market ? '上架' : '公开' : '私有'}</span>
</div>
<Button className="themeCorBorBut ml30" style={{width: '68px', height: '36px'}}><Link to={`/settings/mybot/configuration/${item.bot_id}`}>编辑</Link></Button>
</div>})}
</div> : <div className="nullBotsBox mt80">
<img src={nullBot} alt="" width={62}/>
<p className="font-18 showBigTip">您当前暂未注册任何Bot</p>
<div className="showTip font-14">Bot可以帮助您自动化处理一些繁杂的项目任务欢迎您<Link to="/settings/mybot/new"> 注册一个Bot </Link>基于GitLink API进行开发<br/>此外您还可以在<Link to="/softbot"> Bot市场 </Link>中共享或安装他人的Bot</div>
</div>)}
<Modal
className="f8HeadModal"
width="550px"
title={`拒绝${refuseBotDetail && refuseBotDetail.bot_name}Bot`}
visible={visible}
maskClosable={false}
footer={[
<Button onClick={()=>{setVisible(false)}} className="mr30 grayBorBut whiteBackBut" style={{width:"104px", height: '36px'}}>取消</Button>,
<Button className="redFontBut grayBorBut whiteBackBut" style={{width:"104px", height: '36px'}} onClick={refuseBot}>确认</Button>
]}
onCancel={()=>{setVisible(false)}}
>
<div className="font-16 titleTip mt20"><span className="circleRed font-18">!</span>您确定要拒绝此Bot吗</div>
<div className="deleteTip">拒绝后开发Bot权限将返回至转让人</div>
</Modal>
</div>
}
export default ExploitBot;

View File

@ -0,0 +1,114 @@
.makeOverResultItem, .makeOverItem, .softBotItem{
display: flex;
justify-content: space-between;
align-items: center;
}
.exploitHead{
height: 65px;
border-bottom: 1px solid #e0e6f5;
display: flex;
justify-content: space-between;
align-items: center;
color:#151d40;
}
.makeOverResultItem{
background-color:rgba(255, 0, 0, 0.04);
border:1px solid #f60011;
border-radius:4px;
margin-top: 16px;
padding: 0 15px;
height: 32px;
color:rgba(0, 0, 0, 0.65);
.circle{
display: inline-block;
width:14px;
height:14px;
text-align: center;
line-height: 14px;
border-radius: 50%;
background-color:#cc0209;
color: white;
font-size: 12px;
margin-right: 5px;
}
}
.makeOverBox{
background-color:rgba(59, 105, 255, 0.03);
border:1px solid #ebf1ff;
margin-top: 16px;
padding: 12px 30px 0px 12px;
.makeOverItem{
border-bottom: 1px dashed #e0e6f5;
padding: 10px 0;
color:#99a2af;
&:first-child{
margin-top: -12px;
}
.fromUserNameBot{
color:#99a2af;
&:hover{
color: $primary-color;
}
}
}
.botName{
color:#202d40;
}
}
.softBotListBox{
margin-top: 16px;
color:#202d40;
.softBotItem{
border-bottom: 1px dashed #e0e6f5;
padding: 20px 0 15px;
}
.imgBox{
width: 44px;
height: 44px;
border-radius: 50%;
object-fit: cover;
}
}
.nullBotsBox{
border-radius: 4px 4px 0px 0px;
text-align: center;
padding: 25px 158px 65px;
.showBigTip{
color:#333333;
margin-top: 16px;
margin-bottom: 22px !important;
}
.showTip{color: #666;}
}
.botOneLine{
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&.botNameWarpBox{
display: flex;
align-items: center;
}
}
.botNameBox{
max-width: 80%;
display: inline-block;
}
.statusBot{
border: 1px solid $primary-color;
border-radius: 5px;
color: $primary-color;
padding: 4px 6px 4px;
background-color: rgba(70, 106, 255, 0.09);
line-height: 12px;
&.public{
background-color: rgba(58, 213, 115, 0.08);
border-color: #25c589;
color: #25c589;
}
&.private{
background-color: rgba(230, 126, 34, 0.08);
border-color: rgba(230, 126, 34, 1);
color: rgba(230, 126, 34, 1);
}
}

View File

@ -0,0 +1,201 @@
import React, { forwardRef, useState, useEffect } from "react";
import { Button, Checkbox, Form, Input, message, Radio, Select } from "antd";
import { Link } from "react-router-dom";
import MDEditor from '../../../../../modules/tpm/challengesnew/tpm-md-editor';
import './index.scss';
import { registerBot } from "../../../../../softbot/api";
const {Option} = Select;
function Disposition(props){
const {form, current_user} = props;
const { getFieldDecorator, validateFields , setFieldsValue, getFieldsValue } = form;
const resource = [{value: 2, name: '无权限'}, {value: 0, name: '只读'}, {value: 1, name: '读写'}];
const [ content , setContent ] = useState(undefined);
const [ codeSelectValue , setCodeSelectValue ] = useState(resource[0].value);
const [ prSelectValue , setPrSelectValue ] = useState(resource[0].value);
useEffect(()=>{
document.title='注册新的Bot';
}, [])
function onContentChange(value){
setContent(value);
};
function codeControl(e){
setCodeSelectValue(e);
return e;
}
function prControl(e){
setPrSelectValue(e);
return e;
}
// webhooks
function checkAddr(rule, value, callback) {
let reg = /(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?/;
if(!value){
callback();
}
if (!reg.test(value)){
callback("请输入有效的URL");
}
callback();
}
// 访
function jurisDictionPrValidator(rule, value, callback){
const {juris_diction_code} = getFieldsValue();
if(juris_diction_code === 2 && value === 2){
callback('请至少选择一个权限');
return;
}
callback();
}
//
function eventValidator(rule, value, callback, type){
const {event_code, event_pr} = getFieldsValue();
const resourceValue = resource[0].value;
const boolPr = (event_code === undefined || (event_code && event_code.length === 0)) && prSelectValue === resourceValue;
const boolCode = (event_pr === undefined || (event_pr && event_pr.length === 0)) && codeSelectValue === resourceValue;
const bool = prSelectValue !== resourceValue && codeSelectValue !== resourceValue && (event_pr === undefined || (event_pr && event_pr.length === 0)) && (event_code === undefined || (event_code && event_code.length === 0))
if(boolPr || boolCode){
callback("请至少选择一个订阅");
return;
}
if(bool && type === "pr"){
callback("请至少选择一个订阅");
return;
}
callback();
}
// bot
function submit(e){
e.preventDefault();
validateFields((err, fieldsValue)=>{
if(err){
return;
}
const {bot_name, bot_des, webhook, is_public, juris_diction_code, juris_diction_pr, event_code, event_pr, oauth_callback_url} = fieldsValue;
const param = {
bot_name, bot_des, webhook, is_public,
login: current_user && current_user.login,
user_id: current_user && current_user.user_id,
logo: `/api/attachments/${window.location.host !== 'www.gitlink.org.cn' ? 347246 : 412603}`,
limit_and_events: {
event_code: event_code ? event_code.toString() : "",
event_pr: event_pr ? event_pr.toString() : "",
juris_diction_code,
juris_diction_pr
},
oauth_callback_url
}
registerBot(param).then(res=>{
if(res && res.code === 200){
message.success('注册成功');
props.history.push('/settings/mybot/configuration/'+res.data.bot_id);
}else{
message.success('注册失败: '+res.data);
}
})
})
}
return <div className="createExploitBox">
<div className="ceHead pb5 font-16 mb10">
<Link to={`/settings/mybot`} className="blue-Purple">我的Bot</Link><span className="ceTitle font-18"><i className="iconfont icon-youjiantou ml5 mr5 font-12"></i>注册新的Bot</span>
</div>
<Form className="createExploitForm" onSubmit={submit}>
<Form.Item label="Bot名称" className="width50 botItem">
{getFieldDecorator("bot_name",{
rules:[{required:true,message:"请输入Bot名称"}, {min: 3, max: 50, message: "Bot名称3-50个字符"}]
})(
<Input.TextArea placeholder="请输入Bot名称" maxLength={51} className="height36" autoSize={true}/>
)}
</Form.Item>
<Form.Item label="详细介绍" className="botItem introduce">
{getFieldDecorator("bot_des",{
rules:[]
})(
<MDEditor
placeholder={"请输入详细介绍"}
height={500}
mdID={"order-new-description"}
initValue={content}
onChange={onContentChange}
className="mt20"
></MDEditor>
)}
</Form.Item>
<Form.Item label="Webhook地址" className="width50 botItem">
{getFieldDecorator("webhook",{
rules:[{required:true,message:"请输入Webhook地址"},{validator:checkAddr}]
})(
<Input.TextArea placeholder="请输入Webhook地址" maxLength={200} className="height36" autoSize={true}/>
)}
</Form.Item>
<Form.Item label="回调地址" className="width50 botItem">
{getFieldDecorator("oauth_callback_url",{
rules:[{required:true,message:"请输入回调地址"},{validator:checkAddr}]
})(
<Input.TextArea placeholder="请输入回调地址" maxLength={200} className="height36" autoSize={true}/>
)}
</Form.Item>
<div className="resourceTitle font-16 pb15 mb10">仓库访问权限</div>
<Form.Item label="代码库权限" className="resourceBox botItem dashedBorder">
<div className="color-99">代码库git推送分支的创建与删除</div>
{getFieldDecorator("juris_diction_code",{initialValue: resource[0].value, getValueFromEvent: codeControl})(
<Select>
{resource.map(item=>{return <Option value={item.value} key={item.value}>{item.name}</Option>})}
</Select>
)}
</Form.Item>
<div className="dashedBor"></div>
<Form.Item label="合并请求(PR)权限" className="resourceBox botItem">
<div className="color-99">合并请求的打开关闭编辑分配</div>
{getFieldDecorator("juris_diction_pr",{rules:[{validator: jurisDictionPrValidator}],initialValue: resource[0].value, getValueFromEvent: prControl})(
<Select>
{resource.map(item=>{return <Option value={item.value} key={item.value}>{item.name}</Option>})}
</Select>
)}
</Form.Item>
{(codeSelectValue !== resource[0].value || prSelectValue !== resource[0].value) && <div className="resourceTitle font-16 pb15 mb10">订阅事件</div>}
{codeSelectValue !== resource[0].value && <Form.Item label="代码库事件" className="botItem width50 checkBox three dashedBorder">
{getFieldDecorator("event_code",{
rules:[{validator: (rule, value, callback)=>{eventValidator(rule, value, callback, "code")}}]
})(
<Checkbox.Group>
<Checkbox value={0}>推送<br/><span className="color-99">git推送到存储库</span></Checkbox>
<Checkbox value={1}>创建<br/><span className="color-99">创建分支或标签</span></Checkbox>
<Checkbox value={2}>删除<br/><span className="color-99">删除分支或标签</span></Checkbox>
</Checkbox.Group>
)}
</Form.Item>}
{(codeSelectValue !== resource[0].value && prSelectValue !== resource[0].value) && <div className="dashedBor"></div>}
{prSelectValue !== resource[0].value && <Form.Item label="合并请求事件" className="botItem width50 checkBox">
{getFieldDecorator("event_pr",{
rules:[{validator: (rule, value, callback)=>{eventValidator(rule, value, callback, "pr")}}]
})(
<Checkbox.Group>
<Checkbox value={3}>合并请求<br/><span className="color-99">合并请求被打开被关闭被重新打开或被编辑</span></Checkbox>
<Checkbox value={4}>合并请求分配<br/><span className="color-99">合并请求被分配或取消分配</span></Checkbox>
</Checkbox.Group>
)}
</Form.Item>}
<Form.Item label="公/私有设置" className="botItem resourceTitleItem">
{getFieldDecorator("is_public",{initialValue: 0})(
<Radio.Group>
<Radio value={1}>公开</Radio>
<Radio value={0}>私有</Radio>
</Radio.Group>
)}
</Form.Item>
<Form.Item><Button style={{width:"129px", height: '36px'}} type="primary" htmlType="submit">注册Bot</Button></Form.Item>
</Form>
</div>
}
export default Form.create()(forwardRef(Disposition));

View File

@ -0,0 +1,86 @@
.ceHead{
border-bottom: 1px solid #e0e6f5;
.ceTitle{
font-weight:700;
color:#151d40;
}
}
#resource1, #resource2{
position: relative;
top: -15px;
width: 88px;
}
.resourceBox .ant-form-item-children{
display: flex;
justify-content: space-between;
position: relative;
}
.resourceBox .ant-select{
width: auto;
}
.resourceBox .has-error .ant-select{
color: #f5222d;
}
.width50{
width: 60%;
&.checkBox{
width: 68%;
}
.height36{
height: 36px;
}
}
.botItem{
.ant-form-item-label{
line-height: 30px;
}
label{
color:#202d40;
font-size:15px;
&.ant-checkbox-wrapper{
line-height: 30px;
margin-top: 10px;
}
&::after{
display: none;
}
}
&.introduce .ant-form-item-control-wrapper{
margin-top: -15px;
margin-bottom: 0;
}
&.checkBox .ant-checkbox-group{
display: flex;
flex-wrap: wrap;
justify-content: space-between;
.color-99{padding-left: 24px;}
}
&.three .ant-checkbox-wrapper{
width: 34%;
margin-left: 0px;
padding-left: 12px;
&:first-child, &:last-child{
padding-left: 0px;
}
}
}
.dashedBor{
border-bottom: 1px dashed #e0e6f5;
margin-bottom: 12px;
}
.resourceBox + .dashedBor{
margin-top: -15px;
}
.resourceTitle, .resourceTitleItem .ant-form-item-label{
font-weight:700;
color:#151d40;
border-bottom: 1px solid #e0e6f5;
width: 100%;
text-align: left;
label{font-size: 16px;}
}
.resourceTitleItem .ant-form-item-label{padding-bottom: 10px;}
.resourceTitleItem .ant-checkbox-wrapper{margin-right: 40px;}
.color-99{
color:#99a2af;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -44,6 +44,10 @@ const ManageNew = Loadable({
loader: () => import("./ManageWebNew"),
loading: Loading,
});
const SoftBot = Loadable({
loader: () => import("./softBot"),
loading: Loading,
});
class Index extends Component {
render() {
const { projectsId , owner } = this.props.match.params;
@ -107,6 +111,16 @@ class Index extends Component {
</Link>
</p>
</li> */}
<li
className={pathname.indexOf("settings/softbot") > -1 ? "active" : ""}
>
<p>
<Link to={`/${owner}/${projectsId}/settings/softbot`} className="w-100">
<i className="iconfont icon-a-jiqirenzhineng-02 font-18 mr10 color-grey-6"></i>
已安装Bot
</Link>
</p>
</li>
{/* <li
className={
@ -182,6 +196,12 @@ class Index extends Component {
<Manage {...this.props} {...props} {...this.state} />
)}
></Route>
<Route
path="/:owner/:projectsId/settings/softbot"
render={(props) => (
<SoftBot {...this.props} {...props} {...this.state} />
)}
></Route>
{/* 修改仓库信息 */}
<Route
path="/:owner/:projectsId/settings"

View File

@ -1,11 +1,12 @@
import React, { Component } from "react";
import { Form, Input, Checkbox, Select , Spin } from "antd";
import { Form, Input, Checkbox, Select , Spin, Button } from "antd";
import Title from '../Component/Title';
import {WhiteBack} from '../Component/layout';
import DivertModal from '../Divert/DivertModal';
import { Link } from 'react-router-dom';
import axios from "axios";
import "./setting.scss";
import { unInstallMarketBot } from "../../softbot/api";
const { TextArea } = Input;
const { Option } = Select;
@ -194,17 +195,26 @@ class Setting extends Component {
deleteProject = () => {
const { projectsId , owner } = this.props.match.params;
const { projectName } = this.state;
const {current_user:{login, user_id}, projectDetail:{project_id}} = this.props;
this.props.confirm({
content: <span style={{display:"block",textAlign:"left"}}>该操作无法撤销且将会一并删除相关的疑修合并请求工作流里程碑动态等数据<br/>是否确认删除 <span style={{fontWeight:"bold"}}>{owner}/{projectName}({projectsId})</span></span>,
onOk: () => {
const url = `/${owner}/${projectsId}.json`;
axios.delete(url).then((result) => {
this.props.showNotification("仓库删除成功!");
this.props.history.push(`/${owner}`);
// 卸载bot判断bot是否安装后端做了判断
unInstallMarketBot({
login,
user_id,
store_id: project_id,
store_identifier: projectsId
})
.catch((error) => {
console.log(error);
});
window.scrollTo(0,0);
this.props.showNotification("仓库删除成功!");
this.props.history.push(`/${owner}`);
})
.catch((error) => {
console.log(error);
});
},
});
};
@ -400,9 +410,9 @@ class Setting extends Component {
</div>
{
is_transfering ?
<a onClick={this.CancelDivertProject} className="red_deleteBtn">取消转移</a>
<Button onClick={this.CancelDivertProject} className="red_deleteBtn">取消转移</Button>
:
<a onClick={this.DivertProject} className="red_deleteBtn">转移</a>
<Button onClick={this.DivertProject} className="red_deleteBtn">转移</Button>
}
</div>
<div className="flex-a-center padding15-10">
@ -413,9 +423,9 @@ class Setting extends Component {
无法撤消且删除后与仓库关联的项目/任务/合并请求/版本发布等均会被删除
</p>
</div>
<a onClick={this.deleteProject} className="red_deleteBtn">
<Button onClick={this.deleteProject} className="red_deleteBtn">
删除本仓库
</a>
</Button>
</div>
</div>
</WhiteBack>

View File

@ -86,12 +86,13 @@ function Index(props) {
}
function gotoEditWebhook(webhook){
if(webhook.isJianMu){
setEditId(webhook.id);
setEditVisible(true);
}else{
props.history.push(`/${owner}/${projectsId}/settings/webhooks/${webhook.id}`);
}
props.history.push(`/${owner}/${projectsId}/settings/webhooks/${webhook.id}`);
// if(webhook.isJianMu){
// setEditId(webhook.id);
// setEditVisible(true);
// }else{
// props.history.push(`/${owner}/${projectsId}/settings/webhooks/${webhook.id}`);
// }
}
return(
@ -104,7 +105,7 @@ function Index(props) {
content="您确定要删除此Webhook吗"
subTitle={deleteWebHookModSubTitle}
/>
<Modal
{/* <Modal
visible={editVisible}
onCancel={()=>{setEditVisible(false)}}
title="请谨慎编辑此Webhook"
@ -115,7 +116,7 @@ function Index(props) {
>
<AlignCenter className="editWebhookModalTitle font-20 mb10"><i className="iconfont icon-shanchu_tc_icon mr10 font-36" style={{color: '#ca0002'}}></i>您确定要编辑此Webhook吗</AlignCenter>
<p className="task-hide-2" style={{WebkitLineClamp:5}}>该Webhook由流水线创建, 编辑此Webhook后, 将大概率导致本仓库相关流水线失效确定要修改此Webhook?</p>
</Modal>
</Modal> */}
<Banner>
<span>Webhooks(网络钩子)</span>
<Button type="primary" size="large" onClick={addFunc}>添加Webhook</Button>
@ -128,15 +129,15 @@ function Index(props) {
{data.map((i,k)=>{
return(
<List.Item key={k}>
<i className={`iconfont mr12 font-17 ${i.isJianMu ? 'icon-gongzuoliuicon color-grey-6' : 'icon-a-xuanzhongwebhookicon color-grey-d'}`}></i>
<i className={`iconfont mr12 font-17 color-grey-6 ${i.isJianMu ? 'icon-gongzuoliuicon' : i.type === "softbot" ? 'icon-a-zu37 font-20' : 'icon-a-xuanzhongwebhookicon'}`}></i>
<span className="webName">{i.isJianMu ? <Tooltip title="该Webhook由流水线创建请勿编辑或删除以防流水线失效"><span className="webName spanBox">{i.url}</span></Tooltip> : i.url}</span>
{!i.isJianMu && <span>
{!i.isJianMu && i.type !== "softbot" && <span>
<Button ghost type={"primary"} onClick={()=>{gotoEditWebhook(i)}}>编辑</Button>
<Button ghost className="ml20" type="danger" onClick={()=>{deleteFunc(i)}}>删除</Button>
</span>}
{i.isJianMu && <span>
<Popover content={"该webhook由流水线创建不可编辑或删除更改配置将导致流水线失效"} title="无法编辑此Webhook" overlayClassName="disabledButPopover"><Button type={"primary"} onClick={()=>{gotoEditWebhook(i)}} disabled>编辑</Button></Popover>
<Popover content={"该webhook由流水线创建不可编辑或删除更改配置将导致流水线失效"} title="无法删除此Webhook" overlayClassName="disabledButPopover"><Button className="ml20" type="danger" onClick={()=>{deleteFunc(i)}} disabled>删除</Button></Popover>
{(i.isJianMu || i.type === "softbot") && <span>
<Popover content={`该webhook由${i.isJianMu ? '流水线创建,不可编辑或删除,更改配置将导致流水线失效' : '已安装的Bot创建请勿编辑或删除以防Bot失效'}`} title="无法编辑此Webhook" overlayClassName="disabledButPopover"><Button ghost type={"primary"} onClick={()=>{gotoEditWebhook(i)}}>查看</Button></Popover>
<Popover content={`该webhook由${i.isJianMu ? '流水线创建,不可编辑或删除,更改配置将导致流水线失效' : '已安装的Bot创建请勿编辑或删除以防Bot失效'}`} title="无法删除此Webhook" overlayClassName="disabledButPopover"><Button className="ml20" type="danger" disabled>删除</Button></Popover>
</span>}
</List.Item>
)

View File

@ -20,6 +20,7 @@ function New({ form , match , showNotification , history, projectDetail }) {
const [ visible , setVisible ] = useState(false);
const [ data , setData ] = useState(undefined);
const [ eventFlag , setEventFlag ] = useState(false);
const [ unEdit, setUnEdit] = useState(false);
const { getFieldDecorator, validateFields , setFieldsValue } = form;
const { id , owner , projectsId } = match.params;
@ -69,6 +70,10 @@ function New({ form , match , showNotification , history, projectDetail }) {
const url = `/${owner}/${projectsId}/webhooks/${id}/edit.json`;
axios.get(url).then(result=>{
if(result){
const reg1 = new RegExp("https://(.*?)jianmuhub.com(.*?)");
const reg2 = new RegExp("https://jianmu.gitlink.org.cn(.*?)");
const {url, type} = result.data;
setUnEdit(reg1.test(url) || reg2.test(url) || type === "softbot")
let e = compareArray(result.data.events);
setFieldsValue({
...result.data,
@ -190,7 +195,7 @@ function New({ form , match , showNotification , history, projectDetail }) {
<span>
<Link to={`/${owner}/${projectsId}/settings/webhooks`} className="color-blue">Webhooks</Link>
<i className="iconfont icon-youjiantou ml5 mr5 font-12"></i>
<span>{id ? "更新" : "添加"}Webhook</span>
<span>{ unEdit ? '查看' : id ? "更新" : "添加"}Webhook</span>
</span>
</Banner>
<div>
@ -206,21 +211,21 @@ function New({ form , match , showNotification , history, projectDetail }) {
}
]
})(
<Input placeholder="请输入目标URL" maxLength="200" size="large" autocomplete='off'/>
<Input placeholder="请输入目标URL" maxLength={200} size="large" autoComplete='off' disabled = {unEdit}/>
)}
</Form.Item>
<Form.Item label="Webhook密钥" colon={false}>
{getFieldDecorator("secret",{
rules:[]
})(
<Input.Password placeholder="请输入Webhook密钥" autocomplete='new-password' size="large" maxLength={"200"}/>
<Input.Password placeholder="请输入Webhook密钥" autoComplete='new-password' size="large" maxLength={200} disabled = {unEdit}/>
)}
</Form.Item>
<Form.Item label="Http请求方法" colon={false}>
{getFieldDecorator("http_method",{
rules:[]
})(
<Select onSelect={changeHTTP} size="large">
<Select onSelect={changeHTTP} size="large" disabled = {unEdit}>
<Select.Option value="GET">GET</Select.Option>
<Select.Option value="POST">POST</Select.Option>
</Select>
@ -234,7 +239,7 @@ function New({ form , match , showNotification , history, projectDetail }) {
{getFieldDecorator("content_type",{
rules:[]
})(
<Select size="large">
<Select size="large" disabled = {unEdit}>
<Select.Option value="json">application/json</Select.Option>
<Select.Option value="form">application/x-www-form-urlencoded</Select.Option>
</Select>
@ -244,7 +249,7 @@ function New({ form , match , showNotification , history, projectDetail }) {
{getFieldDecorator("eventCondition",{
rules:[]
})(
<Radio.Group onChange={changeCondition}>
<Radio.Group onChange={changeCondition} disabled = {unEdit}>
<Radio style={radioStyle} value="push">只是push事件</Radio>
<Radio style={radioStyle} value="all">所有事件</Radio>
<Radio style={radioStyle} value="forevent">自定义事件</Radio>
@ -254,7 +259,7 @@ function New({ form , match , showNotification , history, projectDetail }) {
<div className="eventCb"
style={{display:condition === "forevent" ?"block":"none"}}
>
<Checkbox.Group value={event} onChange={(e)=>{setEvent(e)}}>
<Checkbox.Group value={event} onChange={(e)=>{setEvent(e)}} disabled = {unEdit}>
<p className="color-grey-3 mb10">代码库事件</p>
<div className="colSpan">
<span>
@ -284,14 +289,14 @@ function New({ form , match , showNotification , history, projectDetail }) {
<Checkbox value="pull_request_assign">合并请求分配</Checkbox>
<span>合并请求被分配或取消分配</span>
</span>
{/* <span>
<Checkbox value="pull_request_milestone">合并请求收入里程碑</Checkbox>
<span>合并请求被记录或取消记录于里程碑中</span>
</span> */}
{/* <span>
<Checkbox value="pull_request_comment">合并请求被评论</Checkbox>
<span>合并请求评论被创建编辑或删除</span>
</span> */}
{/* <span>
<Checkbox value="pull_request_milestone">合并请求收入里程碑</Checkbox>
<span>合并请求被记录或取消记录于里程碑中</span>
</span> */}
{/* <span>
<Checkbox value="pull_request_label">合并请求标签</Checkbox>
<span>合并请求的标签被更新或清除</span>
@ -305,6 +310,25 @@ function New({ form , match , showNotification , history, projectDetail }) {
<span>合并请求被同步</span>
</span> */}
</div>
{/* <p className="color-grey-3 mt10 mb10"></p>
<div className="colSpan">
<span>
<Checkbox value="issues_only">疑修</Checkbox>
<span>疑修已打开已关闭已重新打开或编辑</span>
</span>
<span>
<Checkbox value="issue_assign">疑修已指派</Checkbox>
<span>疑修已被指派或取消指派</span>
</span>
<span>
<Checkbox value="issue_label">已标记疑修</Checkbox>
<span>疑修标记被更新或清除</span>
</span>
<span>
<Checkbox value="issue_comment">疑修评论</Checkbox>
<span>疑修评论被创建编辑或删除</span>
</span>
</div> */}
</Checkbox.Group>
{ eventFlag && <span style={{color:"#DF0002"}}>请选择自定义事件</span>}
</div>
@ -318,7 +342,7 @@ function New({ form , match , showNotification , history, projectDetail }) {
{getFieldDecorator("branch_filter",{
rules:[]
})(
<Input size="large"/>
<Input size="large" disabled = {unEdit}/>
)}
</Form.Item>
<Form.Item
@ -329,13 +353,13 @@ function New({ form , match , showNotification , history, projectDetail }) {
{getFieldDecorator("active",{
valuePropName:"checked"
})(
<Checkbox>激活<span className="subCbDesc">激活后触发事件的信息将发送到此Webhook地址</span></Checkbox>
<Checkbox disabled = {unEdit}>激活<span className="subCbDesc">激活后触发事件的信息将发送到此Webhook地址</span></Checkbox>
)}
</Form.Item>
<Button type="primary" onClick={submit} className="ml20" style={{width:id?"":"100px"}}>{id ? "更新Webhook":"添加"}</Button>
<Button type="primary" onClick={submit} className="ml20" style={{width:id?"":"100px"}} disabled = {unEdit}>{id ? "更新Webhook":"添加"}</Button>
{
id && <Button type="danger" className="ml20" onClick={deleteFunc}>删除</Button>
id && <Button type="danger" className="ml20" onClick={deleteFunc} disabled = {unEdit}>删除</Button>
}
</Form>
</div>

View File

@ -283,4 +283,69 @@
border-bottom-left-radius: 0;
}
}
}
.softBotClass{
.softBoxHeadTitle{
color:#151d40;
padding: 8px 0;
border-bottom: 1px solid #e0e6f5;
}
.botListBox{
margin-top: 16px;
color:#202d40;
.statusBox{
background-color:rgba(255, 81, 81, 0.08);
border:1px solid#f23737;
color:#f92d2d;
border-radius:4px;
padding: 4px 6px;
line-height: normal;
&.active{
background-color:rgba(58, 213, 115, 0.08);
border-color:#25c589;
color:#25c589;
}
}
.disBotItem{
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px dashed #e0e6f5;
padding: 20px 0 15px;
}
.flexCenter{
display: inline-flex;
align-items: center;
}
.botName{
display: inline-block;
max-width: 600px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.imgBox{
width: 44px;
height: 44px;
border-radius: 50%;
object-fit: cover;
}
}
.nullBotBox{
background-color: #fafcff;
border-radius: 4px 4px 0px 0px;
text-align: center;
padding: 25px 158px 65px;
.showBigTip{
color:#333333;
margin-top: 16px;
margin-bottom: 22px !important;
}
.showTip{color: #666;}
.borTip{
width: 82%;
margin: 20px auto 18px;
border-top: 1px solid rgba(90, 117, 193, 0.23)
}
}
}

View File

@ -0,0 +1,52 @@
import React, {useState} from "react";
import { Link } from "react-router-dom";
import { Button } from "antd";
import nullBot from '../../softbot/image/notBot.png';
import './setting.scss';
import { useEffect } from "react";
import { getStoreAllInstallBots } from "../../softbot/api";
import { getImageUrl } from 'educoder';
function SoftBot(props){
const {projectDetail} = props;
const [botList, setBotList] = useState(undefined);
useEffect(()=>{
// bot
projectDetail && getStoreAllInstallBots({
store_id: projectDetail.project_id
}).then(res=>{
if(res && res.code === 200){
setBotList(res.data.install_bot_infos);
}
})
//
if(projectDetail && document.title.indexOf('已安装Bot-') === -1){
const { author, name} = projectDetail;
document.title = `已安装Bot-${author.name}/${name}`;
}
}, [projectDetail])
return <div className="softBotClass">
<div className="softBoxHeadTitle font-18">Bot安装</div>
{/* bot列表显示区域 */}
{botList && (botList.length > 0 ? <div className="botListBox">
{botList.map((item, index)=>{return <div className="disBotItem" key={index}>
<div className="flexCenter">
<img src={getImageUrl(item.logo)} alt="" className="imgBox mr20"/>
<span className="font-15 botName">{item.bot_name}</span>
<span className={`statusBox font-12 ml10 ${item.state === 0 ? '' : 'active'}`}>{item.state === 0 ? '挂起' : '启用'}</span>
</div>
<Button className="themeCorBorBut" style={{width: '68px', height: '36px'}}><Link to={`/settings/installbot/${item.bot_id}`}>配置</Link></Button>
</div>})}
</div> : <div className="nullBotBox mt20">
<img src={nullBot} alt="" width={62}/>
<p className="font-18 showBigTip">您当前暂未安装任何Bot</p>
<div className="showTip font-14">Bot可以帮助项目贡献者处理繁杂的项目任务比如关闭疑修合并请求等以节约时间精力提升开发效率快去市场中挑选一个适合您项目的Bot吧!</div>
<div className="borTip"></div>
<Button type="primary" style={{width: '112px', height: '36px', padding: 0}}><Link to={'/softbot'}>去Bot市场逛逛</Link></Button>
</div>)}
</div>
}
export default SoftBot

View File

@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
import { Upload , Icon , message } from "antd";
import { getUploadActionUrl } from 'educoder';
function UploadImage({ getImage , url }){
function UploadImage({ getImage , url, getImageId }){
const [ imageUrl , setImageUrl ] = useState(undefined);
useEffect(()=>{
if(url){
@ -33,6 +33,8 @@ function UploadImage({ getImage , url }){
//
function handleChange(info){
if(info && info.file && info.file.status === "done"){
console.log('info', info);
getImageId && getImageId(info.file.response.id);
getBase64(info.file.originFileObj, imageUrl =>
setImageUrl(imageUrl)
);

View File

@ -59,8 +59,9 @@ class Index extends Component {
return {
fileList: newFileList,
};
},()=>{
this.fileIdList(this.state.fileList);
});
this.fileIdList(this.state.fileList);
} else {
this.props.showNotification(response.data.message)
}

View File

@ -3,28 +3,34 @@ import { Button, Checkbox, Form, Icon, Radio, Input, message } from 'antd';
import MDEditor from "../../modules/tpm/challengesnew/tpm-md-editor";
import DelModal from './components/ModalFun';
import { weekModal, monthModal } from './components/config';
import { getWiki, wikiPages, addWiki, updateWiki } from './api';
import { getWiki, wikiPages, addWiki, updateWiki, markdownToTree, treeToMd } from './api';
import './Index.scss';
import { Base64 } from 'js-base64';
export default Form.create()(({ form, history, showNotification, projectDetail, match, project }) => {
// const permission = projectDetail && projectDetail.permission !== "Reporter";
const {location:{pathname, search}} = history;
const permission = projectDetail && projectDetail.permission && projectDetail.permission !== "Reporter";
const { getFieldDecorator, validateFields, setFieldsValue } = form;
const [fileArrInit, setFileArrInit] = useState(null);
let projectsId = match.params.projectsId;
let owner = match.params.owner;
let wikiName = history.location.pathname.split('/').pop();
if (wikiName === 'add') {
wikiName = '';
let wikiName = ''
let key = '';
if (!pathname.endsWith('/wiki/add')) {
wikiName = decodeURI(pathname.split('/')[4]);
key = pathname.split('/')[5];
}else{
key = search.split('=').pop();
}
const [content, setContent] = useState(undefined);
const [fileArrInit, setFileArrInit] = useState(null);
const [content, setContent] = useState("欢迎来到Wiki");
const [modal, setModal] = useState(false);
const [operateFlag, setOperateFlag ] = useState(false);
const [modalType, setModalType] = useState();
const [sidebar, setSidebar] = useState(undefined);
const [menuList, setMenuList] = useState(undefined);
useEffect(()=>{
if(projectDetail){
@ -41,14 +47,15 @@ export default Form.create()(({ form, history, showNotification, projectDetail,
wikiName && project && getWiki({
owner,
repo: projectsId,
pagename: wikiName,
pageName: wikiName,
projectId: project.id
}).then(res => {
if (res && res.data) {
setContent(res.data.md_content);
const content = Base64.decode(res.data.md_content);
setContent(content);
setFieldsValue({
name: res.data.name,
content: res.data.md_content,
title: res.data.name,
md_content: content,
});
}
});
@ -63,6 +70,22 @@ export default Form.create()(({ form, history, showNotification, projectDetail,
}).then(res => {
if (res && res.message === "200" && Array.isArray(res.data)) {
setFileArrInit(res.data);
// wiki
if(res.data.length){
// sidebar
getWiki({
owner,
repo: projectsId,
pageName: '_Sidebar',
projectId: project.id
}).then(res => {
if (res && res.data) {
const sidebarDecode = Base64.decode(res.data.md_content);
setSidebar(sidebarDecode);
key && setMenuList(markdownToTree(sidebarDecode))
}
});
}
} else {
setFileArrInit([]);
}
@ -81,38 +104,42 @@ export default Form.create()(({ form, history, showNotification, projectDetail,
function onContentChange(value) {
setContent(value);
setFieldsValue({
content: value
md_content: value
});
};
// wiki
function saveFile() {
if(operateFlag) return;
setOperateFlag(true);
if(operateFlag) {
setOperateFlag(true);
return;
};
validateFields((err, values) => {
if (!err) {
let regEn = /[\[\]`\/:*?''<>|%-+_]/g;
if (regEn.test(values.name)) {
message.error('文件名不能有特殊字符');
let regEn = /[\\/:*?"<>|[\]-]/g;
if (regEn.test(values.title)) {
message.error('文件名不能有特殊字符: \\ / : * ? \" < > | [ \] -');
setOperateFlag(false);
return;
}
const {md_content, title} = values;
if (wikiName) {
updateWiki({
owner,
repo: projectsId,
projectId: project.id,
pagename: wikiName,
...values,
pageName: wikiName,
title,
commit_message: '',
}).then(res => {
dealRes(res);
setOperateFlag(false);
});
content_base64: Base64.encode(md_content)
}).then(res => dealRes(res, title));
} else {
// wikiwikinamemd
if (Array.isArray(fileArrInit)) {
for (const item of fileArrInit) {
if (item.name === values.name) {
if (item.name === title) {
message.error('不能与已有文件标题相同');
setOperateFlag(false);
return false;
}
}
@ -121,31 +148,127 @@ export default Form.create()(({ form, history, showNotification, projectDetail,
owner,
repo: projectsId,
projectId: project.id,
pagename: values.name,
...values,
commit_message: '',
}).then(res =>{
dealRes(res);
setOperateFlag(false);
});
pageName: title,
title,
message: '',
content_base64: Base64.encode(md_content)
}).then(res => dealRes(res, title));
}
}
})
}
function dealRes(res) {
if (res && res.message === "200") {
showNotification("操作成功");
goBack();
function dealRes(res, title) {
if (res && (res.message === "201" || res.message === "200")) {
if(wikiName){
// wikisidebar
if(wikiName === title){
message.success("操作成功");
goBack();
}else{
// keynodetitlesidebar
const newMenuList = menuList;
editTitleByKey(newMenuList, key, title);
updateSidebar(treeToMd(newMenuList), title);
}
}else{
//
if(search.includes("?key")){
// menuListkeychildren push[[]]
const list = menuList;
addChildrenByKey(list, key, title);
updateSidebar(treeToMd(list), title);
}else{
if(fileArrInit.length){
updateSidebar(`${sidebar}\n[[${title}]]`, title);
}else{
// wiki
addWiki({
owner,
repo: projectsId,
projectId: project.id,
pageName: "_Sidebar",
title: "_Sidebar",
message: '',
content_base64: Base64.encode(`[[${title}]]`)
}).then(res => {
if(res && res.message === "201"){
message.success("操作成功");
goBack();
}
});
}
}
}
} else if (res && res.message === "500") {
setOperateFlag(false);
message.error('请检查格式是否正确或文件名是否重复');
} else {
showNotification(res.message || "操作失败");
setOperateFlag(false);
showNotification(res.data || "操作失败");
}
}
function updateSidebar(content, newTitle){
updateWiki({
owner,
repo: projectsId,
projectId: project.id,
pageName: '_Sidebar',
title: '_Sidebar',
commit_message: '',
content_base64: Base64.encode(content)
}).then(res => {
if (res && res.message === "200") {
message.success("操作成功");
if(newTitle){
// wiki
history.push(`/${owner}/${projectsId}/wiki/${encodeURI(newTitle)}/${key}`)
return;
}
goBack();
}
});
}
// menuList, keytitle
function editTitleByKey(list, key, newTitle){
for (let index = 0; index < list.length; index++) {
const element = list[index];
if (element.key == key) {
const {title} = element;
element.title = `${title.substring(0, title.indexOf('[['))}[[${newTitle}]]`;
return true;
}else if (element.children.length) {
let result = editTitleByKey(element.children, key, newTitle);
if (result) {
return result;
}
}
}
}
// menuList
function addChildrenByKey(list, key, name){
for (let index = 0; index < list.length; index++) {
const element = list[index];
if (element.key == key) {
const {title} = element;
element.children.push({
title: `\t${title.substring(0, title.indexOf('- '))}[[${name}]]`,
children: []
});
return true;
}else if (element.children.length) {
let result = addChildrenByKey(element.children, key, name);
if (result) {
return result;
}
}
}
}
function goBack() {
// history.push(`/${owner}/${projectsId}/wiki`);
history.go(-1);
}
@ -179,12 +302,12 @@ export default Form.create()(({ form, history, showNotification, projectDetail,
if (value === '周报') {
setContent(weekModal);
setFieldsValue({
content: weekModal
md_content: weekModal
});
} else if (value === '月报') {
setContent(monthModal);
setFieldsValue({
content: monthModal
md_content: monthModal
});
}
}
@ -200,7 +323,7 @@ export default Form.create()(({ form, history, showNotification, projectDetail,
<h4 className="mt20 mb0">标题</h4>
{helper(
"",
"name",
"title",
[
{ required: true, message: "请输入标题" },
// { pattern: /[^`\[\]\/:*?''<>|%-+_]/g, message: '' }
@ -221,9 +344,10 @@ export default Form.create()(({ form, history, showNotification, projectDetail,
onChange={onContentChange}
className="mt20 wikiMEDEditor"
></MDEditor>
{getFieldDecorator('content', {
{getFieldDecorator('md_content', {
rules: [{ required: true, message: "请输入wiki内容" }],
validateFirst: true
validateFirst: true,
initialValue: "欢迎来到Wiki"
})(<Input style={{ display: 'none' }} />)}
</Form.Item>

View File

@ -1,35 +1,49 @@
import React, { useEffect, useCallback, useState } from 'react';
import { Button, Dropdown, Icon, Input, Menu, Tooltip, Select, Upload, message, Spin } from 'antd';
import React, { useEffect, useCallback, useState, useMemo } from 'react';
import { Button, Dropdown, Icon, Input, Menu, Tooltip, Select, Upload, message, Spin, Modal } from 'antd';
import { getImageUrl, timeAgo } from 'educoder';
import cookie from 'react-cookies';
import CopyTool from '../Component/CopyTool';
import DelModal from './components/ModalFun';
import Welcome from './Welcome';
import RenderHtml from "../../components/render-html";
import { wikiPages, getWiki, deleteWiki } from './api';
import { wikiPages, getWiki, parseSidebar, updateWiki, treeToMd } from './api';
import { httpUrl, TokenKey } from './fetch';
import './Index.scss';
import './components/ModalFun/index.scss';
import { isArray } from 'lodash';
import { Base64 } from 'js-base64';
import Sidebar from './components/sidebar';
import UploadWiki from './components/uploadWiki';
const Search = Input.Search;
const InputGroup = Input.Group;
const { Option } = Select;
export default (props) => {
const { match, history, showNotification, project, projectDetail } = props;
const {location:{pathname}} = history;
const permission = projectDetail && projectDetail.permission && projectDetail.permission !== "Reporter";
let projectsId = match.params.projectsId;
let owner = match.params.owner;
let wikiName = match.params.wikiName;
const [fileArrInit, setFileArrInit] = useState(null);
const [checkItem, setCheckItem] = useState({});
const [itemDetail, setItemDetail] = useState({});
const [fileArr, setFileArr] = useState([]);
const [fileList, setFileList] = useState([]);
const [fileListInit, setFileListInit] = useState([]);
const [reload, setReload] = useState();
const [urlType, setUrlType] = useState('HTTPS');
const [visible, setVisible] = useState(false);
const [menuName, setMenuName] = useState(undefined);
const [addMenuError, setAddMenuError] = useState(undefined);
const [sidebar, setSidebar] = useState(undefined);
const [defaultSelectedKeys, setDefaultSelectedKeys] = useState(undefined);
useEffect(()=>{
window.scrollTo(0,0);
}, [])
useEffect(()=>{
if(projectDetail){
@ -48,18 +62,43 @@ export default (props) => {
owner: owner,
repo: projectsId,
projectId: project.id
}).then(res => {
}).then(async res => {
if (res && res.message === "200" && isArray(res.data)) {
setFileArr(res.data);
setFileArrInit(res.data);
if (res.data.length) {
if (wikiName) {
let activeItem = res.data.filter(item => { return item.name == wikiName })[0] || res.data[0];
setCheckItem(activeItem)
} else {
setCheckItem(res.data[0]);
}
};
// sidebarlist
const info = await parseSidebar(res.data, {
owner: owner,
repo: projectsId,
projectId: project.id
})
if(!info){return}
const {menuList, sidebar} = info;
setSidebar(sidebar)
setFileList(menuList);
setFileListInit(menuList);
let firstWikiNode = undefined;
// if(!pathname.endsWith('/wiki')){
// firstWikiNode = findNodeByKey(menuList, pathname.split('/').pop())
// }
const arr = pathname.split('/');
if(!pathname.endsWith('/wiki')){
firstWikiNode = findNodeByName(menuList, arr[arr.length-2]);
} else{
// [[]]
firstWikiNode = findFirstWiki(menuList);
}
// wiki
!firstWikiNode && (firstWikiNode = findFirstWiki(menuList));
//
setDefaultSelectedKeys([firstWikiNode.key+""]);
const title = firstWikiNode.title.trim().substring(2,firstWikiNode.title.trim().length-2);
const firstWiki = res.data.filter(item => { return title === item.name })[0];
setCheckItem({
...firstWiki,
key: firstWikiNode.key
})
history.push(`/${owner}/${projectsId}/wiki/${title}/${firstWikiNode.key}`);
} else {
setFileArr([]);
setFileArrInit([]);
@ -72,7 +111,7 @@ export default (props) => {
project && checkItem.name && getWiki({
owner: owner,
repo: projectsId,
pagename: checkItem.name,
pageName: checkItem.name,
projectId: project.id
}).then(res => {
if (res && res.message === "200") {
@ -83,40 +122,66 @@ export default (props) => {
});
}, [project, checkItem]);
function changeSearchValue(e) {
let value = e.target.value;
let newFileArr = [];
for (const item of fileArrInit) {
if (item.name.indexOf(value) > -1) {
newFileArr.push(item);
function findNodeByName(list, name){
for (let index = 0; index < list.length; index++) {
const element = list[index];
if (element.title.indexOf(`[[${name}]]`) !== -1) {
return element;
}else if (element.children.length) {
let result = findNodeByName(element.children, name);
if (result) {
return result;
}
}
}
setFileArr(newFileArr);
}
// wiki
function deleteFileModal(e, item) {
e.stopPropagation();
DelModal({
title: '删除页面',
contentTitle: `您确定要删除“${item.name}”此页面吗?`,
content: '此操作将删除该页面,请进行确认以防文件的丢失',
onOk: () => {
deleteWiki({
owner: owner,
repo: projectsId,
pagename: item.name,
projectId: project.id
}).then(res => {
if (res && res.message === "200") {
message.success('删除成功');
setReload(Math.random());
} else {
message.error('删除失败');
}
});
// menulist
function findFirstWiki(list){
for (let index = 0; index < list.length; index++) {
const element = list[index];
if (element.title.trim().startsWith('[[')) {
return element;
}else if (element.children.length) {
let result = findFirstWiki(element.children);
if (result) {
return result;
}
}
});
}
}
function changeSearchValue(value) {
if(value){
// wiki,
let filterList = filterTree(fileListInit, value);
setFileList(filterList);
}else{
setFileList(JSON.parse(JSON.stringify(fileListInit)));
}
}
function filterTree(list, value, arr = []){
if(!list.length) return []
for(let item of list){
const bool = item.title.match(new RegExp(value, 'i'));
let node = JSON.parse(JSON.stringify(item));
node.children = [];
bool && arr.push(node);
if(item.children && item.children.length){
filterTree(item.children, value, bool ? node.children : arr);
}
}
return arr
// return list.filter(item => {
// if (item.title.includes(value)) {
// return true
// }
// // if(item.children && item.children.length > 0){
// // item.children = findUnHasValue(item.children , value);
// return findUnHasValue(item.children , value);
// // }
// })
}
@ -125,11 +190,64 @@ export default (props) => {
}
function addFile() {
//
history.push(`/${owner}/${projectsId}/wiki/add`);
}
//
function addMenu(){
if(!menuName){
setAddMenuError('请输入目录名称');
return;
}
if(new RegExp("^[ ]+$").test(menuName)){
setAddMenuError("不能仅输入空格");
return;
}
const regEn = /[\\/:*?"<>|[\]-]/g;
if (regEn.test(menuName)) {
setAddMenuError("不能有特殊字符: \\ / : * ? \" < > | [ \] -");
return;
}
//
const oneMenuList = fileList.filter(item=>!item.title.trim().startsWith('[[')).map(i=>i.title.trim())
if(oneMenuList.includes(`- ${menuName}`)){
setAddMenuError('不能与已有文件标题相同');
return;
}
// fileListmdsidebar
const newSidebarList = fileList.concat({title: `- ${menuName}`, children: []})
updateWikiFun(newSidebarList, 1);
}
// wiki sidebar
function updateWikiFun(sidebarList, type){
updateWiki({
owner,
repo: projectsId,
projectId: project.id,
pageName: '_Sidebar',
title: '_Sidebar',
commit_message: '',
content_base64: Base64.encode(treeToMd(sidebarList))
}).then(res => {
if (res && res.message === "200") {
if(type === 1){
setVisible(false);
setMenuName(undefined);
message.success('操作成功');
}
// setFileList(sidebarList);
setReload(Math.random());
}else {
message.error(res.data || "操作失败");
}
});
}
function goEdit() {
history.push(`/${owner}/${projectsId}/wiki/edit/${encodeURI(checkItem.name)}`);
history.push(`/${owner}/${projectsId}/wiki/${encodeURI(checkItem.name)}/${checkItem.key}/edit`);
}
function preview() {
@ -155,67 +273,18 @@ export default (props) => {
window.open(`${httpUrl}/api/wikiExport/wikiExport-wrapper?repoName=${projectsId}&owner=${owner}&type=${type}&projectName=${project.name}&projectId=${project.id}`);
}
function beforeUpload(file) {
if (!['md', 'txt'].includes(file.name.split('.').pop())) {
message.error('只能上传md、txt文件');
return false;
}
let regEn = /[\[\]`\/:*?''<>|%-+_]/g;
if (regEn.test(file.name)) {
message.error('文件名不能有特殊字符');
return;
}
for (const item of fileArrInit) {
if (item.name === file.name) {
message.error('不能上传与已有文件相同文件名的文件');
return false;
}
}
const isLt100M = file.size / 1024 / 1024 < 100;
if (!isLt100M) {
showNotification(`文件大小必须小于${100}MB!`);
}
return isLt100M;
}
const uploadProps = {
name: 'multipartFile',
withCredentials: true,
action: `${httpUrl}/api/wikiExport/uploadWiki/${owner}/${projectsId}/${project && project.id}`, //?token=${sessionStorage.taskToken}
showUploadList: false,
headers: {
Authorization: cookie.load(TokenKey) || sessionStorage.taskToken,
},
beforeUpload: beforeUpload,
onChange(info) {
if (info.file.status === 'uploading') {
console.log(info.file, info.fileList);
}
if (info.file.status === 'done') {
if (info.file.response.message === '200') {
message.success(`${info.file.name} 上传成功`);
setReload(Math.random());
} else if (info.file.response.message === '500') {
let data = JSON.parse(info.file.response.data);
message.error(data && data.message ? data.message : '文件上传失败');
} else {
message.error('文件上传失败');
}
} else if (info.file.status === 'error') {
message.error(`${info.file.name} 上传失败`);
}
},
};
function changeitem(item){
history.push(`/${owner}/${projectsId}/wiki/${item.name}`)
setCheckItem(item);
const {name, key} = item;
const wiki = fileArrInit.filter(item => { return item.name == name })[0];
setCheckItem({
...wiki,
key
})
history.push(`/${owner}/${projectsId}/wiki/${name}/${key}`);
}
return (
< Spin spinning={!fileArrInit} className="opacitySpin">
<Spin spinning={!fileArrInit} className="opacitySpin">
{fileArrInit && fileArrInit.length ?
<div className="wiki-main">
<div className="wiki-head">
@ -223,18 +292,15 @@ export default (props) => {
<span className="head-title">
{
permission ?
<Button type="default" onClick={addFile}><Icon type="plus" />新增页面</Button>
: "Wiki文档"
<div>
<Button type="default" onClick={()=>{setVisible(true)}} className='mr15'><Icon type="plus"/>新增目录</Button>
<Button type="default" onClick={addFile}><Icon type="plus" />新增页面</Button>
</div> : "Wiki文档"
}
</span>
<div>
{
permission &&
<Upload {...uploadProps}>
<Tooltip placement="top" title={'支持导入txt、markdown格式文件'}>
<Button type="default" className="ml10"><Icon type="plus" />导入模板</Button>
</Tooltip>
</Upload>
permission && <UploadWiki {...props} fileList={fileList} setFileList={setFileList} fileArrInit={fileArrInit} setReload={setReload}/>
}
<Dropdown overlay={menu}>
<Button type="default" className="ml10">导出<Icon type="caret-down" /></Button>
@ -246,27 +312,18 @@ export default (props) => {
<div className="wiki-body">
<div className="wiki-nav-parent">
<div className="wiki-nav">
<Search
// allowClear
placeholder="输入关键字搜索文件"
className="wiki-search"
onChange={changeSearchValue}
/>
<div className='wiki-nav-scroll'>
<div className="wiki-nav">
<Search
allowClear
placeholder="输入关键字搜索文件"
className="wiki-search"
onSearch={changeSearchValue}
/>
{
fileArr.map(item => {
return <div className="wiki-nav-title-parent" key={item.name}>
<div className={`wiki-nav-title ${item.name === checkItem.name ? 'active' : ''}`} onClick={() => { changeitem(item) }}>
<div className="nav-title-left">
<i className="iconfont icon-wenjianjia2 mr3"></i>
<span className="nav-title-left-text">{item.name}</span>
</div>
{permission && <i className="iconfont icon-shanchuicon1 delete-title-icon color-grey-6" onClick={(e) => { deleteFileModal(e, item) }}></i>}
</div>
</div>
})
}
{/* 读sidebar转换成目录树 */}
<Sidebar fileList = {fileList} changeitem={changeitem} projectsId={projectsId} owner={owner} project={project} setReload={setReload} sidebar={sidebar} history={history} permission={permission} defaultSelectedKeys={defaultSelectedKeys}/>
</div>
</div>
@ -287,20 +344,25 @@ export default (props) => {
<h3 className="wiki-detail-title">{checkItem.name}</h3>
<span className="user-box mr10" onClick={() => { checkItem.commit && goUser(checkItem.commit.author.name) }}>
{itemDetail.image_url && <img alt="头像" className="head-log-small" src={getImageUrl(`/${itemDetail.image_url}`)} />}
<span >{itemDetail.userName}</span>
<span className='font-14'>{itemDetail.userName}</span>
</span>
<span className="time-ago">上次修改于{checkItem.commit ? timeAgo(checkItem.commit.author.when) : '刚刚'}</span>
<span className="time-ago font-14">上次修改于{checkItem.commit ? timeAgo(checkItem.commit.author.date) : '刚刚'}</span>
</div>
{permission && <Button type="primary" onClick={goEdit}>编辑</Button>}
</div>
{itemDetail&&itemDetail.simple_content&&<RenderHtml className="wiki-content-detail editor-content-panel" value={ itemDetail&&itemDetail.md_content } url={history.location}/>}
{itemDetail&&itemDetail.md_content&&<RenderHtml className="wiki-content-detail editor-content-panel" value={ itemDetail && Base64.decode(itemDetail.md_content) } url={history.location}/>}
</div>
</div>
</div >
:
<Welcome {...props} reloadList={setReload} />
<Welcome {...props} reloadList={setReload} fileList={fileList} setFileList={setFileList} fileArrInit={fileArrInit} setReload={setReload}/>
}
<Modal title="新增目录" visible={visible} onCancel={()=>{setVisible(false);setAddMenuError(undefined);setMenuName(undefined);}} onOk={addMenu} className="wikiAddMenu myself-modal" width={450} centered>
<div className='mb10'>目录名称: </div>
<Input placeholder='请输入目录名称' value={menuName} onChange={(e)=>{setMenuName(e.target.value);setAddMenuError(undefined)}} maxLength={50}></Input>
<div className='wikiAddMenuError mt5'>{addMenuError}</div>
</Modal>
</Spin>
)
}

View File

@ -73,8 +73,8 @@ body {
}
.head-log-small {
width: 1.25rem;
height: 1.25rem;
width: 1.5rem;
height: 1.5rem;
margin-right: 0.35rem;
border-radius: 50%;
}
@ -113,12 +113,8 @@ body {
max-height: 60vh;
.wiki-search {
padding: 0 14px;
.ant-input {
padding-left: 30px;
}
.ant-input-suffix {
left: 26px;
right: auto;
right: 20px;
}
&:hover .ant-input,
&:focus .ant-input {
@ -128,8 +124,12 @@ body {
}
.wiki-nav-parent {
width: 260px;
width: 280px;
flex: none;
// .wiki-nav-scroll{
// width: 280px;
// overflow-y: scroll;
// }
}
.ant-form-item-children .ant-input:hover {
@ -223,13 +223,19 @@ body {
}
.wiki-nav {
min-height: 300px;
min-height: 500px;
padding: 20px 0;
background: #ffffff;
border: 1px solid rgba(153, 153, 153, 0.22);
overflow-y: scroll;
flex: none;
color: #333;
.ant-tree.ant-tree-directory > li.ant-tree-treenode-selected > span.ant-tree-node-content-wrapper::before, .ant-tree.ant-tree-directory .ant-tree-child-tree > li.ant-tree-treenode-selected > span.ant-tree-node-content-wrapper::before{
background-color: #F4F6FF;
}
.ant-tree.ant-tree-directory > li span.ant-tree-node-content-wrapper.ant-tree-node-selected, .ant-tree.ant-tree-directory .ant-tree-child-tree > li span.ant-tree-node-content-wrapper.ant-tree-node-selected{
color: $primary-color;
}
}
.wiki-nav-title {
@ -305,8 +311,19 @@ body {
.wiki-preview {
overflow-y: scroll;
height: 100%;
.previewWiki{
overflow-y: auto;
.ant-tree-title{
font-size: 16px;
padding-left: 6px;
}
}
.previewContent{
flex: none;
margin: 0 auto;
}
.wiki-body {
width: 90%;
width: 95%;
}
.ant-btn-primary {
background-color: $wikiColor;
@ -323,7 +340,7 @@ body {
align-items: center;
padding: 0 12rem 0 2rem;
width: 100%;
height: 80px;
height: 8vh;
background: rgb(39, 47, 76);
color: #fff;
}
@ -368,7 +385,7 @@ body {
padding: 20px 0;
width: 20vw;
border-bottom: 0;
height: 80vh;
height: 92vh;
}
.wiki-content-detail {
padding: 0 40px;
@ -385,4 +402,13 @@ body {
}
.wikiMEDEditor .editormd-dialog-footer{
padding: 0;
}
.wikiAddMenu{
&.myself-modal .ant-modal-body{
text-align: left;
padding: 40px 30px;
}
}
.wikiAddMenuError{
color: red;
}

View File

@ -1,43 +1,54 @@
import React, { useEffect, useCallback, useState } from 'react';
import { Input, Button, Select, Dropdown, Icon, Menu, message } from 'antd';
import React, { useEffect, useState } from 'react';
import { Input, Button, Select, Dropdown, Icon, Menu, message, Tree } from 'antd';
import CopyTool from '../Component/CopyTool';
import { wikiPages, getWiki, } from './api';
import { wikiPages, getWiki, parseSidebar, } from './api';
import { httpUrl } from './fetch';
import './Index.scss';
import { Base64 } from 'js-base64';
import RenderHtml from '../../components/render-html';
const {DirectoryTree, TreeNode} = Tree;
const InputGroup = Input.Group;
const { Option } = Select;
export default (props) => {
const { match, history, showNotification, project, projectDetail } = props;
const permission = projectDetail && projectDetail.permission && projectDetail.permission !== "Reporter";
const { match, history, showNotification } = props;
let projectsId = match.params.projectsId;
let owner = match.params.owner;
let projectName = match.params.projectName;
let projectId = match.params.projectId;
let key = 1;
const [checkItem, setCheckItem] = useState({});
const [itemDetail, setItemDetail] = useState({});
const [fileArr, setFileArr] = useState([]);
const [urlType, setUrlType] = useState('HTTPS');
const [fileList, setFileList] = useState([]);
useEffect(()=>{
if(checkItem){
if(checkItem && checkItem.name){
document.title = `${checkItem.name}-维基预览`
}
}, [checkItem, projectDetail])
}, [checkItem])
useEffect(() => {
projectsId && wikiPages({
owner: owner,
repo: projectsId,
projectId: projectId,
}).then(res => {
}).then(async res => {
if (res && res.message === "200") {
setFileArr(res.data);
// sidebarlist
const info = await parseSidebar(res.data, {
owner: owner,
repo: projectsId,
projectId
})
const {menuList, sidebar} = info;
setFileList(menuList);
if (res.data.length) {
setCheckItem(res.data[0]);
};
@ -45,14 +56,14 @@ export default (props) => {
showNotification("加载失败")
}
});
}, [project])
}, [])
useEffect(() => {
projectsId && checkItem.name && getWiki({
owner: owner,
repo: projectsId,
pagename: checkItem.name,
pageName: checkItem.name,
projectId: projectId,
}).then(res => {
if (res && res.message === "200") {
@ -61,11 +72,7 @@ export default (props) => {
showNotification("加载失败")
}
});
}, [project, checkItem]);
function goEdit() {
history.push(`/${owner}/${projectsId}/wiki/edit/${checkItem.name}`);
}
}, [checkItem]);
function goBack() {
history.push(`/${owner}/${projectsId}/wiki`);
@ -90,6 +97,28 @@ export default (props) => {
window.open(`${httpUrl}/api/wikiExport/wikiExport-wrapper?repoName=${projectsId}&owner=${owner}&type=${type}&projectName=${projectName}&projectId=${projectId}`);
}
function renderTreeNodes(data) {
return data && data.length > 0 && data.map((item) => {
let title = item.title.trim();
const isFile = title.startsWith('[[') && title.endsWith(']]');
title = isFile ? title.substring(2,title.length-2) : title.substring(2, title.length);
return (
<TreeNode title={title} key={key++} isLeaf={isFile} dataRef={item}>
{renderTreeNodes(item.children)}
</TreeNode>
);
});
}
function selectTree(keys,event){
let {title, key} = event.node.props.dataRef;
title = title.trim()
const isFile = title.startsWith('[[') && title.endsWith(']]');
if(isFile){
title = title.substring(2,title.length-2);
setCheckItem({name: title, key});
}
}
return (
<div className="wiki-preview">
@ -99,16 +128,16 @@ export default (props) => {
<span className="ml10">{projectName}</span>
</div>
<div className="preview-head-right">
<span className="copy-desc">克隆地址</span>
<span className="copy-desc font-16">克隆地址</span>
{
checkItem.wiki_clone_link && <InputGroup className="copy-url" compact>
fileArr.length && fileArr[0].wiki_clone_link ? <InputGroup className="copy-url" compact>
<Select dropdownClassName="wiki-url-type" defaultValue="HTTPS" onChange={(v) => { setUrlType(v) }}>
<Option value="HTTPS">HTTPS</Option>
<Option value="SSH">SSH</Option>
</Select>
<Input id="wikiUrl" value={urlType === 'HTTPS' ? checkItem.wiki_clone_link.https : checkItem.wiki_clone_link.ssh} />
<Input id="wikiUrl" value={urlType === 'HTTPS' ? fileArr[0].wiki_clone_link.https : fileArr[0].wiki_clone_link.ssh} />
<CopyTool className="copy-wiki" inputId="wikiUrl"/>
</InputGroup>
</InputGroup> : ''
}
<Dropdown overlay={menu}>
@ -119,8 +148,8 @@ export default (props) => {
<div className="wiki-body">
<div className="wiki-nav">
{
<div className="wiki-nav previewWiki">
{/* {
fileArr.map(item => {
return <div className={`${item.name === checkItem.name ? 'active' : ''} wiki-nav-title`} key={item.name} onClick={() => { setCheckItem(item) }}>
<span className="nav-title-left" >
@ -129,17 +158,19 @@ export default (props) => {
</span>
</div>
})
}
} */}
{/* 读sidebar转换成目录树 */}
{fileList && fileList.length ? <DirectoryTree onSelect={selectTree} defaultExpandAll>
{renderTreeNodes(fileList)}
</DirectoryTree> : ''}
</div>
<div className="wiki-content">
<div className="wiki-content previewContent">
<div className="wiki-content-head">
<h3>{checkItem.name}</h3>
{permission && <Button type="primary" onClick={goEdit}>编辑</Button>}
<h3 className='mt10 pb10'>{checkItem.name}</h3>
</div>
<div className="wiki-content-detail editor-content-panel markdown-body" dangerouslySetInnerHTML={{ __html: itemDetail && itemDetail.simple_content }} ></div>
{itemDetail && itemDetail.md_content && <RenderHtml className="wiki-content-detail editor-content-panel markdown-body" value={Base64.decode(itemDetail.md_content)} url={history.location}/>}
</div>
</div>

View File

@ -2,30 +2,14 @@ import React from 'react';
import { Button } from 'antd';
// import { addWiki } from '../api';
import './index.scss';
import UploadWiki from '../components/uploadWiki';
export default ({ project, isManager, history, showNotification, match, reloadList }) => {
export default (props) => {
const { project, isManager, history, showNotification, match, reloadList } = props;
let projectsId = match.params.projectsId;
let owner = match.params.owner;
// function addFile() {
// addWiki({
// owner,
// repo: projectsId,
// pagename: 'Home',
// name: 'Home',
// content: 'Welcome to the wiki!',
// commit_message: '',
// }).then(res => {
// if (res.message === "200") {
// reloadList(Math.random());
// showNotification("");
// } else {
// showNotification("");
// }
// });
// }
function addFile() {
history.push(`/${owner}/${projectsId}/wiki/add`);
}
@ -42,8 +26,10 @@ export default ({ project, isManager, history, showNotification, match, reloadLi
<div className="wiki-line"></div>
{
isManager ? <Button type="primary" onClick={addFile}>创建Wiki文档</Button>
: <p className="welcome-des">该项目暂时没有创建Wiki</p>
isManager ? <div>
<Button type="primary" onClick={addFile}>创建Wiki文档</Button>
<UploadWiki {...props} butClass="welcomeUploadBtn"/>
</div> : <p className="welcome-des">该项目暂时没有创建Wiki</p>
}
</div>
)

View File

@ -52,4 +52,8 @@
color: #333333;
font-weight: 500;
}
.welcomeUploadBtn{
color: $primary-color;
border-color: $primary-color;
}
}

Some files were not shown because too many files have changed in this diff Show More