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:
170
resources/js/wysiwyg/lexical/utils/markSelection.ts
Normal file
170
resources/js/wysiwyg/lexical/utils/markSelection.ts
Normal file
@ -0,0 +1,170 @@
|
||||
/**
|
||||
* 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 {
|
||||
$getSelection,
|
||||
$isRangeSelection,
|
||||
type EditorState,
|
||||
ElementNode,
|
||||
type LexicalEditor,
|
||||
TextNode,
|
||||
} from 'lexical';
|
||||
import invariant from 'lexical/shared/invariant';
|
||||
|
||||
import mergeRegister from './mergeRegister';
|
||||
import positionNodeOnRange from './positionNodeOnRange';
|
||||
import px from './px';
|
||||
|
||||
export default function markSelection(
|
||||
editor: LexicalEditor,
|
||||
onReposition?: (node: Array<HTMLElement>) => void,
|
||||
): () => void {
|
||||
let previousAnchorNode: null | TextNode | ElementNode = null;
|
||||
let previousAnchorOffset: null | number = null;
|
||||
let previousFocusNode: null | TextNode | ElementNode = null;
|
||||
let previousFocusOffset: null | number = null;
|
||||
let removeRangeListener: () => void = () => {};
|
||||
function compute(editorState: EditorState) {
|
||||
editorState.read(() => {
|
||||
const selection = $getSelection();
|
||||
if (!$isRangeSelection(selection)) {
|
||||
// TODO
|
||||
previousAnchorNode = null;
|
||||
previousAnchorOffset = null;
|
||||
previousFocusNode = null;
|
||||
previousFocusOffset = null;
|
||||
removeRangeListener();
|
||||
removeRangeListener = () => {};
|
||||
return;
|
||||
}
|
||||
const {anchor, focus} = selection;
|
||||
const currentAnchorNode = anchor.getNode();
|
||||
const currentAnchorNodeKey = currentAnchorNode.getKey();
|
||||
const currentAnchorOffset = anchor.offset;
|
||||
const currentFocusNode = focus.getNode();
|
||||
const currentFocusNodeKey = currentFocusNode.getKey();
|
||||
const currentFocusOffset = focus.offset;
|
||||
const currentAnchorNodeDOM = editor.getElementByKey(currentAnchorNodeKey);
|
||||
const currentFocusNodeDOM = editor.getElementByKey(currentFocusNodeKey);
|
||||
const differentAnchorDOM =
|
||||
previousAnchorNode === null ||
|
||||
currentAnchorNodeDOM === null ||
|
||||
currentAnchorOffset !== previousAnchorOffset ||
|
||||
currentAnchorNodeKey !== previousAnchorNode.getKey() ||
|
||||
(currentAnchorNode !== previousAnchorNode &&
|
||||
(!(previousAnchorNode instanceof TextNode) ||
|
||||
currentAnchorNode.updateDOM(
|
||||
previousAnchorNode,
|
||||
currentAnchorNodeDOM,
|
||||
editor._config,
|
||||
)));
|
||||
const differentFocusDOM =
|
||||
previousFocusNode === null ||
|
||||
currentFocusNodeDOM === null ||
|
||||
currentFocusOffset !== previousFocusOffset ||
|
||||
currentFocusNodeKey !== previousFocusNode.getKey() ||
|
||||
(currentFocusNode !== previousFocusNode &&
|
||||
(!(previousFocusNode instanceof TextNode) ||
|
||||
currentFocusNode.updateDOM(
|
||||
previousFocusNode,
|
||||
currentFocusNodeDOM,
|
||||
editor._config,
|
||||
)));
|
||||
if (differentAnchorDOM || differentFocusDOM) {
|
||||
const anchorHTMLElement = editor.getElementByKey(
|
||||
anchor.getNode().getKey(),
|
||||
);
|
||||
const focusHTMLElement = editor.getElementByKey(
|
||||
focus.getNode().getKey(),
|
||||
);
|
||||
// TODO handle selection beyond the common TextNode
|
||||
if (
|
||||
anchorHTMLElement !== null &&
|
||||
focusHTMLElement !== null &&
|
||||
anchorHTMLElement.tagName === 'SPAN' &&
|
||||
focusHTMLElement.tagName === 'SPAN'
|
||||
) {
|
||||
const range = document.createRange();
|
||||
let firstHTMLElement;
|
||||
let firstOffset;
|
||||
let lastHTMLElement;
|
||||
let lastOffset;
|
||||
if (focus.isBefore(anchor)) {
|
||||
firstHTMLElement = focusHTMLElement;
|
||||
firstOffset = focus.offset;
|
||||
lastHTMLElement = anchorHTMLElement;
|
||||
lastOffset = anchor.offset;
|
||||
} else {
|
||||
firstHTMLElement = anchorHTMLElement;
|
||||
firstOffset = anchor.offset;
|
||||
lastHTMLElement = focusHTMLElement;
|
||||
lastOffset = focus.offset;
|
||||
}
|
||||
const firstTextNode = firstHTMLElement.firstChild;
|
||||
invariant(
|
||||
firstTextNode !== null,
|
||||
'Expected text node to be first child of span',
|
||||
);
|
||||
const lastTextNode = lastHTMLElement.firstChild;
|
||||
invariant(
|
||||
lastTextNode !== null,
|
||||
'Expected text node to be first child of span',
|
||||
);
|
||||
range.setStart(firstTextNode, firstOffset);
|
||||
range.setEnd(lastTextNode, lastOffset);
|
||||
removeRangeListener();
|
||||
removeRangeListener = positionNodeOnRange(
|
||||
editor,
|
||||
range,
|
||||
(domNodes) => {
|
||||
for (const domNode of domNodes) {
|
||||
const domNodeStyle = domNode.style;
|
||||
if (domNodeStyle.background !== 'Highlight') {
|
||||
domNodeStyle.background = 'Highlight';
|
||||
}
|
||||
if (domNodeStyle.color !== 'HighlightText') {
|
||||
domNodeStyle.color = 'HighlightText';
|
||||
}
|
||||
if (domNodeStyle.zIndex !== '-1') {
|
||||
domNodeStyle.zIndex = '-1';
|
||||
}
|
||||
if (domNodeStyle.pointerEvents !== 'none') {
|
||||
domNodeStyle.pointerEvents = 'none';
|
||||
}
|
||||
if (domNodeStyle.marginTop !== px(-1.5)) {
|
||||
domNodeStyle.marginTop = px(-1.5);
|
||||
}
|
||||
if (domNodeStyle.paddingTop !== px(4)) {
|
||||
domNodeStyle.paddingTop = px(4);
|
||||
}
|
||||
if (domNodeStyle.paddingBottom !== px(0)) {
|
||||
domNodeStyle.paddingBottom = px(0);
|
||||
}
|
||||
}
|
||||
if (onReposition !== undefined) {
|
||||
onReposition(domNodes);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
previousAnchorNode = currentAnchorNode;
|
||||
previousAnchorOffset = currentAnchorOffset;
|
||||
previousFocusNode = currentFocusNode;
|
||||
previousFocusOffset = currentFocusOffset;
|
||||
});
|
||||
}
|
||||
compute(editor.getEditorState());
|
||||
return mergeRegister(
|
||||
editor.registerUpdateListener(({editorState}) => compute(editorState)),
|
||||
removeRangeListener,
|
||||
() => {
|
||||
removeRangeListener();
|
||||
},
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user