Skip to content

Commit

Permalink
refactor: revised editor unit tests, implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Josh David Miller authored and goshacmd committed Sep 27, 2016
1 parent 6bfed5f commit 1b1c683
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 295 deletions.
7 changes: 7 additions & 0 deletions conf/storybook/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"presets": [
"es2015",
"react",
"stage-0"
]
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
},
"homepage": "https://github.com/StoryShop/app#readme",
"devDependencies": {
"@kadira/storybook": "^2.14.0",
"@kadira/storybook": "^2.5.2",
"autoprefixer": "^6.3.3",
"babel-loader": "^6.2.2",
"babel-plugin-module-resolver": "^2.2.0",
Expand Down
128 changes: 9 additions & 119 deletions src/components/editor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,10 @@ const EditorComponent = (React, ...behaviours) => reactStamp(React).compose({
},

init() {
const content = convertFromRaw(this.props.content);
this.state = {
editor: EditorState.createWithContent(content),
editor: EditorState.createWithContent( convertFromRaw( this.props.content ) ),
readOnly: true,
};

this.onClick = this.onClick.bind(this);
this.onChange = this.onChange.bind(this);
this.onFocus = this.onFocus.bind(this);
this.onBlur = this.onBlur.bind(this);

this.handleKeyCommand = this.handleKeyCommand.bind(this);
this.handleReturn = this.handleReturn.bind(this);

this.save = this.save.bind(this);
},

componentWillUnmount() {
Expand Down Expand Up @@ -86,7 +75,7 @@ const EditorComponent = (React, ...behaviours) => reactStamp(React).compose({

if (this.props.onChange) {
this.deleteTimeout();
this._timeout = setTimeout(this.save, 3000);
this._timeout = setTimeout(::this.save, 3000);
}
},

Expand All @@ -103,119 +92,20 @@ const EditorComponent = (React, ...behaviours) => reactStamp(React).compose({
}
},

handleKeyCommand(command) {
const { editor } = this.state;
const newState = RichUtils.handleKeyCommand(editor, command);
if (newState) {
this.onChange(newState);
return true;
}
return false;
},

handleReturn(e) {
const { editor } = this.state;
const selection = editor.getSelection();
const content = editor.getCurrentContent();
const block = content.getBlockForKey(selection.getStartKey());

// We only care if there is no current selection (e.g. selection is a caret).
if (!selection.isCollapsed()) {
console.log("not collapsed")
return;
}

// We only care if we're at the end of the line.
// TODO: implement splitting current block at selection
if (block.getLength() !== selection.getStartOffset()) {
console.log("not at end of line")
return;
}

const previousBlock = content.getBlockBefore(block.getKey());
if (block.getText().length === 0) {
if (!previousBlock || previousBlock.getText().length === 0) {
// no empty lines between paragraphs
return true;
} else {
// insert header block
this._insertHeader();
return true;
}
} else if (block.getType() === 'unstyled') {
// current line is non-empty and is unstyled already, so let the browser do its thing and
// insert another one.
return false;
} else {
// non-empty and not unstyled, so let's insert a new paragraph and move the cursor there.
this._insertParagraph();
return true;
}
},

_insertParagraph () {
return this._insertBlock({
key: genKey(),
type: 'unstyled',
text: '',
characterList: List()
});
},

_insertBlock (blockData) {
const { editor } = this.state;
const selection = editor.getSelection();
const content = editor.getCurrentContent();
const block = content.getBlockForKey(selection.getStartKey());

// Insert new unstyled block
const newBlock = new ContentBlock(blockData);

const blockArr = [];
content.getBlockMap().forEach((oldBlock, key) => {
blockArr.push(oldBlock);

if (key === block.getKey()) {
blockArr.push(newBlock);
}
});

const newBlockMap = BlockMapBuilder.createFromArray(blockArr);

const newContent = content.merge({
blockMap: newBlockMap,
selectionBefore: selection,
selectionAfter: selection.merge({
anchorKey: newBlock.getKey(),
anchorOffset: 0,
focusKey: newBlock.getKey(),
focusOffset: 0,
isBackward: false,
}),
});

const newState = EditorState.push(editor, newContent, 'insert-fragment');

this.setState({ editor: newState });
},

render() {
const { plugins = [] } = this.props;
const isReadOnly = this.props.readOnly ? true : this.state.readOnly;
const plugins = (this.props.plugins || []).concat([stripPastePlugin]);

return (
<div onClick={this.onClick}>
<span style={{ fontSize: 12, color: '#555' }}>{isReadOnly ? 'Reading' : 'Writing'}</span>
<div onClick={::this.onClick}>
<Editor
ref="upstream"
editorState={this.state.editor}
plugins={plugins}
onChange={this.onChange}
onFocus={this.onFocus}
onBlur={this.onBlur}
plugins={[ ...plugins, stripPastePlugin ]}
onChange={::this.onChange}
onFocus={::this.onFocus}
onBlur={::this.onBlur}
readOnly={isReadOnly}

handleKeyCommand={this.handleKeyCommand}
handleReturn={this.handleReturn}
/>
</div>
);
Expand Down
70 changes: 30 additions & 40 deletions src/components/editor/paste-plugin/index.js
Original file line number Diff line number Diff line change
@@ -1,54 +1,44 @@
import {
EditorState,
ContentState,
ContentBlock,
convertFromHTML,
convertToRaw,
convertFromRaw,
RichUtils,
Modifier,
BlockMapBuilder,
genKey,
CharacterMetadata,
} from 'draft-js';

function insertFragment(editorState, fragment, entityMap) {
var newContent = Modifier.replaceWithFragment(
editorState.getCurrentContent(),
editorState.getSelection(),
fragment
);

return EditorState.push(
editorState,
newContent,
'insert-fragment'
);
}

const stripPastePlugin = (convert = convertFromHTML) => ({
const stripPastePlugin = ( convert = convertFromHTML ) => ({
handlePastedText(text, html, { getEditorState, setEditorState }) {
if (html) {
const htmlFrag = convert(html);
if (htmlFrag) {
const ALLOWED_STYLE = ['ITALIC', 'BOLD'];
const contentBlocks = htmlFrag.map(block => {
const characterList = block.getCharacterList().map(list => {
const styles = list.getStyle().filter(s => {
return ALLOWED_STYLE.indexOf(s) !== -1;
});
return list.set('style', styles);
});
return block.set('type', 'unstyled').set('characterList', characterList);
if ( ! html ) {
return;
}

const htmlFrag = convert( html );
if ( ! htmlFrag ) {
return;
}

const ALLOWED_STYLE = ['ITALIC', 'BOLD'];
const contentBlocks = htmlFrag.map(block => {
const characterList = block.getCharacterList().map(list => {
const styles = list.getStyle().filter(s => {
return ALLOWED_STYLE.indexOf(s) !== -1;
});
return list.set('style', styles);
});
return block.set('type', 'unstyled').set('characterList', characterList);
});

const htmlMap = BlockMapBuilder.createFromArray(contentBlocks);
const htmlMap = BlockMapBuilder.createFromArray(contentBlocks);

const newState = insertFragment(getEditorState(), htmlMap);
setEditorState(newState);
return true;
}
}
const editorState = getEditorState();
const newContent = Modifier.replaceWithFragment(
editorState.getCurrentContent(),
editorState.getSelection(),
htmlMap
);

setEditorState( EditorState.push( editorState, newContent, 'insert-fragment' ) );

return true;
},
});

Expand Down
Loading

0 comments on commit 1b1c683

Please sign in to comment.