import {LexicalNode, Spread} from "lexical";
import type {SerializedElementNode} from "lexical/nodes/LexicalElementNode";
+import {sizeToPixels} from "../utils/dom";
export type CommonBlockAlignment = 'left' | 'right' | 'center' | 'justify' | '';
const validAlignments: CommonBlockAlignment[] = ['left', 'right', 'center', 'justify'];
export type SerializedCommonBlockNode = Spread<{
id: string;
alignment: CommonBlockAlignment;
+ inset: number;
}, SerializedElementNode>
export interface NodeHasAlignment {
getId(): string;
}
-interface CommonBlockInterface extends NodeHasId, NodeHasAlignment {}
+export interface NodeHasInset {
+ readonly __inset: number;
+ setInset(inset: number): void;
+ getInset(): number;
+}
+
+interface CommonBlockInterface extends NodeHasId, NodeHasAlignment, NodeHasInset {}
export function extractAlignmentFromElement(element: HTMLElement): CommonBlockAlignment {
const textAlignStyle: string = element.style.textAlign || '';
return '';
}
+export function extractInsetFromElement(element: HTMLElement): number {
+ const elemPadding: string = element.style.paddingLeft || '0';
+ return sizeToPixels(elemPadding);
+}
+
export function setCommonBlockPropsFromElement(element: HTMLElement, node: CommonBlockInterface): void {
if (element.id) {
node.setId(element.id);
}
node.setAlignment(extractAlignmentFromElement(element));
+ node.setInset(extractInsetFromElement(element));
}
export function commonPropertiesDifferent(nodeA: CommonBlockInterface, nodeB: CommonBlockInterface): boolean {
return nodeA.__id !== nodeB.__id ||
- nodeA.__alignment !== nodeB.__alignment;
+ nodeA.__alignment !== nodeB.__alignment ||
+ nodeA.__inset !== nodeB.__inset;
}
export function updateElementWithCommonBlockProps(element: HTMLElement, node: CommonBlockInterface): void {
if (node.__alignment) {
element.classList.add('align-' + node.__alignment);
}
+
+ if (node.__inset) {
+ element.style.paddingLeft = `${node.__inset}px`;
+ }
+}
+
+export function deserializeCommonBlockNode(serializedNode: SerializedCommonBlockNode, node: CommonBlockInterface): void {
+ node.setId(serializedNode.id);
+ node.setAlignment(serializedNode.alignment);
+ node.setInset(serializedNode.inset);
}
export interface NodeHasSize {
import type {EditorConfig} from "lexical/LexicalEditor";
import type {RangeSelection} from "lexical/LexicalSelection";
import {
- CommonBlockAlignment, commonPropertiesDifferent,
+ CommonBlockAlignment, commonPropertiesDifferent, deserializeCommonBlockNode,
SerializedCommonBlockNode,
setCommonBlockPropsFromElement,
updateElementWithCommonBlockProps
__id: string = '';
__category: CalloutCategory = 'info';
__alignment: CommonBlockAlignment = '';
+ __inset: number = 0;
static getType() {
return 'callout';
const newNode = new CalloutNode(node.__category, node.__key);
newNode.__id = node.__id;
newNode.__alignment = node.__alignment;
+ newNode.__inset = node.__inset;
return newNode;
}
return self.__alignment;
}
+ setInset(size: number) {
+ const self = this.getWritable();
+ self.__inset = size;
+ }
+
+ getInset(): number {
+ const self = this.getLatest();
+ return self.__inset;
+ }
+
createDOM(_config: EditorConfig, _editor: LexicalEditor) {
const element = document.createElement('p');
element.classList.add('callout', this.__category || '');
category: this.__category,
id: this.__id,
alignment: this.__alignment,
+ inset: this.__inset,
};
}
static importJSON(serializedNode: SerializedCalloutNode): CalloutNode {
const node = $createCalloutNode(serializedNode.category);
- node.setId(serializedNode.id);
- node.setAlignment(serializedNode.alignment);
+ deserializeCommonBlockNode(serializedNode, node);
return node;
}
import {EditorConfig} from "lexical/LexicalEditor";
import {HeadingNode, HeadingTagType, SerializedHeadingNode} from "@lexical/rich-text";
import {
- CommonBlockAlignment, commonPropertiesDifferent,
+ CommonBlockAlignment, commonPropertiesDifferent, deserializeCommonBlockNode,
SerializedCommonBlockNode,
setCommonBlockPropsFromElement,
updateElementWithCommonBlockProps
export class CustomHeadingNode extends HeadingNode {
__id: string = '';
__alignment: CommonBlockAlignment = '';
+ __inset: number = 0;
static getType() {
return 'custom-heading';
return self.__alignment;
}
+ setInset(size: number) {
+ const self = this.getWritable();
+ self.__inset = size;
+ }
+
+ getInset(): number {
+ const self = this.getLatest();
+ return self.__inset;
+ }
+
static clone(node: CustomHeadingNode) {
const newNode = new CustomHeadingNode(node.__tag, node.__key);
newNode.__alignment = node.__alignment;
+ newNode.__inset = node.__inset;
return newNode;
}
version: 1,
id: this.__id,
alignment: this.__alignment,
+ inset: this.__inset,
};
}
static importJSON(serializedNode: SerializedCustomHeadingNode): CustomHeadingNode {
const node = $createCustomHeadingNode(serializedNode.tag);
- node.setId(serializedNode.id);
- node.setAlignment(serializedNode.alignment);
+ deserializeCommonBlockNode(serializedNode, node);
return node;
}
} from "lexical";
import {EditorConfig} from "lexical/LexicalEditor";
import {
- CommonBlockAlignment, commonPropertiesDifferent,
+ CommonBlockAlignment, commonPropertiesDifferent, deserializeCommonBlockNode,
SerializedCommonBlockNode,
setCommonBlockPropsFromElement,
updateElementWithCommonBlockProps
export class CustomParagraphNode extends ParagraphNode {
__id: string = '';
__alignment: CommonBlockAlignment = '';
+ __inset: number = 0;
static getType() {
return 'custom-paragraph';
return self.__alignment;
}
+ setInset(size: number) {
+ const self = this.getWritable();
+ self.__inset = size;
+ }
+
+ getInset(): number {
+ const self = this.getLatest();
+ return self.__inset;
+ }
+
static clone(node: CustomParagraphNode): CustomParagraphNode {
const newNode = new CustomParagraphNode(node.__key);
newNode.__id = node.__id;
newNode.__alignment = node.__alignment;
+ newNode.__inset = node.__inset;
return newNode;
}
version: 1,
id: this.__id,
alignment: this.__alignment,
+ inset: this.__inset,
};
}
static importJSON(serializedNode: SerializedCustomParagraphNode): CustomParagraphNode {
const node = $createCustomParagraphNode();
- node.setId(serializedNode.id);
- node.setAlignment(serializedNode.alignment);
+ deserializeCommonBlockNode(serializedNode, node);
return node;
}
import {EditorConfig} from "lexical/LexicalEditor";
import {QuoteNode, SerializedQuoteNode} from "@lexical/rich-text";
import {
- CommonBlockAlignment, commonPropertiesDifferent,
+ CommonBlockAlignment, commonPropertiesDifferent, deserializeCommonBlockNode,
SerializedCommonBlockNode,
setCommonBlockPropsFromElement,
updateElementWithCommonBlockProps
export class CustomQuoteNode extends QuoteNode {
__id: string = '';
__alignment: CommonBlockAlignment = '';
+ __inset: number = 0;
static getType() {
return 'custom-quote';
return self.__alignment;
}
+ setInset(size: number) {
+ const self = this.getWritable();
+ self.__inset = size;
+ }
+
+ getInset(): number {
+ const self = this.getLatest();
+ return self.__inset;
+ }
+
static clone(node: CustomQuoteNode) {
const newNode = new CustomQuoteNode(node.__key);
newNode.__id = node.__id;
newNode.__alignment = node.__alignment;
+ newNode.__inset = node.__inset;
return newNode;
}
version: 1,
id: this.__id,
alignment: this.__alignment,
+ inset: this.__inset,
};
}
static importJSON(serializedNode: SerializedCustomQuoteNode): CustomQuoteNode {
const node = $createCustomQuoteNode();
- node.setId(serializedNode.id);
- node.setAlignment(serializedNode.alignment);
+ deserializeCommonBlockNode(serializedNode, node);
return node;
}
import {el, extractStyleMapFromElement, StyleMap} from "../utils/dom";
import {getTableColumnWidths} from "../utils/tables";
import {
- CommonBlockAlignment,
+ CommonBlockAlignment, deserializeCommonBlockNode,
SerializedCommonBlockNode,
setCommonBlockPropsFromElement,
updateElementWithCommonBlockProps
__colWidths: string[] = [];
__styles: StyleMap = new Map;
__alignment: CommonBlockAlignment = '';
+ __inset: number = 0;
static getType() {
return 'custom-table';
return self.__alignment;
}
+ setInset(size: number) {
+ const self = this.getWritable();
+ self.__inset = size;
+ }
+
+ getInset(): number {
+ const self = this.getLatest();
+ return self.__inset;
+ }
+
setColWidths(widths: string[]) {
const self = this.getWritable();
self.__colWidths = widths;
newNode.__colWidths = node.__colWidths;
newNode.__styles = new Map(node.__styles);
newNode.__alignment = node.__alignment;
+ newNode.__inset = node.__inset;
return newNode;
}
colWidths: this.__colWidths,
styles: Object.fromEntries(this.__styles),
alignment: this.__alignment,
+ inset: this.__inset,
};
}
static importJSON(serializedNode: SerializedCustomTableNode): CustomTableNode {
const node = $createCustomTableNode();
- node.setId(serializedNode.id);
+ deserializeCommonBlockNode(serializedNode, node);
node.setColWidths(serializedNode.colWidths);
node.setStyles(new Map(Object.entries(serializedNode.styles)));
- node.setAlignment(serializedNode.alignment);
return node;
}
import {el, setOrRemoveAttribute, sizeToPixels} from "../utils/dom";
import {
- CommonBlockAlignment,
+ CommonBlockAlignment, deserializeCommonBlockNode,
SerializedCommonBlockNode,
setCommonBlockPropsFromElement,
updateElementWithCommonBlockProps
__tag: MediaNodeTag;
__attributes: Record<string, string> = {};
__sources: MediaNodeSource[] = [];
+ __inset: number = 0;
static getType() {
return 'media';
newNode.__sources = node.__sources.map(s => Object.assign({}, s));
newNode.__id = node.__id;
newNode.__alignment = node.__alignment;
+ newNode.__inset = node.__inset;
return newNode;
}
return self.__alignment;
}
+ setInset(size: number) {
+ const self = this.getWritable();
+ self.__inset = size;
+ }
+
+ getInset(): number {
+ const self = this.getLatest();
+ return self.__inset;
+ }
+
setHeight(height: number): void {
if (!height) {
return;
}
}
+ if (prevNode.__inset !== this.__inset) {
+ dom.style.paddingLeft = `${this.__inset}px`;
+ }
+
return false;
}
version: 1,
id: this.__id,
alignment: this.__alignment,
+ inset: this.__inset,
tag: this.__tag,
attributes: this.__attributes,
sources: this.__sources,
static importJSON(serializedNode: SerializedMediaNode): MediaNode {
const node = $createMediaNode(serializedNode.tag);
- node.setId(serializedNode.id);
- node.setAlignment(serializedNode.alignment);
+ deserializeCommonBlockNode(serializedNode, node);
return node;
}
## Main Todo
+- Align list nesting with old editor
- Mac: Shortcut support via command.
## Secondary Todo
import {$isListNode, ListNode, ListType} from "@lexical/list";
import {EditorButtonDefinition} from "../../framework/buttons";
import {EditorUiContext} from "../../framework/core";
-import {BaseSelection, LexicalNode} from "lexical";
+import {
+ BaseSelection,
+ LexicalEditor,
+ LexicalNode,
+} from "lexical";
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 {$selectionContainsNodeType} from "../../../utils/selection";
+import indentIncreaseIcon from "@icons/editor/indent-increase.svg";
+import indentDecreaseIcon from "@icons/editor/indent-decrease.svg";
+import {
+ $getBlockElementNodesInSelection,
+ $selectionContainsNodeType,
+ $toggleSelection,
+ getLastSelection
+} from "../../../utils/selection";
import {toggleSelectionAsList} from "../../../utils/formats";
+import {nodeHasInset} from "../../../utils/nodes";
function buildListButton(label: string, type: ListType, icon: string): EditorButtonDefinition {
export const bulletList: EditorButtonDefinition = buildListButton('Bullet list', 'bullet', listBulletIcon);
export const numberList: EditorButtonDefinition = buildListButton('Numbered list', 'number', listNumberedIcon);
export const taskList: EditorButtonDefinition = buildListButton('Task list', 'check', listCheckIcon);
+
+
+function setInsetForSelection(editor: LexicalEditor, change: number): void {
+ const selection = getLastSelection(editor);
+
+ const elements = $getBlockElementNodesInSelection(selection);
+ for (const node of elements) {
+ if (nodeHasInset(node)) {
+ const currentInset = node.getInset();
+ const newInset = Math.min(Math.max(currentInset + change, 0), 500);
+ node.setInset(newInset)
+ }
+ }
+
+ $toggleSelection(editor);
+}
+
+export const indentIncrease: EditorButtonDefinition = {
+ label: 'Increase indent',
+ icon: indentIncreaseIcon,
+ action(context: EditorUiContext) {
+ context.editor.update(() => {
+ setInsetForSelection(context.editor, 40);
+ });
+ },
+ isActive() {
+ return false;
+ }
+};
+
+export const indentDecrease: EditorButtonDefinition = {
+ label: 'Decrease indent',
+ icon: indentDecreaseIcon,
+ action(context: EditorUiContext) {
+ context.editor.update(() => {
+ setInsetForSelection(context.editor, -40);
+ });
+ },
+ isActive() {
+ return false;
+ }
+};
\ No newline at end of file
underline
} from "./defaults/buttons/inline-formats";
import {alignCenter, alignJustify, alignLeft, alignRight} from "./defaults/buttons/alignments";
-import {bulletList, numberList, taskList} from "./defaults/buttons/lists";
+import {
+ bulletList,
+ indentDecrease,
+ indentIncrease,
+ numberList,
+ taskList
+} from "./defaults/buttons/lists";
import {
codeBlock,
details,
]),
// Lists
- new EditorOverflowContainer(3, [
+ new EditorOverflowContainer(5, [
new EditorButton(bulletList),
new EditorButton(numberList),
new EditorButton(taskList),
+ new EditorButton(indentDecrease),
+ new EditorButton(indentIncrease),
]),
// Insert types
import {$createCustomParagraphNode} from "../nodes/custom-paragraph";
import {$generateNodesFromDOM} from "@lexical/html";
import {htmlToDom} from "./dom";
-import {NodeHasAlignment} from "../nodes/_common";
+import {NodeHasAlignment, NodeHasInset} from "../nodes/_common";
import {$findMatchingParent} from "@lexical/utils";
function wrapTextNodes(nodes: LexicalNode[]): LexicalNode[] {
export function nodeHasAlignment(node: object): node is NodeHasAlignment {
return '__alignment' in node;
+}
+
+export function nodeHasInset(node: object): node is NodeHasInset {
+ return '__inset' in node;
}
\ No newline at end of file