Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor onChangeEditableValue handling (version1) #12099

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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