diff --git a/public/locales/en-US/dashboard.json b/public/locales/en-US/dashboard.json index 5d956e6..8296c3e 100644 --- a/public/locales/en-US/dashboard.json +++ b/public/locales/en-US/dashboard.json @@ -112,6 +112,14 @@ "retryDelayDes": "Initial delay time (seconds) for task retries." }, "settings": { + "headlessFooter": "Landing page footer", + "headlessFooterDes": "Custom HTML content displayed at the bottom of the login, sign up and callback result pages.", + "headlessBottom": "Landing page bottom", + "headlessBottomDes": "Custom HTML content displayed at the bottom of the login, sign up and callback result pages.", + "customHTML": "Custom HTML", + "customHTMLDes": "Insert custom HTML content at the preset position of the site.", + "sidebarBottom": "Sidebar bottom", + "sidebarBottomDes": "Custom HTML content displayed at the bottom of the sidebar.", "addNavItem": "Add navigation item", "customNavItems": "Custom sidebar items", "customNavItemsDes": "You can add custom items to the sidebar, and users will be redirected to the corresponding link when clicked.", diff --git a/public/locales/ja-JP/dashboard.json b/public/locales/ja-JP/dashboard.json index 88759a1..e5e459a 100644 --- a/public/locales/ja-JP/dashboard.json +++ b/public/locales/ja-JP/dashboard.json @@ -112,6 +112,14 @@ "retryDelayDes": "タスク再試行の初期遅延時間(秒)" }, "settings": { + "headlessFooter": "ログインセッションページの下部", + "headlessFooterDes": "ユーザーがログイン、登録、コールバック結果などのページの下部に表示するカスタム HTML コンテンツ。", + "headlessBottom": "ログインセッションページの主体下部", + "headlessBottomDes": "ユーザーがログイン、登録、コールバック結果などのページの主体ボックスの下部に表示するカスタム HTML コンテンツ。", + "customHTML": "カスタム HTML", + "customHTMLDes": "サイトの既定の位置にカスタム HTML コンテンツを挿入します。", + "sidebarBottom": "サイドバーの下部", + "sidebarBottomDes": "サイドバーの下部に表示するカスタム HTML コンテンツ。", "addNavItem": "ナビゲーション項目を追加", "customNavItems": "サイドナビゲーションバーのカスタマイズ", "customNavItemsDes": "左側のナビゲーションバーにカスタム項目を追加できます。ユーザーがクリックすると、対応するリンクに移動します。", diff --git a/public/locales/zh-CN/dashboard.json b/public/locales/zh-CN/dashboard.json index 5786cce..ae9b42f 100644 --- a/public/locales/zh-CN/dashboard.json +++ b/public/locales/zh-CN/dashboard.json @@ -112,6 +112,14 @@ "retryDelayDes": "任务重试的初始延迟时间(秒)。" }, "settings": { + "headlessFooter": "登录会话页面底部", + "headlessFooterDes": "用户登录、注册、回调结果等页面底部展示的自定义 HTML 内容。", + "headlessBottom": "登录会话页面主体底部", + "headlessBottomDes": "用户登录、注册、回调结果等页面主体框底部展示的自定义 HTML 内容。", + "customHTML": "自定义 HTML", + "customHTMLDes": "在站点的预设位置插入展示自定义的 HTML 内容。", + "sidebarBottom": "侧边栏底部", + "sidebarBottomDes": "在侧边栏底部展示的自定义 HTML 内容。", "addNavItem": "添加导航条目", "customNavItems": "自定义侧边导航栏", "customNavItemsDes": "你可以在左侧导航栏中添加自定义的条目,用户点击后会跳转到对应的链接。", diff --git a/public/locales/zh-TW/dashboard.json b/public/locales/zh-TW/dashboard.json index 907abc8..ed070b7 100644 --- a/public/locales/zh-TW/dashboard.json +++ b/public/locales/zh-TW/dashboard.json @@ -112,6 +112,14 @@ "retryDelayDes": "任務重試的初始延遲時間(秒)。" }, "settings": { + "headlessFooter": "登入會話頁面底部", + "headlessFooterDes": "使用者登入、註冊、回調結果等頁面底部展示的自訂 HTML 內容。", + "headlessBottom": "登入會話頁面主體底部", + "headlessBottomDes": "使用者登入、註冊、回調結果等頁面主體框底部展示的自訂 HTML 內容。", + "customHTML": "自訂 HTML", + "customHTMLDes": "在站點的預設位置插入展示自訂的 HTML 內容。", + "sidebarBottom": "側邊欄底部", + "sidebarBottomDes": "在側邊欄底部展示的自訂 HTML 內容。", "addNavItem": "新增導航條目", "customNavItems": "自定義側邊導航欄", "customNavItemsDes": "你可以在側邊導航欄中新增自定義的條目,用戶點擊後會跳轉到對應的鏈接。", diff --git a/src/api/site.ts b/src/api/site.ts index d329095..d3ecb89 100644 --- a/src/api/site.ts +++ b/src/api/site.ts @@ -43,6 +43,7 @@ export interface SiteConfig { thumbnail_height?: number; custom_props?: CustomProps[]; custom_nav_items?: CustomNavItem[]; + custom_html?: CustomHTML; } export interface CaptchaResponse { @@ -55,3 +56,9 @@ export interface CustomNavItem { url: string; icon: string; } + +export interface CustomHTML { + headless_footer?: string; + headless_bottom?: string; + sidebar_bottom?: string; +} diff --git a/src/component/Admin/Settings/Appearance/Appearance.tsx b/src/component/Admin/Settings/Appearance/Appearance.tsx index 8d881ec..f9a8d29 100644 --- a/src/component/Admin/Settings/Appearance/Appearance.tsx +++ b/src/component/Admin/Settings/Appearance/Appearance.tsx @@ -3,6 +3,7 @@ import { useContext } from "react"; import { useTranslation } from "react-i18next"; import { SettingSection } from "../Settings"; import { SettingContext } from "../SettingWrapper"; +import CustomHTML from "./CustomHTML"; import CustomNavItems from "./CustomNavItems"; import ThemeOptions from "./ThemeOptions"; @@ -27,6 +28,9 @@ const Appearance = () => { onChange={(value: string) => setSettings({ custom_nav_items: value })} /> + + + ); diff --git a/src/component/Admin/Settings/Appearance/CustomHTML.tsx b/src/component/Admin/Settings/Appearance/CustomHTML.tsx new file mode 100644 index 0000000..e8bef55 --- /dev/null +++ b/src/component/Admin/Settings/Appearance/CustomHTML.tsx @@ -0,0 +1,249 @@ +import { LoadingButton } from "@mui/lab"; +import { + Box, + CircularProgress, + Container, + FormControl, + Grid, + Grid2, + Paper, + Stack, + Typography, + useTheme, +} from "@mui/material"; +import { Suspense, useContext } from "react"; +import { useTranslation } from "react-i18next"; +import { OutlineIconTextField } from "../../../Common/Form/OutlineIconTextField"; +import Logo from "../../../Common/Logo"; +import DrawerHeader from "../../../Frame/NavBar/DrawerHeader"; +import { SideNavItemComponent } from "../../../Frame/NavBar/PageNavigation"; +import StorageSummary from "../../../Frame/NavBar/StorageSummary"; +import PoweredBy from "../../../Frame/PoweredBy"; +import CloudDownload from "../../../Icons/CloudDownload"; +import CloudDownloadOutlined from "../../../Icons/CloudDownloadOutlined"; +import CubeSync from "../../../Icons/CubeSync"; +import CubeSyncFilled from "../../../Icons/CubeSyncFilled"; +import MailOutlined from "../../../Icons/MailOutlined"; +import PhoneLaptop from "../../../Icons/PhoneLaptop"; +import PhoneLaptopOutlined from "../../../Icons/PhoneLaptopOutlined"; +import SettingForm from "../../../Pages/Setting/SettingForm"; +import MonacoEditor from "../../../Viewers/CodeViewer/MonacoEditor"; +import { SettingContext } from "../SettingWrapper"; +import { NoMarginHelperText } from "../Settings"; + +export interface CustomHTMLProps {} + +const HeadlessFooterPreview = ({ footer, bottom }: { footer?: string; bottom?: string }) => { + const { t } = useTranslation("application"); + return ( + + theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900], + }} + > + + + + `${theme.spacing(2)} ${theme.spacing(3)} ${theme.spacing(3)}`, + }} + > + +
+ + {t("login.siginToYourAccount")} + + } /> + + + {t("login.continue")} + + {bottom && ( + +
+ + )} + +
+ +
+ + {footer && ( + +
+ + )} + + + + ); +}; + +const SidebarBottomPreview = ({ bottom }: { bottom?: string }) => { + return ( + + theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900], + }} + > + + + + + + + + + {bottom && ( + +
+ + )} + + + ); +}; + +const CustomHTML = ({}: CustomHTMLProps) => { + const { t } = useTranslation("dashboard"); + const { formRef, setSettings, values } = useContext(SettingContext); + const theme = useTheme(); + + return ( + + + {t("settings.customHTML")} + + + {t("settings.customHTMLDes")} + + + + + + } + > + + }> + setSettings({ headless_footer_html: e as string })} + /> + + {t("settings.headlessFooterDes")} + + + + + + } + > + + }> + setSettings({ headless_bottom_html: e as string })} + /> + + {t("settings.headlessBottomDes")} + + + + + + } + > + + }> + setSettings({ sidebar_bottom_html: e as string })} + /> + + {t("settings.sidebarBottomDes")} + + + + + ); +}; + +export default CustomHTML; diff --git a/src/component/Admin/Settings/Settings.tsx b/src/component/Admin/Settings/Settings.tsx index 6f5aa3a..25b1d6a 100644 --- a/src/component/Admin/Settings/Settings.tsx +++ b/src/component/Admin/Settings/Settings.tsx @@ -300,7 +300,16 @@ const Settings = () => { )} {tab === SettingsPageTab.Appearance && ( - + )} diff --git a/src/component/Frame/HeadlessFrame.tsx b/src/component/Frame/HeadlessFrame.tsx index cd6df13..53e3e61 100644 --- a/src/component/Frame/HeadlessFrame.tsx +++ b/src/component/Frame/HeadlessFrame.tsx @@ -23,6 +23,9 @@ const Loading = () => { const HeadlessFrame = () => { const loading = useAppSelector((state) => state.globalState.loading.headlessFrame); + const { headless_footer, headless_bottom, sidebar_bottom } = useAppSelector( + (state) => state.siteConfig.basic?.config?.custom_html ?? {}, + ); const dispatch = useAppDispatch(); let navigation = useNavigation(); @@ -66,6 +69,11 @@ const HeadlessFrame = () => { }} > + {headless_bottom && ( + +
+ + )} {(loading || navigation.state !== "idle") && }
@@ -73,6 +81,11 @@ const HeadlessFrame = () => {
+ {headless_footer && ( + +
+ + )} diff --git a/src/component/Frame/NavBar/AppDrawer.tsx b/src/component/Frame/NavBar/AppDrawer.tsx index ed9561d..2089923 100644 --- a/src/component/Frame/NavBar/AppDrawer.tsx +++ b/src/component/Frame/NavBar/AppDrawer.tsx @@ -1,14 +1,15 @@ import { Box, Drawer, Popover, PopoverProps, Stack, useMediaQuery, useTheme } from "@mui/material"; +import { useContext, useRef } from "react"; import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; -import DrawerHeader from "./DrawerHeader.tsx"; +import SessionManager from "../../../session"; import TreeNavigation from "../../FileManager/TreeView/TreeNavigation.tsx"; +import { PageVariant, PageVariantContext } from "../NavBarFrame.tsx"; +import DrawerHeader from "./DrawerHeader.tsx"; import PageNavigation, { AdminPageNavigation } from "./PageNavigation.tsx"; import StorageSummary from "./StorageSummary.tsx"; -import { useContext, useRef } from "react"; -import SessionManager from "../../../session"; -import { PageVariant, PageVariantContext } from "../NavBarFrame.tsx"; const DrawerContent = () => { + const { sidebar_bottom } = useAppSelector((state) => state.siteConfig.basic?.config?.custom_html ?? {}); const scrollRef = useRef(); const user = SessionManager.currentLoginOrNull(); const theme = useTheme(); @@ -38,6 +39,11 @@ const DrawerContent = () => { )} {isDashboard && } + {sidebar_bottom && ( + +
+ + )} ); diff --git a/src/component/Frame/NavBar/DrawerHeader.tsx b/src/component/Frame/NavBar/DrawerHeader.tsx index 1f6f972..ef7c3ed 100644 --- a/src/component/Frame/NavBar/DrawerHeader.tsx +++ b/src/component/Frame/NavBar/DrawerHeader.tsx @@ -1,8 +1,8 @@ +import { ChevronLeft } from "@mui/icons-material"; import { Box, Fade, IconButton, styled, useMediaQuery, useTheme } from "@mui/material"; +import { useState } from "react"; import { setDrawerOpen } from "../../../redux/globalStateSlice.ts"; import { useAppDispatch } from "../../../redux/hooks.ts"; -import { ChevronLeft } from "@mui/icons-material"; -import { useState } from "react"; import Logo from "../../Common/Logo.tsx"; export const DrawerHeaderContainer = styled("div")(({ theme }) => ({ @@ -14,7 +14,7 @@ export const DrawerHeaderContainer = styled("div")(({ theme }) => ({ justifyContent: "flex-end", })); -const DrawerHeader = () => { +const DrawerHeader = ({ disabled }: { disabled?: boolean }) => { const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down("sm")); const dispatch = useAppDispatch(); @@ -22,7 +22,10 @@ const DrawerHeader = () => { const [showCollapse, setShowCollapse] = useState(false); return ( - setShowCollapse(true)} onMouseLeave={() => setShowCollapse(false)}> + setShowCollapse(disabled ? false : true)} + onMouseLeave={() => setShowCollapse(false)} + > { sx={{ height: 20, }} - src={ - theme.palette.mode === "dark" - ? "https://docs.cloudreve.org/logo_light.svg" - : "https://docs.cloudreve.org/logo.svg" - } - alt="Cloudreve" + src={theme.palette.mode === "dark" ? LogoIconDark : LogoIcon} /> diff --git a/src/component/Frame/assets/logo.svg b/src/component/Frame/assets/logo.svg new file mode 100644 index 0000000..de7c93d --- /dev/null +++ b/src/component/Frame/assets/logo.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/component/Frame/assets/logo_light.svg b/src/component/Frame/assets/logo_light.svg new file mode 100644 index 0000000..7bdb432 --- /dev/null +++ b/src/component/Frame/assets/logo_light.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +