import TextSizeIcon from '@mui/icons-material/TextFields'; import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; import { $createHeadingNode, $isHeadingNode, type HeadingTagType } from '@lexical/rich-text'; import { $setBlocksType } from '@lexical/selection'; import { $findMatchingParent, mergeRegister } from '@lexical/utils'; import { $createParagraphNode, $getSelection, $isRangeSelection, $isRootOrShadowRoot, COMMAND_PRIORITY_CRITICAL, SELECTION_CHANGE_COMMAND } from 'lexical'; import { always, cond, equals, isNil, T } from 'ramda'; import { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Menu } from '../../../components'; import { useStyles } from './ToolbarPlugin.styles'; interface Props { disabled: boolean; } const blockTypeToBlockName = { h3: 'Huge', h5: 'Large', h6: 'Small', paragraph: 'Normal' }; const blockTypes = ['h3', 'h5', 'paragraph', 'h6']; const blockTypeOptions = blockTypes.map((blockType) => ({ id: blockType, name: blockTypeToBlockName[blockType as keyof typeof blockTypeToBlockName] })); const BlockButtons = ({ disabled }: Props): JSX.Element => { const { classes } = useStyles(); const { t } = useTranslation(); const [blockType, setBlockType] = useState('paragraph'); const [editor] = useLexicalComposerContext(); const formatParagraph = (): void => { editor.update(() => { const selection = $getSelection(); $setBlocksType(selection, () => $createParagraphNode()); }); }; const formatHeading = (headingSize: HeadingTagType): void => { if (blockType !== headingSize) { editor.update(() => { const selection = $getSelection(); if ($isRangeSelection(selection)) { $setBlocksType(selection, () => $createHeadingNode(headingSize)); } }); } }; const changeBlockType = (newBlockType: string): void => { const formatFunction = cond, (value?: unknown) => void>([ [equals('h3'), always(() => formatHeading('h3'))], [equals('h5'), always(() => formatHeading('h5'))], [equals('h6'), always(() => formatHeading('h6'))], [T, always(formatParagraph)] ])(newBlockType || ''); formatFunction(); }; const updateToolbar = useCallback(() => { const selection = $getSelection(); // @ts-expect-error - suppressing pre-existing type mismatch const anchorNode = selection?.anchor.getNode(); const element = equals(anchorNode?.getKey(), 'root') ? anchorNode : $findMatchingParent(anchorNode, (e) => { const parent = e.getParent(); return parent !== null && $isRootOrShadowRoot(parent); }) || anchorNode?.getTopLevelElementOrThrow(); const elementKey = element?.getKey(); const elementDOM = editor.getElementByKey(elementKey); if (isNil(elementDOM)) { return; } const type = $isHeadingNode(element) ? element.getTag() : element.getType(); if (type in blockTypeToBlockName) { setBlockType(type as keyof typeof blockTypeToBlockName); } }, [editor]); useEffect(() => { return editor.registerCommand( SELECTION_CHANGE_COMMAND, () => { updateToolbar(); return false; }, COMMAND_PRIORITY_CRITICAL ); }, [editor, updateToolbar]); useEffect(() => { return mergeRegister( editor.registerUpdateListener(({ editorState }) => { editorState.read(() => { updateToolbar(); }); }) ); }, [editor, updateToolbar]); return ( {blockTypeOptions.map(({ id, name }) => ( changeBlockType(id)} > {t(name)} ))} ); }; export default BlockButtons;