(frontend) add quote blocks to the editor

Add a custom block to quote in the editor.
This commit is contained in:
Anthony LC
2025-02-18 14:30:40 +01:00
committed by Anthony LC
parent ef2127585c
commit 7f6ffa0123
6 changed files with 116 additions and 9 deletions

View File

@@ -1,6 +1,7 @@
import { import {
BlockNoteSchema, BlockNoteSchema,
Dictionary, Dictionary,
defaultBlockSpecs,
locales, locales,
withPageBreak, withPageBreak,
} from '@blocknote/core'; } from '@blocknote/core';
@@ -26,8 +27,16 @@ import { randomColor } from '../utils';
import { BlockNoteSuggestionMenu } from './BlockNoteSuggestionMenu'; import { BlockNoteSuggestionMenu } from './BlockNoteSuggestionMenu';
import { BlockNoteToolbar } from './BlockNoteToolbar'; import { BlockNoteToolbar } from './BlockNoteToolbar';
import { QuoteBlock } from './custom-blocks';
export const blockNoteSchema = withPageBreak(BlockNoteSchema.create()); export const blockNoteSchema = withPageBreak(
BlockNoteSchema.create({
blockSpecs: {
...defaultBlockSpecs,
quote: QuoteBlock,
},
}),
);
interface BlockNoteEditorProps { interface BlockNoteEditorProps {
doc: Doc; doc: Doc;
@@ -141,8 +150,8 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
editable={!readOnly} editable={!readOnly}
theme="light" theme="light"
> >
<BlockNoteToolbar />
<BlockNoteSuggestionMenu /> <BlockNoteSuggestionMenu />
<BlockNoteToolbar />
</BlockNoteView> </BlockNoteView>
</Box> </Box>
); );

View File

@@ -5,13 +5,19 @@ import {
getDefaultReactSlashMenuItems, getDefaultReactSlashMenuItems,
getPageBreakReactSlashMenuItems, getPageBreakReactSlashMenuItems,
useBlockNoteEditor, useBlockNoteEditor,
useDictionary,
} from '@blocknote/react'; } from '@blocknote/react';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { DocsBlockNoteEditor } from '../types'; import { DocsBlockSchema } from '../types';
import { getQuoteReactSlashMenuItems } from './custom-blocks';
export const BlockNoteSuggestionMenu = () => { export const BlockNoteSuggestionMenu = () => {
const editor = useBlockNoteEditor() as DocsBlockNoteEditor; const editor = useBlockNoteEditor<DocsBlockSchema>();
const { t } = useTranslation();
const basicBlocksName = useDictionary().slash_menu.page_break.group;
const getSlashMenuItems = useMemo(() => { const getSlashMenuItems = useMemo(() => {
return async (query: string) => return async (query: string) =>
@@ -20,11 +26,12 @@ export const BlockNoteSuggestionMenu = () => {
combineByGroup( combineByGroup(
getDefaultReactSlashMenuItems(editor), getDefaultReactSlashMenuItems(editor),
getPageBreakReactSlashMenuItems(editor), getPageBreakReactSlashMenuItems(editor),
getQuoteReactSlashMenuItems(editor, t, basicBlocksName),
), ),
query, query,
), ),
); );
}, [editor]); }, [basicBlocksName, editor, t]);
return ( return (
<SuggestionMenuController <SuggestionMenuController

View File

@@ -2,19 +2,28 @@ import '@blocknote/mantine/style.css';
import { import {
FormattingToolbar, FormattingToolbar,
FormattingToolbarController, FormattingToolbarController,
FormattingToolbarProps, blockTypeSelectItems,
getFormattingToolbarItems, getFormattingToolbarItems,
useDictionary,
} from '@blocknote/react'; } from '@blocknote/react';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { AIGroupButton } from './AIButton'; import { AIGroupButton } from './AIButton';
import { MarkdownButton } from './MarkdownButton'; import { MarkdownButton } from './MarkdownButton';
import { getQuoteFormattingToolbarItems } from './custom-blocks';
export const BlockNoteToolbar = () => { export const BlockNoteToolbar = () => {
const dict = useDictionary();
const { t } = useTranslation();
const formattingToolbar = useCallback( const formattingToolbar = useCallback(
({ blockTypeSelectItems }: FormattingToolbarProps) => ( () => (
<FormattingToolbar> <FormattingToolbar>
{getFormattingToolbarItems(blockTypeSelectItems)} {getFormattingToolbarItems([
...blockTypeSelectItems(dict),
getQuoteFormattingToolbarItems(t),
])}
{/* Extra button to do some AI powered actions */} {/* Extra button to do some AI powered actions */}
<AIGroupButton key="AIButton" /> <AIGroupButton key="AIButton" />
@@ -23,7 +32,7 @@ export const BlockNoteToolbar = () => {
<MarkdownButton key="customButton" /> <MarkdownButton key="customButton" />
</FormattingToolbar> </FormattingToolbar>
), ),
[], [dict, t],
); );
return <FormattingToolbarController formattingToolbar={formattingToolbar} />; return <FormattingToolbarController formattingToolbar={formattingToolbar} />;

View File

@@ -0,0 +1,77 @@
import { defaultProps, insertOrUpdateBlock } from '@blocknote/core';
import { BlockTypeSelectItem, createReactBlockSpec } from '@blocknote/react';
import { TFunction } from 'i18next';
import React from 'react';
import { Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { DocsBlockNoteEditor } from '../../types';
export const QuoteBlock = createReactBlockSpec(
{
type: 'quote',
propSchema: {
textAlignment: defaultProps.textAlignment,
textColor: defaultProps.textColor,
},
content: 'inline',
},
{
render: (props) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const { colorsTokens } = useCunninghamTheme();
return (
<Text
className="inline-content"
$margin="0 0 1rem 0"
$padding="0.5rem 1rem"
$variation="600"
style={{
borderLeft: `4px solid ${colorsTokens()['greyscale-300']}`,
fontStyle: 'italic',
flexGrow: 1,
}}
ref={props.contentRef}
/>
);
},
},
);
export const getQuoteReactSlashMenuItems = (
editor: DocsBlockNoteEditor,
t: TFunction<'translation', undefined>,
group: string,
) => [
{
title: t('Quote'),
onItemClick: () => {
insertOrUpdateBlock(editor, {
type: 'quote',
});
},
aliases: ['quote', 'blockquote', 'citation'],
group,
icon: (
<Text $isMaterialIcon $size="18px">
format_quote
</Text>
),
subtext: t('Add a quote block'),
},
];
export const getQuoteFormattingToolbarItems = (
t: TFunction<'translation', undefined>,
): BlockTypeSelectItem => ({
name: t('Quote'),
type: 'quote',
icon: () => (
<Text $isMaterialIcon $size="16px">
format_quote
</Text>
),
isSelected: (block) => block.type === 'quote',
});

View File

@@ -0,0 +1 @@
export * from './QuoteBlock';

View File

@@ -6,6 +6,10 @@ export const cssEditor = (readonly: boolean) => css`
& .ProseMirror { & .ProseMirror {
height: 100%; height: 100%;
.bn-side-menu[data-block-type='quote'] {
height: 46px;
}
.collaboration-cursor-custom__base { .collaboration-cursor-custom__base {
position: relative; position: relative;
} }