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

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 {
$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,

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