Lexical: Added custom alignment handling for blocks
To align with pre-existing use of alignment classes.
This commit is contained in:
parent
0039f893cc
commit
111a313d51
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,22 +5,27 @@ import {
|
||||||
ElementNode,
|
ElementNode,
|
||||||
LexicalEditor,
|
LexicalEditor,
|
||||||
LexicalNode,
|
LexicalNode,
|
||||||
ParagraphNode, SerializedElementNode, Spread
|
ParagraphNode, Spread
|
||||||
} from 'lexical';
|
} from 'lexical';
|
||||||
import type {EditorConfig} from "lexical/LexicalEditor";
|
import type {EditorConfig} from "lexical/LexicalEditor";
|
||||||
import type {RangeSelection} from "lexical/LexicalSelection";
|
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 CalloutCategory = 'info' | 'danger' | 'warning' | 'success';
|
||||||
|
|
||||||
export type SerializedCalloutNode = Spread<{
|
export type SerializedCalloutNode = Spread<{
|
||||||
category: CalloutCategory;
|
category: CalloutCategory;
|
||||||
id: string;
|
}, SerializedCommonBlockNode>
|
||||||
}, SerializedElementNode>
|
|
||||||
|
|
||||||
export class CalloutNode extends ElementNode {
|
export class CalloutNode extends ElementNode {
|
||||||
__id: string = '';
|
__id: string = '';
|
||||||
__category: CalloutCategory = 'info';
|
__category: CalloutCategory = 'info';
|
||||||
|
__alignment: CommonBlockAlignment = '';
|
||||||
|
|
||||||
static getType() {
|
static getType() {
|
||||||
return 'callout';
|
return 'callout';
|
||||||
|
@ -57,19 +62,26 @@ export class CalloutNode extends ElementNode {
|
||||||
return self.__id;
|
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) {
|
createDOM(_config: EditorConfig, _editor: LexicalEditor) {
|
||||||
const element = document.createElement('p');
|
const element = document.createElement('p');
|
||||||
element.classList.add('callout', this.__category || '');
|
element.classList.add('callout', this.__category || '');
|
||||||
if (this.__id) {
|
updateElementWithCommonBlockProps(element, this);
|
||||||
element.setAttribute('id', this.__id);
|
|
||||||
}
|
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDOM(prevNode: unknown, dom: HTMLElement) {
|
updateDOM(prevNode: CalloutNode): boolean {
|
||||||
// Returning false tells Lexical that this node does not need its
|
return prevNode.__category !== this.__category ||
|
||||||
// DOM element replacing with a new copy from createDOM.
|
commonPropertiesDifferent(prevNode, this);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
insertNewAfter(selection: RangeSelection, restoreSelection?: boolean): CalloutNode|ParagraphNode {
|
insertNewAfter(selection: RangeSelection, restoreSelection?: boolean): CalloutNode|ParagraphNode {
|
||||||
|
@ -106,9 +118,7 @@ export class CalloutNode extends ElementNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
const node = new CalloutNode(category);
|
const node = new CalloutNode(category);
|
||||||
if (element.id) {
|
setCommonBlockPropsFromElement(element, node);
|
||||||
node.setId(element.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
node,
|
node,
|
||||||
|
@ -129,12 +139,14 @@ export class CalloutNode extends ElementNode {
|
||||||
version: 1,
|
version: 1,
|
||||||
category: this.__category,
|
category: this.__category,
|
||||||
id: this.__id,
|
id: this.__id,
|
||||||
|
alignment: this.__alignment,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static importJSON(serializedNode: SerializedCalloutNode): CalloutNode {
|
static importJSON(serializedNode: SerializedCalloutNode): CalloutNode {
|
||||||
const node = $createCalloutNode(serializedNode.category);
|
const node = $createCalloutNode(serializedNode.category);
|
||||||
node.setId(serializedNode.id);
|
node.setId(serializedNode.id);
|
||||||
|
node.setAlignment(serializedNode.alignment);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,24 @@
|
||||||
import {
|
import {
|
||||||
DOMConversionMap,
|
DOMConversionMap,
|
||||||
DOMConversionOutput, ElementFormatType,
|
DOMConversionOutput,
|
||||||
LexicalNode,
|
LexicalNode,
|
||||||
Spread
|
Spread
|
||||||
} from "lexical";
|
} from "lexical";
|
||||||
import {EditorConfig} from "lexical/LexicalEditor";
|
import {EditorConfig} from "lexical/LexicalEditor";
|
||||||
import {HeadingNode, HeadingTagType, SerializedHeadingNode} from "@lexical/rich-text";
|
import {HeadingNode, HeadingTagType, SerializedHeadingNode} from "@lexical/rich-text";
|
||||||
|
import {
|
||||||
|
CommonBlockAlignment, commonPropertiesDifferent,
|
||||||
|
SerializedCommonBlockNode,
|
||||||
|
setCommonBlockPropsFromElement,
|
||||||
|
updateElementWithCommonBlockProps
|
||||||
|
} from "./_common";
|
||||||
|
|
||||||
|
|
||||||
export type SerializedCustomHeadingNode = Spread<{
|
export type SerializedCustomHeadingNode = Spread<SerializedCommonBlockNode, SerializedHeadingNode>
|
||||||
id: string;
|
|
||||||
}, SerializedHeadingNode>
|
|
||||||
|
|
||||||
export class CustomHeadingNode extends HeadingNode {
|
export class CustomHeadingNode extends HeadingNode {
|
||||||
__id: string = '';
|
__id: string = '';
|
||||||
|
__alignment: CommonBlockAlignment = '';
|
||||||
|
|
||||||
static getType() {
|
static getType() {
|
||||||
return 'custom-heading';
|
return 'custom-heading';
|
||||||
|
@ -29,31 +34,47 @@ export class CustomHeadingNode extends HeadingNode {
|
||||||
return self.__id;
|
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) {
|
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 {
|
createDOM(config: EditorConfig): HTMLElement {
|
||||||
const dom = super.createDOM(config);
|
const dom = super.createDOM(config);
|
||||||
if (this.__id) {
|
updateElementWithCommonBlockProps(dom, this);
|
||||||
dom.setAttribute('id', this.__id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return dom;
|
return dom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateDOM(prevNode: CustomHeadingNode, dom: HTMLElement): boolean {
|
||||||
|
return super.updateDOM(prevNode, dom)
|
||||||
|
|| commonPropertiesDifferent(prevNode, this);
|
||||||
|
}
|
||||||
|
|
||||||
exportJSON(): SerializedCustomHeadingNode {
|
exportJSON(): SerializedCustomHeadingNode {
|
||||||
return {
|
return {
|
||||||
...super.exportJSON(),
|
...super.exportJSON(),
|
||||||
type: 'custom-heading',
|
type: 'custom-heading',
|
||||||
version: 1,
|
version: 1,
|
||||||
id: this.__id,
|
id: this.__id,
|
||||||
|
alignment: this.__alignment,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static importJSON(serializedNode: SerializedCustomHeadingNode): CustomHeadingNode {
|
static importJSON(serializedNode: SerializedCustomHeadingNode): CustomHeadingNode {
|
||||||
const node = $createCustomHeadingNode(serializedNode.tag);
|
const node = $createCustomHeadingNode(serializedNode.tag);
|
||||||
node.setId(serializedNode.id);
|
node.setId(serializedNode.id);
|
||||||
|
node.setAlignment(serializedNode.alignment);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,12 +120,7 @@ function $convertHeadingElement(element: HTMLElement): DOMConversionOutput {
|
||||||
nodeName === 'h6'
|
nodeName === 'h6'
|
||||||
) {
|
) {
|
||||||
node = $createCustomHeadingNode(nodeName);
|
node = $createCustomHeadingNode(nodeName);
|
||||||
if (element.style !== null) {
|
setCommonBlockPropsFromElement(element, node);
|
||||||
node.setFormat(element.style.textAlign as ElementFormatType);
|
|
||||||
}
|
|
||||||
if (element.id) {
|
|
||||||
node.setId(element.id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return {node};
|
return {node};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,23 @@
|
||||||
import {
|
import {
|
||||||
DOMConversion,
|
DOMConversion,
|
||||||
DOMConversionMap,
|
DOMConversionMap,
|
||||||
DOMConversionOutput, ElementFormatType,
|
DOMConversionOutput,
|
||||||
LexicalNode,
|
LexicalNode,
|
||||||
ParagraphNode,
|
ParagraphNode, SerializedParagraphNode, Spread,
|
||||||
SerializedParagraphNode,
|
|
||||||
Spread
|
|
||||||
} from "lexical";
|
} from "lexical";
|
||||||
import {EditorConfig} from "lexical/LexicalEditor";
|
import {EditorConfig} from "lexical/LexicalEditor";
|
||||||
|
import {
|
||||||
|
CommonBlockAlignment, commonPropertiesDifferent,
|
||||||
|
SerializedCommonBlockNode,
|
||||||
|
setCommonBlockPropsFromElement,
|
||||||
|
updateElementWithCommonBlockProps
|
||||||
|
} from "./_common";
|
||||||
|
|
||||||
|
export type SerializedCustomParagraphNode = Spread<SerializedCommonBlockNode, SerializedParagraphNode>
|
||||||
export type SerializedCustomParagraphNode = Spread<{
|
|
||||||
id: string;
|
|
||||||
}, SerializedParagraphNode>
|
|
||||||
|
|
||||||
export class CustomParagraphNode extends ParagraphNode {
|
export class CustomParagraphNode extends ParagraphNode {
|
||||||
__id: string = '';
|
__id: string = '';
|
||||||
|
__alignment: CommonBlockAlignment = '';
|
||||||
|
|
||||||
static getType() {
|
static getType() {
|
||||||
return 'custom-paragraph';
|
return 'custom-paragraph';
|
||||||
|
@ -31,33 +33,48 @@ export class CustomParagraphNode extends ParagraphNode {
|
||||||
return self.__id;
|
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 {
|
static clone(node: CustomParagraphNode): CustomParagraphNode {
|
||||||
const newNode = new CustomParagraphNode(node.__key);
|
const newNode = new CustomParagraphNode(node.__key);
|
||||||
newNode.__id = node.__id;
|
newNode.__id = node.__id;
|
||||||
|
newNode.__alignment = node.__alignment;
|
||||||
return newNode;
|
return newNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
createDOM(config: EditorConfig): HTMLElement {
|
createDOM(config: EditorConfig): HTMLElement {
|
||||||
const dom = super.createDOM(config);
|
const dom = super.createDOM(config);
|
||||||
if (this.__id) {
|
updateElementWithCommonBlockProps(dom, this);
|
||||||
dom.setAttribute('id', this.__id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return dom;
|
return dom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateDOM(prevNode: CustomParagraphNode, dom: HTMLElement, config: EditorConfig): boolean {
|
||||||
|
return super.updateDOM(prevNode, dom, config)
|
||||||
|
|| commonPropertiesDifferent(prevNode, this);
|
||||||
|
}
|
||||||
|
|
||||||
exportJSON(): SerializedCustomParagraphNode {
|
exportJSON(): SerializedCustomParagraphNode {
|
||||||
return {
|
return {
|
||||||
...super.exportJSON(),
|
...super.exportJSON(),
|
||||||
type: 'custom-paragraph',
|
type: 'custom-paragraph',
|
||||||
version: 1,
|
version: 1,
|
||||||
id: this.__id,
|
id: this.__id,
|
||||||
|
alignment: this.__alignment,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static importJSON(serializedNode: SerializedCustomParagraphNode): CustomParagraphNode {
|
static importJSON(serializedNode: SerializedCustomParagraphNode): CustomParagraphNode {
|
||||||
const node = $createCustomParagraphNode();
|
const node = $createCustomParagraphNode();
|
||||||
node.setId(serializedNode.id);
|
node.setId(serializedNode.id);
|
||||||
|
node.setAlignment(serializedNode.alignment);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,17 +84,14 @@ export class CustomParagraphNode extends ParagraphNode {
|
||||||
return {
|
return {
|
||||||
conversion: (element: HTMLElement): DOMConversionOutput|null => {
|
conversion: (element: HTMLElement): DOMConversionOutput|null => {
|
||||||
const node = $createCustomParagraphNode();
|
const node = $createCustomParagraphNode();
|
||||||
if (element.style) {
|
if (element.style.textIndent) {
|
||||||
node.setFormat(element.style.textAlign as ElementFormatType);
|
|
||||||
const indent = parseInt(element.style.textIndent, 10) / 20;
|
const indent = parseInt(element.style.textIndent, 10) / 20;
|
||||||
if (indent > 0) {
|
if (indent > 0) {
|
||||||
node.setIndent(indent);
|
node.setIndent(indent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (element.id) {
|
setCommonBlockPropsFromElement(element, node);
|
||||||
node.setId(element.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {node};
|
return {node};
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,19 +1,24 @@
|
||||||
import {
|
import {
|
||||||
DOMConversionMap,
|
DOMConversionMap,
|
||||||
DOMConversionOutput, ElementFormatType,
|
DOMConversionOutput,
|
||||||
LexicalNode,
|
LexicalNode,
|
||||||
Spread
|
Spread
|
||||||
} from "lexical";
|
} from "lexical";
|
||||||
import {EditorConfig} from "lexical/LexicalEditor";
|
import {EditorConfig} from "lexical/LexicalEditor";
|
||||||
import {QuoteNode, SerializedQuoteNode} from "@lexical/rich-text";
|
import {QuoteNode, SerializedQuoteNode} from "@lexical/rich-text";
|
||||||
|
import {
|
||||||
|
CommonBlockAlignment, commonPropertiesDifferent,
|
||||||
|
SerializedCommonBlockNode,
|
||||||
|
setCommonBlockPropsFromElement,
|
||||||
|
updateElementWithCommonBlockProps
|
||||||
|
} from "./_common";
|
||||||
|
|
||||||
|
|
||||||
export type SerializedCustomQuoteNode = Spread<{
|
export type SerializedCustomQuoteNode = Spread<SerializedCommonBlockNode, SerializedQuoteNode>
|
||||||
id: string;
|
|
||||||
}, SerializedQuoteNode>
|
|
||||||
|
|
||||||
export class CustomQuoteNode extends QuoteNode {
|
export class CustomQuoteNode extends QuoteNode {
|
||||||
__id: string = '';
|
__id: string = '';
|
||||||
|
__alignment: CommonBlockAlignment = '';
|
||||||
|
|
||||||
static getType() {
|
static getType() {
|
||||||
return 'custom-quote';
|
return 'custom-quote';
|
||||||
|
@ -29,33 +34,47 @@ export class CustomQuoteNode extends QuoteNode {
|
||||||
return self.__id;
|
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) {
|
static clone(node: CustomQuoteNode) {
|
||||||
const newNode = new CustomQuoteNode(node.__key);
|
const newNode = new CustomQuoteNode(node.__key);
|
||||||
newNode.__id = node.__id;
|
newNode.__id = node.__id;
|
||||||
|
newNode.__alignment = node.__alignment;
|
||||||
return newNode;
|
return newNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
createDOM(config: EditorConfig): HTMLElement {
|
createDOM(config: EditorConfig): HTMLElement {
|
||||||
const dom = super.createDOM(config);
|
const dom = super.createDOM(config);
|
||||||
if (this.__id) {
|
updateElementWithCommonBlockProps(dom, this);
|
||||||
dom.setAttribute('id', this.__id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return dom;
|
return dom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateDOM(prevNode: CustomQuoteNode): boolean {
|
||||||
|
return commonPropertiesDifferent(prevNode, this);
|
||||||
|
}
|
||||||
|
|
||||||
exportJSON(): SerializedCustomQuoteNode {
|
exportJSON(): SerializedCustomQuoteNode {
|
||||||
return {
|
return {
|
||||||
...super.exportJSON(),
|
...super.exportJSON(),
|
||||||
type: 'custom-quote',
|
type: 'custom-quote',
|
||||||
version: 1,
|
version: 1,
|
||||||
id: this.__id,
|
id: this.__id,
|
||||||
|
alignment: this.__alignment,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static importJSON(serializedNode: SerializedCustomQuoteNode): CustomQuoteNode {
|
static importJSON(serializedNode: SerializedCustomQuoteNode): CustomQuoteNode {
|
||||||
const node = $createCustomQuoteNode();
|
const node = $createCustomQuoteNode();
|
||||||
node.setId(serializedNode.id);
|
node.setId(serializedNode.id);
|
||||||
|
node.setAlignment(serializedNode.alignment);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,12 +90,7 @@ export class CustomQuoteNode extends QuoteNode {
|
||||||
|
|
||||||
function $convertBlockquoteElement(element: HTMLElement): DOMConversionOutput {
|
function $convertBlockquoteElement(element: HTMLElement): DOMConversionOutput {
|
||||||
const node = $createCustomQuoteNode();
|
const node = $createCustomQuoteNode();
|
||||||
if (element.style !== null) {
|
setCommonBlockPropsFromElement(element, node);
|
||||||
node.setFormat(element.style.textAlign as ElementFormatType);
|
|
||||||
}
|
|
||||||
if (element.id) {
|
|
||||||
node.setId(element.id);
|
|
||||||
}
|
|
||||||
return {node};
|
return {node};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,13 +21,16 @@ import {
|
||||||
} from "@lexical/table";
|
} from "@lexical/table";
|
||||||
import {TableCellHeaderState} from "@lexical/table/LexicalTableCellNode";
|
import {TableCellHeaderState} from "@lexical/table/LexicalTableCellNode";
|
||||||
import {extractStyleMapFromElement, StyleMap} from "../utils/dom";
|
import {extractStyleMapFromElement, StyleMap} from "../utils/dom";
|
||||||
|
import {CommonBlockAlignment, extractAlignmentFromElement} from "./_common";
|
||||||
|
|
||||||
export type SerializedCustomTableCellNode = Spread<{
|
export type SerializedCustomTableCellNode = Spread<{
|
||||||
styles: Record<string, string>,
|
styles: Record<string, string>;
|
||||||
|
alignment: CommonBlockAlignment;
|
||||||
}, SerializedTableCellNode>
|
}, SerializedTableCellNode>
|
||||||
|
|
||||||
export class CustomTableCellNode extends TableCellNode {
|
export class CustomTableCellNode extends TableCellNode {
|
||||||
__styles: StyleMap = new Map;
|
__styles: StyleMap = new Map;
|
||||||
|
__alignment: CommonBlockAlignment = '';
|
||||||
|
|
||||||
static getType(): string {
|
static getType(): string {
|
||||||
return 'custom-table-cell';
|
return 'custom-table-cell';
|
||||||
|
@ -42,6 +45,7 @@ export class CustomTableCellNode extends TableCellNode {
|
||||||
);
|
);
|
||||||
cellNode.__rowSpan = node.__rowSpan;
|
cellNode.__rowSpan = node.__rowSpan;
|
||||||
cellNode.__styles = new Map(node.__styles);
|
cellNode.__styles = new Map(node.__styles);
|
||||||
|
cellNode.__alignment = node.__alignment;
|
||||||
return cellNode;
|
return cellNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,6 +64,16 @@ export class CustomTableCellNode extends TableCellNode {
|
||||||
self.__styles = new Map(styles);
|
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 {
|
updateTag(tag: string): void {
|
||||||
const isHeader = tag.toLowerCase() === 'th';
|
const isHeader = tag.toLowerCase() === 'th';
|
||||||
const state = isHeader ? TableCellHeaderStates.ROW : TableCellHeaderStates.NO_STATUS;
|
const state = isHeader ? TableCellHeaderStates.ROW : TableCellHeaderStates.NO_STATUS;
|
||||||
|
@ -74,12 +88,17 @@ export class CustomTableCellNode extends TableCellNode {
|
||||||
element.style.setProperty(name, value);
|
element.style.setProperty(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.__alignment) {
|
||||||
|
element.classList.add('align-' + this.__alignment);
|
||||||
|
}
|
||||||
|
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDOM(prevNode: CustomTableCellNode): boolean {
|
updateDOM(prevNode: CustomTableCellNode): boolean {
|
||||||
return super.updateDOM(prevNode)
|
return super.updateDOM(prevNode)
|
||||||
|| this.__styles !== prevNode.__styles;
|
|| this.__styles !== prevNode.__styles
|
||||||
|
|| this.__alignment !== prevNode.__alignment;
|
||||||
}
|
}
|
||||||
|
|
||||||
static importDOM(): DOMConversionMap | null {
|
static importDOM(): DOMConversionMap | null {
|
||||||
|
@ -110,6 +129,7 @@ export class CustomTableCellNode extends TableCellNode {
|
||||||
);
|
);
|
||||||
|
|
||||||
node.setStyles(new Map(Object.entries(serializedNode.styles)));
|
node.setStyles(new Map(Object.entries(serializedNode.styles)));
|
||||||
|
node.setAlignment(serializedNode.alignment);
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
@ -119,6 +139,7 @@ export class CustomTableCellNode extends TableCellNode {
|
||||||
...super.exportJSON(),
|
...super.exportJSON(),
|
||||||
type: 'custom-table-cell',
|
type: 'custom-table-cell',
|
||||||
styles: Object.fromEntries(this.__styles),
|
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) {
|
if (domNode instanceof HTMLElement && output.node instanceof CustomTableCellNode) {
|
||||||
output.node.setStyles(extractStyleMapFromElement(domNode));
|
output.node.setStyles(extractStyleMapFromElement(domNode));
|
||||||
|
output.node.setAlignment(extractAlignmentFromElement(domNode));
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
|
|
|
@ -4,17 +4,23 @@ import {EditorConfig} from "lexical/LexicalEditor";
|
||||||
|
|
||||||
import {el, extractStyleMapFromElement, StyleMap} from "../utils/dom";
|
import {el, extractStyleMapFromElement, StyleMap} from "../utils/dom";
|
||||||
import {getTableColumnWidths} from "../utils/tables";
|
import {getTableColumnWidths} from "../utils/tables";
|
||||||
|
import {
|
||||||
|
CommonBlockAlignment,
|
||||||
|
SerializedCommonBlockNode,
|
||||||
|
setCommonBlockPropsFromElement,
|
||||||
|
updateElementWithCommonBlockProps
|
||||||
|
} from "./_common";
|
||||||
|
|
||||||
export type SerializedCustomTableNode = Spread<{
|
export type SerializedCustomTableNode = Spread<Spread<{
|
||||||
id: string;
|
|
||||||
colWidths: string[];
|
colWidths: string[];
|
||||||
styles: Record<string, string>,
|
styles: Record<string, string>,
|
||||||
}, SerializedTableNode>
|
}, SerializedTableNode>, SerializedCommonBlockNode>
|
||||||
|
|
||||||
export class CustomTableNode extends TableNode {
|
export class CustomTableNode extends TableNode {
|
||||||
__id: string = '';
|
__id: string = '';
|
||||||
__colWidths: string[] = [];
|
__colWidths: string[] = [];
|
||||||
__styles: StyleMap = new Map;
|
__styles: StyleMap = new Map;
|
||||||
|
__alignment: CommonBlockAlignment = '';
|
||||||
|
|
||||||
static getType() {
|
static getType() {
|
||||||
return 'custom-table';
|
return 'custom-table';
|
||||||
|
@ -30,6 +36,16 @@ export class CustomTableNode extends TableNode {
|
||||||
return self.__id;
|
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[]) {
|
setColWidths(widths: string[]) {
|
||||||
const self = this.getWritable();
|
const self = this.getWritable();
|
||||||
self.__colWidths = widths;
|
self.__colWidths = widths;
|
||||||
|
@ -55,15 +71,13 @@ export class CustomTableNode extends TableNode {
|
||||||
newNode.__id = node.__id;
|
newNode.__id = node.__id;
|
||||||
newNode.__colWidths = node.__colWidths;
|
newNode.__colWidths = node.__colWidths;
|
||||||
newNode.__styles = new Map(node.__styles);
|
newNode.__styles = new Map(node.__styles);
|
||||||
|
newNode.__alignment = node.__alignment;
|
||||||
return newNode;
|
return newNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
createDOM(config: EditorConfig): HTMLElement {
|
createDOM(config: EditorConfig): HTMLElement {
|
||||||
const dom = super.createDOM(config);
|
const dom = super.createDOM(config);
|
||||||
const id = this.getId();
|
updateElementWithCommonBlockProps(dom, this);
|
||||||
if (id) {
|
|
||||||
dom.setAttribute('id', id);
|
|
||||||
}
|
|
||||||
|
|
||||||
const colWidths = this.getColWidths();
|
const colWidths = this.getColWidths();
|
||||||
if (colWidths.length > 0) {
|
if (colWidths.length > 0) {
|
||||||
|
@ -97,6 +111,7 @@ export class CustomTableNode extends TableNode {
|
||||||
id: this.__id,
|
id: this.__id,
|
||||||
colWidths: this.__colWidths,
|
colWidths: this.__colWidths,
|
||||||
styles: Object.fromEntries(this.__styles),
|
styles: Object.fromEntries(this.__styles),
|
||||||
|
alignment: this.__alignment,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,6 +120,7 @@ export class CustomTableNode extends TableNode {
|
||||||
node.setId(serializedNode.id);
|
node.setId(serializedNode.id);
|
||||||
node.setColWidths(serializedNode.colWidths);
|
node.setColWidths(serializedNode.colWidths);
|
||||||
node.setStyles(new Map(Object.entries(serializedNode.styles)));
|
node.setStyles(new Map(Object.entries(serializedNode.styles)));
|
||||||
|
node.setAlignment(serializedNode.alignment);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,10 +130,7 @@ export class CustomTableNode extends TableNode {
|
||||||
return {
|
return {
|
||||||
conversion: (element: HTMLElement): DOMConversionOutput|null => {
|
conversion: (element: HTMLElement): DOMConversionOutput|null => {
|
||||||
const node = $createCustomTableNode();
|
const node = $createCustomTableNode();
|
||||||
|
setCommonBlockPropsFromElement(element, node);
|
||||||
if (element.id) {
|
|
||||||
node.setId(element.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
const colWidths = getTableColumnWidths(element as HTMLTableElement);
|
const colWidths = getTableColumnWidths(element as HTMLTableElement);
|
||||||
node.setColWidths(colWidths);
|
node.setColWidths(colWidths);
|
||||||
|
|
|
@ -35,17 +35,17 @@ export function getNodesForPageEditor(): (KlassConstructor<typeof LexicalNode> |
|
||||||
CustomHeadingNode,
|
CustomHeadingNode,
|
||||||
CustomQuoteNode,
|
CustomQuoteNode,
|
||||||
CustomListNode,
|
CustomListNode,
|
||||||
CustomListItemNode,
|
CustomListItemNode, // TODO - Alignment?
|
||||||
CustomTableNode,
|
CustomTableNode,
|
||||||
CustomTableRowNode,
|
CustomTableRowNode,
|
||||||
CustomTableCellNode,
|
CustomTableCellNode,
|
||||||
ImageNode,
|
ImageNode, // TODO - Alignment
|
||||||
HorizontalRuleNode,
|
HorizontalRuleNode,
|
||||||
DetailsNode, SummaryNode,
|
DetailsNode, SummaryNode,
|
||||||
CodeBlockNode,
|
CodeBlockNode,
|
||||||
DiagramNode,
|
DiagramNode,
|
||||||
MediaNode,
|
MediaNode, // TODO - Alignment
|
||||||
CustomParagraphNode, // TODO - ID
|
CustomParagraphNode,
|
||||||
LinkNode,
|
LinkNode,
|
||||||
{
|
{
|
||||||
replace: ParagraphNode,
|
replace: ParagraphNode,
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
## Main Todo
|
## Main Todo
|
||||||
|
|
||||||
- Alignments: Use existing classes for blocks (including table cells)
|
|
||||||
- Alignments: Handle inline block content (image, video)
|
- Alignments: Handle inline block content (image, video)
|
||||||
- Image paste upload
|
- Image paste upload
|
||||||
- Keyboard shortcuts support
|
- Keyboard shortcuts support
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {$getSelection, BaseSelection, ElementFormatType} from "lexical";
|
import {$getSelection, BaseSelection} from "lexical";
|
||||||
import {EditorButtonDefinition} from "../../framework/buttons";
|
import {EditorButtonDefinition} from "../../framework/buttons";
|
||||||
import alignLeftIcon from "@icons/editor/align-left.svg";
|
import alignLeftIcon from "@icons/editor/align-left.svg";
|
||||||
import {EditorUiContext} from "../../framework/core";
|
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 alignRightIcon from "@icons/editor/align-right.svg";
|
||||||
import alignJustifyIcon from "@icons/editor/align-justify.svg";
|
import alignJustifyIcon from "@icons/editor/align-justify.svg";
|
||||||
import {$getBlockElementNodesInSelection, $selectionContainsElementFormat} from "../../../utils/selection";
|
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 selection = $getSelection();
|
||||||
const elements = $getBlockElementNodesInSelection(selection);
|
const elements = $getBlockElementNodesInSelection(selection);
|
||||||
for (const node of elements) {
|
for (const node of elements) {
|
||||||
node.setFormat(alignment);
|
if (nodeHasAlignment(node)) {
|
||||||
|
node.setAlignment(alignment)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import {LexicalNodeMatcher} from "../nodes";
|
||||||
import {$createCustomParagraphNode} from "../nodes/custom-paragraph";
|
import {$createCustomParagraphNode} from "../nodes/custom-paragraph";
|
||||||
import {$generateNodesFromDOM} from "@lexical/html";
|
import {$generateNodesFromDOM} from "@lexical/html";
|
||||||
import {htmlToDom} from "./dom";
|
import {htmlToDom} from "./dom";
|
||||||
|
import {NodeHasAlignment} from "../nodes/_common";
|
||||||
|
|
||||||
function wrapTextNodes(nodes: LexicalNode[]): LexicalNode[] {
|
function wrapTextNodes(nodes: LexicalNode[]): LexicalNode[] {
|
||||||
return nodes.map(node => {
|
return nodes.map(node => {
|
||||||
|
@ -70,4 +71,8 @@ export function $getNearestBlockNodeForCoords(editor: LexicalEditor, x: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function nodeHasAlignment(node: object): node is NodeHasAlignment {
|
||||||
|
return '__alignment' in node;
|
||||||
}
|
}
|
|
@ -32,6 +32,9 @@
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
.align-justify {
|
||||||
|
text-align: justify;
|
||||||
|
}
|
||||||
h1, h2, h3, h4, h5, h6, pre {
|
h1, h2, h3, h4, h5, h6, pre {
|
||||||
clear: left;
|
clear: left;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue