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 / adding internal support for tree data st…
Browse files Browse the repository at this point in the history
…ructures

Summary:
This PR is part of a series of PR's that will be exploring **tree data block support** in Draft.

**Adding support for tree data structures**

This PR includes the missing parts for adding support for internal tree data structure by providing:

* last transaction insertFragmentIntoContentState.js
* last modifier AtomicBlockUtils
* fixing a bug on the randomizeBlockMapKeys to handle orphanated fragments

This is the last PR of the series to add support for internal tree data structures, from here we should then either update `RichTextEditorUtil` to have some `tree` awareness and having some controls for creating nesting content or create a new `NestedRichTextEditorUtil` that can be used for that. Ideally that work would also come with a `demo` example page

***

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

Differential Revision: D6679776

fbshipit-source-id: ad25593a7dd01552848a9ae2cdc7d34491b8130e
  • Loading branch information
mitermayer authored and facebook-github-bot committed Jan 9, 2018
1 parent 0059dd4 commit 3689a93
Show file tree
Hide file tree
Showing 11 changed files with 7,132 additions and 1,542 deletions.
44 changes: 32 additions & 12 deletions src/model/modifier/AtomicBlockUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import type {DraftInsertionType} from 'DraftInsertionType';
const BlockMapBuilder = require('BlockMapBuilder');
const CharacterMetadata = require('CharacterMetadata');
const ContentBlock = require('ContentBlock');
const ContentBlockNode = require('ContentBlockNode');
const DraftFeatureFlags = require('DraftFeatureFlags');
const DraftModifier = require('DraftModifier');
const EditorState = require('EditorState');
const Immutable = require('immutable');
Expand All @@ -27,6 +29,11 @@ const SelectionState = require('SelectionState');
const generateRandomKey = require('generateRandomKey');
const moveBlockInContentState = require('moveBlockInContentState');

const experimentalTreeDataSupport = DraftFeatureFlags.draft_tree_data_support;
const ContentBlockRecord = experimentalTreeDataSupport
? ContentBlockNode
: ContentBlock;

const {List, Repeat} = Immutable;

const AtomicBlockUtils = {
Expand Down Expand Up @@ -56,19 +63,32 @@ const AtomicBlockUtils = {

const charData = CharacterMetadata.create({entity: entityKey});

let atomicBlockConfig = {
key: generateRandomKey(),
type: 'atomic',
text: character,
characterList: List(Repeat(charData, character.length)),
};

let atomicDividerBlockConfig = {
key: generateRandomKey(),
type: 'unstyled',
};

if (experimentalTreeDataSupport) {
atomicBlockConfig = {
...atomicBlockConfig,
nextSibling: atomicDividerBlockConfig.key,
};
atomicDividerBlockConfig = {
...atomicDividerBlockConfig,
prevSibling: atomicBlockConfig.key,
};
}

const fragmentArray = [
new ContentBlock({
key: generateRandomKey(),
type: 'atomic',
text: character,
characterList: List(Repeat(charData, character.length)),
}),
new ContentBlock({
key: generateRandomKey(),
type: 'unstyled',
text: '',
characterList: List(),
}),
new ContentBlockRecord(atomicBlockConfig),
new ContentBlockRecord(atomicDividerBlockConfig),
];

const fragment = BlockMapBuilder.createFromArray(fragmentArray);
Expand Down
124 changes: 104 additions & 20 deletions src/model/modifier/__tests__/AtomicBlockUtils-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ jest.disableAutomock();

jest.mock('generateRandomKey');

const {insertAtomicBlock, moveAtomicBlock} = require('AtomicBlockUtils');
const AtomicBlockUtils = require('AtomicBlockUtils');
const BlockMapBuilder = require('BlockMapBuilder');
const ContentBlockNode = require('ContentBlockNode');
const Entity = require('DraftEntity');
const EditorState = require('EditorState');
const SelectionState = require('SelectionState');
Expand All @@ -29,16 +31,20 @@ const initialBlock = contentState.getBlockMap().first();
const ENTITY_KEY = Entity.create('TOKEN', 'MUTABLE');
const CHARACTER = ' ';

const toggleExperimentalTreeDataSupport = enabled => {
jest.doMock('DraftFeatureFlags', () => {
return {
draft_tree_data_support: enabled,
};
});
};

const assertAtomic = state => {
expect(
state
.getCurrentContent()
.getBlockMap()
.map(block => ({
key: block.getKey(),
type: block.getType(),
text: block.getText(),
}))
.toIndexedSeq()
.toJS(),
).toMatchSnapshot();
};
Expand All @@ -47,8 +53,10 @@ const assertInsertAtomicBlock = (
state = editorState,
entity = ENTITY_KEY,
character = CHARACTER,
experimentalTreeDataSupport = false,
) => {
const newState = insertAtomicBlock(state, entity, character);
toggleExperimentalTreeDataSupport(experimentalTreeDataSupport);
const newState = AtomicBlockUtils.insertAtomicBlock(state, entity, character);
assertAtomic(newState);
return newState;
};
Expand All @@ -59,11 +67,18 @@ const assertMoveAtomicBlock = (
state = editorState,
insertionType = null,
) => {
const newState = moveAtomicBlock(state, atomicBlock, seletion, insertionType);
const newState = AtomicBlockUtils.moveAtomicBlock(
state,
atomicBlock,
seletion,
insertionType,
);
assertAtomic(newState);
return newState;
};

beforeEach(() => jest.resetModules());

test('must insert atomic at start of block with collapsed seletion', () => {
assertInsertAtomicBlock();
});
Expand Down Expand Up @@ -228,7 +243,7 @@ test("mustn't move atomic next to itself with collapsed selection", () => {
// Move atomic block above itself by moving it after preceding block by
// replacement
expect(() => {
moveAtomicBlock(
AtomicBlockUtils.moveAtomicBlock(
resultEditor,
atomicBlock,
new SelectionState({
Expand All @@ -242,7 +257,7 @@ test("mustn't move atomic next to itself with collapsed selection", () => {

// Move atomic block above itself by moving it after preceding block
expect(() => {
moveAtomicBlock(
AtomicBlockUtils.moveAtomicBlock(
resultEditor,
atomicBlock,
new SelectionState({
Expand All @@ -255,19 +270,21 @@ test("mustn't move atomic next to itself with collapsed selection", () => {

// Move atomic block above itself by replacement
expect(() => {
moveAtomicBlock(
AtomicBlockUtils.moveAtomicBlock(
resultEditor,
atomicBlock,
new SelectionState({
anchorKey: atomicBlock.getKey(),
focusKey: atomicBlock.getKey(),
anchorOffset: atomicBlock.getLength(),
focusOffset: atomicBlock.getLength(),
}),
);
}).toThrow(new Error('Block cannot be moved next to itself.'));

// Move atomic block above itself
expect(() => {
moveAtomicBlock(
AtomicBlockUtils.moveAtomicBlock(
resultEditor,
atomicBlock,
new SelectionState({
Expand All @@ -277,10 +294,9 @@ test("mustn't move atomic next to itself with collapsed selection", () => {
);
}).toThrow(new Error('Block cannot be moved next to itself.'));

// Move atomic block below itself by moving it before following block by
// replacement
// Move atomic block below itself by moving it before following block by replacement
expect(() => {
moveAtomicBlock(
AtomicBlockUtils.moveAtomicBlock(
resultEditor,
atomicBlock,
new SelectionState({
Expand All @@ -292,7 +308,7 @@ test("mustn't move atomic next to itself with collapsed selection", () => {

// Move atomic block below itself by moving it before following block
expect(() => {
moveAtomicBlock(
AtomicBlockUtils.moveAtomicBlock(
resultEditor,
atomicBlock,
new SelectionState({
Expand All @@ -305,7 +321,7 @@ test("mustn't move atomic next to itself with collapsed selection", () => {

// Move atomic block below itself by replacement
expect(() => {
moveAtomicBlock(
AtomicBlockUtils.moveAtomicBlock(
resultEditor,
atomicBlock,
new SelectionState({
Expand All @@ -319,7 +335,7 @@ test("mustn't move atomic next to itself with collapsed selection", () => {

// Move atomic block below itself
expect(() => {
moveAtomicBlock(
AtomicBlockUtils.moveAtomicBlock(
resultEditor,
atomicBlock,
new SelectionState({
Expand Down Expand Up @@ -534,7 +550,7 @@ test("mustn't move atomic next to itself", () => {
// Move atomic block above itself by moving it after preceding block by
// replacement
expect(() => {
moveAtomicBlock(
AtomicBlockUtils.moveAtomicBlock(
resultEditor,
atomicBlock,
new SelectionState({
Expand All @@ -549,7 +565,7 @@ test("mustn't move atomic next to itself", () => {
// Move atomic block below itself by moving it before following block by
// replacement
expect(() => {
moveAtomicBlock(
AtomicBlockUtils.moveAtomicBlock(
resultEditor,
atomicBlock,
new SelectionState({
Expand All @@ -561,3 +577,71 @@ test("mustn't move atomic next to itself", () => {
);
}).toThrow(new Error('Block cannot be moved next to itself.'));
});

test('must be able to insert atomic block when experimentalTreeDataSupport is enabled', () => {
// Insert atomic block at the first position
assertInsertAtomicBlock(
EditorState.forceSelection(
EditorState.createWithContent(
contentState.set(
'blockMap',
BlockMapBuilder.createFromArray([
new ContentBlockNode({
text: 'first block',
key: 'A',
}),
]),
),
),
SelectionState.createEmpty('A'),
),
ENTITY_KEY,
CHARACTER,
true,
);
});

test('must be able to move atomic block when experimentalTreeDataSupport is enabled', () => {
// Insert atomic block at the first position
const resultEditor = assertInsertAtomicBlock(
EditorState.forceSelection(
EditorState.createWithContent(
contentState.set(
'blockMap',
BlockMapBuilder.createFromArray([
new ContentBlockNode({
text: 'first block',
key: 'A',
}),
]),
),
),
SelectionState.createEmpty('A'),
),
ENTITY_KEY,
CHARACTER,
true,
);

const resultContent = resultEditor.getCurrentContent();
const lastBlock = resultContent.getBlockMap().last();
const atomicBlock = resultContent
.getBlockMap()
.skip(1)
.first();

// Move atomic block at end of the last block
assertMoveAtomicBlock(
atomicBlock,
new SelectionState({
anchorKey: lastBlock.getKey(),
anchorOffset: lastBlock.getLength(),
focusKey: lastBlock.getKey(),
focusOffset: lastBlock.getLength(),
isBackward: false,
hasFocus: false,
}),
resultEditor,
'after',
);
});
Loading

0 comments on commit 3689a93

Please sign in to comment.