mirror of
				https://github.com/BookStackApp/BookStack.git
				synced 2025-11-03 02:13:16 +03:00 
			
		
		
		
	Lexical: Added list support, started todo
This commit is contained in:
		@@ -1,13 +1,13 @@
 | 
				
			|||||||
import {
 | 
					import {
 | 
				
			||||||
    $createNodeSelection,
 | 
					    $createNodeSelection,
 | 
				
			||||||
    $createParagraphNode, $getRoot,
 | 
					    $createParagraphNode, $getRoot,
 | 
				
			||||||
    $getSelection,
 | 
					    $getSelection, $isElementNode,
 | 
				
			||||||
    $isTextNode, $setSelection,
 | 
					    $isTextNode, $setSelection,
 | 
				
			||||||
    BaseSelection,
 | 
					    BaseSelection, ElementFormatType, ElementNode,
 | 
				
			||||||
    LexicalEditor, LexicalNode, TextFormatType
 | 
					    LexicalEditor, LexicalNode, TextFormatType
 | 
				
			||||||
} from "lexical";
 | 
					} from "lexical";
 | 
				
			||||||
import {getNodesForPageEditor, LexicalElementNodeCreator, LexicalNodeMatcher} from "./nodes";
 | 
					import {LexicalElementNodeCreator, LexicalNodeMatcher} from "./nodes";
 | 
				
			||||||
import {$getNearestBlockElementAncestorOrThrow} from "@lexical/utils";
 | 
					import {$findMatchingParent, $getNearestBlockElementAncestorOrThrow} from "@lexical/utils";
 | 
				
			||||||
import {$setBlocksType} from "@lexical/selection";
 | 
					import {$setBlocksType} from "@lexical/selection";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function el(tag: string, attrs: Record<string, string|null> = {}, children: (string|HTMLElement)[] = []): HTMLElement {
 | 
					export function el(tag: string, attrs: Record<string, string|null> = {}, children: (string|HTMLElement)[] = []): HTMLElement {
 | 
				
			||||||
@@ -115,3 +115,33 @@ export function selectionContainsNode(selection: BaseSelection|null, node: Lexic
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return false;
 | 
					    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());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										27
									
								
								resources/js/wysiwyg/todo.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								resources/js/wysiwyg/todo.md
									
									
									
									
									
										Normal file
									
								
							@@ -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.
 | 
				
			||||||
@@ -1,15 +1,28 @@
 | 
				
			|||||||
import {EditorBasicButtonDefinition, EditorButton, EditorButtonDefinition} from "../framework/buttons";
 | 
					import {EditorBasicButtonDefinition, EditorButton, EditorButtonDefinition} from "../framework/buttons";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    $createNodeSelection,
 | 
					    $createNodeSelection,
 | 
				
			||||||
    $createParagraphNode, $createTextNode, $getRoot, $getSelection,
 | 
					    $createParagraphNode,
 | 
				
			||||||
    $isParagraphNode, $isTextNode, $setSelection,
 | 
					    $createTextNode,
 | 
				
			||||||
    BaseSelection, CAN_REDO_COMMAND, CAN_UNDO_COMMAND, COMMAND_PRIORITY_LOW, ElementNode, FORMAT_TEXT_COMMAND,
 | 
					    $getRoot,
 | 
				
			||||||
 | 
					    $getSelection,
 | 
				
			||||||
 | 
					    $isParagraphNode,
 | 
				
			||||||
 | 
					    $isTextNode,
 | 
				
			||||||
 | 
					    $setSelection,
 | 
				
			||||||
 | 
					    BaseSelection,
 | 
				
			||||||
 | 
					    CAN_REDO_COMMAND,
 | 
				
			||||||
 | 
					    CAN_UNDO_COMMAND,
 | 
				
			||||||
 | 
					    COMMAND_PRIORITY_LOW,
 | 
				
			||||||
 | 
					    ElementFormatType,
 | 
				
			||||||
 | 
					    ElementNode,
 | 
				
			||||||
 | 
					    FORMAT_TEXT_COMMAND,
 | 
				
			||||||
    LexicalNode,
 | 
					    LexicalNode,
 | 
				
			||||||
    REDO_COMMAND, TextFormatType,
 | 
					    REDO_COMMAND,
 | 
				
			||||||
 | 
					    TextFormatType,
 | 
				
			||||||
    UNDO_COMMAND
 | 
					    UNDO_COMMAND
 | 
				
			||||||
} from "lexical";
 | 
					} from "lexical";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    getNodeFromSelection, insertNewBlockNodeAtSelection,
 | 
					    getBlockElementNodesInSelection,
 | 
				
			||||||
 | 
					    getNodeFromSelection, insertNewBlockNodeAtSelection, selectionContainsElementFormat,
 | 
				
			||||||
    selectionContainsNodeType,
 | 
					    selectionContainsNodeType,
 | 
				
			||||||
    selectionContainsTextFormat,
 | 
					    selectionContainsTextFormat,
 | 
				
			||||||
    toggleSelectionBlockNodeType
 | 
					    toggleSelectionBlockNodeType
 | 
				
			||||||
@@ -29,31 +42,35 @@ import {$isImageNode, ImageNode} from "../../nodes/image";
 | 
				
			|||||||
import {$createDetailsNode, $isDetailsNode} from "../../nodes/details";
 | 
					import {$createDetailsNode, $isDetailsNode} from "../../nodes/details";
 | 
				
			||||||
import {getEditorContentAsHtml} from "../../actions";
 | 
					import {getEditorContentAsHtml} from "../../actions";
 | 
				
			||||||
import {$isListNode, insertList, ListNode, ListType, removeList} from "@lexical/list";
 | 
					import {$isListNode, insertList, ListNode, ListType, removeList} from "@lexical/list";
 | 
				
			||||||
import undoIcon from "@icons/editor/undo.svg"
 | 
					import undoIcon from "@icons/editor/undo.svg";
 | 
				
			||||||
import redoIcon from "@icons/editor/redo.svg"
 | 
					import redoIcon from "@icons/editor/redo.svg";
 | 
				
			||||||
import boldIcon from "@icons/editor/bold.svg"
 | 
					import boldIcon from "@icons/editor/bold.svg";
 | 
				
			||||||
import italicIcon from "@icons/editor/italic.svg"
 | 
					import italicIcon from "@icons/editor/italic.svg";
 | 
				
			||||||
import underlinedIcon from "@icons/editor/underlined.svg"
 | 
					import underlinedIcon from "@icons/editor/underlined.svg";
 | 
				
			||||||
import textColorIcon from "@icons/editor/text-color.svg";
 | 
					import textColorIcon from "@icons/editor/text-color.svg";
 | 
				
			||||||
import highlightIcon from "@icons/editor/highlighter.svg";
 | 
					import highlightIcon from "@icons/editor/highlighter.svg";
 | 
				
			||||||
import strikethroughIcon from "@icons/editor/strikethrough.svg"
 | 
					import strikethroughIcon from "@icons/editor/strikethrough.svg";
 | 
				
			||||||
import superscriptIcon from "@icons/editor/superscript.svg"
 | 
					import superscriptIcon from "@icons/editor/superscript.svg";
 | 
				
			||||||
import subscriptIcon from "@icons/editor/subscript.svg"
 | 
					import subscriptIcon from "@icons/editor/subscript.svg";
 | 
				
			||||||
import codeIcon from "@icons/editor/code.svg"
 | 
					import codeIcon from "@icons/editor/code.svg";
 | 
				
			||||||
import formatClearIcon from "@icons/editor/format-clear.svg"
 | 
					import formatClearIcon from "@icons/editor/format-clear.svg";
 | 
				
			||||||
import listBulletIcon from "@icons/editor/list-bullet.svg"
 | 
					import alignLeftIcon from "@icons/editor/align-left.svg";
 | 
				
			||||||
import listNumberedIcon from "@icons/editor/list-numbered.svg"
 | 
					import alignCenterIcon from "@icons/editor/align-center.svg";
 | 
				
			||||||
import listCheckIcon from "@icons/editor/list-check.svg"
 | 
					import alignRightIcon from "@icons/editor/align-right.svg";
 | 
				
			||||||
import linkIcon from "@icons/editor/link.svg"
 | 
					import alignJustifyIcon from "@icons/editor/align-justify.svg";
 | 
				
			||||||
import unlinkIcon from "@icons/editor/unlink.svg"
 | 
					import listBulletIcon from "@icons/editor/list-bullet.svg";
 | 
				
			||||||
import tableIcon from "@icons/editor/table.svg"
 | 
					import listNumberedIcon from "@icons/editor/list-numbered.svg";
 | 
				
			||||||
import imageIcon from "@icons/editor/image.svg"
 | 
					import listCheckIcon from "@icons/editor/list-check.svg";
 | 
				
			||||||
import horizontalRuleIcon from "@icons/editor/horizontal-rule.svg"
 | 
					import linkIcon from "@icons/editor/link.svg";
 | 
				
			||||||
import codeBlockIcon from "@icons/editor/code-block.svg"
 | 
					import unlinkIcon from "@icons/editor/unlink.svg";
 | 
				
			||||||
import detailsIcon from "@icons/editor/details.svg"
 | 
					import tableIcon from "@icons/editor/table.svg";
 | 
				
			||||||
import sourceIcon from "@icons/editor/source-view.svg"
 | 
					import imageIcon from "@icons/editor/image.svg";
 | 
				
			||||||
import fullscreenIcon from "@icons/editor/fullscreen.svg"
 | 
					import horizontalRuleIcon from "@icons/editor/horizontal-rule.svg";
 | 
				
			||||||
import editIcon from "@icons/edit.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 {$createHorizontalRuleNode, $isHorizontalRuleNode} from "../../nodes/horizontal-rule";
 | 
				
			||||||
import {$createCodeBlockNode, $isCodeBlockNode, $openCodeEditorForNode, CodeBlockNode} from "../../nodes/code-block";
 | 
					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 {
 | 
					function buildListButton(label: string, type: ListType, icon: string): EditorButtonDefinition {
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
        label,
 | 
					        label,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,8 @@
 | 
				
			|||||||
import {EditorButton} from "./framework/buttons";
 | 
					import {EditorButton} from "./framework/buttons";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
 | 
					    alignCenter, alignJustify,
 | 
				
			||||||
 | 
					    alignLeft,
 | 
				
			||||||
 | 
					    alignRight,
 | 
				
			||||||
    blockquote, bold, bulletList, clearFormating, code, codeBlock,
 | 
					    blockquote, bold, bulletList, clearFormating, code, codeBlock,
 | 
				
			||||||
    dangerCallout, details, editCodeBlock, fullscreen,
 | 
					    dangerCallout, details, editCodeBlock, fullscreen,
 | 
				
			||||||
    h2, h3, h4, h5, highlightColor, horizontalRule, image,
 | 
					    h2, h3, h4, h5, highlightColor, horizontalRule, image,
 | 
				
			||||||
@@ -62,6 +65,14 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement {
 | 
				
			|||||||
            new EditorButton(clearFormating),
 | 
					            new EditorButton(clearFormating),
 | 
				
			||||||
        ]),
 | 
					        ]),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Alignment
 | 
				
			||||||
 | 
					        new EditorOverflowContainer(4, [
 | 
				
			||||||
 | 
					            new EditorButton(alignLeft),
 | 
				
			||||||
 | 
					            new EditorButton(alignCenter),
 | 
				
			||||||
 | 
					            new EditorButton(alignRight),
 | 
				
			||||||
 | 
					            new EditorButton(alignJustify),
 | 
				
			||||||
 | 
					        ]),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Lists
 | 
					        // Lists
 | 
				
			||||||
        new EditorOverflowContainer(3, [
 | 
					        new EditorOverflowContainer(3, [
 | 
				
			||||||
            new EditorButton(bulletList),
 | 
					            new EditorButton(bulletList),
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user