✨(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:
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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: '/',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './useHeadings';
|
||||
export * from './useSaveDoc';
|
||||
export * from './useShortcuts';
|
||||
export * from './useUploadFile';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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]);
|
||||
};
|
||||
Reference in New Issue
Block a user