Merge commit 'f469c5480612039ef24f65313f2995e0749fccc8' into pre_gitlink_ssr

This commit is contained in:
黄心宇 2024-05-11 15:11:45 +08:00
commit 03e7ab5b3e
37 changed files with 417 additions and 238 deletions

View File

@ -1,6 +1,6 @@
// import app from './src/App'
import app from './src/App'
// import users from './src/forge/users/Index';
// import Detail from "./src/forge/Main/Detail";
import Detail from "./src/forge/Main/Detail";
// import Information from "./src/forge/Information/index"
// import NewsDetail from './src/forge/Information/Pages/newsDetail';
@ -18,7 +18,7 @@ export default [
{
key:"detail",
path: "/:owner/:projectsId",
// component: app,
component: app,
}
];
@ -26,21 +26,31 @@ export const deepRoutes = [
{
key: "zoneNews",
path: "/zone/:deptId/newdetail/:id",
onlyHead: true,
// component: NewsDetail
},
{
key: "zone",
path: "/zone/:deptId",
onlyHead: true,
// component: Information
},
{
key: "detailBranch",
path: "/:owner/:projectsId/tree/:branchName",
onlyHead: false,
component: Detail,
},
{
key: "detail",
path: "/:owner/:projectsId",
// component: Detail,
onlyHead: false,
component: Detail,
},
{
key: "owner",
path: "/:owner",
onlyHead: true,
// component: Detail,
}
]

View File

@ -159,12 +159,13 @@ const serverConfig = {
{
test: /\.scss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath,
},
},
'isomorphic-style-loader',
// {
// loader: MiniCssExtractPlugin.loader,
// options: {
// publicPath,
// },
// },
{
loader: require.resolve("css-loader"),
options: {

35
package-lock.json generated
View File

@ -11456,7 +11456,7 @@
},
"isomorphic-style-loader": {
"version": "5.3.2",
"resolved": "https://registry.npmmirror.com/isomorphic-style-loader/-/isomorphic-style-loader-5.3.2.tgz",
"resolved": "https://registry.npmjs.org/isomorphic-style-loader/-/isomorphic-style-loader-5.3.2.tgz",
"integrity": "sha512-5mwHrN2xK5zsKBxSUYF7iDhoU9Kpcpfgn0lFOP0SKk3aKwkl26zi6kh+KDrekjlLzNbYsFnn8o1yWaB3OflVXQ==",
"requires": {
"hoist-non-react-statics": "^3.0.0",
@ -14459,23 +14459,11 @@
"pinkie": "^2.0.0"
}
},
"pinyin": {
"version": "4.0.0-alpha.0",
"resolved": "https://registry.npmmirror.com/pinyin/-/pinyin-4.0.0-alpha.0.tgz",
"integrity": "sha512-hdxO/2RuLWarhLaIcGKdXjkFZ0hFr3W/Yrvrd0YPxuIafixMZSAa4jvxskYLgYHE8OkOXkGsxMsEScwGzqKe3g==",
"requires": {
"commander": "~1.1.1"
},
"dependencies": {
"commander": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/commander/-/commander-1.1.1.tgz",
"integrity": "sha512-71Rod2AhcH3JhkBikVpNd0pA+fWsmAaVoti6OR38T76chA7vE3pSerS0Jor4wDw+tOueD2zLVvFOw5H0Rcj7rA==",
"requires": {
"keypress": "0.1.x"
}
}
}
"pirates": {
"version": "4.0.6",
"resolved": "https://registry.npmmirror.com/pirates/-/pirates-4.0.6.tgz",
"integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
"dev": true
},
"pkg-dir": {
"version": "3.0.0",
@ -21600,17 +21588,26 @@
"version": "2.1.1",
"resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true
"dev": true,
"optional": true
},
"is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"optional": true,
"requires": {
"is-extglob": "^2.1.1"
}
},
"normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true,
"optional": true
},
"normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz",

View File

@ -159,6 +159,7 @@
"analyz": "NODE_ENV=production npm_config_report=true npm run build",
"dev": "npm-run-all --parallel dev:**",
"dev:server": "cross-env NODE_ENV=dev nodemon --exec babel-node \"./buildserver/bundle.js\" --watch config --watch server",
"test:build:server": "cross-env NODE_ENV=localtest webpack --config config/webpack.server.js",
"dev:build:server": "cross-env NODE_ENV=dev webpack --config config/webpack.server.js",
"pm2": "pm2 start pm2.json"
},

1
public/css/detail.css Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -15,7 +15,7 @@ app.use('/build', express.static('build'));
const options = (target) => {
return {
target: target,
changeOrigin: false, // 允许在请求头中更改主机
changeOrigin: true, // 允许在请求头中更改主机
timeout: 30000,
onProxyRes: (proxyRes, req, res) => {
// 代理成功后输出日志
@ -29,25 +29,27 @@ const options = (target) => {
};
}
const proxy = createProxyMiddleware(options(`http://localhost:${ rubyPort }`));
// const proxy = createProxyMiddleware(options(url));
let proxy
if (process.env.NODE_ENV === 'dev') {
// 本地调试用设置代理
proxy = createProxyMiddleware(options(url));
app.use(['/api/zone', '/api/cms'], (req, res) => {
const proxy = createProxyMiddleware(options(zoneUrl));
proxy(req, res);
});
app.use(['/api', '/images', '/system', '/favicon', '/robots.txt', '/sitemap*.xml', '/sitemap*.txt', '/favicon.ico', '/admins', '/assets', '/attachments', '/oauth'], (req, res) => {
proxy(req, res);
});
} else {
proxy = createProxyMiddleware(options(`http://localhost:${ rubyPort }`));
// 设置代理
app.use(['/api', '/admins', '/attachments', '/oauth', '/competitions', '/projects', '/auth'], (req, res) => {
proxy(req, res);
});
}
// 本地调试用设置代理
// app.use(['/api/zone', '/api/cms'], (req, res) => {
// const proxy = createProxyMiddleware(options(zoneUrl));
// proxy(req, res);
// });
// 本地调试用设置代理
// app.use(['/api', '/images', '/system', '/favicon', '/robots.txt', '/sitemap*.xml', '/sitemap*.txt', '/favicon.ico', '/admins', '/assets', '/attachments', '/oauth'], (req, res) => {
// proxy(req, res);
// });
// 设置代理
app.use(['/api', '/admins', '/attachments', '/oauth', '/competitions'], (req, res) => {
proxy(req, res);
});
// 匹配路径匹配到的返回处理后的html没匹配到转发到后端
app.get('*',async function (req,res) {

View File

@ -1,28 +1,30 @@
// import React from "react";
// import {renderToString} from "react-dom/server";
import React from "react";
import {renderToString} from "react-dom/server";
import {StaticRouter, matchPath} from "react-router-dom";
// import {renderRoutes} from "react-router-config";
import StyleContext from 'isomorphic-style-loader/StyleContext'
import {renderRoutes} from "react-router-config";
import Routes, { deepRoutes, specialRoute } from "../Routes";
// import { Provider } from "react-redux";
// import { serverStore } from "../src/redux/stores/configureStore";
// import App from "../src/App";
// import { Route } from 'react-router-dom';
// import { setDefaultMeta } from '../src/common/UrlTool'
import { Provider } from "react-redux";
import { serverStore } from "../src/redux/stores/configureStore";
import { setDefaultMeta } from '../src/common/UrlTool'
import Logger from "./log";
import { changeHead } from './setHead'
// 目前只用项目详情页的,不需要动态添加
const css = new Set() // 这个必须在路由函数里面在外面的话就会累加出现之前的css
const insertCss = (...styles) => styles.forEach(style => css.add(style._getCss()));
export const render = async (req,res)=>{
let _route = null,
_match = null;
// Logger.info(`${req.url} UA:${req.headers['user-agent']}`)
Logger.info(`${req.url} UA:${req.headers['user-agent']}`)
// let store = serverStore();
let store = serverStore();
function matchSubRoutes(routes, url) {
for (const route of routes) {
// 检查当前路由是否匹配
let match = matchPath(url, route.path);
let match = matchPath(url, { path: route.path, exact: true });
if (match) {
// 如果当前路由匹配,并且它有子路由,递归检查子路由
if (route.routes) {
@ -50,59 +52,84 @@ export const render = async (req,res)=>{
code: 200,
};
if (_route && _route.component && _route.component.preFetch) {
// 设置服务端请求cookie
let cookies = req.headers.cookie.split(';')
for (let index = 0; index < cookies.length; index++) {
const element = cookies[index];
domObj.window.document.cookie = element
}
context = await _route.component.preFetch({
store,
match: _match,
query: req.path,
});
// 当前只做了项目详情页,没获取到数据使用原来的
if(!store.getState().projectReducer.projectBase.id) {
return false
}
}
let content = ''
// 匹配路由、并且不是特定一级路由、且不是只动态修改head的在服务端渲染页面
if (_route && !specialRoute.includes(_match.url && _match.url.replace('/', '')) && !_route.onlyHead) {
content = renderToString((
<Provider store={store}>
<StaticRouter location={req.path} context={context}>
<StyleContext.Provider value={{insertCss}}>
{renderRoutes(Routes)}
{/* <Route path='/:owner/:projectsId' exact component={App}></Route> */}
</StyleContext.Provider>
</StaticRouter>
</Provider>
))
}
if (!content) {
// 未匹配到详情页恢复默认header
setDefaultMeta()
} else {
// 匹配到服务端渲染页面注入state
let script = domObj.window.document.getElementById('initState')
if (!script) {
script = domObj.window.document.createElement('script')
script.setAttribute('id', 'initState')
}
script.textContent = `window.__initState__ = ${ JSON.stringify(store.getState()) }`
// 目前直接把详情页涉及到的css抽出来塞到返回的html里没有使用insertCss
const styleNode = domObj.window.document.createElement('style')
styleNode.type = 'text/css'
// styleNode.innerHTML = `${ [...css].join('') }`
styleNode.innerHTML = detailCss
domObj.window.document.head.appendChild(script);
domObj.window.document.head.appendChild(styleNode);
}
if (_route && !specialRoute.includes(_match.url && _match.url.replace('/', ''))) {
Logger.info(`matchRoot: ${ _match.url }`)
await changeHead(_route, _match.params)
// 匹配上的并且只修改head的页面
if (_route.onlyHead) {
Logger.info(`matchRoot: ${ _match.url }`)
await changeHead(_route, _match.params)
}
} else {
return false
}
// if (_route && _route.component && _route.component.preFetch) {
// context = await _route.component.preFetch({
// store,
// match: _match,
// query: req.path,
// });
// }
// 暂时不开放ssr只修改head
// const content = renderToString((
// <Provider store={store}>
// <StaticRouter location={req.path} context={context}>
// {renderRoutes(Routes)}
// {/* <Route path='/:owner/:projectsId' exact component={App}></Route> */}
// </StaticRouter>
// </Provider>
// ))
// 暂时不开放ssr只修改head
// if (!content) {
// // 未匹配到详情页恢复默认header
// setDefaultMeta()
// } else {
// // 匹配到服务端渲染页面注入state
// let script = domObj.window.document.getElementById('initState')
// if (!script) {
// script = domObj.window.document.createElement('script')
// script.setAttribute('id', 'initState')
// }
// script.textContent = `window.__initState__ = ${ JSON.stringify(store.getState()) }`
// domObj.window.document.head.appendChild(script);
// }
let html = domObj.serialize()
const prepHTML=(data,rootString)=>{
// 暂时不开放ssr只修改head
// data=data.replace('<div id="root" class="page -layout-v -fit widthunit"></div>',`<div id="root" class="page -layout-v -fit widthunit">${rootString}</div>`);
data=data.replace('<div id="root" class="page -layout-v -fit widthunit"></div>',`<div id="root" class="page -layout-v -fit widthunit">${rootString}</div>`);
return data;
}
// 暂时不开放ssr只修改head
// res.send(prepHTML(html, content))
res.send(prepHTML(html, ''))
res.send(prepHTML(html, content))
// store = null
store = null
}

View File

@ -6,15 +6,16 @@ export const changeHead = async ({ key }, params) => {
let res
let data
switch (key) {
case 'detail':
res = await getProjectDetailFunc(params.owner, params.projectsId);
data = res.data
if (data && data.status === 1) {
let keyWords=`${params.owner},${params.projectsId}${ data.topics.length > 0 && ','}${ data.topics.length > 0 && data.topics.map(item => item.name).join(',') }`;
let title= `${params.owner}/${params.projectsId}${data.description?''+data.description:''}`;
setSeoMeta(keyWords,title,data.description || '',`/${params.owner}/${params.projectsId}`,params.owner,params.projectsId);
}
break;
// 详情页不再修改head直接ssr渲染页面
// case 'detail':
// res = await getProjectDetailFunc(params.owner, params.projectsId);
// data = res.data
// if (data && data.status === 1) {
// let keyWords=`${params.owner},${params.projectsId}${ data.topics.length > 0 && ','}${ data.topics.length > 0 && data.topics.map(item => item.name).join(',') }`;
// let title= `${params.owner}/${params.projectsId}${data.description?''+data.description:''}`;
// setSeoMeta(keyWords,title,data.description || '',`/${params.owner}/${params.projectsId}`,params.owner,params.projectsId);
// }
// break;
case 'zoneNews':
res = await getNewsDetail(params.id)
data = res.data.data
@ -33,7 +34,7 @@ export const changeHead = async ({ key }, params) => {
res = await getOwnerInfo(params.owner);
data = res.data
const name = data.nickname || data.username
if (data && data.status === 1) {
if (data && data.name) {
setSeoMeta(`${name}`, name, data.description || '', `/${params.owner}`)
}
break;

View File

@ -1,11 +1,12 @@
const jsdom = require("jsdom");
import fs from 'fs'
import path from 'path';
import axios from "axios";
import { getSetting } from '../src/services/project'
const { JSDOM } = jsdom;
const html = fs.readFileSync(path.join(path.resolve('./build'),'index.html'),'utf-8');
// 压缩的项目详情页需要的css
const css = fs.readFileSync(path.join(path.resolve('./build/css'),'detail.css'),'utf-8');
const { window } = new JSDOM(html);
const $ = require( "jquery" )( window )
@ -13,20 +14,20 @@ const $ = require( "jquery" )( window )
global.window = window;
global.document = window.document;
global.navigator = window.navigator;
global.localStorage = {}
global.localStorage = {};
global.defaultSetting = {}
// 全局缓存css
global.detailCss = css
const setGlobalSetting = async () => {
const setting = await getSetting()
if (setting.data) {
global.defaultSetting = setting.data.setting
}
}
setGlobalSetting()
export const getDomObj = () => {
return new JSDOM(html)
}
// axios.get('https://gw.alipayobjects.com/os/lib/alipay/alex/2.0.19/bundle/alex.all.global.min.js')
// .then(response => {
// // 处理响应
// if (response) {
// // 这里你可以直接使用response.data而不是eval
// eval(response);
// }
// })
// .catch(error => {
// console.error('Error fetching data:', error);
// });

View File

@ -23,7 +23,9 @@ import SiderBarHelp from './glcc/siderBarHelp';
import { SnackbarHOC, configShareForCustom } from 'educoder';
import { initAxiosInterceptors } from './AppConfig'
import cookie from 'react-cookies';
import InfosIndex from './forge/users/Index'
import OrganizeIndex from './forge/Team/Index'
import { connect } from 'react-redux';
window.marked = marked;
const theme = createMuiTheme({
@ -75,15 +77,15 @@ const http500 = Loadable({
loader: () => import('./modules/500/http500'),
loading: Loading,
})
const InfosIndex = Loadable({
loader: () => import('./forge/users/Index'),
loading: Loading,
})
// const InfosIndex = Loadable({
// loader: () => import('./forge/users/Index'),
// loading: Loading,
// })
// 组织
const OrganizeIndex = Loadable({
loader: () => import('./forge/Team/Index'),
loading: Loading,
})
// const OrganizeIndex = Loadable({
// loader: () => import('./forge/Team/Index'),
// loading: Loading,
// })
const Search = Loadable({
loader: () => import('./modules/search/'),
@ -169,7 +171,7 @@ class App extends Component {
mydisplay: false,
occupation: 0,
mygetHelmetapi: null,
pathType: null,
pathType: props.projectBase && props.projectBase.author && props.projectBase.author.type,
pathName: null,
}
}
@ -651,5 +653,18 @@ moment.defineLocale('zh-cn', {
doy: 4 // The week that contains Jan 4th is the first week of the year.
}
});
export default SnackbarHOC()(App);
const mapStateToProps = (state) => {
const {
projectBase,
} = state.projectReducer;
return {
projectBase,
};
}
export default connect(
mapStateToProps,
)(SnackbarHOC()(App));

View File

@ -4,7 +4,6 @@ import {Input} from "antd";
import { url as ssrOrigin, host as ssrHost } from '../ssrUrl'
const { Search } = Input;
if (!__SERVER__) {
const $ = window.$;
}

View File

@ -9,7 +9,7 @@
import './index.scss';
import React, { useState } from 'react';
import { Form, Button, Input } from 'antd';
import QuillForEditor from '../../quillForEditor';
// import QuillForEditor from '../../quillForEditor';
const FormItem = Form.Item;
function CommentForm(props) {
@ -116,7 +116,7 @@ function CommentForm(props) {
)
}
<QuillForEditor
{/* <QuillForEditor
imgAttrs={{ width: '60px', height: '30px' }}
wrapStyle={{
height: showQuill ? 'auto' : '0px',
@ -131,7 +131,7 @@ function CommentForm(props) {
value={ctx}
showUploadImage={handleShowImage}
onContentChange={handleContentChange}
/>
/> */}
</FormItem>
<FormItem style={{ textAlign: 'right', display: showQuill ? 'block' : 'none' }}>
<Button onClick={handleCancle}>取消</Button>

View File

@ -68,7 +68,7 @@ export { default as ActionBtn } from './course/ActionBtn'
export { default as MarkdownToHtml } from './components/markdown/MarkdownToHtml'
export { default as QuillForEditor } from './quillForEditor'
// export { default as QuillForEditor } from './quillForEditor'
export { default as Clappr } from './components/media/Clappr'
export { default as AliyunUploader } from './components/media/AliyunUploader'

View File

@ -1,7 +1,7 @@
import './index.scss'
import 'quill/dist/quill.core.css' // 核心样式
import 'quill/dist/quill.snow.css' // 有工具栏
import 'quill/dist/quill.bubble.css' // 无工具栏
// import 'quill/dist/quill.core.css' // 核心样式
// import 'quill/dist/quill.snow.css' // 有工具栏
// import 'quill/dist/quill.bubble.css' // 无工具栏
import './font.css'
import React, { useState, useRef, useEffect } from 'react'
import Quill from 'quill'

View File

@ -123,24 +123,26 @@
text-align: center;
}
.urlMenu{
line-height: 30px;
margin-bottom: 10px;
padding:15px 20px 0px 20px;
border-bottom: none;
li.ant-menu-item{
height: 30px;
.downMenu {
.urlMenu{
line-height: 30px;
padding:0px 5px;
margin-right: 20px!important;
&.ant-menu-item-selected,&.ant-menu-item-active{
color: #333;
}
&.ant-menu-item-selected{
border-color:#466aff!important;
}
&.ant-menu-item-active{
border-color:transparent ;
margin-bottom: 10px;
padding:15px 20px 0px 20px;
border-bottom: none;
li.ant-menu-item{
height: 30px;
line-height: 30px;
padding:0px 5px;
margin-right: 20px!important;
&.ant-menu-item-selected,&.ant-menu-item-active{
color: #333;
}
&.ant-menu-item-selected{
border-color:#466aff!important;
}
&.ant-menu-item-active{
border-color:transparent ;
}
}
}
}

View File

@ -22,15 +22,15 @@ export default ({ url , name , column , id , login })=>{
return(
id?
<Link to={`/${login}`}>
<Img>
<span className="User">
{ url && <img src={url} alt=""/> }
<span>{name}</span>
</Img>
</span>
</Link>
:
<Img>
<span className="User">
{ url && <img src={url} alt=""/> }
<span>{name}</span>
</Img>
</span>
)
}

View File

@ -2,11 +2,11 @@ import React, { useEffect , useState } from 'react';
import './header.scss';
function Footer(){
const [ value , setValue ] = useState(undefined);
const [ value , setValue ] = useState(__CLIENT__ ? undefined : defaultSetting && defaultSetting.footer);
useEffect(()=>{
try {
var chromesettingArray = JSON.parse(localStorage.getItem('chromesetting'));
let chromesettingArray = JSON.parse(localStorage.getItem('chromesetting'));
setValue(chromesettingArray.footer);
} catch (e) {
}

View File

@ -13,7 +13,7 @@ import axios from 'axios';
import Path from './CoderDepotPath';
import Catalogue from './CoderDepotCatalogue';
import ReadMe from './CoderDepotReadme';
import CoderRootFileDetail from './CoderRootFileDetail';
// import CoderRootFileDetail from './CoderRootFileDetail';
import './Index.scss';
import Releases from '../Component/Releases';
import Contributors from '../Component/Contributors';
@ -24,6 +24,14 @@ import Nodata from '../Nodata';
import Invite from './sub/Invite';
import CheckProfile from '../Component/ProfileModal/Profile';
import RenderHtml from '../../components/render-html';
import Loadable from 'react-loadable';
import Loading from '../../Loading';
const CoderRootFileDetail = Loadable({
loader: () => import("./CoderRootFileDetail"),
loading: Loading,
});
/**
* projectDetail.type:0是托管项目1是镜像项目2是同步镜像项目(为2时不支持在线创建在线上传在线修改在线删除创建合并请求等功能)
*/
@ -40,7 +48,7 @@ function CoderDepot(props){
const [ lastCommitAuthor,setLastCommitAuthor ] = useState((projectEntries.last_commit && projectEntries.last_commit.author) || undefined);
const [ type ,setType ] = useState('dir');
const [ hide , setHide ] = useState(true);
const [ hideBtn , setHideBtn ] = useState(false);
const [ hideBtn , setHideBtn ] = useState(true);
const [ commitCount ,setCommitCount ] = useState(projectEntries.commits_count || 0);
const [ dirInfo ,setDirInfo ] = useState(projectEntries.entries || undefined);//
const [ fileInfo ,setFileInfo ] = useState(undefined);//
@ -222,7 +230,7 @@ function CoderDepot(props){
let ele = document.getElementById("ptxt");
if(ele){
let h = ele.offsetHeight;
if( h > 36 ) setHideBtn(true);
if( h <= 36 ) setHideBtn(false);
}
}
},[projectDetail,lastCommit])
@ -402,7 +410,7 @@ function CoderDepot(props){
const baseOperate = projectDetail && projectDetail.permission && projectDetail.permission !=="Reporter" && projectDetail.type !==2 && pullsFlag && props.platform;
const fileOperate = type === "dir" && projectDetail && projectDetail.type !==2 && ((projectDetail.permission && projectDetail.permission !=="Reporter") || (current_user && current_user.admin));
return(
<WhiteBack>
<div className='WhiteBack'>
{mirror_status ===0 && <UpdateDescModal desc={desc} website={website} lesson_url={lesson_url} topicsInfo={topics} visible={openModal} onCancel={()=>setOpenModal(false)} onOk={okUpdate}/> }
<Spin spinning={isSpin}>
{
@ -427,11 +435,11 @@ function CoderDepot(props){
<div style={{minHeight:"500px"}}>
{
projectDetail &&
<Box className="Panels">
<LongWidth>
<div className="Panels Box">
<div className="LongWidth">
<div className="panelmenu">
<FlexAJ>
<AlignCenter>
<div className="FlexAJ">
<div className="AlignCenter">
<div className="mr30">
{
props && (props.platform && mirror_status ===0) ?
@ -459,7 +467,7 @@ function CoderDepot(props){
/>
:
<React.Fragment>
<AlignCenter className="mr20">
<div className="mr20 AlignCenter">
{
props.platform ?
<Link to={`/${owner}/${projectsId}/branches`} className="iconBtn">
@ -474,8 +482,8 @@ function CoderDepot(props){
<span className="ml3">{projectDetail && projectDetail.branches_count}</span>
</span>
}
</AlignCenter>
<AlignCenter className="mr20">
</div>
<div className="mr20 AlignCenter">
{
props.platform ?
<Link to={`/${owner}/${projectsId}/tags`} className="iconBtn">
@ -489,11 +497,11 @@ function CoderDepot(props){
<span className="ml3">{projectDetail && projectDetail.tags_count}</span>
</span>
}
</AlignCenter>
</div>
</React.Fragment>
}
</AlignCenter>
<AlignCenter className="depotBtn">
</div>
<div className="depotBtn AlignCenter">
<div className="addOptionBtn">
{
baseOperate &&
@ -525,8 +533,8 @@ function CoderDepot(props){
<Dropdown overlay={downloadMenu} placement="bottomRight" trigger={['click']}>
<Button type={'primary'}>下载 <i className="iconfont icon-sanjiaoxing-down ml3 font-14 color-white mr-3"></i></Button>
</Dropdown>
</AlignCenter>
</FlexAJ>
</div>
</div>
{
(dirInfo && dirInfo.length>0) || fileInfo ?
<div className="listtable">
@ -543,7 +551,7 @@ function CoderDepot(props){
<pre id="ptxt"><Link to={`/${owner}/${projectsId}/commits/${truncateCommitId(lastCommit.sha)}`}><RenderHtml value={lastCommit.message}/></Link></pre>
</div>
}
{ hideBtn && <span className="ellipsis" onClick={()=>changeHide(hide)}><i className="iconfont icon-shenglvehao"></i></span> }
{ hideBtn && __CLIENT__ && <div className="ellipsis" onClick={()=>changeHide(hide)}><i className="iconfont icon-shenglvehao"></i></div> }
<span className="ml20 color-grey-6 font-12 mt3">{lastCommit.time_from_now}</span>
{
@ -571,7 +579,7 @@ function CoderDepot(props){
})
}
{
fileInfo &&
fileInfo && __CLIENT__ &&
<CoderRootFileDetail
{...props}
detail={fileInfo}
@ -594,18 +602,18 @@ function CoderDepot(props){
{/* readme文件显示(显示文件详情时不显示readme文件) */}
{ (readme && readme.replace_content) ? <ReadMe ChangeFile={ChangeFile} readme={readme} operate={props && (props.isManager || props.isDeveloper) && projectDetail.type !==2 } history={props.history} /> :"" }
</div>
</LongWidth>
</div>
{
(!(treeValuePath && treeValuePath.length > 0) && !fileInfo ) &&
<ShortWidth>
<Gap style={{paddingLeft:"30px"}}>
<div className="ShortWidth">
<div className="Gap" style={{paddingLeft:"30px"}}>
<div className="panelmenu">
<FlexAJ className="font-18 color-ooo mb20" style={{lineHeight:"28px"}}>关于
<div className="font-18 color-ooo mb20 FlexAJ" style={{lineHeight:"28px"}}>关于
{
projectDetail.permission && (projectDetail.permission==="Admin" || projectDetail.permission==="Owner" || projectDetail.permission==="Manager") &&
<i onClick={()=>setOpenModal(true)} className="iconfont icon-a-shezhi color-grey-9 font-15 pointer"></i>
}
</FlexAJ>
</div>
{desc && <p className="font-14 color-grey-3 mb15 task-hide-2" style={{lineHeight:"24px",WebkitLineClamp:"4",textAlign:"justify",wordBreak:"break-all"}} title={desc}>{desc}</p>}
{/* 项目标签 */}
{topics && <div className='projectHomeTopics'>
@ -667,14 +675,14 @@ function CoderDepot(props){
{mirror_status ===0 && <Contributors owner={owner} projectsId={projectsId} /> }
{/* 语言 */}
{ mirror_status ===0 && <LanguagePower owner={owner} projectsId={projectsId}/> }
</Gap>
</ShortWidth>
</div>
</div>
}
</Box>
</div>
}
</div>
</Spin>
</WhiteBack>
</div>
)
}

View File

@ -43,7 +43,7 @@ function CoderDepotReadme({ operate , history , readme , ChangeFile }){
<div className="commonBox readBox" id="readme">
<Anchor offsetTop={58}>
<div className="commonBox-title boxTitle">
<AlignCenter>
<div className="AlignCenter">
<Dropdown overlay={menu()} trigger={['hover']} overlayClassName="menuslist">
<span className="catelogue">
<i className="iconfont icon-muluicon font-12 mr5"></i>
@ -53,7 +53,7 @@ function CoderDepotReadme({ operate , history , readme , ChangeFile }){
<span className="commonBox-title-read"><a href="#readme ">{readme.name}</a></span>
</AlignCenter>
</div>
{
operate ?
<a className="ml20 pull-right" onClick={() =>ChangeFile(readme && readme.path, false)}>

View File

@ -8,6 +8,7 @@ import ReadmeCatelogue from "./sub/ReadmeCatelogue";
import { Base64 } from "js-base64";
import ReplaceModal from "./Modal/ReplaceModal";
import {returnbar , turnbar} from 'educoder';
import 'codemirror/lib/codemirror.css';
const $ = window.$;
function bytesToSize(bytes) {

View File

@ -4,6 +4,7 @@ import { Route , Switch } from 'react-router-dom';
import Loadable from 'react-loadable';
import Loading from '../../Loading';
import './Index.scss';
import 'codemirror/lib/codemirror.css';
const FileNew = Loadable({
loader: () => import('../Newfile/Index'),

View File

@ -15,6 +15,7 @@ import types from '../../redux/actions/actionTypes'
import { getProjectFunc, getProjectDetailFunc } from '../../services/project'
import '../css/index.scss'
import './list.scss';
import '../../modules/courses/css/Courses.css'
import { ImageLayerOfCommentHOC } from "../../modules/page/layers/ImageLayerOfCommentHOC";
@ -617,9 +618,9 @@ class Detail extends Component {
<div>
<div className="detailHeader-wrapper">
<div className="normal">
<AlignTop style={{ padding: "18px 0px 10px", justifyContent: "space-between" }}>
<div className="AlignTop" style={{ padding: "18px 0px 10px", justifyContent: "space-between" }}>
<div>
<AlignTop>
<div className="AlignTop">
<div className="projectallName">
{project && project.author &&
<Link to={`/${project.author.login}`}>{project.author.name}</Link>
@ -629,7 +630,7 @@ class Detail extends Component {
</div>
{projectDetail && projectDetail.private && <span className="privateTag mt6">私有</span>}
{!platform && <span className="privateTag red mt6">只读</span>}
</AlignTop>
</div>
<div className="mt8">
{
projectDetail && projectDetail.forked_from_project_id && projectDetail.fork_info ?
@ -697,7 +698,7 @@ class Detail extends Component {
</span>
}
</div>
</AlignTop>
</div>
{/* <div className="mt6" style={{minHeight:"20px"}}>
{
projectDetail && projectDetail.forked_from_project_id && projectDetail.fork_info ?
@ -931,7 +932,7 @@ class Detail extends Component {
<Route path="/:owner/:projectsId/:subIndex"
render={
(props) => (<CoderRootIndex {...this.props} {...props} {...this.state} {...common} />)
(props) => ( __CLIENT__ && <CoderRootIndex {...this.props} {...props} {...this.state} {...common} />)
}
></Route>
<Route path="/:owner/:projectsId"
@ -970,9 +971,9 @@ Detail.preFetch = ({ store, match, query }) => {
const promises = [
dispatch(setProjectDetail(match.params)),
dispatch(setProject(match.params)),
dispatch(setProjectEntries({...match.params, branch: ''})),
dispatch(setProjectEntries({...match.params, branch: match.params.branchName ? match.params.branchName : ''})),
dispatch(setProjectMenuList(match.params)),
dispatch(setProjectReadMe({...match.params, branch: ''}))
dispatch(setProjectReadMe({...match.params, branch: match.params.branchName ? match.params.branchName : ''}))
];
const data = await Promise.all(promises);
resolve({
@ -981,7 +982,13 @@ Detail.preFetch = ({ store, match, query }) => {
});
};
export default withRouter(connect(
export default __CLIENT__ ? withRouter(connect(
mapStateToProps,
mapDispatchToProps
)(ImageLayerOfCommentHOC({
imgSelector: ".imageLayerParent img, .imageLayerParent .imageTarget",
parentSelector: ".newContainer",
})(Detail))) : withRouter(connect(
mapStateToProps,
mapDispatchToProps
)(ImageLayerOfCommentHOC({

View File

@ -7,7 +7,7 @@ import datasetImg from '../img/dataset.jpg';
import axios from 'axios';
function DetailBanner({ history,list , owner , projectsId ,showNotification , url , pathname , state , urlFlag , projectDetail , platform ,open_devops ,current_user, showNpsModal }){
const [ menuName , setMenuName ] = useState(undefined);
const [ menuName , setMenuName ] = useState(list);
const [ visible , setVisible ] = useState(false);
useEffect(()=>{

View File

@ -232,7 +232,9 @@ class Index extends Component {
}).then((result) => {
if (result && result.data.id) {
projectsType && projectsType !== "mirror" && this.props.showNotification(`项目创建成功!`);
this.props.history.push({pathname: `/${result.data.login}/${result.data.identifier}`, query:{showNps: true}});
setTimeout(() => {
this.props.history.push({pathname: `/${result.data.login}/${result.data.identifier}`, query:{showNps: true}});
}, 500);
}
this.setState({
isSpin: false

View File

@ -45,11 +45,11 @@ function RecordList(props){
// errorBox
let columns = [
{ title: '序号', dataIndex: 'index', className:"recordColumns", render: (text, item, index) => <span>{(current-1)*pageSize+index + 1}</span> },
{ title: '序号', dataIndex: 'index', className:"recordColumns", align: 'center', render: (text, item, index) => <span>{(current-1)*pageSize+index + 1}</span> },
{ title: '代码变更方', dataIndex: 'change_from', className:"recordColumns"},
{ title: '同步时间', dataIndex: 'sync_time', className:"recordColumns"},
{ title: '同步状态', dataIndex: 'sync_status', className:"recordColumns", render: (text)=>sync_status(text)},
{ title: 'commit id', dataIndex: 'commit_id', className:"primaryColor recordColumns"},
{ title: 'commit id', dataIndex: 'commit_id', className:"recordColumns", render: (text) =>{ return <Link to={`/${owner}/${projectsId}/commits/${text}`} className="primaryColor">{text}</Link>}},
{ title: '同步日志', dataIndex: 'action', align: 'center', className:"primaryColor recordColumns"},
]

View File

@ -12,8 +12,9 @@ function RecordList(props){
const {sync_repositories_unique=[]} = storeDetail || {};
const [token, setToken] = useState({});
function inviteClick() {
const copyEle = document.querySelector('#webhook'); //
function inviteClick(idName) {
console.log('idName', idName);
const copyEle = document.querySelector(idName); //
if (!copyEle) {
console.error("您的CopyTool未设置正确的inputId");
return;
@ -35,7 +36,7 @@ function RecordList(props){
<Breadcrumb.Item className="font-15"><Link to={`/${owner}/${projectsId}/service/reposyncer`} className="primaryColor">同步分支列表</Link></Breadcrumb.Item>
<Breadcrumb.Item className="font-15">同步配置</Breadcrumb.Item>
</Breadcrumb>
{sync_repositories_unique.map(item => {
{sync_repositories_unique.map((item, index) => {
const {type, external_repo_address, sync_repository_ids} = item
return <Fragment>
<div className="font-15 mt20 mb7">同步仓库平台</div>
@ -44,11 +45,11 @@ function RecordList(props){
<div className="infoBox"><a href={external_repo_address} target="_blank">{external_repo_address}</a></div>
<div className="font-15 mt20 mb7">Webhook接受请求地址</div>
<Input
id="webhook"
id={`webhook${index}`}
className="webhookCopyBox copyWebhookInput"
value={`${window.location.origin}/api/v1/${owner}/${projectsId}/sync_repositories/sync?sync_direction=2&repo_type=${type}`}
readOnly
addonAfter={<a className="primaryColor" onClick={inviteClick}>复制链接</a>}
addonAfter={<a className="primaryColor" onClick={()=>{inviteClick(`#webhook${index}`)}}>复制链接</a>}
/>
<div className="font-15 mt20 mb7">同步仓库授权</div>
<Input.Password addonBefore="token" placeholder={`请输入${type.split("::")[1]}的授权token`} style={{width: '60%'}} defaultValue={item.external_token} maxLength={300} onChange={(e)=>{

View File

@ -318,13 +318,9 @@
}
.storeSetting{
.infoBox{
width: 70%;
padding: 3px 15px;
background-color: rgba(70,106,255,0.03);
}
.webhookCopyBox{
width: 70%;
}
.branchTable{
width: 30%;
}

View File

@ -7,6 +7,7 @@ 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';
@ -35,10 +36,10 @@ const SubDetailIndex = Loadable({
loading: Loading,
});
// forge
const ProjectDetail = Loadable({
loader: () => import("../Main/Detail"),
loading: Loading,
});
// const ProjectDetail = Loadable({
// loader: () => import("../Main/Detail"),
// loading: Loading,
// });
const team = CNotificationHOC()(SnackbarHOC()(TPMIndexHOC(
((props)=>{
return (

View File

@ -6,15 +6,17 @@ 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'
const Infos = Loadable({
loader: () => import("./Infos"),
loading: Loading,
});
// forge
const ProjectDetail = Loadable({
loader: () => import("../Main/Detail"),
loading: Loading,
});
// const ProjectDetail = Loadable({
// loader: () => import("../Main/Detail"),
// loading: Loading,
// });
class DetailTop extends Component {
constructor(props) {

View File

@ -18,7 +18,7 @@ function Index(props) {
const [ bannerTab , setBannerTab ] = useState(undefined);
const register = props && props.mygetHelmetapi && props.mygetHelmetapi.common && props.mygetHelmetapi.common.register;
const {mygetHelmetapi:{name}} = props
const name = props && props.mygetHelmetapi && props.mygetHelmetapi.name ;
const { current_user } = props;
useEffect(()=>{

View File

@ -186,4 +186,57 @@ i.color-orange05:hover {
.color-light-green {
color: #29bd8b !important;
}
.AlignTop {
display:flex;
align-items: flex-start
}
.WhiteBack {
background-color:#fff;
border-radius:5px
}
.Box {
display:flex;
align-items:flex-start
}
.LongWidth {
flex:1;
width:0;
border-radius:5px;
margin-bottom:30px
}
.ShortWidth {
width:300px;
border-radius:5px;
margin-bottom:30px
}
.Gap {
padding-left:20px;
box-sizing:border-box;
}
.AlignCenter {
display:flex;
align-items: center
}
.FlexAJ {
display:flex;
align-items: center;
justify-content: space-between
}
.mt25 {
margin-top:25px
}
.User {
display:flex;
font-weight: bold;
align-items: center
}
.User img {
width:30px;
height:30px;
border-radius:50%
}
.User span {
margin-left:8px
}

View File

@ -9,13 +9,17 @@ import { configureUrlQuery } from 'react-url-query';
import history from './history';
import configureStore from './redux/stores/configureStore';
import { Provider } from 'react-redux';
import StyleContext from 'isomorphic-style-loader/StyleContext'
configureUrlQuery({ history });
const store = configureStore();
window.__useKindEditor = false;
const insertCss = (...styles) => {
const removeCss = styles.map(style => style._insertCss && style._insertCss());
return () => removeCss.forEach(dispose => dispose && dispose())
}
const render = (Component) => {
ReactDOM.hydrate(
@ -24,7 +28,9 @@ const render = (Component) => {
<Provider store={store}>
<BrowserRouter basename='/'>
<Route path='/' component={App}></Route>
<StyleContext.Provider value={{insertCss}}>
<Route path='/' component={App}></Route>
</StyleContext.Provider>
</BrowserRouter>
</Provider>

View File

@ -22,6 +22,9 @@ class ImageLayer extends Component {
render() {
let { showImage, imageSrc, } = this.props;
if (__SERVER__) {
return null
}
return ReactDOM.createPortal(
<div>
{showImage ?

View File

@ -47,7 +47,9 @@ export function ImageLayerOfCommentHOC(options = {}) {
}
componentWillUnmount() {
$(options.parentSelector || ".commentsDelegateParent", 'click', this.onDelegateClick)
if (__CLIENT__) {
$(options.parentSelector || ".commentsDelegateParent", 'click', this.onDelegateClick)
}
}
onImageLayerClose = () => {

View File

@ -27,7 +27,7 @@ export function TPMIndexHOC(WrappedComponent) {
globalLoading: false,
dataquerys: {},
isloginCancel: undefined,
mygetHelmetapi: null,
mygetHelmetapi: __CLIENT__ ? null : defaultSetting,
giteaVisible:false,
email:undefined,
completeProfile:false,
@ -61,12 +61,13 @@ export function TPMIndexHOC(WrappedComponent) {
window.removeEventListener('keyup', this.keyupListener)
}
componentWillMount() {
this.setState({
current_user: {}
})
if (!__SERVER__) {
this.fetchUsers();
} else {
this.setState({
current_user: {}
})
}
}
@ -316,7 +317,7 @@ export function TPMIndexHOC(WrappedComponent) {
{...this.state}
/> : ""}
{npsModalVisible && <NpsModal closeNpsModal={()=>{this.closeNpsModal()}} npsActionType={npsActionType} npsActionId={npsActionId}/>}
{ !pathCheck && <Header {...this.state} {...this.props} {...common} publicNav={publicNav}></Header> }
{ !pathCheck && <Header {...this.state} {...this.props} {...common} publicNav={publicNav} mygetHelmetapi={ mygetHelmetapi }></Header> }
<Spin spinning={this.state.globalLoading} delay={0} className="globalSpin"
size="large" tip={this._gLoadingTip || "加载中..."}
>

View File

@ -14,45 +14,83 @@ const axiosInstance = axios.create({
withCredentials: true
});
axiosInstance.interceptors.response.use(
response => {
return response;
},
error => {
console.log(error);
}
)
//
export async function getProjectFunc (owner, projectsId) {
const url = `${baseUrl}/${owner}/${projectsId}/simple.json`;
if (__SERVER__) {
axiosInstance.defaults.headers.cookie = domObj.window.document.cookie
}
return axiosInstance.get(url);
}
export async function getProjectDetailFunc (owner, projectsId) {
const url = `${baseUrl}/${owner}/${projectsId}/detail.json`;
if (__SERVER__) {
axiosInstance.defaults.headers.cookie = domObj.window.document.cookie
}
return axiosInstance.get(url);
}
export async function getProjectEntriesFunc (owner, projectsId, branch) {
const url = `${baseUrl}/${owner}/${projectsId}/entries.json`;
if (__SERVER__) {
axiosInstance.defaults.headers.cookie = domObj.window.document.cookie
}
return axiosInstance.get(url, {params: { ref: branch }});
}
export async function getBannerFunc (owner, projectsId) {
const url = `${baseUrl}/${owner}/${projectsId}/menu_list.json`;
if (__SERVER__) {
axiosInstance.defaults.headers.cookie = domObj.window.document.cookie
}
return axiosInstance.get(url);
}
export async function getProjectReadMe (owner, projectsId, branch) {
const url = `${baseUrl}/${owner}/${projectsId}/readme.json`;
if (__SERVER__) {
axiosInstance.defaults.headers.cookie = domObj.window.document.cookie
}
return axiosInstance.get(url, {params: { ref: branch }});
}
export async function getOwnerInfo (owner) {
const url = `${baseUrl}/owners/${owner}.json`;
if (__SERVER__) {
axiosInstance.defaults.headers.cookie = domObj.window.document.cookie
}
return axiosInstance.get(url);
}
export async function getMainInfos (deptId) {
const url = `${baseZoneUrl}/zone/open/zoneKey/${deptId}`;
if (__SERVER__) {
axiosInstance.defaults.headers.cookie = domObj.window.document.cookie
}
return axiosInstance.get(url);
}
export async function getNewsDetail (id) {
const url = `${baseZoneUrl}/cms/doc/open/${id}`;
if (__SERVER__) {
axiosInstance.defaults.headers.cookie = domObj.window.document.cookie
}
return axiosInstance.get(url);
}
export async function getSetting () {
const url = `${baseUrl}/setting.json`;
return axiosInstance.get(url);
}