mirror of
https://github.com/usememos/memos.git
synced 2024-11-28 23:04:57 +03:00
feat: update appearance selector (#645)
This commit is contained in:
parent
eaebc6dcef
commit
7c6d7226f5
@ -61,9 +61,6 @@ func NewServer(profile *profile.Profile) *Server {
|
||||
rootGroup := e.Group("")
|
||||
s.registerRSSRoutes(rootGroup)
|
||||
|
||||
webhookGroup := e.Group("/h")
|
||||
s.registerResourcePublicRoutes(webhookGroup)
|
||||
|
||||
publicGroup := e.Group("/o")
|
||||
s.registerResourcePublicRoutes(publicGroup)
|
||||
s.registerGetterPublicRoutes(publicGroup)
|
||||
|
@ -6,12 +6,12 @@ import { useAppSelector } from "./store";
|
||||
import Loading from "./pages/Loading";
|
||||
import router from "./router";
|
||||
import * as storage from "./helpers/storage";
|
||||
import useApperance from "./hooks/useApperance";
|
||||
import useAppearance from "./hooks/useAppearance";
|
||||
|
||||
function App() {
|
||||
const { i18n } = useTranslation();
|
||||
const { locale, systemStatus } = useAppSelector((state) => state.global);
|
||||
useApperance();
|
||||
useAppearance();
|
||||
|
||||
useEffect(() => {
|
||||
locationService.updateStateWithLocation();
|
||||
|
@ -1,14 +1,16 @@
|
||||
import { Option, Select } from "@mui/joy";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { globalService } from "../services";
|
||||
import { useAppSelector } from "../store";
|
||||
import Icon from "./Icon";
|
||||
import { APPERANCE_OPTIONS } from "../helpers/consts";
|
||||
import useApperance, { Apperance } from "../hooks/useApperance";
|
||||
|
||||
const ApperanceSelect = () => {
|
||||
const [apperance, setApperance] = useApperance();
|
||||
const appearanceList = ["system", "light", "dark"];
|
||||
|
||||
const AppearanceSelect = () => {
|
||||
const appearance = useAppSelector((state) => state.global.appearance);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const getPrefixIcon = (apperance: Apperance) => {
|
||||
const getPrefixIcon = (apperance: Appearance) => {
|
||||
const className = "w-4 h-auto";
|
||||
if (apperance === "light") {
|
||||
return <Icon.Sun className={className} />;
|
||||
@ -19,16 +21,22 @@ const ApperanceSelect = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectChange = (appearance: Appearance) => {
|
||||
globalService.setAppearance(appearance);
|
||||
};
|
||||
|
||||
return (
|
||||
<Select
|
||||
className="!min-w-[10rem] w-auto text-sm"
|
||||
value={apperance}
|
||||
onChange={(_, value) => {
|
||||
setApperance(value as Apperance);
|
||||
value={appearance}
|
||||
onChange={(_, appearance) => {
|
||||
if (appearance) {
|
||||
handleSelectChange(appearance);
|
||||
}
|
||||
}}
|
||||
startDecorator={getPrefixIcon(apperance)}
|
||||
startDecorator={getPrefixIcon(appearance)}
|
||||
>
|
||||
{APPERANCE_OPTIONS.map((item) => (
|
||||
{appearanceList.map((item) => (
|
||||
<Option key={item} value={item} className="whitespace-nowrap">
|
||||
{t(`setting.apperance-option.${item}`)}
|
||||
</Option>
|
||||
@ -37,4 +45,4 @@ const ApperanceSelect = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default ApperanceSelect;
|
||||
export default AppearanceSelect;
|
@ -82,6 +82,7 @@ const ChangePasswordDialog: React.FC<Props> = ({ destroy }: Props) => {
|
||||
<p className="text-sm mb-1">{t("common.new-password")}</p>
|
||||
<input
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
className="input-text"
|
||||
placeholder={t("common.repeat-new-password")}
|
||||
value={newPassword}
|
||||
@ -90,6 +91,7 @@ const ChangePasswordDialog: React.FC<Props> = ({ destroy }: Props) => {
|
||||
<p className="text-sm mb-1 mt-2">{t("common.repeat-new-password")}</p>
|
||||
<input
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
className="input-text"
|
||||
placeholder={t("common.repeat-new-password")}
|
||||
value={newPasswordAgain}
|
||||
|
@ -34,7 +34,14 @@ const SearchBar = () => {
|
||||
<div className="search-bar-container">
|
||||
<div className="search-bar-inputer">
|
||||
<Icon.Search className="icon-img" />
|
||||
<input className="text-input" autoComplete="off" type="text" placeholder="" value={queryText} onChange={handleTextQueryInput} />
|
||||
<input
|
||||
className="text-input"
|
||||
autoComplete="new-password"
|
||||
type="text"
|
||||
placeholder=""
|
||||
value={queryText}
|
||||
onChange={handleTextQueryInput}
|
||||
/>
|
||||
</div>
|
||||
<div className="quickly-action-wrapper">
|
||||
<div className="quickly-action-container">
|
||||
|
@ -12,7 +12,6 @@ import "../../less/settings/member-section.less";
|
||||
interface State {
|
||||
createUserUsername: string;
|
||||
createUserPassword: string;
|
||||
repeatUserPassword: string;
|
||||
}
|
||||
|
||||
const PreferencesSection = () => {
|
||||
@ -21,7 +20,6 @@ const PreferencesSection = () => {
|
||||
const [state, setState] = useState<State>({
|
||||
createUserUsername: "",
|
||||
createUserPassword: "",
|
||||
repeatUserPassword: "",
|
||||
});
|
||||
const [userList, setUserList] = useState<User[]>([]);
|
||||
|
||||
@ -48,22 +46,11 @@ const PreferencesSection = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleRepeatPasswordInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setState({
|
||||
...state,
|
||||
repeatUserPassword: event.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleCreateUserBtnClick = async () => {
|
||||
if (state.createUserUsername === "" || state.createUserPassword === "") {
|
||||
toastHelper.error(t("message.fill-form"));
|
||||
return;
|
||||
}
|
||||
if (state.createUserPassword !== state.repeatUserPassword) {
|
||||
toastHelper.error(t("message.password-not-match"));
|
||||
return;
|
||||
}
|
||||
|
||||
const userCreate: UserCreate = {
|
||||
username: state.createUserUsername,
|
||||
@ -80,7 +67,6 @@ const PreferencesSection = () => {
|
||||
setState({
|
||||
createUserUsername: "",
|
||||
createUserPassword: "",
|
||||
repeatUserPassword: "",
|
||||
});
|
||||
};
|
||||
|
||||
@ -131,19 +117,22 @@ const PreferencesSection = () => {
|
||||
<div className="create-member-container">
|
||||
<div className="input-form-container">
|
||||
<span className="field-text">{t("common.username")}</span>
|
||||
<input type="text" placeholder={t("common.username")} value={state.createUserUsername} onChange={handleUsernameInputChange} />
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="new-password"
|
||||
placeholder={t("common.username")}
|
||||
value={state.createUserUsername}
|
||||
onChange={handleUsernameInputChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="input-form-container">
|
||||
<span className="field-text">{t("common.password")}</span>
|
||||
<input type="password" placeholder={t("common.password")} value={state.createUserPassword} onChange={handlePasswordInputChange} />
|
||||
</div>
|
||||
<div className="input-form-container">
|
||||
<span className="field-text">{t("common.repeat-password-short")}</span>
|
||||
<input
|
||||
type="password"
|
||||
placeholder={t("common.repeat-password")}
|
||||
value={state.repeatUserPassword}
|
||||
onChange={handleRepeatPasswordInputChange}
|
||||
autoComplete="new-password"
|
||||
placeholder={t("common.password")}
|
||||
value={state.createUserPassword}
|
||||
onChange={handlePasswordInputChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="btns-container">
|
||||
|
@ -4,6 +4,7 @@ import { globalService, userService } from "../../services";
|
||||
import { useAppSelector } from "../../store";
|
||||
import { VISIBILITY_SELECTOR_ITEMS, MEMO_DISPLAY_TS_OPTION_SELECTOR_ITEMS } from "../../helpers/consts";
|
||||
import Selector from "../common/Selector";
|
||||
import AppearanceSelect from "../AppearanceSelect";
|
||||
import "../../less/settings/preferences-section.less";
|
||||
|
||||
const localeSelectorItems = [
|
||||
@ -66,6 +67,10 @@ const PreferencesSection = () => {
|
||||
<span className="normal-text">{t("common.language")}</span>
|
||||
<Selector className="ml-2 w-32" value={setting.locale} dataSource={localeSelectorItems} handleValueChanged={handleLocaleChanged} />
|
||||
</label>
|
||||
<label className="form-label selector">
|
||||
<span className="normal-text">Theme</span>
|
||||
<AppearanceSelect />
|
||||
</label>
|
||||
<p className="title-text">{t("setting.preference")}</p>
|
||||
<label className="form-label selector">
|
||||
<span className="normal-text">{t("setting.preference-section.default-memo-visibility")}</span>
|
||||
|
@ -14,8 +14,8 @@ import toastHelper from "./Toast";
|
||||
import MemoContent from "./MemoContent";
|
||||
import MemoResources from "./MemoResources";
|
||||
import Selector from "./common/Selector";
|
||||
import useAppearance from "../hooks/useAppearance";
|
||||
import "../less/share-memo-image-dialog.less";
|
||||
import useApperance from "../hooks/useApperance";
|
||||
|
||||
interface Props extends DialogProps {
|
||||
memo: Memo;
|
||||
@ -36,7 +36,7 @@ const ShareMemoImageDialog: React.FC<Props> = (props: Props) => {
|
||||
shortcutImgUrl: "",
|
||||
memoVisibility: propsMemo.visibility,
|
||||
});
|
||||
const [apperance] = useApperance();
|
||||
const [appearance] = useAppearance();
|
||||
const loadingState = useLoading();
|
||||
const memoElRef = useRef<HTMLDivElement>(null);
|
||||
const memo = {
|
||||
@ -72,7 +72,7 @@ const ShareMemoImageDialog: React.FC<Props> = (props: Props) => {
|
||||
}
|
||||
|
||||
toImage(memoElRef.current, {
|
||||
backgroundColor: apperance === "light" ? "#f4f4f5" : "#27272a",
|
||||
backgroundColor: appearance === "light" ? "#f4f4f5" : "#27272a",
|
||||
pixelRatio: window.devicePixelRatio * 2,
|
||||
})
|
||||
.then((url) => {
|
||||
|
@ -19,6 +19,3 @@ export const MEMO_DISPLAY_TS_OPTION_SELECTOR_ITEMS = [
|
||||
];
|
||||
|
||||
export const TAB_SPACE_WIDTH = 2;
|
||||
|
||||
export const APPERANCE_OPTIONS = ["auto", "light", "dark"] as const;
|
||||
export const APPERANCE_OPTIONS_STORAGE_KEY = "setting_APPERANCE_OPTIONS";
|
||||
|
65
web/src/hooks/useAppearance.ts
Normal file
65
web/src/hooks/useAppearance.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { useEffect } from "react";
|
||||
import { useColorScheme } from "@mui/joy/styles";
|
||||
import { useAppSelector } from "../store";
|
||||
import { globalService } from "../services";
|
||||
|
||||
const getSystemColorScheme = () => {
|
||||
if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||
return "dark";
|
||||
} else {
|
||||
return "light";
|
||||
}
|
||||
};
|
||||
|
||||
const useAppearance = () => {
|
||||
const user = useAppSelector((state) => state.user.user);
|
||||
const appearance = useAppSelector((state) => state.global.appearance);
|
||||
const { mode, setMode } = useColorScheme();
|
||||
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
globalService.setAppearance(user.setting.appearance);
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
useEffect(() => {
|
||||
let mode = appearance;
|
||||
if (appearance === "system") {
|
||||
mode = getSystemColorScheme();
|
||||
}
|
||||
setMode(mode);
|
||||
}, [appearance]);
|
||||
|
||||
useEffect(() => {
|
||||
const colorSchemeChangeHandler = (event: MediaQueryListEvent) => {
|
||||
const newColorScheme = event.matches ? "dark" : "light";
|
||||
if (globalService.getState().appearance === "system") {
|
||||
setMode(newColorScheme);
|
||||
}
|
||||
};
|
||||
|
||||
if (appearance !== "system") {
|
||||
window.matchMedia("(prefers-color-scheme: dark)").removeEventListener("change", colorSchemeChangeHandler);
|
||||
return;
|
||||
}
|
||||
|
||||
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", colorSchemeChangeHandler);
|
||||
|
||||
return () => {
|
||||
window.matchMedia("(prefers-color-scheme: dark)").removeEventListener("change", colorSchemeChangeHandler);
|
||||
};
|
||||
}, [appearance]);
|
||||
|
||||
useEffect(() => {
|
||||
const root = document.documentElement;
|
||||
if (mode === "dark") {
|
||||
root.classList.add("dark");
|
||||
} else if (mode === "light") {
|
||||
root.classList.remove("dark");
|
||||
}
|
||||
}, [mode]);
|
||||
|
||||
return [appearance, globalService.setAppearance] as const;
|
||||
};
|
||||
|
||||
export default useAppearance;
|
@ -1,30 +0,0 @@
|
||||
import { useEffect } from "react";
|
||||
import { useColorScheme } from "@mui/joy/styles";
|
||||
|
||||
import { APPERANCE_OPTIONS, APPERANCE_OPTIONS_STORAGE_KEY } from "../helpers/consts";
|
||||
import useLocalStorage from "./useLocalStorage";
|
||||
import useMediaQuery from "./useMediaQuery";
|
||||
|
||||
export type Apperance = typeof APPERANCE_OPTIONS[number];
|
||||
|
||||
const useApperance = () => {
|
||||
const [apperance, setApperance] = useLocalStorage<Apperance>(APPERANCE_OPTIONS_STORAGE_KEY, APPERANCE_OPTIONS[0]);
|
||||
const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)");
|
||||
|
||||
const { setMode } = useColorScheme();
|
||||
|
||||
useEffect(() => {
|
||||
const root = document.documentElement;
|
||||
if (apperance === "dark" || (apperance === "auto" && prefersDarkMode)) {
|
||||
root.classList.add("dark");
|
||||
setMode("dark");
|
||||
} else {
|
||||
root.classList.remove("dark");
|
||||
setMode("light");
|
||||
}
|
||||
}, [apperance, prefersDarkMode]);
|
||||
|
||||
return [apperance, setApperance] as const;
|
||||
};
|
||||
|
||||
export default useApperance;
|
@ -1,26 +0,0 @@
|
||||
import { useState } from "react";
|
||||
|
||||
const useLocalStorage = <T>(key: string, initialValue: T) => {
|
||||
const [storedValue, setStoredValue] = useState<T>(() => {
|
||||
try {
|
||||
const item = window.localStorage.getItem(key);
|
||||
return item ? JSON.parse(item) : initialValue;
|
||||
} catch (error) {
|
||||
return initialValue;
|
||||
}
|
||||
});
|
||||
|
||||
const setValue = (value: T | ((val: T) => T)) => {
|
||||
try {
|
||||
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
||||
setStoredValue(valueToStore);
|
||||
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
return [storedValue, setValue] as const;
|
||||
};
|
||||
|
||||
export default useLocalStorage;
|
@ -152,7 +152,7 @@
|
||||
"enable-folding-memo": "Enable folding memo",
|
||||
"editor-font-style": "Editor font style",
|
||||
"mobile-editor-style": "Mobile editor style",
|
||||
"default-memo-sort-option": "Display by created/updated time",
|
||||
"default-memo-sort-option": "Memo display time",
|
||||
"created_ts": "Created Time",
|
||||
"updated_ts": "Updated Time"
|
||||
},
|
||||
@ -168,9 +168,9 @@
|
||||
"additional-script-placeholder": "Additional JavaScript codes"
|
||||
},
|
||||
"apperance-option": {
|
||||
"auto": "Follow system",
|
||||
"light": "Always light",
|
||||
"dark": "Always dark"
|
||||
"dark": "Always dark",
|
||||
"system": "Follow system"
|
||||
}
|
||||
},
|
||||
"amount-text": {
|
||||
|
@ -152,7 +152,7 @@
|
||||
"enable-folding-memo": "Activer le mémo pliable",
|
||||
"editor-font-style": "Style de police de l'éditeur",
|
||||
"mobile-editor-style": "Style de l'éditeur mobile",
|
||||
"default-memo-sort-option": "Affichage par heure de création/mise à jour",
|
||||
"default-memo-sort-option": "Memo display time",
|
||||
"created_ts": "Heure de création",
|
||||
"updated_ts": "Heure de mise à jour"
|
||||
},
|
||||
|
@ -151,7 +151,7 @@
|
||||
"enable-folding-memo": "Enable folding memo",
|
||||
"editor-font-style": "Thay đổi font cho trình soạn thảo",
|
||||
"mobile-editor-style": "Vị trí editor trên mobile",
|
||||
"default-memo-sort-option": "Sắp xếp theo thời gian đã tạo",
|
||||
"default-memo-sort-option": "Memo display time",
|
||||
"created_ts": "tạo thời gian",
|
||||
"updated_ts": "Thời gian cập nhật"
|
||||
},
|
||||
|
@ -152,7 +152,7 @@
|
||||
"enable-folding-memo": "开启折叠 Memo",
|
||||
"editor-font-style": "编辑器字体样式",
|
||||
"mobile-editor-style": "移动端编辑器样式",
|
||||
"default-memo-sort-option": "按创建时间/更新时间显示",
|
||||
"default-memo-sort-option": "Memo 显示时间",
|
||||
"created_ts": "创建时间",
|
||||
"updated_ts": "更新时间"
|
||||
},
|
||||
@ -168,9 +168,9 @@
|
||||
"additional-script-placeholder": "自定义 JavaScript 代码"
|
||||
},
|
||||
"apperance-option": {
|
||||
"auto": "跟随系统",
|
||||
"light": "总是浅色",
|
||||
"dark": "总是深色"
|
||||
"dark": "总是深色",
|
||||
"system": "跟随系统"
|
||||
}
|
||||
},
|
||||
"amount-text": {
|
||||
|
@ -9,7 +9,7 @@ import useLoading from "../hooks/useLoading";
|
||||
import { globalService, userService } from "../services";
|
||||
import Icon from "../components/Icon";
|
||||
import toastHelper from "../components/Toast";
|
||||
import ApperanceSelect from "../components/ApperanceSelect";
|
||||
import AppearanceSelect from "../components/AppearanceSelect";
|
||||
import "../less/auth.less";
|
||||
|
||||
const validateConfig: ValidatorConfig = {
|
||||
@ -177,7 +177,7 @@ const Auth = () => {
|
||||
<Option value="vi">Tiếng Việt</Option>
|
||||
<Option value="fr">French</Option>
|
||||
</Select>
|
||||
<ApperanceSelect />
|
||||
<AppearanceSelect />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -18,6 +18,7 @@ const router = createBrowserRouter([
|
||||
} catch (error) {
|
||||
// do nth
|
||||
}
|
||||
return null;
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -37,6 +38,7 @@ const router = createBrowserRouter([
|
||||
} else if (isNullorUndefined(user)) {
|
||||
return redirect("/explore");
|
||||
}
|
||||
return null;
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -54,6 +56,7 @@ const router = createBrowserRouter([
|
||||
if (isNullorUndefined(host)) {
|
||||
return redirect("/auth");
|
||||
}
|
||||
return null;
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -71,6 +74,7 @@ const router = createBrowserRouter([
|
||||
if (isNullorUndefined(host)) {
|
||||
return redirect("/auth");
|
||||
}
|
||||
return null;
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -88,6 +92,7 @@ const router = createBrowserRouter([
|
||||
if (isNullorUndefined(host)) {
|
||||
return redirect("/auth");
|
||||
}
|
||||
return null;
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import store from "../store";
|
||||
import * as api from "../helpers/api";
|
||||
import * as storage from "../helpers/storage";
|
||||
import { setGlobalState, setLocale } from "../store/modules/global";
|
||||
import { setAppearance, setGlobalState, setLocale } from "../store/modules/global";
|
||||
|
||||
const globalService = {
|
||||
getState: () => {
|
||||
@ -11,6 +11,7 @@ const globalService = {
|
||||
initialState: async () => {
|
||||
const defaultGlobalState = {
|
||||
locale: "en" as Locale,
|
||||
appearance: "system" as Appearance,
|
||||
systemStatus: {
|
||||
allowSignUp: false,
|
||||
additionalStyle: "",
|
||||
@ -38,6 +39,10 @@ const globalService = {
|
||||
setLocale: (locale: Locale) => {
|
||||
store.dispatch(setLocale(locale));
|
||||
},
|
||||
|
||||
setAppearance: (appearance: Appearance) => {
|
||||
store.dispatch(setAppearance(appearance));
|
||||
},
|
||||
};
|
||||
|
||||
export default globalService;
|
||||
|
@ -8,6 +8,7 @@ import { setUser, patchUser, setHost, setOwner } from "../store/modules/user";
|
||||
|
||||
const defaultSetting: Setting = {
|
||||
locale: "en",
|
||||
appearance: "system",
|
||||
memoVisibility: "PRIVATE",
|
||||
memoDisplayTsOption: "created_ts",
|
||||
};
|
||||
|
@ -2,6 +2,7 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
|
||||
interface State {
|
||||
locale: Locale;
|
||||
appearance: Appearance;
|
||||
systemStatus: SystemStatus;
|
||||
}
|
||||
|
||||
@ -9,6 +10,7 @@ const globalSlice = createSlice({
|
||||
name: "global",
|
||||
initialState: {
|
||||
locale: "en",
|
||||
appearance: "system",
|
||||
systemStatus: {
|
||||
host: undefined,
|
||||
profile: {
|
||||
@ -31,9 +33,15 @@ const globalSlice = createSlice({
|
||||
locale: action.payload,
|
||||
};
|
||||
},
|
||||
setAppearance: (state, action: PayloadAction<Appearance>) => {
|
||||
return {
|
||||
...state,
|
||||
appearance: action.payload,
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { setGlobalState, setLocale } = globalSlice.actions;
|
||||
export const { setGlobalState, setLocale, setAppearance } = globalSlice.actions;
|
||||
|
||||
export default globalSlice.reducer;
|
||||
|
@ -3,10 +3,8 @@ import { extendTheme } from "@mui/joy";
|
||||
const theme = extendTheme({
|
||||
components: {
|
||||
JoySelect: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
fontSize: "0.875rem",
|
||||
},
|
||||
defaultProps: {
|
||||
size: "sm",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
10
web/src/types/modules/setting.d.ts
vendored
10
web/src/types/modules/setting.d.ts
vendored
@ -1,5 +1,8 @@
|
||||
type Appearance = "light" | "dark" | "system";
|
||||
|
||||
interface Setting {
|
||||
locale: Locale;
|
||||
appearance: Appearance;
|
||||
memoVisibility: Visibility;
|
||||
memoDisplayTsOption: "created_ts" | "updated_ts";
|
||||
}
|
||||
@ -13,12 +16,17 @@ interface UserLocaleSetting {
|
||||
value: Locale;
|
||||
}
|
||||
|
||||
interface UserAppearanceSetting {
|
||||
key: "appearance";
|
||||
value: Appearance;
|
||||
}
|
||||
|
||||
interface UserMemoVisibilitySetting {
|
||||
key: "memoVisibility";
|
||||
value: Visibility;
|
||||
}
|
||||
|
||||
type UserSetting = UserLocaleSetting | UserMemoVisibilitySetting;
|
||||
type UserSetting = UserLocaleSetting | UserAppearanceSetting | UserMemoVisibilitySetting;
|
||||
|
||||
interface UserSettingUpsert {
|
||||
key: keyof Setting;
|
||||
|
Loading…
Reference in New Issue
Block a user