Skip to content

Commit

Permalink
Refactor onChangeEditableValue handling
Browse files Browse the repository at this point in the history
Move it to the `rich-text` package.
  • Loading branch information
atimmer committed Nov 20, 2018
1 parent 2fc6cae commit 16b4bd6
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 40 deletions.
29 changes: 3 additions & 26 deletions packages/editor/src/components/rich-text/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ import {
getSelectionStart,
getSelectionEnd,
remove,
removeFormat,
isCollapsed,
LINE_SEPARATOR,
charAt,
Expand Down Expand Up @@ -109,6 +108,7 @@ export class RichText extends Component {
this.valueToFormat = this.valueToFormat.bind( this );
this.setRef = this.setRef.bind( this );
this.isActive = this.isActive.bind( this );
this.onChangeEditableValue = this.onChangeEditableValue.bind( this );

this.formatToValue = memize( this.formatToValue.bind( this ), { size: 1 } );

Expand Down Expand Up @@ -187,6 +187,7 @@ export class RichText extends Component {
removeAttribute: ( attribute ) => attribute.indexOf( 'data-mce-' ) === 0,
filterString: ( string ) => string.replace( TINYMCE_ZWSP, '' ),
prepareEditableTree: this.props.prepareEditableTree,
onChangeEditableValue: this.onChangeEditableValue,
} );
}

Expand Down Expand Up @@ -416,7 +417,7 @@ export class RichText extends Component {
* @param {Array} formats The formats of the latest rich-text value.
* @param {string} text The text of the latest rich-text value.
*/
onChangeEditableValue( { formats, text } ) {
onChangeEditableValue( formats, text ) {
get( this.props, [ 'onChangeEditableValue' ], [] ).forEach( ( eventHandler ) => {
eventHandler( formats, text );
} );
Expand All @@ -436,8 +437,6 @@ export class RichText extends Component {

const { start, end } = record;

this.onChangeEditableValue( record );

this.savedContent = this.valueToFormat( record );
this.props.onChange( this.savedContent );
this.setState( { start, end } );
Expand Down Expand Up @@ -777,35 +776,13 @@ export class RichText extends Component {
} ).body.innerHTML;
}

/**
* Removes editor only formats from the value.
*
* Editor only formats are applied using `prepareEditableTree`, so we need to
* remove them before converting the internal state
*
* @param {Object} value The internal rich-text value.
* @return {Object} A new rich-text value.
*/
removeEditorOnlyFormats( value ) {
this.props.formatTypes.forEach( ( formatType ) => {
// Remove formats created by prepareEditableTree, because they are editor only.
if ( formatType.__experimentalCreatePrepareEditableTree ) {
value = removeFormat( value, formatType.name, 0, value.text.length );
}
} );

return value;
}

/**
* Converts the internal value to the external data format.
*
* @param {Object} value The internal rich-text value.
* @return {*} The external data format, data type depends on props.
*/
valueToFormat( value ) {
value = this.removeEditorOnlyFormats( value );

// Handle deprecated `children` and `node` sources.
if ( this.usedDeprecatedChildrenSource ) {
return children.fromDOM( unstableToDom( {
Expand Down
117 changes: 103 additions & 14 deletions packages/rich-text/src/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,17 @@ function toFormat( { type, attributes } ) {
}
}

return {
const format = {
type: formatType.name,
attributes: registeredAttributes,
unregisteredAttributes,
};

if ( formatType.__experimentalCreatePrepareEditableTree ) {
format.onlyShadow = true;
}

return format;
}

/**
Expand Down Expand Up @@ -120,6 +126,7 @@ export function create( {
unwrapNode,
filterString,
removeAttribute,
onChangeEditableValue,
} = {} ) {
if ( typeof text === 'string' && text.length > 0 ) {
return {
Expand All @@ -137,17 +144,23 @@ export function create( {
}

if ( ! multilineTag ) {
return createFromElement( {
const { accumulator, shadowAccumulator } = createFromElement( {
element,
range,
removeNode,
unwrapNode,
filterString,
removeAttribute,
} );

if ( onChangeEditableValue ) {
onChangeEditableValue( shadowAccumulator, accumulator.text );
}

return accumulator;
}

return createFromMultilineElement( {
const { accumulator, shadowAccumulator } = createFromMultilineElement( {
element,
range,
multilineTag,
Expand All @@ -157,6 +170,12 @@ export function create( {
filterString,
removeAttribute,
} );

if ( onChangeEditableValue ) {
onChangeEditableValue( shadowAccumulator, accumulator.text );
}

return accumulator;
}

/**
Expand Down Expand Up @@ -279,20 +298,29 @@ function createFromElement( {
multilineTag,
multilineWrapperTags,
currentWrapperTags = [],
shadowCurrentWrapperTags = [],
removeNode,
unwrapNode,
filterString,
removeAttribute,
} ) {
const accumulator = createEmptyValue();
const shadowAccumulator = [];

if ( ! element ) {
return accumulator;
return {
accumulator,
shadowAccumulator,
};
}

if ( ! element.hasChildNodes() ) {
accumulateSelection( accumulator, element, range, createEmptyValue() );
return accumulator;

return {
accumulator,
shadowAccumulator,
};
}

const length = element.childNodes.length;
Expand Down Expand Up @@ -323,6 +351,7 @@ function createFromElement( {
// Create a sparse array of the same length as `text`, in which
// formats can be added.
accumulator.formats.length += text.length;
shadowAccumulator.length += text.length;
continue;
}

Expand All @@ -342,22 +371,31 @@ function createFromElement( {
accumulateSelection( accumulator, node, range, createEmptyValue() );
accumulator.text += '\n';
accumulator.formats.length += 1;
shadowAccumulator.length += 1;
continue;
}

const lastFormats = accumulator.formats[ accumulator.formats.length - 1 ];
const lastFormat = lastFormats && lastFormats[ lastFormats.length - 1 ];
const shadowLastFormats = shadowAccumulator[ accumulator.formats.length - 1 ];
const shadowLastFormat = shadowLastFormats && shadowLastFormats[ shadowLastFormats.length - 1 ];
let format;
let shadowFormat;
let value;

if ( ! unwrapNode || ! unwrapNode( node ) ) {
const newFormat = toFormat( {
let newFormat = toFormat( {
type,
attributes: getAttributes( {
element: node,
removeAttribute,
} ),
} );
const shadowNewFormat = newFormat;

if ( newFormat.hasOwnProperty( 'onlyShadow' ) && newFormat.onlyShadow ) {
newFormat = null;
}

if ( newFormat ) {
// Reuse the last format if it's equal.
Expand All @@ -367,10 +405,19 @@ function createFromElement( {
format = newFormat;
}
}

if ( shadowNewFormat ) {
// Reuse the last format if it's equal.
if ( isFormatEqual( shadowNewFormat, shadowLastFormat ) ) {
shadowFormat = shadowLastFormat;
} else {
shadowFormat = shadowNewFormat;
}
}
}

if ( multilineWrapperTags && multilineWrapperTags.indexOf( type ) !== -1 ) {
value = createFromMultilineElement( {
const returned = createFromMultilineElement( {
element: node,
range,
multilineTag,
Expand All @@ -380,10 +427,14 @@ function createFromElement( {
filterString,
removeAttribute,
currentWrapperTags: [ ...currentWrapperTags, format ],
shadowCurrentWrapperTags: [ ...shadowCurrentWrapperTags, shadowFormat ],
} );
value = returned.accumulator;

format = undefined;
shadowFormat = undefined;
} else {
value = createFromElement( {
const returned = createFromElement( {
element: node,
range,
multilineTag,
Expand All @@ -393,6 +444,8 @@ function createFromElement( {
filterString,
removeAttribute,
} );

value = returned.accumulator;
}

const text = value.text;
Expand All @@ -416,9 +469,16 @@ function createFromElement( {
} else {
formats[ start ] = [ format ];
}

if ( shadowAccumulator[ start ] ) {
shadowAccumulator[ start ].unshift( shadowFormat );
} else {
shadowAccumulator[ start ] = [ shadowFormat ];
}
} else {
accumulator.text += text;
accumulator.formats.length += text.length;
shadowAccumulator.length += text.length;

let i = value.formats.length;

Expand All @@ -434,18 +494,35 @@ function createFromElement( {
}
}

if ( shadowFormat ) {
if ( shadowAccumulator[ formatIndex ] ) {
shadowAccumulator[ formatIndex ].push( shadowFormat );
} else {
shadowAccumulator[ formatIndex ] = [ shadowFormat ];
}
}

if ( value.formats[ i ] ) {
if ( formats[ formatIndex ] ) {
formats[ formatIndex ].push( ...value.formats[ i ] );
} else {
formats[ formatIndex ] = value.formats[ i ];
}

if ( shadowAccumulator[ formatIndex ] ) {
shadowAccumulator[ formatIndex ].push( ...value.formats[ i ] );
} else {
shadowAccumulator[ formatIndex ] = value.formats[ i ];
}
}
}
}
}

return accumulator;
return {
accumulator,
shadowAccumulator,
};
}

/**
Expand Down Expand Up @@ -482,11 +559,16 @@ function createFromMultilineElement( {
filterString,
removeAttribute,
currentWrapperTags = [],
shadowCurrentWrapperTags = [],
} ) {
const accumulator = createEmptyValue();
let shadowAccumulator = createEmptyValue().formats;

if ( ! element || ! element.hasChildNodes() ) {
return accumulator;
return {
accumulator,
shadowAccumulator,
};
}

const length = element.children.length;
Expand All @@ -499,7 +581,7 @@ function createFromMultilineElement( {
continue;
}

let value = createFromElement( {
const childAccumulator = createFromElement( {
element: node,
range,
multilineTag,
Expand All @@ -510,9 +592,11 @@ function createFromMultilineElement( {
filterString,
removeAttribute,
} );
let { accumulator: value } = childAccumulator;
const { shadowAccumulator: shadowValue } = childAccumulator;

// If a line consists of one single line break (invisible), consider the
// line empty, wether this is the browser's doing or not.
// line empty, whether this is the browser's doing or not.
if ( value.text === '\n' ) {
const start = value.start;
const end = value.end;
Expand All @@ -531,17 +615,22 @@ function createFromMultilineElement( {
// Multiline value text should be separated by a double line break.
if ( index !== 0 || currentWrapperTags.length > 0 ) {
const formats = currentWrapperTags.length > 0 ? [ currentWrapperTags ] : [ , ];
const shadowFormats = shadowCurrentWrapperTags.length > 0 ? [ shadowCurrentWrapperTags ] : [ , ];
accumulator.formats = accumulator.formats.concat( formats );
shadowAccumulator = shadowAccumulator.concat( shadowFormats );
accumulator.text += LINE_SEPARATOR;
}

accumulateSelection( accumulator, node, range, value );

accumulator.formats = accumulator.formats.concat( value.formats );
shadowAccumulator = shadowAccumulator.concat( shadowValue );
accumulator.text += value.text;
}

return accumulator;
return {
accumulator,
shadowAccumulator,
};
}

/**
Expand Down

0 comments on commit 16b4bd6

Please sign in to comment.