From 003fbaade5be29f08027cfd555677e5268bef7df Mon Sep 17 00:00:00 2001 From: yuanyxh <15766118362@139.com> Date: Fri, 10 May 2024 18:11:02 +0800 Subject: [PATCH] feat: before complete add md file --- src/components/AddFileModal/AddFileModal.tsx | 110 +++++++++ src/components/ContextMenu/ContextMenu.tsx | 2 +- src/components/index.ts | 1 + src/filehandle/components/FileContent.tsx | 118 +-------- .../md_editor/component/MDSidebar.tsx | 231 ++++++++++++++---- .../component/styles/MDSidebar.module.less | 19 +- 6 files changed, 320 insertions(+), 161 deletions(-) create mode 100644 src/components/AddFileModal/AddFileModal.tsx diff --git a/src/components/AddFileModal/AddFileModal.tsx b/src/components/AddFileModal/AddFileModal.tsx new file mode 100644 index 0000000..86f38e7 --- /dev/null +++ b/src/components/AddFileModal/AddFileModal.tsx @@ -0,0 +1,110 @@ +import { useMemo, useRef, useState } from 'react'; + +import type { InputRef } from 'antd'; +import { Input, type InputProps, Modal, Typography } from 'antd'; + +import { sleep, validateFileName } from '@/utils'; + +import { FileType } from '@/filehandle/utils/fileManager'; + +function AddFileModal({ + open, + type, + names, + onOk, + onCancel +}: { + open: boolean; + type: FileType; + names: string[]; + onOk: (name: string, type: FileType) => any; + onCancel: () => any; +}) { + const [inputStatus, setInputStatus] = useState<{ + name: string; + status: InputProps['status']; + message: string; + }>({ + name: '', + status: '', + message: '' + }); + + const inputRef = useRef(null); + + useMemo(() => { + if (open) { + // Delay to make the input box get the focus + sleep(100, () => inputRef.current?.focus()); + } + }, [open]); + + const handleInputChange = async (e: React.ChangeEvent) => { + const value = e.target.value.trim(); + + if (value !== '' && !validateFileName(value)) { + setInputStatus({ + name: value, + status: 'error', + message: '无效的文件名' + }); + } else if (names.some((c) => c === value)) { + setInputStatus({ + name: value, + status: 'error', + message: '当前目录已包含同名的文件' + }); + } else { + setInputStatus({ name: value, status: '', message: '' }); + } + }; + + const handleKeyUp = (e: React.KeyboardEvent) => { + if (e.key.toLocaleLowerCase() !== 'enter') { + return false; + } + + handleOk(); + }; + + const handleOk = () => { + if (inputStatus.name === '' || inputStatus.status === 'error') { + return false; + } + + onOk(inputStatus.name, type); + + setInputStatus({ name: '', status: '', message: '' }); + }; + + return ( + { + setInputStatus({ name: '', status: '', message: '' }); + onCancel(); + }} + > + + + {inputStatus.message ? ( + + {inputStatus.message} + + ) : null} + + ); +} + +export default AddFileModal; diff --git a/src/components/ContextMenu/ContextMenu.tsx b/src/components/ContextMenu/ContextMenu.tsx index ab03b80..8e9a57e 100644 --- a/src/components/ContextMenu/ContextMenu.tsx +++ b/src/components/ContextMenu/ContextMenu.tsx @@ -86,7 +86,7 @@ const ContextMenu: React.FC> = (props) => { bindElement.removeEventListener('contextmenu', onContextMenu); cancelGlobalListener(); }; - }, []); + }, [menu]); return (
any; - onCancel: () => any; -}) { - const [inputStatus, setInputStatus] = useState<{ - name: string; - status: InputProps['status']; - message: string; - }>({ - name: '', - status: '', - message: '' - }); - - const { children } = useContext(FileSystemContext); - - const inputRef = useRef(null); - - useMemo(() => { - if (open) { - // Delay to make the input box get the focus - sleep(100, () => inputRef.current?.focus()); - } - }, [open]); - - const handleInputChange = async (e: React.ChangeEvent) => { - const value = e.target.value.trim(); - - if (value !== '' && !validateFileName(value)) { - setInputStatus({ - name: value, - status: 'error', - message: '无效的文件名' - }); - } else if (children.some((c) => c.name === value)) { - setInputStatus({ - name: value, - status: 'error', - message: '当前目录已包含同名的文件' - }); - } else { - setInputStatus({ name: value, status: '', message: '' }); - } - }; - - const handleKeyUp = (e: React.KeyboardEvent) => { - if (e.key.toLocaleLowerCase() !== 'enter') { - return false; - } - - handleOk(); - }; - - const handleOk = () => { - if (inputStatus.name === '' || inputStatus.status === 'error') { - return false; - } - - onOk(inputStatus.name, type); - - setInputStatus({ name: '', status: '', message: '' }); - }; - - return ( - { - setInputStatus({ name: '', status: '', message: '' }); - onCancel(); - }} - > - - - {inputStatus.message ? ( - - {inputStatus.message} - - ) : null} - - ); -} - function MountWebdavModal(props: { open: boolean; close(): void }) { const { open, close } = props; @@ -289,7 +180,7 @@ const FileContent: React.FC = (props) => { const root = fileLinked?.root?.value; - const titleRef = useRef(0); + const titleRef = useRef(FileType.FILE); const sectionRef = useRef(null); const [isModalOpen, setModalOpen] = useState(false); @@ -476,6 +367,7 @@ const FileContent: React.FC = (props) => { c.name)} onOk={handleOk} onCancel={handleCancel} /> diff --git a/src/filehandle/md_editor/component/MDSidebar.tsx b/src/filehandle/md_editor/component/MDSidebar.tsx index 4918527..7e41cd3 100644 --- a/src/filehandle/md_editor/component/MDSidebar.tsx +++ b/src/filehandle/md_editor/component/MDSidebar.tsx @@ -1,6 +1,6 @@ import { useMemo, useRef, useState } from 'react'; -import { Layout } from 'antd'; +import { Input, Layout } from 'antd'; import classNames from 'classnames'; @@ -14,7 +14,7 @@ import { getChildren } from '@/filehandle/utils/fileManager'; -import { Icon } from '@/components'; +import { ContextMenu, Icon } from '@/components'; import styles from './styles/MDSidebar.module.less'; @@ -24,18 +24,35 @@ interface ISidebarProps { onSelect(handle: FH): void; } -interface ExtendFileInfo extends FileInfo { +type ExtendFileInfo = FileInfo & { id: string; + parent?: ExtendFileInfo; children?: ExtendFileInfo[]; -} +}; + +const ADD_KEY = '.$<>/\\.#$%' + uuid(); const { Sider } = Layout; -function wrapperFileItem(file: FileInfo): ExtendFileInfo { - return { ...file, id: uuid() }; +function wrapperFileItem( + parent: ExtendFileInfo, + file: FileInfo +): ExtendFileInfo { + return { ...file, id: uuid(), parent }; } -async function getDeepChildren(handle: DH): Promise { +async function getDeepChildren( + handle: DH, + parent?: ExtendFileInfo +): Promise { + parent = parent || { + id: uuid(), + name: handle.name, + type: FileType.DIRECTORY, + icon: '', + handle + }; + const children = await getChildren(handle); return Promise.all( @@ -44,9 +61,12 @@ async function getDeepChildren(handle: DH): Promise { (value) => value.type === FileType.DIRECTORY || value.ext === '.md' ) .map(async (child) => { - const newChild = wrapperFileItem(child); + const newChild = wrapperFileItem(parent, child); if (child.type === FileType.DIRECTORY) { - newChild.children = await getDeepChildren(child.handle as DH); + newChild.children = await getDeepChildren( + child.handle as DH, + newChild + ); } return newChild; @@ -54,16 +74,39 @@ async function getDeepChildren(handle: DH): Promise { ); } +function AddFileInput({ file }: { file: ExtendFileInfo }) { + const DEFAULT_NAME = 'default'; + const handleAdd = () => {}; + + file; + DEFAULT_NAME; + + return ( + { + e.stopPropagation(); + e.preventDefault(); + }} + onKeyUp={handleAdd} + onBlur={handleAdd} + /> + ); +} + function Menu({ activeId, changed, items, - onItemClick + onItemClick, + onItemContextMenu }: { activeId: string; changed: boolean; items: ExtendFileInfo[]; onItemClick: (file: ExtendFileInfo) => void; + onItemContextMenu: (file: ExtendFileInfo) => void; }) { const [expands, setExpands] = useState([]); @@ -90,31 +133,42 @@ function Menu({ [styles.expand]: expands.includes(item.id) })} > - e.preventDefault()} title={item.name}> - - +
handleSetExpands(item.id) + : () => handleSwitchFile(item) + } + > + {item.children ? ( + + ) : null} + {item.name} +
+ + )} {item.children ? ( ) : null} @@ -136,7 +191,10 @@ export const Sidebar: React.FC> = (props) => { const [list, setList] = useState([]); const [activeId, setActiveId] = useState(''); + const [selection, setSelection] = useState([]); + const queryingRef = useRef(false); + const siderRef = useRef(null); useMemo(() => { if (queryingRef.current) { @@ -144,7 +202,6 @@ export const Sidebar: React.FC> = (props) => { } queryingRef.current = true; - getDeepChildren(handle) .then((list) => { setList(list); @@ -164,14 +221,102 @@ export const Sidebar: React.FC> = (props) => { } }; + const handleHide = () => { + selection.length && setSelection([]); + }; + + const handleContextMenu = (file: ExtendFileInfo) => { + setSelection([file]); + }; + + const addExtendFileInfo = (type: FileType) => { + const curr = selection[0]; + + const child = { name: ADD_KEY, id: uuid(), icon: '' }; + + if (curr && curr.type === FileType.DIRECTORY) { + (curr.children || (curr.children = [])).unshift({ + ...child, + handle: curr.handle, + type + }); + } else if (curr && curr.parent) { + const parent = curr.parent; + (parent.children || (parent.children = [])).unshift({ + ...child, + handle: parent.handle, + type + }); + } else { + list.unshift({ + ...child, + handle, + type + }); + } + + setList([...list]); + }; + + const handleAddFile = () => { + addExtendFileInfo(FileType.FILE); + }; + + const handleAddDirectory = () => { + addExtendFileInfo(FileType.DIRECTORY); + }; + + const handleDeleteFile = () => {}; + return ( - - - + <> + +
+ +
+ + siderRef.current!} + getBindElement={() => siderRef.current!} + onHide={handleHide} + menu={[ + { + name: '新建文件', + icon: , + onClick: handleAddFile + }, + { + name: '新建文件夹', + icon: ( + + ), + onClick: handleAddDirectory + }, + { + name: '删除', + icon: ( + + ), + style: { + display: selection.length === 0 ? 'none' : void 0 + }, + onClick: handleDeleteFile + } + ]} + /> +
+ ); }; diff --git a/src/filehandle/md_editor/component/styles/MDSidebar.module.less b/src/filehandle/md_editor/component/styles/MDSidebar.module.less index a41b1f2..879fb9c 100644 --- a/src/filehandle/md_editor/component/styles/MDSidebar.module.less +++ b/src/filehandle/md_editor/component/styles/MDSidebar.module.less @@ -1,12 +1,23 @@ .sidebar { height: 100%; - overflow: auto; + font-size: 15px; box-shadow: 0.5px 0 rgb(0 0 0 / 12%); + transform: scale(1); + + :global(.ant-layout-sider-children) { + height: 100%; + } - &::-webkit-scrollbar { - width: 3px; - height: 3px; + .scrollbar { + width: 100%; + height: 100%; + overflow: auto; + + &::-webkit-scrollbar { + width: 3px; + height: 3px; + } } .list {