From e6bc586df70222cb03951c34265d1a54f7bb38fa Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Mon, 27 Jul 2026 17:58:07 +0200 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F(frontend)=20change=20favorit?= =?UTF-8?q?e=20to=20star?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change the favorite to star, it will be part of the grid list as the others, we can select them directly from a filter in the url. --- CHANGELOG.md | 1 + .../e2e/__tests__/app-impress/auth.setup.ts | 2 +- .../__tests__/app-impress/doc-grid.spec.ts | 40 +++-- .../__tests__/app-impress/doc-header.spec.ts | 40 +---- .../app-impress/doc-trashbin.spec.ts | 37 ++-- .../e2e/__tests__/app-impress/header.spec.ts | 2 +- .../__tests__/app-impress/left-panel.spec.ts | 2 +- .../apps/impress/src/assets/icons/doc-all.svg | 20 --- .../impress/src/assets/icons/ui-kit/clock.svg | 29 ++-- .../src/assets/icons/ui-kit/globe-lock.svg | 21 +++ .../impress/src/assets/icons/ui-kit/globe.svg | 9 +- .../src/assets/icons/ui-kit/star-filled.svg | 7 +- .../src/assets/icons/ui-kit/star-slash.svg | 7 +- .../impress/src/assets/icons/ui-kit/star.svg | 7 +- .../impress/src/assets/icons/ui-kit/trash.svg | 10 +- .../impress/src/assets/icons/ui-kit/user.svg | 12 ++ .../doc-header/components/DocHeaderInfo.tsx | 35 +++- .../api/useCreateFavoriteDoc.tsx | 4 +- .../api/useDeleteFavoriteDoc.tsx | 4 +- .../doc-management/api/useDocsFavorite.tsx | 7 +- .../doc-management/assets/pinned-document.svg | 138 --------------- .../doc-management/components/DocToolBox.tsx | 30 ++-- .../components/SimpleDocItem.tsx | 32 +--- .../docs/doc-management/hooks/useTrans.tsx | 3 +- .../features/docs/doc-management/types.tsx | 1 + .../doc-search/components/DocSearchItem.tsx | 1 - .../docs/docs-grid/api/useDocsGridQuery.tsx | 26 ++- .../docs/docs-grid/components/DocsGrid.tsx | 22 ++- .../docs-grid/components/DocsGridActions.tsx | 36 ++-- .../components/DocsGridColumnName.tsx | 158 ++++++++++++------ .../docs-grid/components/DocsGridItem.tsx | 107 ++++++------ .../components/LefPanelTargetFilters.tsx | 61 +++++-- .../components/LeftPanelContent.tsx | 8 - .../components/LeftPanelFavorites.tsx | 130 -------------- 34 files changed, 454 insertions(+), 595 deletions(-) delete mode 100644 src/frontend/apps/impress/src/assets/icons/doc-all.svg create mode 100644 src/frontend/apps/impress/src/assets/icons/ui-kit/globe-lock.svg create mode 100644 src/frontend/apps/impress/src/assets/icons/ui-kit/user.svg delete mode 100644 src/frontend/apps/impress/src/features/docs/doc-management/assets/pinned-document.svg delete mode 100644 src/frontend/apps/impress/src/features/left-panel/components/LeftPanelFavorites.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index ba1922e1d..2c544d2dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to - ♿️(frontend) use anchor links for interlinking sub-documents #2391 - ✨(frontend) reset side panel state between documents #2583 - ♿️(frontend) announce search loading state for screen readers #2526 +- ♻️(frontend) change favorite to star #2539 ### Fixed diff --git a/src/frontend/apps/e2e/__tests__/app-impress/auth.setup.ts b/src/frontend/apps/e2e/__tests__/app-impress/auth.setup.ts index a28440f44..7f1a2b5d2 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/auth.setup.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/auth.setup.ts @@ -28,7 +28,7 @@ const saveStorageState = async ( * If the grid is displayed, it means the user is logged in and the storage state can be saved. */ await expect( - page.getByRole('heading', { name: 'All docs', level: 2 }), + page.getByRole('heading', { name: 'Recent', level: 2 }), ).toBeVisible({ timeout: 10000 }); await page.context().storageState({ 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 58222c3a2..98a9133e6 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 @@ -112,29 +112,35 @@ test.describe('Document grid item options', () => { ).toBeVisible(); }); - test('it pins a document', async ({ page, browserName }) => { + test('it stars a document', async ({ page, browserName }) => { const [docTitle] = await createDoc(page, `Favorite doc`, browserName); + const [docTitle2] = await createDoc(page, `Not Favorite doc`, browserName); - await page.goto('/'); + await page.getByRole('button', { name: 'Back to homepage' }).click(); const row = await getGridRow(page, docTitle); - // Pin + // Star await row.getByRole('button', { name: /Open the menu of actions/ }).click(); - await page.getByRole('menuitem', { name: 'Pin' }).click(); + await page.getByRole('menuitem', { name: 'Star' }).click(); - // Check is pinned - await expect(row.getByTestId('doc-pinned-icon')).toBeVisible(); - const leftPanelFavorites = page.getByTestId('left-panel-favorites'); - await expect(leftPanelFavorites.getByText(docTitle)).toBeVisible(); + // Check is starred + await expect(row.getByText(/This document is starred/)).toBeVisible(); + await expect(page.getByText(docTitle2)).toBeVisible(); - // Unpin + await page.getByRole('link', { name: 'Starred', exact: true }).click(); + await expect(row.getByText(/This document is starred/)).toBeVisible(); + await expect(page.getByText(docTitle2)).toBeHidden(); + + // Unstar await row.getByRole('button', { name: /Open the menu of actions/ }).click(); - await page.getByText('Unpin').click(); + await page.getByText('Unstar').click(); + await expect(row).toBeHidden(); - // Check is unpinned - await expect(row.getByTestId('doc-pinned-icon')).toBeHidden(); - await expect(leftPanelFavorites.getByText(docTitle)).toBeHidden(); + // Check is unstarred + await page.getByRole('link', { name: 'Recent', exact: true }).click(); + await expect(row).toBeVisible(); + await expect(row.getByText(/This document is starred/)).toBeHidden(); }); test('it deletes the document', async ({ page, browserName }) => { @@ -148,13 +154,6 @@ test.describe('Document grid item options', () => { const row = await getGridRow(page, docTitle); await row.getByRole('button', { name: /Open the menu of actions/ }).click(); - await page.getByRole('menuitem', { name: 'Pin' }).click(); - - const leftPanelFavorites = page.getByTestId('left-panel-favorites'); - await expect(leftPanelFavorites.getByText(docTitle)).toBeVisible(); - - await row.getByRole('button', { name: /Open the menu of actions/ }).click(); - await page.getByRole('menuitem', { name: 'Delete' }).click(); await expect( @@ -174,7 +173,6 @@ test.describe('Document grid item options', () => { await expect( page.getByLabel('Documents grid').getByText(docTitle), ).toBeHidden(); - await expect(leftPanelFavorites.getByText(docTitle)).toBeHidden(); }); test('it checks the leave feature', async ({ page, browserName }) => { 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 7cb42a835..a95ff4974 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 @@ -520,46 +520,22 @@ test.describe('Doc Header', () => { expect(clipboardContent.trim()).toMatch(url); }); - test('it pins a document', async ({ page, browserName }) => { - const [docTitle] = await createDoc(page, `Pin doc`, browserName); + test('it stars a document', async ({ page, browserName }) => { + await createDoc(page, `Star doc`, browserName); + // Star await page .getByRole('button', { name: 'Open the document options' }) .click(); + await page.getByRole('menuitem', { name: 'Star' }).click(); + await expect(page.getByText('This document is starred')).toBeVisible(); - // Pin - await page.getByRole('menuitem', { name: 'Pin' }).click(); + // UnStar await page .getByRole('button', { name: 'Open the document options' }) .click(); - await expect(page.getByText('Unpin')).toBeVisible(); - - await page.goto('/'); - - const row = await getGridRow(page, docTitle); - - // Check is pinned - await expect(row.getByTestId('doc-pinned-icon')).toBeVisible(); - const leftPanelFavorites = page.getByTestId('left-panel-favorites'); - await expect(leftPanelFavorites.getByText(docTitle)).toBeVisible(); - - await row.getByText(docTitle).click(); - await page - .getByRole('button', { name: 'Open the document options' }) - .click(); - - // Unpin - await page.getByRole('menuitem', { name: 'Unpin' }).click(); - await page - .getByRole('button', { name: 'Open the document options' }) - .click(); - await expect(page.getByRole('menuitem', { name: 'Pin' })).toBeVisible(); - - await page.goto('/'); - - // Check is unpinned - await expect(row.getByTestId('doc-pinned-icon')).toBeHidden(); - await expect(leftPanelFavorites.getByText(docTitle)).toBeHidden(); + await page.getByRole('menuitem', { name: 'Unstar' }).click(); + await expect(page.getByText('This document is starred')).toBeHidden(); }); test('it duplicates a document', async ({ page, browserName }) => { diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-trashbin.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-trashbin.spec.ts index 9a3d2ae9e..469deb076 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-trashbin.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-trashbin.spec.ts @@ -32,24 +32,27 @@ test.describe('Doc Trashbin', () => { await page.getByRole('button', { name: 'Back to homepage' }).click(); + // Delete the first document - Is not displayed const row1 = await getGridRow(page, title1); await clickInGridMenu(page, row1, 'Delete'); await page.getByRole('button', { name: 'Delete document' }).click(); await expect(row1.getByText(title1)).toBeHidden(); + // Star the second document - Is displayed in the starred list const row2 = await getGridRow(page, title2); - await clickInGridMenu(page, row2, 'Pin'); - const leftPanelFavorites = page.getByTestId('left-panel-favorites'); - await expect(leftPanelFavorites.getByText(title2)).toBeVisible(); + await clickInGridMenu(page, row2, 'Star'); + await page.getByRole('link', { name: 'Starred', exact: true }).click(); + await expect(row2.getByText(title2)).toBeVisible(); + // Delete the second document - It is not displayed in the starred list anymore await clickInGridMenu(page, row2, 'Delete'); await page.getByRole('button', { name: 'Delete document' }).click(); await expect(row2.getByText(title2)).toBeHidden(); - await expect(leftPanelFavorites.getByText(title2)).toBeHidden(); + // It is displayed in the trashbin list await page.getByRole('link', { name: 'Trashbin' }).click(); - const docsGrid = page.getByTestId('docs-grid'); + await expect(row2.getByText(title2)).toBeVisible(); await expect(docsGrid.getByText('Days remaining')).toBeVisible(); try { @@ -86,8 +89,13 @@ test.describe('Doc Trashbin', () => { await clickInGridMenu(page, row2, 'Restore'); await expect(row2.getByText(title2)).toBeHidden(); - await expect(leftPanelFavorites.getByText(title2)).toBeVisible(); - await page.getByRole('link', { name: 'All docs' }).click(); + + // It is displayed in the starred list again + await page.getByRole('link', { name: 'Starred', exact: true }).click(); + await expect(row2.getByText(title2)).toBeVisible(); + + // It is displayed in the recent list again + await page.getByRole('link', { name: 'Recent' }).click(); const row2Restored = await getGridRow(page, title2); await expect(row2Restored.getByText(title2)).toBeVisible(); await row2Restored.getByRole('link', { name: /Open document/ }).click(); @@ -115,7 +123,6 @@ test.describe('Doc Trashbin', () => { browserName, 1, ); - await verifyDocName(page, topParent); const { name: subDocName } = await createRootSubPage( page, browserName, @@ -132,12 +139,13 @@ test.describe('Doc Trashbin', () => { await navigateToPageFromTree({ page, title: subDocName }); await verifyDocName(page, subDocName); + const docsGrid = page.getByTestId('docs-grid'); - await clickInEditorMenu(page, 'Pin'); + await clickInEditorMenu(page, 'Star'); await page.getByRole('button', { name: 'Back to homepage' }).click(); - const leftPanelFavorites = page.getByTestId('left-panel-favorites'); - await expect(leftPanelFavorites.getByText(subDocName)).toBeVisible(); - await leftPanelFavorites.getByText(subDocName).click(); + await page.getByRole('link', { name: 'Starred', exact: true }).click(); + await expect(docsGrid.getByText(subDocName)).toBeVisible(); + await page.getByText(subDocName).click(); await verifyDocName(page, subDocName); await clickInEditorMenu(page, 'Delete'); @@ -145,7 +153,7 @@ test.describe('Doc Trashbin', () => { await verifyDocName(page, topParent); await page.getByRole('button', { name: 'Back to homepage' }).click(); - await expect(leftPanelFavorites.getByText(subDocName)).toBeHidden(); + await expect(docsGrid.getByText(subDocName)).toBeHidden(); await page.getByRole('link', { name: 'Trashbin' }).click(); let row; @@ -194,6 +202,7 @@ test.describe('Doc Trashbin', () => { await expect(page.getByRole('button', { name: 'Share' })).toBeEnabled(); await expect(docTree.getByText(topParent)).toBeVisible(); await page.getByRole('button', { name: 'Back to homepage' }).click(); - await expect(leftPanelFavorites.getByText(subDocName)).toBeVisible(); + await page.getByRole('link', { name: 'Starred', exact: true }).click(); + await expect(docsGrid.getByText(subDocName)).toBeVisible(); }); }); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/header.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/header.spec.ts index 34c6b6907..cbb2dcf2c 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/header.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/header.spec.ts @@ -19,7 +19,7 @@ test.describe('Header', () => { // Clicking moves focus to the page heading await skipLink.click(); const pageHeading = page.getByRole('heading', { - name: 'All docs', + name: 'Recent', level: 2, }); await expect(pageHeading).toBeFocused(); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/left-panel.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/left-panel.spec.ts index 8e79553a9..d1cd268db 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/left-panel.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/left-panel.spec.ts @@ -285,7 +285,7 @@ test.describe('Left panel responsive', () => { await page.setViewportSize({ width: 900, height: 1200 }); await page.goto('/'); - await expect(page.getByRole('link', { name: 'All docs' })).toBeInViewport(); + await expect(page.getByRole('link', { name: 'Recent' })).toBeInViewport(); await expect(newDocButton).toBeInViewport(); await expect(userMenu).toBeInViewport(); await expect(header.getByLabel('Toggle left panel')).toBeVisible(); diff --git a/src/frontend/apps/impress/src/assets/icons/doc-all.svg b/src/frontend/apps/impress/src/assets/icons/doc-all.svg deleted file mode 100644 index a4e61a5aa..000000000 --- a/src/frontend/apps/impress/src/assets/icons/doc-all.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - diff --git a/src/frontend/apps/impress/src/assets/icons/ui-kit/clock.svg b/src/frontend/apps/impress/src/assets/icons/ui-kit/clock.svg index 06bc5be0d..7cf330c52 100644 --- a/src/frontend/apps/impress/src/assets/icons/ui-kit/clock.svg +++ b/src/frontend/apps/impress/src/assets/icons/ui-kit/clock.svg @@ -1,12 +1,19 @@ - - - + + + + + + + + + + diff --git a/src/frontend/apps/impress/src/assets/icons/ui-kit/globe-lock.svg b/src/frontend/apps/impress/src/assets/icons/ui-kit/globe-lock.svg new file mode 100644 index 000000000..f17289b61 --- /dev/null +++ b/src/frontend/apps/impress/src/assets/icons/ui-kit/globe-lock.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + diff --git a/src/frontend/apps/impress/src/assets/icons/ui-kit/globe.svg b/src/frontend/apps/impress/src/assets/icons/ui-kit/globe.svg index a4986a4ae..3aace368e 100644 --- a/src/frontend/apps/impress/src/assets/icons/ui-kit/globe.svg +++ b/src/frontend/apps/impress/src/assets/icons/ui-kit/globe.svg @@ -1,3 +1,8 @@ - - + + diff --git a/src/frontend/apps/impress/src/assets/icons/ui-kit/star-filled.svg b/src/frontend/apps/impress/src/assets/icons/ui-kit/star-filled.svg index 8d9682b4e..3c2df45af 100644 --- a/src/frontend/apps/impress/src/assets/icons/ui-kit/star-filled.svg +++ b/src/frontend/apps/impress/src/assets/icons/ui-kit/star-filled.svg @@ -1,3 +1,6 @@ - - + + diff --git a/src/frontend/apps/impress/src/assets/icons/ui-kit/star-slash.svg b/src/frontend/apps/impress/src/assets/icons/ui-kit/star-slash.svg index 41d706173..12929a781 100644 --- a/src/frontend/apps/impress/src/assets/icons/ui-kit/star-slash.svg +++ b/src/frontend/apps/impress/src/assets/icons/ui-kit/star-slash.svg @@ -1,3 +1,6 @@ - - + + diff --git a/src/frontend/apps/impress/src/assets/icons/ui-kit/star.svg b/src/frontend/apps/impress/src/assets/icons/ui-kit/star.svg index 8590d2bfe..51a89dbe0 100644 --- a/src/frontend/apps/impress/src/assets/icons/ui-kit/star.svg +++ b/src/frontend/apps/impress/src/assets/icons/ui-kit/star.svg @@ -1,3 +1,6 @@ - - + + diff --git a/src/frontend/apps/impress/src/assets/icons/ui-kit/trash.svg b/src/frontend/apps/impress/src/assets/icons/ui-kit/trash.svg index 17a8d7d85..4aa852367 100644 --- a/src/frontend/apps/impress/src/assets/icons/ui-kit/trash.svg +++ b/src/frontend/apps/impress/src/assets/icons/ui-kit/trash.svg @@ -1,12 +1,6 @@ - + - diff --git a/src/frontend/apps/impress/src/assets/icons/ui-kit/user.svg b/src/frontend/apps/impress/src/assets/icons/ui-kit/user.svg new file mode 100644 index 000000000..cc927d340 --- /dev/null +++ b/src/frontend/apps/impress/src/assets/icons/ui-kit/user.svg @@ -0,0 +1,12 @@ + + + + diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeaderInfo.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeaderInfo.tsx index c5f7f8d73..9f38b10a5 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeaderInfo.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeaderInfo.tsx @@ -1,8 +1,6 @@ import { t } from 'i18next'; -import PublicSVG from '@/assets/icons/ui-kit/public.svg'; -import ProtedtedSVG from '@/assets/icons/ui-kit/vpn_lock.svg'; -import { Box, Text } from '@/components'; +import { Box, Icon, Text } from '@/components'; import { useConfig } from '@/core'; import { Doc, @@ -13,6 +11,9 @@ import { useTrans, } from '@/docs/doc-management'; import { useDate } from '@/hooks'; +import PublicSVG from '@/icons/public.svg'; +import StarIcon from '@/icons/star-filled.svg'; +import ProtedtedSVG from '@/icons/vpn_lock.svg'; interface DocHeaderInfoProps { doc: Doc; @@ -41,7 +42,31 @@ export const DocHeaderInfo = ({ doc }: DocHeaderInfoProps) => { } return ( - + + {doc.is_favorite && ( + <> + + {t('This document is starred')} + + + } + /> + + + )} {t('Role')} @@ -56,7 +81,7 @@ export const DocHeaderInfo = ({ doc }: DocHeaderInfoProps) => { > {transRole(isEditable ? doc.user_role || doc.link_role : Role.READER)} -  ·  +   ·  {dateLabel} diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/api/useCreateFavoriteDoc.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/api/useCreateFavoriteDoc.tsx index a78f04025..9f6aab58b 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/api/useCreateFavoriteDoc.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/api/useCreateFavoriteDoc.tsx @@ -42,13 +42,13 @@ export function useCreateFavoriteDoc({ }); }); - const message = t('Document pinned successfully!'); + const message = t('Document starred successfully!'); announce(message, 'polite'); onSuccess?.(); }, onError: () => { - const message = t('Failed to pin the document.'); + const message = t('Failed to star the document.'); announce(message, 'assertive'); }, }); diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/api/useDeleteFavoriteDoc.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/api/useDeleteFavoriteDoc.tsx index b8372d51e..863803a4f 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/api/useDeleteFavoriteDoc.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/api/useDeleteFavoriteDoc.tsx @@ -42,13 +42,13 @@ export function useDeleteFavoriteDoc({ }); }); - const message = t('Document unpinned successfully!'); + const message = t('Document unstarred successfully!'); announce(message, 'polite'); onSuccess?.(); }, onError: () => { - const message = t('Failed to unpin the document.'); + const message = t('Failed to unstar the document.'); announce(message, 'assertive'); }, }); diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/api/useDocsFavorite.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/api/useDocsFavorite.tsx index 3baa45102..fc10d1c6e 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/api/useDocsFavorite.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/api/useDocsFavorite.tsx @@ -9,10 +9,11 @@ import { useAPIInfiniteQuery, } from '@/api'; -import { Doc } from '../types'; +import { Doc, DocsOrdering } from '../types'; export type DocsFavoriteParams = { page: number; + ordering?: DocsOrdering; }; export type DocsFavoriteResponse = APIList; @@ -24,7 +25,9 @@ export const getDocsFavorite = async ( if (params.page) { searchParams.set('page', params.page.toString()); } - + if (params.ordering) { + searchParams.set('ordering', params.ordering); + } const response = await fetchAPI( `documents/favorite_list/?${searchParams.toString()}`, ); diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/assets/pinned-document.svg b/src/frontend/apps/impress/src/features/docs/doc-management/assets/pinned-document.svg deleted file mode 100644 index 027fb33b6..000000000 --- a/src/frontend/apps/impress/src/features/docs/doc-management/assets/pinned-document.svg +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/components/DocToolBox.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/components/DocToolBox.tsx index 9546ae400..03c6dff4b 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/components/DocToolBox.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/components/DocToolBox.tsx @@ -10,19 +10,19 @@ import { useRouter } from 'next/router'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import ContentCopyIcon from '@/assets/icons/ui-kit/copy.svg'; -import DownloadSVG from '@/assets/icons/ui-kit/download.svg'; -import HistorySVG from '@/assets/icons/ui-kit/history.svg'; -import KeepSVG from '@/assets/icons/ui-kit/keep.svg'; -import KeepOffSVG from '@/assets/icons/ui-kit/keep_off.svg'; -import LeaveSVG from '@/assets/icons/ui-kit/leave.svg'; -import LinkIcon from '@/assets/icons/ui-kit/link.svg'; -import MoreSVG from '@/assets/icons/ui-kit/more_horiz.svg'; -import PrintIcon from '@/assets/icons/ui-kit/print.svg'; -import SharedIcon from '@/assets/icons/ui-kit/shared.svg'; -import DeleteIcon from '@/assets/icons/ui-kit/trash.svg'; import { usePresenterStore } from '@/docs/doc-presenter/stores'; import { useAuth } from '@/features/auth'; +import ContentCopyIcon from '@/icons/copy.svg'; +import DownloadSVG from '@/icons/download.svg'; +import HistorySVG from '@/icons/history.svg'; +import LeaveSVG from '@/icons/leave.svg'; +import LinkIcon from '@/icons/link.svg'; +import MoreSVG from '@/icons/more_horiz.svg'; +import PrintIcon from '@/icons/print.svg'; +import SharedIcon from '@/icons/shared.svg'; +import StarSlashIcon from '@/icons/star-slash.svg'; +import StarIcon from '@/icons/star.svg'; +import DeleteIcon from '@/icons/trash.svg'; import { useFocusStore, useResponsiveStore } from '@/stores'; import { printDocumentWithStyles } from '../../doc-export/utils_print'; @@ -167,11 +167,11 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => { }, { type: 'separator' }, { - label: doc.is_favorite ? t('Unpin') : t('Pin'), + label: doc.is_favorite ? t('Unstar') : t('Star'), icon: doc.is_favorite ? ( - + ) : ( - + ), callback: () => { if (doc.is_favorite) { @@ -181,7 +181,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => { } }, isHidden: !doc.abilities.favorite, - testId: `docs-actions-${doc.is_favorite ? 'unpin' : 'pin'}-${doc.id}`, + testId: `docs-actions-${doc.is_favorite ? 'unstar' : 'star'}-${doc.id}`, }, { label: t('Duplicate', { diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/components/SimpleDocItem.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/components/SimpleDocItem.tsx index fb3dd9e16..3c3741b41 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/components/SimpleDocItem.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/components/SimpleDocItem.tsx @@ -6,7 +6,6 @@ import { useDate } from '@/hooks/useDate'; import DocsIcon from '@/icons/Docs.svg'; import SubdocsIcon from '@/icons/Subdocs.svg'; import ArrowIcon from '@/icons/arrow-corner-down-right.svg'; -import PinnedIcon from '@/icons/pinned.svg'; import { useResponsiveStore } from '@/stores'; import { useDocUtils, useTrans } from '../hooks'; @@ -26,13 +25,11 @@ const ItemTextCss = css` type SimpleDocItemProps = { doc: Doc; breadcrumb?: string; - isPinned?: boolean; showDate?: boolean; }; export const SimpleDocItem = ({ doc, - isPinned = false, showDate = false, breadcrumb, }: SimpleDocItemProps) => { @@ -59,34 +56,7 @@ export const SimpleDocItem = ({ className="--docs--simple-doc-item" aria-label={itemAriaLabel} > - {isPinned ? ( - - - } - $shrink="0" - /> - - - - ) : isChild ? ( + {isChild ? ( { }; const translatedFilters = { - [DocDefaultFilter.ALL_DOCS]: t('All docs'), + [DocDefaultFilter.ALL_DOCS]: t('Recent'), [DocDefaultFilter.MY_DOCS]: t('My docs'), [DocDefaultFilter.SHARED_WITH_ME]: t('Shared with me'), + [DocDefaultFilter.STARRED]: t('Starred'), [DocDefaultFilter.TRASHBIN]: t('Trashbin'), }; diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/types.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/types.tsx index 8126614cd..ec1309978 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/types.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/types.tsx @@ -114,6 +114,7 @@ export enum DocDefaultFilter { ALL_DOCS = 'all_docs', MY_DOCS = 'my_docs', SHARED_WITH_ME = 'shared_with_me', + STARRED = 'starred', TRASHBIN = 'trashbin', } diff --git a/src/frontend/apps/impress/src/features/docs/doc-search/components/DocSearchItem.tsx b/src/frontend/apps/impress/src/features/docs/doc-search/components/DocSearchItem.tsx index 1be332807..0800aee57 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-search/components/DocSearchItem.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-search/components/DocSearchItem.tsx @@ -28,7 +28,6 @@ export const DocSearchItem = ({ doc }: DocSearchItemProps) => { diff --git a/src/frontend/apps/impress/src/features/docs/docs-grid/api/useDocsGridQuery.tsx b/src/frontend/apps/impress/src/features/docs/docs-grid/api/useDocsGridQuery.tsx index 7a3c94e1f..661fbd323 100644 --- a/src/frontend/apps/impress/src/features/docs/docs-grid/api/useDocsGridQuery.tsx +++ b/src/frontend/apps/impress/src/features/docs/docs-grid/api/useDocsGridQuery.tsx @@ -2,7 +2,8 @@ import { DocDefaultFilter, DocsOrdering, useInfiniteDocs, -} from '../../doc-management'; + useInfiniteDocsFavorite, +} from '@/docs/doc-management'; import { useInfiniteDocsTrashbin } from './useDocsTrashbin'; @@ -19,6 +20,16 @@ export const useDocsGridQuery = ( }, ); + const favoriteQuery = useInfiniteDocsFavorite( + { + page: 1, + ordering, + }, + { + enabled: target === DocDefaultFilter.STARRED, + }, + ); + const docsQuery = useInfiniteDocs( { page: 1, @@ -29,9 +40,18 @@ export const useDocsGridQuery = ( }), }, { - enabled: target !== DocDefaultFilter.TRASHBIN, + enabled: + target !== DocDefaultFilter.TRASHBIN && + target !== DocDefaultFilter.STARRED, }, ); - return target === DocDefaultFilter.TRASHBIN ? trashbinQuery : docsQuery; + switch (target) { + case DocDefaultFilter.TRASHBIN: + return trashbinQuery; + case DocDefaultFilter.STARRED: + return favoriteQuery; + default: + return docsQuery; + } }; 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 679ecee0f..4d5c85d08 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 @@ -4,13 +4,17 @@ import { useTranslation } from 'react-i18next'; import { InView } from 'react-intersection-observer'; import { css } from 'styled-components'; -import AllDocs from '@/assets/icons/doc-all.svg'; -import { Box, Card, Icon, Loading, Text } from '@/components'; +import { Box, Card, Loading, Text } from '@/components'; import { FadeComponent } from '@/components/Effect'; import { useImport } from '@/docs/doc-management/hooks/useImport'; import { DocDefaultFilter, DocsOrdering } from '@/docs/doc-management/types'; import DocsIcon from '@/icons/Docs.svg'; import BinIcon from '@/icons/bin.svg'; +import ClockIcon from '@/icons/clock.svg'; +import SharedIcon from '@/icons/shared.svg'; +import StarIcon from '@/icons/star.svg'; +import TrashIcon from '@/icons/trash.svg'; +import UserIcon from '@/icons/user.svg'; import { useResponsiveStore } from '@/stores'; import { useDocsGridQuery } from '../api/useDocsGridQuery'; @@ -174,16 +178,19 @@ const DocGridTitleBar = ({ const { t } = useTranslation(); const { isDesktop } = useResponsiveStore(); - let title = t('All docs'); - let icon = } />; + let title = t('Recent'); + let icon = ; if (target === DocDefaultFilter.MY_DOCS) { - icon = ; + icon = ; title = t('My docs'); } else if (target === DocDefaultFilter.SHARED_WITH_ME) { - icon = ; + icon = ; title = t('Shared with me'); + } else if (target === DocDefaultFilter.STARRED) { + icon = ; + title = t('Starred'); } else if (target === DocDefaultFilter.TRASHBIN) { - icon = ; + icon = ; title = t('Trashbin'); } @@ -217,6 +224,7 @@ const DocGridNoDocs = ({ target }: { target: DocDefaultFilter }) => { DocDefaultFilter.ALL_DOCS, DocDefaultFilter.MY_DOCS, DocDefaultFilter.SHARED_WITH_ME, + DocDefaultFilter.STARRED, ].includes(target) && ( <> 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 25068538a..a2b114567 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 @@ -8,14 +8,6 @@ import dynamic from 'next/dynamic'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import ContentCopySVG from '@/assets/icons/ui-kit/content_copy.svg'; -import DeleteSVG from '@/assets/icons/ui-kit/delete.svg'; -import DocMoveInSVG from '@/assets/icons/ui-kit/doc-move-in.svg'; -import GroupSVG from '@/assets/icons/ui-kit/group.svg'; -import KeepSVG from '@/assets/icons/ui-kit/keep.svg'; -import KeepOffSVG from '@/assets/icons/ui-kit/keep_off.svg'; -import LeaveSVG from '@/assets/icons/ui-kit/leave.svg'; -import MoreSVG from '@/assets/icons/ui-kit/more_horiz.svg'; import { Icon } from '@/components/Icon'; import { Doc, @@ -27,6 +19,14 @@ import { useRestoreDoc, useTrans, } from '@/docs/doc-management'; +import ContentCopyIcon from '@/icons/content_copy.svg'; +import DeleteIcon from '@/icons/delete.svg'; +import DocMoveInIcon from '@/icons/doc-move-in.svg'; +import GroupIcon from '@/icons/group.svg'; +import LeaveIcon from '@/icons/leave.svg'; +import MoreIcon from '@/icons/more_horiz.svg'; +import StarSlashIcon from '@/icons/star-slash.svg'; +import StarIcon from '@/icons/star.svg'; import { focusMainContentStart } from '@/layouts/utils'; import { useFocusStore } from '@/stores'; @@ -101,11 +101,11 @@ const DocsGridActionsGlobal = ({ doc }: { doc: Doc }) => { const options: DropdownMenuItem[] = [ { - label: doc.is_favorite ? t('Unpin') : t('Pin'), + label: doc.is_favorite ? t('Unstar') : t('Star'), icon: doc.is_favorite ? ( - + ) : ( - + ), callback: () => { if (doc.is_favorite) { @@ -114,12 +114,12 @@ const DocsGridActionsGlobal = ({ doc }: { doc: Doc }) => { makeFavoriteDoc.mutate({ id: doc.id }); } }, - testId: `docs-grid-actions-${doc.is_favorite ? 'unpin' : 'pin'}-${doc.id}`, + testId: `docs-grid-actions-${doc.is_favorite ? 'unstar' : 'star'}-${doc.id}`, showSeparator: true, }, { label: t('Share'), - icon: , + icon: , callback: () => { setIsModalShareOpen(true); }, @@ -128,7 +128,7 @@ const DocsGridActionsGlobal = ({ doc }: { doc: Doc }) => { }, { label: t('Move into a doc'), - icon: , + icon: , callback: () => { setIsModalMoveOpen(true); }, @@ -137,7 +137,7 @@ const DocsGridActionsGlobal = ({ doc }: { doc: Doc }) => { }, { label: t('Duplicate'), - icon: , + icon: , isDisabled: !doc.abilities.duplicate, callback: () => { duplicateDoc({ @@ -150,14 +150,14 @@ const DocsGridActionsGlobal = ({ doc }: { doc: Doc }) => { }, { label: t('Leave'), - icon: , + icon: , callback: () => { setIsModalLeaveOpen(true); }, }, { label: t('Delete'), - icon: , + icon: , callback: () => { setIsModalRemoveOpen(true); }, @@ -296,7 +296,7 @@ const DocsGridDropdown = ({ doc, options }: DocsGridDropdownProps) => { title: doc.title || untitledDocument, })} size="nano" - icon={} + icon={} color="neutral" variant="tertiary" onClick={(e) => { diff --git a/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGridColumnName.tsx b/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGridColumnName.tsx index 2a7ffea82..faef939d9 100644 --- a/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGridColumnName.tsx +++ b/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGridColumnName.tsx @@ -20,16 +20,8 @@ export const DocsGridColumnName = ({ ordering, setOrdering, }: DocsGridColumnNameProps) => { - const { t } = useTranslation(); - const { isSmallMobile } = useResponsiveStore(); - - const canSort = target !== DocDefaultFilter.TRASHBIN; - - const toggleOrdering = (field: 'title' | 'updated_at') => { - setOrdering((prevOrdering) => - prevOrdering === field ? (`-${field}` as DocsOrdering) : field, - ); - }; + const canSort = + target !== DocDefaultFilter.TRASHBIN && target !== DocDefaultFilter.STARRED; return ( + {canSort ? ( + + ) : ( + + )} + + ); +}; + +const DocGridColumnWithSort = ({ + ordering, + setOrdering, +}: { + ordering: DocsOrdering; + setOrdering: React.Dispatch>; +}) => { + const { t } = useTranslation(); + const { isSmallMobile } = useResponsiveStore(); + const defaultOrdering: Record<'title' | 'updated_at', DocsOrdering> = { + title: 'title', + updated_at: '-updated_at', + }; + const toggleOrdering = (field: 'title' | 'updated_at') => { + setOrdering((prevOrdering) => { + if (prevOrdering === field) { + return `-${field}` as DocsOrdering; + } + if (prevOrdering === `-${field}`) { + return field; + } + return defaultOrdering[field]; + }); + }; + + return ( + <> - {canSort ? ( - toggleOrdering('title')} - /> - ) : ( - - {t('Name')} - - )} + toggleOrdering('title')} + /> {!isSmallMobile && ( - {canSort ? ( - - {' '} - {t('Last modified')} - - } - ariaLabel={t('Last modified')} - ordering={ordering} - field="updated_at" - onClick={() => toggleOrdering('updated_at')} - /> - ) : ( - - {t('Days remaining')} - - )} + + {' '} + {t('Last modified')} + + } + ariaLabel={t('Last modified')} + ordering={ordering} + field="updated_at" + defaultOrdering={defaultOrdering.updated_at} + onClick={() => toggleOrdering('updated_at')} + /> )} - + + ); +}; + +const DocGridColumnWithoutSort = ({ target }: { target: DocDefaultFilter }) => { + const { t } = useTranslation(); + const { isSmallMobile } = useResponsiveStore(); + + return ( + <> + + + {t('Name')} + + + {!isSmallMobile && ( + + + {' '} + {target === DocDefaultFilter.STARRED + ? t('Last modified') + : t('Days remaining')} + + + )} + ); }; @@ -93,20 +143,22 @@ const DocGridSortButton = ({ ariaLabel, field, ordering, + defaultOrdering, onClick, }: { label: ReactNode; ariaLabel: string; field: 'title' | 'updated_at'; ordering: DocsOrdering; + defaultOrdering: DocsOrdering; onClick: () => void; }) => { const { t } = useTranslation(); - const isDesc = ordering === `-${field}`; - const isActive = ordering === field || isDesc; + const isActive = ordering?.includes(field); + const isDesc = ordering !== defaultOrdering; return ( - + {label} @@ -127,7 +179,7 @@ const DocGridSortButton = ({ } iconPosition="right" icon={} - variant="tertiary" + variant={isDesc ? 'tertiary' : 'secondary'} color={isActive ? 'brand' : 'neutral'} /> diff --git a/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGridItem.tsx b/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGridItem.tsx index b35b8b028..c2e0f6536 100644 --- a/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGridItem.tsx +++ b/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGridItem.tsx @@ -11,6 +11,9 @@ import { useCunninghamTheme } from '@/cunningham'; import { Doc, LinkReach, SimpleDocItem, useTrans } from '@/docs/doc-management'; import { useLeftPanelStore } from '@/features/left-panel'; import { useDate } from '@/hooks'; +import GlobeLockIcon from '@/icons/globe-lock.svg'; +import GlobeIcon from '@/icons/globe.svg'; +import StarIcon from '@/icons/star-filled.svg'; import { useResponsiveStore } from '@/stores'; import { DocsGridActions } from './DocsGridActions'; @@ -65,7 +68,11 @@ export const DocsGridItem = ({ &:nth-child(1n):not(:last-child) { border-bottom: 1px solid - var(--c--contextuals--border--surface--primary); + color-mix( + in srgb, + var(--c--contextuals--border--surface--primary) 50%, + transparent + ); } ${$css} @@ -173,42 +180,38 @@ export const DocsGridItemTitle = ({ $padding={{ right: isDesktop ? 'md' : '3xs' }} $maxWidth="100%" > - - {isShared && ( - - {withTooltip ? ( - - {isPublic - ? t('Accessible to anyone') - : t('Accessible to authenticated users')} - - } - placement="top" - > - - - - - ) : ( - - )} - - )} + + + {doc.is_favorite && ( + <> + } + /> + {t('This document is starred')} + + )} + {isShared && !withTooltip && } + {isShared && withTooltip && ( + + {isPublic + ? t('Accessible to anyone') + : t('Accessible to authenticated users')} + + } + placement="top" + > + + + + + )} + ); }; @@ -217,20 +220,24 @@ const IconPublic = ({ isPublic }: { isPublic: boolean }) => { const { t } = useTranslation(); return ( - <> - - - {isPublic + - + : t('Accessible to authenticated users') + } + icon={ + isPublic ? ( +