✨(frontend) enhance document management types and utilities
- Updated the `Access` and `Doc` interfaces to include new properties for role management and document link reach. - Introduced utility functions to handle document link reach and role, improving the logic for determining access levels. - Refactored the `isOwnerOrAdmin` function to simplify role checks for document ownership and admin status.
This commit is contained in:
committed by
Anthony LC
parent
adb15dedb8
commit
93d9dec068
@@ -2,9 +2,16 @@ import { User } from '@/features/auth';
|
|||||||
|
|
||||||
export interface Access {
|
export interface Access {
|
||||||
id: string;
|
id: string;
|
||||||
|
max_ancestors_role: Role;
|
||||||
role: Role;
|
role: Role;
|
||||||
|
max_role: Role;
|
||||||
team: string;
|
team: string;
|
||||||
user: User;
|
user: User;
|
||||||
|
document: {
|
||||||
|
id: string;
|
||||||
|
path: string;
|
||||||
|
depth: number;
|
||||||
|
};
|
||||||
abilities: {
|
abilities: {
|
||||||
destroy: boolean;
|
destroy: boolean;
|
||||||
partial_update: boolean;
|
partial_update: boolean;
|
||||||
@@ -21,10 +28,17 @@ export enum Role {
|
|||||||
OWNER = 'owner',
|
OWNER = 'owner',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const RoleImportance = {
|
||||||
|
[Role.READER]: 1,
|
||||||
|
[Role.EDITOR]: 2,
|
||||||
|
[Role.ADMIN]: 3,
|
||||||
|
[Role.OWNER]: 4,
|
||||||
|
};
|
||||||
|
|
||||||
export enum LinkReach {
|
export enum LinkReach {
|
||||||
RESTRICTED = 'restricted',
|
RESTRICTED = 'restricted',
|
||||||
PUBLIC = 'public',
|
|
||||||
AUTHENTICATED = 'authenticated',
|
AUTHENTICATED = 'authenticated',
|
||||||
|
PUBLIC = 'public',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum LinkRole {
|
export enum LinkRole {
|
||||||
@@ -43,13 +57,19 @@ export interface Doc {
|
|||||||
created_at: string;
|
created_at: string;
|
||||||
creator: string;
|
creator: string;
|
||||||
depth: number;
|
depth: number;
|
||||||
|
path: string;
|
||||||
is_favorite: boolean;
|
is_favorite: boolean;
|
||||||
link_reach: LinkReach;
|
link_reach: LinkReach;
|
||||||
link_role: LinkRole;
|
link_role: LinkRole;
|
||||||
nb_accesses_direct: number;
|
nb_accesses_direct: number;
|
||||||
nb_accesses_ancestors: number;
|
nb_accesses_ancestors: number;
|
||||||
|
computed_link_reach: LinkReach;
|
||||||
|
computed_link_role?: LinkRole;
|
||||||
|
ancestors_link_reach: LinkReach;
|
||||||
|
ancestors_link_role?: LinkRole;
|
||||||
numchild: number;
|
numchild: number;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
|
user_role: Role;
|
||||||
user_roles: Role[];
|
user_roles: Role[];
|
||||||
abilities: {
|
abilities: {
|
||||||
accesses_manage: boolean;
|
accesses_manage: boolean;
|
||||||
@@ -74,9 +94,16 @@ export interface Doc {
|
|||||||
versions_destroy: boolean;
|
versions_destroy: boolean;
|
||||||
versions_list: boolean;
|
versions_list: boolean;
|
||||||
versions_retrieve: boolean;
|
versions_retrieve: boolean;
|
||||||
|
link_select_options: LinkSelectOption;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LinkSelectOption {
|
||||||
|
public?: LinkRole[];
|
||||||
|
authenticated?: LinkRole[];
|
||||||
|
restricted?: LinkRole[];
|
||||||
|
}
|
||||||
|
|
||||||
export enum DocDefaultFilter {
|
export enum DocDefaultFilter {
|
||||||
ALL_DOCS = 'all_docs',
|
ALL_DOCS = 'all_docs',
|
||||||
MY_DOCS = 'my_docs',
|
MY_DOCS = 'my_docs',
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as Y from 'yjs';
|
import * as Y from 'yjs';
|
||||||
|
|
||||||
import { Doc, Role } from './types';
|
import { Doc, LinkReach, LinkRole, Role } from './types';
|
||||||
|
|
||||||
export const currentDocRole = (abilities: Doc['abilities']): Role => {
|
export const currentDocRole = (abilities: Doc['abilities']): Role => {
|
||||||
return abilities.destroy
|
return abilities.destroy
|
||||||
@@ -22,3 +22,23 @@ export const base64ToYDoc = (base64: string) => {
|
|||||||
export const base64ToBlocknoteXmlFragment = (base64: string) => {
|
export const base64ToBlocknoteXmlFragment = (base64: string) => {
|
||||||
return base64ToYDoc(base64).getXmlFragment('document-store');
|
return base64ToYDoc(base64).getXmlFragment('document-store');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getDocLinkReach = (doc: Doc): LinkReach => {
|
||||||
|
return doc.computed_link_reach ?? doc.link_reach;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDocLinkRole = (doc: Doc): LinkRole => {
|
||||||
|
return doc.computed_link_role ?? doc.link_role;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const docLinkIsDesync = (doc: Doc) => {
|
||||||
|
// If the document has no ancestors
|
||||||
|
if (!doc.ancestors_link_reach) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
doc.computed_link_reach !== doc.ancestors_link_reach ||
|
||||||
|
doc.computed_link_role !== doc.ancestors_link_role
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from './useCreateChildren';
|
export * from './useCreateChildren';
|
||||||
export * from './useDocChildren';
|
export * from './useDocChildren';
|
||||||
|
export * from './useMove';
|
||||||
|
|||||||
@@ -10,12 +10,11 @@ import { css } from 'styled-components';
|
|||||||
|
|
||||||
import { Box, StyledLink } from '@/components';
|
import { Box, StyledLink } from '@/components';
|
||||||
import { useCunninghamTheme } from '@/cunningham';
|
import { useCunninghamTheme } from '@/cunningham';
|
||||||
|
import { Doc, KEY_SUB_PAGE, useDoc, useDocStore } from '@/docs/doc-management';
|
||||||
|
import { SimpleDocItem } from '@/docs/docs-grid';
|
||||||
|
|
||||||
import { Doc, KEY_SUB_PAGE, useDoc, useDocStore } from '../../doc-management';
|
|
||||||
import { SimpleDocItem } from '../../docs-grid';
|
|
||||||
import { useDocTree } from '../api/useDocTree';
|
import { useDocTree } from '../api/useDocTree';
|
||||||
import { useMoveDoc } from '../api/useMove';
|
import { useMoveDoc } from '../api/useMove';
|
||||||
import { canDrag, canDrop } from '../utils';
|
|
||||||
|
|
||||||
import { DocSubPageItem } from './DocSubPageItem';
|
import { DocSubPageItem } from './DocSubPageItem';
|
||||||
import { DocTreeItemActions } from './DocTreeItemActions';
|
import { DocTreeItemActions } from './DocTreeItemActions';
|
||||||
@@ -211,13 +210,13 @@ export const DocTree = ({ initialTargetId }: DocTreeProps) => {
|
|||||||
}
|
}
|
||||||
const parentDoc = parentNode?.data.value as Doc;
|
const parentDoc = parentNode?.data.value as Doc;
|
||||||
if (!parentDoc) {
|
if (!parentDoc) {
|
||||||
return canDrop(rootNode);
|
return rootNode?.abilities.move;
|
||||||
}
|
}
|
||||||
return canDrop(parentDoc);
|
return parentDoc?.abilities.move;
|
||||||
}}
|
}}
|
||||||
canDrag={(node) => {
|
canDrag={(node) => {
|
||||||
const doc = node.value as Doc;
|
const doc = node.value as Doc;
|
||||||
return canDrag(doc);
|
return doc.abilities.move;
|
||||||
}}
|
}}
|
||||||
rootNodeId={treeContext.root?.id ?? ''}
|
rootNodeId={treeContext.root?.id ?? ''}
|
||||||
renderNode={DocSubPageItem}
|
renderNode={DocSubPageItem}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import { useCreateChildrenDoc } from '../api/useCreateChildren';
|
|||||||
import { useDetachDoc } from '../api/useDetach';
|
import { useDetachDoc } from '../api/useDetach';
|
||||||
import MoveDocIcon from '../assets/doc-extract-bold.svg';
|
import MoveDocIcon from '../assets/doc-extract-bold.svg';
|
||||||
import { useTreeUtils } from '../hooks';
|
import { useTreeUtils } from '../hooks';
|
||||||
import { isOwnerOrAdmin } from '../utils';
|
|
||||||
|
|
||||||
type DocTreeItemActionsProps = {
|
type DocTreeItemActionsProps = {
|
||||||
doc: Doc;
|
doc: Doc;
|
||||||
@@ -36,7 +35,6 @@ export const DocTreeItemActions = ({
|
|||||||
const deleteModal = useModal();
|
const deleteModal = useModal();
|
||||||
const { togglePanel } = useLeftPanelStore();
|
const { togglePanel } = useLeftPanelStore();
|
||||||
const copyLink = useCopyDocLink(doc.id);
|
const copyLink = useCopyDocLink(doc.id);
|
||||||
const canUpdate = isOwnerOrAdmin(doc);
|
|
||||||
const { isCurrentParent } = useTreeUtils(doc);
|
const { isCurrentParent } = useTreeUtils(doc);
|
||||||
const { mutate: detachDoc } = useDetachDoc();
|
const { mutate: detachDoc } = useDetachDoc();
|
||||||
const treeContext = useTreeContext<Doc>();
|
const treeContext = useTreeContext<Doc>();
|
||||||
@@ -70,7 +68,7 @@ export const DocTreeItemActions = ({
|
|||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
label: t('Convert to doc'),
|
label: t('Convert to doc'),
|
||||||
isDisabled: !canUpdate,
|
isDisabled: !doc.abilities.move,
|
||||||
icon: (
|
icon: (
|
||||||
<Box
|
<Box
|
||||||
$css={css`
|
$css={css`
|
||||||
@@ -86,7 +84,7 @@ export const DocTreeItemActions = ({
|
|||||||
: []),
|
: []),
|
||||||
{
|
{
|
||||||
label: t('Delete'),
|
label: t('Delete'),
|
||||||
isDisabled: !canUpdate,
|
isDisabled: !doc.abilities.destroy,
|
||||||
icon: <Icon iconName="delete" $size="24px" />,
|
icon: <Icon iconName="delete" $size="24px" />,
|
||||||
callback: deleteModal.open,
|
callback: deleteModal.open,
|
||||||
},
|
},
|
||||||
@@ -138,7 +136,7 @@ export const DocTreeItemActions = ({
|
|||||||
$variation="600"
|
$variation="600"
|
||||||
/>
|
/>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
{canUpdate && (
|
{doc.abilities.children_create && (
|
||||||
<BoxButton
|
<BoxButton
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { TreeViewDataType } from '@gouvfr-lasuite/ui-kit';
|
import { TreeViewDataType } from '@gouvfr-lasuite/ui-kit';
|
||||||
|
|
||||||
import { Doc, Role } from '../doc-management';
|
import { Doc } from '../doc-management';
|
||||||
|
|
||||||
export const subPageToTree = (children: Doc[]): TreeViewDataType<Doc>[] => {
|
export const subPageToTree = (children: Doc[]): TreeViewDataType<Doc>[] => {
|
||||||
children.forEach((child) => {
|
children.forEach((child) => {
|
||||||
@@ -9,17 +9,3 @@ export const subPageToTree = (children: Doc[]): TreeViewDataType<Doc>[] => {
|
|||||||
});
|
});
|
||||||
return children;
|
return children;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isOwnerOrAdmin = (doc: Doc): boolean => {
|
|
||||||
return doc.user_roles.some(
|
|
||||||
(role) => role === Role.OWNER || role === Role.ADMIN,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const canDrag = (doc: Doc): boolean => {
|
|
||||||
return isOwnerOrAdmin(doc);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const canDrop = (doc: Doc): boolean => {
|
|
||||||
return isOwnerOrAdmin(doc);
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
|
|
||||||
import { Box, Text } from '@/components';
|
import { Box, Text } from '@/components';
|
||||||
import { Doc, KEY_LIST_DOC } from '@/docs/doc-management';
|
import { Doc, KEY_LIST_DOC } from '@/docs/doc-management';
|
||||||
import { useMoveDoc } from '@/docs/doc-tree/api/useMove';
|
import { useMoveDoc } from '@/docs/doc-tree';
|
||||||
|
|
||||||
import { useDragAndDrop } from '../hooks/useDragAndDrop';
|
import { useDragAndDrop } from '../hooks/useDragAndDrop';
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export function useDragAndDrop(
|
|||||||
const [selectedDoc, setSelectedDoc] = useState<Doc>();
|
const [selectedDoc, setSelectedDoc] = useState<Doc>();
|
||||||
const [canDrop, setCanDrop] = useState<boolean>();
|
const [canDrop, setCanDrop] = useState<boolean>();
|
||||||
|
|
||||||
const canDrag = selectedDoc?.abilities.move;
|
const canDrag = !!selectedDoc?.abilities.move;
|
||||||
|
|
||||||
const mouseSensor = useSensor(MouseSensor, { activationConstraint });
|
const mouseSensor = useSensor(MouseSensor, { activationConstraint });
|
||||||
const touchSensor = useSensor(TouchSensor, { activationConstraint });
|
const touchSensor = useSensor(TouchSensor, { activationConstraint });
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { WorkboxPlugin } from 'workbox-core';
|
import { WorkboxPlugin } from 'workbox-core';
|
||||||
|
|
||||||
import { Doc, DocsResponse } from '@/docs/doc-management';
|
import { Doc, DocsResponse } from '@/docs/doc-management';
|
||||||
import { LinkReach, LinkRole } from '@/docs/doc-management/types';
|
import { LinkReach, LinkRole, Role } from '@/docs/doc-management/types';
|
||||||
|
|
||||||
import { DBRequest, DocsDB } from '../DocsDB';
|
import { DBRequest, DocsDB } from '../DocsDB';
|
||||||
import { RequestSerializer } from '../RequestSerializer';
|
import { RequestSerializer } from '../RequestSerializer';
|
||||||
@@ -201,10 +201,21 @@ export class ApiPlugin implements WorkboxPlugin {
|
|||||||
versions_destroy: true,
|
versions_destroy: true,
|
||||||
versions_list: true,
|
versions_list: true,
|
||||||
versions_retrieve: true,
|
versions_retrieve: true,
|
||||||
|
link_select_options: {
|
||||||
|
public: [LinkRole.READER, LinkRole.EDITOR],
|
||||||
|
authenticated: [LinkRole.READER, LinkRole.EDITOR],
|
||||||
|
restricted: undefined,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
link_reach: LinkReach.RESTRICTED,
|
link_reach: LinkReach.RESTRICTED,
|
||||||
link_role: LinkRole.READER,
|
link_role: LinkRole.READER,
|
||||||
user_roles: [],
|
user_roles: [Role.OWNER],
|
||||||
|
user_role: Role.OWNER,
|
||||||
|
path: '',
|
||||||
|
computed_link_reach: LinkReach.RESTRICTED,
|
||||||
|
computed_link_role: LinkRole.READER,
|
||||||
|
ancestors_link_reach: LinkReach.RESTRICTED,
|
||||||
|
ancestors_link_role: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
await DocsDB.cacheResponse(
|
await DocsDB.cacheResponse(
|
||||||
|
|||||||
Reference in New Issue
Block a user