Skip to content

Commit

Permalink
RichText: change value to have separate keys for line and object form…
Browse files Browse the repository at this point in the history
…ats (#13948)

* Add objects and lineFormats

* Update RichText

* Fix image toolbar

* Update format placeholder

* lineFormat => lines

* concatPair => mergePair

* Update selectedFormat checks

* Add some extra info to create docs

* Move create docs inline

* Merge lines and objects

* Fix typos

* Add getActiveObject unit tests

* Update docs

* Rebase

* Adjust unstableToDom arguments

* Remove normaliseFormats from list functions

* Update native files

* Update native file
  • Loading branch information
ellatrix authored and youknowriad committed Mar 20, 2019
1 parent 5f0b479 commit 68c04f3
Show file tree
Hide file tree
Showing 50 changed files with 583 additions and 445 deletions.
13 changes: 10 additions & 3 deletions packages/block-editor/src/components/rich-text/format-edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/
import { withSelect } from '@wordpress/data';
import { Fragment } from '@wordpress/element';
import { getActiveFormat } from '@wordpress/rich-text';
import { getActiveFormat, getActiveObject } from '@wordpress/rich-text';

const FormatEdit = ( { formatTypes, onChange, value } ) => {
return (
Expand All @@ -15,13 +15,20 @@ const FormatEdit = ( { formatTypes, onChange, value } ) => {

const activeFormat = getActiveFormat( value, name );
const isActive = activeFormat !== undefined;
const activeAttributes = isActive ? activeFormat.attributes || {} : {};
const activeObject = getActiveObject( value );
const isObjectActive = activeObject !== undefined;

return (
<Edit
key={ name }
isActive={ isActive }
activeAttributes={ activeAttributes }
activeAttributes={
isActive ? activeFormat.attributes || {} : {}
}
isObjectActive={ isObjectActive }
activeObjectAttributes={
isObjectActive ? activeObject.attributes || {} : {}
}
value={ value }
onChange={ onChange }
/>
Expand Down
24 changes: 13 additions & 11 deletions packages/block-editor/src/components/rich-text/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,10 @@ export class RichText extends Component {
* @return {Object} The current record (value and selection).
*/
getRecord() {
const { formats, text } = this.formatToValue( this.props.value );
const { formats, replacements, text } = this.formatToValue( this.props.value );
const { start, end, selectedFormat } = this.state;

return { formats, text, start, end, selectedFormat };
return { formats, replacements, text, start, end, selectedFormat };
}

createRecord() {
Expand Down Expand Up @@ -394,13 +394,17 @@ export class RichText extends Component {
}

let { selectedFormat } = this.state;
const { formats, text, start, end } = this.createRecord();
const { formats, replacements, text, start, end } = this.createRecord();

if ( this.formatPlaceholder ) {
formats[ this.state.start ] = formats[ this.state.start ] || [];
formats[ this.state.start ].push( this.formatPlaceholder );
selectedFormat = formats[ this.state.start ].length;
} else if ( selectedFormat ) {
selectedFormat = this.formatPlaceholder.length;

if ( selectedFormat > 0 ) {
formats[ this.state.start ] = this.formatPlaceholder;
} else {
delete formats[ this.state.start ];
}
} else if ( selectedFormat > 0 ) {
const formatsBefore = formats[ start - 1 ] || [];
const formatsAfter = formats[ start ] || [];

Expand All @@ -411,12 +415,13 @@ export class RichText extends Component {
}

source = source.slice( 0, selectedFormat );

formats[ this.state.start ] = source;
} else {
delete formats[ this.state.start ];
}

const change = { formats, text, start, end, selectedFormat };
const change = { formats, replacements, text, start, end, selectedFormat };

this.onChange( change, {
withoutHistory: true,
Expand Down Expand Up @@ -936,7 +941,6 @@ export class RichText extends Component {
return unstableToDom( {
value,
multilineTag: this.multilineTag,
multilineWrapperTags: this.multilineWrapperTags,
prepareEditableTree: this.props.prepareEditableTree,
} ).body.innerHTML;
}
Expand Down Expand Up @@ -975,7 +979,6 @@ export class RichText extends Component {
return children.fromDOM( unstableToDom( {
value,
multilineTag: this.multilineTag,
multilineWrapperTags: this.multilineWrapperTags,
isEditableTree: false,
} ).body.childNodes );
}
Expand All @@ -984,7 +987,6 @@ export class RichText extends Component {
return toHTMLString( {
value,
multilineTag: this.multilineTag,
multilineWrapperTags: this.multilineWrapperTags,
} );
}

Expand Down
15 changes: 7 additions & 8 deletions packages/block-editor/src/components/rich-text/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,9 @@ export class RichText extends Component {
const { formatPlaceholder, start, end } = this.state;
// Since we get the text selection from Aztec we need to be in sync with the HTML `value`
// Removing leading white spaces using `trim()` should make sure this is the case.
const { formats, text } = this.formatToValue( this.props.value === undefined ? undefined : this.props.value.trimLeft() );
const { formats, replacements, text } = this.formatToValue( this.props.value === undefined ? undefined : this.props.value.trimLeft() );

return { formats, formatPlaceholder, text, start, end };
return { formats, replacements, formatPlaceholder, text, start, end };
}

/*
Expand Down Expand Up @@ -156,13 +156,12 @@ export class RichText extends Component {
onSplit( before, after, ...blocks );
}

valueToFormat( { formats, text } ) {
const value = toHTMLString( {
value: { formats, text },
multilineTag: this.multilineTag,
} );
valueToFormat( value ) {
// remove the outer root tags
return this.removeRootTagsProduceByAztec( value );
return this.removeRootTagsProduceByAztec( toHTMLString( {
value,
multilineTag: this.multilineTag,
} ) );
}

getActiveFormatNames( record ) {
Expand Down
21 changes: 10 additions & 11 deletions packages/format-library/src/image/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const image = {
}

static getDerivedStateFromProps( props, state ) {
const { activeAttributes: { style } } = props;
const { activeObjectAttributes: { style } } = props;

if ( style === state.previousStyle ) {
return null;
Expand Down Expand Up @@ -79,8 +79,8 @@ export const image = {
}

render() {
const { value, onChange, isActive, activeAttributes } = this.props;
const { style } = activeAttributes;
const { value, onChange, isObjectActive, activeObjectAttributes } = this.props;
const { style } = activeObjectAttributes;
// Rerender PositionedAtSelection when the selection changes or when
// the width changes.
const key = value.start + style;
Expand All @@ -91,7 +91,7 @@ export const image = {
icon={ <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><Path d="M4 16h10c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v9c0 1.1.9 2 2 2zM4 5h10v9H4V5zm14 9v2h4v-2h-4zM2 20h20v-2H2v2zm6.4-8.8L7 9.4 5 12h8l-2.6-3.4-2 2.6z" /></SVG> }
title={ __( 'Inline Image' ) }
onClick={ this.openModal }
isActive={ isActive }
isActive={ isObjectActive }
/>
{ this.state.modal && <MediaUpload
allowedTypes={ ALLOWED_MEDIA_TYPES }
Expand All @@ -113,7 +113,7 @@ export const image = {
return null;
} }
/> }
{ isActive && <PositionedAtSelection key={ key }>
{ isObjectActive && <PositionedAtSelection key={ key }>
<Popover
position="bottom center"
focusOnMount={ false }
Expand All @@ -125,20 +125,19 @@ export const image = {
onKeyPress={ stopKeyPropagation }
onKeyDown={ this.onKeyDown }
onSubmit={ ( event ) => {
const newFormats = value.formats.slice( 0 );
const newReplacements = value.replacements.slice();

newFormats[ value.start ] = [ {
newReplacements[ value.start ] = {
type: name,
object: true,
attributes: {
...activeAttributes,
...activeObjectAttributes,
style: `width: ${ this.state.width }px;`,
},
} ];
};

onChange( {
...value,
formats: newFormats,
replacements: newReplacements,
} );

event.preventDefault();
Expand Down
65 changes: 49 additions & 16 deletions packages/rich-text/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,26 @@ called without any input, an empty value will be created. If
`multilineTag` will be separated by two newlines. The optional functions can
be used to filter out content.

A value will have the following shape, which you are strongly encouraged not
to modify without the use of helper functions:

```js
{
text: string,
formats: Array,
replacements: Array,
?start: number,
?end: number,
}
```

As you can see, text and formatting are separated. `text` holds the text,
including any replacement characters for objects and lines. `formats`,
`objects` and `lines` are all sparse arrays of the same length as `text`. It
holds information about the formatting at the relevant text indices. Finally
`start` and `end` state which text indices are selected. They are only
provided if a `Range` was given.

**Parameters**

- **$1** `[Object]`: Optional named arguments.
Expand Down Expand Up @@ -93,9 +113,23 @@ is no format at the selection.

`(Object|undefined)`: Active format object of the specified type, or undefined.

### getActiveObject

[src/index.js#L11-L11](src/index.js#L11-L11)

Gets the active object, if there is any.

**Parameters**

- **value** `Object`: Value to inspect.

**Returns**

`?Object`: Active object, or undefined.

### getTextContent

[src/index.js#L13-L13](src/index.js#L13-L13)
[src/index.js#L14-L14](src/index.js#L14-L14)

Get the textual content of a Rich Text value. This is similar to
`Element.textContent`.
Expand All @@ -110,7 +144,7 @@ Get the textual content of a Rich Text value. This is similar to

### insert

[src/index.js#L21-L21](src/index.js#L21-L21)
[src/index.js#L22-L22](src/index.js#L22-L22)

Insert a Rich Text value, an HTML string, or a plain text string, into a
Rich Text value at the given `startIndex`. Any content between `startIndex`
Expand All @@ -130,7 +164,7 @@ none are provided.

### insertObject

[src/index.js#L24-L24](src/index.js#L24-L24)
[src/index.js#L25-L25](src/index.js#L25-L25)

Insert a format as an object into a Rich Text value at the given
`startIndex`. Any content between `startIndex` and `endIndex` will be
Expand All @@ -149,7 +183,7 @@ removed. Indices are retrieved from the selection if none are provided.

### isCollapsed

[src/index.js#L14-L14](src/index.js#L14-L14)
[src/index.js#L15-L15](src/index.js#L15-L15)

Check if the selection of a Rich Text value is collapsed or not. Collapsed
means that no characters are selected, but there is a caret present. If there
Expand All @@ -166,7 +200,7 @@ is no selection, `undefined` will be returned. This is similar to

### isEmpty

[src/index.js#L15-L15](src/index.js#L15-L15)
[src/index.js#L16-L16](src/index.js#L16-L16)

Check if a Rich Text value is Empty, meaning it contains no text or any
objects (such as images).
Expand All @@ -181,7 +215,7 @@ objects (such as images).

### join

[src/index.js#L16-L16](src/index.js#L16-L16)
[src/index.js#L17-L17](src/index.js#L17-L17)

Combine an array of Rich Text values into one, optionally separated by
`separator`, which can be a Rich Text value, HTML string, or plain text
Expand All @@ -198,7 +232,7 @@ string. This is similar to `Array.prototype.join`.

### registerFormatType

[src/index.js#L17-L17](src/index.js#L17-L17)
[src/index.js#L18-L18](src/index.js#L18-L18)

Registers a new format provided a unique name and an object defining its
behavior.
Expand All @@ -218,7 +252,7 @@ behavior.

### remove

[src/index.js#L19-L19](src/index.js#L19-L19)
[src/index.js#L20-L20](src/index.js#L20-L20)

Remove content from a Rich Text value between the given `startIndex` and
`endIndex`. Indices are retrieved from the selection if none are provided.
Expand All @@ -235,7 +269,7 @@ Remove content from a Rich Text value between the given `startIndex` and

### removeFormat

[src/index.js#L18-L18](src/index.js#L18-L18)
[src/index.js#L19-L19](src/index.js#L19-L19)

Remove any format object from a Rich Text value by type from the given
`startIndex` to the given `endIndex`. Indices are retrieved from the
Expand All @@ -254,7 +288,7 @@ selection if none are provided.

### replace

[src/index.js#L20-L20](src/index.js#L20-L20)
[src/index.js#L21-L21](src/index.js#L21-L21)

Search a Rich Text value and replace the match(es) with `replacement`. This
is similar to `String.prototype.replace`.
Expand All @@ -271,7 +305,7 @@ is similar to `String.prototype.replace`.

### slice

[src/index.js#L25-L25](src/index.js#L25-L25)
[src/index.js#L26-L26](src/index.js#L26-L26)

Slice a Rich Text value from `startIndex` to `endIndex`. Indices are
retrieved from the selection if none are provided. This is similar to
Expand All @@ -289,7 +323,7 @@ retrieved from the selection if none are provided. This is similar to

### split

[src/index.js#L26-L26](src/index.js#L26-L26)
[src/index.js#L27-L27](src/index.js#L27-L27)

Split a Rich Text value in two at the given `startIndex` and `endIndex`, or
split at the given separator. This is similar to `String.prototype.split`.
Expand All @@ -307,7 +341,7 @@ Indices are retrieved from the selection if none are provided.

### toggleFormat

[src/index.js#L29-L29](src/index.js#L29-L29)
[src/index.js#L30-L30](src/index.js#L30-L30)

Toggles a format object to a Rich Text value at the current selection.

Expand All @@ -322,7 +356,7 @@ Toggles a format object to a Rich Text value at the current selection.

### toHTMLString

[src/index.js#L28-L28](src/index.js#L28-L28)
[src/index.js#L29-L29](src/index.js#L29-L29)

Create an HTML string from a Rich Text value. If a `multilineTag` is
provided, text separated by a line separator will be wrapped in it.
Expand All @@ -332,15 +366,14 @@ provided, text separated by a line separator will be wrapped in it.
- **$1** `Object`: Named argements.
- **$1.value** `Object`: Rich text value.
- **$1.multilineTag** `[string]`: Multiline tag.
- **$1.multilineWrapperTags** `[Array]`: Tags where lines can be found if nesting is possible.

**Returns**

`string`: HTML string.

### unregisterFormatType

[src/index.js#L31-L31](src/index.js#L31-L31)
[src/index.js#L32-L32](src/index.js#L32-L32)

Unregisters a format.

Expand Down
Loading

0 comments on commit 68c04f3

Please sign in to comment.