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 @@
+
+
+