diff --git a/resources/js/wysiwyg/helpers.ts b/resources/js/wysiwyg/helpers.ts index 3708c2b25..600e71dd1 100644 --- a/resources/js/wysiwyg/helpers.ts +++ b/resources/js/wysiwyg/helpers.ts @@ -1,13 +1,13 @@ import { $createNodeSelection, $createParagraphNode, $getRoot, - $getSelection, + $getSelection, $isElementNode, $isTextNode, $setSelection, - BaseSelection, + BaseSelection, ElementFormatType, ElementNode, LexicalEditor, LexicalNode, TextFormatType } from "lexical"; -import {getNodesForPageEditor, LexicalElementNodeCreator, LexicalNodeMatcher} from "./nodes"; -import {$getNearestBlockElementAncestorOrThrow} from "@lexical/utils"; +import {LexicalElementNodeCreator, LexicalNodeMatcher} from "./nodes"; +import {$findMatchingParent, $getNearestBlockElementAncestorOrThrow} from "@lexical/utils"; import {$setBlocksType} from "@lexical/selection"; export function el(tag: string, attrs: Record = {}, children: (string|HTMLElement)[] = []): HTMLElement { @@ -114,4 +114,34 @@ export function selectionContainsNode(selection: BaseSelection|null, node: Lexic } return false; +} + +export function selectionContainsElementFormat(selection: BaseSelection|null, format: ElementFormatType): boolean { + const nodes = getBlockElementNodesInSelection(selection); + for (const node of nodes) { + if (node.getFormatType() === format) { + return true; + } + } + + return false; +} + +export function getBlockElementNodesInSelection(selection: BaseSelection|null): ElementNode[] { + if (!selection) { + return []; + } + + const blockNodes: Map = new Map(); + for (const node of selection.getNodes()) { + const blockElement = $findMatchingParent(node, (node) => { + return $isElementNode(node) && !node.isInline(); + }) as ElementNode|null; + + if (blockElement) { + blockNodes.set(blockElement.getKey(), blockElement); + } + } + + return Array.from(blockNodes.values()); } \ No newline at end of file diff --git a/resources/js/wysiwyg/todo.md b/resources/js/wysiwyg/todo.md new file mode 100644 index 000000000..67b5fb780 --- /dev/null +++ b/resources/js/wysiwyg/todo.md @@ -0,0 +1,27 @@ +# Lexical based editor todo + +## Main Todo + +- Alignments: Use existing classes for blocks +- Alignments: Handle inline block content (image, video) +- Add Type: Video/media/embed +- Add Type: Drawings +- Handle toolbars on scroll +- Table features +- Image paste upload +- Keyboard shortcuts support +- Global/shared editor events support +- Draft/change management (connect with page editor component) +- Add ID support to all block types +- Template drag & drop / insert +- Video attachment drop / insert +- Task list render/import from existing format +- Link popup menu for cross-content reference +- Link heading-based ID reference menu +- Image gallery integration for insert +- Image gallery integration for form + +## Bugs + +- Image resizing currently bugged, maybe change to ghost resizer in decorator instead of updating core node. +- Table resize bars often floating around in wrong place, and shows on hover or interrupts mouse actions. \ No newline at end of file diff --git a/resources/js/wysiwyg/ui/defaults/button-definitions.ts b/resources/js/wysiwyg/ui/defaults/button-definitions.ts index c6ea85b0d..d1d22dae1 100644 --- a/resources/js/wysiwyg/ui/defaults/button-definitions.ts +++ b/resources/js/wysiwyg/ui/defaults/button-definitions.ts @@ -1,15 +1,28 @@ import {EditorBasicButtonDefinition, EditorButton, EditorButtonDefinition} from "../framework/buttons"; import { $createNodeSelection, - $createParagraphNode, $createTextNode, $getRoot, $getSelection, - $isParagraphNode, $isTextNode, $setSelection, - BaseSelection, CAN_REDO_COMMAND, CAN_UNDO_COMMAND, COMMAND_PRIORITY_LOW, ElementNode, FORMAT_TEXT_COMMAND, + $createParagraphNode, + $createTextNode, + $getRoot, + $getSelection, + $isParagraphNode, + $isTextNode, + $setSelection, + BaseSelection, + CAN_REDO_COMMAND, + CAN_UNDO_COMMAND, + COMMAND_PRIORITY_LOW, + ElementFormatType, + ElementNode, + FORMAT_TEXT_COMMAND, LexicalNode, - REDO_COMMAND, TextFormatType, + REDO_COMMAND, + TextFormatType, UNDO_COMMAND } from "lexical"; import { - getNodeFromSelection, insertNewBlockNodeAtSelection, + getBlockElementNodesInSelection, + getNodeFromSelection, insertNewBlockNodeAtSelection, selectionContainsElementFormat, selectionContainsNodeType, selectionContainsTextFormat, toggleSelectionBlockNodeType @@ -29,31 +42,35 @@ import {$isImageNode, ImageNode} from "../../nodes/image"; import {$createDetailsNode, $isDetailsNode} from "../../nodes/details"; import {getEditorContentAsHtml} from "../../actions"; import {$isListNode, insertList, ListNode, ListType, removeList} from "@lexical/list"; -import undoIcon from "@icons/editor/undo.svg" -import redoIcon from "@icons/editor/redo.svg" -import boldIcon from "@icons/editor/bold.svg" -import italicIcon from "@icons/editor/italic.svg" -import underlinedIcon from "@icons/editor/underlined.svg" +import undoIcon from "@icons/editor/undo.svg"; +import redoIcon from "@icons/editor/redo.svg"; +import boldIcon from "@icons/editor/bold.svg"; +import italicIcon from "@icons/editor/italic.svg"; +import underlinedIcon from "@icons/editor/underlined.svg"; import textColorIcon from "@icons/editor/text-color.svg"; import highlightIcon from "@icons/editor/highlighter.svg"; -import strikethroughIcon from "@icons/editor/strikethrough.svg" -import superscriptIcon from "@icons/editor/superscript.svg" -import subscriptIcon from "@icons/editor/subscript.svg" -import codeIcon from "@icons/editor/code.svg" -import formatClearIcon from "@icons/editor/format-clear.svg" -import listBulletIcon from "@icons/editor/list-bullet.svg" -import listNumberedIcon from "@icons/editor/list-numbered.svg" -import listCheckIcon from "@icons/editor/list-check.svg" -import linkIcon from "@icons/editor/link.svg" -import unlinkIcon from "@icons/editor/unlink.svg" -import tableIcon from "@icons/editor/table.svg" -import imageIcon from "@icons/editor/image.svg" -import horizontalRuleIcon from "@icons/editor/horizontal-rule.svg" -import codeBlockIcon from "@icons/editor/code-block.svg" -import detailsIcon from "@icons/editor/details.svg" -import sourceIcon from "@icons/editor/source-view.svg" -import fullscreenIcon from "@icons/editor/fullscreen.svg" -import editIcon from "@icons/edit.svg" +import strikethroughIcon from "@icons/editor/strikethrough.svg"; +import superscriptIcon from "@icons/editor/superscript.svg"; +import subscriptIcon from "@icons/editor/subscript.svg"; +import codeIcon from "@icons/editor/code.svg"; +import formatClearIcon from "@icons/editor/format-clear.svg"; +import alignLeftIcon from "@icons/editor/align-left.svg"; +import alignCenterIcon from "@icons/editor/align-center.svg"; +import alignRightIcon from "@icons/editor/align-right.svg"; +import alignJustifyIcon from "@icons/editor/align-justify.svg"; +import listBulletIcon from "@icons/editor/list-bullet.svg"; +import listNumberedIcon from "@icons/editor/list-numbered.svg"; +import listCheckIcon from "@icons/editor/list-check.svg"; +import linkIcon from "@icons/editor/link.svg"; +import unlinkIcon from "@icons/editor/unlink.svg"; +import tableIcon from "@icons/editor/table.svg"; +import imageIcon from "@icons/editor/image.svg"; +import horizontalRuleIcon from "@icons/editor/horizontal-rule.svg"; +import codeBlockIcon from "@icons/editor/code-block.svg"; +import detailsIcon from "@icons/editor/details.svg"; +import sourceIcon from "@icons/editor/source-view.svg"; +import fullscreenIcon from "@icons/editor/fullscreen.svg"; +import editIcon from "@icons/edit.svg"; import {$createHorizontalRuleNode, $isHorizontalRuleNode} from "../../nodes/horizontal-rule"; import {$createCodeBlockNode, $isCodeBlockNode, $openCodeEditorForNode, CodeBlockNode} from "../../nodes/code-block"; @@ -203,6 +220,59 @@ export const clearFormating: EditorButtonDefinition = { } }; +function setAlignmentForSection(alignment: ElementFormatType): void { + const selection = $getSelection(); + const elements = getBlockElementNodesInSelection(selection); + for (const node of elements) { + node.setFormat(alignment); + } +} + +export const alignLeft: EditorButtonDefinition = { + label: 'Align left', + icon: alignLeftIcon, + action(context: EditorUiContext) { + context.editor.update(() => setAlignmentForSection('left')); + }, + isActive(selection: BaseSelection|null) { + return selectionContainsElementFormat(selection, 'left'); + } +}; + +export const alignCenter: EditorButtonDefinition = { + label: 'Align center', + icon: alignCenterIcon, + action(context: EditorUiContext) { + context.editor.update(() => setAlignmentForSection('center')); + }, + isActive(selection: BaseSelection|null) { + return selectionContainsElementFormat(selection, 'center'); + } +}; + +export const alignRight: EditorButtonDefinition = { + label: 'Align right', + icon: alignRightIcon, + action(context: EditorUiContext) { + context.editor.update(() => setAlignmentForSection('right')); + }, + isActive(selection: BaseSelection|null) { + return selectionContainsElementFormat(selection, 'right'); + } +}; + +export const alignJustify: EditorButtonDefinition = { + label: 'Align justify', + icon: alignJustifyIcon, + action(context: EditorUiContext) { + context.editor.update(() => setAlignmentForSection('justify')); + }, + isActive(selection: BaseSelection|null) { + return selectionContainsElementFormat(selection, 'justify'); + } +}; + + function buildListButton(label: string, type: ListType, icon: string): EditorButtonDefinition { return { label, diff --git a/resources/js/wysiwyg/ui/toolbars.ts b/resources/js/wysiwyg/ui/toolbars.ts index d512b58e2..9145b8761 100644 --- a/resources/js/wysiwyg/ui/toolbars.ts +++ b/resources/js/wysiwyg/ui/toolbars.ts @@ -1,5 +1,8 @@ import {EditorButton} from "./framework/buttons"; import { + alignCenter, alignJustify, + alignLeft, + alignRight, blockquote, bold, bulletList, clearFormating, code, codeBlock, dangerCallout, details, editCodeBlock, fullscreen, h2, h3, h4, h5, highlightColor, horizontalRule, image, @@ -62,6 +65,14 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement { new EditorButton(clearFormating), ]), + // Alignment + new EditorOverflowContainer(4, [ + new EditorButton(alignLeft), + new EditorButton(alignCenter), + new EditorButton(alignRight), + new EditorButton(alignJustify), + ]), + // Lists new EditorOverflowContainer(3, [ new EditorButton(bulletList),