Lexical: Added cell width fetching, Created custom row node

This commit is contained in:
Dan Brown 2024-08-09 11:24:25 +01:00
parent e8532ef4de
commit da54e1d87c
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
8 changed files with 172 additions and 61 deletions

View File

@ -20,13 +20,14 @@ import {
TableCellNode
} from "@lexical/table";
import {TableCellHeaderState} from "@lexical/table/LexicalTableCellNode";
import {createStyleMapFromDomStyles, StyleMap} from "../utils/styles";
export type SerializedCustomTableCellNode = Spread<{
styles: Record<string, string>,
}, SerializedTableCellNode>
export class CustomTableCellNode extends TableCellNode {
__styles: Map<string, string> = new Map;
__styles: StyleMap = new Map;
static getType(): string {
return 'custom-table-cell';
@ -44,12 +45,12 @@ export class CustomTableCellNode extends TableCellNode {
return cellNode;
}
getStyles(): Map<string, string> {
getStyles(): StyleMap {
const self = this.getLatest();
return new Map(self.__styles);
}
setStyles(styles: Map<string, string>): void {
setStyles(styles: StyleMap): void {
const self = this.getWritable();
self.__styles = new Map(styles);
}
@ -103,7 +104,7 @@ export class CustomTableCellNode extends TableCellNode {
serializedNode.width,
);
node.setStyles(new Map<string, string>(Object.entries(serializedNode.styles)));
node.setStyles(new Map(Object.entries(serializedNode.styles)));
return node;
}
@ -121,12 +122,7 @@ function $convertCustomTableCellNodeElement(domNode: Node): DOMConversionOutput
const output = $convertTableCellNodeElement(domNode);
if (domNode instanceof HTMLElement && output.node instanceof CustomTableCellNode) {
const styleMap = new Map<string, string>();
const styleNames = Array.from(domNode.style);
for (const style of styleNames) {
styleMap.set(style, domNode.style.getPropertyValue(style));
}
output.node.setStyles(styleMap);
output.node.setStyles(createStyleMapFromDomStyles(domNode.style));
}
return output;

View File

@ -0,0 +1,113 @@
import {
$createParagraphNode,
$isElementNode,
$isLineBreakNode,
$isTextNode,
DOMConversionMap,
DOMConversionOutput,
EditorConfig,
LexicalNode,
Spread
} from "lexical";
import {
$createTableCellNode,
$isTableCellNode,
SerializedTableRowNode,
TableCellHeaderStates,
TableRowNode
} from "@lexical/table";
import {createStyleMapFromDomStyles, StyleMap} from "../utils/styles";
import {NodeKey} from "lexical/LexicalNode";
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(createStyleMapFromDomStyles(domNode.style));
}
return {node: rowNode};
}
export function $createCustomTableRowNode(): CustomTableRowNode {
return new CustomTableRowNode();
}
export function $isCustomTableRowNode(node: LexicalNode | null | undefined): node is CustomTableRowNode {
return node instanceof CustomTableRowNode;
}

View File

@ -20,7 +20,8 @@ import {DiagramNode} from "./diagram";
import {EditorUiContext} from "../ui/framework/core";
import {MediaNode} from "./media";
import {CustomListItemNode} from "./custom-list-item";
import {CustomTableCellNode} from "./custom-table-cell-node";
import {CustomTableCellNode} from "./custom-table-cell";
import {CustomTableRowNode} from "./custom-table-row";
/**
* Load the nodes for lexical.
@ -33,7 +34,7 @@ export function getNodesForPageEditor(): (KlassConstructor<typeof LexicalNode> |
ListNode, // Todo - Create custom
CustomListItemNode,
CustomTableNode,
TableRowNode,
CustomTableRowNode,
CustomTableCellNode,
ImageNode,
HorizontalRuleNode,
@ -49,6 +50,12 @@ export function getNodesForPageEditor(): (KlassConstructor<typeof LexicalNode> |
return new CustomParagraphNode();
}
},
{
replace: ListItemNode,
with: (node: ListItemNode) => {
return new CustomListItemNode(node.__value, node.__checked);
}
},
{
replace: TableNode,
with(node: TableNode) {
@ -56,9 +63,9 @@ export function getNodesForPageEditor(): (KlassConstructor<typeof LexicalNode> |
}
},
{
replace: ListItemNode,
with: (node: ListItemNode) => {
return new CustomListItemNode(node.__value, node.__checked);
replace: TableRowNode,
with(node: TableRowNode) {
return new CustomTableRowNode();
}
},
{

View File

@ -19,8 +19,8 @@ import {
} from "@lexical/table";
import {$getNodeFromSelection, $selectionContainsNodeType} from "../../../utils/selection";
import {$getParentOfType} from "../../../utils/nodes";
import {$isCustomTableCellNode} from "../../../nodes/custom-table-cell-node";
import {showCellPropertiesForm} from "../forms/tables";
import {$isCustomTableCellNode} from "../../../nodes/custom-table-cell";
import {$showCellPropertiesForm} from "../forms/tables";
import {$mergeTableCellsInSelection} from "../../../utils/tables";
const neverActive = (): boolean => false;
@ -317,7 +317,7 @@ export const cellProperties: EditorButtonDefinition = {
context.editor.getEditorState().read(() => {
const cell = $getNodeFromSelection($getSelection(), $isCustomTableCellNode);
if ($isCustomTableCellNode(cell)) {
showCellPropertiesForm(cell, context);
$showCellPropertiesForm(cell, context);
}
});
},

View File

@ -5,10 +5,10 @@ import {
EditorSelectFormFieldDefinition
} from "../../framework/forms";
import {EditorUiContext} from "../../framework/core";
import {CustomTableCellNode} from "../../../nodes/custom-table-cell-node";
import {CustomTableCellNode} from "../../../nodes/custom-table-cell";
import {EditorFormModal} from "../../framework/modals";
import {$getSelection, ElementFormatType} from "lexical";
import {$getTableCellsFromSelection, $setTableCellColumnWidth} from "../../../utils/tables";
import {$getTableCellColumnWidth, $getTableCellsFromSelection, $setTableCellColumnWidth} from "../../../utils/tables";
import {formatSizeValue} from "../../../utils/dom";
const borderStyleInput: EditorSelectFormFieldDefinition = {
@ -54,11 +54,11 @@ const alignmentInput: EditorSelectFormFieldDefinition = {
}
};
export function showCellPropertiesForm(cell: CustomTableCellNode, context: EditorUiContext): EditorFormModal {
export function $showCellPropertiesForm(cell: CustomTableCellNode, context: EditorUiContext): EditorFormModal {
const styles = cell.getStyles();
const modalForm = context.manager.createModal('cell_properties');
modalForm.show({
width: '', // TODO
width: $getTableCellColumnWidth(context.editor, cell),
height: styles.get('height') || '',
type: cell.getTag(),
h_align: cell.getFormatType(),
@ -171,45 +171,18 @@ export const rowProperties: EditorFormDefinition = {
return true;
},
fields: [
// Removed fields:
// Removed 'Row Type' as we don't currently support thead/tfoot elements
// TinyMCE would move rows up/down into these parents when set
// Removed 'Alignment' since this was broken in our editor (applied alignment class to whole parent table)
{
build() {
const generalFields: EditorFormFieldDefinition[] = [
{
label: 'Row type',
name: 'type',
type: 'select',
valuesByLabel: {
'Body': 'body',
'Header': 'header',
'Footer': 'footer',
}
} as EditorSelectFormFieldDefinition,
alignmentInput,
{
label: 'Height',
name: 'height',
type: 'text',
},
];
const advancedFields: EditorFormFieldDefinition[] = [
borderStyleInput,
borderColorInput,
backgroundColorInput,
];
return new EditorFormTabs([
{
label: 'General',
contents: generalFields,
},
{
label: 'Advanced',
contents: advancedFields,
}
])
}
label: 'Height', // style on tr: height
name: 'height',
type: 'text',
},
borderStyleInput, // style on tr: height
borderColorInput, // style on tr: height
backgroundColorInput, // style on tr: height
],
};
export const tableProperties: EditorFormDefinition = {

View File

@ -0,0 +1,11 @@
export type StyleMap = Map<string, string>;
export function createStyleMapFromDomStyles(domStyles: CSSStyleDeclaration): StyleMap {
const styleMap: StyleMap = new Map();
const styleNames: string[] = Array.from(domStyles);
for (const style of styleNames) {
styleMap.set(style, domStyles.getPropertyValue(style));
}
return styleMap;
}

View File

@ -1,5 +1,5 @@
import {CustomTableNode} from "../nodes/custom-table";
import {$isCustomTableCellNode, CustomTableCellNode} from "../nodes/custom-table-cell-node";
import {$isCustomTableCellNode, CustomTableCellNode} from "../nodes/custom-table-cell";
import {$isTableRowNode} from "@lexical/table";
export class TableMap {

View File

@ -1,7 +1,7 @@
import {BaseSelection, LexicalEditor} from "lexical";
import {$isTableRowNode, $isTableSelection, TableRowNode, TableSelection, TableSelectionShape} from "@lexical/table";
import {$isCustomTableNode, CustomTableNode} from "../nodes/custom-table";
import {$isCustomTableCellNode, CustomTableCellNode} from "../nodes/custom-table-cell-node";
import {$isCustomTableCellNode, CustomTableCellNode} from "../nodes/custom-table-cell";
import {$getParentOfType} from "./nodes";
import {$getNodeFromSelection} from "./selection";
import {formatSizeValue} from "./dom";
@ -124,6 +124,17 @@ export function $setTableCellColumnWidth(cell: CustomTableCellNode, width: strin
}
}
export function $getTableCellColumnWidth(editor: LexicalEditor, cell: CustomTableCellNode): string {
const table = $getTableFromCell(cell)
const index = $getCellColumnIndex(cell);
if (!table) {
return '';
}
const widths = table.getColWidths();
return (widths.length > index) ? widths[index] : '';
}
export function $getTableCellsFromSelection(selection: BaseSelection|null): CustomTableCellNode[] {
if ($isTableSelection(selection)) {
const nodes = selection.getNodes();