mirror of
https://github.com/docmost/docmost.git
synced 2026-05-18 23:44:24 +08:00
feat: edit mode preference (#666)
* lock/unlock pages * remove using isLocked column - add default page edit state preference * * Move state management to editors (avoids flickers on edit mode switch) * Rename variables * Add strings to translation file * Memoize components in page component * Fix title editor sending update request on editable state change * fixed errors merging main * Fix embed view in read-only mode * remove unused line * sync * fix responsiveness on mobile --------- Co-authored-by: Philipinho <16838612+Philipinho@users.noreply.github.com>
This commit is contained in:
@@ -354,6 +354,9 @@
|
|||||||
"Character count: {{characterCount}}": "Character count: {{characterCount}}",
|
"Character count: {{characterCount}}": "Character count: {{characterCount}}",
|
||||||
"New update": "New update",
|
"New update": "New update",
|
||||||
"{{latestVersion}} is available": "{{latestVersion}} is available",
|
"{{latestVersion}} is available": "{{latestVersion}} is available",
|
||||||
|
"Default page edit mode": "Default page edit mode",
|
||||||
|
"Choose your preferred page edit mode. Avoid accidental edits.": "Choose your preferred page edit mode. Avoid accidental edits.",
|
||||||
|
"Reading": "Reading"
|
||||||
"Delete member": "Delete member",
|
"Delete member": "Delete member",
|
||||||
"Member deleted successfully": "Member deleted successfully",
|
"Member deleted successfully": "Member deleted successfully",
|
||||||
"Are you sure you want to delete this workspace member? This action is irreversible.": "Are you sure you want to delete this workspace member? This action is irreversible.",
|
"Are you sure you want to delete this workspace member? This action is irreversible.": "Are you sure you want to delete this workspace member? This action is irreversible.",
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ const schema = z.object({
|
|||||||
|
|
||||||
export default function EmbedView(props: NodeViewProps) {
|
export default function EmbedView(props: NodeViewProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { node, selected, updateAttributes } = props;
|
const { node, selected, updateAttributes, editor } = props;
|
||||||
const { src, provider } = node.attrs;
|
const { src, provider } = node.attrs;
|
||||||
|
|
||||||
const embedUrl = useMemo(() => {
|
const embedUrl = useMemo(() => {
|
||||||
@@ -50,6 +50,10 @@ export default function EmbedView(props: NodeViewProps) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function onSubmit(data: { url: string }) {
|
async function onSubmit(data: { url: string }) {
|
||||||
|
if (!editor.isEditable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (provider) {
|
if (provider) {
|
||||||
const embedProvider = getEmbedProviderById(provider);
|
const embedProvider = getEmbedProviderById(provider);
|
||||||
if (embedProvider.id === "iframe") {
|
if (embedProvider.id === "iframe") {
|
||||||
@@ -85,7 +89,13 @@ export default function EmbedView(props: NodeViewProps) {
|
|||||||
</AspectRatio>
|
</AspectRatio>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Popover width={300} position="bottom" withArrow shadow="md">
|
<Popover
|
||||||
|
width={300}
|
||||||
|
position="bottom"
|
||||||
|
withArrow
|
||||||
|
shadow="md"
|
||||||
|
disabled={!editor.isEditable}
|
||||||
|
>
|
||||||
<Popover.Target>
|
<Popover.Target>
|
||||||
<Card
|
<Card
|
||||||
radius="md"
|
radius="md"
|
||||||
|
|||||||
@@ -42,7 +42,11 @@ export function FullEditor({
|
|||||||
spaceSlug={spaceSlug}
|
spaceSlug={spaceSlug}
|
||||||
editable={editable}
|
editable={editable}
|
||||||
/>
|
/>
|
||||||
<MemoizedPageEditor pageId={pageId} editable={editable} content={content} />
|
<MemoizedPageEditor
|
||||||
|
pageId={pageId}
|
||||||
|
editable={editable}
|
||||||
|
content={content}
|
||||||
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ import { IPage } from "@/features/page/types/page.types.ts";
|
|||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { extractPageSlugId } from "@/lib";
|
import { extractPageSlugId } from "@/lib";
|
||||||
import { FIVE_MINUTES } from "@/lib/constants.ts";
|
import { FIVE_MINUTES } from "@/lib/constants.ts";
|
||||||
|
import { PageEditMode } from "@/features/user/types/user.types.ts";
|
||||||
import { jwtDecode } from "jwt-decode";
|
import { jwtDecode } from "jwt-decode";
|
||||||
|
|
||||||
interface PageEditorProps {
|
interface PageEditorProps {
|
||||||
@@ -85,6 +86,8 @@ export default function PageEditor({
|
|||||||
const [isCollabReady, setIsCollabReady] = useState(false);
|
const [isCollabReady, setIsCollabReady] = useState(false);
|
||||||
const { pageSlug } = useParams();
|
const { pageSlug } = useParams();
|
||||||
const slugId = extractPageSlugId(pageSlug);
|
const slugId = extractPageSlugId(pageSlug);
|
||||||
|
const userPageEditMode =
|
||||||
|
currentUser?.user?.settings?.preferences?.pageEditMode ?? PageEditMode.Edit;
|
||||||
|
|
||||||
const localProvider = useMemo(() => {
|
const localProvider = useMemo(() => {
|
||||||
const provider = new IndexeddbPersistence(documentName, ydoc);
|
const provider = new IndexeddbPersistence(documentName, ydoc);
|
||||||
@@ -290,6 +293,17 @@ export default function PageEditor({
|
|||||||
return () => clearTimeout(collabReadyTimeout);
|
return () => clearTimeout(collabReadyTimeout);
|
||||||
}, [isRemoteSynced, isLocalSynced, remoteProvider?.status]);
|
}, [isRemoteSynced, isLocalSynced, remoteProvider?.status]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// honor user default page edit mode preference
|
||||||
|
if (userPageEditMode && editor && editable && isSynced) {
|
||||||
|
if (userPageEditMode === PageEditMode.Edit) {
|
||||||
|
editor.setEditable(true);
|
||||||
|
} else if (userPageEditMode === PageEditMode.Read) {
|
||||||
|
editor.setEditable(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [userPageEditMode, editor, editable, isSynced]);
|
||||||
|
|
||||||
return isCollabReady ? (
|
return isCollabReady ? (
|
||||||
<div>
|
<div>
|
||||||
<div ref={menuContainerRef}>
|
<div ref={menuContainerRef}>
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import { useTranslation } from "react-i18next";
|
|||||||
import EmojiCommand from "@/features/editor/extensions/emoji-command.ts";
|
import EmojiCommand from "@/features/editor/extensions/emoji-command.ts";
|
||||||
import { UpdateEvent } from "@/features/websocket/types";
|
import { UpdateEvent } from "@/features/websocket/types";
|
||||||
import localEmitter from "@/lib/local-emitter.ts";
|
import localEmitter from "@/lib/local-emitter.ts";
|
||||||
|
import { currentUserAtom } from "@/features/user/atoms/current-user-atom.ts";
|
||||||
|
import { PageEditMode } from "@/features/user/types/user.types.ts";
|
||||||
|
|
||||||
export interface TitleEditorProps {
|
export interface TitleEditorProps {
|
||||||
pageId: string;
|
pageId: string;
|
||||||
@@ -44,6 +46,9 @@ export function TitleEditor({
|
|||||||
const emit = useQueryEmit();
|
const emit = useQueryEmit();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [activePageId, setActivePageId] = useState(pageId);
|
const [activePageId, setActivePageId] = useState(pageId);
|
||||||
|
const [currentUser] = useAtom(currentUserAtom);
|
||||||
|
const userPageEditMode =
|
||||||
|
currentUser?.user?.settings?.preferences?.pageEditMode ?? PageEditMode.Edit;
|
||||||
|
|
||||||
const titleEditor = useEditor({
|
const titleEditor = useEditor({
|
||||||
extensions: [
|
extensions: [
|
||||||
@@ -136,7 +141,18 @@ export function TitleEditor({
|
|||||||
};
|
};
|
||||||
}, [pageId]);
|
}, [pageId]);
|
||||||
|
|
||||||
function handleTitleKeyDown(event) {
|
useEffect(() => {
|
||||||
|
// honor user default page edit mode preference
|
||||||
|
if (userPageEditMode && titleEditor && editable) {
|
||||||
|
if (userPageEditMode === PageEditMode.Edit) {
|
||||||
|
titleEditor.setEditable(true);
|
||||||
|
} else if (userPageEditMode === PageEditMode.Read) {
|
||||||
|
titleEditor.setEditable(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [userPageEditMode, titleEditor, editable]);
|
||||||
|
|
||||||
|
function handleTitleKeyDown(event: any) {
|
||||||
if (!titleEditor || !pageEditor || event.shiftKey) return;
|
if (!titleEditor || !pageEditor || event.shiftKey) return;
|
||||||
|
|
||||||
const { key } = event;
|
const { key } = event;
|
||||||
|
|||||||
@@ -1,24 +1,30 @@
|
|||||||
.breadcrumbs {
|
.breadcrumbs {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--mantine-color-default-color);
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mantine-Breadcrumbs-breadcrumb {
|
||||||
|
min-width: 1px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
flex-wrap: nowrap;
|
}
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--mantine-color-default-color);
|
|
||||||
line-height: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mantine-Breadcrumbs-breadcrumb {
|
|
||||||
min-width: 1px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.truncatedText {
|
.truncatedText {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.breadcrumbDiv {
|
||||||
|
overflow: hidden;
|
||||||
|
@media (max-width: $mantine-breakpoint-sm) {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ export default function Breadcrumb() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ overflow: "hidden" }}>
|
<div className={classes.breadcrumbDiv}>
|
||||||
{breadcrumbNodes && (
|
{breadcrumbNodes && (
|
||||||
<Breadcrumbs className={classes.breadcrumbs}>
|
<Breadcrumbs className={classes.breadcrumbs}>
|
||||||
{isMobile ? getMobileBreadcrumbItems() : getBreadcrumbItems()}
|
{isMobile ? getMobileBreadcrumbItems() : getBreadcrumbItems()}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import {
|
|||||||
yjsConnectionStatusAtom,
|
yjsConnectionStatusAtom,
|
||||||
} from "@/features/editor/atoms/editor-atoms.ts";
|
} from "@/features/editor/atoms/editor-atoms.ts";
|
||||||
import { formattedDate, timeAgo } from "@/lib/time.ts";
|
import { formattedDate, timeAgo } from "@/lib/time.ts";
|
||||||
|
import { PageStateSegmentedControl } from "@/features/user/components/page-state-pref.tsx";
|
||||||
import MovePageModal from "@/features/page/components/move-page-modal.tsx";
|
import MovePageModal from "@/features/page/components/move-page-modal.tsx";
|
||||||
import { useTimeAgo } from "@/hooks/use-time-ago.tsx";
|
import { useTimeAgo } from "@/hooks/use-time-ago.tsx";
|
||||||
import ShareModal from "@/features/share/components/share-modal.tsx";
|
import ShareModal from "@/features/share/components/share-modal.tsx";
|
||||||
@@ -59,6 +60,8 @@ export default function PageHeaderMenu({ readOnly }: PageHeaderMenuProps) {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{!readOnly && <PageStateSegmentedControl size="xs" />}
|
||||||
|
|
||||||
<ShareModal readOnly={readOnly} />
|
<ShareModal readOnly={readOnly} />
|
||||||
|
|
||||||
<Tooltip label={t("Comments")} openDelay={250} withArrow>
|
<Tooltip label={t("Comments")} openDelay={250} withArrow>
|
||||||
|
|||||||
@@ -1,15 +1,27 @@
|
|||||||
.header {
|
.header {
|
||||||
height: 45px;
|
height: 45px;
|
||||||
background-color: var(--mantine-color-body);
|
background-color: var(--mantine-color-body);
|
||||||
padding-left: var(--mantine-spacing-md);
|
padding-left: var(--mantine-spacing-md);
|
||||||
padding-right: var(--mantine-spacing-md);
|
padding-right: var(--mantine-spacing-md);
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
top: var(--app-shell-header-offset, 0rem);
|
top: var(--app-shell-header-offset, 0rem);
|
||||||
inset-inline-start: var(--app-shell-navbar-offset, 0rem);
|
inset-inline-start: var(--app-shell-navbar-offset, 0rem);
|
||||||
inset-inline-end: var(--app-shell-aside-offset, 0rem);
|
inset-inline-end: var(--app-shell-aside-offset, 0rem);
|
||||||
|
|
||||||
@media print {
|
@media (max-width: $mantine-breakpoint-sm) {
|
||||||
display: none;
|
padding-left: var(--mantine-spacing-xs);
|
||||||
}
|
padding-right: var(--mantine-spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.group {
|
||||||
|
@media (max-width: $mantine-breakpoint-sm) {
|
||||||
|
gap: var(--mantine-spacing-sm);
|
||||||
|
padding-inline: 0 !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ interface Props {
|
|||||||
export default function PageHeader({ readOnly }: Props) {
|
export default function PageHeader({ readOnly }: Props) {
|
||||||
return (
|
return (
|
||||||
<div className={classes.header}>
|
<div className={classes.header}>
|
||||||
<Group justify="space-between" h="100%" px="md" wrap="nowrap">
|
<Group justify="space-between" h="100%" px="md" wrap="nowrap" className={classes.group}>
|
||||||
<Breadcrumb />
|
<Breadcrumb />
|
||||||
|
|
||||||
<Group justify="flex-end" h="100%" px="md" wrap="nowrap">
|
<Group justify="flex-end" h="100%" px="md" wrap="nowrap" gap="var(--mantine-spacing-xs)">
|
||||||
<PageHeaderMenu readOnly={readOnly} />
|
<PageHeaderMenu readOnly={readOnly} />
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ export interface IPageInput {
|
|||||||
icon: string;
|
icon: string;
|
||||||
coverPhoto: string;
|
coverPhoto: string;
|
||||||
position: string;
|
position: string;
|
||||||
|
isLocked: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IExportPageParams {
|
export interface IExportPageParams {
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { Group, Text, MantineSize, SegmentedControl } from "@mantine/core";
|
||||||
|
import { useAtom } from "jotai";
|
||||||
|
import { userAtom } from "@/features/user/atoms/current-user-atom.ts";
|
||||||
|
import { updateUser } from "@/features/user/services/user-service.ts";
|
||||||
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { PageEditMode } from "@/features/user/types/user.types.ts";
|
||||||
|
|
||||||
|
export default function PageStatePref() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group justify="space-between" wrap="nowrap" gap="xl">
|
||||||
|
<div>
|
||||||
|
<Text size="md">{t("Default page edit mode")}</Text>
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
{t("Choose your preferred page edit mode. Avoid accidental edits.")}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<PageStateSegmentedControl />
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PageStateSegmentedControlProps {
|
||||||
|
size?: MantineSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PageStateSegmentedControl({
|
||||||
|
size,
|
||||||
|
}: PageStateSegmentedControlProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [user, setUser] = useAtom(userAtom);
|
||||||
|
const pageEditMode =
|
||||||
|
user?.settings?.preferences?.pageEditMode ?? PageEditMode.Edit;
|
||||||
|
const [value, setValue] = useState(pageEditMode);
|
||||||
|
|
||||||
|
const handleChange = useCallback(
|
||||||
|
async (value: string) => {
|
||||||
|
const updatedUser = await updateUser({ pageEditMode: value });
|
||||||
|
setValue(value);
|
||||||
|
setUser(updatedUser);
|
||||||
|
},
|
||||||
|
[user, setUser],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (pageEditMode !== value) {
|
||||||
|
setValue(pageEditMode);
|
||||||
|
}
|
||||||
|
}, [pageEditMode, value]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SegmentedControl
|
||||||
|
size={size}
|
||||||
|
value={value}
|
||||||
|
onChange={handleChange}
|
||||||
|
data={[
|
||||||
|
{ label: t("Edit"), value: PageEditMode.Edit },
|
||||||
|
{ label: t("Read"), value: PageEditMode.Read },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ export interface IUser {
|
|||||||
deactivatedAt: Date;
|
deactivatedAt: Date;
|
||||||
deletedAt: Date;
|
deletedAt: Date;
|
||||||
fullPageWidth: boolean; // used for update
|
fullPageWidth: boolean; // used for update
|
||||||
|
pageEditMode: string; // used for update
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICurrentUser {
|
export interface ICurrentUser {
|
||||||
@@ -29,5 +30,11 @@ export interface ICurrentUser {
|
|||||||
export interface IUserSettings {
|
export interface IUserSettings {
|
||||||
preferences: {
|
preferences: {
|
||||||
fullPageWidth: boolean;
|
fullPageWidth: boolean;
|
||||||
|
pageEditMode: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum PageEditMode {
|
||||||
|
Read = "read",
|
||||||
|
Edit = "edit",
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ import {
|
|||||||
SpaceCaslSubject,
|
SpaceCaslSubject,
|
||||||
} from "@/features/space/permissions/permissions.type.ts";
|
} from "@/features/space/permissions/permissions.type.ts";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const MemoizedFullEditor = React.memo(FullEditor);
|
||||||
|
const MemoizedPageHeader = React.memo(PageHeader);
|
||||||
|
const MemoizedHistoryModal = React.memo(HistoryModal);
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -49,14 +54,14 @@ export default function Page() {
|
|||||||
<title>{`${page?.icon || ""} ${page?.title || t("untitled")}`}</title>
|
<title>{`${page?.icon || ""} ${page?.title || t("untitled")}`}</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
|
||||||
<PageHeader
|
<MemoizedPageHeader
|
||||||
readOnly={spaceAbility.cannot(
|
readOnly={spaceAbility.cannot(
|
||||||
SpaceCaslAction.Manage,
|
SpaceCaslAction.Manage,
|
||||||
SpaceCaslSubject.Page,
|
SpaceCaslSubject.Page,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FullEditor
|
<MemoizedFullEditor
|
||||||
key={page.id}
|
key={page.id}
|
||||||
pageId={page.id}
|
pageId={page.id}
|
||||||
title={page.title}
|
title={page.title}
|
||||||
@@ -68,7 +73,7 @@ export default function Page() {
|
|||||||
SpaceCaslSubject.Page,
|
SpaceCaslSubject.Page,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<HistoryModal pageId={page.id} />
|
<MemoizedHistoryModal pageId={page.id} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import SettingsTitle from "@/components/settings/settings-title.tsx";
|
|||||||
import AccountLanguage from "@/features/user/components/account-language.tsx";
|
import AccountLanguage from "@/features/user/components/account-language.tsx";
|
||||||
import AccountTheme from "@/features/user/components/account-theme.tsx";
|
import AccountTheme from "@/features/user/components/account-theme.tsx";
|
||||||
import PageWidthPref from "@/features/user/components/page-width-pref.tsx";
|
import PageWidthPref from "@/features/user/components/page-width-pref.tsx";
|
||||||
|
import PageEditPref from "@/features/user/components/page-state-pref";
|
||||||
import { getAppName } from "@/lib/config.ts";
|
import { getAppName } from "@/lib/config.ts";
|
||||||
import { Divider } from "@mantine/core";
|
import { Divider } from "@mantine/core";
|
||||||
import { Helmet } from "react-helmet-async";
|
import { Helmet } from "react-helmet-async";
|
||||||
@@ -28,6 +29,10 @@ export default function AccountPreferences() {
|
|||||||
<Divider my={"md"} />
|
<Divider my={"md"} />
|
||||||
|
|
||||||
<PageWidthPref />
|
<PageWidthPref />
|
||||||
|
|
||||||
|
<Divider my={"md"} />
|
||||||
|
|
||||||
|
<PageEditPref />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { OmitType, PartialType } from '@nestjs/mapped-types';
|
import { OmitType, PartialType } from '@nestjs/mapped-types';
|
||||||
import { IsBoolean, IsOptional, IsString } from 'class-validator';
|
import { IsBoolean, IsIn, IsOptional, IsString } from 'class-validator';
|
||||||
import { CreateUserDto } from '../../auth/dto/create-user.dto';
|
import { CreateUserDto } from '../../auth/dto/create-user.dto';
|
||||||
|
|
||||||
export class UpdateUserDto extends PartialType(
|
export class UpdateUserDto extends PartialType(
|
||||||
@@ -13,6 +13,11 @@ export class UpdateUserDto extends PartialType(
|
|||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
fullPageWidth: boolean;
|
fullPageWidth: boolean;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@IsIn(['read', 'edit'])
|
||||||
|
pageEditMode: string;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
locale: string;
|
locale: string;
|
||||||
|
|||||||
@@ -34,6 +34,14 @@ export class UserService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof updateUserDto.pageEditMode !== 'undefined') {
|
||||||
|
return this.userRepo.updatePreference(
|
||||||
|
userId,
|
||||||
|
'pageEditMode',
|
||||||
|
updateUserDto.pageEditMode.toLowerCase(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (updateUserDto.name) {
|
if (updateUserDto.name) {
|
||||||
user.name = updateUserDto.name;
|
user.name = updateUserDto.name;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user