mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Lexical: Started linking up cell properties form
This commit is contained in:
parent
efec752985
commit
8939f310db
@ -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;
|
||||
}
|
90
resources/js/wysiwyg/nodes/custom-table-cell-node.ts
Normal file
90
resources/js/wysiwyg/nodes/custom-table-cell-node.ts
Normal 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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user