Files
outline/app/scenes/Settings/Preferences.tsx
codegen-sh[bot] 879c568a2c Upgrade Prettier to v3.6.2 (#9500)
* Upgrade Prettier to v3.6.2 and eslint-plugin-prettier to v5.5.1

- Upgraded prettier from ^2.8.8 to ^3.6.2 (latest version)
- Upgraded eslint-plugin-prettier from ^4.2.1 to ^5.5.1 for compatibility
- Applied automatic formatting changes from new Prettier version
- All existing ESLint and Prettier configurations remain compatible

* Applied automatic fixes

* Trigger CI

---------

Co-authored-by: codegen-sh[bot] <131295404+codegen-sh[bot]@users.noreply.github.com>
Co-authored-by: Tom Moor <tom@getoutline.com>
2025-06-28 10:22:28 -04:00

272 lines
8.1 KiB
TypeScript

import { observer } from "mobx-react";
import { SettingsIcon } from "outline-icons";
import * as React from "react";
import { Trans, useTranslation } from "react-i18next";
import { toast } from "sonner";
import { languageOptions as availableLanguages } from "@shared/i18n";
import { TeamPreference, UserPreference } from "@shared/types";
import { Theme } from "~/stores/UiStore";
import Button from "~/components/Button";
import Heading from "~/components/Heading";
import { InputSelectNew, Option } from "~/components/InputSelectNew";
import Scene from "~/components/Scene";
import Switch from "~/components/Switch";
import Text from "~/components/Text";
import useCurrentTeam from "~/hooks/useCurrentTeam";
import useCurrentUser from "~/hooks/useCurrentUser";
import usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores";
import UserDelete from "../UserDelete";
import SettingRow from "./components/SettingRow";
function Preferences() {
const { t } = useTranslation();
const { ui, dialogs } = useStores();
const user = useCurrentUser();
const team = useCurrentTeam();
const can = usePolicy(user.id);
const languageOptions: Option[] = React.useMemo(
() =>
availableLanguages.map(
(lang) =>
({
type: "item",
label: lang.label,
value: lang.value,
}) satisfies Option
),
[]
);
const themeOptions: Option[] = React.useMemo(
() =>
[
{ type: "item", label: t("Light"), value: Theme.Light },
{ type: "item", label: t("Dark"), value: Theme.Dark },
{ type: "item", label: t("System"), value: Theme.System },
] satisfies Option[],
[t]
);
const handleUseCursorPointerChange = React.useCallback(
async (checked: boolean) => {
user.setPreference(UserPreference.UseCursorPointer, checked);
await user.save();
toast.success(t("Preferences saved"));
},
[user, t]
);
const handleCodeBlockLineNumbersChange = React.useCallback(
async (checked: boolean) => {
user.setPreference(UserPreference.CodeBlockLineNumers, checked);
await user.save();
toast.success(t("Preferences saved"));
},
[user, t]
);
const handleSeamlessEditChange = React.useCallback(
async (checked: boolean) => {
user.setPreference(UserPreference.SeamlessEdit, !checked);
await user.save();
toast.success(t("Preferences saved"));
},
[user, t]
);
const handleRememberLastPathChange = React.useCallback(
async (checked: boolean) => {
user.setPreference(UserPreference.RememberLastPath, checked);
await user.save();
toast.success(t("Preferences saved"));
},
[user, t]
);
const handleEnableSmartTextChange = React.useCallback(
async (checked: boolean) => {
user.setPreference(UserPreference.EnableSmartText, checked);
await user.save();
toast.success(t("Preferences saved"));
},
[user, t]
);
const handleLanguageChange = React.useCallback(
async (language: string) => {
await user.save({ language });
toast.success(t("Preferences saved"));
},
[t, user]
);
const handleThemeChange = React.useCallback(
(theme) => {
ui.setTheme(theme as Theme);
toast.success(t("Preferences saved"));
},
[t, ui]
);
const showDeleteAccount = () => {
dialogs.openModal({
title: t("Delete account"),
content: <UserDelete onSubmit={dialogs.closeAllModals} />,
});
};
return (
<Scene title={t("Preferences")} icon={<SettingsIcon />}>
<Heading>{t("Preferences")}</Heading>
<Text as="p" type="secondary">
<Trans>Manage settings that affect your personal experience.</Trans>
</Text>
<Heading as="h2">{t("Display")}</Heading>
<SettingRow
label={t("Language")}
name="language"
description={
<>
<Trans>
Choose the interface language. Community translations are accepted
though our{" "}
<a
href="https://translate.getoutline.com"
target="_blank"
rel="noreferrer"
>
translation portal
</a>
.
</Trans>
</>
}
>
<InputSelectNew
options={languageOptions}
value={user.language}
onChange={handleLanguageChange}
ariaLabel={t("Language")}
label={t("Language")}
hideLabel
/>
</SettingRow>
<SettingRow
name="theme"
label={t("Appearance")}
description={t("Choose your preferred interface color scheme.")}
>
<InputSelectNew
options={themeOptions}
value={ui.theme}
onChange={handleThemeChange}
ariaLabel={t("Appearance")}
label={t("Appearance")}
hideLabel
/>
</SettingRow>
<SettingRow
name={UserPreference.UseCursorPointer}
label={t("Use pointer cursor")}
description={t(
"Show a hand cursor when hovering over interactive elements."
)}
>
<Switch
id={UserPreference.UseCursorPointer}
name={UserPreference.UseCursorPointer}
checked={user.getPreference(UserPreference.UseCursorPointer)}
onChange={handleUseCursorPointerChange}
/>
</SettingRow>
<SettingRow
name={UserPreference.CodeBlockLineNumers}
label={t("Show line numbers")}
description={t("Show line numbers on code blocks in documents.")}
border={false}
>
<Switch
id={UserPreference.CodeBlockLineNumers}
name={UserPreference.CodeBlockLineNumers}
checked={user.getPreference(UserPreference.CodeBlockLineNumers)}
onChange={handleCodeBlockLineNumbersChange}
/>
</SettingRow>
<Heading as="h2">{t("Behavior")}</Heading>
<SettingRow
name={UserPreference.SeamlessEdit}
label={t("Separate editing")}
description={t(
`When enabled, documents have a separate editing mode. When disabled, documents are always editable when you have permission.`
)}
>
<Switch
id={UserPreference.SeamlessEdit}
name={UserPreference.SeamlessEdit}
checked={
!user.getPreference(
UserPreference.SeamlessEdit,
team.getPreference(TeamPreference.SeamlessEdit)
)
}
onChange={handleSeamlessEditChange}
/>
</SettingRow>
<SettingRow
name={UserPreference.RememberLastPath}
label={t("Remember previous location")}
description={t(
"Automatically return to the document you were last viewing when the app is re-opened."
)}
>
<Switch
id={UserPreference.RememberLastPath}
name={UserPreference.RememberLastPath}
checked={!!user.getPreference(UserPreference.RememberLastPath)}
onChange={handleRememberLastPathChange}
/>
</SettingRow>
<SettingRow
border={false}
name={UserPreference.EnableSmartText}
label={t("Smart text replacements")}
description={t(
"Auto-format text by replacing shortcuts with symbols, dashes, smart quotes, and other typographical elements."
)}
>
<Switch
id={UserPreference.EnableSmartText}
name={UserPreference.EnableSmartText}
checked={!!user.getPreference(UserPreference.EnableSmartText)}
onChange={handleEnableSmartTextChange}
/>
</SettingRow>
{can.delete && (
<>
<Heading as="h2">{t("Danger")}</Heading>
<SettingRow
name="delete"
label={t("Delete account")}
description={t(
"You may delete your account at any time, note that this is unrecoverable"
)}
>
<span>
<Button onClick={showDeleteAccount} neutral>
{t("Delete account")}
</Button>
</span>
</SettingRow>
</>
)}
</Scene>
);
}
export default observer(Preferences);