Skip to content

Commit 63c1dfd

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 47a17a0 commit 63c1dfd

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
@@ -751,4 +751,20 @@ test.describe('Doc Editor', () => {
751751

752752
await verifyDocName(page, docChild1);
753753
});
754+
755+
test('it checks interlink shortcut @', async ({ page, browserName }) => {
756+
const [randomDoc] = await createDoc(page, 'doc-interlink', browserName, 1);
757+
758+
await verifyDocName(page, randomDoc);
759+
760+
const editor = page.locator('.bn-block-outer').last();
761+
await editor.click();
762+
await page.keyboard.press('@');
763+
764+
await expect(
765+
page.locator(
766+
"span[data-inline-content-type='interlinkingSearchInline'] input",
767+
),
768+
).toBeVisible();
769+
});
754770
});

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';
@@ -140,6 +145,7 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
140145
);
141146

142147
useHeadings(editor);
148+
useShortcuts(editor);
143149
useUploadStatus(editor);
144150

145151
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
@@ -40,6 +40,7 @@ const inputStyle = css`
4040
`;
4141

4242
type SearchPageProps = {
43+
trigger: string;
4344
updateInlineContent: (
4445
update: PartialCustomInlineContentFromConfig<
4546
{
@@ -48,6 +49,9 @@ type SearchPageProps = {
4849
disabled: {
4950
default: boolean;
5051
};
52+
trigger: {
53+
default: string;
54+
};
5155
};
5256
content: 'styled';
5357
},
@@ -59,6 +63,7 @@ type SearchPageProps = {
5963

6064
export const SearchPage = ({
6165
contentRef,
66+
trigger,
6267
updateInlineContent,
6368
}: SearchPageProps) => {
6469
const { colorsTokens } = useCunninghamTheme();
@@ -101,7 +106,7 @@ export const SearchPage = ({
101106
tabIndex={-1} // Ensure the span is focusable
102107
>
103108
{' '}
104-
/
109+
{trigger}
105110
<Box
106111
as="input"
107112
$padding={{ left: '3px' }}
@@ -120,6 +125,7 @@ export const SearchPage = ({
120125
type: 'interlinkingSearchInline',
121126
props: {
122127
disabled: true,
128+
trigger,
123129
},
124130
});
125131

@@ -180,6 +186,7 @@ export const SearchPage = ({
180186
type: 'interlinkingSearchInline',
181187
props: {
182188
disabled: true,
189+
trigger,
183190
},
184191
});
185192

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)