diff --git a/index.html b/index.html index 47c93d0..7d859af 100644 --- a/index.html +++ b/index.html @@ -3,24 +3,47 @@ - + - + + Loading...
diff --git a/package.json b/package.json index f59d932..bc910f7 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "@types/react-dom": "^17.0.11", "@types/react-router-dom": "^5.1.8", "@types/streamsaver": "^2.0.1", + "@vitejs/plugin-legacy": "^1.8.1", "@vitejs/plugin-react-refresh": "^1.3.1", "typescript": "^4.5.4", "vite": "^2.7.10", diff --git a/src/pages/list/context.tsx b/src/pages/list/context.tsx index 9917adc..94156ba 100644 --- a/src/pages/list/context.tsx +++ b/src/pages/list/context.tsx @@ -36,6 +36,7 @@ export interface Meta { driver: string; upload: boolean; total: number; + readme: string; } export interface PathResp { @@ -68,7 +69,8 @@ type TypeType = | "error" | "loading" | "unauthorized" - | "nexting"; + | "nexting" + | "search"; interface Sort { orderBy?: "name" | "updated_at" | "size"; @@ -132,7 +134,7 @@ export const IContext = createContext({ selectFiles: [], setSelectFiles: () => {}, setType: () => {}, - meta: { driver: "", upload: false, total: 0 }, + meta: { driver: "", upload: false, total: 0, readme: "" }, setMeta: () => {}, loggedIn: false, page: { page_num: 1, page_size: 30 }, @@ -263,6 +265,7 @@ const IContextProvider = (props: any) => { driver: "", upload: false, total: 0, + readme: "", }); const [loggedIn, setLoggedIn] = React.useState(false); if (!settingLoaded) { diff --git a/src/pages/list/index.tsx b/src/pages/list/index.tsx index efd7de7..06ac247 100644 --- a/src/pages/list/index.tsx +++ b/src/pages/list/index.tsx @@ -47,11 +47,14 @@ const Do = (props: any) => { const history = useHistory(); const location = useLocation(); const toast = useToast(); - const { path,cancelPath } = useApi(); + const { path, cancelPath } = useApi(); const refresh = useSyncCallback(() => { if (!settingLoaded) { return; } + if (switchToSearch()) { + return; + } cancelPath(); console.log("refresh"); console.log(page); @@ -159,6 +162,20 @@ const Do = (props: any) => { useChangeEffect(() => { nextPage(); }, [page]); + const switchToSearch = () => { + const query = new URLSearchParams(location.search); + const search = query.get("s"); + if (search) { + setType("search"); + return true; + } + return false; + }; + useEffect(() => { + if(!switchToSearch()){ + allRefresh(); + } + }, [location.search]); const { isOpen, onClose, onOpen } = useDisclosure(); const initialRef = React.useRef(); useEffect(() => { diff --git a/src/pages/list/layout/header.tsx b/src/pages/list/layout/header.tsx index de55872..7791570 100644 --- a/src/pages/list/layout/header.tsx +++ b/src/pages/list/layout/header.tsx @@ -9,8 +9,9 @@ import { Tooltip, useColorModeValue, Box, + SlideFade, } from "@chakra-ui/react"; -import React, { useContext, useRef } from "react"; +import React, { useContext, useRef, useState } from "react"; import { IContext } from "../context"; import { FaListUl } from "react-icons/fa"; // import { AiTwotoneCopy } from "react-icons/ai"; @@ -25,6 +26,7 @@ import { Link, useLocation } from "react-router-dom"; import { copyToClip } from "../../../utils/copy-clip"; import useFileUrl from "../../../hooks/useFileUrl"; import Uploader, { UploaderHandle } from "./uploader"; +import Search from "./search"; const Header = () => { const { t } = useTranslation(); @@ -46,16 +48,14 @@ const Header = () => { logos.split(",").shift(), logos.split(",").pop() ) as string; - const { pathname } = useLocation(); const uploadRef = useRef(null); + const [isSearch, setIsSearch] = useState(false); return ( {logo.includes("http") ? ( - } + fallback={} rounded="lg" h="44px" w="auto" @@ -66,113 +66,117 @@ const Header = () => { )} - {type === "file" && ( - - { - if (type === "file") { - let url = fileUrl(); - window.open(url, "_blank"); - return; - } - // if (multiSelect) { - // downPack(selectFiles); - // return; - // } - // if (type === "folder") { - // downPack(files); - // return; - // } - }} - /> - + {["folder", "search"].includes(type) && ( + )} - {type === "folder" && - !getSetting("no upload").split(",").includes(meta.driver) && - (meta.upload || loggedIn) && ( - + {!isSearch && ( + + + {type === "file" && ( + + { + if (type === "file") { + let url = fileUrl(); + window.open(url, "_blank"); + return; + } + }} + /> + + )} + {type === "folder" && + !getSetting("no upload").split(",").includes(meta.driver) && + (meta.upload || loggedIn) && ( + + + { + uploadRef.current!.upload(); + }} + /> + + + + )} + {type !== "error" && ( + + { + let content = ""; + if (type === "file") { + content = fileUrl(); + } else { + let files_ = files; + if (multiSelect) { + files_ = selectFiles; + } + content = files_ + .filter((file) => file.type !== 1) + .map((file) => { + return fileUrl(file); + }) + .join("\n"); + } + copyToClip(content); + toast({ + title: t("Copied"), + status: "success", + duration: 3000, + isClosable: true, + }); + }} + /> + + )} { - uploadRef.current!.upload(); + setShow!(show === "list" ? "grid" : "list"); + localStorage.setItem( + "show", + show === "list" ? "grid" : "list" + ); }} + as={show === "list" ? BsFillGridFill : FaListUl} /> - - - )} - {type !== "error" && ( - - { - let content = ""; - if (type === "file") { - content = fileUrl(); - } else { - let files_ = files; - if (multiSelect) { - files_ = selectFiles; - } - content = files_ - .filter((file) => file.type !== 1) - .map((file) => { - return fileUrl(file); - }) - .join("\n"); - } - copyToClip(content); - toast({ - title: t("Copied"), - status: "success", - duration: 3000, - isClosable: true, - }); - }} - /> - + + )} - - { - setShow!(show === "list" ? "grid" : "list"); - localStorage.setItem("show", show === "list" ? "grid" : "list"); - }} - as={show === "list" ? BsFillGridFill : FaListUl} - /> - ); diff --git a/src/pages/list/layout/index.tsx b/src/pages/list/layout/index.tsx index 2d55915..022ffb5 100644 --- a/src/pages/list/layout/index.tsx +++ b/src/pages/list/layout/index.tsx @@ -1,14 +1,7 @@ -import React, { - lazy, - Suspense, - useContext, - useEffect, - useMemo, -} from "react"; +import React, { lazy, Suspense, useContext, useEffect, useMemo } from "react"; import { Box, useColorModeValue, - Spinner, Center, VStack, @@ -21,7 +14,8 @@ import Nav from "./nav"; import Error from "./error"; import Markdown from "../preview/markdown"; import Overlay from "../../../components/overlay"; -import { IContext,File as File_ } from "../context"; +import { IContext, File as File_ } from "../context"; +import Results from "./results"; const Files = lazy(() => import("./files")); const File = lazy(() => import("./file")); @@ -29,7 +23,7 @@ const File = lazy(() => import("./file")); const KuttyHero = () => { // console.log("KuttyHero"); const bgColor = useColorModeValue("white", "gray.700"); - + const { t } = useTranslation(); const { getSetting, @@ -39,30 +33,34 @@ const KuttyHero = () => { type, msg, files, + meta, } = useContext(IContext); const readme = useMemo(() => { if (type === "file") { return undefined; } + const readmeFile: File_ = { + name: "README.md", + size: 0, + type: -1, + driver: "local", + updated_at: "", + thumbnail: "", + url: meta.readme, + }; + if (meta.readme) { + return readmeFile; + } const file = files.find((file) => file.name.toLowerCase() === "readme.md"); - if ( - file === undefined && - location.pathname === "/" && - getSetting("home readme url") - ) { - const homeReadmeFile: File_ = { - name: "README.md", - size: 0, - type: -1, - driver: "local", - updated_at: "", - thumbnail: "", - url: getSetting("home readme url"), - }; - return homeReadmeFile; + if (file) { + return file; + } + if (getSetting("global readme url")) { + readmeFile.url = getSetting("global readme url"); + return readmeFile; } - return file; + return undefined; }, [files, type, settingLoaded]); return ( @@ -101,6 +99,8 @@ const KuttyHero = () => { ) : type === "file" ? ( + ) : type === "search" ? ( + ) : ( )} @@ -108,7 +108,7 @@ const KuttyHero = () => { )} - {type !== "loading" && readme && ( + {type === "folder" && readme && ( { + const location = useLocation(); + const [loading, setLoading] = useState(true); + const { getSetting } = useContext(IContext); + const toast = useToast(); + const [results, setResults] = useState([]); + const search = () => { + const searchParams = new URLSearchParams(location.search); + const path = location.pathname; + const keyword = searchParams.get("s"); + if(!keyword) return; + setLoading(true); + request + .post("search", { + path, + keyword, + }) + .then((resp) => { + setLoading(false); + const res: Resp = resp.data; + if (res.code === 200) { + setResults(res.data); + } else { + toast({ + title: res.message, + status: "error", + duration: 3000, + isClosable: true, + }); + } + }); + }; + useEffect(() => { + search(); + }, [location.search]); + const { t } = useTranslation(); + if (loading) { + return ( +
+ +
+ ); + } + return ( + + + {[ + { name: "name", base: 2 / 3, md: "50%", textAlign: "left" }, + { name: "size", base: 1 / 3, md: 1 / 6, textAlign: "right" }, + { name: "path", base: 0, md: 1 / 3, textAlign: "right" }, + ].map((item) => { + return ( + + + {t(item.name)} + + + ); + })} + + {results.map((result) => { + return ; + })} + + ); +}; + +export default Results; diff --git a/src/pages/list/layout/results/result.tsx b/src/pages/list/layout/results/result.tsx new file mode 100644 index 0000000..07de19c --- /dev/null +++ b/src/pages/list/layout/results/result.tsx @@ -0,0 +1,80 @@ +import { + Box, + Flex, + HStack, + Icon, + LinkBox, + LinkOverlay, + Text, + Tooltip, +} from "@chakra-ui/react"; +import React, { useContext } from "react"; +import { Link } from "react-router-dom"; +import { getFileSize, pathJoin } from "~/utils/file"; +import getIcon from "~/utils/icon"; +import { Result } from "."; +import { IContext } from "../../context"; + +const Result = (file: Result) => { + const { getSetting } = useContext(IContext); + + return ( + + + + + + + + + {file.name} + + + + {getFileSize(file.size)} + + + {file.path} + + + + + + + ); +}; + +export default Result; diff --git a/src/pages/list/layout/search.tsx b/src/pages/list/layout/search.tsx new file mode 100644 index 0000000..f0f481c --- /dev/null +++ b/src/pages/list/layout/search.tsx @@ -0,0 +1,88 @@ +import { Box, HStack, Icon, Input, Tooltip } from "@chakra-ui/react"; +import React, { useContext, useEffect, useRef, useState } from "react"; +import { IContext } from "../context"; +import { FcSearch } from "react-icons/fc"; +import { useTranslation } from "react-i18next"; +import { useHistory } from "react-router-dom"; + +interface SearchProps { + isSearch: boolean; + setIsSearch: (isSearch: boolean) => void; +} + +const Search = (props: SearchProps) => { + const inputRef = useRef(null); + const { t } = useTranslation(); + const { getSetting } = useContext(IContext); + const [keyword, setKeyword] = useState(""); + const search = () => { + // console.log(keyword); + history.push(`?s=${keyword}`); + }; + const history = useHistory(); + useEffect(() => { + if (props.isSearch) { + inputRef.current?.focus(); + } + const switchSearch = (e: KeyboardEvent) => { + if (["f", "F"].includes(e.key) && e.ctrlKey) { + props.setIsSearch(!props.isSearch); + e.preventDefault(); + } + }; + document.addEventListener("keydown", switchSearch); + const cancelSearch = (e: MouseEvent) => { + props.setIsSearch(false); + }; + document.addEventListener("click", cancelSearch); + return () => { + document.removeEventListener("keydown", switchSearch); + document.removeEventListener("click", cancelSearch); + }; + }, [props.isSearch]); + // if (!getSetting("enable search")) return null; + return ( + { + e.stopPropagation(); + }} + > + { + if (e.key === "Enter") { + search(); + } + }} + onChange={(e) => { + setKeyword(e.target.value); + }} + ref={inputRef} + /> + + + { + if (!props.isSearch) { + props.setIsSearch(true); + } else { + search(); + } + }} + as={FcSearch} + /> + + + ); +}; + +export default Search; diff --git a/src/pages/manage/accounts.tsx b/src/pages/manage/accounts.tsx index 14b3895..7cbeb6d 100644 --- a/src/pages/manage/accounts.tsx +++ b/src/pages/manage/accounts.tsx @@ -172,6 +172,7 @@ const Accounts = () => { React.useState(EmptyAccount); const [isEdit, setIsEdit] = React.useState(false); const [loading, setLoading] = React.useState(false); + const [addAccountLoading, setAddAccountLoading] = React.useState(false); const editDisclosure = useDisclosure(); const initialDrivers = () => { admin.get("drivers").then((resp) => { @@ -464,11 +465,14 @@ const Accounts = () => { {t("Paste")}