(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 {
BlockNoteSchema,
Dictionary,
defaultBlockSpecs,
locales,
withPageBreak,
} from '@blocknote/core';
@@ -26,8 +27,16 @@ import { randomColor } from '../utils';
import { BlockNoteSuggestionMenu } from './BlockNoteSuggestionMenu';
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 {
doc: Doc;
@@ -141,8 +150,8 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
editable={!readOnly}
theme="light"
>
<BlockNoteToolbar />
<BlockNoteSuggestionMenu />
<BlockNoteToolbar />
</BlockNoteView>
</Box>
);

View File

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

View File

@@ -2,19 +2,28 @@ import '@blocknote/mantine/style.css';
import {
FormattingToolbar,
FormattingToolbarController,
FormattingToolbarProps,
blockTypeSelectItems,
getFormattingToolbarItems,
useDictionary,
} from '@blocknote/react';
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { AIGroupButton } from './AIButton';
import { MarkdownButton } from './MarkdownButton';
import { getQuoteFormattingToolbarItems } from './custom-blocks';
export const BlockNoteToolbar = () => {
const dict = useDictionary();
const { t } = useTranslation();
const formattingToolbar = useCallback(
({ blockTypeSelectItems }: FormattingToolbarProps) => (
() => (
<FormattingToolbar>
{getFormattingToolbarItems(blockTypeSelectItems)}
{getFormattingToolbarItems([
...blockTypeSelectItems(dict),
getQuoteFormattingToolbarItems(t),
])}
{/* Extra button to do some AI powered actions */}
<AIGroupButton key="AIButton" />
@@ -23,7 +32,7 @@ export const BlockNoteToolbar = () => {
<MarkdownButton key="customButton" />
</FormattingToolbar>
),
[],
[dict, t],
);
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 {
height: 100%;
.bn-side-menu[data-block-type='quote'] {
height: 46px;
}
.collaboration-cursor-custom__base {
position: relative;
}