mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Lexical: Started table menu options
Updated UI elements to handle new scenarios needed in more complex table menu
This commit is contained in:
parent
13f8f39dd5
commit
6b06d490c5
@ -1,4 +1,4 @@
|
||||
import {$isListNode, ListItemNode, ListNode, SerializedListItemNode} from "@lexical/list";
|
||||
import {$isListNode, ListItemNode, SerializedListItemNode} from "@lexical/list";
|
||||
import {EditorConfig} from "lexical/LexicalEditor";
|
||||
import {DOMExportOutput, LexicalEditor, LexicalNode} from "lexical";
|
||||
import {el} from "../helpers";
|
||||
|
@ -2,13 +2,14 @@
|
||||
|
||||
## In progress
|
||||
|
||||
//
|
||||
- Table features
|
||||
- Continued table dropdown menu
|
||||
|
||||
## Main Todo
|
||||
|
||||
- Alignments: Use existing classes for blocks
|
||||
- Alignments: Handle inline block content (image, video)
|
||||
- Table features
|
||||
|
||||
- Image paste upload
|
||||
- Keyboard shortcuts support
|
||||
- Add ID support to all block types
|
||||
|
@ -8,17 +8,18 @@ import insertColumnBeforeIcon from "@icons/editor/table-insert-column-before.svg
|
||||
import insertRowAboveIcon from "@icons/editor/table-insert-row-above.svg";
|
||||
import insertRowBelowIcon from "@icons/editor/table-insert-row-below.svg";
|
||||
import {EditorUiContext} from "../../framework/core";
|
||||
import {$getBlockElementNodesInSelection, $getNodeFromSelection, $getParentOfType} from "../../../helpers";
|
||||
import {$getSelection} from "lexical";
|
||||
import {$isCustomTableNode, CustomTableNode} from "../../../nodes/custom-table";
|
||||
import {
|
||||
$deleteTableColumn, $deleteTableColumn__EXPERIMENTAL,
|
||||
$getNodeFromSelection,
|
||||
$selectionContainsNodeType
|
||||
} from "../../../helpers";
|
||||
import {$getSelection} from "lexical";
|
||||
import {$isCustomTableNode} from "../../../nodes/custom-table";
|
||||
import {
|
||||
$deleteTableColumn__EXPERIMENTAL,
|
||||
$deleteTableRow__EXPERIMENTAL,
|
||||
$getTableRowIndexFromTableCellNode, $insertTableColumn, $insertTableColumn__EXPERIMENTAL,
|
||||
$insertTableRow, $insertTableRow__EXPERIMENTAL,
|
||||
$isTableCellNode,
|
||||
$isTableRowNode,
|
||||
TableCellNode
|
||||
$insertTableColumn__EXPERIMENTAL,
|
||||
$insertTableRow__EXPERIMENTAL,
|
||||
$isTableNode,
|
||||
} from "@lexical/table";
|
||||
|
||||
|
||||
@ -43,6 +44,14 @@ export const deleteTable: EditorButtonDefinition = {
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteTableMenuAction: EditorButtonDefinition = {
|
||||
...deleteTable,
|
||||
format: 'long',
|
||||
isDisabled(selection) {
|
||||
return !$selectionContainsNodeType(selection, $isTableNode);
|
||||
},
|
||||
};
|
||||
|
||||
export const insertRowAbove: EditorButtonDefinition = {
|
||||
label: 'Insert row above',
|
||||
icon: insertRowAboveIcon,
|
||||
|
@ -3,22 +3,34 @@ import {handleDropdown} from "../helpers/dropdowns";
|
||||
import {EditorContainerUiElement, EditorUiElement} from "../core";
|
||||
import {EditorBasicButtonDefinition, EditorButton} from "../buttons";
|
||||
|
||||
export type EditorDropdownButtonOptions = {
|
||||
showOnHover?: boolean;
|
||||
direction?: 'vertical'|'horizontal';
|
||||
button: EditorBasicButtonDefinition|EditorButton;
|
||||
};
|
||||
|
||||
const defaultOptions: EditorDropdownButtonOptions = {
|
||||
showOnHover: false,
|
||||
direction: 'horizontal',
|
||||
button: {label: 'Menu'},
|
||||
}
|
||||
|
||||
export class EditorDropdownButton extends EditorContainerUiElement {
|
||||
protected button: EditorButton;
|
||||
protected childItems: EditorUiElement[];
|
||||
protected open: boolean = false;
|
||||
protected showOnHover: boolean = false;
|
||||
protected options: EditorDropdownButtonOptions;
|
||||
|
||||
constructor(button: EditorBasicButtonDefinition|EditorButton, showOnHover: boolean, children: EditorUiElement[]) {
|
||||
constructor(options: EditorDropdownButtonOptions, children: EditorUiElement[]) {
|
||||
super(children);
|
||||
this.childItems = children;
|
||||
this.showOnHover = showOnHover;
|
||||
this.options = Object.assign(defaultOptions, options);
|
||||
|
||||
if (button instanceof EditorButton) {
|
||||
this.button = button;
|
||||
if (options.button instanceof EditorButton) {
|
||||
this.button = options.button;
|
||||
} else {
|
||||
this.button = new EditorButton({
|
||||
...button,
|
||||
...options.button,
|
||||
action() {
|
||||
return false;
|
||||
},
|
||||
@ -41,7 +53,7 @@ export class EditorDropdownButton extends EditorContainerUiElement {
|
||||
|
||||
const childElements: HTMLElement[] = this.childItems.map(child => child.getDOMElement());
|
||||
const menu = el('div', {
|
||||
class: 'editor-dropdown-menu',
|
||||
class: `editor-dropdown-menu editor-dropdown-menu-${this.options.direction}`,
|
||||
hidden: 'true',
|
||||
}, childElements);
|
||||
|
||||
@ -50,7 +62,7 @@ export class EditorDropdownButton extends EditorContainerUiElement {
|
||||
}, [button, menu]);
|
||||
|
||||
handleDropdown({toggle : button, menu : menu,
|
||||
showOnHover: this.showOnHover,
|
||||
showOnHover: this.options.showOnHover,
|
||||
onOpen : () => {
|
||||
this.open = true;
|
||||
this.getContext().manager.triggerStateUpdateForElement(this.button);
|
||||
|
@ -7,7 +7,7 @@ export class EditorFormatMenu extends EditorContainerUiElement {
|
||||
buildDOM(): HTMLElement {
|
||||
const childElements: HTMLElement[] = this.getChildren().map(child => child.getDOMElement());
|
||||
const menu = el('div', {
|
||||
class: 'editor-format-menu-dropdown editor-dropdown-menu editor-menu-list',
|
||||
class: 'editor-format-menu-dropdown editor-dropdown-menu editor-dropdown-menu-vertical',
|
||||
hidden: 'true',
|
||||
}, childElements);
|
||||
|
||||
|
@ -15,9 +15,11 @@ export class EditorOverflowContainer extends EditorContainerUiElement {
|
||||
this.size = size;
|
||||
this.content = children;
|
||||
this.overflowButton = new EditorDropdownButton({
|
||||
button: {
|
||||
label: 'More',
|
||||
icon: moreHorizontal,
|
||||
}, false, []);
|
||||
},
|
||||
}, []);
|
||||
this.addChildren(this.overflowButton);
|
||||
}
|
||||
|
||||
|
@ -5,11 +5,13 @@ import {el} from "../../helpers";
|
||||
export interface EditorBasicButtonDefinition {
|
||||
label: string;
|
||||
icon?: string|undefined;
|
||||
format?: 'small' | 'long';
|
||||
}
|
||||
|
||||
export interface EditorButtonDefinition extends EditorBasicButtonDefinition {
|
||||
action: (context: EditorUiContext, button: EditorButton) => void;
|
||||
isActive: (selection: BaseSelection|null, context: EditorUiContext) => boolean;
|
||||
isDisabled?: (selection: BaseSelection|null, context: EditorUiContext) => boolean;
|
||||
setup?: (context: EditorUiContext, button: EditorButton) => void;
|
||||
}
|
||||
|
||||
@ -47,20 +49,27 @@ export class EditorButton extends EditorUiElement {
|
||||
}
|
||||
|
||||
protected buildDOM(): HTMLButtonElement {
|
||||
|
||||
const label = this.getLabel();
|
||||
let child: string|HTMLElement = label;
|
||||
if (this.definition.icon) {
|
||||
child = el('div', {class: 'editor-button-icon'});
|
||||
child.innerHTML = this.definition.icon;
|
||||
const format = this.definition.format || 'small';
|
||||
const children: (string|HTMLElement)[] = [];
|
||||
|
||||
if (this.definition.icon || format === 'long') {
|
||||
const icon = el('div', {class: 'editor-button-icon'});
|
||||
icon.innerHTML = this.definition.icon || '';
|
||||
children.push(icon);
|
||||
}
|
||||
|
||||
if (!this.definition.icon ||format === 'long') {
|
||||
const text = el('div', {class: 'editor-button-text'}, [label]);
|
||||
children.push(text);
|
||||
}
|
||||
|
||||
const button = el('button', {
|
||||
type: 'button',
|
||||
class: 'editor-button',
|
||||
class: `editor-button editor-button-${format}`,
|
||||
title: this.definition.icon ? label : null,
|
||||
disabled: this.disabled ? 'true' : null,
|
||||
}, [child]) as HTMLButtonElement;
|
||||
}, children) as HTMLButtonElement;
|
||||
|
||||
button.addEventListener('click', this.onClick.bind(this));
|
||||
|
||||
@ -71,11 +80,18 @@ export class EditorButton extends EditorUiElement {
|
||||
this.definition.action(this.getContext(), this);
|
||||
}
|
||||
|
||||
updateActiveState(selection: BaseSelection|null) {
|
||||
protected updateActiveState(selection: BaseSelection|null) {
|
||||
const isActive = this.definition.isActive(selection, this.getContext());
|
||||
this.setActiveState(isActive);
|
||||
}
|
||||
|
||||
protected updateDisabledState(selection: BaseSelection|null) {
|
||||
if (this.definition.isDisabled) {
|
||||
const isDisabled = this.definition.isDisabled(selection, this.getContext());
|
||||
this.toggleDisabled(isDisabled);
|
||||
}
|
||||
}
|
||||
|
||||
setActiveState(active: boolean) {
|
||||
this.active = active;
|
||||
this.dom?.classList.toggle('editor-button-active', this.active);
|
||||
@ -83,6 +99,7 @@ export class EditorButton extends EditorUiElement {
|
||||
|
||||
updateState(state: EditorUiStateUpdate): void {
|
||||
this.updateActiveState(state.selection);
|
||||
this.updateDisabledState(state.selection);
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {EditorButton} from "./framework/buttons";
|
||||
import {EditorContainerUiElement, EditorSimpleClassContainer, EditorUiElement} from "./framework/core";
|
||||
import {el} from "../helpers";
|
||||
import {$selectionContainsNodeType, el} from "../helpers";
|
||||
import {EditorFormatMenu} from "./framework/blocks/format-menu";
|
||||
import {FormatPreviewButton} from "./framework/blocks/format-preview-button";
|
||||
import {EditorDropdownButton} from "./framework/blocks/dropdown-button";
|
||||
@ -11,7 +11,7 @@ import {EditorOverflowContainer} from "./framework/blocks/overflow-container";
|
||||
import {
|
||||
deleteColumn,
|
||||
deleteRow,
|
||||
deleteTable, insertColumnAfter,
|
||||
deleteTable, deleteTableMenuAction, insertColumnAfter,
|
||||
insertColumnBefore,
|
||||
insertRowAbove,
|
||||
insertRowBelow,
|
||||
@ -50,6 +50,7 @@ import {
|
||||
link, media,
|
||||
unlink
|
||||
} from "./defaults/buttons/objects";
|
||||
import {$isTableNode} from "@lexical/table";
|
||||
|
||||
export function getMainEditorFullToolbar(): EditorContainerUiElement {
|
||||
return new EditorSimpleClassContainer('editor-toolbar-main', [
|
||||
@ -68,7 +69,7 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement {
|
||||
new FormatPreviewButton(el('h5'), h5),
|
||||
new FormatPreviewButton(el('blockquote'), blockquote),
|
||||
new FormatPreviewButton(el('p'), paragraph),
|
||||
new EditorDropdownButton({label: 'Callouts'}, true, [
|
||||
new EditorDropdownButton({button: {label: 'Callouts'}, showOnHover: true, direction: 'vertical'}, [
|
||||
new FormatPreviewButton(el('p', {class: 'callout info'}), infoCallout),
|
||||
new FormatPreviewButton(el('p', {class: 'callout success'}), successCallout),
|
||||
new FormatPreviewButton(el('p', {class: 'callout warning'}), warningCallout),
|
||||
@ -81,10 +82,10 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement {
|
||||
new EditorButton(bold),
|
||||
new EditorButton(italic),
|
||||
new EditorButton(underline),
|
||||
new EditorDropdownButton(new EditorColorButton(textColor, 'color'), false, [
|
||||
new EditorDropdownButton({ button: new EditorColorButton(textColor, 'color') }, [
|
||||
new EditorColorPicker('color'),
|
||||
]),
|
||||
new EditorDropdownButton(new EditorColorButton(highlightColor, 'background-color'), false, [
|
||||
new EditorDropdownButton({button: new EditorColorButton(highlightColor, 'background-color')}, [
|
||||
new EditorColorPicker('background-color'),
|
||||
]),
|
||||
new EditorButton(strikethrough),
|
||||
@ -112,9 +113,14 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement {
|
||||
// Insert types
|
||||
new EditorOverflowContainer(8, [
|
||||
new EditorButton(link),
|
||||
new EditorDropdownButton(table, false, [
|
||||
|
||||
new EditorDropdownButton({button: table, direction: 'vertical'}, [
|
||||
new EditorDropdownButton({button: {...table, format: 'long'}, showOnHover: true}, [
|
||||
new EditorTableCreator(),
|
||||
]),
|
||||
new EditorButton(deleteTableMenuAction),
|
||||
]),
|
||||
|
||||
new EditorButton(image),
|
||||
new EditorButton(horizontalRule),
|
||||
new EditorButton(codeBlock),
|
||||
|
@ -59,6 +59,18 @@ body.editor-is-fullscreen {
|
||||
background-color: #ceebff;
|
||||
color: #000;
|
||||
}
|
||||
.editor-button-long {
|
||||
display: flex !important;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
gap: .5rem;
|
||||
}
|
||||
.editor-button-text {
|
||||
font-weight: 400;
|
||||
color: #000;
|
||||
font-size: 12.2px;
|
||||
}
|
||||
.editor-button-format-preview {
|
||||
padding: 4px 6px;
|
||||
display: block;
|
||||
@ -84,21 +96,20 @@ body.editor-is-fullscreen {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.editor-menu-list {
|
||||
.editor-dropdown-menu-vertical {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
.editor-menu-list .editor-button {
|
||||
.editor-dropdown-menu-vertical .editor-button {
|
||||
border-bottom: 0;
|
||||
text-align: start;
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
.editor-menu-list > .editor-dropdown-menu-container .editor-dropdown-menu {
|
||||
.editor-dropdown-menu-vertical > .editor-dropdown-menu-container .editor-dropdown-menu {
|
||||
inset-inline-start: 100%;
|
||||
top: 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.editor-format-menu-toggle {
|
||||
|
Loading…
Reference in New Issue
Block a user