diff --git a/public/assets/kindeditor/kindeditor.js b/public/assets/kindeditor/kindeditor.js index 1df0f835a..9668701f6 100644 --- a/public/assets/kindeditor/kindeditor.js +++ b/public/assets/kindeditor/kindeditor.js @@ -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); } diff --git a/public/css/iconfont.css b/public/css/iconfont.css index 3747ea067..f877beb85 100644 --- a/public/css/iconfont.css +++ b/public/css/iconfont.css @@ -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"; } diff --git a/public/css/iconfont.js b/public/css/iconfont.js index 82eb93ac3..433c5d5f1 100644 --- a/public/css/iconfont.js +++ b/public/css/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_2340181='',function(c){var a=(a=document.getElementsByTagName("script"))[a.length-1],l=a.getAttribute("data-injectcss"),a=a.getAttribute("data-disable-injectsvg");if(!a){var h,i,o,z,t,p=function(a,l){l.parentNode.insertBefore(a,l)};if(l&&!c.__iconfont__svg__cssinject__){c.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}h=function(){var a,l=document.createElement("div");l.innerHTML=c._iconfont_svg_string_2340181,(l=l.getElementsByTagName("svg")[0])&&(l.setAttribute("aria-hidden","true"),l.style.position="absolute",l.style.width=0,l.style.height=0,l.style.overflow="hidden",l=l,(a=document.body).firstChild?p(l,a.firstChild):a.appendChild(l))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(h,0):(i=function(){document.removeEventListener("DOMContentLoaded",i,!1),h()},document.addEventListener("DOMContentLoaded",i,!1)):document.attachEvent&&(o=h,z=c.document,t=!1,m(),z.onreadystatechange=function(){"complete"==z.readyState&&(z.onreadystatechange=null,v())})}function v(){t||(t=!0,o())}function m(){try{z.documentElement.doScroll("left")}catch(a){return void setTimeout(m,50)}v()}}(window); \ No newline at end of file +window._iconfont_svg_string_2340181='',(c=>{var a=(l=(l=document.getElementsByTagName("script"))[l.length-1]).getAttribute("data-injectcss"),l=l.getAttribute("data-disable-injectsvg");if(!l){var h,i,o,z,t,p=function(a,l){l.parentNode.insertBefore(a,l)};if(a&&!c.__iconfont__svg__cssinject__){c.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}h=function(){var a,l=document.createElement("div");l.innerHTML=c._iconfont_svg_string_2340181,(l=l.getElementsByTagName("svg")[0])&&(l.setAttribute("aria-hidden","true"),l.style.position="absolute",l.style.width=0,l.style.height=0,l.style.overflow="hidden",l=l,(a=document.body).firstChild?p(l,a.firstChild):a.appendChild(l))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(h,0):(i=function(){document.removeEventListener("DOMContentLoaded",i,!1),h()},document.addEventListener("DOMContentLoaded",i,!1)):document.attachEvent&&(o=h,z=c.document,t=!1,m(),z.onreadystatechange=function(){"complete"==z.readyState&&(z.onreadystatechange=null,v())})}function v(){t||(t=!0,o())}function m(){try{z.documentElement.doScroll("left")}catch(a){return void setTimeout(m,50)}v()}})(window); \ No newline at end of file diff --git a/public/css/iconfont.json b/public/css/iconfont.json index b178c0013..4ebfc428b 100644 --- a/public/css/iconfont.json +++ b/public/css/iconfont.json @@ -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", diff --git a/public/css/iconfont.ttf b/public/css/iconfont.ttf index 1d84cef01..30b005296 100644 Binary files a/public/css/iconfont.ttf and b/public/css/iconfont.ttf differ diff --git a/public/css/iconfont.woff b/public/css/iconfont.woff index 05eb3238b..fad2a7bab 100644 Binary files a/public/css/iconfont.woff and b/public/css/iconfont.woff differ diff --git a/public/css/iconfont.woff2 b/public/css/iconfont.woff2 index 0dd506438..d8eab05f1 100644 Binary files a/public/css/iconfont.woff2 and b/public/css/iconfont.woff2 differ diff --git a/src/App.js b/src/App.js index 779881133..74c1fcbab 100644 --- a/src/App.js +++ b/src/App.js @@ -93,7 +93,7 @@ const Search = Loadable({ }) const WikiPreview = Loadable({ - loader: () => import('./forge/Wiki/Preview'), + loader: () => import('./forge/Wiki/page/preview'), loading: Loading, }) @@ -340,9 +340,12 @@ class App extends Component { this.Modifyloginvalue()}> {/* */} - {!pathName || (pathName && pathName.toLowerCase() !== 'glcc') ? : } + {/* {!pathName || (pathName && pathName.toLowerCase() !== 'glcc') ? : } */} + {pathName && pathName.toLowerCase() === 'glcc' && } {/* */} - + { + pathType ? + {/* iframe页面 */} {/* wiki预览 */} - { return () } @@ -528,14 +531,6 @@ class App extends Component { : pathType === '404' ? : "" } - ( - - ) - } - /> - {/* 个人主页 */} + : + ( + + ) + } + /> + } + ); diff --git a/src/AppConfig.js b/src/AppConfig.js index 45f12c805..add2d851f 100644 --- a/src/AppConfig.js +++ b/src/AppConfig.js @@ -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'); } diff --git a/src/common/util/ShareUtil.js b/src/common/util/ShareUtil.js index 6e3bf6398..e8ffd8886 100644 --- a/src/common/util/ShareUtil.js +++ b/src/common/util/ShareUtil.js @@ -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]; diff --git a/src/forge/Activity/ActivityItem.js b/src/forge/Activity/ActivityItem.js index 19ce15366..1ee593e70 100644 --- a/src/forge/Activity/ActivityItem.js +++ b/src/forge/Activity/ActivityItem.js @@ -32,10 +32,18 @@ class ActivityItem extends Component {

}

- - - {item.user_name} - + { + item && item.user_login ? + + + {item.user_name} + + : + + + {item.user_name} + + } {item.created_at && 创建于{item.created_at}}

diff --git a/src/forge/Component/SearchUser.jsx b/src/forge/Component/SearchUser.jsx index 79d23fb65..de8ea9a3a 100644 --- a/src/forge/Component/SearchUser.jsx +++ b/src/forge/Component/SearchUser.jsx @@ -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 })=>{ trigger.parentNode} dataSource={source} - value={searchKey} style={{ width: width || 300 }} - onChange={changeInputUser} + onSearch={(value)=>{getUserList(value)}} onSelect={selectInputUser} placeholder={placeholder || "搜索需要添加的用户..."} allowClear + optionLabelProp="name" /> ) diff --git a/src/forge/Component/SiderBar.js b/src/forge/Component/SiderBar.js index b3900311b..4cc1d372d 100644 --- a/src/forge/Component/SiderBar.js +++ b/src/forge/Component/SiderBar.js @@ -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
diff --git a/src/forge/Component/User.jsx b/src/forge/Component/User.jsx index 198667b08..a789609dc 100644 --- a/src/forge/Component/User.jsx +++ b/src/forge/Component/User.jsx @@ -20,8 +20,8 @@ export default ({ url , name , column , id , login })=>{ } `; return( - id? - + (id && login)? + { url && } {name} diff --git a/src/forge/Dataset/Index.jsx b/src/forge/Dataset/Index.jsx index 070bb57cb..53b667a25 100644 --- a/src/forge/Dataset/Index.jsx +++ b/src/forge/Dataset/Index.jsx @@ -75,7 +75,7 @@ function Index(props){ key:3, width:"10%", render:(value,item)=>{ - return + return } }, { diff --git a/src/forge/Divert/DivertModal.jsx b/src/forge/Divert/DivertModal.jsx index d4c1f2014..27e3e4f79 100644 --- a/src/forge/Divert/DivertModal.jsx +++ b/src/forge/Divert/DivertModal.jsx @@ -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} >
{ @@ -113,7 +108,8 @@ function DivertModal({form , visible , onSuccess , onCancel,owner,repo}){
{getFieldDecorator("goal",{ - rules:[] + rules:[], + initialValue: 0 })( 个人 @@ -128,7 +124,7 @@ function DivertModal({form , visible , onSuccess , onCancel,owner,repo}){ rules:[{required:true,message:"请输入目标用户名"}] })( // - + )} } diff --git a/src/forge/Information/Component/comments/list.jsx b/src/forge/Information/Component/comments/list.jsx index 4014063d4..39c765cf9 100644 --- a/src/forge/Information/Component/comments/list.jsx +++ b/src/forge/Information/Component/comments/list.jsx @@ -125,14 +125,19 @@ function IssueCommentList(props){ {journals && (journals.length > 0 && journals.map(item=>{return
{/* 评论 */}
- + { + item.user.login ? + + : + + }
{/* 判断是否是编辑状态 */} {(showEdit === 2 && updateId === item.id) ?
:
- {item.user.name} + {item.user.login ? {item.user.name}:{item.user.name}} {timeAgo(item.created_at)}
{login && !isPhone() &&
diff --git a/src/forge/Information/Pages/dataset/fileList.jsx b/src/forge/Information/Pages/dataset/fileList.jsx index 8c83ac13c..667e9c120 100644 --- a/src/forge/Information/Pages/dataset/fileList.jsx +++ b/src/forge/Information/Pages/dataset/fileList.jsx @@ -62,7 +62,7 @@ function Index(props){ key:3, width:"10%", render:(value,item)=>{ - return + return } }, { diff --git a/src/forge/Information/Pages/help.jsx b/src/forge/Information/Pages/help.jsx index 823a6fdf0..62ebcefbf 100644 --- a/src/forge/Information/Pages/help.jsx +++ b/src/forge/Information/Pages/help.jsx @@ -1,30 +1,25 @@ import React, { useEffect, useState } from 'react'; import { Box, LongWidth, FlexAJ } from '../../Component/layout'; -import { Affix, Button, Input, message, Spin, Tree } from 'antd'; +import { Affix, Button, message, Spin } from 'antd'; import Nodata from '../../Nodata'; import nodata from '../img/nodata.png'; import { tempEnum } from '../tempInfo'; -import { findFirstWiki, generateList, getParentKey, getWiki, parseSidebar, wikiPages } from '../../Wiki/api'; +import { findNodeByFirstOrContrastName, getMenuListAndSidebarData, getWiki } from '../../Wiki/api'; import RenderHtml from '../../../components/render-html'; import { Base64 } from 'js-base64'; import { getTocContent } from '../../../common/marked'; -import { splitAndCombine } from '../../Wiki/utils'; - -const { Search } = Input; -const { DirectoryTree, TreeNode } = Tree; +import TreeByWikiSidebar from '../../Wiki/components/treeByWikiSidebar'; function ProjectSource(props) { const [loading, setLoading] = useState(false); - const [wikiList, setWikiList] = useState(undefined); - const [flattenedMenuList, setFlattenedMenuList] = useState([]); - const [initMenuList, setInitMenuList] = useState([]); - const [expandedKeys, setExpandedKeys] = useState(undefined); + const [wikiPageInitDetail, setWikiPageInitDetail] = useState({}) const [wiki, setWiki] = useState(undefined); const [wikiDetail, setWikiDetail] = useState(undefined); - const [autoExpandParent, setAutoExpandParent] = useState(false); - const [searchValue, setSearchValue] = useState(""); const [wikiContMenu, setWikiContMenu] = useState(undefined); - const { temp, history, data, adminUrl } = props; + const { temp, history, data, adminUrl, match:{params:{deptId}}, location} = props; + const {menuList=[], sidebar="", wikiPages=undefined} = wikiPageInitDetail; + const search = new URLSearchParams(location.search); + const wikiBySearch = search.get("wiki") || ''; let scroller = undefined; const { owner, identifier, projectId } = (data && data.gitlinkProject) || {}; @@ -38,26 +33,22 @@ function ProjectSource(props) { useEffect(() => { if(!owner) return // 获取wiki 列表 - wikiPages(wikiParams).then(async res => { - if (res && res.message === "200") { - setWikiList(res.data); - // 解析sidebar获取目录list - const info = await parseSidebar(res.data, wikiParams); - if (!info) return - const { menuList } = info; - // 默认打开第一个wiki - const firstWiki = findFirstWiki(menuList); - if(firstWiki){ - setExpandedKeys(splitAndCombine(firstWiki.key+"")); - setWiki(firstWiki); - } - setFlattenedMenuList(generateList(menuList)); - setInitMenuList(menuList); - } else { - setWikiList([]); - } + getMenuListAndSidebarData(wikiParams).then(res=>{ + setWikiPageInitDetail(res); }) }, [owner]) + + useEffect(()=>{ + if(!menuList.length) return + // 通过路由直接访问 + let node = findNodeByFirstOrContrastName(menuList, wikiBySearch) + // 有可能wikiBySearch是错误的值,导致右侧显示为空 + if(!node){ + history.push(`/zone/${deptId}/help`) + return + } + setWiki(node); + }, [menuList, wikiBySearch]) useEffect(() =>{ if(!wiki){ @@ -70,7 +61,7 @@ function ProjectSource(props) { ...wikiParams }).then(res=>{ setLoading(false); - if (res && res.message === "200") { + if (res && res.code === 200) { setWikiDetail(res.data); const tocContent = getTocContent(); setWikiContMenu(tocContent); @@ -154,77 +145,28 @@ function ProjectSource(props) { }; }, []) - function changeSearchValue(value) { - if (value) { - const va = value.trim(); - // 找到命中搜索的item的父元素 - const expandedKeys = flattenedMenuList.map(item => { - if (item.title.match(new RegExp(va, 'i'))) { - return getParentKey(item.key, initMenuList); - } - return null; - }).filter((item, i, self) => item && self.indexOf(item) === i); - setSearchValue(va); - setExpandedKeys(splitAndCombine(expandedKeys[0])); - } else { - setSearchValue(""); - } - } - - // 收起展开 - function onExpand(expandedKeys) { - setAutoExpandParent(false); - setExpandedKeys(expandedKeys); - } - - // 选中节点 - function selectTree(keys,event){ - let {isFile} = event.node.props.dataRef; - if(isFile){ - setWiki(event.node.props.dataRef); - } - } - - function renderTreeNodes(data) { - return data && data.length > 0 && data.map((item) => { - const matchResult = item.titleStr.match(new RegExp(searchValue, 'i')); - const title = matchResult ? ( - - {item.titleStr.substr(0, matchResult.index)} - {item.titleStr.substring(matchResult.index, matchResult.index+searchValue.length)} - {item.titleStr.substr(matchResult.index+searchValue.length)} - - ) : ( - {item.titleStr} - ); - return ( - - {renderTreeNodes(item.children)} - - ); - }); - } - - const noDataText = adminUrl ? 前往配置 : "正在建设中,敬请期待" + const noDataText = adminUrl ? 前往配置 : "正在建设中,敬请期待" return (

{data && data.sectionHelperTitle}

- + { - wikiList && (wikiList.length > 0 ? + wikiPages && (wikiPages.length > 0 ? {/* wiki目录导航 */}
- - - {renderTreeNodes(initMenuList)} - + { + history.push(`/zone/${deptId}/help?wiki=${encodeURIComponent(node.titleStr)}`) + }} + history={history} + hasOpen={false} + searchClassName="searchHC mb10" + treeNodeClassName="menuHcNode"/>
{/* 内容区 */} @@ -233,12 +175,12 @@ function ProjectSource(props) {
-
{wikiDetail.name}
- {wikiDetail.commit &&
更新时间:{wikiDetail.commit.author.date || '刚刚'}
} +
{wikiDetail.title}
+ {wikiDetail.last_commit &&
更新时间:{wikiDetail.last_commit.author.date || '刚刚'}
}
- {adminUrl && } + {adminUrl && }
- {wikiDetail.md_content && } + {wikiDetail.content_base64 && }
{wikiContMenu && wikiContMenu !== "
    " &&
    diff --git a/src/forge/Information/Pages/main.jsx b/src/forge/Information/Pages/main.jsx index 60d3fd758..ff5659fcd 100644 --- a/src/forge/Information/Pages/main.jsx +++ b/src/forge/Information/Pages/main.jsx @@ -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); } diff --git a/src/forge/Information/Pages/newsList.jsx b/src/forge/Information/Pages/newsList.jsx index 67a84e154..47278e649 100644 --- a/src/forge/Information/Pages/newsList.jsx +++ b/src/forge/Information/Pages/newsList.jsx @@ -25,7 +25,6 @@ function NewsList(props){ const [ menuSpin , setMenuSpin ] = useState(true); const { id, temp } = props; - useEffect(()=>{ if(id && cateId ){ setMenuSpin(true); diff --git a/src/forge/Information/api.js b/src/forge/Information/api.js index 392e0c5a1..63d577eb9 100644 --- a/src/forge/Information/api.js +++ b/src/forge/Information/api.js @@ -385,4 +385,45 @@ export function getProjectListByScore(id,params) { method: 'GET', params }) -} \ No newline at end of file +} + +// 获取已开通企业的组织列表 +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', + }) + } + diff --git a/src/forge/Information/fetch.js b/src/forge/Information/fetch.js index f74c1faef..af0dc1afd 100644 --- a/src/forge/Information/fetch.js +++ b/src/forge/Information/fetch.js @@ -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; \ No newline at end of file +export default service; diff --git a/src/forge/Information/index.jsx b/src/forge/Information/index.jsx index 079f2bed2..9dadf8f5e 100644 --- a/src/forge/Information/index.jsx +++ b/src/forge/Information/index.jsx @@ -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(); } diff --git a/src/forge/Information/index.scss b/src/forge/Information/index.scss index 92dce3583..d0705e466 100644 --- a/src/forge/Information/index.scss +++ b/src/forge/Information/index.scss @@ -35,6 +35,10 @@ } } +.themeSpin .ant-spin-dot-item{ + background-color: var(--primary-color); +} + .information_main{ background-color:#f3f5f8; min-height: 100vh; diff --git a/src/forge/Issues/Component/comments/list.jsx b/src/forge/Issues/Component/comments/list.jsx index afabf21a2..42f2bd82d 100644 --- a/src/forge/Issues/Component/comments/list.jsx +++ b/src/forge/Issues/Component/comments/list.jsx @@ -129,8 +129,8 @@ function IssueCommentList(props){
    - -
    + +
    {item.user.name}  {(item.user.name.length + item.operate_content.length) > 62 ? {item.user.name}
    }> : } @@ -142,14 +142,19 @@ function IssueCommentList(props){
    : '' :
    {/* 评论 */}
    - + { + item.user.login ? + + : + + }
    {/* 判断是否是编辑状态 */} {(showEdit === 2 && updateId === item.id) ?
    :
    - {item.user.name} + {item.user.login ? {item.user.name}:{item.user.name}} {timeAgo(item.created_at)}
    {login &&
    diff --git a/src/forge/Issues/Component/comments/list.scss b/src/forge/Issues/Component/comments/list.scss index be95b6e3d..fd1ca9219 100644 --- a/src/forge/Issues/Component/comments/list.scss +++ b/src/forge/Issues/Component/comments/list.scss @@ -62,6 +62,11 @@ position: absolute; left: 12px; } + .operationWords{ + a{ + color:#466aff; + } + } .operationLog+.commentContentBox{ &>a, &>.commentContentRight{ margin-top: 25px; diff --git a/src/forge/Issues/Component/datas.jsx b/src/forge/Issues/Component/datas.jsx index ea9ae9c6f..27d130c61 100644 --- a/src/forge/Issues/Component/datas.jsx +++ b/src/forge/Issues/Component/datas.jsx @@ -53,8 +53,13 @@ function Datas({checkbox ,item , projectsId,owner,chooseTagFunc}){ #{item.project_issues_index} }
    - - {item.author && item.author.name} + + {item.author && item.author.login ? + + {item.author && item.author.name} + :{item.author && item.author.name} + } + {item.created_at} 发布 {item.updated_at}更新 {item.blockchain_token_num && {item.blockchain_token_num}} diff --git a/src/forge/Issues/Pages/details.jsx b/src/forge/Issues/Pages/details.jsx index 88c1b693c..ac2b4b137 100644 --- a/src/forge/Issues/Pages/details.jsx +++ b/src/forge/Issues/Pages/details.jsx @@ -391,9 +391,13 @@ function Details(props){ #{details.project_issues_index}
    { - details.author && + details.author &&
    - {details.author.name} + { details.author.login ? + {details.author.name} + : + {details.author.name} + } 添加于{details.created_at}
    } diff --git a/src/forge/Issues/Pages/sign.jsx b/src/forge/Issues/Pages/sign.jsx index 645b8a7e5..bb475b7fd 100644 --- a/src/forge/Issues/Pages/sign.jsx +++ b/src/forge/Issues/Pages/sign.jsx @@ -185,6 +185,7 @@ function Sign(props){ } function createSign(){ + setEditId(undefined); const c = Math.random().toString(16).substr(-6); c && setDefaultColor(`#${c}`); setEdit(true); diff --git a/src/forge/Main/CoderDepot.jsx b/src/forge/Main/CoderDepot.jsx index bb0778aa0..163766edf 100644 --- a/src/forge/Main/CoderDepot.jsx +++ b/src/forge/Main/CoderDepot.jsx @@ -207,7 +207,7 @@ function CoderDepot(props){ axios.get(url, { params: { ref:branch} }).then((result) => { - if (result) { + if (result && result.data.status !== 404) { setCommitCount(result.data.commits_count); setDirInfo(result.data.entries); setFileInfo(undefined); @@ -222,6 +222,9 @@ function CoderDepot(props){ setEditReadme(false); setHide(true); getReadmeInfo('', branchName || defaultBranch); + }else{ + // entries接口返回404:分支不存在或被删除 + props.history.push(`/${owner}/${projectsId}`) } setTimeout(function(){setIsSpin(false);},500); }).catch(error=>{setIsSpin(false);}) diff --git a/src/forge/Main/Detail.js b/src/forge/Main/Detail.js index ebd9653eb..932372ded 100644 --- a/src/forge/Main/Detail.js +++ b/src/forge/Main/Detail.js @@ -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 { } - + {/* mirror_status = 1的情况不显示项目详情子模块 */} + {!firstSync && {/* 服务 */} {/* wiki编辑文件 */} - () } > - {/* wiki具体某一个地址 */} - () + () => () } > {/* wiki */} @@ -960,7 +970,7 @@ class Detail extends Component { (props) => } > - + }
    ) diff --git a/src/forge/Merge/MergeItem.js b/src/forge/Merge/MergeItem.js index 583e71651..50d1475a3 100644 --- a/src/forge/Merge/MergeItem.js +++ b/src/forge/Merge/MergeItem.js @@ -79,25 +79,34 @@ class MergeItem extends Component {

    - - - - - {item && item.author_name} - + { + item && item.author_login ? + + + {item && item.author_name} + + : + + {item && item.author_name} + + } + {item.pull_request_staus === "open" ? "创建于" @@ -114,10 +123,8 @@ class MergeItem extends Component { item.is_original ? ( (item.fork_project_user && item.fork_project_identifier) ? - + {item.fork_project_user_name || "仓库已删除"} : {item.pull_request_head || "分支已删除"} : diff --git a/src/forge/Merge/MessageCount.js b/src/forge/Merge/MessageCount.js index 251db6fef..a9516b81d 100644 --- a/src/forge/Merge/MessageCount.js +++ b/src/forge/Merge/MessageCount.js @@ -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 { {

    - - {data.pull_request && data.pull_request.fork_project_user}: {data.pull_request && data.pull_request.head} - + { + (data.pull_request.fork_project_user || data.issue.author_name!=="已注销") ? + + {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} + + : + + {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} + + + } - + - {data.issue.project_author}: {data.pull_request.base} + {data.issue.project_author_name}: {data.pull_request.base}
    }
    - - - + { + data.issue.author_login? + + + + : + + } - - {data.issue.author_name} - + { + data.issue.author_login ? + + {data.issue.author_name} + + : + {data.issue.author_name} + } {pr_status === 0 ? "创建于" diff --git a/src/forge/Newfile/codeReview/mock/component/index.jsx b/src/forge/Newfile/codeReview/mock/component/index.jsx index bbcf026fc..e219f0a72 100755 --- a/src/forge/Newfile/codeReview/mock/component/index.jsx +++ b/src/forge/Newfile/codeReview/mock/component/index.jsx @@ -146,18 +146,14 @@ const ReplyItem = memo(({ note, isChild }) => {
    - - + : "images/avatars/User/b"} size={24} className="mr10"/> {note.user && note.user.username} - 于 {timeAgo(note.createdAt)} 发表评论: + + 于 {timeAgo(note.createdAt)} 发表评论: {user && (user.admin || diff --git a/src/forge/Notice/Apply.jsx b/src/forge/Notice/Apply.jsx index aeacd67a2..f16e9a34c 100644 --- a/src/forge/Notice/Apply.jsx +++ b/src/forge/Notice/Apply.jsx @@ -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); diff --git a/src/forge/SecuritySetting/bot/disposition/index.jsx b/src/forge/SecuritySetting/bot/disposition/index.jsx index 5c7a125f6..d2e685a33 100644 --- a/src/forge/SecuritySetting/bot/disposition/index.jsx +++ b/src/forge/SecuritySetting/bot/disposition/index.jsx @@ -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 ?
    {botList.map((item, index)=>{return
    - + -1 || item.logo.indexOf("412603")>-1)) ? getImageUrl(item.logo) : LogoBot} alt="" className="imgBox mr20"/> {item.bot_name} {item.state === 0 ? '挂起' : '启用'}
    diff --git a/src/forge/SecuritySetting/bot/exploit/index.jsx b/src/forge/SecuritySetting/bot/exploit/index.jsx index db4a9667e..a234457e1 100644 --- a/src/forge/SecuritySetting/bot/exploit/index.jsx +++ b/src/forge/SecuritySetting/bot/exploit/index.jsx @@ -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 ?
    {botList.map((item, index)=>{return
    - + -1 || item.logo.indexOf("412603")>-1)) ? getImageUrl(item.logo) : LogoBot} alt="" className="imgBox mr20" object-fit="fill"/> {item.bot_name} {item.is_public ? item.is_market ? '上架' : '公开' : '私有'}
    diff --git a/src/forge/SecuritySetting/bot/exploit/new/index.jsx b/src/forge/SecuritySetting/bot/exploit/new/index.jsx index bdecfa7de..f11ffbc80 100644 --- a/src/forge/SecuritySetting/bot/exploit/new/index.jsx +++ b/src/forge/SecuritySetting/bot/exploit/new/index.jsx @@ -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() : '', diff --git a/src/forge/Server/List.jsx b/src/forge/Server/List.jsx index e5cd281a0..5dc33711f 100644 --- a/src/forge/Server/List.jsx +++ b/src/forge/Server/List.jsx @@ -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); } diff --git a/src/forge/Settings/image/logoBot.png b/src/forge/Settings/image/logoBot.png new file mode 100644 index 000000000..9bcd94646 Binary files /dev/null and b/src/forge/Settings/image/logoBot.png differ diff --git a/src/forge/Settings/softBot.jsx b/src/forge/Settings/softBot.jsx index ec371767c..7719a2e69 100644 --- a/src/forge/Settings/softBot.jsx +++ b/src/forge/Settings/softBot.jsx @@ -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 ?
    {botList.map((item, index)=>{return
    - + -1 || item.logo.indexOf("412603")>-1)) ? getImageUrl(item.logo) : LogoBot} alt="" className="imgBox mr20"/> {item.bot_name} {item.state === 0 ? '挂起' : '启用'}
    diff --git a/src/forge/Team/Index.jsx b/src/forge/Team/Index.jsx index 0461b26ec..9fcd20b5e 100644 --- a/src/forge/Team/Index.jsx +++ b/src/forge/Team/Index.jsx @@ -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 (
    @@ -84,7 +112,7 @@ const team = CNotificationHOC()(SnackbarHOC()(TPMIndexHOC( { - return + return }} > @@ -92,10 +120,18 @@ const team = CNotificationHOC()(SnackbarHOC()(TPMIndexHOC( ( - + )} > + {/* 开通企业工作台 */} + { + return + }} + > + {/* 组织下的项目详情 */} ( - + )} >
    ) }) -))) - -team.preFetch = ProjectDetail.preFetch - -export default withRouter(team) \ No newline at end of file +)))) \ No newline at end of file diff --git a/src/forge/Team/Index.scss b/src/forge/Team/Index.scss index 4e1f5f672..b5874862a 100644 --- a/src/forge/Team/Index.scss +++ b/src/forge/Team/Index.scss @@ -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; + } } \ No newline at end of file diff --git a/src/forge/Team/List.jsx b/src/forge/Team/List.jsx index c69910749..f43f423e0 100644 --- a/src/forge/Team/List.jsx +++ b/src/forge/Team/List.jsx @@ -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){
    - +

    - {organizeDetail && organizeDetail.nickname} - {organizeDetail && organizeDetail.is_admin ? - 设置 - :""} + {organizeDetail && organizeDetail.nickname} + {organizeDetail && ( +

    + {organizeDetail.pms_enable && organizeDetail.is_admin && !enterpriseOpenInfo.isOpen && + + + + } + { + organizeDetail.pms_enable && enterpriseOpenInfo.isOpen && organizeDetail.is_member && (enterpriseOpenInfo.url ? + + : + ) + } + {/* { + organizeDetail.pms_enable && organizeDetail.is_admin &&( + !enterpriseOpenInfo.isOpen ? ( + + + + ) : + enterpriseOpenInfo.url ? ( + + ) : ( + + ) + ) + } */} + {organizeDetail.is_admin && } +
    + )}

    - {organizeDetail && organizeDetail.description} + {organizeDetail && organizeDetail.description}
    @@ -129,7 +141,7 @@ function List(props){
    { organizeDetail.news_title && -
    +

    新闻动态

    {organizeDetail.news_title} @@ -228,6 +240,7 @@ function List(props){ showCompeleteDialog={props.showCompeleteDialog} completeProfile={props.completeProfile} history={props.history} + enterpriseOpened = {enterpriseOpenInfo.isOpen} />

    diff --git a/src/forge/Team/RightBox.jsx b/src/forge/Team/RightBox.jsx index df2364503..5df49bcf4 100644 --- a/src/forge/Team/RightBox.jsx +++ b/src/forge/Team/RightBox.jsx @@ -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 :"" } - } @@ -188,6 +188,7 @@ function RightBox({ OIdentifier , history , admin , showCompeleteDialog ,complet : } + }
    ) } diff --git a/src/forge/Team/Setting/TeamSettingIndex.jsx b/src/forge/Team/Setting/TeamSettingIndex.jsx index 91db1156b..989514ea4 100644 --- a/src/forge/Team/Setting/TeamSettingIndex.jsx +++ b/src/forge/Team/Setting/TeamSettingIndex.jsx @@ -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} ], diff --git a/src/forge/Team/Setting/cla/CLAinfos.jsx b/src/forge/Team/Setting/cla/CLAinfos.jsx index 3896f0cf2..6a98bce79 100644 --- a/src/forge/Team/Setting/cla/CLAinfos.jsx +++ b/src/forge/Team/Setting/cla/CLAinfos.jsx @@ -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){ diff --git a/src/forge/Team/Setting/cla/TeamSettingCLA.jsx b/src/forge/Team/Setting/cla/TeamSettingCLA.jsx index a207f0c81..3ca2923f0 100644 --- a/src/forge/Team/Setting/cla/TeamSettingCLA.jsx +++ b/src/forge/Team/Setting/cla/TeamSettingCLA.jsx @@ -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(
    CLA管理 @@ -40,7 +49,7 @@ function TeamSettingCLA(props){

    {list.name}

    签署人数{list.count || 0}
    - +
    diff --git a/src/forge/Team/Setting/enterprise.jsx b/src/forge/Team/Setting/enterprise.jsx new file mode 100644 index 000000000..709c38182 --- /dev/null +++ b/src/forge/Team/Setting/enterprise.jsx @@ -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( + + ) + } + }, + { + title: '用户名', + dataIndex: 'nickname', + }, + { + title: '邮箱', + dataIndex: 'email', + }, + { + title: '所属团队', + dataIndex: ['role', 'roleKey'], + width:"20%", + render:(value,record)=>{ + return + } + }, + { + title: '操作', + dataIndex: 'username', + key: "action", + render:(value)=>{ + return ( + + ) + } + } + ] + + 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( + + +
    + 首次创建组织工作台时,系统将根据组织团队用户在组织工作台中根据不同团队初始化成不同角色。请您知悉以下角色权限,再次确认组织成员的角色分配。
    + 组织管理员:原owner团队成员,对组织内所有代码库均具有管理员权限,在组织工作台中拥有最高管理权限
    + 项目管理员:原管理员团队成员,对组织内所有代码库均具有管理员权限,在组织工作台中拥有项目管理权限
    + 普通成员:原开发者及报告者团队成员,对组织内所有代码库均具有开发者权限,在组织工作台中拥有项目基础使用权限及查看权限 +
    + +
    + {/* {setSearch(value)}}/> */} +
    + {/* + 角色筛选 + */} +
    +
    +
    + {/* { + total > limit ? +
    + +
    + :"" + } */} +
    +
    + + +
    +
    +
    + ) +}) \ No newline at end of file diff --git a/src/forge/Team/Setting/teamSetting.scss b/src/forge/Team/Setting/teamSetting.scss index 6f5a21bc6..02903d757 100644 --- a/src/forge/Team/Setting/teamSetting.scss +++ b/src/forge/Team/Setting/teamSetting.scss @@ -129,4 +129,14 @@ margin-right: 40px; } } +} +.exterpriseBox{ + .padding30{ + color: #4C5876; + line-height: 30px; + border-bottom: 1px dashed #eee; + } + .font-bd{ + color: #0D0F12; + } } \ No newline at end of file diff --git a/src/forge/Team/Sub/Detail.jsx b/src/forge/Team/Sub/Detail.jsx index 6062fd13a..a8f17a49f 100644 --- a/src/forge/Team/Sub/Detail.jsx +++ b/src/forge/Team/Sub/Detail.jsx @@ -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( -
    +
    { detail && - {flag && !buttonflag && detail.is_admin ? - 设置 - :""} - {buttonflag && - - 组织成员{detail.num_users && {detail.num_users}} - 组织团队{detail.num_teams &&{detail.num_teams}} - - } - + rightBtn={buttonflag && !enterpriseOpened && + + 组织成员{detail.num_users && {detail.num_users}} + 组织团队{detail.num_teams &&{detail.num_teams}} + } bottomInfos={ !buttonflag &&
    @@ -137,19 +136,20 @@ function Detail(props){ }} > - { - return - }} - > - { - return + return }} > + + { + return + }} + > +
    ) diff --git a/src/forge/Team/TeamMember.jsx b/src/forge/Team/TeamMember.jsx index 676e0d7fe..86f047a16 100644 --- a/src/forge/Team/TeamMember.jsx +++ b/src/forge/Team/TeamMember.jsx @@ -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}){ 组织成员 { - organizeDetail && organizeDetail.is_admin && + !enterpriseOpened && organizeDetail && organizeDetail.is_admin && {item.filesize} ) : ( - {item.title} {item.filesize} - + )} {canDelete ? ( diff --git a/src/forge/Upload/read.js b/src/forge/Upload/read.js index 481298261..b50d9cb32 100644 --- a/src/forge/Upload/read.js +++ b/src/forge/Upload/read.js @@ -84,7 +84,7 @@ class Read extends Component { 暂仅支持文本格式,不支持图片,excel等不可以txt读取的文件

    */}

    - 文件名请使用英文且不得超过{size}MB + 文件名请使用英文且不得超过{size}MB(超过{size}MB文件请用git工具上传)

    )} diff --git a/src/forge/Wiki/EditWiki.jsx b/src/forge/Wiki/EditWiki.jsx deleted file mode 100644 index bf89653c8..000000000 --- a/src/forge/Wiki/EditWiki.jsx +++ /dev/null @@ -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) => ( - - {getFieldDecorator(name, { rules, initialValue, validateFirst: true, })(widget)} - {rightComponent} - - ), []); - - - function onContentChange(value) { - setContent(value); - setFieldsValue({ - md_content: value - }); - }; - - // 保存wiki文件,包括新增和修改 - function saveFile() { - if(operateFlag) { - setOperateFlag(true); - return; - }; - validateFields((err, values) => { - if (!err) { - const {md_content, title} = values; - if (wikiName && search!== "?copy") { - updateWiki({ - owner, - repo: projectsId, - projectId: project.id, - pageName: wikiName, - title, - message: '', - content_base64: Base64.encode(md_content) - }).then(res => dealRes(res, title)); - } else { - // 由于wiki在底层是由wikiname标识的md文件,所以对于所有的页面,都不能重名 - if (Array.isArray(fileArrInit)) { - for (const item of fileArrInit) { - if (item.name === title) { - message.error('不能与已有文件标题相同'); - setOperateFlag(false); - return false; - } - } - } - addWiki({ - owner, - repo: projectsId, - projectId: project.id, - pageName: title, - title, - message: '', - content_base64: Base64.encode(md_content) - }).then(res => dealRes(res, title)); - } - } - }) - } - - function dealRes(res, title) { - if (res && (res.message === "201" || res.message === "200")) { - if(wikiName && search!== "?copy"){ - // 如果有更改wiki文件名,则修改sidebar - if(wikiName === title){ - message.success("操作成功"); - goBack(); - }else{ - // 找到相同的key值node修改title参数,再保存sidebar - const newMenuList = menuList; - editTitleByKey(newMenuList, key, title); - updateSidebar(treeToMd(newMenuList), title); - } - }else{ - // 根据路由判断是新增的一级页面还是子页面 - if(search === "?copy"){ - // 截断key的前几位,获取父元素的key值 - const keyArr = key.split('-'); - keyArr.pop(); - const list = menuList; - addChildrenByKey(list, keyArr.join('-'), title); - updateSidebar(treeToMd(list), title); - }else if(search.includes("?key")){ - // 遍历menuList找到相同key的节点,children push[[页面]] - const list = menuList; - addChildrenByKey(list, key, title); - updateSidebar(treeToMd(list), title); - }else{ - if(fileArrInit.length){ - updateSidebar(`${sidebar}\n[[${title}]]`, title); - }else{ - // 仓库第一次创建wiki文件 - addWiki({ - owner, - repo: projectsId, - projectId: project.id, - pageName: "_Sidebar", - title: "_Sidebar", - message: '', - content_base64: Base64.encode(`[[${title}]]`) - }).then(res => { - if(res && res.message === "201"){ - message.success("操作成功"); - goBack(); - } - }); - } - } - } - } else if (res && res.message === "500") { - setOperateFlag(false); - message.error('请检查格式是否正确或文件名是否重复'); - } else { - setOperateFlag(false); - showNotification(res.data || "操作失败"); - } - } - - function updateSidebar(content, newTitle){ - updateWiki({ - owner, - repo: projectsId, - projectId: project.id, - pageName: '_Sidebar', - title: '_Sidebar', - message: '', - content_base64: Base64.encode(content) - }).then(res => { - if (res && res.message === "200") { - message.success("操作成功"); - if(newTitle){ - // 用户修改了wiki名称 - history.push(`/${owner}/${projectsId}/wiki/${encodeURI(newTitle)}/${key}`) - return; - } - goBack(); - } - }); - } - - // 递归menuList, 找到相同key值修改title名称 - function editTitleByKey(list, key, newTitle){ - for (let index = 0; index < list.length; index++) { - const element = list[index]; - if (element.key == key) { - const {title} = element; - element.title = `${title.substring(0, title.indexOf('[['))}[[${newTitle}]]`; - return true; - }else if (element.children.length) { - let result = editTitleByKey(element.children, key, newTitle); - if (result) { - return result; - } - } - } - } - - // 递归menuList 创建子页面 - function addChildrenByKey(list, key, name){ - for (let index = 0; index < list.length; index++) { - const element = list[index]; - if (element.key == key) { - const {title} = element; - element.children.push({ - title: `\t${title.substring(0, title.indexOf('- '))}[[${name}]]`, - children: [] - }); - return true; - }else if (element.children.length) { - let result = addChildrenByKey(element.children, key, name); - if (result) { - return result; - } - } - } - } - - function goBack() { - history.go(-1); - } - - function changeModal(e) { - setModal(e.target.checked); - if (!e.target.checked) { - setModalType(); - } - } - - function changeModalType(e) { - let value = e.target.value; - if (!content) { - setModalContent(value); - return; - } - DelModal({ - title: '添加模版', - contentTitle: `您确定要添加“${value}”模板吗`, - content: `此操作会将“${value}”模板替换编辑栏内所有内容,请确认以防文件的丢失`, - okText: '确认添加', - onOk: () => { - setModalType(value); - setModal(true); - setModalContent(value); - } - }); - } - - function setModalContent(value) { - if (value === '周报') { - setContent(weekModal); - setFieldsValue({ - md_content: weekModal - }); - } else if (value === '月报') { - setContent(monthModal); - setFieldsValue({ - md_content: monthModal - }); - } - } - - - return ( -
    -
    - Wiki {wikiName ? search === "?copy" ? '复制' : '编辑' : '新增'}页面 -
    - -
    -

    标题

    - {helper( - "", - "title", - [ - { required: true, message: "请输入标题" }, - { pattern: /^(?!-).*$/, message: '不能以-开头' } - ], - - )} - - - - {getFieldDecorator('md_content', { - rules: [{ required: true, message: "请输入wiki内容" }], - validateFirst: true, - initialValue: "欢迎来到Wiki" - })()} - - - 添加模版 - - - 周报 - 月报 - - -
    - - {permission && } -
    - - ) -}) \ No newline at end of file diff --git a/src/forge/Wiki/Index.jsx b/src/forge/Wiki/Index.jsx deleted file mode 100644 index 08288c366..000000000 --- a/src/forge/Wiki/Index.jsx +++ /dev/null @@ -1,342 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Button, Dropdown, Icon, Input, Menu, Select, message, Spin, Modal } from 'antd'; -import { getImageUrl, timeAgo } from 'educoder'; -import CopyTool from '../Component/CopyTool'; -import Welcome from './Welcome'; -import RenderHtml from "../../components/render-html"; - -import { wikiPages, getWiki, parseSidebar, updateWiki, treeToMd, deleteSameKey } from './api'; -import { httpUrl } from './fetch'; -import './Index.scss'; -import './components/ModalFun/index.scss'; -import { isArray } from 'lodash'; -import { Base64 } from 'js-base64'; -import Sidebar from './components/sidebar'; -import UploadWiki from './components/uploadWiki'; -import axios from 'axios'; -const InputGroup = Input.Group; -const { Option } = Select; - -export default (props) => { - const { match, history, showNotification, project, projectDetail } = props; - const {location:{pathname}} = history; - const permission = projectDetail && projectDetail.permission && projectDetail.permission !== "Reporter"; - let projectsId = match.params.projectsId; - let owner = match.params.owner; - - const [fileArrInit, setFileArrInit] = useState(null); - const [checkItem, setCheckItem] = useState({}); - const [itemDetail, setItemDetail] = useState({}); - - const [fileArr, setFileArr] = useState([]); - const [fileList, setFileList] = useState([]); - const [reload, setReload] = useState(); - - const [urlType, setUrlType] = useState('HTTPS'); - const [visible, setVisible] = useState(false); - const [menuName, setMenuName] = useState(undefined); - const [addMenuError, setAddMenuError] = useState(undefined); - const [sidebar, setSidebar] = useState(undefined); - const [defaultSelectedKeys, setDefaultSelectedKeys] = useState(undefined); - - useEffect(()=>{ - window.scrollTo(0,0); - }, []) - - useEffect(()=>{ - if(projectDetail){ - const { author, name} = projectDetail; - if(Object.keys(checkItem).length === 0){ - document.title = `维基-${author.name}/${name}`; - }else{ - document.title = `${checkItem.name}-维基-${author.name}/${name}`; - } - } - }, [projectDetail, checkItem]) - - // 加载wiki列表 - useEffect(() => { - project && wikiPages({ - owner: owner, - repo: projectsId, - projectId: project.id - }).then(async res => { - if (res && res.message === "200" && isArray(res.data)) { - setFileArr(res.data); - setFileArrInit(res.data); - // 解析sidebar获取目录list - const info = await parseSidebar(res.data, { - owner: owner, - repo: projectsId, - projectId: project.id - }) - if(!info){return} - const {menuList, sidebar} = info; - setSidebar(sidebar) - setFileList(menuList); - let firstWikiNode = undefined; - if(!pathname.endsWith('/wiki')){ - const {wikiName} = match.params; - firstWikiNode = findNodeByName(menuList, wikiName); - } else{ - // 找到目录树中第一个[[页面]] - firstWikiNode = findFirstWiki(menuList); - } - // 当前选中wiki被删除 - !firstWikiNode && (firstWikiNode = findFirstWiki(menuList)); - // 设置目录 选中节点初始化 - setDefaultSelectedKeys([firstWikiNode.key+""]); - const title = firstWikiNode.titleStr; - const firstWiki = res.data.filter(item => { return title === item.name })[0]; - setCheckItem({ - ...firstWiki, - key: firstWikiNode.key - }) - if(window.location.pathname.indexOf('/wiki') === -1) return - history.push(encodeURI(`/${owner}/${projectsId}/wiki/${firstWiki.sub_url}/${firstWikiNode.key}`)); - } else { - setFileArr([]); - setFileArrInit([]); - } - }); - }, [project, reload]) - - // 加载单个wiki详情,sub_url: 后端转义的字符串 - useEffect(() => { - project && checkItem.sub_url && getWiki({ - owner: owner, - repo: projectsId, - pageName: checkItem.sub_url, - projectId: project.id - }).then(res => { - if (res && res.message === "200") { - setItemDetail(res.data); - } else { - showNotification("加载失败") - } - }); - }, [project, checkItem]); - - function findNodeByName(list, name){ - for (let index = 0; index < list.length; index++) { - const element = list[index]; - if (element.title.indexOf(`[[${name}]]`) !== -1) { - return element; - }else if (element.children.length) { - let result = findNodeByName(element.children, name); - if (result) { - return result; - } - } - } - } - - // 初始化页面:递归menulist找到第一个页面 - function findFirstWiki(list){ - for (let index = 0; index < list.length; index++) { - const element = list[index]; - if (element.title.trim().startsWith('[[')) { - return element; - }else if (element.children.length) { - let result = findFirstWiki(element.children); - if (result) { - return result; - } - } - } - } - - - function filterTree(list, value, arr = []){ - if(!list.length) return [] - for(let item of list){ - const bool = item.title.match(new RegExp(value, 'i')); - let node = JSON.parse(JSON.stringify(item)); - node.children = []; - bool && arr.push(node); - if(item.children && item.children.length){ - filterTree(item.children, value, bool ? node.children : arr); - } - } - return arr - } - - - function goUser(login) { - window.location.href = `/${login}`; - } - - function addFile() { - // 新增一级页面 - history.push(`/${owner}/${projectsId}/wiki/add`); - } - - // 新增目录 - function addMenu(){ - if(!menuName){ - setAddMenuError('请输入目录名称'); - return; - } - if(new RegExp("^[ ]+$").test(menuName)){ - setAddMenuError("不能仅输入空格"); - return; - } - // 校验一级目录中是否有相同名称 - const oneMenuList = fileList.filter(item=>!item.title.trim().startsWith('[[')).map(i=>i.title.trim()) - if(oneMenuList.includes(`- ${menuName}`)){ - setAddMenuError('不能与已有文件标题相同'); - return; - } - - // 重写fileList,再转换成md内容,最后保存sidebar - const newSidebarList = fileList.concat({title: `- ${menuName}`, children: []}) - updateWikiFun(newSidebarList, 1); - } - - // 更新wiki sidebar文件 - function updateWikiFun(sidebarList, type){ - updateWiki({ - owner, - repo: projectsId, - projectId: project.id, - pageName: '_Sidebar', - title: '_Sidebar', - message: '', - content_base64: Base64.encode(treeToMd(sidebarList)) - }).then(res => { - if (res && res.message === "200") { - if(type === 1){ - setVisible(false); - setMenuName(undefined); - message.success('操作成功'); - } - // setFileList(sidebarList); - setReload(Math.random()); - }else { - message.error(res.data || "操作失败"); - } - }); - } - - function goEdit(params) { - history.push(encodeURI(`/${owner}/${projectsId}/wiki/${checkItem.sub_url}/${checkItem.key}/edit${params}`)); - } - - function preview() { - window.open(encodeURI(`/${owner}/${projectsId}/wiki/preview/${project.name}/${project.id}`)); - } - - // 支持 Markdown,Html,Pdf格式文件 - const menu = ( - - { downloadWiki('markdown') }}> - Markdown - - { downloadWiki('html') }}> - HTML - - { downloadWiki('pdf') }}> - PDF - - - ); - - function downloadWiki(type) { - window.open(`${httpUrl}/api/wikiExport/wikiExport-wrapper?repoName=${projectsId}&owner=${owner}&type=${type}&projectName=${project.name}&projectId=${project.id}`); - } - - function changeitem(item){ - const {name, key} = item; - const wiki = fileArrInit.filter(item => { return item.name == name })[0]; - if(!wiki){ - // 不存在的wiki,修正sidebarfindSameKeyByDel - const menuListByDel = deleteSameKey(fileList, key); - updateWikiFun(menuListByDel); - message.success("不存在的wiki") - return - } - setCheckItem({ - ...wiki, - key - }) - history.push(encodeURI(`/${owner}/${projectsId}/wiki/${name}/${key}`)); - } - - return ( - - {fileArrInit && fileArrInit.length ? -
    -
    - - - { - permission ? -
    - - -
    : "Wiki文档" - } -
    -
    - { - permission && - } - - - - - -
    -
    - -
    -
    -
    -
    - - {/* 读sidebar转换成目录树 */} - -
    -
    - - - {checkItem.wiki_clone_link && - - - - } - -
    - -
    -
    -
    -

    {checkItem.name}

    - { checkItem.commit && goUser(checkItem.commit.author.name) }}> - {itemDetail.image_url && 头像} - {itemDetail.userName} - - 上次修改于{checkItem.commit ? timeAgo(checkItem.commit.author.date) : '刚刚'} -
    - {permission && } - {permission && } -
    - - {itemDetail && itemDetail.md_content && } -
    -
    -
    - : - - } - {setVisible(false);setAddMenuError(undefined);setMenuName(undefined);}} onOk={addMenu} className="wikiAddMenu myself-modal" width={450} centered> -
    目录名称:
    - {setMenuName(e.target.value);setAddMenuError(undefined)}} maxLength={50} autoFocus> -
    {addMenuError}
    -
    -
    - ) -} diff --git a/src/forge/Wiki/Index.scss b/src/forge/Wiki/Index.scss deleted file mode 100644 index 58ffcef09..000000000 --- a/src/forge/Wiki/Index.scss +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/src/forge/Wiki/Preview.jsx b/src/forge/Wiki/Preview.jsx deleted file mode 100644 index 4dfec1cc3..000000000 --- a/src/forge/Wiki/Preview.jsx +++ /dev/null @@ -1,181 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Input, Button, Select, Dropdown, Icon, Menu, message, Tree } from 'antd'; -import CopyTool from '../Component/CopyTool'; -import { wikiPages, getWiki, parseSidebar, } from './api'; -import { httpUrl } from './fetch'; -import './Index.scss'; -import { Base64 } from 'js-base64'; -import RenderHtml from '../../components/render-html'; -const {DirectoryTree, TreeNode} = Tree; -const InputGroup = Input.Group; -const { Option } = Select; - -export default (props) => { - const { match, history, showNotification } = props; - - let projectsId = match.params.projectsId; - let owner = match.params.owner; - - let projectName = match.params.projectName; - let projectId = match.params.projectId; - let key = 1; - - const [checkItem, setCheckItem] = useState({}); - const [itemDetail, setItemDetail] = useState({}); - - const [fileArr, setFileArr] = useState([]); - const [urlType, setUrlType] = useState('HTTPS'); - const [fileList, setFileList] = useState([]); - - useEffect(()=>{ - if(checkItem && checkItem.name){ - document.title = `${checkItem.name}-维基预览` - } - }, [checkItem]) - - useEffect(() => { - projectsId && wikiPages({ - owner: owner, - repo: projectsId, - projectId: projectId, - }).then(async res => { - if (res && res.message === "200") { - setFileArr(res.data); - // 解析sidebar获取目录list - const info = await parseSidebar(res.data, { - owner: owner, - repo: projectsId, - projectId - }) - const {menuList, sidebar} = info; - setFileList(menuList); - if (res.data.length) { - setCheckItem(res.data[0]); - }; - } else { - showNotification("加载失败") - } - }); - }, []) - - - useEffect(() => { - projectsId && checkItem.sub_url && getWiki({ - owner: owner, - repo: projectsId, - pageName: checkItem.sub_url, - projectId: projectId, - }).then(res => { - if (res && res.message === "200") { - setItemDetail(res.data); - } else { - showNotification("加载失败") - } - }); - }, [checkItem]); - - function goBack() { - history.push(`/${owner}/${projectsId}/wiki`); - } - - // 支持 Markdown,Html,Pdf格式文件 - const menu = ( - - { downloadWiki('markdown') }}> - Markdown - - { downloadWiki('html') }}> - HTML - - { downloadWiki('pdf') }}> - PDF - - - ); - - 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 ( - - {renderTreeNodes(item.children)} - - ); - }); - } - - 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 ( -
    -
    -
    - - {projectName} -
    -
    - 克隆地址 - { - fileArr.length && fileArr[0].wiki_clone_link ? - - - - : '' - } - - - - -
    -
    - - -
    -
    - {/* { - fileArr.map(item => { - return
    { setCheckItem(item) }}> - - - {item.name} - -
    - }) - } */} - {/* 读sidebar转换成目录树 */} - {fileList && fileList.length ? - {renderTreeNodes(fileList)} - : ''} -
    - -
    -
    -

    {checkItem.name}

    -
    - - {itemDetail && itemDetail.md_content && } -
    -
    - - -
    - ) -} \ No newline at end of file diff --git a/src/forge/Wiki/Welcome/index.css b/src/forge/Wiki/Welcome/index.css deleted file mode 100644 index d146549c8..000000000 --- a/src/forge/Wiki/Welcome/index.css +++ /dev/null @@ -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 */ diff --git a/src/forge/Wiki/Welcome/index.jsx b/src/forge/Wiki/Welcome/index.jsx deleted file mode 100644 index a83adb269..000000000 --- a/src/forge/Wiki/Welcome/index.jsx +++ /dev/null @@ -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 ( -
    - -

    欢迎使用  - - {project && project.name} - -  Wiki

    -

    Wiki主要是您项目的产品设计、文档描述、注释等等

    -
    - - { - isManager ?
    - - -
    :

    该项目暂时没有创建Wiki

    - } -
    - ) -} \ No newline at end of file diff --git a/src/forge/Wiki/Welcome/index.scss b/src/forge/Wiki/Welcome/index.scss deleted file mode 100644 index 2250db0df..000000000 --- a/src/forge/Wiki/Welcome/index.scss +++ /dev/null @@ -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; - } -} diff --git a/src/forge/Wiki/api.js b/src/forge/Wiki/api.js index 301d096cf..ff83ed70e 100644 --- a/src/forge/Wiki/api.js +++ b/src/forge/Wiki/api.js @@ -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{ + 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 }) diff --git a/src/forge/Wiki/components/FolderTree/index.js b/src/forge/Wiki/components/FolderTree/index.js deleted file mode 100644 index 93c06ff3f..000000000 --- a/src/forge/Wiki/components/FolderTree/index.js +++ /dev/null @@ -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 ; - } - return expanded ? : - // return -} - -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, 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 ( - - ); - }; - - render() { - return {this.renderDirectoryTree}; - } -} - -polyfill(DirectoryTree); - -export default DirectoryTree; diff --git a/src/forge/Wiki/components/FolderTree/index.scss b/src/forge/Wiki/components/FolderTree/index.scss deleted file mode 100644 index f74cfaf93..000000000 --- a/src/forge/Wiki/components/FolderTree/index.scss +++ /dev/null @@ -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; - } -} - - diff --git a/src/forge/Wiki/components/ModalFun/index.scss b/src/forge/Wiki/components/ModalFun/index.scss index 9f2e95b0e..019294d64 100644 --- a/src/forge/Wiki/components/ModalFun/index.scss +++ b/src/forge/Wiki/components/ModalFun/index.scss @@ -16,6 +16,9 @@ .ant-modal-body { text-align: center; } + .ant-form-explain{ + text-align: left; + } .content-title { display: flex; justify-content: center; diff --git a/src/forge/Wiki/components/clone.jsx b/src/forge/Wiki/components/clone.jsx new file mode 100644 index 000000000..c71029f1b --- /dev/null +++ b/src/forge/Wiki/components/clone.jsx @@ -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 && { setUrlType(v) }}> + + + + } addonAfter={} id="wikiUrl" value={urlType === 'HTTPS' ? wikiPages[0].wiki_clone_link.https : wikiPages[0].wiki_clone_link.ssh}/> +}) + +export default Clone \ No newline at end of file diff --git a/src/forge/Wiki/components/config.js b/src/forge/Wiki/components/config.js deleted file mode 100644 index bda413390..000000000 --- a/src/forge/Wiki/components/config.js +++ /dev/null @@ -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 月度总结:"; diff --git a/src/forge/Wiki/components/download.jsx b/src/forge/Wiki/components/download.jsx new file mode 100644 index 000000000..bc1042ed9 --- /dev/null +++ b/src/forge/Wiki/components/download.jsx @@ -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 + {downloadType.map(item=>{ + return { + window.open(exportWiki(`?repoName=${repo}&owner=${owner}&projectName=${projectName}&projectId=${projectId}&type=${item.label}`)); + }}>{item.label} + })} + + }> + + +}) + +export default Download \ No newline at end of file diff --git a/src/forge/Wiki/components/editMenuModal.jsx b/src/forge/Wiki/components/editMenuModal.jsx new file mode 100644 index 000000000..a02f011e8 --- /dev/null +++ b/src/forge/Wiki/components/editMenuModal.jsx @@ -0,0 +1,179 @@ +import React, { useEffect, useState } from "react"; +import { Input, Modal, Form, Button, message } from "antd"; +import './ModalFun/index.scss' +import { addChildrenByKey, editMenuTitleByKey, findBrotherNodesByKey, findNodeByKey, findSamaNameWiki, getWiki, treeToMd, updateSidebar, updateWiki } from "../api"; + +// 新建/编辑弹框 +const EditModal = Form.create()((props) => { + const {wikiParams, form: editForm, typeMenuModal, wikiPageInitDetail, cancel, history, reload} = props; + const {validateFields, getFieldDecorator} = editForm; + // typeMenuModal: 类型 新建子目录/目录 编辑 + const regex = /^addMenuBy.*$/; + const regexEdit = /^editMenu.*$/; + const regexEditWiki = /^editWiki.*$/; + const title = regex.test(typeMenuModal) ? '新增子' : /^edit.*$/.test(typeMenuModal) ? '编辑' : '新建'; + const {menuList=[], sidebar="", wikiPages=[]} = wikiPageInitDetail || {}; + const [initMenu, setInitMenu] = useState(); + const [wikiContent, setWikiContent] = useState(); + const [label, setLabel] = useState("目录"); + + useEffect(()=>{ + if(!typeMenuModal || !menuList.length) return + // 编辑名称 + if(/^edit.*$/.test(typeMenuModal)){ + const key = typeMenuModal.substring(8); + const node = findNodeByKey(menuList, key) + setLabel(node.isFile ? '页面' : '目录') + setInitMenu({ + title: node.titleStr + }) + editForm.setFieldsValue({ + name: node.titleStr + }) + if(regexEditWiki.test(typeMenuModal)){ + setInitMenu({ + title: node.titleStr, + title_sub: node.title_sub + }) + // 获取wiki content + getWiki({ + ...wikiParams, + pageName: node.title_sub + }).then(res=>{ + if(res && res.code === 200){ + setWikiContent(res.data.content_base64); + } + }) + } + } + }, [typeMenuModal, sidebar]) + + // 提交表单 + function submit(e) { + e.preventDefault(); + validateFields(async (err, values) => { + if (!err) { + const { name } = values; + let sidebarContent = undefined; + let sameNameMenu = undefined; + if (typeMenuModal === "addMenu") { + // 新增一级目录:校验一级目录中是否有相同名称 + sameNameMenu = menuList.find(item => !item.isFile && item.titleStr === name); + sidebarContent = `${sidebar}\n- ${name}` + } else if (regex.test(typeMenuModal)) { + // 新增子目录: 通过key找到节点,遍历节点children,是否有相同名称 + const key = typeMenuModal.substring(9) || ''; + const nodeBySameKey = findNodeByKey(menuList, key); + sameNameMenu = nodeBySameKey.children.find(item => !item.isFile && item.titleStr === name); + addChildrenByKey(menuList, key, `- ${name}`); + sidebarContent = treeToMd(menuList) + } else if (regexEdit.test(typeMenuModal)) { + // 编辑目录名称: 兄弟节点无相同名称 + const key = typeMenuModal.substring(8) || ''; + const brotherNodes = findBrotherNodesByKey(menuList, key); + sameNameMenu = brotherNodes.find(item => !item.isFile && item.titleStr !== initMenu.title && item.titleStr === name) + editMenuTitleByKey(menuList, key, `${'\t'.repeat(key.split('-').length - 1)}- ${name}`); + sidebarContent = treeToMd(menuList) + } else if (regexEditWiki.test(typeMenuModal)) { + // 编辑wiki名称,不允许wiki名称重复,并且还需要修改wiki的标题 + // findSamaNameWiki: 返回的是个wiki对象 + sameNameMenu = findSamaNameWiki(wikiPageInitDetail.wikiPages, name); + } + if (sameNameMenu) { + // message.error('不能与已有名称重复'); + editForm.setFields({name: {value: name, errors:[new Error('不能与已有名称重复')]}}); + } else { + // 编辑wiki成功后再给sidebarContent赋值,修改sidebar + if (regexEditWiki.test(typeMenuModal)) { + wikiContent && await updateWiki({ + ...wikiParams, + // 原名称 + pageName: initMenu.title_sub, + // 现名称 + title: name, + content_base64: wikiContent, + message: '', + }).then(res => { + if (res && res.code === 200) { + const key = typeMenuModal.substring(8) || ''; + editMenuTitleByKey(menuList, key, `${'\t'.repeat(key.split('-').length - 1)}[[${name}]]`); + sidebarContent = treeToMd(menuList) + } + }) + } + // 编辑sidebar + sidebarContent && updateSidebar(wikiParams, sidebarContent).then(resByUpdateSidebar => { + if (resByUpdateSidebar && resByUpdateSidebar.code === 200) { + message.success(/^add.*$/.test(typeMenuModal) ? "新增成功" : '编辑成功'); + close(); + reload(); + if(regexEditWiki.test(typeMenuModal)){ + history.push(`/${wikiParams.owner}/${wikiParams.repo}/wiki?wiki=${encodeURIComponent(name)}`) + } + } + }) + } + } + }) + } + + function close(){ + editForm.resetFields(); + cancel(); + } + + return ( + + + + + {getFieldDecorator('name', { + rules: [ + { required: true, message: `请输入${label}名称` }, + { pattern: /^(?!-).*$/, message: '不能以-开头' } + ], + })()} + + + + + + + + + ); +}); + +export default EditModal \ No newline at end of file diff --git a/src/forge/Wiki/components/sidebar/index.jsx b/src/forge/Wiki/components/sidebar/index.jsx deleted file mode 100644 index 5004a0345..000000000 --- a/src/forge/Wiki/components/sidebar/index.jsx +++ /dev/null @@ -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 - - { addFile(e, item) }}> - 添加子页面 - - - - {e.stopPropagation();setVisible(item);}}> - 添加子目录 - - - - { updateName(e, item, 1) }}> - 重命名 - - - - { deleteFileModal(e, title, isFile, item) }}>删除 - - - }; - - const fileMenu = (title, isFile, item) =>{ - return - - { updateName(e, item, 2) }}> - 重命名 - - - - { deleteFileModal(e, title, isFile, item) }}>删除 - - - }; - - const treeTitle = (title, isFile, key, item, level)=>{ - return - {title} - {permission && - {setActionVisible(value ? key : undefined)}}> - - - } - - } - - 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 ( - - {renderTreeNodes(item.children)} - - ); - }); - } - - // 遍历list,找到相同的key callback - const loop = (data, key, callback) => { - data.forEach((item, index, arr) => { - if (item.key === key) { - return callback(item, index, arr); - } - if (item.children) { - return loop(item.children, key, callback); - } - }); - }; - - // 选中节点 - function selectTree(keys,event){ - let {title, key} = event.node.props.dataRef; - setSelectedKey([key+""]); - title = title.trim(); - const isFile = title.startsWith('[[') && title.endsWith(']]'); - if(isFile){ - title = title.substring(2,title.length-2); - changeitem({name: title, key}); - } - } - - // 收起展开 - function onExpand(keys, obj){ - const oldKeys = new Set(keys); - const {expanded} = obj; - let {key} = obj.node.props.dataRef; - if(expanded){ - oldKeys.add(key); - }else{ - oldKeys.delete(key); - } - setExpandedKeys(Array.from(oldKeys)) - } - - // 删除wiki模态框 - function deleteFileModal(e, title, isFile, item) { - e.stopPropagation(); - DelModal({ - title: `删除${isFile ? '页面' : '目录'}`, - contentTitle: `您确定要删除“${title}”此${isFile ? '页面' : '目录'}吗?`, - content: `此操作将删除此${isFile ? '页面' : '目录'},请进行确认以防文件的丢失`, - onOk: () => { - const {node, children, key} = item; - if(isFile){ - // 删除页面 - deleteWiki({owner: owner, repo: projectsId, projectId: project.id, pageName: item.title_sub}).then(res => { - if(res && res.message === "204"){ - // 递归过滤相同key值的节点,执行删除操作 - const menuListByDel = findSameKeyByDel(menuList, key); - updateSidebar(treeToMd(menuListByDel), 1); - } - }); - }else{ - // 删除目录(递归找到所有的[[页面]],遍历删除) - // needDelNode: 被删除的子节点 - const needDelNode = findNodeByKey(menuList, key); - // needDelWikis:所有需要删除的子目录列表 - const needDelWikis = []; - findDelNode(needDelNode.children, needDelWikis); - if(needDelWikis.length){ - // 有需要删除的子目录 - const promiseList = [] - needDelWikis.map(item=>{ - promiseList.push(deleteWikiFun({owner: owner, repo: projectsId, projectId: project.id},item.title.trim())) - }) - Promise.all(promiseList).then((values) => { - if(values.filter(item=>item.message === '204' || item.message === '404').length === needDelWikis.length){ - // 执行修改sidebar操作:递归过滤相同key值的节点,执行删除操作 - const menuListByDel = findSameKeyByDel(menuList, key); - updateSidebar(treeToMd(menuListByDel), 1); - }else{ - message.error('发生未知错误,请联系系统管理员') - } - }); - }else{ - // 没有需要删除的子目录,直接修改sidebar文件即可 - const menuListByDel = findSameKeyByDel(menuList, key); - updateSidebar(treeToMd(menuListByDel), 1); - } - } - } - }); - } - - // 删除页面:递归过滤相同key值的节点 - function findSameKeyByDel(list, key){ - return list.filter(item => { - if (item.key === key) { - return false - } - if(item.children && item.children.length > 0){ - item.children = findSameKeyByDel(item.children , key) - } - return true - }) - } - - // 删除目录:递归找到需要删除的子目录 - function findDelNode(list, nodes){ - //利用foreach循环遍历 - list.forEach(item => { - //判断递归结束条件 - const title = item.title.trim(); - if (title.startsWith('[[') && title.endsWith(']]')) { - // 存储数据到空数组 - nodes.push(item); - } - if (item.children.length){ - //递归调用 - const result = findDelNode(item.children, nodes); - if(result) return result - }else{ - return true - } - }) - return nodes; - } - - // 删除wiki - function deleteWikiFun(info, title){ - return new Promise((resolve, reject)=>{ - deleteWiki({ - ...info, - pageName: title - }).then(res => { - resolve(res) - }) - }) - } - - // 修改此仓库的sidebar文件 type 1删除 2拖拽 3添加子目录 4目录重命名 - function updateSidebar(content, type, addWikiName){ - if(type === 1 && !content){ - // 删除最后一个wiki的时候,同时删除sidebar - deleteWiki({owner: owner, repo: projectsId, projectId: project.id, pageName: "_Sidebar"}).then(res => { - if(res && res.message === "204"){ - history.push(`/${owner}/${projectsId}/wiki`) - setReload(Math.random()); - message.success('删除成功'); - } - }); - return - } - updateWiki({ - owner, - repo: projectsId, - projectId: project.id, - pageName: '_Sidebar', - title: '_Sidebar', - message: '', - content_base64: Base64.encode(content) - }).then(res => { - if (res && res.message === "200") { - setReload(Math.random()); - if(type === 1){ - message.success('删除成功'); - }else if(type === 3){ - setMenuName(undefined); - setVisible(false); - message.success('新增成功'); - }else if(type === 4){ - setIsEditName(false); - setMenuName(undefined); - setVisible(false); - message.success('重命名成功'); - }else{ - // message.success('修改文件目录顺序成功'); - } - }else{ - message.error(res.message); - } - }); - } - - // 添加子页面 - function addFile(e, item){ - e.stopPropagation(); - history.push(`/${owner}/${projectsId}/wiki/add?key=${item.key}`) - } - - // 重命名目录文件 type:1 目录 2页面 - function updateName(e, item, type){ - e.stopPropagation(); - setVisible(item); - setIsEditName(type); - const {titleStr, title_sub} = item; - type === 2 && getWiki({ - owner: owner, - repo: projectsId, - pageName: title_sub, - projectId: project.id - }).then(res => { - if (res && res.message === "200") { - setWikiContent(res.data.md_content); - } - }) - setMenuName(titleStr); - } - - // 新增子目录 或者 重命名目录 - function submit(){ - if(!menuName){ - setAddMenuError('请输入目录名称'); - return; - } - if(new RegExp("^[ ]+$").test(menuName)){ - setAddMenuError("不能仅输入空格"); - return; - } - if (isEditName === 2 && /^-/.test(menuName)) { - setAddMenuError('不能以-开头'); - return; - } - if(!isEditName){ - // 新增目录:校验同级目录中是否有相同名称 - const {children, key} = visible; - const mList = children.filter(item=>!item.title.trim().startsWith('[[')).map(i=>i.title.trim()) - if(mList.includes(`- ${menuName}`)){ - setAddMenuError('不能与已有目录名称相同'); - return; - } - const newList = menuList; - // 设置Tree 打开的目录 - const keys = expandedKeys || []; - const list = key.split('-'); - list.map((item, index)=>{ - keys.push(index > 0 ? `${keys[index-1]}-${item}` : item) - }) - keys.push(`${key}-${children.length}`); - setExpandedKeys(Array.from(new Set(keys))); - addChildrenByKey(newList, key, menuName); - updateSidebar(treeToMd(newList), 3); - }else{ - // isEditName 1 编辑目录名称 2编辑页面名称 - const titleToJudge = isEditName === 2 ? `[[${menuName}]]` : `- ${menuName}`; - const {key, titleStr, title_sub} = visible; - // 判断是否有修改名称 - if(titleStr === menuName){ - setAddMenuError("请修改名称"); - return - } - // 找兄弟节点 - const list = findBrotherNodesByKey(menuList, key) || []; - if(list.map(item=>item.titleStr).includes(menuName)){ - setAddMenuError("不能与同级目录名称相同"); - return - } - // wiki名称不可重复 - const sameNameWiki = fileArrInit.filter(item=>item.name === menuName) - if(isEditName === 2 && sameNameWiki.length){ - setAddMenuError("wiki名称不可重复"); - return - } - // 编辑sidebar - const newMenuList = JSON.parse(JSON.stringify(menuList)) - loop(newMenuList, key, (item)=>{ - const {key} = item; - item.title = `${'\t'.repeat(key.split('-').length-1)}${titleToJudge}` - }) - if(isEditName === 2){ - // 编辑页面名称-> 修改wiki - updateWiki({ - owner, - repo: projectsId, - projectId: project.id, - // 原名称 - pageName: title_sub, - // 现名称 - title: menuName, - content_base64: wikiContent, - message: '', - }).then(res=>{ - if(res && res.message === '200'){ - // 修改wiki名称接口请求成功,更新sidebar - updateSidebar(treeToMd(newMenuList), 4); - history.push(`/${owner}/${projectsId}/wiki/${encodeURI(menuName)}/${key}`); - } - }) - return; - } - updateSidebar(treeToMd(newMenuList), 4) - } - } - - // 递归menuList 创建子目录 - function addChildrenByKey(list, key, name){ - for (let index = 0; index < list.length; index++) { - const element = list[index]; - if (element.key == key) { - const {title} = element; - element.children.push({ - title: `\t${title.substring(0, title.indexOf('- '))}- ${name}`, - children: [] - }); - return true; - }else if (element.children.length) { - let result = addChildrenByKey(element.children, key, name); - if (result) { - return result; - } - } - } - } - - - function onDrop(info){ - // dragNode: 拖拽的节点 node: 放入的节点 - const dropKey = info.node.props.dataRef.key; - const dragKey = info.dragNode.props.dataRef.key; - const dropPos = info.node.props.pos.split('-'); - const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]); - // dropPosition 0元素之上 1元素之下 -1元素之上 - - function upTitle(list, title){ - const kongGe = title.substring(0, title.indexOf('- ') === -1 ? title.indexOf('[[') : title.indexOf('- ')); - list.map(item=>{ - item.title = `\t${kongGe}${item.title.trim()}`; - if(item.children){ - return upTitle(item.children, item.title); - } - }) - } - - const data = JSON.parse(JSON.stringify(menuList)); - - // Find dragObject - let dragObj; - loop(data, dragKey, (item, index, arr) => { - arr.splice(index, 1); - dragObj = item; - }); - // 如果拖拽的是目录,那么需要遍历所有的子节点,改变\t个数 - if(!dragObj.isFile){ - // 放入的节点标题 - const title = info.node.props.dataRef.title; - const addBool = !info.dropToGap - upTitle(dragObj.children, addBool ? `\t${title}` : title); - } - - // 是否要保存sidebar文件 - let bool = false; - - // dropToGap: true拖拽在节点中间 false拖拽到节点之上 - if (!info.dropToGap) { - // 拖拽到节点之上->追加到尾部 - loop(data, dropKey, (item, index, arr) => { - const {title, key} = item; - if(title.indexOf('- ') === -1){ - message.error('不能拖拽到页面上'); - return; - } - const childrenName = item.children.map(item=>item.titleStr); - if(childrenName.includes(dragObj.titleStr)){ - message.error('不能与同级目录名称相同'); - return; - } - dragObj.title = `${splicingTabs(key.split('-').length)}${dragObj.title.trim()}`; - item.children = item.children || []; - item.children.push(dragObj); - bool = true; - }); - } - // else if ( - // (info.node.props.children || []).length > 0 && // Has children - // dropPosition === 1 // On the bottom gap - // ) { - // // 拖拽到目录节点下 - // loop(data, dropKey, item => { - // const childrenName = item.children.map(item=>item.title.trim()) - // if(childrenName.includes(dragObj.title.trim())){ - // message.error('不能与同级目录名称相同'); - // return; - // } - // console.log('item', item, 'dragObj', dragObj); - // const {title} = item; - // dragObj.title = `\t${title.substring(0, title.indexOf('- '))}${dragObj.title.trim()}`; - // item.children = item.children || []; - // item.children.unshift(dragObj); - // }); - // bool = true; - // } - else { - let ar; - let i; - loop(data, dropKey, (item, index, arr) => { - ar = arr; - i = index; - }); - const childrenName = ar.map(item=>item.titleStr) - if(childrenName.includes(dragObj.titleStr)){ - message.error('不能与同级目录名称相同'); - return; - } - dragObj.title = `${splicingTabs(ar[i].key.split('-').length-1)}${dragObj.title.trim()}`; - if (dropPosition === -1) { - ar.splice(i, 0, dragObj); - } else { - ar.splice(i + 1, 0, dragObj); - } - bool = true; - } - bool && updateSidebar(treeToMd(data), 2); - setMenuList(bool ? data : initMenuList); - } - - // modal cancel - function onCancel(){ - setVisible(false); - setAddMenuError(undefined); - setIsEditName(false); - setMenuName(undefined); - } - - // 展开收起全部节点 - function expandAll(){ - setIsExpendedAll(!isExpendedAll); - if(isExpendedAll){ - setExpandedKeys([]); - return; - } - const allKeys = []; - findMenuKey(allKeys, menuList); - setExpandedKeys(allKeys); - } - - function findMenuKey(keys, list){ - list.map(item=>{ - if(item && !item.title.startsWith("[[")){ - keys.push(item.key); - } - if(item && item.children.length){ - findMenuKey(keys, item.children); - } - }) - } - - function changeSearchValue(value) { - if(value){ - // 找到符合条件的子节点, 重新渲染页面 - let keysMap = findNodesToKeys(initMenuList, value); - const newMenuList = []; - let openedKeysMap = []; - const oneNodeKeys = new Set(); - keysMap.map((item, index)=>{ - const {path, isFile, title} = item; - // 把所有的一级目录key筛选出来 - oneNodeKeys.add(path[0]); - openedKeysMap = openedKeysMap.concat(path); - if(!index && isFile){ - setSelectedKey([path[path.length-1]]); - const titleSplit = title.substring(2,title.length-2); - changeitem({key: path[path.length-1], name: titleSplit}) - } - }) - Array.from(oneNodeKeys).map(item=>loop(initMenuList, item,i=>{ - newMenuList.push(i); - })) - const openedKeys = Array.from(new Set(openedKeysMap)); - setExpandedKeys(openedKeys); - setMenuList(newMenuList); - }else{ - setMenuList(JSON.parse(JSON.stringify(initMenuList))); - } - } - - function findNodesToKeys(arr, value, path, nodes) { - path = path || []; - nodes = nodes || []; - if (Array.isArray(arr)) { - arr.forEach(function (obj, index) { - if (obj && typeof obj === 'object') { - const {children, key} = obj; - const title = obj.title.trim(); - const isFile = title.startsWith('[[') && title.endsWith(']]'); - if (title.match(new RegExp(value, 'i'))) { - nodes.push({path: path.concat([key]), isFile, title}); - } else { - findNodesToKeys(children, value, path.concat([key]), nodes); - } - } - }); - } - return nodes; - } - - return( -
    - - - {/* 目录是否全部展开 */} -
    {isExpendedAll ? '收起' : '展开'}所有节点
    - - {/* wiki目录 */} - {menuList && menuList.length ? - {renderTreeNodes(menuList)} - : ''} - - {/* 新增子目录弹窗 */} - -
    {isEditName === 2 ? '页面' : '目录'}名称:
    - {setMenuName(e.target.value);setAddMenuError(undefined)}} maxLength={50} autoFocus> -
    {addMenuError}
    -
    -
    - ); -} \ No newline at end of file diff --git a/src/forge/Wiki/components/sidebar/index.scss b/src/forge/Wiki/components/sidebar/index.scss deleted file mode 100644 index e39609b4b..000000000 --- a/src/forge/Wiki/components/sidebar/index.scss +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/src/forge/Wiki/components/treeByWikiSidebar.jsx b/src/forge/Wiki/components/treeByWikiSidebar.jsx new file mode 100644 index 000000000..2c9c2ea0a --- /dev/null +++ b/src/forge/Wiki/components/treeByWikiSidebar.jsx @@ -0,0 +1,403 @@ +import React , { useEffect, useState } from 'react'; +import { ConfigProvider, Divider, Dropdown, Icon, Input, Menu, Modal, Tree, TreeNodeProps, message } from 'antd'; +import { Link } from 'react-router-dom'; +import DelModal from './../components/ModalFun'; +import { deleteWiki, findNodeByFirstOrContrastName, findNodeByKey, findSameKeyByDel, treeToMd, updateSidebar } from '../api'; +const {DirectoryTree, TreeNode} = Tree; +const Search = Input.Search; + +const TreeByWikiSidebar = (props)=>{ + const {wikiParams, hasPermission, hasSearch=true, wikiPageInitDetail, clickNode, history, setEditMenu, reload, hasOpen=true, searchClassName="", treeNodeClassName=""} = props; + const {sidebar="", wikiPages=[]} = wikiPageInitDetail || {}; + const searchParams = new URLSearchParams(window.location.search); + const wikiBySearch = searchParams.get("wiki") || ''; + + const [selectedKey, setSelectedKey] = useState([]); + const [expandedKeys, setExpandedKeys] = useState([]); + // 搜索 + const [search, setSearch] = useState(); + const [menuList, setMenuList] = useState(undefined); + + useEffect(()=>{ + const menuListByinit = wikiPageInitDetail.menuList || []; + if(menuListByinit.length){ + // 设置选中的节点 + const node = findNodeByFirstOrContrastName(menuListByinit, wikiBySearch); + if(!node) return + setSelectedKey([node.key]) + // 通过分割key设置默认展开的key + const keys = expandedKeys; + const list = node.key.split('-'); + list.map((item, index)=>{ + keys.push(index > 0 ? `${keys[index-1]}-${item}` : item) + }) + setExpandedKeys(Array.from(new Set(keys))); + } + + }, [sidebar, wikiBySearch]) + + // 搜索 + useEffect(()=>{ + if(search && wikiPageInitDetail.menuList.length){ + const initMenuList = wikiPageInitDetail.menuList + // 找到符合条件的子节点, 重新渲染页面 + let keysMap = findNodesToKeys(initMenuList, search, [], []); + const newMenuList = []; + let openedKeysMap = []; + const oneNodeKeys = new Set(); + keysMap.map((item, index)=>{ + const {path=[], isFile, title} = item; + // 把所有的一级目录key筛选出来 + oneNodeKeys.add(path[0]); + openedKeysMap = openedKeysMap.concat(path); + if(!index && isFile){ + wikiClick(item) + } + }) + Array.from(oneNodeKeys).map(item=>loop(initMenuList, item, (i)=>{ + newMenuList.push(i); + })) + const openedKeys = Array.from(new Set(openedKeysMap)); + setExpandedKeys(openedKeys); + setMenuList(newMenuList); + }else{ + setMenuList(undefined) + } + + function findNodesToKeys(arr, value, path, nodes) { + arr.map(item=>{ + const {children, key, titleStr=""} = item; + if (titleStr.match(new RegExp(value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i'))) { + nodes.push({path: path.concat([key]), ...item}); + } else { + findNodesToKeys(children, value, path.concat([key]), nodes); + } + }) + return nodes; + } + }, [search]) + + function wikiClick(node){ + clickNode(node) + } + + // 收起展开 + function onExpand(keys, obj){ + + const keySet = new Set(expandedKeys); + const {expanded} = obj; + let {key} = obj.node.props.dataRef; + if(expanded){ + keySet.add(key); + }else{ + keySet.delete(key); + } + setExpandedKeys(Array.from(keySet)); + } + + // 遍历list,找到相同的key callback + const loop = (data, key, callback) => { + data.forEach((item, index, arr) => { + if (item.key === key) { + return callback(item, index, arr); + } + if (item.children) { + return loop(item.children, key, callback); + } + }); + }; + + function splicingTabs(count) { + var tabs = ""; + for (var i = 0; i < count; i++) { + tabs += "\t"; + } + return tabs; + } + + function onDrop(info){ + + // dragNode: 拖拽的节点 node: 放入的节点 + const dropKey = info.node.props.dataRef.key; + const dragKey = info.dragNode.props.dataRef.key; + const dropPos = info.node.props.pos.split('-'); + //0元素之上 1元素之下 -1元素之上 + const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]); + + function upTitle(list, title){ + const kongGe = title.substring(0, title.indexOf('- ') === -1 ? title.indexOf('[[') : title.indexOf('- ')); + list.map(item=>{ + item.title = `\t${kongGe}${item.title.trim()}`; + if(item.children){ + return upTitle(item.children, item.title); + } + }) + } + + const data = JSON.parse(JSON.stringify(wikiPageInitDetail.menuList)); + + // Find dragObject + let dragObj; + loop(data, dragKey, (item, index, arr) => { + arr.splice(index, 1); + dragObj = item; + }); + // 如果拖拽的是目录,那么需要遍历所有的子节点,改变\t个数 + if(!dragObj.isFile){ + // 放入的节点标题 + const title = info.node.props.dataRef.title; + const addBool = !info.dropToGap + upTitle(dragObj.children, addBool ? `\t${title}` : title); + } + + // 是否要保存sidebar文件 + let bool = false; + + // dropToGap: true拖拽在节点中间 false拖拽到节点之上 + if (!info.dropToGap) { + // 拖拽到节点之上->追加到尾部 + loop(data, dropKey, (item) => { + const {title, key} = item; + if(title.indexOf('- ') === -1){ + message.error('不能拖拽到页面上'); + return; + } + const childrenName = item.children.map(item=>item.titleStr); + if(childrenName.includes(dragObj.titleStr)){ + message.error('不能与同级目录名称相同'); + return; + } + dragObj.title = `${splicingTabs(key.split('-').length)}${dragObj.title.trim()}`; + item.children = item.children || []; + item.children.push(dragObj); + bool = true; + }); + } else { + let ar; + let i; + loop(data, dropKey, (item, index, arr) => { + ar = arr; + i = index; + }); + const childrenName = ar.map((item)=>item.titleStr) + if(childrenName.includes(dragObj.titleStr)){ + message.error('不能与同级目录名称相同'); + return; + } + + dragObj.title = `${splicingTabs(ar[i].key.split('-').length-1)}${dragObj.title.trim()}`; + if (dropPosition === -1) { + ar.splice(i, 0, dragObj); + } else { + ar.splice(i + 1, 0, dragObj); + } + bool = true; + } + if(!bool) return + updateSidebar(wikiParams, treeToMd(data)).then(res=>{ + if(res && res.code === 200){ + message.success('操作成功') + reload && reload(); + } + }) + } + + // 新增子目录/重命名 + function menuClick(e, key, typeMenuModal){ + e.stopPropagation(); + onExpand([], {expanded: true, node: {props: {dataRef: {key}}}}) + setEditMenu && setEditMenu(typeMenuModal); + } + + // 展开收起全部节点 + function expandAll(){ + if(expandedKeys.length > 0){ + setExpandedKeys([]); + return; + } + const allKeys = []; + findMenuKey(allKeys, wikiPageInitDetail.menuList); + setExpandedKeys(allKeys); + + function findMenuKey(keys, list){ + list && list.map(item=>{ + if(!item.isFile){ + keys.push(item.key); + } + if(item && item.children.length){ + findMenuKey(keys, item.children); + } + }) + } + } + + // 删除wiki模态框 + function deleteFileModal(e, item) { + const {key, isFile, titleStr, title_sub} = item; + e.stopPropagation(); + DelModal({ + title: `删除${isFile ? '页面' : '目录'}`, + contentTitle: `您确定要删除“${titleStr}”吗?`, + content: `此操作将删除此${isFile ? '页面' : '目录'},请进行确认以防文件的丢失`, + onOk: () => { + if (isFile) { + // 删除页面 + deleteWiki({ ...wikiParams, pageName: title_sub }).then(res => { + if (res && res.code === 204) { + handle() + } + }); + } else { + // 删除目录(递归找到所有的[[页面]],遍历删除) + if (wikiPageInitDetail && !wikiPageInitDetail.menuList || !key) return + // needDelNode: 被删除的子节点 + const needDelNode = findNodeByKey(wikiPageInitDetail.menuList, key); + // needDelWikis:所有需要删除的子目录列表 + const needDelWikis = []; + findDelNode(needDelNode.children || [], needDelWikis); + if (needDelWikis.length) { + // 有需要删除的子目录 + const promiseList = [] + // 删除wiki + function deleteWikiFun(pageName){ + return new Promise((resolve, reject)=>{ + deleteWiki({ ...wikiParams, pageName: pageName }).then(res => { + resolve(res) + }) + }) + } + needDelWikis.map(item => { + promiseList.push(deleteWikiFun(item.title_sub)) + }) + Promise.all(promiseList).then((values) => { + if (values.filter(item => item.code === 204 || item.code === 404).length === needDelWikis.length) { + handle() + } else { + message.error('发生未知错误,请联系系统管理员') + } + }); + } else { + // 没有需要删除的子目录,直接修改sidebar文件即可 + handle() + } + } + function handle() { + if (wikiPageInitDetail && !wikiPageInitDetail.menuList || !key) return + // 递归过滤相同key值的节点,执行删除操作 + const menuListByDel = findSameKeyByDel(wikiPageInitDetail.menuList, key); + const sidebarContent = treeToMd(menuListByDel); + if(!sidebarContent){ + // 删除最后一个wiki的时候,同时删除sidebar + deleteWiki({...wikiParams, pageName: "_Sidebar"}).then(res => { + if(res && res.code === 204){ + success(); + } + }); + return + } + updateSidebar(wikiParams, sidebarContent).then(res=>{ + if(res && res.code === 200){ + success(); + } + }); + + } + // 删除目录:递归找到需要删除的子目录 + function findDelNode(list, nodes){ + //利用foreach循环遍历 + list.forEach(item => { + //判断递归结束条件 + if (item.isFile) { + // 存储数据到空数组 + nodes.push(item); + } + if (item.children.length){ + //递归调用 + const result = findDelNode(item.children, nodes); + if(result) return result + }else{ + return true + } + }) + return nodes; + } + + function success(){ + reload && reload(); + message.success('删除成功'); + history.push(`/${wikiParams.owner}/${wikiParams.repo}/wiki`); + } + } + }); + } + + function renderTreeNodes(data) { + return data && data.map((item) => { + const {titleStr, isFile, key} = item; + return ( + + + + {titleStr} + + {hasPermission && + {!isFile && + 添加子页面 + } + {!isFile && + { menuClick(e, key, `addMenuBy${key}`)}}> + 添加子目录 + + } + + { menuClick(e, key, isFile ? `editWiki${key}` : `editMenu${key}`) }}> + 重命名 + + + + { deleteFileModal(e, item) }}>删除 + + + } placement="bottomRight"> + + } + } key={item.key+""} isLeaf={isFile} dataRef={item} className={treeNodeClassName}> + {renderTreeNodes(item.children)} + + ); + }); + } + + return
    + {/* draggable={permission} onDrop={onDrop} */} + {hasSearch &&
    +
    + {setSearch(value)}} + className={searchClassName} + /> +
    + {hasOpen &&
    {expandedKeys.length > 0 ? '收起' : '展开'}节点
    } +
    } + { + const {dataRef} = event.node.props; + dataRef.isFile && wikiClick(dataRef); + }} + draggable={hasPermission} + onDrop={onDrop} + > + {renderTreeNodes(menuList || wikiPageInitDetail.menuList)} + +
    +} +export default TreeByWikiSidebar \ No newline at end of file diff --git a/src/forge/Wiki/components/uploadWiki.jsx b/src/forge/Wiki/components/uploadWiki.jsx new file mode 100644 index 000000000..e794351c8 --- /dev/null +++ b/src/forge/Wiki/components/uploadWiki.jsx @@ -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 + + + + +} \ No newline at end of file diff --git a/src/forge/Wiki/components/uploadWiki/index.jsx b/src/forge/Wiki/components/uploadWiki/index.jsx deleted file mode 100644 index 30cfe492c..000000000 --- a/src/forge/Wiki/components/uploadWiki/index.jsx +++ /dev/null @@ -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 - - - - -} \ No newline at end of file diff --git a/src/forge/Wiki/config.js b/src/forge/Wiki/config.js new file mode 100644 index 000000000..bbe125f62 --- /dev/null +++ b/src/forge/Wiki/config.js @@ -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 +}] \ No newline at end of file diff --git a/src/forge/Wiki/page/edit.jsx b/src/forge/Wiki/page/edit.jsx new file mode 100644 index 000000000..d6a2cfe71 --- /dev/null +++ b/src/forge/Wiki/page/edit.jsx @@ -0,0 +1,215 @@ +import React, { useEffect, useState } from "react"; +import { Form, Icon, Input, Button, Spin, message, Radio } from "antd"; +import './index.scss'; +import { addChildrenByKey, addWiki, editMenuTitleByKey, findSamaNameWiki, getMenuListAndSidebarData, getWiki, treeToMd, updateSidebar, updateWiki } from "../api"; +import { template } from "../config"; +import DelModal from "../components/ModalFun"; +import { Base64 } from "js-base64"; +import TpmMdEditor from "../../../modules/tpm/challengesnew/tpm-md-editor"; + +// 新增/编辑wiki +export default Form.create()((props) => { + const titleByEditType = { + "add": "新增", + "edit": "编辑", + "copy": "复制" + } + const {form, history, project_id, match:{params:{owner, projectsId: repoIdentifier}}, location:{pathname, search}, projectDetail} = props; + const { author: projectAuthor, name: projectName} = projectDetail || {}; + const searchParams = new URLSearchParams(search); + const {getFieldDecorator, validateFields, getFieldValue} = form; + const wikiParams = {owner, repo: repoIdentifier, projectId: project_id}; + let editType = pathname.split("/").pop(); //编辑wiki(addWiki addWikiBy${key} edit) + + const [loading, setLoading] = useState(false); + const [wikiPageInitDetail, setWikiPageInitDetail] = useState({}) + const {menuList=[], sidebar="", wikiPages=[]} = wikiPageInitDetail; + const [wikiDetail, setWikiDetail] = useState(); + const [templateBySelect, setTemplateBySelect] = useState(); + + useEffect(()=>{ + if(!project_id) return + // 获取wiki 目录树、sidebar内容 + getMenuListAndSidebarData(wikiParams).then(res=>{ + setWikiPageInitDetail(res); + }) + }, [project_id]) + + useEffect(()=>{ + if(editType !== "add" && wikiPages.length){ + setLoading(true); + // 根据wiki名称获取wiki内容 + const wikiName = searchParams.get("wiki") + const sameNameWiki = findSamaNameWiki(wikiPages, wikiName) + document.title = `${titleByEditType[editType]}${sameNameWiki.title}-维基-${projectAuthor && projectAuthor.name}/${projectName}`; + getWiki({ + ...wikiParams, + pageName: sameNameWiki.sub_url + }).then(res=>{ + if(res && res.code === 200){ + res.data.content = Base64.decode(res.data.content_base64) + setWikiDetail({ + ...res.data, + sub_url: sameNameWiki.sub_url + }); + form.setFieldsValue({ + title: `${sameNameWiki.title}${editType === "copy" ? "(复制)" : ""}`, + md_content: res.data.content + }) + } + setLoading(false); + }) + }else{ + document.title = `${titleByEditType[editType]}-维基-${projectAuthor && projectAuthor.name}/${projectName}`; + } + }, [editType, sidebar]) + + function submit(e){ + e.preventDefault(); + validateFields(async (err, values)=>{ + if(!err){ + setLoading(true); + const {title, md_content} = values; + const sameNameWiki = findSamaNameWiki(wikiPages, title) + if(editType === "edit"){ + // 编辑wiki + if(sameNameWiki && sameNameWiki.title !== wikiDetail.title){ + message.error('不能与已有文件标题相同'); + setLoading(false); + return + } + await updateWiki({ + ...wikiParams, + pageName: wikiDetail.sub_url, + title, + message: '', + content_base64: Base64.encode(md_content) + }).then(async res => { + if(res && res.code === 200){ + if(wikiDetail.title === title){ + // 只修改了wiki内容,重新加载wiki详情即可 + message.success('修改成功'); + reload(); + return + } + // 修改了wiki标题,需要修改sidebar文件,重新加载目录树、wiki详情 + const key = searchParams.get("key") + editMenuTitleByKey(menuList, key, `${'\t'.repeat(key.split('-').length-1)}[[${title}]]`); + updateSidebar(wikiParams, treeToMd(menuList)).then(response=>{ + if(response && response.code === 200){ + message.success('修改成功'); + reload(); + } + }) + } + }); + }else{ + // 新增wiki:先新增,再更新sidebar + if(sameNameWiki){ + message.error('不能与已有文件标题相同'); + setLoading(false); + return + } + await addWiki(wikiParams, title, md_content).then(async res=>{ + if(res && res.code === 201){ + // 构建sidebar内容,新增一级页面(/add)、新增子级页面(/add?key=xx)、复制页面(/copy?key=xx) + let sidebarContent = ""; + const key = searchParams.get("key") + if((editType === "add" && !key) || (editType === "copy" && !key.includes("-"))){ + // 新增/复制一级wiki + sidebarContent = sidebar ? `${sidebar}\n[[${title}]]` : `[[${title}]]` + }else if(editType === "add"){ + // 新增子级页面, 加到key的子页面 + addChildrenByKey(menuList, key, `[[${title}]]`) + sidebarContent = treeToMd(menuList) + }else{ + // 复制子级页面页面,加到key的同级位置 + const supKey = key.split("-"); + supKey.pop(); + addChildrenByKey(menuList, supKey.join('-'), `[[${title}]]`) + sidebarContent = treeToMd(menuList) + } + // 新增/修改sidebar wiki文件 + if(!sidebar){ + // 第一次创建wiki + await addWiki(wikiParams,"_Sidebar", sidebarContent) + }else{ + await updateSidebar(wikiParams, sidebarContent) + } + message.success("新增成功"); + reload(); + } + }); + } + setLoading(false); + function reload(){ + // 修改路由 + history.push(`/${owner}/${repoIdentifier}/wiki?wiki=${encodeURIComponent(title)}`) + } + } + }) + } + + return
    +
    + + {history.go(-1)}} className="primaryText">Wiki + + {titleByEditType[editType]}页面 + +
    + +
    + + {getFieldDecorator('title', { + rules: [ + { required: true, message: '请输入页面标题内容!' }, + { pattern: /^(?!-).*$/, message: '不能以-开头' } + ], + })()} + + + + {getFieldDecorator('md_content', { + rules: [{ required: true, message: "请输入wiki内容" }], + })()} + + +

    添加模版

    + + {template.map(item=>{ + return { + DelModal({ + title: '添加模版', + contentTitle: `您确定要添加“${item.name}”模板吗`, + content: `此操作会将“${item.name}”模板替换编辑栏内所有内容,请确认以防文件的丢失`, + okText: '确认添加', + onOk: () => { + setTemplateBySelect(item.name); + form.setFieldsValue({ + md_content: item.value + }) + } + }) + }}>{item.name} + })} + + + + + +
    +
    + +
    +}) \ No newline at end of file diff --git a/src/forge/Wiki/page/index.jsx b/src/forge/Wiki/page/index.jsx new file mode 100644 index 000000000..b4f7c77b8 --- /dev/null +++ b/src/forge/Wiki/page/index.jsx @@ -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 + {/* 空数据页面 */} + {!spinning && !wikiPages.length && } + {/* wiki首页 */} + {!!wikiPages.length &&
    +
    + + { + permission ? +
    + + +
    : "Wiki文档" + } +
    +
    + { + permission && + } + + + +
    +
    + +
    +
    +
    +
    + {/* 读sidebar转换成目录树 */} + {history.push(`/${owner}/${repoIdentifier}/wiki?wiki=${encodeURIComponent(node.titleStr)}`)}} history={history} setEditMenu={setTypeMenuModal} reload={refush}/> +
    +
    + + + +
    + {wikiDetail &&
    + +
    +
    +

    {wikiDetail.title}

    +
    + + {wikiDetail.image_url && 头像} + {wikiDetail.userName} + + 上次修改于{wikiDetail.last_commit ? timeAgo(wikiDetail.last_commit.author.date) : '刚刚'} +
    +
    + {permission &&
    + + +
    } +
    + + + {wikiDetail.content_base64 && } +
    +
    } +
    +
    } + {/* 新建/编辑目录名称 */} + {setTypeMenuModal(undefined)}} history={history} reload={refush}/> +
    +} \ No newline at end of file diff --git a/src/forge/Wiki/page/index.scss b/src/forge/Wiki/page/index.scss new file mode 100644 index 000000000..0f98f10ee --- /dev/null +++ b/src/forge/Wiki/page/index.scss @@ -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 + } + } +} \ No newline at end of file diff --git a/src/forge/Wiki/page/preview.jsx b/src/forge/Wiki/page/preview.jsx new file mode 100644 index 000000000..ef859e248 --- /dev/null +++ b/src/forge/Wiki/page/preview.jsx @@ -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 ( +
    +
    + + + {projectName} + +
    +
    克隆地址
    + + +
    +
    + + +
    +
    + {/* 读sidebar转换成目录树 */} + { + history.push(`${location.pathname}?name=${projectName}&wiki=${encodeURIComponent(node.titleStr)}`) + }}/> +
    + +
    +

    {checkItem.titleStr}

    + + {itemDetail && itemDetail.content_base64 && } +
    +
    + + +
    + ) +} \ No newline at end of file diff --git a/src/forge/Wiki/page/welcome.jsx b/src/forge/Wiki/page/welcome.jsx new file mode 100644 index 000000000..8d0d2ce68 --- /dev/null +++ b/src/forge/Wiki/page/welcome.jsx @@ -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 ( +
    + +

    欢迎使用  + + {project && project.name} + +  Wiki

    +

    Wiki主要是您项目的产品设计、文档描述、注释等等

    + + { + isManager ?
    + + +
    :

    该项目暂时没有创建Wiki

    + } +
    + ) +} \ No newline at end of file diff --git a/src/forge/Wiki/utils.js b/src/forge/Wiki/utils.js deleted file mode 100644 index 8eede2e38..000000000 --- a/src/forge/Wiki/utils.js +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/src/forge/comments/children_comments.js b/src/forge/comments/children_comments.js index 809ffdb54..bcf6265d0 100644 --- a/src/forge/comments/children_comments.js +++ b/src/forge/comments/children_comments.js @@ -124,20 +124,15 @@ class children_comments extends Component {
    - - {item && item.user_name}
    diff --git a/src/forge/comments/comments.js b/src/forge/comments/comments.js index 52707b8be..d28871e49 100644 --- a/src/forge/comments/comments.js +++ b/src/forge/comments/comments.js @@ -397,24 +397,34 @@ class comments extends Component { return (
    - - - - - {item && item.user_name} - + { + item && item.user_login ? + + + {item && item.user_name} + + : + + + {item && item.user_name} + + } +
    {item.content ? ( diff --git a/src/forge/users/Index.jsx b/src/forge/users/Index.jsx index bdd2db132..9ad571647 100644 --- a/src/forge/users/Index.jsx +++ b/src/forge/users/Index.jsx @@ -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 = ''; diff --git a/src/forge/users/Infos.js b/src/forge/users/Infos.js index 4d3f49439..7e60cd5a6 100644 --- a/src/forge/users/Infos.js +++ b/src/forge/users/Infos.js @@ -410,7 +410,7 @@ class Infos extends Component { {/* 当前用户+配置了贡献确权 */} {current_user && user && user.login === current_user.login && current_user.open_blockchain && - 贡献确权 + 贡献确权 } } diff --git a/src/forge/users/Material/Password.jsx b/src/forge/users/Material/Password.jsx index c2dda19c4..bbb4a30c2 100644 --- a/src/forge/users/Material/Password.jsx +++ b/src/forge/users/Material/Password.jsx @@ -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( @@ -333,14 +360,15 @@ export default Form.create()( : type === '2' ?
    + {setOffVisible(false)}} {...props}/>
    请您谨慎操作,注销后帐号内所有数据都会被清空,且无法恢复帐号!
    - GitLink目前提供邮件渠道帐号注销服务。如需注销帐号,请通过发送邮件注销申请邮件到 gitlink@ccf.org.cn
    - 相关注意事项如下:
    - 必须使用要注销的 GitLink 帐号所绑定的邮箱地址发送邮件。 - 发送注销申请邮件之前确认帐号下无创建/加入组织,帐号名下无仓库信息。 - 邮件标题: 注销 GitLink 帐号,邮件正文请注明要注销帐号的邮箱和昵称。 - 发送邮件后,我们会积极处理,请耐心等候我们的邮件回复。 + 您可点击按钮完成账号的注销,注销后相关数据将不可恢复,注意事项如下:
    + 提交注销前,请确保此账号下没有拥有的组织及代码库,若存在上述情况请及时转让或删除。 + 注销后该账号产生的所有数据将会丢失且不可恢复,请提前进行数据备份。 + {/* 注销账号后,该账号在Gitlink产生的所有过程数据(如疑修,合并请求,论坛帖子等)也将被删除。 + 注销后你将丢失该帐号产生的所有数据,一旦注销相关数据将不可恢复,请输入登录密码再次确认。 */} + { current_user && }
    : diff --git a/src/forge/users/Material/component/offAccount.jsx b/src/forge/users/Material/component/offAccount.jsx new file mode 100644 index 000000000..44acb2e8e --- /dev/null +++ b/src/forge/users/Material/component/offAccount.jsx @@ -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( + +
    +

    是否仍要注销?

    +

    注销后你将丢失该帐号产生的所有数据,一旦注销相关数据将不可恢复,请输入登录密码再次确认

    +
    + + {getFieldDecorator("password",{ + rules:[ + {required:true,message:"请输入登录密码"}, + // {validator:(rule, value, callback)=>checkNewPass(rule, value, callback, NewPassRepeat)} + ], + validateFirst: true, + validateTrigger:"onBlur", + })( + + )} + + + {getFieldDecorator("memo",{ + rules:[{required:true,message:"请输入注销原因"}], + })( +