diff --git a/proto/api/v1/user_service.proto b/proto/api/v1/user_service.proto index 66ee38853..2e00601b1 100644 --- a/proto/api/v1/user_service.proto +++ b/proto/api/v1/user_service.proto @@ -399,8 +399,6 @@ message UserSetting { message GeneralSetting { // The preferred locale of the user. string locale = 1 [(google.api.field_behavior) = OPTIONAL]; - // The preferred appearance of the user. - string appearance = 2 [(google.api.field_behavior) = OPTIONAL]; // The default visibility of the memo. string memo_visibility = 3 [(google.api.field_behavior) = OPTIONAL]; // The preferred theme of the user. diff --git a/proto/api/v1/workspace_service.proto b/proto/api/v1/workspace_service.proto index d975f4ea9..fc30aee2a 100644 --- a/proto/api/v1/workspace_service.proto +++ b/proto/api/v1/workspace_service.proto @@ -112,7 +112,6 @@ message WorkspaceSetting { string description = 2; string logo_url = 3; string locale = 4; - string appearance = 5; } } diff --git a/proto/gen/api/v1/user_service.pb.go b/proto/gen/api/v1/user_service.pb.go index 24eb5c7fd..e2114b51d 100644 --- a/proto/gen/api/v1/user_service.pb.go +++ b/proto/gen/api/v1/user_service.pb.go @@ -2239,8 +2239,6 @@ type UserSetting_GeneralSetting struct { state protoimpl.MessageState `protogen:"open.v1"` // The preferred locale of the user. Locale string `protobuf:"bytes,1,opt,name=locale,proto3" json:"locale,omitempty"` - // The preferred appearance of the user. - Appearance string `protobuf:"bytes,2,opt,name=appearance,proto3" json:"appearance,omitempty"` // The default visibility of the memo. MemoVisibility string `protobuf:"bytes,3,opt,name=memo_visibility,json=memoVisibility,proto3" json:"memo_visibility,omitempty"` // The preferred theme of the user. @@ -2288,13 +2286,6 @@ func (x *UserSetting_GeneralSetting) GetLocale() string { return "" } -func (x *UserSetting_GeneralSetting) GetAppearance() string { - if x != nil { - return x.Appearance - } - return "" -} - func (x *UserSetting_GeneralSetting) GetMemoVisibility() string { if x != nil { return x.MemoVisibility @@ -2613,18 +2604,15 @@ const file_api_v1_user_service_proto_rawDesc = "" + "\x11memos.api.v1/UserR\x04name\"\x19\n" + "\x17ListAllUserStatsRequest\"I\n" + "\x18ListAllUserStatsResponse\x12-\n" + - "\x05stats\x18\x01 \x03(\v2\x17.memos.api.v1.UserStatsR\x05stats\"\xd9\a\n" + + "\x05stats\x18\x01 \x03(\v2\x17.memos.api.v1.UserStatsR\x05stats\"\xb3\a\n" + "\vUserSetting\x12\x17\n" + "\x04name\x18\x01 \x01(\tB\x03\xe0A\bR\x04name\x12S\n" + "\x0fgeneral_setting\x18\x02 \x01(\v2(.memos.api.v1.UserSetting.GeneralSettingH\x00R\x0egeneralSetting\x12V\n" + "\x10sessions_setting\x18\x03 \x01(\v2).memos.api.v1.UserSetting.SessionsSettingH\x00R\x0fsessionsSetting\x12c\n" + "\x15access_tokens_setting\x18\x04 \x01(\v2-.memos.api.v1.UserSetting.AccessTokensSettingH\x00R\x13accessTokensSetting\x12V\n" + - "\x10webhooks_setting\x18\x05 \x01(\v2).memos.api.v1.UserSetting.WebhooksSettingH\x00R\x0fwebhooksSetting\x1a\x9b\x01\n" + + "\x10webhooks_setting\x18\x05 \x01(\v2).memos.api.v1.UserSetting.WebhooksSettingH\x00R\x0fwebhooksSetting\x1av\n" + "\x0eGeneralSetting\x12\x1b\n" + - "\x06locale\x18\x01 \x01(\tB\x03\xe0A\x01R\x06locale\x12#\n" + - "\n" + - "appearance\x18\x02 \x01(\tB\x03\xe0A\x01R\n" + - "appearance\x12,\n" + + "\x06locale\x18\x01 \x01(\tB\x03\xe0A\x01R\x06locale\x12,\n" + "\x0fmemo_visibility\x18\x03 \x01(\tB\x03\xe0A\x01R\x0ememoVisibility\x12\x19\n" + "\x05theme\x18\x04 \x01(\tB\x03\xe0A\x01R\x05theme\x1aH\n" + "\x0fSessionsSetting\x125\n" + diff --git a/proto/gen/api/v1/workspace_service.pb.go b/proto/gen/api/v1/workspace_service.pb.go index 0f6677a6a..60f577086 100644 --- a/proto/gen/api/v1/workspace_service.pb.go +++ b/proto/gen/api/v1/workspace_service.pb.go @@ -777,7 +777,6 @@ type WorkspaceSetting_GeneralSetting_CustomProfile struct { Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` LogoUrl string `protobuf:"bytes,3,opt,name=logo_url,json=logoUrl,proto3" json:"logo_url,omitempty"` Locale string `protobuf:"bytes,4,opt,name=locale,proto3" json:"locale,omitempty"` - Appearance string `protobuf:"bytes,5,opt,name=appearance,proto3" json:"appearance,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -840,13 +839,6 @@ func (x *WorkspaceSetting_GeneralSetting_CustomProfile) GetLocale() string { return "" } -func (x *WorkspaceSetting_GeneralSetting_CustomProfile) GetAppearance() string { - if x != nil { - return x.Appearance - } - return "" -} - // S3 configuration for cloud storage backend. // Reference: https://developers.cloudflare.com/r2/examples/aws/aws-sdk-go/ type WorkspaceSetting_StorageSetting_S3Config struct { @@ -943,12 +935,12 @@ const file_api_v1_workspace_service_proto_rawDesc = "" + "\aversion\x18\x02 \x01(\tR\aversion\x12\x12\n" + "\x04mode\x18\x03 \x01(\tR\x04mode\x12!\n" + "\finstance_url\x18\x06 \x01(\tR\vinstanceUrl\"\x1c\n" + - "\x1aGetWorkspaceProfileRequest\"\xb8\x11\n" + + "\x1aGetWorkspaceProfileRequest\"\x97\x11\n" + "\x10WorkspaceSetting\x12\x17\n" + "\x04name\x18\x01 \x01(\tB\x03\xe0A\bR\x04name\x12X\n" + "\x0fgeneral_setting\x18\x02 \x01(\v2-.memos.api.v1.WorkspaceSetting.GeneralSettingH\x00R\x0egeneralSetting\x12X\n" + "\x0fstorage_setting\x18\x03 \x01(\v2-.memos.api.v1.WorkspaceSetting.StorageSettingH\x00R\x0estorageSetting\x12e\n" + - "\x14memo_related_setting\x18\x04 \x01(\v21.memos.api.v1.WorkspaceSetting.MemoRelatedSettingH\x00R\x12memoRelatedSetting\x1a\x9a\x05\n" + + "\x14memo_related_setting\x18\x04 \x01(\v21.memos.api.v1.WorkspaceSetting.MemoRelatedSettingH\x00R\x12memoRelatedSetting\x1a\xf9\x04\n" + "\x0eGeneralSetting\x12\x14\n" + "\x05theme\x18\x01 \x01(\tR\x05theme\x12<\n" + "\x1adisallow_user_registration\x18\x02 \x01(\bR\x18disallowUserRegistration\x124\n" + @@ -958,15 +950,12 @@ const file_api_v1_workspace_service_proto_rawDesc = "" + "\x0ecustom_profile\x18\x06 \x01(\v2;.memos.api.v1.WorkspaceSetting.GeneralSetting.CustomProfileR\rcustomProfile\x121\n" + "\x15week_start_day_offset\x18\a \x01(\x05R\x12weekStartDayOffset\x128\n" + "\x18disallow_change_username\x18\b \x01(\bR\x16disallowChangeUsername\x128\n" + - "\x18disallow_change_nickname\x18\t \x01(\bR\x16disallowChangeNickname\x1a\x9a\x01\n" + + "\x18disallow_change_nickname\x18\t \x01(\bR\x16disallowChangeNickname\x1az\n" + "\rCustomProfile\x12\x14\n" + "\x05title\x18\x01 \x01(\tR\x05title\x12 \n" + "\vdescription\x18\x02 \x01(\tR\vdescription\x12\x19\n" + "\blogo_url\x18\x03 \x01(\tR\alogoUrl\x12\x16\n" + - "\x06locale\x18\x04 \x01(\tR\x06locale\x12\x1e\n" + - "\n" + - "appearance\x18\x05 \x01(\tR\n" + - "appearance\x1a\xbe\x04\n" + + "\x06locale\x18\x04 \x01(\tR\x06locale\x1a\xbe\x04\n" + "\x0eStorageSetting\x12\\\n" + "\fstorage_type\x18\x01 \x01(\x0e29.memos.api.v1.WorkspaceSetting.StorageSetting.StorageTypeR\vstorageType\x12+\n" + "\x11filepath_template\x18\x02 \x01(\tR\x10filepathTemplate\x12/\n" + diff --git a/proto/gen/openapi.yaml b/proto/gen/openapi.yaml index cac215c08..10fa5425e 100644 --- a/proto/gen/openapi.yaml +++ b/proto/gen/openapi.yaml @@ -2466,8 +2466,6 @@ components: type: string locale: type: string - appearance: - type: string description: Custom profile configuration for workspace branding. GetCurrentSessionResponse: type: object @@ -3633,9 +3631,6 @@ components: locale: type: string description: The preferred locale of the user. - appearance: - type: string - description: The preferred appearance of the user. memoVisibility: type: string description: The default visibility of the memo. diff --git a/proto/gen/store/user_setting.pb.go b/proto/gen/store/user_setting.pb.go index 7d5c229f4..37bd9b5db 100644 --- a/proto/gen/store/user_setting.pb.go +++ b/proto/gen/store/user_setting.pb.go @@ -235,13 +235,11 @@ type GeneralUserSetting struct { state protoimpl.MessageState `protogen:"open.v1"` // The user's locale. Locale string `protobuf:"bytes,1,opt,name=locale,proto3" json:"locale,omitempty"` - // The user's appearance setting. - Appearance string `protobuf:"bytes,2,opt,name=appearance,proto3" json:"appearance,omitempty"` // The user's memo visibility setting. - MemoVisibility string `protobuf:"bytes,3,opt,name=memo_visibility,json=memoVisibility,proto3" json:"memo_visibility,omitempty"` + MemoVisibility string `protobuf:"bytes,2,opt,name=memo_visibility,json=memoVisibility,proto3" json:"memo_visibility,omitempty"` // The user's theme preference. // This references a CSS file in the web/public/themes/ directory. - Theme string `protobuf:"bytes,4,opt,name=theme,proto3" json:"theme,omitempty"` + Theme string `protobuf:"bytes,3,opt,name=theme,proto3" json:"theme,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -283,13 +281,6 @@ func (x *GeneralUserSetting) GetLocale() string { return "" } -func (x *GeneralUserSetting) GetAppearance() string { - if x != nil { - return x.Appearance - } - return "" -} - func (x *GeneralUserSetting) GetMemoVisibility() string { if x != nil { return x.MemoVisibility @@ -832,14 +823,11 @@ const file_store_user_setting_proto_rawDesc = "" + "\rACCESS_TOKENS\x10\x03\x12\r\n" + "\tSHORTCUTS\x10\x04\x12\f\n" + "\bWEBHOOKS\x10\x05B\a\n" + - "\x05value\"\x8b\x01\n" + + "\x05value\"k\n" + "\x12GeneralUserSetting\x12\x16\n" + - "\x06locale\x18\x01 \x01(\tR\x06locale\x12\x1e\n" + - "\n" + - "appearance\x18\x02 \x01(\tR\n" + - "appearance\x12'\n" + - "\x0fmemo_visibility\x18\x03 \x01(\tR\x0ememoVisibility\x12\x14\n" + - "\x05theme\x18\x04 \x01(\tR\x05theme\"\xf3\x03\n" + + "\x06locale\x18\x01 \x01(\tR\x06locale\x12'\n" + + "\x0fmemo_visibility\x18\x02 \x01(\tR\x0ememoVisibility\x12\x14\n" + + "\x05theme\x18\x03 \x01(\tR\x05theme\"\xf3\x03\n" + "\x13SessionsUserSetting\x12D\n" + "\bsessions\x18\x01 \x03(\v2(.memos.store.SessionsUserSetting.SessionR\bsessions\x1a\xfd\x01\n" + "\aSession\x12\x1d\n" + diff --git a/proto/gen/store/workspace_setting.pb.go b/proto/gen/store/workspace_setting.pb.go index d3514c0e1..b1c5fcb86 100644 --- a/proto/gen/store/workspace_setting.pb.go +++ b/proto/gen/store/workspace_setting.pb.go @@ -437,7 +437,6 @@ type WorkspaceCustomProfile struct { Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` LogoUrl string `protobuf:"bytes,3,opt,name=logo_url,json=logoUrl,proto3" json:"logo_url,omitempty"` Locale string `protobuf:"bytes,4,opt,name=locale,proto3" json:"locale,omitempty"` - Appearance string `protobuf:"bytes,5,opt,name=appearance,proto3" json:"appearance,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -500,13 +499,6 @@ func (x *WorkspaceCustomProfile) GetLocale() string { return "" } -func (x *WorkspaceCustomProfile) GetAppearance() string { - if x != nil { - return x.Appearance - } - return "" -} - type WorkspaceStorageSetting struct { state protoimpl.MessageState `protogen:"open.v1"` // storage_type is the storage type. @@ -807,15 +799,12 @@ const file_store_workspace_setting_proto_rawDesc = "" + "\x0ecustom_profile\x18\x06 \x01(\v2#.memos.store.WorkspaceCustomProfileR\rcustomProfile\x121\n" + "\x15week_start_day_offset\x18\a \x01(\x05R\x12weekStartDayOffset\x128\n" + "\x18disallow_change_username\x18\b \x01(\bR\x16disallowChangeUsername\x128\n" + - "\x18disallow_change_nickname\x18\t \x01(\bR\x16disallowChangeNickname\"\xa3\x01\n" + + "\x18disallow_change_nickname\x18\t \x01(\bR\x16disallowChangeNickname\"\x83\x01\n" + "\x16WorkspaceCustomProfile\x12\x14\n" + "\x05title\x18\x01 \x01(\tR\x05title\x12 \n" + "\vdescription\x18\x02 \x01(\tR\vdescription\x12\x19\n" + "\blogo_url\x18\x03 \x01(\tR\alogoUrl\x12\x16\n" + - "\x06locale\x18\x04 \x01(\tR\x06locale\x12\x1e\n" + - "\n" + - "appearance\x18\x05 \x01(\tR\n" + - "appearance\"\xd5\x02\n" + + "\x06locale\x18\x04 \x01(\tR\x06locale\"\xd5\x02\n" + "\x17WorkspaceStorageSetting\x12S\n" + "\fstorage_type\x18\x01 \x01(\x0e20.memos.store.WorkspaceStorageSetting.StorageTypeR\vstorageType\x12+\n" + "\x11filepath_template\x18\x02 \x01(\tR\x10filepathTemplate\x12/\n" + diff --git a/proto/store/user_setting.proto b/proto/store/user_setting.proto index 7ec03b0bb..87c8657f4 100644 --- a/proto/store/user_setting.proto +++ b/proto/store/user_setting.proto @@ -36,13 +36,11 @@ message UserSetting { message GeneralUserSetting { // The user's locale. string locale = 1; - // The user's appearance setting. - string appearance = 2; // The user's memo visibility setting. - string memo_visibility = 3; + string memo_visibility = 2; // The user's theme preference. // This references a CSS file in the web/public/themes/ directory. - string theme = 4; + string theme = 3; } message SessionsUserSetting { diff --git a/proto/store/workspace_setting.proto b/proto/store/workspace_setting.proto index 17432a621..eb86aba5f 100644 --- a/proto/store/workspace_setting.proto +++ b/proto/store/workspace_setting.proto @@ -62,7 +62,6 @@ message WorkspaceCustomProfile { string description = 2; string logo_url = 3; string locale = 4; - string appearance = 5; } message WorkspaceStorageSetting { diff --git a/server/router/api/v1/user_service.go b/server/router/api/v1/user_service.go index 4e27c3cd6..e00d69a4d 100644 --- a/server/router/api/v1/user_service.go +++ b/server/router/api/v1/user_service.go @@ -306,7 +306,6 @@ func (s *APIV1Service) DeleteUser(ctx context.Context, request *v1pb.DeleteUserR func getDefaultUserGeneralSetting() *v1pb.UserSetting_GeneralSetting { return &v1pb.UserSetting_GeneralSetting{ Locale: "en", - Appearance: "system", MemoVisibility: "PRIVATE", Theme: "", } @@ -394,7 +393,6 @@ func (s *APIV1Service) UpdateUserSetting(ctx context.Context, request *v1pb.Upda // Start with existing general setting values existingGeneral := existingUserSetting.GetGeneral() updatedGeneral := &v1pb.UserSetting_GeneralSetting{ - Appearance: existingGeneral.GetAppearance(), MemoVisibility: existingGeneral.GetMemoVisibility(), Locale: existingGeneral.GetLocale(), Theme: existingGeneral.GetTheme(), @@ -404,8 +402,6 @@ func (s *APIV1Service) UpdateUserSetting(ctx context.Context, request *v1pb.Upda incomingGeneral := request.Setting.GetGeneralSetting() for _, field := range request.UpdateMask.Paths { switch field { - case "appearance": - updatedGeneral.Appearance = incomingGeneral.Appearance case "memoVisibility": updatedGeneral.MemoVisibility = incomingGeneral.MemoVisibility case "theme": @@ -1165,7 +1161,6 @@ func convertUserSettingFromStore(storeSetting *storepb.UserSetting, userID int32 setting.Value = &v1pb.UserSetting_GeneralSetting_{ GeneralSetting: &v1pb.UserSetting_GeneralSetting{ Locale: general.Locale, - Appearance: general.Appearance, MemoVisibility: general.MemoVisibility, Theme: general.Theme, }, @@ -1249,7 +1244,6 @@ func convertUserSettingToStore(apiSetting *v1pb.UserSetting, userID int32, key s storeSetting.Value = &storepb.UserSetting_General{ General: &storepb.GeneralUserSetting{ Locale: general.Locale, - Appearance: general.Appearance, MemoVisibility: general.MemoVisibility, Theme: general.Theme, }, diff --git a/server/router/api/v1/workspace_service.go b/server/router/api/v1/workspace_service.go index b71b16e1c..fee1e42c0 100644 --- a/server/router/api/v1/workspace_service.go +++ b/server/router/api/v1/workspace_service.go @@ -171,7 +171,6 @@ func convertWorkspaceGeneralSettingFromStore(setting *storepb.WorkspaceGeneralSe Description: setting.CustomProfile.Description, LogoUrl: setting.CustomProfile.LogoUrl, Locale: setting.CustomProfile.Locale, - Appearance: setting.CustomProfile.Appearance, } } return generalSetting @@ -197,7 +196,6 @@ func convertWorkspaceGeneralSettingToStore(setting *v1pb.WorkspaceSetting_Genera Description: setting.CustomProfile.Description, LogoUrl: setting.CustomProfile.LogoUrl, Locale: setting.CustomProfile.Locale, - Appearance: setting.CustomProfile.Appearance, } } return generalSetting diff --git a/web/src/App.tsx b/web/src/App.tsx index f61559a26..9de74c605 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1,8 +1,7 @@ import { observer } from "mobx-react-lite"; -import { useEffect, useState } from "react"; +import { useEffect } from "react"; import { useTranslation } from "react-i18next"; import { Outlet } from "react-router-dom"; -import { getSystemColorScheme } from "./helpers/utils"; import useNavigateTo from "./hooks/useNavigateTo"; import { userStore, workspaceStore } from "./store"; import { loadTheme } from "./utils/theme"; @@ -10,7 +9,6 @@ import { loadTheme } from "./utils/theme"; const App = observer(() => { const { i18n } = useTranslation(); const navigateTo = useNavigateTo(); - const [mode, setMode] = useState<"light" | "dark">("light"); const workspaceProfile = workspaceStore.state.profile; const userGeneralSetting = userStore.state.userGeneralSetting; const workspaceGeneralSetting = workspaceStore.state.generalSetting; @@ -22,20 +20,6 @@ const App = observer(() => { } }, [workspaceProfile.owner]); - useEffect(() => { - const darkMediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); - const handleColorSchemeChange = (e: MediaQueryListEvent) => { - const mode = e.matches ? "dark" : "light"; - setMode(mode); - }; - - try { - darkMediaQuery.addEventListener("change", handleColorSchemeChange); - } catch (error) { - console.error("failed to initial color scheme listener", error); - } - }, []); - useEffect(() => { if (workspaceGeneralSetting.additionalStyle) { const styleEl = document.createElement("style"); @@ -76,23 +60,6 @@ const App = observer(() => { } }, [workspaceStore.state.locale]); - useEffect(() => { - let currentAppearance = workspaceStore.state.appearance as Appearance; - if (currentAppearance === "system") { - currentAppearance = getSystemColorScheme(); - } - setMode(currentAppearance); - }, [workspaceStore.state.appearance]); - - useEffect(() => { - const root = document.documentElement; - if (mode === "light") { - root.classList.remove("dark"); - } else if (mode === "dark") { - root.classList.add("dark"); - } - }, [mode]); - useEffect(() => { if (!userGeneralSetting) { return; @@ -100,16 +67,17 @@ const App = observer(() => { workspaceStore.state.setPartial({ locale: userGeneralSetting.locale || workspaceStore.state.locale, - appearance: userGeneralSetting.appearance || workspaceStore.state.appearance, + theme: userGeneralSetting.theme || workspaceStore.state.theme, }); - }, [userGeneralSetting?.locale, userGeneralSetting?.appearance]); + }, [userGeneralSetting?.locale, userGeneralSetting?.theme]); - // Load theme when user setting changes (user theme is already backfilled with workspace theme) + // Load theme when workspace theme changes or user setting changes useEffect(() => { - if (userGeneralSetting?.theme) { - loadTheme(userGeneralSetting.theme); + const currentTheme = userGeneralSetting?.theme || workspaceStore.state.theme; + if (currentTheme) { + loadTheme(currentTheme); } - }, [userGeneralSetting?.theme]); + }, [userGeneralSetting?.theme, workspaceStore.state.theme]); return ; }); diff --git a/web/src/components/AppearanceSelect.tsx b/web/src/components/AppearanceSelect.tsx deleted file mode 100644 index 5c679858c..000000000 --- a/web/src/components/AppearanceSelect.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { SunIcon, MoonIcon, SmileIcon } from "lucide-react"; -import { FC } from "react"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { useTranslate } from "@/utils/i18n"; - -interface Props { - value: Appearance; - onChange: (appearance: Appearance) => void; -} - -const appearanceList = ["system", "light", "dark"] as const; - -const AppearanceSelect: FC = (props: Props) => { - const { onChange, value } = props; - const t = useTranslate(); - - const getPrefixIcon = (appearance: Appearance) => { - const className = "w-4 h-auto"; - if (appearance === "light") { - return ; - } else if (appearance === "dark") { - return ; - } else { - return ; - } - }; - - const handleSelectChange = async (appearance: Appearance) => { - onChange(appearance); - }; - - return ( - - ); -}; - -export default AppearanceSelect; diff --git a/web/src/components/AuthFooter.tsx b/web/src/components/AuthFooter.tsx index 2a449bb21..b689490be 100644 --- a/web/src/components/AuthFooter.tsx +++ b/web/src/components/AuthFooter.tsx @@ -1,8 +1,8 @@ import { observer } from "mobx-react-lite"; import { cn } from "@/lib/utils"; import { workspaceStore } from "@/store"; -import AppearanceSelect from "./AppearanceSelect"; import LocaleSelect from "./LocaleSelect"; +import ThemeSelect from "./ThemeSelect"; interface Props { className?: string; @@ -13,14 +13,10 @@ const AuthFooter = observer(({ className }: Props) => { workspaceStore.state.setPartial({ locale }); }; - const handleAppearanceSelectChange = (appearance: Appearance) => { - workspaceStore.state.setPartial({ appearance }); - }; - return (
- +
); }); diff --git a/web/src/components/MemoContent/CodeBlock.tsx b/web/src/components/MemoContent/CodeBlock.tsx index e9b58e4f3..c36897fc0 100644 --- a/web/src/components/MemoContent/CodeBlock.tsx +++ b/web/src/components/MemoContent/CodeBlock.tsx @@ -37,8 +37,6 @@ const CodeBlock: React.FC = ({ language, content }: Props) => { useEffect(() => { const dynamicImportStyle = async () => { - const isDark = document.documentElement.classList.contains("dark"); - // Remove any existing highlight.js style const existingStyle = document.querySelector("style[data-hljs-theme]"); if (existingStyle) { @@ -46,15 +44,13 @@ const CodeBlock: React.FC = ({ language, content }: Props) => { } try { - // Dynamically import the appropriate CSS. - const cssModule = isDark - ? await import("highlight.js/styles/atom-one-dark.css?inline") - : await import("highlight.js/styles/github.css?inline"); + // Always use the github light theme + const cssModule = await import("highlight.js/styles/github.css?inline"); // Create and inject the style const style = document.createElement("style"); style.textContent = cssModule.default; - style.setAttribute("data-hljs-theme", isDark ? "dark" : "light"); + style.setAttribute("data-hljs-theme", "light"); document.head.appendChild(style); } catch (error) { console.warn("Failed to load highlight.js theme:", error); @@ -62,15 +58,6 @@ const CodeBlock: React.FC = ({ language, content }: Props) => { }; dynamicImportStyle(); - - // Watch for changes to the dark class - const observer = new MutationObserver(dynamicImportStyle); - observer.observe(document.documentElement, { - attributes: true, - attributeFilter: ["class"], - }); - - return () => observer.disconnect(); }, []); const highlightedCode = useMemo(() => { diff --git a/web/src/components/MemoContent/MermaidBlock.tsx b/web/src/components/MemoContent/MermaidBlock.tsx index b8e9dbef8..3dd303f4a 100644 --- a/web/src/components/MemoContent/MermaidBlock.tsx +++ b/web/src/components/MemoContent/MermaidBlock.tsx @@ -9,9 +9,8 @@ const MermaidBlock: React.FC = ({ content }: Props) => { useEffect(() => { const initializeMermaid = async () => { - const isDark = document.documentElement.classList.contains("dark"); const mermaid = (await import("mermaid")).default; - mermaid.initialize({ startOnLoad: false, theme: isDark ? "dark" : "default" }); + mermaid.initialize({ startOnLoad: false, theme: "default" }); if (mermaidDockBlock.current) { mermaid.run({ nodes: [mermaidDockBlock.current], diff --git a/web/src/components/MemoRelationForceGraph/MemoRelationForceGraph.tsx b/web/src/components/MemoRelationForceGraph/MemoRelationForceGraph.tsx index da816f883..0bcb8a939 100644 --- a/web/src/components/MemoRelationForceGraph/MemoRelationForceGraph.tsx +++ b/web/src/components/MemoRelationForceGraph/MemoRelationForceGraph.tsx @@ -18,30 +18,11 @@ const DEFAULT_NODE_COLOR = "#a1a1aa"; const MemoRelationForceGraph = ({ className, memo, parentPage }: Props) => { const navigateTo = useNavigateTo(); - const [mode, setMode] = useState<"light" | "dark">("light"); + const [mode] = useState<"light">("light"); const containerRef = useRef(null); const graphRef = useRef, LinkObject> | undefined>(undefined); const [graphSize, setGraphSize] = useState({ width: 0, height: 0 }); - // Simple dark mode detection - useEffect(() => { - const updateMode = () => { - const isDark = document.documentElement.classList.contains("dark"); - setMode(isDark ? "dark" : "light"); - }; - - updateMode(); - - // Watch for changes to the dark class - const observer = new MutationObserver(updateMode); - observer.observe(document.documentElement, { - attributes: true, - attributeFilter: ["class"], - }); - - return () => observer.disconnect(); - }, []); - useEffect(() => { if (!containerRef.current) return; setGraphSize(containerRef.current.getBoundingClientRect()); diff --git a/web/src/components/Settings/PreferencesSection.tsx b/web/src/components/Settings/PreferencesSection.tsx index a61fbe234..af9cf5123 100644 --- a/web/src/components/Settings/PreferencesSection.tsx +++ b/web/src/components/Settings/PreferencesSection.tsx @@ -6,9 +6,8 @@ import { Visibility } from "@/types/proto/api/v1/memo_service"; import { UserSetting_GeneralSetting } from "@/types/proto/api/v1/user_service"; import { useTranslate } from "@/utils/i18n"; import { convertVisibilityFromString, convertVisibilityToString } from "@/utils/memo"; -import AppearanceSelect from "../AppearanceSelect"; import LocaleSelect from "../LocaleSelect"; -import ThemeSelector from "../ThemeSelector"; +import ThemeSelect from "../ThemeSelect"; import VisibilityIcon from "../VisibilityIcon"; import WebhookSection from "./WebhookSection"; @@ -20,10 +19,6 @@ const PreferencesSection = observer(() => { await userStore.updateUserGeneralSetting({ locale }, ["locale"]); }; - const handleAppearanceSelectChange = async (appearance: Appearance) => { - await userStore.updateUserGeneralSetting({ appearance }, ["appearance"]); - }; - const handleDefaultMemoVisibilityChanged = async (value: string) => { await userStore.updateUserGeneralSetting({ memoVisibility: value }, ["memoVisibility"]); }; @@ -35,9 +30,8 @@ const PreferencesSection = observer(() => { // Provide default values if setting is not loaded yet const setting: UserSetting_GeneralSetting = generalSetting || { locale: "en", - appearance: "system", memoVisibility: "PRIVATE", - theme: "", + theme: "default", }; return ( @@ -49,14 +43,9 @@ const PreferencesSection = observer(() => { -
- {t("setting.preference-section.apperance")} - -
-
{t("setting.preference-section.theme")} - +

{t("setting.preference")}

diff --git a/web/src/components/Settings/WorkspaceSection.tsx b/web/src/components/Settings/WorkspaceSection.tsx index 526e3db36..1a090af0e 100644 --- a/web/src/components/Settings/WorkspaceSection.tsx +++ b/web/src/components/Settings/WorkspaceSection.tsx @@ -16,7 +16,7 @@ import { workspaceSettingNamePrefix } from "@/store/common"; import { IdentityProvider } from "@/types/proto/api/v1/idp_service"; import { WorkspaceSetting_GeneralSetting, WorkspaceSetting_Key } from "@/types/proto/api/v1/workspace_service"; import { useTranslate } from "@/utils/i18n"; -import ThemeSelector from "../ThemeSelector"; +import ThemeSelect from "../ThemeSelect"; import UpdateCustomizedProfileDialog from "../UpdateCustomizedProfileDialog"; const WorkspaceSection = observer(() => { @@ -84,9 +84,9 @@ const WorkspaceSection = observer(() => {

{t("setting.system-section.title")}

Theme - updatePartialSetting({ theme: value })} + onValueChange={(value: string) => updatePartialSetting({ theme: value })} className="min-w-fit" />
diff --git a/web/src/components/ThemeSelect.tsx b/web/src/components/ThemeSelect.tsx new file mode 100644 index 000000000..4ff918f31 --- /dev/null +++ b/web/src/components/ThemeSelect.tsx @@ -0,0 +1,52 @@ +import { Moon, Palette, Sun, Wallpaper } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; +import { workspaceStore } from "@/store"; + +interface ThemeSelectProps { + value?: string; + onValueChange?: (theme: string) => void; + className?: string; +} + +const ThemeSelect = ({ value, onValueChange, className }: ThemeSelectProps = {}) => { + const currentTheme = value || workspaceStore.state.theme || "default"; + + const themeOptions: { value: Theme; icon: JSX.Element; label: string }[] = [ + { value: "default", icon: , label: "Default Light" }, + { value: "default-dark", icon: , label: "Default Dark" }, + { value: "paper", icon: , label: "Paper" }, + { value: "whitewall", icon: , label: "Whitewall" }, + ]; + + const handleThemeChange = (newTheme: Theme) => { + if (onValueChange) { + onValueChange(newTheme); + } else { + workspaceStore.setTheme(newTheme); + } + }; + + const currentThemeOption = themeOptions.find((option) => option.value === currentTheme); + + return ( + + + + + + {themeOptions.map((option) => ( + handleThemeChange(option.value)}> + {option.icon} + {option.label} + + ))} + + + ); +}; + +export default ThemeSelect; diff --git a/web/src/components/ThemeSelector.tsx b/web/src/components/ThemeSelector.tsx deleted file mode 100644 index 81b411b3e..000000000 --- a/web/src/components/ThemeSelector.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; - -interface ThemeSelectorProps { - value?: string; - onValueChange: (value: string) => void; - className?: string; -} - -const THEMES = [ - { value: "default", label: "Default" }, - { value: "paper", label: "Paper" }, - { value: "whitewall", label: "Whitewall" }, -] as const; - -export const ThemeSelector = ({ value = "default", onValueChange, className }: ThemeSelectorProps) => { - return ( - - ); -}; - -export default ThemeSelector; diff --git a/web/src/components/UpdateCustomizedProfileDialog.tsx b/web/src/components/UpdateCustomizedProfileDialog.tsx index 4c7896332..06eff0e19 100644 --- a/web/src/components/UpdateCustomizedProfileDialog.tsx +++ b/web/src/components/UpdateCustomizedProfileDialog.tsx @@ -9,8 +9,8 @@ import { workspaceStore } from "@/store"; import { workspaceSettingNamePrefix } from "@/store/common"; import { WorkspaceSetting_GeneralSetting_CustomProfile, WorkspaceSetting_Key } from "@/types/proto/api/v1/workspace_service"; import { useTranslate } from "@/utils/i18n"; -import AppearanceSelect from "./AppearanceSelect"; import LocaleSelect from "./LocaleSelect"; +import ThemeSelect from "./ThemeSelect"; interface Props { open: boolean; @@ -58,19 +58,12 @@ function UpdateCustomizedProfileDialog({ open, onOpenChange, onSuccess }: Props) }); }; - const handleAppearanceSelectChange = (appearance: Appearance) => { - setPartialState({ - appearance: appearance, - }); - }; - const handleRestoreButtonClick = () => { setPartialState({ title: "Memos", logoUrl: "/logo.webp", description: "", locale: "en", - appearance: "system", }); }; @@ -140,8 +133,8 @@ function UpdateCustomizedProfileDialog({ open, onOpenChange, onSuccess }: Props)
- - + +
diff --git a/web/src/components/ui/badge.tsx b/web/src/components/ui/badge.tsx index 49d433d4b..2710bfef5 100644 --- a/web/src/components/ui/badge.tsx +++ b/web/src/components/ui/badge.tsx @@ -10,7 +10,7 @@ const badgeVariants = cva( variant: { default: "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90", secondary: "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", - destructive: "border-transparent bg-destructive text-destructive-foreground [a&]:hover:bg-destructive/90 dark:bg-destructive/60", + destructive: "border-transparent bg-destructive text-destructive-foreground [a&]:hover:bg-destructive/90", outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", }, }, diff --git a/web/src/components/ui/button.tsx b/web/src/components/ui/button.tsx index bc81817ba..13fc5d825 100644 --- a/web/src/components/ui/button.tsx +++ b/web/src/components/ui/button.tsx @@ -9,11 +9,10 @@ const buttonVariants = cva( variants: { variant: { default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", - destructive: "bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90 dark:bg-destructive/60", - outline: - "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-border dark:hover:bg-input/50", + destructive: "bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90", + outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground", secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", - ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", + ghost: "hover:bg-accent hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline", }, size: { diff --git a/web/src/components/ui/checkbox.tsx b/web/src/components/ui/checkbox.tsx index baa1441a3..7af4abea4 100644 --- a/web/src/components/ui/checkbox.tsx +++ b/web/src/components/ui/checkbox.tsx @@ -8,7 +8,7 @@ function Checkbox({ className, ...props }: React.ComponentProps) { type={type} data-slot="input" className={cn( - "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-border flex h-8 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", + "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground border-border flex h-8 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", className, )} {...props} diff --git a/web/src/components/ui/radio-group.tsx b/web/src/components/ui/radio-group.tsx index 4ecddc5f9..0cbcd9bbf 100644 --- a/web/src/components/ui/radio-group.tsx +++ b/web/src/components/ui/radio-group.tsx @@ -12,7 +12,7 @@ function RadioGroupItem({ className, ...props }: React.ComponentProps diff --git a/web/src/components/ui/textarea.tsx b/web/src/components/ui/textarea.tsx index da0b6b315..7cfa482c9 100644 --- a/web/src/components/ui/textarea.tsx +++ b/web/src/components/ui/textarea.tsx @@ -6,7 +6,7 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {