Skip to content

Commit

Permalink
Deprecate DraftEntity before removing (facebookarchive#790)
Browse files Browse the repository at this point in the history
A previous PR had completely removed 'DraftEntity' the module and moved
control over entities into ContentState.[1] This changed several public
APIs of Draft.js in a non-backwards compabible way.

[1]: facebookarchive#376

To make it easier to migrate multiple use cases to the new Draft.js API,
and because the transition is too complex for a simple codemod, we are
releasing a version that supports both the new and the old API and then
later releasing a version that breaks the old API completely.

In order to renew support for the old API while also supporting the new
API, this PR
 - restores the DraftEntity module and associated tests/mocks
 - calls into DraftEntity methods under the hood of the new API

We tried to leave as much of the new API code in place and unchanged as
possible, so that things will be easier when we are fully removing
DraftEntity and switching to the new approach.

Next steps:
 - A PR to add warnings and comments about the deprecation of the old
   API
 - Add this, the warnings, and other recent PRs to 0.10.0 alpha version
 - Add documentation about migrating from deprecated to new API
 - Release 0.10.0; support both APIs
 - Make PR that basically undoes this one, moving entity control into
   ContentState, and release that as version 0.11.0, with warning that
   it no longer supports the deprecated API.
  • Loading branch information
flarnie authored and ouchxp committed Apr 7, 2017
1 parent a705b1d commit c3920b8
Show file tree
Hide file tree
Showing 16 changed files with 281 additions and 66 deletions.
4 changes: 2 additions & 2 deletions examples/convertFromHTML/convert.html
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
}
}

function findLinkEntities(contentState, contentBlock, callback) {
function findLinkEntities(contentBlock, callback, contentState) {
contentBlock.findEntityRanges(
(character) => {
const entityKey = character.getEntity();
Expand All @@ -125,7 +125,7 @@
);
};

function findImageEntities(contentState, contentBlock, callback) {
function findImageEntities(contentBlock, callback, contentState) {
contentBlock.findEntityRanges(
(character) => {
const entityKey = character.getEntity();
Expand Down
4 changes: 2 additions & 2 deletions examples/entity/entity.html
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@
}

function getEntityStrategy(mutability) {
return function(contentState, contentBlock, callback) {
return function(contentBlock, callback, contentState) {
contentBlock.findEntityRanges(
(character) => {
const entityKey = character.getEntity();
Expand Down Expand Up @@ -176,7 +176,7 @@
props.contentState.getEntity(props.entityKey).getMutability()
);
return (
<span {...props} style={style}>
<span data-offset-key={props.offsetkey} style={style}>
{props.children}
</span>
);
Expand Down
2 changes: 1 addition & 1 deletion examples/link/link.html
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@
}
}

function findLinkEntities(contentState, contentBlock, callback) {
function findLinkEntities(contentBlock, callback, contentState) {
contentBlock.findEntityRanges(
(character) => {
const entityKey = character.getEntity();
Expand Down
4 changes: 2 additions & 2 deletions examples/tweet/tweet.html
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,11 @@
const HANDLE_REGEX = /\@[\w]+/g;
const HASHTAG_REGEX = /\#[\w\u0590-\u05ff]+/g;

function handleStrategy(contentState, contentBlock, callback) {
function handleStrategy(contentBlock, callback, contentState) {
findWithRegex(HANDLE_REGEX, contentBlock, callback);
}

function hashtagStrategy(contentState, contentBlock, callback) {
function hashtagStrategy(contentBlock, callback, contentState) {
findWithRegex(HASHTAG_REGEX, contentBlock, callback);
}

Expand Down
2 changes: 2 additions & 0 deletions src/Draft.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const DefaultDraftBlockRenderMap = require('DefaultDraftBlockRenderMap');
const DefaultDraftInlineStyle = require('DefaultDraftInlineStyle');
const DraftEditor = require('DraftEditor.react');
const DraftEditorBlock = require('DraftEditorBlock.react');
const DraftEntity = require('DraftEntity');
const DraftModifier = require('DraftModifier');
const DraftEntityInstance = require('DraftEntityInstance');
const EditorState = require('EditorState');
Expand All @@ -42,6 +43,7 @@ const DraftPublic = {
EditorState,

CompositeDecorator: CompositeDraftDecorator,
Entity: DraftEntity,
EntityInstance: DraftEntityInstance,

BlockMapBuilder,
Expand Down
6 changes: 4 additions & 2 deletions src/component/handlers/edit/editOnPaste.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,11 +196,13 @@ function insertFragment(
editorState.getSelection(),
fragment
);
const mergedEntityMap = newContent.getEntityMap().merge(entityMap);
// TODO: merge the entity map once we stop using DraftEntity
// like this:
// const mergedEntityMap = newContent.getEntityMap().merge(entityMap);

return EditorState.push(
editorState,
newContent.set('entityMap', mergedEntityMap),
newContent.set('entityMap', entityMap),
'insert-fragment'
);
}
Expand Down
5 changes: 3 additions & 2 deletions src/model/decorators/CompositeDraftDecorator.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,16 @@ class CompositeDraftDecorator {
(/*object*/ decorator, /*number*/ ii) => {
var counter = 0;
var strategy = decorator.strategy;
strategy(contentState, block, (/*number*/ start, /*number*/ end) => {
var callback = (/*number*/ start, /*number*/ end) => {
// Find out if any of our matching range is already occupied
// by another decorator. If so, discard the match. Otherwise, store
// the component key for rendering.
if (canOccupySlice(decorations, start, end)) {
occupySlice(decorations, start, end, ii + DELIMITER + counter);
counter++;
}
});
};
strategy(block, callback, contentState);
}
);

Expand Down
4 changes: 2 additions & 2 deletions src/model/decorators/DraftDecorator.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ import type ContentState from 'ContentState';
*/
export type DraftDecorator = {
strategy: (
contentState: ContentState,
block: ContentBlock,
callback: (start: number, end: number) => void
callback: (start: number, end: number) => void,
contentState: ContentState,
) => void,
component: Function,
props?: Object,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('CompositeDraftDecorator', () => {
}

function searchWith(regex) {
return function(contentState, block, callback) {
return function(block, callback, contentState) {
var text = block.getText();
text.replace(
regex,
Expand Down
33 changes: 13 additions & 20 deletions src/model/encoding/convertFromHTMLToContentBlocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@
const CharacterMetadata = require('CharacterMetadata');
const ContentBlock = require('ContentBlock');
const DefaultDraftBlockRenderMap = require('DefaultDraftBlockRenderMap');
const DraftEntityInstance = require('DraftEntityInstance');
const DraftEntity = require('DraftEntity');
const Immutable = require('immutable');
const URI = require('URI');

const addEntityToEntityMap = require('addEntityToEntityMap');
const generateRandomKey = require('generateRandomKey');
const getSafeBodyFromHTML = require('getSafeBodyFromHTML');
const invariant = require('invariant');
Expand All @@ -35,7 +34,6 @@ import type {EntityMap} from 'EntityMap';
var {
List,
OrderedSet,
OrderedMap,
} = Immutable;

var NBSP = '&nbsp;';
Expand Down Expand Up @@ -390,15 +388,12 @@ function genFragment(
const imageURI = new URI(entityConfig.src).toString();
node.textContent = imageURI; // Output src if no decorator

newEntityMap = addEntityToEntityMap(
newEntityMap,
new DraftEntityInstance({
type: 'IMAGE',
mutability: 'MUTABLE',
data: entityConfig || {},
})
// TODO: update this when we remove DraftEntity entirely
inEntity = DraftEntity.create(
'IMAGE',
'MUTABLE',
entityConfig || {},
);
inEntity = newEntityMap.keySeq().last();
}

var chunk = getEmptyChunk();
Expand Down Expand Up @@ -460,15 +455,12 @@ function genFragment(
});

entityConfig.url = new URI(anchor.href).toString();
newEntityMap = addEntityToEntityMap(
newEntityMap,
new DraftEntityInstance({
type: 'LINK',
mutability: 'MUTABLE',
data: entityConfig || {},
})
// TODO: update this when we remove DraftEntity completely
entityId = DraftEntity.create(
'LINK',
'MUTABLE',
entityConfig || {},
);
entityId = newEntityMap.keySeq().last();
} else {
entityId = undefined;
}
Expand Down Expand Up @@ -602,7 +594,8 @@ function convertFromHTMLtoContentBlocks(
// arbitrary code in whatever environment you're running this in. For an
// example of how we try to do this in-browser, see getSafeBodyFromHTML.

var chunkData = getChunkForHTML(html, DOMBuilder, blockRenderMap, OrderedMap());
// TODO: replace DraftEntity with an OrderedMap here
var chunkData = getChunkForHTML(html, DOMBuilder, blockRenderMap, DraftEntity);

if (chunkData == null) {
return null;
Expand Down
19 changes: 7 additions & 12 deletions src/model/encoding/convertFromRawToDraftState.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,13 @@

var ContentBlock = require('ContentBlock');
var ContentState = require('ContentState');
var DraftEntityInstance = require('DraftEntityInstance');
var DraftEntity = require('DraftEntity');

const addEntityToEntityMap = require('addEntityToEntityMap');
var createCharacterList = require('createCharacterList');
var decodeEntityRanges = require('decodeEntityRanges');
var decodeInlineStyleRanges = require('decodeInlineStyleRanges');
var generateRandomKey = require('generateRandomKey');
var Immutable = require('immutable');
var {OrderedMap} = Immutable;

import type {RawDraftContentState} from 'RawDraftContentState';

Expand All @@ -35,17 +33,14 @@ function convertFromRawToDraftState(

var fromStorageToLocal = {};

const newEntityMap = Object.keys(entityMap).reduce(
(updatedEntityMap, storageKey) => {
// TODO: Update this once we completely remove DraftEntity
Object.keys(entityMap).forEach(
storageKey => {
var encodedEntity = entityMap[storageKey];
var {type, mutability, data} = encodedEntity;
const instance = new DraftEntityInstance({type, mutability, data: data || {}});
const tempEntityMap = addEntityToEntityMap(updatedEntityMap, instance);
const newKey = tempEntityMap.keySeq().last();
var newKey = DraftEntity.create(type, mutability, data || {});
fromStorageToLocal[storageKey] = newKey;
return tempEntityMap;
},
OrderedMap()
}
);

var contentBlocks = blocks.map(
Expand Down Expand Up @@ -81,7 +76,7 @@ function convertFromRawToDraftState(
}
);

return ContentState.createFromBlockArray(contentBlocks, newEntityMap);
return ContentState.createFromBlockArray(contentBlocks);
}

module.exports = convertFromRawToDraftState;
116 changes: 116 additions & 0 deletions src/model/entity/DraftEntity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule DraftEntity
* @typechecks
* @flow
*/

var DraftEntityInstance = require('DraftEntityInstance');
var Immutable = require('immutable');

var invariant = require('invariant');

import type {DraftEntityMutability} from 'DraftEntityMutability';
import type {DraftEntityType} from 'DraftEntityType';

var {Map} = Immutable;

var instances: Map<string, DraftEntityInstance> = Map();
var instanceKey = 0;

/**
* A "document entity" is an object containing metadata associated with a
* piece of text in a ContentBlock.
*
* For example, a `link` entity might include a `uri` property. When a
* ContentBlock is rendered in the browser, text that refers to that link
* entity may be rendered as an anchor, with the `uri` as the href value.
*
* In a ContentBlock, every position in the text may correspond to zero
* or one entities. This correspondence is tracked using a key string,
* generated via DraftEntity.create() and used to obtain entity metadata
* via DraftEntity.get().
*/
var DraftEntity = {
/**
* Get the random key string from whatever entity was last created.
* We need this to support the new API, as part of transitioning to put Entity
* storage in contentState.
*/
getLastCreatedEntityKey: function(): string {
return '' + instanceKey;
},

/**
* Create a DraftEntityInstance and store it for later retrieval.
*
* A random key string will be generated and returned. This key may
* be used to track the entity's usage in a ContentBlock, and for
* retrieving data about the entity at render time.
*/
create: function(
type: DraftEntityType,
mutability: DraftEntityMutability,
data?: Object,
): string {
return DraftEntity.add(
new DraftEntityInstance({type, mutability, data: data || {}})
);
},

/**
* Add an existing DraftEntityInstance to the DraftEntity map. This is
* useful when restoring instances from the server.
*/
add: function(instance: DraftEntityInstance): string {
var key = '' + (++instanceKey);
instances = instances.set(key, instance);
return key;
},

/**
* Retrieve the entity corresponding to the supplied key string.
*/
get: function(key: string): DraftEntityInstance {
var instance = instances.get(key);
invariant(!!instance, 'Unknown DraftEntity key.');
return instance;
},

/**
* Entity instances are immutable. If you need to update the data for an
* instance, this method will merge your data updates and return a new
* instance.
*/
mergeData: function(
key: string,
toMerge: {[key: string]: any}
): DraftEntityInstance {
var instance = DraftEntity.get(key);
var newData = {...instance.getData(), ...toMerge};
var newInstance = instance.set('data', newData);
instances = instances.set(key, newInstance);
return newInstance;
},

/**
* Completely replace the data for a given instance.
*/
replaceData: function(
key: string,
newData: {[key: string]: any}
): DraftEntityInstance {
const instance = DraftEntity.get(key);
const newInstance = instance.set('data', newData);
instances = instances.set(key, newInstance);
return newInstance;
},
};

module.exports = DraftEntity;
29 changes: 29 additions & 0 deletions src/model/entity/__mocks__/DraftEntity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

var DraftEntity = jest.genMockFromModule('DraftEntity');

var DraftEntityInstance = {
getType: jest.fn(() => ''),
getMutability: jest.fn(() => ''),
getData: jest.fn(() => ({})),
};

var count = 0;

DraftEntity.create = jest.fn(function() {
count++;
return '' + count;
});

DraftEntity.get = jest.fn(function() {
return DraftEntityInstance;
});

module.exports = DraftEntity;
Loading

0 comments on commit c3920b8

Please sign in to comment.