]> BookStack Code Mirror - bookstack/commitdiff
Lexical: Added list support, started todo
authorDan Brown <redacted>
Wed, 17 Jul 2024 15:38:20 +0000 (16:38 +0100)
committerDan Brown <redacted>
Wed, 17 Jul 2024 15:38:20 +0000 (16:38 +0100)
resources/js/wysiwyg/helpers.ts
resources/js/wysiwyg/todo.md [new file with mode: 0644]
resources/js/wysiwyg/ui/defaults/button-definitions.ts
resources/js/wysiwyg/ui/toolbars.ts

index 3708c2b25917a375e6b8c98fc60e3d1a8f5cd154..600e71dd1ccc589d996f65276d34e4a704bff358 100644 (file)
@@ -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<string, string|null> = {}, 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<string, ElementNode> = 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 (file)
index 0000000..67b5fb7
--- /dev/null
@@ -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
index c6ea85b0d9e83788267fe4f53f9a1b7dfedcb37c..d1d22dae1eaff400b554e559dbb42e17141a5fe7 100644 (file)
@@ -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,
index d512b58e23d1de5e7f4704fef06a3f48ba632120..9145b8761931439d4cf80724bf53791ef3012327 100644 (file)
@@ -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),
Morty Proxy This is a proxified and sanitized view of the page, visit original site.