Skip to content
This repository has been archived by the owner on Feb 6, 2023. It is now read-only.

Take over enhancements to render Draft.js in an iframe #1938

Closed
Closed
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion meta/bundle-size-stats/Draft.js.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion meta/bundle-size-stats/Draft.min.js.json

Large diffs are not rendered by default.

19 changes: 10 additions & 9 deletions src/component/base/DraftEditor.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const DraftEditorEditHandler = require('DraftEditorEditHandler');
const DraftEditorPlaceholder = require('DraftEditorPlaceholder.react');
const DraftEffects = require('DraftEffects');
const EditorState = require('EditorState');
const isHTMLElement = require('isHTMLElement');
const React = require('React');
const ReactDOM = require('ReactDOM');
const Scroll = require('Scroll');
Expand Down Expand Up @@ -445,7 +446,13 @@ class DraftEditor extends React.Component<DraftEditorProps, State> {
* ie9-beta-minor-change-list.aspx
*/
if (isIE) {
document.execCommand('AutoUrlDetect', false, false);
// editor can be null after mounting
// https://stackoverflow.com/questions/44074747/componentdidmount-called-before-ref-callback
if (!this.editor) {
global.execCommand('AutoUrlDetect', false, false);
} else {
this.editor.ownerDocument.execCommand('AutoUrlDetect', false, false);
}
}
}

Expand Down Expand Up @@ -479,10 +486,7 @@ class DraftEditor extends React.Component<DraftEditorProps, State> {
const scrollParent = Style.getScrollParent(editorNode);
const {x, y} = scrollPosition || getScrollPosition(scrollParent);

invariant(
editorNode instanceof HTMLElement,
'editorNode is not an HTMLElement',
);
invariant(isHTMLElement(editorNode), 'editorNode is not an HTMLElement');

editorNode.focus();

Expand All @@ -506,10 +510,7 @@ class DraftEditor extends React.Component<DraftEditorProps, State> {

blur = (): void => {
const editorNode = ReactDOM.findDOMNode(this.editor);
invariant(
editorNode instanceof HTMLElement,
'editorNode is not an HTMLElement',
);
invariant(isHTMLElement(editorNode), 'editorNode is not an HTMLElement');
editorNode.blur();
};

Expand Down
6 changes: 2 additions & 4 deletions src/component/contents/DraftEditorBlock.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const getElementPosition = require('getElementPosition');
const getScrollPosition = require('getScrollPosition');
const getViewportDimensions = require('getViewportDimensions');
const invariant = require('invariant');
const isHTMLElement = require('isHTMLElement');
const nullthrows = require('nullthrows');

const SCROLL_BUFFER = 10;
Expand Down Expand Up @@ -119,10 +120,7 @@ class DraftEditorBlock extends React.Component<Props> {
);
}
} else {
invariant(
blockNode instanceof HTMLElement,
'blockNode is not an HTMLElement',
);
invariant(isHTMLElement(blockNode), 'blockNode is not an HTMLElement');
const blockBottom = blockNode.offsetHeight + blockNode.offsetTop;
const scrollBottom = scrollParent.offsetHeight + scrollPosition.y;
scrollDelta = blockBottom - scrollBottom;
Expand Down
4 changes: 3 additions & 1 deletion src/component/contents/DraftEditorTextNode.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const ReactDOM = require('ReactDOM');
const UserAgent = require('UserAgent');

const invariant = require('invariant');
const isElement = require('isElement');

// In IE, spans with <br> tags render as two newlines. By rendering a span
// with only a newline character, we can be sure to render a single line.
Expand Down Expand Up @@ -81,7 +82,8 @@ class DraftEditorTextNode extends React.Component<Props> {
shouldComponentUpdate(nextProps: Props): boolean {
const node = ReactDOM.findDOMNode(this);
const shouldBeNewline = nextProps.children === '';
invariant(node instanceof Element, 'node is not an Element');

invariant(isElement(node), 'node is not an Element');
if (shouldBeNewline) {
return !isNewline(node);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const getScrollPosition = require('getScrollPosition');
const getViewportDimensions = require('getViewportDimensions');
const Immutable = require('immutable');
const invariant = require('invariant');
const isHTMLElement = require('isHTMLElement');

const SCROLL_BUFFER = 10;

Expand Down Expand Up @@ -253,10 +254,7 @@ class DraftEditorBlockNode extends React.Component<Props> {
);
}
} else {
invariant(
blockNode instanceof HTMLElement,
'blockNode is not an HTMLElement',
);
invariant(isHTMLElement(blockNode), 'blockNode is not an HTMLElement');
const blockBottom = blockNode.offsetHeight + blockNode.offsetTop;
const scrollBottom = scrollParent.offsetHeight + scrollPosition.y;
scrollDelta = blockBottom - scrollBottom;
Expand Down
6 changes: 4 additions & 2 deletions src/component/handlers/drag/DraftEditorDragHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@ function getSelectionForEvent(
/* $FlowFixMe(>=0.68.0 site=www,mobile) This comment suppresses an error
* found when Flow v0.68 was deployed. To see the error delete this comment
* and run Flow. */
if (typeof document.caretRangeFromPoint === 'function') {
const dropRange = document.caretRangeFromPoint(event.x, event.y);
const {ownerDocument} = event.currentTarget;

if (typeof ownerDocument.caretRangeFromPoint === 'function') {
const dropRange = ownerDocument.caretRangeFromPoint(event.x, event.y);
node = dropRange.startContainer;
offset = dropRange.startOffset;
} else if (event.rangeParent) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const removeTextWithStrategy = require('removeTextWithStrategy');

function keyCommandBackspaceToStartOfLine(
editorState: EditorState,
e: SyntheticKeyboardEvent<HTMLElement>,
): EditorState {
const afterRemoval = removeTextWithStrategy(
editorState,
Expand All @@ -30,8 +31,8 @@ function keyCommandBackspaceToStartOfLine(
if (selection.isCollapsed() && selection.getAnchorOffset() === 0) {
return moveSelectionBackward(strategyState, 1);
}

const domSelection = global.getSelection();
const {ownerDocument} = e.currentTarget;
const domSelection = ownerDocument.defaultView.getSelection();
let range = domSelection.getRangeAt(0);
range = expandRangeToStartOfLine(range);

Expand Down
9 changes: 7 additions & 2 deletions src/component/handlers/edit/editOnBeforeInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ function replaceText(
*/
function editOnBeforeInput(
editor: DraftEditor,
e: SyntheticInputEvent<>,
e: SyntheticInputEvent<HTMLElement>,
): void {
if (editor._pendingStateFromBeforeInput !== undefined) {
editor.update(editor._pendingStateFromBeforeInput);
Expand Down Expand Up @@ -161,7 +161,12 @@ function editOnBeforeInput(
// Chrome will also split up a node into two pieces if it contains a Tab
// char, for no explicable reason. Seemingly caused by this commit:
// https://chromium.googlesource.com/chromium/src/+/013ac5eaf3%5E%21/
const nativeSelection = global.getSelection();

// in test environment, e.target is not available
const nativeSelection = (e.currentTarget
? e.currentTarget.ownerDocument.defaultView
: global
).getSelection();
// Selection is necessarily collapsed at this point due to earlier check.
if (
nativeSelection.anchorNode &&
Expand Down
7 changes: 4 additions & 3 deletions src/component/handlers/edit/editOnBlur.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const EditorState = require('EditorState');
const containsNode = require('containsNode');
const getActiveElement = require('getActiveElement');

function editOnBlur(editor: DraftEditor, e: SyntheticEvent<>): void {
function editOnBlur(editor: DraftEditor, e: SyntheticEvent<HTMLElement>): void {
// In a contentEditable element, when you select a range and then click
// another active element, this does trigger a `blur` event but will not
// remove the DOM selection from the contenteditable.
Expand All @@ -29,8 +29,9 @@ function editOnBlur(editor: DraftEditor, e: SyntheticEvent<>): void {
// We therefore force the issue to be certain, checking whether the active
// element is `body` to force it when blurring occurs within the window (as
// opposed to clicking to another tab or window).
if (getActiveElement() === document.body) {
const selection = global.getSelection();
const {ownerDocument} = e.currentTarget;
if (getActiveElement(ownerDocument) === ownerDocument.body) {
const selection = ownerDocument.defaultView.getSelection();
const editorNode = editor.editor;
if (
selection.rangeCount === 1 &&
Expand Down
4 changes: 2 additions & 2 deletions src/component/handlers/edit/editOnCut.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ import type DraftEditor from 'DraftEditor.react';
const DraftModifier = require('DraftModifier');
const EditorState = require('EditorState');
const Style = require('Style');

const getFragmentFromSelection = require('getFragmentFromSelection');
const getScrollPosition = require('getScrollPosition');
const isNode = require('isInstanceOfNode');

/**
* On `cut` events, native behavior is allowed to occur so that the system
Expand All @@ -45,7 +45,7 @@ function editOnCut(editor: DraftEditor, e: SyntheticClipboardEvent<>): void {

// Track the current scroll position so that it can be forced back in place
// after the editor regains control of the DOM.
if (element instanceof Node) {
if (isNode(element)) {
scrollPosition = getScrollPosition(Style.getScrollParent(element));
}

Expand Down
5 changes: 3 additions & 2 deletions src/component/handlers/edit/editOnInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ function editOnInput(editor: DraftEditor): void {
editor.update(editor._pendingStateFromBeforeInput);
editor._pendingStateFromBeforeInput = undefined;
}

const domSelection = global.getSelection();
// at this point editor is not null for sure (after input)
const castedEditorElement: HTMLElement = (editor.editor: any);
const domSelection = castedEditorElement.ownerDocument.defaultView.getSelection();

const {anchorNode, isCollapsed} = domSelection;
const isNotTextNode = anchorNode.nodeType !== Node.TEXT_NODE;
Expand Down
10 changes: 7 additions & 3 deletions src/component/handlers/edit/editOnKeyDown.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const isChrome = UserAgent.isBrowser('Chrome');
function onKeyCommand(
command: DraftEditorCommand | string,
editorState: EditorState,
e: SyntheticKeyboardEvent<HTMLElement>,
): EditorState {
switch (command) {
case 'redo':
Expand All @@ -57,7 +58,7 @@ function onKeyCommand(
case 'backspace-word':
return keyCommandBackspaceWord(editorState);
case 'backspace-to-start-of-line':
return keyCommandBackspaceToStartOfLine(editorState);
return keyCommandBackspaceToStartOfLine(editorState, e);
case 'split-block':
return keyCommandInsertNewline(editorState);
case 'transpose-characters':
Expand All @@ -84,7 +85,10 @@ function onKeyCommand(
* See `getDefaultKeyBinding` for defaults. Alternatively, the top-level
* component may provide a custom mapping via the `keyBindingFn` prop.
*/
function editOnKeyDown(editor: DraftEditor, e: SyntheticKeyboardEvent<>): void {
function editOnKeyDown(
editor: DraftEditor,
e: SyntheticKeyboardEvent<HTMLElement>,
): void {
const keyCode = e.which;
const editorState = editor._latestEditorState;
function callDeprecatedHandler(
Expand Down Expand Up @@ -195,7 +199,7 @@ function editOnKeyDown(editor: DraftEditor, e: SyntheticKeyboardEvent<>): void {
return;
}

const newState = onKeyCommand(command, editorState);
const newState = onKeyCommand(command, editorState, e);
if (newState !== editorState) {
editor.update(newState);
}
Expand Down
3 changes: 2 additions & 1 deletion src/component/handlers/edit/editOnSelect.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const ReactDOM = require('ReactDOM');

const getDraftEditorSelection = require('getDraftEditorSelection');
const invariant = require('invariant');
const isHTMLElement = require('isHTMLElement');

function editOnSelect(editor: DraftEditor): void {
if (
Expand All @@ -44,7 +45,7 @@ function editOnSelect(editor: DraftEditor): void {
const editorNode = ReactDOM.findDOMNode(editor.editorContainer);
invariant(editorNode, 'Missing editorNode');
invariant(
editorNode.firstChild instanceof HTMLElement,
isHTMLElement(editorNode.firstChild),
'editorNode.firstChild is not an HTMLElement',
);
const documentSelection = getDraftEditorSelection(
Expand Down
7 changes: 4 additions & 3 deletions src/component/selection/expandRangeToStartOfLine.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,16 @@

const UnicodeUtils = require('UnicodeUtils');

const getCorrectDocumentFromNode = require('getCorrectDocumentFromNode');
const getRangeClientRects = require('getRangeClientRects');
const invariant = require('invariant');

/**
* Return the computed line height, in pixels, for the provided element.
*/
function getLineHeightPx(element: Element): number {
const computed = getComputedStyle(element);
const div = document.createElement('div');
const correctDocument = getCorrectDocumentFromNode(element);
const div = correctDocument.createElement('div');
div.style.fontFamily = computed.fontFamily;
div.style.fontSize = computed.fontSize;
div.style.fontStyle = computed.fontStyle;
Expand All @@ -30,7 +31,7 @@ function getLineHeightPx(element: Element): number {
div.style.position = 'absolute';
div.textContent = 'M';

const documentBody = document.body;
const documentBody = correctDocument.body;
invariant(documentBody, 'Missing document.body');

// forced layout here
Expand Down
7 changes: 5 additions & 2 deletions src/component/selection/findAncestorOffsetKey.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,17 @@

'use strict';

const getCorrectDocumentFromNode = require('getCorrectDocumentFromNode');
const getSelectionOffsetKeyForNode = require('getSelectionOffsetKeyForNode');

/**
* Get the key from the node's nearest offset-aware ancestor.
*/
function findAncestorOffsetKey(node: Node): ?string {
let searchNode = node;
while (searchNode && searchNode !== document.documentElement) {
while (
searchNode &&
searchNode !== getCorrectDocumentFromNode(node).documentElement
) {
const key = getSelectionOffsetKeyForNode(searchNode);
if (key != null) {
return key;
Expand Down
2 changes: 1 addition & 1 deletion src/component/selection/getDraftEditorSelection.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function getDraftEditorSelection(
editorState: EditorState,
root: HTMLElement,
): DOMDerivedSelection {
const selection = global.getSelection();
const selection = root.ownerDocument.defaultView.getSelection();

// No active selection.
if (selection.rangeCount === 0) {
Expand Down
15 changes: 11 additions & 4 deletions src/component/selection/getDraftEditorSelectionWithNodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const findAncestorOffsetKey = require('findAncestorOffsetKey');
const getSelectionOffsetKeyForNode = require('getSelectionOffsetKeyForNode');
const getUpdatedSelectionState = require('getUpdatedSelectionState');
const invariant = require('invariant');
const isElement = require('isElement');
const nullthrows = require('nullthrows');

type SelectionPoint = {
Expand Down Expand Up @@ -125,8 +126,8 @@ function getFirstLeaf(node: any): Node {
while (
node.firstChild &&
// data-blocks has no offset
((node.firstChild instanceof Element &&
node.firstChild.getAttribute('data-blocks') === 'true') ||
((isElement(node.firstChild) &&
(node.firstChild: Element).getAttribute('data-blocks') === 'true') ||
getSelectionOffsetKeyForNode(node.firstChild))
) {
node = node.firstChild;
Expand All @@ -141,7 +142,7 @@ function getLastLeaf(node: any): Node {
while (
node.lastChild &&
// data-blocks has no offset
((node.lastChild instanceof Element &&
((isElement(node.lastChild) &&
node.lastChild.getAttribute('data-blocks') === 'true') ||
getSelectionOffsetKeyForNode(node.lastChild))
) {
Expand All @@ -168,8 +169,14 @@ function getPointForNonTextNode(
// wrapper.
if (editorRoot === node) {
node = node.firstChild;
invariant(isElement(node), 'Invalid DraftEditorContents node.');
const castedNode: Element = (node: any);

// assignment only added for flow :/
// otherwise it throws in line 200 saying that node can be null or undefined
node = castedNode;
invariant(
node instanceof Element && node.getAttribute('data-contents') === 'true',
node.getAttribute('data-contents') === 'true',
'Invalid DraftEditorContents structure.',
);
if (childOffset > 0) {
Expand Down
13 changes: 9 additions & 4 deletions src/component/selection/getSelectionOffsetKeyForNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,19 @@
* Get offset key from a node or it's child nodes. Return the first offset key
* found on the DOM tree of given node.
*/
const isElement = require('isElement');

function getSelectionOffsetKeyForNode(node: Node): ?string {
if (node instanceof Element) {
const offsetKey = node.getAttribute('data-offset-key');
if (isElement(node)) {
const castedNode: Element = (node: any);
const offsetKey = castedNode.getAttribute('data-offset-key');
if (offsetKey) {
return offsetKey;
}
for (let ii = 0; ii < node.childNodes.length; ii++) {
const childOffsetKey = getSelectionOffsetKeyForNode(node.childNodes[ii]);
for (let ii = 0; ii < castedNode.childNodes.length; ii++) {
const childOffsetKey = getSelectionOffsetKeyForNode(
castedNode.childNodes[ii],
);
if (childOffsetKey) {
return childOffsetKey;
}
Expand Down