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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to

- ♿️(frontend) restore skip to content link after header redesign #2510
- 🌐(i18n) rename cn_CN to zh_CN, add eo_PL and zh_TW locales #2486
- ✨(frontend) Add "Copy link to block" feature #2547

### Fixed

Expand Down
71 changes: 66 additions & 5 deletions src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,8 @@ test.describe('Doc Editor', () => {
}) => {
await createDoc(page, 'doc-toolbar', browserName, 1);

await verifyDocName(page, 'doc-toolbar');

const editor = await writeInEditor({ page, text: 'test content' });

await page.waitForTimeout(1500);

await editor
.getByText('test content', {
exact: true,
Expand Down Expand Up @@ -106,6 +102,30 @@ test.describe('Doc Editor', () => {
).toBeVisible();
});

test('it checks side menu buttons are displayed', async ({
page,
browserName,
}) => {
await createDoc(page, 'doc-side-menu', browserName, 1);

const { editor } = await openSuggestionMenu({ page, suggestion: 'Table' });

await editor.locator('.tableWrapper').first().hover();

await page.locator('.bn-side-menu > button').last().click();
await expect(page.getByRole('menuitem', { name: 'Colors' })).toBeVisible();
await expect(
page.getByRole('menuitem', { name: 'Header row' }),
).toBeVisible();
await expect(
page.getByRole('menuitem', { name: 'Header column' }),
).toBeVisible();
await expect(page.getByRole('menuitem', { name: 'Delete' })).toBeVisible();
await expect(
page.getByRole('menuitem', { name: 'Copy link to block' }),
).toBeVisible();
});

test('markdown button converts from markdown to the editor syntax json', async ({
page,
browserName,
Expand Down Expand Up @@ -391,7 +411,7 @@ test.describe('Doc Editor', () => {
await page.keyboard.press('Escape');

await page.locator('.bn-side-menu > button').last().click();
await page.locator('.mantine-Menu-dropdown > button').last().click();
await page.getByRole('menuitem', { name: 'Color' }).click();
await page.locator('.bn-color-picker-dropdown > button').last().click();

await expect(
Expand Down Expand Up @@ -637,4 +657,45 @@ test.describe('Doc Editor', () => {

await expect(editor.getByText('Mobile Text')).toBeVisible();
});

test('it checks "Copy link to block" feature', async ({
page,
browserName,
}) => {
await createDoc(page, 'doc-scroll', browserName, 1);

const editor = await writeInEditor({ page, text: 'First Block' });

for (let i = 0; i < 30; i++) {
await page.keyboard.press('Enter');
}

await writeInEditor({ page, text: 'My Block' });

await editor
.locator('.bn-block-outer')
.filter({ hasText: 'My Block' })
.first()
.hover();

await page.locator('.bn-side-menu > button').last().click();
await page.getByRole('menuitem', { name: 'Link to block' }).click();
await expect(page.getByText('Link Copied !')).toBeVisible();

const url = page.url();

const handle = await page.evaluateHandle(() =>
navigator.clipboard.readText(),
);
const clipboardContent = await handle.jsonValue();

await expect(editor.getByText('First Block')).not.toBeInViewport();
await page.goto(url);
await expect(editor.getByText('First Block')).toBeInViewport();
await expect(editor.getByText('My Block')).not.toBeInViewport();

await page.goto(clipboardContent);
await expect(editor.getByText('First Block')).not.toBeInViewport();
await expect(editor.getByText('My Block')).toBeInViewport();
});
});
15 changes: 15 additions & 0 deletions src/frontend/apps/impress/src/assets/icons/ui-kit/color.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 16 additions & 2 deletions src/frontend/apps/impress/src/assets/icons/ui-kit/link.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 5 additions & 2 deletions src/frontend/apps/impress/src/assets/icons/ui-kit/trash.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { AI_FEATURE_FLAG, DEFAULT_LOCALE } from '../conf';
import {
useHeadings,
useSaveDoc,
useScrollToBlockAnchor,
useShortcuts,
useUploadFile,
useUploadStatus,
Expand All @@ -53,6 +54,7 @@ import { randomColor, sanitizeColor } from '../utils';
import BlockNoteAI from './AI';
import { BlockNoteSuggestionMenu } from './BlockNoteSuggestionMenu';
import { BlockNoteToolbar } from './BlockNoteToolBar/BlockNoteToolbar';
import { DocsSideMenu } from './DocsSideMenu/DocsSideMenu';
import { CalloutBlock, PdfBlock, UploadLoaderBlock } from './custom-blocks';
const AIMenu = BlockNoteAI?.AIMenu;
const AIMenuController = BlockNoteAI?.AIMenuController;
Expand Down Expand Up @@ -263,6 +265,8 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {

useUploadStatus(editor);

useScrollToBlockAnchor();

useEffect(() => {
setEditor(editor);

Expand Down Expand Up @@ -292,6 +296,7 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
editor={editor}
formattingToolbar={false}
slashMenu={false}
sideMenu={false}
theme="light"
comments={false}
aria-label={t('Document editor')}
Expand All @@ -303,6 +308,7 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
)}
<BlockNoteSuggestionMenu aiAllowed={aiBlockNoteAllowed} />
<BlockNoteToolbar aiAllowed={aiBlockNoteAllowed} />
<DocsSideMenu />
{showComments && <FloatingComposerController />}
{showComments && !isCommentSideBarOpen && <FloatingThreadController />}
{threadsSidebarTarget &&
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* Overrides the default DragHandleMenu with a custom implementation for the Docs editor.
* See: https://github.com/TypeCellOS/BlockNote/blob/main/packages/react/src/components/SideMenu/DragHandleMenu/DragHandleMenu.tsx
*/
import {
BlockColorsItem,
RemoveBlockItem,
SideMenu,
SideMenuController,
TableColumnHeaderItem,
TableRowHeaderItem,
useComponentsContext,
useDictionary,
} from '@blocknote/react';

import { Box, HorizontalSeparator } from '@/components';
import ColorIcon from '@/icons/color.svg';
import TableHeaderColumnIcon from '@/icons/table-header-column.svg';
import TableHeaderRowIcon from '@/icons/table-header-row.svg';
import TrashIcon from '@/icons/trash.svg';

import { LinkToBlockItem } from './LinkToBlockItem';
import { TableHeaderSeparator } from './TableHeaderSeparator';

const DocsDragHandleMenu = () => {
const Components = useComponentsContext();
const dict = useDictionary();

if (Components === undefined) {
return null;
}

return (
<Components.Generic.Menu.Dropdown className="bn-menu-dropdown bn-drag-handle-menu">
<BlockColorsItem>
<Box $align="center" $gap="xxs" $direction="row">
<ColorIcon width="16" height="16" aria-hidden="true" />
{dict.drag_handle.colors_menuitem}
</Box>
</BlockColorsItem>
<HorizontalSeparator $margin={{ vertical: '3xs' }} />
<LinkToBlockItem />
<TableHeaderSeparator />
<TableRowHeaderItem>
<Box $align="center" $gap="xxs" $direction="row">
<TableHeaderRowIcon width="16" height="16" aria-hidden="true" />
{dict.drag_handle.header_row_menuitem}
</Box>
</TableRowHeaderItem>
<TableColumnHeaderItem>
<Box $align="center" $gap="xxs" $direction="row">
<TableHeaderColumnIcon width="16" height="16" aria-hidden="true" />
{dict.drag_handle.header_column_menuitem}
</Box>
</TableColumnHeaderItem>
<HorizontalSeparator $margin={{ vertical: '3xs' }} />
<RemoveBlockItem>
<Box $align="center" $gap="xxs" $direction="row">
<TrashIcon width="16" height="16" aria-hidden="true" />
{dict.drag_handle.delete_menuitem}
</Box>
</RemoveBlockItem>
</Components.Generic.Menu.Dropdown>
);
};

export const DocsSideMenu = () => (
<SideMenuController
sideMenu={() => <SideMenu dragHandleMenu={DocsDragHandleMenu} />}
/>
);
Loading
Loading