(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
This commit is contained in:
Anthony LC
2025-04-27 22:21:20 +02:00
parent 2a7c0ef800
commit e7709badbb
8 changed files with 86 additions and 8 deletions

View File

@@ -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();
});
});

View File

@@ -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(() => {

View File

@@ -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 <SearchPage {...props} contentRef={props.contentRef} />;
return (
<SearchPage
{...props}
trigger={props.inlineContent.props.trigger}
contentRef={props.contentRef}
/>
);
},
},
);
@@ -45,6 +55,7 @@ export const getInterlinkinghMenuItems = (
type: 'interlinkingSearchInline',
props: {
disabled: false,
trigger: '/',
},
},
]);

View File

@@ -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}
<Box
as="input"
$padding={{ left: '3px' }}
@@ -128,6 +133,7 @@ export const SearchPage = ({
type: 'interlinkingSearchInline',
props: {
disabled: true,
trigger,
},
});
@@ -212,6 +218,7 @@ export const SearchPage = ({
type: 'interlinkingSearchInline',
props: {
disabled: true,
trigger,
},
});

View File

@@ -5,7 +5,7 @@ import * as Y from 'yjs';
import { AppWrapper } from '@/tests/utils';
import useSaveDoc from '../useSaveDoc';
import { useSaveDoc } from '../useSaveDoc';
jest.mock('next/router', () => ({
useRouter: jest.fn(),

View File

@@ -1,3 +1,4 @@
export * from './useHeadings';
export * from './useSaveDoc';
export * from './useShortcuts';
export * from './useUploadFile';

View File

@@ -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;

View File

@@ -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]);
};