diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts index 8ae32f08..41098f25 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts @@ -761,4 +761,20 @@ test.describe('Doc Editor', () => { await verifyDocName(page, docChild2); }); + + test('it checks interlink shortcut @', async ({ page, browserName }) => { + const [randomDoc] = await createDoc(page, 'doc-interlink', browserName, 1); + + await verifyDocName(page, randomDoc); + + const editor = page.locator('.bn-block-outer').last(); + await editor.click(); + await page.keyboard.press('@'); + + await expect( + page.locator( + "span[data-inline-content-type='interlinkingSearchInline'] input", + ), + ).toBeVisible(); + }); }); diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx index 71b6c00d..6b7b77a4 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx @@ -19,8 +19,13 @@ import { Box, TextErrors } from '@/components'; import { Doc, useIsCollaborativeEditable } from '@/docs/doc-management'; import { useAuth } from '@/features/auth'; -import { useHeadings, useUploadFile, useUploadStatus } from '../hook/'; -import useSaveDoc from '../hook/useSaveDoc'; +import { + useHeadings, + useSaveDoc, + useShortcuts, + useUploadFile, + useUploadStatus, +} from '../hook'; import { useEditorStore } from '../stores'; import { cssEditor } from '../styles'; import { DocsBlockNoteEditor } from '../types'; @@ -153,6 +158,7 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => { ); useHeadings(editor); + useShortcuts(editor); useUploadStatus(editor); useEffect(() => { diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/InterlinkingSearchInlineContent.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/InterlinkingSearchInlineContent.tsx index d4a5acf1..94d56da6 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/InterlinkingSearchInlineContent.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/InterlinkingSearchInlineContent.tsx @@ -13,6 +13,10 @@ export const InterlinkingSearchInlineContent = createReactInlineContentSpec( { type: 'interlinkingSearchInline', propSchema: { + trigger: { + default: '/', + values: ['/', '@'], + }, disabled: { default: false, values: [true, false], @@ -26,7 +30,13 @@ export const InterlinkingSearchInlineContent = createReactInlineContentSpec( return null; } - return ; + return ( + + ); }, }, ); @@ -45,6 +55,7 @@ export const getInterlinkinghMenuItems = ( type: 'interlinkingSearchInline', props: { disabled: false, + trigger: '/', }, }, ]); diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/SearchPage.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/SearchPage.tsx index ce3953e8..4de6fd4c 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/SearchPage.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/SearchPage.tsx @@ -44,6 +44,7 @@ const inputStyle = css` `; type SearchPageProps = { + trigger: string; updateInlineContent: ( update: PartialCustomInlineContentFromConfig< { @@ -52,6 +53,9 @@ type SearchPageProps = { disabled: { default: boolean; }; + trigger: { + default: string; + }; }; content: 'styled'; }, @@ -63,6 +67,7 @@ type SearchPageProps = { export const SearchPage = ({ contentRef, + trigger, updateInlineContent, }: SearchPageProps) => { const { colorsTokens } = useCunninghamTheme(); @@ -106,7 +111,7 @@ export const SearchPage = ({ tabIndex={-1} // Ensure the span is focusable > {' '} - / + {trigger} ({ useRouter: jest.fn(), diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/hook/index.ts b/src/frontend/apps/impress/src/features/docs/doc-editor/hook/index.ts index 3934dfa2..95a0804b 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/hook/index.ts +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/hook/index.ts @@ -1,3 +1,4 @@ export * from './useHeadings'; export * from './useSaveDoc'; +export * from './useShortcuts'; export * from './useUploadFile'; diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useSaveDoc.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useSaveDoc.tsx index c6ca782e..57656a63 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useSaveDoc.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useSaveDoc.tsx @@ -10,7 +10,7 @@ import { toBase64 } from '../utils'; const SAVE_INTERVAL = 60000; -const useSaveDoc = ( +export const useSaveDoc = ( docId: string, yDoc: Y.Doc, canSave: boolean, @@ -105,5 +105,3 @@ const useSaveDoc = ( }; }, [router.events, saveDoc]); }; - -export default useSaveDoc; diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useShortcuts.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useShortcuts.tsx new file mode 100644 index 00000000..f2f3722f --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useShortcuts.tsx @@ -0,0 +1,39 @@ +import { useEffect } from 'react'; + +import { DocsBlockNoteEditor } from '../types'; + +export const useShortcuts = (editor: DocsBlockNoteEditor) => { + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === '@' && editor?.isFocused()) { + const selection = window.getSelection(); + const previousChar = + selection?.anchorNode?.textContent?.charAt( + selection.anchorOffset - 1, + ) || ''; + + if (![' ', ''].includes(previousChar)) { + return; + } + + event.preventDefault(); + editor.insertInlineContent([ + { + type: 'interlinkingSearchInline', + props: { + disabled: false, + trigger: '@', + }, + }, + ]); + } + }; + + // Attach the event listener to the document instead of the window + document.addEventListener('keydown', handleKeyDown); + + return () => { + document.removeEventListener('keydown', handleKeyDown); + }; + }, [editor]); +};