mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-06-07 03:14:33 +08: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