mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-06-07 03:14:33 +08:00
Lexical: Merged custom table node code
This commit is contained in:
@ -28,7 +28,8 @@ import {
|
|||||||
ElementNode,
|
ElementNode,
|
||||||
} from 'lexical';
|
} from 'lexical';
|
||||||
|
|
||||||
import {COLUMN_WIDTH, PIXEL_VALUE_REG_EXP} from './constants';
|
import {extractStyleMapFromElement, StyleMap} from "../../utils/dom";
|
||||||
|
import {CommonBlockAlignment, extractAlignmentFromElement} from "../../nodes/_common";
|
||||||
|
|
||||||
export const TableCellHeaderStates = {
|
export const TableCellHeaderStates = {
|
||||||
BOTH: 3,
|
BOTH: 3,
|
||||||
@ -47,6 +48,8 @@ export type SerializedTableCellNode = Spread<
|
|||||||
headerState: TableCellHeaderState;
|
headerState: TableCellHeaderState;
|
||||||
width?: number;
|
width?: number;
|
||||||
backgroundColor?: null | string;
|
backgroundColor?: null | string;
|
||||||
|
styles: Record<string, string>;
|
||||||
|
alignment: CommonBlockAlignment;
|
||||||
},
|
},
|
||||||
SerializedElementNode
|
SerializedElementNode
|
||||||
>;
|
>;
|
||||||
@ -63,6 +66,10 @@ export class TableCellNode extends ElementNode {
|
|||||||
__width?: number;
|
__width?: number;
|
||||||
/** @internal */
|
/** @internal */
|
||||||
__backgroundColor: null | string;
|
__backgroundColor: null | string;
|
||||||
|
/** @internal */
|
||||||
|
__styles: StyleMap = new Map;
|
||||||
|
/** @internal */
|
||||||
|
__alignment: CommonBlockAlignment = '';
|
||||||
|
|
||||||
static getType(): string {
|
static getType(): string {
|
||||||
return 'tablecell';
|
return 'tablecell';
|
||||||
@ -77,6 +84,8 @@ export class TableCellNode extends ElementNode {
|
|||||||
);
|
);
|
||||||
cellNode.__rowSpan = node.__rowSpan;
|
cellNode.__rowSpan = node.__rowSpan;
|
||||||
cellNode.__backgroundColor = node.__backgroundColor;
|
cellNode.__backgroundColor = node.__backgroundColor;
|
||||||
|
cellNode.__styles = new Map(node.__styles);
|
||||||
|
cellNode.__alignment = node.__alignment;
|
||||||
return cellNode;
|
return cellNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,16 +103,20 @@ export class TableCellNode extends ElementNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static importJSON(serializedNode: SerializedTableCellNode): TableCellNode {
|
static importJSON(serializedNode: SerializedTableCellNode): TableCellNode {
|
||||||
const colSpan = serializedNode.colSpan || 1;
|
const node = $createTableCellNode(
|
||||||
const rowSpan = serializedNode.rowSpan || 1;
|
serializedNode.headerState,
|
||||||
const cellNode = $createTableCellNode(
|
serializedNode.colSpan,
|
||||||
serializedNode.headerState,
|
serializedNode.width,
|
||||||
colSpan,
|
|
||||||
serializedNode.width || undefined,
|
|
||||||
);
|
);
|
||||||
cellNode.__rowSpan = rowSpan;
|
|
||||||
cellNode.__backgroundColor = serializedNode.backgroundColor || null;
|
if (serializedNode.rowSpan) {
|
||||||
return cellNode;
|
node.setRowSpan(serializedNode.rowSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
node.setStyles(new Map(Object.entries(serializedNode.styles)));
|
||||||
|
node.setAlignment(serializedNode.alignment);
|
||||||
|
|
||||||
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -144,34 +157,19 @@ export class TableCellNode extends ElementNode {
|
|||||||
this.hasHeader() && config.theme.tableCellHeader,
|
this.hasHeader() && config.theme.tableCellHeader,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
for (const [name, value] of this.__styles.entries()) {
|
||||||
|
element.style.setProperty(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.__alignment) {
|
||||||
|
element.classList.add('align-' + this.__alignment);
|
||||||
|
}
|
||||||
|
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
exportDOM(editor: LexicalEditor): DOMExportOutput {
|
exportDOM(editor: LexicalEditor): DOMExportOutput {
|
||||||
const {element} = super.exportDOM(editor);
|
const {element} = super.exportDOM(editor);
|
||||||
|
|
||||||
if (element) {
|
|
||||||
const element_ = element as HTMLTableCellElement;
|
|
||||||
element_.style.border = '1px solid black';
|
|
||||||
if (this.__colSpan > 1) {
|
|
||||||
element_.colSpan = this.__colSpan;
|
|
||||||
}
|
|
||||||
if (this.__rowSpan > 1) {
|
|
||||||
element_.rowSpan = this.__rowSpan;
|
|
||||||
}
|
|
||||||
element_.style.width = `${this.getWidth() || COLUMN_WIDTH}px`;
|
|
||||||
|
|
||||||
element_.style.verticalAlign = 'top';
|
|
||||||
element_.style.textAlign = 'start';
|
|
||||||
|
|
||||||
const backgroundColor = this.getBackgroundColor();
|
|
||||||
if (backgroundColor !== null) {
|
|
||||||
element_.style.backgroundColor = backgroundColor;
|
|
||||||
} else if (this.hasHeader()) {
|
|
||||||
element_.style.backgroundColor = '#f2f3f5';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
element,
|
element,
|
||||||
};
|
};
|
||||||
@ -186,6 +184,8 @@ export class TableCellNode extends ElementNode {
|
|||||||
rowSpan: this.__rowSpan,
|
rowSpan: this.__rowSpan,
|
||||||
type: 'tablecell',
|
type: 'tablecell',
|
||||||
width: this.getWidth(),
|
width: this.getWidth(),
|
||||||
|
styles: Object.fromEntries(this.__styles),
|
||||||
|
alignment: this.__alignment,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,6 +231,38 @@ export class TableCellNode extends ElementNode {
|
|||||||
return this.getLatest().__width;
|
return this.getLatest().__width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearWidth(): void {
|
||||||
|
const self = this.getWritable();
|
||||||
|
self.__width = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
getStyles(): StyleMap {
|
||||||
|
const self = this.getLatest();
|
||||||
|
return new Map(self.__styles);
|
||||||
|
}
|
||||||
|
|
||||||
|
setStyles(styles: StyleMap): void {
|
||||||
|
const self = this.getWritable();
|
||||||
|
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;
|
||||||
|
const self = this.getWritable();
|
||||||
|
self.__headerState = state;
|
||||||
|
}
|
||||||
|
|
||||||
getBackgroundColor(): null | string {
|
getBackgroundColor(): null | string {
|
||||||
return this.getLatest().__backgroundColor;
|
return this.getLatest().__backgroundColor;
|
||||||
}
|
}
|
||||||
@ -265,7 +297,9 @@ export class TableCellNode extends ElementNode {
|
|||||||
prevNode.__width !== this.__width ||
|
prevNode.__width !== this.__width ||
|
||||||
prevNode.__colSpan !== this.__colSpan ||
|
prevNode.__colSpan !== this.__colSpan ||
|
||||||
prevNode.__rowSpan !== this.__rowSpan ||
|
prevNode.__rowSpan !== this.__rowSpan ||
|
||||||
prevNode.__backgroundColor !== this.__backgroundColor
|
prevNode.__backgroundColor !== this.__backgroundColor ||
|
||||||
|
prevNode.__styles !== this.__styles ||
|
||||||
|
prevNode.__alignment !== this.__alignment
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,38 +321,42 @@ export class TableCellNode extends ElementNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function $convertTableCellNodeElement(
|
export function $convertTableCellNodeElement(
|
||||||
domNode: Node,
|
domNode: Node,
|
||||||
): DOMConversionOutput {
|
): DOMConversionOutput {
|
||||||
const domNode_ = domNode as HTMLTableCellElement;
|
const domNode_ = domNode as HTMLTableCellElement;
|
||||||
const nodeName = domNode.nodeName.toLowerCase();
|
const nodeName = domNode.nodeName.toLowerCase();
|
||||||
|
|
||||||
let width: number | undefined = undefined;
|
let width: number | undefined = undefined;
|
||||||
|
|
||||||
|
|
||||||
|
const PIXEL_VALUE_REG_EXP = /^(\d+(?:\.\d+)?)px$/;
|
||||||
if (PIXEL_VALUE_REG_EXP.test(domNode_.style.width)) {
|
if (PIXEL_VALUE_REG_EXP.test(domNode_.style.width)) {
|
||||||
width = parseFloat(domNode_.style.width);
|
width = parseFloat(domNode_.style.width);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tableCellNode = $createTableCellNode(
|
const tableCellNode = $createTableCellNode(
|
||||||
nodeName === 'th'
|
nodeName === 'th'
|
||||||
? TableCellHeaderStates.ROW
|
? TableCellHeaderStates.ROW
|
||||||
: TableCellHeaderStates.NO_STATUS,
|
: TableCellHeaderStates.NO_STATUS,
|
||||||
domNode_.colSpan,
|
domNode_.colSpan,
|
||||||
width,
|
width,
|
||||||
);
|
);
|
||||||
|
|
||||||
tableCellNode.__rowSpan = domNode_.rowSpan;
|
tableCellNode.__rowSpan = domNode_.rowSpan;
|
||||||
const backgroundColor = domNode_.style.backgroundColor;
|
|
||||||
if (backgroundColor !== '') {
|
|
||||||
tableCellNode.__backgroundColor = backgroundColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
const style = domNode_.style;
|
const style = domNode_.style;
|
||||||
const textDecoration = style.textDecoration.split(' ');
|
const textDecoration = style.textDecoration.split(' ');
|
||||||
const hasBoldFontWeight =
|
const hasBoldFontWeight =
|
||||||
style.fontWeight === '700' || style.fontWeight === 'bold';
|
style.fontWeight === '700' || style.fontWeight === 'bold';
|
||||||
const hasLinethroughTextDecoration = textDecoration.includes('line-through');
|
const hasLinethroughTextDecoration = textDecoration.includes('line-through');
|
||||||
const hasItalicFontStyle = style.fontStyle === 'italic';
|
const hasItalicFontStyle = style.fontStyle === 'italic';
|
||||||
const hasUnderlineTextDecoration = textDecoration.includes('underline');
|
const hasUnderlineTextDecoration = textDecoration.includes('underline');
|
||||||
|
|
||||||
|
if (domNode instanceof HTMLElement) {
|
||||||
|
tableCellNode.setStyles(extractStyleMapFromElement(domNode));
|
||||||
|
tableCellNode.setAlignment(extractAlignmentFromElement(domNode));
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
after: (childLexicalNodes) => {
|
after: (childLexicalNodes) => {
|
||||||
if (childLexicalNodes.length === 0) {
|
if (childLexicalNodes.length === 0) {
|
||||||
@ -330,8 +368,8 @@ export function $convertTableCellNodeElement(
|
|||||||
if ($isTableCellNode(parentLexicalNode) && !$isElementNode(lexicalNode)) {
|
if ($isTableCellNode(parentLexicalNode) && !$isElementNode(lexicalNode)) {
|
||||||
const paragraphNode = $createParagraphNode();
|
const paragraphNode = $createParagraphNode();
|
||||||
if (
|
if (
|
||||||
$isLineBreakNode(lexicalNode) &&
|
$isLineBreakNode(lexicalNode) &&
|
||||||
lexicalNode.getTextContent() === '\n'
|
lexicalNode.getTextContent() === '\n'
|
||||||
) {
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -360,7 +398,7 @@ export function $convertTableCellNodeElement(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function $createTableCellNode(
|
export function $createTableCellNode(
|
||||||
headerState: TableCellHeaderState,
|
headerState: TableCellHeaderState = TableCellHeaderStates.NO_STATUS,
|
||||||
colSpan = 1,
|
colSpan = 1,
|
||||||
width?: number,
|
width?: number,
|
||||||
): TableCellNode {
|
): TableCellNode {
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type {TableCellNode} from './LexicalTableCellNode';
|
import type {TableCellNode} from './LexicalTableCellNode';
|
||||||
import type {
|
import {
|
||||||
DOMConversionMap,
|
DOMConversionMap,
|
||||||
DOMConversionOutput,
|
DOMConversionOutput,
|
||||||
DOMExportOutput,
|
DOMExportOutput,
|
||||||
@ -15,7 +15,7 @@ import type {
|
|||||||
LexicalEditor,
|
LexicalEditor,
|
||||||
LexicalNode,
|
LexicalNode,
|
||||||
NodeKey,
|
NodeKey,
|
||||||
SerializedElementNode,
|
SerializedElementNode, Spread,
|
||||||
} from 'lexical';
|
} from 'lexical';
|
||||||
|
|
||||||
import {addClassNamesToElement, isHTMLElement} from '@lexical/utils';
|
import {addClassNamesToElement, isHTMLElement} from '@lexical/utils';
|
||||||
@ -27,19 +27,36 @@ import {
|
|||||||
|
|
||||||
import {$isTableCellNode} from './LexicalTableCellNode';
|
import {$isTableCellNode} from './LexicalTableCellNode';
|
||||||
import {TableDOMCell, TableDOMTable} from './LexicalTableObserver';
|
import {TableDOMCell, TableDOMTable} from './LexicalTableObserver';
|
||||||
import {$isTableRowNode, TableRowNode} from './LexicalTableRowNode';
|
|
||||||
import {getTable} from './LexicalTableSelectionHelpers';
|
import {getTable} from './LexicalTableSelectionHelpers';
|
||||||
|
import {CommonBlockNode, copyCommonBlockProperties} from "lexical/nodes/CommonBlockNode";
|
||||||
|
import {
|
||||||
|
commonPropertiesDifferent, deserializeCommonBlockNode,
|
||||||
|
SerializedCommonBlockNode, setCommonBlockPropsFromElement,
|
||||||
|
updateElementWithCommonBlockProps
|
||||||
|
} from "../../nodes/_common";
|
||||||
|
import {el, extractStyleMapFromElement, StyleMap} from "../../utils/dom";
|
||||||
|
import {getTableColumnWidths} from "../../utils/tables";
|
||||||
|
|
||||||
export type SerializedTableNode = SerializedElementNode;
|
export type SerializedTableNode = Spread<{
|
||||||
|
colWidths: string[];
|
||||||
|
styles: Record<string, string>,
|
||||||
|
}, SerializedCommonBlockNode>
|
||||||
|
|
||||||
/** @noInheritDoc */
|
/** @noInheritDoc */
|
||||||
export class TableNode extends ElementNode {
|
export class TableNode extends CommonBlockNode {
|
||||||
|
__colWidths: string[] = [];
|
||||||
|
__styles: StyleMap = new Map;
|
||||||
|
|
||||||
static getType(): string {
|
static getType(): string {
|
||||||
return 'table';
|
return 'table';
|
||||||
}
|
}
|
||||||
|
|
||||||
static clone(node: TableNode): TableNode {
|
static clone(node: TableNode): TableNode {
|
||||||
return new TableNode(node.__key);
|
const newNode = new TableNode(node.__key);
|
||||||
|
copyCommonBlockProperties(node, newNode);
|
||||||
|
newNode.__colWidths = node.__colWidths;
|
||||||
|
newNode.__styles = new Map(node.__styles);
|
||||||
|
return newNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
static importDOM(): DOMConversionMap | null {
|
static importDOM(): DOMConversionMap | null {
|
||||||
@ -52,18 +69,24 @@ export class TableNode extends ElementNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static importJSON(_serializedNode: SerializedTableNode): TableNode {
|
static importJSON(_serializedNode: SerializedTableNode): TableNode {
|
||||||
return $createTableNode();
|
const node = $createTableNode();
|
||||||
|
deserializeCommonBlockNode(_serializedNode, node);
|
||||||
|
node.setColWidths(_serializedNode.colWidths);
|
||||||
|
node.setStyles(new Map(Object.entries(_serializedNode.styles)));
|
||||||
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(key?: NodeKey) {
|
constructor(key?: NodeKey) {
|
||||||
super(key);
|
super(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
exportJSON(): SerializedElementNode {
|
exportJSON(): SerializedTableNode {
|
||||||
return {
|
return {
|
||||||
...super.exportJSON(),
|
...super.exportJSON(),
|
||||||
type: 'table',
|
type: 'table',
|
||||||
version: 1,
|
version: 1,
|
||||||
|
colWidths: this.__colWidths,
|
||||||
|
styles: Object.fromEntries(this.__styles),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,11 +95,33 @@ export class TableNode extends ElementNode {
|
|||||||
|
|
||||||
addClassNamesToElement(tableElement, config.theme.table);
|
addClassNamesToElement(tableElement, config.theme.table);
|
||||||
|
|
||||||
|
updateElementWithCommonBlockProps(tableElement, this);
|
||||||
|
|
||||||
|
const colWidths = this.getColWidths();
|
||||||
|
if (colWidths.length > 0) {
|
||||||
|
const colgroup = el('colgroup');
|
||||||
|
for (const width of colWidths) {
|
||||||
|
const col = el('col');
|
||||||
|
if (width) {
|
||||||
|
col.style.width = width;
|
||||||
|
}
|
||||||
|
colgroup.append(col);
|
||||||
|
}
|
||||||
|
tableElement.append(colgroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [name, value] of this.__styles.entries()) {
|
||||||
|
tableElement.style.setProperty(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
return tableElement;
|
return tableElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDOM(): boolean {
|
updateDOM(_prevNode: TableNode): boolean {
|
||||||
return false;
|
return commonPropertiesDifferent(_prevNode, this)
|
||||||
|
|| this.__colWidths.join(':') !== _prevNode.__colWidths.join(':')
|
||||||
|
|| this.__styles.size !== _prevNode.__styles.size
|
||||||
|
|| (Array.from(this.__styles.values()).join(':') !== (Array.from(_prevNode.__styles.values()).join(':')));
|
||||||
}
|
}
|
||||||
|
|
||||||
exportDOM(editor: LexicalEditor): DOMExportOutput {
|
exportDOM(editor: LexicalEditor): DOMExportOutput {
|
||||||
@ -115,6 +160,26 @@ export class TableNode extends ElementNode {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setColWidths(widths: string[]) {
|
||||||
|
const self = this.getWritable();
|
||||||
|
self.__colWidths = widths;
|
||||||
|
}
|
||||||
|
|
||||||
|
getColWidths(): string[] {
|
||||||
|
const self = this.getLatest();
|
||||||
|
return self.__colWidths;
|
||||||
|
}
|
||||||
|
|
||||||
|
getStyles(): StyleMap {
|
||||||
|
const self = this.getLatest();
|
||||||
|
return new Map(self.__styles);
|
||||||
|
}
|
||||||
|
|
||||||
|
setStyles(styles: StyleMap): void {
|
||||||
|
const self = this.getWritable();
|
||||||
|
self.__styles = new Map(styles);
|
||||||
|
}
|
||||||
|
|
||||||
getCordsFromCellNode(
|
getCordsFromCellNode(
|
||||||
tableCellNode: TableCellNode,
|
tableCellNode: TableCellNode,
|
||||||
table: TableDOMTable,
|
table: TableDOMTable,
|
||||||
@ -239,8 +304,15 @@ export function $getElementForTableNode(
|
|||||||
return getTable(tableElement);
|
return getTable(tableElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function $convertTableElement(_domNode: Node): DOMConversionOutput {
|
export function $convertTableElement(element: HTMLElement): DOMConversionOutput {
|
||||||
return {node: $createTableNode()};
|
const node = $createTableNode();
|
||||||
|
setCommonBlockPropsFromElement(element, node);
|
||||||
|
|
||||||
|
const colWidths = getTableColumnWidths(element as HTMLTableElement);
|
||||||
|
node.setColWidths(colWidths);
|
||||||
|
node.setStyles(extractStyleMapFromElement(element));
|
||||||
|
|
||||||
|
return {node};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function $createTableNode(): TableNode {
|
export function $createTableNode(): TableNode {
|
||||||
|
@ -20,11 +20,12 @@ import {
|
|||||||
SerializedElementNode,
|
SerializedElementNode,
|
||||||
} from 'lexical';
|
} from 'lexical';
|
||||||
|
|
||||||
import {PIXEL_VALUE_REG_EXP} from './constants';
|
import {extractStyleMapFromElement, sizeToPixels, StyleMap} from "../../utils/dom";
|
||||||
|
|
||||||
export type SerializedTableRowNode = Spread<
|
export type SerializedTableRowNode = Spread<
|
||||||
{
|
{
|
||||||
height?: number;
|
styles: Record<string, string>,
|
||||||
|
height?: number,
|
||||||
},
|
},
|
||||||
SerializedElementNode
|
SerializedElementNode
|
||||||
>;
|
>;
|
||||||
@ -33,13 +34,17 @@ export type SerializedTableRowNode = Spread<
|
|||||||
export class TableRowNode extends ElementNode {
|
export class TableRowNode extends ElementNode {
|
||||||
/** @internal */
|
/** @internal */
|
||||||
__height?: number;
|
__height?: number;
|
||||||
|
/** @internal */
|
||||||
|
__styles: StyleMap = new Map();
|
||||||
|
|
||||||
static getType(): string {
|
static getType(): string {
|
||||||
return 'tablerow';
|
return 'tablerow';
|
||||||
}
|
}
|
||||||
|
|
||||||
static clone(node: TableRowNode): TableRowNode {
|
static clone(node: TableRowNode): TableRowNode {
|
||||||
return new TableRowNode(node.__height, node.__key);
|
const newNode = new TableRowNode(node.__key);
|
||||||
|
newNode.__styles = new Map(node.__styles);
|
||||||
|
return newNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
static importDOM(): DOMConversionMap | null {
|
static importDOM(): DOMConversionMap | null {
|
||||||
@ -52,20 +57,24 @@ export class TableRowNode extends ElementNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static importJSON(serializedNode: SerializedTableRowNode): TableRowNode {
|
static importJSON(serializedNode: SerializedTableRowNode): TableRowNode {
|
||||||
return $createTableRowNode(serializedNode.height);
|
const node = $createTableRowNode();
|
||||||
|
|
||||||
|
node.setStyles(new Map(Object.entries(serializedNode.styles)));
|
||||||
|
|
||||||
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(height?: number, key?: NodeKey) {
|
constructor(key?: NodeKey) {
|
||||||
super(key);
|
super(key);
|
||||||
this.__height = height;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exportJSON(): SerializedTableRowNode {
|
exportJSON(): SerializedTableRowNode {
|
||||||
return {
|
return {
|
||||||
...super.exportJSON(),
|
...super.exportJSON(),
|
||||||
...(this.getHeight() && {height: this.getHeight()}),
|
|
||||||
type: 'tablerow',
|
type: 'tablerow',
|
||||||
version: 1,
|
version: 1,
|
||||||
|
styles: Object.fromEntries(this.__styles),
|
||||||
|
height: this.__height || 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,6 +85,10 @@ export class TableRowNode extends ElementNode {
|
|||||||
element.style.height = `${this.__height}px`;
|
element.style.height = `${this.__height}px`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const [name, value] of this.__styles.entries()) {
|
||||||
|
element.style.setProperty(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
addClassNamesToElement(element, config.theme.tableRow);
|
addClassNamesToElement(element, config.theme.tableRow);
|
||||||
|
|
||||||
return element;
|
return element;
|
||||||
@ -85,6 +98,16 @@ export class TableRowNode extends ElementNode {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getStyles(): StyleMap {
|
||||||
|
const self = this.getLatest();
|
||||||
|
return new Map(self.__styles);
|
||||||
|
}
|
||||||
|
|
||||||
|
setStyles(styles: StyleMap): void {
|
||||||
|
const self = this.getWritable();
|
||||||
|
self.__styles = new Map(styles);
|
||||||
|
}
|
||||||
|
|
||||||
setHeight(height: number): number | null | undefined {
|
setHeight(height: number): number | null | undefined {
|
||||||
const self = this.getWritable();
|
const self = this.getWritable();
|
||||||
self.__height = height;
|
self.__height = height;
|
||||||
@ -96,7 +119,8 @@ export class TableRowNode extends ElementNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateDOM(prevNode: TableRowNode): boolean {
|
updateDOM(prevNode: TableRowNode): boolean {
|
||||||
return prevNode.__height !== this.__height;
|
return prevNode.__height !== this.__height
|
||||||
|
|| prevNode.__styles !== this.__styles;
|
||||||
}
|
}
|
||||||
|
|
||||||
canBeEmpty(): false {
|
canBeEmpty(): false {
|
||||||
@ -109,18 +133,21 @@ export class TableRowNode extends ElementNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function $convertTableRowElement(domNode: Node): DOMConversionOutput {
|
export function $convertTableRowElement(domNode: Node): DOMConversionOutput {
|
||||||
const domNode_ = domNode as HTMLTableCellElement;
|
const rowNode = $createTableRowNode();
|
||||||
let height: number | undefined = undefined;
|
const domNode_ = domNode as HTMLElement;
|
||||||
|
|
||||||
if (PIXEL_VALUE_REG_EXP.test(domNode_.style.height)) {
|
const height = sizeToPixels(domNode_.style.height);
|
||||||
height = parseFloat(domNode_.style.height);
|
rowNode.setHeight(height);
|
||||||
|
|
||||||
|
if (domNode instanceof HTMLElement) {
|
||||||
|
rowNode.setStyles(extractStyleMapFromElement(domNode));
|
||||||
}
|
}
|
||||||
|
|
||||||
return {node: $createTableRowNode(height)};
|
return {node: rowNode};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function $createTableRowNode(height?: number): TableRowNode {
|
export function $createTableRowNode(): TableRowNode {
|
||||||
return $applyNodeReplacement(new TableRowNode(height));
|
return $applyNodeReplacement(new TableRowNode());
|
||||||
}
|
}
|
||||||
|
|
||||||
export function $isTableRowNode(
|
export function $isTableRowNode(
|
||||||
|
@ -438,59 +438,6 @@ export function applyTableHandlers(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
tableObserver.listenersToRemove.add(
|
|
||||||
editor.registerCommand<ElementFormatType>(
|
|
||||||
FORMAT_ELEMENT_COMMAND,
|
|
||||||
(formatType) => {
|
|
||||||
const selection = $getSelection();
|
|
||||||
if (
|
|
||||||
!$isTableSelection(selection) ||
|
|
||||||
!$isSelectionInTable(selection, tableNode)
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const anchorNode = selection.anchor.getNode();
|
|
||||||
const focusNode = selection.focus.getNode();
|
|
||||||
if (!$isTableCellNode(anchorNode) || !$isTableCellNode(focusNode)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [tableMap, anchorCell, focusCell] = $computeTableMap(
|
|
||||||
tableNode,
|
|
||||||
anchorNode,
|
|
||||||
focusNode,
|
|
||||||
);
|
|
||||||
const maxRow = Math.max(anchorCell.startRow, focusCell.startRow);
|
|
||||||
const maxColumn = Math.max(
|
|
||||||
anchorCell.startColumn,
|
|
||||||
focusCell.startColumn,
|
|
||||||
);
|
|
||||||
const minRow = Math.min(anchorCell.startRow, focusCell.startRow);
|
|
||||||
const minColumn = Math.min(
|
|
||||||
anchorCell.startColumn,
|
|
||||||
focusCell.startColumn,
|
|
||||||
);
|
|
||||||
for (let i = minRow; i <= maxRow; i++) {
|
|
||||||
for (let j = minColumn; j <= maxColumn; j++) {
|
|
||||||
const cell = tableMap[i][j].cell;
|
|
||||||
cell.setFormat(formatType);
|
|
||||||
|
|
||||||
const cellChildren = cell.getChildren();
|
|
||||||
for (let k = 0; k < cellChildren.length; k++) {
|
|
||||||
const child = cellChildren[k];
|
|
||||||
if ($isElementNode(child) && !child.isInline()) {
|
|
||||||
child.setFormat(formatType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
COMMAND_PRIORITY_CRITICAL,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
tableObserver.listenersToRemove.add(
|
tableObserver.listenersToRemove.add(
|
||||||
editor.registerCommand(
|
editor.registerCommand(
|
||||||
CONTROLLED_TEXT_INSERTION_COMMAND,
|
CONTROLLED_TEXT_INSERTION_COMMAND,
|
||||||
|
@ -39,10 +39,9 @@ describe('LexicalTableRowNode tests', () => {
|
|||||||
`<tr class="${editorConfig.theme.tableRow}"></tr>`,
|
`<tr class="${editorConfig.theme.tableRow}"></tr>`,
|
||||||
);
|
);
|
||||||
|
|
||||||
const rowHeight = 36;
|
const rowWithCustomHeightNode = $createTableRowNode();
|
||||||
const rowWithCustomHeightNode = $createTableRowNode(36);
|
|
||||||
expect(rowWithCustomHeightNode.createDOM(editorConfig).outerHTML).toBe(
|
expect(rowWithCustomHeightNode.createDOM(editorConfig).outerHTML).toBe(
|
||||||
`<tr style="height: ${rowHeight}px;" class="${editorConfig.theme.tableRow}"></tr>`,
|
`<tr class="${editorConfig.theme.tableRow}"></tr>`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,247 +0,0 @@
|
|||||||
import {
|
|
||||||
$createParagraphNode,
|
|
||||||
$isElementNode,
|
|
||||||
$isLineBreakNode,
|
|
||||||
$isTextNode,
|
|
||||||
DOMConversionMap,
|
|
||||||
DOMConversionOutput,
|
|
||||||
DOMExportOutput,
|
|
||||||
EditorConfig,
|
|
||||||
LexicalEditor,
|
|
||||||
LexicalNode,
|
|
||||||
Spread
|
|
||||||
} from "lexical";
|
|
||||||
|
|
||||||
import {
|
|
||||||
$createTableCellNode,
|
|
||||||
$isTableCellNode,
|
|
||||||
SerializedTableCellNode,
|
|
||||||
TableCellHeaderStates,
|
|
||||||
TableCellNode
|
|
||||||
} 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>;
|
|
||||||
alignment: CommonBlockAlignment;
|
|
||||||
}, SerializedTableCellNode>
|
|
||||||
|
|
||||||
export class CustomTableCellNode extends TableCellNode {
|
|
||||||
__styles: StyleMap = new Map;
|
|
||||||
__alignment: CommonBlockAlignment = '';
|
|
||||||
|
|
||||||
static getType(): string {
|
|
||||||
return 'custom-table-cell';
|
|
||||||
}
|
|
||||||
|
|
||||||
static clone(node: CustomTableCellNode): CustomTableCellNode {
|
|
||||||
const cellNode = new CustomTableCellNode(
|
|
||||||
node.__headerState,
|
|
||||||
node.__colSpan,
|
|
||||||
node.__width,
|
|
||||||
node.__key,
|
|
||||||
);
|
|
||||||
cellNode.__rowSpan = node.__rowSpan;
|
|
||||||
cellNode.__styles = new Map(node.__styles);
|
|
||||||
cellNode.__alignment = node.__alignment;
|
|
||||||
return cellNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
clearWidth(): void {
|
|
||||||
const self = this.getWritable();
|
|
||||||
self.__width = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
getStyles(): StyleMap {
|
|
||||||
const self = this.getLatest();
|
|
||||||
return new Map(self.__styles);
|
|
||||||
}
|
|
||||||
|
|
||||||
setStyles(styles: StyleMap): void {
|
|
||||||
const self = this.getWritable();
|
|
||||||
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;
|
|
||||||
const self = this.getWritable();
|
|
||||||
self.__headerState = state;
|
|
||||||
}
|
|
||||||
|
|
||||||
createDOM(config: EditorConfig): HTMLElement {
|
|
||||||
const element = super.createDOM(config);
|
|
||||||
|
|
||||||
for (const [name, value] of this.__styles.entries()) {
|
|
||||||
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.__alignment !== prevNode.__alignment;
|
|
||||||
}
|
|
||||||
|
|
||||||
static importDOM(): DOMConversionMap | null {
|
|
||||||
return {
|
|
||||||
td: (node: Node) => ({
|
|
||||||
conversion: $convertCustomTableCellNodeElement,
|
|
||||||
priority: 0,
|
|
||||||
}),
|
|
||||||
th: (node: Node) => ({
|
|
||||||
conversion: $convertCustomTableCellNodeElement,
|
|
||||||
priority: 0,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
exportDOM(editor: LexicalEditor): DOMExportOutput {
|
|
||||||
const element = this.createDOM(editor._config);
|
|
||||||
return {
|
|
||||||
element
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static importJSON(serializedNode: SerializedCustomTableCellNode): CustomTableCellNode {
|
|
||||||
const node = $createCustomTableCellNode(
|
|
||||||
serializedNode.headerState,
|
|
||||||
serializedNode.colSpan,
|
|
||||||
serializedNode.width,
|
|
||||||
);
|
|
||||||
|
|
||||||
node.setStyles(new Map(Object.entries(serializedNode.styles)));
|
|
||||||
node.setAlignment(serializedNode.alignment);
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
exportJSON(): SerializedCustomTableCellNode {
|
|
||||||
return {
|
|
||||||
...super.exportJSON(),
|
|
||||||
type: 'custom-table-cell',
|
|
||||||
styles: Object.fromEntries(this.__styles),
|
|
||||||
alignment: this.__alignment,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function $convertCustomTableCellNodeElement(domNode: Node): DOMConversionOutput {
|
|
||||||
const output = $convertTableCellNodeElement(domNode);
|
|
||||||
|
|
||||||
if (domNode instanceof HTMLElement && output.node instanceof CustomTableCellNode) {
|
|
||||||
output.node.setStyles(extractStyleMapFromElement(domNode));
|
|
||||||
output.node.setAlignment(extractAlignmentFromElement(domNode));
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function taken from:
|
|
||||||
* https://github.com/facebook/lexical/blob/e1881a6e409e1541c10dd0b5378f3a38c9dc8c9e/packages/lexical-table/src/LexicalTableCellNode.ts#L289
|
|
||||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
||||||
* MIT LICENSE
|
|
||||||
* Modified since copy.
|
|
||||||
*/
|
|
||||||
export function $convertTableCellNodeElement(
|
|
||||||
domNode: Node,
|
|
||||||
): DOMConversionOutput {
|
|
||||||
const domNode_ = domNode as HTMLTableCellElement;
|
|
||||||
const nodeName = domNode.nodeName.toLowerCase();
|
|
||||||
|
|
||||||
let width: number | undefined = undefined;
|
|
||||||
|
|
||||||
|
|
||||||
const PIXEL_VALUE_REG_EXP = /^(\d+(?:\.\d+)?)px$/;
|
|
||||||
if (PIXEL_VALUE_REG_EXP.test(domNode_.style.width)) {
|
|
||||||
width = parseFloat(domNode_.style.width);
|
|
||||||
}
|
|
||||||
|
|
||||||
const tableCellNode = $createTableCellNode(
|
|
||||||
nodeName === 'th'
|
|
||||||
? TableCellHeaderStates.ROW
|
|
||||||
: TableCellHeaderStates.NO_STATUS,
|
|
||||||
domNode_.colSpan,
|
|
||||||
width,
|
|
||||||
);
|
|
||||||
|
|
||||||
tableCellNode.__rowSpan = domNode_.rowSpan;
|
|
||||||
|
|
||||||
const style = domNode_.style;
|
|
||||||
const textDecoration = style.textDecoration.split(' ');
|
|
||||||
const hasBoldFontWeight =
|
|
||||||
style.fontWeight === '700' || style.fontWeight === 'bold';
|
|
||||||
const hasLinethroughTextDecoration = textDecoration.includes('line-through');
|
|
||||||
const hasItalicFontStyle = style.fontStyle === 'italic';
|
|
||||||
const hasUnderlineTextDecoration = textDecoration.includes('underline');
|
|
||||||
return {
|
|
||||||
after: (childLexicalNodes) => {
|
|
||||||
if (childLexicalNodes.length === 0) {
|
|
||||||
childLexicalNodes.push($createParagraphNode());
|
|
||||||
}
|
|
||||||
return childLexicalNodes;
|
|
||||||
},
|
|
||||||
forChild: (lexicalNode, parentLexicalNode) => {
|
|
||||||
if ($isTableCellNode(parentLexicalNode) && !$isElementNode(lexicalNode)) {
|
|
||||||
const paragraphNode = $createParagraphNode();
|
|
||||||
if (
|
|
||||||
$isLineBreakNode(lexicalNode) &&
|
|
||||||
lexicalNode.getTextContent() === '\n'
|
|
||||||
) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if ($isTextNode(lexicalNode)) {
|
|
||||||
if (hasBoldFontWeight) {
|
|
||||||
lexicalNode.toggleFormat('bold');
|
|
||||||
}
|
|
||||||
if (hasLinethroughTextDecoration) {
|
|
||||||
lexicalNode.toggleFormat('strikethrough');
|
|
||||||
}
|
|
||||||
if (hasItalicFontStyle) {
|
|
||||||
lexicalNode.toggleFormat('italic');
|
|
||||||
}
|
|
||||||
if (hasUnderlineTextDecoration) {
|
|
||||||
lexicalNode.toggleFormat('underline');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
paragraphNode.append(lexicalNode);
|
|
||||||
return paragraphNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
return lexicalNode;
|
|
||||||
},
|
|
||||||
node: tableCellNode,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export function $createCustomTableCellNode(
|
|
||||||
headerState: TableCellHeaderState = TableCellHeaderStates.NO_STATUS,
|
|
||||||
colSpan = 1,
|
|
||||||
width?: number,
|
|
||||||
): CustomTableCellNode {
|
|
||||||
return new CustomTableCellNode(headerState, colSpan, width);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function $isCustomTableCellNode(node: LexicalNode | null | undefined): node is CustomTableCellNode {
|
|
||||||
return node instanceof CustomTableCellNode;
|
|
||||||
}
|
|
@ -1,106 +0,0 @@
|
|||||||
import {
|
|
||||||
DOMConversionMap,
|
|
||||||
DOMConversionOutput,
|
|
||||||
EditorConfig,
|
|
||||||
LexicalNode,
|
|
||||||
Spread
|
|
||||||
} from "lexical";
|
|
||||||
|
|
||||||
import {
|
|
||||||
SerializedTableRowNode,
|
|
||||||
TableRowNode
|
|
||||||
} from "@lexical/table";
|
|
||||||
import {NodeKey} from "lexical/LexicalNode";
|
|
||||||
import {extractStyleMapFromElement, StyleMap} from "../utils/dom";
|
|
||||||
|
|
||||||
export type SerializedCustomTableRowNode = Spread<{
|
|
||||||
styles: Record<string, string>,
|
|
||||||
}, SerializedTableRowNode>
|
|
||||||
|
|
||||||
export class CustomTableRowNode extends TableRowNode {
|
|
||||||
__styles: StyleMap = new Map();
|
|
||||||
|
|
||||||
constructor(key?: NodeKey) {
|
|
||||||
super(0, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
static getType(): string {
|
|
||||||
return 'custom-table-row';
|
|
||||||
}
|
|
||||||
|
|
||||||
static clone(node: CustomTableRowNode): CustomTableRowNode {
|
|
||||||
const cellNode = new CustomTableRowNode(node.__key);
|
|
||||||
|
|
||||||
cellNode.__styles = new Map(node.__styles);
|
|
||||||
return cellNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
getStyles(): StyleMap {
|
|
||||||
const self = this.getLatest();
|
|
||||||
return new Map(self.__styles);
|
|
||||||
}
|
|
||||||
|
|
||||||
setStyles(styles: StyleMap): void {
|
|
||||||
const self = this.getWritable();
|
|
||||||
self.__styles = new Map(styles);
|
|
||||||
}
|
|
||||||
|
|
||||||
createDOM(config: EditorConfig): HTMLElement {
|
|
||||||
const element = super.createDOM(config);
|
|
||||||
|
|
||||||
for (const [name, value] of this.__styles.entries()) {
|
|
||||||
element.style.setProperty(name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateDOM(prevNode: CustomTableRowNode): boolean {
|
|
||||||
return super.updateDOM(prevNode)
|
|
||||||
|| this.__styles !== prevNode.__styles;
|
|
||||||
}
|
|
||||||
|
|
||||||
static importDOM(): DOMConversionMap | null {
|
|
||||||
return {
|
|
||||||
tr: (node: Node) => ({
|
|
||||||
conversion: $convertTableRowElement,
|
|
||||||
priority: 0,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static importJSON(serializedNode: SerializedCustomTableRowNode): CustomTableRowNode {
|
|
||||||
const node = $createCustomTableRowNode();
|
|
||||||
|
|
||||||
node.setStyles(new Map(Object.entries(serializedNode.styles)));
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
exportJSON(): SerializedCustomTableRowNode {
|
|
||||||
return {
|
|
||||||
...super.exportJSON(),
|
|
||||||
height: 0,
|
|
||||||
type: 'custom-table-row',
|
|
||||||
styles: Object.fromEntries(this.__styles),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function $convertTableRowElement(domNode: Node): DOMConversionOutput {
|
|
||||||
const rowNode = $createCustomTableRowNode();
|
|
||||||
|
|
||||||
if (domNode instanceof HTMLElement) {
|
|
||||||
rowNode.setStyles(extractStyleMapFromElement(domNode));
|
|
||||||
}
|
|
||||||
|
|
||||||
return {node: rowNode};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function $createCustomTableRowNode(): CustomTableRowNode {
|
|
||||||
return new CustomTableRowNode();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function $isCustomTableRowNode(node: LexicalNode | null | undefined): node is CustomTableRowNode {
|
|
||||||
return node instanceof CustomTableRowNode;
|
|
||||||
}
|
|
@ -1,166 +0,0 @@
|
|||||||
import {SerializedTableNode, TableNode} from "@lexical/table";
|
|
||||||
import {DOMConversion, DOMConversionMap, DOMConversionOutput, LexicalNode, Spread} from "lexical";
|
|
||||||
import {EditorConfig} from "lexical/LexicalEditor";
|
|
||||||
|
|
||||||
import {el, extractStyleMapFromElement, StyleMap} from "../utils/dom";
|
|
||||||
import {getTableColumnWidths} from "../utils/tables";
|
|
||||||
import {
|
|
||||||
CommonBlockAlignment, deserializeCommonBlockNode,
|
|
||||||
SerializedCommonBlockNode,
|
|
||||||
setCommonBlockPropsFromElement,
|
|
||||||
updateElementWithCommonBlockProps
|
|
||||||
} from "./_common";
|
|
||||||
|
|
||||||
export type SerializedCustomTableNode = Spread<Spread<{
|
|
||||||
colWidths: string[];
|
|
||||||
styles: Record<string, string>,
|
|
||||||
}, SerializedTableNode>, SerializedCommonBlockNode>
|
|
||||||
|
|
||||||
export class CustomTableNode extends TableNode {
|
|
||||||
__id: string = '';
|
|
||||||
__colWidths: string[] = [];
|
|
||||||
__styles: StyleMap = new Map;
|
|
||||||
__alignment: CommonBlockAlignment = '';
|
|
||||||
__inset: number = 0;
|
|
||||||
|
|
||||||
static getType() {
|
|
||||||
return 'custom-table';
|
|
||||||
}
|
|
||||||
|
|
||||||
setId(id: string) {
|
|
||||||
const self = this.getWritable();
|
|
||||||
self.__id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
getId(): string {
|
|
||||||
const self = this.getLatest();
|
|
||||||
return self.__id;
|
|
||||||
}
|
|
||||||
|
|
||||||
setAlignment(alignment: CommonBlockAlignment) {
|
|
||||||
const self = this.getWritable();
|
|
||||||
self.__alignment = alignment;
|
|
||||||
}
|
|
||||||
|
|
||||||
getAlignment(): CommonBlockAlignment {
|
|
||||||
const self = this.getLatest();
|
|
||||||
return self.__alignment;
|
|
||||||
}
|
|
||||||
|
|
||||||
setInset(size: number) {
|
|
||||||
const self = this.getWritable();
|
|
||||||
self.__inset = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
getInset(): number {
|
|
||||||
const self = this.getLatest();
|
|
||||||
return self.__inset;
|
|
||||||
}
|
|
||||||
|
|
||||||
setColWidths(widths: string[]) {
|
|
||||||
const self = this.getWritable();
|
|
||||||
self.__colWidths = widths;
|
|
||||||
}
|
|
||||||
|
|
||||||
getColWidths(): string[] {
|
|
||||||
const self = this.getLatest();
|
|
||||||
return self.__colWidths;
|
|
||||||
}
|
|
||||||
|
|
||||||
getStyles(): StyleMap {
|
|
||||||
const self = this.getLatest();
|
|
||||||
return new Map(self.__styles);
|
|
||||||
}
|
|
||||||
|
|
||||||
setStyles(styles: StyleMap): void {
|
|
||||||
const self = this.getWritable();
|
|
||||||
self.__styles = new Map(styles);
|
|
||||||
}
|
|
||||||
|
|
||||||
static clone(node: CustomTableNode) {
|
|
||||||
const newNode = new CustomTableNode(node.__key);
|
|
||||||
newNode.__id = node.__id;
|
|
||||||
newNode.__colWidths = node.__colWidths;
|
|
||||||
newNode.__styles = new Map(node.__styles);
|
|
||||||
newNode.__alignment = node.__alignment;
|
|
||||||
newNode.__inset = node.__inset;
|
|
||||||
return newNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
createDOM(config: EditorConfig): HTMLElement {
|
|
||||||
const dom = super.createDOM(config);
|
|
||||||
updateElementWithCommonBlockProps(dom, this);
|
|
||||||
|
|
||||||
const colWidths = this.getColWidths();
|
|
||||||
if (colWidths.length > 0) {
|
|
||||||
const colgroup = el('colgroup');
|
|
||||||
for (const width of colWidths) {
|
|
||||||
const col = el('col');
|
|
||||||
if (width) {
|
|
||||||
col.style.width = width;
|
|
||||||
}
|
|
||||||
colgroup.append(col);
|
|
||||||
}
|
|
||||||
dom.append(colgroup);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [name, value] of this.__styles.entries()) {
|
|
||||||
dom.style.setProperty(name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return dom;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateDOM(): boolean {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
exportJSON(): SerializedCustomTableNode {
|
|
||||||
return {
|
|
||||||
...super.exportJSON(),
|
|
||||||
type: 'custom-table',
|
|
||||||
version: 1,
|
|
||||||
id: this.__id,
|
|
||||||
colWidths: this.__colWidths,
|
|
||||||
styles: Object.fromEntries(this.__styles),
|
|
||||||
alignment: this.__alignment,
|
|
||||||
inset: this.__inset,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static importJSON(serializedNode: SerializedCustomTableNode): CustomTableNode {
|
|
||||||
const node = $createCustomTableNode();
|
|
||||||
deserializeCommonBlockNode(serializedNode, node);
|
|
||||||
node.setColWidths(serializedNode.colWidths);
|
|
||||||
node.setStyles(new Map(Object.entries(serializedNode.styles)));
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
static importDOM(): DOMConversionMap|null {
|
|
||||||
return {
|
|
||||||
table(node: HTMLElement): DOMConversion|null {
|
|
||||||
return {
|
|
||||||
conversion: (element: HTMLElement): DOMConversionOutput|null => {
|
|
||||||
const node = $createCustomTableNode();
|
|
||||||
setCommonBlockPropsFromElement(element, node);
|
|
||||||
|
|
||||||
const colWidths = getTableColumnWidths(element as HTMLTableElement);
|
|
||||||
node.setColWidths(colWidths);
|
|
||||||
node.setStyles(extractStyleMapFromElement(element));
|
|
||||||
|
|
||||||
return {node};
|
|
||||||
},
|
|
||||||
priority: 1,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function $createCustomTableNode(): CustomTableNode {
|
|
||||||
return new CustomTableNode();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function $isCustomTableNode(node: LexicalNode | null | undefined): node is CustomTableNode {
|
|
||||||
return node instanceof CustomTableNode;
|
|
||||||
}
|
|
@ -11,14 +11,11 @@ import {ImageNode} from "./image";
|
|||||||
import {DetailsNode, SummaryNode} from "./details";
|
import {DetailsNode, SummaryNode} from "./details";
|
||||||
import {ListItemNode, ListNode} from "@lexical/list";
|
import {ListItemNode, ListNode} from "@lexical/list";
|
||||||
import {TableCellNode, TableNode, TableRowNode} from "@lexical/table";
|
import {TableCellNode, TableNode, TableRowNode} from "@lexical/table";
|
||||||
import {CustomTableNode} from "./custom-table";
|
|
||||||
import {HorizontalRuleNode} from "./horizontal-rule";
|
import {HorizontalRuleNode} from "./horizontal-rule";
|
||||||
import {CodeBlockNode} from "./code-block";
|
import {CodeBlockNode} from "./code-block";
|
||||||
import {DiagramNode} from "./diagram";
|
import {DiagramNode} from "./diagram";
|
||||||
import {EditorUiContext} from "../ui/framework/core";
|
import {EditorUiContext} from "../ui/framework/core";
|
||||||
import {MediaNode} from "./media";
|
import {MediaNode} from "./media";
|
||||||
import {CustomTableCellNode} from "./custom-table-cell";
|
|
||||||
import {CustomTableRowNode} from "./custom-table-row";
|
|
||||||
import {HeadingNode} from "@lexical/rich-text/LexicalHeadingNode";
|
import {HeadingNode} from "@lexical/rich-text/LexicalHeadingNode";
|
||||||
import {QuoteNode} from "@lexical/rich-text/LexicalQuoteNode";
|
import {QuoteNode} from "@lexical/rich-text/LexicalQuoteNode";
|
||||||
|
|
||||||
@ -32,9 +29,9 @@ export function getNodesForPageEditor(): (KlassConstructor<typeof LexicalNode> |
|
|||||||
QuoteNode,
|
QuoteNode,
|
||||||
ListNode,
|
ListNode,
|
||||||
ListItemNode,
|
ListItemNode,
|
||||||
CustomTableNode,
|
TableNode,
|
||||||
CustomTableRowNode,
|
TableRowNode,
|
||||||
CustomTableCellNode,
|
TableCellNode,
|
||||||
ImageNode, // TODO - Alignment
|
ImageNode, // TODO - Alignment
|
||||||
HorizontalRuleNode,
|
HorizontalRuleNode,
|
||||||
DetailsNode, SummaryNode,
|
DetailsNode, SummaryNode,
|
||||||
@ -43,30 +40,6 @@ export function getNodesForPageEditor(): (KlassConstructor<typeof LexicalNode> |
|
|||||||
MediaNode, // TODO - Alignment
|
MediaNode, // TODO - Alignment
|
||||||
ParagraphNode,
|
ParagraphNode,
|
||||||
LinkNode,
|
LinkNode,
|
||||||
{
|
|
||||||
replace: TableNode,
|
|
||||||
with(node: TableNode) {
|
|
||||||
return new CustomTableNode();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
replace: TableRowNode,
|
|
||||||
with(node: TableRowNode) {
|
|
||||||
return new CustomTableRowNode();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
replace: TableCellNode,
|
|
||||||
with: (node: TableCellNode) => {
|
|
||||||
const cell = new CustomTableCellNode(
|
|
||||||
node.__headerState,
|
|
||||||
node.__colSpan,
|
|
||||||
node.__width,
|
|
||||||
);
|
|
||||||
cell.__rowSpan = node.__rowSpan;
|
|
||||||
return cell;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,17 +9,15 @@ import insertRowAboveIcon from "@icons/editor/table-insert-row-above.svg";
|
|||||||
import insertRowBelowIcon from "@icons/editor/table-insert-row-below.svg";
|
import insertRowBelowIcon from "@icons/editor/table-insert-row-below.svg";
|
||||||
import {EditorUiContext} from "../../framework/core";
|
import {EditorUiContext} from "../../framework/core";
|
||||||
import {$getSelection, BaseSelection} from "lexical";
|
import {$getSelection, BaseSelection} from "lexical";
|
||||||
import {$isCustomTableNode} from "../../../nodes/custom-table";
|
|
||||||
import {
|
import {
|
||||||
$deleteTableColumn__EXPERIMENTAL,
|
$deleteTableColumn__EXPERIMENTAL,
|
||||||
$deleteTableRow__EXPERIMENTAL,
|
$deleteTableRow__EXPERIMENTAL,
|
||||||
$insertTableColumn__EXPERIMENTAL,
|
$insertTableColumn__EXPERIMENTAL,
|
||||||
$insertTableRow__EXPERIMENTAL,
|
$insertTableRow__EXPERIMENTAL, $isTableCellNode,
|
||||||
$isTableNode, $isTableSelection, $unmergeCell, TableCellNode,
|
$isTableNode, $isTableRowNode, $isTableSelection, $unmergeCell, TableCellNode,
|
||||||
} from "@lexical/table";
|
} from "@lexical/table";
|
||||||
import {$getNodeFromSelection, $selectionContainsNodeType} from "../../../utils/selection";
|
import {$getNodeFromSelection, $selectionContainsNodeType} from "../../../utils/selection";
|
||||||
import {$getParentOfType} from "../../../utils/nodes";
|
import {$getParentOfType} from "../../../utils/nodes";
|
||||||
import {$isCustomTableCellNode} from "../../../nodes/custom-table-cell";
|
|
||||||
import {$showCellPropertiesForm, $showRowPropertiesForm, $showTablePropertiesForm} from "../forms/tables";
|
import {$showCellPropertiesForm, $showRowPropertiesForm, $showTablePropertiesForm} from "../forms/tables";
|
||||||
import {
|
import {
|
||||||
$clearTableFormatting,
|
$clearTableFormatting,
|
||||||
@ -27,7 +25,6 @@ import {
|
|||||||
$getTableRowsFromSelection,
|
$getTableRowsFromSelection,
|
||||||
$mergeTableCellsInSelection
|
$mergeTableCellsInSelection
|
||||||
} from "../../../utils/tables";
|
} from "../../../utils/tables";
|
||||||
import {$isCustomTableRowNode} from "../../../nodes/custom-table-row";
|
|
||||||
import {
|
import {
|
||||||
$copySelectedColumnsToClipboard,
|
$copySelectedColumnsToClipboard,
|
||||||
$copySelectedRowsToClipboard,
|
$copySelectedRowsToClipboard,
|
||||||
@ -41,7 +38,7 @@ import {
|
|||||||
} from "../../../utils/table-copy-paste";
|
} from "../../../utils/table-copy-paste";
|
||||||
|
|
||||||
const neverActive = (): boolean => false;
|
const neverActive = (): boolean => false;
|
||||||
const cellNotSelected = (selection: BaseSelection|null) => !$selectionContainsNodeType(selection, $isCustomTableCellNode);
|
const cellNotSelected = (selection: BaseSelection|null) => !$selectionContainsNodeType(selection, $isTableCellNode);
|
||||||
|
|
||||||
export const table: EditorBasicButtonDefinition = {
|
export const table: EditorBasicButtonDefinition = {
|
||||||
label: 'Table',
|
label: 'Table',
|
||||||
@ -54,7 +51,7 @@ export const tableProperties: EditorButtonDefinition = {
|
|||||||
action(context: EditorUiContext) {
|
action(context: EditorUiContext) {
|
||||||
context.editor.getEditorState().read(() => {
|
context.editor.getEditorState().read(() => {
|
||||||
const table = $getTableFromSelection($getSelection());
|
const table = $getTableFromSelection($getSelection());
|
||||||
if ($isCustomTableNode(table)) {
|
if ($isTableNode(table)) {
|
||||||
$showTablePropertiesForm(table, context);
|
$showTablePropertiesForm(table, context);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -68,13 +65,13 @@ export const clearTableFormatting: EditorButtonDefinition = {
|
|||||||
format: 'long',
|
format: 'long',
|
||||||
action(context: EditorUiContext) {
|
action(context: EditorUiContext) {
|
||||||
context.editor.update(() => {
|
context.editor.update(() => {
|
||||||
const cell = $getNodeFromSelection($getSelection(), $isCustomTableCellNode);
|
const cell = $getNodeFromSelection($getSelection(), $isTableCellNode);
|
||||||
if (!$isCustomTableCellNode(cell)) {
|
if (!$isTableCellNode(cell)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const table = $getParentOfType(cell, $isTableNode);
|
const table = $getParentOfType(cell, $isTableNode);
|
||||||
if ($isCustomTableNode(table)) {
|
if ($isTableNode(table)) {
|
||||||
$clearTableFormatting(table);
|
$clearTableFormatting(table);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -88,13 +85,13 @@ export const resizeTableToContents: EditorButtonDefinition = {
|
|||||||
format: 'long',
|
format: 'long',
|
||||||
action(context: EditorUiContext) {
|
action(context: EditorUiContext) {
|
||||||
context.editor.update(() => {
|
context.editor.update(() => {
|
||||||
const cell = $getNodeFromSelection($getSelection(), $isCustomTableCellNode);
|
const cell = $getNodeFromSelection($getSelection(), $isTableCellNode);
|
||||||
if (!$isCustomTableCellNode(cell)) {
|
if (!$isTableCellNode(cell)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const table = $getParentOfType(cell, $isCustomTableNode);
|
const table = $getParentOfType(cell, $isTableNode);
|
||||||
if ($isCustomTableNode(table)) {
|
if ($isTableNode(table)) {
|
||||||
$clearTableSizes(table);
|
$clearTableSizes(table);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -108,7 +105,7 @@ export const deleteTable: EditorButtonDefinition = {
|
|||||||
icon: deleteIcon,
|
icon: deleteIcon,
|
||||||
action(context: EditorUiContext) {
|
action(context: EditorUiContext) {
|
||||||
context.editor.update(() => {
|
context.editor.update(() => {
|
||||||
const table = $getNodeFromSelection($getSelection(), $isCustomTableNode);
|
const table = $getNodeFromSelection($getSelection(), $isTableNode);
|
||||||
if (table) {
|
if (table) {
|
||||||
table.remove();
|
table.remove();
|
||||||
}
|
}
|
||||||
@ -169,7 +166,7 @@ export const rowProperties: EditorButtonDefinition = {
|
|||||||
action(context: EditorUiContext) {
|
action(context: EditorUiContext) {
|
||||||
context.editor.getEditorState().read(() => {
|
context.editor.getEditorState().read(() => {
|
||||||
const rows = $getTableRowsFromSelection($getSelection());
|
const rows = $getTableRowsFromSelection($getSelection());
|
||||||
if ($isCustomTableRowNode(rows[0])) {
|
if ($isTableRowNode(rows[0])) {
|
||||||
$showRowPropertiesForm(rows[0], context);
|
$showRowPropertiesForm(rows[0], context);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -350,8 +347,8 @@ export const cellProperties: EditorButtonDefinition = {
|
|||||||
format: 'long',
|
format: 'long',
|
||||||
action(context: EditorUiContext) {
|
action(context: EditorUiContext) {
|
||||||
context.editor.getEditorState().read(() => {
|
context.editor.getEditorState().read(() => {
|
||||||
const cell = $getNodeFromSelection($getSelection(), $isCustomTableCellNode);
|
const cell = $getNodeFromSelection($getSelection(), $isTableCellNode);
|
||||||
if ($isCustomTableCellNode(cell)) {
|
if ($isTableCellNode(cell)) {
|
||||||
$showCellPropertiesForm(cell, context);
|
$showCellPropertiesForm(cell, context);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -387,7 +384,7 @@ export const splitCell: EditorButtonDefinition = {
|
|||||||
},
|
},
|
||||||
isActive: neverActive,
|
isActive: neverActive,
|
||||||
isDisabled(selection) {
|
isDisabled(selection) {
|
||||||
const cell = $getNodeFromSelection(selection, $isCustomTableCellNode) as TableCellNode|null;
|
const cell = $getNodeFromSelection(selection, $isTableCellNode) as TableCellNode|null;
|
||||||
if (cell) {
|
if (cell) {
|
||||||
const merged = cell.getRowSpan() > 1 || cell.getColSpan() > 1;
|
const merged = cell.getRowSpan() > 1 || cell.getColSpan() > 1;
|
||||||
return !merged;
|
return !merged;
|
||||||
|
@ -5,7 +5,6 @@ import {
|
|||||||
EditorSelectFormFieldDefinition
|
EditorSelectFormFieldDefinition
|
||||||
} from "../../framework/forms";
|
} from "../../framework/forms";
|
||||||
import {EditorUiContext} from "../../framework/core";
|
import {EditorUiContext} from "../../framework/core";
|
||||||
import {CustomTableCellNode} from "../../../nodes/custom-table-cell";
|
|
||||||
import {EditorFormModal} from "../../framework/modals";
|
import {EditorFormModal} from "../../framework/modals";
|
||||||
import {$getSelection, ElementFormatType} from "lexical";
|
import {$getSelection, ElementFormatType} from "lexical";
|
||||||
import {
|
import {
|
||||||
@ -16,8 +15,8 @@ import {
|
|||||||
$setTableCellColumnWidth
|
$setTableCellColumnWidth
|
||||||
} from "../../../utils/tables";
|
} from "../../../utils/tables";
|
||||||
import {formatSizeValue} from "../../../utils/dom";
|
import {formatSizeValue} from "../../../utils/dom";
|
||||||
import {CustomTableRowNode} from "../../../nodes/custom-table-row";
|
import {TableCellNode, TableNode, TableRowNode} from "@lexical/table";
|
||||||
import {CustomTableNode} from "../../../nodes/custom-table";
|
import {CommonBlockAlignment} from "../../../nodes/_common";
|
||||||
|
|
||||||
const borderStyleInput: EditorSelectFormFieldDefinition = {
|
const borderStyleInput: EditorSelectFormFieldDefinition = {
|
||||||
label: 'Border style',
|
label: 'Border style',
|
||||||
@ -62,14 +61,14 @@ const alignmentInput: EditorSelectFormFieldDefinition = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export function $showCellPropertiesForm(cell: CustomTableCellNode, context: EditorUiContext): EditorFormModal {
|
export function $showCellPropertiesForm(cell: TableCellNode, context: EditorUiContext): EditorFormModal {
|
||||||
const styles = cell.getStyles();
|
const styles = cell.getStyles();
|
||||||
const modalForm = context.manager.createModal('cell_properties');
|
const modalForm = context.manager.createModal('cell_properties');
|
||||||
modalForm.show({
|
modalForm.show({
|
||||||
width: $getTableCellColumnWidth(context.editor, cell),
|
width: $getTableCellColumnWidth(context.editor, cell),
|
||||||
height: styles.get('height') || '',
|
height: styles.get('height') || '',
|
||||||
type: cell.getTag(),
|
type: cell.getTag(),
|
||||||
h_align: cell.getFormatType(),
|
h_align: cell.getAlignment(),
|
||||||
v_align: styles.get('vertical-align') || '',
|
v_align: styles.get('vertical-align') || '',
|
||||||
border_width: styles.get('border-width') || '',
|
border_width: styles.get('border-width') || '',
|
||||||
border_style: styles.get('border-style') || '',
|
border_style: styles.get('border-style') || '',
|
||||||
@ -89,7 +88,7 @@ export const cellProperties: EditorFormDefinition = {
|
|||||||
|
|
||||||
$setTableCellColumnWidth(cell, width);
|
$setTableCellColumnWidth(cell, width);
|
||||||
cell.updateTag(formData.get('type')?.toString() || '');
|
cell.updateTag(formData.get('type')?.toString() || '');
|
||||||
cell.setFormat((formData.get('h_align')?.toString() || '') as ElementFormatType);
|
cell.setAlignment((formData.get('h_align')?.toString() || '') as CommonBlockAlignment);
|
||||||
|
|
||||||
const styles = cell.getStyles();
|
const styles = cell.getStyles();
|
||||||
styles.set('height', formatSizeValue(formData.get('height')?.toString() || ''));
|
styles.set('height', formatSizeValue(formData.get('height')?.toString() || ''));
|
||||||
@ -172,7 +171,7 @@ export const cellProperties: EditorFormDefinition = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export function $showRowPropertiesForm(row: CustomTableRowNode, context: EditorUiContext): EditorFormModal {
|
export function $showRowPropertiesForm(row: TableRowNode, context: EditorUiContext): EditorFormModal {
|
||||||
const styles = row.getStyles();
|
const styles = row.getStyles();
|
||||||
const modalForm = context.manager.createModal('row_properties');
|
const modalForm = context.manager.createModal('row_properties');
|
||||||
modalForm.show({
|
modalForm.show({
|
||||||
@ -216,7 +215,7 @@ export const rowProperties: EditorFormDefinition = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export function $showTablePropertiesForm(table: CustomTableNode, context: EditorUiContext): EditorFormModal {
|
export function $showTablePropertiesForm(table: TableNode, context: EditorUiContext): EditorFormModal {
|
||||||
const styles = table.getStyles();
|
const styles = table.getStyles();
|
||||||
const modalForm = context.manager.createModal('table_properties');
|
const modalForm = context.manager.createModal('table_properties');
|
||||||
modalForm.show({
|
modalForm.show({
|
||||||
@ -229,7 +228,7 @@ export function $showTablePropertiesForm(table: CustomTableNode, context: Editor
|
|||||||
border_color: styles.get('border-color') || '',
|
border_color: styles.get('border-color') || '',
|
||||||
background_color: styles.get('background-color') || '',
|
background_color: styles.get('background-color') || '',
|
||||||
// caption: '', TODO
|
// caption: '', TODO
|
||||||
align: table.getFormatType(),
|
align: table.getAlignment(),
|
||||||
});
|
});
|
||||||
return modalForm;
|
return modalForm;
|
||||||
}
|
}
|
||||||
@ -253,12 +252,12 @@ export const tableProperties: EditorFormDefinition = {
|
|||||||
styles.set('background-color', formData.get('background_color')?.toString() || '');
|
styles.set('background-color', formData.get('background_color')?.toString() || '');
|
||||||
table.setStyles(styles);
|
table.setStyles(styles);
|
||||||
|
|
||||||
table.setFormat(formData.get('align') as ElementFormatType);
|
table.setAlignment(formData.get('align') as CommonBlockAlignment);
|
||||||
|
|
||||||
const cellPadding = (formData.get('cell_padding')?.toString() || '');
|
const cellPadding = (formData.get('cell_padding')?.toString() || '');
|
||||||
if (cellPadding) {
|
if (cellPadding) {
|
||||||
const cellPaddingFormatted = formatSizeValue(cellPadding);
|
const cellPaddingFormatted = formatSizeValue(cellPadding);
|
||||||
$forEachTableCell(table, (cell: CustomTableCellNode) => {
|
$forEachTableCell(table, (cell: TableCellNode) => {
|
||||||
const styles = cell.getStyles();
|
const styles = cell.getStyles();
|
||||||
styles.set('padding', cellPaddingFormatted);
|
styles.set('padding', cellPaddingFormatted);
|
||||||
cell.setStyles(styles);
|
cell.setStyles(styles);
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import {EditorUiElement} from "../core";
|
import {EditorUiElement} from "../core";
|
||||||
import {$createTableNodeWithDimensions} from "@lexical/table";
|
import {$createTableNodeWithDimensions} from "@lexical/table";
|
||||||
import {CustomTableNode} from "../../../nodes/custom-table";
|
|
||||||
import {$insertNewBlockNodeAtSelection} from "../../../utils/selection";
|
import {$insertNewBlockNodeAtSelection} from "../../../utils/selection";
|
||||||
import {el} from "../../../utils/dom";
|
import {el} from "../../../utils/dom";
|
||||||
|
|
||||||
@ -78,7 +77,7 @@ export class EditorTableCreator extends EditorUiElement {
|
|||||||
const colWidths = Array(columns).fill(targetColWidth + 'px');
|
const colWidths = Array(columns).fill(targetColWidth + 'px');
|
||||||
|
|
||||||
this.getContext().editor.update(() => {
|
this.getContext().editor.update(() => {
|
||||||
const table = $createTableNodeWithDimensions(rows, columns, false) as CustomTableNode;
|
const table = $createTableNodeWithDimensions(rows, columns, false);
|
||||||
table.setColWidths(colWidths);
|
table.setColWidths(colWidths);
|
||||||
$insertNewBlockNodeAtSelection(table);
|
$insertNewBlockNodeAtSelection(table);
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import {$getNearestNodeFromDOMNode, LexicalEditor} from "lexical";
|
import {$getNearestNodeFromDOMNode, LexicalEditor} from "lexical";
|
||||||
import {MouseDragTracker, MouseDragTrackerDistance} from "./mouse-drag-tracker";
|
import {MouseDragTracker, MouseDragTrackerDistance} from "./mouse-drag-tracker";
|
||||||
import {CustomTableNode} from "../../../nodes/custom-table";
|
import {TableNode, TableRowNode} from "@lexical/table";
|
||||||
import {TableRowNode} from "@lexical/table";
|
|
||||||
import {el} from "../../../utils/dom";
|
import {el} from "../../../utils/dom";
|
||||||
import {$getTableColumnWidth, $setTableColumnWidth} from "../../../utils/tables";
|
import {$getTableColumnWidth, $setTableColumnWidth} from "../../../utils/tables";
|
||||||
|
|
||||||
@ -148,7 +147,7 @@ class TableResizer {
|
|||||||
|
|
||||||
_this.editor.update(() => {
|
_this.editor.update(() => {
|
||||||
const table = $getNearestNodeFromDOMNode(parentTable);
|
const table = $getNearestNodeFromDOMNode(parentTable);
|
||||||
if (table instanceof CustomTableNode) {
|
if (table instanceof TableNode) {
|
||||||
const originalWidth = $getTableColumnWidth(_this.editor, table, cellIndex);
|
const originalWidth = $getTableColumnWidth(_this.editor, table, cellIndex);
|
||||||
const newWidth = Math.max(originalWidth + change, 10);
|
const newWidth = Math.max(originalWidth + change, 10);
|
||||||
$setTableColumnWidth(table, cellIndex, newWidth);
|
$setTableColumnWidth(table, cellIndex, newWidth);
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import {$getNodeByKey, LexicalEditor} from "lexical";
|
import {$getNodeByKey, LexicalEditor} from "lexical";
|
||||||
import {NodeKey} from "lexical/LexicalNode";
|
import {NodeKey} from "lexical/LexicalNode";
|
||||||
import {
|
import {
|
||||||
|
$isTableNode,
|
||||||
applyTableHandlers,
|
applyTableHandlers,
|
||||||
HTMLTableElementWithWithTableSelectionState,
|
HTMLTableElementWithWithTableSelectionState,
|
||||||
TableNode,
|
TableNode,
|
||||||
TableObserver
|
TableObserver
|
||||||
} from "@lexical/table";
|
} from "@lexical/table";
|
||||||
import {$isCustomTableNode, CustomTableNode} from "../../../nodes/custom-table";
|
|
||||||
|
|
||||||
// File adapted from logic in:
|
// File adapted from logic in:
|
||||||
// https://github.com/facebook/lexical/blob/f373759a7849f473d34960a6bf4e34b2a011e762/packages/lexical-react/src/LexicalTablePlugin.ts#L49
|
// https://github.com/facebook/lexical/blob/f373759a7849f473d34960a6bf4e34b2a011e762/packages/lexical-react/src/LexicalTablePlugin.ts#L49
|
||||||
@ -25,12 +25,12 @@ class TableSelectionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected init() {
|
protected init() {
|
||||||
this.unregisterMutationListener = this.editor.registerMutationListener(CustomTableNode, (mutations) => {
|
this.unregisterMutationListener = this.editor.registerMutationListener(TableNode, (mutations) => {
|
||||||
for (const [nodeKey, mutation] of mutations) {
|
for (const [nodeKey, mutation] of mutations) {
|
||||||
if (mutation === 'created') {
|
if (mutation === 'created') {
|
||||||
this.editor.getEditorState().read(() => {
|
this.editor.getEditorState().read(() => {
|
||||||
const tableNode = $getNodeByKey<CustomTableNode>(nodeKey);
|
const tableNode = $getNodeByKey<TableNode>(nodeKey);
|
||||||
if ($isCustomTableNode(tableNode)) {
|
if ($isTableNode(tableNode)) {
|
||||||
this.initializeTableNode(tableNode);
|
this.initializeTableNode(tableNode);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,24 +1,28 @@
|
|||||||
import {NodeClipboard} from "./node-clipboard";
|
import {NodeClipboard} from "./node-clipboard";
|
||||||
import {CustomTableRowNode} from "../nodes/custom-table-row";
|
|
||||||
import {$getTableCellsFromSelection, $getTableFromSelection, $getTableRowsFromSelection} from "./tables";
|
import {$getTableCellsFromSelection, $getTableFromSelection, $getTableRowsFromSelection} from "./tables";
|
||||||
import {$getSelection, BaseSelection, LexicalEditor} from "lexical";
|
import {$getSelection, BaseSelection, LexicalEditor} from "lexical";
|
||||||
import {$createCustomTableCellNode, $isCustomTableCellNode, CustomTableCellNode} from "../nodes/custom-table-cell";
|
|
||||||
import {CustomTableNode} from "../nodes/custom-table";
|
|
||||||
import {TableMap} from "./table-map";
|
import {TableMap} from "./table-map";
|
||||||
import {$isTableSelection} from "@lexical/table";
|
import {
|
||||||
|
$createTableCellNode,
|
||||||
|
$isTableCellNode,
|
||||||
|
$isTableSelection,
|
||||||
|
TableCellNode,
|
||||||
|
TableNode,
|
||||||
|
TableRowNode
|
||||||
|
} from "@lexical/table";
|
||||||
import {$getNodeFromSelection} from "./selection";
|
import {$getNodeFromSelection} from "./selection";
|
||||||
|
|
||||||
const rowClipboard: NodeClipboard<CustomTableRowNode> = new NodeClipboard<CustomTableRowNode>();
|
const rowClipboard: NodeClipboard<TableRowNode> = new NodeClipboard<TableRowNode>();
|
||||||
|
|
||||||
export function isRowClipboardEmpty(): boolean {
|
export function isRowClipboardEmpty(): boolean {
|
||||||
return rowClipboard.size() === 0;
|
return rowClipboard.size() === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validateRowsToCopy(rows: CustomTableRowNode[]): void {
|
export function validateRowsToCopy(rows: TableRowNode[]): void {
|
||||||
let commonRowSize: number|null = null;
|
let commonRowSize: number|null = null;
|
||||||
|
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
const cells = row.getChildren().filter(n => $isCustomTableCellNode(n));
|
const cells = row.getChildren().filter(n => $isTableCellNode(n));
|
||||||
let rowSize = 0;
|
let rowSize = 0;
|
||||||
for (const cell of cells) {
|
for (const cell of cells) {
|
||||||
rowSize += cell.getColSpan() || 1;
|
rowSize += cell.getColSpan() || 1;
|
||||||
@ -35,10 +39,10 @@ export function validateRowsToCopy(rows: CustomTableRowNode[]): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validateRowsToPaste(rows: CustomTableRowNode[], targetTable: CustomTableNode): void {
|
export function validateRowsToPaste(rows: TableRowNode[], targetTable: TableNode): void {
|
||||||
const tableColCount = (new TableMap(targetTable)).columnCount;
|
const tableColCount = (new TableMap(targetTable)).columnCount;
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
const cells = row.getChildren().filter(n => $isCustomTableCellNode(n));
|
const cells = row.getChildren().filter(n => $isTableCellNode(n));
|
||||||
let rowSize = 0;
|
let rowSize = 0;
|
||||||
for (const cell of cells) {
|
for (const cell of cells) {
|
||||||
rowSize += cell.getColSpan() || 1;
|
rowSize += cell.getColSpan() || 1;
|
||||||
@ -49,7 +53,7 @@ export function validateRowsToPaste(rows: CustomTableRowNode[], targetTable: Cus
|
|||||||
}
|
}
|
||||||
|
|
||||||
while (rowSize < tableColCount) {
|
while (rowSize < tableColCount) {
|
||||||
row.append($createCustomTableCellNode());
|
row.append($createTableCellNode());
|
||||||
rowSize++;
|
rowSize++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -98,11 +102,11 @@ export function $pasteClipboardRowsAfter(editor: LexicalEditor): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const columnClipboard: NodeClipboard<CustomTableCellNode>[] = [];
|
const columnClipboard: NodeClipboard<TableCellNode>[] = [];
|
||||||
|
|
||||||
function setColumnClipboard(columns: CustomTableCellNode[][]): void {
|
function setColumnClipboard(columns: TableCellNode[][]): void {
|
||||||
const newClipboards = columns.map(cells => {
|
const newClipboards = columns.map(cells => {
|
||||||
const clipboard = new NodeClipboard<CustomTableCellNode>();
|
const clipboard = new NodeClipboard<TableCellNode>();
|
||||||
clipboard.set(...cells);
|
clipboard.set(...cells);
|
||||||
return clipboard;
|
return clipboard;
|
||||||
});
|
});
|
||||||
@ -122,9 +126,9 @@ function $getSelectionColumnRange(selection: BaseSelection|null): TableRange|nul
|
|||||||
return {from: shape.fromX, to: shape.toX};
|
return {from: shape.fromX, to: shape.toX};
|
||||||
}
|
}
|
||||||
|
|
||||||
const cell = $getNodeFromSelection(selection, $isCustomTableCellNode);
|
const cell = $getNodeFromSelection(selection, $isTableCellNode);
|
||||||
const table = $getTableFromSelection(selection);
|
const table = $getTableFromSelection(selection);
|
||||||
if (!$isCustomTableCellNode(cell) || !table) {
|
if (!$isTableCellNode(cell) || !table) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,7 +141,7 @@ function $getSelectionColumnRange(selection: BaseSelection|null): TableRange|nul
|
|||||||
return {from: range.fromX, to: range.toX};
|
return {from: range.fromX, to: range.toX};
|
||||||
}
|
}
|
||||||
|
|
||||||
function $getTableColumnCellsFromSelection(range: TableRange, table: CustomTableNode): CustomTableCellNode[][] {
|
function $getTableColumnCellsFromSelection(range: TableRange, table: TableNode): TableCellNode[][] {
|
||||||
const map = new TableMap(table);
|
const map = new TableMap(table);
|
||||||
const columns = [];
|
const columns = [];
|
||||||
for (let x = range.from; x <= range.to; x++) {
|
for (let x = range.from; x <= range.to; x++) {
|
||||||
@ -148,7 +152,7 @@ function $getTableColumnCellsFromSelection(range: TableRange, table: CustomTable
|
|||||||
return columns;
|
return columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateColumnsToCopy(columns: CustomTableCellNode[][]): void {
|
function validateColumnsToCopy(columns: TableCellNode[][]): void {
|
||||||
let commonColSize: number|null = null;
|
let commonColSize: number|null = null;
|
||||||
|
|
||||||
for (const cells of columns) {
|
for (const cells of columns) {
|
||||||
@ -203,7 +207,7 @@ export function $copySelectedColumnsToClipboard(): void {
|
|||||||
setColumnClipboard(columns);
|
setColumnClipboard(columns);
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateColumnsToPaste(columns: CustomTableCellNode[][], targetTable: CustomTableNode) {
|
function validateColumnsToPaste(columns: TableCellNode[][], targetTable: TableNode) {
|
||||||
const tableRowCount = (new TableMap(targetTable)).rowCount;
|
const tableRowCount = (new TableMap(targetTable)).rowCount;
|
||||||
for (const cells of columns) {
|
for (const cells of columns) {
|
||||||
let colSize = 0;
|
let colSize = 0;
|
||||||
@ -216,7 +220,7 @@ function validateColumnsToPaste(columns: CustomTableCellNode[][], targetTable: C
|
|||||||
}
|
}
|
||||||
|
|
||||||
while (colSize < tableRowCount) {
|
while (colSize < tableRowCount) {
|
||||||
cells.push($createCustomTableCellNode());
|
cells.push($createTableCellNode());
|
||||||
colSize++;
|
colSize++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import {CustomTableNode} from "../nodes/custom-table";
|
import {$isTableCellNode, $isTableRowNode, TableCellNode, TableNode} from "@lexical/table";
|
||||||
import {$isCustomTableCellNode, CustomTableCellNode} from "../nodes/custom-table-cell";
|
|
||||||
import {$isTableRowNode} from "@lexical/table";
|
|
||||||
|
|
||||||
export type CellRange = {
|
export type CellRange = {
|
||||||
fromX: number;
|
fromX: number;
|
||||||
@ -16,15 +14,15 @@ export class TableMap {
|
|||||||
|
|
||||||
// Represents an array (rows*columns in length) of cell nodes from top-left to
|
// Represents an array (rows*columns in length) of cell nodes from top-left to
|
||||||
// bottom right. Cells may repeat where merged and covering multiple spaces.
|
// bottom right. Cells may repeat where merged and covering multiple spaces.
|
||||||
cells: CustomTableCellNode[] = [];
|
cells: TableCellNode[] = [];
|
||||||
|
|
||||||
constructor(table: CustomTableNode) {
|
constructor(table: TableNode) {
|
||||||
this.buildCellMap(table);
|
this.buildCellMap(table);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected buildCellMap(table: CustomTableNode) {
|
protected buildCellMap(table: TableNode) {
|
||||||
const rowsAndCells: CustomTableCellNode[][] = [];
|
const rowsAndCells: TableCellNode[][] = [];
|
||||||
const setCell = (x: number, y: number, cell: CustomTableCellNode) => {
|
const setCell = (x: number, y: number, cell: TableCellNode) => {
|
||||||
if (typeof rowsAndCells[y] === 'undefined') {
|
if (typeof rowsAndCells[y] === 'undefined') {
|
||||||
rowsAndCells[y] = [];
|
rowsAndCells[y] = [];
|
||||||
}
|
}
|
||||||
@ -36,7 +34,7 @@ export class TableMap {
|
|||||||
const rowNodes = table.getChildren().filter(r => $isTableRowNode(r));
|
const rowNodes = table.getChildren().filter(r => $isTableRowNode(r));
|
||||||
for (let rowIndex = 0; rowIndex < rowNodes.length; rowIndex++) {
|
for (let rowIndex = 0; rowIndex < rowNodes.length; rowIndex++) {
|
||||||
const rowNode = rowNodes[rowIndex];
|
const rowNode = rowNodes[rowIndex];
|
||||||
const cellNodes = rowNode.getChildren().filter(c => $isCustomTableCellNode(c));
|
const cellNodes = rowNode.getChildren().filter(c => $isTableCellNode(c));
|
||||||
let targetColIndex: number = 0;
|
let targetColIndex: number = 0;
|
||||||
for (let cellIndex = 0; cellIndex < cellNodes.length; cellIndex++) {
|
for (let cellIndex = 0; cellIndex < cellNodes.length; cellIndex++) {
|
||||||
const cellNode = cellNodes[cellIndex];
|
const cellNode = cellNodes[cellIndex];
|
||||||
@ -60,7 +58,7 @@ export class TableMap {
|
|||||||
this.columnCount = Math.max(...rowsAndCells.map(r => r.length));
|
this.columnCount = Math.max(...rowsAndCells.map(r => r.length));
|
||||||
|
|
||||||
const cells = [];
|
const cells = [];
|
||||||
let lastCell: CustomTableCellNode = rowsAndCells[0][0];
|
let lastCell: TableCellNode = rowsAndCells[0][0];
|
||||||
for (let y = 0; y < this.rowCount; y++) {
|
for (let y = 0; y < this.rowCount; y++) {
|
||||||
for (let x = 0; x < this.columnCount; x++) {
|
for (let x = 0; x < this.columnCount; x++) {
|
||||||
if (!rowsAndCells[y] || !rowsAndCells[y][x]) {
|
if (!rowsAndCells[y] || !rowsAndCells[y][x]) {
|
||||||
@ -75,7 +73,7 @@ export class TableMap {
|
|||||||
this.cells = cells;
|
this.cells = cells;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCellAtPosition(x: number, y: number): CustomTableCellNode {
|
public getCellAtPosition(x: number, y: number): TableCellNode {
|
||||||
const position = (y * this.columnCount) + x;
|
const position = (y * this.columnCount) + x;
|
||||||
if (position >= this.cells.length) {
|
if (position >= this.cells.length) {
|
||||||
throw new Error(`TableMap Error: Attempted to get cell ${position+1} of ${this.cells.length}`);
|
throw new Error(`TableMap Error: Attempted to get cell ${position+1} of ${this.cells.length}`);
|
||||||
@ -84,13 +82,13 @@ export class TableMap {
|
|||||||
return this.cells[position];
|
return this.cells[position];
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCellsInRange(range: CellRange): CustomTableCellNode[] {
|
public getCellsInRange(range: CellRange): TableCellNode[] {
|
||||||
const minX = Math.max(Math.min(range.fromX, range.toX), 0);
|
const minX = Math.max(Math.min(range.fromX, range.toX), 0);
|
||||||
const maxX = Math.min(Math.max(range.fromX, range.toX), this.columnCount - 1);
|
const maxX = Math.min(Math.max(range.fromX, range.toX), this.columnCount - 1);
|
||||||
const minY = Math.max(Math.min(range.fromY, range.toY), 0);
|
const minY = Math.max(Math.min(range.fromY, range.toY), 0);
|
||||||
const maxY = Math.min(Math.max(range.fromY, range.toY), this.rowCount - 1);
|
const maxY = Math.min(Math.max(range.fromY, range.toY), this.rowCount - 1);
|
||||||
|
|
||||||
const cells = new Set<CustomTableCellNode>();
|
const cells = new Set<TableCellNode>();
|
||||||
|
|
||||||
for (let y = minY; y <= maxY; y++) {
|
for (let y = minY; y <= maxY; y++) {
|
||||||
for (let x = minX; x <= maxX; x++) {
|
for (let x = minX; x <= maxX; x++) {
|
||||||
@ -101,7 +99,7 @@ export class TableMap {
|
|||||||
return [...cells.values()];
|
return [...cells.values()];
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCellsInColumn(columnIndex: number): CustomTableCellNode[] {
|
public getCellsInColumn(columnIndex: number): TableCellNode[] {
|
||||||
return this.getCellsInRange({
|
return this.getCellsInRange({
|
||||||
fromX: columnIndex,
|
fromX: columnIndex,
|
||||||
toX: columnIndex,
|
toX: columnIndex,
|
||||||
@ -110,7 +108,7 @@ export class TableMap {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public getRangeForCell(cell: CustomTableCellNode): CellRange|null {
|
public getRangeForCell(cell: TableCellNode): CellRange|null {
|
||||||
let range: CellRange|null = null;
|
let range: CellRange|null = null;
|
||||||
const cellKey = cell.getKey();
|
const cellKey = cell.getKey();
|
||||||
|
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
import {BaseSelection, LexicalEditor} from "lexical";
|
import {BaseSelection, LexicalEditor} from "lexical";
|
||||||
import {$isTableRowNode, $isTableSelection, TableRowNode, TableSelection, TableSelectionShape} from "@lexical/table";
|
import {
|
||||||
import {$isCustomTableNode, CustomTableNode} from "../nodes/custom-table";
|
$isTableCellNode,
|
||||||
import {$isCustomTableCellNode, CustomTableCellNode} from "../nodes/custom-table-cell";
|
$isTableNode,
|
||||||
|
$isTableRowNode,
|
||||||
|
$isTableSelection, TableCellNode, TableNode,
|
||||||
|
TableRowNode,
|
||||||
|
TableSelection,
|
||||||
|
} from "@lexical/table";
|
||||||
import {$getParentOfType} from "./nodes";
|
import {$getParentOfType} from "./nodes";
|
||||||
import {$getNodeFromSelection} from "./selection";
|
import {$getNodeFromSelection} from "./selection";
|
||||||
import {formatSizeValue} from "./dom";
|
import {formatSizeValue} from "./dom";
|
||||||
import {TableMap} from "./table-map";
|
import {TableMap} from "./table-map";
|
||||||
import {$isCustomTableRowNode, CustomTableRowNode} from "../nodes/custom-table-row";
|
|
||||||
|
|
||||||
function $getTableFromCell(cell: CustomTableCellNode): CustomTableNode|null {
|
function $getTableFromCell(cell: TableCellNode): TableNode|null {
|
||||||
return $getParentOfType(cell, $isCustomTableNode) as CustomTableNode|null;
|
return $getParentOfType(cell, $isTableNode) as TableNode|null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTableColumnWidths(table: HTMLTableElement): string[] {
|
export function getTableColumnWidths(table: HTMLTableElement): string[] {
|
||||||
@ -55,7 +59,7 @@ function extractWidthFromElement(element: HTMLElement): string {
|
|||||||
return width || '';
|
return width || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function $setTableColumnWidth(node: CustomTableNode, columnIndex: number, width: number|string): void {
|
export function $setTableColumnWidth(node: TableNode, columnIndex: number, width: number|string): void {
|
||||||
const rows = node.getChildren() as TableRowNode[];
|
const rows = node.getChildren() as TableRowNode[];
|
||||||
let maxCols = 0;
|
let maxCols = 0;
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
@ -78,7 +82,7 @@ export function $setTableColumnWidth(node: CustomTableNode, columnIndex: number,
|
|||||||
node.setColWidths(colWidths);
|
node.setColWidths(colWidths);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function $getTableColumnWidth(editor: LexicalEditor, node: CustomTableNode, columnIndex: number): number {
|
export function $getTableColumnWidth(editor: LexicalEditor, node: TableNode, columnIndex: number): number {
|
||||||
const colWidths = node.getColWidths();
|
const colWidths = node.getColWidths();
|
||||||
if (colWidths.length > columnIndex && colWidths[columnIndex].endsWith('px')) {
|
if (colWidths.length > columnIndex && colWidths[columnIndex].endsWith('px')) {
|
||||||
return Number(colWidths[columnIndex].replace('px', ''));
|
return Number(colWidths[columnIndex].replace('px', ''));
|
||||||
@ -97,14 +101,14 @@ export function $getTableColumnWidth(editor: LexicalEditor, node: CustomTableNod
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function $getCellColumnIndex(node: CustomTableCellNode): number {
|
function $getCellColumnIndex(node: TableCellNode): number {
|
||||||
const row = node.getParent();
|
const row = node.getParent();
|
||||||
if (!$isTableRowNode(row)) {
|
if (!$isTableRowNode(row)) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let index = 0;
|
let index = 0;
|
||||||
const cells = row.getChildren<CustomTableCellNode>();
|
const cells = row.getChildren<TableCellNode>();
|
||||||
for (const cell of cells) {
|
for (const cell of cells) {
|
||||||
let colSpan = cell.getColSpan() || 1;
|
let colSpan = cell.getColSpan() || 1;
|
||||||
index += colSpan;
|
index += colSpan;
|
||||||
@ -116,7 +120,7 @@ function $getCellColumnIndex(node: CustomTableCellNode): number {
|
|||||||
return index - 1;
|
return index - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function $setTableCellColumnWidth(cell: CustomTableCellNode, width: string): void {
|
export function $setTableCellColumnWidth(cell: TableCellNode, width: string): void {
|
||||||
const table = $getTableFromCell(cell)
|
const table = $getTableFromCell(cell)
|
||||||
const index = $getCellColumnIndex(cell);
|
const index = $getCellColumnIndex(cell);
|
||||||
|
|
||||||
@ -125,7 +129,7 @@ export function $setTableCellColumnWidth(cell: CustomTableCellNode, width: strin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function $getTableCellColumnWidth(editor: LexicalEditor, cell: CustomTableCellNode): string {
|
export function $getTableCellColumnWidth(editor: LexicalEditor, cell: TableCellNode): string {
|
||||||
const table = $getTableFromCell(cell)
|
const table = $getTableFromCell(cell)
|
||||||
const index = $getCellColumnIndex(cell);
|
const index = $getCellColumnIndex(cell);
|
||||||
if (!table) {
|
if (!table) {
|
||||||
@ -136,13 +140,13 @@ export function $getTableCellColumnWidth(editor: LexicalEditor, cell: CustomTabl
|
|||||||
return (widths.length > index) ? widths[index] : '';
|
return (widths.length > index) ? widths[index] : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function $getTableCellsFromSelection(selection: BaseSelection|null): CustomTableCellNode[] {
|
export function $getTableCellsFromSelection(selection: BaseSelection|null): TableCellNode[] {
|
||||||
if ($isTableSelection(selection)) {
|
if ($isTableSelection(selection)) {
|
||||||
const nodes = selection.getNodes();
|
const nodes = selection.getNodes();
|
||||||
return nodes.filter(n => $isCustomTableCellNode(n));
|
return nodes.filter(n => $isTableCellNode(n));
|
||||||
}
|
}
|
||||||
|
|
||||||
const cell = $getNodeFromSelection(selection, $isCustomTableCellNode) as CustomTableCellNode;
|
const cell = $getNodeFromSelection(selection, $isTableCellNode) as TableCellNode;
|
||||||
return cell ? [cell] : [];
|
return cell ? [cell] : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,12 +197,12 @@ export function $mergeTableCellsInSelection(selection: TableSelection): void {
|
|||||||
firstCell.setRowSpan(newHeight);
|
firstCell.setRowSpan(newHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function $getTableRowsFromSelection(selection: BaseSelection|null): CustomTableRowNode[] {
|
export function $getTableRowsFromSelection(selection: BaseSelection|null): TableRowNode[] {
|
||||||
const cells = $getTableCellsFromSelection(selection);
|
const cells = $getTableCellsFromSelection(selection);
|
||||||
const rowsByKey: Record<string, CustomTableRowNode> = {};
|
const rowsByKey: Record<string, TableRowNode> = {};
|
||||||
for (const cell of cells) {
|
for (const cell of cells) {
|
||||||
const row = cell.getParent();
|
const row = cell.getParent();
|
||||||
if ($isCustomTableRowNode(row)) {
|
if ($isTableRowNode(row)) {
|
||||||
rowsByKey[row.getKey()] = row;
|
rowsByKey[row.getKey()] = row;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -206,28 +210,28 @@ export function $getTableRowsFromSelection(selection: BaseSelection|null): Custo
|
|||||||
return Object.values(rowsByKey);
|
return Object.values(rowsByKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function $getTableFromSelection(selection: BaseSelection|null): CustomTableNode|null {
|
export function $getTableFromSelection(selection: BaseSelection|null): TableNode|null {
|
||||||
const cells = $getTableCellsFromSelection(selection);
|
const cells = $getTableCellsFromSelection(selection);
|
||||||
if (cells.length === 0) {
|
if (cells.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const table = $getParentOfType(cells[0], $isCustomTableNode);
|
const table = $getParentOfType(cells[0], $isTableNode);
|
||||||
if ($isCustomTableNode(table)) {
|
if ($isTableNode(table)) {
|
||||||
return table;
|
return table;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function $clearTableSizes(table: CustomTableNode): void {
|
export function $clearTableSizes(table: TableNode): void {
|
||||||
table.setColWidths([]);
|
table.setColWidths([]);
|
||||||
|
|
||||||
// TODO - Extra form things once table properties and extra things
|
// TODO - Extra form things once table properties and extra things
|
||||||
// are supported
|
// are supported
|
||||||
|
|
||||||
for (const row of table.getChildren()) {
|
for (const row of table.getChildren()) {
|
||||||
if (!$isCustomTableRowNode(row)) {
|
if (!$isTableRowNode(row)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,7 +240,7 @@ export function $clearTableSizes(table: CustomTableNode): void {
|
|||||||
rowStyles.delete('width');
|
rowStyles.delete('width');
|
||||||
row.setStyles(rowStyles);
|
row.setStyles(rowStyles);
|
||||||
|
|
||||||
const cells = row.getChildren().filter(c => $isCustomTableCellNode(c));
|
const cells = row.getChildren().filter(c => $isTableCellNode(c));
|
||||||
for (const cell of cells) {
|
for (const cell of cells) {
|
||||||
const cellStyles = cell.getStyles();
|
const cellStyles = cell.getStyles();
|
||||||
cellStyles.delete('height');
|
cellStyles.delete('height');
|
||||||
@ -247,23 +251,21 @@ export function $clearTableSizes(table: CustomTableNode): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function $clearTableFormatting(table: CustomTableNode): void {
|
export function $clearTableFormatting(table: TableNode): void {
|
||||||
table.setColWidths([]);
|
table.setColWidths([]);
|
||||||
table.setStyles(new Map);
|
table.setStyles(new Map);
|
||||||
|
|
||||||
for (const row of table.getChildren()) {
|
for (const row of table.getChildren()) {
|
||||||
if (!$isCustomTableRowNode(row)) {
|
if (!$isTableRowNode(row)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
row.setStyles(new Map);
|
row.setStyles(new Map);
|
||||||
row.setFormat('');
|
|
||||||
|
|
||||||
const cells = row.getChildren().filter(c => $isCustomTableCellNode(c));
|
const cells = row.getChildren().filter(c => $isTableCellNode(c));
|
||||||
for (const cell of cells) {
|
for (const cell of cells) {
|
||||||
cell.setStyles(new Map);
|
cell.setStyles(new Map);
|
||||||
cell.clearWidth();
|
cell.clearWidth();
|
||||||
cell.setFormat('');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -272,14 +274,14 @@ export function $clearTableFormatting(table: CustomTableNode): void {
|
|||||||
* Perform the given callback for each cell in the given table.
|
* Perform the given callback for each cell in the given table.
|
||||||
* Returning false from the callback stops the function early.
|
* Returning false from the callback stops the function early.
|
||||||
*/
|
*/
|
||||||
export function $forEachTableCell(table: CustomTableNode, callback: (c: CustomTableCellNode) => void|false): void {
|
export function $forEachTableCell(table: TableNode, callback: (c: TableCellNode) => void|false): void {
|
||||||
outer: for (const row of table.getChildren()) {
|
outer: for (const row of table.getChildren()) {
|
||||||
if (!$isCustomTableRowNode(row)) {
|
if (!$isTableRowNode(row)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const cells = row.getChildren();
|
const cells = row.getChildren();
|
||||||
for (const cell of cells) {
|
for (const cell of cells) {
|
||||||
if (!$isCustomTableCellNode(cell)) {
|
if (!$isTableCellNode(cell)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const result = callback(cell);
|
const result = callback(cell);
|
||||||
@ -290,10 +292,10 @@ export function $forEachTableCell(table: CustomTableNode, callback: (c: CustomTa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function $getCellPaddingForTable(table: CustomTableNode): string {
|
export function $getCellPaddingForTable(table: TableNode): string {
|
||||||
let padding: string|null = null;
|
let padding: string|null = null;
|
||||||
|
|
||||||
$forEachTableCell(table, (cell: CustomTableCellNode) => {
|
$forEachTableCell(table, (cell: TableCellNode) => {
|
||||||
const cellPadding = cell.getStyles().get('padding') || ''
|
const cellPadding = cell.getStyles().get('padding') || ''
|
||||||
if (padding === null) {
|
if (padding === null) {
|
||||||
padding = cellPadding;
|
padding = cellPadding;
|
||||||
|
Reference in New Issue
Block a user