From 8e45f8ca6fa17f4014079f6160f725cfec5aaed9 Mon Sep 17 00:00:00 2001 From: naiba Date: Sun, 15 Dec 2024 15:08:57 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=90=8E=E5=8F=B0=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E4=BB=A3=E7=A0=81=20&=20=E5=90=8E=E7=AB=AF=E8=AF=AD?= =?UTF-8?q?=E8=A8=80=E4=BC=98=E5=85=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/settings.ts | 6 +-- src/hooks/useSetting.tsx | 3 +- src/lib/i18n.ts | 2 +- src/main.tsx | 1 - src/routes/root.tsx | 61 ++++++++++++++++++++++++++--- src/routes/settings.tsx | 84 ++++++++++++++++------------------------ 6 files changed, 92 insertions(+), 65 deletions(-) diff --git a/src/api/settings.ts b/src/api/settings.ts index ca74009..d891432 100644 --- a/src/api/settings.ts +++ b/src/api/settings.ts @@ -1,11 +1,7 @@ -import { ModelSettingForm, ModelSettingResponse } from "@/types" +import { ModelSettingForm } from "@/types" import { FetcherMethod, fetcher } from "./api" export const updateSettings = async (data: ModelSettingForm): Promise => { return fetcher(FetcherMethod.PATCH, `/api/v1/setting`, data) } - -export const getSettings = async (): Promise => { - return fetcher(FetcherMethod.GET, "/api/v1/setting", null) -} diff --git a/src/hooks/useSetting.tsx b/src/hooks/useSetting.tsx index 83013ac..cebccef 100644 --- a/src/hooks/useSetting.tsx +++ b/src/hooks/useSetting.tsx @@ -3,6 +3,5 @@ import { ModelSettingResponse } from "@/types" import useSWR from "swr" export default function useSetting() { - const { data } = useSWR("/api/v1/setting", swrFetcher) - return data + return useSWR("/api/v1/setting", swrFetcher) } diff --git a/src/lib/i18n.ts b/src/lib/i18n.ts index 7bc3ab9..616cdcf 100644 --- a/src/lib/i18n.ts +++ b/src/lib/i18n.ts @@ -22,7 +22,7 @@ const resources = { } const getStoredLanguage = () => { - return localStorage.getItem("language") || "zh-CN" + return localStorage.getItem("language") || "en-US" } i18n.use(initReactI18next).init({ diff --git a/src/main.tsx b/src/main.tsx index 5121ac2..dd07a08 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,4 +1,3 @@ -import { StrictMode } from "react" import { createRoot } from "react-dom/client" import { RouterProvider, createBrowserRouter } from "react-router-dom" diff --git a/src/routes/root.tsx b/src/routes/root.tsx index 50a6db6..437e02c 100644 --- a/src/routes/root.tsx +++ b/src/routes/root.tsx @@ -2,17 +2,68 @@ import Header from "@/components/header" import { ThemeProvider } from "@/components/theme-provider" import { Toaster } from "@/components/ui/sonner" import useSetting from "@/hooks/useSetting" -import { useEffect } from "react" +import i18n from "@/lib/i18n" +import { useCallback, useEffect } from "react" import { useTranslation } from "react-i18next" import { Outlet } from "react-router-dom" export default function Root() { const { t } = useTranslation() - const settings = useSetting() + const { data: settingData, error } = useSetting() useEffect(() => { - document.title = settings?.site_name || "哪吒监控 Nezha Monitoring" - }, [settings]) + document.title = settingData?.site_name || "哪吒监控 Nezha Monitoring" + }, [settingData]) + + const InjectContext = useCallback((content: string) => { + const tempDiv = document.createElement("div") + tempDiv.innerHTML = content + + const handlers: { [key: string]: (element: HTMLElement) => void } = { + SCRIPT: (element) => { + const script = document.createElement("script") + if ((element as HTMLScriptElement).src) { + script.src = (element as HTMLScriptElement).src + } else { + script.textContent = element.textContent + } + document.body.appendChild(script) + }, + STYLE: (element) => { + const style = document.createElement("style") + style.textContent = element.textContent + document.head.appendChild(style) + }, + DEFAULT: (element) => { + document.body.appendChild(element) + }, + } + + Array.from(tempDiv.childNodes).forEach((node) => { + if (node.nodeType === Node.ELEMENT_NODE) { + const element = node as HTMLElement + ; (handlers[element.tagName] || handlers.DEFAULT)(element) + } else if (node.nodeType === Node.TEXT_NODE) { + document.body.appendChild(document.createTextNode(node.textContent || "")) + } + }) + }, []) + + if (error) { + throw error + } + + if (!settingData) { + return null + } + + if (settingData?.language && !localStorage.getItem("language")) { + i18n.changeLanguage(settingData?.language) + } + + if (settingData?.custom_code_dashboard) { + InjectContext(settingData?.custom_code_dashboard) + } return ( @@ -24,7 +75,7 @@ export default function Root() {
- © 2019-2024 {t("nezha")} {settings?.version} + © 2019-2024 {t("nezha")} {settingData?.version}
diff --git a/src/routes/settings.tsx b/src/routes/settings.tsx index faa15c3..3d21384 100644 --- a/src/routes/settings.tsx +++ b/src/routes/settings.tsx @@ -1,4 +1,4 @@ -import { getSettings, updateSettings } from "@/api/settings" +import { updateSettings } from "@/api/settings" import { SettingsTab } from "@/components/settings-tab" import { Button } from "@/components/ui/button" import { Card, CardContent } from "@/components/ui/card" @@ -21,10 +21,11 @@ import { SelectValue, } from "@/components/ui/select" import { Textarea } from "@/components/ui/textarea" +import useSetting from "@/hooks/useSetting" import { asOptionalField } from "@/lib/utils" -import { ModelSettingResponse, nezhaLang, settingCoverageTypes } from "@/types" +import { nezhaLang, settingCoverageTypes } from "@/types" import { zodResolver } from "@hookform/resolvers/zod" -import { useEffect, useState } from "react" +import { useEffect } from "react" import { useForm } from "react-hook-form" import { useTranslation } from "react-i18next" import { toast } from "sonner" @@ -50,49 +51,27 @@ const settingFormSchema = z.object({ export default function SettingsPage() { const { t, i18n } = useTranslation() - const [config, setConfig] = useState() - const [error, setError] = useState() - - useEffect(() => { - if (error) - toast(t("Error"), { - description: t("Results.ErrorFetchingResource", { - error: error.message, - }), - }) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [error]) - - useEffect(() => { - ;(async () => { - try { - const c = await getSettings() - setConfig(c) - } catch (e) { - if (e instanceof Error) setError(e) - } - })() - }, []) + const { data: config, mutate } = useSetting() const form = useForm>({ resolver: zodResolver(settingFormSchema), defaultValues: config ? { - ...config, - language: config.language.replace("_", "-"), - site_name: config.site_name || "", - user_template: - config.user_template || - Object.keys(config.frontend_templates.filter((t) => !t.is_admin) || {})[0] || - "user-dist", - } + ...config, + language: config.language, + site_name: config.site_name || "", + user_template: + config.user_template || + Object.keys(config.frontend_templates.filter((t) => !t.is_admin) || {})[0] || + "user-dist", + } : { - ip_change_notification_group_id: 0, - cover: 1, - site_name: "", - language: "", - user_template: "user-dist", - }, + ip_change_notification_group_id: 0, + cover: 1, + site_name: "", + language: "", + user_template: "user-dist", + }, resetOptions: { keepDefaultValues: false, }, @@ -107,11 +86,14 @@ export default function SettingsPage() { const onSubmit = async (values: z.infer) => { try { await updateSettings(values) - const newConfig = await getSettings() - setConfig(newConfig) + await mutate() form.reset() } catch (e) { - if (e instanceof Error) setError(e) + toast(t("Error"), { + description: t("Results.ErrorFetchingResource", { + error: e?.toString(), + }), + }) return } finally { if (values.language != i18n.language) { @@ -237,15 +219,15 @@ export default function SettingsPage() { {!config?.frontend_templates?.find( (t) => t.path === field.value, )?.is_official && ( -
-
- {t("CommunityThemeWarning")} +
+
+ {t("CommunityThemeWarning")} +
+
+ {t("CommunityThemeDescription")} +
-
- {t("CommunityThemeDescription")} -
-
- )} + )} )} />