Skip to content

Commit 32230a0

Browse files
committed
🐛(frontend) fix callout block arrow navigation
Fixed the behavior of arrow navigation around and between callout blocks. When navigating backwards (arrow up or left) from a callout that is either document's first block or comes right after a callout, the cursor doesn't go into a text selection, which it should. Similar behavior if when going forward. This bug is trigged by any of the four arrow keys. Added a command to set the cursor position as it is expected.
1 parent 96cb0f8 commit 32230a0

File tree

6 files changed

+378
-19
lines changed

6 files changed

+378
-19
lines changed

src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts

Lines changed: 112 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -670,9 +670,7 @@ test.describe('Doc Editor', () => {
670670
).toBeHidden();
671671
});
672672

673-
test('it checks if callout custom block', async ({ page, browserName }) => {
674-
await createDoc(page, 'doc-toolbar', browserName, 1);
675-
673+
test('it checks if callout custom block', async ({ page }) => {
676674
const editor = page.locator('.ProseMirror');
677675
await editor.click();
678676
await page.locator('.bn-block-outer').last().fill('/');
@@ -706,4 +704,115 @@ test.describe('Doc Editor', () => {
706704
'pink',
707705
);
708706
});
707+
708+
test('it checks callout block navigation', async ({ page }) => {
709+
const editor = page.locator('.ProseMirror');
710+
await editor.click();
711+
712+
await page.locator('.bn-block-outer').last().fill('/');
713+
await page.getByText('Add a callout block').click();
714+
715+
const ta = 'first callout';
716+
const calloutA = page.locator('div[data-content-type="callout"]').first();
717+
const inlineA = calloutA.locator('.inline-content');
718+
await inlineA.fill(ta);
719+
720+
await editor.press('Escape');
721+
722+
await page.locator('.bn-block-outer').last().fill('/');
723+
await page.getByText('Add a callout block').click();
724+
725+
const tb = 'second callout';
726+
const calloutB = page.locator('div[data-content-type="callout"]').nth(1);
727+
const inlineB = calloutB.locator('.inline-content');
728+
await inlineB.fill(tb);
729+
730+
await editor.press('Escape');
731+
732+
await page.locator('.bn-block-outer').last().fill('/');
733+
await page.getByText('Add a callout block').click();
734+
735+
const tc = 'third callout';
736+
const calloutC = page.locator('div[data-content-type="callout"]').nth(2);
737+
const inlineC = calloutC.locator('.inline-content');
738+
await inlineC.fill(tc);
739+
740+
await editor.press('Escape');
741+
742+
// focuses the first callout
743+
await inlineA.click();
744+
745+
// arrow up and down navigation
746+
await editor.press('ArrowDown');
747+
748+
let sel = await inlineB.evaluate((element) =>
749+
document
750+
.getSelection()
751+
?.focusNode?.parentElement?.parentElement?.isSameNode(element),
752+
);
753+
expect(sel).toBeTruthy();
754+
755+
await editor.press('ArrowDown');
756+
sel = await inlineC.evaluate((element) =>
757+
document
758+
.getSelection()
759+
?.focusNode?.parentElement?.parentElement?.isSameNode(element),
760+
);
761+
expect(sel).toBeTruthy();
762+
763+
await editor.press('ArrowUp');
764+
sel = await inlineB.evaluate((element) =>
765+
document
766+
.getSelection()
767+
?.focusNode?.parentElement?.parentElement?.isSameNode(element),
768+
);
769+
expect(sel).toBeTruthy();
770+
771+
await editor.press('ArrowUp');
772+
sel = await inlineA.evaluate((element) =>
773+
document
774+
.getSelection()
775+
?.focusNode?.parentElement?.parentElement?.isSameNode(element),
776+
);
777+
expect(sel).toBeTruthy();
778+
779+
await editor.press('ArrowUp');
780+
await editor.press('ArrowUp');
781+
await editor.press('ArrowUp');
782+
sel = await inlineA.evaluate((element) =>
783+
document
784+
.getSelection()
785+
?.focusNode?.parentElement?.parentElement?.isSameNode(element),
786+
);
787+
expect(sel).toBeTruthy();
788+
789+
await editor.press('Escape');
790+
791+
await inlineA.click();
792+
await editor.press('ArrowRight');
793+
sel = await inlineB.evaluate((element) =>
794+
document
795+
.getSelection()
796+
?.focusNode?.parentElement?.parentElement?.isSameNode(element),
797+
);
798+
expect(sel).toBeTruthy();
799+
800+
for (let i = 0; i <= tb.length; i++) {
801+
await editor.press('ArrowRight');
802+
}
803+
sel = await inlineC.evaluate((element) =>
804+
document
805+
.getSelection()
806+
?.focusNode?.parentElement?.parentElement?.isSameNode(element),
807+
);
808+
expect(sel).toBeTruthy();
809+
810+
await editor.press('ArrowLeft');
811+
sel = await inlineB.evaluate((element) =>
812+
document
813+
.getSelection()
814+
?.focusNode?.parentElement?.parentElement?.isSameNode(element),
815+
);
816+
expect(sel).toBeTruthy();
817+
});
709818
});

src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@ import { Box, TextErrors } from '@/components';
1818
import { Doc, useIsCollaborativeEditable } from '@/docs/doc-management';
1919
import { useAuth } from '@/features/auth';
2020

21-
import { useHeadings, useUploadFile, useUploadStatus } from '../hook/';
21+
import {
22+
useCalloutBlock,
23+
useHeadings,
24+
useUploadFile,
25+
useUploadStatus,
26+
} from '../hook/';
2227
import useSaveDoc from '../hook/useSaveDoc';
2328
import { useEditorStore } from '../stores';
2429
import { cssEditor } from '../styles';
@@ -131,6 +136,7 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
131136

132137
useHeadings(editor);
133138
useUploadStatus(editor);
139+
useCalloutBlock(editor);
134140

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

src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/CalloutBlock.tsx

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,15 @@ export const CalloutBlock = createReactBlockSpec(
8787
onEmojiSelect={onEmojiSelect}
8888
/>
8989
)}
90-
<Box as="p" className="inline-content" ref={contentRef}
90+
<Box
91+
as="p"
92+
className="inline-content"
93+
ref={contentRef}
9194
$css={css`
9295
& > div {
9396
padding-top: 2px;
9497
}
95-
`}
98+
`}
9699
/>
97100
</Box>
98101
);
@@ -105,19 +108,19 @@ export const getCalloutReactSlashMenuItems = (
105108
t: TFunction<'translation', undefined>,
106109
group: string,
107110
) => [
108-
{
109-
title: t('Callout'),
110-
onItemClick: () => {
111-
insertOrUpdateBlock(editor, {
112-
type: 'callout',
113-
});
114-
},
115-
aliases: ['callout', 'encadré', 'hervorhebung', 'benadrukken'],
116-
group,
117-
icon: <Icon iconName="lightbulb" $size="18px" />,
118-
subtext: t('Add a callout block'),
111+
{
112+
title: t('Callout'),
113+
onItemClick: () => {
114+
insertOrUpdateBlock(editor, {
115+
type: 'callout',
116+
});
119117
},
120-
];
118+
aliases: ['callout', 'encadré', 'hervorhebung', 'benadrukken'],
119+
group,
120+
icon: <Icon iconName="lightbulb" $size="18px" />,
121+
subtext: t('Add a callout block'),
122+
},
123+
];
121124

122125
export const getCalloutFormattingToolbarItems = (
123126
t: TFunction<'translation', undefined>,
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './useHeadings';
22
export * from './useSaveDoc';
33
export * from './useUploadFile';
4+
export * from './useCalloutBlock';

0 commit comments

Comments
 (0)