优化wiki目录搜索、支持在目录树进行wiki重命名操作、目录树收起展开、高亮问题

This commit is contained in:
谢思 2023-04-12 16:45:05 +08:00
parent e45efe2467
commit 66e9063952
4 changed files with 189 additions and 61 deletions

View File

@ -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) => {
<div className="wiki-nav-parent">
<div className='wiki-nav-scroll'>
<div className="wiki-nav">
<Search
allowClear
placeholder="输入关键字搜索文件"
className="wiki-search"
onSearch={changeSearchValue}
/>
{/* 读sidebar转换成目录树 */}
<Sidebar fileList = {fileList} changeitem={changeitem} projectsId={projectsId} owner={owner} project={project} setReload={setReload} sidebar={sidebar} history={history} permission={permission} defaultSelectedKeys={defaultSelectedKeys}/>
<Sidebar fileList = {fileList} changeitem={changeitem} projectsId={projectsId} owner={owner} project={project} setReload={setReload} sidebar={sidebar} history={history} permission={permission} defaultSelectedKeys={defaultSelectedKeys} fileArrInit={fileArrInit}/>
</div>
</div>

View File

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

View File

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

View File

@ -6,11 +6,12 @@ import '../../components/ModalFun/index.scss';
import { deleteWiki, findNodeByChildrenKey, findNodeByKey, treeToMd, updateWiki } from '../../api';
import { useEffect } from 'react';
import { Base64 } from 'js-base64';
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} = 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);
@ -19,10 +20,24 @@ export default function Sidebar(props) {
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);
useEffect(()=>{
setSelectedKey(defaultSelectedKeys);
if(defaultSelectedKeys){
// wiki
setSelectedKey(defaultSelectedKeys);
//
const keys = [];
const list = defaultSelectedKeys[0].split('-');
list.map((item, index)=>{
keys.push(index > 0 ? `${keys[index-1]}-${item}` : item)
})
setExpandedKeys(keys);
}
}, [defaultSelectedKeys])
useEffect(()=>{
@ -43,7 +58,7 @@ export default function Sidebar(props) {
</a>
</Menu.Item>
<Menu.Item>
<a onClick={(e) => { updateName(e, item) }}>
<a onClick={(e) => { updateName(e, item, 1) }}>
重命名
</a>
</Menu.Item>
@ -57,6 +72,11 @@ export default function Sidebar(props) {
const fileMenu = (title, isFile, item) =>{
return <Menu>
<Menu.Item>
<a onClick={(e) => { updateName(e, item, 2) }}>
重命名
</a>
</Menu.Item>
<Menu.Item>
<a onClick={(e) => { deleteFileModal(e, title, isFile, item) }}>
删除
@ -102,6 +122,7 @@ export default function Sidebar(props) {
});
};
//
function selectTree(keys,event){
let {title, key} = event.node.props.dataRef;
setSelectedKey([key+""]);
@ -113,6 +134,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,11 +291,11 @@ 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);
@ -284,7 +318,7 @@ export default function Sidebar(props) {
return;
}
if(!isEditName){
//
//
const {children, node, title, key} = visible;
const mList = children.filter(item=>!item.title.trim().startsWith('[[')).map(i=>i.title.trim())
if(mList.includes(`- ${menuName}`)){
@ -295,23 +329,52 @@ export default function Sidebar(props) {
addChildrenByKey(newList, key, menuName);
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,
commit_message: '',
}).then(res=>{
if(res && res.message === '200'){
// wikisidebar
updateSidebar(treeToMd(newMenuList), 4);
history.push(`/${owner}/${projectsId}/wiki/${encodeURI(menuName)}/${key}`);
}
})
return;
}
updateSidebar(treeToMd(newMenuList), 4)
}
}
@ -440,16 +503,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(
<div className='wikiSidebar mt5'>
{menuList && menuList.length ? <DirectoryTree onSelect={selectTree} draggable={permission} onDrop={onDrop} selectedKeys={selectedKey}>
<div className='wikiSidebar'>
<Search
allowClear
placeholder="输入关键字搜索文件"
className="wiki-search"
onSearch={changeSearchValue}
/>
{/* 目录是否全部展开 */}
<div className='expendedAllAction mt5' onClick={expandAll}>{isExpendedAll ? '收起' : '展开'}所有节点</div>
{/* wiki目录 */}
{menuList && menuList.length ? <DirectoryTree onSelect={selectTree} draggable={permission} onDrop={onDrop} selectedKeys={selectedKey} expandedKeys={expandedKeys} onExpand={onExpand}>
{renderTreeNodes(menuList)}
</DirectoryTree> : ''}
{/* 新增子目录弹窗 */}
<Modal title={isEditName ? "编辑目录" : "新增目录"} visible={Boolean(visible)} onCancel={onCancel} onOk={submit} 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}></Input>
<Modal title={isEditName ? `编辑${isEditName === 1 ? '目录' : '页面'}` : "新增目录"} visible={Boolean(visible)} onCancel={onCancel} onOk={submit} className="wikiAddMenu myself-modal" width={450} centered>
<div className='mb10'>{isEditName === 2 ? '页面' : '目录'}名称: </div>
<Input placeholder={`请输入${isEditName === 2 ? '页面' : '目录'}名称`} value={menuName} onChange={(e)=>{setMenuName(e.target.value);setAddMenuError(undefined)}} maxLength={50}></Input>
<div className='wikiAddMenuError mt5'>{addMenuError}</div>
</Modal>
</div>