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")}