Skip to content

Commit e7709ba

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 2a7c0ef commit e7709ba

File tree

8 files changed

+86
-8
lines changed

8 files changed

+86
-8
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
@@ -761,4 +761,20 @@ test.describe('Doc Editor', () => {
761761

762762
await verifyDocName(page, docChild2);
763763
});
764+
765+
test('it checks interlink shortcut @', async ({ page, browserName }) => {
766+
const [randomDoc] = await createDoc(page, 'doc-interlink', browserName, 1);
767+
768+
await verifyDocName(page, randomDoc);
769+
770+
const editor = page.locator('.bn-block-outer').last();
771+
await editor.click();
772+
await page.keyboard.press('@');
773+
774+
await expect(
775+
page.locator(
776+
"span[data-inline-content-type='interlinkingSearchInline'] input",
777+
),
778+
).toBeVisible();
779+
});
764780
});

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

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

22-
import { useHeadings, useUploadFile, useUploadStatus } from '../hook/';
23-
import useSaveDoc from '../hook/useSaveDoc';
22+
import {
23+
useHeadings,
24+
useSaveDoc,
25+
useShortcuts,
26+
useUploadFile,
27+
useUploadStatus,
28+
} from '../hook';
2429
import { useEditorStore } from '../stores';
2530
import { cssEditor } from '../styles';
2631
import { DocsBlockNoteEditor } from '../types';
@@ -153,6 +158,7 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
153158
);
154159

155160
useHeadings(editor);
161+
useShortcuts(editor);
156162
useUploadStatus(editor);
157163

158164
useEffect(() => {

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
@@ -44,6 +44,7 @@ const inputStyle = css`
4444
`;
4545

4646
type SearchPageProps = {
47+
trigger: string;
4748
updateInlineContent: (
4849
update: PartialCustomInlineContentFromConfig<
4950
{
@@ -52,6 +53,9 @@ type SearchPageProps = {
5253
disabled: {
5354
default: boolean;
5455
};
56+
trigger: {
57+
default: string;
58+
};
5559
};
5660
content: 'styled';
5761
},
@@ -63,6 +67,7 @@ type SearchPageProps = {
6367

6468
export const SearchPage = ({
6569
contentRef,
70+
trigger,
6671
updateInlineContent,
6772
}: SearchPageProps) => {
6873
const { colorsTokens } = useCunninghamTheme();
@@ -106,7 +111,7 @@ export const SearchPage = ({
106111
tabIndex={-1} // Ensure the span is focusable
107112
>
108113
{' '}
109-
/
114+
{trigger}
110115
<Box
111116
as="input"
112117
$padding={{ left: '3px' }}
@@ -128,6 +133,7 @@ export const SearchPage = ({
128133
type: 'interlinkingSearchInline',
129134
props: {
130135
disabled: true,
136+
trigger,
131137
},
132138
});
133139

@@ -212,6 +218,7 @@ export const SearchPage = ({
212218
type: 'interlinkingSearchInline',
213219
props: {
214220
disabled: true,
221+
trigger,
215222
},
216223
});
217224

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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './useHeadings';
22
export * from './useSaveDoc';
3+
export * from './useShortcuts';
34
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 = (
13+
export const useSaveDoc = (
1414
docId: string,
1515
yDoc: Y.Doc,
1616
canSave: boolean,
@@ -105,5 +105,3 @@ const useSaveDoc = (
105105
};
106106
}, [router.events, saveDoc]);
107107
};
108-
109-
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)