Skip to content

Commit e6ad7d3

Browse files
committed
✨(frontend) create editor shortcuts hook
We created the editor shortcuts hook to handle the shortcuts for the editor. We implemented the following shortcuts: - "@" to open the interlinking inline content
1 parent 57e5a19 commit e6ad7d3

File tree

8 files changed

+81
-9
lines changed

8 files changed

+81
-9
lines changed

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,4 +500,20 @@ test.describe('Doc Editor', () => {
500500

501501
await verifyDocName(page, docChild1);
502502
});
503+
504+
test('it checks interlink shortcut @', async ({ page, browserName }) => {
505+
const [randomDoc] = await createDoc(page, 'doc-interlink', browserName, 1);
506+
507+
await verifyDocName(page, randomDoc);
508+
509+
const editor = page.locator('.bn-block-outer').last();
510+
await editor.click();
511+
await page.keyboard.press('@');
512+
513+
await expect(
514+
page.locator(
515+
"span[data-inline-content-type='interlinkingSearchInline'] input",
516+
),
517+
).toBeVisible();
518+
});
503519
});

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@ import { Box, TextErrors } from '@/components';
1919
import { Doc } from '@/docs/doc-management';
2020
import { useAuth } from '@/features/auth';
2121

22-
import { useUploadFile } from '../hook';
23-
import { useHeadings } from '../hook/useHeadings';
24-
import useSaveDoc from '../hook/useSaveDoc';
22+
import { useHeadings, useSaveDoc, useShortcuts, useUploadFile } from '../hook';
2523
import { useEditorStore } from '../stores';
2624
import { cssEditor } from '../styles';
2725
import { randomColor } from '../utils';
@@ -129,6 +127,7 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
129127
[collabName, lang, provider, uploadFile],
130128
);
131129
useHeadings(editor);
130+
useShortcuts(editor);
132131

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

src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/InterlinkingSearchInlineContent.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ export const InterlinkingSearchInlineContent = createReactInlineContentSpec(
1313
{
1414
type: 'interlinkingSearchInline',
1515
propSchema: {
16+
trigger: {
17+
default: '/',
18+
values: ['/', '@'],
19+
},
1620
disabled: {
1721
default: false,
1822
values: [true, false],
@@ -26,7 +30,13 @@ export const InterlinkingSearchInlineContent = createReactInlineContentSpec(
2630
return null;
2731
}
2832

29-
return <SearchPage {...props} contentRef={props.contentRef} />;
33+
return (
34+
<SearchPage
35+
{...props}
36+
trigger={props.inlineContent.props.trigger}
37+
contentRef={props.contentRef}
38+
/>
39+
);
3040
},
3141
},
3242
);
@@ -45,6 +55,7 @@ export const getInterlinkinghMenuItems = (
4555
type: 'interlinkingSearchInline',
4656
props: {
4757
disabled: false,
58+
trigger: '/',
4859
},
4960
},
5061
]);

src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/SearchPage.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ const inputStyle = css`
3939
`;
4040

4141
type SearchPageProps = {
42+
trigger: string;
4243
updateInlineContent: (
4344
update: PartialCustomInlineContentFromConfig<
4445
{
@@ -47,6 +48,9 @@ type SearchPageProps = {
4748
disabled: {
4849
default: boolean;
4950
};
51+
trigger: {
52+
default: string;
53+
};
5054
};
5155
content: 'styled';
5256
},
@@ -58,6 +62,7 @@ type SearchPageProps = {
5862

5963
export const SearchPage = ({
6064
contentRef,
65+
trigger,
6166
updateInlineContent,
6267
}: SearchPageProps) => {
6368
const { colorsTokens } = useCunninghamTheme();
@@ -99,7 +104,7 @@ export const SearchPage = ({
99104
tabIndex={-1} // Ensure the span is focusable
100105
>
101106
{' '}
102-
/
107+
{trigger}
103108
<Box
104109
as="input"
105110
$padding={{ left: '3px' }}
@@ -118,6 +123,7 @@ export const SearchPage = ({
118123
type: 'interlinkingSearchInline',
119124
props: {
120125
disabled: true,
126+
trigger,
121127
},
122128
});
123129

@@ -174,6 +180,7 @@ export const SearchPage = ({
174180
type: 'interlinkingSearchInline',
175181
props: {
176182
disabled: true,
183+
trigger,
177184
},
178185
});
179186

src/frontend/apps/impress/src/features/docs/doc-editor/hook/__tests__/useSaveDoc.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as Y from 'yjs';
55

66
import { AppWrapper } from '@/tests/utils';
77

8-
import useSaveDoc from '../useSaveDoc';
8+
import { useSaveDoc } from '../useSaveDoc';
99

1010
jest.mock('next/router', () => ({
1111
useRouter: jest.fn(),
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
1+
export * from './useHeadings';
12
export * from './useSaveDoc';
3+
export * from './useShortcuts';
24
export * from './useUploadFile';

src/frontend/apps/impress/src/features/docs/doc-editor/hook/useSaveDoc.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { toBase64 } from '../utils';
1010

1111
const SAVE_INTERVAL = 60000;
1212

13-
const useSaveDoc = (docId: string, yDoc: Y.Doc, canSave: boolean) => {
13+
export const useSaveDoc = (docId: string, yDoc: Y.Doc, canSave: boolean) => {
1414
const { mutate: updateDoc } = useUpdateDoc({
1515
listInvalideQueries: [KEY_LIST_DOC_VERSIONS],
1616
onSuccess: () => {
@@ -92,5 +92,3 @@ const useSaveDoc = (docId: string, yDoc: Y.Doc, canSave: boolean) => {
9292
};
9393
}, [router.events, saveDoc]);
9494
};
95-
96-
export default useSaveDoc;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { useEffect } from 'react';
2+
3+
import { DocsBlockNoteEditor } from '../types';
4+
5+
export const useShortcuts = (editor: DocsBlockNoteEditor) => {
6+
useEffect(() => {
7+
const handleKeyDown = (event: KeyboardEvent) => {
8+
if (event.key === '@' && editor?.isFocused()) {
9+
const selection = window.getSelection();
10+
const previousChar =
11+
selection?.anchorNode?.textContent?.charAt(
12+
selection.anchorOffset - 1,
13+
) || '';
14+
15+
if (![' ', ''].includes(previousChar)) {
16+
return;
17+
}
18+
19+
event.preventDefault();
20+
editor.insertInlineContent([
21+
{
22+
type: 'interlinkingSearchInline',
23+
props: {
24+
disabled: false,
25+
trigger: '@',
26+
},
27+
},
28+
]);
29+
}
30+
};
31+
32+
// Attach the event listener to the document instead of the window
33+
document.addEventListener('keydown', handleKeyDown);
34+
35+
return () => {
36+
document.removeEventListener('keydown', handleKeyDown);
37+
};
38+
}, [editor]);
39+
};

0 commit comments

Comments
 (0)