diff --git a/src/App.js b/src/App.js index f5c3fce4b..26fb64694 100644 --- a/src/App.js +++ b/src/App.js @@ -150,6 +150,11 @@ const Badge = Loadable({ // loading: Loading, // }) +const Topic = Loadable({ + loader: () => import('./forge/topic'), + loading: Loading, +}) + // 此处仅维护前端可能的一级路由,不用进行项目或者组织判断的字段。 const keyWord = ["explore", "settings", "setting", "mulan", "wiki", "issues", "setting", "trending", "code", "projects", "pulls", "mine", "login", "register", "email", "export", "nopage", "404", "403", "500", "501", "search", "organize", "login", "register", "resetPassword", "aboutus","educoder", "glcc","bindlogin", "softbot"]; @@ -390,6 +395,9 @@ class App extends Component { {/* 查询 */} + {/* 项目标签列表页 */} + + ( diff --git a/src/forge/Head/AppPullRefresh.jsx b/src/forge/Head/AppPullRefresh.jsx index 2b9c76606..cfb16a24c 100644 --- a/src/forge/Head/AppPullRefresh.jsx +++ b/src/forge/Head/AppPullRefresh.jsx @@ -81,7 +81,7 @@ class PullRefresh extends Component { {children} { - count < 1 && + count < 1 && } {/* 大于分页数据才显示loading */} diff --git a/src/forge/Images/nodata.png b/src/forge/Images/nodata.png index ad4c76b97..5a1231e7f 100644 Binary files a/src/forge/Images/nodata.png and b/src/forge/Images/nodata.png differ diff --git a/src/forge/Main/CoderDepot.jsx b/src/forge/Main/CoderDepot.jsx index b0bb7fcba..6871a5f94 100644 --- a/src/forge/Main/CoderDepot.jsx +++ b/src/forge/Main/CoderDepot.jsx @@ -592,7 +592,7 @@ function CoderDepot(props){ {desc &&

{desc}

} {/* 项目标签 */} {topics &&
- {topics.map(item=>{item.name})} + {topics.map(item=>{item.name})}
} { website && diff --git a/src/forge/Main/IndexItem.js b/src/forge/Main/IndexItem.js index efd3fc3b6..a591ed0d8 100644 --- a/src/forge/Main/IndexItem.js +++ b/src/forge/Main/IndexItem.js @@ -71,7 +71,7 @@ class IndexItem extends Component {

{item.description}

{/* 项目标签 */} {item.topics &&
- {item.topics.map(topic=>{topic.name})} + {item.topics.map(topic=>{topic.name})}
}
diff --git a/src/forge/Main/projecthome/Index.jsx b/src/forge/Main/projecthome/Index.jsx index 719e21a5c..5ea66b1e7 100644 --- a/src/forge/Main/projecthome/Index.jsx +++ b/src/forge/Main/projecthome/Index.jsx @@ -123,7 +123,7 @@ function Index() {
{/* 项目标签 */} {i.topics &&
- {i.topics.map(item=>{item.name})} + {i.topics.map(item=>{item.name})}
}
{i.category && i.category.id ? {i.category.name} :"" } diff --git a/src/forge/Main/sub/UpdateDescModal.jsx b/src/forge/Main/sub/UpdateDescModal.jsx index f0546bcee..0043b865a 100644 --- a/src/forge/Main/sub/UpdateDescModal.jsx +++ b/src/forge/Main/sub/UpdateDescModal.jsx @@ -37,10 +37,13 @@ function UpdateDescModal({form , visible , onCancel , onOk,desc,website,lesson_u // 校验项目标签 function checkTopics(rule, value, callback){ const boolLength = value.filter(item=>item.length > 20); + const bool = value.filter(item=>!/^(?![-])[a-zA-Z0-9\u4e00-\u9fa5\\-]+$/.test(item)); if(value.length > 5){ callback("最多创建5个标签"); }else if(boolLength.length){ callback("每个标签最多支持20个汉字"); + }else if(bool.length){ + callback("标签名只允许包含中文、字母、数字或者中划线(-), 不能以中划线开头"); } callback(); } diff --git a/src/forge/Order/MilepostDetail.jsx b/src/forge/Order/MilepostDetail.jsx index 5c519106b..3db34c636 100644 --- a/src/forge/Order/MilepostDetail.jsx +++ b/src/forge/Order/MilepostDetail.jsx @@ -179,7 +179,7 @@ function MilepostDetail(props){ {milepost.effective_date || '暂无截止时间'} - {(milepost.percent || milepost.percent===0) ? {milepost.percent > 0 ? milepost.percent.toFixed(2)*100:milepost.percent}%完成 :"" } + {(milepost.percent || milepost.percent===0) ? {milepost.percent > 0 ? parseInt(milepost.percent*100):milepost.percent}%完成 :"" }

diff --git a/src/forge/SecuritySetting/bot/disposition/detail.jsx b/src/forge/SecuritySetting/bot/disposition/detail.jsx index 6a267f1a4..ebcc209f9 100644 --- a/src/forge/SecuritySetting/bot/disposition/detail.jsx +++ b/src/forge/SecuritySetting/bot/disposition/detail.jsx @@ -5,6 +5,7 @@ import { Link } from "react-router-dom"; import { getInstallBot, updateInstallBot } from "../../../../softbot/api"; import { getImageUrl } from 'educoder'; import './index.scss'; +import '../exploit/index.scss'; function DispositionDetail(props){ const {match:{params:{id}}, current_user, current_user:{login, user_id}} = props; @@ -16,6 +17,8 @@ function DispositionDetail(props){ // bot状态 const [botState, setBotState] = useState(true); const [error, setError] = useState(false); + // 保存按钮loading效果 + const [saveLoading, setSaveLoading] = useState(false); // 获取bot详情 useEffect(()=>{ @@ -122,6 +125,7 @@ function DispositionDetail(props){ repo_owner_map[item.id+""] = login; }); } + setSaveLoading(true); current_user && updateInstallBot({ bot_id: id, password: "", @@ -134,6 +138,7 @@ function DispositionDetail(props){ installed_store_list: botDetail.store_id_list }).then(res=>{ if(res && res.code === 200){ + setSaveLoading(false); setSelectValue([]); const repoIds = new Set(); options.map(item=>{ @@ -143,6 +148,7 @@ function DispositionDetail(props){ getInsBotDetail(repoIds); message.success('更改成功'); }else{ + setSaveLoading(false); message.error(res.message); } }) @@ -154,7 +160,10 @@ function DispositionDetail(props){ {botDetail &&
- +
+ {botDetail && botDetail.market_name} + {botDetail.is_public ? '公开' : '私有'} +
{botDetail && botDetail.register_name} {botDetail && botDetail.create_time}
@@ -192,7 +201,7 @@ function DispositionDetail(props){ {botState ? '开始使用该Bot,授予该Bot所需的仓库访问权限' : '暂停停止使用该bot,阻塞该bot拥有的仓库访问权限'}
- {botDetail && !botDetail.is_public && login !== botDetail.register_login ? : } + {botDetail && !botDetail.is_public && login !== botDetail.register_login ? : } } diff --git a/src/forge/SecuritySetting/bot/disposition/index.scss b/src/forge/SecuritySetting/bot/disposition/index.scss index c2d580a31..e020c4b1e 100644 --- a/src/forge/SecuritySetting/bot/disposition/index.scss +++ b/src/forge/SecuritySetting/bot/disposition/index.scss @@ -106,6 +106,10 @@ } } .changeStatusTip{color:#99a2af;} + .myInstallBotStatus{ + position: relative; + top: -4px; + } } .nullBotsBox{ border-radius: 4px 4px 0px 0px; diff --git a/src/forge/SecuritySetting/bot/exploit/configuration/compenent/jurisdiction.jsx b/src/forge/SecuritySetting/bot/exploit/configuration/compenent/jurisdiction.jsx index a9a199fcd..bf223907e 100644 --- a/src/forge/SecuritySetting/bot/exploit/configuration/compenent/jurisdiction.jsx +++ b/src/forge/SecuritySetting/bot/exploit/configuration/compenent/jurisdiction.jsx @@ -1,5 +1,5 @@ import React from "react"; -import { Button, Checkbox, Form, Input, message, Select } from "antd"; +import { Button, Checkbox, Form, Input, message, Select, Row, Col } from "antd"; import { useState } from "react"; import '../../new/index.scss'; import './index.scss'; @@ -13,12 +13,14 @@ function Jurisdiction(props){ const resource = [{value: 2, name: '无权限'}, {value: 0, name: '只读'}, {value: 1, name: '读写'}]; const [codeSelectValue , setCodeSelectValue ] = useState(undefined); const [prSelectValue , setPrSelectValue ] = useState(undefined); + const [ issueSelectValue , setIssueSelectValue ] = useState(undefined); useEffect(()=>{ if(botDetail){ - const {limit_and_events:{juris_diction_code, juris_diction_pr}} = botDetail + const {limit_and_events:{juris_diction_code, juris_diction_pr, juris_diction_issue}} = botDetail setCodeSelectValue(juris_diction_code); setPrSelectValue(juris_diction_pr); + setIssueSelectValue(juris_diction_issue); } }, [botDetail]) @@ -32,6 +34,11 @@ function Jurisdiction(props){ return e; } + function issueControl(e){ + setIssueSelectValue(e); + return e; + } + // 修改注册bot的权限 function submit(e){ e.preventDefault(); @@ -39,15 +46,17 @@ function Jurisdiction(props){ if(err){ return; } - const {juris_diction_code, juris_diction_pr, event_code, event_pr} = fieldsValue; + const {juris_diction_code, juris_diction_pr, event_code, event_pr, event_issue, juris_diction_issue} = fieldsValue; const params = { ...botDetail, bot_id: parseInt(id), limit_and_events: { - event_code: event_code ? event_code.toString() : "", - event_pr: event_pr ? event_pr.toString() : "", + event_code: event_code ? event_code.toString() : '', + event_pr: event_pr ? event_pr.toString() : '', + event_issue: event_issue ? event_issue.toString(): '', juris_diction_code, - juris_diction_pr + juris_diction_pr, + juris_diction_issue }, user_id: current_user && current_user.user_id, } @@ -64,8 +73,8 @@ function Jurisdiction(props){ // 校验仓库访问权限 function jurisDictionPrValidator(rule, value, callback){ - const {juris_diction_code} = getFieldsValue(); - if(juris_diction_code === 2 && value === 2){ + const {juris_diction_code, juris_diction_pr} = getFieldsValue(); + if(juris_diction_code === 2 && value === 2 && juris_diction_pr === 2){ callback('请至少选择一个权限'); return; } @@ -73,21 +82,13 @@ function Jurisdiction(props){ } // 校验用户必选一个订阅事件 - function eventValidator(rule, value, callback, type){ - const {event_code, event_pr} = getFieldsValue(); - const resourceValue = resource[0].value; - const boolPr = (event_code === undefined || (event_code && event_code.length === 0)) && prSelectValue === resourceValue; - const boolCode = (event_pr === undefined || (event_pr && event_pr.length === 0)) && codeSelectValue === resourceValue; - const bool = prSelectValue !== resourceValue && codeSelectValue !== resourceValue && (event_pr === undefined || (event_pr && event_pr.length === 0)) && (event_code === undefined || (event_code && event_code.length === 0)) - if(boolPr || boolCode){ - callback("请至少选择一个订阅"); - return; + function eventValidator(rule, value, callback){ + const {event_code, event_pr, event_issue} = getFieldsValue(); + if(event_code || event_pr || event_issue){ + callback(); + }else{ + callback("请至少选择一个订阅事件"); } - if(bool && type === "pr"){ - callback("请至少选择一个订阅"); - return; - } - callback(); } return
@@ -104,7 +105,16 @@ function Jurisdiction(props){
合并请求的打开、关闭、编辑、分配
- {getFieldDecorator("juris_diction_pr",{rules:[{validator: jurisDictionPrValidator}],initialValue: botDetail.limit_and_events.juris_diction_pr, getValueFromEvent: prControl})( + {getFieldDecorator("juris_diction_pr",{initialValue: botDetail.limit_and_events.juris_diction_pr, getValueFromEvent: prControl})( + + )} +
+
+ +
疑修的打开、关闭、编辑、分配、评论
+ {getFieldDecorator("juris_diction_issue",{rules:[{validator: jurisDictionPrValidator}],initialValue: botDetail.limit_and_events.juris_diction_issue, getValueFromEvent: issueControl})( @@ -114,12 +124,14 @@ function Jurisdiction(props){ {codeSelectValue !== resource[0].value && {getFieldDecorator("event_code", { initialValue: botDetail.limit_and_events.event_code ? botDetail.limit_and_events.event_code.split(',').map(item=>item = parseInt(item)) : [], - rules:[{validator: (rule, value, callback)=>{eventValidator(rule, value, callback, "code")}}] + rules:[{validator: (rule, value, callback)=>{eventValidator(rule, value, callback)}}] })( - 推送
git推送到存储库
- 创建
创建分支或标签
- 删除
删除分支或标签
+ + 推送
git推送到存储库
+ 创建
创建分支或标签
+ 删除
删除分支或标签
+
)}
} @@ -127,11 +139,30 @@ function Jurisdiction(props){ {prSelectValue !== resource[0].value && {getFieldDecorator("event_pr", { initialValue: botDetail.limit_and_events.event_pr ? botDetail.limit_and_events.event_pr.split(',').map(item=>item = parseInt(item)) : [], - rules:[{validator: (rule, value, callback)=>{eventValidator(rule, value, callback, "pr")}}] + rules:[{validator: (rule, value, callback)=>{eventValidator(rule, value, callback)}}] })( - 合并请求
合并请求被打开、被关闭、被重新打开或被编辑
- 合并请求分配
合并请求被分配或取消分配
+ + 合并请求
合并请求被打开、被关闭或被编辑
+ 合并请求分配
合并请求被分配或取消分配
+ 合并请求被评论
合并请求评论被创建或删除
+
+
+ )} +
} + {(codeSelectValue !== resource[0].value || prSelectValue !== resource[0].value) && issueSelectValue !== resource[0].value &&
} + {issueSelectValue !== resource[0].value && + {getFieldDecorator("event_issue",{ + initialValue: botDetail.limit_and_events.event_issue ? botDetail.limit_and_events.event_issue.split(',').map(item=>item = parseInt(item)) : [], + rules:[{validator: (rule, value, callback)=>{eventValidator(rule, value, callback)}}] + })( + + + 疑修
疑修已打开、已关闭、已重新打开或编辑
+ 疑修已指派
疑修已被指派或取消指派
+ 已标记疑修
疑修标记被更新或清除
+ 疑修评论
疑修评论被创建、编辑或删除
+
)}
} diff --git a/src/forge/SecuritySetting/bot/exploit/new/index.jsx b/src/forge/SecuritySetting/bot/exploit/new/index.jsx index e18877ddd..bdecfa7de 100644 --- a/src/forge/SecuritySetting/bot/exploit/new/index.jsx +++ b/src/forge/SecuritySetting/bot/exploit/new/index.jsx @@ -1,5 +1,5 @@ import React, { forwardRef, useState, useEffect } from "react"; -import { Button, Checkbox, Form, Input, message, Radio, Select } from "antd"; +import { Button, Checkbox, Col, Form, Input, message, Radio, Row, Select } from "antd"; import { Link } from "react-router-dom"; import MDEditor from '../../../../../modules/tpm/challengesnew/tpm-md-editor'; import './index.scss'; @@ -14,7 +14,8 @@ function Disposition(props){ const [ content , setContent ] = useState(undefined); const [ codeSelectValue , setCodeSelectValue ] = useState(resource[0].value); const [ prSelectValue , setPrSelectValue ] = useState(resource[0].value); - + const [ issueSelectValue , setIssueSelectValue ] = useState(resource[0].value); + useEffect(()=>{ document.title='注册新的Bot'; }, []) @@ -33,6 +34,11 @@ function Disposition(props){ return e; } + function issueControl(e){ + setIssueSelectValue(e); + return e; + } + // webhooks校验 function checkAddr(rule, value, callback) { let reg = /(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?/; @@ -47,8 +53,8 @@ function Disposition(props){ // 校验仓库访问权限 function jurisDictionPrValidator(rule, value, callback){ - const {juris_diction_code} = getFieldsValue(); - if(juris_diction_code === 2 && value === 2){ + const {juris_diction_code, juris_diction_pr} = getFieldsValue(); + if(juris_diction_code === 2 && value === 2 && juris_diction_pr === 2){ callback('请至少选择一个权限'); return; } @@ -56,21 +62,13 @@ function Disposition(props){ } // 校验用户必选一个订阅事件 - function eventValidator(rule, value, callback, type){ - const {event_code, event_pr} = getFieldsValue(); - const resourceValue = resource[0].value; - const boolPr = (event_code === undefined || (event_code && event_code.length === 0)) && prSelectValue === resourceValue; - const boolCode = (event_pr === undefined || (event_pr && event_pr.length === 0)) && codeSelectValue === resourceValue; - const bool = prSelectValue !== resourceValue && codeSelectValue !== resourceValue && (event_pr === undefined || (event_pr && event_pr.length === 0)) && (event_code === undefined || (event_code && event_code.length === 0)) - if(boolPr || boolCode){ - callback("请至少选择一个订阅"); - return; + function eventValidator(rule, value, callback){ + const {event_code, event_pr, event_issue} = getFieldsValue(); + if(event_code || event_pr || event_issue){ + callback(); + }else{ + callback("请至少选择一个订阅事件"); } - if(bool && type === "pr"){ - callback("请至少选择一个订阅"); - return; - } - callback(); } // 注册新的bot @@ -80,17 +78,19 @@ function Disposition(props){ if(err){ return; } - const {bot_name, bot_des, webhook, is_public, juris_diction_code, juris_diction_pr, event_code, event_pr, oauth_callback_url} = fieldsValue; + const {bot_name, bot_des, webhook, is_public, juris_diction_code, juris_diction_pr, event_code, event_pr, oauth_callback_url, event_issue, juris_diction_issue} = fieldsValue; const param = { 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}`, limit_and_events: { - event_code: event_code ? event_code.toString() : "", - event_pr: event_pr ? event_pr.toString() : "", + event_code: event_code ? event_code.toString() : '', + event_pr: event_pr ? event_pr.toString() : '', + event_issue: event_issue ? event_issue.toString(): '', juris_diction_code, - juris_diction_pr + juris_diction_pr, + juris_diction_issue }, oauth_callback_url } @@ -157,32 +157,61 @@ function Disposition(props){
合并请求的打开、关闭、编辑、分配
- {getFieldDecorator("juris_diction_pr",{rules:[{validator: jurisDictionPrValidator}],initialValue: resource[0].value, getValueFromEvent: prControl})( + {getFieldDecorator("juris_diction_pr",{initialValue: resource[0].value, getValueFromEvent: prControl})( + + )} +
+
+ +
疑修的打开、关闭、编辑、分配、评论
+ {getFieldDecorator("juris_diction_issue",{rules:[{validator: jurisDictionPrValidator}],initialValue: resource[0].value, getValueFromEvent: issueControl})( )}
{(codeSelectValue !== resource[0].value || prSelectValue !== resource[0].value) &&
订阅事件
} - {codeSelectValue !== resource[0].value && + {codeSelectValue !== resource[0].value && {getFieldDecorator("event_code",{ - rules:[{validator: (rule, value, callback)=>{eventValidator(rule, value, callback, "code")}}] + rules:[{validator: (rule, value, callback)=>{eventValidator(rule, value, callback)}}] })( - 推送
git推送到存储库
- 创建
创建分支或标签
- 删除
删除分支或标签
+ + 推送
git推送到存储库
+ 创建
创建分支或标签
+ 删除
删除分支或标签
+
)}
} {(codeSelectValue !== resource[0].value && prSelectValue !== resource[0].value) &&
} {prSelectValue !== resource[0].value && {getFieldDecorator("event_pr",{ - rules:[{validator: (rule, value, callback)=>{eventValidator(rule, value, callback, "pr")}}] + rules:[{validator: (rule, value, callback)=>{eventValidator(rule, value, callback)}}] })( - 合并请求
合并请求被打开、被关闭、被重新打开或被编辑
- 合并请求分配
合并请求被分配或取消分配
+ + 合并请求
合并请求被打开、被关闭或被编辑
+ 合并请求分配
合并请求被分配或取消分配
+ 合并请求被评论
合并请求评论被创建或删除
+
+
+ )} +
} + {(codeSelectValue !== resource[0].value || prSelectValue !== resource[0].value) && issueSelectValue !== resource[0].value &&
} + {issueSelectValue !== resource[0].value && + {getFieldDecorator("event_issue",{ + rules:[{validator: (rule, value, callback)=>{eventValidator(rule, value, callback)}}] + })( + + + 疑修
疑修已打开、已关闭、已重新打开或编辑
+ 疑修已指派
疑修已被指派或取消指派
+ 已标记疑修
疑修标记被更新或清除
+ 疑修评论
疑修评论被创建、编辑或删除
+
)}
} diff --git a/src/forge/SecuritySetting/bot/exploit/new/index.scss b/src/forge/SecuritySetting/bot/exploit/new/index.scss index 010ef40f3..84921de72 100644 --- a/src/forge/SecuritySetting/bot/exploit/new/index.scss +++ b/src/forge/SecuritySetting/bot/exploit/new/index.scss @@ -24,7 +24,7 @@ .width50{ width: 60%; &.checkBox{ - width: 68%; + width: 75%; } .height36{ height: 36px; @@ -50,23 +50,15 @@ margin-bottom: 0; } &.checkBox .ant-checkbox-group{ - display: flex; - flex-wrap: wrap; - justify-content: space-between; + width: 100%; + .ant-row{width: 100%;} .color-99{padding-left: 24px;} } - &.three .ant-checkbox-wrapper{ - width: 34%; - margin-left: 0px; - padding-left: 12px; - &:first-child, &:last-child{ - padding-left: 0px; - } - } } .dashedBor{ border-bottom: 1px dashed #e0e6f5; margin-bottom: 12px; + margin-top: -20px; } .resourceBox + .dashedBor{ margin-top: -15px; diff --git a/src/forge/Settings/Webhooks/New.jsx b/src/forge/Settings/Webhooks/New.jsx index 06d46b6db..1e2d4caae 100644 --- a/src/forge/Settings/Webhooks/New.jsx +++ b/src/forge/Settings/Webhooks/New.jsx @@ -283,16 +283,16 @@ function New({ form , match , showNotification , history, projectDetail }) {
合并请求 - 合并请求被打开、被关闭、被重新打开或被编辑 + 合并请求被打开、被关闭或被编辑 合并请求分配 合并请求被分配或取消分配 - {/* + 合并请求被评论 - 合并请求评论被创建、编辑或删除 - */} + 合并请求评论被创建或删除 + {/* 合并请求收入里程碑 合并请求被记录或取消记录于里程碑中 @@ -310,7 +310,7 @@ function New({ form , match , showNotification , history, projectDetail }) { 合并请求被同步 */}
- {/*

疑修事件

+

疑修事件

疑修 @@ -328,7 +328,7 @@ function New({ form , match , showNotification , history, projectDetail }) { 疑修评论 疑修评论被创建、编辑或删除 -
*/} + { eventFlag && 请选择自定义事件!} diff --git a/src/forge/Wiki/Index.jsx b/src/forge/Wiki/Index.jsx index 96772549e..587060276 100644 --- a/src/forge/Wiki/Index.jsx +++ b/src/forge/Wiki/Index.jsx @@ -14,7 +14,6 @@ import { isArray } from 'lodash'; import { Base64 } from 'js-base64'; import Sidebar from './components/sidebar'; import UploadWiki from './components/uploadWiki'; -const Search = Input.Search; const InputGroup = Input.Group; const { Option } = Select; @@ -31,7 +30,6 @@ export default (props) => { const [fileArr, setFileArr] = useState([]); const [fileList, setFileList] = useState([]); - const [fileListInit, setFileListInit] = useState([]); const [reload, setReload] = useState(); const [urlType, setUrlType] = useState('HTTPS'); @@ -76,11 +74,7 @@ export default (props) => { const {menuList, sidebar} = info; setSidebar(sidebar) setFileList(menuList); - setFileListInit(menuList); let firstWikiNode = undefined; - // if(!pathname.endsWith('/wiki')){ - // firstWikiNode = findNodeByKey(menuList, pathname.split('/').pop()) - // } const arr = pathname.split('/'); if(!pathname.endsWith('/wiki')){ firstWikiNode = findNodeByName(menuList, arr[arr.length-2]); @@ -151,15 +145,6 @@ export default (props) => { } } - function changeSearchValue(value) { - if(value){ - // 过滤不包含wiki的内容, 重新渲染页面 - let filterList = filterTree(fileListInit, value); - setFileList(filterList); - }else{ - setFileList(JSON.parse(JSON.stringify(fileListInit))); - } - } function filterTree(list, value, arr = []){ if(!list.length) return [] @@ -173,15 +158,6 @@ export default (props) => { } } return arr - // return list.filter(item => { - // if (item.title.includes(value)) { - // return true - // } - // // if(item.children && item.children.length > 0){ - // // item.children = findUnHasValue(item.children , value); - // return findUnHasValue(item.children , value); - // // } - // }) } @@ -314,15 +290,9 @@ export default (props) => {
- {/* 读sidebar转换成目录树 */} - +
diff --git a/src/forge/Wiki/Index.scss b/src/forge/Wiki/Index.scss index 974bc4f75..e79567d69 100644 --- a/src/forge/Wiki/Index.scss +++ b/src/forge/Wiki/Index.scss @@ -236,6 +236,9 @@ body { .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 { @@ -411,4 +414,11 @@ body { } .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/api.js b/src/forge/Wiki/api.js index 48fc0d907..6b6ee58a1 100644 --- a/src/forge/Wiki/api.js +++ b/src/forge/Wiki/api.js @@ -111,7 +111,6 @@ export function markdownToTree(markdownText){ let root = { title: "root", children: [], - node: undefined, key: key++ } lines.map((item, index) =>{ @@ -119,18 +118,17 @@ export function markdownToTree(markdownText){ const node = { title: item, children: [], - node: undefined, - key: key++ + key: undefined } // level:空格的个数(层级) const level = item.search(/\S/); - // 下一个节点的空格个数(层级) - const nextNodeLevel = lines[index+1] && lines[index+1].search(/\S/); if(!level){ + node.key = key+""; + key++; // push根目录 - stack = [] - root.children.push(node); + stack = []; stack.push(node); + root.children.push(node); }else{ // 解析子目录 // 当前节点的父节点 @@ -139,12 +137,16 @@ export function markdownToTree(markdownText){ const rootNode = root.children.filter(i => i.key === stack[0].key)[0]; if(level === 1){ // 二级目录 + node.key = `${rootNode.key}-${rootNode.children.length}`; rootNode.children.push(node); }else{ // 更深的层级 找key = stack最后一个item的key const parentNodeByRoot = findNodeByKey(rootNode.children, parentNode.key); + node.key = `${parentNodeByRoot.key}-${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('* '))){ stack.push(node); }else if(nextNodeLevel{ - setSelectedKey(defaultSelectedKeys); + 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(()=>{ @@ -43,7 +60,7 @@ export default function Sidebar(props) { - { updateName(e, item) }}> + { updateName(e, item, 1) }}> 重命名 @@ -57,6 +74,11 @@ export default function Sidebar(props) { const fileMenu = (title, isFile, item) =>{ return + + { updateName(e, item, 2) }}> + 重命名 + + { deleteFileModal(e, title, isFile, item) }}> 删除 @@ -102,6 +124,7 @@ export default function Sidebar(props) { }); }; + // 选中节点 function selectTree(keys,event){ let {title, key} = event.node.props.dataRef; setSelectedKey([key+""]); @@ -113,6 +136,19 @@ export default function Sidebar(props) { } } + // 收起展开 + 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(); @@ -257,14 +293,24 @@ export default function Sidebar(props) { history.push(`/${owner}/${projectsId}/wiki/add?key=${item.key}`) } - // 重命名目录文件 - function updateName(e, item){ + // 重命名目录文件 type:1 目录 2页面 + function updateName(e, item, type){ e.stopPropagation(); setVisible(item); - setIsEditName(true); + setIsEditName(type); let title = item.title.trim(); const isFile = title.startsWith('[[') && title.endsWith(']]'); title = isFile ? title.substring(2,title.length-2) : title.substring(2, title.length); + type === 2 && getWiki({ + owner: owner, + repo: projectsId, + pageName: title, + projectId: project.id + }).then(res => { + if (res && res.message === "200") { + setWikiContent(res.data.md_content); + } + }) setMenuName(title); } @@ -284,34 +330,72 @@ export default function Sidebar(props) { return; } if(!isEditName){ - // 校验同级目录中是否有相同名称 - const {children, node, title, key} = visible; + // 新增目录:校验同级目录中是否有相同名称 + 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) + updateSidebar(treeToMd(newList), 3); }else{ - // 编辑sidebar + // isEditName 1 编辑目录名称 2编辑页面名称 + const titleToJudge = isEditName === 2 ? `[[${menuName}]]` : `- ${menuName}`; const {children, node, title, key} = visible; const list = findNodeByChildrenKey(menuList, key) || []; - if(title.trim() === `- ${menuName}`){ - setAddMenuError("请修改目录名称"); + if(title.trim() === titleToJudge){ + setAddMenuError("请修改名称"); return } - if(list.map(item=>item.title.trim()).includes(`- ${menuName}`)){ + if(list.map(item=>item.title.trim()).includes(titleToJudge)){ message.error("不能与同级目录名称相同"); - }else{ - const newMenuList = JSON.parse(JSON.stringify(menuList)) - loop(newMenuList, key, (item)=>{ - const {title} = item; - item.title = `${title.substring(0, title.indexOf('- '))}- ${menuName}` - }) - updateSidebar(treeToMd(newMenuList), 4) + return } + // wiki名称不可重复 + const sameNameWiki = fileArrInit.filter(item=>item.name === menuName) + if(isEditName === 2 && sameNameWiki.length){ + message.error("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 + const oldTitle = title.substring(title.indexOf('[[')+2, title.indexOf(']]')) + updateWiki({ + owner, + repo: projectsId, + projectId: project.id, + // 原名称 + pageName: oldTitle, + // 现名称 + title: menuName, + content_base64: wikiContent, + commit_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) } } @@ -440,16 +524,99 @@ export default function Sidebar(props) { 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( -
- {menuList && menuList.length ? +
+ + + {/* 目录是否全部展开 */} +
{isExpendedAll ? '收起' : '展开'}所有节点
+ + {/* wiki目录 */} + {menuList && menuList.length ? {renderTreeNodes(menuList)} : ''} {/* 新增子目录弹窗 */} - -
目录名称:
- {setMenuName(e.target.value);setAddMenuError(undefined)}} maxLength={50}> + +
{isEditName === 2 ? '页面' : '目录'}名称:
+ {setMenuName(e.target.value);setAddMenuError(undefined)}} maxLength={50}>
{addMenuError}
diff --git a/src/forge/css/index.scss b/src/forge/css/index.scss index d100cb152..9d36571d6 100644 --- a/src/forge/css/index.scss +++ b/src/forge/css/index.scss @@ -170,10 +170,11 @@ ul,ol,dl{ &.small{ height: 120px; img{ - width:50px; + width:200px; } } img{ + width:300px; margin-bottom: 15px; } } diff --git a/src/forge/topic/index.jsx b/src/forge/topic/index.jsx new file mode 100644 index 000000000..af78de9bb --- /dev/null +++ b/src/forge/topic/index.jsx @@ -0,0 +1,124 @@ +import React, { useState } from 'react'; +import { withRouter } from "react-router"; +import { SnackbarHOC } from "educoder"; +import { CNotificationHOC } from "../../modules/courses/common/CNotificationHOC"; +import { TPMIndexHOC } from "../../modules/tpm/TPMIndexHOC"; +import './index.scss'; +import { useEffect } from 'react'; +import { Pagination, Select } from 'antd'; +import axios from 'axios'; +import { Link } from 'react-router-dom'; +import { getImageUrl } from 'educoder'; +import Nodata from '../Nodata'; +const Option = Select.Option; + +export default withRouter((CNotificationHOC()(SnackbarHOC()(TPMIndexHOC((props) => { + const {match:{params:{name, id}}, current_user:{user_id}} = props; + const [languageList, setLanguageList] = useState(undefined); + const [languageId, setLanguageId] = useState(undefined); + const [sort, setSort] = useState("praises_count"); + const [page, setPage] = useState(1); + const [limit, setLimit] = useState(15); + const [projectsList, setProjectsList] = useState(undefined); + const [total, setTotal] = useState(undefined); + + useEffect(()=>{ + // 设置网页标题 + name && (document.title = `项目标签-${decodeURIComponent(name)}`); + // 获取语言列表 + axios.get(`/project_languages.json`).then(result=>{ + result && setLanguageList(result.data.project_languages) + }) + }, []) + + useEffect(()=>{ + axios.get(`/projects.json`, { + params: { + user_id: user_id, + page, + limit, + sort_by: sort, + language_id:languageId? languageId : undefined, + topic_id:id + } + }).then((result) => { + if (result) { + setProjectsList(result.data.projects); + setTotal(result.data.total_count); + } + }).catch((error) => { + console.log('error', error); + }) + }, [sort, page, languageId]) + + return ( +
+

#{decodeURIComponent(name)}

+
+ 平台共{total}个公开项目与此标签相匹配 +
+ + +
+
+ {/* 无数据 */} + {projectsList && projectsList.length === 0 &&
} + {/* 项目列表 */} + {projectsList && projectsList.length>0 && projectsList.map(i=>{ + return
+ {/* 仓库拥有者头像 */} + +
+
+ {/* 仓库拥有者名称 */} + {i.author && i.author.name}/{i.name} + {/* 点赞数 */} + {i.praises_count > 0 ? {i.praises_count} :"" } + {/* fork数 */} + {i.forked_count > 0 ? {i.forked_count}:""} +
+ {/* 仓库描述 */} +
{i.description}
+ {/* 项目标签 */} + {i.topics &&
+ {i.topics.map(item=>{item.name})} +
} +
+ {/* 仓库类别 */} + {i.category && i.category.id ? {i.category.name} :"" } + {/* 仓库语言 */} + {i.language && i.language.id ? {i.language.name} :""} + {/* 更新时间 */} + 更新于{i.time_ago} +
+
+
+ })} + {/* 分页 */} + {total > limit && limit} + onChange={(page) => { setPage(page) }} + current={page} + total={total} + showTotal={total => `共 ${total} 条`} + />} +
+ )} +))))) \ No newline at end of file diff --git a/src/forge/topic/index.scss b/src/forge/topic/index.scss new file mode 100644 index 000000000..1fc7f1748 --- /dev/null +++ b/src/forge/topic/index.scss @@ -0,0 +1,99 @@ +.topicRepoList{ + width: 900px; + margin: 0 auto; + min-height: 700px; + .flexBoxTopic{ + display: flex; + align-items: center; + justify-content: space-between; + } + &>h2{ + font-weight: 700; + } + .topicRepoHead{ + color:#5f6872; + padding: 10px 15px; + border-bottom:1px solid rgba(153, 153, 153, 0.16); + } + .repoImg{ + width:32px; + height: 32px; + object-fit: cover; + border-radius: 50%; + } + .repoTopic{ + align-items: flex-start; + border-bottom:1px solid rgba(153, 153, 153, 0.1); + justify-content: flex-start; + padding: 20px 0 15px; + .rightInfo{ + flex: 1; + } + .repoTitle{ + flex: 1; + } + } + .descTopic{ + color:#414141; + line-height: 24px; + } + .proListTopics{ + display: flex; + flex-wrap: wrap; + .proListTopic{ + background-color:#f3f8ff; + border-radius: 4px; + max-width: 158px; + padding: 0 10px; + height: 24px; + line-height: 24px; + color: #4c5b76; + margin-bottom: 5px !important; + } + } + .info3{ + justify-content: flex-start; + color: #7D7D7D; + height: 20px; + line-height: 20px; + display: flex; + align-items: center; + .category{ + position: relative; + padding-left: 11px; + margin-right: 10px; + &::before{ + position: absolute; + content: ""; + width: 6px; + height: 6px; + border-radius: 50%; + background-color: #466AFF; + top: 7px; + left: 0px; + } + } + .language{ + position: relative; + &.hasCate{ + padding-left: 11px; + } + &.hasCate::before{ + position: absolute; + content: ""; + width: 1px; + height: 10px; + border-radius: 50%; + border-left: 1px solid #9e9e9e; + bottom: 5px; + left: 0px; + } + } + } + .pagTopic{ + text-align: center; + } + .repoByTopicNodata .none_panels{ + min-height: 520px; + } +} \ No newline at end of file diff --git a/src/forge/users/GeneralView/ConcentrateProject.jsx b/src/forge/users/GeneralView/ConcentrateProject.jsx index 2dde1cca0..9fe436ead 100644 --- a/src/forge/users/GeneralView/ConcentrateProject.jsx +++ b/src/forge/users/GeneralView/ConcentrateProject.jsx @@ -72,7 +72,7 @@ function ConcentrateProject({userLogin,current,showCompeleteDialog,completeProfi

{i.description}

{/* 项目标签 */} {i.topics &&
- {i.topics.map(item=>{item.name})} + {i.topics.map(item=>{item.name})}
} { i.category && {i.category.name} } diff --git a/src/modules/loginRegister/Register.jsx b/src/modules/loginRegister/Register.jsx index 6083fe6f6..970786243 100644 --- a/src/modules/loginRegister/Register.jsx +++ b/src/modules/loginRegister/Register.jsx @@ -41,7 +41,7 @@ function Register(props){ if (!err) { values.agreement && axios.post(`/accounts/register.json`, { login: values.email, - namespace: values.register_username, + namespace: values.register_username.trim(), password: values.register_psd, password_confirmation: values.psdComfirm, code: values.captcha @@ -53,7 +53,7 @@ function Register(props){ setEmailStr(values.email); }else if(response.data && response.data.status === 0){ //注册成功,页面跳转即可,注册后登录流程forge处理了 - window.location.href = "/"+values.register_username; + window.location.href = "/"+values.register_username.trim(); } else { setEmailStr(values.email); setMess(response.data.message); @@ -67,11 +67,11 @@ function Register(props){ function usernameConfirm(rule, value, callback){ setUserNameGo(true); value && (userNameGo || value !== loginStr) ? axios.post(`/accounts/check.json`, { - value: value, + value: value.trim(), type: 1 }).then(response => { if (response.data.status === -1) { - callback('该名称已经被使用'); + callback(response.data.message); } else { setLoginStr(value); setUserNameGo(false); @@ -238,6 +238,9 @@ function Register(props){ {getFieldDecorator('register_username',{ rules:[ + { + transform: (value)=>{return value.trim()} + }, { required:true, message:"请输入用户名" @@ -250,6 +253,10 @@ function Register(props){ pattern: /[a-zA-Z0-9]$/, message: "用户名只能使用英文字母和数字" }, + { + pattern: /^[^\s]*$/, + message: "用户名不能包含空格" + }, { min: 4, max: 15, diff --git a/src/softbot/softBotDetail/index.jsx b/src/softbot/softBotDetail/index.jsx index ccf383f7c..3aa711067 100644 --- a/src/softbot/softBotDetail/index.jsx +++ b/src/softbot/softBotDetail/index.jsx @@ -221,7 +221,8 @@ function SoftBotDEtail(props){
权限信息
{detail &&
代码库{detail.limit_and_events.juris_diction_code === 2 ? '无' : detail.limit_and_events.juris_diction_code === 1 ? '读写' : '只读'}权限
} - {detail &&
合并请求(PR){detail.limit_and_events.juris_diction_pr === 2 ? '无' : detail.limit_and_events.juris_diction_pr === 1 ? '读写' : '只读'}权限
} + {detail &&
合并请求(PR){detail.limit_and_events.juris_diction_pr === 2 ? '无' : detail.limit_and_events.juris_diction_pr === 1 ? '读写' : '只读'}权限
} + {detail &&
疑修(Issue){detail.limit_and_events.juris_diction_issue === 2 ? '无' : detail.limit_and_events.juris_diction_issue === 1 ? '读写' : '只读'}权限
}
diff --git a/src/softbot/softBotList/index.scss b/src/softbot/softBotList/index.scss index 631aaa6cf..53d6a0720 100644 --- a/src/softbot/softBotList/index.scss +++ b/src/softbot/softBotList/index.scss @@ -132,7 +132,7 @@ flex-wrap: wrap; justify-content: flex-start; &>.none_panels{ - margin: 0 auto; + margin: 125px auto; } .botLoading{ margin: 200px auto;