merge gitlink_server 代码

This commit is contained in:
caishi 2024-04-08 10:16:43 +08:00
commit bc4facb87c
48 changed files with 880 additions and 266 deletions

12
package-lock.json generated
View File

@ -172,7 +172,7 @@
"dependencies": {
"jsesc": {
"version": "2.5.2",
"resolved": "http://173.15.15.82:8081/repository/npm-all/jsesc/-/jsesc-2.5.2.tgz",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA=="
}
}
@ -245,7 +245,7 @@
"dependencies": {
"chalk": {
"version": "2.4.2",
"resolved": "http://173.15.15.82:8081/repository/npm-all/chalk/-/chalk-2.4.2.tgz",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"requires": {
"ansi-styles": "^3.2.1",
@ -341,12 +341,12 @@
},
"globals": {
"version": "11.12.0",
"resolved": "http://173.15.15.82:8081/repository/npm-all/globals/-/globals-11.12.0.tgz",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
},
"ms": {
"version": "2.1.2",
"resolved": "http://173.15.15.82:8081/repository/npm-all/ms/-/ms-2.1.2.tgz",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
@ -7248,7 +7248,7 @@
},
"promise": {
"version": "7.3.1",
"resolved": "http://173.15.15.82:8081/repository/npm-all/promise/-/promise-7.3.1.tgz",
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
"requires": {
"asap": "~2.0.3"
@ -9677,7 +9677,7 @@
},
"chalk": {
"version": "2.4.2",
"resolved": "http://173.15.15.82:8081/repository/npm-all/chalk/-/chalk-2.4.2.tgz",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"requires": {
"ansi-styles": "^3.2.1",

File diff suppressed because one or more lines are too long

View File

@ -3373,8 +3373,8 @@ a.edu-greyline-btn:hover {
.defalutSubmitbtn {
display: block;
border: 1px solid #4CACFF;
background-color: #4CACFF;
border: 1px solid #466aff;
background-color: #466aff;
color: #fff !important;
width: 120px;
text-align: center;
@ -3382,7 +3382,6 @@ a.edu-greyline-btn:hover {
border-radius: 2px;
width: 130px;
height: 40px;
background: rgba(76, 172, 255, 1);
border-radius: 4px;
font-size: 16px;
font-family: MicrosoftYaHei;

View File

@ -2,16 +2,17 @@
<html lang="zh-hans-CN" class="notranslate translated-ltr" translate="no">
<head>
<title>GitLink | 新一代开源创新服务平台</title>
<meta charset="utf-8">
<meta name="Keywords" Content="gitLink,GitLink,gitlink,git,trustie,trustieforge,forge,开源,确实开源,代码托管,Git,开源,内源,项目管理,版本控制,开源代码,代码分享,项目协作,开源项目托管,免费代码托管,Git代码托管,Git托管服务,确实让创建更美好,协同开发平台">
<meta name="description" Content="GitLink,新一代开源创新服务平台 分布式协作开发 一站式过程管理 高效流水线运维 多层次代码分析 多维度用户画像 分布式协作开发 基于Git打造分布式代码托管环境">
<meta name="Keywords" content="GitLink, git, 开源, 代码托管, 项目管理, 版本控制, 代码分享, 项目协作, 协同开发平台">
<meta name="description" content="GitLink 是一个新一代的开源创新服务平台,提供分布式协作开发、流水线运维、代码分析等功能,基于 Git 打造分布式代码托管环境。">
<meta property="og:title" content="GitLink | 确实开源" />
<meta property="og:type" content="Object" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://gitlink.org.cn/" />
<meta property="og:image" content="https://www.gitlink.org.cn/images/logo.png" />
<meta property="og:image:alt" content="GitLink | 确实开源" />
<meta property="og:site_name" content="GitLink" />
<meta property="og:description" content="GitLink,新一代开源创新服务平台 分布式协作开发 一站式过程管理 高效流水线运维 多层次代码分析 多维度用户画像 分布式协作开发 基于Git打造分布式代码托管环境" />
<meta property="og:description" content="GitLink 是一个新一代的开源创新服务平台,提供分布式协作开发、流水线运维、代码分析等功能,基于 Git 打造分布式代码托管环境。" />
<meta name="hostname" content="gitlink.org.cn">
<meta name="keyboard-shortcuts-preference" content="all">
<meta name="expected-hostname" content="gitlink.org.cn">
@ -20,14 +21,8 @@
<meta name="octolytics-dimension-repository_nwo" content="GitLink"></meta>
<meta name="octolytics-dimension-repository_network_root_nwo" content="GitLink"></meta>
<meta name="theme-color" content="#000000">
<!-- <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests" /> -->
<meta content="IE=edge" http-equiv="X-UA-Compatible">
<meta name="twitter:image:src" content="https://www.gitlink.org.cn/images/logo.png">
<meta name="twitter:site" content="@gitlink">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="GitLink | 确实开源">
<meta name="twitter:description" content="GitLink,新一代开源创新服务平台 分布式协作开发 一站式过程管理 高效流水线运维 多层次代码分析 多维度用户画像 分布式协作开发 基于Git打造分布式代码托管环境">
<meta content="always" name="referrer">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="referrer" content="always">
<link rel="canonical" href="https://gitlink.org.cn" data-turbo-transient="">
<link rel="fluid-icon" href="https://www.gitlink.org.cn/images/logo.png" title="GitLink">
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -32,8 +32,8 @@ export function initAxiosInterceptors(props) {
// 判断网络是否连接
initOnlineOfflineListener();
// var proxy = "http://172.20.32.202:4000";
var proxy = "https://www.gitlink.org.cn";
var proxy = "https://testforgeplus.trustie.net";
//响应前的设置
axios.interceptors.request.use(

View File

@ -344,25 +344,21 @@ export function addMeta(name, content){
* projectId 项目唯一标识
*/
export function setSeoMeta(keyWords, title, description, url, owner, projectId) {
let keyStatement = ' ';
if(owner)keyStatement += owner;
if(projectId)keyStatement += `/${projectId}`;
document.querySelector(`meta[property='og:title']`).content = title + keyStatement + ' for gitlink' + keyStatement + ' for git';
document.querySelector(`meta[property='og:url']`).content = window.location.origin + url;
document.querySelector(`meta[property='og:description']`).content = description + ' - ' + title + ' for gitlink' + keyStatement + ' for git';
document.querySelector(`meta[property='og:image:alt']`).content = description + ' - ' + title + ' for gitlink' + keyStatement + ' for git';
document.querySelector('meta[name="Keywords"]').content= `${ keyWords } ${ keyWords ? '' : 'gitLink,GitLink,gitlink,trustie,trustieforge,forge,开源,确实开源,代码托管,Git,开源,内源,项目管理,版本控制,开源代码,代码分享,项目协作,开源项目托管,免费代码托管,Git代码托管,Git托管服务,确实让创建更美好,协同开发平台' }` ;
document.querySelector(`meta[name='description']`).content = description + keyStatement + ' for gitlink' + keyStatement + ' for git';
document.querySelector(`meta[name='go-import']`).content = window.location.host + url + ' git ' + window.location.origin + url;
// 服务端没有location
const host = window.location.host
const origin = window.location.origin
document.querySelector(`meta[property='og:title']`).content = title + ' | GitLink';
document.querySelector(`title`).textContent = title + ' | GitLink';
document.querySelector(`meta[property='og:url']`).content = origin + url;
document.querySelector(`meta[property='og:description']`).content = description + ' | GitLink';
document.querySelector(`meta[property='og:image:alt']`).content = description + ' | GitLink';
document.querySelector('meta[name="Keywords"]').content= `${ keyWords },${ keyWords ? '' : 'GitLink, git, 开源, 代码托管, 项目管理, 版本控制, 代码分享, 项目协作, 协同开发平台' }` ;
document.querySelector(`meta[name='description']`).content = description + ' | GitLink';
document.querySelector(`meta[name='go-import']`).content = host + url + ' git ' + origin + url;
document.querySelector(`meta[name='octolytics-dimension-user_login']`).content = owner;
if (projectId) {
document.querySelector(`meta[name='octolytics-dimension-repository_nwo']`).content = owner + '/' + projectId;
document.querySelector(`meta[name='octolytics-dimension-repository_network_root_nwo']`).content = owner + '/' + projectId;
}
document.querySelector(`meta[name='twitter:title']`).content = title + keyStatement + ' for gitlink' + keyStatement + ' for git';
document.querySelector(`meta[name='twitter:description']`).content = description + ' - ' + title + keyStatement + ' for gitlink' + keyStatement + ' for git';
document.querySelector(`link[rel='canonical']`).href = window.location.origin + url;
document.querySelector(`link[rel='canonical']`).href = origin + url;
}

View File

@ -14,9 +14,10 @@ function Index(props){
const [ content , setContent ] = useState(undefined);
const [ edit , setEdit ] = useState(false);
const [ fileList , setFileList ] = useState(undefined);
const [ editOpration , setEditOpration ] = useState(false);
// const [ editOpration , setEditOpration ] = useState(false);
const { owner , projectsId } = props.match.params;
const { isManager , isDeveloper , current_user, projectDetail } = props;
const editOpration = current_user && current_user.login && isManager
useEffect(()=>{
if(owner && projectsId && projectDetail){
@ -25,11 +26,11 @@ function Index(props){
}
},[owner,projectsId, projectDetail])
useEffect(()=>{
if( (current_user && current_user.login) && (isManager === true || isDeveloper === true)){
setEditOpration(true);
}
},[isManager , isDeveloper])
// useEffect(()=>{
// if( (current_user && current_user.login) && (isManager === true || isDeveloper === true)){
// setEditOpration(true);
// }
// },[isManager , isDeveloper])
function Init(){
const url = `/${owner}/${projectsId}/about.json`;
axios.get(url).then(result=>{

View File

@ -18,11 +18,12 @@ function Index(props){
<img src={logo} alt="" height="48px" />
</div>
<div className={"content"}>
<p>关联账号</p>
<p>关联第三方账号</p>
<div className="panels">
<p className="tips">您已完成头歌/github/gitee/qq/中国研究生赛事平台授权请补充Gitlink登录信息以完成第三方账号绑定</p>
<Menu selectedKeys={[key]} mode={`horizontal`} className="panelsMenu" onClick={chooseMenuFunc}>
<Menu.Item key="0">已有账号进行绑定</Menu.Item>
<Menu.Item key="1">账号注册并绑定</Menu.Item>
<Menu.Item key="0">已有Gitlink账号进行绑定</Menu.Item>
<Menu.Item key="1">Gitlink账号注册并绑定</Menu.Item>
</Menu>
{
key === "0" ?

View File

@ -93,7 +93,7 @@ function Bind(props){
<Input.Password autoComplete="new-password" addonBefore={<img src={lock} alt="" width="13px" />} className="psd" size="large" placeholder="请输入登录密码" />,
)}
</Form.Item>
<Button type="primary" disabled={bindFlag} size="large" style={{width:"100%",marginTop:"40px"}} onClick={bindloginFunc}>
<Button type="primary" disabled={bindFlag} size="large" style={{width:"100%",marginTop:"20px"}} onClick={bindloginFunc}>
绑定并登录
</Button>
</Form>

View File

@ -23,7 +23,17 @@
.panels{
background-color: #fff;
border-radius:6.6px;
padding:32px 42px;
padding:40px 32px;
.tips{
border:1px solid #FF6F00;
color: #FF6F00;
background: rgba(255,111,0,0.1);
padding:5px 15px;
border-radius: 3px;
line-height: 22px;
font-size: 13px;
margin-bottom: 15px!important;
}
.panelsMenu{
border-bottom: none!important;
margin-bottom: 36px!important;
@ -33,7 +43,7 @@
}
}
.bind_form{
padding-bottom: 48px;
padding-bottom: 10px;
.has-error .ant-input:focus,.has-error .ant-input, .has-error .ant-input:hover{
border-color: #ff4d4f!important;
}

View File

@ -310,7 +310,7 @@ function Bind(props){
<Input.Password autoComplete="new-password" addonBefore={<img src={lock} alt="" width="13px" />} className="psd" size="large" placeholder="请确认登录密码" />,
)}
</Form.Item>
<Button type="primary" disabled={bindFlag} size="large" style={{width:"100%",marginTop:"40px"}} onClick={bindRegFunc}>
<Button type="primary" disabled={bindFlag} size="large" style={{width:"100%",marginTop:"20px"}} onClick={bindRegFunc}>
注册并登录
</Button>
</Form>

View File

@ -4,8 +4,9 @@ import './branch.scss';
import SelectOverlay from './SelectOverlay';
import { findDOMNode } from 'react-dom';
import { turnbar } from 'educoder';
import { truncateCommitId } from "../common/util";
export default (({ projectsId , branch , owner , changeBranch , branchList , tagflag = true })=>{
export default (({ projectsId , branch , owner , changeBranch , branchList , tagflag = true , commitflag=false })=>{
const [ showValue , setShowValue ] = useState(branch);
const [ visible , setVisible ] = useState(false);
@ -34,10 +35,11 @@ export default (({ projectsId , branch , owner , changeBranch , branchList , tag
setShowValue(branch);
},[branch])
function ChangeB(params) {
function ChangeB(params,type) {
setVisible(false);
let en = turnbar(params);
changeBranch(en);
// let en = turnbar(params);
changeBranch(params);
setShowValue(type ? truncateCommitId(params) : params);
}
const menu = (
@ -46,6 +48,7 @@ export default (({ projectsId , branch , owner , changeBranch , branchList , tag
visible={visible}
changeBranch={ChangeB}
tagflag={tagflag}
commitflag={commitflag}
projectsId={projectsId}
owner={owner}
branchList={branchList}

View File

@ -1,13 +1,15 @@
import React , { useState , useEffect } from 'react';
import { Input , Spin , Menu } from 'antd';
import { getBranch , getTag } from '../GetData/getData';
import { getBranch , getTag , getCommit } from '../GetData/getData';
import { truncateCommitId } from "../common/util";
function SelectOverlay({ changeBranch , tagflag , projectsId , owner , visible }) {
function SelectOverlay({ changeBranch , tagflag , commitflag , projectsId , owner , visible }) {
const [ inputValue , setInputValue] = useState(undefined);
const [ nav , setNav ] = useState(0);
const [ isSpin , setIsSpin ] = useState(true);
const [ data , setData ] = useState(undefined);
const [ commitData , setCommitData ] = useState(undefined);
const [ datas , setDatas ] = useState(undefined);
const [ keys ,setKeys] = useState("branch");
@ -31,33 +33,68 @@ function SelectOverlay({ changeBranch , tagflag , projectsId , owner , visible }
setDatas(result);
setIsSpin(false);
}
function chooseitem(value){
changeBranch(value);
async function getCommits(id,owner,search){
let result = await getCommit(id,owner,search);
setCommitData(result.data && result.data.commits);
setIsSpin(false);
}
function chooseitem(value,type){
changeBranch(value,type);
}
function changeInputValue(e){
setInputValue(e.target.value);
let filter = e.target.value ? data && data.length>0 && data.filter(item=>item.name.indexOf(e.target.value)>-1) : data;
setDatas(filter);
if(keys ==="commit"){
//
getCommits(projectsId,owner,e.target.value);
}else{
let filter = e.target.value ? data && data.length>0 && data.filter(item=>item.name.indexOf(e.target.value)>-1) : data;
setDatas(filter);
}
}
function changeNav(e){
setKeys(e.key);
setIsSpin(true);
setInputValue(undefined);
if(e.key === "branch"){
getBranchs(projectsId,owner);
setNav(0);
}else{
}else if(e.key === "commit"){
getCommits(projectsId,owner);
setNav(2);
}
else{
getTags(projectsId,owner);
setNav(1);
}
}
const commitEle =(ele)=> (
ele && ele.length>0 ?
<ul className="commitUl">
{
ele.map((i,k)=>{
return(
<li onClick={()=>chooseitem(i.sha,"commit")}>
<span style={{color:"#111"}}>{truncateCommitId(i.sha)}</span>
<div className="commitMsg">
<div className="task-hide">{i.commit_message}</div> @ {i.time_from_now}
</div>
</li>
)
})
}
</ul>
:
<div style={{padding:"10px 20px"}}>暂无提交数据</div>
)
return(
<div className="overlayBranch">
<div className="padding15" style={{paddingBottom:"0px"}}>
<Input
prefix={<i className="iconfont icon-sousuo_icon1 font-14"></i>}
placeholder={`请输入分支${tagflag ? "或标签" :""}名称搜索`}
placeholder={keys === "commit" ?"搜索提交":`请输入分支${tagflag ? "或标签" :""}名称搜索`}
autocomplete="off" className="OptionsInput"
value={inputValue}
onChange={changeInputValue}
@ -66,23 +103,27 @@ function SelectOverlay({ changeBranch , tagflag , projectsId , owner , visible }
<Menu mode="horizontal" className="navUl" selectedKeys={[keys]} onClick={changeNav}>
<Menu.Item key={"branch"}>分支</Menu.Item>
{ tagflag && <Menu.Item key={"tag"}>标签</Menu.Item> }
{commitflag && <Menu.Item key={"commit"}>最近提交</Menu.Item> }
</Menu>
<Spin spinning={isSpin}>
<ul className="OptionsUl" id="ul-btn">
{
datas && datas.length>0 &&
datas.map((item,key)=>{
return(
<li key={key} onClick={()=>chooseitem(item.name)}><a className="task-hide ulALink">{item.name}</a></li>
)
})
}
{
datas && datas.length === 0 &&
<p className="listTips">暂无{inputValue}{nav === 0 ?"分支":"标签"}~</p>
}
</ul>
{
keys === "commit" ? commitEle(commitData)
:
<ul className="OptionsUl" id="ul-btn">
{
datas && datas.length>0 &&
datas.map((item,key)=>{
return(
<li key={key} onClick={()=>chooseitem(item.name)}><a className="task-hide ulALink">{item.name}</a></li>
)
})
}
{
datas && datas.length === 0 &&
<p className="listTips">暂无{inputValue}{nav === 0 ?"分支":"标签"}~</p>
}
</ul>
}
</Spin>
</div>
)

View File

@ -22,7 +22,7 @@
background: #fff;
max-height: 300px;
}
.OptionsUl{
.OptionsUl,.commitUl{
min-height: 50px;
max-height: 220px;
overflow-y: auto;
@ -46,6 +46,26 @@
line-height: 32px;
width: 100%;
}
.commitUl{
padding:0px 20px;
}
.commitUl li{
padding:15px 0px;
border-bottom: 1px dashed rgba(168,168,168,0.31);
&:last-child{
border:none;
}
cursor: pointer;
}
.commitUl .commitMsg{
margin-top: 8px;
display: flex;
div{
word-break: break-all;
flex: 1;
width: 0;
}
}
.branch-tagBox{
border:1px solid #D0D0D0;
border-radius: 3px;

View File

@ -28,7 +28,7 @@ function Releases({ owner, projectsId, releaseVersions, distribution }) {
:
<div className="mt8">
暂无发行版
{distribution && <Link className="color-blue ml20" to={{pathname:`/${owner}/${projectsId}/releases/new`,state:{stable:true}}}>创建新版本</Link>}
{distribution && <Link className="color-blue ml20" to={{pathname:`/${owner}/${projectsId}/releases/new`,state:{stable:true}}}>发布新版本</Link>}
</div>
}

146
src/forge/Dataset/Index.jsx Normal file
View File

@ -0,0 +1,146 @@
import React, { useState , useEffect } from 'react';
import { Button , Table , Tooltip } from 'antd';
import { getImageUrl } from 'educoder';
import paperImg from './image/paperImg.png';
import NewModal from './component/new';
import { Link } from 'react-router-dom';
import "./index.scss";
import axios from 'axios';
function Index(props){
const { project , match , current_user , projectDetail , getProject } =props;
const permission = current_user && current_user.login ? (projectDetail && projectDetail.permission):"";
const { owner ,projectsId } = match && match.params;
const [ dataSource , setDataSource ] = useState([]);
const [ empty , setEmpty ] = useState(project && (!project.has_dataset));
const [ total , setTotal ] = useState(0);
const [ page , setPage ] = useState(1);
const [ visible , setVisible ] = useState(false);
const [ detail , setDetail ] = useState({});
const pageSize = 10;
useEffect(()=>{
if(!empty){
Init();
}
},[empty,page])
function Init(){
const url = `/v1/${owner}/${projectsId}/dataset`;
axios.get(url,{
params:{
page,limit:pageSize
}
}).then((result) => {
if (result) {
setDetail(result.data);
setDataSource(result.data && result.data.attachments);
setTotal(result.data.attachment_total_count);
}
}).catch((error) => { })
}
const columns=[
{
title:"文件名称",
dataIndex:"title",
key:1,
ellipsis:true,
width:"25%"
},
{
title:"描述",
dataIndex:"description",
key:2,
ellipsis:true,
width:"30%",
render:(value)=>{
return <Tooltip title={value}>{value}</Tooltip>
}
},
{
title:"创建者",
dataIndex:"creator",
key:3,
width:"10%",
render:(value,item)=>{
return <Link to={`/${value.login}`}><img src={getImageUrl(`/${value.image_url}`)} alt="" style={{borderRadius:"50%"}} width="32px" height="32px" /></Link>
}
},
{
title:"上传时间",
dataIndex:"created_on",
key:4,
width:"15%"
},
{
title:"大小",
dataIndex:"filesize",
key:5,
},
{
title:"操作",
dataIndex:"url",
key:6,
width:"10%",
render:(value,item)=>{
return <a href={value} className="color-blue">下载</a>
}
},
]
//
function addFunc(){
setEmpty(false);
setVisible(false);
getProject && getProject();
detail && detail.id && Init();
}
return(
<div className={`dataset`}>
<NewModal visible={visible} detail={detail} owner={owner} repo={projectsId} onCancel={()=>setVisible(false)} onOk={addFunc}/>
<div className={`mnistData`}>
{empty ?
<span className="font-16">数据集</span>
:
<React.Fragment>
<div style={{width:0,flex:1}} className="mr20">
<div className="df mb15" style={{alignItems:"center"}}><p className="font-16 weight500 task-hide">{detail.title}</p>{ detail.license_name ? <span className="license ml20">许可证 : {detail.license_name}</span> :""}</div>
<p style={{wordBreak:"break-all"}}>{detail.description}</p>
</div>
{(permission && permission!== "") ? <div className="df">
<Button type="primary" ghost onClick={()=>{setVisible(true)}}><i className="iconfont icon-a-bianji12 color-blue mr5 font-12"></i>编辑</Button>
<Link className="ml20 operateButton" to={`/${owner}/${projectsId}/dataset/upload`}><i className="iconfont icon-a-shangchuan2x color-white mr5 font-13"></i>上传文件</Link>
</div>:""}
</React.Fragment>
}
</div>
{
empty?
<div className={"emtpyData"}>
<img src={require('./image/empty.png')} alt="" width="68px"/>
<span className="font-22 weight400 color-grey-3">暂无数据集</span>
{ (permission && permission!== "") ? <p className="mt25 font-15 color-grey-6 weight400">我们非常欢迎您创建并分享您的数据集以便与其他用户共同促进开源社区的发展</p> :"" }
{ (permission && permission!== "") ? <Button type="primary" className="mt15" onClick={()=>{setVisible(true)}}>创建数据集</Button> :""}
</div>
:
<Table
className={`datasetTable`}
columns={columns}
dataSource={dataSource}
pagination={{total,pageSize,current:page,hideOnSinglePage:true,onChange:(p)=>setPage(p)}}
/>
}
{
detail.paper_content ?
<div className="mt25">
<p className="mb15 df" style={{alignItems:'center'}}><img src={paperImg} width={24} className="mr10" alt="" /><span className="font-16">研究论文</span></p>
<div className={`mnistData`}>{ detail.paper_content }</div>
</div>
:""
}
</div>
)
}
export default Index;

View File

@ -0,0 +1,155 @@
import React, { useEffect, useState } from 'react';
import { Modal ,Form ,Input , AutoComplete , Button , Select } from 'antd';
import '../index.scss';
import axios from 'axios';
const { TextArea } = Input;
const Option = Select.Option;
function New(props){
const { form , visible , onCancel , onOk , owner , repo , detail } = props;
const [ licenseId , setLicenseId ] =useState(undefined);
const [ licensesList , setLicensesList] =useState([]);
const [ filterLicensesList , setFilterLicensesList ] =useState([]);
const { getFieldDecorator , setFieldsValue } = form;
useEffect(()=>{
if(detail && detail.id){
setFieldsValue({...detail,license_id:detail.license_name});
setLicenseId(detail.license_id);
}
},[detail])
useEffect(()=>{
getLicenses();
},[])
function getLicenses(){
const url = `/licenses.json`
axios.get(url).then((result) => {
if (result) {
setLicensesList(result.data.licenses);
setFilterLicensesList(result.data.licenses);
}
}).catch((error) => { })
}
function submit(){
form.validateFields((err, values) => {
if(!err){
const url = `/v1/${owner}/${repo}/dataset.json`;
if(detail && detail.id){
axios.put(url,{
...values,license_id:licenseId
}).then((result) => {
if (result) {
onOk && onOk();
}
}).catch((error) => { })
}else{
axios.post(url,{
...values,license_id:licenseId
}).then((result) => {
if (result) {
onOk && onOk();
}
}).catch((error) => { })
}
}
})
}
function ChangePlatform(value){
let _data = [];
if (licensesList.length>0) {
_data = licensesList.filter(item => item.name.toLowerCase().indexOf(value.toLowerCase()) > -1);
}
setFilterLicensesList(_data);
}
function checkId(rule, value, callback, list, title){
let filter = list.filter(item => item.name === value);
if(!value){
callback();
}
if (filter && filter.length > 0) {
callback();
} else {
callback('请在下拉选项中选择正确的' + title + "!");
}
callback();
}
return(
<Modal
title={`${detail && detail.id?"编辑":"新建"}数据集`}
width="648px"
visible={visible}
footer={null}
className="newBoxForm"
centered={true}
closable={true}
onCancel={onCancel}
>
<Form>
<Form.Item label="数据集名称">
{getFieldDecorator('title', {
rules: [
{
required:true,
message:"请输入数据集名称"
}
],
})(
<Input placeholder="请输入数据集名称" maxLength={100} />,
)}
</Form.Item>
<Form.Item label="开源许可证">
{getFieldDecorator('license_id', {
rules: [{
validator: (rule, value, callback) => checkId(rule, value, callback, licensesList, '开源许可证')
}],
})(
<AutoComplete
placeholder="请选择开源许可证"
onChange={ChangePlatform}
className="plateAutoComplete"
onSelect={(e,e1)=>{e1.props && setLicenseId(e1.props.id)}}
>
{
filterLicensesList && filterLicensesList.map((item) => (
<Option key={item.id} value={item.name} id={item.id}>
{item.name}
</Option>
))
}
</AutoComplete>
)}
</Form.Item>
<Form.Item label="数据集描述">
{getFieldDecorator('description', {
rules: [
{
required:true,
message:"请输入描述内容"
}
]})(
<TextArea maxLength={500} placeholder="请输入描述内容" autosize={{ minRows: 5, maxRows: 5 }} showCount/>,
)}
</Form.Item>
<Form.Item label="研究论文">
{getFieldDecorator('paper_content', {rules:[]})(
<TextArea maxLength={500} placeholder="请输入研究论文" autosize={{ minRows: 5, maxRows: 5 }} showCount/>,
)}
</Form.Item>
<div className="center">
<Button style={{width:"95px"}} onClick={onCancel}>取消</Button>
<Button type="primary" className="ml20" style={{width:"95px"}} onClick={submit}>确定</Button>
</div>
</Form>
</Modal>
)
}
export default Form.create({ name: 'New' })(New);;

View File

@ -0,0 +1,126 @@
import React, { useEffect, useState } from 'react';
import { Upload ,Form ,Input , Button , message } from 'antd';
import '../index.scss';
import { Link } from 'react-router-dom';
import axios from 'axios';
const { Dragger } = Upload;
const { TextArea } = Input;
function UploadModal(props){
const { match , form ,history } =props;
const { owner ,projectsId } = match && match.params;
const [fileList, setFileList] = useState([]);
const [ detail , setDetail ] = useState({});
const [ loading , setLoading ] = useState(false);
const { getFieldDecorator } = form;
useEffect(()=>{
Init();
},[])
function Init(){
const url = `/v1/${owner}/${projectsId}/dataset`;
axios.get(url).then((result) => {
if (result) {
setDetail(result.data);
}
}).catch((error) => { })
}
function submit(){
form.validateFields((err, values) => {
if(!err){
const url = `/attachments.json`;
const { description ,files } = values;
setLoading(true);
const formData = new FormData();
formData.append("container_type","ProjectDataset");
formData.append("description",description);
formData.append("container_id",detail && detail.id);
formData.append("file",files.file);
const config = {
headers: { "Content-Type": "multipart/form-data" }
};
axios.post(url,formData,config).then((result) => {
if (result) {
setLoading(false);
history.push(`/${owner}/${projectsId}/dataset`);
}
}).catch((error) => { })
}
})
}
function isFolder(fileName) {
//
const lastDotIndex = fileName.lastIndexOf('.');
//
const nameAfterLastSlash = fileName.substring(fileName.lastIndexOf('/') + 1);
//
return lastDotIndex !== -1 && nameAfterLastSlash.includes('.');
}
const upload= {
name: 'file',
multiple: false,
maxCount:1,
type:"file",
beforeUpload: file => {
const isDirectory = file.type;
const name = file.name;
if (!isDirectory && !isFolder(name)) {
message.error('不能上传文件夹!');
}else{
setFileList([file]);
}
return false;
},
onRemove: file => {
const index = fileList.indexOf(file);
const newFileList = fileList.slice();
newFileList.splice(index, 1);
setFileList(newFileList);
},
fileList,
};
return(
<div className={`dataset`}>
<p className="font-18 fileTitle">上传数据集文件</p>
<Form className="newBoxForm">
<Form.Item label="文件描述">
{getFieldDecorator('description', {
rules: [
{
required:true,
message:"请输入文件描述"
}
],
})(
<TextArea autosize={{minRows:5,maxRows:5}} placeholder="描述字数不超过255个字符" maxLength={255} />,
)}
</Form.Item>
<Form.Item label="数据上传">
{getFieldDecorator('files', {
rules: [
{
required:true,
message:"请上传文件"
}
],
})(
<Dragger {...upload} className={"drag"}>
<p className="ant-upload-hint font-15">点击添加文件或直接拖拽文件到此处</p>
</Dragger>,
)}
</Form.Item>
<div>
<Link className="btn-83" to={`/${owner}/${projectsId}/dataset`}>取消</Link>
<Button loading={loading} type="primary" className="ml5" style={{width:"95px"}} onClick={submit}>确定</Button>
</div>
</Form>
</div>
)
}
export default Form.create({ name: 'UploadModal' })(UploadModal);;

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 468 B

View File

@ -0,0 +1,73 @@
.dataset{
margin:0px auto;
width: 1200px;
padding:25px 0px;
.mnistData{
background: #FAFCFF;
border-radius: 3px;
border: 1px solid rgba(42,97,255,0.23);
padding:20px;
display: flex;
align-items: center;
justify-content: space-between;
p{
font-weight: 500;
font-size: 14px;
color: #202D40;
line-height: 22px;
&:first-child{
max-width: 600px;
}
}
.license{
height: 40px;
line-height: 40px;
background: #ECEFFE;
border-radius: 6px 6px 6px 6px;
padding:0px 12px;
font-weight: bold;
font-size: 15px;
color: #202D40;
display: inline-block;
}
}
.datasetTable{
.ant-table-thead > tr > th{
background-color: #fff;
}
}
.emtpyData{
background: #FAFCFF;
border-radius: 4px 4px 0px 0px;
min-height: 356px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin-top: 25px;
}
}
.newBoxForm{
.ant-row.ant-form-item{
margin-bottom: 20px!important;
position: relative;
}
.ant-col.ant-form-item-label{
line-height: unset;
font-weight: 400;
font-size: 15px;
color: #4C5B76;
}
.ant-form-explain{
position: absolute;
}
}
.fileTitle{
line-height: 25px;
padding-bottom: 22px;
margin-bottom: 15px!important;
border-bottom:1px solid rgba(167,178,194,0.44);
}
.drag{
height: 110px!important;
}

View File

@ -8,6 +8,10 @@ export const getBranch = async (id,owner)=>{
export const getTag = async (id,owner)=>{
return (await axios.get(`/${owner}/${id}/tags.json`,{params:{only_name:true}})).data.tags;
}
//
export const getCommit = async(id,owner,search)=>{
return await axios.get(`/v1/${owner}/${id}/commits/recent`,{params:{keyword:search}})
}
// hooks-web
export const getHooks = async (id,params)=>{
return (await axios.get(`/projects/${id}/hooks.json`,{params})).data;

View File

@ -16,8 +16,8 @@ function NewPanel(props,ref){
const { form: { getFieldDecorator, validateFields , setFieldsValue } } = props;
useState(()=>{
title && setTimeout(()=>{
setFieldsValue({subject:title});
setTimeout(()=>{
title && setFieldsValue({subject:title});
desc && onContentChange(desc);
},100)
files && setAttachments(files);

View File

@ -65,7 +65,7 @@ function CoderDepot(props){
let search = props.history.location.search
const { platform , mirror_status , bannerList , projectDetail } = props ;
//distribution
const distribution = projectDetail && projectDetail.type ===0 && ( projectDetail.permission && projectDetail.permission !== "Reporter");
const distribution = projectDetail && ( projectDetail.permission && projectDetail.permission !== "Reporter");
useEffect(()=>{
if(bannerList && bannerList.length>0){
@ -109,9 +109,9 @@ function CoderDepot(props){
const { author, name, description, default_branch} = projectDetail;
if(branchName && branchName !== default_branch){
// /
document.title = `${author.name}/${name}-${branchName}-for gitlink;for git`;
document.title = `${author.name}/${name}-${branchName} | GitLink`;
}else{
document.title = `${author.name}/${name}${description?': '+description:''}-for gitlink;for git` ;
document.title = `${author.name}/${name}${description?': '+description:''} | GitLink` ;
}
}
}, [treeValuePath, projectDetail, branchName])

View File

@ -99,7 +99,14 @@ const TrendsIndex = Loadable({
loader: () => import('../Activity/Activity'),
loading: Loading,
})
const DataSetIndex = Loadable({
loader: () => import('../Dataset/Index'),
loading: Loading,
})
const DataSetUpload = Loadable({
loader: () => import('../Dataset/component/uploadModal'),
loading: Loading,
})
const DevAbout = Loadable({
loader: () => import('../About/Index'),
loading: Loading,
@ -153,6 +160,8 @@ function checkPathname(projectsId, owner, pathname) {
name = "activity"
} else if (url.indexOf("/settings") > -1) {
name = "settings"
} else if (url.indexOf("/dataset") > -1) {
name = "dataset"
} else if (url.indexOf(`/devops`) > -1) {
name = "devops"
} else if (url.indexOf(`/source`) > -1) {
@ -555,6 +564,7 @@ class Detail extends Component {
const common = {
getDetail: this.getDetail,
getBanner: this.getBanner,
getProject:this.getProject,
changeOpenDevops: this.changeOpenDevops,
defaultBranch
}
@ -701,6 +711,18 @@ class Detail extends Component {
() => (<DevAbout {...this.props} {...this.state} {...common} />)
}
></Route>
{/* 数据集 */}
<Route path="/:owner/:projectsId/dataset/upload"
render={
(props) => (<DataSetUpload {...this.props} {...props} {...this.state} {...common} />)
}
></Route>
<Route path="/:owner/:projectsId/dataset"
render={
(props) => (<DataSetIndex {...this.props} {...props} {...this.state} {...common}/>)
}
></Route>
{/* wiki新增文件 */}
<Route path="/:owner/:projectsId/wiki/add"
render={

Binary file not shown.

After

Width:  |  Height:  |  Size: 898 B

View File

@ -228,7 +228,7 @@
li{
text-align: center;
padding:0px;
margin-right: 35px;
margin-right: 30px;
display: flex;
& > a{
position: relative;

View File

@ -3,6 +3,7 @@ import { Skeleton , Tooltip} from 'antd';
import { Link } from 'react-router-dom';
import { numFormat } from 'educoder';
import QuitBox from './quit';
import datasetImg from '../img/dataset.jpg';
import axios from 'axios';
function DetailBanner({ history,list , owner , projectsId ,showNotification , url , pathname , state , urlFlag , projectDetail , platform ,open_devops ,current_user, showNpsModal }){
@ -104,6 +105,16 @@ function DetailBanner({ history,list , owner , projectsId ,showNotification , ur
</li>
:""
}
{
item.menu_name === "dataset" ?
<li className={pathname==="dataset" ? "active" : ""}>
<Link className='newTab' to={{ pathname: `/${owner}/${projectsId}/dataset`, state:{...state,open_devops} }}>
<img src={datasetImg} height={15} alt="" className="mr5 mt5"/>数据集
{projectDetail && projectDetail.ops_count ? <span>{projectDetail.ops_count}</span> : ""}
</Link>
</li>
:""
}
{/* {
item.menu_name === "resources" &&
<li className={pathname==="source" ? "active" : ""}>
@ -168,6 +179,7 @@ function DetailBanner({ history,list , owner , projectsId ,showNotification , ur
)
})
}
</ul>
:
<Skeleton paragraph={false} active={true}/>

View File

@ -71,7 +71,7 @@ function Index(props) {
dataIndex: "commit",
key: 3,
ellipsis: true,
width: "240px",
width: "260px",
render: (txt, item) => {
return (
<div className='treeinfo'>
@ -97,7 +97,7 @@ function Index(props) {
dataIndex: "commit_id",
key: 4,
ellipsis: true,
width: "250px",
width: "225px",
render: (txt, item) => {
return <div className="treecopy">
<div className='treeCopyBox'>
@ -110,54 +110,57 @@ function Index(props) {
</div>
</div>
}
},
{
title: "分支类型",
dataIndex: "default_branch",
key: 5,
width: "150px",
render: (txt, item) => {
return isManager ? txt === item.name ? <a className='btn-83' style={{width: '93px'}}>默认分支</a> : <Select value={1} onChange={(value)=>{updateDefaultBranch(value, item)}}>
<Select.Option value={0}>默认分支</Select.Option>
<Select.Option value={1}>普通分支</Select.Option>
</Select> : txt === item.name ? '默认分支' : '普通分支'
}
},
{
title: "操作",
dataIndex: "name",
key: 6,
align: "center",
width: "300px",
className: "branchActionColumn",
render: (txt, item,key) => {
return (
// item.is_deleted ?
// <div style={{textAlign:"right"}}>
// <Popover content={<span> Commits<span className="color-blue ml3 mr3">{truncateCommitId(item.commit_id)}</span></span>}>
// <Button onClick={()=>restoreBranch(item.branch_id,item.name)}></Button>
// </Popover>
// </div>:
<div className="treeabout">
{
(isManager || isDeveloper) && (projectDetail && projectDetail.type!==2) &&
<Tooltip title={`${txt}为源分支创建一个合并请求`}><Link to={
// forkpr
projectDetail && projectDetail.fork_info?
`/${projectDetail.fork_info.fork_project_user_login}/${projectDetail.fork_info.fork_project_identifier}/compare/${turnbar(txt || defaultBranch)}...${owner + ':' + turnbar(txt)}`
:`/${owner}/${projectsId}/compare/${turnbar(defaultBranch)}...${turnbar(txt)}`
} className="btn-83">+ 合并请求</Link></Tooltip>
}
<Dropdown overlay={menu(item.zip_url,item.tar_url)} trigger={['click']} placement="bottomRight">
<a className="btn-83" style={{width: '76px'}}>下载<i className="iconfont icon-sanjiaoxing-down font-14"></i></a>
</Dropdown>
{isManager && (txt === item.default_branch ? <Tooltip title={`该分支为默认分支,不支持删除操作`}><Button style={{width: '76px'}} disabled>删除</Button></Tooltip> : <Button className='f6_bor_but' style={{width: '76px'}} onClick={()=>{setVisible(item)}}>删除</Button>)}
</div>
)
}
}
];
const mergeCol = {
title: "分支类型",
dataIndex: "default_branch",
key: 5,
width:"150px",
render: (txt, item) => {
return isManager ? txt === item.name ? <a className='btn-83' style={{width: '93px'}}>默认分支</a> : <Select value={1} onChange={(value)=>{updateDefaultBranch(value, item)}}>
<Select.Option value={0}>默认分支</Select.Option>
<Select.Option value={1}>普通分支</Select.Option>
</Select> : txt === item.name ? '默认分支' : '普通分支'
}
}
const operCol ={
title: "操作",
dataIndex: "name",
key: 6,
align: "center",
width: "300px",
className: "branchActionColumn",
render: (txt, item,key) => {
return (
item.is_deleted ?
isManager ? <div style={{textAlign:"right"}}>
<Popover content={<span>将基于 Commits<span className="color-blue ml3 mr3">{truncateCommitId(item.commit_id)}</span>恢复分支</span>}>
<Button onClick={()=>restoreBranch(item.branch_id,item.name)}>恢复分支</Button>
</Popover>
</div>:""
:
<div className="treeabout">
{
(isManager || isDeveloper) && (projectDetail && projectDetail.type!==2) &&
<Tooltip title={`${txt}为源分支创建一个合并请求`}><Link to={
// forkpr
projectDetail && projectDetail.fork_info?
`/${projectDetail.fork_info.fork_project_user_login}/${projectDetail.fork_info.fork_project_identifier}/compare/${turnbar(txt || defaultBranch)}...${owner + ':' + turnbar(txt)}`
:`/${owner}/${projectsId}/compare/${turnbar(defaultBranch)}...${turnbar(txt)}`
} className="btn-83">+ 合并请求</Link></Tooltip>
}
<Dropdown overlay={menu(item.zip_url,item.tar_url)} trigger={['click']} placement="bottomRight">
<a className="btn-83" style={{width: '76px'}}>下载<i className="iconfont icon-sanjiaoxing-down font-14"></i></a>
</Dropdown>
{isManager && (txt === item.default_branch ? <Tooltip title={`该分支为默认分支,不支持删除操作`}><Button style={{width: '76px'}} disabled>删除</Button></Tooltip> : <Button className='f6_bor_but' style={{width: '76px'}} onClick={()=>{setVisible(item)}}>删除</Button>)}
</div>
)
}
}
function restoreBranch(id,name){
const url = `/v1/${owner}/${projectsId}/branches/restore.json`;
Axios.post(url,{
@ -216,9 +219,10 @@ function Index(props) {
}).then(res=>{
if(res && res.data){
//
setIsSpin(true);
getDetail();
setReload(Math.random());
setAddBranch(false);
setTimeout(()=>{getList()},1500);
message.success('新建分支成功');
}
})
@ -292,10 +296,10 @@ function Index(props) {
<div>
<Input.Search placeholder='请输入分支名称' style={{width: '354px'}} className='searchBranch' onSearch={(value)=>{setCurrent(1);setName(value)}} allowClear/>
{/* <Radio.Group value={chooseStatus} buttonStyle="solid" onChange={(v)=>{setChooseStatus(v.target.value)}} style={{marginLeft:"25px"}}>
<Radio.Group value={chooseStatus} buttonStyle="solid" onChange={(v)=>{setChooseStatus(v.target.value)}} style={{marginLeft:"25px"}}>
<Radio.Button value="all">代码库分支</Radio.Button>
<Radio.Button value="deleted">已删除分支</Radio.Button>
</Radio.Group> */}
</Radio.Group>
</div>
<div>
{isManager && <Link to={`/${owner}/${projectsId}/settings/branches`} className="btn-83" style={{width: '102px'}}><i className="iconfont icon-xuanzhongjibenshezhiicon font-14 mr5"></i>分支设置</Link>}
@ -306,7 +310,7 @@ function Index(props) {
rowKey={'name'}
className='branchListTable'
dataSource={list}
columns={columns}
columns={chooseStatus==="deleted"?[...columns,operCol]:[...columns,mergeCol,operCol]}
pagination={{current: current,
pageSize: pageSize,
total: total,
@ -396,7 +400,7 @@ function Index(props) {
>
<div className="contents mb30" style={{justifyContent: 'flex-start'}}>
<i className='iconfont icon-shanchu_tc_icon1 font-36 mr18' style={{color: '#ca0002'}}></i>
<p style={{color: '#333'}}>删除分支本操作将会关闭已开启的Pull Request</p>
<p style={{color: '#333'}}>删除分支本操作将会关闭已开启的合并请求(PR)</p>
</div>
</Modals>
<Modal

View File

@ -30,6 +30,7 @@ export default Form.create()(
const [tagList, setTagList] = useState(undefined);
const [desc, setDesc] = useState(null);
const [branch, setBranch ] = useState(null);
const [saveBranch, setSaveBranch ] = useState(null);
const [fileList, setFileList] = useState(undefined);
const [attachment, setAttachment] = useState(undefined);
const [options , setOptions] = useState(undefined);
@ -104,7 +105,7 @@ export default Form.create()(
...value,
body: desc,
attachment_ids: fileList,
target_commitish:branch
target_commitish:saveBranch
})
.then(result => {
if (result) {
@ -118,7 +119,7 @@ export default Form.create()(
...value,
body: desc,
attachment_ids: fileList,
target_commitish:branch
target_commitish:saveBranch
})
.then(result => {
if (result) {
@ -138,7 +139,8 @@ export default Form.create()(
}
function changeBranch(params) {
setBranch(params);
// setBranch(params);
setSaveBranch(params)
}
return (
@ -177,6 +179,7 @@ export default Form.create()(
owner={owner}
history={history}
tagflag={false}
commitflag={true}
branchList={projectDetail && projectDetail.branches && projectDetail.branches.list}
></SelectBranch>
<p className="font-12 color-grey-6 weight400">选择一个已经存在的标签或者在发布时新建一个标签</p>
@ -214,7 +217,7 @@ export default Form.create()(
icon={
<i className="iconfont icon-shangchuanicon dragIcon" />
}
size={100}
size={300}
showNotification={showNotification}
/>
{versionId && attachment && attachment.length > 0 ? (
@ -264,7 +267,7 @@ export default Form.create()(
<div className="infosTip">
<p className="font-16 mb14 weight">附件大小说明</p>
<p>
单个附件不能超过 100M每个仓库总附件不可超过
单个附件不能超过 300M每个仓库总附件不可超过
1G附件总容量统计包括仓库附件和发行版附件
</p>
</div>

View File

@ -82,8 +82,8 @@ function version(props) {
<div className="versionName">
<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 &&
{data && data.user_admin_permission && <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 &&
<Popconfirm
placement="bottom"
title={'您确定要删除当前发行版吗?'}
@ -131,7 +131,7 @@ function version(props) {
return (
<React.Fragment>
{
data && data.user_permission && type !== 2 &&
data && data.user_permission &&
<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_permission && type !== 2}
operation={data && data.user_permission}
addFunc={addFunc}
/>
)

View File

@ -67,7 +67,7 @@ function Files({ data,history,owner,projectsId , parentsSha }){
)
function subStrContent(content){
return content ? content.slice(1) :"";
return content ? " " + content.slice(1) :"";
}
return(

View File

@ -16,6 +16,7 @@ const menu = [
{name:"疑修 (Issue)",index:"issues"},
{name:"合并请求 (PR)",index:"pulls"},
{name:"引擎 (Engine)",index:"devops"},
{name:"数据集",index:"dataset"},
// {name:"资源库",index:"resources"},
{name:"里程碑",index:"versions"},
{name:"维基 (Wiki)",index:"wiki"},
@ -163,13 +164,16 @@ class Setting extends Component {
update=(values)=>{
const { projectsId , owner } = this.props.match.params;
const { private_check , project_units } = this.state;
const {project_language_id, project_category_id, pr_view_admin} = values;
const url = `/${owner}/${projectsId}.json`;
axios.put(url, {
axios.patch(url, {
name: values.project_name,
description: values.project_description,
private: private_check,
identifier:values.project_identifier,
...values,
project_language_id,
project_category_id,
pr_view_admin
}).then((result) => {
if (result) {
this.props.showNotification(`仓库信息修改成功!`);
@ -394,7 +398,7 @@ class Setting extends Component {
{getFieldDecorator("project_units", {
rules: [],
})(
<Checkbox.Group>
<Checkbox.Group className="unitStyle">
{
menu.map((item,key)=>{
return(

View File

@ -348,4 +348,11 @@
border-top: 1px solid rgba(90, 117, 193, 0.23)
}
}
}
.unitStyle{
.ant-checkbox-wrapper + .ant-checkbox-wrapper{
margin-left: 0px;
margin-right: 8px;
margin-bottom: 10px;
}
}

View File

@ -77,7 +77,7 @@ export default Form.create()(
callback("只能以数字或字母开头仅支持横杠、下划线、点三种符号不允许符号连续排列长度4-50个字符");
}
if(value && (value.length<4 || value.length>50)){
callback("只能以数字或字母开头仅支持横杠、下划线、点三种符号不允许符号连续排列长度4-50个字符12");
callback("只能以数字或字母开头仅支持横杠、下划线、点三种符号不允许符号连续排列长度4-50个字符");
}
callback();
}

View File

@ -141,9 +141,9 @@ export default Form.create()(
"name",
[
{ required: true, message: "请输入组织账号" },
{
validator:checkname
}
// {
// validator:checkname
// }
],
<Input placeholder="请输入组织账号" disabled/>,true
)}
@ -170,7 +170,7 @@ export default Form.create()(
"官方网站:",
"website",
[],
<Input placeholder="请输入官方网站" />
<Input placeholder="请输入官方网站" maxLength={100}/>
)}
{helper(
'所在地区:',

View File

@ -18,7 +18,7 @@ export default Form.create()(({ form, history, showNotification, projectDetail,
let wikiName = ''
let key = '';
if (!pathname.endsWith('/wiki/add')) {
wikiName = decodeURI(pathname.split('/')[4]);
wikiName = pathname.split('/')[4];
key = pathname.split('/')[5];
}else{
key = search.split('=').pop();
@ -116,12 +116,6 @@ export default Form.create()(({ form, history, showNotification, projectDetail,
};
validateFields((err, values) => {
if (!err) {
let regEn = /[\\/:*?"<>|[\]-]/g;
if (regEn.test(values.title)) {
message.error('文件名不能有特殊字符: \\ / : * ? \" < > | [ \] -');
setOperateFlag(false);
return;
}
const {md_content, title} = values;
if (wikiName && search!== "?copy") {
updateWiki({
@ -130,7 +124,7 @@ export default Form.create()(({ form, history, showNotification, projectDetail,
projectId: project.id,
pageName: wikiName,
title,
commit_message: '',
message: '',
content_base64: Base64.encode(md_content)
}).then(res => dealRes(res, title));
} else {
@ -223,7 +217,7 @@ export default Form.create()(({ form, history, showNotification, projectDetail,
projectId: project.id,
pageName: '_Sidebar',
title: '_Sidebar',
commit_message: '',
message: '',
content_base64: Base64.encode(content)
}).then(res => {
if (res && res.message === "200") {
@ -333,7 +327,7 @@ export default Form.create()(({ form, history, showNotification, projectDetail,
"title",
[
{ required: true, message: "请输入标题" },
// { pattern: /[^`\[\]\/:*?''<>|%-+_]/g, message: '' }
{ pattern: /^(?!-).*$/, message: '不能以-开头' }
],
<Input
placeholder={"请输入标题"}
@ -360,7 +354,7 @@ export default Form.create()(({ form, history, showNotification, projectDetail,
})(<Input style={{ display: 'none' }} />)}
</Form.Item>
<Checkbox checked={modal} onChange={changeModal}>添加模版</Checkbox>
<Checkbox checked={modal} onChange={changeModal} className='mt10'>添加模版</Checkbox>
<Radio.Group onChange={changeModalType} value={modalType}>
<Radio value='周报'>周报</Radio>

View File

@ -5,7 +5,7 @@ import CopyTool from '../Component/CopyTool';
import Welcome from './Welcome';
import RenderHtml from "../../components/render-html";
import { wikiPages, getWiki, parseSidebar, updateWiki, treeToMd } from './api';
import { wikiPages, getWiki, parseSidebar, updateWiki, treeToMd, deleteSameKey } from './api';
import { httpUrl } from './fetch';
import './Index.scss';
import './components/ModalFun/index.scss';
@ -38,18 +38,9 @@ export default (props) => {
const [addMenuError, setAddMenuError] = useState(undefined);
const [sidebar, setSidebar] = useState(undefined);
const [defaultSelectedKeys, setDefaultSelectedKeys] = useState(undefined);
const [masterProtected, setMasterProtected] = useState(false);
useEffect(()=>{
window.scrollTo(0,0);
// master
if(projectsId && owner){
axios.get(`/v1/${owner}/${projectsId}/branches.json`, {keyword: 'master'}).then(res=>{
if(res && res.data && res.data.total_count === 1){
setMasterProtected(res.data.branches[0].protected);
}
})
}
}, [])
useEffect(()=>{
@ -84,9 +75,9 @@ export default (props) => {
setSidebar(sidebar)
setFileList(menuList);
let firstWikiNode = undefined;
const arr = pathname.split('/');
if(!pathname.endsWith('/wiki')){
firstWikiNode = findNodeByName(menuList, arr[arr.length-2]);
const {wikiName} = match.params;
firstWikiNode = findNodeByName(menuList, wikiName);
} else{
// [[]]
firstWikiNode = findFirstWiki(menuList);
@ -95,14 +86,14 @@ export default (props) => {
!firstWikiNode && (firstWikiNode = findFirstWiki(menuList));
//
setDefaultSelectedKeys([firstWikiNode.key+""]);
const title = firstWikiNode.title.trim().substring(2,firstWikiNode.title.trim().length-2);
const title = firstWikiNode.titleStr;
const firstWiki = res.data.filter(item => { return title === item.name })[0];
setCheckItem({
...firstWiki,
key: firstWikiNode.key
})
if(window.location.pathname.indexOf('/wiki') === -1) return
history.push(`/${owner}/${projectsId}/wiki/${title}/${firstWikiNode.key}`);
history.push(encodeURI(`/${owner}/${projectsId}/wiki/${firstWiki.sub_url}/${firstWikiNode.key}`));
} else {
setFileArr([]);
setFileArrInit([]);
@ -110,12 +101,12 @@ export default (props) => {
});
}, [project, reload])
// wiki
// wiki,sub_url:
useEffect(() => {
project && checkItem.name && getWiki({
project && checkItem.sub_url && getWiki({
owner: owner,
repo: projectsId,
pageName: checkItem.name,
pageName: checkItem.sub_url,
projectId: project.id
}).then(res => {
if (res && res.message === "200") {
@ -190,11 +181,6 @@ export default (props) => {
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}`)){
@ -215,7 +201,7 @@ export default (props) => {
projectId: project.id,
pageName: '_Sidebar',
title: '_Sidebar',
commit_message: '',
message: '',
content_base64: Base64.encode(treeToMd(sidebarList))
}).then(res => {
if (res && res.message === "200") {
@ -233,11 +219,11 @@ export default (props) => {
}
function goEdit(params) {
history.push(`/${owner}/${projectsId}/wiki/${encodeURI(checkItem.name)}/${checkItem.key}/edit${params}`);
history.push(encodeURI(`/${owner}/${projectsId}/wiki/${checkItem.sub_url}/${checkItem.key}/edit${params}`));
}
function preview() {
window.open(`/${owner}/${projectsId}/wiki/preview/${encodeURI(project.name)}/${project.id}`);
window.open(encodeURI(`/${owner}/${projectsId}/wiki/preview/${project.name}/${project.id}`));
}
// MarkdownHtmlPdf
@ -262,11 +248,18 @@ export default (props) => {
function changeitem(item){
const {name, key} = item;
const wiki = fileArrInit.filter(item => { return item.name == name })[0];
if(!wiki){
// wikisidebarfindSameKeyByDel
const menuListByDel = deleteSameKey(fileList, key);
updateWikiFun(menuListByDel);
message.success("不存在的wiki")
return
}
setCheckItem({
...wiki,
key
})
history.push(`/${owner}/${projectsId}/wiki/${name}/${key}`);
history.push(encodeURI(`/${owner}/${projectsId}/wiki/${name}/${key}`));
}
return (
@ -302,7 +295,7 @@ export default (props) => {
<div className="wiki-nav">
{/* 读sidebar转换成目录树 */}
<Sidebar fileList = {fileList} changeitem={changeitem} projectsId={projectsId} owner={owner} project={project} setReload={setReload} sidebar={sidebar} history={history} permission={permission} defaultSelectedKeys={defaultSelectedKeys} fileArrInit={fileArrInit} masterProtected={masterProtected}/>
<Sidebar fileList = {fileList} changeitem={changeitem} projectsId={projectsId} owner={owner} project={project} setReload={setReload} sidebar={sidebar} history={history} permission={permission} defaultSelectedKeys={defaultSelectedKeys} fileArrInit={fileArrInit}/>
</div>
</div>
@ -332,7 +325,7 @@ export default (props) => {
{permission && <Button type="primary" onClick={()=>{goEdit("")}}>编辑</Button>}
</div>
{itemDetail&&itemDetail.md_content&&<RenderHtml className="wiki-content-detail editor-content-panel imageLayerParent" value={ itemDetail && Base64.decode(itemDetail.md_content) } url={history.location}/>}
{itemDetail && itemDetail.md_content && <RenderHtml className="wiki-content-detail editor-content-panel imageLayerParent" value={Base64.decode(itemDetail.md_content) } url={history.location}/>}
</div>
</div>
</div >

View File

@ -102,13 +102,13 @@ body {
font-family: "PingFangSC-Regular";
}
.has-error .ant-form-explain,
.has-error .ant-form-split {
position: absolute;
}
.wiki-md .ant-form-explain{
bottom: -6px;
}
// .has-error .ant-form-explain,
// .has-error .ant-form-split {
// position: absolute;
// }
// .wiki-md .ant-form-explain{
// bottom: -6px;
// }
.wiki-nav {
max-height: 60vh;
.wiki-search {

View File

@ -60,10 +60,10 @@ export default (props) => {
useEffect(() => {
projectsId && checkItem.name && getWiki({
projectsId && checkItem.sub_url && getWiki({
owner: owner,
repo: projectsId,
pageName: checkItem.name,
pageName: checkItem.sub_url,
projectId: projectId,
}).then(res => {
if (res && res.message === "200") {
@ -111,12 +111,13 @@ export default (props) => {
}
function selectTree(keys,event){
let {title, key} = event.node.props.dataRef;
title = title.trim()
const isFile = title.startsWith('[[') && title.endsWith(']]');
let {isFile, key, titleStr} = event.node.props.dataRef;
if(isFile){
title = title.substring(2,title.length-2);
setCheckItem({name: title, key});
const wiki = fileArr.filter(item => { return item.name == titleStr })[0];
setCheckItem({
...wiki,
key
})
}
}

View File

@ -72,7 +72,7 @@ export async function parseSidebar(wikiList, info){
})
}
ReInfo={
menuList: markdownToTree(list.length ? `${sidebarCont}${wikiSidebar}` : sidebarCont),
menuList: markdownToTree(list.length ? `${sidebarCont}${wikiSidebar}` : sidebarCont, wikiList),
sidebar: sidebarCont
}
}
@ -96,14 +96,14 @@ export async function parseSidebar(wikiList, info){
content_base64: Base64.encode(wikiSidebarStr)
})
ReInfo={
menuList: markdownToTree(wikiSidebarStr),
menuList: markdownToTree(wikiSidebarStr, wikiList),
sidebar: wikiSidebarStr
}
}
return ReInfo;
}
export function markdownToTree(markdownText){
export function markdownToTree(markdownText, wikiList){
const lines = markdownText.split('\n');
let key = -1;
// stack: [{一级目录}{二级目录}]
@ -113,16 +113,23 @@ export function markdownToTree(markdownText){
children: [],
key: key++
}
const wikiNameAndSubUrl = {}
wikiList && wikiList.map(item=>{
return wikiNameAndSubUrl[item.name] = item.sub_url
})
lines.map((item, index) =>{
const title = item.trim();
const isFile = title.startsWith('[[') && title.endsWith(']]');
const titleStr = isFile ? title.substring(2, title.length - 2) : title.substring(2, title.length)
const node = {
// 带空格+[[]]或者- 标识
title: item,
children: [],
key: undefined,
// 纯内容
titleStr: isFile ? title.substring(2, title.length - 2) : title.substring(2, title.length),
titleStr,
// 编码后的title,获取wiki内容用title_sub
title_sub: wikiNameAndSubUrl[titleStr],
// 是否是wiki文件
isFile: isFile
}
@ -253,4 +260,17 @@ export function findFirstWiki(list){
}
}
}
}
// 递归过滤相同key值的节点
export function deleteSameKey(list, key){
return list.filter(item => {
if (item.key === key) {
return false
}
if(item.children && item.children.length > 0){
item.children = deleteSameKey(item.children , key)
}
return true
})
}

View File

@ -12,7 +12,7 @@ const {DirectoryTree, TreeNode} = Tree;
// wiki
export default function Sidebar(props) {
const {fileList, changeitem, owner, projectsId, project, setReload, sidebar, history, permission, defaultSelectedKeys, fileArrInit, masterProtected} = props;
const {fileList, changeitem, owner, projectsId, project, setReload, sidebar, history, permission, defaultSelectedKeys, fileArrInit} = props;
const {location:{pathname}} = history;
const [initMenuList, setInitMenuList] = useState(undefined);
const [menuList, setMenuList] = useState(undefined);
@ -66,10 +66,7 @@ export default function Sidebar(props) {
</a>
</Menu.Item>
<Menu.Item>
{
masterProtected && item.children.length ? <a onClick={() => { message.error("由于master分支设置了分支保护不允许删除wiki") }}>删除</a> :
<a onClick={(e) => { deleteFileModal(e, title, isFile, item) }}>删除</a>
}
<a onClick={(e) => { deleteFileModal(e, title, isFile, item) }}>删除</a>
</Menu.Item>
</Menu>
};
@ -82,10 +79,7 @@ export default function Sidebar(props) {
</a>
</Menu.Item>
<Menu.Item>
{
masterProtected ? <a onClick={() => { message.error("由于master分支设置了分支保护不允许删除wiki") }}>删除</a> :
<a onClick={(e) => { deleteFileModal(e, title, isFile, item) }}>删除</a>
}
<a onClick={(e) => { deleteFileModal(e, title, isFile, item) }}>删除</a>
</Menu.Item>
</Menu>
};
@ -163,7 +157,7 @@ export default function Sidebar(props) {
const {node, children, key} = item;
if(isFile){
//
deleteWiki({owner: owner, repo: projectsId, projectId: project.id, pageName: title}).then(res => {
deleteWiki({owner: owner, repo: projectsId, projectId: project.id, pageName: item.title_sub}).then(res => {
if(res && res.message === "204"){
// key
const menuListByDel = findSameKeyByDel(menuList, key);
@ -267,7 +261,7 @@ export default function Sidebar(props) {
projectId: project.id,
pageName: '_Sidebar',
title: '_Sidebar',
commit_message: '',
message: '',
content_base64: Base64.encode(content)
}).then(res => {
if (res && res.message === "200") {
@ -303,20 +297,18 @@ export default function Sidebar(props) {
e.stopPropagation();
setVisible(item);
setIsEditName(type);
let title = item.title.trim();
const isFile = title.startsWith('[[') && title.endsWith(']]');
title = isFile ? title.substring(2,title.length-2) : title.substring(2, title.length);
const {titleStr, title_sub} = item;
type === 2 && getWiki({
owner: owner,
repo: projectsId,
pageName: title,
pageName: title_sub,
projectId: project.id
}).then(res => {
if (res && res.message === "200") {
setWikiContent(res.data.md_content);
}
})
setMenuName(title);
setMenuName(titleStr);
}
//
@ -329,9 +321,8 @@ export default function Sidebar(props) {
setAddMenuError("不能仅输入空格");
return;
}
const regEn = /[\\/:*?"<>|[\]-]/g;
if (regEn.test(menuName)) {
setAddMenuError('不能有特殊字符: \\ / : * ? \" < > | [ \] -');
if (isEditName === 2 && /^-/.test(menuName)) {
setAddMenuError('不能以-开头');
return;
}
if(!isEditName){
@ -356,14 +347,15 @@ export default function Sidebar(props) {
}else{
// isEditName 1 2
const titleToJudge = isEditName === 2 ? `[[${menuName}]]` : `- ${menuName}`;
const {title, key} = visible;
if(title.trim() === titleToJudge){
const {key, titleStr, title_sub} = visible;
//
if(titleStr === menuName){
setAddMenuError("请修改名称");
return
}
//
const list = findBrotherNodesByKey(menuList, key) || [];
if(list.map(item=>item.title.trim()).includes(titleToJudge)){
if(list.map(item=>item.titleStr).includes(menuName)){
setAddMenuError("不能与同级目录名称相同");
return
}
@ -381,17 +373,16 @@ export default function Sidebar(props) {
})
if(isEditName === 2){
// -> wiki
const oldTitle = title.substring(title.indexOf('[[')+2, title.indexOf(']]'))
updateWiki({
owner,
repo: projectsId,
projectId: project.id,
//
pageName: oldTitle,
pageName: title_sub,
//
title: menuName,
content_base64: wikiContent,
commit_message: '',
message: '',
}).then(res=>{
if(res && res.message === '200'){
// wikisidebar

View File

@ -59,10 +59,8 @@ export default function UploadWiki(props) {
message.error('只能上传md、txt文件');
return false;
}
let regEn = /[\\/:*?"<>|[\]-]/g;
if (regEn.test(file.name)) {
message.error('文件名不能有特殊字符: \\ / : * ? \" < > | [ \] -');
if (/^-/.test(file.name)) {
message.error('文件名不能以-开头');
return false;
}
@ -86,7 +84,7 @@ export default function UploadWiki(props) {
projectId: project.id,
pageName: '_Sidebar',
title: '_Sidebar',
commit_message: '',
message: '',
content_base64: Base64.encode(treeToMd(sidebarList))
}).then(res => {
if (res && res.message === "200") {

View File

@ -81,8 +81,8 @@ li {
.markdown-body {
text-align: justify;
word-break: break-all;
text-align: left;
word-break: unset;
}
/* !!覆盖之前的样式*/

View File

@ -2139,7 +2139,8 @@ input.ant-input-number-input:focus {
}
/* md代码类样式渲染 */
.new_li .markdown-body ul>li ,.markdown-body ul>li {
/* issues/3984: ,.markdown-body ul>li */
.new_li .markdown-body ul>li {
list-style-type: disc !important;
margin-bottom: 0 !important;
white-space: pre-wrap;

View File

@ -19,13 +19,13 @@ function LoginRegisterPage(props){
<img src={banner} className="banner" alt="" />
</div>
<div className="login_register_right">
{props.location.pathname === "/login" ?
<Login {...props}/>
{props.location.pathname === "/resetPassword" ?
<ResetPassword mygetHelmetapi={mygetHelmetapi}/>
:
props.location.pathname === "/register" ?
<Register mygetHelmetapi={mygetHelmetapi}/>
:
<ResetPassword mygetHelmetapi={mygetHelmetapi}/>
<Login {...props}/>
}
<img src={img1} className="img1" alt="" />
<img src={img2} className="img2" alt="" />