From d4713fd69e70e5cc9c8362dc46a509f5bea82c82 Mon Sep 17 00:00:00 2001 From: cialloo Date: Wed, 22 Oct 2025 17:48:42 +0800 Subject: [PATCH] feat: add text alignment options to toolbar with corresponding styles and commands --- src/editor/plugins/ToolbarPlugin.tsx | 56 +++++++++++++++++++++++++++- src/editor/styles/editor.css | 17 +++++++++ src/editor/styles/toolbar.css | 16 ++++++++ src/editor/themes/EditorTheme.ts | 4 ++ 4 files changed, 92 insertions(+), 1 deletion(-) diff --git a/src/editor/plugins/ToolbarPlugin.tsx b/src/editor/plugins/ToolbarPlugin.tsx index 0fa420d..ffee164 100644 --- a/src/editor/plugins/ToolbarPlugin.tsx +++ b/src/editor/plugins/ToolbarPlugin.tsx @@ -7,12 +7,16 @@ import { UNDO_COMMAND, SELECTION_CHANGE_COMMAND, FORMAT_TEXT_COMMAND, + FORMAT_ELEMENT_COMMAND, COMMAND_PRIORITY_CRITICAL, $getSelection, $isRangeSelection, + $isRootOrShadowRoot, + $isElementNode, } from 'lexical'; -import type { TextFormatType } from 'lexical'; +import type { ElementFormatType, TextFormatType } from 'lexical'; import { $getSelectionStyleValueForProperty, $patchStyleText } from '@lexical/selection'; +import { $findMatchingParent } from '@lexical/utils'; import DropdownColorPicker from '../ui/DropdownColorPicker'; import '../styles/toolbar.css'; @@ -30,6 +34,7 @@ export default function ToolbarPlugin() { const [fontColor, setFontColor] = useState('#000'); const [bgColor, setBgColor] = useState('#fff'); const [fontSize, setFontSize] = useState('15px'); + const [elementFormat, setElementFormat] = useState('left'); const updateToolbar = useCallback(() => { const selection = $getSelection(); @@ -51,6 +56,21 @@ export default function ToolbarPlugin() { setFontSize( $getSelectionStyleValueForProperty(selection, 'font-size', '15px') ); + + // Update element format (alignment) + const node = selection.anchor.getNode(); + const element = + node.getKey() === 'root' + ? node + : $findMatchingParent(node, (e) => { + const parent = e.getParent(); + return parent !== null && $isRootOrShadowRoot(parent); + }); + + if (element !== null && $isElementNode(element)) { + const formatType = element.getFormatType(); + setElementFormat(formatType || 'left'); + } } }, [editor]); @@ -225,6 +245,40 @@ export default function ToolbarPlugin() { onChange={onBgColorSelect} title="bg color" /> +
+ + + + +
); } diff --git a/src/editor/styles/editor.css b/src/editor/styles/editor.css index c83edf1..223c27e 100644 --- a/src/editor/styles/editor.css +++ b/src/editor/styles/editor.css @@ -334,3 +334,20 @@ left: -6px; cursor: nw-resize; } + +/* Text Alignment */ +.editor-text-left { + text-align: left; +} + +.editor-text-center { + text-align: center; +} + +.editor-text-right { + text-align: right; +} + +.editor-text-justify { + text-align: justify; +} diff --git a/src/editor/styles/toolbar.css b/src/editor/styles/toolbar.css index 5b2dabb..2ed63a4 100644 --- a/src/editor/styles/toolbar.css +++ b/src/editor/styles/toolbar.css @@ -128,3 +128,19 @@ i.icon.bg-color { width: 18px; background-size: contain; } + +i.format.left-align { + background-image: url('data:image/svg+xml;utf8,'); +} + +i.format.center-align { + background-image: url('data:image/svg+xml;utf8,'); +} + +i.format.right-align { + background-image: url('data:image/svg+xml;utf8,'); +} + +i.format.justify-align { + background-image: url('data:image/svg+xml;utf8,'); +} diff --git a/src/editor/themes/EditorTheme.ts b/src/editor/themes/EditorTheme.ts index 278497e..7b4b50f 100644 --- a/src/editor/themes/EditorTheme.ts +++ b/src/editor/themes/EditorTheme.ts @@ -27,6 +27,10 @@ const theme: EditorThemeClasses = { underline: 'editor-text-underline', strikethrough: 'editor-text-strikethrough', code: 'editor-text-code', + left: 'editor-text-left', + center: 'editor-text-center', + right: 'editor-text-right', + justify: 'editor-text-justify', }, code: 'editor-code', codeHighlight: {},