Skip to content

Commit

Permalink
Handle multiple formats with format placeholder
Browse files Browse the repository at this point in the history
  • Loading branch information
Tug committed Dec 23, 2018
1 parent 0c1d597 commit 0c85a37
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 15 deletions.
13 changes: 10 additions & 3 deletions packages/editor/src/components/rich-text/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,9 @@ export class RichText extends Component {
onSplit( before, after );
}

valueToFormat( { formats, formatPlaceholder, text } ) {
valueToFormat( { formats, text } ) {
const value = toHTMLString( {
value: { formats, formatPlaceholder, text },
value: { formats, text },
multilineTag: this.multilineTag,
} );
// remove the outer root tags
Expand Down Expand Up @@ -226,10 +226,17 @@ export class RichText extends Component {
// Let's fix that here so `rich-text/slice` can work properly
const realStart = Math.min( start, end );
const realEnd = Math.max( start, end );
const jump = this.state.start + 1 !== realStart;
// update format placeholder to continue writing in the current format
// or set it to null if user jumped to another part in the text
const formatPlaceholder = ! jump && this.state.formatPlaceholder ? {
...this.state.formatPlaceholder,
index: realStart,
} : null;
this.setState( {
start: realStart,
end: realEnd,
formatPlaceholder: null,
formatPlaceholder,
lastValue: text,
} );
}
Expand Down
71 changes: 71 additions & 0 deletions packages/rich-text/src/apply-format.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* External dependencies
*/

import { find, without } from 'lodash';

/**
* Internal dependencies
*/

import { normaliseFormats } from './normalise-formats';

/**
* Apply a format object to a Rich Text value from the given `startIndex` to the
* given `endIndex`. Indices are retrieved from the selection if none are
* provided.
*
* @param {Object} value Value to modify.
* @param {Object} format Format to apply.
* @param {number} startIndex Start index.
* @param {number} endIndex End index.
*
* @return {Object} A new value with the format applied.
*/
export function applyFormat(
{ formats, formatPlaceholder, text, start, end },
format,
startIndex = start,
endIndex = end
) {
const newFormats = formats.slice( 0 );
const previousFormats = newFormats[ startIndex - 1 ] || [];
const placeholderFormats = formatPlaceholder && formatPlaceholder.index === start && formatPlaceholder.formats;
// Follow the same logic as in getActiveFormat: placeholderFormats has priority over previousFormats
const activeFormats = ( placeholderFormats ? placeholderFormats : previousFormats ) || [];
const hasType = find( activeFormats, { type: format.type } );

// The selection is collpased, insert a placeholder with the format so new input appears
// with the format applied.
if ( startIndex === endIndex ) {
return {
formats,
text,
start,
end,
formatPlaceholder: {
index: start,
formats: [
...without( activeFormats, hasType ),
...! hasType && [ format ],
],
},
};
}

for ( let index = startIndex; index < endIndex; index++ ) {
applyFormats( newFormats, index, format );
}

return normaliseFormats( { formats: newFormats, text, start, end } );
}

function applyFormats( formats, index, format ) {
if ( formats[ index ] ) {
const newFormatsAtIndex = formats[ index ].filter( ( { type } ) => type !== format.type );
newFormatsAtIndex.push( format );
formats[ index ] = newFormatsAtIndex;
} else {
formats[ index ] = [ format ];
}
}
19 changes: 7 additions & 12 deletions packages/rich-text/src/get-active-format.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,17 @@ export function getActiveFormat( { formats, formatPlaceholder, start, end }, for
return find( formats[ start ], { type: formatType } );
}

// otherwise get the previous character format (or the next one at the beginning of the text)
const previousLetterFormat = find( formats[ start > 0 ? start - 1 : start ], { type: formatType } );
// if user picked (or unpicked) formats but didn't write anything in those formats yet return this format
if ( formatPlaceholder && formatPlaceholder.index === start ) {
return find( formatPlaceholder.formats, { type: formatType } );
}

// otherwise get the previous character format
const previousLetterFormat = find( formats[ start - 1 ], { type: formatType } );

if ( previousLetterFormat ) {
return previousLetterFormat;
}

// if user picked a format but didn't write anything in this format yet return this format
if (
formatPlaceholder &&
formatPlaceholder.format &&
formatPlaceholder.index === start &&
formatPlaceholder.format.type === formatType
) {
return formatPlaceholder.format;
}

return undefined;
}
36 changes: 36 additions & 0 deletions packages/rich-text/src/normalise-formats.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Internal dependencies
*/

import { isFormatEqual } from './is-format-equal';

/**
* Normalises formats: ensures subsequent equal formats have the same reference.
*
* @param {Object} value Value to normalise formats of.
*
* @return {Object} New value with normalised formats.
*/
export function normaliseFormats( { formats, formatPlaceholder, text, start, end } ) {
const newFormats = formats.slice( 0 );

newFormats.forEach( ( formatsAtIndex, index ) => {
const lastFormatsAtIndex = newFormats[ index - 1 ];

if ( lastFormatsAtIndex ) {
const newFormatsAtIndex = formatsAtIndex.slice( 0 );

newFormatsAtIndex.forEach( ( format, formatIndex ) => {
const lastFormat = lastFormatsAtIndex[ formatIndex ];

if ( isFormatEqual( format, lastFormat ) ) {
newFormatsAtIndex[ formatIndex ] = lastFormat;
}
} );

newFormats[ index ] = newFormatsAtIndex;
}
} );

return { formats: newFormats, formatPlaceholder, text, start, end };
}
59 changes: 59 additions & 0 deletions packages/rich-text/src/remove-format.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* External dependencies
*/

import { find, without } from 'lodash';

/**
* Internal dependencies
*/

import { normaliseFormats } from './normalise-formats';

/**
* Remove any format object from a Rich Text value by type from the given
* `startIndex` to the given `endIndex`. Indices are retrieved from the
* selection if none are provided.
*
* @param {Object} value Value to modify.
* @param {string} formatType Format type to remove.
* @param {number} startIndex Start index.
* @param {number} endIndex End index.
*
* @return {Object} A new value with the format applied.
*/
export function removeFormat(
{ formats, formatPlaceholder, text, start, end },
formatType,
startIndex = start,
endIndex = end
) {
const newFormats = formats.slice( 0 );
let newFormatPlaceholder = null;

if ( start === end && formatPlaceholder && formatPlaceholder.index === start ) {
newFormatPlaceholder = {
...formatPlaceholder,
formats: without( formatPlaceholder.formats || [], find( formatPlaceholder.formats || [], { type: formatType } ) ),
};
}

// Do not remove format if selection is empty
for ( let i = startIndex; i < endIndex; i++ ) {
if ( newFormats[ i ] ) {
filterFormats( newFormats, i, formatType );
}
}

return normaliseFormats( { formats: newFormats, formatPlaceholder: newFormatPlaceholder, text, start, end } );
}

function filterFormats( formats, index, formatType ) {
const newFormats = formats[ index ].filter( ( { type } ) => type !== formatType );

if ( newFormats.length ) {
formats[ index ] = newFormats;
} else {
delete formats[ index ];
}
}

0 comments on commit 0c85a37

Please sign in to comment.