mirror of
https://github.com/facebook/lexical.git
synced 2025-05-20 16:48:04 +08:00
Make tabs flexible in code (#4520)
This commit is contained in:
@ -18,22 +18,23 @@ import type {
|
||||
LineBreakNode,
|
||||
SerializedElementNode,
|
||||
SerializedTabNode,
|
||||
TabNode,
|
||||
} from 'lexical';
|
||||
|
||||
import {ElementNode, TextNode, TabNode} from 'lexical';
|
||||
import {ElementNode, TextNode} from 'lexical';
|
||||
|
||||
/**
|
||||
* CodeHighlighter
|
||||
*/
|
||||
declare export function getEndOfCodeInLine(
|
||||
anchor: CodeHighlightNode | CodeTabNode,
|
||||
): CodeHighlightNode | CodeTabNode;
|
||||
anchor: CodeHighlightNode | TabNode,
|
||||
): CodeHighlightNode | TabNode;
|
||||
|
||||
declare export function getStartOfCodeInLine(
|
||||
anchor: CodeHighlightNode | CodeTabNode,
|
||||
anchor: CodeHighlightNode | TabNode,
|
||||
offset: number,
|
||||
): null | {
|
||||
node: CodeHighlightNode | CodeTabNode | LineBreakNode,
|
||||
node: CodeHighlightNode | TabNode | LineBreakNode,
|
||||
offset: number,
|
||||
};
|
||||
|
||||
@ -91,14 +92,14 @@ declare export var getCodeLanguages: () => Array<string>;
|
||||
declare export var getDefaultCodeLanguage: () => string;
|
||||
|
||||
declare export function getFirstCodeNodeOfLine(
|
||||
anchor: CodeHighlightNode | CodeTabNode | LineBreakNode,
|
||||
): null | CodeHighlightNode | CodeTabNode | LineBreakNode;
|
||||
anchor: CodeHighlightNode | TabNode | LineBreakNode,
|
||||
): null | CodeHighlightNode | TabNode | LineBreakNode;
|
||||
|
||||
declare export function getLanguageFriendlyName(lang: string): string;
|
||||
|
||||
declare export function getLastCodeNodeOfLine(
|
||||
anchor: CodeHighlightNode | CodeTabNode | LineBreakNode,
|
||||
): CodeHighlightNode | CodeTabNode | LineBreakNode;
|
||||
anchor: CodeHighlightNode | TabNode | LineBreakNode,
|
||||
): CodeHighlightNode | TabNode | LineBreakNode;
|
||||
|
||||
declare export function normalizeCodeLang(lang: string): string;
|
||||
|
||||
@ -127,34 +128,8 @@ declare export class CodeNode extends ElementNode {
|
||||
insertNewAfter(
|
||||
selection: RangeSelection,
|
||||
restoreSelection?: boolean,
|
||||
): null | ParagraphNode | CodeHighlightNode | CodeTabNode;
|
||||
): null | ParagraphNode | CodeHighlightNode | TabNode;
|
||||
collapseAtStart(): true;
|
||||
setLanguage(language: string): void;
|
||||
getLanguage(): string | void;
|
||||
}
|
||||
|
||||
/**
|
||||
* CodeTabNode
|
||||
*/
|
||||
|
||||
export type SerializedCodeTabNode = SerializedTabNode;
|
||||
|
||||
declare export function $createCodeTabNode(): CodeTabNode;
|
||||
|
||||
declare export function $isCodeTabNode(
|
||||
node: LexicalNode | null | void,
|
||||
): boolean %checks(node instanceof CodeTabNode);
|
||||
|
||||
declare export class CodeTabNode extends TabNode {
|
||||
static getType(): string;
|
||||
// $FlowFixMe
|
||||
static clone(node: CodeTabNode): CodeTabNode;
|
||||
static importJSON(_serializedTabNode: SerializedCodeTabNode): CodeTabNode;
|
||||
exportJSON(): SerializedTabNode;
|
||||
createDOM(config: EditorConfig): HTMLElement;
|
||||
updateDOM(
|
||||
prevNode: TextNode,
|
||||
dom: HTMLElement,
|
||||
config: EditorConfig,
|
||||
): boolean;
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import type {
|
||||
NodeKey,
|
||||
SerializedTextNode,
|
||||
Spread,
|
||||
TabNode,
|
||||
} from 'lexical';
|
||||
|
||||
import 'prismjs/components/prism-clike';
|
||||
@ -35,11 +36,15 @@ import {
|
||||
addClassNamesToElement,
|
||||
removeClassNamesFromElement,
|
||||
} from '@lexical/utils';
|
||||
import {$applyNodeReplacement, ElementNode, TextNode} from 'lexical';
|
||||
import {
|
||||
$applyNodeReplacement,
|
||||
$isTabNode,
|
||||
ElementNode,
|
||||
TextNode,
|
||||
} from 'lexical';
|
||||
import * as Prism from 'prismjs';
|
||||
|
||||
import {$createCodeNode} from './CodeNode';
|
||||
import {$isCodeTabNode, CodeTabNode} from './CodeTabNode';
|
||||
|
||||
export const DEFAULT_CODE_LANGUAGE = 'javascript';
|
||||
|
||||
@ -229,11 +234,11 @@ export function $isCodeHighlightNode(
|
||||
}
|
||||
|
||||
export function getFirstCodeNodeOfLine(
|
||||
anchor: CodeHighlightNode | CodeTabNode | LineBreakNode,
|
||||
): null | CodeHighlightNode | CodeTabNode | LineBreakNode {
|
||||
anchor: CodeHighlightNode | TabNode | LineBreakNode,
|
||||
): null | CodeHighlightNode | TabNode | LineBreakNode {
|
||||
let previousNode = anchor;
|
||||
let node: null | LexicalNode = anchor;
|
||||
while ($isCodeHighlightNode(node) || $isCodeTabNode(node)) {
|
||||
while ($isCodeHighlightNode(node) || $isTabNode(node)) {
|
||||
previousNode = node;
|
||||
node = node.getPreviousSibling();
|
||||
}
|
||||
@ -241,11 +246,11 @@ export function getFirstCodeNodeOfLine(
|
||||
}
|
||||
|
||||
export function getLastCodeNodeOfLine(
|
||||
anchor: CodeHighlightNode | CodeTabNode | LineBreakNode,
|
||||
): CodeHighlightNode | CodeTabNode | LineBreakNode {
|
||||
anchor: CodeHighlightNode | TabNode | LineBreakNode,
|
||||
): CodeHighlightNode | TabNode | LineBreakNode {
|
||||
let nextNode = anchor;
|
||||
let node: null | LexicalNode = anchor;
|
||||
while ($isCodeHighlightNode(node) || $isCodeTabNode(node)) {
|
||||
while ($isCodeHighlightNode(node) || $isTabNode(node)) {
|
||||
nextNode = node;
|
||||
node = node.getNextSibling();
|
||||
}
|
||||
|
@ -50,13 +50,14 @@ import {
|
||||
INDENT_CONTENT_COMMAND,
|
||||
KEY_ARROW_DOWN_COMMAND,
|
||||
KEY_ARROW_UP_COMMAND,
|
||||
TabNode,
|
||||
MOVE_TO_END,
|
||||
MOVE_TO_START,
|
||||
$insertNodes,
|
||||
OUTDENT_CONTENT_COMMAND,
|
||||
KEY_TAB_COMMAND,
|
||||
TextNode,
|
||||
$isTabNode,
|
||||
TabNode,
|
||||
} from 'lexical';
|
||||
|
||||
import {
|
||||
@ -70,7 +71,6 @@ import {
|
||||
|
||||
import {$isCodeNode, CodeNode} from './CodeNode';
|
||||
import invariant from 'shared/invariant';
|
||||
import {CodeTabNode, $createCodeTabNode, $isCodeTabNode} from './CodeTabNode';
|
||||
|
||||
type TokenContent = string | Token | (string | Token)[];
|
||||
|
||||
@ -95,18 +95,18 @@ export const PrismTokenizer: Tokenizer = {
|
||||
};
|
||||
|
||||
export function getStartOfCodeInLine(
|
||||
anchor: CodeHighlightNode | CodeTabNode,
|
||||
anchor: CodeHighlightNode | TabNode,
|
||||
offset: number,
|
||||
): null | {
|
||||
node: CodeHighlightNode | CodeTabNode | LineBreakNode;
|
||||
node: CodeHighlightNode | TabNode | LineBreakNode;
|
||||
offset: number;
|
||||
} {
|
||||
let last: null | {
|
||||
node: CodeHighlightNode | CodeTabNode | LineBreakNode;
|
||||
node: CodeHighlightNode | TabNode | LineBreakNode;
|
||||
offset: number;
|
||||
} = null;
|
||||
let lastNonBlank: null | {node: CodeHighlightNode; offset: number} = null;
|
||||
let node: null | CodeHighlightNode | CodeTabNode | LineBreakNode = anchor;
|
||||
let node: null | CodeHighlightNode | TabNode | LineBreakNode = anchor;
|
||||
let nodeOffset = offset;
|
||||
let nodeTextContent = anchor.getTextContent();
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
@ -118,9 +118,9 @@ export function getStartOfCodeInLine(
|
||||
}
|
||||
invariant(
|
||||
$isCodeHighlightNode(node) ||
|
||||
$isCodeTabNode(node) ||
|
||||
$isTabNode(node) ||
|
||||
$isLineBreakNode(node),
|
||||
'Expected a valid Code Node: CodeHighlightNode, CodeTabNode, LineBreakNode',
|
||||
'Expected a valid Code Node: CodeHighlightNode, TabNode, LineBreakNode',
|
||||
);
|
||||
if ($isLineBreakNode(node)) {
|
||||
last = {
|
||||
@ -208,8 +208,8 @@ function findNextNonBlankInLine(
|
||||
}
|
||||
|
||||
export function getEndOfCodeInLine(
|
||||
anchor: CodeHighlightNode | CodeTabNode,
|
||||
): CodeHighlightNode | CodeTabNode {
|
||||
anchor: CodeHighlightNode | TabNode,
|
||||
): CodeHighlightNode | TabNode {
|
||||
const lastNode = getLastCodeNodeOfLine(anchor);
|
||||
invariant(
|
||||
!$isLineBreakNode(lastNode),
|
||||
@ -340,7 +340,7 @@ function getHighlightNodes(tokens: (string | Token)[]): LexicalNode[] {
|
||||
if (part === '\n' || part === '\r\n') {
|
||||
nodes.push($createLineBreakNode());
|
||||
} else if (part === '\t') {
|
||||
nodes.push($createCodeTabNode());
|
||||
nodes.push($createTabNode());
|
||||
} else if (part.length > 0) {
|
||||
nodes.push($createCodeHighlightNode(part));
|
||||
}
|
||||
@ -485,7 +485,7 @@ function isEqual(nodeA: LexicalNode, nodeB: LexicalNode): boolean {
|
||||
$isCodeHighlightNode(nodeB) &&
|
||||
nodeA.__text === nodeB.__text &&
|
||||
nodeA.__highlightType === nodeB.__highlightType) ||
|
||||
($isCodeTabNode(nodeA) && $isCodeTabNode(nodeB)) ||
|
||||
($isTabNode(nodeA) && $isTabNode(nodeB)) ||
|
||||
($isLineBreakNode(nodeA) && $isLineBreakNode(nodeB))
|
||||
);
|
||||
}
|
||||
@ -507,20 +507,18 @@ function $isSelectionInCode(
|
||||
|
||||
function $getCodeLines(
|
||||
selection: RangeSelection,
|
||||
): Array<Array<CodeHighlightNode | CodeTabNode>> {
|
||||
): Array<Array<CodeHighlightNode | TabNode>> {
|
||||
const nodes = selection.getNodes();
|
||||
const lines: Array<Array<CodeHighlightNode | CodeTabNode>> = [[]];
|
||||
const lines: Array<Array<CodeHighlightNode | TabNode>> = [[]];
|
||||
if (nodes.length === 1 && $isCodeNode(nodes[0])) {
|
||||
return lines;
|
||||
}
|
||||
let lastLine: Array<CodeHighlightNode | CodeTabNode> = lines[0];
|
||||
let lastLine: Array<CodeHighlightNode | TabNode> = lines[0];
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const node = nodes[i];
|
||||
invariant(
|
||||
$isCodeHighlightNode(node) ||
|
||||
$isCodeTabNode(node) ||
|
||||
$isLineBreakNode(node),
|
||||
'Expected selection to be inside CodeBlock and consisting of CodeHighlightNode, CodeTabNode and LineBreakNode',
|
||||
$isCodeHighlightNode(node) || $isTabNode(node) || $isLineBreakNode(node),
|
||||
'Expected selection to be inside CodeBlock and consisting of CodeHighlightNode, TabNode and LineBreakNode',
|
||||
);
|
||||
if ($isLineBreakNode(node)) {
|
||||
if (i !== 0 && lastLine.length > 0) {
|
||||
@ -554,9 +552,9 @@ function handleTab(shiftKey: boolean): null | LexicalCommand<void> {
|
||||
invariant(
|
||||
$isCodeNode(firstNode) ||
|
||||
$isCodeHighlightNode(firstNode) ||
|
||||
$isCodeTabNode(firstNode) ||
|
||||
$isTabNode(firstNode) ||
|
||||
$isLineBreakNode(firstNode),
|
||||
'Expected selection firstNode to be CodeHighlightNode or CodeTabNode',
|
||||
'Expected selection firstNode to be CodeHighlightNode or TabNode',
|
||||
);
|
||||
if ($isCodeNode(firstNode)) {
|
||||
return indentOrOutdent;
|
||||
@ -600,19 +598,16 @@ function handleMultilineIndent(type: LexicalCommand<void>): boolean {
|
||||
for (let i = 0; i < codeLinesLength; i++) {
|
||||
const line = codeLines[i];
|
||||
if (line.length > 0) {
|
||||
let firstOfLine:
|
||||
| null
|
||||
| CodeHighlightNode
|
||||
| CodeTabNode
|
||||
| LineBreakNode = line[0];
|
||||
let firstOfLine: null | CodeHighlightNode | TabNode | LineBreakNode =
|
||||
line[0];
|
||||
// First and last lines might not be complete
|
||||
if (i === 0) {
|
||||
firstOfLine = getFirstCodeNodeOfLine(firstOfLine);
|
||||
}
|
||||
if (firstOfLine !== null) {
|
||||
if (type === INDENT_CONTENT_COMMAND) {
|
||||
firstOfLine.insertBefore($createCodeTabNode());
|
||||
} else if ($isCodeTabNode(firstOfLine)) {
|
||||
firstOfLine.insertBefore($createTabNode());
|
||||
} else if ($isTabNode(firstOfLine)) {
|
||||
firstOfLine.remove();
|
||||
}
|
||||
}
|
||||
@ -626,14 +621,14 @@ function handleMultilineIndent(type: LexicalCommand<void>): boolean {
|
||||
invariant(
|
||||
$isCodeNode(firstNode) ||
|
||||
$isCodeHighlightNode(firstNode) ||
|
||||
$isCodeTabNode(firstNode) ||
|
||||
$isTabNode(firstNode) ||
|
||||
$isLineBreakNode(firstNode),
|
||||
'Expected selection firstNode to be CodeHighlightNode or CodeTabNode',
|
||||
);
|
||||
if ($isCodeNode(firstNode)) {
|
||||
// CodeNode is empty
|
||||
if (type === INDENT_CONTENT_COMMAND) {
|
||||
selection.insertNodes([$createCodeTabNode()]);
|
||||
selection.insertNodes([$createTabNode()]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -644,11 +639,11 @@ function handleMultilineIndent(type: LexicalCommand<void>): boolean {
|
||||
);
|
||||
if (type === INDENT_CONTENT_COMMAND) {
|
||||
if ($isLineBreakNode(firstOfLine)) {
|
||||
firstOfLine.insertAfter($createCodeTabNode());
|
||||
firstOfLine.insertAfter($createTabNode());
|
||||
} else {
|
||||
firstOfLine.insertBefore($createCodeTabNode());
|
||||
firstOfLine.insertBefore($createTabNode());
|
||||
}
|
||||
} else if ($isCodeTabNode(firstOfLine)) {
|
||||
} else if ($isTabNode(firstOfLine)) {
|
||||
firstOfLine.remove();
|
||||
}
|
||||
return true;
|
||||
@ -676,8 +671,8 @@ function handleShiftLines(
|
||||
// Ensure the selection is within the codeblock
|
||||
if (
|
||||
!$isSelectionInCode(selection) ||
|
||||
!($isCodeHighlightNode(anchorNode) || $isCodeTabNode(anchorNode)) ||
|
||||
!($isCodeHighlightNode(focusNode) || $isCodeTabNode(focusNode))
|
||||
!($isCodeHighlightNode(anchorNode) || $isTabNode(anchorNode)) ||
|
||||
!($isCodeHighlightNode(focusNode) || $isTabNode(focusNode))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@ -731,7 +726,7 @@ function handleShiftLines(
|
||||
const node = range[i];
|
||||
if (
|
||||
!$isCodeHighlightNode(node) &&
|
||||
!$isCodeTabNode(node) &&
|
||||
!$isTabNode(node) &&
|
||||
!$isLineBreakNode(node)
|
||||
) {
|
||||
return false;
|
||||
@ -759,7 +754,7 @@ function handleShiftLines(
|
||||
|
||||
const maybeInsertionPoint =
|
||||
$isCodeHighlightNode(sibling) ||
|
||||
$isCodeTabNode(sibling) ||
|
||||
$isTabNode(sibling) ||
|
||||
$isLineBreakNode(sibling)
|
||||
? arrowIsUp
|
||||
? getFirstCodeNodeOfLine(sibling)
|
||||
@ -801,8 +796,8 @@ function handleMoveTo(
|
||||
const isMoveToStart = type === MOVE_TO_START;
|
||||
|
||||
if (
|
||||
!($isCodeHighlightNode(anchorNode) || $isCodeTabNode(anchorNode)) ||
|
||||
!($isCodeHighlightNode(focusNode) || $isCodeTabNode(focusNode))
|
||||
!($isCodeHighlightNode(anchorNode) || $isTabNode(anchorNode)) ||
|
||||
!($isCodeHighlightNode(focusNode) || $isTabNode(focusNode))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@ -830,18 +825,6 @@ function handleMoveTo(
|
||||
return true;
|
||||
}
|
||||
|
||||
function tabNodeTransform(node: TabNode): void {
|
||||
if ($isCodeNode(node.getParent())) {
|
||||
node.replace($createCodeTabNode());
|
||||
}
|
||||
}
|
||||
|
||||
function codeTabNodeTransform(node: CodeTabNode): void {
|
||||
if (!$isCodeNode(node.getParent())) {
|
||||
node.replace($createTabNode());
|
||||
}
|
||||
}
|
||||
|
||||
export function registerCodeHighlighting(
|
||||
editor: LexicalEditor,
|
||||
tokenizer?: Tokenizer,
|
||||
@ -878,12 +861,6 @@ export function registerCodeHighlighting(
|
||||
editor.registerNodeTransform(CodeHighlightNode, (node) =>
|
||||
textNodeTransform(node, editor, tokenizer as Tokenizer),
|
||||
),
|
||||
editor.registerNodeTransform(TabNode, (node) => {
|
||||
tabNodeTransform(node);
|
||||
}),
|
||||
editor.registerNodeTransform(CodeTabNode, (node) => {
|
||||
codeTabNodeTransform(node);
|
||||
}),
|
||||
editor.registerCommand(
|
||||
KEY_TAB_COMMAND,
|
||||
(event) => {
|
||||
@ -904,7 +881,7 @@ export function registerCodeHighlighting(
|
||||
if (!$isSelectionInCode(selection)) {
|
||||
return false;
|
||||
}
|
||||
$insertNodes([$createCodeTabNode()]);
|
||||
$insertNodes([$createTabNode()]);
|
||||
return true;
|
||||
},
|
||||
COMMAND_PRIORITY_LOW,
|
||||
|
@ -17,13 +17,9 @@ import type {
|
||||
RangeSelection,
|
||||
SerializedElementNode,
|
||||
Spread,
|
||||
TabNode,
|
||||
} from 'lexical';
|
||||
import type {CodeHighlightNode, CodeTabNode} from '@lexical/code';
|
||||
import {
|
||||
$isCodeHighlightNode,
|
||||
$isCodeTabNode,
|
||||
$createCodeTabNode,
|
||||
} from '@lexical/code';
|
||||
import type {CodeHighlightNode} from '@lexical/code';
|
||||
|
||||
import 'prismjs/components/prism-clike';
|
||||
import 'prismjs/components/prism-javascript';
|
||||
@ -46,8 +42,11 @@ import {
|
||||
$createLineBreakNode,
|
||||
$createParagraphNode,
|
||||
ElementNode,
|
||||
$isTabNode,
|
||||
$createTabNode,
|
||||
} from 'lexical';
|
||||
import {
|
||||
$isCodeHighlightNode,
|
||||
$createCodeHighlightNode,
|
||||
getFirstCodeNodeOfLine,
|
||||
} from './CodeHighlightNode';
|
||||
@ -222,7 +221,7 @@ export class CodeNode extends ElementNode {
|
||||
insertNewAfter(
|
||||
selection: RangeSelection,
|
||||
restoreSelection = true,
|
||||
): null | ParagraphNode | CodeHighlightNode | CodeTabNode {
|
||||
): null | ParagraphNode | CodeHighlightNode | TabNode {
|
||||
const children = this.getChildren();
|
||||
const childrenLength = children.length;
|
||||
|
||||
@ -250,14 +249,14 @@ export class CodeNode extends ElementNode {
|
||||
const firstSelectionNode = firstPoint.getNode();
|
||||
if (
|
||||
$isCodeHighlightNode(firstSelectionNode) ||
|
||||
$isCodeTabNode(firstSelectionNode)
|
||||
$isTabNode(firstSelectionNode)
|
||||
) {
|
||||
let node = getFirstCodeNodeOfLine(firstSelectionNode);
|
||||
const insertNodes = [];
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
if ($isCodeTabNode(node)) {
|
||||
insertNodes.push($createCodeTabNode());
|
||||
if ($isTabNode(node)) {
|
||||
insertNodes.push($createTabNode());
|
||||
node = node.getNextSibling();
|
||||
} else if ($isCodeHighlightNode(node)) {
|
||||
let spaces = 0;
|
||||
|
@ -1,70 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
import type {
|
||||
EditorConfig,
|
||||
LexicalNode,
|
||||
SerializedTabNode,
|
||||
TextNode,
|
||||
} from 'lexical';
|
||||
|
||||
import {$applyNodeReplacement, TabNode} from 'lexical';
|
||||
|
||||
export type SerializedCodeTabNode = SerializedTabNode;
|
||||
|
||||
/** @noInheritDoc */
|
||||
export class CodeTabNode extends TabNode {
|
||||
static getType(): string {
|
||||
return 'code-tab';
|
||||
}
|
||||
|
||||
static clone(node: CodeTabNode): CodeTabNode {
|
||||
return new CodeTabNode(node.__key);
|
||||
}
|
||||
|
||||
static importJSON(_serializedTabNode: SerializedCodeTabNode): CodeTabNode {
|
||||
return $createCodeTabNode();
|
||||
}
|
||||
|
||||
exportJSON(): SerializedTabNode {
|
||||
return {
|
||||
...super.exportJSON(),
|
||||
type: 'code-tab',
|
||||
version: 1,
|
||||
};
|
||||
}
|
||||
|
||||
createDOM(config: EditorConfig): HTMLElement {
|
||||
const span = super.createDOM(config);
|
||||
// TODO pass through theme
|
||||
span.style.letterSpacing = '15px';
|
||||
const text = span.firstChild;
|
||||
if (text !== null) {
|
||||
span.replaceChild(document.createTextNode(' '), text);
|
||||
}
|
||||
return span;
|
||||
}
|
||||
|
||||
updateDOM(
|
||||
prevNode: TextNode,
|
||||
dom: HTMLElement,
|
||||
config: EditorConfig,
|
||||
): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export function $createCodeTabNode(): CodeTabNode {
|
||||
return $applyNodeReplacement(new CodeTabNode());
|
||||
}
|
||||
|
||||
export function $isCodeTabNode(
|
||||
node: LexicalNode | null | undefined,
|
||||
): node is CodeTabNode {
|
||||
return node instanceof CodeTabNode;
|
||||
}
|
@ -9,7 +9,6 @@
|
||||
import {
|
||||
$createCodeNode,
|
||||
$isCodeHighlightNode,
|
||||
$isCodeTabNode,
|
||||
registerCodeHighlighting,
|
||||
} from '@lexical/code';
|
||||
import {registerTabIndentation} from '@lexical/react/LexicalTabIndentationPlugin';
|
||||
@ -24,6 +23,7 @@ import {
|
||||
$getSelection,
|
||||
$isLineBreakNode,
|
||||
$isRangeSelection,
|
||||
$isTabNode,
|
||||
$setSelection,
|
||||
KEY_ARROW_DOWN_COMMAND,
|
||||
KEY_ARROW_UP_COMMAND,
|
||||
@ -180,15 +180,15 @@ describe('LexicalCodeNode tests', () => {
|
||||
});
|
||||
await editor.dispatchCommand(KEY_TAB_COMMAND, tabKeyboardEvent());
|
||||
expect(testEnv.innerHTML).toBe(
|
||||
'<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span data-lexical-text="true">function</span><span style="letter-spacing: 15px;" data-lexical-text="true"> </span></code>',
|
||||
'<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span data-lexical-text="true">function</span><span data-lexical-text="true">\t</span></code>',
|
||||
);
|
||||
|
||||
// CodeNode should only render diffs, make sure that the CodeTabNode is not cloned when
|
||||
// CodeNode should only render diffs, make sure that the TabNode is not cloned when
|
||||
// appending more text
|
||||
let tabKey;
|
||||
await editor.update(() => {
|
||||
tabKey = $dfs()
|
||||
.find(({node}) => $isCodeTabNode(node))
|
||||
.find(({node}) => $isTabNode(node))
|
||||
.node.getKey();
|
||||
$getSelection().insertText('foo');
|
||||
});
|
||||
@ -198,7 +198,7 @@ describe('LexicalCodeNode tests', () => {
|
||||
}),
|
||||
);
|
||||
expect(testEnv.innerHTML).toBe(
|
||||
'<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span data-lexical-text="true">function</span><span style="letter-spacing: 15px;" data-lexical-text="true"> </span><span data-lexical-text="true">foo</span></code>',
|
||||
'<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span data-lexical-text="true">function</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">foo</span></code>',
|
||||
);
|
||||
});
|
||||
|
||||
@ -221,7 +221,7 @@ describe('LexicalCodeNode tests', () => {
|
||||
});
|
||||
await editor.dispatchCommand(KEY_TAB_COMMAND, tabKeyboardEvent());
|
||||
expect(testEnv.innerHTML).toBe(
|
||||
'<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span data-lexical-text="true">f</span><span style="letter-spacing: 15px;" data-lexical-text="true"> </span></code>',
|
||||
'<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span data-lexical-text="true">f</span><span data-lexical-text="true">\t</span></code>',
|
||||
);
|
||||
});
|
||||
|
||||
@ -244,7 +244,7 @@ describe('LexicalCodeNode tests', () => {
|
||||
});
|
||||
await editor.dispatchCommand(KEY_TAB_COMMAND, tabKeyboardEvent());
|
||||
expect(testEnv.innerHTML).toBe(
|
||||
'<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span style="letter-spacing: 15px;" data-lexical-text="true"> </span><span data-lexical-text="true">function</span></code>',
|
||||
'<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span data-lexical-text="true">\t</span><span data-lexical-text="true">function</span></code>',
|
||||
);
|
||||
|
||||
await editor.update(() => {
|
||||
@ -280,7 +280,7 @@ describe('LexicalCodeNode tests', () => {
|
||||
});
|
||||
await editor.dispatchCommand(KEY_TAB_COMMAND, tabKeyboardEvent());
|
||||
expect(testEnv.innerHTML).toBe(
|
||||
'<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span style="letter-spacing: 15px;" data-lexical-text="true"> </span><span data-lexical-text="true">function</span></code>',
|
||||
'<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span data-lexical-text="true">\t</span><span data-lexical-text="true">function</span></code>',
|
||||
);
|
||||
|
||||
await editor.update(() => {
|
||||
@ -322,13 +322,13 @@ describe('LexicalCodeNode tests', () => {
|
||||
await editor.dispatchCommand(KEY_TAB_COMMAND, tabKeyboardEvent());
|
||||
expect(testEnv.innerHTML).toBe(
|
||||
`<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1
|
||||
2"><span style="letter-spacing: 15px;" data-lexical-text="true"> </span><span data-lexical-text="true">hello</span><span style="letter-spacing: 15px;" data-lexical-text="true"> </span><span data-lexical-text="true">world</span><br><span style="letter-spacing: 15px;" data-lexical-text="true"> </span><span data-lexical-text="true">hello</span><span style="letter-spacing: 15px;" data-lexical-text="true"> </span><span data-lexical-text="true">world</span></code>`,
|
||||
2"><span data-lexical-text="true">\t</span><span data-lexical-text="true">hello</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">world</span><br><span data-lexical-text="true">\t</span><span data-lexical-text="true">hello</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">world</span></code>`,
|
||||
);
|
||||
|
||||
await editor.dispatchCommand(KEY_TAB_COMMAND, shiftTabKeyboardEvent());
|
||||
expect(testEnv.innerHTML).toBe(
|
||||
`<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1
|
||||
2"><span data-lexical-text="true">hello</span><span style="letter-spacing: 15px;" data-lexical-text="true"> </span><span data-lexical-text="true">world</span><br><span data-lexical-text="true">hello</span><span style="letter-spacing: 15px;" data-lexical-text="true"> </span><span data-lexical-text="true">world</span></code>`,
|
||||
2"><span data-lexical-text="true">hello</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">world</span><br><span data-lexical-text="true">hello</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">world</span></code>`,
|
||||
);
|
||||
});
|
||||
|
||||
@ -347,7 +347,7 @@ describe('LexicalCodeNode tests', () => {
|
||||
await editor.dispatchCommand(KEY_TAB_COMMAND, tabKeyboardEvent());
|
||||
expect(testEnv.innerHTML)
|
||||
.toBe(`<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1
|
||||
2"><span data-lexical-text="true">hello</span><br><span style="letter-spacing: 15px;" data-lexical-text="true"> </span></code>`);
|
||||
2"><span data-lexical-text="true">hello</span><br><span data-lexical-text="true">\t</span></code>`);
|
||||
});
|
||||
|
||||
test('can outdent at arbitrary points in the line (with tabs)', async () => {
|
||||
@ -390,7 +390,7 @@ describe('LexicalCodeNode tests', () => {
|
||||
await editor.dispatchCommand(KEY_ARROW_UP_COMMAND, keyEvent);
|
||||
expect(testEnv.innerHTML)
|
||||
.toBe(`<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1
|
||||
2"><span data-lexical-text="true">ghi</span><span style="letter-spacing: 15px;" data-lexical-text="true"> </span><span data-lexical-text="true">jkl</span><br><span data-lexical-text="true">abc</span><span style="letter-spacing: 15px;" data-lexical-text="true"> </span><span data-lexical-text="true">def</span></code>`);
|
||||
2"><span data-lexical-text="true">ghi</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">jkl</span><br><span data-lexical-text="true">abc</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">def</span></code>`);
|
||||
});
|
||||
|
||||
test('code blocks can shift multiple lines (with tab)', async () => {
|
||||
@ -424,7 +424,7 @@ describe('LexicalCodeNode tests', () => {
|
||||
expect(testEnv.innerHTML)
|
||||
.toBe(`<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1
|
||||
2
|
||||
3"><span data-lexical-text="true">mno</span><span style="letter-spacing: 15px;" data-lexical-text="true"> </span><span data-lexical-text="true">pqr</span><br><span data-lexical-text="true">abc</span><span style="letter-spacing: 15px;" data-lexical-text="true"> </span><span data-lexical-text="true">def</span><br><span data-lexical-text="true">ghi</span><span style="letter-spacing: 15px;" data-lexical-text="true"> </span><span data-lexical-text="true">jkl</span></code>`);
|
||||
3"><span data-lexical-text="true">mno</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">pqr</span><br><span data-lexical-text="true">abc</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">def</span><br><span data-lexical-text="true">ghi</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">jkl</span></code>`);
|
||||
});
|
||||
|
||||
describe('arrows', () => {
|
||||
@ -493,7 +493,7 @@ describe('LexicalCodeNode tests', () => {
|
||||
expect(selection.isCollapsed()).toBe(true);
|
||||
if (moveTo === 'start') {
|
||||
if (tabOrSpaces === 'tab') {
|
||||
expect($isCodeTabNode(selection.anchor.getNode())).toBe(true);
|
||||
expect($isTabNode(selection.anchor.getNode())).toBe(true);
|
||||
expect(
|
||||
$isCodeHighlightNode(
|
||||
selection.anchor.getNode().getNextSibling(),
|
||||
@ -533,7 +533,7 @@ describe('LexicalCodeNode tests', () => {
|
||||
expect(selection.isCollapsed()).toBe(true);
|
||||
if (moveTo === 'start') {
|
||||
if (tabOrSpaces === 'tab') {
|
||||
expect($isCodeTabNode(selection.anchor.getNode())).toBe(true);
|
||||
expect($isTabNode(selection.anchor.getNode())).toBe(true);
|
||||
expect(
|
||||
$isCodeHighlightNode(
|
||||
selection.anchor.getNode().getNextSibling(),
|
||||
@ -645,7 +645,7 @@ describe('LexicalCodeNode tests', () => {
|
||||
expect(selection.isCollapsed()).toBe(true);
|
||||
if (moveTo === 'start') {
|
||||
if (tabOrSpaces === 'tab') {
|
||||
expect($isCodeTabNode(selection.anchor.getNode())).toBe(true);
|
||||
expect($isTabNode(selection.anchor.getNode())).toBe(true);
|
||||
expect(
|
||||
$isCodeHighlightNode(
|
||||
selection.anchor.getNode().getNextSibling(),
|
||||
@ -690,7 +690,7 @@ describe('LexicalCodeNode tests', () => {
|
||||
expect(selection.isCollapsed()).toBe(true);
|
||||
if (moveTo === 'start') {
|
||||
if (tabOrSpaces === 'tab') {
|
||||
expect($isCodeTabNode(selection.anchor.getNode())).toBe(true);
|
||||
expect($isTabNode(selection.anchor.getNode())).toBe(true);
|
||||
expect(
|
||||
$isCodeHighlightNode(
|
||||
selection.anchor.getNode().getNextSibling(),
|
||||
@ -731,7 +731,7 @@ describe('LexicalCodeNode tests', () => {
|
||||
expect(selection.isCollapsed()).toBe(true);
|
||||
if (moveTo === 'start') {
|
||||
if (tabOrSpaces === 'tab') {
|
||||
expect($isCodeTabNode(selection.anchor.getNode())).toBe(true);
|
||||
expect($isTabNode(selection.anchor.getNode())).toBe(true);
|
||||
expect(
|
||||
$isCodeHighlightNode(
|
||||
selection.anchor.getNode().getNextSibling(),
|
||||
@ -772,7 +772,7 @@ describe('LexicalCodeNode tests', () => {
|
||||
expect(selection.isCollapsed()).toBe(true);
|
||||
if (moveTo === 'start') {
|
||||
if (tabOrSpaces === 'tab') {
|
||||
expect($isCodeTabNode(selection.anchor.getNode())).toBe(true);
|
||||
expect($isTabNode(selection.anchor.getNode())).toBe(true);
|
||||
expect(
|
||||
$isCodeHighlightNode(
|
||||
selection.anchor.getNode().getNextSibling(),
|
||||
@ -818,7 +818,7 @@ describe('LexicalCodeNode tests', () => {
|
||||
expect(selection.isCollapsed()).toBe(true);
|
||||
if (moveTo === 'start') {
|
||||
if (tabOrSpaces === 'tab') {
|
||||
expect($isCodeTabNode(selection.anchor.getNode())).toBe(true);
|
||||
expect($isTabNode(selection.anchor.getNode())).toBe(true);
|
||||
expect(
|
||||
$isCodeHighlightNode(
|
||||
selection.anchor.getNode().getNextSibling(),
|
||||
|
@ -30,5 +30,3 @@ export {
|
||||
} from './CodeHighlightNode';
|
||||
export type {SerializedCodeNode} from './CodeNode';
|
||||
export {$createCodeNode, $isCodeNode, CodeNode} from './CodeNode';
|
||||
export type {SerializedCodeTabNode} from './CodeTabNode';
|
||||
export {$createCodeTabNode, $isCodeTabNode, CodeTabNode} from './CodeTabNode';
|
||||
|
@ -361,7 +361,7 @@ test.describe('CodeBlock', () => {
|
||||
;
|
||||
</span>
|
||||
<br />
|
||||
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
|
||||
<span data-lexical-text="true"></span>
|
||||
<span
|
||||
class="PlaygroundEditorTheme__tokenFunction"
|
||||
data-lexical-text="true">
|
||||
@ -388,7 +388,7 @@ test.describe('CodeBlock', () => {
|
||||
;
|
||||
</span>
|
||||
<br />
|
||||
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
|
||||
<span data-lexical-text="true"></span>
|
||||
<span
|
||||
class="PlaygroundEditorTheme__tokenPunctuation"
|
||||
data-lexical-text="true">
|
||||
@ -447,7 +447,7 @@ test.describe('CodeBlock', () => {
|
||||
{
|
||||
</span>
|
||||
<br />
|
||||
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
|
||||
<span data-lexical-text="true"></span>
|
||||
<span
|
||||
class="PlaygroundEditorTheme__tokenFunction"
|
||||
data-lexical-text="true">
|
||||
@ -494,8 +494,8 @@ test.describe('CodeBlock', () => {
|
||||
spellcheck="false"
|
||||
data-gutter="123"
|
||||
data-highlight-language="javascript">
|
||||
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
|
||||
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
|
||||
<span data-lexical-text="true"></span>
|
||||
<span data-lexical-text="true"></span>
|
||||
<span
|
||||
class="PlaygroundEditorTheme__tokenAttr"
|
||||
data-lexical-text="true">
|
||||
@ -520,9 +520,9 @@ test.describe('CodeBlock', () => {
|
||||
{
|
||||
</span>
|
||||
<br />
|
||||
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
|
||||
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
|
||||
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
|
||||
<span data-lexical-text="true"></span>
|
||||
<span data-lexical-text="true"></span>
|
||||
<span data-lexical-text="true"></span>
|
||||
<span
|
||||
class="PlaygroundEditorTheme__tokenFunction"
|
||||
data-lexical-text="true">
|
||||
@ -544,8 +544,8 @@ test.describe('CodeBlock', () => {
|
||||
;
|
||||
</span>
|
||||
<br />
|
||||
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
|
||||
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
|
||||
<span data-lexical-text="true"></span>
|
||||
<span data-lexical-text="true"></span>
|
||||
<span
|
||||
class="PlaygroundEditorTheme__tokenPunctuation"
|
||||
data-lexical-text="true">
|
||||
@ -567,7 +567,7 @@ test.describe('CodeBlock', () => {
|
||||
spellcheck="false"
|
||||
data-gutter="123"
|
||||
data-highlight-language="javascript">
|
||||
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
|
||||
<span data-lexical-text="true"></span>
|
||||
<span
|
||||
class="PlaygroundEditorTheme__tokenAttr"
|
||||
data-lexical-text="true">
|
||||
@ -592,8 +592,8 @@ test.describe('CodeBlock', () => {
|
||||
{
|
||||
</span>
|
||||
<br />
|
||||
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
|
||||
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
|
||||
<span data-lexical-text="true"></span>
|
||||
<span data-lexical-text="true"></span>
|
||||
<span
|
||||
class="PlaygroundEditorTheme__tokenFunction"
|
||||
data-lexical-text="true">
|
||||
@ -615,7 +615,7 @@ test.describe('CodeBlock', () => {
|
||||
;
|
||||
</span>
|
||||
<br />
|
||||
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
|
||||
<span data-lexical-text="true"></span>
|
||||
<span
|
||||
class="PlaygroundEditorTheme__tokenPunctuation"
|
||||
data-lexical-text="true">
|
||||
@ -1091,10 +1091,10 @@ test.describe('CodeBlock', () => {
|
||||
spellcheck="false"
|
||||
data-gutter="12"
|
||||
data-highlight-language="javascript">
|
||||
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
|
||||
<span data-lexical-text="true"></span>
|
||||
<span data-lexical-text="true">a b</span>
|
||||
<br />
|
||||
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
|
||||
<span data-lexical-text="true"></span>
|
||||
<span data-lexical-text="true">c d</span>
|
||||
</code>
|
||||
`,
|
||||
|
@ -66,7 +66,7 @@ test.describe('Tab', () => {
|
||||
spellcheck="false"
|
||||
data-gutter="1"
|
||||
data-highlight-language="javascript">
|
||||
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
|
||||
<span data-lexical-text="true"></span>
|
||||
<span
|
||||
class="PlaygroundEditorTheme__tokenAttr"
|
||||
data-lexical-text="true">
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import type {Klass, LexicalNode} from 'lexical';
|
||||
|
||||
import {CodeHighlightNode, CodeNode, CodeTabNode} from '@lexical/code';
|
||||
import {CodeHighlightNode, CodeNode} from '@lexical/code';
|
||||
import {HashtagNode} from '@lexical/hashtag';
|
||||
import {AutoLinkNode, LinkNode} from '@lexical/link';
|
||||
import {ListItemNode, ListNode} from '@lexical/list';
|
||||
@ -67,7 +67,6 @@ const PlaygroundNodes: Array<Klass<LexicalNode>> = [
|
||||
CollapsibleContainerNode,
|
||||
CollapsibleContentNode,
|
||||
CollapsibleTitleNode,
|
||||
CodeTabNode,
|
||||
];
|
||||
|
||||
export default PlaygroundNodes;
|
||||
|
@ -101,6 +101,7 @@
|
||||
margin-bottom: 8px;
|
||||
overflow-x: auto;
|
||||
position: relative;
|
||||
tab-size: 2;
|
||||
}
|
||||
.PlaygroundEditorTheme__code:before {
|
||||
content: attr(data-gutter);
|
||||
|
@ -18,7 +18,7 @@ import type {
|
||||
SerializedTextNode,
|
||||
} from 'lexical';
|
||||
|
||||
import {CodeHighlightNode, CodeNode, CodeTabNode} from '@lexical/code';
|
||||
import {CodeHighlightNode, CodeNode} from '@lexical/code';
|
||||
import {HashtagNode} from '@lexical/hashtag';
|
||||
import {AutoLinkNode, LinkNode} from '@lexical/link';
|
||||
import {ListItemNode, ListNode} from '@lexical/list';
|
||||
@ -422,7 +422,6 @@ const DEFAULT_NODES = [
|
||||
ListItemNode,
|
||||
QuoteNode,
|
||||
CodeNode,
|
||||
CodeTabNode,
|
||||
TableNode,
|
||||
TableCellNode,
|
||||
TableRowNode,
|
||||
|
Reference in New Issue
Block a user