From bf30771d666f8d8c540bb3acfe8119d6bdb06658 Mon Sep 17 00:00:00 2001 From: shixiaohua Date: Mon, 4 Mar 2024 16:14:12 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=96=87=E4=BB=B6dir=E4=BF=9D=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ItemTree/index.jsx | 54 +++++- .../plugins/ContextMenuPlugin/index.jsx | 1 + .../plugins/ContextMenuPlugin/index.less | 92 +++++++++ src/pages/Note/index.jsx | 2 +- src/redux/dirMessage_reducer.js | 180 ++---------------- src/redux/tableBarItem_reducer.js | 4 - src/utils/FileTree.js | 118 ++++++++++++ src/utils/PathOperate/index.jsx | 10 +- 8 files changed, 291 insertions(+), 170 deletions(-) create mode 100644 src/utils/FileTree.js diff --git a/src/components/ItemTree/index.jsx b/src/components/ItemTree/index.jsx index 8541fb3..afe6f9b 100644 --- a/src/components/ItemTree/index.jsx +++ b/src/components/ItemTree/index.jsx @@ -16,6 +16,7 @@ import DirAddFile from "./DirAddFile"; import DirDeleteFile from "./DirDeleteFile"; import DirAddDir from "./DirAddDir"; import {createPortal} from "react-dom"; +import {isArray} from "@craco/craco/lib/utils"; // const defaultData = []; // 将树平铺用于查找 const dataList = []; @@ -77,12 +78,55 @@ function generateChildList(fileList) { const titleExtended = (fileName, filePath) => { return {fileName} } +function findFirstEndGEZ(childrenList,child){ + if (isArray(childrenList)&& childrenList.length>0){ + if (childrenList.find(fileDir=> fileDir.dirFlag&&fileDir.end>0)){ + child.push(...childrenList) + return childrenList; + }else { + childrenList.forEach(fileDir=>{ + if (fileDir.dirFlag&&isArray(fileDir.children)&&fileDir.children.length>0){ + findFirstEndGEZ(fileDir.children,child) + } + }) + } + } +} /** * 将文件信息改为树信息 * @param fileDirDate * @returns {*[]} */ -const flushTree = (fileDirDate) => { +const flushTree = (fileDirTree) => { + // 找到第一次出现end>o的位置。 + // 循环递归处理数据 + let fileList = [] + findFirstEndGEZ(fileDirTree.children,fileList) + + const defaultValueStateSet = []; + if (Array.isArray(fileList) && fileList.length > 0) { + for (let i = 0; i < fileList.length; i++) { + const node = fileList[i]; + console.log("node:", node) + const {fileName, filePath, children, dirFlag} = node; + const childrenM = [] + if (Array.isArray(children) && children.length > 0) { + childrenM.push(...generateChildList(children)); + } + defaultValueStateSet.push({ + "key": filePath, + // 修改属性后此处也需要修改 + "title": titleExtended(getFileNameByPath(fileName), filePath), + "icon": , + "dirFlag": dirFlag, + "children": childrenM + }); + + } + } + return defaultValueStateSet; +} +const flushTreeList = (fileDirDate) => { const defaultValueStateSet = []; if (Array.isArray(fileDirDate) && fileDirDate.length > 0) { for (let i = 0; i < fileDirDate.length; i++) { @@ -123,7 +167,7 @@ const ItemTree = (prop) => { // const [expandedKeys, setExpandedKeys] = useState([]); const expandedKeys = useSelector(state => state.tableBarItem.expandedKeyList) const [searchValue, setSearchValue] = useState(''); - const [autoExpandParent, setAutoExpandParent] = useState(true); + const [autoExpandParent, setAutoExpandParent] = useState(false); // let filePath = useSelector(state => state.dirMessage.data); const [defaultValueState, setDefaultValueState] = useState(flushTree(prop.filePath)); const [state, setState] = useState(); @@ -197,7 +241,7 @@ const ItemTree = (prop) => { if (Array.isArray(fileStateList[0].children) && fileStateList[0].children.length > 0) { dispatch(refreshDirReducer({selectDirKey: filePath, fileStateList})) // 添加下级节点 - addChildNode(defaultValueState, flushTree(fileStateList)) + addChildNode(defaultValueState, flushTreeList(fileStateList)) const result = [...defaultValueState] console.log("[...defaultValueState]:", result) setDefaultValueState(result) @@ -217,7 +261,7 @@ const ItemTree = (prop) => { if (Array.isArray(fileStateList[0].children) && fileStateList[0].children.length > 0) { dispatch(nextDirAdd({selectDirKey: e.node.key, fileStateList})) // 添加下级节点 - addChildNode(defaultValueState, flushTree(fileStateList)) + addChildNode(defaultValueState, flushTreeList(fileStateList)) const result = [...defaultValueState] console.log("[...defaultValueState]:", result) setDefaultValueState(result) @@ -354,4 +398,4 @@ const ItemTree = (prop) => { ); }; -export default ItemTree; \ No newline at end of file +export default ItemTree; diff --git a/src/pages/Note/Hlexical/plugins/ContextMenuPlugin/index.jsx b/src/pages/Note/Hlexical/plugins/ContextMenuPlugin/index.jsx index 4e8ac02..6e1bbdf 100644 --- a/src/pages/Note/Hlexical/plugins/ContextMenuPlugin/index.jsx +++ b/src/pages/Note/Hlexical/plugins/ContextMenuPlugin/index.jsx @@ -13,6 +13,7 @@ import { CUT_COMMAND, PASTE_COMMAND, } from 'lexical'; +import "./index.less" function ContextMenuItem({ index, isSelected, diff --git a/src/pages/Note/Hlexical/plugins/ContextMenuPlugin/index.less b/src/pages/Note/Hlexical/plugins/ContextMenuPlugin/index.less index e69de29..e0a475d 100644 --- a/src/pages/Note/Hlexical/plugins/ContextMenuPlugin/index.less +++ b/src/pages/Note/Hlexical/plugins/ContextMenuPlugin/index.less @@ -0,0 +1,92 @@ + +.typeahead-popover { + background: #fff; + box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.3); + border-radius: 8px; + position: fixed; +} + +.typeahead-popover ul { + padding: 0; + list-style: none; + margin: 0; + border-radius: 8px; + max-height: 200px; + overflow-y: scroll; +} + +.typeahead-popover ul::-webkit-scrollbar { + display: none; +} + +.typeahead-popover ul { + -ms-overflow-style: none; + scrollbar-width: none; +} + +.typeahead-popover ul li { + margin: 0; + min-width: 180px; + font-size: 14px; + outline: none; + cursor: pointer; + border-radius: 8px; +} + +.typeahead-popover ul li.selected { + background: #eee; +} + +.typeahead-popover li { + margin: 0 8px 0 8px; + padding: 8px; + color: #050505; + cursor: pointer; + line-height: 16px; + font-size: 15px; + display: flex; + align-content: center; + flex-direction: row; + flex-shrink: 0; + background-color: #fff; + border-radius: 8px; + border: 0; +} + +.typeahead-popover li.active { + display: flex; + width: 20px; + height: 20px; + background-size: contain; +} + +.typeahead-popover li:first-child { + border-radius: 8px 8px 0px 0px; +} + +.typeahead-popover li:last-child { + border-radius: 0px 0px 8px 8px; +} + +.typeahead-popover li:hover { + background-color: #eee; +} + +.typeahead-popover li .text { + display: flex; + line-height: 20px; + flex-grow: 1; + min-width: 150px; +} + +.typeahead-popover li .icon { + display: flex; + width: 20px; + height: 20px; + user-select: none; + margin-right: 8px; + line-height: 16px; + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} diff --git a/src/pages/Note/index.jsx b/src/pages/Note/index.jsx index 7ba481d..dc7951d 100644 --- a/src/pages/Note/index.jsx +++ b/src/pages/Note/index.jsx @@ -21,7 +21,7 @@ const Note = () => { const activeKey=useSelector(state => state.tableBarItem.activeKey); const items = useSelector(state => state.tableBarItem.data) - let filePath = useSelector(state => state.dirMessage.data); + let filePath = useSelector(state => state.dirMessage.dirTree); const itemTreeTab=[ { diff --git a/src/redux/dirMessage_reducer.js b/src/redux/dirMessage_reducer.js index 00a412d..72a3f70 100644 --- a/src/redux/dirMessage_reducer.js +++ b/src/redux/dirMessage_reducer.js @@ -1,6 +1,8 @@ import {createSlice} from '@reduxjs/toolkit' import {isEmpty} from "../utils/ObjectUtils"; -import {fullFileNameFormat, getFileFullNameByPath} from "../utils/PathOperate"; +import {fileDirFormat, filePathSplit, getFileDirByPath, getFileFullNameByPath} from "../utils/PathOperate"; +import {FileTree, insertNode, removeNode, updateNode} from "../utils/FileTree" +import {func} from "prop-types"; /* fileTitle:文件名 fileName:文件名.文件扩展名 @@ -16,101 +18,43 @@ export const dirMessageSlice = createSlice({ name: 'dirMessage', initialState: { data: [], - // selectDirKey:"" + dirTree:new FileTree("root","/root",true,[]) }, reducers: { - // 一级目录有相同的就要和并,从第一次展开处和并,不需要展示上一级。 dirAdd: (state, action) => { console.log("dirMessage:dirAdd", state, action) - if (action.payload) { - // 新添加进来的目录,要判断是否包含或者被包含,如果包含则统一树结构 - let fileFilter = state.data.filter((fileMessage) => - fileMessage.filePath.startsWith(action.payload[0].filePath) - ); - let actionFilter = state.data.filter((fileMessage) => - action.payload[0].filePath.startsWith(fileMessage.filePath) - ); - let equalsFilter = state.data.filter((fileMessage) => - action.payload[0].filePath===fileMessage.filePath - ); - // 打开相同目录不用处理 - if (equalsFilter.length > 0) { - console.log('equalsFilter', filter) - } - // 打开上级目录 - else if (actionFilter.length>0){ - - } - // 打开下级目录 - else if (fileFilter.length>0){ - - } - // 无关目录 - else { - // 添加进当前目录 - state.data = [...new Set([...state.data, ...action.payload])]; - console.log('state.data:', state.data) - } - } + // 添加目录 + insertNode(state.dirTree,action.payload[0]) }, newFileAdd :(state,action)=>{ - + console.log("dirMessage:newFileAdd", state, action) + insertNode(state.dirTree,action.payload[0]) }, dirRemove:(state,action)=>{ console.log("dirMessage:dirRemove", state, action) // 获取当前选中的key let selectDirKey = action.payload.selectDirKey; - state.data = state.data.filter(file=>{ - if (file.filePath === selectDirKey && file.dirFlag) { - return false; - } else if (file.children.length > 0 && selectDirKey.startsWith(file.filePath)) { - file.children = filterChild(file.children, selectDirKey) - return true; - }else { - return true; - } - }) + if (isEmpty(state.dirTree.filePath)){ + return + } + removeNode(state.dirTree,{fileName:getFileFullNameByPath(selectDirKey),filePath:selectDirKey,dirFlag:true,children:[]}) }, nextDirAdd: (state, action) => { console.log("dirMessage:nextDirAdd", state, action) - // 获取当前选中的key - let selectDirKey = action.payload.selectDirKey; - // 遍历文件树,找到对应的key并加入其中, - // 如果包含下级目录则不更新,在刷新中更新。 - state.data.forEach(file => { - if (file.filePath === selectDirKey && file.dirFlag && file.children.length === 0) { - file.children.push(action.payload.fileStateList[0].children) - } else if (file.children.length > 0) { - findChild(file.children, action, selectDirKey) - } - }) + insertNode(state.dirTree,action.payload.fileStateList[0]) }, refreshDir: (state, action) => { console.log("dirMessage:refreshDir", state, action) - // 获取当前选中的key - let selectDirKey = action.payload.selectDirKey; - // 遍历文件树,找到对应的key并加入其中, - state.data.forEach(file => { - if (file.filePath === selectDirKey && file.dirFlag) { - file.children=action.payload.fileStateList[0].children - } else if (file.children.length > 0) { - refreshChild(file.children, action, selectDirKey) - } - }) + insertNode(state.dirTree,action.payload.fileStateList[0]) }, updateFileName:(state,action)=>{ console.log("dirMessage:updateFileName", state, action) let newFilePath = action.payload.newFilePath let oldFilePath = action.payload.oldFilePath - // 查找旧文件并且修改文件信息 - state.data.forEach(file => { - if (file.filePath === oldFilePath) { - file.filePath = newFilePath - file.fileName = getFileFullNameByPath(newFilePath) - } else if (file.children.length > 0 && oldFilePath.startsWith(file.filePath)) { - updateFileNameChild(file.children, oldFilePath, newFilePath) - } - }) + updateNode(state.dirTree, + {fileName:getFileFullNameByPath(oldFilePath),filePath:oldFilePath,dirFlag:false,children:[]}, + {fileName:getFileFullNameByPath(newFilePath),filePath:newFilePath,dirFlag:false,children:[]} + ) }, dirFileAdd:(state,action)=>{ console.log("dirMessage:dirFileAdd", state, action) @@ -123,18 +67,7 @@ export const dirMessageSlice = createSlice({ "dirFlag": false, "children": [] } - // 查找旧文件并且修改文件信息 - state.data.forEach(file => { - if (file.filePath === fileDir) { - if (Array.isArray(file.children)){ - file.children.push(fileMessage) - }else { - file.children=[fileMessage] - } - } else if (file.children.length > 0 && filePath.startsWith(file.filePath)) { - dirFileAddChild(file.children, fileDir, fileMessage) - } - }) + insertNode(state.dirTree,fileMessage) }, dirDirAdd:(state,action)=>{ console.log("dirMessage:dirDirAdd", state, action) @@ -147,87 +80,16 @@ export const dirMessageSlice = createSlice({ "dirFlag": true, "children": [] } - // 查找旧文件并且修改文件信息 - state.data.forEach(file => { - if (file.filePath === fileDir) { - if (Array.isArray(file.children)){ - file.children.push(fileMessage) - }else { - file.children=[fileMessage] - } - } else if (file.children.length > 0 && fileDir.startsWith(file.filePath)) { - dirFileAddChild(file.children, fileDir, fileMessage) - } - }) + insertNode(state.dirTree,fileMessage) }, dirFileRemove:(state,action)=>{ console.log("dirMessage:dirFileRemove", state, action) let filePath = action.payload.filePath; - state.data = state.data.filter(file=>{ - if (file.filePath === filePath && !file.dirFlag) { - return false; - } else if (file.children.length > 0 && filePath.startsWith(file.filePath)) { - file.children = dirFileRemoveChild(file.children, filePath) - return true; - }else { - return true; - } - }) + removeNode(state.dirTree,{fileName:getFileFullNameByPath(filePath),filePath:filePath,dirFlag:false,children:[]}) } } }) -function dirFileRemoveChild(fileList, selectDirKey) { - return fileList.filter(file => { - if (file.filePath === selectDirKey && !file.dirFlag) { - return false - }else if (file.dirFlag && selectDirKey.startsWith(file.filePath) && Array.isArray(file.children) && file.children.length > 0) { - file.children = dirFileRemoveChild(file.children, selectDirKey) - return true - }else { - return true - } - }) -} - -function dirFileAddChild(fileList, fileDir, fileMessage){ - fileList.forEach(file => { - if (file.filePath === fileDir) { - if (Array.isArray(file.children)){ - file.children.push(fileMessage) - }else { - file.children=[fileMessage] - } - } else if (file.children.length > 0 && fileDir.startsWith(file.filePath)) { - dirFileAddChild(file.children, fileDir, fileMessage) - } - }) -} -function filterChild(fileList, selectDirKey) { - return fileList.filter(file => { - if (file.filePath === selectDirKey && file.dirFlag) { - return false - }else if (file.dirFlag && selectDirKey.startsWith(file.filePath) && Array.isArray(file.children) && file.children.length > 0) { - filterChild(file.children, selectDirKey) - return true - }else { - return true - } - }) -} - -function refreshChild(fileList, action, selectDirKey) { - fileList.forEach(file => { - if (file.filePath === selectDirKey && file.dirFlag) { - file.children = action.payload.fileStateList[0].children - return - } - if (file.dirFlag && Array.isArray(file.children) && file.children.length > 0) { - refreshChild(file.children, action, selectDirKey) - } - }) -} - function findChild(fileList, action, selectDirKey) { fileList.forEach(file => { if (file.filePath === selectDirKey && file.dirFlag && diff --git a/src/redux/tableBarItem_reducer.js b/src/redux/tableBarItem_reducer.js index 08c0d1d..d6ce8b2 100644 --- a/src/redux/tableBarItem_reducer.js +++ b/src/redux/tableBarItem_reducer.js @@ -80,9 +80,6 @@ export const tableBarItemSlice = createSlice({ console.log("tableBarItemSlice:setExpandedKeys",action.payload) state.expandedKeyList=action.payload }, - removeExpandedKeys:(state, action)=>{ - state.expandedKeyList=state.expandedKeyList.filter(key=>key!==action.payload) - }, editLeftTableOfContents:(state, action)=>{ state.leftTableOfContents=action.payload.leftTableOfContents } @@ -93,7 +90,6 @@ export const { addTableBarItem, setActiveKey, updatedSavedFile, setExpandedKeys, - removeExpandedKeys, addExpandedKeys, updateFileName, editLeftTableOfContents diff --git a/src/utils/FileTree.js b/src/utils/FileTree.js new file mode 100644 index 0000000..d7b9deb --- /dev/null +++ b/src/utils/FileTree.js @@ -0,0 +1,118 @@ +import {fileDirFormat, fileDirFormatArray, filePathSplit, getFileDirByPath} from "./PathOperate"; +import {isObject} from "formik"; +import {isEmpty} from "./ObjectUtils"; + +export class FileTree { + constructor(fileName,filePath,dirFlag,children) { + this.fileName = fileName; + this.filePath = filePath; + this.dirFlag = dirFlag; + this.children = children; + this.pass=0 + this.end=0 + } +} + +export function insertNode(root,fileMessage) { + // let copy = JSON.parse(JSON.stringify(root)) + // let currentNode = copy; + let currentNode = root; + let fileDir; + if (fileMessage.dirFlag){ + fileDir=fileMessage.filePath + }else { + fileDir=getFileDirByPath(fileMessage.filePath) + } + + let fileDirSplit = filePathSplit(fileDir).filter(fileD=>!isObject(fileD)&&fileD!==""); + for (let i = 0; i < fileDirSplit.length; i++) { + let find = currentNode.children.find(file=>file.dirFlag&&file.fileName===fileDirSplit[i]); + if (find &&!isEmpty(find.fileName)){ + currentNode=find; + currentNode.pass=currentNode.pass+1 + }else { + let newNode = new FileTree(fileDirSplit[i],fileDirFormatArray(fileDirSplit.slice(0,i+1)),true,[]); + currentNode.children.push(newNode) + currentNode=newNode + currentNode.pass=currentNode.pass+1 + } + if (i===fileDirSplit.length-1){ + currentNode.end =1 + if (!fileMessage.dirFlag){ + currentNode.children.push(fileMessage) + } else { + currentNode.children = fileMessage.children + } + } + } + // return copy; +} + +export function removeNode(root,fileMessage) { + let currentNode = root; + let fileDir; + if (fileMessage.dirFlag){ + fileDir=fileMessage.filePath + }else { + fileDir=getFileDirByPath(fileMessage.filePath) + } + let fileDirSplit = filePathSplit(fileDir).filter(fileD=>!isObject(fileD)&&fileD!==""); + for (let i = 0; i < fileDirSplit.length; i++) { + if (i===fileDirSplit.length-1) { + if (fileMessage.dirFlag) { + currentNode.children = currentNode.children.filter(file => + file.fileName !== fileDirSplit[i] && file.dirFlag) + break + } else { + currentNode.children = currentNode.children.filter(file => + file.fileName !== fileMessage.fileName && !file.dirFlag) + break + } + } + let find = currentNode.children.find(file=>file.dirFlag&&file.fileName===fileDirSplit[i]); + if (!isEmpty(find)){ + currentNode=find; + }else { + break + } + } +} + +export function updateNode(root,fileMessage,newFileMessage) { + let currentNode = root; + let fileDir; + if (fileMessage.dirFlag){ + fileDir=fileMessage.filePath + }else { + fileDir=getFileDirByPath(fileMessage.filePath) + } + + let fileDirSplit = filePathSplit(fileDir).filter(fileD=>!isObject(fileD)&&fileD!==""); + for (let i = 0; i < fileDirSplit.length; i++) { + if (fileMessage.dirFlag){ + if (i===fileDirSplit.length-1){ + let node =currentNode.children.find(file=> + file.fileName===fileDirSplit[i]&&file.dirFlag); + node.fileName=newFileMessage.fileName + node.filePath=newFileMessage.filePath + node.dirFlag=newFileMessage.dirFlag + node.children=newFileMessage.children + } + }else { + if (i===fileDirSplit.length-1){ + let node=currentNode.children.find(file=> + file.fileName===fileMessage.fileName&&!file.dirFlag) + node.fileName=newFileMessage.fileName + node.filePath=newFileMessage.filePath + node.dirFlag=newFileMessage.dirFlag + node.children=newFileMessage.children + } + } + let find = currentNode.children.find(file=>file.dirFlag&&file.fileName===fileDirSplit[i]); + if (!isEmpty(find)){ + currentNode=find; + }else { + break + } + } +} diff --git a/src/utils/PathOperate/index.jsx b/src/utils/PathOperate/index.jsx index 9394102..1c99469 100644 --- a/src/utils/PathOperate/index.jsx +++ b/src/utils/PathOperate/index.jsx @@ -24,7 +24,15 @@ export function fileNameFormat(dir,fileName,ext){ export function fileDirFormat(dir,fileName){ return dir+pathOperate.sep+fileName } +export function fileDirFormatArray(dirArray){ + // todo 不同系统处理 + return pathOperate.sep+dirArray.join(pathOperate.sep) +} export function fullFileNameFormat(dir,fileName){ return pathOperate.format({"dir":dir,"base":fileName}) -} \ No newline at end of file +} + +export function filePathSplit(filePath){ + return filePath.split(pathOperate.sep) +}