合并代码
This commit is contained in:
commit
1695780bef
|
|
@ -2522,7 +2522,6 @@ _extend(KRange, {
|
|||
return self;
|
||||
},
|
||||
dump : function() {
|
||||
console.log('--------------------');
|
||||
console.log(this.startContainer.nodeType == 3 ? this.startContainer.nodeValue : this.startContainer, this.startOffset);
|
||||
console.log(this.endContainer.nodeType == 3 ? this.endContainer.nodeValue : this.endContainer, this.endOffset);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 2340181 */
|
||||
src: url('iconfont.woff2?t=1715677391041') format('woff2'),
|
||||
url('iconfont.woff?t=1715677391041') format('woff'),
|
||||
url('iconfont.ttf?t=1715677391041') format('truetype');
|
||||
src: url('iconfont.woff2?t=1727340588188') format('woff2'),
|
||||
url('iconfont.woff?t=1727340588188') format('woff'),
|
||||
url('iconfont.ttf?t=1727340588188') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
|
|
@ -13,6 +13,42 @@
|
|||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-gongxianquequan:before {
|
||||
content: "\e979";
|
||||
}
|
||||
|
||||
.icon-xuanzhong5:before {
|
||||
content: "\e97a";
|
||||
}
|
||||
|
||||
.icon-big-circle:before {
|
||||
content: "\e978";
|
||||
}
|
||||
|
||||
.icon-gitee:before {
|
||||
content: "\e974";
|
||||
}
|
||||
|
||||
.icon-gitea:before {
|
||||
content: "\e975";
|
||||
}
|
||||
|
||||
.icon-coding:before {
|
||||
content: "\e976";
|
||||
}
|
||||
|
||||
.icon-github1:before {
|
||||
content: "\e977";
|
||||
}
|
||||
|
||||
.icon-gitlab:before {
|
||||
content: "\e973";
|
||||
}
|
||||
|
||||
.icon-a-lianhe6:before {
|
||||
content: "\e972";
|
||||
}
|
||||
|
||||
.icon-a-22ziliaoshouce-xianxing:before {
|
||||
content: "\e971";
|
||||
}
|
||||
|
|
@ -413,6 +449,10 @@
|
|||
content: "\e90e";
|
||||
}
|
||||
|
||||
.icon-shanchu_important:before {
|
||||
content: "\f1d8";
|
||||
}
|
||||
|
||||
.icon-shanchu_tc_icon1:before {
|
||||
content: "\e90c";
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -5,6 +5,69 @@
|
|||
"css_prefix_text": "icon-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "41876673",
|
||||
"name": "贡献确权",
|
||||
"font_class": "gongxianquequan",
|
||||
"unicode": "e979",
|
||||
"unicode_decimal": 59769
|
||||
},
|
||||
{
|
||||
"icon_id": "657556",
|
||||
"name": "分期-选中",
|
||||
"font_class": "xuanzhong5",
|
||||
"unicode": "e97a",
|
||||
"unicode_decimal": 59770
|
||||
},
|
||||
{
|
||||
"icon_id": "157771",
|
||||
"name": "细圆",
|
||||
"font_class": "big-circle",
|
||||
"unicode": "e978",
|
||||
"unicode_decimal": 59768
|
||||
},
|
||||
{
|
||||
"icon_id": "41539674",
|
||||
"name": "gitee",
|
||||
"font_class": "gitee",
|
||||
"unicode": "e974",
|
||||
"unicode_decimal": 59764
|
||||
},
|
||||
{
|
||||
"icon_id": "41539676",
|
||||
"name": "gitea",
|
||||
"font_class": "gitea",
|
||||
"unicode": "e975",
|
||||
"unicode_decimal": 59765
|
||||
},
|
||||
{
|
||||
"icon_id": "41539677",
|
||||
"name": "coding",
|
||||
"font_class": "coding",
|
||||
"unicode": "e976",
|
||||
"unicode_decimal": 59766
|
||||
},
|
||||
{
|
||||
"icon_id": "41539675",
|
||||
"name": "github",
|
||||
"font_class": "github1",
|
||||
"unicode": "e977",
|
||||
"unicode_decimal": 59767
|
||||
},
|
||||
{
|
||||
"icon_id": "41539679",
|
||||
"name": "gitlab",
|
||||
"font_class": "gitlab",
|
||||
"unicode": "e973",
|
||||
"unicode_decimal": 59763
|
||||
},
|
||||
{
|
||||
"icon_id": "41195024",
|
||||
"name": "联合 6",
|
||||
"font_class": "a-lianhe6",
|
||||
"unicode": "e972",
|
||||
"unicode_decimal": 59762
|
||||
},
|
||||
{
|
||||
"icon_id": "40333433",
|
||||
"name": "使用手册",
|
||||
|
|
@ -705,6 +768,13 @@
|
|||
"unicode": "e90e",
|
||||
"unicode_decimal": 59662
|
||||
},
|
||||
{
|
||||
"icon_id": "41976790",
|
||||
"name": "shanchu_tc_icon-copy",
|
||||
"font_class": "shanchu_important",
|
||||
"unicode": "f1d8",
|
||||
"unicode_decimal": 61912
|
||||
},
|
||||
{
|
||||
"icon_id": "26470602",
|
||||
"name": "shanchu_tc_icon",
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
29
src/App.js
29
src/App.js
|
|
@ -93,7 +93,7 @@ const Search = Loadable({
|
|||
})
|
||||
|
||||
const WikiPreview = Loadable({
|
||||
loader: () => import('./forge/Wiki/Preview'),
|
||||
loader: () => import('./forge/Wiki/page/preview'),
|
||||
loading: Loading,
|
||||
})
|
||||
|
||||
|
|
@ -340,9 +340,12 @@ class App extends Component {
|
|||
<MuiThemeProvider theme={theme}>
|
||||
<LoginDialog {...this.props} {...this.state} Modifyloginvalue={() => this.Modifyloginvalue()}></LoginDialog>
|
||||
{/* <GlccModal /> */}
|
||||
{!pathName || (pathName && pathName.toLowerCase() !== 'glcc') ? <SiderBar {...this.props}/> : <SiderBarHelp/>}
|
||||
{/* {!pathName || (pathName && pathName.toLowerCase() !== 'glcc') ? <SiderBar {...this.props}/> : <SiderBarHelp/>} */}
|
||||
{pathName && pathName.toLowerCase() === 'glcc' && <SiderBarHelp/>}
|
||||
{/* <Router> */}
|
||||
<Switch>
|
||||
{
|
||||
pathType ?
|
||||
<Switch>
|
||||
{/* iframe页面 */}
|
||||
<Route path="/hiagent"
|
||||
render={
|
||||
|
|
@ -350,7 +353,7 @@ class App extends Component {
|
|||
}
|
||||
></Route>
|
||||
{/* wiki预览 */}
|
||||
<Route path="/:owner/:projectsId/wiki/preview/:projectName/:projectId" render={
|
||||
<Route path="/:owner/:proIdentity/:projectId/wiki/preview" render={
|
||||
(props) => {
|
||||
return (<WikiPreview {...this.props} {...props} {...this.state} />)
|
||||
}
|
||||
|
|
@ -528,14 +531,6 @@ class App extends Component {
|
|||
</Route> : pathType === '404' ? <Route component={Shixunnopage} />:
|
||||
""
|
||||
}
|
||||
<Route exact path="/"
|
||||
render={
|
||||
(props) => (
|
||||
<Home {...props} {...this.props} {...this.state}/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
{/* 个人主页 */}
|
||||
<Route path="/:username"
|
||||
render={
|
||||
|
|
@ -547,6 +542,16 @@ class App extends Component {
|
|||
|
||||
<Route component={Shixunnopage} />
|
||||
</Switch>
|
||||
:
|
||||
<Route exact path="/"
|
||||
render={
|
||||
(props) => (
|
||||
<Home {...props} {...this.props} {...this.state}/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
}
|
||||
|
||||
</MuiThemeProvider>
|
||||
</ConfigProvider>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -17,22 +17,23 @@ function locationurl(list) {
|
|||
}
|
||||
// TODO 开发期多个身份切换
|
||||
let debugType = ""
|
||||
if (isDev) {
|
||||
const _search = window.location.search;
|
||||
let parsed = {};
|
||||
if (_search) {
|
||||
parsed = queryString.parse(_search);
|
||||
}
|
||||
debugType = window.location.search.indexOf('debug=t') !== -1 ? 'teacher' :
|
||||
window.location.search.indexOf('debug=s') !== -1 ? 'student' :
|
||||
window.location.search.indexOf('debug=a') !== -1 ? 'admin' : parsed.debug || 'admin'
|
||||
}
|
||||
// if (isDev) {
|
||||
// const _search = window.location.search;
|
||||
// let parsed = {};
|
||||
// if (_search) {
|
||||
// parsed = queryString.parse(_search);
|
||||
// }
|
||||
// debugType = window.location.search.indexOf('debug=t') !== -1 ? 'teacher' :
|
||||
// window.location.search.indexOf('debug=s') !== -1 ? 'student' :
|
||||
// window.location.search.indexOf('debug=a') !== -1 ? 'admin' : parsed.debug || 'student'
|
||||
// }
|
||||
window._debugType = debugType;
|
||||
export function initAxiosInterceptors(props) {
|
||||
// 判断网络是否连接
|
||||
initOnlineOfflineListener();
|
||||
|
||||
var proxy = "https://testforgeplus.trustie.net";
|
||||
// var proxy = "http://172.20.32.201:4000";
|
||||
// var proxy = "https://www.gitlink.org.cn";
|
||||
|
||||
//响应前的设置
|
||||
|
|
@ -44,19 +45,20 @@ export function initAxiosInterceptors(props) {
|
|||
requestProxy(config);
|
||||
let url = `/api${config.url}`;
|
||||
|
||||
if (`${config[0]}` !== `true`) {
|
||||
if (window.location.port === "3007") {
|
||||
config.url = `${proxy}${url}`;
|
||||
if (config.url.indexOf('?') === -1) {
|
||||
config.url = `${config.url}?debug=${debugType}`;
|
||||
} else {
|
||||
config.url = `${config.url}&debug=${debugType}`;
|
||||
}
|
||||
} else {
|
||||
config.url = url;
|
||||
}
|
||||
}
|
||||
// if (`${config[0]}` !== `true`) {
|
||||
// if (window.location.port === "3007") {
|
||||
// config.url = `${proxy}${url}`;
|
||||
// if (config.url.indexOf('?') === -1) {
|
||||
// config.url = `${config.url}?debug=${debugType}`;
|
||||
// } else {
|
||||
// config.url = `${config.url}&debug=${debugType}`;
|
||||
// }
|
||||
// } else {
|
||||
// config.url = url;
|
||||
// }
|
||||
// }
|
||||
config.withCredentials = false;
|
||||
config.url = url;
|
||||
return config;
|
||||
},
|
||||
err => {
|
||||
|
|
@ -95,11 +97,12 @@ export function initAxiosInterceptors(props) {
|
|||
}
|
||||
|
||||
if (response.data.status === 404) {
|
||||
console.log("response:",response);
|
||||
let responseURL = (response && response.request) ? response.request.responseURL:'';
|
||||
// 组织和个人的拥有情况,404不跳转
|
||||
if (responseURL.indexOf('/api/users/') === -1 && responseURL.indexOf('/api/organizations/') === -1 ) {
|
||||
// 邀请页面不进行404跳转
|
||||
if( window.location.pathname.includes('/invite') && (responseURL.includes('/simple.json')||responseURL.includes('/detail.json')||responseURL.includes('/menu_list.json'))){
|
||||
if( window.location.pathname.includes('/invite') && (responseURL.includes('/simple.json')||responseURL.includes('/detail.json')||responseURL.includes('/menu_list.json'))||responseURL.includes('/entries.json')){
|
||||
}else{
|
||||
locationurl('/nopage');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ function requestForSignatrue (callback) {
|
|||
service.post(url, {
|
||||
url: currentUrl,
|
||||
}).then((response) => {
|
||||
console.log('got res')
|
||||
const data = response.data && response.data.data;
|
||||
|
||||
wx.config({
|
||||
|
|
@ -125,7 +124,6 @@ export function configShareForCourses () {
|
|||
|
||||
// detail
|
||||
export function configShareForCustom (title, desc, imgUrl, path) {
|
||||
console.log('--------------------', title,desc)
|
||||
requestForSignatrue(() => {
|
||||
console.log('configShareForCustom', host)
|
||||
const _url = window.location.href.split('#')[0];
|
||||
|
|
|
|||
|
|
@ -32,10 +32,18 @@ class ActivityItem extends Component {
|
|||
</p >
|
||||
}
|
||||
<p className="itemLine mt10">
|
||||
<Link to={`/${item && item.user_login}`} className="show-user-link">
|
||||
<img alt="" src={getImageUrl(`/${item.user_avatar}`)} className="createImage" />
|
||||
<span className="mr20">{item.user_name}</span>
|
||||
</Link>
|
||||
{
|
||||
item && item.user_login ?
|
||||
<Link to={`/${item.user_login}`} className="show-user-link">
|
||||
<img alt="" src={getImageUrl(`/${item.user_avatar}`)} className="createImage" />
|
||||
<span className="mr20">{item.user_name}</span>
|
||||
</Link>
|
||||
:
|
||||
<span className="show-user-link">
|
||||
<img alt="" src={getImageUrl(`/${item.user_avatar}`)} className="createImage" />
|
||||
<span className="mr20 color-grey-6">{item.user_name}</span>
|
||||
</span>
|
||||
}
|
||||
{item.created_at && <span className="color-grey-9">创建于<span className="ml2 color-grey-6">{item.created_at}</span></span>}
|
||||
</p >
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,25 +5,21 @@ import axios from 'axios';
|
|||
|
||||
const Option = AutoComplete.Option;
|
||||
|
||||
export default ({ getUser , placeholder, width ,value })=>{
|
||||
export default ({ getUser , placeholder, width ,value, userList=[] })=>{
|
||||
const [ source , setSource ] = useState(undefined);
|
||||
const [ searchKey , setSearchKey ] = useState(undefined);
|
||||
|
||||
useEffect(()=>{
|
||||
if(!value){
|
||||
setSearchKey(undefined);
|
||||
if(userList && userList.length){
|
||||
sourceOptions(userList);
|
||||
}else{
|
||||
getUserList();
|
||||
}
|
||||
},[value])
|
||||
|
||||
useEffect(()=>{
|
||||
getUserList();
|
||||
},[searchKey])
|
||||
},[])
|
||||
|
||||
function getUserList(e){
|
||||
function getUserList(value){
|
||||
const url = `/users/list.json`;
|
||||
axios.get(url, {
|
||||
params: {
|
||||
search: searchKey,
|
||||
search: value,
|
||||
},
|
||||
}).then((result) => {
|
||||
if (result) {
|
||||
|
|
@ -61,13 +57,9 @@ export default ({ getUser , placeholder, width ,value })=>{
|
|||
setSource(s);
|
||||
}
|
||||
|
||||
function changeInputUser(e){
|
||||
setSearchKey(e);
|
||||
};
|
||||
|
||||
// 选择用户
|
||||
function selectInputUser(e, option){
|
||||
setSearchKey(option.props.name);
|
||||
getUserList(option.props.name);
|
||||
getUser(option.props.login);
|
||||
};
|
||||
|
||||
|
|
@ -76,12 +68,12 @@ export default ({ getUser , placeholder, width ,value })=>{
|
|||
<AutoComplete
|
||||
getPopupContainer={trigger => trigger.parentNode}
|
||||
dataSource={source}
|
||||
value={searchKey}
|
||||
style={{ width: width || 300 }}
|
||||
onChange={changeInputUser}
|
||||
onSearch={(value)=>{getUserList(value)}}
|
||||
onSelect={selectInputUser}
|
||||
placeholder={placeholder || "搜索需要添加的用户..."}
|
||||
allowClear
|
||||
optionLabelProp="name"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ $(window).scroll(function () {
|
|||
});
|
||||
|
||||
function SiderBar(props) {
|
||||
const {location, history} = props;
|
||||
const {location, history,user} = props;
|
||||
const {search, pathname} = location;
|
||||
const [ data , setData ] = useState([]);
|
||||
const [ visible , setVisible ] = useState(false);
|
||||
|
|
@ -30,9 +30,13 @@ function SiderBar(props) {
|
|||
const [ feedBackValue, setFeedBackValue] = useState(undefined);
|
||||
const [ failApply, setFailApply] = useState(undefined);
|
||||
|
||||
useEffect(()=>{
|
||||
user && setLogin(user.login);
|
||||
},[user])
|
||||
|
||||
useEffect(()=>{
|
||||
getFAQ();
|
||||
getCurrentUser();
|
||||
// getCurrentUser();
|
||||
//页面加载完成之后隐藏回到顶点
|
||||
$(".-task-sidebar .gotop").hide();
|
||||
// 意见反馈弹窗
|
||||
|
|
@ -51,15 +55,6 @@ function SiderBar(props) {
|
|||
}).catch(error=>{})
|
||||
}
|
||||
|
||||
//获取当前登录账号信息->用于建议反馈
|
||||
function getCurrentUser(){
|
||||
let url = `/users/get_user_info.json`;
|
||||
axios.get(url).then((response) => {
|
||||
if (response && response.data && response.data.login) {
|
||||
setLogin(response.data.login);
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
|
||||
function content(list){
|
||||
return <div>
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ export default ({ url , name , column , id , login })=>{
|
|||
}
|
||||
`;
|
||||
return(
|
||||
id?
|
||||
<Link to={`/${login}`}>
|
||||
(id && login)?
|
||||
<Link to={`/${login ||""}`}>
|
||||
<span className="User">
|
||||
{ url && <img src={url} alt=""/> }
|
||||
<span>{name}</span>
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ function Index(props){
|
|||
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>
|
||||
return <Link to={`/${value.login}`}><img src={`${value.image_url}`} alt="" style={{borderRadius:"50%"}} width="32px" height="32px" /></Link>
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,14 +8,9 @@ const { Option } = Select;
|
|||
function DivertModal({form , visible , onSuccess , onCancel,owner,repo}){
|
||||
const { getFieldDecorator, validateFields , setFieldsValue } = form;
|
||||
const [ cate , setCate ] = useState(0);
|
||||
const [ value , setValue ] = useState(undefined);
|
||||
|
||||
const [ organizations , setOrganizations ] = useState(undefined);
|
||||
|
||||
useEffect(()=>{
|
||||
setFieldsValue({goal:cate})
|
||||
},[])
|
||||
|
||||
useEffect(()=>{
|
||||
if(owner && repo && visible===true){
|
||||
getTeam();
|
||||
|
|
@ -25,7 +20,7 @@ function DivertModal({form , visible , onSuccess , onCancel,owner,repo}){
|
|||
owner_name:undefined,
|
||||
identifier:undefined
|
||||
})
|
||||
setValue(undefined)
|
||||
setCate(0);
|
||||
}
|
||||
},[repo,owner,visible])
|
||||
|
||||
|
|
@ -79,7 +74,6 @@ function DivertModal({form , visible , onSuccess , onCancel,owner,repo}){
|
|||
};
|
||||
|
||||
function getUser(id){
|
||||
setValue(id);
|
||||
setFieldsValue({
|
||||
owner_name:id
|
||||
})
|
||||
|
|
@ -94,6 +88,7 @@ function DivertModal({form , visible , onSuccess , onCancel,owner,repo}){
|
|||
okText="确认转移"
|
||||
cancelText={"取消"}
|
||||
centered
|
||||
destroyOnClose={true}
|
||||
>
|
||||
<div className="diverModal">
|
||||
{
|
||||
|
|
@ -113,7 +108,8 @@ function DivertModal({form , visible , onSuccess , onCancel,owner,repo}){
|
|||
<Form {...layout} colon={false} layout={"horizontal"}>
|
||||
<Form.Item label="转移给:" style={{marginBottom:"0px"}}>
|
||||
{getFieldDecorator("goal",{
|
||||
rules:[]
|
||||
rules:[],
|
||||
initialValue: 0
|
||||
})(
|
||||
<Radio.Group onChange={changeType}>
|
||||
<Radio value={0}>个人</Radio>
|
||||
|
|
@ -128,7 +124,7 @@ function DivertModal({form , visible , onSuccess , onCancel,owner,repo}){
|
|||
rules:[{required:true,message:"请输入目标用户名"}]
|
||||
})(
|
||||
// <Input placeholder="请输入目标用户" autoComplete={"off"}/>
|
||||
<SearchUser getUser={getUser} width={"100%"} placeholder="请输入目标用户" value={value}/>
|
||||
<SearchUser getUser={getUser} width={"100%"} placeholder="请输入目标用户"/>
|
||||
)}
|
||||
</Form.Item>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,14 +125,19 @@ function IssueCommentList(props){
|
|||
{journals && (journals.length > 0 && journals.map(item=>{return <div key={item.id} className='commentContentBox pb30'>
|
||||
{/* 评论 */}
|
||||
<div className='commentOperationBor'></div>
|
||||
<Link to={`/${item.user.login}`}><img src={getImageUrl(item.user.image_url)} alt="" className='commentUserImg mr15'/></Link>
|
||||
{
|
||||
item.user.login ?
|
||||
<Link to={`/${item.user.login}`}><img src={getImageUrl(item.user.image_url)} alt="" className='commentUserImg mr15'/></Link>
|
||||
:
|
||||
<span><img src={getImageUrl(item.user.image_url)} alt="" className='commentUserImg mr15'/></span>
|
||||
}
|
||||
<div className='commentContentRight'>
|
||||
{/* 判断是否是编辑状态 */}
|
||||
{(showEdit === 2 && updateId === item.id) ? <div className='mt15 mr20'><EditComment {...props} cancelMd={cancelMd} updateId={updateId} reloadComment={reloadComment} content={item.notes} defaultFileList={item.attachments} showUserImg={false}/></div> : <div>
|
||||
<div className='commentContent'>
|
||||
<div className='flexCenter font-14'>
|
||||
<div>
|
||||
<Link to={`/${item.user.login}`}>{item.user.name}</Link>
|
||||
{item.user.login ? <Link to={`/${item.user.login}`}>{item.user.name}</Link>:<span>{item.user.name}</span>}
|
||||
<span className='ml15 timeAgo font-14'>{timeAgo(item.created_at)}</span>
|
||||
</div>
|
||||
{login && !isPhone() && <div>
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ function Index(props){
|
|||
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>
|
||||
return <Link to={`/${value.login}`}><img src={`${value.image_url}`} alt="" style={{borderRadius:"50%"}} width="32px" height="32px" /></Link>
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,30 +1,25 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { Box, LongWidth, FlexAJ } from '../../Component/layout';
|
||||
import { Affix, Button, Input, message, Spin, Tree } from 'antd';
|
||||
import { Affix, Button, message, Spin } from 'antd';
|
||||
import Nodata from '../../Nodata';
|
||||
import nodata from '../img/nodata.png';
|
||||
import { tempEnum } from '../tempInfo';
|
||||
import { findFirstWiki, generateList, getParentKey, getWiki, parseSidebar, wikiPages } from '../../Wiki/api';
|
||||
import { findNodeByFirstOrContrastName, getMenuListAndSidebarData, getWiki } from '../../Wiki/api';
|
||||
import RenderHtml from '../../../components/render-html';
|
||||
import { Base64 } from 'js-base64';
|
||||
import { getTocContent } from '../../../common/marked';
|
||||
import { splitAndCombine } from '../../Wiki/utils';
|
||||
|
||||
const { Search } = Input;
|
||||
const { DirectoryTree, TreeNode } = Tree;
|
||||
import TreeByWikiSidebar from '../../Wiki/components/treeByWikiSidebar';
|
||||
|
||||
function ProjectSource(props) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [wikiList, setWikiList] = useState(undefined);
|
||||
const [flattenedMenuList, setFlattenedMenuList] = useState([]);
|
||||
const [initMenuList, setInitMenuList] = useState([]);
|
||||
const [expandedKeys, setExpandedKeys] = useState(undefined);
|
||||
const [wikiPageInitDetail, setWikiPageInitDetail] = useState({})
|
||||
const [wiki, setWiki] = useState(undefined);
|
||||
const [wikiDetail, setWikiDetail] = useState(undefined);
|
||||
const [autoExpandParent, setAutoExpandParent] = useState(false);
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
const [wikiContMenu, setWikiContMenu] = useState(undefined);
|
||||
const { temp, history, data, adminUrl } = props;
|
||||
const { temp, history, data, adminUrl, match:{params:{deptId}}, location} = props;
|
||||
const {menuList=[], sidebar="", wikiPages=undefined} = wikiPageInitDetail;
|
||||
const search = new URLSearchParams(location.search);
|
||||
const wikiBySearch = search.get("wiki") || '';
|
||||
let scroller = undefined;
|
||||
|
||||
const { owner, identifier, projectId } = (data && data.gitlinkProject) || {};
|
||||
|
|
@ -38,26 +33,22 @@ function ProjectSource(props) {
|
|||
useEffect(() => {
|
||||
if(!owner) return
|
||||
// 获取wiki 列表
|
||||
wikiPages(wikiParams).then(async res => {
|
||||
if (res && res.message === "200") {
|
||||
setWikiList(res.data);
|
||||
// 解析sidebar获取目录list
|
||||
const info = await parseSidebar(res.data, wikiParams);
|
||||
if (!info) return
|
||||
const { menuList } = info;
|
||||
// 默认打开第一个wiki
|
||||
const firstWiki = findFirstWiki(menuList);
|
||||
if(firstWiki){
|
||||
setExpandedKeys(splitAndCombine(firstWiki.key+""));
|
||||
setWiki(firstWiki);
|
||||
}
|
||||
setFlattenedMenuList(generateList(menuList));
|
||||
setInitMenuList(menuList);
|
||||
} else {
|
||||
setWikiList([]);
|
||||
}
|
||||
getMenuListAndSidebarData(wikiParams).then(res=>{
|
||||
setWikiPageInitDetail(res);
|
||||
})
|
||||
}, [owner])
|
||||
|
||||
useEffect(()=>{
|
||||
if(!menuList.length) return
|
||||
// 通过路由直接访问
|
||||
let node = findNodeByFirstOrContrastName(menuList, wikiBySearch)
|
||||
// 有可能wikiBySearch是错误的值,导致右侧显示为空
|
||||
if(!node){
|
||||
history.push(`/zone/${deptId}/help`)
|
||||
return
|
||||
}
|
||||
setWiki(node);
|
||||
}, [menuList, wikiBySearch])
|
||||
|
||||
useEffect(() =>{
|
||||
if(!wiki){
|
||||
|
|
@ -70,7 +61,7 @@ function ProjectSource(props) {
|
|||
...wikiParams
|
||||
}).then(res=>{
|
||||
setLoading(false);
|
||||
if (res && res.message === "200") {
|
||||
if (res && res.code === 200) {
|
||||
setWikiDetail(res.data);
|
||||
const tocContent = getTocContent();
|
||||
setWikiContMenu(tocContent);
|
||||
|
|
@ -154,77 +145,28 @@ function ProjectSource(props) {
|
|||
};
|
||||
}, [])
|
||||
|
||||
function changeSearchValue(value) {
|
||||
if (value) {
|
||||
const va = value.trim();
|
||||
// 找到命中搜索的item的父元素
|
||||
const expandedKeys = flattenedMenuList.map(item => {
|
||||
if (item.title.match(new RegExp(va, 'i'))) {
|
||||
return getParentKey(item.key, initMenuList);
|
||||
}
|
||||
return null;
|
||||
}).filter((item, i, self) => item && self.indexOf(item) === i);
|
||||
setSearchValue(va);
|
||||
setExpandedKeys(splitAndCombine(expandedKeys[0]));
|
||||
} else {
|
||||
setSearchValue("");
|
||||
}
|
||||
}
|
||||
|
||||
// 收起展开
|
||||
function onExpand(expandedKeys) {
|
||||
setAutoExpandParent(false);
|
||||
setExpandedKeys(expandedKeys);
|
||||
}
|
||||
|
||||
// 选中节点
|
||||
function selectTree(keys,event){
|
||||
let {isFile} = event.node.props.dataRef;
|
||||
if(isFile){
|
||||
setWiki(event.node.props.dataRef);
|
||||
}
|
||||
}
|
||||
|
||||
function renderTreeNodes(data) {
|
||||
return data && data.length > 0 && data.map((item) => {
|
||||
const matchResult = item.titleStr.match(new RegExp(searchValue, 'i'));
|
||||
const title = matchResult ? (
|
||||
<span>
|
||||
{item.titleStr.substr(0, matchResult.index)}
|
||||
<span style={{ color: '#f50' }}>{item.titleStr.substring(matchResult.index, matchResult.index+searchValue.length)}</span>
|
||||
{item.titleStr.substr(matchResult.index+searchValue.length)}
|
||||
</span>
|
||||
) : (
|
||||
<span>{item.titleStr}</span>
|
||||
);
|
||||
return (
|
||||
<TreeNode title={title} key={item.key + ""} isLeaf={item.isFile} dataRef={item} className='menuHcNode font-15'>
|
||||
{renderTreeNodes(item.children)}
|
||||
</TreeNode>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const noDataText = adminUrl ? <a href={`${data && data.gitlinkProject.wiki_url}`} target="_blank" style={{color: '#466aff'}}>前往配置</a> : "正在建设中,敬请期待"
|
||||
const noDataText = adminUrl ? <a href={`${data && data.gitlinkProject.wiki_url}`} target="_blank" className='zone_apply_button'>前往配置</a> : "正在建设中,敬请期待"
|
||||
return (
|
||||
<div className="in_pro">
|
||||
<div className="boxmain" style={{ paddingTop: "56px" }}>
|
||||
<p className="in_title mb30">{data && data.sectionHelperTitle}</p>
|
||||
<Spin spinning={loading}>
|
||||
<Spin spinning={loading} wrapperClassName='themeSpin'>
|
||||
{
|
||||
wikiList && (wikiList.length > 0 ? <Box className='zoneHelpBox'>
|
||||
wikiPages && (wikiPages.length > 0 ? <Box className='zoneHelpBox'>
|
||||
{/* wiki目录导航 */}
|
||||
<div className='leftMenuBox mr20'>
|
||||
<div className='leftMenuHC'>
|
||||
<Search
|
||||
allowClear
|
||||
placeholder="输入关键字搜索文件"
|
||||
className="searchHC mb10"
|
||||
onSearch={changeSearchValue}
|
||||
/>
|
||||
<DirectoryTree onSelect={selectTree} expandedKeys={expandedKeys} onExpand={onExpand} autoExpandParent={autoExpandParent} showIcon={false}>
|
||||
{renderTreeNodes(initMenuList)}
|
||||
</DirectoryTree>
|
||||
<TreeByWikiSidebar
|
||||
wikiParams={wikiParams}
|
||||
hasPermission={false}
|
||||
wikiPageInitDetail={wikiPageInitDetail}
|
||||
clickNode={(node)=>{
|
||||
history.push(`/zone/${deptId}/help?wiki=${encodeURIComponent(node.titleStr)}`)
|
||||
}}
|
||||
history={history}
|
||||
hasOpen={false}
|
||||
searchClassName="searchHC mb10"
|
||||
treeNodeClassName="menuHcNode"/>
|
||||
</div>
|
||||
</div>
|
||||
{/* 内容区 */}
|
||||
|
|
@ -233,12 +175,12 @@ function ProjectSource(props) {
|
|||
<LongWidth>
|
||||
<FlexAJ style={{alignItems: 'stretch'}}>
|
||||
<div>
|
||||
<div className='wikiTitleHC font-20 mb5'>{wikiDetail.name}</div>
|
||||
{wikiDetail.commit && <div className='wikiUpdateTimeHC font-15'>更新时间:{wikiDetail.commit.author.date || '刚刚'}</div>}
|
||||
<div className='wikiTitleHC font-20 mb5'>{wikiDetail.title}</div>
|
||||
{wikiDetail.last_commit && <div className='wikiUpdateTimeHC font-15'>更新时间:{wikiDetail.last_commit.author.date || '刚刚'}</div>}
|
||||
</div>
|
||||
{adminUrl && <Button onClick={()=>{wiki && window.open(`${data && data.gitlinkProject.wiki_url}/${wiki.title_sub}/${wiki.key}`)}} className='zone_apply_button' style={{width: '90px', height: '34px'}}>配置内容</Button>}
|
||||
{adminUrl && <Button onClick={()=>{wiki && window.open(`${data && data.gitlinkProject.wiki_url}?wiki=${encodeURIComponent(wikiDetail.title)}`)}} className='zone_apply_button' style={{width: '90px', height: '34px'}}>配置内容</Button>}
|
||||
</FlexAJ>
|
||||
{wikiDetail.md_content && <RenderHtml className="wikiContentHC editor-content-panel mt20 imageLayerParent" value={ Base64.decode(wikiDetail.md_content) } url={history.location}/>}
|
||||
{wikiDetail.content_base64 && <RenderHtml className="wikiContentHC editor-content-panel mt20 imageLayerParent" value={ Base64.decode(wikiDetail.content_base64) } url={history.location}/>}
|
||||
</LongWidth>
|
||||
{wikiContMenu && wikiContMenu !== "<ul></ul>" && <Affix offsetTop={80}>
|
||||
<div id="wikiContMenuBox" className='wikiContMenuBox font-15' dangerouslySetInnerHTML={{ __html: wikiContMenu }}></div>
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ function Main(props){
|
|||
setMainList(rows);
|
||||
// 隐藏判断原因:首页查看全部跳转过来后要默认选择指定的cateID而不是第一个
|
||||
// if (temp !== tempEnum.zone) {
|
||||
selectCate(cateId)
|
||||
selectCate(cateId || (rows[0] && rows[0].id))
|
||||
// }
|
||||
setIsSpin(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ function NewsList(props){
|
|||
const [ menuSpin , setMenuSpin ] = useState(true);
|
||||
const { id, temp } = props;
|
||||
|
||||
|
||||
useEffect(()=>{
|
||||
if(id && cateId ){
|
||||
setMenuSpin(true);
|
||||
|
|
|
|||
|
|
@ -385,4 +385,45 @@ export function getProjectListByScore(id,params) {
|
|||
method: 'GET',
|
||||
params
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 获取已开通企业的组织列表
|
||||
export function getOrzCompanyList(){
|
||||
return fetch({
|
||||
url:`/pms/pmsEnterprise/myList`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
// 查询组织是否开通企业
|
||||
export function checkOpenedEnterprise(orgId){
|
||||
return fetch({
|
||||
url:`/pms/pmsEnterprise/${orgId}/workbenchUrl`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
// 组织升级为企业
|
||||
export function createEnterpriseByGitlinkOrgId(orgId, data) {
|
||||
return fetch({
|
||||
url: `/pms/pmsEnterprise/createByGitlinkOrgId/${orgId}`,
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 可选角色列表
|
||||
export function getRoleList(){
|
||||
return fetch({
|
||||
url:`/pms/pmsEnterprise/roleList`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
// 获取工作台初始用户列表
|
||||
export function getUserList(orgId){
|
||||
return fetch({
|
||||
url:`/pms/pmsEnterprise/gitlinkUserList/${orgId}`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ function beforeFetch(actionUrl){
|
|||
axios.defaults.withCredentials = true;
|
||||
}
|
||||
const config = {
|
||||
// headers:{Authorization:"Bearer eyJhbGciOiJIUzUxMiJ9.eyJ1c2VyX2lkIjo0NCwidXNlcl9rZXkiOiI3NWY3MmJmYmE2NGU4N2U3NWVjNWMwZDk2OThkMDFiN2EyODE0YjYwIiwidXNlcm5hbWUiOiJpbm5vdiJ9.IviAyZQjPFc4rT3ejAKfJYx3_QgS78RCKHx1XS-A4uI_mrc7_l6wK76HVIoPCEvP0Qimzyc6dpeVW4cE2fILZw"},
|
||||
baseURL: actionUrl,
|
||||
timeout: 1800000, // 请求超时时间
|
||||
}
|
||||
|
|
@ -47,9 +48,9 @@ function beforeFetch(actionUrl){
|
|||
)
|
||||
return service;
|
||||
}
|
||||
let settings = localStorage.chromesetting&&JSON.parse(localStorage.chromesetting);
|
||||
let settings = localStorage.chromesetting&& localStorage.chromesetting !=="undefined"&& JSON.parse(localStorage.chromesetting);
|
||||
let actionUrl = settings && settings.common.zone +'/api';
|
||||
|
||||
const service = beforeFetch(actionUrl);
|
||||
export const httpUrl = actionUrl;
|
||||
export default service;
|
||||
export default service;
|
||||
|
|
|
|||
|
|
@ -119,6 +119,9 @@ function Index(props){
|
|||
|
||||
|
||||
useEffect(()=>{
|
||||
if(deptId && deptId === "KYDS7th"){
|
||||
window.location.href = 'https://gitlink.org.cn/competitions/2024';
|
||||
}
|
||||
if(deptId && deptId!=="apply"){
|
||||
getDetail();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.themeSpin .ant-spin-dot-item{
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.information_main{
|
||||
background-color:#f3f5f8;
|
||||
min-height: 100vh;
|
||||
|
|
|
|||
|
|
@ -129,8 +129,8 @@ function IssueCommentList(props){
|
|||
<div className='operationCommentBor'></div>
|
||||
<div className='flexCenter font-14' style={{width: '100%'}}>
|
||||
<div className='flexCenter opBox'>
|
||||
<span className='iconBackBox mr10'><i className={`iconfont font-12 ${journalsIcon[item.operate_category]}`}></i></span>
|
||||
<div className='task-hide' style={{maxWidth: item.closeAndSpan && item.id === item.start? '550px' : '700px'}}>
|
||||
<span className='iconBackBox mr10'><i className={`iconfont font-12 ${journalsIcon[item.operate_category] || "icon-xiugaibaobiaomoba"}`}></i></span>
|
||||
<div className='task-hide operationWords' style={{maxWidth: item.closeAndSpan && item.id === item.start? '550px' : '700px'}}>
|
||||
<Link to={`/${item.user.login}`}><img src={getImageUrl(item.user.image_url)} alt="" className='commentUserImg mr5'/></Link>
|
||||
<Link to={`/${item.user.login}`}>{item.user.name} </Link>
|
||||
{(item.user.name.length + item.operate_content.length) > 62 ? <Tooltip title={<div><span>{item.user.name} </span><span dangerouslySetInnerHTML={{__html:item.operate_content}}></span></div>}><span dangerouslySetInnerHTML={{__html:item.operate_content}}></span></Tooltip> : <span dangerouslySetInnerHTML={{__html:item.operate_content}}></span>}
|
||||
|
|
@ -142,14 +142,19 @@ function IssueCommentList(props){
|
|||
</div> : '' : <div key={item.id} className='commentContentBox pb30'>
|
||||
{/* 评论 */}
|
||||
<div className='commentOperationBor'></div>
|
||||
<Link to={`/${item.user.login}`}><img src={getImageUrl(item.user.image_url)} alt="" className='commentUserImg mr15'/></Link>
|
||||
{
|
||||
item.user.login ?
|
||||
<Link to={`/${item.user.login}`}><img src={getImageUrl(item.user.image_url)} alt="" className='commentUserImg mr15'/></Link>
|
||||
:
|
||||
<span><img src={getImageUrl(item.user.image_url)} alt="" className='commentUserImg mr15'/></span>
|
||||
}
|
||||
<div className='commentContentRight'>
|
||||
{/* 判断是否是编辑状态 */}
|
||||
{(showEdit === 2 && updateId === item.id) ? <div className='mt15 mr20'><EditComment {...props} cancelMd={cancelMd} updateId={updateId} reloadComment={reloadComment} content={item.notes} defaultFileList={item.attachments} showUserImg={false}/></div> : <div>
|
||||
<div className='commentContent'>
|
||||
<div className='flexCenter font-14'>
|
||||
<div>
|
||||
<Link to={`/${item.user.login}`}>{item.user.name}</Link>
|
||||
{item.user.login ? <Link to={`/${item.user.login}`}>{item.user.name}</Link>:<span>{item.user.name}</span>}
|
||||
<span className='ml15 timeAgo font-14'>{timeAgo(item.created_at)}</span>
|
||||
</div>
|
||||
{login && <div>
|
||||
|
|
|
|||
|
|
@ -62,6 +62,11 @@
|
|||
position: absolute;
|
||||
left: 12px;
|
||||
}
|
||||
.operationWords{
|
||||
a{
|
||||
color:#466aff;
|
||||
}
|
||||
}
|
||||
.operationLog+.commentContentBox{
|
||||
&>a, &>.commentContentRight{
|
||||
margin-top: 25px;
|
||||
|
|
|
|||
|
|
@ -53,8 +53,13 @@ function Datas({checkbox ,item , projectsId,owner,chooseTagFunc}){
|
|||
<Copy value={`/${owner}/${projectsId}/issues/${item.project_issues_index}`}><span className="number">#{item.project_issues_index}</span></Copy>
|
||||
}
|
||||
</div>
|
||||
<Link to={`/${item.author && item.author.login}`}><i className="iconfont icon-chengyuan2 mr3 font-12" style={{color:'#898d9d'}}></i></Link>
|
||||
<span className="mr12"><Link style={{color:"#898d9d"}} to={`/${item.author && item.author.login}`}>{item.author && item.author.name}</Link></span>
|
||||
<span className="mr12">
|
||||
{item.author && item.author.login ?
|
||||
<Link style={{color:"#898d9d"}} to={`/${item.author.login}`}>
|
||||
<i className="iconfont icon-chengyuan2 mr3 font-12" style={{color:'#898d9d'}}></i>{item.author && item.author.name}</Link>
|
||||
:<span><i className="iconfont icon-chengyuan2 mr3 font-12" style={{color:'#898d9d'}}></i>{item.author && item.author.name}</span>
|
||||
}
|
||||
</span>
|
||||
<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>}
|
||||
|
|
|
|||
|
|
@ -391,9 +391,13 @@ function Details(props){
|
|||
<Copy value={`/${owner}/${projectsId}/issues/${index}`}><span className="number">#{details.project_issues_index}</span></Copy>
|
||||
</div>
|
||||
{
|
||||
details.author &&
|
||||
details.author &&
|
||||
<div>
|
||||
<Link to={`/${details.author.login}`} className="author"><img src={getImageUrl(details.author.image_url)} alt="" />{details.author.name}</Link>
|
||||
{ details.author.login ?
|
||||
<Link to={`/${details.author.login}`} className="author"><img src={details.author.image_url} alt="" />{details.author.name}</Link>
|
||||
:
|
||||
<span className="author color-grey-6"><img src={details.author.image_url} alt="" />{details.author.name}</span>
|
||||
}
|
||||
<span className="ml10" style={{color:"#898d9d"}}>添加于{details.created_at}</span>
|
||||
</div>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -185,6 +185,7 @@ function Sign(props){
|
|||
}
|
||||
|
||||
function createSign(){
|
||||
setEditId(undefined);
|
||||
const c = Math.random().toString(16).substr(-6);
|
||||
c && setDefaultColor(`#${c}`);
|
||||
setEdit(true);
|
||||
|
|
|
|||
|
|
@ -207,7 +207,7 @@ function CoderDepot(props){
|
|||
axios.get(url, {
|
||||
params: { ref:branch}
|
||||
}).then((result) => {
|
||||
if (result) {
|
||||
if (result && result.data.status !== 404) {
|
||||
setCommitCount(result.data.commits_count);
|
||||
setDirInfo(result.data.entries);
|
||||
setFileInfo(undefined);
|
||||
|
|
@ -222,6 +222,9 @@ function CoderDepot(props){
|
|||
setEditReadme(false);
|
||||
setHide(true);
|
||||
getReadmeInfo('', branchName || defaultBranch);
|
||||
}else{
|
||||
// entries接口返回404:分支不存在或被删除
|
||||
props.history.push(`/${owner}/${projectsId}`)
|
||||
}
|
||||
setTimeout(function(){setIsSpin(false);},500);
|
||||
}).catch(error=>{setIsSpin(false);})
|
||||
|
|
|
|||
|
|
@ -132,11 +132,11 @@ const DevIndex = Loadable({
|
|||
loading: Loading,
|
||||
});
|
||||
const Wiki = Loadable({
|
||||
loader: () => import('../Wiki/Index'),
|
||||
loader: () => import('../Wiki/page/index'),
|
||||
loading: Loading,
|
||||
});
|
||||
const WikiEdit = Loadable({
|
||||
loader: () => import('../Wiki/EditWiki'),
|
||||
loader: () => import('../Wiki/page/edit'),
|
||||
loading: Loading,
|
||||
});
|
||||
const Invite = Loadable({
|
||||
|
|
@ -209,15 +209,14 @@ class Detail extends Component {
|
|||
defaultBranch: undefined,
|
||||
|
||||
// 非本平台项目
|
||||
platform: false,
|
||||
platform: true,
|
||||
mirror_status:0,
|
||||
bannerList: props.projectMenu || []
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
componentDidMount = () => {
|
||||
// this.getProject();
|
||||
componentDidMount=()=>{
|
||||
let history = this.props.location;
|
||||
this.clearIssueCookies(history);
|
||||
if (this.props.clearProject) {
|
||||
|
|
@ -228,7 +227,8 @@ class Detail extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
componentDidUpdate = async (prevState) => {
|
||||
|
||||
componentDidUpdate = async (prevState) => {
|
||||
let prevParam = prevState.match.params;
|
||||
let propsParam = this.props.match.params;
|
||||
if (prevState && this.props && (prevParam.projectsId !== propsParam.projectsId || prevParam.owner !== propsParam.owner)) {
|
||||
|
|
@ -243,7 +243,7 @@ class Detail extends Component {
|
|||
// 非issue链接地址清除cookie states,具体保存和操作在order.js页面
|
||||
this.clearIssueCookies(history);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
clearIssueCookies = (history) => {
|
||||
const { pathname } = history;
|
||||
|
|
@ -255,9 +255,7 @@ class Detail extends Component {
|
|||
}
|
||||
}
|
||||
async componentWillMount() {
|
||||
|
||||
const { projectsId, owner } = this.props.match.params;
|
||||
|
||||
if (!this.props.projectBase) {
|
||||
const data = await getProjectFunc(owner, projectsId);
|
||||
if (data && data.data) {
|
||||
|
|
@ -451,19 +449,28 @@ class Detail extends Component {
|
|||
// 获取动态导航栏菜单
|
||||
async getBanner(data) {
|
||||
const { projectsId, owner } = this.props.match.params;
|
||||
if (!data) {
|
||||
const url = `/${owner}/${projectsId}/menu_list.json`;
|
||||
const res = await axios.get(url)
|
||||
if (res.data) {
|
||||
data = res.data
|
||||
const url = `/${owner}/${projectsId}/menu_list.json`;
|
||||
axios.get(url).then(res=>{
|
||||
if(res && res.data){
|
||||
this.setState({
|
||||
bannerList: res.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (data) {
|
||||
this.setState({
|
||||
bannerList: data
|
||||
})
|
||||
}
|
||||
})
|
||||
// if (!data) {
|
||||
// const url = `/${owner}/${projectsId}/menu_list.json`;
|
||||
// axios.get(url).then(res=>{
|
||||
// if(res && res.data){
|
||||
// this.setState({
|
||||
// bannerList: res.data
|
||||
// })
|
||||
// }
|
||||
// })
|
||||
// }else{
|
||||
// this.setState({
|
||||
// bannerList: data
|
||||
// })
|
||||
// }
|
||||
}
|
||||
|
||||
// 关注和取消关注
|
||||
|
|
@ -535,12 +542,14 @@ class Detail extends Component {
|
|||
const url = `/${owner}/${projectsId}/forks.json`;
|
||||
axios.post(url).then(result => {
|
||||
if (result && result.data.status === 0) {
|
||||
if (result.data.message === "fork失败,你已拥有了这个项目") {
|
||||
this.props.showNotification(result.data.message);
|
||||
if (result.data.message && result.data.message.indexOf("fork失败")>-1) {
|
||||
// fork失败,已经fork过,跳转到fork的仓库
|
||||
this.props.history.push(`/${current_user && current_user.login}/${projectsId}`);
|
||||
return;
|
||||
}
|
||||
this.props.history.push(`/${current_user && current_user.login}/${result.data.identifier}`);
|
||||
this.props.showNotification(result.data.message);
|
||||
|
||||
this.props.showNpsModal("indexProject", 3);
|
||||
}
|
||||
this.setState({
|
||||
|
|
@ -758,7 +767,8 @@ class Detail extends Component {
|
|||
</Content>
|
||||
}
|
||||
<Spin spinning={secondSync && !firstSync} className="spinstyle" tip="正在同步镜像" size="large">
|
||||
<Switch {...this.props}>
|
||||
{/* mirror_status = 1的情况不显示项目详情子模块 */}
|
||||
{!firstSync && <Switch {...this.props}>
|
||||
{/* 服务 */}
|
||||
<Route path="/:owner/:projectsId/service"
|
||||
render={
|
||||
|
|
@ -796,15 +806,15 @@ class Detail extends Component {
|
|||
}
|
||||
></Route>
|
||||
{/* wiki编辑文件 */}
|
||||
<Route path="/:owner/:projectsId/wiki/:wikiName/:key/edit"
|
||||
<Route path="/:owner/:projectsId/wiki/edit"
|
||||
render={
|
||||
() => (<WikiEdit {...this.props} {...this.state} {...common} />)
|
||||
}
|
||||
></Route>
|
||||
{/* wiki具体某一个地址 */}
|
||||
<Route path="/:owner/:projectsId/wiki/:wikiName/:key"
|
||||
{/* wiki复制文件 */}
|
||||
<Route path="/:owner/:projectsId/wiki/copy"
|
||||
render={
|
||||
(props) => (<Wiki {...this.props} {...props} {...this.state} {...common} />)
|
||||
() => (<WikiEdit {...this.props} {...this.state} {...common} />)
|
||||
}
|
||||
></Route>
|
||||
{/* wiki */}
|
||||
|
|
@ -960,7 +970,7 @@ class Detail extends Component {
|
|||
(props) => <CoderDepot {...this.props} {...props} {...this.state} {...common} mirror_status={mirror_status}/>
|
||||
}
|
||||
></Route>
|
||||
</Switch>
|
||||
</Switch>}
|
||||
</Spin>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -79,25 +79,34 @@ class MergeItem extends Component {
|
|||
</Tag>
|
||||
</p>
|
||||
<p className="grid-item font-13">
|
||||
<Link
|
||||
to={`/${item && item.author_login}`}
|
||||
className="show-user-link"
|
||||
>
|
||||
<img
|
||||
className="radius"
|
||||
src={getImageUrl(`/${item && item.avatar_url}`)}
|
||||
alt=""
|
||||
width="24"
|
||||
height="24"
|
||||
/>
|
||||
</Link>
|
||||
<AlignCenter>
|
||||
<Link
|
||||
to={`/${item && item.author_login}`}
|
||||
className="show-user-link color-grey-8 ml5"
|
||||
>
|
||||
{item && item.author_name}
|
||||
</Link>
|
||||
{
|
||||
item && item.author_login ?
|
||||
<Link
|
||||
to={`/${item.author_login}`}
|
||||
className="show-user-link color-grey-8"
|
||||
>
|
||||
<img
|
||||
className="radius mr5"
|
||||
src={getImageUrl(`/${item && item.avatar_url}`)}
|
||||
alt=""
|
||||
width="24"
|
||||
height="24"
|
||||
/>
|
||||
{item && item.author_name}
|
||||
</Link>
|
||||
:
|
||||
<span className="color-grey-6">
|
||||
<img
|
||||
className="radius mr5"
|
||||
src={getImageUrl(`/${item && item.avatar_url}`)}
|
||||
alt=""
|
||||
width="24"
|
||||
height="24"
|
||||
/>{item && item.author_name}
|
||||
</span>
|
||||
}
|
||||
|
||||
<span className="ml15 color-grey-8">
|
||||
{item.pull_request_staus === "open"
|
||||
? "创建于"
|
||||
|
|
@ -114,10 +123,8 @@ class MergeItem extends Component {
|
|||
item.is_original ?
|
||||
(
|
||||
(item.fork_project_user && item.fork_project_identifier) ?
|
||||
<Link
|
||||
to={`/${ item.fork_project_user }/${ item.fork_project_identifier }/tree/${turnbar(item.pull_request_head)}`}
|
||||
className="maxW200px task-hide ver-middle" style={{maxWidth:"300px"}}
|
||||
>
|
||||
<Link to={`/${ item.fork_project_user }/${ item.fork_project_identifier }/tree/${turnbar(item.pull_request_head)}`}
|
||||
className="maxW200px task-hide ver-middle" style={{maxWidth:"300px"}} >
|
||||
{item.fork_project_user_name || "仓库已删除"} : {item.pull_request_head || "分支已删除"}
|
||||
</Link>
|
||||
:
|
||||
|
|
|
|||
|
|
@ -342,7 +342,7 @@ class MessageCount extends Component {
|
|||
const {
|
||||
data,
|
||||
SpinMerge,
|
||||
pr_status,
|
||||
pr_status,
|
||||
isSpin,
|
||||
ismesrge,
|
||||
SpinFlag,
|
||||
|
|
@ -404,42 +404,56 @@ class MessageCount extends Component {
|
|||
{
|
||||
<div className="mt15">
|
||||
<Tag className="pr-branch-tag">
|
||||
<Link
|
||||
to={`/${data.pull_request.is_original ? data.pull_request.fork_project_user : data.issue.project_author}/${data.pull_request.is_original?data.project_identifier:projectsId}/tree/${turnbar(data.pull_request && data.pull_request.head)}`}
|
||||
className="ver-middle task-hide" style={{maxWidth:"300px"}} title={`${data.pull_request.fork_project_user}: ${data.pull_request && data.pull_request.head}`}
|
||||
>
|
||||
{data.pull_request && data.pull_request.fork_project_user}: {data.pull_request && data.pull_request.head}
|
||||
</Link>
|
||||
{
|
||||
(data.pull_request.fork_project_user || data.issue.author_name!=="已注销") ?
|
||||
<Link
|
||||
to={`/${data.pull_request.is_original ? data.pull_request.fork_project_user : data.issue.author_name}/${data.pull_request.is_original?data.project_identifier:projectsId}/tree/${turnbar(data.pull_request && data.pull_request.head)}`}
|
||||
className="ver-middle task-hide" style={{maxWidth:"300px"}} title={`${data.pull_request.fork_project_user}: ${data.pull_request && data.pull_request.head}`}
|
||||
>
|
||||
{data.pull_request && (data.pull_request.is_original ? data.pull_request.fork_project_user:data.issue.author_name)}: {data.pull_request && data.pull_request.head}
|
||||
</Link>
|
||||
:
|
||||
<span className="ver-middle task-hide" style={{maxWidth:"300px"}} title={`${data.pull_request.fork_project_user}: ${data.pull_request && data.pull_request.head}`}>
|
||||
{data.pull_request && (data.pull_request.is_original ? data.pull_request.fork_project_user:data.issue.author_name)}: {data.pull_request && data.pull_request.head}
|
||||
</span>
|
||||
|
||||
}
|
||||
</Tag>
|
||||
<span className="mr8 ver-middle">
|
||||
<i
|
||||
className={
|
||||
"iconfont icon-youjiang color-grey-c font-16"
|
||||
}
|
||||
></i>
|
||||
<i className={"iconfont icon-youjiang color-grey-c font-16"} />
|
||||
</span>
|
||||
<Tag className="pr-branch-tag">
|
||||
<Link
|
||||
to={`/${owner}/${projectsId}/tree/${data.pull_request.base}`}
|
||||
className="ver-middle task-hide" style={{maxWidth:"300px"}} title={`${data.issue.project_author_name}:${data.pull_request.base}`}
|
||||
>
|
||||
{data.issue.project_author}: {data.pull_request.base}
|
||||
{data.issue.project_author_name}: {data.pull_request.base}
|
||||
</Link>
|
||||
</Tag>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div className="mt15">
|
||||
<Link to={`/${data.issue.author_login}`} className="show-user-link">
|
||||
<img className="mr5" src={getImageUrl(`/${data.issue.author_picture}`)}
|
||||
alt="" width="24" height="24" style={{borderRadius:"50%"}}
|
||||
/>
|
||||
</Link>
|
||||
{
|
||||
data.issue.author_login?
|
||||
<Link to={`/${data.issue.author_login}`} className="show-user-link">
|
||||
<img className="mr5" src={getImageUrl(`/${data.issue.author_picture}`)}
|
||||
alt="" width="24" height="24" style={{borderRadius:"50%"}}
|
||||
/>
|
||||
</Link>
|
||||
:
|
||||
<span><img className="mr5" src={getImageUrl(`/${data.issue.author_picture}`)} alt="" width="24" height="24" style={{borderRadius:"50%"}}/></span>
|
||||
}
|
||||
<span className="ver-middle">
|
||||
<span className="color-grey-8 mr5">由</span>
|
||||
<Link to={`/${data.issue.author_login}`} className="show-user-link color-blue">
|
||||
{data.issue.author_name}
|
||||
</Link>
|
||||
{
|
||||
data.issue.author_login ?
|
||||
<Link to={`/${data.issue.author_login}`} className="show-user-link color-blue">
|
||||
{data.issue.author_name}
|
||||
</Link>
|
||||
:
|
||||
<span className="show-user-link color-grey-6">{data.issue.author_name}</span>
|
||||
}
|
||||
<span className="ml5 color-grey-8">
|
||||
{pr_status === 0
|
||||
? "创建于"
|
||||
|
|
|
|||
|
|
@ -146,18 +146,14 @@ const ReplyItem = memo(({ note, isChild }) => {
|
|||
<div className="reply-item-head">
|
||||
<Link
|
||||
to={`/${note.user && note.user.login}`}
|
||||
className="show-user-link"
|
||||
className="show-user-link color-black fwb"
|
||||
>
|
||||
<Avatar src={note.user && note.user.imageUrl
|
||||
? getImageUrl(`/${note.user.imageUrl}`)
|
||||
: "images/avatars/User/b"} size={24} />
|
||||
</Link>
|
||||
<Link
|
||||
to={`/${note.user && note.user.login}`}
|
||||
className="show-user-link color-black ml10 fwb"
|
||||
>
|
||||
: "images/avatars/User/b"} size={24} className="mr10"/>
|
||||
{note.user && note.user.username}
|
||||
</Link> 于 <span className="show-user-link color-black">{timeAgo(note.createdAt)}</span> 发表评论:
|
||||
</Link>
|
||||
于 <span className="show-user-link color-black">{timeAgo(note.createdAt)}</span> 发表评论:
|
||||
<span className="btn-right">
|
||||
{user &&
|
||||
(user.admin ||
|
||||
|
|
|
|||
|
|
@ -19,11 +19,15 @@ function Apply(props) {
|
|||
setIsSpin(true);
|
||||
getList();
|
||||
}
|
||||
},[username])
|
||||
},[username, page])
|
||||
|
||||
function getList() {
|
||||
const url = `/users/${username}/applied_projects.json`;
|
||||
Axios.get(url).then(result=>{
|
||||
Axios.get(url,{
|
||||
params:{
|
||||
page,per_page:limit
|
||||
}
|
||||
}).then(result=>{
|
||||
if(result){
|
||||
setList(result.data.applied_projects);
|
||||
setTotal(result.data.total_count);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import nullBot from '../../../../softbot/image/notBot.png';
|
|||
import { getAllInstallBots, deleteInstallBot } from "../../../../softbot/api";
|
||||
import './index.scss';
|
||||
import axios from "axios";
|
||||
import LogoBot from '../../../Settings/image/logoBot.png';
|
||||
|
||||
// 配置已安装的bot
|
||||
function DispositionBot(props){
|
||||
|
|
@ -85,7 +86,7 @@ function DispositionBot(props){
|
|||
{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"/>
|
||||
<img src={(item.logo && !(item.logo.indexOf("347246")>-1 || item.logo.indexOf("412603")>-1)) ? getImageUrl(item.logo) : LogoBot} 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>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { getImageUrl } from 'educoder';
|
|||
import { getMyBot, getTransferToBot, receiveTransferBot, refuseTransferBot } from "../../../../softbot/api";
|
||||
import nullBot from '../../../../softbot/image/notBot1.png';
|
||||
import moment from "moment";
|
||||
import LogoBot from '../../../Settings/image/logoBot.png';
|
||||
|
||||
function ExploitBot(props){
|
||||
const {current_user} = props;
|
||||
|
|
@ -96,7 +97,7 @@ function ExploitBot(props){
|
|||
{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"/>
|
||||
<img src={(item.logo && !(item.logo.indexOf("347246")>-1 || item.logo.indexOf("412603")>-1)) ? getImageUrl(item.logo) : LogoBot} 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>
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ function Disposition(props){
|
|||
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}`,
|
||||
// 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() : '',
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ function Main(props){
|
|||
|
||||
useEffect(()=>{
|
||||
if(current_user && !current_user.login){
|
||||
props.history.push(`/login?go_page=/${owner}/${projectsId}/service`);
|
||||
// 未登录用户访问会跳转到代码库主页,此处不用处理
|
||||
// props.history.push(`/login?go_page=/${owner}/${projectsId}/service`);
|
||||
}else{
|
||||
setHas_trace_user(current_user.has_trace_user);
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
|
|
@ -6,6 +6,7 @@ import './setting.scss';
|
|||
import { useEffect } from "react";
|
||||
import { getStoreAllInstallBots } from "../../softbot/api";
|
||||
import { getImageUrl } from 'educoder';
|
||||
import LogoBot from './image/logoBot.png';
|
||||
|
||||
function SoftBot(props){
|
||||
const {projectDetail} = props;
|
||||
|
|
@ -34,7 +35,7 @@ function SoftBot(props){
|
|||
{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"/>
|
||||
<img src={(item.logo && !(item.logo.indexOf("347246")>-1 || item.logo.indexOf("412603")>-1)) ? getImageUrl(item.logo) : LogoBot} 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>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { Route, Switch } from "react-router-dom";
|
||||
import Loadable from "react-loadable";
|
||||
|
|
@ -7,9 +7,10 @@ import { withRouter } from "react-router";
|
|||
import { SnackbarHOC } from "educoder";
|
||||
import { CNotificationHOC } from "../../modules/courses/common/CNotificationHOC";
|
||||
import { TPMIndexHOC } from "../../modules/tpm/TPMIndexHOC";
|
||||
import ProjectDetail from '../Main/Detail'
|
||||
import '../css/index.scss';
|
||||
import './Index.scss';
|
||||
import { checkOpenedEnterprise } from '../Information/api';
|
||||
import axios from 'axios';
|
||||
|
||||
const GroupNew = Loadable({
|
||||
loader: () => import("./Group/GroupNew"),
|
||||
|
|
@ -36,12 +37,39 @@ const SubDetailIndex = Loadable({
|
|||
loading: Loading,
|
||||
});
|
||||
// forge项目详情
|
||||
// const ProjectDetail = Loadable({
|
||||
// loader: () => import("../Main/Detail"),
|
||||
// loading: Loading,
|
||||
// });
|
||||
const team = CNotificationHOC()(SnackbarHOC()(TPMIndexHOC(
|
||||
const ProjectDetail = Loadable({
|
||||
loader: () => import("../Main/Detail"),
|
||||
loading: Loading,
|
||||
});
|
||||
export default withRouter(CNotificationHOC()(SnackbarHOC()(TPMIndexHOC(
|
||||
((props)=>{
|
||||
const OIdentifier = props.match.params.OIdentifier;
|
||||
const [ organizeDetail , setOrganizeDetail ] = useState(undefined);
|
||||
const [ enterpriseOpenInfo, setEnterpriseOpenInfo] = useState(false);
|
||||
useEffect(()=>{
|
||||
if(OIdentifier){
|
||||
getDetail(OIdentifier);
|
||||
}
|
||||
},[OIdentifier]);
|
||||
|
||||
function getDetail(identifier) {
|
||||
const url = `/organizations/${identifier}.json`;
|
||||
axios.get(url).then(result=>{
|
||||
if(result && result.data){
|
||||
setOrganizeDetail(result.data);
|
||||
const {id} = result.data;
|
||||
checkOrgOpenEnterprise(id)
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
|
||||
function checkOrgOpenEnterprise(orgId){
|
||||
checkOpenedEnterprise(orgId).then(res=>{
|
||||
if(res && res.data.code === 200){
|
||||
setEnterpriseOpenInfo(res.data.data)
|
||||
}
|
||||
})
|
||||
}
|
||||
return (
|
||||
<div className="newMain">
|
||||
<Switch>
|
||||
|
|
@ -84,7 +112,7 @@ const team = CNotificationHOC()(SnackbarHOC()(TPMIndexHOC(
|
|||
<Route
|
||||
path="/:OIdentifier/members"
|
||||
render={(p) => {
|
||||
return <TeamDetailIndex {...props} {...p}/>
|
||||
return <TeamDetailIndex {...props} {...p} enterpriseOpened = {enterpriseOpenInfo && enterpriseOpenInfo.isOpen}/>
|
||||
}}
|
||||
></Route>
|
||||
|
||||
|
|
@ -92,10 +120,18 @@ const team = CNotificationHOC()(SnackbarHOC()(TPMIndexHOC(
|
|||
<Route
|
||||
path="/:OIdentifier/setting"
|
||||
render={(p) => (
|
||||
<TeamDetailIndex {...props} {...p}/>
|
||||
<TeamDetailIndex {...props} {...p} enterpriseOpened = {enterpriseOpenInfo && enterpriseOpenInfo.isOpen}/>
|
||||
)}
|
||||
></Route>
|
||||
|
||||
{/* 开通企业工作台 */}
|
||||
<Route
|
||||
path="/:OIdentifier/enterprise"
|
||||
render={(p) => {
|
||||
return <TeamDetailIndex {...props} {...p}/>
|
||||
}}
|
||||
></Route>
|
||||
|
||||
{/* 组织下的项目详情 */}
|
||||
<Route
|
||||
path="/:owner/:projectsId"
|
||||
|
|
@ -108,15 +144,11 @@ const team = CNotificationHOC()(SnackbarHOC()(TPMIndexHOC(
|
|||
<Route
|
||||
path="/:OIdentifier"
|
||||
render={(p) => (
|
||||
<DetailIndex {...props} {...p}/>
|
||||
<DetailIndex {...props} {...p} organizeDetail={organizeDetail} enterpriseOpenInfo={enterpriseOpenInfo}/>
|
||||
)}
|
||||
></Route>
|
||||
</Switch>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
)))
|
||||
|
||||
team.preFetch = ProjectDetail.preFetch
|
||||
|
||||
export default withRouter(team)
|
||||
))))
|
||||
|
|
@ -576,4 +576,13 @@
|
|||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.settingBtn{
|
||||
color: #466AFF;
|
||||
border-color: #466AFF!important;
|
||||
line-height: 30px;
|
||||
&.ant-btn.ant-btn-background-ghost.ant-btn-primary{
|
||||
background-color: #fff!important;
|
||||
color: #466AFF!important;
|
||||
}
|
||||
}
|
||||
|
|
@ -7,12 +7,13 @@ import Item from './ListItem';
|
|||
import Right from './RightBox';
|
||||
import NoData from '../Nodata';
|
||||
import CheckProfile from '../Component/ProfileModal/Profile';
|
||||
import { Menu , Pagination , Dropdown , Spin , Tooltip , Radio } from 'antd';
|
||||
import { Menu , Pagination , Dropdown , Spin , Tooltip , Radio, Button } from 'antd';
|
||||
import ConcentrateProject from '../users/GeneralView/ConcentrateProject';
|
||||
import { getImageUrl } from 'educoder';
|
||||
import RenderHtml from "../../components/render-html";
|
||||
import EmptyImg from '../Images/nodata.png';
|
||||
import axios from 'axios';
|
||||
import { checkOpenedEnterprise } from '../Information/api';
|
||||
|
||||
const limit = 15;
|
||||
function List(props){
|
||||
|
|
@ -22,27 +23,11 @@ function List(props){
|
|||
const [ search , setSearch ] = useState(undefined);
|
||||
const [ page , setPage ] = useState(1);
|
||||
const [ sortBy , setSortBy ] = useState("updated_on");
|
||||
const [ organizeDetail , setOrganizeDetail ] = useState(undefined);
|
||||
const [ mode , setMode ] = useState("overview");
|
||||
|
||||
const OIdentifier = props.match.params.OIdentifier;
|
||||
const { current_user } = props;
|
||||
console.log(props);
|
||||
const { current_user, organizeDetail, enterpriseOpenInfo } = props;
|
||||
|
||||
useEffect(()=>{
|
||||
if(OIdentifier){
|
||||
getDetail(OIdentifier);
|
||||
}
|
||||
},[OIdentifier]);
|
||||
|
||||
function getDetail(id) {
|
||||
const url = `/organizations/${id}.json`;
|
||||
axios.get(url).then(result=>{
|
||||
if(result && result.data){
|
||||
setOrganizeDetail(result.data);
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
|
||||
useEffect(()=>{
|
||||
if(organizeDetail){
|
||||
|
|
@ -109,17 +94,44 @@ function List(props){
|
|||
<div className="content-orz">
|
||||
<div className="top-orz">
|
||||
<div className="box">
|
||||
<img src={getImageUrl(organizeDetail && organizeDetail.avatar_url)} alt=""/>
|
||||
<img src={getImageUrl(organizeDetail && organizeDetail.avatar_url)} alt="" />
|
||||
<div className="info-orz">
|
||||
<p className="orz-main">
|
||||
<span className="orz-name">{organizeDetail && organizeDetail.nickname}</span>
|
||||
{organizeDetail && organizeDetail.is_admin ?
|
||||
<Link to={`/${OIdentifier}/setting`} className="color-blue ml10 font-14 df"><i className="iconfont icon-shezhi2"></i>设置</Link>
|
||||
:""}
|
||||
<span className="orz-name task-hide">{organizeDetail && organizeDetail.nickname}</span>
|
||||
{organizeDetail && (
|
||||
<div>
|
||||
{organizeDetail.pms_enable && organizeDetail.is_admin && !enterpriseOpenInfo.isOpen &&
|
||||
<Link to={`/${OIdentifier}/enterprise`}>
|
||||
<Button type='primary'>开通工作台</Button>
|
||||
</Link>
|
||||
}
|
||||
{
|
||||
organizeDetail.pms_enable && enterpriseOpenInfo.isOpen && organizeDetail.is_member && (enterpriseOpenInfo.url ?
|
||||
<Button type='primary' onClick={() => { window.open(enterpriseOpenInfo.url) }}>前往工作台</Button>
|
||||
:
|
||||
<Button type='primary'>正在开通工作台</Button>)
|
||||
}
|
||||
{/* {
|
||||
organizeDetail.pms_enable && organizeDetail.is_admin &&(
|
||||
!enterpriseOpenInfo.isOpen ? (
|
||||
<Link to={`/${OIdentifier}/enterprise`}>
|
||||
<Button type='primary'>开通工作台</Button>
|
||||
</Link>
|
||||
) :
|
||||
enterpriseOpenInfo.url ? (
|
||||
<Button type='primary' onClick={() => { window.open(enterpriseOpenInfo.url) }}>前往工作台</Button>
|
||||
) : (
|
||||
<Button type='primary'>正在开通工作台</Button>
|
||||
)
|
||||
)
|
||||
} */}
|
||||
{organizeDetail.is_admin && <Link to={`/${OIdentifier}/setting`} className="color-blue ml20 font-14"><Button type='primary' ghost>设置</Button></Link>}
|
||||
</div>
|
||||
)}
|
||||
</p>
|
||||
<div className="orz-desc task-hide-2">
|
||||
<Tooltip title={organizeDetail && organizeDetail.description} placement={"bottomLeft"}>
|
||||
<span style={{display:"block"}}>{organizeDetail && organizeDetail.description}</span>
|
||||
<span style={{ display: "block" }}>{organizeDetail && organizeDetail.description}</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -129,7 +141,7 @@ function List(props){
|
|||
<div className="list-l">
|
||||
{
|
||||
organizeDetail.news_title &&
|
||||
<div className="teamnewstatus">
|
||||
<div className="teamnewstatus mb30">
|
||||
<p className="teamname"><i className="iconfont icon-dongtaiicon1" /><span>新闻动态</span></p>
|
||||
<p className="teamtitle">
|
||||
{organizeDetail.news_title}
|
||||
|
|
@ -228,6 +240,7 @@ function List(props){
|
|||
showCompeleteDialog={props.showCompeleteDialog}
|
||||
completeProfile={props.completeProfile}
|
||||
history={props.history}
|
||||
enterpriseOpened = {enterpriseOpenInfo.isOpen}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ const Img = styled.img`{
|
|||
height:45px;
|
||||
margin-right:12px;
|
||||
}`
|
||||
function RightBox({ OIdentifier , history , admin , showCompeleteDialog ,completeProfile }) {
|
||||
function RightBox({ OIdentifier , history , admin , showCompeleteDialog ,completeProfile , enterpriseOpened }) {
|
||||
const [ languageData, setLanguageData ] = useState(undefined);
|
||||
const [ memberData, setMemberData ] = useState(undefined);
|
||||
const [ groupData, setGroupData ] = useState(undefined);
|
||||
|
|
@ -148,7 +148,7 @@ function RightBox({ OIdentifier , history , admin , showCompeleteDialog ,complet
|
|||
</Box>
|
||||
:""
|
||||
}
|
||||
<Box
|
||||
{!enterpriseOpened && <Box
|
||||
name="组织团队"
|
||||
count={groupData && groupData.total_count}
|
||||
icon={<i className="iconfont icon-zuzhituandui font-17 mr8"></i>}
|
||||
|
|
@ -188,6 +188,7 @@ function RightBox({ OIdentifier , history , admin , showCompeleteDialog ,complet
|
|||
</React.Fragment>:<Nodata _html="暂无团队" small/>
|
||||
}
|
||||
</Box>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ const CLANew = Loadable({
|
|||
export default (( props )=>{
|
||||
const pathname = props.location.pathname;
|
||||
const OIdentifier = props.match.params.OIdentifier;
|
||||
const {organizeDetail} = props;
|
||||
const { organizeDetail , enterpriseOpened } = props;
|
||||
useEffect(()=>{
|
||||
if(organizeDetail){
|
||||
const {nickname} = organizeDetail;
|
||||
|
|
@ -68,8 +68,9 @@ export default (( props )=>{
|
|||
const array = {list:[
|
||||
{name:'基本设置',icon:"icon-base",href:`/${OIdentifier}/setting`},
|
||||
{name:'组织首页管理',icon:"icon-huabanfuben",href:`/${OIdentifier}/setting/index`},
|
||||
{name:'组织成员管理',icon:"icon-zuzhichengyuan",href:`/${OIdentifier}/setting/member`},
|
||||
{name:'组织团队管理',icon:"icon-zuzhixiangmu",href:`/${OIdentifier}/setting/group`},
|
||||
// ,href: enterpriseOpened ? `${ mygetHelmetapi && mygetHelmetapi.common.zone }/${OIdentifier}/members` : `/${OIdentifier}/setting/member`
|
||||
{name:'组织成员管理',icon:"icon-zuzhichengyuan",href:`/${OIdentifier}/setting/member`, hide: enterpriseOpened},
|
||||
{name:'组织团队管理',icon:"icon-zuzhixiangmu",href:`/${OIdentifier}/setting/group`, hide: enterpriseOpened},
|
||||
// {name:'管理web钩子',icon:"icon-zhongqingdianxinicon10",href:`/${OIdentifier}/setting/hooks`}
|
||||
{name:'CLA管理',img:claIcon,href:`/${OIdentifier}/setting/agreement`,hide:organizeDetail && !organizeDetail.enabling_cla}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -23,11 +23,7 @@ function CLAinfos(props){
|
|||
}
|
||||
},[current_user,details])
|
||||
|
||||
useEffect(()=>{
|
||||
if(current_user &&!current_user.login){
|
||||
props.history.push(`/login?go_page=/${OIdentifier}/cla/${claid}`);
|
||||
}
|
||||
},[current_user])
|
||||
|
||||
|
||||
useEffect(()=>{
|
||||
if(claid){
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ function TeamSettingCLA(props){
|
|||
const OIdentifier = props.match.params.OIdentifier;
|
||||
const [ list , setList ] = useState(undefined);
|
||||
const [ isSpin , setisSpin ] = useState(false);
|
||||
const { current_user } = props;
|
||||
|
||||
useEffect(()=>{
|
||||
if(OIdentifier){
|
||||
|
|
@ -28,6 +29,14 @@ function TeamSettingCLA(props){
|
|||
}).catch(error=>{})
|
||||
}
|
||||
|
||||
function toDetail(){
|
||||
if(current_user && current_user.login){
|
||||
window.open(`/${OIdentifier}/cla/${list.key}`, '_blank');
|
||||
}else{
|
||||
window.open(`/login?go_page=/${OIdentifier}/cla/${claid}`, '_blank');
|
||||
}
|
||||
}
|
||||
|
||||
return(
|
||||
<div style={{border:list && list.id ? "none":"1px solid #eee"}}>
|
||||
<Title>CLA管理</Title>
|
||||
|
|
@ -40,7 +49,7 @@ function TeamSettingCLA(props){
|
|||
<p className="task-hide cla_name">{list.name}</p>
|
||||
<span className="cla_personCount">签署人数<span>{list.count || 0}</span></span>
|
||||
<div className="cla_btn">
|
||||
<Button href={`/${OIdentifier}/cla/${list.key}`} target="_blank" style={{color:"#466aff",borderColor:"#466aff"}}>查看协议</Button>
|
||||
<Button onClick={toDetail} style={{color:"#466aff",borderColor:"#466aff"}}>查看协议</Button>
|
||||
<Button onClick={()=>{props.history.push(`/${OIdentifier}/setting/agreement/${list.key}`)}} style={{color:"#466aff",borderColor:"#466aff"}} className="ml22">编辑</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,176 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { WhiteBack , FlexAJ } from '../../Component/layout';
|
||||
import { Table , Pagination , Popconfirm, Button, message, Select, Spin } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
import './teamSetting.scss';
|
||||
import { createEnterpriseByGitlinkOrgId, getRoleList, getUserList, checkOpenedEnterprise } from '../../Information/api';
|
||||
|
||||
const Img = styled.img`{
|
||||
width:30px;
|
||||
height:30px;
|
||||
border-radius:50%;
|
||||
}`
|
||||
const limit = 15;
|
||||
export default (({organizeDetail,history,match, current_user})=>{
|
||||
const OIdentifier = match.params.OIdentifier;
|
||||
const {login} = current_user
|
||||
const [ page , setPage ] = useState(1);
|
||||
const [ total , setTotal ] = useState(0);
|
||||
const [ data , setData ] = useState(undefined);
|
||||
const [ roleList, setRoleList] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
// 定时器
|
||||
let intervalId = null;
|
||||
|
||||
useEffect(()=>{
|
||||
getRoleList().then(res=>{
|
||||
const {code, rows} = res && res.data
|
||||
if(code === 200){
|
||||
setRoleList(rows);
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
|
||||
useEffect(()=>{
|
||||
if(organizeDetail){
|
||||
const {nickname} = organizeDetail;
|
||||
document.title = `开通组织工作台-${nickname}`;
|
||||
pollData()
|
||||
}
|
||||
}, [organizeDetail])
|
||||
|
||||
useEffect(()=>{
|
||||
if(organizeDetail && organizeDetail.id){
|
||||
getData(organizeDetail.id);
|
||||
}
|
||||
},[organizeDetail,page])
|
||||
|
||||
// 轮询函数
|
||||
const pollData = () => {
|
||||
checkOpenedEnterprise(organizeDetail.id).then(res=>{
|
||||
if(res && res.data.code === 200){
|
||||
const {data} = res.data;
|
||||
if(data.isOpen && !data.url){
|
||||
setLoading(true)
|
||||
}else if(data.isOpen){
|
||||
clearInterval(intervalId);
|
||||
window.location = data.url
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
function getData(id){
|
||||
getUserList(id).then(res=>{
|
||||
const {code, rows, total} = res && res.data
|
||||
if(code === 200){
|
||||
setData(rows);
|
||||
setTotal(total);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 切换分页
|
||||
function ChangePage(page){
|
||||
setPage(page);
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '头像',
|
||||
dataIndex: 'imageUrl',
|
||||
render:(value)=>{
|
||||
return(
|
||||
<Img src={value}></Img>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '用户名',
|
||||
dataIndex: 'nickname',
|
||||
},
|
||||
{
|
||||
title: '邮箱',
|
||||
dataIndex: 'email',
|
||||
},
|
||||
{
|
||||
title: '所属团队',
|
||||
dataIndex: ['role', 'roleKey'],
|
||||
width:"20%",
|
||||
render:(value,record)=>{
|
||||
return <Select value={value} onChange={(value)=>{
|
||||
const users = [...data];
|
||||
users.map(item=>item.username === record.username && (item.role.roleKey = value))
|
||||
setData(users);
|
||||
}}>
|
||||
{roleList && roleList.map(item=>{return <Select.Option key={item.roleKey}>{item.roleName}</Select.Option>})}
|
||||
</Select>
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'username',
|
||||
key: "action",
|
||||
render:(value)=>{
|
||||
return (
|
||||
<Button type='danger' size='small' disabled={value === login} onClick={()=>{
|
||||
const users = data.filter(item=>{return item.username !== value});
|
||||
setData(users);
|
||||
}}><i className='iconfont icon-fuzhi-shanchu font-14'></i></Button>
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
function createEnterprise(){
|
||||
const users = data.map(item=>{return {userId: item.userId, roleKey: item.role.roleKey}})
|
||||
createEnterpriseByGitlinkOrgId(organizeDetail.id, users).then(res=>{
|
||||
if(res && res.data.code === 200){
|
||||
message.success('开通成功');
|
||||
intervalId = setInterval(pollData, 1000);
|
||||
}else{
|
||||
message.error(res && res.data.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
return(
|
||||
<Spin spinning={loading}>
|
||||
<WhiteBack style={{border:"1px solid #eee"}} className='exterpriseBox'>
|
||||
<div className='padding30'>
|
||||
首次创建组织工作台时,系统将根据组织团队用户在组织工作台中根据不同团队初始化成不同角色。请您知悉以下角色权限,再次确认组织成员的角色分配。<br/>
|
||||
<span className='font-bd'>组织管理员:</span>原owner团队成员,对组织内所有代码库均具有管理员权限,在组织工作台中拥有最高管理权限<br/>
|
||||
<span className='font-bd'>项目管理员:</span>原管理员团队成员,对组织内所有代码库均具有管理员权限,在组织工作台中拥有项目管理权限<br/>
|
||||
<span className='font-bd'>普通成员:</span>原开发者及报告者团队成员,对组织内所有代码库均具有开发者权限,在组织工作台中拥有项目基础使用权限及查看权限
|
||||
</div>
|
||||
<FlexAJ className="padding20-30">
|
||||
<div style={{width:"580px"}}>
|
||||
{/* <Search placeholder="输入用户名或邮箱、团队名搜索" onSearch={(value)=>{setSearch(value)}}/> */}
|
||||
</div>
|
||||
{/* <Sort menu={menu}>
|
||||
<a className="color-blue">角色筛选<i className="iconfont icon-sanjiaoxing-down ml3 font-14"></i></a>
|
||||
</Sort> */}
|
||||
</FlexAJ>
|
||||
<div className="pl30 pr30 pb30" style={{minHeight:"400px"}}>
|
||||
<Table
|
||||
size="small"
|
||||
columns={columns}
|
||||
dataSource={data}
|
||||
pagination={false}
|
||||
className="teamMemberTable"
|
||||
></Table>
|
||||
{/* {
|
||||
total > limit ?
|
||||
<div className="edu-txt-center mt30 mb20">
|
||||
<Pagination simple current={page} total={total} pageSize={limit} onChange={ChangePage}></Pagination>
|
||||
</div>
|
||||
:""
|
||||
} */}
|
||||
</div>
|
||||
<div className='edu-txt-right pl30 pr30 pb30'>
|
||||
<Button type='primary' ghost className='mr20' onClick={()=>{history.push(`/${OIdentifier}`)}}>暂不开通</Button>
|
||||
<Button type='primary' onClick={createEnterprise}>确认并开通工作台</Button>
|
||||
</div>
|
||||
</WhiteBack>
|
||||
</Spin>
|
||||
)
|
||||
})
|
||||
|
|
@ -129,4 +129,14 @@
|
|||
margin-right: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.exterpriseBox{
|
||||
.padding30{
|
||||
color: #4C5876;
|
||||
line-height: 30px;
|
||||
border-bottom: 1px dashed #eee;
|
||||
}
|
||||
.font-bd{
|
||||
color: #0D0F12;
|
||||
}
|
||||
}
|
||||
|
|
@ -26,9 +26,15 @@ const Setting = Loadable({
|
|||
loader: () => import("../Setting/TeamSettingIndex"),
|
||||
loading: Loading,
|
||||
});
|
||||
const Enterprise = Loadable({
|
||||
loader: () => import("../Setting/enterprise"),
|
||||
loading: Loading,
|
||||
});
|
||||
|
||||
function Detail(props){
|
||||
const OIdentifier = props.match.params.OIdentifier;
|
||||
const pathname = props.location.pathname;
|
||||
const {enterpriseOpened} = props;
|
||||
|
||||
const [ detail , setDetail ] = useState(undefined);
|
||||
const [ flag , setFlag ] = useState(true);
|
||||
|
|
@ -55,30 +61,30 @@ function Detail(props){
|
|||
|
||||
useEffect(()=>{
|
||||
if(OIdentifier){
|
||||
getDetail();
|
||||
getDetail(OIdentifier);
|
||||
}
|
||||
},[OIdentifier]);
|
||||
|
||||
function getDetail() {
|
||||
const url = `/organizations/${OIdentifier}.json`;
|
||||
function getDetail(id) {
|
||||
const url = `/organizations/${id}.json`;
|
||||
axios.get(url).then(result=>{
|
||||
if(result && result.data){
|
||||
setDetail(result.data);
|
||||
let keyWords=`${result.data.nickname},${OIdentifier},`;
|
||||
let title= result.data.nickname+'('+ OIdentifier +') ' +result.data.description;
|
||||
setSeoMeta(keyWords,title,title,`/${OIdentifier}`,OIdentifier);
|
||||
let keyWords=`${result.data.nickname},${id},`;
|
||||
let title= result.data.nickname+'('+ id +') ' +result.data.description;
|
||||
setSeoMeta(keyWords,title,title,`/${id}`,id);
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
|
||||
// function updateDetail(name,desc){
|
||||
// let d = detail;
|
||||
// d.name = name;
|
||||
// d.description = desc;
|
||||
// setDetail(d);
|
||||
// }
|
||||
function updateDetail(name,desc){
|
||||
let d = detail;
|
||||
d.name = name;
|
||||
d.description = desc;
|
||||
setDetail(d);
|
||||
}
|
||||
return(
|
||||
<div className="teamDetail">
|
||||
<div className="teamDetail mb50">
|
||||
{
|
||||
detail &&
|
||||
<Cards
|
||||
|
|
@ -86,18 +92,11 @@ function Detail(props){
|
|||
title={detail.nickname}
|
||||
desc={!buttonflag && detail.description}
|
||||
img={detail.avatar_url}
|
||||
rightBtn={
|
||||
<React.Fragment>
|
||||
{flag && !buttonflag && detail.is_admin ?
|
||||
<Link to={`/${OIdentifier}/setting`} className="color-blue ml10 font-14">设置<i className="iconfont icon-shezhi2 ml3 fr"></i></Link>
|
||||
:""}
|
||||
{buttonflag &&
|
||||
<span className="subNavs">
|
||||
<Link to={`/${OIdentifier}/members`} className={pathname ===`/${OIdentifier}/members` ? "active":""}><span>组织成员</span>{detail.num_users && <lable>{detail.num_users}</lable>}</Link>
|
||||
<Link to={`/${OIdentifier}/teams`} className={pathname ===`/${OIdentifier}/teams` ? "active":""}><span>组织团队</span>{detail.num_teams &&<lable>{detail.num_teams}</lable>}</Link>
|
||||
</span>
|
||||
}
|
||||
</React.Fragment>
|
||||
rightBtn={buttonflag && !enterpriseOpened &&
|
||||
<span className="subNavs">
|
||||
<Link to={`/${OIdentifier}/members`} className={pathname ===`/${OIdentifier}/members` ? "active":""}><span>组织成员</span>{detail.num_users && <lable>{detail.num_users}</lable>}</Link>
|
||||
<Link to={`/${OIdentifier}/teams`} className={pathname ===`/${OIdentifier}/teams` ? "active":""}><span>组织团队</span>{detail.num_teams &&<lable>{detail.num_teams}</lable>}</Link>
|
||||
</span>
|
||||
}
|
||||
bottomInfos={
|
||||
!buttonflag && <div>
|
||||
|
|
@ -137,19 +136,20 @@ function Detail(props){
|
|||
}}
|
||||
></Route>
|
||||
|
||||
<Route
|
||||
path="/:OIdentifier/setting"
|
||||
render={(p) => {
|
||||
return <Setting {...props} {...p} organizeDetail={detail} updateFunc={getDetail}/>
|
||||
}}
|
||||
></Route>
|
||||
|
||||
<Route
|
||||
path="/:OIdentifier"
|
||||
path="/:OIdentifier/setting"
|
||||
render={(p) => {
|
||||
return <DetailIndex {...props} {...p} organizeDetail={detail}/>
|
||||
return <Setting {...props} {...p} organizeDetail={detail} updateFunc={updateDetail} enterpriseOpened={enterpriseOpened}/>
|
||||
}}
|
||||
></Route>
|
||||
|
||||
<Route
|
||||
path="/:OIdentifier/enterprise"
|
||||
render={(p) => {
|
||||
return <Enterprise {...props} {...p} organizeDetail={detail}/>
|
||||
}}
|
||||
></Route>
|
||||
|
||||
</Switch>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { Pagination , Spin } from 'antd';
|
|||
import AddMemberBox from './Component/AddMemberBox';
|
||||
|
||||
const limit = 16;
|
||||
function TeamMember({organizeDetail,current_user,history,match}){
|
||||
function TeamMember({organizeDetail,current_user,history,match,enterpriseOpened}){
|
||||
const OIdentifier = match.params.OIdentifier;
|
||||
const [ page , setPage ] = useState(1);
|
||||
const [ total , setTotal ] = useState(0);
|
||||
|
|
@ -46,7 +46,7 @@ function TeamMember({organizeDetail,current_user,history,match}){
|
|||
<WhiteBack style={{marginBottom:"30px",border:'1px solid #eee'}}>
|
||||
<Banner>组织成员
|
||||
{
|
||||
organizeDetail && organizeDetail.is_admin &&
|
||||
!enterpriseOpened && organizeDetail && organizeDetail.is_admin &&
|
||||
<AddMemberBox
|
||||
className="addMemberBtn"
|
||||
orzId={organizeDetail && organizeDetail.id}
|
||||
|
|
|
|||
|
|
@ -136,8 +136,8 @@ class Attachment extends Component {
|
|||
<span className="ml20">{item.filesize}</span>
|
||||
</a>
|
||||
) : (
|
||||
<Link
|
||||
to={`${item.url}`}
|
||||
<a
|
||||
href={`${item.url}`}
|
||||
target="_blank"
|
||||
className="attachment-list-a"
|
||||
download
|
||||
|
|
@ -145,7 +145,7 @@ class Attachment extends Component {
|
|||
<i className="iconfont icon-fujian mr8 paper-clip-color font-12"></i>
|
||||
<span>{item.title}</span>
|
||||
<span className="ml20">{item.filesize}</span>
|
||||
</Link>
|
||||
</a>
|
||||
)}
|
||||
|
||||
{canDelete ? (
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ class Read extends Component {
|
|||
暂仅支持文本格式,不支持图片,excel等不可以txt读取的文件
|
||||
</p> */}
|
||||
<p className="mt10">
|
||||
文件名请使用英文且不得超过{size}MB
|
||||
文件名请使用英文且不得超过{size}MB<span style={{color:"#f07622"}}>(超过{size}MB文件请用git工具上传)</span>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,370 +0,0 @@
|
|||
import React, { useEffect, useCallback, useState } from 'react';
|
||||
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, markdownToTree, treeToMd } from './api';
|
||||
import './Index.scss';
|
||||
import { Base64 } from 'js-base64';
|
||||
|
||||
|
||||
export default Form.create()(({ form, history, showNotification, projectDetail, match, project }) => {
|
||||
const {location:{pathname, search}} = history;
|
||||
const permission = projectDetail && projectDetail.permission && projectDetail.permission !== "Reporter";
|
||||
|
||||
const { getFieldDecorator, validateFields, setFieldsValue } = form;
|
||||
let projectsId = match.params.projectsId;
|
||||
let owner = match.params.owner;
|
||||
let wikiName = ''
|
||||
let key = '';
|
||||
if (!pathname.endsWith('/wiki/add')) {
|
||||
wikiName = pathname.split('/')[4];
|
||||
key = pathname.split('/')[5];
|
||||
}else{
|
||||
key = search.split('=').pop();
|
||||
}
|
||||
|
||||
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){
|
||||
const { author, name} = projectDetail;
|
||||
if(wikiName){
|
||||
document.title = `编辑${wikiName}-维基-${author.name}/${name}`;
|
||||
}else{
|
||||
document.title = `新建维基-${author.name}/${name}`;
|
||||
}
|
||||
}
|
||||
}, [projectDetail, wikiName])
|
||||
|
||||
useEffect(() => {
|
||||
wikiName && project && getWiki({
|
||||
owner,
|
||||
repo: projectsId,
|
||||
pageName: wikiName,
|
||||
projectId: project.id
|
||||
}).then(res => {
|
||||
if (res && res.data) {
|
||||
const content = Base64.decode(res.data.md_content);
|
||||
setContent(content);
|
||||
setFieldsValue({
|
||||
title: search === "?copy" ? `${res.data.name}(复制)` : res.data.name,
|
||||
md_content: content,
|
||||
});
|
||||
}
|
||||
});
|
||||
}, [owner, wikiName]);
|
||||
|
||||
// 加载wiki列表
|
||||
useEffect(() => {
|
||||
project && wikiPages({
|
||||
owner: owner,
|
||||
repo: projectsId,
|
||||
projectId: project.id
|
||||
}).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([]);
|
||||
}
|
||||
});
|
||||
}, [project]);
|
||||
|
||||
const helper = useCallback(
|
||||
(label, name, rules, widget, initialValue, rightComponent) => (
|
||||
<Form.Item label={label} className="mb0">
|
||||
{getFieldDecorator(name, { rules, initialValue, validateFirst: true, })(widget)}
|
||||
{rightComponent}
|
||||
</Form.Item>
|
||||
), []);
|
||||
|
||||
|
||||
function onContentChange(value) {
|
||||
setContent(value);
|
||||
setFieldsValue({
|
||||
md_content: value
|
||||
});
|
||||
};
|
||||
|
||||
// 保存wiki文件,包括新增和修改
|
||||
function saveFile() {
|
||||
if(operateFlag) {
|
||||
setOperateFlag(true);
|
||||
return;
|
||||
};
|
||||
validateFields((err, values) => {
|
||||
if (!err) {
|
||||
const {md_content, title} = values;
|
||||
if (wikiName && search!== "?copy") {
|
||||
updateWiki({
|
||||
owner,
|
||||
repo: projectsId,
|
||||
projectId: project.id,
|
||||
pageName: wikiName,
|
||||
title,
|
||||
message: '',
|
||||
content_base64: Base64.encode(md_content)
|
||||
}).then(res => dealRes(res, title));
|
||||
} else {
|
||||
// 由于wiki在底层是由wikiname标识的md文件,所以对于所有的页面,都不能重名
|
||||
if (Array.isArray(fileArrInit)) {
|
||||
for (const item of fileArrInit) {
|
||||
if (item.name === title) {
|
||||
message.error('不能与已有文件标题相同');
|
||||
setOperateFlag(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
addWiki({
|
||||
owner,
|
||||
repo: projectsId,
|
||||
projectId: project.id,
|
||||
pageName: title,
|
||||
title,
|
||||
message: '',
|
||||
content_base64: Base64.encode(md_content)
|
||||
}).then(res => dealRes(res, title));
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function dealRes(res, title) {
|
||||
if (res && (res.message === "201" || res.message === "200")) {
|
||||
if(wikiName && search!== "?copy"){
|
||||
// 如果有更改wiki文件名,则修改sidebar
|
||||
if(wikiName === title){
|
||||
message.success("操作成功");
|
||||
goBack();
|
||||
}else{
|
||||
// 找到相同的key值node修改title参数,再保存sidebar
|
||||
const newMenuList = menuList;
|
||||
editTitleByKey(newMenuList, key, title);
|
||||
updateSidebar(treeToMd(newMenuList), title);
|
||||
}
|
||||
}else{
|
||||
// 根据路由判断是新增的一级页面还是子页面
|
||||
if(search === "?copy"){
|
||||
// 截断key的前几位,获取父元素的key值
|
||||
const keyArr = key.split('-');
|
||||
keyArr.pop();
|
||||
const list = menuList;
|
||||
addChildrenByKey(list, keyArr.join('-'), title);
|
||||
updateSidebar(treeToMd(list), title);
|
||||
}else if(search.includes("?key")){
|
||||
// 遍历menuList找到相同key的节点,children 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 {
|
||||
setOperateFlag(false);
|
||||
showNotification(res.data || "操作失败");
|
||||
}
|
||||
}
|
||||
|
||||
function updateSidebar(content, newTitle){
|
||||
updateWiki({
|
||||
owner,
|
||||
repo: projectsId,
|
||||
projectId: project.id,
|
||||
pageName: '_Sidebar',
|
||||
title: '_Sidebar',
|
||||
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, 找到相同key值修改title名称
|
||||
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.go(-1);
|
||||
}
|
||||
|
||||
function changeModal(e) {
|
||||
setModal(e.target.checked);
|
||||
if (!e.target.checked) {
|
||||
setModalType();
|
||||
}
|
||||
}
|
||||
|
||||
function changeModalType(e) {
|
||||
let value = e.target.value;
|
||||
if (!content) {
|
||||
setModalContent(value);
|
||||
return;
|
||||
}
|
||||
DelModal({
|
||||
title: '添加模版',
|
||||
contentTitle: `您确定要添加“${value}”模板吗`,
|
||||
content: `此操作会将“${value}”模板替换编辑栏内所有内容,请确认以防文件的丢失`,
|
||||
okText: '确认添加',
|
||||
onOk: () => {
|
||||
setModalType(value);
|
||||
setModal(true);
|
||||
setModalContent(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setModalContent(value) {
|
||||
if (value === '周报') {
|
||||
setContent(weekModal);
|
||||
setFieldsValue({
|
||||
md_content: weekModal
|
||||
});
|
||||
} else if (value === '月报') {
|
||||
setContent(monthModal);
|
||||
setFieldsValue({
|
||||
md_content: monthModal
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className="wiki-main">
|
||||
<div className="wiki-head">
|
||||
<span className="head-title"><span className="back-wiki" onClick={goBack}>Wiki</span> <Icon type="right" /> {wikiName ? search === "?copy" ? '复制' : '编辑' : '新增'}页面</span>
|
||||
</div>
|
||||
|
||||
<div >
|
||||
<h4 className="mt20 mb0">标题</h4>
|
||||
{helper(
|
||||
"",
|
||||
"title",
|
||||
[
|
||||
{ required: true, message: "请输入标题" },
|
||||
{ pattern: /^(?!-).*$/, message: '不能以-开头' }
|
||||
],
|
||||
<Input
|
||||
placeholder={"请输入标题"}
|
||||
className="contact-input"
|
||||
maxLength={50}
|
||||
autoFocus
|
||||
/>
|
||||
)}
|
||||
|
||||
<Form.Item className="mb0 wiki-md">
|
||||
<MDEditor
|
||||
placeholder={"请输入wiki内容"}
|
||||
height={500}
|
||||
mdID={"order-new-description"}
|
||||
initValue={content}
|
||||
onChange={onContentChange}
|
||||
className="mt20 wikiMEDEditor"
|
||||
isFocus={false}
|
||||
></MDEditor>
|
||||
{getFieldDecorator('md_content', {
|
||||
rules: [{ required: true, message: "请输入wiki内容" }],
|
||||
validateFirst: true,
|
||||
initialValue: "欢迎来到Wiki"
|
||||
})(<Input style={{ display: 'none' }} />)}
|
||||
</Form.Item>
|
||||
|
||||
<Checkbox checked={modal} onChange={changeModal} className='mt10'>添加模版</Checkbox>
|
||||
|
||||
<Radio.Group onChange={changeModalType} value={modalType}>
|
||||
<Radio value='周报'>周报</Radio>
|
||||
<Radio value='月报'>月报</Radio>
|
||||
</Radio.Group>
|
||||
|
||||
</div>
|
||||
|
||||
{permission && <Button className="mt25" type="primary" onClick={saveFile}>保存</Button>}
|
||||
</div>
|
||||
|
||||
)
|
||||
})
|
||||
|
|
@ -1,342 +0,0 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Dropdown, Icon, Input, Menu, Select, message, Spin, Modal } from 'antd';
|
||||
import { getImageUrl, timeAgo } from 'educoder';
|
||||
import CopyTool from '../Component/CopyTool';
|
||||
import Welcome from './Welcome';
|
||||
import RenderHtml from "../../components/render-html";
|
||||
|
||||
import { wikiPages, getWiki, parseSidebar, updateWiki, treeToMd, deleteSameKey } from './api';
|
||||
import { httpUrl } 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';
|
||||
import axios from 'axios';
|
||||
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;
|
||||
|
||||
const [fileArrInit, setFileArrInit] = useState(null);
|
||||
const [checkItem, setCheckItem] = useState({});
|
||||
const [itemDetail, setItemDetail] = useState({});
|
||||
|
||||
const [fileArr, setFileArr] = useState([]);
|
||||
const [fileList, setFileList] = 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){
|
||||
const { author, name} = projectDetail;
|
||||
if(Object.keys(checkItem).length === 0){
|
||||
document.title = `维基-${author.name}/${name}`;
|
||||
}else{
|
||||
document.title = `${checkItem.name}-维基-${author.name}/${name}`;
|
||||
}
|
||||
}
|
||||
}, [projectDetail, checkItem])
|
||||
|
||||
// 加载wiki列表
|
||||
useEffect(() => {
|
||||
project && wikiPages({
|
||||
owner: owner,
|
||||
repo: projectsId,
|
||||
projectId: project.id
|
||||
}).then(async res => {
|
||||
if (res && res.message === "200" && isArray(res.data)) {
|
||||
setFileArr(res.data);
|
||||
setFileArrInit(res.data);
|
||||
// 解析sidebar获取目录list
|
||||
const info = await parseSidebar(res.data, {
|
||||
owner: owner,
|
||||
repo: projectsId,
|
||||
projectId: project.id
|
||||
})
|
||||
if(!info){return}
|
||||
const {menuList, sidebar} = info;
|
||||
setSidebar(sidebar)
|
||||
setFileList(menuList);
|
||||
let firstWikiNode = undefined;
|
||||
if(!pathname.endsWith('/wiki')){
|
||||
const {wikiName} = match.params;
|
||||
firstWikiNode = findNodeByName(menuList, wikiName);
|
||||
} else{
|
||||
// 找到目录树中第一个[[页面]]
|
||||
firstWikiNode = findFirstWiki(menuList);
|
||||
}
|
||||
// 当前选中wiki被删除
|
||||
!firstWikiNode && (firstWikiNode = findFirstWiki(menuList));
|
||||
// 设置目录 选中节点初始化
|
||||
setDefaultSelectedKeys([firstWikiNode.key+""]);
|
||||
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(encodeURI(`/${owner}/${projectsId}/wiki/${firstWiki.sub_url}/${firstWikiNode.key}`));
|
||||
} else {
|
||||
setFileArr([]);
|
||||
setFileArrInit([]);
|
||||
}
|
||||
});
|
||||
}, [project, reload])
|
||||
|
||||
// 加载单个wiki详情,sub_url: 后端转义的字符串
|
||||
useEffect(() => {
|
||||
project && checkItem.sub_url && getWiki({
|
||||
owner: owner,
|
||||
repo: projectsId,
|
||||
pageName: checkItem.sub_url,
|
||||
projectId: project.id
|
||||
}).then(res => {
|
||||
if (res && res.message === "200") {
|
||||
setItemDetail(res.data);
|
||||
} else {
|
||||
showNotification("加载失败")
|
||||
}
|
||||
});
|
||||
}, [project, checkItem]);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化页面:递归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 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
|
||||
}
|
||||
|
||||
|
||||
function goUser(login) {
|
||||
window.location.href = `/${login}`;
|
||||
}
|
||||
|
||||
function addFile() {
|
||||
// 新增一级页面
|
||||
history.push(`/${owner}/${projectsId}/wiki/add`);
|
||||
}
|
||||
|
||||
// 新增目录
|
||||
function addMenu(){
|
||||
if(!menuName){
|
||||
setAddMenuError('请输入目录名称');
|
||||
return;
|
||||
}
|
||||
if(new RegExp("^[ ]+$").test(menuName)){
|
||||
setAddMenuError("不能仅输入空格");
|
||||
return;
|
||||
}
|
||||
// 校验一级目录中是否有相同名称
|
||||
const oneMenuList = fileList.filter(item=>!item.title.trim().startsWith('[[')).map(i=>i.title.trim())
|
||||
if(oneMenuList.includes(`- ${menuName}`)){
|
||||
setAddMenuError('不能与已有文件标题相同');
|
||||
return;
|
||||
}
|
||||
|
||||
// 重写fileList,再转换成md内容,最后保存sidebar
|
||||
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',
|
||||
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(params) {
|
||||
history.push(encodeURI(`/${owner}/${projectsId}/wiki/${checkItem.sub_url}/${checkItem.key}/edit${params}`));
|
||||
}
|
||||
|
||||
function preview() {
|
||||
window.open(encodeURI(`/${owner}/${projectsId}/wiki/preview/${project.name}/${project.id}`));
|
||||
}
|
||||
|
||||
// 支持 Markdown,Html,Pdf格式文件
|
||||
const menu = (
|
||||
<Menu >
|
||||
<Menu.Item key="1" onClick={() => { downloadWiki('markdown') }}>
|
||||
Markdown
|
||||
</Menu.Item>
|
||||
<Menu.Item key="2" onClick={() => { downloadWiki('html') }}>
|
||||
HTML
|
||||
</Menu.Item>
|
||||
<Menu.Item key="3" onClick={() => { downloadWiki('pdf') }}>
|
||||
PDF
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
function downloadWiki(type) {
|
||||
window.open(`${httpUrl}/api/wikiExport/wikiExport-wrapper?repoName=${projectsId}&owner=${owner}&type=${type}&projectName=${project.name}&projectId=${project.id}`);
|
||||
}
|
||||
|
||||
function changeitem(item){
|
||||
const {name, key} = item;
|
||||
const wiki = fileArrInit.filter(item => { return item.name == name })[0];
|
||||
if(!wiki){
|
||||
// 不存在的wiki,修正sidebarfindSameKeyByDel
|
||||
const menuListByDel = deleteSameKey(fileList, key);
|
||||
updateWikiFun(menuListByDel);
|
||||
message.success("不存在的wiki")
|
||||
return
|
||||
}
|
||||
setCheckItem({
|
||||
...wiki,
|
||||
key
|
||||
})
|
||||
history.push(encodeURI(`/${owner}/${projectsId}/wiki/${name}/${key}`));
|
||||
}
|
||||
|
||||
return (
|
||||
<Spin spinning={!fileArrInit} className="opacitySpin">
|
||||
{fileArrInit && fileArrInit.length ?
|
||||
<div className="wiki-main">
|
||||
<div className="wiki-head">
|
||||
|
||||
<span className="head-title">
|
||||
{
|
||||
permission ?
|
||||
<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 && <UploadWiki {...props} fileList={fileList} setFileList={setFileList} fileArrInit={fileArrInit} setReload={setReload}/>
|
||||
}
|
||||
<Dropdown overlay={menu}>
|
||||
<Button type="default" className="ml10">导出<Icon type="caret-down" /></Button>
|
||||
</Dropdown>
|
||||
<Button type="default" className="ml10" onClick={preview}>预览</Button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="wiki-body">
|
||||
<div className="wiki-nav-parent">
|
||||
<div className='wiki-nav-scroll'>
|
||||
<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}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{checkItem.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} />
|
||||
<CopyTool className="copy-wiki" inputId="wikiUrl" />
|
||||
</InputGroup>}
|
||||
|
||||
</div>
|
||||
|
||||
<div className="wiki-content">
|
||||
<div className="wiki-content-head">
|
||||
<div className="wiki-content-head-left">
|
||||
<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 className='font-14'>{itemDetail.userName}</span>
|
||||
</span>
|
||||
<span className="time-ago font-14">上次修改于{checkItem.commit ? timeAgo(checkItem.commit.author.date) : '刚刚'}</span>
|
||||
</div>
|
||||
{permission && <Button type="primary" ghost onClick={()=>{goEdit(`?copy`)}} className='mr10'>复制</Button>}
|
||||
{permission && <Button type="primary" onClick={()=>{goEdit("")}}>编辑</Button>}
|
||||
</div>
|
||||
|
||||
{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 >
|
||||
:
|
||||
<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} autoFocus></Input>
|
||||
<div className='wikiAddMenuError mt5'>{addMenuError}</div>
|
||||
</Modal>
|
||||
</Spin>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,424 +0,0 @@
|
|||
$wikiColor: #466aff;
|
||||
$primaryBtnHover: #6482ff;
|
||||
body {
|
||||
width: 100% !important;
|
||||
}
|
||||
.ant-spin-nested-loading > div > .ant-spin.opacitySpin{
|
||||
max-height: 100vh;
|
||||
background: rgba(255,255,255,1);
|
||||
}
|
||||
.wiki-main {
|
||||
width: 1200px;
|
||||
min-height: 400px;
|
||||
margin: 20px auto 60px;
|
||||
|
||||
.ant-btn-primary {
|
||||
background-color: $wikiColor;
|
||||
border-color: $wikiColor;
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
background-color: $primaryBtnHover;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-btn-default:hover,
|
||||
.ant-btn-default:active,
|
||||
.ant-btn-default:focus {
|
||||
background: #f3f4f6;
|
||||
color: #333;
|
||||
border-color: #d0d0d0;
|
||||
}
|
||||
|
||||
.wiki-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
height: 64px;
|
||||
background: #fafcff;
|
||||
box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.13);
|
||||
border-radius: 4px;
|
||||
border-bottom-left-radius: 0;
|
||||
.ant-btn .anticon {
|
||||
margin: 0 -3px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.head-title {
|
||||
font-size: 20px;
|
||||
color: #05101a;
|
||||
line-height: 30px;
|
||||
font-weight: 500;
|
||||
|
||||
.anticon-right {
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
.back-wiki {
|
||||
color: $wikiColor;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: $primaryBtnHover;
|
||||
}
|
||||
}
|
||||
|
||||
.head-log-middle {
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
margin-right: 0.35rem;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.head-log-small {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
margin-right: 0.35rem;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.user-box {
|
||||
font-size: 12px;
|
||||
font-family: "PingFangSC-Medium";
|
||||
color: $wikiColor;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: $primaryBtnHover;
|
||||
}
|
||||
.head-log-small {
|
||||
position: relative;
|
||||
top: -2px;
|
||||
}
|
||||
}
|
||||
|
||||
.time-ago {
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
letter-spacing: 0;
|
||||
line-height: 17px;
|
||||
font-weight: 400;
|
||||
font-family: "PingFangSC-Regular";
|
||||
}
|
||||
|
||||
// .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 {
|
||||
padding: 0 14px;
|
||||
.ant-input-suffix {
|
||||
right: 20px;
|
||||
}
|
||||
&:hover .ant-input,
|
||||
&:focus .ant-input {
|
||||
border-color: $wikiColor !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wiki-nav-parent {
|
||||
width: 280px;
|
||||
flex: none;
|
||||
// .wiki-nav-scroll{
|
||||
// width: 280px;
|
||||
// overflow-y: scroll;
|
||||
// }
|
||||
}
|
||||
|
||||
.ant-form-item-children .ant-input:hover {
|
||||
border-color: $wikiColor !important;
|
||||
}
|
||||
|
||||
.ant-checkbox-checked .ant-checkbox-inner {
|
||||
background-color: $wikiColor;
|
||||
border-color: $wikiColor;
|
||||
}
|
||||
.ant-radio-checked .ant-radio-inner,
|
||||
.ant-radio-checked::after {
|
||||
border-color: $wikiColor;
|
||||
}
|
||||
|
||||
.ant-radio-inner::after {
|
||||
background-color: $wikiColor;
|
||||
}
|
||||
|
||||
.ant-radio-group {
|
||||
display: block;
|
||||
margin: 10px 0 0 30px;
|
||||
}
|
||||
|
||||
.ant-radio-wrapper:hover .ant-radio,
|
||||
.ant-radio:hover .ant-radio-inner,
|
||||
.ant-radio-input:focus + .ant-radio-inner {
|
||||
border-color: $wikiColor;
|
||||
}
|
||||
}
|
||||
|
||||
#wikiUrl:focus {
|
||||
border-right: 1px solid #d9d9d9 !important;
|
||||
}
|
||||
|
||||
.wiki-body {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.wiki-content {
|
||||
flex: auto;
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
.wiki-content-detail {
|
||||
padding: 0 20px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.ant-input-group.ant-input-group-compact.copy-url {
|
||||
display: flex;
|
||||
margin-top: 20px;
|
||||
.ant-select-selection__rendered {
|
||||
margin: 0 14px 0 5px;
|
||||
width: 3rem;
|
||||
font-size: 12px;
|
||||
}
|
||||
.ant-select-arrow {
|
||||
right: 4px;
|
||||
}
|
||||
.ant-input {
|
||||
font-size: 12px;
|
||||
font-family: "PingFangSC-Regular";
|
||||
color: #666;
|
||||
letter-spacing: 0;
|
||||
font-weight: 400;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.copy-wiki {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 5px;
|
||||
background: #fff;
|
||||
font-size: 1rem !important;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-left: 0;
|
||||
color: $wikiColor;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.wiki-url-type {
|
||||
.ant-select-dropdown-menu-item {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.wiki-nav {
|
||||
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;
|
||||
}
|
||||
.ant-tree.ant-tree-directory > li.ant-tree-treenode-selected > span.ant-tree-switcher, .ant-tree.ant-tree-directory .ant-tree-child-tree > li.ant-tree-treenode-selected > span.ant-tree-switcher{
|
||||
color: $primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
.wiki-nav-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 50px;
|
||||
padding: 0 10px 0 10px;
|
||||
font-size: 14px;
|
||||
letter-spacing: 0;
|
||||
font-weight: 400;
|
||||
font-family: "PingFangSC-Regular";
|
||||
border-bottom: 1px solid #eee;
|
||||
line-height: 16px;
|
||||
cursor: pointer;
|
||||
|
||||
.delete-title-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #fbfbfb;
|
||||
.delete-title-icon {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wiki-nav-title-parent {
|
||||
padding: 0 14px;
|
||||
&:hover {
|
||||
background: #fbfbfb;
|
||||
}
|
||||
}
|
||||
|
||||
.wiki-nav-title.active {
|
||||
color: $wikiColor;
|
||||
}
|
||||
|
||||
.wiki-content-head {
|
||||
margin: 20px 0 20px 20px;
|
||||
padding: 0 20px 20px 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.wiki-content-head-left {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.nav-title-left {
|
||||
display: inline-flex;
|
||||
max-width: 90%;
|
||||
svg {
|
||||
margin-right: 0.5rem;
|
||||
flex: none;
|
||||
}
|
||||
.nav-title-left-text {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.wiki-detail-title {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
// 预览页面样式文件
|
||||
.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: 95%;
|
||||
}
|
||||
.ant-btn-primary {
|
||||
background-color: $wikiColor;
|
||||
border-color: $wikiColor;
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
background-color: $primaryBtnHover;
|
||||
}
|
||||
}
|
||||
.preview-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 12rem 0 2rem;
|
||||
width: 100%;
|
||||
height: 8vh;
|
||||
background: rgb(39, 47, 76);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.preview-head-left {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
|
||||
.icon-wendangyulan_icon {
|
||||
font-size: 24px !important;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-head-right {
|
||||
display: flex;
|
||||
|
||||
.copy-url {
|
||||
margin-top: 0;
|
||||
}
|
||||
.copy-desc {
|
||||
width: 6rem;
|
||||
padding-top: 1px;
|
||||
flex:none
|
||||
}
|
||||
}
|
||||
|
||||
.wiki-nav-title {
|
||||
padding: 0 10px 0 40px;
|
||||
}
|
||||
.wiki-nav-title.active {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
|
||||
.wiki-content-head {
|
||||
padding: 10px 20px 10px 20px;
|
||||
}
|
||||
|
||||
.wiki-nav {
|
||||
padding: 20px 0;
|
||||
width: 20vw;
|
||||
border-bottom: 0;
|
||||
height: 92vh;
|
||||
}
|
||||
.wiki-content-detail {
|
||||
padding: 0 40px;
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-btn:hover {
|
||||
background: #f3f4f6;
|
||||
color: #333;
|
||||
border-color: #d0d0d0;
|
||||
}
|
||||
}
|
||||
.wikiMEDEditor .editormd-dialog-footer{
|
||||
padding: 0;
|
||||
}
|
||||
.wikiAddMenu{
|
||||
&.myself-modal .ant-modal-body{
|
||||
text-align: left;
|
||||
padding: 40px 30px;
|
||||
}
|
||||
}
|
||||
.wikiAddMenuError{
|
||||
color: red;
|
||||
}
|
||||
.expendedAllAction{
|
||||
cursor: pointer;
|
||||
text-align: right;
|
||||
padding-right: 15px;
|
||||
color: $primary-color;
|
||||
border-bottom: 1px solid #d0d0d0;
|
||||
}
|
||||
|
|
@ -1,181 +0,0 @@
|
|||
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, 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 } = 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 && checkItem.name){
|
||||
document.title = `${checkItem.name}-维基预览`
|
||||
}
|
||||
}, [checkItem])
|
||||
|
||||
useEffect(() => {
|
||||
projectsId && wikiPages({
|
||||
owner: owner,
|
||||
repo: projectsId,
|
||||
projectId: projectId,
|
||||
}).then(async res => {
|
||||
if (res && res.message === "200") {
|
||||
setFileArr(res.data);
|
||||
// 解析sidebar获取目录list
|
||||
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]);
|
||||
};
|
||||
} else {
|
||||
showNotification("加载失败")
|
||||
}
|
||||
});
|
||||
}, [])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
projectsId && checkItem.sub_url && getWiki({
|
||||
owner: owner,
|
||||
repo: projectsId,
|
||||
pageName: checkItem.sub_url,
|
||||
projectId: projectId,
|
||||
}).then(res => {
|
||||
if (res && res.message === "200") {
|
||||
setItemDetail(res.data);
|
||||
} else {
|
||||
showNotification("加载失败")
|
||||
}
|
||||
});
|
||||
}, [checkItem]);
|
||||
|
||||
function goBack() {
|
||||
history.push(`/${owner}/${projectsId}/wiki`);
|
||||
}
|
||||
|
||||
// 支持 Markdown,Html,Pdf格式文件
|
||||
const menu = (
|
||||
<Menu >
|
||||
<Menu.Item key="1" onClick={() => { downloadWiki('markdown') }}>
|
||||
Markdown
|
||||
</Menu.Item>
|
||||
<Menu.Item key="2" onClick={() => { downloadWiki('html') }}>
|
||||
HTML
|
||||
</Menu.Item>
|
||||
<Menu.Item key="3" onClick={() => { downloadWiki('pdf') }}>
|
||||
PDF
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
function downloadWiki(type) {
|
||||
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 {isFile, key, titleStr} = event.node.props.dataRef;
|
||||
if(isFile){
|
||||
const wiki = fileArr.filter(item => { return item.name == titleStr })[0];
|
||||
setCheckItem({
|
||||
...wiki,
|
||||
key
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="wiki-preview">
|
||||
<div className="preview-head">
|
||||
<div className="preview-head-left" onClick={goBack}>
|
||||
<i className="iconfont icon-wendangyulan_icon mr3"></i>
|
||||
<span className="ml10">{projectName}</span>
|
||||
</div>
|
||||
<div className="preview-head-right">
|
||||
<span className="copy-desc font-16">克隆地址</span>
|
||||
{
|
||||
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' ? fileArr[0].wiki_clone_link.https : fileArr[0].wiki_clone_link.ssh} />
|
||||
<CopyTool className="copy-wiki" inputId="wikiUrl"/>
|
||||
</InputGroup> : ''
|
||||
}
|
||||
|
||||
<Dropdown overlay={menu}>
|
||||
<Button className="ml10">导出<Icon type="caret-down" /></Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="wiki-body">
|
||||
<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" >
|
||||
<i className="iconfont icon-wenjianjia2 mr3"></i>
|
||||
<span className="nav-title-left-text">{item.name}</span>
|
||||
</span>
|
||||
</div>
|
||||
})
|
||||
} */}
|
||||
{/* 读sidebar转换成目录树 */}
|
||||
{fileList && fileList.length ? <DirectoryTree onSelect={selectTree} defaultExpandAll>
|
||||
{renderTreeNodes(fileList)}
|
||||
</DirectoryTree> : ''}
|
||||
</div>
|
||||
|
||||
<div className="wiki-content previewContent">
|
||||
<div className="wiki-content-head">
|
||||
<h3 className='mt10 pb10'>{checkItem.name}</h3>
|
||||
</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>
|
||||
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
.welcome-main {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 1200px;
|
||||
min-height: 400px;
|
||||
padding: 20px;
|
||||
margin: 20px auto;
|
||||
background: #fafcff;
|
||||
font-family: "PingFangSC-Medium";
|
||||
border-radius: 4px;
|
||||
border: 1px solid rgba(42, 97, 255, 0.23);
|
||||
}
|
||||
.welcome-main .icon-huanying_icon {
|
||||
font-size: 48px !important;
|
||||
font-weight: 700;
|
||||
}
|
||||
.welcome-main .welcome-title {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin: 10px 0;
|
||||
font-size: 26px;
|
||||
color: #333333;
|
||||
font-weight: 500;
|
||||
}
|
||||
.welcome-main .wiki-title {
|
||||
display: inline-block;
|
||||
max-width: 20em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.welcome-main .welcome-content {
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
font-weight: 400;
|
||||
}
|
||||
.welcome-main .wiki-line {
|
||||
margin: 50px 0 40px;
|
||||
width: 400px;
|
||||
height: 1px;
|
||||
background: #eeeeee;
|
||||
}
|
||||
.welcome-main .welcome-des {
|
||||
font-size: 16px;
|
||||
color: #333333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=index.css.map */
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Button } from 'antd';
|
||||
// import { addWiki } from '../api';
|
||||
import './index.scss';
|
||||
import UploadWiki from '../components/uploadWiki';
|
||||
|
||||
export default (props) => {
|
||||
const { project, isManager, history, showNotification, match, reloadList } = props;
|
||||
|
||||
let projectsId = match.params.projectsId;
|
||||
let owner = match.params.owner;
|
||||
|
||||
function addFile() {
|
||||
history.push(`/${owner}/${projectsId}/wiki/add`);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="welcome-main">
|
||||
<i className="iconfont icon-huanying_icon" ></i>
|
||||
<p className="welcome-title">欢迎使用
|
||||
<span className="wiki-title">
|
||||
{project && project.name}
|
||||
</span>
|
||||
Wiki</p>
|
||||
<p className="welcome-content">Wiki主要是您项目的产品设计、文档描述、注释等等</p>
|
||||
<div className="wiki-line"></div>
|
||||
|
||||
{
|
||||
isManager ? <div>
|
||||
<Button type="primary" onClick={addFile}>创建Wiki文档</Button>
|
||||
<UploadWiki {...props} butClass="welcomeUploadBtn"/>
|
||||
</div> : <p className="welcome-des">该项目暂时没有创建Wiki</p>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
.welcome-main {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 1200px;
|
||||
min-height: 400px;
|
||||
padding: 20px;
|
||||
margin: 20px auto;
|
||||
background: rgba(250, 252, 255, 1);
|
||||
font-family: "PingFangSC-Medium";
|
||||
border-radius: 4px;
|
||||
border: 1px solid rgba(42, 97, 255, 0.23);
|
||||
|
||||
.icon-huanying_icon {
|
||||
font-size: 48px !important;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.welcome-title {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin: 10px 0;
|
||||
font-size: 26px;
|
||||
color: #333333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.wiki-title {
|
||||
display: inline-block;
|
||||
max-width: 20em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.welcome-content {
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.wiki-line {
|
||||
margin: 50px 0 40px;
|
||||
width: 400px;
|
||||
height: 1px;
|
||||
background: #eeeeee;
|
||||
}
|
||||
|
||||
.welcome-des {
|
||||
font-size: 16px;
|
||||
color: #333333;
|
||||
font-weight: 500;
|
||||
}
|
||||
.welcomeUploadBtn{
|
||||
color: $primary-color;
|
||||
border-color: $primary-color;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import { Base64 } from 'js-base64';
|
||||
import fetch from './fetch';
|
||||
import fetch, {httpUrl} from './fetch';
|
||||
|
||||
// 获取wiki列表
|
||||
export function wikiPages(params) {
|
||||
export function getWikiPages(params) {
|
||||
return fetch({
|
||||
url: '/api/wiki/wikiPages',
|
||||
url: '/api/wiki/open/wikiPages',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
|
|
@ -13,25 +13,31 @@ export function wikiPages(params) {
|
|||
// 获取单个wiki
|
||||
export function getWiki(params) {
|
||||
return fetch({
|
||||
url: '/api/wiki/getWiki',
|
||||
url: '/api/wiki/open/getWiki',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
//新增wiki
|
||||
export function addWiki(data) {
|
||||
export function addWiki(repoInfo, title, content){
|
||||
return fetch({
|
||||
url: '/api/wiki/createWiki',
|
||||
url: '/api/wiki/open/createWiki',
|
||||
method: 'post',
|
||||
data: data
|
||||
data: {
|
||||
...repoInfo,
|
||||
pageName: title,
|
||||
title: title,
|
||||
message: '',
|
||||
content_base64: Base64.encode(content)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//更新wiki
|
||||
export function updateWiki(data) {
|
||||
return fetch({
|
||||
url: '/api/wiki/updateWiki',
|
||||
url: '/api/wiki/open/updateWiki',
|
||||
method: 'PUT',
|
||||
data: data
|
||||
});
|
||||
|
|
@ -40,103 +46,133 @@ export function updateWiki(data) {
|
|||
//删除wiki
|
||||
export function deleteWiki(data) {
|
||||
return fetch({
|
||||
url: '/api/wiki/deleteWiki',
|
||||
url: '/api/wiki/open/deleteWiki',
|
||||
method: 'DELETE',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// 初始化sidebar文件
|
||||
export async function parseSidebar(wikiList, info){
|
||||
const sidebarArr = wikiList.filter(item => item.name === '_Sidebar');
|
||||
let ReInfo = undefined;
|
||||
if(sidebarArr.length){
|
||||
// 有sidebar文件,执行解析
|
||||
// 通过getWiki获取sidebar文件内容
|
||||
await getWiki({
|
||||
...info,
|
||||
pageName: '_Sidebar'
|
||||
}).then(res=>{
|
||||
if(res && res.message === "200"){
|
||||
const sidebarCont = Base64.decode(res.data.md_content);
|
||||
const list = wikiList.filter(item=> sidebarCont.indexOf(`[[${item.name}]]`) === -1 && item.name !== "_Sidebar");
|
||||
const wikiSidebar = list.map(item=>`\n[[${item.name}]]`).join("");
|
||||
if(list.length){
|
||||
// sidebar存在丢失的wiki
|
||||
updateWiki({
|
||||
...info,
|
||||
pageName: '_Sidebar',
|
||||
title: '_Sidebar',
|
||||
message: '',
|
||||
content_base64: Base64.encode(`${sidebarCont}${wikiSidebar}`)
|
||||
})
|
||||
}
|
||||
ReInfo={
|
||||
menuList: markdownToTree(list.length ? `${sidebarCont}${wikiSidebar}` : sidebarCont, wikiList),
|
||||
sidebar: sidebarCont
|
||||
}
|
||||
}
|
||||
})
|
||||
}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,
|
||||
// 导出wiki
|
||||
export function exportWiki(params){
|
||||
return `${httpUrl}/api/wiki/open/wikiExport-wrapper${params}`;
|
||||
}
|
||||
|
||||
// 修改sidebar文件
|
||||
export function updateSidebar(repoInfo, sidebarContent){
|
||||
return updateWiki({
|
||||
...repoInfo,
|
||||
pageName: '_Sidebar',
|
||||
title: '_Sidebar',
|
||||
message: '',
|
||||
content_base64: Base64.encode(sidebarContent)
|
||||
})
|
||||
}
|
||||
|
||||
// 检查有wiki但是sidebar没有目录的情况
|
||||
function updateSidebarByCheckWikiInSidebar(repoInfo, wikiList, sidebar){
|
||||
const list = wikiList.filter(item=> sidebar.indexOf(`[[${item.title}]]`) === -1 && item.title !== "_Sidebar");
|
||||
const wikiSidebar = list.map(item=>`\n[[${item.title}]]`).join("");
|
||||
if(list.length){
|
||||
// sidebar存在丢失的wiki
|
||||
updateWiki({
|
||||
...repoInfo,
|
||||
pageName: '_Sidebar',
|
||||
title: '_Sidebar',
|
||||
message: '',
|
||||
content_base64: Base64.encode(wikiSidebarStr)
|
||||
content_base64: Base64.encode(`${sidebar}${wikiSidebar}`)
|
||||
})
|
||||
ReInfo={
|
||||
menuList: markdownToTree(wikiSidebarStr, wikiList),
|
||||
sidebar: wikiSidebarStr
|
||||
}
|
||||
}
|
||||
return ReInfo;
|
||||
return list.length ? `${sidebar}${wikiSidebar}` : sidebar
|
||||
}
|
||||
|
||||
export function markdownToTree(markdownText, wikiList){
|
||||
const lines = markdownText.split('\n');
|
||||
export async function getMenuListAndSidebarData(repoInfo){
|
||||
// deleteWiki({...repoInfo, pageName: "_Sidebar"})
|
||||
// return
|
||||
async function getWikis(){
|
||||
await getWikiPages(repoInfo).then(res=>{
|
||||
if(res && res.code === 200){
|
||||
info.wikiPages = res.data
|
||||
}
|
||||
})
|
||||
}
|
||||
const info = {
|
||||
wikiPages: [],
|
||||
menuList: [],
|
||||
sidebar: ''
|
||||
};
|
||||
await getWikis();
|
||||
const {wikiPages} = info;
|
||||
if(!wikiPages.length){
|
||||
// 未创建过wiki
|
||||
return info;
|
||||
}
|
||||
const sidebarIndex = wikiPages.findIndex(item=>item.title === "_Sidebar");
|
||||
// 判断有无sidebar文件,有转化sidebar内容为目录树,没有则根据pages创建平级关系的sidebar
|
||||
if(sidebarIndex > -1){
|
||||
// 转化sidebar内容为目录树
|
||||
await getWiki({
|
||||
...repoInfo,
|
||||
pageName: '_Sidebar'
|
||||
}).then(res=>{
|
||||
if(res && res.code === 200){
|
||||
const sidebarContent = updateSidebarByCheckWikiInSidebar(repoInfo, wikiPages, Base64.decode(res.data.content_base64));
|
||||
info.menuList = markdownToTree(sidebarContent, wikiPages);
|
||||
info.sidebar = sidebarContent;
|
||||
}
|
||||
})
|
||||
}else{
|
||||
// 没有sidebar文件,根据用户的目录结构进行初始化(\n[[item.title]]结构)
|
||||
const wikiArr = [];
|
||||
wikiPages.map(item=>{
|
||||
wikiArr.push(`[[${item.title}]]`)
|
||||
});
|
||||
const sidebarContent = wikiArr.join('\n');
|
||||
await addWiki(repoInfo, "_Sidebar", sidebarContent)
|
||||
info.menuList = markdownToTree(sidebarContent, wikiPages);
|
||||
info.sidebar = sidebarContent;
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
export function markdownToTree(sidebarContent, wikiPages){
|
||||
// 构建{wikiName: wikiName转义得到的地址(java端做了特殊处理,所以不能用encodeURIComponent等方法)}
|
||||
const wikiNameAndSubUrl = {}
|
||||
wikiPages && wikiPages.map(item=>{
|
||||
return wikiNameAndSubUrl[item.title] = item.sub_url
|
||||
})
|
||||
|
||||
const lines = sidebarContent.split('\n');
|
||||
let key = -1;
|
||||
// stack: [{一级目录},{二级目录}]
|
||||
let stack = [];
|
||||
let stack = []; // stack: [{一级目录},{二级目录}]
|
||||
|
||||
let root = {
|
||||
title: "root",
|
||||
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)
|
||||
// level:空格的个数(层级)
|
||||
const level = item.search(/\S/);
|
||||
const node = {
|
||||
// 带空格+[[]]或者- 标识
|
||||
title: item,
|
||||
children: [],
|
||||
key: undefined,
|
||||
key: '-1',
|
||||
// 纯内容
|
||||
titleStr,
|
||||
// 编码后的title,获取wiki内容用title_sub
|
||||
title_sub: wikiNameAndSubUrl[titleStr],
|
||||
// 是否是wiki文件
|
||||
isFile: isFile
|
||||
isFile: isFile,
|
||||
// 层级(空格的个数)
|
||||
level
|
||||
}
|
||||
// level:空格的个数(层级)
|
||||
const level = item.search(/\S/);
|
||||
if(!level){
|
||||
node.key = key+"";
|
||||
node["key"] = key+"";
|
||||
key++;
|
||||
// push根目录
|
||||
stack = [];
|
||||
|
|
@ -155,14 +191,14 @@ export function markdownToTree(markdownText, wikiList){
|
|||
}else{
|
||||
// 更深的层级 找key = stack最后一个item的key
|
||||
const parentNodeByRoot = findNodeByKey(rootNode.children, parentNode.key);
|
||||
node.key = `${parentNodeByRoot.key}-${parentNodeByRoot.children.length}`
|
||||
node.key = `${parentNodeByRoot && parentNodeByRoot.key}-${parentNodeByRoot && parentNodeByRoot.children.length}`
|
||||
parentNodeByRoot && parentNodeByRoot.children.push(node);
|
||||
}
|
||||
// 下一个节点的空格个数(层级)
|
||||
const nextNodeLevel = lines[index+1] && lines[index+1].search(/\S/);
|
||||
if(nextNodeLevel>level && (title.startsWith('- ') || title.startsWith('* '))){
|
||||
if(+nextNodeLevel>level && (title.startsWith('- ') || title.startsWith('* '))){
|
||||
stack.push(node);
|
||||
}else if(nextNodeLevel<level){
|
||||
}else if(+nextNodeLevel<level){
|
||||
stack.pop()
|
||||
}
|
||||
}
|
||||
|
|
@ -170,6 +206,81 @@ export function markdownToTree(markdownText, wikiList){
|
|||
return root.children;
|
||||
}
|
||||
|
||||
// 目录结构转md内容
|
||||
export function treeToMd(menuList){
|
||||
function readList(list, readChildren){
|
||||
readChildren.map(item=>{
|
||||
list.push(item.title)
|
||||
readList(list, item.children)
|
||||
})
|
||||
}
|
||||
|
||||
const list = [];
|
||||
readList(list, menuList)
|
||||
return list.join('\n');
|
||||
}
|
||||
|
||||
// 检查wiki文件是否重名
|
||||
export function findSamaNameWiki(list=[], name){
|
||||
return list && list.find(item=>{return item.title === name})
|
||||
}
|
||||
|
||||
// 递归menuList, 找到相同key值,push子节点
|
||||
export 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: [],
|
||||
key: '',
|
||||
});
|
||||
return true;
|
||||
}else if (element.children.length) {
|
||||
let result = addChildrenByKey(element.children, key, name);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 遍历目录树,找到第一个wiki / 通过wiki名称找节点
|
||||
export function findNodeByFirstOrContrastName(list, name){
|
||||
for (let index = 0; index < list.length; index++) {
|
||||
const element = list[index];
|
||||
if (name ? name === element.titleStr : element.isFile) {
|
||||
return element;
|
||||
}else if (element.children.length) {
|
||||
let result = findNodeByFirstOrContrastName(element.children, name);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 递归menuList, 找到相同key值修改title名称
|
||||
// newTitle: [[name]] / - name
|
||||
export function editMenuTitleByKey(list, key, newTitle){
|
||||
for (let index = 0; index < list.length; index++) {
|
||||
const element = list[index];
|
||||
if (element.key == key) {
|
||||
element.title = newTitle;
|
||||
return true;
|
||||
}else if (element.children.length) {
|
||||
let result = editMenuTitleByKey(element.children, key, newTitle);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 通过key找节点
|
||||
export function findNodeByKey(list, key){
|
||||
for (let index = 0; index < list.length; index++) {
|
||||
const element = list[index];
|
||||
|
|
@ -185,7 +296,7 @@ export function findNodeByKey(list, key){
|
|||
return undefined;
|
||||
}
|
||||
|
||||
// 找到children中有相同key的数组
|
||||
// 遍历目录树, 找到children中有相同key的数组
|
||||
export function findBrotherNodesByKey(list, key){
|
||||
for (let index = 0; index < list.length; index++) {
|
||||
const element = list[index];
|
||||
|
|
@ -201,75 +312,14 @@ export function findBrotherNodesByKey(list, key){
|
|||
return [];
|
||||
}
|
||||
|
||||
// 目录结构转md内容
|
||||
export function treeToMd(treeList){
|
||||
const list = []
|
||||
readList(list, treeList)
|
||||
return list.join('\n');
|
||||
}
|
||||
|
||||
function readList(list, readChildren){
|
||||
readChildren.map(item=>{
|
||||
list.push(item.title)
|
||||
readList(list, item.children)
|
||||
})
|
||||
}
|
||||
|
||||
export function generateList(list) {
|
||||
const dataList = [];
|
||||
generate(list);
|
||||
|
||||
function generate(data){
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const node = data[i];
|
||||
const { key, titleStr } = node;
|
||||
dataList.push({ key, title: titleStr });
|
||||
if (node.children) {
|
||||
generate(node.children);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dataList;
|
||||
};
|
||||
|
||||
export const getParentKey = (key, tree) => {
|
||||
let parentKey;
|
||||
for (let i = 0; i < tree.length; i++) {
|
||||
const node = tree[i];
|
||||
if (node.children) {
|
||||
if (node.children.some(item => item.key === key)) {
|
||||
parentKey = node.key;
|
||||
} else if (getParentKey(key, node.children)) {
|
||||
parentKey = getParentKey(key, node.children);
|
||||
}
|
||||
}
|
||||
}
|
||||
return parentKey;
|
||||
};
|
||||
|
||||
export function findFirstWiki(list){
|
||||
for (let index = 0; index < list.length; index++) {
|
||||
const element = list[index];
|
||||
if (element.isFile) {
|
||||
return element;
|
||||
}else if (element.children.length) {
|
||||
let result = findFirstWiki(element.children);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 递归过滤相同key值的节点
|
||||
export function deleteSameKey(list, key){
|
||||
// 递归menuList, 删除相同key值的节点
|
||||
export function findSameKeyByDel(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)
|
||||
item.children = findSameKeyByDel(item.children , key)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,244 +0,0 @@
|
|||
import * as React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Tree } from 'antd';
|
||||
import omit from 'omit.js';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { conductExpandParent, convertTreeToEntities } from 'rc-tree/lib/util';
|
||||
import { polyfill } from 'react-lifecycles-compat';
|
||||
import { ConfigConsumer } from '../../../../../node_modules/antd/lib/config-provider';
|
||||
import {
|
||||
calcRangeKeys,
|
||||
getFullKeyList,
|
||||
convertDirectoryKeysToNodes,
|
||||
getFullKeyListByTreeData,
|
||||
} from '../../../../../node_modules/antd/lib/tree/util';
|
||||
|
||||
import { FolderSvg, FolderExpandSvg, FileSvg } from '../../Svg';
|
||||
import './index.scss';
|
||||
|
||||
|
||||
function getIcon(props) {
|
||||
// console.log(props);
|
||||
// console.log(props.expanded);
|
||||
const { isLeaf, expanded ,selected} = props;
|
||||
if (isLeaf) {
|
||||
return <FileSvg color={selected ? "#2A61FF" : "#666"}/>;
|
||||
}
|
||||
return expanded ? <FolderExpandSvg color={"#666"}/> : <FolderSvg color={"#666"} />
|
||||
// return <FolderSvg color={expanded ? "#2A61FF" : "#666"} />
|
||||
}
|
||||
|
||||
class DirectoryTree extends React.Component {
|
||||
static defaultProps = {
|
||||
showIcon: true,
|
||||
expandAction: 'click',
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(nextProps) {
|
||||
const newState = {};
|
||||
if ('expandedKeys' in nextProps) {
|
||||
newState.expandedKeys = nextProps.expandedKeys;
|
||||
}
|
||||
if ('selectedKeys' in nextProps) {
|
||||
newState.selectedKeys = nextProps.selectedKeys;
|
||||
}
|
||||
return newState;
|
||||
}
|
||||
|
||||
// state: DirectoryTreeState;
|
||||
|
||||
// tree: Tree;
|
||||
|
||||
// onDebounceExpand: (event: React.MouseEvent<HTMLElement>, node: AntTreeNode) => void;
|
||||
|
||||
// Shift click usage
|
||||
// lastSelectedKey?: string;
|
||||
|
||||
// cachedSelectedKeys?: string[];
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const {
|
||||
defaultExpandAll,
|
||||
defaultExpandParent,
|
||||
expandedKeys,
|
||||
defaultExpandedKeys,
|
||||
children,
|
||||
} = props;
|
||||
const { keyEntities } = convertTreeToEntities(children);
|
||||
|
||||
// Selected keys
|
||||
this.state = {
|
||||
selectedKeys: props.selectedKeys || props.defaultSelectedKeys || [],
|
||||
};
|
||||
|
||||
// Expanded keys
|
||||
if (defaultExpandAll) {
|
||||
if (props.treeData) {
|
||||
this.state.expandedKeys = getFullKeyListByTreeData(props.treeData);
|
||||
} else {
|
||||
this.state.expandedKeys = getFullKeyList(props.children);
|
||||
}
|
||||
} else if (defaultExpandParent) {
|
||||
this.state.expandedKeys = conductExpandParent(
|
||||
expandedKeys || defaultExpandedKeys,
|
||||
keyEntities,
|
||||
);
|
||||
} else {
|
||||
this.state.expandedKeys = expandedKeys || defaultExpandedKeys;
|
||||
}
|
||||
|
||||
this.onDebounceExpand = debounce(this.expandFolderNode, 200, {
|
||||
leading: true,
|
||||
});
|
||||
}
|
||||
|
||||
onExpand = (expandedKeys, info) => {
|
||||
const { onExpand } = this.props;
|
||||
|
||||
this.setUncontrolledState({ expandedKeys });
|
||||
|
||||
// Call origin function
|
||||
if (onExpand) {
|
||||
return onExpand(expandedKeys, info);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
onClick = (event, node) => {
|
||||
const { onClick, expandAction } = this.props;
|
||||
|
||||
// Expand the tree
|
||||
if (expandAction === 'click') {
|
||||
this.onDebounceExpand(event, node);
|
||||
}
|
||||
|
||||
if (onClick) {
|
||||
onClick(event, node);
|
||||
}
|
||||
};
|
||||
|
||||
onDoubleClick = (event, node) => {
|
||||
const { onDoubleClick, expandAction } = this.props;
|
||||
|
||||
// Expand the tree
|
||||
if (expandAction === 'doubleClick') {
|
||||
this.onDebounceExpand(event, node);
|
||||
}
|
||||
|
||||
if (onDoubleClick) {
|
||||
onDoubleClick(event, node);
|
||||
}
|
||||
};
|
||||
|
||||
onSelect = (keys, event) => {
|
||||
const { onSelect, multiple, children } = this.props;
|
||||
const { expandedKeys = [] } = this.state;
|
||||
const { node, nativeEvent } = event;
|
||||
const { eventKey = '' } = node.props;
|
||||
|
||||
const newState = {};
|
||||
|
||||
// We need wrap this event since some value is not same
|
||||
const newEvent = {
|
||||
...event,
|
||||
selected: true, // Directory selected always true
|
||||
};
|
||||
|
||||
// Windows / Mac single pick
|
||||
const ctrlPick = nativeEvent.ctrlKey || nativeEvent.metaKey;
|
||||
const shiftPick = nativeEvent.shiftKey;
|
||||
|
||||
// Generate new selected keys
|
||||
let newSelectedKeys;
|
||||
if (multiple && ctrlPick) {
|
||||
// Control click
|
||||
newSelectedKeys = keys;
|
||||
this.lastSelectedKey = eventKey;
|
||||
this.cachedSelectedKeys = newSelectedKeys;
|
||||
newEvent.selectedNodes = convertDirectoryKeysToNodes(children, newSelectedKeys);
|
||||
} else if (multiple && shiftPick) {
|
||||
// Shift click
|
||||
newSelectedKeys = Array.from(
|
||||
new Set([
|
||||
...(this.cachedSelectedKeys || []),
|
||||
...calcRangeKeys(children, expandedKeys, eventKey, this.lastSelectedKey),
|
||||
]),
|
||||
);
|
||||
newEvent.selectedNodes = convertDirectoryKeysToNodes(children, newSelectedKeys);
|
||||
} else {
|
||||
// Single click
|
||||
newSelectedKeys = [eventKey];
|
||||
this.lastSelectedKey = eventKey;
|
||||
this.cachedSelectedKeys = newSelectedKeys;
|
||||
newEvent.selectedNodes = [event.node];
|
||||
}
|
||||
newState.selectedKeys = newSelectedKeys;
|
||||
|
||||
if (onSelect) {
|
||||
onSelect(newSelectedKeys, newEvent);
|
||||
}
|
||||
|
||||
this.setUncontrolledState(newState);
|
||||
};
|
||||
|
||||
setTreeRef = (node) => {
|
||||
this.tree = node;
|
||||
};
|
||||
|
||||
expandFolderNode = (event, node) => {
|
||||
const { isLeaf } = node.props;
|
||||
|
||||
if (isLeaf || event.shiftKey || event.metaKey || event.ctrlKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get internal rc-tree
|
||||
const internalTree = this.tree.tree;
|
||||
|
||||
// Call internal rc-tree expand function
|
||||
// https://github.com/ant-design/ant-design/issues/12567
|
||||
internalTree.onNodeExpand(event, node);
|
||||
};
|
||||
|
||||
setUncontrolledState = (state) => {
|
||||
const newState = omit(state, Object.keys(this.props));
|
||||
if (Object.keys(newState).length) {
|
||||
this.setState(newState);
|
||||
}
|
||||
};
|
||||
|
||||
renderDirectoryTree = ({ getPrefixCls }) => {
|
||||
const { prefixCls: customizePrefixCls, className, ...props } = this.props;
|
||||
const { expandedKeys, selectedKeys } = this.state;
|
||||
|
||||
const prefixCls = getPrefixCls('tree', customizePrefixCls);
|
||||
const connectClassName = classNames(`${prefixCls}-directory`, className);
|
||||
|
||||
return (
|
||||
<Tree
|
||||
icon={getIcon}
|
||||
ref={this.setTreeRef}
|
||||
{...props}
|
||||
prefixCls={prefixCls}
|
||||
className={connectClassName}
|
||||
expandedKeys={expandedKeys}
|
||||
selectedKeys={selectedKeys}
|
||||
onSelect={this.onSelect}
|
||||
onClick={this.onClick}
|
||||
onDoubleClick={this.onDoubleClick}
|
||||
onExpand={this.onExpand}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
return <ConfigConsumer>{this.renderDirectoryTree}</ConfigConsumer>;
|
||||
}
|
||||
}
|
||||
|
||||
polyfill(DirectoryTree);
|
||||
|
||||
export default DirectoryTree;
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
.hide-file-icon.ant-tree.ant-tree-directory {
|
||||
li span.ant-tree-switcher {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
// li[role="treeitem"]:hover > span.ant-tree-switcher,
|
||||
// li.ant-tree-treenode-selected > span.ant-tree-switcher {
|
||||
// z-index: 1000;
|
||||
// display: inline-block !important;
|
||||
// }
|
||||
|
||||
.ant-tree-node-content-wrapper{
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tree-title-icon{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ant-tree-node-content-wrapper:hover .tree-title-icon{
|
||||
transition: all 0.3s;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
&.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: #fff;
|
||||
margin-top: -3px;
|
||||
}
|
||||
|
||||
&.ant-tree.ant-tree-directory > li span.ant-tree-node-content-wrapper::before,
|
||||
&.ant-tree.ant-tree-directory
|
||||
.ant-tree-child-tree
|
||||
> li
|
||||
span.ant-tree-node-content-wrapper::before {
|
||||
margin-top: -3px;
|
||||
}
|
||||
|
||||
> li span.ant-tree-node-content-wrapper.ant-tree-node-selected,
|
||||
.ant-tree-child-tree
|
||||
> li
|
||||
span.ant-tree-node-content-wrapper.ant-tree-node-selected {
|
||||
color: #2a61ff;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.ant-tree-title{
|
||||
display: inline-flex;
|
||||
justify-content:space-between;
|
||||
flex: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -16,6 +16,9 @@
|
|||
.ant-modal-body {
|
||||
text-align: center;
|
||||
}
|
||||
.ant-form-explain{
|
||||
text-align: left;
|
||||
}
|
||||
.content-title {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
import React, { useState } from "react";
|
||||
import { Input, Select } from "antd";
|
||||
import CopyTool from "../../Component/CopyTool";
|
||||
const { Option } = Select;
|
||||
|
||||
const Clone = (({wikiPages})=>{
|
||||
const [urlType, setUrlType] = useState("HTTPS");
|
||||
|
||||
return !!wikiPages.length && wikiPages[0].wiki_clone_link && <Input addonBefore={
|
||||
<Select dropdownClassName="wiki-url-type" defaultValue="HTTPS" onChange={(v) => { setUrlType(v) }}>
|
||||
<Option value="HTTPS">HTTPS</Option>
|
||||
<Option value="SSH">SSH</Option>
|
||||
</Select>
|
||||
} addonAfter={<CopyTool className="copy-wiki" inputId="wikiUrl" />} id="wikiUrl" value={urlType === 'HTTPS' ? wikiPages[0].wiki_clone_link.https : wikiPages[0].wiki_clone_link.ssh}/>
|
||||
})
|
||||
|
||||
export default Clone
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
import { httpUrl } from '../fetch';
|
||||
|
||||
export const editorConfig = {
|
||||
placeholder: '请输入',
|
||||
uploadImgServer: httpUrl + '/busiAttachments/upload',
|
||||
uploadFileName: 'file',
|
||||
uploadImgHeaders: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
excludeMenus: [
|
||||
'list',
|
||||
'todo',
|
||||
'emoticon',
|
||||
'video'
|
||||
],
|
||||
uploadImgHooks: {
|
||||
// 图片上传并返回了结果,想要自己把图片插入到编辑器中
|
||||
customInsert: function (insertImgFn, result) {
|
||||
// insertImgFn 可把图片插入到编辑器,传入图片 src ,执行函数即可
|
||||
if (result && result.data && result.data.id) {
|
||||
insertImgFn(`${httpUrl}/busiAttachments/view/${result.data.id}`);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
export const weekModal="| 工作项目 | 本周工作计划 | 本周完成情况 |下周计划 | 待协同事项 |\n| ------------ | ------------ | ------------ | ------------ | ------------ |\n| | | | | |\n| | | | | |\n| | | | | |\n| | | | | |\n\n 备注:";
|
||||
export const monthModal="|工作项目 | 工作内容 | 项目进度情况 | 问题列表及解决方案| 下月工作计划 | 遗留未解决的问题 |\n| ------------ | ------------ | ------------ | ------------ | ------------ | ------------ |\n| | | | | | |\n| | | | | | |\n| | | | | | |\n| | | | | | |\n\n 月度总结:";
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import React from "react";
|
||||
import { Button, Dropdown, Icon, Menu } from "antd";
|
||||
import { exportWiki } from "../api";
|
||||
|
||||
const Download = (({wikiParams, projectName})=>{
|
||||
const downloadType = [
|
||||
{key: 0, label: 'markdown'},
|
||||
{key: 1, label: 'html'},
|
||||
{key: 2, label: 'pdf'}
|
||||
]
|
||||
const {owner, repo, projectId} = wikiParams;
|
||||
|
||||
return <Dropdown overlay={
|
||||
<Menu>
|
||||
{downloadType.map(item=>{
|
||||
return <Menu.Item key={item.key} onClick={()=>{
|
||||
window.open(exportWiki(`?repoName=${repo}&owner=${owner}&projectName=${projectName}&projectId=${projectId}&type=${item.label}`));
|
||||
}}>{item.label}</Menu.Item>
|
||||
})}
|
||||
</Menu>
|
||||
}>
|
||||
<Button type="default" className="ml10">导出<Icon type="caret-down" /></Button>
|
||||
</Dropdown>
|
||||
})
|
||||
|
||||
export default Download
|
||||
|
|
@ -0,0 +1,179 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Input, Modal, Form, Button, message } from "antd";
|
||||
import './ModalFun/index.scss'
|
||||
import { addChildrenByKey, editMenuTitleByKey, findBrotherNodesByKey, findNodeByKey, findSamaNameWiki, getWiki, treeToMd, updateSidebar, updateWiki } from "../api";
|
||||
|
||||
// 新建/编辑弹框
|
||||
const EditModal = Form.create()((props) => {
|
||||
const {wikiParams, form: editForm, typeMenuModal, wikiPageInitDetail, cancel, history, reload} = props;
|
||||
const {validateFields, getFieldDecorator} = editForm;
|
||||
// typeMenuModal: 类型 新建子目录/目录 编辑
|
||||
const regex = /^addMenuBy.*$/;
|
||||
const regexEdit = /^editMenu.*$/;
|
||||
const regexEditWiki = /^editWiki.*$/;
|
||||
const title = regex.test(typeMenuModal) ? '新增子' : /^edit.*$/.test(typeMenuModal) ? '编辑' : '新建';
|
||||
const {menuList=[], sidebar="", wikiPages=[]} = wikiPageInitDetail || {};
|
||||
const [initMenu, setInitMenu] = useState();
|
||||
const [wikiContent, setWikiContent] = useState();
|
||||
const [label, setLabel] = useState("目录");
|
||||
|
||||
useEffect(()=>{
|
||||
if(!typeMenuModal || !menuList.length) return
|
||||
// 编辑名称
|
||||
if(/^edit.*$/.test(typeMenuModal)){
|
||||
const key = typeMenuModal.substring(8);
|
||||
const node = findNodeByKey(menuList, key)
|
||||
setLabel(node.isFile ? '页面' : '目录')
|
||||
setInitMenu({
|
||||
title: node.titleStr
|
||||
})
|
||||
editForm.setFieldsValue({
|
||||
name: node.titleStr
|
||||
})
|
||||
if(regexEditWiki.test(typeMenuModal)){
|
||||
setInitMenu({
|
||||
title: node.titleStr,
|
||||
title_sub: node.title_sub
|
||||
})
|
||||
// 获取wiki content
|
||||
getWiki({
|
||||
...wikiParams,
|
||||
pageName: node.title_sub
|
||||
}).then(res=>{
|
||||
if(res && res.code === 200){
|
||||
setWikiContent(res.data.content_base64);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [typeMenuModal, sidebar])
|
||||
|
||||
// 提交表单
|
||||
function submit(e) {
|
||||
e.preventDefault();
|
||||
validateFields(async (err, values) => {
|
||||
if (!err) {
|
||||
const { name } = values;
|
||||
let sidebarContent = undefined;
|
||||
let sameNameMenu = undefined;
|
||||
if (typeMenuModal === "addMenu") {
|
||||
// 新增一级目录:校验一级目录中是否有相同名称
|
||||
sameNameMenu = menuList.find(item => !item.isFile && item.titleStr === name);
|
||||
sidebarContent = `${sidebar}\n- ${name}`
|
||||
} else if (regex.test(typeMenuModal)) {
|
||||
// 新增子目录: 通过key找到节点,遍历节点children,是否有相同名称
|
||||
const key = typeMenuModal.substring(9) || '';
|
||||
const nodeBySameKey = findNodeByKey(menuList, key);
|
||||
sameNameMenu = nodeBySameKey.children.find(item => !item.isFile && item.titleStr === name);
|
||||
addChildrenByKey(menuList, key, `- ${name}`);
|
||||
sidebarContent = treeToMd(menuList)
|
||||
} else if (regexEdit.test(typeMenuModal)) {
|
||||
// 编辑目录名称: 兄弟节点无相同名称
|
||||
const key = typeMenuModal.substring(8) || '';
|
||||
const brotherNodes = findBrotherNodesByKey(menuList, key);
|
||||
sameNameMenu = brotherNodes.find(item => !item.isFile && item.titleStr !== initMenu.title && item.titleStr === name)
|
||||
editMenuTitleByKey(menuList, key, `${'\t'.repeat(key.split('-').length - 1)}- ${name}`);
|
||||
sidebarContent = treeToMd(menuList)
|
||||
} else if (regexEditWiki.test(typeMenuModal)) {
|
||||
// 编辑wiki名称,不允许wiki名称重复,并且还需要修改wiki的标题
|
||||
// findSamaNameWiki: 返回的是个wiki对象
|
||||
sameNameMenu = findSamaNameWiki(wikiPageInitDetail.wikiPages, name);
|
||||
}
|
||||
if (sameNameMenu) {
|
||||
// message.error('不能与已有名称重复');
|
||||
editForm.setFields({name: {value: name, errors:[new Error('不能与已有名称重复')]}});
|
||||
} else {
|
||||
// 编辑wiki成功后再给sidebarContent赋值,修改sidebar
|
||||
if (regexEditWiki.test(typeMenuModal)) {
|
||||
wikiContent && await updateWiki({
|
||||
...wikiParams,
|
||||
// 原名称
|
||||
pageName: initMenu.title_sub,
|
||||
// 现名称
|
||||
title: name,
|
||||
content_base64: wikiContent,
|
||||
message: '',
|
||||
}).then(res => {
|
||||
if (res && res.code === 200) {
|
||||
const key = typeMenuModal.substring(8) || '';
|
||||
editMenuTitleByKey(menuList, key, `${'\t'.repeat(key.split('-').length - 1)}[[${name}]]`);
|
||||
sidebarContent = treeToMd(menuList)
|
||||
}
|
||||
})
|
||||
}
|
||||
// 编辑sidebar
|
||||
sidebarContent && updateSidebar(wikiParams, sidebarContent).then(resByUpdateSidebar => {
|
||||
if (resByUpdateSidebar && resByUpdateSidebar.code === 200) {
|
||||
message.success(/^add.*$/.test(typeMenuModal) ? "新增成功" : '编辑成功');
|
||||
close();
|
||||
reload();
|
||||
if(regexEditWiki.test(typeMenuModal)){
|
||||
history.push(`/${wikiParams.owner}/${wikiParams.repo}/wiki?wiki=${encodeURIComponent(name)}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function close(){
|
||||
editForm.resetFields();
|
||||
cancel();
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={`${title}${label}`}
|
||||
visible={Boolean(typeMenuModal)}
|
||||
width={450}
|
||||
onCancel={close}
|
||||
footer={null}
|
||||
className="myself-modal"
|
||||
>
|
||||
<Form
|
||||
name="basic"
|
||||
form={editForm}
|
||||
layout="vertical"
|
||||
onSubmit={submit}
|
||||
autoComplete="off"
|
||||
>
|
||||
<Form.Item
|
||||
name="id"
|
||||
hidden
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label={`${label}名称`}>
|
||||
{getFieldDecorator('name', {
|
||||
rules: [
|
||||
{ required: true, message: `请输入${label}名称` },
|
||||
{ pattern: /^(?!-).*$/, message: '不能以-开头' }
|
||||
],
|
||||
})(<Input
|
||||
placeholder={`请输入${label}名称(不超过50个字)`}
|
||||
maxLength={50}
|
||||
autoFocus
|
||||
/>)}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item className="center mt80">
|
||||
<Button
|
||||
className="mr30"
|
||||
style={{ width: "100px" }}
|
||||
onClick={() => {
|
||||
close();
|
||||
}}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
<Button type="primary" style={{ width: "100px" }} htmlType="submit">
|
||||
提交
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
|
||||
export default EditModal
|
||||
|
|
@ -1,625 +0,0 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Tree, Dropdown, Menu, message, Modal, Input } from 'antd';
|
||||
import DelModal from '../../components/ModalFun';
|
||||
import './index.scss';
|
||||
import '../../components/ModalFun/index.scss';
|
||||
import { deleteWiki, findBrotherNodesByKey, findNodeByKey, getWiki, treeToMd, updateWiki } from '../../api';
|
||||
import { useEffect } from 'react';
|
||||
import { Base64 } from 'js-base64';
|
||||
import { splicingTabs } from '../../utils';
|
||||
const Search = Input.Search;
|
||||
const {DirectoryTree, TreeNode} = Tree;
|
||||
|
||||
// wiki目录树
|
||||
export default function Sidebar(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);
|
||||
const [actionVisible, setActionVisible] = useState(undefined);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [menuName, setMenuName] = useState(undefined);
|
||||
const [addMenuError, setAddMenuError] = useState(undefined);
|
||||
const [selectedKey, setSelectedKey] = useState(undefined);
|
||||
const [expandedKeys, setExpandedKeys] = useState(undefined);
|
||||
// 是否全部展开
|
||||
const [isExpendedAll, setIsExpendedAll] = useState(false);
|
||||
// 重命名目录名称
|
||||
const [isEditName, setIsEditName] = useState(undefined);
|
||||
// wiki重命名-wiki内容
|
||||
const [wikiContent, setWikiContent] = useState(undefined);
|
||||
|
||||
useEffect(()=>{
|
||||
if(defaultSelectedKeys){
|
||||
// 设置选中wiki
|
||||
setSelectedKey(defaultSelectedKeys);
|
||||
// 第一次渲染,默认打开选中的页面所处目录
|
||||
const keys = expandedKeys || [];
|
||||
const list = defaultSelectedKeys[0].split('-');
|
||||
list.map((item, index)=>{
|
||||
keys.push(index > 0 ? `${keys[index-1]}-${item}` : item)
|
||||
})
|
||||
setExpandedKeys(Array.from(new Set(keys)));
|
||||
}
|
||||
}, [defaultSelectedKeys])
|
||||
|
||||
useEffect(()=>{
|
||||
setMenuList(fileList);
|
||||
setInitMenuList(fileList);
|
||||
}, [fileList])
|
||||
|
||||
const menu = (title, isFile, item, level) =>{
|
||||
return <Menu>
|
||||
<Menu.Item>
|
||||
<a onClick={(e) => { addFile(e, item) }}>
|
||||
添加子页面
|
||||
</a>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<a onClick={(e) => {e.stopPropagation();setVisible(item);}}>
|
||||
添加子目录
|
||||
</a>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<a onClick={(e) => { updateName(e, item, 1) }}>
|
||||
重命名
|
||||
</a>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<a onClick={(e) => { deleteFileModal(e, title, isFile, item) }}>删除</a>
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
};
|
||||
|
||||
const fileMenu = (title, isFile, item) =>{
|
||||
return <Menu>
|
||||
<Menu.Item>
|
||||
<a onClick={(e) => { updateName(e, item, 2) }}>
|
||||
重命名
|
||||
</a>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<a onClick={(e) => { deleteFileModal(e, title, isFile, item) }}>删除</a>
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
};
|
||||
|
||||
const treeTitle = (title, isFile, key, item, level)=>{
|
||||
return <React.Fragment>
|
||||
<span className='wikiName font-15'>{title}</span>
|
||||
{permission && <span className= {actionVisible === key ? 'visibleIcon wikiActionBox' : 'wikiActionBox'}>
|
||||
<Dropdown overlay={isFile ? fileMenu(title, isFile, item) : menu(title, isFile, item, level)} placement="bottomRight" onVisibleChange={(value)=>{setActionVisible(value ? key : undefined)}}>
|
||||
<i className="iconfont icon-kongbai font-14"></i>
|
||||
</Dropdown>
|
||||
</span>}
|
||||
</React.Fragment>
|
||||
}
|
||||
|
||||
function renderTreeNodes(data) {
|
||||
return data && data.length > 0 && data.map((item) => {
|
||||
const level = item.title.search(/\S/);
|
||||
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={treeTitle(title, isFile, item.key, item, level)} key={item.key+""} isLeaf={isFile} dataRef={item}>
|
||||
{renderTreeNodes(item.children)}
|
||||
</TreeNode>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// 遍历list,找到相同的key callback
|
||||
const loop = (data, key, callback) => {
|
||||
data.forEach((item, index, arr) => {
|
||||
if (item.key === key) {
|
||||
return callback(item, index, arr);
|
||||
}
|
||||
if (item.children) {
|
||||
return loop(item.children, key, callback);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 选中节点
|
||||
function selectTree(keys,event){
|
||||
let {title, key} = event.node.props.dataRef;
|
||||
setSelectedKey([key+""]);
|
||||
title = title.trim();
|
||||
const isFile = title.startsWith('[[') && title.endsWith(']]');
|
||||
if(isFile){
|
||||
title = title.substring(2,title.length-2);
|
||||
changeitem({name: title, key});
|
||||
}
|
||||
}
|
||||
|
||||
// 收起展开
|
||||
function onExpand(keys, obj){
|
||||
const oldKeys = new Set(keys);
|
||||
const {expanded} = obj;
|
||||
let {key} = obj.node.props.dataRef;
|
||||
if(expanded){
|
||||
oldKeys.add(key);
|
||||
}else{
|
||||
oldKeys.delete(key);
|
||||
}
|
||||
setExpandedKeys(Array.from(oldKeys))
|
||||
}
|
||||
|
||||
// 删除wiki模态框
|
||||
function deleteFileModal(e, title, isFile, item) {
|
||||
e.stopPropagation();
|
||||
DelModal({
|
||||
title: `删除${isFile ? '页面' : '目录'}`,
|
||||
contentTitle: `您确定要删除“${title}”此${isFile ? '页面' : '目录'}吗?`,
|
||||
content: `此操作将删除此${isFile ? '页面' : '目录'},请进行确认以防文件的丢失`,
|
||||
onOk: () => {
|
||||
const {node, children, key} = item;
|
||||
if(isFile){
|
||||
// 删除页面
|
||||
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);
|
||||
updateSidebar(treeToMd(menuListByDel), 1);
|
||||
}
|
||||
});
|
||||
}else{
|
||||
// 删除目录(递归找到所有的[[页面]],遍历删除)
|
||||
// needDelNode: 被删除的子节点
|
||||
const needDelNode = findNodeByKey(menuList, key);
|
||||
// needDelWikis:所有需要删除的子目录列表
|
||||
const needDelWikis = [];
|
||||
findDelNode(needDelNode.children, needDelWikis);
|
||||
if(needDelWikis.length){
|
||||
// 有需要删除的子目录
|
||||
const promiseList = []
|
||||
needDelWikis.map(item=>{
|
||||
promiseList.push(deleteWikiFun({owner: owner, repo: projectsId, projectId: project.id},item.title.trim()))
|
||||
})
|
||||
Promise.all(promiseList).then((values) => {
|
||||
if(values.filter(item=>item.message === '204' || item.message === '404').length === needDelWikis.length){
|
||||
// 执行修改sidebar操作:递归过滤相同key值的节点,执行删除操作
|
||||
const menuListByDel = findSameKeyByDel(menuList, key);
|
||||
updateSidebar(treeToMd(menuListByDel), 1);
|
||||
}else{
|
||||
message.error('发生未知错误,请联系系统管理员')
|
||||
}
|
||||
});
|
||||
}else{
|
||||
// 没有需要删除的子目录,直接修改sidebar文件即可
|
||||
const menuListByDel = findSameKeyByDel(menuList, key);
|
||||
updateSidebar(treeToMd(menuListByDel), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 删除页面:递归过滤相同key值的节点
|
||||
function findSameKeyByDel(list, key){
|
||||
return list.filter(item => {
|
||||
if (item.key === key) {
|
||||
return false
|
||||
}
|
||||
if(item.children && item.children.length > 0){
|
||||
item.children = findSameKeyByDel(item.children , key)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// 删除目录:递归找到需要删除的子目录
|
||||
function findDelNode(list, nodes){
|
||||
//利用foreach循环遍历
|
||||
list.forEach(item => {
|
||||
//判断递归结束条件
|
||||
const title = item.title.trim();
|
||||
if (title.startsWith('[[') && title.endsWith(']]')) {
|
||||
// 存储数据到空数组
|
||||
nodes.push(item);
|
||||
}
|
||||
if (item.children.length){
|
||||
//递归调用
|
||||
const result = findDelNode(item.children, nodes);
|
||||
if(result) return result
|
||||
}else{
|
||||
return true
|
||||
}
|
||||
})
|
||||
return nodes;
|
||||
}
|
||||
|
||||
// 删除wiki
|
||||
function deleteWikiFun(info, title){
|
||||
return new Promise((resolve, reject)=>{
|
||||
deleteWiki({
|
||||
...info,
|
||||
pageName: title
|
||||
}).then(res => {
|
||||
resolve(res)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 修改此仓库的sidebar文件 type 1删除 2拖拽 3添加子目录 4目录重命名
|
||||
function updateSidebar(content, type, addWikiName){
|
||||
if(type === 1 && !content){
|
||||
// 删除最后一个wiki的时候,同时删除sidebar
|
||||
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,
|
||||
projectId: project.id,
|
||||
pageName: '_Sidebar',
|
||||
title: '_Sidebar',
|
||||
message: '',
|
||||
content_base64: Base64.encode(content)
|
||||
}).then(res => {
|
||||
if (res && res.message === "200") {
|
||||
setReload(Math.random());
|
||||
if(type === 1){
|
||||
message.success('删除成功');
|
||||
}else if(type === 3){
|
||||
setMenuName(undefined);
|
||||
setVisible(false);
|
||||
message.success('新增成功');
|
||||
}else if(type === 4){
|
||||
setIsEditName(false);
|
||||
setMenuName(undefined);
|
||||
setVisible(false);
|
||||
message.success('重命名成功');
|
||||
}else{
|
||||
// message.success('修改文件目录顺序成功');
|
||||
}
|
||||
}else{
|
||||
message.error(res.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 添加子页面
|
||||
function addFile(e, item){
|
||||
e.stopPropagation();
|
||||
history.push(`/${owner}/${projectsId}/wiki/add?key=${item.key}`)
|
||||
}
|
||||
|
||||
// 重命名目录文件 type:1 目录 2页面
|
||||
function updateName(e, item, type){
|
||||
e.stopPropagation();
|
||||
setVisible(item);
|
||||
setIsEditName(type);
|
||||
const {titleStr, title_sub} = item;
|
||||
type === 2 && getWiki({
|
||||
owner: owner,
|
||||
repo: projectsId,
|
||||
pageName: title_sub,
|
||||
projectId: project.id
|
||||
}).then(res => {
|
||||
if (res && res.message === "200") {
|
||||
setWikiContent(res.data.md_content);
|
||||
}
|
||||
})
|
||||
setMenuName(titleStr);
|
||||
}
|
||||
|
||||
// 新增子目录 或者 重命名目录
|
||||
function submit(){
|
||||
if(!menuName){
|
||||
setAddMenuError('请输入目录名称');
|
||||
return;
|
||||
}
|
||||
if(new RegExp("^[ ]+$").test(menuName)){
|
||||
setAddMenuError("不能仅输入空格");
|
||||
return;
|
||||
}
|
||||
if (isEditName === 2 && /^-/.test(menuName)) {
|
||||
setAddMenuError('不能以-开头');
|
||||
return;
|
||||
}
|
||||
if(!isEditName){
|
||||
// 新增目录:校验同级目录中是否有相同名称
|
||||
const {children, key} = visible;
|
||||
const mList = children.filter(item=>!item.title.trim().startsWith('[[')).map(i=>i.title.trim())
|
||||
if(mList.includes(`- ${menuName}`)){
|
||||
setAddMenuError('不能与已有目录名称相同');
|
||||
return;
|
||||
}
|
||||
const newList = menuList;
|
||||
// 设置Tree 打开的目录
|
||||
const keys = expandedKeys || [];
|
||||
const list = key.split('-');
|
||||
list.map((item, index)=>{
|
||||
keys.push(index > 0 ? `${keys[index-1]}-${item}` : item)
|
||||
})
|
||||
keys.push(`${key}-${children.length}`);
|
||||
setExpandedKeys(Array.from(new Set(keys)));
|
||||
addChildrenByKey(newList, key, menuName);
|
||||
updateSidebar(treeToMd(newList), 3);
|
||||
}else{
|
||||
// isEditName 1 编辑目录名称 2编辑页面名称
|
||||
const titleToJudge = isEditName === 2 ? `[[${menuName}]]` : `- ${menuName}`;
|
||||
const {key, titleStr, title_sub} = visible;
|
||||
// 判断是否有修改名称
|
||||
if(titleStr === menuName){
|
||||
setAddMenuError("请修改名称");
|
||||
return
|
||||
}
|
||||
// 找兄弟节点
|
||||
const list = findBrotherNodesByKey(menuList, key) || [];
|
||||
if(list.map(item=>item.titleStr).includes(menuName)){
|
||||
setAddMenuError("不能与同级目录名称相同");
|
||||
return
|
||||
}
|
||||
// wiki名称不可重复
|
||||
const sameNameWiki = fileArrInit.filter(item=>item.name === menuName)
|
||||
if(isEditName === 2 && sameNameWiki.length){
|
||||
setAddMenuError("wiki名称不可重复");
|
||||
return
|
||||
}
|
||||
// 编辑sidebar
|
||||
const newMenuList = JSON.parse(JSON.stringify(menuList))
|
||||
loop(newMenuList, key, (item)=>{
|
||||
const {key} = item;
|
||||
item.title = `${'\t'.repeat(key.split('-').length-1)}${titleToJudge}`
|
||||
})
|
||||
if(isEditName === 2){
|
||||
// 编辑页面名称-> 修改wiki
|
||||
updateWiki({
|
||||
owner,
|
||||
repo: projectsId,
|
||||
projectId: project.id,
|
||||
// 原名称
|
||||
pageName: title_sub,
|
||||
// 现名称
|
||||
title: menuName,
|
||||
content_base64: wikiContent,
|
||||
message: '',
|
||||
}).then(res=>{
|
||||
if(res && res.message === '200'){
|
||||
// 修改wiki名称接口请求成功,更新sidebar
|
||||
updateSidebar(treeToMd(newMenuList), 4);
|
||||
history.push(`/${owner}/${projectsId}/wiki/${encodeURI(menuName)}/${key}`);
|
||||
}
|
||||
})
|
||||
return;
|
||||
}
|
||||
updateSidebar(treeToMd(newMenuList), 4)
|
||||
}
|
||||
}
|
||||
|
||||
// 递归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 onDrop(info){
|
||||
// dragNode: 拖拽的节点 node: 放入的节点
|
||||
const dropKey = info.node.props.dataRef.key;
|
||||
const dragKey = info.dragNode.props.dataRef.key;
|
||||
const dropPos = info.node.props.pos.split('-');
|
||||
const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]);
|
||||
// dropPosition 0元素之上 1元素之下 -1元素之上
|
||||
|
||||
function upTitle(list, title){
|
||||
const kongGe = title.substring(0, title.indexOf('- ') === -1 ? title.indexOf('[[') : title.indexOf('- '));
|
||||
list.map(item=>{
|
||||
item.title = `\t${kongGe}${item.title.trim()}`;
|
||||
if(item.children){
|
||||
return upTitle(item.children, item.title);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const data = JSON.parse(JSON.stringify(menuList));
|
||||
|
||||
// Find dragObject
|
||||
let dragObj;
|
||||
loop(data, dragKey, (item, index, arr) => {
|
||||
arr.splice(index, 1);
|
||||
dragObj = item;
|
||||
});
|
||||
// 如果拖拽的是目录,那么需要遍历所有的子节点,改变\t个数
|
||||
if(!dragObj.isFile){
|
||||
// 放入的节点标题
|
||||
const title = info.node.props.dataRef.title;
|
||||
const addBool = !info.dropToGap
|
||||
upTitle(dragObj.children, addBool ? `\t${title}` : title);
|
||||
}
|
||||
|
||||
// 是否要保存sidebar文件
|
||||
let bool = false;
|
||||
|
||||
// dropToGap: true拖拽在节点中间 false拖拽到节点之上
|
||||
if (!info.dropToGap) {
|
||||
// 拖拽到节点之上->追加到尾部
|
||||
loop(data, dropKey, (item, index, arr) => {
|
||||
const {title, key} = item;
|
||||
if(title.indexOf('- ') === -1){
|
||||
message.error('不能拖拽到页面上');
|
||||
return;
|
||||
}
|
||||
const childrenName = item.children.map(item=>item.titleStr);
|
||||
if(childrenName.includes(dragObj.titleStr)){
|
||||
message.error('不能与同级目录名称相同');
|
||||
return;
|
||||
}
|
||||
dragObj.title = `${splicingTabs(key.split('-').length)}${dragObj.title.trim()}`;
|
||||
item.children = item.children || [];
|
||||
item.children.push(dragObj);
|
||||
bool = true;
|
||||
});
|
||||
}
|
||||
// else if (
|
||||
// (info.node.props.children || []).length > 0 && // Has children
|
||||
// dropPosition === 1 // On the bottom gap
|
||||
// ) {
|
||||
// // 拖拽到目录节点下
|
||||
// loop(data, dropKey, item => {
|
||||
// const childrenName = item.children.map(item=>item.title.trim())
|
||||
// if(childrenName.includes(dragObj.title.trim())){
|
||||
// message.error('不能与同级目录名称相同');
|
||||
// return;
|
||||
// }
|
||||
// console.log('item', item, 'dragObj', dragObj);
|
||||
// const {title} = item;
|
||||
// dragObj.title = `\t${title.substring(0, title.indexOf('- '))}${dragObj.title.trim()}`;
|
||||
// item.children = item.children || [];
|
||||
// item.children.unshift(dragObj);
|
||||
// });
|
||||
// bool = true;
|
||||
// }
|
||||
else {
|
||||
let ar;
|
||||
let i;
|
||||
loop(data, dropKey, (item, index, arr) => {
|
||||
ar = arr;
|
||||
i = index;
|
||||
});
|
||||
const childrenName = ar.map(item=>item.titleStr)
|
||||
if(childrenName.includes(dragObj.titleStr)){
|
||||
message.error('不能与同级目录名称相同');
|
||||
return;
|
||||
}
|
||||
dragObj.title = `${splicingTabs(ar[i].key.split('-').length-1)}${dragObj.title.trim()}`;
|
||||
if (dropPosition === -1) {
|
||||
ar.splice(i, 0, dragObj);
|
||||
} else {
|
||||
ar.splice(i + 1, 0, dragObj);
|
||||
}
|
||||
bool = true;
|
||||
}
|
||||
bool && updateSidebar(treeToMd(data), 2);
|
||||
setMenuList(bool ? data : initMenuList);
|
||||
}
|
||||
|
||||
// modal cancel
|
||||
function onCancel(){
|
||||
setVisible(false);
|
||||
setAddMenuError(undefined);
|
||||
setIsEditName(false);
|
||||
setMenuName(undefined);
|
||||
}
|
||||
|
||||
// 展开收起全部节点
|
||||
function expandAll(){
|
||||
setIsExpendedAll(!isExpendedAll);
|
||||
if(isExpendedAll){
|
||||
setExpandedKeys([]);
|
||||
return;
|
||||
}
|
||||
const allKeys = [];
|
||||
findMenuKey(allKeys, menuList);
|
||||
setExpandedKeys(allKeys);
|
||||
}
|
||||
|
||||
function findMenuKey(keys, list){
|
||||
list.map(item=>{
|
||||
if(item && !item.title.startsWith("[[")){
|
||||
keys.push(item.key);
|
||||
}
|
||||
if(item && item.children.length){
|
||||
findMenuKey(keys, item.children);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function changeSearchValue(value) {
|
||||
if(value){
|
||||
// 找到符合条件的子节点, 重新渲染页面
|
||||
let keysMap = findNodesToKeys(initMenuList, value);
|
||||
const newMenuList = [];
|
||||
let openedKeysMap = [];
|
||||
const oneNodeKeys = new Set();
|
||||
keysMap.map((item, index)=>{
|
||||
const {path, isFile, title} = item;
|
||||
// 把所有的一级目录key筛选出来
|
||||
oneNodeKeys.add(path[0]);
|
||||
openedKeysMap = openedKeysMap.concat(path);
|
||||
if(!index && isFile){
|
||||
setSelectedKey([path[path.length-1]]);
|
||||
const titleSplit = title.substring(2,title.length-2);
|
||||
changeitem({key: path[path.length-1], name: titleSplit})
|
||||
}
|
||||
})
|
||||
Array.from(oneNodeKeys).map(item=>loop(initMenuList, item,i=>{
|
||||
newMenuList.push(i);
|
||||
}))
|
||||
const openedKeys = Array.from(new Set(openedKeysMap));
|
||||
setExpandedKeys(openedKeys);
|
||||
setMenuList(newMenuList);
|
||||
}else{
|
||||
setMenuList(JSON.parse(JSON.stringify(initMenuList)));
|
||||
}
|
||||
}
|
||||
|
||||
function findNodesToKeys(arr, value, path, nodes) {
|
||||
path = path || [];
|
||||
nodes = nodes || [];
|
||||
if (Array.isArray(arr)) {
|
||||
arr.forEach(function (obj, index) {
|
||||
if (obj && typeof obj === 'object') {
|
||||
const {children, key} = obj;
|
||||
const title = obj.title.trim();
|
||||
const isFile = title.startsWith('[[') && title.endsWith(']]');
|
||||
if (title.match(new RegExp(value, 'i'))) {
|
||||
nodes.push({path: path.concat([key]), isFile, title});
|
||||
} else {
|
||||
findNodesToKeys(children, value, path.concat([key]), nodes);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
return(
|
||||
<div className='wikiSidebar'>
|
||||
<Search
|
||||
allowClear
|
||||
placeholder="输入关键字搜索文件"
|
||||
className="wiki-search"
|
||||
onSearch={changeSearchValue}
|
||||
/>
|
||||
|
||||
{/* 目录是否全部展开 */}
|
||||
<div className='expendedAllAction mt5' onClick={expandAll}>{isExpendedAll ? '收起' : '展开'}所有节点</div>
|
||||
|
||||
{/* wiki目录 */}
|
||||
{menuList && menuList.length ? <DirectoryTree onSelect={selectTree} draggable={permission} onDrop={onDrop} selectedKeys={selectedKey} expandedKeys={expandedKeys} onExpand={onExpand}>
|
||||
{renderTreeNodes(menuList)}
|
||||
</DirectoryTree> : ''}
|
||||
|
||||
{/* 新增子目录弹窗 */}
|
||||
<Modal title={isEditName ? `编辑${isEditName === 1 ? '目录' : '页面'}` : "新增目录"} visible={Boolean(visible)} onCancel={onCancel} onOk={submit} className="wikiAddMenu myself-modal" width={450} centered destroyOnClose>
|
||||
<div className='mb10'>{isEditName === 2 ? '页面' : '目录'}名称: </div>
|
||||
<Input placeholder={`请输入${isEditName === 2 ? '页面' : '目录'}名称`} value={menuName} onChange={(e)=>{setMenuName(e.target.value);setAddMenuError(undefined)}} maxLength={50} autoFocus></Input>
|
||||
<div className='wikiAddMenuError mt5'>{addMenuError}</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
.wikiSidebar{
|
||||
.ant-tree li .ant-tree-node-content-wrapper{
|
||||
margin: 0 -12px;
|
||||
}
|
||||
.ant-tree-node-content-wrapper{
|
||||
width: 95%;
|
||||
.ant-tree-title{
|
||||
// position: static;
|
||||
// display: inline-flex;
|
||||
// width: 88%;
|
||||
// justify-content: space-between;
|
||||
}
|
||||
.wikiName{
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
padding-top: 2px;
|
||||
}
|
||||
}
|
||||
.ant-tree.ant-tree-directory > li span.ant-tree-node-content-wrapper > .ant-tree-title, .ant-tree.ant-tree-directory .ant-tree-child-tree > li span.ant-tree-node-content-wrapper > .ant-tree-title{
|
||||
position: static;
|
||||
}
|
||||
.ant-tree li span.ant-tree-iconEle{
|
||||
position: relative;
|
||||
}
|
||||
.wikiActionBox{
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
padding-top: 2px;
|
||||
}
|
||||
.icon-kongbai{
|
||||
visibility: hidden;
|
||||
}
|
||||
.ant-tree-directory .ant-tree-title:hover, .visibleIcon{
|
||||
.icon-kongbai{
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
.ant-tree-node-selected .wikiName{
|
||||
max-width: 200px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,403 @@
|
|||
import React , { useEffect, useState } from 'react';
|
||||
import { ConfigProvider, Divider, Dropdown, Icon, Input, Menu, Modal, Tree, TreeNodeProps, message } from 'antd';
|
||||
import { Link } from 'react-router-dom';
|
||||
import DelModal from './../components/ModalFun';
|
||||
import { deleteWiki, findNodeByFirstOrContrastName, findNodeByKey, findSameKeyByDel, treeToMd, updateSidebar } from '../api';
|
||||
const {DirectoryTree, TreeNode} = Tree;
|
||||
const Search = Input.Search;
|
||||
|
||||
const TreeByWikiSidebar = (props)=>{
|
||||
const {wikiParams, hasPermission, hasSearch=true, wikiPageInitDetail, clickNode, history, setEditMenu, reload, hasOpen=true, searchClassName="", treeNodeClassName=""} = props;
|
||||
const {sidebar="", wikiPages=[]} = wikiPageInitDetail || {};
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
const wikiBySearch = searchParams.get("wiki") || '';
|
||||
|
||||
const [selectedKey, setSelectedKey] = useState([]);
|
||||
const [expandedKeys, setExpandedKeys] = useState([]);
|
||||
// 搜索
|
||||
const [search, setSearch] = useState();
|
||||
const [menuList, setMenuList] = useState(undefined);
|
||||
|
||||
useEffect(()=>{
|
||||
const menuListByinit = wikiPageInitDetail.menuList || [];
|
||||
if(menuListByinit.length){
|
||||
// 设置选中的节点
|
||||
const node = findNodeByFirstOrContrastName(menuListByinit, wikiBySearch);
|
||||
if(!node) return
|
||||
setSelectedKey([node.key])
|
||||
// 通过分割key设置默认展开的key
|
||||
const keys = expandedKeys;
|
||||
const list = node.key.split('-');
|
||||
list.map((item, index)=>{
|
||||
keys.push(index > 0 ? `${keys[index-1]}-${item}` : item)
|
||||
})
|
||||
setExpandedKeys(Array.from(new Set(keys)));
|
||||
}
|
||||
|
||||
}, [sidebar, wikiBySearch])
|
||||
|
||||
// 搜索
|
||||
useEffect(()=>{
|
||||
if(search && wikiPageInitDetail.menuList.length){
|
||||
const initMenuList = wikiPageInitDetail.menuList
|
||||
// 找到符合条件的子节点, 重新渲染页面
|
||||
let keysMap = findNodesToKeys(initMenuList, search, [], []);
|
||||
const newMenuList = [];
|
||||
let openedKeysMap = [];
|
||||
const oneNodeKeys = new Set();
|
||||
keysMap.map((item, index)=>{
|
||||
const {path=[], isFile, title} = item;
|
||||
// 把所有的一级目录key筛选出来
|
||||
oneNodeKeys.add(path[0]);
|
||||
openedKeysMap = openedKeysMap.concat(path);
|
||||
if(!index && isFile){
|
||||
wikiClick(item)
|
||||
}
|
||||
})
|
||||
Array.from(oneNodeKeys).map(item=>loop(initMenuList, item, (i)=>{
|
||||
newMenuList.push(i);
|
||||
}))
|
||||
const openedKeys = Array.from(new Set(openedKeysMap));
|
||||
setExpandedKeys(openedKeys);
|
||||
setMenuList(newMenuList);
|
||||
}else{
|
||||
setMenuList(undefined)
|
||||
}
|
||||
|
||||
function findNodesToKeys(arr, value, path, nodes) {
|
||||
arr.map(item=>{
|
||||
const {children, key, titleStr=""} = item;
|
||||
if (titleStr.match(new RegExp(value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i'))) {
|
||||
nodes.push({path: path.concat([key]), ...item});
|
||||
} else {
|
||||
findNodesToKeys(children, value, path.concat([key]), nodes);
|
||||
}
|
||||
})
|
||||
return nodes;
|
||||
}
|
||||
}, [search])
|
||||
|
||||
function wikiClick(node){
|
||||
clickNode(node)
|
||||
}
|
||||
|
||||
// 收起展开
|
||||
function onExpand(keys, obj){
|
||||
|
||||
const keySet = new Set(expandedKeys);
|
||||
const {expanded} = obj;
|
||||
let {key} = obj.node.props.dataRef;
|
||||
if(expanded){
|
||||
keySet.add(key);
|
||||
}else{
|
||||
keySet.delete(key);
|
||||
}
|
||||
setExpandedKeys(Array.from(keySet));
|
||||
}
|
||||
|
||||
// 遍历list,找到相同的key callback
|
||||
const loop = (data, key, callback) => {
|
||||
data.forEach((item, index, arr) => {
|
||||
if (item.key === key) {
|
||||
return callback(item, index, arr);
|
||||
}
|
||||
if (item.children) {
|
||||
return loop(item.children, key, callback);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function splicingTabs(count) {
|
||||
var tabs = "";
|
||||
for (var i = 0; i < count; i++) {
|
||||
tabs += "\t";
|
||||
}
|
||||
return tabs;
|
||||
}
|
||||
|
||||
function onDrop(info){
|
||||
|
||||
// dragNode: 拖拽的节点 node: 放入的节点
|
||||
const dropKey = info.node.props.dataRef.key;
|
||||
const dragKey = info.dragNode.props.dataRef.key;
|
||||
const dropPos = info.node.props.pos.split('-');
|
||||
//0元素之上 1元素之下 -1元素之上
|
||||
const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]);
|
||||
|
||||
function upTitle(list, title){
|
||||
const kongGe = title.substring(0, title.indexOf('- ') === -1 ? title.indexOf('[[') : title.indexOf('- '));
|
||||
list.map(item=>{
|
||||
item.title = `\t${kongGe}${item.title.trim()}`;
|
||||
if(item.children){
|
||||
return upTitle(item.children, item.title);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const data = JSON.parse(JSON.stringify(wikiPageInitDetail.menuList));
|
||||
|
||||
// Find dragObject
|
||||
let dragObj;
|
||||
loop(data, dragKey, (item, index, arr) => {
|
||||
arr.splice(index, 1);
|
||||
dragObj = item;
|
||||
});
|
||||
// 如果拖拽的是目录,那么需要遍历所有的子节点,改变\t个数
|
||||
if(!dragObj.isFile){
|
||||
// 放入的节点标题
|
||||
const title = info.node.props.dataRef.title;
|
||||
const addBool = !info.dropToGap
|
||||
upTitle(dragObj.children, addBool ? `\t${title}` : title);
|
||||
}
|
||||
|
||||
// 是否要保存sidebar文件
|
||||
let bool = false;
|
||||
|
||||
// dropToGap: true拖拽在节点中间 false拖拽到节点之上
|
||||
if (!info.dropToGap) {
|
||||
// 拖拽到节点之上->追加到尾部
|
||||
loop(data, dropKey, (item) => {
|
||||
const {title, key} = item;
|
||||
if(title.indexOf('- ') === -1){
|
||||
message.error('不能拖拽到页面上');
|
||||
return;
|
||||
}
|
||||
const childrenName = item.children.map(item=>item.titleStr);
|
||||
if(childrenName.includes(dragObj.titleStr)){
|
||||
message.error('不能与同级目录名称相同');
|
||||
return;
|
||||
}
|
||||
dragObj.title = `${splicingTabs(key.split('-').length)}${dragObj.title.trim()}`;
|
||||
item.children = item.children || [];
|
||||
item.children.push(dragObj);
|
||||
bool = true;
|
||||
});
|
||||
} else {
|
||||
let ar;
|
||||
let i;
|
||||
loop(data, dropKey, (item, index, arr) => {
|
||||
ar = arr;
|
||||
i = index;
|
||||
});
|
||||
const childrenName = ar.map((item)=>item.titleStr)
|
||||
if(childrenName.includes(dragObj.titleStr)){
|
||||
message.error('不能与同级目录名称相同');
|
||||
return;
|
||||
}
|
||||
|
||||
dragObj.title = `${splicingTabs(ar[i].key.split('-').length-1)}${dragObj.title.trim()}`;
|
||||
if (dropPosition === -1) {
|
||||
ar.splice(i, 0, dragObj);
|
||||
} else {
|
||||
ar.splice(i + 1, 0, dragObj);
|
||||
}
|
||||
bool = true;
|
||||
}
|
||||
if(!bool) return
|
||||
updateSidebar(wikiParams, treeToMd(data)).then(res=>{
|
||||
if(res && res.code === 200){
|
||||
message.success('操作成功')
|
||||
reload && reload();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 新增子目录/重命名
|
||||
function menuClick(e, key, typeMenuModal){
|
||||
e.stopPropagation();
|
||||
onExpand([], {expanded: true, node: {props: {dataRef: {key}}}})
|
||||
setEditMenu && setEditMenu(typeMenuModal);
|
||||
}
|
||||
|
||||
// 展开收起全部节点
|
||||
function expandAll(){
|
||||
if(expandedKeys.length > 0){
|
||||
setExpandedKeys([]);
|
||||
return;
|
||||
}
|
||||
const allKeys = [];
|
||||
findMenuKey(allKeys, wikiPageInitDetail.menuList);
|
||||
setExpandedKeys(allKeys);
|
||||
|
||||
function findMenuKey(keys, list){
|
||||
list && list.map(item=>{
|
||||
if(!item.isFile){
|
||||
keys.push(item.key);
|
||||
}
|
||||
if(item && item.children.length){
|
||||
findMenuKey(keys, item.children);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 删除wiki模态框
|
||||
function deleteFileModal(e, item) {
|
||||
const {key, isFile, titleStr, title_sub} = item;
|
||||
e.stopPropagation();
|
||||
DelModal({
|
||||
title: `删除${isFile ? '页面' : '目录'}`,
|
||||
contentTitle: `您确定要删除“${titleStr}”吗?`,
|
||||
content: `此操作将删除此${isFile ? '页面' : '目录'},请进行确认以防文件的丢失`,
|
||||
onOk: () => {
|
||||
if (isFile) {
|
||||
// 删除页面
|
||||
deleteWiki({ ...wikiParams, pageName: title_sub }).then(res => {
|
||||
if (res && res.code === 204) {
|
||||
handle()
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 删除目录(递归找到所有的[[页面]],遍历删除)
|
||||
if (wikiPageInitDetail && !wikiPageInitDetail.menuList || !key) return
|
||||
// needDelNode: 被删除的子节点
|
||||
const needDelNode = findNodeByKey(wikiPageInitDetail.menuList, key);
|
||||
// needDelWikis:所有需要删除的子目录列表
|
||||
const needDelWikis = [];
|
||||
findDelNode(needDelNode.children || [], needDelWikis);
|
||||
if (needDelWikis.length) {
|
||||
// 有需要删除的子目录
|
||||
const promiseList = []
|
||||
// 删除wiki
|
||||
function deleteWikiFun(pageName){
|
||||
return new Promise((resolve, reject)=>{
|
||||
deleteWiki({ ...wikiParams, pageName: pageName }).then(res => {
|
||||
resolve(res)
|
||||
})
|
||||
})
|
||||
}
|
||||
needDelWikis.map(item => {
|
||||
promiseList.push(deleteWikiFun(item.title_sub))
|
||||
})
|
||||
Promise.all(promiseList).then((values) => {
|
||||
if (values.filter(item => item.code === 204 || item.code === 404).length === needDelWikis.length) {
|
||||
handle()
|
||||
} else {
|
||||
message.error('发生未知错误,请联系系统管理员')
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 没有需要删除的子目录,直接修改sidebar文件即可
|
||||
handle()
|
||||
}
|
||||
}
|
||||
function handle() {
|
||||
if (wikiPageInitDetail && !wikiPageInitDetail.menuList || !key) return
|
||||
// 递归过滤相同key值的节点,执行删除操作
|
||||
const menuListByDel = findSameKeyByDel(wikiPageInitDetail.menuList, key);
|
||||
const sidebarContent = treeToMd(menuListByDel);
|
||||
if(!sidebarContent){
|
||||
// 删除最后一个wiki的时候,同时删除sidebar
|
||||
deleteWiki({...wikiParams, pageName: "_Sidebar"}).then(res => {
|
||||
if(res && res.code === 204){
|
||||
success();
|
||||
}
|
||||
});
|
||||
return
|
||||
}
|
||||
updateSidebar(wikiParams, sidebarContent).then(res=>{
|
||||
if(res && res.code === 200){
|
||||
success();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
// 删除目录:递归找到需要删除的子目录
|
||||
function findDelNode(list, nodes){
|
||||
//利用foreach循环遍历
|
||||
list.forEach(item => {
|
||||
//判断递归结束条件
|
||||
if (item.isFile) {
|
||||
// 存储数据到空数组
|
||||
nodes.push(item);
|
||||
}
|
||||
if (item.children.length){
|
||||
//递归调用
|
||||
const result = findDelNode(item.children, nodes);
|
||||
if(result) return result
|
||||
}else{
|
||||
return true
|
||||
}
|
||||
})
|
||||
return nodes;
|
||||
}
|
||||
|
||||
function success(){
|
||||
reload && reload();
|
||||
message.success('删除成功');
|
||||
history.push(`/${wikiParams.owner}/${wikiParams.repo}/wiki`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderTreeNodes(data) {
|
||||
return data && data.map((item) => {
|
||||
const {titleStr, isFile, key} = item;
|
||||
return (
|
||||
<TreeNode title={<span className='nodeBox'>
|
||||
<span className='task-hide font-15'>
|
||||
<Icon type={isFile ? "file" : "folder"} className="mr10 font-14"/>
|
||||
{titleStr}
|
||||
</span>
|
||||
{hasPermission && <Dropdown overlay={
|
||||
<Menu>
|
||||
{!isFile && <Menu.Item>
|
||||
<Link to={`/${wikiParams.owner}/${wikiParams.repo}/wiki/add?key=${key}`}>添加子页面</Link>
|
||||
</Menu.Item>}
|
||||
{!isFile && <Menu.Item>
|
||||
<a onClick={(e) => { menuClick(e, key, `addMenuBy${key}`)}}>
|
||||
添加子目录
|
||||
</a>
|
||||
</Menu.Item>}
|
||||
<Menu.Item>
|
||||
<a onClick={(e) => { menuClick(e, key, isFile ? `editWiki${key}` : `editMenu${key}`) }}>
|
||||
重命名
|
||||
</a>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<a onClick={(e) => { deleteFileModal(e, item) }}>删除</a>
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
} placement="bottomRight">
|
||||
<i className="action iconfont icon-kongbai font-14"></i>
|
||||
</Dropdown>}
|
||||
</span>} key={item.key+""} isLeaf={isFile} dataRef={item} className={treeNodeClassName}>
|
||||
{renderTreeNodes(item.children)}
|
||||
</TreeNode>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return <div className='wikiSidebar'>
|
||||
{/* draggable={permission} onDrop={onDrop} */}
|
||||
{hasSearch && <div className='alignCenter mb10'>
|
||||
<div className='ml10 mr10 mt20'>
|
||||
<Search
|
||||
allowClear
|
||||
placeholder="输入关键字搜索文件"
|
||||
onSearch={(value)=>{setSearch(value)}}
|
||||
className={searchClassName}
|
||||
/>
|
||||
</div>
|
||||
{hasOpen && <div className='expendedAllAction mt5' onClick={expandAll}>{expandedKeys.length > 0 ? '收起' : '展开'}节点</div>}
|
||||
</div>}
|
||||
<DirectoryTree
|
||||
className="sidebarBywiki"
|
||||
showIcon={false}
|
||||
blockNode={true}
|
||||
expandedKeys={expandedKeys || null}
|
||||
onExpand={onExpand}
|
||||
selectedKeys={selectedKey}
|
||||
onSelect={(_,event)=>{
|
||||
const {dataRef} = event.node.props;
|
||||
dataRef.isFile && wikiClick(dataRef);
|
||||
}}
|
||||
draggable={hasPermission}
|
||||
onDrop={onDrop}
|
||||
>
|
||||
{renderTreeNodes(menuList || wikiPageInitDetail.menuList)}
|
||||
</DirectoryTree>
|
||||
</div>
|
||||
}
|
||||
export default TreeByWikiSidebar
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Button, Dropdown, Icon, Input, Menu, Tooltip, Select, Upload, message, Spin, Modal } from 'antd';
|
||||
import cookie from 'react-cookies';
|
||||
import { Base64 } from 'js-base64';
|
||||
import { httpUrl, TokenKey } from '../fetch';
|
||||
import { findSamaNameWiki, updateSidebar } from '../api';
|
||||
|
||||
export default function UploadWiki(props) {
|
||||
const { sidebar, wikiParams, wikiPages=[], refush } = props;
|
||||
const {owner, repo, projectId} = wikiParams;
|
||||
|
||||
const uploadProps = {
|
||||
name: 'multipartFile',
|
||||
withCredentials: true,
|
||||
action: `${httpUrl}/api/wiki/open/uploadWiki/${owner}/${repo}/${projectId}`,
|
||||
accept: ".md, .txt",
|
||||
showUploadList: false,
|
||||
headers: {
|
||||
Authorization: cookie.load(TokenKey),
|
||||
},
|
||||
beforeUpload(file) {
|
||||
const {name} = file;
|
||||
const fileSuffix = name.split('.').pop() || ""
|
||||
const fileName = name.split('.').shift() || ""
|
||||
if (!['md', 'txt', 'markdown'].includes(fileSuffix)) {
|
||||
message.error('只能上传md、txt文件');
|
||||
return false;
|
||||
}
|
||||
if (/^-/.test(file.name)) {
|
||||
message.error('文件名不能以-开头');
|
||||
return false;
|
||||
}
|
||||
if (!(file.size / 1024 / 1024 < 100)) {
|
||||
message.error(`文件大小必须小于${100}MB!`);
|
||||
return false;
|
||||
}
|
||||
const sameNameWiki = findSamaNameWiki(wikiPages, fileName)
|
||||
if(sameNameWiki){
|
||||
message.error('不能上传与已有文件相同文件名的文件');
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
onChange(info) {
|
||||
const {status, response, name} = info.file;
|
||||
const fileName = name.split('.').shift() || "";
|
||||
if(!status || status === "uploading") return
|
||||
if(status === "done" && response && response.code === 201){
|
||||
// 上传成功
|
||||
const sidebarContent = `${sidebar}[[${fileName}]]`
|
||||
updateSidebar(wikiParams, sidebarContent).then(resByUpdateSidebar=>{
|
||||
if(resByUpdateSidebar && resByUpdateSidebar.code === 200){
|
||||
message.success("上传成功")
|
||||
refush();
|
||||
}
|
||||
})
|
||||
return;
|
||||
}
|
||||
message.error(response && response.msg || '文件上传失败')
|
||||
},
|
||||
}
|
||||
|
||||
return <Upload {...uploadProps}>
|
||||
<Tooltip placement="right" title={'支持导入txt、markdown格式文件'}>
|
||||
<Button type="default" className="ml10">
|
||||
<Icon type="plus" />导入模板
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Upload>
|
||||
}
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Button, Dropdown, Icon, Input, Menu, Tooltip, Select, Upload, message, Spin, Modal } from 'antd';
|
||||
import { httpUrl, TokenKey } from '../../fetch';
|
||||
import cookie from 'react-cookies';
|
||||
import { updateWiki, treeToMd, addWiki } from '../../api';
|
||||
import { Base64 } from 'js-base64';
|
||||
|
||||
export default function UploadWiki(props) {
|
||||
const { match, history, showNotification, project, fileList, setFileList, fileArrInit, setReload, butClass } = props;
|
||||
const {location:{pathname}} = history;
|
||||
let projectsId = match.params.projectsId;
|
||||
let owner = match.params.owner;
|
||||
|
||||
const uploadProps = {
|
||||
name: 'multipartFile',
|
||||
withCredentials: true,
|
||||
action: `${httpUrl}/api/wikiExport/uploadWiki/${owner}/${projectsId}/${project && project.id}`,
|
||||
showUploadList: false,
|
||||
headers: {
|
||||
Authorization: cookie.load(TokenKey),
|
||||
},
|
||||
beforeUpload: beforeUpload,
|
||||
onChange(info) {
|
||||
// 截取文件名称
|
||||
const fileName = info.file.name.substring(0,info.file.name.lastIndexOf("."))
|
||||
if (info.file.status === 'done') {
|
||||
if (info.file.response.message === '201') {
|
||||
// 修改sidebar文件
|
||||
if(!fileArrInit.length){
|
||||
// 仓库没有wiki文件
|
||||
addWiki({owner,repo: projectsId,projectId: project.id,pageName: "_Sidebar",title: "_Sidebar",message: '',content_base64: Base64.encode(`[[${fileName}]]`)}).then(res=>{
|
||||
if(res && res.message === "201"){
|
||||
message.success(`${fileName} 上传成功`);
|
||||
history.push(`/${owner}/${projectsId}/wiki/${encodeURI(fileName)}/0`);
|
||||
setReload(Math.random());
|
||||
}
|
||||
})
|
||||
return;
|
||||
}
|
||||
const newSidebarList = fileList.concat({title: `[[${fileName}]]`, children: []})
|
||||
updateWikiFun(newSidebarList, 0);
|
||||
message.success(`${fileName} 上传成功`);
|
||||
history.push(`/${owner}/${projectsId}/wiki/${encodeURI(fileName)}/0`);
|
||||
setReload(Math.random());
|
||||
}
|
||||
} else if (info.file.status === 'error') {
|
||||
const {message: responseMes} = info.file.response;
|
||||
if(responseMes.indexOf('wiki page already exists') !== -1){
|
||||
message.error("已存在相同文件名的文件")
|
||||
}else{
|
||||
message.error(responseMes || `${fileName} 上传失败`);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function beforeUpload(file) {
|
||||
if (!['md', 'txt', 'markdown'].includes(file.name.split('.').pop())) {
|
||||
message.error('只能上传md、txt文件');
|
||||
return false;
|
||||
}
|
||||
if (/^-/.test(file.name)) {
|
||||
message.error('文件名不能以-开头');
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
function updateWikiFun(sidebarList, type){
|
||||
updateWiki({
|
||||
owner,
|
||||
repo: projectsId,
|
||||
projectId: project.id,
|
||||
pageName: '_Sidebar',
|
||||
title: '_Sidebar',
|
||||
message: '',
|
||||
content_base64: Base64.encode(treeToMd(sidebarList))
|
||||
}).then(res => {
|
||||
if (res && res.message === "200") {
|
||||
setFileList(sidebarList);
|
||||
}else {
|
||||
message.error(res.data || "操作失败");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return <Upload {...uploadProps}>
|
||||
<Tooltip placement="top" title={'支持导入txt、markdown格式文件'}>
|
||||
<Button type="default" className={`ml10 ${butClass}`}><Icon type="plus" />导入模板</Button>
|
||||
</Tooltip>
|
||||
</Upload>
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
export const weekModal="| 工作项目 | 本周工作计划 | 本周完成情况 |下周计划 | 待协同事项 |\n| ------------ | ------------ | ------------ | ------------ | ------------ |\n| | | | | |\n| | | | | |\n| | | | | |\n| | | | | |\n\n 备注:";
|
||||
export const monthModal="|工作项目 | 工作内容 | 项目进度情况 | 问题列表及解决方案| 下月工作计划 | 遗留未解决的问题 |\n| ------------ | ------------ | ------------ | ------------ | ------------ | ------------ |\n| | | | | | |\n| | | | | | |\n| | | | | | |\n| | | | | | |\n\n 月度总结:";
|
||||
|
||||
export const template = [{
|
||||
name: "周报",
|
||||
value: weekModal
|
||||
}, {
|
||||
name: "月报",
|
||||
value: monthModal
|
||||
}]
|
||||
|
|
@ -0,0 +1,215 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Form, Icon, Input, Button, Spin, message, Radio } from "antd";
|
||||
import './index.scss';
|
||||
import { addChildrenByKey, addWiki, editMenuTitleByKey, findSamaNameWiki, getMenuListAndSidebarData, getWiki, treeToMd, updateSidebar, updateWiki } from "../api";
|
||||
import { template } from "../config";
|
||||
import DelModal from "../components/ModalFun";
|
||||
import { Base64 } from "js-base64";
|
||||
import TpmMdEditor from "../../../modules/tpm/challengesnew/tpm-md-editor";
|
||||
|
||||
// 新增/编辑wiki
|
||||
export default Form.create()((props) => {
|
||||
const titleByEditType = {
|
||||
"add": "新增",
|
||||
"edit": "编辑",
|
||||
"copy": "复制"
|
||||
}
|
||||
const {form, history, project_id, match:{params:{owner, projectsId: repoIdentifier}}, location:{pathname, search}, projectDetail} = props;
|
||||
const { author: projectAuthor, name: projectName} = projectDetail || {};
|
||||
const searchParams = new URLSearchParams(search);
|
||||
const {getFieldDecorator, validateFields, getFieldValue} = form;
|
||||
const wikiParams = {owner, repo: repoIdentifier, projectId: project_id};
|
||||
let editType = pathname.split("/").pop(); //编辑wiki(addWiki addWikiBy${key} edit)
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [wikiPageInitDetail, setWikiPageInitDetail] = useState({})
|
||||
const {menuList=[], sidebar="", wikiPages=[]} = wikiPageInitDetail;
|
||||
const [wikiDetail, setWikiDetail] = useState();
|
||||
const [templateBySelect, setTemplateBySelect] = useState();
|
||||
|
||||
useEffect(()=>{
|
||||
if(!project_id) return
|
||||
// 获取wiki 目录树、sidebar内容
|
||||
getMenuListAndSidebarData(wikiParams).then(res=>{
|
||||
setWikiPageInitDetail(res);
|
||||
})
|
||||
}, [project_id])
|
||||
|
||||
useEffect(()=>{
|
||||
if(editType !== "add" && wikiPages.length){
|
||||
setLoading(true);
|
||||
// 根据wiki名称获取wiki内容
|
||||
const wikiName = searchParams.get("wiki")
|
||||
const sameNameWiki = findSamaNameWiki(wikiPages, wikiName)
|
||||
document.title = `${titleByEditType[editType]}${sameNameWiki.title}-维基-${projectAuthor && projectAuthor.name}/${projectName}`;
|
||||
getWiki({
|
||||
...wikiParams,
|
||||
pageName: sameNameWiki.sub_url
|
||||
}).then(res=>{
|
||||
if(res && res.code === 200){
|
||||
res.data.content = Base64.decode(res.data.content_base64)
|
||||
setWikiDetail({
|
||||
...res.data,
|
||||
sub_url: sameNameWiki.sub_url
|
||||
});
|
||||
form.setFieldsValue({
|
||||
title: `${sameNameWiki.title}${editType === "copy" ? "(复制)" : ""}`,
|
||||
md_content: res.data.content
|
||||
})
|
||||
}
|
||||
setLoading(false);
|
||||
})
|
||||
}else{
|
||||
document.title = `${titleByEditType[editType]}-维基-${projectAuthor && projectAuthor.name}/${projectName}`;
|
||||
}
|
||||
}, [editType, sidebar])
|
||||
|
||||
function submit(e){
|
||||
e.preventDefault();
|
||||
validateFields(async (err, values)=>{
|
||||
if(!err){
|
||||
setLoading(true);
|
||||
const {title, md_content} = values;
|
||||
const sameNameWiki = findSamaNameWiki(wikiPages, title)
|
||||
if(editType === "edit"){
|
||||
// 编辑wiki
|
||||
if(sameNameWiki && sameNameWiki.title !== wikiDetail.title){
|
||||
message.error('不能与已有文件标题相同');
|
||||
setLoading(false);
|
||||
return
|
||||
}
|
||||
await updateWiki({
|
||||
...wikiParams,
|
||||
pageName: wikiDetail.sub_url,
|
||||
title,
|
||||
message: '',
|
||||
content_base64: Base64.encode(md_content)
|
||||
}).then(async res => {
|
||||
if(res && res.code === 200){
|
||||
if(wikiDetail.title === title){
|
||||
// 只修改了wiki内容,重新加载wiki详情即可
|
||||
message.success('修改成功');
|
||||
reload();
|
||||
return
|
||||
}
|
||||
// 修改了wiki标题,需要修改sidebar文件,重新加载目录树、wiki详情
|
||||
const key = searchParams.get("key")
|
||||
editMenuTitleByKey(menuList, key, `${'\t'.repeat(key.split('-').length-1)}[[${title}]]`);
|
||||
updateSidebar(wikiParams, treeToMd(menuList)).then(response=>{
|
||||
if(response && response.code === 200){
|
||||
message.success('修改成功');
|
||||
reload();
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
}else{
|
||||
// 新增wiki:先新增,再更新sidebar
|
||||
if(sameNameWiki){
|
||||
message.error('不能与已有文件标题相同');
|
||||
setLoading(false);
|
||||
return
|
||||
}
|
||||
await addWiki(wikiParams, title, md_content).then(async res=>{
|
||||
if(res && res.code === 201){
|
||||
// 构建sidebar内容,新增一级页面(/add)、新增子级页面(/add?key=xx)、复制页面(/copy?key=xx)
|
||||
let sidebarContent = "";
|
||||
const key = searchParams.get("key")
|
||||
if((editType === "add" && !key) || (editType === "copy" && !key.includes("-"))){
|
||||
// 新增/复制一级wiki
|
||||
sidebarContent = sidebar ? `${sidebar}\n[[${title}]]` : `[[${title}]]`
|
||||
}else if(editType === "add"){
|
||||
// 新增子级页面, 加到key的子页面
|
||||
addChildrenByKey(menuList, key, `[[${title}]]`)
|
||||
sidebarContent = treeToMd(menuList)
|
||||
}else{
|
||||
// 复制子级页面页面,加到key的同级位置
|
||||
const supKey = key.split("-");
|
||||
supKey.pop();
|
||||
addChildrenByKey(menuList, supKey.join('-'), `[[${title}]]`)
|
||||
sidebarContent = treeToMd(menuList)
|
||||
}
|
||||
// 新增/修改sidebar wiki文件
|
||||
if(!sidebar){
|
||||
// 第一次创建wiki
|
||||
await addWiki(wikiParams,"_Sidebar", sidebarContent)
|
||||
}else{
|
||||
await updateSidebar(wikiParams, sidebarContent)
|
||||
}
|
||||
message.success("新增成功");
|
||||
reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
function reload(){
|
||||
// 修改路由
|
||||
history.push(`/${owner}/${repoIdentifier}/wiki?wiki=${encodeURIComponent(title)}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return <div className="wiki-main pb50">
|
||||
<div className="wiki-head">
|
||||
<span className="font-20">
|
||||
<a onClick={()=>{history.go(-1)}} className="primaryText">Wiki</a>
|
||||
<Icon type="right" className="font-16 ml5 mr5"/>
|
||||
{titleByEditType[editType]}页面
|
||||
</span>
|
||||
</div>
|
||||
<Spin spinning={loading}>
|
||||
<Form form={form} onSubmit={submit}>
|
||||
<Form.Item label="标题">
|
||||
{getFieldDecorator('title', {
|
||||
rules: [
|
||||
{ required: true, message: '请输入页面标题内容!' },
|
||||
{ pattern: /^(?!-).*$/, message: '不能以-开头' }
|
||||
],
|
||||
})(<Input
|
||||
placeholder={"请输入标题"}
|
||||
maxLength={50}
|
||||
autoFocus
|
||||
/>)}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
{getFieldDecorator('md_content', {
|
||||
rules: [{ required: true, message: "请输入wiki内容" }],
|
||||
})(<TpmMdEditor
|
||||
placeholder={"请输入wiki内容"}
|
||||
height={500}
|
||||
mdID={"wiki_content"}
|
||||
initValue={getFieldValue("md_content")}
|
||||
isFocus={false}
|
||||
/>)}
|
||||
</Form.Item>
|
||||
|
||||
<h3>添加模版</h3>
|
||||
<Radio.Group value={templateBySelect} className="mt10 mb20">
|
||||
{template.map(item=>{
|
||||
return <Radio value={item.name} onChange={()=>{
|
||||
DelModal({
|
||||
title: '添加模版',
|
||||
contentTitle: `您确定要添加“${item.name}”模板吗`,
|
||||
content: `此操作会将“${item.name}”模板替换编辑栏内所有内容,请确认以防文件的丢失`,
|
||||
okText: '确认添加',
|
||||
onOk: () => {
|
||||
setTemplateBySelect(item.name);
|
||||
form.setFieldsValue({
|
||||
md_content: item.value
|
||||
})
|
||||
}
|
||||
})
|
||||
}}>{item.name}</Radio>
|
||||
})}
|
||||
</Radio.Group>
|
||||
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit">保存</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Spin>
|
||||
|
||||
</div>
|
||||
})
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Button, Divider, Icon, Spin } from "antd";
|
||||
import { getImageUrl, timeAgo } from 'educoder';
|
||||
import { findNodeByFirstOrContrastName, getMenuListAndSidebarData, getWiki } from "../api";
|
||||
import Welcome from './welcome';
|
||||
import RenderHtml from "../../../components/render-html";
|
||||
import { Link } from "react-router-dom";
|
||||
import Sidebar from '../components/treeByWikiSidebar';
|
||||
import EditMenu from '../components/editMenuModal';
|
||||
import Clone from '../components/clone';
|
||||
import { Base64 } from "js-base64";
|
||||
import Download from "../components/download";
|
||||
import UploadWiki from "../components/uploadWiki";
|
||||
import './index.scss';
|
||||
|
||||
export default (props) => {
|
||||
const {project_id, match:{params:{owner, projectsId: repoIdentifier}}, projectDetail, history, location} = props;
|
||||
const wikiParams = {owner, repo: repoIdentifier, projectId: project_id};
|
||||
const permission = projectDetail && projectDetail.permission && projectDetail.permission !== "Reporter";
|
||||
const { author: projectAuthor, name: projectName} = projectDetail || {};
|
||||
const search = new URLSearchParams(location.search);
|
||||
const wikiBySearch = search.get("wiki") || '';
|
||||
|
||||
const [spinning, setSpinning] = useState(false);
|
||||
const [reload, setReload] = useState();
|
||||
const [wikiPageInitDetail, setWikiPageInitDetail] = useState({})
|
||||
const {menuList=[], sidebar="", wikiPages=[]} = wikiPageInitDetail;
|
||||
const [selectNodeDetail, setSelectNodeDetail] = useState({});
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [wikiDetail, setWikiDetail] = useState({});
|
||||
|
||||
// 弹框编辑目录
|
||||
const [typeMenuModal, setTypeMenuModal] = useState();
|
||||
|
||||
useEffect(()=>{
|
||||
setSpinning(true);
|
||||
if(!project_id) return
|
||||
setWikiPageInitDetail({})
|
||||
// 获取wiki 目录树、sidebar内容
|
||||
getMenuListAndSidebarData(wikiParams).then(res=>{
|
||||
setSpinning(false);
|
||||
setWikiPageInitDetail(res);
|
||||
})
|
||||
}, [project_id, reload])
|
||||
|
||||
useEffect(()=>{
|
||||
if(!menuList.length) return
|
||||
// 通过路由直接访问
|
||||
let node = findNodeByFirstOrContrastName(menuList, wikiBySearch)
|
||||
// 有可能wikiBySearch是错误的值,导致右侧显示为空
|
||||
if(!node){
|
||||
history.push(`/${owner}/${repoIdentifier}/wiki`)
|
||||
return
|
||||
}
|
||||
setSelectNodeDetail(node);
|
||||
}, [menuList, wikiBySearch])
|
||||
|
||||
useEffect(()=>{
|
||||
document.title = `${selectNodeDetail.titleStr ? `${selectNodeDetail.titleStr}-` : ""}维基-${projectAuthor && projectAuthor.name}/${projectName}`;
|
||||
if(selectNodeDetail.title_sub){
|
||||
setLoading(true);
|
||||
getWiki({
|
||||
...wikiParams,
|
||||
pageName: selectNodeDetail.title_sub
|
||||
}).then(res=>{
|
||||
if(res && res.code === 200){
|
||||
res.data.content = Base64.decode(res.data.content_base64);
|
||||
setWikiDetail(res.data);
|
||||
}
|
||||
setLoading(false);
|
||||
})
|
||||
}
|
||||
}, [selectNodeDetail.title_sub])
|
||||
|
||||
function refush(){
|
||||
setReload(Math.random())
|
||||
}
|
||||
|
||||
return <Spin spinning={spinning}>
|
||||
{/* 空数据页面 */}
|
||||
{!spinning && !wikiPages.length && <Welcome {...props} wikiParams={wikiParams} refush={refush}/>}
|
||||
{/* wiki首页 */}
|
||||
{!!wikiPages.length && <div className="wiki-main wiki-home pb50">
|
||||
<div className="wiki-head">
|
||||
<span className="head-title">
|
||||
{
|
||||
permission ?
|
||||
<div>
|
||||
<Button type="default" onClick={()=>{
|
||||
setTypeMenuModal("addMenu")
|
||||
}} className='mr15'><Icon type="plus"/>新增目录</Button>
|
||||
<Link to={`/${owner}/${repoIdentifier}/wiki/add`}><Button type="default"><Icon type="plus"/>新增页面</Button></Link>
|
||||
</div> : "Wiki文档"
|
||||
}
|
||||
</span>
|
||||
<div>
|
||||
{
|
||||
permission && <UploadWiki wikiParams={wikiParams} sidebar={`${sidebar}\n`} refush={refush} wikiPages={wikiPages}/>
|
||||
}
|
||||
<Download wikiParams={wikiParams} projectName={projectDetail.name}/>
|
||||
<Link to={`/${owner}/${repoIdentifier}/${project_id}/wiki/preview?name=${projectDetail.name}`}><Button type="default" className="ml10">预览</Button></Link>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="wiki-body">
|
||||
<div className="wiki-nav-parent">
|
||||
<div className='wiki-nav-scroll mb20'>
|
||||
<div className="wiki-nav">
|
||||
{/* 读sidebar转换成目录树 */}
|
||||
<Sidebar wikiParams={wikiParams} hasPermission={permission} wikiPageInitDetail={wikiPageInitDetail} clickNode={(node)=>{history.push(`/${owner}/${repoIdentifier}/wiki?wiki=${encodeURIComponent(node.titleStr)}`)}} history={history} setEditMenu={setTypeMenuModal} reload={refush}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Clone wikiPages={wikiPages}/>
|
||||
|
||||
</div>
|
||||
{wikiDetail && <div className="wiki-content">
|
||||
<Spin spinning={loading}>
|
||||
<div className="flexSpaceBetween pt20 pl20 pr20">
|
||||
<div style={{width: '80%'}}>
|
||||
<p className="font-16 task-hide">{wikiDetail.title}</p>
|
||||
<div>
|
||||
<Link to={`/${wikiDetail.last_commit && wikiDetail.last_commit.author.name || ""}`}>
|
||||
{wikiDetail.image_url && <img alt="头像" className="commit-user-avator mb5" src={getImageUrl(`/${wikiDetail.image_url}`)} />}
|
||||
{wikiDetail.userName}
|
||||
</Link>
|
||||
上次修改于{wikiDetail.last_commit ? timeAgo(wikiDetail.last_commit.author.date) : '刚刚'}
|
||||
</div>
|
||||
</div>
|
||||
{permission && <div>
|
||||
<Link to={`/${owner}/${repoIdentifier}/wiki/copy?wiki=${encodeURIComponent(wikiDetail.title)}&key=${selectNodeDetail.key}`}><Button type="primary" ghost className='mr10'>复制</Button></Link>
|
||||
<Link to={`/${owner}/${repoIdentifier}/wiki/edit?wiki=${encodeURIComponent(wikiDetail.title)}&key=${selectNodeDetail.key}`}><Button type="primary">编辑</Button></Link>
|
||||
</div>}
|
||||
</div>
|
||||
<Divider/>
|
||||
|
||||
{wikiDetail.content_base64 && <RenderHtml className="pl20 pr20 editor-content-panel imageLayerParent" value={Base64.decode(wikiDetail.content_base64)} url={history.location} />}
|
||||
</Spin>
|
||||
</div>}
|
||||
</div>
|
||||
</div >}
|
||||
{/* 新建/编辑目录名称 */}
|
||||
<EditMenu wikiParams={wikiParams} typeMenuModal={typeMenuModal} wikiPageInitDetail={wikiPageInitDetail} cancel={()=>{setTypeMenuModal(undefined)}} history={history} reload={refush}/>
|
||||
</Spin>
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
.flexSpaceBetween{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.wiki-main {
|
||||
width: 1200px;
|
||||
min-height: 400px;
|
||||
margin: 20px auto 60px;
|
||||
}
|
||||
.wiki-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
height: 64px;
|
||||
background: #fafcff;
|
||||
box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.13);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.primaryText{
|
||||
color: $primary-color;
|
||||
}
|
||||
.welcome-main {
|
||||
text-align: center;
|
||||
background: rgba(250, 252, 255, 1);
|
||||
border-radius: 4px;
|
||||
border: 1px solid rgba(42, 97, 255, 0.23);
|
||||
}
|
||||
.wiki-home{
|
||||
.wiki-nav-parent {
|
||||
width: 280px;
|
||||
flex: none;
|
||||
}
|
||||
}
|
||||
|
||||
.wiki-nav {
|
||||
min-height: 500px;
|
||||
background: #ffffff;
|
||||
border: 1px solid rgba(153, 153, 153, 0.22);
|
||||
overflow-y: scroll;
|
||||
flex: none;
|
||||
color: #333;
|
||||
.expendedAllAction{
|
||||
cursor: pointer;
|
||||
text-align: right;
|
||||
padding-right: 15px;
|
||||
color: $primary-color;
|
||||
border-bottom: 1px solid #d0d0d0;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
.ant-tree.ant-tree-directory > li.ant-tree-treenode-selected > span.ant-tree-switcher, .ant-tree.ant-tree-directory .ant-tree-child-tree > li.ant-tree-treenode-selected > span.ant-tree-switcher{
|
||||
color: $primary-color;
|
||||
}
|
||||
}
|
||||
.wikiSidebar{
|
||||
// .ant-tree li span.ant-tree-switcher{
|
||||
// display: none;
|
||||
// }
|
||||
.nodeBox{
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
padding-top: 2px;
|
||||
.action {
|
||||
display: none;
|
||||
}
|
||||
&:hover .action, .action.ant-dropdown-open{
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.sidebarByWiki{
|
||||
.ant-tree-node-content-wrapper {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.wiki-body {
|
||||
display: flex;
|
||||
.wiki-content {
|
||||
flex: 1;
|
||||
width: 0;
|
||||
}
|
||||
.commit-user-avator {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
margin-right: 0.35rem;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.wiki-preview {
|
||||
overflow-y: scroll;
|
||||
height: 100%;
|
||||
.previewWiki{
|
||||
overflow-y: auto;
|
||||
width: 20vw;
|
||||
}
|
||||
.wiki-nav {
|
||||
min-height: 92vh;
|
||||
border: none;
|
||||
border-right: 1px solid rgba(153, 153, 153, 0.22);
|
||||
}
|
||||
.preview-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 12rem 0 2rem;
|
||||
width: 100%;
|
||||
height: 8vh;
|
||||
background: rgb(39, 47, 76);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.preview-head-right {
|
||||
display: flex;
|
||||
.copy-desc {
|
||||
flex:none
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Base64 } from 'js-base64';
|
||||
import { Divider } from 'antd';
|
||||
import { getWiki, getMenuListAndSidebarData, findNodeByFirstOrContrastName, } from '../api';
|
||||
import RenderHtml from '../../../components/render-html';
|
||||
import TreeByWikiSidebar from '../components/treeByWikiSidebar';
|
||||
import Clone from '../components/clone';
|
||||
import Download from '../components/download';
|
||||
import './index.scss';
|
||||
|
||||
export default (props) => {
|
||||
const { match: {params: {owner, proIdentity, projectId}}, history, showNotification, location } = props;
|
||||
const wikiParams = {owner, repo: proIdentity, projectId: projectId};
|
||||
const search = new URLSearchParams(location.search);
|
||||
const projectName = search.get("name") || '';
|
||||
const wikiBySearch = search.get("wiki") || '';
|
||||
|
||||
const [wikiPageInitDetail, setWikiPageInitDetail] = useState({})
|
||||
const [checkItem, setCheckItem] = useState({});
|
||||
const [itemDetail, setItemDetail] = useState({});
|
||||
|
||||
const {wikiPages=[], menuList=[]} = wikiPageInitDetail;
|
||||
|
||||
useEffect(()=>{
|
||||
if(checkItem && checkItem.name){
|
||||
document.title = `${checkItem.name}-维基预览`
|
||||
}
|
||||
}, [checkItem])
|
||||
|
||||
|
||||
useEffect(()=>{
|
||||
// 获取wiki 目录树、sidebar内容
|
||||
getMenuListAndSidebarData(wikiParams).then(res=>{
|
||||
setWikiPageInitDetail(res);
|
||||
})
|
||||
}, [])
|
||||
|
||||
useEffect(()=>{
|
||||
if(!menuList.length) return
|
||||
// 通过路由直接访问
|
||||
let node = findNodeByFirstOrContrastName(menuList, wikiBySearch)
|
||||
// 有可能wikiBySearch是错误的值,导致右侧显示为空
|
||||
if(!node){
|
||||
search.delete('wiki');
|
||||
history.push(`${window.location.pathname}?${search.toString()}`)
|
||||
return
|
||||
}
|
||||
setCheckItem(node);
|
||||
}, [menuList, wikiBySearch])
|
||||
|
||||
useEffect(() => {
|
||||
document.title = `${checkItem && checkItem.titleStr}-维基预览`
|
||||
checkItem.title_sub && getWiki({
|
||||
...wikiParams,
|
||||
pageName: checkItem.title_sub
|
||||
}).then(res => {
|
||||
if(res && res.code === 200){
|
||||
setItemDetail(res.data);
|
||||
} else {
|
||||
showNotification("加载失败")
|
||||
}
|
||||
});
|
||||
}, [checkItem]);
|
||||
|
||||
return (
|
||||
<div className="wiki-preview">
|
||||
<div className="preview-head">
|
||||
<Link to={`/${owner}/${proIdentity}/wiki`} className="font-24" style={{color: 'white'}}>
|
||||
<i className="iconfont icon-wendangyulan_icon mr3 font-24"></i>
|
||||
<span className="ml10">{projectName}</span>
|
||||
</Link>
|
||||
<div className="preview-head-right">
|
||||
<div className="font-16 copy-desc mr10">克隆地址</div>
|
||||
<Clone wikiPages={wikiPages}/>
|
||||
<Download wikiParams={wikiParams} projectName={projectName}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="wiki-body">
|
||||
<div className="wiki-nav previewWiki mt20">
|
||||
{/* 读sidebar转换成目录树 */}
|
||||
<TreeByWikiSidebar wikiParams={wikiParams} hasPermission={false} hasSearch={false} wikiPageInitDetail={wikiPageInitDetail} clickNode={(node)=>{
|
||||
history.push(`${location.pathname}?name=${projectName}&wiki=${encodeURIComponent(node.titleStr)}`)
|
||||
}}/>
|
||||
</div>
|
||||
|
||||
<div className="wiki-content padding20">
|
||||
<h3>{checkItem.titleStr}</h3>
|
||||
<Divider/>
|
||||
{itemDetail && itemDetail.content_base64 && <RenderHtml className="wiki-content-detail editor-content-panel markdown-body" value={Base64.decode(itemDetail.content_base64)} url={history.location}/>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Button, Divider } from 'antd';
|
||||
import UploadWiki from '../components/uploadWiki';
|
||||
import './index.scss';
|
||||
|
||||
export default (props) => {
|
||||
const { project, isManager, match: {params: {projectsId, owner}}, refush, wikiParams} = props;
|
||||
|
||||
return (
|
||||
<div className="wiki-main welcome-main pt30">
|
||||
<i className="iconfont icon-huanying_icon font-50" ></i>
|
||||
<p className="welcome-title font-26">欢迎使用
|
||||
<span className="wiki-title">
|
||||
{project && project.name}
|
||||
</span>
|
||||
Wiki</p>
|
||||
<p className="welcome-content">Wiki主要是您项目的产品设计、文档描述、注释等等</p>
|
||||
<Divider style={{minWidth: 400, width: 400, margin: '50px auto 45px'}}/>
|
||||
{
|
||||
isManager ? <div>
|
||||
<Link to={`/${owner}/${projectsId}/wiki/add`}><Button type="primary">创建Wiki文档</Button></Link>
|
||||
<UploadWiki wikiParams={wikiParams} sidebar="" refush={refush}/>
|
||||
</div> : <p className="welcome-des">该项目暂时没有创建Wiki</p>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
// 拼接\t
|
||||
export function splicingTabs(count) {
|
||||
var tabs = "";
|
||||
for (var i = 0; i < count; i++) {
|
||||
tabs += "\t";
|
||||
}
|
||||
return tabs;
|
||||
}
|
||||
|
||||
// 输入:'2-2-2-1' 返回:['2', '2-2', '2-2-2', '2-2-2-1']
|
||||
export function splitAndCombine(str) {
|
||||
if(!str) return []
|
||||
var parts = str.split('-');
|
||||
var result = parts.reduce(function(acc, curr) {
|
||||
if (acc.length === 0) {
|
||||
acc.push(curr);
|
||||
} else {
|
||||
var lastItem = acc[acc.length - 1];
|
||||
acc.push(lastItem + '-' + curr);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
return result;
|
||||
}
|
||||
|
|
@ -124,20 +124,15 @@ class children_comments extends Component {
|
|||
<div className="grid-item pb5">
|
||||
<Link
|
||||
to={`/${item && item.user_login}`}
|
||||
className="show-user-link"
|
||||
className="show-user-link color-black fwb"
|
||||
>
|
||||
<img
|
||||
className="radius"
|
||||
className="radius mr10"
|
||||
src={getImageUrl(`/${item && item.user_picture}`)}
|
||||
alt=""
|
||||
width="30"
|
||||
height="30"
|
||||
/>
|
||||
</Link>
|
||||
<Link
|
||||
to={`/${item && item.user_login}`}
|
||||
className="show-user-link color-black ml10 fwb"
|
||||
>
|
||||
{item && item.user_name}
|
||||
</Link>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -397,24 +397,34 @@ class comments extends Component {
|
|||
return (
|
||||
<div className="width100" key={item.id}>
|
||||
<div className="pb5">
|
||||
<Link
|
||||
to={`/${item && item.user_login}`}
|
||||
className="show-user-link"
|
||||
>
|
||||
<img
|
||||
className="radius"
|
||||
src={getImageUrl(`/${item && item.user_picture}`)}
|
||||
alt=""
|
||||
width="30"
|
||||
height="30"
|
||||
/>
|
||||
</Link>
|
||||
<Link
|
||||
to={`/${item && item.user_login}`}
|
||||
className="show-user-link color-black ml10 fwb"
|
||||
>
|
||||
{item && item.user_name}
|
||||
</Link>
|
||||
{
|
||||
item && item.user_login ?
|
||||
<Link
|
||||
to={`/${item && item.user_login}`}
|
||||
className="show-user-link color-black fwb"
|
||||
>
|
||||
<img
|
||||
className="radius mr10"
|
||||
src={getImageUrl(`/${item && item.user_picture}`)}
|
||||
alt=""
|
||||
width="30"
|
||||
height="30"
|
||||
/>
|
||||
{item && item.user_name}
|
||||
</Link>
|
||||
:
|
||||
<span>
|
||||
<img
|
||||
className="radius mr10"
|
||||
src={getImageUrl(`/${item && item.user_picture}`)}
|
||||
alt=""
|
||||
width="30"
|
||||
height="30"
|
||||
/>
|
||||
{item && item.user_name}
|
||||
</span>
|
||||
}
|
||||
|
||||
</div>
|
||||
<div className="ml40">
|
||||
{item.content ? (
|
||||
|
|
|
|||
|
|
@ -23,54 +23,6 @@ class DetailTop extends Component {
|
|||
super(props);
|
||||
this.state = {}
|
||||
}
|
||||
|
||||
getProject = (num) => {
|
||||
const { projectsId, owner } = this.props.match.params;
|
||||
const url = `https://www.gitlink.org.cn/${owner}/${projectsId}/simple.json`;
|
||||
axios.get(url).then((result) => {
|
||||
if (result && result.data) {
|
||||
this.setState({
|
||||
project: result.data,
|
||||
open_devops: result.data.open_devops,
|
||||
platform: result.data.platform && result.data.platform !== 'educoder'
|
||||
})
|
||||
if (result.data.type !== 0 && result.data.mirror_status === 1) {
|
||||
console.log("--------start channel --------");
|
||||
// 是镜像项目,且未完成迁移
|
||||
this.canvasChannel();
|
||||
if (num) {
|
||||
this.setState({
|
||||
secondSync: true,
|
||||
firstSync: false
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
firstSync: true,
|
||||
secondSync: false
|
||||
})
|
||||
}
|
||||
this.setState({
|
||||
mirror_status:1
|
||||
})
|
||||
} else if (result.data.mirror_status === 2) {
|
||||
this.setState({
|
||||
mirror_status:2,
|
||||
firstSync: false
|
||||
})
|
||||
this.deleteProjectBack();
|
||||
} else {
|
||||
this.setState({
|
||||
firstSync: false,
|
||||
secondSync: false,
|
||||
mirror_status:0
|
||||
})
|
||||
this.getBanner();
|
||||
this.getDetail();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
// 个人中心新增的二级目录都需在此添加一次。将项目放组织和个人名下后,此处根据二级目录作判断,
|
||||
let secondRouter = '';
|
||||
|
|
|
|||
|
|
@ -410,7 +410,7 @@ class Infos extends Component {
|
|||
</Menu.Item>
|
||||
{/* 当前用户+配置了贡献确权 */}
|
||||
{current_user && user && user.login === current_user.login && current_user.open_blockchain && <Menu.Item key="7">
|
||||
<Link to={`/${user && user.login}/blockchain`}><i className="iconfont icon-zuzhi"></i>贡献确权</Link>
|
||||
<Link to={`/${user && user.login}/blockchain`}><i className="iconfont icon-gongxianquequan"></i>贡献确权</Link>
|
||||
</Menu.Item>}
|
||||
</Menu>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,11 @@ import img1 from '../../Images/personalInfo3.png';
|
|||
import { setmiyah } from '../../../common/Component';
|
||||
import { useEffect } from 'react';
|
||||
import Phone from './Phone';
|
||||
import OffAccount from './component/offAccount';
|
||||
|
||||
export default Form.create()(
|
||||
forwardRef((props)=>{
|
||||
const { getFieldDecorator, validateFields, resetFields, getFieldsValue } = props && props.form;
|
||||
const { getFieldDecorator, validateFields, resetFields, getFieldsValue , setFieldsValue } = props && props.form;
|
||||
const { current_user, type, resetUserInfo} = props;
|
||||
|
||||
const [ NewPass , setNewPass ] = useState(undefined);
|
||||
|
|
@ -20,6 +21,9 @@ export default Form.create()(
|
|||
const [ passMap, setPassMap] = useState({password: undefined, email: undefined, code: undefined});
|
||||
const [ updatePsdMap, setUpdatePsdMap] = useState({oldPassword: undefined, password: undefined, password1: undefined});
|
||||
const [ visible, setVisible] = useState(false);
|
||||
const [ offVisible , setOffVisible ] = useState(false);
|
||||
const [ offAgain , setOffAgain ] = useState(false);
|
||||
const [ offLoading , setOffLoading ] = useState(false);
|
||||
|
||||
useEffect(()=>{
|
||||
setPassMap({password: undefined, email: undefined, code: undefined});
|
||||
|
|
@ -156,6 +160,8 @@ export default Form.create()(
|
|||
setPassMap(map);
|
||||
callback(res.data.message);
|
||||
}
|
||||
// 防止已经输入过验证码但是还未输入邮箱地址
|
||||
setFieldsValue({code:""})
|
||||
})
|
||||
}else{
|
||||
setEmailValue(undefined);
|
||||
|
|
@ -168,7 +174,7 @@ export default Form.create()(
|
|||
// 校验用户填写验证码是否正确
|
||||
function checkCode(rule, value, callback){
|
||||
const map = passMap;
|
||||
if(value){
|
||||
if(value && emailValue){
|
||||
current_user && value && value.length > 4 && Axios.post(`/v1/${current_user.login}/check_email_verify_code.json`,{
|
||||
code_type: 10,
|
||||
email: emailValue,
|
||||
|
|
@ -237,6 +243,27 @@ export default Form.create()(
|
|||
}).catch(error=>{})
|
||||
}
|
||||
}
|
||||
// 注销账号检测
|
||||
function checkAccount(){
|
||||
if(current_user){
|
||||
setOffLoading(true);
|
||||
const url = `/v1/${current_user.login}/check_user_can_delete.json`;
|
||||
Axios.post(url).then(result=>{
|
||||
if(result.data){
|
||||
let can_delete = result.data.can_delete;
|
||||
if(can_delete === false){
|
||||
setOffAgain(true);
|
||||
message.error("当前账号名下存在拥有的组织/代码库,请先删除或转让后再尝试注销操作");
|
||||
} else {
|
||||
setOffAgain(false);
|
||||
setOffVisible(true);
|
||||
message.success("已满足注销条件");
|
||||
}
|
||||
setOffLoading(false);
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}
|
||||
}
|
||||
// type: 0邮箱管理 1密码管理 2账号注销
|
||||
return(
|
||||
<Fragment>
|
||||
|
|
@ -333,14 +360,15 @@ export default Form.create()(
|
|||
</Form>
|
||||
: type === '2' ?
|
||||
<div>
|
||||
<OffAccount visible={offVisible} onCancel={()=>{setOffVisible(false)}} {...props}/>
|
||||
<div className='tipsBox mb15'><img src={img1} alt="" width={16} className='mr10'/>请您谨慎操作,注销后帐号内所有数据都会被清空,且无法恢复帐号!</div>
|
||||
<div className='tipCont'>
|
||||
GitLink目前提供邮件渠道帐号注销服务。如需注销帐号,请通过发送邮件注销申请邮件到 <span className='orange'>gitlink@ccf.org.cn</span>。<br/>
|
||||
相关注意事项如下:<br/>
|
||||
<span className='oneTipBox mt10'>必须使用要注销的 GitLink 帐号所绑定的邮箱地址发送邮件。</span>
|
||||
<span className='oneTipBox mt5'>发送注销申请邮件之前确认帐号下无创建/加入组织,帐号名下无仓库信息。</span>
|
||||
<span className='oneTipBox mt5'>邮件标题: 注销 GitLink 帐号,邮件正文请注明要注销帐号的邮箱和昵称。</span>
|
||||
<span className='oneTipBox mt5'>发送邮件后,我们会积极处理,请耐心等候我们的邮件回复。</span>
|
||||
您可点击按钮完成账号的注销,注销后相关数据将不可恢复,注意事项如下:<br/>
|
||||
<span className='oneTipBox mt10'>提交注销前,请确保此账号下没有拥有的组织及代码库,若存在上述情况请及时转让或删除。</span>
|
||||
<span className='oneTipBox mt5'>注销后该账号产生的所有数据将会丢失且不可恢复,请提前进行数据备份。</span>
|
||||
{/* <span className='oneTipBox mt5'>注销账号后,该账号在Gitlink产生的所有过程数据(如疑修,合并请求,论坛帖子等)也将被删除。</span>
|
||||
<span className='oneTipBox mt5'>注销后你将丢失该帐号产生的所有数据,一旦注销相关数据将不可恢复,请输入登录密码再次确认。</span> */}
|
||||
{ current_user && <Button className='mt30' loading={offLoading} onClick={checkAccount} style={{color:"red",borderColor:"red"}}>{!offAgain ? "注销账号" : "重新检测"}</Button> }
|
||||
</div>
|
||||
</div>
|
||||
:<Phone {...props}/>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
import React , { forwardRef, useState } from 'react';
|
||||
import { Modal , Form , Button , Input } from 'antd';
|
||||
import Axios from 'axios';
|
||||
const { TextArea } = Input;
|
||||
|
||||
export default Form.create()(forwardRef((props)=>{
|
||||
const { getFieldDecorator , validateFields } = props && props.form;
|
||||
const { onCancel, visible , current_user} = props;
|
||||
const [ loading , setLoading ] =useState(false);
|
||||
|
||||
|
||||
function onDelete(){
|
||||
validateFields((error,values)=>{
|
||||
if(!error && current_user){
|
||||
setLoading(true);
|
||||
const url = `/v1/${current_user.login}.json`;
|
||||
Axios.delete(url,{
|
||||
data:{...values}
|
||||
}).then(result=>{
|
||||
if(result.data && result.data.status === 0){
|
||||
window.location.href = `/`;
|
||||
onCancel();
|
||||
}
|
||||
setLoading(false);
|
||||
}).catch(error=>{setLoading(false);})
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
return(
|
||||
<Modal
|
||||
title="注销账号"
|
||||
width={560}
|
||||
visible={visible}
|
||||
footer={null}
|
||||
onCancel={onCancel}
|
||||
destroyOnClose={true}
|
||||
>
|
||||
<div>
|
||||
<p className='AlignCenter font-bd font-17'><i className='iconfont icon-shanchu_important font-25 color-red mr5'/>是否仍要注销?</p>
|
||||
<p className='mt15'>注销后你将丢失该帐号产生的所有数据,一旦注销相关数据将不可恢复,请输入登录密码再次确认</p>
|
||||
<Form className='mt25'>
|
||||
<Form.Item label="密码校验">
|
||||
{getFieldDecorator("password",{
|
||||
rules:[
|
||||
{required:true,message:"请输入登录密码"},
|
||||
// {validator:(rule, value, callback)=>checkNewPass(rule, value, callback, NewPassRepeat)}
|
||||
],
|
||||
validateFirst: true,
|
||||
validateTrigger:"onBlur",
|
||||
})(
|
||||
<Input.Password placeholder="请输入登录密码"/>
|
||||
)}
|
||||
</Form.Item>
|
||||
<Form.Item label="注销原因">
|
||||
{getFieldDecorator("memo",{
|
||||
rules:[{required:true,message:"请输入注销原因"}],
|
||||
})(
|
||||
<TextArea rows={3} maxLength={500} placeholder="我们非常遗憾看到您想要注销账号。在您做出最终决定之前,请告诉我们您注销的原因,这将帮助我们改进社区服务。您的反馈对我们至关重要。"
|
||||
/>
|
||||
)}
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<div style={{textAlign:"center"}}>
|
||||
<Button onClick={onDelete} style={{minWidth:"95px"}} loading={loading}>仍要注销</Button>
|
||||
<Button type={"primary"} style={{width:"95px"}} className='ml25' onClick={onCancel}>再想想</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)})
|
||||
)
|
||||
|
|
@ -1,11 +1,8 @@
|
|||
|
||||
import React , { useEffect , useState } from 'react';
|
||||
import { Button, Dropdown, Input, Menu , message, Pagination } from 'antd';
|
||||
import Search from '../../Component/Search';
|
||||
import Item from '../Team-item';
|
||||
import Nodata from '../../Nodata';
|
||||
import axios from 'axios';
|
||||
import CheckProfile from '../../Component/ProfileModal/Profile';
|
||||
import './index.scss';
|
||||
import SearchUser from '../../Component/SearchUser';
|
||||
import { FlexAJ } from '../../Component/layout';
|
||||
|
|
@ -28,6 +25,10 @@ function Contribution(props){
|
|||
const [error2, setError2] = useState(undefined);
|
||||
// 操作仓库id
|
||||
const [proId, setProId] = useState(undefined);
|
||||
// 名称搜索
|
||||
const [content, setContent] = useState(undefined);
|
||||
// 平台用户列表
|
||||
const [userList, setUserList] = useState([]);
|
||||
const {current_user:{user_id, login, open_blockchain}, current_user} = props;
|
||||
const {user} = props;
|
||||
const username = props.match.params.username;
|
||||
|
|
@ -43,7 +44,8 @@ function Contribution(props){
|
|||
axios.get(`/users/blockchain/balance.json`,{params: {
|
||||
user_id,
|
||||
limit,
|
||||
page
|
||||
page,
|
||||
keyword: content
|
||||
}}).then(res=>{
|
||||
if(res && res.data){
|
||||
const {projects} = res.data;
|
||||
|
|
@ -63,7 +65,7 @@ function Contribution(props){
|
|||
setTotal(res.data.total_count);
|
||||
}
|
||||
})
|
||||
}, [reload, page])
|
||||
}, [reload, page, content])
|
||||
|
||||
useEffect(()=>{
|
||||
if(user){
|
||||
|
|
@ -73,6 +75,16 @@ function Contribution(props){
|
|||
}
|
||||
},[user])
|
||||
|
||||
useEffect(()=>{
|
||||
// 获取平台用户列表
|
||||
const url = `/users/list.json`;
|
||||
axios.get(url).then((result) => {
|
||||
if (result) {
|
||||
setUserList(result.data.users);
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
|
||||
// 目标转账用户
|
||||
function getUser(id, login){
|
||||
// 使用JSON进行对象深拷贝
|
||||
|
|
@ -113,7 +125,7 @@ function Contribution(props){
|
|||
project_id: id
|
||||
}).then(res=>{
|
||||
if(res && res.data && res.data.status === 2){
|
||||
getUser(id, undefined);
|
||||
// getUser(id, undefined);
|
||||
setToken(id, undefined);
|
||||
setReload(Math.random());
|
||||
message.success('转账成功');
|
||||
|
|
@ -124,10 +136,14 @@ function Contribution(props){
|
|||
|
||||
return(<div className='mt15 contribution'>
|
||||
{/* 转账/记录切换 */}
|
||||
{/* <div className='contHead'>
|
||||
<a className={`itemCont ${type ? 'active' : ''}`} onClick={()=>{setType(1)}}>token转账</a>
|
||||
<a className={`itemCont ${type ? '' : 'active'}`} onClick={()=>{setType(0)}}>token记录</a>
|
||||
</div> */}
|
||||
<div className='contHead clearfix'>
|
||||
{/* <a className={`itemCont ${type ? 'active' : ''}`} onClick={()=>{setType(1)}}>token转账</a>
|
||||
<a className={`itemCont ${type ? '' : 'active'}`} onClick={()=>{setType(0)}}>token记录</a> */}
|
||||
<Input.Search className='fr' style={{width: 'auto'}} placeholder='请输入项目名称进行搜索' allowClear onSearch={(value)=>{
|
||||
setPage(1);
|
||||
setContent(value);
|
||||
}}/>
|
||||
</div>
|
||||
{/* token转账 */}
|
||||
{type ? <div className='contributionTable mt25'>
|
||||
{list && list.length > 0 && <div className='flexCenterCont tableHeadTri mb5'>
|
||||
|
|
@ -137,12 +153,12 @@ function Contribution(props){
|
|||
<div className='tableColumn4'>转账token数额</div>
|
||||
<div className='tableColumn5'>操作</div>
|
||||
</div>}
|
||||
{list && !list.length && <Nodata _html="暂无数据"/>}
|
||||
{((list && !list.length) || !list) && <Nodata _html="暂无数据"/>}
|
||||
{list && list.map(item=>{return item.id ? <div key={item.id} className='flexCenterCont transferAccount'>
|
||||
<div className='tableColumn1'>{item.name}</div>
|
||||
<div className='tableColumn2'>{item.balance}</div>
|
||||
<div className='tableColumn3'>
|
||||
<SearchUser getUser={(login)=>{getUser(item.id, login)}} width={"100%"} placeholder="搜索转账目标用户" value={userLogin[item.id]}/>
|
||||
<SearchUser getUser={(login)=>{getUser(item.id, login)}} width={"100%"} placeholder="搜索转账目标用户" userList={userList}/>
|
||||
{proId === item.id && error1 && <div className='errorTipTri'>{error1}</div>}
|
||||
</div>
|
||||
<div className='tableColumn4'>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ 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 {getWiki, getMenuListAndSidebarData} from '../../forge/Wiki/api';
|
||||
import bg from '../img/help_bg.png';
|
||||
import './index.scss';
|
||||
function Help(props) {
|
||||
|
|
@ -12,34 +12,30 @@ function Help(props) {
|
|||
const [itemDetail, setItemDetail] = useState({});
|
||||
// locationStatus true 正式环境 false 测试环境或者开发环境
|
||||
const locationStatus = window.location.host.indexOf("50008") !== -1 || window.location.host.indexOf("gitlink") !== -1;
|
||||
const wikiParams = {
|
||||
owner: locationStatus ? 'CCF-GLCC' : 'kkky',
|
||||
repo: locationStatus ? 'glcc-help' : 'Javaaa',
|
||||
projectId: locationStatus ? '1403908' : '736'
|
||||
}
|
||||
|
||||
// 获取仓库wiki列表
|
||||
useEffect(()=>{
|
||||
wikiPages({
|
||||
owner: locationStatus ? 'CCF-GLCC' : 'forgetest2',
|
||||
repo: locationStatus ? 'glcc-help' : 'wiki1',
|
||||
projectId: locationStatus ? '1403908' : '546103'
|
||||
}).then(res => {
|
||||
if (res && res.message === "200" && Array.isArray(res.data)) {
|
||||
setFileArrInit(res.data.filter(item=>item.name !== "_Sidebar"));
|
||||
if (res.data.length) {
|
||||
setCheckItem(res.data[0]);
|
||||
};
|
||||
} else {
|
||||
setFileArrInit([]);
|
||||
}
|
||||
});
|
||||
getMenuListAndSidebarData(wikiParams).then(res=>{
|
||||
const {wikiPages=[]} = res;
|
||||
setFileArrInit(wikiPages.filter(item=>item.title !== "_Sidebar"));
|
||||
if (wikiPages.length) {
|
||||
setCheckItem(wikiPages[0]);
|
||||
};
|
||||
})
|
||||
}, [])
|
||||
|
||||
// 获取选择wiki详情
|
||||
useEffect(() => {
|
||||
checkItem.name && getWiki({
|
||||
owner: locationStatus ? 'CCF-GLCC' : 'forgetest2',
|
||||
repo: locationStatus ? 'glcc-help' : 'wiki1',
|
||||
pageName: checkItem.name,
|
||||
projectId: locationStatus ? '1403908' : '546103'
|
||||
checkItem.title && getWiki({
|
||||
...wikiParams,
|
||||
pageName: checkItem.title,
|
||||
}).then(res => {
|
||||
if (res && res.message === "200") {
|
||||
if (res && res.code === 200) {
|
||||
setItemDetail(res.data);
|
||||
} else {
|
||||
showNotification("加载失败")
|
||||
|
|
@ -53,10 +49,10 @@ function Help(props) {
|
|||
<div className='menuList'>
|
||||
<span className='font-18 mb15'>帮助文档</span>
|
||||
{fileArrInit && fileArrInit.map(item => {
|
||||
return <span className={`wiki_title font-15 mb15 ${item.name === checkItem.name? 'active' : ''}`} key={item.name} onClick={() => { setCheckItem(item) }}>{item.name}</span>})}
|
||||
return <span className={`wiki_title font-15 mb15 ${item.title === checkItem.title? 'active' : ''}`} key={item.title} onClick={() => { setCheckItem(item) }}>{item.title}</span>})}
|
||||
</div>
|
||||
<div className="help_cntent pt10" >
|
||||
{itemDetail && itemDetail.md_content && <RenderHtml value={ itemDetail && Base64.decode(itemDetail.md_content) } url={history.location}/>}
|
||||
{itemDetail && itemDetail.content_base64 && <RenderHtml value={ Base64.decode(itemDetail.content_base64) } url={history.location}/>}
|
||||
<img src={bg} className='bg' alt='bg'/>
|
||||
</div>
|
||||
</div>}
|
||||
|
|
|
|||
|
|
@ -79,13 +79,13 @@ export default (props) => {
|
|||
<div className="pt6">掌握项目课题详细信息</div>
|
||||
</Link>}
|
||||
{/* 结项考核结果公示页 */}
|
||||
{period === "finalExamine3" && <Link className="apply" to={`/glcc/${yearByRound[currentRound]}/result`}>
|
||||
{/* {period === "finalExamine3" && <Link className="apply" to={`/glcc/${yearByRound[currentRound]}/result`}>
|
||||
<div>
|
||||
<img src={img1} alt="" className="applyIcon" />
|
||||
<span className="til">正式课题考核结果</span>
|
||||
</div>
|
||||
<div className="pt6">结项课题考核结果公示</div>
|
||||
</Link>}
|
||||
</Link>} */}
|
||||
|
||||
{/* 0元项目 */}
|
||||
{/* <Link to={`/glcc/freeproject`} className="apply project" >
|
||||
|
|
@ -168,12 +168,12 @@ export default (props) => {
|
|||
</Link>}
|
||||
|
||||
{/* 中期考核结果公示页 */}
|
||||
{period === "mediumExamine3" && <Link className="apply" to={`/glcc/${yearByRound[currentRound]}/result`}>
|
||||
{(period === "mediumExamine3" || period === "finalExamine3") && <Link className="apply" to={`/glcc/${yearByRound[currentRound]}/result`}>
|
||||
<div>
|
||||
<img src={img1} alt="" className="applyIcon" />
|
||||
<span className="til">考核结果</span>
|
||||
</div>
|
||||
<div className="pt6">中期课题考核结果公示</div>
|
||||
<div className="pt6">{period === "mediumExamine3" ? "中期" : "结项"}课题考核结果公示</div>
|
||||
</Link>}
|
||||
</div>}
|
||||
{/* </div> */}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
}
|
||||
|
||||
.ant-message {
|
||||
z-index: 20000;
|
||||
z-index: 20000!important;
|
||||
}
|
||||
|
||||
.ant-upload-list-item-info .anticon-loading,
|
||||
|
|
|
|||
|
|
@ -331,7 +331,7 @@ class LoginDialog extends Component {
|
|||
this.setState({
|
||||
isphone: flag,
|
||||
//查询第三方登录信息
|
||||
settings: JSON.parse(localStorage.getItem("chromesetting") || '{}'),
|
||||
settings: localStorage.getItem("chromesetting") &&localStorage.getItem("chromesetting")!=="undefined" && JSON.parse(localStorage.getItem("chromesetting") || '{}'),
|
||||
})
|
||||
|
||||
if (this.props.isRender != undefined) {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import SystemNotice from '../../forge/Component/NoticeModal/SystemNotice';
|
|||
import cookie from 'react-cookies';
|
||||
import NpsModal from '../modals/npsModal';
|
||||
import { isPhone } from 'educoder';
|
||||
import SiderBar from '../../forge/Component/SiderBar';
|
||||
|
||||
export function TPMIndexHOC(WrappedComponent) {
|
||||
return class II extends React.Component {
|
||||
|
|
@ -297,6 +298,7 @@ export function TPMIndexHOC(WrappedComponent) {
|
|||
let pathCheck = path.indexOf("/zone")>-1 && path.indexOf("/newdetail")>-1 && isPhone();
|
||||
return (
|
||||
<div className="indexHOC">
|
||||
{(!path || (path && !path.includes('glcc') )) && <SiderBar {...this.props} {...this.state}/>}
|
||||
<SystemNotice
|
||||
system_notification={mygetHelmetapi && mygetHelmetapi.system_notification}
|
||||
history={this.props.history}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@ import { url, zoneUrl } from '../ssrUrl';
|
|||
|
||||
let baseUrl = `${url}/api`
|
||||
let baseZoneUrl = `${zoneUrl}/api`
|
||||
|
||||
if (__CLIENT__ ) {
|
||||
let settings = localStorage.chromesetting && localStorage.chromesetting !== 'undefined' &&JSON.parse(localStorage.chromesetting);
|
||||
baseUrl = settings && settings.common && settings.common.main_site_url ? `${settings.common.main_site_url}/api` : baseUrl;
|
||||
baseZoneUrl = settings ? `${settings && settings.common.zone}/api` : `${zoneUrl}/api`;
|
||||
}
|
||||
console.log("__CLIENT__",__CLIENT__,baseUrl);
|
||||
|
||||
const axiosInstance = axios.create({
|
||||
withCredentials: true
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import javaFetch from '../forge/javaFetch';
|
||||
|
||||
let settings = localStorage.chromesetting && JSON.parse(localStorage.chromesetting);
|
||||
let settings = localStorage && localStorage.chromesetting && localStorage.chromesetting !=="undefined" && JSON.parse(localStorage.chromesetting);
|
||||
let actionUrl = settings && settings.common.softbot;
|
||||
// let actionUrl = 'http://111.8.36.180:8069';
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import './index.scss';
|
|||
import axios from "axios";
|
||||
import RenderHtml from "../../components/render-html";
|
||||
import { Link } from "react-router-dom";
|
||||
import LogoBot from '../image/logoBot.png';
|
||||
|
||||
function SoftBotDEtail(props){
|
||||
const {match:{params:{id}}, current_user, history, showLoginDialog} = props;
|
||||
|
|
@ -171,7 +172,7 @@ function SoftBotDEtail(props){
|
|||
return <div className="softBotDetailBox">
|
||||
{detail && <div className="botDetailHead">
|
||||
<div className="botDetailHeadCont">
|
||||
<div className="botImgBg"><img src={getImageUrl(detail.logo)} alt="" className="botImg"></img></div>
|
||||
<div className="botImgBg"><img src={(detail.logo && !(detail.logo.indexOf("347246")>-1 || detail.logo.indexOf("412603")>-1)) ? getImageUrl(detail.logo) : LogoBot} alt="" className="botImg"></img></div>
|
||||
<div className="centerBox">
|
||||
<p className="font-24">{detail.bot_name}</p>
|
||||
<div className="userBox font-16">{detail.first_func && <span className="typeBox font-15 mr15">{detail.first_func}</span>}{detail.second_func && <span className="typeBox font-15 mr15">{detail.second_func}</span>}<a href={`/${detail.developer_login}`}>{detail.developer_name}</a></div>
|
||||
|
|
@ -187,7 +188,7 @@ function SoftBotDEtail(props){
|
|||
</div>
|
||||
</div>
|
||||
<Modal
|
||||
title={<div><div className="installBotImgBox"><img src={getImageUrl(detail && detail.logo)} alt="" className="installBotImg"/></div><div className="font-18 installBotName">{detail && detail.bot_name}</div></div>}
|
||||
title={<div><div className="installBotImgBox"><img src={(detail && detail.logo && !(detail.logo.indexOf("347246")>-1 || detail.logo.indexOf("412603")>-1)) ? getImageUrl(detail.logo) : LogoBot} alt="" className="installBotImg"/></div><div className="font-18 installBotName">{detail && detail.bot_name}</div></div>}
|
||||
visible={visible}
|
||||
onOk={okClick}
|
||||
onCancel={()=>{setSelectValue([]);setVisible(false)}}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ import { getImageUrl } from 'educoder'
|
|||
import './index.scss';
|
||||
import { Link } from "react-router-dom";
|
||||
import {getAllBotCategory, getContentsLikeNameAndFunc} from '../api.js';
|
||||
import Nodata from '../../forge/Nodata'
|
||||
import Nodata from '../../forge/Nodata';
|
||||
import LogoBot from '../image/logoBot.png';
|
||||
|
||||
function SoftBot(props){
|
||||
const {mygetHelmetapi, current_user, history, showLoginDialog} = props;
|
||||
|
|
@ -85,7 +86,7 @@ function SoftBot(props){
|
|||
{!loading && botList && botList.length > 0 && botList.map((item, index)=>{
|
||||
return <div className="oneBotItem mt30" key={index} onClick={()=>{window.location.href=`/softbot/${item.bot_id}`}}>
|
||||
<div className="oneItemHead">
|
||||
<div className="oneBotImgBox"><img src={getImageUrl(item.logo)} alt="" className="oneBotImg"/></div>
|
||||
<div className="oneBotImgBox"><img src={(item.logo && !(item.logo.indexOf("347246")>-1 || item.logo.indexOf("412603")>-1)) ? getImageUrl(item.logo) : LogoBot} alt="" className="oneBotImg"/></div>
|
||||
<div className="oneBotLine">
|
||||
<p className="font-16 oneBotName oneBotLine">{item.market_name}</p>
|
||||
<p>{item.developer_name}</p>
|
||||
|
|
|
|||
Loading…
Reference in New Issue