mirror of
				https://github.com/BookStackApp/BookStack.git
				synced 2025-11-04 13:31:45 +03:00 
			
		
		
		
	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.
		
			
				
	
	
		
			171 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			171 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
/**
 | 
						|
 * 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();
 | 
						|
    },
 | 
						|
  );
 | 
						|
}
 |