diff --git a/CHANGELOG.md b/CHANGELOG.md index efc2c7328a..0d931c0d0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,8 @@ and this project adheres to - πŸ›(backend) fix s3 version_id validation - πŸ›(frontend) retry check media status after page reload #1555 - πŸ›(frontend) fix Interlinking memory leak #1560 +- πŸ›(frontend) button new doc UI fix #1557 +- πŸ›(frontend) interlinking UI fix #1557 ## [3.8.2] - 2025-10-17 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 61535fffe7..2bdb9d8e66 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 @@ -134,6 +134,8 @@ test.describe('Document grid item options', () => { test('it deletes the document', async ({ page, browserName }) => { const [docTitle] = await createDoc(page, `delete doc`, browserName); + await verifyDocName(page, docTitle); + await page.goto('/'); await expect(page.getByText(docTitle)).toBeVisible(); 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 89f0528641..99635176fd 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 @@ -68,9 +68,18 @@ test.describe('Doc Header', () => { await createDoc(page, 'doc-update-emoji', browserName, 1); const emojiPicker = page.locator('.--docs--doc-title').getByRole('button'); + const optionMenu = page.getByLabel('Open the document options'); + const addEmojiMenuItem = page.getByRole('menuitem', { name: 'Add emoji' }); + const removeEmojiMenuItem = page.getByRole('menuitem', { + name: 'Remove emoji', + }); // Top parent should not have emoji picker await expect(emojiPicker).toBeHidden(); + await optionMenu.click(); + await expect(addEmojiMenuItem).toBeHidden(); + await expect(removeEmojiMenuItem).toBeHidden(); + await page.keyboard.press('Escape'); const { name: docChild } = await createRootSubPage( page, @@ -80,13 +89,23 @@ test.describe('Doc Header', () => { await verifyDocName(page, docChild); - await expect(emojiPicker).toBeVisible(); + // Emoji picker should be hidden initially + await expect(emojiPicker).toBeHidden(); + + // Add emoji + await optionMenu.click(); + await expect(removeEmojiMenuItem).toBeHidden(); + await addEmojiMenuItem.click(); + await expect(emojiPicker).toHaveText('πŸ“„'); + + // Change emoji await emojiPicker.click({ delay: 100, }); await page.getByRole('button', { name: 'πŸ˜€' }).first().click(); await expect(emojiPicker).toHaveText('πŸ˜€'); + // Update title const docTitle = page.getByRole('textbox', { name: 'Document title' }); await docTitle.fill('Hello Emoji World'); await docTitle.blur(); @@ -95,6 +114,12 @@ test.describe('Doc Header', () => { // Check the tree const row = await getTreeRow(page, 'Hello Emoji World'); await expect(row.getByText('πŸ˜€')).toBeVisible(); + + // Remove emoji + await optionMenu.click(); + await expect(addEmojiMenuItem).toBeHidden(); + await removeEmojiMenuItem.click(); + await expect(emojiPicker).toBeHidden(); }); test('it deletes the doc', 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 5a445a6083..babd522748 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 @@ -119,6 +119,10 @@ test.describe('Doc Trashbin', () => { await row.getByText(subDocName).click(); await verifyDocName(page, subDocName); + await expect( + page.locator('.--docs--editor-container.--docs--doc-deleted'), + ).toBeVisible(); + await expect(page.getByLabel('Alert deleted document')).toBeVisible(); await expect(page.getByRole('button', { name: 'Share' })).toBeDisabled(); await expect(page.locator('.bn-editor')).toHaveAttribute( diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-tree.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-tree.spec.ts index 422231f714..0e3bf49cc5 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-tree.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-tree.spec.ts @@ -352,7 +352,7 @@ test.describe('Doc Tree', () => { await page.getByRole('menuitem', { name: 'Remove emoji' }).click(); await expect(row.getByText('πŸ˜€')).toBeHidden(); - await expect(titleEmojiPicker).not.toHaveText('πŸ˜€'); + await expect(titleEmojiPicker).toBeHidden(); }); }); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-visibility.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-visibility.spec.ts index 88881e439f..d240c7bf27 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-visibility.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-visibility.spec.ts @@ -260,6 +260,10 @@ test.describe('Doc Visibility: Public', () => { await expect(card).toBeVisible(); await expect(card.getByText('Reader')).toBeVisible(); + await expect( + otherPage.locator('.--docs--editor-container.--docs--doc-readonly'), + ).toBeVisible(); + const otherEditor = await getEditor({ page: otherPage }); await expect(otherEditor).toHaveAttribute('contenteditable', 'false'); await expect(otherEditor.getByText('Hello Public Viewonly')).toBeVisible(); diff --git a/src/frontend/apps/impress/src/components/DropButton.tsx b/src/frontend/apps/impress/src/components/DropButton.tsx index 30172b4267..09772b4b49 100644 --- a/src/frontend/apps/impress/src/components/DropButton.tsx +++ b/src/frontend/apps/impress/src/components/DropButton.tsx @@ -31,7 +31,7 @@ const StyledButton = styled(Button)` font-weight: 500; font-size: 0.938rem; padding: 0; - ${({ $css }) => $css}; + border-radius: 4px; &:hover { background-color: var( --c--components--button--primary-text--background--color-hover @@ -41,6 +41,7 @@ const StyledButton = styled(Button)` box-shadow: 0 0 0 2px var(--c--theme--colors--primary-400); border-radius: 4px; } + ${({ $css }) => $css}; `; export interface DropButtonProps { diff --git a/src/frontend/apps/impress/src/components/Icon.tsx b/src/frontend/apps/impress/src/components/Icon.tsx index fd1458a825..f5c0baa1aa 100644 --- a/src/frontend/apps/impress/src/components/Icon.tsx +++ b/src/frontend/apps/impress/src/components/Icon.tsx @@ -13,7 +13,7 @@ export const Icon = ({ iconName, disabled, variant = 'outlined', - $variation, + $variation = 'text', ...textProps }: IconProps) => { const hasLabel = 'aria-label' in textProps || 'aria-labelledby' in textProps; @@ -41,15 +41,19 @@ type IconOptionsProps = TextType & { isHorizontal?: boolean; }; -export const IconOptions = ({ isHorizontal, ...props }: IconOptionsProps) => { +export const IconOptions = ({ + isHorizontal, + $css, + ...props +}: IconOptionsProps) => { return ( ); }; diff --git a/src/frontend/apps/impress/src/cunningham/cunningham-style.css b/src/frontend/apps/impress/src/cunningham/cunningham-style.css index bc4e5e2484..54d0c67d0b 100644 --- a/src/frontend/apps/impress/src/cunningham/cunningham-style.css +++ b/src/frontend/apps/impress/src/cunningham/cunningham-style.css @@ -44,6 +44,11 @@ contain: content; } +.c__button--medium { + min-height: var(--c--components--button--medium-height); + height: auto; +} + /** * Modal */ diff --git a/src/frontend/apps/impress/src/features/auth/api/useAuthQuery.tsx b/src/frontend/apps/impress/src/features/auth/api/useAuthQuery.tsx index 0b3fe070c1..592ce4c780 100644 --- a/src/frontend/apps/impress/src/features/auth/api/useAuthQuery.tsx +++ b/src/frontend/apps/impress/src/features/auth/api/useAuthQuery.tsx @@ -44,7 +44,7 @@ export function useAuthQuery( staleTime: 1000 * 60 * 15, // 15 minutes retry: (failureCount, error) => { // we assume that a 401 means the user is not logged in - if (error.status == 401) { + if (error.status === 401) { return false; } return failureCount < DEFAULT_QUERY_RETRY; 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 e2ee1543c2..4d23e1be98 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 @@ -28,6 +28,7 @@ import { useUploadStatus, } from '../hook'; import { useEditorStore } from '../stores'; +import { cssEditor } from '../styles'; import { DocsBlockNoteEditor } from '../types'; import { randomColor } from '../utils'; @@ -169,7 +170,7 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => { }, [setEditor, editor]); return ( - + {errorAttachment && ( { useHeadings(editor); return ( - + + + ); }; 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 1ec11e9137..a2d04ba298 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 @@ -1,3 +1,4 @@ +import clsx from 'clsx'; import { useEffect } from 'react'; import { css } from 'styled-components'; @@ -12,8 +13,6 @@ import { TableContent } from '@/docs/doc-table-content/'; import { useSkeletonStore } from '@/features/skeletons'; import { useResponsiveStore } from '@/stores'; -import { cssEditor } from '../styles'; - import { BlockNoteEditor, BlockNoteReader } from './BlockNoteEditor'; interface DocEditorContainerProps { @@ -55,10 +54,13 @@ export const DocEditorContainer = ({ > {docEditor} @@ -77,7 +79,9 @@ export const DocEditor = ({ doc }: DocEditorProps) => { const { isDesktop } = useResponsiveStore(); const { provider, isReady } = useProviderStore(); const { isEditable, isLoading } = useIsCollaborativeEditable(doc); - const readOnly = !doc.abilities.partial_update || !isEditable || isLoading; + const isDeletedDoc = !!doc.deleted_at; + const readOnly = + !doc.abilities.partial_update || !isEditable || isLoading || isDeletedDoc; const { setIsSkeletonVisible } = useSkeletonStore(); const isProviderReady = isReady && provider; @@ -117,7 +121,7 @@ export const DocEditor = ({ doc }: DocEditorProps) => { ) } - isDeletedDoc={!!doc.deleted_at} + isDeletedDoc={isDeletedDoc} readOnly={readOnly} /> diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/InterlinkingLinkInlineContent.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/InterlinkingLinkInlineContent.tsx index 769d6c15ec..28166de12d 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/InterlinkingLinkInlineContent.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/InterlinkingLinkInlineContent.tsx @@ -77,7 +77,7 @@ const LinkSelected = ({ url, title }: LinkSelectedProps) => { onClick={handleClick} draggable="false" $css={css` - display: inline; + display: contents; padding: 0.1rem 0.4rem; border-radius: 4px; & svg { @@ -89,6 +89,10 @@ const LinkSelected = ({ url, title }: LinkSelectedProps) => { background-color: ${colorsTokens['greyscale-100']}; } transition: background-color 0.2s ease-in-out; + + .--docs--doc-deleted & { + pointer-events: none; + } `} > {emoji ? ( diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/styles.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/styles.tsx index cb6c3a5353..3bade52bc0 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/styles.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/styles.tsx @@ -1,12 +1,13 @@ import { css } from 'styled-components'; -export const cssEditor = (readonly: boolean, isDeletedDoc: boolean) => css` +export const cssEditor = css` &, & > .bn-container, & .ProseMirror { height: 100%; - padding-bottom: 2rem; + } + & .ProseMirror { /** * WCAG Accessibility contrast fixes for BlockNote editor */ @@ -131,13 +132,6 @@ export const cssEditor = (readonly: boolean, isDeletedDoc: boolean) => css` .bn-block-outer:not([data-prev-depth-changed]):before { border-left: none; } - - ${isDeletedDoc && - ` - .node-interlinkingLinkInline button { - pointer-events: none; - } - `} } & .bn-editor { @@ -187,8 +181,10 @@ export const cssEditor = (readonly: boolean, isDeletedDoc: boolean) => css` } @media screen and (width <= 560px) { + .--docs--doc-readonly & .bn-editor { + padding-left: 10px; + } & .bn-editor { - ${readonly && `padding-left: 10px;`} padding-right: 10px; } .bn-side-menu[data-block-type='heading'][data-level='1'] { diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/components/BoutonShare.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/components/BoutonShare.tsx index c0e31db591..c821e5ac3f 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-header/components/BoutonShare.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-header/components/BoutonShare.tsx @@ -76,7 +76,7 @@ export const BoutonShare = ({ return (