Improve multi element indentation (#1982)

* Improve multi element indentation

* Remove bad UT

* Remove bad UT

* Add e2e test

* Address feedback
This commit is contained in:
Dominic Gannaway
2022-04-26 04:41:22 +01:00
committed by GitHub
parent 687c3a5c5c
commit c963cfa896
22 changed files with 1485 additions and 74 deletions

View File

@ -28,7 +28,7 @@ declare class CodeNode extends ElementNode {
insertNewAfter(
selection: RangeSelection,
): null | ParagraphNode | CodeHighlightNode;
canInsertTab(): true;
canInsertTab(): boolean;
collapseAtStart(): true;
setLanguage(language: string): void;
getLanguage(): string | void;

View File

@ -28,7 +28,7 @@ declare export class CodeNode extends ElementNode {
insertNewAfter(
selection: RangeSelection,
): null | ParagraphNode | CodeHighlightNode;
canInsertTab(): true;
canInsertTab(): boolean;
collapseAtStart(): true;
setLanguage(language: string): void;
getLanguage(): string | void;

View File

@ -92,14 +92,6 @@ describe('LexicalCodeNode tests', () => {
});
});
test('CodeNode.canInsertTab()', async () => {
const {editor} = testEnv;
await editor.update(() => {
const codeNode = $createCodeNode();
expect(codeNode.canInsertTab()).toBe(true);
});
});
test('$createCodeNode()', async () => {
const {editor} = testEnv;
await editor.update(() => {

View File

@ -313,10 +313,18 @@ export class CodeNode extends ElementNode {
return null;
}
canInsertTab(): true {
canInsertTab(): boolean {
const selection = $getSelection();
if (!$isRangeSelection(selection) || !selection.isCollapsed()) {
return false;
}
return true;
}
canIndent(): false {
return false;
}
collapseAtStart(): true {
const paragraph = $createParagraphNode();
const children = this.getChildren();

View File

@ -22,7 +22,7 @@ export function $getListDepth(listNode: ListNode): number;
export function $handleListInsertParagraph(): boolean;
export function $isListItemNode(node?: LexicalNode): node is ListItemNode;
export function $isListNode(node?: LexicalNode): node is ListNode;
export function indentList(): boolean;
export function indentList(): void;
export function insertList(editor: LexicalEditor, listType: 'ul' | 'ol'): void;
export declare class ListItemNode extends ElementNode {
append(...nodes: LexicalNode[]): ListItemNode;
@ -42,7 +42,7 @@ export declare class ListNode extends ElementNode {
append(...nodesToAppend: LexicalNode[]): ListNode;
getTag(): ListNodeTagType;
}
export function outdentList(): boolean;
export function outdentList(): void;
export function removeList(editor: LexicalEditor): boolean;
export var INSERT_UNORDERED_LIST_COMMAND: LexicalCommand<void>;

View File

@ -30,7 +30,7 @@ declare export function $isListItemNode(
declare export function $isListNode(
node: ?LexicalNode,
): boolean %checks(node instanceof ListNode);
declare export function indentList(): boolean;
declare export function indentList(): void;
declare export function insertList(
editor: LexicalEditor,
listType: 'ul' | 'ol',
@ -56,7 +56,7 @@ declare export class ListNode extends ElementNode {
getTag(): ListNodeTagType;
getStart(): number;
}
declare export function outdentList(): boolean;
declare export function outdentList(): void;
declare export function removeList(editor: LexicalEditor): boolean;
declare export var INSERT_UNORDERED_LIST_COMMAND: LexicalCommand<void>;

View File

@ -227,6 +227,11 @@ export class ListItemNode extends ElementNode {
return this;
}
canIndent(): false {
// Indent/outdent is handled specifically in the RichText logic.
return false;
}
insertBefore(nodeToInsert: LexicalNode): LexicalNode {
const siblings = this.getNextSiblings();
if ($isListItemNode(nodeToInsert)) {

View File

@ -94,6 +94,10 @@ export class ListNode extends ElementNode {
return false;
}
canIndent(): false {
return false;
}
append(...nodesToAppend: LexicalNode[]): ListNode {
for (let i = 0; i < nodesToAppend.length; i++) {
const currentNode = nodesToAppend[i];

View File

@ -300,10 +300,10 @@ export function $handleOutdent(listItemNodes: Array<ListItemNode>): void {
});
}
function maybeIndentOrOutdent(direction: 'indent' | 'outdent'): boolean {
function maybeIndentOrOutdent(direction: 'indent' | 'outdent'): void {
const selection = $getSelection();
if (!$isRangeSelection(selection)) {
return false;
return;
}
const selectedNodes = selection.getNodes();
let listItemNodes = [];
@ -326,17 +326,15 @@ function maybeIndentOrOutdent(direction: 'indent' | 'outdent'): boolean {
} else {
$handleOutdent(listItemNodes);
}
return true;
}
return false;
}
export function indentList(): boolean {
return maybeIndentOrOutdent('indent');
export function indentList(): void {
maybeIndentOrOutdent('indent');
}
export function outdentList(): boolean {
return maybeIndentOrOutdent('outdent');
export function outdentList(): void {
maybeIndentOrOutdent('outdent');
}
export function $handleListInsertParagraph(): boolean {

View File

@ -39,7 +39,7 @@ test.describe('Element format', () => {
html`
<p
class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__ltr"
style="padding-inline-start: 80px; text-align: center;"
style="padding-inline-start: 40px; text-align: center;"
dir="ltr">
<span data-lexical-text="true">Hello</span>
<a

File diff suppressed because it is too large Load Diff

View File

@ -34,10 +34,7 @@ export default function useList(editor: LexicalEditor): void {
editor.registerCommand(
INDENT_CONTENT_COMMAND,
() => {
const hasHandledIndention = indentList();
if (hasHandledIndention) {
return true;
}
indentList();
return false;
},
COMMAND_PRIORITY_LOW,
@ -45,10 +42,7 @@ export default function useList(editor: LexicalEditor): void {
editor.registerCommand(
OUTDENT_CONTENT_COMMAND,
() => {
const hasHandledIndention = outdentList();
if (hasHandledIndention) {
return true;
}
outdentList();
return false;
},
COMMAND_PRIORITY_LOW,

View File

@ -41,6 +41,7 @@ import {
$isGridSelection,
$isNodeSelection,
$isRangeSelection,
$isTextNode,
CLICK_COMMAND,
COMMAND_PRIORITY_EDITOR,
COPY_COMMAND,
@ -328,6 +329,33 @@ function onCutForRichText(event: ClipboardEvent, editor: LexicalEditor): void {
});
}
function handleIndentAndOutdent(
insertTab: (node: LexicalNode) => void,
indentOrOutdent: (block: ElementNode) => void,
): void {
const selection = $getSelection();
if (!$isRangeSelection(selection)) {
return;
}
const alreadyHandled = new Set();
const nodes = selection.getNodes();
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
const key = node.getKey();
if (alreadyHandled.has(key)) {
continue;
}
alreadyHandled.add(key);
const parentBlock = $getNearestBlockElementAncestorOrThrow(node);
if (parentBlock.canInsertTab()) {
insertTab(node);
} else if (parentBlock.canIndent()) {
indentOrOutdent(parentBlock);
}
}
}
export function registerRichText(
editor: LexicalEditor,
initialEditorState?: InitialEditorStateType,
@ -478,22 +506,17 @@ export function registerRichText(
editor.registerCommand(
INDENT_CONTENT_COMMAND,
(payload) => {
const selection = $getSelection();
if (!$isRangeSelection(selection)) {
return false;
}
// Handle code blocks
const anchor = selection.anchor;
const parentBlock = $getNearestBlockElementAncestorOrThrow(
anchor.getNode(),
handleIndentAndOutdent(
() => {
editor.dispatchCommand(INSERT_TEXT_COMMAND, '\t');
},
(block) => {
const indent = block.getIndent();
if (indent !== 10) {
block.setIndent(indent + 1);
}
},
);
if (parentBlock.canInsertTab()) {
editor.dispatchCommand(INSERT_TEXT_COMMAND, '\t');
} else {
if (parentBlock.getIndent() !== 10) {
parentBlock.setIndent(parentBlock.getIndent() + 1);
}
}
return true;
},
COMMAND_PRIORITY_EDITOR,
@ -501,27 +524,23 @@ export function registerRichText(
editor.registerCommand(
OUTDENT_CONTENT_COMMAND,
(payload) => {
const selection = $getSelection();
if (!$isRangeSelection(selection)) {
return false;
}
// Handle code blocks
const anchor = selection.anchor;
const anchorNode = anchor.getNode();
const parentBlock = $getNearestBlockElementAncestorOrThrow(
anchor.getNode(),
handleIndentAndOutdent(
(node) => {
if ($isTextNode(node)) {
const textContent = node.getTextContent();
const character = textContent[textContent.length - 1];
if (character === '\t') {
editor.dispatchCommand(DELETE_CHARACTER_COMMAND, true);
}
}
},
(block) => {
const indent = block.getIndent();
if (indent !== 0) {
block.setIndent(indent - 1);
}
},
);
if (parentBlock.canInsertTab()) {
const textContent = anchorNode.getTextContent();
const character = textContent[anchor.offset - 1];
if (character === '\t') {
editor.dispatchCommand(DELETE_CHARACTER_COMMAND, true);
}
} else {
if (parentBlock.getIndent() !== 0) {
parentBlock.setIndent(parentBlock.getIndent() - 1);
}
}
return true;
},
COMMAND_PRIORITY_EDITOR,

View File

@ -54,7 +54,6 @@ export declare class TableCellNode extends ElementNode {
insertNewAfter(
selection: RangeSelection,
): null | ParagraphNode | TableCellNode;
canInsertTab(): true;
collapseAtStart(): true;
getTag(): string;
setHeaderState(headerState: TableCellHeaderState): TableCellHeaderState;
@ -83,7 +82,6 @@ export declare class TableNode extends ElementNode {
createDOM(config: EditorConfig): HTMLElement;
updateDOM(prevNode: TableNode, dom: HTMLElement): boolean;
insertNewAfter(selection: RangeSelection): null | ParagraphNode | TableNode;
canInsertTab(): true;
collapseAtStart(): true;
getCordsFromCellNode(tableCellNode: TableCellNode): {x: number; y: number};
getCellFromCords(x: number, y: number, grid: Grid): ?Cell;
@ -112,7 +110,6 @@ declare class TableRowNode extends ElementNode {
): null | ParagraphNode | TableRowNode;
setHeight(height: number): ?number;
getHeight(): ?number;
canInsertTab(): true;
collapseAtStart(): true;
}
declare function $createTableRowNode(): TableRowNode;

View File

@ -48,7 +48,6 @@ declare export class TableCellNode extends ElementNode {
insertNewAfter(
selection: RangeSelection,
): null | ParagraphNode | TableCellNode;
canInsertTab(): true;
collapseAtStart(): true;
getTag(): string;
setHeaderStyles(headerState: TableCellHeaderState): TableCellHeaderState;
@ -81,7 +80,6 @@ declare export class TableNode extends ElementNode {
createDOM(config: EditorConfig): HTMLElement;
updateDOM(prevNode: TableNode, dom: HTMLElement): boolean;
insertNewAfter(selection: RangeSelection): null | ParagraphNode | TableNode;
canInsertTab(): true;
collapseAtStart(): true;
getCordsFromCellNode(
tableCellNode: TableCellNode,
@ -115,7 +113,6 @@ declare export class TableRowNode extends ElementNode {
insertNewAfter(
selection: RangeSelection,
): null | ParagraphNode | TableRowNode;
canInsertTab(): true;
collapseAtStart(): true;
}
declare export function $createTableRowNode(): TableRowNode;

View File

@ -175,6 +175,10 @@ export class TableCellNode extends GridCellNode {
canBeEmpty(): false {
return false;
}
canIndent(): false {
return false;
}
}
export function convertTableCellNodeElement(

View File

@ -183,6 +183,10 @@ export class TableNode extends GridNode {
canSelectBefore(): true {
return true;
}
canIndent(): false {
return false;
}
}
export function $getElementGridForTableNode(

View File

@ -72,6 +72,10 @@ export class TableRowNode extends GridRowNode {
canBeEmpty(): false {
return false;
}
canIndent(): false {
return false;
}
}
export function convertTableRowElement(domNode: Node): DOMConversionOutput {

View File

@ -696,6 +696,7 @@ export declare class ElementNode extends LexicalNode {
setIndent(indentLevel: number): ElementNode;
insertNewAfter(selection: RangeSelection): null | LexicalNode;
canInsertTab(): boolean;
canIndent(): boolean;
collapseAtStart(selection: RangeSelection): boolean;
excludeFromCopy(): boolean;
canExtractContents(): boolean;

View File

@ -724,6 +724,7 @@ declare export class ElementNode extends LexicalNode {
setIndent(indentLevel: number): this;
insertNewAfter(selection: RangeSelection): null | LexicalNode;
canInsertTab(): boolean;
canIndent(): boolean;
collapseAtStart(selection: RangeSelection): boolean;
excludeFromCopy(): boolean;
canExtractContents(): boolean;

View File

@ -118,7 +118,7 @@ function setTextAlign(domStyle: CSSStyleDeclaration, value: string): void {
function setElementIndent(dom: HTMLElement, indent: number): void {
dom.style.setProperty(
'padding-inline-start',
indent === 0 ? '' : indent * 40 + 'px',
indent === 0 ? '' : indent * 20 + 'px',
);
}

View File

@ -410,6 +410,9 @@ export class ElementNode extends LexicalNode {
canInsertTab(): boolean {
return false;
}
canIndent(): boolean {
return true;
}
collapseAtStart(selection: RangeSelection): boolean {
return false;
}