Merge pull request '0407更新' (#526) from durian/forgeplus-react:gitlink_server into gitlink_server

This commit is contained in:
caishi 2023-04-07 17:50:05 +08:00
commit cc3976fdcc
19 changed files with 219 additions and 58 deletions

6
package-lock.json generated
View File

@ -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",

View File

@ -50,8 +50,12 @@ onChangeFile,addonBefore , name , empty , onEmpty
function changeValue(e){
const value = e.target.value;
console.log(value);
if(value){
if(value.indexOf("/")>-1){
if(value === "/"){
setValue(undefined);
}
else if(value.indexOf("/")>-1){
let a = value.split("/");
let b = newUrl;
let c = b.concat(a[0]);

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'}`}>
@ -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

@ -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);
@ -94,6 +95,7 @@ function CoderDepot(props){
useEffect(()=>{
if(details){
setProjectDetail(details);
setTopics(details.topics);
setDesc(details.description);
setWebsite(details.website);
setLessonUrl(details.lesson_url);
@ -343,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);
}
})
}
@ -382,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) &&
@ -586,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">

View File

@ -529,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) => {
@ -69,7 +69,10 @@ class IndexItem extends Component {
</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

@ -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>
<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

@ -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

@ -211,14 +211,14 @@ function New({ form , match , showNotification , history, projectDetail }) {
}
]
})(
<Input placeholder="请输入目标URL" maxLength="200" size="large" autocomplete='off' disabled = {unEdit}/>
<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"} disabled = {unEdit}/>
<Input.Password placeholder="请输入Webhook密钥" autoComplete='new-password' size="large" maxLength={200} disabled = {unEdit}/>
)}
</Form.Item>
<Form.Item label="Http请求方法" colon={false}>
@ -289,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>
@ -310,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>

View File

@ -72,6 +72,7 @@ export default (props) => {
repo: projectsId,
projectId: project.id
})
if(!info){return}
const {menuList, sidebar} = info;
setSidebar(sidebar)
setFileList(menuList);

View File

@ -78,25 +78,27 @@ export async function parseSidebar(wikiList, info){
}
})
}else{
// 如果没有wiki目录那么不处理
if(!wikiList.length){
return
}
// 没有sidebar文件根据用户的目录结构进行初始化(\n[[item.name]]结构)
const wikiArr = [];
wikiList.map(item=>{
wikiArr.push(`[[${item.name}]]`)
});
const wikiSidebarStr = wikiArr.join('\n');
await addWiki({
...info,
pageName: '_Sidebar',
title: '_Sidebar',
message: '',
content_base64: Base64.encode(wikiArr.join('\n'))
}).then(res=>{
if(res && res.message === "201"){
ReInfo={
menuList: markdownToTree(wikiArr.join('\n')),
sidebar: wikiArr.join('\n')
}
}
content_base64: Base64.encode(wikiSidebarStr)
})
ReInfo={
menuList: markdownToTree(wikiSidebarStr),
sidebar: wikiSidebarStr
}
}
return ReInfo;
}

View File

@ -211,6 +211,17 @@ export default function Sidebar(props) {
// sidebar type 1 2 3 4
function updateSidebar(content, type, addWikiName){
if(type === 1 && !content){
// wikisidebar
deleteWiki({owner: owner, repo: projectsId, projectId: project.id, pageName: "_Sidebar"}).then(res => {
if(res && res.message === "204"){
history.push(`/${owner}/${projectsId}/wiki`)
setReload(Math.random());
message.success('删除成功');
}
});
return
}
updateWiki({
owner,
repo: projectsId,

View File

@ -70,6 +70,10 @@ function ConcentrateProject({userLogin,current,showCompeleteDialog,completeProfi
<li key={i.id}>
<Link to={`/${i.author && i.author.login}/${i.identifier}`} className="name">{i.name}</Link>
<p className="task-hide desc">{i.description}</p>
{/* 项目标签 */}
{i.topics && <div className='viewProListTopics'>
{i.topics.map(item=><span className='viewProListTopic mr15 font-13 task-hide'>{item.name}</span>)}
</div>}
<AlignCenter>
{ i.category && <span className="tagName">{i.category.name}</span> }
<span className="pariseCount"><i className="iconfont icon-xingzhuang"></i>{i.watchers_count}</span>

View File

@ -147,4 +147,18 @@
}
.activeStatic{
padding-top: 20px;
}
.viewProListTopics{
display: flex;
flex-wrap: wrap;
.viewProListTopic{
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

@ -3,7 +3,7 @@ import { getImageUrl } from 'educoder';
function TeamItem({item,history}){
return(
<div onClick={()=>{window.open(`/${item.name}`,"_blank")}} style={{cursor:"pointer"}}>
<div onClick={()=>{history.push(`/${item.name}`,"_blank")}} style={{cursor:"pointer"}}>
<div className="imgBox"><img alt="" src={getImageUrl(`/${item.avatar_url}`)}/></div>
<div style={{flex:'1'}}>
<span className="mb5 font-18 color-grey-3 task-hide" style={{display:"block",maxWidth:"588px"}}>{item.nickname}</span>

View File

@ -1,11 +1,12 @@
import { Base64 } from 'js-base64';
import React , { useEffect , useState } from 'react';
import NodataPanel from '../../components/nodata-panel';
import RenderHtml from '../../components/render-html';
import { wikiPages, getWiki } from '../../forge/Wiki/api';
import bg from '../img/help_bg.png';
import './index.scss';
function Help(props) {
const {showNotification} = props;
const {showNotification, history} = props;
const [fileArrInit, setFileArrInit] = useState(null);
const [checkItem, setCheckItem] = useState({});
const [itemDetail, setItemDetail] = useState({});
@ -55,7 +56,7 @@ function Help(props) {
return <span className={`wiki_title font-15 mb15 ${item.name === checkItem.name? 'active' : ''}`} key={item.name} onClick={() => { setCheckItem(item) }}>{item.name}</span>})}
</div>
<div className="help_cntent pt10" >
<div dangerouslySetInnerHTML={{ __html: itemDetail && itemDetail.md_content && Base64.decode(itemDetail.md_content) }}></div>
{itemDetail && itemDetail.md_content && <RenderHtml value={ itemDetail && Base64.decode(itemDetail.md_content) } url={history.location}/>}
<img src={bg} className='bg' alt='bg'/>
</div>
</div>}