Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions src/backend/core/api/viewsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1338,10 +1338,12 @@ def duplicate(self, request, *args, **kwargs):
document=duplicated_document,
)

return drf_response.Response(
{"id": str(duplicated_document.id)}, status=status.HTTP_201_CREATED
serializer = serializers.DocumentSerializer(
duplicated_document, context=self.get_serializer_context()
)

return drf_response.Response(serializer.data, status=status.HTTP_201_CREATED)

def _duplicate_document(
self,
document_to_duplicate,
Expand Down Expand Up @@ -1369,7 +1371,7 @@ def _duplicate_document(
user_role = document_to_duplicate.get_role(user)
is_owner_or_admin = user_role in models.PRIVILEGED_ROLES

base64_yjs_content = document_to_duplicate.content
base64_yjs_content = document_to_duplicate.content or ""

# Duplicate the document instance
link_kwargs = (
Expand Down
37 changes: 36 additions & 1 deletion src/frontend/apps/impress/src/components/Link.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { Button } from '@gouvfr-lasuite/cunningham-react';
import Link from 'next/link';
import styled, { RuleSet } from 'styled-components';
import { useRouter } from 'next/router';
import { type ComponentProps } from 'react';
import styled, { type RuleSet } from 'styled-components';

export interface LinkProps {
$css?: string | RuleSet<object>;
Expand All @@ -14,3 +17,35 @@ export const StyledLink = styled(Link)<LinkProps>`
display: flex;
${({ $css }) => $css && (typeof $css === 'string' ? `${$css};` : $css)}
`;

type ButtonLinkProps = ComponentProps<typeof Button> & {
href: string;
};

export const ButtonLink = ({
children,
onClick,
ref,
...props
}: ButtonLinkProps) => {
const router = useRouter();

return (
<Button
ref={ref}
onClick={(e) => {
if (!e.ctrlKey && !e.metaKey && !e.shiftKey) {
e.preventDefault();

if (props.href) {
void router.push(props.href);
}
}
onClick?.(e as React.MouseEvent<HTMLButtonElement, MouseEvent>);
}}
{...props}
>
{children}
</Button>
);
};
21 changes: 21 additions & 0 deletions src/frontend/apps/impress/src/cunningham/cunningham-style.css
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@
height: auto;
}

/**
* Checkbox
*/
.c__checkbox:focus-within {
border-color: transparent;
box-shadow: none;
}

.c__checkbox .c__checkbox__wrapper:focus-within {
outline: none;
}

/**
* Modal
*/
Expand Down Expand Up @@ -104,10 +116,19 @@
/**
* Toast
*/
.c__toast {
max-width: min(100%, 100vw);
}

.c__toast__container {
z-index: -1;
}

.c__toast__container:has(.c__toast) {
z-index: 10000;
}

.c__toast__content__children {
flex-shrink: 0;
flex-grow: 0;
}
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
>
{t('Export')}
</Text>
<Box $position="absolute" $css="top: 4px; right: 4px;">
<Box $position="absolute" $css="top: 8px; right: 8px;">
<ButtonCloseModal
aria-label={t('Close the download modal')}
onClick={() => onClose()}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,11 @@ const DocTitleInput = ({ doc }: DocTitleProps) => {
$align="center"
$gap="4px"
$minHeight="40px"
$css={css`
&:focus-within {
outline: none;
}
`}
>
{!isTopRoot && <DocTitleEmojiPicker doc={doc} />}
{/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import LeaveSVG from '@/assets/icons/ui-kit/leave.svg';
import MarkdownCopySVG from '@/assets/icons/ui-kit/markdown_copy.svg';
import MoreSVG from '@/assets/icons/ui-kit/more_horiz.svg';
import {
Doc,
type Doc,
KEY_DOC,
KEY_LIST_DOC,
KEY_LIST_FAVORITE_DOC,
Expand All @@ -32,6 +32,7 @@ import {
useDocUtils,
useDuplicateDoc,
} from '@/docs/doc-management';
import { ConfirmationDuplicateModal } from '@/docs/doc-management/components/ConfirmationDuplicateModal';
import { usePresenterStore } from '@/docs/doc-presenter/stores';
import { useAuth } from '@/features/auth';
import { useFocusStore, useResponsiveStore } from '@/stores';
Expand Down Expand Up @@ -89,13 +90,14 @@ interface DocToolBoxProps {

export const DocToolBox = ({ doc }: DocToolBoxProps) => {
const { t } = useTranslation();
const treeContext = useTreeContext<Doc>();
const treeContext = useTreeContext<Doc | null>();
const router = useRouter();
const { isTopRoot } = useDocUtils(doc);
const isTopParent = doc.id === treeContext?.root?.id; // it can be a child but not for the current user
const { authenticated } = useAuth();
const copyCurrentEditorToClipboard = useCopyCurrentEditorToClipboard();
const [openDropdown, setOpenDropdown] = useState(false);
const [isModalDuplicateOpen, setIsModalDuplicateOpen] = useState(false);
const [isModalRemoveOpen, setIsModalRemoveOpen] = useState(false);
const [isModalExportOpen, setIsModalExportOpen] = useState(false);
const [isModalShareOpen, setIsModalShareOpen] = useState(false);
Expand Down Expand Up @@ -192,11 +194,14 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
icon: <ContentCopySVG width={24} height={24} aria-hidden="true" />,
isDisabled: !doc.abilities.duplicate,
callback: () => {
duplicateDoc({
docId: doc.id,
with_accesses: false,
canSave: doc.abilities.partial_update,
});
if (doc.numchild) {
setIsModalDuplicateOpen(true);
} else {
duplicateDoc({
docId: doc.id,
canSave: doc.abilities.partial_update,
});
}
},
isHidden: !doc.abilities.duplicate,
showSeparator: true,
Expand Down Expand Up @@ -247,6 +252,16 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
/>
</DropdownMenu>

{isModalDuplicateOpen && (
<ConfirmationDuplicateModal
onClose={() => {
setIsModalDuplicateOpen(false);
restoreFocus();
}}
doc={doc}
treeContext={treeContext}
/>
)}
{isModalExportOpen && ModalExport && (
<ModalExport
onClose={() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import {
useToastProvider,
} from '@gouvfr-lasuite/cunningham-react';
import {
UseMutationOptions,
type InfiniteData,
type UseMutationOptions,
useMutation,
useQueryClient,
} from '@tanstack/react-query';
Expand All @@ -18,22 +19,24 @@ import { useProviderStore } from '../stores';
import { Doc } from '../types';

import { useDocContentUpdate } from './useDocContentUpdate';
import { KEY_LIST_DOC } from './useDocs';
import { DocsParams, DocsResponse, KEY_LIST_DOC } from './useDocs';

interface DuplicateDocPayload {
docId: string;
with_accesses?: boolean;
with_descendants?: boolean;
}

type DuplicateDocResponse = Pick<Doc, 'id'>;
type DuplicateDocResponse = Doc;

export const duplicateDoc = async ({
docId,
with_accesses,
with_accesses = false,
with_descendants = true,
}: DuplicateDocPayload): Promise<DuplicateDocResponse> => {
const response = await fetchAPI(`documents/${docId}/duplicate/`, {
method: 'POST',
body: JSON.stringify({ with_accesses }),
body: JSON.stringify({ with_accesses, with_descendants }),
});

if (!response.ok) {
Expand Down Expand Up @@ -84,14 +87,36 @@ export function useDuplicateDoc(options?: DuplicateDocOptions) {
return await duplicateDoc(variables);
},
onSuccess: (data, variables, onMutateResult, context) => {
void queryClient.resetQueries({
queryKey: [KEY_LIST_DOC],
});

const message = t('Document duplicated successfully!');
toast(message, VariantType.SUCCESS, {
duration: 3000,
});
// Add the duplicated document to the list of documents in the cache
// It avoids the need to refetch the list of documents after duplicating a document
queryClient.setQueriesData<InfiniteData<DocsResponse>>(
{
queryKey: [KEY_LIST_DOC],
predicate: (query) => {
const params = query.queryKey[1] as DocsParams | undefined;
return params?.is_creator_me !== false;
},
},
(oldData) => {
if (!oldData) {
return oldData;
}

const [firstPage, ...restPages] = oldData.pages;

return {
...oldData,
pages: [
{
...firstPage,
count: firstPage.count + 1,
results: [data, ...firstPage.results],
},
...restPages,
],
};
},
);

void options?.onSuccess?.(data, variables, onMutateResult, context);
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
UseMutationOptions,
type UseMutationOptions,
useMutation,
useQueryClient,
} from '@tanstack/react-query';
Expand Down
Loading
Loading