Skip to content

Instantly share code, notes, and snippets.

@johanneslumpe
Last active February 23, 2022 13:54
Show Gist options
  • Star 20 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save johanneslumpe/5865625e2992d0718cfb45d8a4c47879 to your computer and use it in GitHub Desktop.
Save johanneslumpe/5865625e2992d0718cfb45d8a4c47879 to your computer and use it in GitHub Desktop.
Draft.js utilities to inspect/modify selected block ranges
import { EditorState, SelectionState } from 'draft-js';
import getSelectedBlocks from './getSelectedBlocks';
/**
* Calls a provided `modifier` function with a selection for each
* selected block in the current editor selection. Passes through additional
* arguments to the modifier.
*
* Note: At the moment it will retain the original selection and override
* possible selection changes from modifiers
*
* @param {object} editorState The current draft.js editor state object
*
* @param {function} modifier A modifier function to be executed.
* Must have the signature (editorState, selection, ...)
*
* @param {mixed} ...args Additional arguments to be passed through to the modifier
*
* @return {object} The new editor state
*/
export default (editorState, modifier, ...args) => {
const contentState = editorState.getCurrentContent();
const currentSelection = editorState.getSelection();
const startKey = currentSelection.getStartKey();
const endKey = currentSelection.getEndKey();
const startOffset = currentSelection.getStartOffset();
const endOffset = currentSelection.getEndOffset();
const isSameBlock = startKey === endKey;
const selectedBlocks = getSelectedBlocks(contentState, startKey, endKey);
let finalEditorState = editorState;
selectedBlocks.forEach(block => {
const currentBlockKey = block.getKey();
let selectionStart = startOffset;
let selectionEnd = endOffset;
if (currentBlockKey === startKey) {
selectionStart = startOffset;
selectionEnd = isSameBlock ? endOffset : block.getText().length;
} else if (currentBlockKey === endKey) {
selectionStart = isSameBlock ? startOffset : 0;
selectionEnd = endOffset;
} else {
selectionStart = 0;
selectionEnd = block.getText().length;
}
const selection = new SelectionState({
anchorKey: currentBlockKey,
anchorOffset: selectionStart,
focusKey: currentBlockKey,
focusOffset: selectionEnd,
});
finalEditorState = modifier(finalEditorState, selection, ...args);
});
return EditorState.forceSelection(finalEditorState, currentSelection);
};
/**
* Returns an array of all `ContentBlock` instances within two block keys
*
* @param {object} contentState A draft.js `ContentState` instance
* @param {string} anchorKey The block key to start searching from
* @param {string} focusKey The block key until which to search
*
* @return {array} An array containing the found content blocks
*/
export default (contentState, anchorKey, focusKey) => {
const isSameBlock = anchorKey === focusKey;
const startingBlock = contentState.getBlockForKey(anchorKey);
const selectedBlocks = [startingBlock];
if (!isSameBlock) {
let blockKey = anchorKey;
while (blockKey !== focusKey) {
const nextBlock = contentState.getBlockAfter(blockKey);
selectedBlocks.push(nextBlock);
blockKey = nextBlock.getKey();
}
}
return selectedBlocks;
};
import getSelectedBlocks from './getSelectedBlocks';
export default strategy => (editorState, selection) => {
const contentState = editorState.getCurrentContent();
const currentSelection = selection || editorState.getSelection();
const startKey = currentSelection.getStartKey();
const endKey = currentSelection.getEndKey();
const startOffset = currentSelection.getStartOffset();
const endOffset = currentSelection.getEndOffset();
const isSameBlock = startKey === endKey;
const selectedBlocks = getSelectedBlocks(contentState, startKey, endKey);
let entitiesFound = false;
// We have to shift the offset to not get false positives when selecting
// a character just before or after an entity
const finalStartOffset = startOffset + 1;
const finalEndOffset = endOffset - 1;
selectedBlocks.forEach(block => {
strategy(
block,
(start, end) => {
if (entitiesFound) {
return;
}
const blockKey = block.getKey();
if (isSameBlock && (end < finalStartOffset || start > finalEndOffset)) {
return;
} else if (blockKey === startKey && end < finalStartOffset) {
return;
} else if (blockKey === endKey && start > finalEndOffset) {
return;
}
entitiesFound = true;
}
);
});
return entitiesFound;
};
@mathiasjakobsen
Copy link

@johanneslumpe, I would expect that selectionContainsEntity would take an entity and return a boolean, but it's not. How would one use the selectionContainsEntity function?

@liesislukas
Copy link

just noticed that getSelectedBlocks uses anchorKey and focusKey yet it doesn't check if selection was a reverse. replaced with startKey and endKey. will try using

function getSelectedBlocks({contentState, startKey, endKey}) {
  const isSameBlock = startKey === endKey;
  const startingBlock = contentState.getBlockForKey(startKey);
  const selectedBlocks = [startingBlock];

  if (!isSameBlock) {
    let blockKey = startKey;

    while (blockKey !== endKey) {
      const nextBlock = contentState.getBlockAfter(blockKey);
      selectedBlocks.push(nextBlock);
      blockKey = nextBlock.getKey();
    }
  }

  return selectedBlocks;
}

export default getSelectedBlocks;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment