diff --git a/CHANGELOG.md b/CHANGELOG.md index b19610696..9e1f33118 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ and this project adheres to - ✨(ci) add security scan #291 - ✨(frontend) Activate versions feature #240 +- ✨(frontend) one-click document creation #275 +- ✨(frontend) edit title inline #275 ## Changed diff --git a/src/frontend/apps/e2e/__tests__/app-impress/common.ts b/src/frontend/apps/e2e/__tests__/app-impress/common.ts index 1cab233f8..c09bc7138 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/common.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/common.ts @@ -29,32 +29,21 @@ export const createDoc = async ( length: number, isPublic: boolean = false, ) => { - const buttonCreate = page.getByRole('button', { - name: 'Create the document', - }); - const randomDocs = randomName(docName, browserName, length); for (let i = 0; i < randomDocs.length; i++) { const header = page.locator('header').first(); await header.locator('h2').getByText('Docs').click(); - const buttonCreateHomepage = page.getByRole('button', { - name: 'Create a new document', - }); - await buttonCreateHomepage.click(); - - // Fill input await page - .getByRole('textbox', { - name: 'Document name', + .getByRole('button', { + name: 'Create a new document', }) - .fill(randomDocs[i]); - - await expect(buttonCreate).toBeEnabled(); - await buttonCreate.click(); + .click(); - await expect(page.locator('h2').getByText(randomDocs[i])).toBeVisible(); + await page.getByRole('heading', { name: 'Untitled document' }).click(); + await page.keyboard.type(randomDocs[i]); + await page.getByText('Created at ').click(); if (isPublic) { await page.getByRole('button', { name: 'Share' }).click(); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-create.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-create.spec.ts index 6fab7777d..f0fcd335b 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-create.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-create.spec.ts @@ -7,48 +7,12 @@ test.beforeEach(async ({ page }) => { }); test.describe('Doc Create', () => { - test('checks all the create doc elements are visible', async ({ page }) => { - const buttonCreateHomepage = page.getByRole('button', { - name: 'Create a new document', - }); - await buttonCreateHomepage.click(); - await expect(buttonCreateHomepage).toBeHidden(); - - const card = page.getByRole('dialog').first(); - - await expect( - card.locator('h2').getByText('Create a new document'), - ).toBeVisible(); - await expect(card.getByLabel('Document name')).toBeVisible(); - - await expect( - card.getByRole('button', { - name: 'Create the document', - }), - ).toBeVisible(); - - await expect(card.getByLabel('Close the modal')).toBeVisible(); - }); - - test('checks the cancel button interaction', async ({ page }) => { - const buttonCreateHomepage = page.getByRole('button', { - name: 'Create a new document', - }); - await buttonCreateHomepage.click(); - await expect(buttonCreateHomepage).toBeHidden(); - - const card = page.getByRole('dialog').first(); - - await card.getByLabel('Close the modal').click(); - - await expect(buttonCreateHomepage).toBeVisible(); - }); - test('it creates a doc', async ({ page, browserName }) => { const [docTitle] = await createDoc(page, 'My new doc', browserName, 1); - expect(await page.locator('title').textContent()).toMatch( - /My new doc - Docs/, + await page.waitForFunction( + () => document.title.match(/My new doc - Docs/), + { timeout: 5000 }, ); const header = page.locator('header').first(); @@ -59,7 +23,8 @@ test.describe('Doc Create', () => { .getByRole('table'); await expect(datagrid.getByLabel('Loading data')).toBeHidden(); - - await expect(datagrid.getByText(docTitle)).toBeVisible(); + await expect(datagrid.getByText(docTitle)).toBeVisible({ + timeout: 5000, + }); }); }); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts index c5da0d512..a39362e23 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts @@ -40,19 +40,18 @@ test.describe('Doc Editor', () => { await expect(page.locator('h2').getByText(randomDoc[0])).toBeVisible(); - await page.locator('.ProseMirror.bn-editor').click(); - await page - .locator('.ProseMirror.bn-editor') - .fill('[test markdown](http://test-markdown.html)'); + const editor = page.locator('.ProseMirror'); + await editor.click(); + await editor.fill('[test markdown](http://test-markdown.html)'); - await expect(page.getByText('[test markdown]')).toBeVisible(); + await expect(editor.getByText('[test markdown]')).toBeVisible(); - await page.getByText('[test markdown]').dblclick(); + await editor.getByText('[test markdown]').dblclick(); await page.locator('button[data-test="convertMarkdown"]').click(); - await expect(page.getByText('[test markdown]')).toBeHidden(); + await expect(editor.getByText('[test markdown]')).toBeHidden(); await expect( - page.getByRole('link', { + editor.getByRole('link', { name: 'test markdown', }), ).toHaveAttribute('href', 'http://test-markdown.html'); @@ -64,38 +63,40 @@ test.describe('Doc Editor', () => { // Check the first doc const firstDoc = await goToGridDoc(page); await expect(page.locator('h2').getByText(firstDoc)).toBeVisible(); - await page.locator('.ProseMirror.bn-editor').click(); - await page.locator('.ProseMirror.bn-editor').fill('Hello World Doc 1'); - await expect(page.getByText('Hello World Doc 1')).toBeVisible(); + + const editor = page.locator('.ProseMirror'); + await editor.click(); + await editor.fill('Hello World Doc 1'); + await expect(editor.getByText('Hello World Doc 1')).toBeVisible(); // Check the second doc const secondDoc = await goToGridDoc(page, { nthRow: 2, }); await expect(page.locator('h2').getByText(secondDoc)).toBeVisible(); - await expect(page.getByText('Hello World Doc 1')).toBeHidden(); - await page.locator('.ProseMirror.bn-editor').click(); - await page.locator('.ProseMirror.bn-editor').fill('Hello World Doc 2'); - await expect(page.getByText('Hello World Doc 2')).toBeVisible(); + await expect(editor.getByText('Hello World Doc 1')).toBeHidden(); + await editor.click(); + await editor.fill('Hello World Doc 2'); + await expect(editor.getByText('Hello World Doc 2')).toBeVisible(); // Check the first doc again await goToGridDoc(page, { title: firstDoc, }); await expect(page.locator('h2').getByText(firstDoc)).toBeVisible(); - await expect(page.getByText('Hello World Doc 2')).toBeHidden(); - await expect(page.getByText('Hello World Doc 1')).toBeVisible(); + await expect(editor.getByText('Hello World Doc 2')).toBeHidden(); + await expect(editor.getByText('Hello World Doc 1')).toBeVisible(); }); test('it saves the doc when we change pages', async ({ page }) => { // Check the first doc const doc = await goToGridDoc(page); await expect(page.locator('h2').getByText(doc)).toBeVisible(); - await page.locator('.ProseMirror.bn-editor').click(); - await page - .locator('.ProseMirror.bn-editor') - .fill('Hello World Doc persisted 1'); - await expect(page.getByText('Hello World Doc persisted 1')).toBeVisible(); + + const editor = page.locator('.ProseMirror'); + await editor.click(); + await editor.fill('Hello World Doc persisted 1'); + await expect(editor.getByText('Hello World Doc persisted 1')).toBeVisible(); const secondDoc = await goToGridDoc(page, { nthRow: 2, @@ -107,7 +108,7 @@ test.describe('Doc Editor', () => { title: doc, }); - await expect(page.getByText('Hello World Doc persisted 1')).toBeVisible(); + await expect(editor.getByText('Hello World Doc persisted 1')).toBeVisible(); }); test('it saves the doc when we quit pages', async ({ page, browserName }) => { @@ -117,11 +118,11 @@ test.describe('Doc Editor', () => { // Check the first doc const doc = await goToGridDoc(page); await expect(page.locator('h2').getByText(doc)).toBeVisible(); - await page.locator('.ProseMirror.bn-editor').click(); - await page - .locator('.ProseMirror.bn-editor') - .fill('Hello World Doc persisted 2'); - await expect(page.getByText('Hello World Doc persisted 2')).toBeVisible(); + + const editor = page.locator('.ProseMirror'); + await editor.click(); + await editor.fill('Hello World Doc persisted 2'); + await expect(editor.getByText('Hello World Doc persisted 2')).toBeVisible(); await page.goto('/'); @@ -129,7 +130,7 @@ test.describe('Doc Editor', () => { title: doc, }); - await expect(page.getByText('Hello World Doc persisted 2')).toBeVisible(); + await expect(editor.getByText('Hello World Doc persisted 2')).toBeVisible(); }); test('it cannot edit if viewer', async ({ page }) => { diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-grid.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-grid.spec.ts index 35eec8a3d..c83ed044c 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-grid.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-grid.spec.ts @@ -212,26 +212,6 @@ test.describe('Documents Grid', () => { ).toHaveText(/.*/); }); - test('it updates document', async ({ page }) => { - const datagrid = page - .getByLabel('Datagrid of the documents page 1') - .getByRole('table'); - - const docRow = datagrid.getByRole('row').nth(1).getByRole('cell'); - - const docName = await docRow.nth(1).textContent(); - - await docRow.getByLabel('Open the document options').click(); - - await page.getByText('Update document').click(); - - await page.getByLabel('Document name').fill(`${docName} updated`); - - await page.getByText('Validate the modification').click(); - - await expect(datagrid.getByText(`${docName} updated`)).toBeVisible(); - }); - test('it deletes the document', async ({ page }) => { const datagrid = page .getByLabel('Datagrid of the documents page 1') @@ -241,11 +221,9 @@ test.describe('Documents Grid', () => { const docName = await docRow.nth(1).textContent(); - await docRow.getByLabel('Open the document options').click(); - - await page + await docRow .getByRole('button', { - name: 'Delete document', + name: 'Delete the document', }) .click(); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts index 9d1a4d1be..ced6677fe 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts @@ -65,49 +65,66 @@ test.describe('Doc Header', () => { await expect(page.getByRole('button', { name: 'Share' })).toBeVisible(); }); - test('it updates the doc', async ({ page, browserName }) => { + test('it updates the title doc', async ({ page, browserName }) => { const [randomDoc] = await createDoc(page, 'doc-update', browserName, 1); - await expect(page.locator('h2').getByText(randomDoc)).toBeVisible(); - await page.getByLabel('Open the document options').click(); + await page.getByRole('heading', { name: randomDoc }).fill(' '); + await page.getByText('Created at').click(); + + await expect( + page.getByRole('heading', { name: 'Untitled document' }), + ).toBeVisible(); + }); + + test('it updates the title doc from editor heading', async ({ page }) => { await page .getByRole('button', { - name: 'Update document', + name: 'Create a new document', }) .click(); + const docHeader = page.getByLabel( + 'It is the card information about the document.', + ); + await expect( - page.locator('h2').getByText(`Update document "${randomDoc}"`), + docHeader.getByRole('heading', { name: 'Untitled document', level: 2 }), ).toBeVisible(); - await page.getByText('Document name').fill(`${randomDoc}-updated`); + const editor = page.locator('.ProseMirror'); - await page - .getByRole('button', { - name: 'Validate the modification', - }) - .click(); + await editor.locator('h1').click(); + await page.keyboard.type('Hello World', { delay: 100 }); await expect( - page.getByText('The document has been updated.'), + docHeader.getByRole('heading', { name: 'Hello World', level: 2 }), ).toBeVisible(); - const docTitle = await goToGridDoc(page, { - title: `${randomDoc}-updated`, - }); + await expect( + page.getByText('Document title updated successfully'), + ).toBeVisible(); - await expect(page.locator('h2').getByText(docTitle)).toBeVisible(); + await docHeader + .getByRole('heading', { name: 'Hello World', level: 2 }) + .fill('Top World'); - await page.getByLabel('Open the document options').click(); - await page - .getByRole('button', { - name: 'Update document', - }) - .click(); + await editor.locator('h1').fill('Super World'); + + await expect( + docHeader.getByRole('heading', { name: 'Top World', level: 2 }), + ).toBeVisible(); + + await editor.locator('h1').fill(''); + + await docHeader + .getByRole('heading', { name: 'Top World', level: 2 }) + .fill(' '); + + await page.getByText('Created at').click(); await expect( - page.getByRole('textbox', { name: 'Document name' }), - ).toHaveValue(`${randomDoc}-updated`); + docHeader.getByRole('heading', { name: 'Untitled document', level: 2 }), + ).toBeVisible(); }); test('it deletes the doc', async ({ page, browserName }) => { @@ -167,16 +184,15 @@ test.describe('Doc Header', () => { await goToGridDoc(page); - await expect(page.locator('h2').getByText('Mocked document')).toBeVisible(); + await expect( + page.locator('h2').getByText('Mocked document'), + ).toHaveAttribute('contenteditable'); await expect(page.getByRole('button', { name: 'Share' })).toBeVisible(); await page.getByLabel('Open the document options').click(); await expect(page.getByRole('button', { name: 'Export' })).toBeVisible(); - await expect( - page.getByRole('button', { name: 'Update document' }), - ).toBeVisible(); await expect( page.getByRole('button', { name: 'Delete document' }), ).toBeHidden(); @@ -199,16 +215,15 @@ test.describe('Doc Header', () => { await goToGridDoc(page); - await expect(page.locator('h2').getByText('Mocked document')).toBeVisible(); + await expect( + page.locator('h2').getByText('Mocked document'), + ).toHaveAttribute('contenteditable'); await expect(page.getByRole('button', { name: 'Share' })).toBeHidden(); await page.getByLabel('Open the document options').click(); await expect(page.getByRole('button', { name: 'Export' })).toBeVisible(); - await expect( - page.getByRole('button', { name: 'Update document' }), - ).toBeVisible(); await expect( page.getByRole('button', { name: 'Delete document' }), ).toBeHidden(); @@ -231,7 +246,9 @@ test.describe('Doc Header', () => { await goToGridDoc(page); - await expect(page.locator('h2').getByText('Mocked document')).toBeVisible(); + await expect( + page.locator('h2').getByText('Mocked document'), + ).not.toHaveAttribute('contenteditable'); await expect(page.getByRole('button', { name: 'Share' })).toBeHidden(); @@ -239,9 +256,6 @@ test.describe('Doc Header', () => { await expect(page.getByRole('button', { name: 'Share' })).toBeHidden(); await expect(page.getByRole('button', { name: 'Export' })).toBeVisible(); - await expect( - page.getByRole('button', { name: 'Update document' }), - ).toBeHidden(); await expect( page.getByRole('button', { name: 'Delete document' }), ).toBeHidden(); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-version.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-version.spec.ts index e853dda59..bca0fc382 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-version.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-version.spec.ts @@ -105,7 +105,8 @@ test.describe('Doc Version', () => { title: randomDoc, }); - await expect(page.getByText('Hello')).toBeVisible(); + const editor = page.locator('.ProseMirror'); + await expect(editor.getByText('Hello')).toBeVisible(); await page.locator('.bn-block-outer').last().click(); await page.keyboard.press('Enter'); await page.locator('.bn-block-outer').last().fill('World'); @@ -153,23 +154,24 @@ test.describe('Doc Version', () => { await expect(page.locator('h2').getByText(randomDoc)).toBeVisible(); - await page.locator('.bn-block-outer').last().click(); - await page.locator('.bn-block-outer').last().fill('Hello'); + const editor = page.locator('.ProseMirror'); + await editor.locator('.bn-block-outer').last().click(); + await editor.locator('.bn-block-outer').last().fill('Hello'); await goToGridDoc(page, { title: randomDoc, }); - await expect(page.getByText('Hello')).toBeVisible(); - await page.locator('.bn-block-outer').last().click(); + await expect(editor.getByText('Hello')).toBeVisible(); + await editor.locator('.bn-block-outer').last().click(); await page.keyboard.press('Enter'); - await page.locator('.bn-block-outer').last().fill('World'); + await editor.locator('.bn-block-outer').last().fill('World'); await goToGridDoc(page, { title: randomDoc, }); - await expect(page.getByText('World')).toBeVisible(); + await expect(editor.getByText('World')).toBeVisible(); await page.getByLabel('Open the document options').click(); await page @@ -180,7 +182,7 @@ test.describe('Doc Version', () => { const panel = page.getByLabel('Document panel'); await panel.locator('li').nth(1).click(); - await expect(page.getByText('World')).toBeHidden(); + await expect(editor.getByText('World')).toBeHidden(); await page .getByRole('button', { @@ -199,7 +201,7 @@ test.describe('Doc Version', () => { await expect(panel.locator('li')).toHaveCount(3); await panel.getByText('Current version').click(); - await expect(page.getByText('Hello')).toBeVisible(); - await expect(page.getByText('World')).toBeHidden(); + await expect(editor.getByText('Hello')).toBeVisible(); + await expect(editor.getByText('World')).toBeHidden(); }); }); diff --git a/src/frontend/apps/impress/src/__tests__/pages.test.tsx b/src/frontend/apps/impress/src/__tests__/pages.test.tsx index 140932bc5..60d1e1ea4 100644 --- a/src/frontend/apps/impress/src/__tests__/pages.test.tsx +++ b/src/frontend/apps/impress/src/__tests__/pages.test.tsx @@ -5,6 +5,14 @@ import { AppWrapper } from '@/tests/utils'; import Page from '../pages'; +jest.mock('next/navigation', () => ({ + useRouter() { + return { + push: jest.fn(), + }; + }, +})); + describe('Page', () => { it('checks Page rendering', () => { render(, { wrapper: AppWrapper }); diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx index 8895e41f1..5d531a057 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx @@ -13,7 +13,7 @@ import { Version } from '@/features/docs/doc-versioning/'; import { useCreateDocAttachment } from '../api/useCreateDocUpload'; import useSaveDoc from '../hook/useSaveDoc'; -import { useDocStore } from '../stores'; +import { useDocStore, useHeadingStore } from '../stores'; import { randomColor } from '../utils'; import { BlockNoteToolbar } from './BlockNoteToolbar'; @@ -78,6 +78,7 @@ export const BlockNoteContent = ({ isError: isErrorAttachment, error: errorAttachment, } = useCreateDocAttachment(); + const { setHeadings, resetHeadings } = useHeadingStore(); const uploadFile = useCallback( async (file: File) => { @@ -116,6 +117,18 @@ export const BlockNoteContent = ({ setStore(storeId, { editor }); }, [setStore, storeId, editor]); + useEffect(() => { + setHeadings(editor); + + editor?.onEditorContentChange(() => { + setHeadings(editor); + }); + + return () => { + resetHeadings(); + }; + }, [editor, resetHeadings, setHeadings]); + return ( {isErrorAttachment && ( diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteToolbar.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteToolbar.tsx index 785f84c27..d7deeee2c 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteToolbar.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteToolbar.tsx @@ -9,12 +9,10 @@ import { NestBlockButton, TextAlignButton, UnnestBlockButton, - useBlockNoteEditor, - useComponentsContext, - useSelectedBlocks, } from '@blocknote/react'; -import { forEach, isArray } from 'lodash'; -import React, { useMemo } from 'react'; +import React from 'react'; + +import { MarkdownButton } from './MarkdownButton'; export const BlockNoteToolbar = () => { return ( @@ -57,79 +55,3 @@ export const BlockNoteToolbar = () => { /> ); }; - -type Block = { - type: string; - text: string; - content: Block[]; -}; - -function isBlock(block: Block): block is Block { - return ( - block.content && - isArray(block.content) && - block.content.length > 0 && - typeof block.type !== 'undefined' - ); -} - -const recursiveContent = (content: Block[], base: string = '') => { - let fullContent = base; - for (const innerContent of content) { - if (innerContent.type === 'text') { - fullContent += innerContent.text; - } else if (isBlock(innerContent)) { - fullContent = recursiveContent(innerContent.content, fullContent); - } - } - - return fullContent; -}; - -/** - * Custom Formatting Toolbar Button to convert markdown to json. - */ -export function MarkdownButton() { - const editor = useBlockNoteEditor(); - const Components = useComponentsContext(); - const selectedBlocks = useSelectedBlocks(editor); - - const handleConvertMarkdown = () => { - const blocks = editor.getSelection()?.blocks; - - forEach(blocks, async (block) => { - if (!isBlock(block as unknown as Block)) { - return; - } - - try { - const fullContent = recursiveContent( - block.content as unknown as Block[], - ); - - const blockMarkdown = - await editor.tryParseMarkdownToBlocks(fullContent); - editor.replaceBlocks([block.id], blockMarkdown); - } catch (error) { - console.error('Error parsing Markdown:', error); - } - }); - }; - - const show = useMemo(() => { - return !!selectedBlocks.find((block) => block.content !== undefined); - }, [selectedBlocks]); - - if (!show || !editor.isEditable || !Components) { - return null; - } - - return ( - - M - - ); -} diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocEditor.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocEditor.tsx index 58342c3c5..c1f4198dd 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocEditor.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocEditor.tsx @@ -8,9 +8,10 @@ import { Box, Card, Text, TextErrors } from '@/components'; import { useCunninghamTheme } from '@/cunningham'; import { DocHeader } from '@/features/docs/doc-header'; import { Doc } from '@/features/docs/doc-management'; -import { useHeading } from '@/features/docs/doc-table-content'; import { Versions, useDocVersion } from '@/features/docs/doc-versioning/'; +import { useHeadingStore } from '../stores'; + import { BlockNoteEditor } from './BlockNoteEditor'; import { IconOpenPanelEditor, PanelEditor } from './PanelEditor'; @@ -23,7 +24,7 @@ export const DocEditor = ({ doc }: DocEditorProps) => { query: { versionId }, } = useRouter(); const { t } = useTranslation(); - const headings = useHeading(doc.id); + const { headings } = useHeadingStore(); const isVersion = versionId && typeof versionId === 'string'; diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/MarkdownButton.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/MarkdownButton.tsx new file mode 100644 index 000000000..967e15af5 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/MarkdownButton.tsx @@ -0,0 +1,84 @@ +import '@blocknote/mantine/style.css'; +import { + useBlockNoteEditor, + useComponentsContext, + useSelectedBlocks, +} from '@blocknote/react'; +import { forEach, isArray } from 'lodash'; +import React, { useMemo } from 'react'; + +type Block = { + type: string; + text: string; + content: Block[]; +}; + +function isBlock(block: Block): block is Block { + return ( + block.content && + isArray(block.content) && + block.content.length > 0 && + typeof block.type !== 'undefined' + ); +} + +const recursiveContent = (content: Block[], base: string = '') => { + let fullContent = base; + for (const innerContent of content) { + if (innerContent.type === 'text') { + fullContent += innerContent.text; + } else if (isBlock(innerContent)) { + fullContent = recursiveContent(innerContent.content, fullContent); + } + } + + return fullContent; +}; + +/** + * Custom Formatting Toolbar Button to convert markdown to json. + */ +export function MarkdownButton() { + const editor = useBlockNoteEditor(); + const Components = useComponentsContext(); + const selectedBlocks = useSelectedBlocks(editor); + + const handleConvertMarkdown = () => { + const blocks = editor.getSelection()?.blocks; + + forEach(blocks, async (block) => { + if (!isBlock(block as unknown as Block)) { + return; + } + + try { + const fullContent = recursiveContent( + block.content as unknown as Block[], + ); + + const blockMarkdown = + await editor.tryParseMarkdownToBlocks(fullContent); + editor.replaceBlocks([block.id], blockMarkdown); + } catch (error) { + console.error('Error parsing Markdown:', error); + } + }); + }; + + const show = useMemo(() => { + return !!selectedBlocks.find((block) => block.content !== undefined); + }, [selectedBlocks]); + + if (!show || !editor.isEditable || !Components) { + return null; + } + + return ( + + M + + ); +} diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/PanelEditor.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/PanelEditor.tsx index 1d81731a0..60ce65b8a 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/PanelEditor.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/PanelEditor.tsx @@ -3,11 +3,12 @@ import { useTranslation } from 'react-i18next'; import { Box, BoxButton, Card, IconBG, Text } from '@/components'; import { useCunninghamTheme } from '@/cunningham'; -import { Doc } from '@/features/docs//doc-management'; -import { HeadingBlock, TableContent } from '@/features/docs/doc-table-content'; +import { Doc } from '@/features/docs/doc-management'; +import { TableContent } from '@/features/docs/doc-table-content'; import { VersionList } from '@/features/docs/doc-versioning'; -import { usePanelEditorStore } from '../stores/usePanelEditorStore'; +import { usePanelEditorStore } from '../stores'; +import { HeadingBlock } from '../types'; interface PanelProps { doc: Doc; @@ -35,7 +36,6 @@ export const PanelEditor = ({ $css={` top: 0vh; transform: translateX(0%); - overflow: hidden; flex: 1; margin-left: 1rem; ${ diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useSaveDoc.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useSaveDoc.tsx index 0a21cd45c..ca1ed0054 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useSaveDoc.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useSaveDoc.tsx @@ -4,6 +4,7 @@ import * as Y from 'yjs'; import { useUpdateDoc } from '@/features/docs/doc-management/'; import { KEY_LIST_DOC_VERSIONS } from '@/features/docs/doc-versioning'; +import { isFirefox } from '@/utils/userAgent'; import { toBase64 } from '../utils'; @@ -87,10 +88,7 @@ const useSaveDoc = (docId: string, doc: Y.Doc, canSave: boolean) => { * if he wants to leave the page, by adding the popup, we let the time to the * request to be sent, and intercepted by the service worker (for the offline part). */ - const isFirefox = - navigator.userAgent.toLowerCase().indexOf('firefox') > -1; - - if (typeof e !== 'undefined' && e.preventDefault && isFirefox) { + if (typeof e !== 'undefined' && e.preventDefault && isFirefox()) { e.preventDefault(); } }; diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/index.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/index.tsx index 24ecfc79e..ad4eaebf5 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/index.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/index.tsx @@ -1,3 +1,4 @@ export * from './components'; export * from './stores'; +export * from './types'; export * from './utils'; diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/stores/index.ts b/src/frontend/apps/impress/src/features/docs/doc-editor/stores/index.ts index 8efa05869..52c5b5095 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/stores/index.ts +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/stores/index.ts @@ -1,2 +1,3 @@ export * from './useDocStore'; +export * from './useHeadingStore'; export * from './usePanelEditorStore'; diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/stores/useDocStore.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/stores/useDocStore.tsx index ceffcf5fc..ee5750840 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/stores/useDocStore.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/stores/useDocStore.tsx @@ -6,6 +6,8 @@ import { create } from 'zustand'; import { providerUrl } from '@/core'; import { Base64 } from '@/features/docs/doc-management'; +import { blocksToYDoc } from '../utils'; + interface DocStore { provider: HocuspocusProvider; editor?: BlockNoteEditor; @@ -28,6 +30,15 @@ export const useDocStore = create((set, get) => ({ if (initialDoc) { Y.applyUpdate(doc, Buffer.from(initialDoc, 'base64')); + } else { + const initialDocContent = [ + { + type: 'heading', + content: '', + }, + ]; + + blocksToYDoc(initialDocContent, doc); } const provider = new HocuspocusProvider({ diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/stores/useHeadingStore.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/stores/useHeadingStore.tsx new file mode 100644 index 000000000..ac9b8a4b3 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/stores/useHeadingStore.tsx @@ -0,0 +1,43 @@ +import { BlockNoteEditor } from '@blocknote/core'; +import { create } from 'zustand'; + +import { HeadingBlock } from '../types'; + +const recursiveTextContent = (content: HeadingBlock['content']): string => { + if (!content) { + return ''; + } + + return content.reduce((acc, content) => { + if (content.type === 'text') { + return acc + content.text; + } else if (content.type === 'link') { + return acc + recursiveTextContent(content.content); + } + + return acc; + }, ''); +}; + +export interface UseHeadingStore { + headings: HeadingBlock[]; + setHeadings: (editor: BlockNoteEditor) => void; + resetHeadings: () => void; +} + +export const useHeadingStore = create((set) => ({ + headings: [], + setHeadings: (editor) => { + const headingBlocks = editor?.document + .filter((block) => block.type === 'heading') + .map((block) => ({ + ...block, + contentText: recursiveTextContent( + block.content as unknown as HeadingBlock['content'], + ), + })) as unknown as HeadingBlock[]; + + set(() => ({ headings: headingBlocks })); + }, + resetHeadings: () => set(() => ({ headings: [] })), +})); diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/types.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/types.tsx index 4390a173b..19094b6c5 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/types.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/types.tsx @@ -1,3 +1,14 @@ export interface DocAttachment { file: string; } + +export type HeadingBlock = { + id: string; + type: string; + text: string; + content: HeadingBlock[]; + contentText: string; + props: { + level: number; + }; +}; diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/utils.ts b/src/frontend/apps/impress/src/features/docs/doc-editor/utils.ts index aa0d43ba4..2c9495ec9 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/utils.ts +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/utils.ts @@ -1,3 +1,5 @@ +import * as Y from 'yjs'; + export const randomColor = () => { const randomInt = (min: number, max: number) => { return Math.floor(Math.random() * (max - min + 1)) + min; @@ -26,3 +28,20 @@ function hslToHex(h: number, s: number, l: number) { export const toBase64 = ( str: WithImplicitCoercion, ) => Buffer.from(str).toString('base64'); + +type BasicBlock = { + type: string; + content: string; +}; +export const blocksToYDoc = (blocks: BasicBlock[], doc: Y.Doc) => { + const xmlFragment = doc.getXmlFragment('document-store'); + + blocks.forEach((block) => { + const xmlElement = new Y.XmlElement(block.type); + if (block.content) { + xmlElement.insert(0, [new Y.XmlText(block.content)]); + } + + xmlFragment.push([xmlElement]); + }); +}; diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeader.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeader.tsx index 4f5511098..3c563707b 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeader.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeader.tsx @@ -8,12 +8,13 @@ import { Doc, Role, currentDocRole, - useTransRole, + useTrans, } from '@/features/docs/doc-management'; import { ModalVersion, Versions } from '@/features/docs/doc-versioning'; import { useDate } from '@/hook'; import { DocTagPublic } from './DocTagPublic'; +import { DocTitle } from './DocTitle'; import { DocToolBox } from './DocToolBox'; interface DocHeaderProps { @@ -25,7 +26,7 @@ export const DocHeader = ({ doc, versionId }: DocHeaderProps) => { const { colorsTokens } = useCunninghamTheme(); const { t } = useTranslation(); const { formatDate } = useDate(); - const transRole = useTransRole(); + const { transRole } = useTrans(); const [isModalVersionOpen, setIsModalVersionOpen] = useState(false); return ( @@ -54,14 +55,8 @@ export const DocHeader = ({ doc, versionId }: DocHeaderProps) => { $background={colorsTokens()['greyscale-100']} $margin={{ horizontal: 'small' }} /> - - - {doc.title} - + + {versionId && ( - )} - {doc.abilities.destroy && ( - - )} {doc.abilities.versions_list && ( + {doc.abilities.destroy && ( + + )} {isModalShareOpen && ( @@ -124,9 +109,6 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => { {isModalPDFOpen && ( setIsModalPDFOpen(false)} doc={doc} /> )} - {isModalUpdateOpen && ( - setIsModalUpdateOpen(false)} doc={doc} /> - )} {isModalRemoveOpen && ( setIsModalRemoveOpen(false)} doc={doc} /> )} diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/api/index.ts b/src/frontend/apps/impress/src/features/docs/doc-management/api/index.ts index 8860f7da6..5cf42cd77 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/api/index.ts +++ b/src/frontend/apps/impress/src/features/docs/doc-management/api/index.ts @@ -1,3 +1,4 @@ +export * from './useCreateDoc'; export * from './useDoc'; export * from './useDocs'; export * from './useUpdateDoc'; diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/components/InputDocName.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/components/InputDocName.tsx deleted file mode 100644 index 7e1f58de1..000000000 --- a/src/frontend/apps/impress/src/features/docs/doc-management/components/InputDocName.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { Input, Loader } from '@openfun/cunningham-react'; -import { useEffect, useState } from 'react'; - -import { APIError } from '@/api'; -import { Box, TextErrors } from '@/components'; - -interface InputDocNameProps { - error: APIError | null; - isError: boolean; - isPending: boolean; - label: string; - setDocName: (newDocName: string) => void; - defaultValue?: string; -} - -export const InputDocName = ({ - defaultValue, - error, - isError, - isPending, - label, - setDocName, -}: InputDocNameProps) => { - const [isInputError, setIsInputError] = useState(isError); - - useEffect(() => { - if (isError) { - setIsInputError(true); - } - }, [isError]); - - return ( - <> - { - setDocName(e.target.value); - setIsInputError(false); - }} - rightIcon={edit} - state={isInputError ? 'error' : 'default'} - /> - {isError && error && } - {isPending && ( - - - - )} - - ); -}; diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/components/ModalCreateUpdateDoc.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/components/ModalCreateUpdateDoc.tsx deleted file mode 100644 index c883715d3..000000000 --- a/src/frontend/apps/impress/src/features/docs/doc-management/components/ModalCreateUpdateDoc.tsx +++ /dev/null @@ -1,176 +0,0 @@ -import { - Alert, - Button, - Modal, - ModalSize, - VariantType, - useToastProvider, -} from '@openfun/cunningham-react'; -import { UseMutationResult } from '@tanstack/react-query'; -import { useRouter } from 'next/navigation'; -import { useState } from 'react'; -import { useTranslation } from 'react-i18next'; - -import { APIError } from '@/api'; -import { Box, Text } from '@/components'; -import useCunninghamTheme from '@/cunningham/useCunninghamTheme'; - -import { KEY_DOC, KEY_LIST_DOC } from '../api'; -import { useCreateDoc } from '../api/useCreateDoc'; -import { useUpdateDoc } from '../api/useUpdateDoc'; -import IconEdit from '../assets/icon-edit.svg'; -import { Doc } from '../types'; - -import { InputDocName } from './InputDocName'; - -interface ModalCreateDocProps { - onClose: () => void; -} - -export const ModalCreateDoc = ({ onClose }: ModalCreateDocProps) => { - const router = useRouter(); - const api = useCreateDoc({ - onSuccess: (doc) => { - router.push(`/docs/${doc.id}`); - }, - }); - const { t } = useTranslation(); - - return ( - - api.mutate({ - title, - }), - ...api, - }} - /> - ); -}; - -interface ModalUpdateDocProps { - onClose: () => void; - doc: Doc; -} - -export const ModalUpdateDoc = ({ onClose, doc }: ModalUpdateDocProps) => { - const { toast } = useToastProvider(); - const { t } = useTranslation(); - - const api = useUpdateDoc({ - onSuccess: () => { - toast(t('The document has been updated.'), VariantType.SUCCESS, { - duration: 4000, - }); - onClose(); - }, - listInvalideQueries: [KEY_DOC, KEY_LIST_DOC], - }); - - return ( - - api.mutate({ - title, - id: doc.id, - }), - ...api, - }} - /> - ); -}; - -type ModalDoc = { - buttonText: string; - onClose: () => void; - titleModal: string; - validate: (title: string) => void; - initialTitle?: string; - infoText?: string; -} & UseMutationResult, T, unknown>; - -const ModalDoc = ({ - buttonText, - infoText, - initialTitle, - onClose, - titleModal, - validate, - ...api -}: ModalDoc) => { - const { colorsTokens } = useCunninghamTheme(); - const { t } = useTranslation(); - const [title, setTitle] = useState(initialTitle || ''); - - return ( - onClose()} - > - {t('Cancel')} - - } - onClose={() => onClose()} - rightActions={ - - } - size={ModalSize.MEDIUM} - title={ - - - - {titleModal} - - - } - > - - {infoText && ( - - {infoText} - - )} - - - - - - - ); -}; diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/components/index.ts b/src/frontend/apps/impress/src/features/docs/doc-management/components/index.ts index 04d5b0c9c..120ce76ff 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/components/index.ts +++ b/src/frontend/apps/impress/src/features/docs/doc-management/components/index.ts @@ -1,3 +1,2 @@ -export * from './ModalCreateUpdateDoc'; export * from './ModalRemoveDoc'; export * from './ModalShare'; diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/hooks/index.ts b/src/frontend/apps/impress/src/features/docs/doc-management/hooks/index.ts index 9a820990c..7697d915a 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/hooks/index.ts +++ b/src/frontend/apps/impress/src/features/docs/doc-management/hooks/index.ts @@ -1 +1 @@ -export * from './useTransRole'; +export * from './useTrans'; diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/hooks/useTransRole.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/hooks/useTrans.tsx similarity index 63% rename from src/frontend/apps/impress/src/features/docs/doc-management/hooks/useTransRole.tsx rename to src/frontend/apps/impress/src/features/docs/doc-management/hooks/useTrans.tsx index 337555787..b7bc24f5a 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/hooks/useTransRole.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/hooks/useTrans.tsx @@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next'; import { Role } from '../types'; -export const useTransRole = () => { +export const useTrans = () => { const { t } = useTranslation(); const translatedRoles = { @@ -12,9 +12,10 @@ export const useTransRole = () => { [Role.EDITOR]: t('Editor'), }; - const transRole = (role: Role) => { - return translatedRoles[role]; + return { + transRole: (role: Role) => { + return translatedRoles[role]; + }, + untitledDocument: t('Untitled document'), }; - - return transRole; }; diff --git a/src/frontend/apps/impress/src/features/docs/doc-table-content/components/TableContent.tsx b/src/frontend/apps/impress/src/features/docs/doc-table-content/components/TableContent.tsx index b9dde6ead..426058b9d 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-table-content/components/TableContent.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-table-content/components/TableContent.tsx @@ -2,11 +2,9 @@ import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Box, BoxButton, Text } from '@/components'; -import { useDocStore } from '@/features/docs/doc-editor'; +import { HeadingBlock, useDocStore } from '@/features/docs/doc-editor'; import { Doc } from '@/features/docs/doc-management'; -import { HeadingBlock } from '../types'; - import { Heading } from './Heading'; interface TableContentProps { diff --git a/src/frontend/apps/impress/src/features/docs/doc-table-content/hooks/index.ts b/src/frontend/apps/impress/src/features/docs/doc-table-content/hooks/index.ts deleted file mode 100644 index d9b27305a..000000000 --- a/src/frontend/apps/impress/src/features/docs/doc-table-content/hooks/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './useHeading'; diff --git a/src/frontend/apps/impress/src/features/docs/doc-table-content/hooks/useHeading.tsx b/src/frontend/apps/impress/src/features/docs/doc-table-content/hooks/useHeading.tsx deleted file mode 100644 index bccc6d659..000000000 --- a/src/frontend/apps/impress/src/features/docs/doc-table-content/hooks/useHeading.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { useCallback, useState } from 'react'; - -import { useDocStore } from '../../doc-editor'; -import { HeadingBlock } from '../types'; - -const recursiveTextContent = (content: HeadingBlock['content']): string => { - if (!content) { - return ''; - } - - return content.reduce((acc, content) => { - if (content.type === 'text') { - return acc + content.text; - } else if (content.type === 'link') { - return acc + recursiveTextContent(content.content); - } - - return acc; - }, ''); -}; - -export const useHeading = (docId: string) => { - const { docsStore } = useDocStore(); - const editor = docsStore?.[docId]?.editor; - - const headingFiltering = useCallback( - () => - editor?.document - .filter((block) => block.type === 'heading') - .map((block) => ({ - ...block, - contentText: recursiveTextContent( - block.content as unknown as HeadingBlock['content'], - ), - })) as unknown as HeadingBlock[], - [editor?.document], - ); - - const [headings, setHeadings] = useState(headingFiltering()); - - editor?.onEditorContentChange(() => { - setHeadings(headingFiltering()); - }); - - return headings; -}; diff --git a/src/frontend/apps/impress/src/features/docs/doc-table-content/index.ts b/src/frontend/apps/impress/src/features/docs/doc-table-content/index.ts index 2103701b1..07635cbbc 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-table-content/index.ts +++ b/src/frontend/apps/impress/src/features/docs/doc-table-content/index.ts @@ -1,3 +1 @@ export * from './components'; -export * from './hooks'; -export * from './types'; diff --git a/src/frontend/apps/impress/src/features/docs/doc-table-content/types.ts b/src/frontend/apps/impress/src/features/docs/doc-table-content/types.ts deleted file mode 100644 index feeefc2ec..000000000 --- a/src/frontend/apps/impress/src/features/docs/doc-table-content/types.ts +++ /dev/null @@ -1,10 +0,0 @@ -export type HeadingBlock = { - id: string; - type: string; - text: string; - content: HeadingBlock[]; - contentText: string; - props: { - level: number; - }; -}; diff --git a/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGrid.tsx b/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGrid.tsx index cbab72d45..ea755d2e3 100644 --- a/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGrid.tsx +++ b/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGrid.tsx @@ -12,7 +12,7 @@ import { currentDocRole, isDocsOrdering, useDocs, - useTransRole, + useTrans, } from '@/features/docs/doc-management'; import { useDate } from '@/hook/'; @@ -48,7 +48,7 @@ function formatSortModel(sortModel: SortModelItem): DocsOrdering | undefined { export const DocsGrid = () => { const { colorsTokens } = useCunninghamTheme(); - const transRole = useTransRole(); + const { transRole } = useTrans(); const { t } = useTranslation(); const { formatDate } = useDate(); const pagination = usePagination({ diff --git a/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGridActions.tsx b/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGridActions.tsx index d9ae203f5..6a57a2fb0 100644 --- a/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGridActions.tsx +++ b/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGridActions.tsx @@ -2,12 +2,7 @@ import { Button } from '@openfun/cunningham-react'; import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Box, DropButton, IconOptions, Text } from '@/components'; -import { - Doc, - ModalRemoveDoc, - ModalUpdateDoc, -} from '@/features/docs/doc-management'; +import { Doc, ModalRemoveDoc } from '@/features/docs/doc-management'; interface DocsGridActionsProps { doc: Doc; @@ -15,58 +10,24 @@ interface DocsGridActionsProps { export const DocsGridActions = ({ doc }: DocsGridActionsProps) => { const { t } = useTranslation(); - const [isModalUpdateOpen, setIsModalUpdateOpen] = useState(false); const [isModalRemoveOpen, setIsModalRemoveOpen] = useState(false); - const [isDropOpen, setIsDropOpen] = useState(false); - if (!doc.abilities.partial_update && !doc.abilities.destroy) { + if (!doc.abilities.destroy) { return null; } return ( <> - - } - onOpenChange={(isOpen) => setIsDropOpen(isOpen)} - isOpen={isDropOpen} - > - - {doc.abilities.partial_update && ( - - )} - {doc.abilities.destroy && ( - - )} - - - {isModalUpdateOpen && ( - setIsModalUpdateOpen(false)} doc={doc} /> - )} + - - + + + - {isModalCreateOpen && ( - setIsModalCreateOpen(false)} /> - )} ); }; diff --git a/src/frontend/apps/impress/src/features/docs/members/members-add/components/ChooseRole.tsx b/src/frontend/apps/impress/src/features/docs/members/members-add/components/ChooseRole.tsx index 7a61fd28f..ba95302fb 100644 --- a/src/frontend/apps/impress/src/features/docs/members/members-add/components/ChooseRole.tsx +++ b/src/frontend/apps/impress/src/features/docs/members/members-add/components/ChooseRole.tsx @@ -1,7 +1,7 @@ import { Select } from '@openfun/cunningham-react'; import { useTranslation } from 'react-i18next'; -import { Role, useTransRole } from '@/features/docs/doc-management'; +import { Role, useTrans } from '@/features/docs/doc-management'; interface ChooseRoleProps { currentRole: Role; @@ -19,7 +19,7 @@ export const ChooseRole = ({ label, }: ChooseRoleProps) => { const { t } = useTranslation(); - const transRole = useTransRole(); + const { transRole } = useTrans(); return (