From 111a313d5125bd890d7df46929802f4621388c95 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 18 Aug 2024 16:51:08 +0100 Subject: [PATCH] Lexical: Added custom alignment handling for blocks To align with pre-existing use of alignment classes. --- resources/js/wysiwyg/nodes/_common.ts | 66 +++++++++++++++++++ resources/js/wysiwyg/nodes/callout.ts | 40 +++++++---- resources/js/wysiwyg/nodes/custom-heading.ts | 46 ++++++++----- .../js/wysiwyg/nodes/custom-paragraph.ts | 48 +++++++++----- resources/js/wysiwyg/nodes/custom-quote.ts | 42 ++++++++---- .../js/wysiwyg/nodes/custom-table-cell.ts | 26 +++++++- resources/js/wysiwyg/nodes/custom-table.ts | 35 ++++++---- resources/js/wysiwyg/nodes/index.ts | 8 +-- resources/js/wysiwyg/todo.md | 2 +- .../wysiwyg/ui/defaults/buttons/alignments.ts | 10 ++- resources/js/wysiwyg/utils/nodes.ts | 5 ++ resources/sass/_content.scss | 3 + 12 files changed, 250 insertions(+), 81 deletions(-) create mode 100644 resources/js/wysiwyg/nodes/_common.ts diff --git a/resources/js/wysiwyg/nodes/_common.ts b/resources/js/wysiwyg/nodes/_common.ts new file mode 100644 index 000000000..cc45dc910 --- /dev/null +++ b/resources/js/wysiwyg/nodes/_common.ts @@ -0,0 +1,66 @@ +import {LexicalNode, Spread} from "lexical"; +import type {SerializedElementNode} from "lexical/nodes/LexicalElementNode"; + +export type CommonBlockAlignment = 'left' | 'right' | 'center' | 'justify' | ''; +const validAlignments: CommonBlockAlignment[] = ['left', 'right', 'center', 'justify']; + +export type SerializedCommonBlockNode = Spread<{ + id: string; + alignment: CommonBlockAlignment; +}, SerializedElementNode> + +export interface NodeHasAlignment { + readonly __alignment: CommonBlockAlignment; + setAlignment(alignment: CommonBlockAlignment): void; + getAlignment(): CommonBlockAlignment; +} + +export interface NodeHasId { + readonly __id: string; + setId(id: string): void; + getId(): string; +} + +interface CommonBlockInterface extends NodeHasId, NodeHasAlignment {} + +export function extractAlignmentFromElement(element: HTMLElement): CommonBlockAlignment { + const textAlignStyle: string = element.style.textAlign || ''; + if (validAlignments.includes(textAlignStyle as CommonBlockAlignment)) { + return textAlignStyle as CommonBlockAlignment; + } + + if (element.classList.contains('align-left')) { + return 'left'; + } else if (element.classList.contains('align-right')) { + return 'right' + } else if (element.classList.contains('align-center')) { + return 'center' + } else if (element.classList.contains('align-justify')) { + return 'justify' + } + + return ''; +} + +export function setCommonBlockPropsFromElement(element: HTMLElement, node: CommonBlockInterface): void { + if (element.id) { + node.setId(element.id); + } + + node.setAlignment(extractAlignmentFromElement(element)); +} + +export function commonPropertiesDifferent(nodeA: CommonBlockInterface, nodeB: CommonBlockInterface): boolean { + return nodeA.__id !== nodeB.__id || + nodeA.__alignment !== nodeB.__alignment; +} + +export function updateElementWithCommonBlockProps(element: HTMLElement, node: CommonBlockInterface): void { + if (node.__id) { + element.setAttribute('id', node.__id); + } + + if (node.__alignment) { + element.classList.add('align-' + node.__alignment); + } +} \ No newline at end of file diff --git a/resources/js/wysiwyg/nodes/callout.ts b/resources/js/wysiwyg/nodes/callout.ts index b720b5c43..8018190c8 100644 --- a/resources/js/wysiwyg/nodes/callout.ts +++ b/resources/js/wysiwyg/nodes/callout.ts @@ -5,22 +5,27 @@ import { ElementNode, LexicalEditor, LexicalNode, - ParagraphNode, SerializedElementNode, Spread + ParagraphNode, Spread } from 'lexical'; import type {EditorConfig} from "lexical/LexicalEditor"; import type {RangeSelection} from "lexical/LexicalSelection"; -import {el} from "../utils/dom"; +import { + CommonBlockAlignment, commonPropertiesDifferent, + SerializedCommonBlockNode, + setCommonBlockPropsFromElement, + updateElementWithCommonBlockProps +} from "./_common"; export type CalloutCategory = 'info' | 'danger' | 'warning' | 'success'; export type SerializedCalloutNode = Spread<{ category: CalloutCategory; - id: string; -}, SerializedElementNode> +}, SerializedCommonBlockNode> export class CalloutNode extends ElementNode { __id: string = ''; __category: CalloutCategory = 'info'; + __alignment: CommonBlockAlignment = ''; static getType() { return 'callout'; @@ -57,19 +62,26 @@ export class CalloutNode extends ElementNode { return self.__id; } + setAlignment(alignment: CommonBlockAlignment) { + const self = this.getWritable(); + self.__alignment = alignment; + } + + getAlignment(): CommonBlockAlignment { + const self = this.getLatest(); + return self.__alignment; + } + createDOM(_config: EditorConfig, _editor: LexicalEditor) { const element = document.createElement('p'); element.classList.add('callout', this.__category || ''); - if (this.__id) { - element.setAttribute('id', this.__id); - } + updateElementWithCommonBlockProps(element, this); return element; } - updateDOM(prevNode: unknown, dom: HTMLElement) { - // Returning false tells Lexical that this node does not need its - // DOM element replacing with a new copy from createDOM. - return false; + updateDOM(prevNode: CalloutNode): boolean { + return prevNode.__category !== this.__category || + commonPropertiesDifferent(prevNode, this); } insertNewAfter(selection: RangeSelection, restoreSelection?: boolean): CalloutNode|ParagraphNode { @@ -106,9 +118,7 @@ export class CalloutNode extends ElementNode { } const node = new CalloutNode(category); - if (element.id) { - node.setId(element.id); - } + setCommonBlockPropsFromElement(element, node); return { node, @@ -129,12 +139,14 @@ export class CalloutNode extends ElementNode { version: 1, category: this.__category, id: this.__id, + alignment: this.__alignment, }; } static importJSON(serializedNode: SerializedCalloutNode): CalloutNode { const node = $createCalloutNode(serializedNode.category); node.setId(serializedNode.id); + node.setAlignment(serializedNode.alignment); return node; } diff --git a/resources/js/wysiwyg/nodes/custom-heading.ts b/resources/js/wysiwyg/nodes/custom-heading.ts index f069ff160..885622ad3 100644 --- a/resources/js/wysiwyg/nodes/custom-heading.ts +++ b/resources/js/wysiwyg/nodes/custom-heading.ts @@ -1,19 +1,24 @@ import { DOMConversionMap, - DOMConversionOutput, ElementFormatType, + DOMConversionOutput, LexicalNode, Spread } from "lexical"; import {EditorConfig} from "lexical/LexicalEditor"; import {HeadingNode, HeadingTagType, SerializedHeadingNode} from "@lexical/rich-text"; +import { + CommonBlockAlignment, commonPropertiesDifferent, + SerializedCommonBlockNode, + setCommonBlockPropsFromElement, + updateElementWithCommonBlockProps +} from "./_common"; -export type SerializedCustomHeadingNode = Spread<{ - id: string; -}, SerializedHeadingNode> +export type SerializedCustomHeadingNode = Spread export class CustomHeadingNode extends HeadingNode { __id: string = ''; + __alignment: CommonBlockAlignment = ''; static getType() { return 'custom-heading'; @@ -29,31 +34,47 @@ export class CustomHeadingNode extends HeadingNode { return self.__id; } + setAlignment(alignment: CommonBlockAlignment) { + const self = this.getWritable(); + self.__alignment = alignment; + } + + getAlignment(): CommonBlockAlignment { + const self = this.getLatest(); + return self.__alignment; + } + static clone(node: CustomHeadingNode) { - return new CustomHeadingNode(node.__tag, node.__key); + const newNode = new CustomHeadingNode(node.__tag, node.__key); + newNode.__alignment = node.__alignment; + return newNode; } createDOM(config: EditorConfig): HTMLElement { const dom = super.createDOM(config); - if (this.__id) { - dom.setAttribute('id', this.__id); - } - + updateElementWithCommonBlockProps(dom, this); return dom; } + updateDOM(prevNode: CustomHeadingNode, dom: HTMLElement): boolean { + return super.updateDOM(prevNode, dom) + || commonPropertiesDifferent(prevNode, this); + } + exportJSON(): SerializedCustomHeadingNode { return { ...super.exportJSON(), type: 'custom-heading', version: 1, id: this.__id, + alignment: this.__alignment, }; } static importJSON(serializedNode: SerializedCustomHeadingNode): CustomHeadingNode { const node = $createCustomHeadingNode(serializedNode.tag); node.setId(serializedNode.id); + node.setAlignment(serializedNode.alignment); return node; } @@ -99,12 +120,7 @@ function $convertHeadingElement(element: HTMLElement): DOMConversionOutput { nodeName === 'h6' ) { node = $createCustomHeadingNode(nodeName); - if (element.style !== null) { - node.setFormat(element.style.textAlign as ElementFormatType); - } - if (element.id) { - node.setId(element.id); - } + setCommonBlockPropsFromElement(element, node); } return {node}; } diff --git a/resources/js/wysiwyg/nodes/custom-paragraph.ts b/resources/js/wysiwyg/nodes/custom-paragraph.ts index cb936a559..663f32dfc 100644 --- a/resources/js/wysiwyg/nodes/custom-paragraph.ts +++ b/resources/js/wysiwyg/nodes/custom-paragraph.ts @@ -1,21 +1,23 @@ import { DOMConversion, DOMConversionMap, - DOMConversionOutput, ElementFormatType, + DOMConversionOutput, LexicalNode, - ParagraphNode, - SerializedParagraphNode, - Spread + ParagraphNode, SerializedParagraphNode, Spread, } from "lexical"; import {EditorConfig} from "lexical/LexicalEditor"; +import { + CommonBlockAlignment, commonPropertiesDifferent, + SerializedCommonBlockNode, + setCommonBlockPropsFromElement, + updateElementWithCommonBlockProps +} from "./_common"; - -export type SerializedCustomParagraphNode = Spread<{ - id: string; -}, SerializedParagraphNode> +export type SerializedCustomParagraphNode = Spread export class CustomParagraphNode extends ParagraphNode { __id: string = ''; + __alignment: CommonBlockAlignment = ''; static getType() { return 'custom-paragraph'; @@ -31,33 +33,48 @@ export class CustomParagraphNode extends ParagraphNode { return self.__id; } + setAlignment(alignment: CommonBlockAlignment) { + const self = this.getWritable(); + self.__alignment = alignment; + } + + getAlignment(): CommonBlockAlignment { + const self = this.getLatest(); + return self.__alignment; + } + static clone(node: CustomParagraphNode): CustomParagraphNode { const newNode = new CustomParagraphNode(node.__key); newNode.__id = node.__id; + newNode.__alignment = node.__alignment; return newNode; } createDOM(config: EditorConfig): HTMLElement { const dom = super.createDOM(config); - if (this.__id) { - dom.setAttribute('id', this.__id); - } - + updateElementWithCommonBlockProps(dom, this); return dom; } + updateDOM(prevNode: CustomParagraphNode, dom: HTMLElement, config: EditorConfig): boolean { + return super.updateDOM(prevNode, dom, config) + || commonPropertiesDifferent(prevNode, this); + } + exportJSON(): SerializedCustomParagraphNode { return { ...super.exportJSON(), type: 'custom-paragraph', version: 1, id: this.__id, + alignment: this.__alignment, }; } static importJSON(serializedNode: SerializedCustomParagraphNode): CustomParagraphNode { const node = $createCustomParagraphNode(); node.setId(serializedNode.id); + node.setAlignment(serializedNode.alignment); return node; } @@ -67,17 +84,14 @@ export class CustomParagraphNode extends ParagraphNode { return { conversion: (element: HTMLElement): DOMConversionOutput|null => { const node = $createCustomParagraphNode(); - if (element.style) { - node.setFormat(element.style.textAlign as ElementFormatType); + if (element.style.textIndent) { const indent = parseInt(element.style.textIndent, 10) / 20; if (indent > 0) { node.setIndent(indent); } } - if (element.id) { - node.setId(element.id); - } + setCommonBlockPropsFromElement(element, node); return {node}; }, diff --git a/resources/js/wysiwyg/nodes/custom-quote.ts b/resources/js/wysiwyg/nodes/custom-quote.ts index 58c62f769..cee289dbe 100644 --- a/resources/js/wysiwyg/nodes/custom-quote.ts +++ b/resources/js/wysiwyg/nodes/custom-quote.ts @@ -1,19 +1,24 @@ import { DOMConversionMap, - DOMConversionOutput, ElementFormatType, + DOMConversionOutput, LexicalNode, Spread } from "lexical"; import {EditorConfig} from "lexical/LexicalEditor"; import {QuoteNode, SerializedQuoteNode} from "@lexical/rich-text"; +import { + CommonBlockAlignment, commonPropertiesDifferent, + SerializedCommonBlockNode, + setCommonBlockPropsFromElement, + updateElementWithCommonBlockProps +} from "./_common"; -export type SerializedCustomQuoteNode = Spread<{ - id: string; -}, SerializedQuoteNode> +export type SerializedCustomQuoteNode = Spread export class CustomQuoteNode extends QuoteNode { __id: string = ''; + __alignment: CommonBlockAlignment = ''; static getType() { return 'custom-quote'; @@ -29,33 +34,47 @@ export class CustomQuoteNode extends QuoteNode { return self.__id; } + setAlignment(alignment: CommonBlockAlignment) { + const self = this.getWritable(); + self.__alignment = alignment; + } + + getAlignment(): CommonBlockAlignment { + const self = this.getLatest(); + return self.__alignment; + } + static clone(node: CustomQuoteNode) { const newNode = new CustomQuoteNode(node.__key); newNode.__id = node.__id; + newNode.__alignment = node.__alignment; return newNode; } createDOM(config: EditorConfig): HTMLElement { const dom = super.createDOM(config); - if (this.__id) { - dom.setAttribute('id', this.__id); - } - + updateElementWithCommonBlockProps(dom, this); return dom; } + updateDOM(prevNode: CustomQuoteNode): boolean { + return commonPropertiesDifferent(prevNode, this); + } + exportJSON(): SerializedCustomQuoteNode { return { ...super.exportJSON(), type: 'custom-quote', version: 1, id: this.__id, + alignment: this.__alignment, }; } static importJSON(serializedNode: SerializedCustomQuoteNode): CustomQuoteNode { const node = $createCustomQuoteNode(); node.setId(serializedNode.id); + node.setAlignment(serializedNode.alignment); return node; } @@ -71,12 +90,7 @@ export class CustomQuoteNode extends QuoteNode { function $convertBlockquoteElement(element: HTMLElement): DOMConversionOutput { const node = $createCustomQuoteNode(); - if (element.style !== null) { - node.setFormat(element.style.textAlign as ElementFormatType); - } - if (element.id) { - node.setId(element.id); - } + setCommonBlockPropsFromElement(element, node); return {node}; } diff --git a/resources/js/wysiwyg/nodes/custom-table-cell.ts b/resources/js/wysiwyg/nodes/custom-table-cell.ts index c8fe58c77..15c305dcb 100644 --- a/resources/js/wysiwyg/nodes/custom-table-cell.ts +++ b/resources/js/wysiwyg/nodes/custom-table-cell.ts @@ -21,13 +21,16 @@ import { } from "@lexical/table"; import {TableCellHeaderState} from "@lexical/table/LexicalTableCellNode"; import {extractStyleMapFromElement, StyleMap} from "../utils/dom"; +import {CommonBlockAlignment, extractAlignmentFromElement} from "./_common"; export type SerializedCustomTableCellNode = Spread<{ - styles: Record, + styles: Record; + alignment: CommonBlockAlignment; }, SerializedTableCellNode> export class CustomTableCellNode extends TableCellNode { __styles: StyleMap = new Map; + __alignment: CommonBlockAlignment = ''; static getType(): string { return 'custom-table-cell'; @@ -42,6 +45,7 @@ export class CustomTableCellNode extends TableCellNode { ); cellNode.__rowSpan = node.__rowSpan; cellNode.__styles = new Map(node.__styles); + cellNode.__alignment = node.__alignment; return cellNode; } @@ -60,6 +64,16 @@ export class CustomTableCellNode extends TableCellNode { self.__styles = new Map(styles); } + setAlignment(alignment: CommonBlockAlignment) { + const self = this.getWritable(); + self.__alignment = alignment; + } + + getAlignment(): CommonBlockAlignment { + const self = this.getLatest(); + return self.__alignment; + } + updateTag(tag: string): void { const isHeader = tag.toLowerCase() === 'th'; const state = isHeader ? TableCellHeaderStates.ROW : TableCellHeaderStates.NO_STATUS; @@ -74,12 +88,17 @@ export class CustomTableCellNode extends TableCellNode { element.style.setProperty(name, value); } + if (this.__alignment) { + element.classList.add('align-' + this.__alignment); + } + return element; } updateDOM(prevNode: CustomTableCellNode): boolean { return super.updateDOM(prevNode) - || this.__styles !== prevNode.__styles; + || this.__styles !== prevNode.__styles + || this.__alignment !== prevNode.__alignment; } static importDOM(): DOMConversionMap | null { @@ -110,6 +129,7 @@ export class CustomTableCellNode extends TableCellNode { ); node.setStyles(new Map(Object.entries(serializedNode.styles))); + node.setAlignment(serializedNode.alignment); return node; } @@ -119,6 +139,7 @@ export class CustomTableCellNode extends TableCellNode { ...super.exportJSON(), type: 'custom-table-cell', styles: Object.fromEntries(this.__styles), + alignment: this.__alignment, }; } } @@ -128,6 +149,7 @@ function $convertCustomTableCellNodeElement(domNode: Node): DOMConversionOutput if (domNode instanceof HTMLElement && output.node instanceof CustomTableCellNode) { output.node.setStyles(extractStyleMapFromElement(domNode)); + output.node.setAlignment(extractAlignmentFromElement(domNode)); } return output; diff --git a/resources/js/wysiwyg/nodes/custom-table.ts b/resources/js/wysiwyg/nodes/custom-table.ts index 1d95b7896..b699763d9 100644 --- a/resources/js/wysiwyg/nodes/custom-table.ts +++ b/resources/js/wysiwyg/nodes/custom-table.ts @@ -4,17 +4,23 @@ import {EditorConfig} from "lexical/LexicalEditor"; import {el, extractStyleMapFromElement, StyleMap} from "../utils/dom"; import {getTableColumnWidths} from "../utils/tables"; +import { + CommonBlockAlignment, + SerializedCommonBlockNode, + setCommonBlockPropsFromElement, + updateElementWithCommonBlockProps +} from "./_common"; -export type SerializedCustomTableNode = Spread<{ - id: string; +export type SerializedCustomTableNode = Spread, -}, SerializedTableNode> +}, SerializedTableNode>, SerializedCommonBlockNode> export class CustomTableNode extends TableNode { __id: string = ''; __colWidths: string[] = []; __styles: StyleMap = new Map; + __alignment: CommonBlockAlignment = ''; static getType() { return 'custom-table'; @@ -30,6 +36,16 @@ export class CustomTableNode extends TableNode { return self.__id; } + setAlignment(alignment: CommonBlockAlignment) { + const self = this.getWritable(); + self.__alignment = alignment; + } + + getAlignment(): CommonBlockAlignment { + const self = this.getLatest(); + return self.__alignment; + } + setColWidths(widths: string[]) { const self = this.getWritable(); self.__colWidths = widths; @@ -55,15 +71,13 @@ export class CustomTableNode extends TableNode { newNode.__id = node.__id; newNode.__colWidths = node.__colWidths; newNode.__styles = new Map(node.__styles); + newNode.__alignment = node.__alignment; return newNode; } createDOM(config: EditorConfig): HTMLElement { const dom = super.createDOM(config); - const id = this.getId(); - if (id) { - dom.setAttribute('id', id); - } + updateElementWithCommonBlockProps(dom, this); const colWidths = this.getColWidths(); if (colWidths.length > 0) { @@ -97,6 +111,7 @@ export class CustomTableNode extends TableNode { id: this.__id, colWidths: this.__colWidths, styles: Object.fromEntries(this.__styles), + alignment: this.__alignment, }; } @@ -105,6 +120,7 @@ export class CustomTableNode extends TableNode { node.setId(serializedNode.id); node.setColWidths(serializedNode.colWidths); node.setStyles(new Map(Object.entries(serializedNode.styles))); + node.setAlignment(serializedNode.alignment); return node; } @@ -114,10 +130,7 @@ export class CustomTableNode extends TableNode { return { conversion: (element: HTMLElement): DOMConversionOutput|null => { const node = $createCustomTableNode(); - - if (element.id) { - node.setId(element.id); - } + setCommonBlockPropsFromElement(element, node); const colWidths = getTableColumnWidths(element as HTMLTableElement); node.setColWidths(colWidths); diff --git a/resources/js/wysiwyg/nodes/index.ts b/resources/js/wysiwyg/nodes/index.ts index 8cbec20da..b5483c500 100644 --- a/resources/js/wysiwyg/nodes/index.ts +++ b/resources/js/wysiwyg/nodes/index.ts @@ -35,17 +35,17 @@ export function getNodesForPageEditor(): (KlassConstructor | CustomHeadingNode, CustomQuoteNode, CustomListNode, - CustomListItemNode, + CustomListItemNode, // TODO - Alignment? CustomTableNode, CustomTableRowNode, CustomTableCellNode, - ImageNode, + ImageNode, // TODO - Alignment HorizontalRuleNode, DetailsNode, SummaryNode, CodeBlockNode, DiagramNode, - MediaNode, - CustomParagraphNode, // TODO - ID + MediaNode, // TODO - Alignment + CustomParagraphNode, LinkNode, { replace: ParagraphNode, diff --git a/resources/js/wysiwyg/todo.md b/resources/js/wysiwyg/todo.md index f3a8da404..fec38271a 100644 --- a/resources/js/wysiwyg/todo.md +++ b/resources/js/wysiwyg/todo.md @@ -6,7 +6,7 @@ ## Main Todo -- Alignments: Use existing classes for blocks (including table cells) + - Alignments: Handle inline block content (image, video) - Image paste upload - Keyboard shortcuts support diff --git a/resources/js/wysiwyg/ui/defaults/buttons/alignments.ts b/resources/js/wysiwyg/ui/defaults/buttons/alignments.ts index 40d9c89dc..78de3c9a2 100644 --- a/resources/js/wysiwyg/ui/defaults/buttons/alignments.ts +++ b/resources/js/wysiwyg/ui/defaults/buttons/alignments.ts @@ -1,4 +1,4 @@ -import {$getSelection, BaseSelection, ElementFormatType} from "lexical"; +import {$getSelection, BaseSelection} from "lexical"; import {EditorButtonDefinition} from "../../framework/buttons"; import alignLeftIcon from "@icons/editor/align-left.svg"; import {EditorUiContext} from "../../framework/core"; @@ -6,13 +6,17 @@ import alignCenterIcon from "@icons/editor/align-center.svg"; import alignRightIcon from "@icons/editor/align-right.svg"; import alignJustifyIcon from "@icons/editor/align-justify.svg"; import {$getBlockElementNodesInSelection, $selectionContainsElementFormat} from "../../../utils/selection"; +import {CommonBlockAlignment} from "../../../nodes/_common"; +import {nodeHasAlignment} from "../../../utils/nodes"; -function setAlignmentForSection(alignment: ElementFormatType): void { +function setAlignmentForSection(alignment: CommonBlockAlignment): void { const selection = $getSelection(); const elements = $getBlockElementNodesInSelection(selection); for (const node of elements) { - node.setFormat(alignment); + if (nodeHasAlignment(node)) { + node.setAlignment(alignment) + } } } diff --git a/resources/js/wysiwyg/utils/nodes.ts b/resources/js/wysiwyg/utils/nodes.ts index 6278186ca..e33cfda7c 100644 --- a/resources/js/wysiwyg/utils/nodes.ts +++ b/resources/js/wysiwyg/utils/nodes.ts @@ -3,6 +3,7 @@ import {LexicalNodeMatcher} from "../nodes"; import {$createCustomParagraphNode} from "../nodes/custom-paragraph"; import {$generateNodesFromDOM} from "@lexical/html"; import {htmlToDom} from "./dom"; +import {NodeHasAlignment} from "../nodes/_common"; function wrapTextNodes(nodes: LexicalNode[]): LexicalNode[] { return nodes.map(node => { @@ -70,4 +71,8 @@ export function $getNearestBlockNodeForCoords(editor: LexicalEditor, x: number, } return null; +} + +export function nodeHasAlignment(node: object): node is NodeHasAlignment { + return '__alignment' in node; } \ No newline at end of file diff --git a/resources/sass/_content.scss b/resources/sass/_content.scss index 3aa4ac653..b187d6408 100644 --- a/resources/sass/_content.scss +++ b/resources/sass/_content.scss @@ -32,6 +32,9 @@ margin-left: auto; margin-right: auto; } + .align-justify { + text-align: justify; + } h1, h2, h3, h4, h5, h6, pre { clear: left; }