Lexical: Started linking up cell properties form

This commit is contained in:
Dan Brown 2024-08-05 15:08:52 +01:00
parent efec752985
commit 8939f310db
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
6 changed files with 177 additions and 35 deletions

View File

@ -93,6 +93,6 @@ export function $createCustomParagraphNode() {
return new CustomParagraphNode();
}
export function $isCustomParagraphNode(node: LexicalNode | null | undefined) {
export function $isCustomParagraphNode(node: LexicalNode | null | undefined): node is CustomParagraphNode {
return node instanceof CustomParagraphNode;
}

View File

@ -0,0 +1,90 @@
import {EditorConfig} from "lexical/LexicalEditor";
import {DOMExportOutput, LexicalEditor, LexicalNode, Spread} from "lexical";
import {SerializedTableCellNode, TableCellHeaderStates, TableCellNode} from "@lexical/table";
import {TableCellHeaderState} from "@lexical/table/LexicalTableCellNode";
export type SerializedCustomTableCellNode = Spread<{
styles: Record<string, string>,
}, SerializedTableCellNode>
export class CustomTableCellNode extends TableCellNode {
__styles: Map<string, string> = new Map;
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);
return cellNode;
}
getStyles(): Map<string, string> {
const self = this.getLatest();
return new Map(self.__styles);
}
setStyles(styles: Map<string, string>): void {
const self = this.getWritable();
self.__styles = new Map(styles);
}
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);
}
return element;
}
// TODO - Import DOM
updateDOM(prevNode: CustomTableCellNode): boolean {
return super.updateDOM(prevNode)
|| this.__styles !== prevNode.__styles;
}
exportDOM(editor: LexicalEditor): DOMExportOutput {
const element = this.createDOM(editor._config);
return {
element
};
}
exportJSON(): SerializedCustomTableCellNode {
return {
...super.exportJSON(),
type: 'custom-table-cell',
styles: Object.fromEntries(this.__styles),
};
}
}
export function $createCustomTableCellNode(
headerState: TableCellHeaderState,
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;
}

View File

@ -20,6 +20,7 @@ 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";
/**
* Load the nodes for lexical.
@ -33,7 +34,7 @@ export function getNodesForPageEditor(): (KlassConstructor<typeof LexicalNode> |
CustomListItemNode,
CustomTableNode,
TableRowNode,
TableCellNode,
CustomTableCellNode,
ImageNode,
HorizontalRuleNode,
DetailsNode, SummaryNode,
@ -59,7 +60,19 @@ export function getNodesForPageEditor(): (KlassConstructor<typeof LexicalNode> |
with: (node: ListItemNode) => {
return new CustomListItemNode(node.__value, node.__checked);
}
}
},
{
replace: TableCellNode,
with: (node: TableCellNode) => {
const cell = new CustomTableCellNode(
node.__headerState,
node.__colSpan,
node.__width,
);
cell.__rowSpan = node.__rowSpan;
return cell;
}
},
];
}

View File

@ -13,7 +13,7 @@
## Main Todo
- Alignments: Use existing classes for blocks
- Alignments: Use existing classes for blocks (including table cells)
- Alignments: Handle inline block content (image, video)
- Image paste upload
- Keyboard shortcuts support

View File

@ -11,18 +11,19 @@ import {EditorUiContext} from "../../framework/core";
import {$getSelection, BaseSelection} from "lexical";
import {$isCustomTableNode} from "../../../nodes/custom-table";
import {
$createTableRowNode,
$deleteTableColumn__EXPERIMENTAL,
$deleteTableRow__EXPERIMENTAL,
$insertTableColumn__EXPERIMENTAL,
$insertTableRow__EXPERIMENTAL, $isTableCellNode,
$isTableNode, $isTableRowNode, $isTableSelection, $unmergeCell, TableCellNode, TableNode,
$insertTableRow__EXPERIMENTAL,
$isTableNode, $isTableRowNode, $isTableSelection, $unmergeCell, TableCellNode,
} 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";
const neverActive = (): boolean => false;
const cellNotSelected = (selection: BaseSelection|null) => !$selectionContainsNodeType(selection, $isTableCellNode);
const cellNotSelected = (selection: BaseSelection|null) => !$selectionContainsNodeType(selection, $isCustomTableCellNode);
export const table: EditorBasicButtonDefinition = {
label: 'Table',
@ -34,8 +35,8 @@ export const tableProperties: EditorButtonDefinition = {
icon: tableIcon,
action(context: EditorUiContext) {
context.editor.getEditorState().read(() => {
const cell = $getNodeFromSelection($getSelection(), $isTableCellNode);
if (!$isTableCellNode(cell)) {
const cell = $getNodeFromSelection($getSelection(), $isCustomTableCellNode);
if (!$isCustomTableCellNode(cell)) {
return;
}
@ -54,8 +55,8 @@ export const clearTableFormatting: EditorButtonDefinition = {
format: 'long',
action(context: EditorUiContext) {
context.editor.getEditorState().read(() => {
const cell = $getNodeFromSelection($getSelection(), $isTableCellNode);
if (!$isTableCellNode(cell)) {
const cell = $getNodeFromSelection($getSelection(), $isCustomTableCellNode);
if (!$isCustomTableCellNode(cell)) {
return;
}
@ -72,8 +73,8 @@ export const resizeTableToContents: EditorButtonDefinition = {
format: 'long',
action(context: EditorUiContext) {
context.editor.getEditorState().read(() => {
const cell = $getNodeFromSelection($getSelection(), $isTableCellNode);
if (!$isTableCellNode(cell)) {
const cell = $getNodeFromSelection($getSelection(), $isCustomTableCellNode);
if (!$isCustomTableCellNode(cell)) {
return;
}
@ -159,8 +160,8 @@ export const rowProperties: EditorButtonDefinition = {
format: 'long',
action(context: EditorUiContext) {
context.editor.getEditorState().read(() => {
const cell = $getNodeFromSelection($getSelection(), $isTableCellNode);
if (!$isTableCellNode(cell)) {
const cell = $getNodeFromSelection($getSelection(), $isCustomTableCellNode);
if (!$isCustomTableCellNode(cell)) {
return;
}
@ -313,11 +314,9 @@ export const cellProperties: EditorButtonDefinition = {
label: 'Cell properties',
action(context: EditorUiContext) {
context.editor.getEditorState().read(() => {
const cell = $getNodeFromSelection($getSelection(), $isTableCellNode);
if ($isTableCellNode(cell)) {
const modalForm = context.manager.createModal('cell_properties');
modalForm.show({});
const cell = $getNodeFromSelection($getSelection(), $isCustomTableCellNode);
if ($isCustomTableCellNode(cell)) {
showCellPropertiesForm(cell, context);
}
});
},
@ -349,7 +348,7 @@ export const splitCell: EditorButtonDefinition = {
},
isActive: neverActive,
isDisabled(selection) {
const cell = $getNodeFromSelection(selection, $isTableCellNode) as TableCellNode|null;
const cell = $getNodeFromSelection(selection, $isCustomTableCellNode) as TableCellNode|null;
if (cell) {
const merged = cell.getRowSpan() > 1 || cell.getColSpan() > 1;
return !merged;

View File

@ -5,6 +5,11 @@ import {
EditorSelectFormFieldDefinition
} from "../../framework/forms";
import {EditorUiContext} from "../../framework/core";
import {$isCustomTableCellNode, CustomTableCellNode} from "../../../nodes/custom-table-cell-node";
import {EditorFormModal} from "../../framework/modals";
import {$getNodeFromSelection} from "../../../utils/selection";
import {$getSelection, ElementFormatType} from "lexical";
import {TableCellHeaderStates} from "@lexical/table";
const borderStyleInput: EditorSelectFormFieldDefinition = {
label: 'Border style',
@ -49,10 +54,46 @@ const alignmentInput: EditorSelectFormFieldDefinition = {
}
};
export function showCellPropertiesForm(cell: CustomTableCellNode, context: EditorUiContext): EditorFormModal {
const styles = cell.getStyles();
const modalForm = context.manager.createModal('cell_properties');
modalForm.show({
width: '', // TODO
height: styles.get('height') || '',
type: cell.getTag(),
h_align: '', // TODO
v_align: styles.get('vertical-align') || '',
border_width: styles.get('border-width') || '',
border_style: styles.get('border-style') || '',
border_color: styles.get('border-color') || '',
background_color: styles.get('background-color') || '',
});
return modalForm;
}
export const cellProperties: EditorFormDefinition = {
submitText: 'Save',
async action(formData, context: EditorUiContext) {
// TODO
// TODO - Set for cell selection range
context.editor.update(() => {
const cell = $getNodeFromSelection($getSelection(), $isCustomTableCellNode);
if ($isCustomTableCellNode(cell)) {
// TODO - Set width
cell.setFormat((formData.get('h_align')?.toString() || '') as ElementFormatType);
cell.updateTag(formData.get('type')?.toString() || '');
const styles = cell.getStyles();
styles.set('height', formData.get('height')?.toString() || '');
styles.set('vertical-align', formData.get('v_align')?.toString() || '');
styles.set('border-width', formData.get('border_width')?.toString() || '');
styles.set('border-style', formData.get('border_style')?.toString() || '');
styles.set('border-color', formData.get('border_color')?.toString() || '');
styles.set('background-color', formData.get('background_color')?.toString() || '');
cell.setStyles(styles);
}
});
return true;
},
fields: [
@ -60,31 +101,31 @@ export const cellProperties: EditorFormDefinition = {
build() {
const generalFields: EditorFormFieldDefinition[] = [
{
label: 'Width',
label: 'Width', // Colgroup width
name: 'width',
type: 'text',
},
{
label: 'Height',
label: 'Height', // inline-style: height
name: 'height',
type: 'text',
},
{
label: 'Cell type',
label: 'Cell type', // element
name: 'type',
type: 'select',
valuesByLabel: {
'Cell': 'cell',
'Header cell': 'header',
'Cell': 'td',
'Header cell': 'th',
}
} as EditorSelectFormFieldDefinition,
{
...alignmentInput,
...alignmentInput, // class: 'align-right/left/center'
label: 'Horizontal align',
name: 'h_align',
},
{
label: 'Vertical align',
label: 'Vertical align', // inline-style: vertical-align
name: 'v_align',
type: 'select',
valuesByLabel: {
@ -98,13 +139,13 @@ export const cellProperties: EditorFormDefinition = {
const advancedFields: EditorFormFieldDefinition[] = [
{
label: 'Border width',
label: 'Border width', // inline-style: border-width
name: 'border_width',
type: 'text',
},
borderStyleInput,
borderColorInput,
backgroundColorInput,
borderStyleInput, // inline-style: border-style
borderColorInput, // inline-style: border-color
backgroundColorInput, // inline-style: background-color
];
return new EditorFormTabs([
@ -170,7 +211,6 @@ export const rowProperties: EditorFormDefinition = {
},
],
};
export const tableProperties: EditorFormDefinition = {
submitText: 'Save',
async action(formData, context: EditorUiContext) {