Skip to content

Commit

Permalink
TinyMCE per block: Trying to the temporary content editable approach
Browse files Browse the repository at this point in the history
  • Loading branch information
youknowriad committed Mar 16, 2017
1 parent 77605ae commit c884f27
Show file tree
Hide file tree
Showing 12 changed files with 136 additions and 23 deletions.
29 changes: 15 additions & 14 deletions tinymce-per-block/build/app.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions tinymce-per-block/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"classnames": "^2.2.5",
"is-equal-shallow": "^0.1.3",
"lodash": "^4.17.4",
"rangy": "^1.3.0",
"react": "^15.4.2",
"react-dom": "^15.4.2",
"react-textarea-autosize": "^4.0.5"
Expand Down
1 change: 1 addition & 0 deletions tinymce-per-block/src/blocks/embed-block/form.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export default class EmbedBlockForm extends Component {
placeholder="Write caption"
focusConfig={ focusConfig }
onFocusChange={ api.focus }
selectionName="caption"
/>
</div>
}
Expand Down
1 change: 1 addition & 0 deletions tinymce-per-block/src/blocks/heading-block/form.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export default class HeadingBlockForm extends Component {
focusConfig={ focusConfig }
onFocusChange={ api.focus }
onType={ api.unselect }
selectionName="content"
single
inline
/>
Expand Down
1 change: 1 addition & 0 deletions tinymce-per-block/src/blocks/html-block/form.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export default class HtmlBlockForm extends Component {
focusConfig={ focusConfig }
onFocusChange={ api.focus }
onType={ api.unselect }
selectionName="caption"
/>
</div>
</div>
Expand Down
1 change: 1 addition & 0 deletions tinymce-per-block/src/blocks/image-block/form.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export default class ImageBlockForm extends Component {
placeholder="Write caption"
focusConfig={ focusConfig }
onFocusChange={ api.focus }
selectionName="caption"
/>
</div>
}
Expand Down
2 changes: 2 additions & 0 deletions tinymce-per-block/src/blocks/quote-block/form.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export default class QuoteBlockForm extends Component {
focusConfig={ focusInput === 'content' ? focusConfig : null }
onFocusChange={ ( config ) => api.focus( Object.assign( { input: 'content' }, config ) ) }
onType={ api.unselect }
selectionName="caption"
inline
/>
</div>
Expand All @@ -124,6 +125,7 @@ export default class QuoteBlockForm extends Component {
focusConfig={ focusInput === 'cite' ? focusConfig : null }
onFocusChange={ ( config ) => api.focus( Object.assign( { input: 'cite' }, config ) ) }
onType={ api.unselect }
selectionName="cite"
inline
single
/>
Expand Down
1 change: 1 addition & 0 deletions tinymce-per-block/src/blocks/text-block/form.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export default class TextBlockForm extends Component {
focusConfig={ focusConfig }
onFocusChange={ api.focus }
onType={ api.unselect }
selectionName="content"
inline
/>
</div>
Expand Down
8 changes: 5 additions & 3 deletions tinymce-per-block/src/external/wp-blocks/editable/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,10 @@ export default class EditableComponent extends Component {
};

onFocus = () => {
const bookmark = this.editor.selection.getBookmark( 2, true );
this.props.onFocusChange( { bookmark } );
if ( ! this.props.focusConfig ) {
const bookmark = this.editor.selection.getBookmark( 2, true );
this.props.onFocusChange( { bookmark } );
}
};

onSetup = ( editor ) => {
Expand Down Expand Up @@ -256,7 +258,7 @@ export default class EditableComponent extends Component {
render() {
return (
<div ref={ this.setRef }>
<div contentEditable />
<div contentEditable data-selection={ this.props.selectionName } />
</div>
);
}
Expand Down
7 changes: 5 additions & 2 deletions tinymce-per-block/src/external/wp-blocks/input/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ export default class EnhancedInput extends Component {
render() {
// Keeping splitValue to exclude it from props
const ignoredProps = [
'value', 'splitValue', 'removePrevious', 'moveCursorDown', 'moveCursorUp', 'focusConfig', 'onFocusChange'
'value', 'splitValue', 'removePrevious', 'moveCursorDown', 'moveCursorUp',
'focusConfig', 'onFocusChange', 'selectionName'
];
const { value } = this.props;
const props = omit( this.props, ignoredProps );
Expand All @@ -100,7 +101,8 @@ export default class EnhancedInput extends Component {
<div
className="textarea-mirror"
ref={ this.bindMirror }
style={ style } />
style={ style }
/>
<Textarea
ref={ this.bindInput }
{ ...props }
Expand All @@ -109,6 +111,7 @@ export default class EnhancedInput extends Component {
onKeyDown={ this.onKeyDown }
onChange={ this.onChange }
onFocus={ this.onFocus }
data-selection={ this.props.selectionName }
/>
</div>
);
Expand Down
4 changes: 4 additions & 0 deletions tinymce-per-block/src/renderers/block/_style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
list-style: none;
padding: 0;
margin: 0;

&:focus {
outline: 0;
}
}

.block-list__block {
Expand Down
103 changes: 99 additions & 4 deletions tinymce-per-block/src/renderers/block/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { map, debounce } from 'lodash';
import { findDOMNode } from 'react-dom';
import classNames from 'classnames';
import { getBlock } from 'wp-blocks';
import rangy from 'rangy';

/**
* Internal dependencies
Expand All @@ -20,10 +21,12 @@ class BlockList extends Component {
selected: null,
focus: { uid: null },
blocks: [],
contentEditable: false
};

blockNodes = [];
blockNodes = {};
commands = [];
selections = [];

bindEditor = ( ref ) => {
this.editor = ref;
Expand All @@ -39,6 +42,57 @@ class BlockList extends Component {
componentDidMount() {
this.setState( { blocks: this.props.content } );
window.addEventListener( 'click', this.handleDocumentClick );
this.editor.addEventListener( 'mousedown', () => {
this.setState( { contentEditable: true } );
} );
this.editor.addEventListener( 'mouseup', () => {
const range = rangy.getSelection().getRangeAt( 0 );
let closestContentEditable;
if ( ! range.commonAncestorContainer.getAttribute ) {
closestContentEditable = range.commonAncestorContainer.parentNode.closest( '[contentEditable]' );
}
else if ( range.commonAncestorContainer.getAttribute( 'contentEditable' ) ) {
closestContentEditable = range.commonAncestorContainer;
} else {
closestContentEditable = range.commonAncestorContainer.closest( '[contentEditable]' );
}
const editorNode = findDOMNode( this.editor );
if ( range.collpased || closestContentEditable !== editorNode ) {
this.setState( { contentEditable: false } );
}
} );
document.addEventListener( 'selectionchange', () => {
const range = rangy.getSelection().getRangeAt( 0 );
if ( range.commonAncestorContainer !== findDOMNode( this.editor ) ) {
return;
}
const blockSelections = [];
this.state.blocks.forEach( ( { uid } ) => {
const blockNode = this.blockNodes[ uid ];
const blockDomNode = findDOMNode( blockNode );
const selectionNodes = Array.from( blockDomNode.querySelectorAll( '[data-selection]' ) );
const contained = range.containsNode( blockDomNode );
const containedPartially = range.containsNode( blockDomNode, true );
const selections = selectionNodes.map( ( selectionNode ) => {
const id = selectionNode.getAttribute( 'data-selection' );
const nodeRange = rangy.createRange();
nodeRange.selectNodeContents( selectionNode );
const intersection = nodeRange.intersection( range );
if ( ! intersection ) {
return { id, content: false };
}
const content = intersection.toHtml();
return { id, content, node: selectionNode, range: intersection };
} );
blockSelections.push( {
uid,
contained,
containedPartially,
selections
} );
} );
this.selections = blockSelections;
} );
}

componentWillUnmount() {
Expand Down Expand Up @@ -99,14 +153,55 @@ class BlockList extends Component {

addBlock = ( id ) => {
this.executeCommand( { type: 'addBlock', id } );
}
};

onKeyDown = ( event ) => {
if ( ! this.state.contentEditable ) {
return;
}
event.preventDefault();
event.stopPropagation();
const containedElements = this.selections.filter( selection => selection.contained );
const containedPartially = this.selections.filter( selection => selection.containedPartially );
containedElements.forEach( ( { uid } ) => {
const command = commands.remove();
command.uid = uid;
this.executeCommand( command );
} );
containedPartially.forEach( ( { uid, selections } ) => {
const changes = selections.reduce(
( memo, selection ) => {
if ( ! selection.content ) {
return memo;
}
selection.range.deleteContents();
memo[ selection.id ] = selection.node.innerHTML;
return memo;
},
{}
);
const command = commands.change( changes );
command.uid = uid;
this.executeCommand( command );
} );
if ( containedPartially.length === 2 ) {
const mergedBlock = containedPartially[ 1 ].uid;
const command = commands.mergeWithPrevious();
command.uid = mergedBlock;
this.executeCommand( command );
}

setTimeout( () => {
this.setState( { contentEditable: false } );
} );
};

render() {
const { blocks, focus, selected, hovered } = this.state;
const { blocks, focus, selected, hovered, contentEditable } = this.state;

return (
<div>
<div className="block-list" ref={ this.bindEditor }>
<div className="block-list" ref={ this.bindEditor } contentEditable={ contentEditable } suppressContentEditableWarning onKeyDown={ this.onKeyDown }>
{ map( blocks, ( block, index ) => {
const isFocused = block.uid === focus.uid;
const api = Object.keys( commands ).reduce( ( memo, command ) => {
Expand Down

0 comments on commit c884f27

Please sign in to comment.