forgeplus-react/src/forge/Wiki/Index.jsx

349 lines
13 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 } 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);
const [masterProtected, setMasterProtected] = useState(false);
useEffect(()=>{
window.scrollTo(0,0);
// 获取仓库分支信息判断master是否设置了分支保护
if(projectsId && owner){
axios.get(`/v1/${owner}/${projectsId}/branches.json`, {keyword: 'master'}).then(res=>{
if(res && res.data && res.data.total_count === 1){
setMasterProtected(res.data.branches[0].protected);
}
})
}
}, [])
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;
const arr = pathname.split('/');
if(!pathname.endsWith('/wiki')){
firstWikiNode = findNodeByName(menuList, arr[arr.length-2]);
} else{
// 找到目录树中第一个[[页面]]
firstWikiNode = findFirstWiki(menuList);
}
// 当前选中wiki被删除
!firstWikiNode && (firstWikiNode = findFirstWiki(menuList));
// 设置目录 选中节点初始化
setDefaultSelectedKeys([firstWikiNode.key+""]);
const title = firstWikiNode.title.trim().substring(2,firstWikiNode.title.trim().length-2);
const firstWiki = res.data.filter(item => { return title === item.name })[0];
setCheckItem({
...firstWiki,
key: firstWikiNode.key
})
history.push(`/${owner}/${projectsId}/wiki/${title}/${firstWikiNode.key}`);
} else {
setFileArr([]);
setFileArrInit([]);
}
});
}, [project, reload])
// 加载单个wiki详情参数尽量用驼峰此处由于后端太麻烦所以不标准
useEffect(() => {
project && checkItem.name && getWiki({
owner: owner,
repo: projectsId,
pageName: checkItem.name,
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 regEn = /[\\/:*?"<>|[\]-]/g;
if (regEn.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',
commit_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(`/${owner}/${projectsId}/wiki/${encodeURI(checkItem.name)}/${checkItem.key}/edit${params}`);
}
function preview() {
window.open(`/${owner}/${projectsId}/wiki/preview/${encodeURI(project.name)}/${project.id}`);
}
// 支持 MarkdownHtmlPdf格式文件
const menu = (
<Menu >
<Menu.Item key="1" onClick={() => { downloadWiki('markdown') }}>
Markdown
</Menu.Item>
<Menu.Item key="2" onClick={() => { downloadWiki('html') }}>
HTML
</Menu.Item>
<Menu.Item key="3" onClick={() => { downloadWiki('pdf') }}>
PDF
</Menu.Item>
</Menu>
);
function downloadWiki(type) {
window.open(`${httpUrl}/api/wikiExport/wikiExport-wrapper?repoName=${projectsId}&owner=${owner}&type=${type}&projectName=${project.name}&projectId=${project.id}`);
}
function changeitem(item){
const {name, key} = item;
const wiki = fileArrInit.filter(item => { return item.name == name })[0];
setCheckItem({
...wiki,
key
})
history.push(`/${owner}/${projectsId}/wiki/${name}/${key}`);
}
return (
<Spin spinning={!fileArrInit} className="opacitySpin">
{fileArrInit && fileArrInit.length ?
<div className="wiki-main">
<div className="wiki-head">
<span className="head-title">
{
permission ?
<div>
<Button type="default" onClick={()=>{setVisible(true)}} className='mr15'><Icon type="plus"/>新增目录</Button>
<Button type="default" onClick={addFile}><Icon type="plus" />新增页面</Button>
</div> : "Wiki"
}
</span>
<div>
{
permission && <UploadWiki {...props} fileList={fileList} setFileList={setFileList} fileArrInit={fileArrInit} setReload={setReload}/>
}
<Dropdown overlay={menu}>
<Button type="default" className="ml10">导出<Icon type="caret-down" /></Button>
</Dropdown>
<Button type="default" className="ml10" onClick={preview}>预览</Button>
</div>
</div>
<div className="wiki-body">
<div className="wiki-nav-parent">
<div className='wiki-nav-scroll'>
<div className="wiki-nav">
{/* 读sidebar转换成目录树 */}
<Sidebar fileList = {fileList} changeitem={changeitem} projectsId={projectsId} owner={owner} project={project} setReload={setReload} sidebar={sidebar} history={history} permission={permission} defaultSelectedKeys={defaultSelectedKeys} fileArrInit={fileArrInit} masterProtected={masterProtected}/>
</div>
</div>
{checkItem.wiki_clone_link && <InputGroup className="copy-url" compact>
<Select dropdownClassName="wiki-url-type" defaultValue="HTTPS" onChange={(v) => { setUrlType(v) }}>
<Option value="HTTPS">HTTPS</Option>
<Option value="SSH">SSH</Option>
</Select>
<Input id="wikiUrl" value={urlType === 'HTTPS' ? checkItem.wiki_clone_link.https : checkItem.wiki_clone_link.ssh} />
<CopyTool className="copy-wiki" inputId="wikiUrl" />
</InputGroup>}
</div>
<div className="wiki-content">
<div className="wiki-content-head">
<div className="wiki-content-head-left">
<h3 className="wiki-detail-title">{checkItem.name}</h3>
<span className="user-box mr10" onClick={() => { checkItem.commit && goUser(checkItem.commit.author.name) }}>
{itemDetail.image_url && <img alt="头像" className="head-log-small" src={getImageUrl(`/${itemDetail.image_url}`)} />}
<span className='font-14'>{itemDetail.userName}</span>
</span>
<span className="time-ago font-14">上次修改于{checkItem.commit ? timeAgo(checkItem.commit.author.date) : '刚刚'}</span>
</div>
{permission && <Button type="primary" ghost onClick={()=>{goEdit(`?copy`)}} className='mr10'>复制</Button>}
{permission && <Button type="primary" onClick={()=>{goEdit("")}}>编辑</Button>}
</div>
{itemDetail&&itemDetail.md_content&&<RenderHtml className="wiki-content-detail editor-content-panel imageLayerParent" value={ itemDetail && Base64.decode(itemDetail.md_content) } url={history.location}/>}
</div>
</div>
</div >
:
<Welcome {...props} reloadList={setReload} fileList={fileList} setFileList={setFileList} fileArrInit={fileArrInit} setReload={setReload}/>
}
<Modal title="新增目录" visible={visible} onCancel={()=>{setVisible(false);setAddMenuError(undefined);setMenuName(undefined);}} onOk={addMenu} className="wikiAddMenu myself-modal" width={450} centered>
<div className='mb10'>目录名称: </div>
<Input placeholder='请输入目录名称' value={menuName} onChange={(e)=>{setMenuName(e.target.value);setAddMenuError(undefined)}} maxLength={50} autoFocus></Input>
<div className='wikiAddMenuError mt5'>{addMenuError}</div>
</Modal>
</Spin>
)
}