mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-04-28 22:02:28 +00:00
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
12 changed files with 250 additions and 81 deletions
resources
js/wysiwyg
nodes
_common.tscallout.tscustom-heading.tscustom-paragraph.tscustom-quote.tscustom-table-cell.tscustom-table.tsindex.ts
todo.mdui/defaults/buttons
utils
sass
66
resources/js/wysiwyg/nodes/_common.ts
Normal file
66
resources/js/wysiwyg/nodes/_common.ts
Normal file
|
@ -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,
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<SerializedCommonBlockNode, SerializedHeadingNode>
|
||||
|
||||
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};
|
||||
}
|
||||
|
|
|
@ -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<SerializedCommonBlockNode, SerializedParagraphNode>
|
||||
|
||||
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};
|
||||
},
|
||||
|
|
|
@ -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<SerializedCommonBlockNode, SerializedQuoteNode>
|
||||
|
||||
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};
|
||||
}
|
||||
|
||||
|
|
|
@ -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<string, string>,
|
||||
styles: Record<string, string>;
|
||||
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;
|
||||
|
|
|
@ -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<Spread<{
|
||||
colWidths: string[];
|
||||
styles: Record<string, string>,
|
||||
}, 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);
|
||||
|
|
|
@ -35,17 +35,17 @@ export function getNodesForPageEditor(): (KlassConstructor<typeof LexicalNode> |
|
|||
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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -32,6 +32,9 @@
|
|||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
.align-justify {
|
||||
text-align: justify;
|
||||
}
|
||||
h1, h2, h3, h4, h5, h6, pre {
|
||||
clear: left;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue