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

Commit

Permalink
Exploration - Feature tree / Rendering tree data format
Browse files Browse the repository at this point in the history
Summary:
This PR is part of a series of PR's that will be exploring **tree data block support** in Draft.

**Rendering tree data format**

This PR adds support for react components to render the tree data structure

***

**Note:**
This is unstable and not part of the public API and should not be used by
production systems.
Closes #1508

Differential Revision: D6341091

fbshipit-source-id: e285bb04ca51eb55b75c6912a2f5d283a4145d2d
  • Loading branch information
mitermayer authored and facebook-github-bot committed Nov 16, 2017
1 parent 5634524 commit c1150a7
Show file tree
Hide file tree
Showing 14 changed files with 2,388 additions and 112 deletions.
106 changes: 50 additions & 56 deletions src/component/contents/DraftEditorBlock.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import type {BlockNodeRecord} from 'BlockNodeRecord';
import type ContentState from 'ContentState';
import type {DraftDecoratorType} from 'DraftDecoratorType';
import type {DraftInlineStyle} from 'DraftInlineStyle';
import type SelectionState from 'SelectionState';
import type {BidiDirection} from 'UnicodeBidiDirection';
import type {List} from 'immutable';
Expand All @@ -39,18 +40,29 @@ const nullthrows = require('nullthrows');
const SCROLL_BUFFER = 10;

type Props = {
contentState: ContentState,
block: BlockNodeRecord,
blockProps?: Object,
blockStyleFn: (block: BlockNodeRecord) => string,
contentState: ContentState,
customStyleFn: (style: DraftInlineStyle, block: BlockNodeRecord) => ?Object,
customStyleMap: Object,
customStyleFn: Function,
tree: List<any>,
selection: SelectionState,
decorator: DraftDecoratorType,
forceSelection: boolean,
decorator: ?DraftDecoratorType,
direction: BidiDirection,
blockProps?: Object,
forceSelection: boolean,
offsetKey: string,
selection: SelectionState,
startIndent?: boolean,
blockStyleFn: Function,
tree: List<any>,
};

/**
* Return whether a block overlaps with either edge of the `SelectionState`.
*/
const isBlockOnSelectionEdge = (
selection: SelectionState,
key: string,
): boolean => {
return selection.getAnchorKey() === key || selection.getFocusKey() === key;
};

/**
Expand Down Expand Up @@ -83,21 +95,21 @@ class DraftEditorBlock extends React.Component<Props> {
* scroll parent.
*/
componentDidMount(): void {
var selection = this.props.selection;
var endKey = selection.getEndKey();
const selection = this.props.selection;
const endKey = selection.getEndKey();
if (!selection.getHasFocus() || endKey !== this.props.block.getKey()) {
return;
}

var blockNode = ReactDOM.findDOMNode(this);
var scrollParent = Style.getScrollParent(blockNode);
var scrollPosition = getScrollPosition(scrollParent);
var scrollDelta;
const blockNode = ReactDOM.findDOMNode(this);
const scrollParent = Style.getScrollParent(blockNode);
const scrollPosition = getScrollPosition(scrollParent);
let scrollDelta;

if (scrollParent === window) {
var nodePosition = getElementPosition(blockNode);
var nodeBottom = nodePosition.y + nodePosition.height;
var viewportHeight = getViewportDimensions().height;
const nodePosition = getElementPosition(blockNode);
const nodeBottom = nodePosition.y + nodePosition.height;
const viewportHeight = getViewportDimensions().height;
scrollDelta = nodeBottom - viewportHeight;
if (scrollDelta > 0) {
window.scrollTo(
Expand All @@ -110,8 +122,8 @@ class DraftEditorBlock extends React.Component<Props> {
blockNode instanceof HTMLElement,
'blockNode is not an HTMLElement',
);
var blockBottom = blockNode.offsetHeight + blockNode.offsetTop;
var scrollBottom = scrollParent.offsetHeight + scrollPosition.y;
const blockBottom = blockNode.offsetHeight + blockNode.offsetTop;
const scrollBottom = scrollParent.offsetHeight + scrollPosition.y;
scrollDelta = blockBottom - scrollBottom;
if (scrollDelta > 0) {
Scroll.setTop(
Expand All @@ -123,32 +135,28 @@ class DraftEditorBlock extends React.Component<Props> {
}

_renderChildren(): Array<React.Element<any>> {
var block = this.props.block;
var blockKey = block.getKey();
var text = block.getText();
var lastLeafSet = this.props.tree.size - 1;
var hasSelection = isBlockOnSelectionEdge(this.props.selection, blockKey);
const block = this.props.block;
const blockKey = block.getKey();
const text = block.getText();
const lastLeafSet = this.props.tree.size - 1;
const hasSelection = isBlockOnSelectionEdge(this.props.selection, blockKey);

return this.props.tree
.map((leafSet, ii) => {
var leavesForLeafSet = leafSet.get('leaves');
var lastLeaf = leavesForLeafSet.size - 1;
var leaves = leavesForLeafSet
const leavesForLeafSet = leafSet.get('leaves');
const lastLeaf = leavesForLeafSet.size - 1;
const leaves = leavesForLeafSet
.map((leaf, jj) => {
var offsetKey = DraftOffsetKey.encode(blockKey, ii, jj);
var start = leaf.get('start');
var end = leaf.get('end');
const offsetKey = DraftOffsetKey.encode(blockKey, ii, jj);
const start = leaf.get('start');
const end = leaf.get('end');
return (
/* $FlowFixMe(>=0.53.0 site=www,mobile) This comment suppresses an
* error when upgrading Flow's support for React. Common errors found
* when upgrading Flow's React support are documented at
* https://fburl.com/eq7bs81w */
<DraftEditorLeaf
key={offsetKey}
offsetKey={offsetKey}
block={block}
start={start}
selection={hasSelection ? this.props.selection : undefined}
selection={hasSelection ? this.props.selection : null}
forceSelection={this.props.forceSelection}
text={text.slice(start, end)}
styleSet={block.getInlineStyleAt(start)}
Expand All @@ -160,7 +168,7 @@ class DraftEditorBlock extends React.Component<Props> {
})
.toArray();

var decoratorKey = leafSet.get('decoratorKey');
const decoratorKey = leafSet.get('decoratorKey');
if (decoratorKey == null) {
return leaves;
}
Expand All @@ -169,23 +177,23 @@ class DraftEditorBlock extends React.Component<Props> {
return leaves;
}

var decorator = nullthrows(this.props.decorator);
const decorator = nullthrows(this.props.decorator);

var DecoratorComponent = decorator.getComponentForKey(decoratorKey);
const DecoratorComponent = decorator.getComponentForKey(decoratorKey);
if (!DecoratorComponent) {
return leaves;
}

var decoratorProps = decorator.getPropsForKey(decoratorKey);
var decoratorOffsetKey = DraftOffsetKey.encode(blockKey, ii, 0);
var decoratedText = text.slice(
const decoratorProps = decorator.getPropsForKey(decoratorKey);
const decoratorOffsetKey = DraftOffsetKey.encode(blockKey, ii, 0);
const decoratedText = text.slice(
leavesForLeafSet.first().get('start'),
leavesForLeafSet.last().get('end'),
);

// Resetting dir to the same value on a child node makes Chrome/Firefox
// confused on cursor movement. See http://jsfiddle.net/d157kLck/3/
var dir = UnicodeBidiDirection.getHTMLDirIfDifferent(
const dir = UnicodeBidiDirection.getHTMLDirIfDifferent(
UnicodeBidi.getDirection(decoratedText),
this.props.direction,
);
Expand All @@ -207,10 +215,6 @@ class DraftEditorBlock extends React.Component<Props> {
}

render(): React.Node {
/* $FlowFixMe(>=0.53.0 site=www,mobile) This comment suppresses an error
* when upgrading Flow's support for React. Common errors found when
* upgrading Flow's React support are documented at
* https://fburl.com/eq7bs81w */
const {direction, offsetKey} = this.props;
const className = cx({
'public/DraftStyleDefault/block': true,
Expand All @@ -226,14 +230,4 @@ class DraftEditorBlock extends React.Component<Props> {
}
}

/**
* Return whether a block overlaps with either edge of the `SelectionState`.
*/
function isBlockOnSelectionEdge(
selection: SelectionState,
key: string,
): boolean {
return selection.getAnchorKey() === key || selection.getFocusKey() === key;
}

module.exports = DraftEditorBlock;
99 changes: 46 additions & 53 deletions src/component/contents/DraftEditorContents.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
'use strict';

import type {BlockNodeRecord} from 'BlockNodeRecord';
import type {DraftBlockRenderMap} from 'DraftBlockRenderMap';
import type {DraftInlineStyle} from 'DraftInlineStyle';
import type {BidiDirection} from 'UnicodeBidiDirection';

const DraftEditorBlock = require('DraftEditorBlock.react');
Expand All @@ -26,12 +28,43 @@ const joinClasses = require('joinClasses');
const nullthrows = require('nullthrows');

type Props = {
blockRendererFn: Function,
blockStyleFn: (block: BlockNodeRecord) => string,
blockRenderMap: DraftBlockRenderMap,
blockRendererFn: (block: BlockNodeRecord) => ?Object,
blockStyleFn?: (block: BlockNodeRecord) => string,
customStyleFn?: (style: DraftInlineStyle, block: BlockNodeRecord) => ?Object,
customStyleMap?: Object,
editorKey?: string,
editorState: EditorState,
textDirectionality?: BidiDirection,
};

/**
* Provide default styling for list items. This way, lists will be styled with
* proper counters and indentation even if the caller does not specify
* their own styling at all. If more than five levels of nesting are needed,
* the necessary CSS classes can be provided via `blockStyleFn` configuration.
*/
const getListItemClasses = (
type: string,
depth: number,
shouldResetCount: boolean,
direction: BidiDirection,
): string => {
return cx({
'public/DraftStyleDefault/unorderedListItem':
type === 'unordered-list-item',
'public/DraftStyleDefault/orderedListItem': type === 'ordered-list-item',
'public/DraftStyleDefault/reset': shouldResetCount,
'public/DraftStyleDefault/depth0': depth === 0,
'public/DraftStyleDefault/depth1': depth === 1,
'public/DraftStyleDefault/depth2': depth === 2,
'public/DraftStyleDefault/depth3': depth === 3,
'public/DraftStyleDefault/depth4': depth === 4,
'public/DraftStyleDefault/listLTR': direction === 'LTR',
'public/DraftStyleDefault/listRTL': direction === 'RTL',
});
};

/**
* `DraftEditorContents` is the container component for all block components
* rendered for a `DraftEditor`. It is optimized to aggressively avoid
Expand Down Expand Up @@ -91,23 +124,14 @@ class DraftEditorContents extends React.Component<Props> {

render(): React.Node {
const {
/* $FlowFixMe(>=0.53.0 site=www,mobile) This comment suppresses an error
* when upgrading Flow's support for React. Common errors found when
* upgrading Flow's React support are documented at
* https://fburl.com/eq7bs81w */
blockRenderMap,
blockRendererFn,
/* $FlowFixMe(>=0.53.0 site=www,mobile) This comment suppresses an error
* when upgrading Flow's support for React. Common errors found when
* upgrading Flow's React support are documented at
* https://fburl.com/eq7bs81w */
blockStyleFn,
customStyleMap,
/* $FlowFixMe(>=0.53.0 site=www,mobile) This comment suppresses an error
* when upgrading Flow's support for React. Common errors found when
* upgrading Flow's React support are documented at
* https://fburl.com/eq7bs81w */
customStyleFn,
editorState,
editorKey,
textDirectionality,
} = this.props;

const content = editorState.getCurrentContent();
Expand All @@ -118,6 +142,7 @@ class DraftEditorContents extends React.Component<Props> {

const blocksAsArray = content.getBlocksAsArray();
const processedBlocks = [];

let currentDepth = null;
let lastWrapperTemplate = null;

Expand All @@ -134,7 +159,6 @@ class DraftEditorContents extends React.Component<Props> {
customEditable = customRenderer.editable;
}

const {textDirectionality} = this.props;
const direction = textDirectionality
? textDirectionality
: directionMap.get(key);
Expand All @@ -143,6 +167,7 @@ class DraftEditorContents extends React.Component<Props> {
contentState: content,
block,
blockProps: customProps,
blockStyleFn,
customStyleMap,
customStyleFn,
decorator,
Expand All @@ -162,7 +187,10 @@ class DraftEditorContents extends React.Component<Props> {
configForType.element || blockRenderMap.get('unstyled').element;

const depth = block.getDepth();
let className = this.props.blockStyleFn(block);
let className = '';
if (blockStyleFn) {
className = blockStyleFn(block);
}

// List items are special snowflakes, since we handle nesting and
// counters manually.
Expand All @@ -181,11 +209,7 @@ class DraftEditorContents extends React.Component<Props> {
let childProps = {
className,
'data-block': true,
/* $FlowFixMe(>=0.53.0 site=www,mobile) This comment suppresses an
* error when upgrading Flow's support for React. Common errors found
* when upgrading Flow's React support are documented at
* https://fburl.com/eq7bs81w */
'data-editor': this.props.editorKey,
'data-editor': editorKey,
'data-offset-key': offsetKey,
key,
};
Expand All @@ -200,10 +224,6 @@ class DraftEditorContents extends React.Component<Props> {
const child = React.createElement(
Element,
childProps,
/* $FlowFixMe(>=0.53.0 site=www,mobile) This comment suppresses an
* error when upgrading Flow's support for React. Common errors found
* when upgrading Flow's React support are documented at
* https://fburl.com/eq7bs81w */
<Component {...componentProps} />,
);

Expand All @@ -225,7 +245,7 @@ class DraftEditorContents extends React.Component<Props> {
// Group contiguous runs of blocks that have the same wrapperTemplate
const outputBlocks = [];
for (let ii = 0; ii < processedBlocks.length; ) {
const info = processedBlocks[ii];
const info: any = processedBlocks[ii];
if (info.wrapperTemplate) {
const blocks = [];
do {
Expand Down Expand Up @@ -254,31 +274,4 @@ class DraftEditorContents extends React.Component<Props> {
}
}

/**
* Provide default styling for list items. This way, lists will be styled with
* proper counters and indentation even if the caller does not specify
* their own styling at all. If more than five levels of nesting are needed,
* the necessary CSS classes can be provided via `blockStyleFn` configuration.
*/
function getListItemClasses(
type: string,
depth: number,
shouldResetCount: boolean,
direction: BidiDirection,
): string {
return cx({
'public/DraftStyleDefault/unorderedListItem':
type === 'unordered-list-item',
'public/DraftStyleDefault/orderedListItem': type === 'ordered-list-item',
'public/DraftStyleDefault/reset': shouldResetCount,
'public/DraftStyleDefault/depth0': depth === 0,
'public/DraftStyleDefault/depth1': depth === 1,
'public/DraftStyleDefault/depth2': depth === 2,
'public/DraftStyleDefault/depth3': depth === 3,
'public/DraftStyleDefault/depth4': depth === 4,
'public/DraftStyleDefault/listLTR': direction === 'LTR',
'public/DraftStyleDefault/listRTL': direction === 'RTL',
});
}

module.exports = DraftEditorContents;
2 changes: 1 addition & 1 deletion src/component/contents/DraftEditorLeaf.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ type Props = {

// The current `SelectionState`, used to represent a selection range in the
// editor
selection: SelectionState,
selection: ?SelectionState,

// The offset of this string within its block.
start: number,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,7 @@ test('defaults to "unstyled" block type for unknown block types', () => {
}

const block = ReactTestRenderer.create(<Container />);
const blockInstance = block.root;
const editorInstace = blockInstance._fiber.stateNode;
const editorInstace = block.getInstance();

expect(() => {
editorInstace.toggleCustomBlock(
Expand Down
Loading

0 comments on commit c1150a7

Please sign in to comment.