mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-07-30 04:23:11 +03:00
Lexical: Imported core lexical libs
Imported at 0.17.1, Modified to work in-app. Added & configured test dependancies. Tests need to be altered to avoid using non-included deps including react dependancies.
This commit is contained in:
918
resources/js/wysiwyg/lexical/selection/__tests__/utils/index.ts
Normal file
918
resources/js/wysiwyg/lexical/selection/__tests__/utils/index.ts
Normal file
@ -0,0 +1,918 @@
|
||||
/**
|
||||
* 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 {
|
||||
$createTextNode,
|
||||
$getSelection,
|
||||
$isNodeSelection,
|
||||
$isRangeSelection,
|
||||
$isTextNode,
|
||||
LexicalEditor,
|
||||
PointType,
|
||||
} from 'lexical';
|
||||
|
||||
Object.defineProperty(HTMLElement.prototype, 'contentEditable', {
|
||||
get() {
|
||||
return this.getAttribute('contenteditable');
|
||||
},
|
||||
|
||||
set(value) {
|
||||
this.setAttribute('contenteditable', value);
|
||||
},
|
||||
});
|
||||
|
||||
type Segment = {
|
||||
index: number;
|
||||
isWordLike: boolean;
|
||||
segment: string;
|
||||
};
|
||||
|
||||
if (!Selection.prototype.modify) {
|
||||
const wordBreakPolyfillRegex =
|
||||
/[\s.,\\/#!$%^&*;:{}=\-`~()\uD800-\uDBFF\uDC00-\uDFFF\u3000-\u303F]/u;
|
||||
|
||||
const pushSegment = function (
|
||||
segments: Array<Segment>,
|
||||
index: number,
|
||||
str: string,
|
||||
isWordLike: boolean,
|
||||
): void {
|
||||
segments.push({
|
||||
index: index - str.length,
|
||||
isWordLike,
|
||||
segment: str,
|
||||
});
|
||||
};
|
||||
|
||||
const getWordsFromString = function (string: string): Array<Segment> {
|
||||
const segments: Segment[] = [];
|
||||
let wordString = '';
|
||||
let nonWordString = '';
|
||||
let i;
|
||||
|
||||
for (i = 0; i < string.length; i++) {
|
||||
const char = string[i];
|
||||
|
||||
if (wordBreakPolyfillRegex.test(char)) {
|
||||
if (wordString !== '') {
|
||||
pushSegment(segments, i, wordString, true);
|
||||
wordString = '';
|
||||
}
|
||||
|
||||
nonWordString += char;
|
||||
} else {
|
||||
if (nonWordString !== '') {
|
||||
pushSegment(segments, i, nonWordString, false);
|
||||
nonWordString = '';
|
||||
}
|
||||
|
||||
wordString += char;
|
||||
}
|
||||
}
|
||||
|
||||
if (wordString !== '') {
|
||||
pushSegment(segments, i, wordString, true);
|
||||
}
|
||||
|
||||
if (nonWordString !== '') {
|
||||
pushSegment(segments, i, nonWordString, false);
|
||||
}
|
||||
|
||||
return segments;
|
||||
};
|
||||
|
||||
Selection.prototype.modify = function (alter, direction, granularity) {
|
||||
// This is not a thorough implementation, it was more to get tests working
|
||||
// given the refactor to use this selection method.
|
||||
const symbol = Object.getOwnPropertySymbols(this)[0];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const impl = (this as any)[symbol];
|
||||
const focus = impl._focus;
|
||||
const anchor = impl._anchor;
|
||||
|
||||
if (granularity === 'character') {
|
||||
let anchorNode = anchor.node;
|
||||
let anchorOffset = anchor.offset;
|
||||
let _$isTextNode = false;
|
||||
|
||||
if (anchorNode.nodeType === 3) {
|
||||
_$isTextNode = true;
|
||||
anchorNode = anchorNode.parentElement;
|
||||
} else if (anchorNode.nodeName === 'BR') {
|
||||
const parentNode = anchorNode.parentElement;
|
||||
const childNodes = Array.from(parentNode.childNodes);
|
||||
anchorOffset = childNodes.indexOf(anchorNode);
|
||||
anchorNode = parentNode;
|
||||
}
|
||||
|
||||
if (direction === 'backward') {
|
||||
if (anchorOffset === 0) {
|
||||
let prevSibling = anchorNode.previousSibling;
|
||||
|
||||
if (prevSibling === null) {
|
||||
prevSibling = anchorNode.parentElement.previousSibling.lastChild;
|
||||
}
|
||||
|
||||
if (prevSibling.nodeName === 'P') {
|
||||
prevSibling = prevSibling.firstChild;
|
||||
}
|
||||
|
||||
if (prevSibling.nodeName === 'BR') {
|
||||
anchor.node = prevSibling;
|
||||
anchor.offset = 0;
|
||||
} else {
|
||||
anchor.node = prevSibling.firstChild;
|
||||
anchor.offset = anchor.node.nodeValue.length - 1;
|
||||
}
|
||||
} else if (!_$isTextNode) {
|
||||
anchor.node = anchorNode.childNodes[anchorOffset - 1];
|
||||
anchor.offset = anchor.node.nodeValue.length - 1;
|
||||
} else {
|
||||
anchor.offset--;
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
(_$isTextNode && anchorOffset === anchorNode.textContent.length) ||
|
||||
(!_$isTextNode &&
|
||||
(anchorNode.childNodes.length === anchorOffset ||
|
||||
(anchorNode.childNodes.length === 1 &&
|
||||
anchorNode.firstChild.nodeName === 'BR')))
|
||||
) {
|
||||
let nextSibling = anchorNode.nextSibling;
|
||||
|
||||
if (nextSibling === null) {
|
||||
nextSibling = anchorNode.parentElement.nextSibling.lastChild;
|
||||
}
|
||||
|
||||
if (nextSibling.nodeName === 'P') {
|
||||
nextSibling = nextSibling.lastChild;
|
||||
}
|
||||
|
||||
if (nextSibling.nodeName === 'BR') {
|
||||
anchor.node = nextSibling;
|
||||
anchor.offset = 0;
|
||||
} else {
|
||||
anchor.node = nextSibling.firstChild;
|
||||
anchor.offset = 0;
|
||||
}
|
||||
} else {
|
||||
anchor.offset++;
|
||||
}
|
||||
}
|
||||
} else if (granularity === 'word') {
|
||||
const anchorNode = this.anchorNode!;
|
||||
const targetTextContent =
|
||||
direction === 'backward'
|
||||
? anchorNode.textContent!.slice(0, this.anchorOffset)
|
||||
: anchorNode.textContent!.slice(this.anchorOffset);
|
||||
const segments = getWordsFromString(targetTextContent);
|
||||
const segmentsLength = segments.length;
|
||||
let index = anchor.offset;
|
||||
let foundWordNode = false;
|
||||
|
||||
if (direction === 'backward') {
|
||||
for (let i = segmentsLength - 1; i >= 0; i--) {
|
||||
const segment = segments[i];
|
||||
const nextIndex = segment.index;
|
||||
|
||||
if (segment.isWordLike) {
|
||||
index = nextIndex;
|
||||
foundWordNode = true;
|
||||
} else if (foundWordNode) {
|
||||
break;
|
||||
} else {
|
||||
index = nextIndex;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < segmentsLength; i++) {
|
||||
const segment = segments[i];
|
||||
const nextIndex = segment.index + segment.segment.length;
|
||||
|
||||
if (segment.isWordLike) {
|
||||
index = nextIndex;
|
||||
foundWordNode = true;
|
||||
} else if (foundWordNode) {
|
||||
break;
|
||||
} else {
|
||||
index = nextIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (direction === 'forward') {
|
||||
index += anchor.offset;
|
||||
}
|
||||
|
||||
anchor.offset = index;
|
||||
}
|
||||
|
||||
if (alter === 'move') {
|
||||
focus.offset = anchor.offset;
|
||||
focus.node = anchor.node;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function printWhitespace(whitespaceCharacter: string) {
|
||||
return whitespaceCharacter.charCodeAt(0) === 160
|
||||
? ' '
|
||||
: whitespaceCharacter;
|
||||
}
|
||||
|
||||
export function insertText(text: string) {
|
||||
return {
|
||||
text,
|
||||
type: 'insert_text',
|
||||
};
|
||||
}
|
||||
|
||||
export function insertTokenNode(text: string) {
|
||||
return {
|
||||
text,
|
||||
type: 'insert_token_node',
|
||||
};
|
||||
}
|
||||
|
||||
export function insertSegmentedNode(text: string) {
|
||||
return {
|
||||
text,
|
||||
type: 'insert_segmented_node',
|
||||
};
|
||||
}
|
||||
|
||||
export function convertToTokenNode() {
|
||||
return {
|
||||
text: null,
|
||||
type: 'convert_to_token_node',
|
||||
};
|
||||
}
|
||||
|
||||
export function convertToSegmentedNode() {
|
||||
return {
|
||||
text: null,
|
||||
type: 'convert_to_segmented_node',
|
||||
};
|
||||
}
|
||||
|
||||
export function insertParagraph() {
|
||||
return {
|
||||
type: 'insert_paragraph',
|
||||
};
|
||||
}
|
||||
|
||||
export function deleteWordBackward(n: number | null | undefined) {
|
||||
return {
|
||||
text: null,
|
||||
times: n,
|
||||
type: 'delete_word_backward',
|
||||
};
|
||||
}
|
||||
|
||||
export function deleteWordForward(n: number | null | undefined) {
|
||||
return {
|
||||
text: null,
|
||||
times: n,
|
||||
type: 'delete_word_forward',
|
||||
};
|
||||
}
|
||||
|
||||
export function moveBackward(n: number | null | undefined) {
|
||||
return {
|
||||
text: null,
|
||||
times: n,
|
||||
type: 'move_backward',
|
||||
};
|
||||
}
|
||||
|
||||
export function moveForward(n: number | null | undefined) {
|
||||
return {
|
||||
text: null,
|
||||
times: n,
|
||||
type: 'move_forward',
|
||||
};
|
||||
}
|
||||
|
||||
export function moveEnd() {
|
||||
return {
|
||||
type: 'move_end',
|
||||
};
|
||||
}
|
||||
|
||||
export function deleteBackward(n: number | null | undefined) {
|
||||
return {
|
||||
text: null,
|
||||
times: n,
|
||||
type: 'delete_backward',
|
||||
};
|
||||
}
|
||||
|
||||
export function deleteForward(n: number | null | undefined) {
|
||||
return {
|
||||
text: null,
|
||||
times: n,
|
||||
type: 'delete_forward',
|
||||
};
|
||||
}
|
||||
|
||||
export function formatBold() {
|
||||
return {
|
||||
format: 'bold',
|
||||
type: 'format_text',
|
||||
};
|
||||
}
|
||||
|
||||
export function formatItalic() {
|
||||
return {
|
||||
format: 'italic',
|
||||
type: 'format_text',
|
||||
};
|
||||
}
|
||||
|
||||
export function formatStrikeThrough() {
|
||||
return {
|
||||
format: 'strikethrough',
|
||||
type: 'format_text',
|
||||
};
|
||||
}
|
||||
|
||||
export function formatUnderline() {
|
||||
return {
|
||||
format: 'underline',
|
||||
type: 'format_text',
|
||||
};
|
||||
}
|
||||
|
||||
export function redo(n: number | null | undefined) {
|
||||
return {
|
||||
text: null,
|
||||
times: n,
|
||||
type: 'redo',
|
||||
};
|
||||
}
|
||||
|
||||
export function undo(n: number | null | undefined) {
|
||||
return {
|
||||
text: null,
|
||||
times: n,
|
||||
type: 'undo',
|
||||
};
|
||||
}
|
||||
|
||||
export function pastePlain(text: string) {
|
||||
return {
|
||||
text: text,
|
||||
type: 'paste_plain',
|
||||
};
|
||||
}
|
||||
|
||||
export function pasteLexical(text: string) {
|
||||
return {
|
||||
text: text,
|
||||
type: 'paste_lexical',
|
||||
};
|
||||
}
|
||||
|
||||
export function pasteHTML(text: string) {
|
||||
return {
|
||||
text: text,
|
||||
type: 'paste_html',
|
||||
};
|
||||
}
|
||||
|
||||
export function moveNativeSelection(
|
||||
anchorPath: number[],
|
||||
anchorOffset: number,
|
||||
focusPath: number[],
|
||||
focusOffset: number,
|
||||
) {
|
||||
return {
|
||||
anchorOffset,
|
||||
anchorPath,
|
||||
focusOffset,
|
||||
focusPath,
|
||||
type: 'move_native_selection',
|
||||
};
|
||||
}
|
||||
|
||||
export function getNodeFromPath(path: number[], rootElement: Node) {
|
||||
let node = rootElement;
|
||||
|
||||
for (let i = 0; i < path.length; i++) {
|
||||
node = node.childNodes[path[i]];
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
export function setNativeSelection(
|
||||
anchorNode: Node,
|
||||
anchorOffset: number,
|
||||
focusNode: Node,
|
||||
focusOffset: number,
|
||||
) {
|
||||
const domSelection = window.getSelection()!;
|
||||
const range = document.createRange();
|
||||
range.setStart(anchorNode, anchorOffset);
|
||||
range.setEnd(focusNode, focusOffset);
|
||||
domSelection.removeAllRanges();
|
||||
domSelection.addRange(range);
|
||||
Promise.resolve().then(() => {
|
||||
document.dispatchEvent(new Event('selectionchange'));
|
||||
});
|
||||
}
|
||||
|
||||
export function setNativeSelectionWithPaths(
|
||||
rootElement: Node,
|
||||
anchorPath: number[],
|
||||
anchorOffset: number,
|
||||
focusPath: number[],
|
||||
focusOffset: number,
|
||||
) {
|
||||
const anchorNode = getNodeFromPath(anchorPath, rootElement);
|
||||
const focusNode = getNodeFromPath(focusPath, rootElement);
|
||||
setNativeSelection(anchorNode, anchorOffset, focusNode, focusOffset);
|
||||
}
|
||||
|
||||
function getLastTextNode(startingNode: Node) {
|
||||
let node = startingNode;
|
||||
|
||||
mainLoop: while (node !== null) {
|
||||
if (node !== startingNode && node.nodeType === 3) {
|
||||
return node;
|
||||
}
|
||||
|
||||
const child = node.lastChild;
|
||||
|
||||
if (child !== null) {
|
||||
node = child;
|
||||
continue;
|
||||
}
|
||||
|
||||
const previousSibling = node.previousSibling;
|
||||
|
||||
if (previousSibling !== null) {
|
||||
node = previousSibling;
|
||||
continue;
|
||||
}
|
||||
|
||||
let parent = node.parentNode;
|
||||
|
||||
while (parent !== null) {
|
||||
const parentSibling = parent.previousSibling;
|
||||
|
||||
if (parentSibling !== null) {
|
||||
node = parentSibling;
|
||||
continue mainLoop;
|
||||
}
|
||||
|
||||
parent = parent.parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getNextTextNode(startingNode: Node) {
|
||||
let node = startingNode;
|
||||
|
||||
mainLoop: while (node !== null) {
|
||||
if (node !== startingNode && node.nodeType === 3) {
|
||||
return node;
|
||||
}
|
||||
|
||||
const child = node.firstChild;
|
||||
|
||||
if (child !== null) {
|
||||
node = child;
|
||||
continue;
|
||||
}
|
||||
|
||||
const nextSibling = node.nextSibling;
|
||||
|
||||
if (nextSibling !== null) {
|
||||
node = nextSibling;
|
||||
continue;
|
||||
}
|
||||
|
||||
let parent = node.parentNode;
|
||||
|
||||
while (parent !== null) {
|
||||
const parentSibling = parent.nextSibling;
|
||||
|
||||
if (parentSibling !== null) {
|
||||
node = parentSibling;
|
||||
continue mainLoop;
|
||||
}
|
||||
|
||||
parent = parent.parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function moveNativeSelectionBackward() {
|
||||
const domSelection = window.getSelection()!;
|
||||
let anchorNode = domSelection.anchorNode!;
|
||||
let anchorOffset = domSelection.anchorOffset!;
|
||||
|
||||
if (domSelection.isCollapsed) {
|
||||
const target = (
|
||||
anchorNode.nodeType === 1 ? anchorNode : anchorNode.parentNode
|
||||
)!;
|
||||
const keyDownEvent = new KeyboardEvent('keydown', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
key: 'ArrowLeft',
|
||||
keyCode: 37,
|
||||
});
|
||||
target.dispatchEvent(keyDownEvent);
|
||||
|
||||
if (!keyDownEvent.defaultPrevented) {
|
||||
if (anchorNode.nodeType === 3) {
|
||||
if (anchorOffset === 0) {
|
||||
const lastTextNode = getLastTextNode(anchorNode);
|
||||
|
||||
if (lastTextNode === null) {
|
||||
throw new Error('moveNativeSelectionBackward: TODO');
|
||||
} else {
|
||||
const textLength = lastTextNode.nodeValue!.length;
|
||||
setNativeSelection(
|
||||
lastTextNode,
|
||||
textLength,
|
||||
lastTextNode,
|
||||
textLength,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
setNativeSelection(
|
||||
anchorNode,
|
||||
anchorOffset - 1,
|
||||
anchorNode,
|
||||
anchorOffset - 1,
|
||||
);
|
||||
}
|
||||
} else if (anchorNode.nodeType === 1) {
|
||||
if (anchorNode.nodeName === 'BR') {
|
||||
const parentNode = anchorNode.parentNode!;
|
||||
const childNodes = Array.from(parentNode.childNodes);
|
||||
anchorOffset = childNodes.indexOf(anchorNode as ChildNode);
|
||||
anchorNode = parentNode;
|
||||
} else {
|
||||
anchorOffset--;
|
||||
}
|
||||
|
||||
setNativeSelection(anchorNode, anchorOffset, anchorNode, anchorOffset);
|
||||
} else {
|
||||
throw new Error('moveNativeSelectionBackward: TODO');
|
||||
}
|
||||
}
|
||||
|
||||
const keyUpEvent = new KeyboardEvent('keyup', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
key: 'ArrowLeft',
|
||||
keyCode: 37,
|
||||
});
|
||||
target.dispatchEvent(keyUpEvent);
|
||||
} else {
|
||||
throw new Error('moveNativeSelectionBackward: TODO');
|
||||
}
|
||||
}
|
||||
|
||||
function moveNativeSelectionForward() {
|
||||
const domSelection = window.getSelection()!;
|
||||
const anchorNode = domSelection.anchorNode!;
|
||||
const anchorOffset = domSelection.anchorOffset!;
|
||||
|
||||
if (domSelection.isCollapsed) {
|
||||
const target = (
|
||||
anchorNode.nodeType === 1 ? anchorNode : anchorNode.parentNode
|
||||
)!;
|
||||
const keyDownEvent = new KeyboardEvent('keydown', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
key: 'ArrowRight',
|
||||
keyCode: 39,
|
||||
});
|
||||
target.dispatchEvent(keyDownEvent);
|
||||
|
||||
if (!keyDownEvent.defaultPrevented) {
|
||||
if (anchorNode.nodeType === 3) {
|
||||
const text = anchorNode.nodeValue!;
|
||||
|
||||
if (text.length === anchorOffset) {
|
||||
const nextTextNode = getNextTextNode(anchorNode);
|
||||
|
||||
if (nextTextNode === null) {
|
||||
throw new Error('moveNativeSelectionForward: TODO');
|
||||
} else {
|
||||
setNativeSelection(nextTextNode, 0, nextTextNode, 0);
|
||||
}
|
||||
} else {
|
||||
setNativeSelection(
|
||||
anchorNode,
|
||||
anchorOffset + 1,
|
||||
anchorNode,
|
||||
anchorOffset + 1,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw new Error('moveNativeSelectionForward: TODO');
|
||||
}
|
||||
}
|
||||
|
||||
const keyUpEvent = new KeyboardEvent('keyup', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
key: 'ArrowRight',
|
||||
keyCode: 39,
|
||||
});
|
||||
target.dispatchEvent(keyUpEvent);
|
||||
} else {
|
||||
throw new Error('moveNativeSelectionForward: TODO');
|
||||
}
|
||||
}
|
||||
|
||||
export async function applySelectionInputs(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
inputs: Record<string, any>[],
|
||||
update: (fn: () => void) => Promise<void>,
|
||||
editor: LexicalEditor,
|
||||
) {
|
||||
const rootElement = editor.getRootElement()!;
|
||||
|
||||
for (let i = 0; i < inputs.length; i++) {
|
||||
const input = inputs[i];
|
||||
const times = input?.times ?? 1;
|
||||
|
||||
for (let j = 0; j < times; j++) {
|
||||
await update(() => {
|
||||
const selection = $getSelection()!;
|
||||
|
||||
switch (input.type) {
|
||||
case 'insert_text': {
|
||||
selection.insertText(input.text);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'insert_paragraph': {
|
||||
if ($isRangeSelection(selection)) {
|
||||
selection.insertParagraph();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'move_backward': {
|
||||
moveNativeSelectionBackward();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'move_forward': {
|
||||
moveNativeSelectionForward();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'move_end': {
|
||||
if ($isRangeSelection(selection)) {
|
||||
const anchorNode = selection.anchor.getNode();
|
||||
if ($isTextNode(anchorNode)) {
|
||||
anchorNode.select();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'delete_backward': {
|
||||
if ($isRangeSelection(selection)) {
|
||||
selection.deleteCharacter(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'delete_forward': {
|
||||
if ($isRangeSelection(selection)) {
|
||||
selection.deleteCharacter(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'delete_word_backward': {
|
||||
if ($isRangeSelection(selection)) {
|
||||
selection.deleteWord(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'delete_word_forward': {
|
||||
if ($isRangeSelection(selection)) {
|
||||
selection.deleteWord(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'format_text': {
|
||||
if ($isRangeSelection(selection)) {
|
||||
selection.formatText(input.format);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'move_native_selection': {
|
||||
setNativeSelectionWithPaths(
|
||||
rootElement,
|
||||
input.anchorPath,
|
||||
input.anchorOffset,
|
||||
input.focusPath,
|
||||
input.focusOffset,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'insert_token_node': {
|
||||
const text = $createTextNode(input.text);
|
||||
text.setMode('token');
|
||||
if ($isRangeSelection(selection)) {
|
||||
selection.insertNodes([text]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'insert_segmented_node': {
|
||||
const text = $createTextNode(input.text);
|
||||
text.setMode('segmented');
|
||||
if ($isRangeSelection(selection)) {
|
||||
selection.insertNodes([text]);
|
||||
}
|
||||
text.selectNext();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'convert_to_token_node': {
|
||||
const text = $createTextNode(selection.getTextContent());
|
||||
text.setMode('token');
|
||||
if ($isRangeSelection(selection)) {
|
||||
selection.insertNodes([text]);
|
||||
}
|
||||
text.selectNext();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'convert_to_segmented_node': {
|
||||
const text = $createTextNode(selection.getTextContent());
|
||||
text.setMode('segmented');
|
||||
if ($isRangeSelection(selection)) {
|
||||
selection.insertNodes([text]);
|
||||
}
|
||||
text.selectNext();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'undo': {
|
||||
rootElement.dispatchEvent(
|
||||
new KeyboardEvent('keydown', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
ctrlKey: true,
|
||||
key: 'z',
|
||||
keyCode: 90,
|
||||
}),
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'redo': {
|
||||
rootElement.dispatchEvent(
|
||||
new KeyboardEvent('keydown', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
ctrlKey: true,
|
||||
key: 'z',
|
||||
keyCode: 90,
|
||||
shiftKey: true,
|
||||
}),
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'paste_plain': {
|
||||
rootElement.dispatchEvent(
|
||||
Object.assign(
|
||||
new Event('paste', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
}),
|
||||
{
|
||||
clipboardData: {
|
||||
getData: (type: string) => {
|
||||
if (type === 'text/plain') {
|
||||
return input.text;
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'paste_lexical': {
|
||||
rootElement.dispatchEvent(
|
||||
Object.assign(
|
||||
new Event('paste', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
}),
|
||||
{
|
||||
clipboardData: {
|
||||
getData: (type: string) => {
|
||||
if (type === 'application/x-lexical-editor') {
|
||||
return input.text;
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'paste_html': {
|
||||
rootElement.dispatchEvent(
|
||||
Object.assign(
|
||||
new Event('paste', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
}),
|
||||
{
|
||||
clipboardData: {
|
||||
getData: (type: string) => {
|
||||
if (type === 'text/html') {
|
||||
return input.text;
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function $setAnchorPoint(
|
||||
point: Pick<PointType, 'type' | 'offset' | 'key'>,
|
||||
) {
|
||||
const selection = $getSelection();
|
||||
|
||||
if (!$isRangeSelection(selection)) {
|
||||
const dummyTextNode = $createTextNode();
|
||||
dummyTextNode.select();
|
||||
return $setAnchorPoint(point);
|
||||
}
|
||||
|
||||
if ($isNodeSelection(selection)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const anchor = selection.anchor;
|
||||
anchor.type = point.type;
|
||||
anchor.offset = point.offset;
|
||||
anchor.key = point.key;
|
||||
}
|
||||
|
||||
export function $setFocusPoint(
|
||||
point: Pick<PointType, 'type' | 'offset' | 'key'>,
|
||||
) {
|
||||
const selection = $getSelection();
|
||||
|
||||
if (!$isRangeSelection(selection)) {
|
||||
const dummyTextNode = $createTextNode();
|
||||
dummyTextNode.select();
|
||||
return $setFocusPoint(point);
|
||||
}
|
||||
|
||||
if ($isNodeSelection(selection)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const focus = selection.focus;
|
||||
focus.type = point.type;
|
||||
focus.offset = point.offset;
|
||||
focus.key = point.key;
|
||||
}
|
Reference in New Issue
Block a user