Lexical: Added list support, started todo

This commit is contained in:
Dan Brown
2024-07-17 16:38:20 +01:00
parent ea4c50c2c2
commit b367490edc
4 changed files with 170 additions and 32 deletions

View File

@ -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());
}

View 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.

View File

@ -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,

View File

@ -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),