mirror of
https://github.com/docmost/docmost.git
synced 2026-07-27 03:24:45 +10:00
feat: workspace default edit preference (#2319)
This commit is contained in:
+71
@@ -0,0 +1,71 @@
|
|||||||
|
import { Group, Text, SegmentedControl } from "@mantine/core";
|
||||||
|
import { useAtom } from "jotai";
|
||||||
|
import { workspaceAtom } from "@/features/user/atoms/current-user-atom.ts";
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { updateWorkspace } from "@/features/workspace/services/workspace-service.ts";
|
||||||
|
import { notifications } from "@mantine/notifications";
|
||||||
|
import { getApiErrorMessage } from "@/lib/api-error.ts";
|
||||||
|
import { PageEditMode } from "@/features/user/types/user.types.ts";
|
||||||
|
|
||||||
|
export default function WorkspaceDefaultPageEditMode() {
|
||||||
|
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 the page edit mode new members start with. Existing members are not affected.",
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DefaultPageEditModeControl />
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DefaultPageEditModeControl() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [workspace, setWorkspace] = useAtom(workspaceAtom);
|
||||||
|
const defaultPageEditMode =
|
||||||
|
workspace?.settings?.defaultPageEditMode ?? PageEditMode.Edit;
|
||||||
|
const [value, setValue] = useState<string>(defaultPageEditMode);
|
||||||
|
|
||||||
|
const handleChange = async (newValue: string) => {
|
||||||
|
const prevValue = value;
|
||||||
|
setValue(newValue);
|
||||||
|
try {
|
||||||
|
const updatedWorkspace = await updateWorkspace({
|
||||||
|
defaultPageEditMode: newValue,
|
||||||
|
});
|
||||||
|
setWorkspace(updatedWorkspace);
|
||||||
|
} catch (err) {
|
||||||
|
setValue(prevValue);
|
||||||
|
notifications.show({
|
||||||
|
message: getApiErrorMessage(err, t("Failed to update setting")),
|
||||||
|
color: "red",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (defaultPageEditMode !== value) {
|
||||||
|
setValue(defaultPageEditMode);
|
||||||
|
}
|
||||||
|
}, [defaultPageEditMode, value]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SegmentedControl
|
||||||
|
aria-label={t("Default page edit mode")}
|
||||||
|
value={value}
|
||||||
|
onChange={handleChange}
|
||||||
|
data={[
|
||||||
|
{ label: t("Edit"), value: PageEditMode.Edit },
|
||||||
|
{ label: t("Read"), value: PageEditMode.Read },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -29,6 +29,7 @@ export interface IWorkspace {
|
|||||||
restrictApiToAdmins?: boolean;
|
restrictApiToAdmins?: boolean;
|
||||||
allowMemberTemplates?: boolean;
|
allowMemberTemplates?: boolean;
|
||||||
allowPersonalSpaces?: boolean;
|
allowPersonalSpaces?: boolean;
|
||||||
|
defaultPageEditMode?: string;
|
||||||
isScimEnabled?: boolean;
|
isScimEnabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,6 +39,7 @@ export interface IWorkspaceSettings {
|
|||||||
api?: IWorkspaceApiSettings;
|
api?: IWorkspaceApiSettings;
|
||||||
templates?: IWorkspaceTemplateSettings;
|
templates?: IWorkspaceTemplateSettings;
|
||||||
spaces?: IWorkspaceSpaceSettings;
|
spaces?: IWorkspaceSpaceSettings;
|
||||||
|
defaultPageEditMode?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IWorkspaceApiSettings {
|
export interface IWorkspaceApiSettings {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { Helmet } from "react-helmet-async";
|
|||||||
import ManageHostname from "@/ee/components/manage-hostname.tsx";
|
import ManageHostname from "@/ee/components/manage-hostname.tsx";
|
||||||
import { Divider } from "@mantine/core";
|
import { Divider } from "@mantine/core";
|
||||||
import AllowMemberTemplates from "@/ee/security/components/allow-member-templates.tsx";
|
import AllowMemberTemplates from "@/ee/security/components/allow-member-templates.tsx";
|
||||||
|
import WorkspaceDefaultPageEditMode from "@/features/workspace/components/settings/components/workspace-default-page-edit-mode.tsx";
|
||||||
import PersonalSpacesSetting from "@/ee/personal-space/components/personal-spaces-setting.tsx";
|
import PersonalSpacesSetting from "@/ee/personal-space/components/personal-spaces-setting.tsx";
|
||||||
|
|
||||||
export default function WorkspaceSettings() {
|
export default function WorkspaceSettings() {
|
||||||
@@ -32,6 +33,9 @@ export default function WorkspaceSettings() {
|
|||||||
<ManageHostname />
|
<ManageHostname />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<Divider my="md" />
|
||||||
|
<WorkspaceDefaultPageEditMode />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { WorkspaceService } from '../../workspace/services/workspace.service';
|
|||||||
import { CreateWorkspaceDto } from '../../workspace/dto/create-workspace.dto';
|
import { CreateWorkspaceDto } from '../../workspace/dto/create-workspace.dto';
|
||||||
import { CreateAdminUserDto } from '../dto/create-admin-user.dto';
|
import { CreateAdminUserDto } from '../dto/create-admin-user.dto';
|
||||||
import { UserRepo } from '@docmost/db/repos/user/user.repo';
|
import { UserRepo } from '@docmost/db/repos/user/user.repo';
|
||||||
|
import { WorkspaceRepo } from '@docmost/db/repos/workspace/workspace.repo';
|
||||||
|
import { getWorkspaceDefaultPageEditMode } from '../../workspace/workspace.util';
|
||||||
import { KyselyDB, KyselyTransaction } from '@docmost/db/types/kysely.types';
|
import { KyselyDB, KyselyTransaction } from '@docmost/db/types/kysely.types';
|
||||||
import { executeTx } from '@docmost/db/utils';
|
import { executeTx } from '@docmost/db/utils';
|
||||||
import { InjectKysely } from 'nestjs-kysely';
|
import { InjectKysely } from 'nestjs-kysely';
|
||||||
@@ -20,6 +22,7 @@ import {
|
|||||||
export class SignupService {
|
export class SignupService {
|
||||||
constructor(
|
constructor(
|
||||||
private userRepo: UserRepo,
|
private userRepo: UserRepo,
|
||||||
|
private workspaceRepo: WorkspaceRepo,
|
||||||
private workspaceService: WorkspaceService,
|
private workspaceService: WorkspaceService,
|
||||||
private groupUserRepo: GroupUserRepo,
|
private groupUserRepo: GroupUserRepo,
|
||||||
@InjectKysely() private readonly db: KyselyDB,
|
@InjectKysely() private readonly db: KyselyDB,
|
||||||
@@ -46,12 +49,16 @@ export class SignupService {
|
|||||||
this.db,
|
this.db,
|
||||||
async (trx) => {
|
async (trx) => {
|
||||||
// create user
|
// create user
|
||||||
|
const workspace = await this.workspaceRepo.findById(workspaceId, {
|
||||||
|
trx,
|
||||||
|
});
|
||||||
const user = await this.userRepo.insertUser(
|
const user = await this.userRepo.insertUser(
|
||||||
{
|
{
|
||||||
...createUserDto,
|
...createUserDto,
|
||||||
workspaceId: workspaceId,
|
workspaceId: workspaceId,
|
||||||
},
|
},
|
||||||
trx,
|
trx,
|
||||||
|
{ pageEditMode: getWorkspaceDefaultPageEditMode(workspace) },
|
||||||
);
|
);
|
||||||
|
|
||||||
// add user to workspace
|
// add user to workspace
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ import { CreateWorkspaceDto } from './create-workspace.dto';
|
|||||||
import {
|
import {
|
||||||
IsArray,
|
IsArray,
|
||||||
IsBoolean,
|
IsBoolean,
|
||||||
|
IsIn,
|
||||||
IsInt,
|
IsInt,
|
||||||
IsOptional,
|
IsOptional,
|
||||||
|
IsString,
|
||||||
Min,
|
Min,
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
|
|
||||||
@@ -61,4 +63,9 @@ export class UpdateWorkspaceDto extends PartialType(CreateWorkspaceDto) {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
allowPersonalSpaces: boolean;
|
allowPersonalSpaces: boolean;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@IsIn(['read', 'edit'])
|
||||||
|
defaultPageEditMode: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,10 @@ import {
|
|||||||
AUDIT_SERVICE,
|
AUDIT_SERVICE,
|
||||||
IAuditService,
|
IAuditService,
|
||||||
} from '../../../integrations/audit/audit.service';
|
} from '../../../integrations/audit/audit.service';
|
||||||
import { isAdminActingOnOwner } from '../workspace.util';
|
import {
|
||||||
|
getWorkspaceDefaultPageEditMode,
|
||||||
|
isAdminActingOnOwner,
|
||||||
|
} from '../workspace.util';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WorkspaceInvitationService {
|
export class WorkspaceInvitationService {
|
||||||
@@ -257,6 +260,7 @@ export class WorkspaceInvitationService {
|
|||||||
workspaceId: workspace.id,
|
workspaceId: workspace.id,
|
||||||
},
|
},
|
||||||
trx,
|
trx,
|
||||||
|
{ pageEditMode: getWorkspaceDefaultPageEditMode(workspace) },
|
||||||
);
|
);
|
||||||
|
|
||||||
// add user to default group
|
// add user to default group
|
||||||
|
|||||||
@@ -527,6 +527,20 @@ export class WorkspaceService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof updateWorkspaceDto.defaultPageEditMode !== 'undefined') {
|
||||||
|
const prev = settingsBefore?.defaultPageEditMode ?? null;
|
||||||
|
const next = updateWorkspaceDto.defaultPageEditMode.toLowerCase();
|
||||||
|
if (prev !== next) {
|
||||||
|
before.defaultPageEditMode = prev;
|
||||||
|
after.defaultPageEditMode = next;
|
||||||
|
}
|
||||||
|
await this.workspaceRepo.updateDefaultPageEditMode(
|
||||||
|
workspaceId,
|
||||||
|
next,
|
||||||
|
trx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
delete updateWorkspaceDto.restrictApiToAdmins;
|
delete updateWorkspaceDto.restrictApiToAdmins;
|
||||||
delete updateWorkspaceDto.aiSearch;
|
delete updateWorkspaceDto.aiSearch;
|
||||||
delete updateWorkspaceDto.generativeAi;
|
delete updateWorkspaceDto.generativeAi;
|
||||||
@@ -535,6 +549,7 @@ export class WorkspaceService {
|
|||||||
delete updateWorkspaceDto.allowMemberTemplates;
|
delete updateWorkspaceDto.allowMemberTemplates;
|
||||||
delete updateWorkspaceDto.aiChat;
|
delete updateWorkspaceDto.aiChat;
|
||||||
delete updateWorkspaceDto.allowPersonalSpaces;
|
delete updateWorkspaceDto.allowPersonalSpaces;
|
||||||
|
delete updateWorkspaceDto.defaultPageEditMode;
|
||||||
|
|
||||||
await this.workspaceRepo.updateWorkspace(
|
await this.workspaceRepo.updateWorkspace(
|
||||||
updateWorkspaceDto,
|
updateWorkspaceDto,
|
||||||
|
|||||||
@@ -6,3 +6,18 @@ export function isAdminActingOnOwner(
|
|||||||
): boolean {
|
): boolean {
|
||||||
return authUserRole === UserRole.ADMIN && targetRole === UserRole.OWNER;
|
return authUserRole === UserRole.ADMIN && targetRole === UserRole.OWNER;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PageEditMode = 'read' | 'edit';
|
||||||
|
|
||||||
|
export function getWorkspaceDefaultPageEditMode(
|
||||||
|
workspace: { settings?: unknown } | null | undefined,
|
||||||
|
): PageEditMode | undefined {
|
||||||
|
const settings = (workspace?.settings ?? {}) as {
|
||||||
|
defaultPageEditMode?: unknown;
|
||||||
|
};
|
||||||
|
const mode = settings.defaultPageEditMode;
|
||||||
|
if (mode === 'read' || mode === 'edit') {
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ export class UserRepo {
|
|||||||
async insertUser(
|
async insertUser(
|
||||||
insertableUser: InsertableUser,
|
insertableUser: InsertableUser,
|
||||||
trx?: KyselyTransaction,
|
trx?: KyselyTransaction,
|
||||||
|
opts?: { pageEditMode?: string },
|
||||||
): Promise<User> {
|
): Promise<User> {
|
||||||
const user: InsertableUser = {
|
const user: InsertableUser = {
|
||||||
name:
|
name:
|
||||||
@@ -126,7 +127,17 @@ export class UserRepo {
|
|||||||
const db = dbOrTx(this.db, trx);
|
const db = dbOrTx(this.db, trx);
|
||||||
return db
|
return db
|
||||||
.insertInto('users')
|
.insertInto('users')
|
||||||
.values({ ...insertableUser, ...user })
|
.values({
|
||||||
|
...insertableUser,
|
||||||
|
...user,
|
||||||
|
...(opts?.pageEditMode
|
||||||
|
? {
|
||||||
|
settings: sql`${JSON.stringify({
|
||||||
|
preferences: { pageEditMode: opts.pageEditMode },
|
||||||
|
})}::text::jsonb`,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
})
|
||||||
.returning(this.baseFields)
|
.returning(this.baseFields)
|
||||||
.executeTakeFirst();
|
.executeTakeFirst();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -271,4 +271,22 @@ export class WorkspaceRepo {
|
|||||||
.executeTakeFirst();
|
.executeTakeFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateDefaultPageEditMode(
|
||||||
|
workspaceId: string,
|
||||||
|
pageEditMode: string,
|
||||||
|
trx?: KyselyTransaction,
|
||||||
|
) {
|
||||||
|
const db = dbOrTx(this.db, trx);
|
||||||
|
return db
|
||||||
|
.updateTable('workspaces')
|
||||||
|
.set({
|
||||||
|
settings: sql`COALESCE(settings, '{}'::jsonb)
|
||||||
|
|| jsonb_build_object('defaultPageEditMode', ${sql.lit(pageEditMode)})`,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
})
|
||||||
|
.where('id', '=', workspaceId)
|
||||||
|
.returning(this.baseFields)
|
||||||
|
.executeTakeFirst();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
Submodule apps/server/src/ee updated: 224d73f925...4669109a18
Reference in New Issue
Block a user