diff --git a/app/menus/DocumentMenu.tsx b/app/menus/DocumentMenu.tsx
index bf1d62984..94806a293 100644
--- a/app/menus/DocumentMenu.tsx
+++ b/app/menus/DocumentMenu.tsx
@@ -40,7 +40,7 @@ import {
openDocumentComments,
} from "~/actions/definitions/documents";
import useActionContext from "~/hooks/useActionContext";
-import useCurrentTeam from "~/hooks/useCurrentTeam";
+import useCurrentUser from "~/hooks/useCurrentUser";
import useMobile from "~/hooks/useMobile";
import usePolicy from "~/hooks/usePolicy";
import useRequest from "~/hooks/useRequest";
@@ -73,7 +73,7 @@ function DocumentMenu({
onOpen,
onClose,
}: Props) {
- const team = useCurrentTeam();
+ const user = useCurrentUser();
const { policies, collections, documents, subscriptions } = useStores();
const { showToast } = useToasts();
const menu = useMenuState({
@@ -263,7 +263,7 @@ function DocumentMenu({
type: "route",
title: t("Edit"),
to: documentEditPath(document),
- visible: !!can.update && !team.seamlessEditing,
+ visible: !!can.update && user.separateEditMode,
icon: ,
},
{
diff --git a/app/models/Team.ts b/app/models/Team.ts
index 1f8c09cbf..9130156da 100644
--- a/app/models/Team.ts
+++ b/app/models/Team.ts
@@ -81,17 +81,6 @@ class Team extends BaseModel {
return this.name ? this.name[0] : "?";
}
- /**
- * Returns whether this team is using a separate editing mode behind an "Edit"
- * button rather than seamless always-editing.
- *
- * @returns True if editing mode is seamless (no button)
- */
- @computed
- get seamlessEditing(): boolean {
- return !!this.getPreference(TeamPreference.SeamlessEdit);
- }
-
/**
* Returns the value of the provided preference.
*
@@ -99,9 +88,15 @@ class Team extends BaseModel {
* @returns The preference value if set, else the default value
*/
getPreference(
- key: T
+ key: T,
+ defaultValue?: TeamPreferences[T]
): TeamPreferences[T] | false {
- return this.preferences?.[key] ?? TeamPreferenceDefaults[key] ?? false;
+ return (
+ this.preferences?.[key] ??
+ TeamPreferenceDefaults[key] ??
+ defaultValue ??
+ false
+ );
}
/**
diff --git a/app/models/User.ts b/app/models/User.ts
index 8b7a38181..3b031d581 100644
--- a/app/models/User.ts
+++ b/app/models/User.ts
@@ -5,6 +5,7 @@ import { UserPreferenceDefaults } from "@shared/constants";
import {
NotificationEventDefaults,
NotificationEventType,
+ TeamPreference,
UserPreference,
UserPreferences,
UserRole,
@@ -85,6 +86,20 @@ class User extends ParanoidModel {
}
}
+ /**
+ * Returns whether this user is using a separate editing mode behind an "Edit"
+ * button rather than seamless always-editing.
+ *
+ * @returns True if editing mode is seamless (no button)
+ */
+ @computed
+ get separateEditMode(): boolean {
+ return !this.getPreference(
+ UserPreference.SeamlessEdit,
+ this.store.rootStore.auth.team.getPreference(TeamPreference.SeamlessEdit)
+ );
+ }
+
/**
* Returns the current preference for the given notification event type taking
* into account the default system value.
@@ -130,8 +145,10 @@ class User extends ParanoidModel {
* @param key The UserPreference key to retrieve
* @returns The value
*/
- getPreference(key: UserPreference): boolean {
- return this.preferences?.[key] ?? UserPreferenceDefaults[key] ?? false;
+ getPreference(key: UserPreference, defaultValue = false): boolean {
+ return (
+ this.preferences?.[key] ?? UserPreferenceDefaults[key] ?? defaultValue
+ );
}
/**
diff --git a/app/scenes/Document/components/DataLoader.tsx b/app/scenes/Document/components/DataLoader.tsx
index 08b4bd63a..21b90e168 100644
--- a/app/scenes/Document/components/DataLoader.tsx
+++ b/app/scenes/Document/components/DataLoader.tsx
@@ -72,7 +72,7 @@ function DataLoader({ match, children }: Props) {
? documents.getSharedTree(document.id)
: undefined;
const isEditRoute = match.path === matchDocumentEdit;
- const isEditing = isEditRoute || !!auth.team?.seamlessEditing;
+ const isEditing = isEditRoute || !auth.user?.separateEditMode;
const can = usePolicy(document?.id);
const location = useLocation();
diff --git a/app/scenes/Document/components/Document.tsx b/app/scenes/Document/components/Document.tsx
index eb996db8f..8e3542d40 100644
--- a/app/scenes/Document/components/Document.tsx
+++ b/app/scenes/Document/components/Document.tsx
@@ -391,7 +391,7 @@ class DocumentScene extends React.Component {
render() {
const { document, revision, readOnly, abilities, auth, ui, shareId, t } =
this.props;
- const team = auth.team;
+ const { team, user } = auth;
const isShare = !!shareId;
const embedsDisabled =
(team && team.documentEmbeds === false) || document.embedsDisabled;
@@ -463,7 +463,7 @@ class DocumentScene extends React.Component {
revision={revision}
shareId={shareId}
isDraft={document.isDraft}
- isEditing={!readOnly && !team?.seamlessEditing}
+ isEditing={!readOnly && !!user?.separateEditMode}
isSaving={this.isSaving}
isPublishing={this.isPublishing}
publishingIsDisabled={
diff --git a/app/scenes/Document/components/Header.tsx b/app/scenes/Document/components/Header.tsx
index 5107d0c15..3a47545de 100644
--- a/app/scenes/Document/components/Header.tsx
+++ b/app/scenes/Document/components/Header.tsx
@@ -85,7 +85,7 @@ function DocumentHeader({
const { ui, auth } = useStores();
const theme = useTheme();
const { resolvedTheme } = ui;
- const { team } = auth;
+ const { team, user } = auth;
const isMobile = useMobile();
const isRevision = !!revision;
@@ -224,11 +224,11 @@ function DocumentHeader({
<>
- {!isPublishing && isSaving && !team?.seamlessEditing && (
+ {!isPublishing && isSaving && user?.separateEditMode && (
{t("Saving")}…
)}
{!isDeleted && !isRevision && }
- {(isEditing || team?.seamlessEditing) && !isTemplate && isNew && (
+ {(isEditing || !user?.separateEditMode) && !isTemplate && isNew && (
- ) => {
- const preferences = {
- ...team.preferences,
- [ev.target.name]: ev.target.checked,
- };
+ const handlePreferenceChange =
+ (inverted = false) =>
+ async (ev: React.ChangeEvent) => {
+ const preferences = {
+ ...team.preferences,
+ [ev.target.name]: inverted ? !ev.target.checked : ev.target.checked,
+ };
- await auth.updateTeam({ preferences });
- showToast(t("Settings saved"), {
- type: "success",
- });
- };
+ await auth.updateTeam({ preferences });
+ showToast(t("Settings saved"), {
+ type: "success",
+ });
+ };
return (
}>
@@ -43,16 +43,16 @@ function Features() {
diff --git a/app/scenes/Settings/Preferences.tsx b/app/scenes/Settings/Preferences.tsx
index ac56db4a9..62008ae30 100644
--- a/app/scenes/Settings/Preferences.tsx
+++ b/app/scenes/Settings/Preferences.tsx
@@ -3,13 +3,14 @@ import { SettingsIcon } from "outline-icons";
import * as React from "react";
import { Trans, useTranslation } from "react-i18next";
import { languageOptions } from "@shared/i18n";
-import { UserPreference } from "@shared/types";
+import { TeamPreference, UserPreference } from "@shared/types";
import Button from "~/components/Button";
import Heading from "~/components/Heading";
import InputSelect from "~/components/InputSelect";
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 useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
@@ -21,21 +22,22 @@ function Preferences() {
const { showToast } = useToasts();
const { dialogs, auth } = useStores();
const user = useCurrentUser();
+ const team = useCurrentTeam();
- const handlePreferenceChange = async (
- ev: React.ChangeEvent
- ) => {
- const preferences = {
- ...user.preferences,
- [ev.target.name]: ev.target.checked,
+ const handlePreferenceChange =
+ (inverted = false) =>
+ async (ev: React.ChangeEvent) => {
+ const preferences = {
+ ...user.preferences,
+ [ev.target.name]: inverted ? !ev.target.checked : ev.target.checked,
+ };
+
+ await auth.updateUser({ preferences });
+ showToast(t("Preferences saved"), {
+ type: "success",
+ });
};
- await auth.updateUser({ preferences });
- showToast(t("Preferences saved"), {
- type: "success",
- });
- };
-
const handleLanguageChange = async (language: string) => {
await auth.updateUser({ language });
showToast(t("Preferences saved"), {
@@ -98,7 +100,7 @@ function Preferences() {
id={UserPreference.UseCursorPointer}
name={UserPreference.UseCursorPointer}
checked={user.getPreference(UserPreference.UseCursorPointer)}
- onChange={handlePreferenceChange}
+ onChange={handlePreferenceChange(false)}
/>
{t("Behavior")}
+
+
+
diff --git a/shared/i18n/locales/en_US/translation.json b/shared/i18n/locales/en_US/translation.json
index 7b14da6f2..e92c0f352 100644
--- a/shared/i18n/locales/en_US/translation.json
+++ b/shared/i18n/locales/en_US/translation.json
@@ -781,8 +781,8 @@
"A full export might take some time, consider exporting a single document or collection. The exported data is a zip of your documents in Markdown format. You may leave this page once the export has started – if you have notifications enabled, we will email a link to {{ userEmail }} when it’s complete.": "A full export might take some time, consider exporting a single document or collection. The exported data is a zip of your documents in Markdown format. You may leave this page once the export has started – if you have notifications enabled, we will email a link to {{ userEmail }} when it’s complete.",
"Recent exports": "Recent exports",
"Manage optional and beta features. Changing these settings will affect the experience for all members of the workspace.": "Manage optional and beta features. Changing these settings will affect the experience for all members of the workspace.",
- "Seamless editing": "Seamless editing",
- "When enabled documents are always editable for team members that have permission. When disabled there is a separate editing view.": "When enabled documents are always editable for team members that have permission. When disabled there is a separate editing view.",
+ "Separate editing": "Separate editing",
+ "When enabled documents have a separate editing mode by default instead of being always editable. This setting can be overridden by user preferences.": "When enabled documents have a separate editing mode by default instead of being always editable. This setting can be overridden by user preferences.",
"Commenting": "Commenting",
"When enabled team members can add comments to documents.": "When enabled team members can add comments to documents.",
"Add a Google Analytics 4 measurement ID to send document views and analytics from the workspace to your own Google Analytics account.": "Add a Google Analytics 4 measurement ID to send document views and analytics from the workspace to your own Google Analytics account.",
@@ -834,6 +834,7 @@
"Show a hand cursor when hovering over interactive elements.": "Show a hand cursor when hovering over interactive elements.",
"Show line numbers": "Show line numbers",
"Show line numbers on code blocks in documents.": "Show line numbers on code blocks in documents.",
+ "When enabled documents have a separate editing mode, when disabled documents are always editable when you have permission.": "When enabled documents have a separate editing mode, when disabled documents are always editable when you have permission.",
"Remember previous location": "Remember previous location",
"Automatically return to the document you were last viewing when the app is re-opened.": "Automatically return to the document you were last viewing when the app is re-opened.",
"You may delete your account at any time, note that this is unrecoverable": "You may delete your account at any time, note that this is unrecoverable",
diff --git a/shared/types.ts b/shared/types.ts
index d9a13bb2a..3802e52ab 100644
--- a/shared/types.ts
+++ b/shared/types.ts
@@ -114,6 +114,8 @@ export enum UserPreference {
UseCursorPointer = "useCursorPointer",
/** Whether code blocks should show line numbers. */
CodeBlockLineNumers = "codeBlockLineNumbers",
+ /** Whether documents have a separate edit mode instead of always editing. */
+ SeamlessEdit = "seamlessEdit",
}
export type UserPreferences = { [key in UserPreference]?: boolean };
@@ -130,7 +132,7 @@ export type PublicTeam = {
};
export enum TeamPreference {
- /** Whether documents have a separate edit mode instead of seamless editing. */
+ /** Whether documents have a separate edit mode instead of always editing. */
SeamlessEdit = "seamlessEdit",
/** Whether to use team logo across the app for branding. */
PublicBranding = "publicBranding",