合并代码

This commit is contained in:
谢思 2024-10-30 15:37:31 +08:00
commit 1695780bef
98 changed files with 2503 additions and 3243 deletions

View File

@ -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);
}

View File

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

View File

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

View File

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

View File

@ -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');
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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>
}
},
{

View File

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

View File

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

View File

@ -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>
}
},
{

View File

@ -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);
// sidebarlist
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>

View File

@ -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);
}

View File

@ -25,7 +25,6 @@ function NewsList(props){
const [ menuSpin , setMenuSpin ] = useState(true);
const { id, temp } = props;
useEffect(()=>{
if(id && cateId ){
setMenuSpin(true);

View File

@ -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',
})
}

View File

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

View File

@ -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();
}

View File

@ -35,6 +35,10 @@
}
}
.themeSpin .ant-spin-dot-item{
background-color: var(--primary-color);
}
.information_main{
background-color:#f3f5f8;
min-height: 100vh;

View File

@ -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}&nbsp;</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>

View File

@ -62,6 +62,11 @@
position: absolute;
left: 12px;
}
.operationWords{
a{
color:#466aff;
}
}
.operationLog+.commentContentBox{
&>a, &>.commentContentRight{
margin-top: 25px;

View File

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

View File

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

View File

@ -185,6 +185,7 @@ function Sign(props){
}
function createSign(){
setEditId(undefined);
const c = Math.random().toString(16).substr(-6);
c && setDefaultColor(`#${c}`);
setEdit(true);

View File

@ -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{
// entries404
props.history.push(`/${owner}/${projectsId}`)
}
setTimeout(function(){setIsSpin(false);},500);
}).catch(error=>{setIsSpin(false);})

View File

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

View File

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

View File

@ -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
? "创建于"

View File

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

View File

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

View File

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

View File

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

View File

@ -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() : '',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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}
],

View File

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

View File

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

View File

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

View File

@ -129,4 +129,14 @@
margin-right: 40px;
}
}
}
.exterpriseBox{
.padding30{
color: #4C5876;
line-height: 30px;
border-bottom: 1px dashed #eee;
}
.font-bd{
color: #0D0F12;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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 {
// wikiwikinamemd
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"){
// wikisidebar
if(wikiName === title){
message.success("操作成功");
goBack();
}else{
// keynodetitlesidebar
const newMenuList = menuList;
editTitleByKey(newMenuList, key, title);
updateSidebar(treeToMd(newMenuList), title);
}
}else{
//
if(search === "?copy"){
// keykey
const keyArr = key.split('-');
keyArr.pop();
const list = menuList;
addChildrenByKey(list, keyArr.join('-'), title);
updateSidebar(treeToMd(list), title);
}else if(search.includes("?key")){
// menuListkeychildren push[[]]
const list = menuList;
addChildrenByKey(list, key, title);
updateSidebar(treeToMd(list), title);
}else{
if(fileArrInit.length){
updateSidebar(`${sidebar}\n[[${title}]]`, title);
}else{
// wiki
addWiki({
owner,
repo: projectsId,
projectId: project.id,
pageName: "_Sidebar",
title: "_Sidebar",
message: '',
content_base64: Base64.encode(`[[${title}]]`)
}).then(res => {
if(res && res.message === "201"){
message.success("操作成功");
goBack();
}
});
}
}
}
} else if (res && res.message === "500") {
setOperateFlag(false);
message.error('请检查格式是否正确或文件名是否重复');
} else {
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, keytitle
function editTitleByKey(list, key, newTitle){
for (let index = 0; index < list.length; index++) {
const element = list[index];
if (element.key == key) {
const {title} = element;
element.title = `${title.substring(0, title.indexOf('[['))}[[${newTitle}]]`;
return true;
}else if (element.children.length) {
let result = editTitleByKey(element.children, key, newTitle);
if (result) {
return result;
}
}
}
}
// menuList
function addChildrenByKey(list, key, name){
for (let index = 0; index < list.length; index++) {
const element = list[index];
if (element.key == key) {
const {title} = element;
element.children.push({
title: `\t${title.substring(0, title.indexOf('- '))}[[${name}]]`,
children: []
});
return true;
}else if (element.children.length) {
let result = addChildrenByKey(element.children, key, name);
if (result) {
return result;
}
}
}
}
function goBack() {
history.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>
)
})

View File

@ -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);
// sidebarlist
const info = await parseSidebar(res.data, {
owner: owner,
repo: projectsId,
projectId: project.id
})
if(!info){return}
const {menuList, sidebar} = info;
setSidebar(sidebar)
setFileList(menuList);
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;
}
// fileListmdsidebar
const newSidebarList = fileList.concat({title: `- ${menuName}`, children: []})
updateWikiFun(newSidebarList, 1);
}
// wiki sidebar
function updateWikiFun(sidebarList, type){
updateWiki({
owner,
repo: projectsId,
projectId: project.id,
pageName: '_Sidebar',
title: '_Sidebar',
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}`));
}
// MarkdownHtmlPdf
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){
// wikisidebarfindSameKeyByDel
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>
)
}

View File

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

View File

@ -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);
// sidebarlist
const info = await parseSidebar(res.data, {
owner: owner,
repo: projectsId,
projectId
})
const {menuList, sidebar} = info;
setFileList(menuList);
if (res.data.length) {
setCheckItem(res.data[0]);
};
} 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`);
}
// MarkdownHtmlPdf
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>
)
}

View File

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

View File

@ -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">欢迎使用&nbsp;
<span className="wiki-title">
{project && project.name}
</span>
&nbsp;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>
)
}

View File

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

View File

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

View File

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

View File

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

View File

@ -16,6 +16,9 @@
.ant-modal-body {
text-align: center;
}
.ant-form-explain{
text-align: left;
}
.content-title {
display: flex;
justify-content: center;

View File

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

View File

@ -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 月度总结:";

View File

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

View File

@ -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)) {
// : keychildren
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)) {
// wikiwikiwiki
// findSamaNameWiki: wiki
sameNameMenu = findSamaNameWiki(wikiPageInitDetail.wikiPages, name);
}
if (sameNameMenu) {
// message.error('');
editForm.setFields({name: {value: name, errors:[new Error('不能与已有名称重复')]}});
} else {
// wikisidebarContentsidebar
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

View File

@ -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>
);
});
}
// listkey 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){
// sidebarkey
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){
// wikisidebar
deleteWiki({owner: owner, repo: projectsId, projectId: project.id, pageName: "_Sidebar"}).then(res => {
if(res && res.message === "204"){
history.push(`/${owner}/${projectsId}/wiki`)
setReload(Math.random());
message.success('删除成功');
}
});
return
}
updateWiki({
owner,
repo: projectsId,
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'){
// wikisidebar
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>
);
}

View File

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

View File

@ -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])
// keykey
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));
}
// listkey 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){
// wikisidebar
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

View File

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

View File

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

10
src/forge/Wiki/config.js Normal file
View File

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

View File

@ -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(); //wikiaddWiki 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);
// wikiwiki
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){
// wikiwiki
message.success('修改成功');
reload();
return
}
// wikisidebarwiki
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{
// wikisidebar
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>
})

View File

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

View File

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

View File

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

View File

@ -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">欢迎使用&nbsp;
<span className="wiki-title">
{project && project.name}
</span>
&nbsp;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>
)
}

View File

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

View File

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

View File

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

View File

@ -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 = '';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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> */}

View File

@ -25,7 +25,7 @@
}
.ant-message {
z-index: 20000;
z-index: 20000!important;
}
.ant-upload-list-item-info .anticon-loading,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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