@wordpress/rich-text
Advanced tools
Comparing version 6.24.0 to 6.25.0
@@ -11,3 +11,3 @@ /** | ||
*/ | ||
import { collapseWhiteSpace, create } from '../create'; | ||
import { create, RichTextData } from '../create'; | ||
import { apply } from '../to-dom'; | ||
@@ -72,5 +72,14 @@ import { toHTMLString } from '../to-html-string'; | ||
_value.current = value; | ||
record.current = create({ | ||
html: preserveWhiteSpace ? value : collapseWhiteSpace(typeof value === 'string' ? value : '') | ||
}); | ||
record.current = value; | ||
if (!(value instanceof RichTextData)) { | ||
record.current = value ? RichTextData.fromHTMLString(value, { | ||
preserveWhiteSpace | ||
}) : RichTextData.empty(); | ||
} | ||
// To do: make rich text internally work with RichTextData. | ||
record.current = { | ||
text: record.current.text, | ||
formats: record.current.formats, | ||
replacements: record.current.replacements | ||
}; | ||
if (disableFormats) { | ||
@@ -112,8 +121,14 @@ record.current.formats = Array(value.length); | ||
} else { | ||
_value.current = toHTMLString({ | ||
value: __unstableBeforeSerialize ? { | ||
...newRecord, | ||
formats: __unstableBeforeSerialize(newRecord) | ||
} : newRecord | ||
}); | ||
const newFormats = __unstableBeforeSerialize ? __unstableBeforeSerialize(newRecord) : newRecord.formats; | ||
newRecord = { | ||
...newRecord, | ||
formats: newFormats | ||
}; | ||
if (typeof value === 'string') { | ||
_value.current = toHTMLString({ | ||
value: newRecord | ||
}); | ||
} else { | ||
_value.current = new RichTextData(newRecord); | ||
} | ||
} | ||
@@ -125,3 +140,3 @@ const { | ||
text | ||
} = newRecord; | ||
} = record.current; | ||
@@ -128,0 +143,0 @@ // Selection must be updated first, so it is recorded in history when |
@@ -13,2 +13,4 @@ /** | ||
import { OBJECT_REPLACEMENT_CHARACTER, ZWNBSP } from './special-characters'; | ||
import { toHTMLString } from './to-html-string'; | ||
import { getTextContent } from './get-text-content'; | ||
@@ -91,3 +93,90 @@ /** @typedef {import('./types').RichTextValue} RichTextValue */ | ||
// Ideally we use a private property. | ||
const RichTextInternalData = Symbol('RichTextInternalData'); | ||
/** | ||
* The RichTextData class is used to instantiate a wrapper around rich text | ||
* values, with methods that can be used to transform or manipulate the data. | ||
* | ||
* - Create an emtpy instance: `new RichTextData()`. | ||
* - Create one from an html string: `RichTextData.fromHTMLString( | ||
* '<em>hello</em>' )`. | ||
* - Create one from a wrapper HTMLElement: `RichTextData.fromHTMLElement( | ||
* document.querySelector( 'p' ) )`. | ||
* - Create one from plain text: `RichTextData.fromPlainText( '1\n2' )`. | ||
* - Create one from a rich text value: `new RichTextData( { text: '...', | ||
* formats: [ ... ] } )`. | ||
* | ||
* @todo Add methods to manipulate the data, such as applyFormat, slice etc. | ||
*/ | ||
export class RichTextData { | ||
static empty() { | ||
return new RichTextData(); | ||
} | ||
static fromPlainText(text) { | ||
return new RichTextData(create({ | ||
text | ||
})); | ||
} | ||
static fromHTMLString(html) { | ||
return new RichTextData(create({ | ||
html | ||
})); | ||
} | ||
static fromHTMLElement(htmlElement, options = {}) { | ||
const { | ||
preserveWhiteSpace = false | ||
} = options; | ||
const element = preserveWhiteSpace ? htmlElement : collapseWhiteSpace(htmlElement); | ||
const richTextData = new RichTextData(create({ | ||
element | ||
})); | ||
Object.defineProperty(richTextData, 'originalHTML', { | ||
value: htmlElement.innerHTML | ||
}); | ||
return richTextData; | ||
} | ||
constructor(init = createEmptyValue()) { | ||
// Setting text, formats, and replacements as enumerable properties | ||
// unfortunately visualises these in the e2e tests. As long as the class | ||
// instance doesn't have any enumerable properties, it will be | ||
// visualised as a string. | ||
Object.defineProperty(this, RichTextInternalData, { | ||
value: init | ||
}); | ||
} | ||
toPlainText() { | ||
return getTextContent(this[RichTextInternalData]); | ||
} | ||
// We could expose `toHTMLElement` at some point as well, but we'd only use | ||
// it internally. | ||
toHTMLString() { | ||
return this.originalHTML || toHTMLString({ | ||
value: this[RichTextInternalData] | ||
}); | ||
} | ||
valueOf() { | ||
return this.toHTMLString(); | ||
} | ||
toString() { | ||
return this.toHTMLString(); | ||
} | ||
toJSON() { | ||
return this.toHTMLString(); | ||
} | ||
get length() { | ||
return this.text.length; | ||
} | ||
get formats() { | ||
return this[RichTextInternalData].formats; | ||
} | ||
get replacements() { | ||
return this[RichTextInternalData].replacements; | ||
} | ||
get text() { | ||
return this[RichTextInternalData].text; | ||
} | ||
} | ||
/** | ||
* Create a RichText value from an `Element` tree (DOM), an HTML string or a | ||
@@ -124,3 +213,2 @@ * plain text string, with optionally a `Range` object to set the selection. If | ||
* @param {boolean} [$1.__unstableIsEditableTree] | ||
* | ||
* @return {RichTextValue} A rich text value. | ||
@@ -135,2 +223,9 @@ */ | ||
} = {}) { | ||
if (html instanceof RichTextData) { | ||
return { | ||
text: html.text, | ||
formats: html.formats, | ||
replacements: html.replacements | ||
}; | ||
} | ||
if (typeof text === 'string' && text.length > 0) { | ||
@@ -264,6 +359,30 @@ return { | ||
* | ||
* @param {string} string | ||
* @param {HTMLElement} element | ||
* @param {boolean} isRoot | ||
* | ||
* @return {HTMLElement} New element with collapsed whitespace. | ||
*/ | ||
export function collapseWhiteSpace(string) { | ||
return string.replace(/[\n\r\t]+/g, ' '); | ||
function collapseWhiteSpace(element, isRoot = true) { | ||
const clone = element.cloneNode(true); | ||
clone.normalize(); | ||
Array.from(clone.childNodes).forEach((node, i, nodes) => { | ||
if (node.nodeType === node.TEXT_NODE) { | ||
let newNodeValue = node.nodeValue; | ||
if (/[\n\t\r\f]/.test(newNodeValue)) { | ||
newNodeValue = newNodeValue.replace(/[\n\t\r\f]+/g, ' '); | ||
} | ||
if (newNodeValue.indexOf(' ') !== -1) { | ||
newNodeValue = newNodeValue.replace(/ {2,}/g, ' '); | ||
} | ||
if (i === 0 && newNodeValue.startsWith(' ')) { | ||
newNodeValue = newNodeValue.slice(1); | ||
} else if (isRoot && i === nodes.length - 1 && newNodeValue.endsWith(' ')) { | ||
newNodeValue = newNodeValue.slice(0, -1); | ||
} | ||
node.nodeValue = newNodeValue; | ||
} else if (node.nodeType === node.ELEMENT_NODE) { | ||
collapseWhiteSpace(node, false); | ||
} | ||
}); | ||
return clone; | ||
} | ||
@@ -270,0 +389,0 @@ |
export { store } from './store'; | ||
export { applyFormat } from './apply-format'; | ||
export { concat } from './concat'; | ||
export { create } from './create'; | ||
export { RichTextData, create } from './create'; | ||
export { getActiveFormat } from './get-active-format'; | ||
@@ -6,0 +6,0 @@ export { getActiveFormats } from './get-active-formats'; |
@@ -33,3 +33,2 @@ /** | ||
* @param {boolean} [$1.__unstableIsEditableTree] | ||
* | ||
* @return {RichTextValue} A rich text value. | ||
@@ -45,23 +44,43 @@ */ | ||
/** | ||
* Collapse any whitespace used for HTML formatting to one space character, | ||
* because it will also be displayed as such by the browser. | ||
* Removes reserved characters used by rich-text (zero width non breaking spaces added by `toTree` and object replacement characters). | ||
* | ||
* We need to strip it from the content because we use white-space: pre-wrap for | ||
* displaying editable rich text. Without using white-space: pre-wrap, the | ||
* browser will litter the content with non breaking spaces, among other issues. | ||
* See packages/rich-text/src/component/use-default-style.js. | ||
* | ||
* @see | ||
* https://developer.mozilla.org/en-US/docs/Web/CSS/white-space-collapse#collapsing_of_white_space | ||
* | ||
* @param {string} string | ||
*/ | ||
export function collapseWhiteSpace(string: string): string; | ||
export function removeReservedCharacters(string: string): string; | ||
/** | ||
* Removes reserved characters used by rich-text (zero width non breaking spaces added by `toTree` and object replacement characters). | ||
* The RichTextData class is used to instantiate a wrapper around rich text | ||
* values, with methods that can be used to transform or manipulate the data. | ||
* | ||
* @param {string} string | ||
* - Create an emtpy instance: `new RichTextData()`. | ||
* - Create one from an html string: `RichTextData.fromHTMLString( | ||
* '<em>hello</em>' )`. | ||
* - Create one from a wrapper HTMLElement: `RichTextData.fromHTMLElement( | ||
* document.querySelector( 'p' ) )`. | ||
* - Create one from plain text: `RichTextData.fromPlainText( '1\n2' )`. | ||
* - Create one from a rich text value: `new RichTextData( { text: '...', | ||
* formats: [ ... ] } )`. | ||
* | ||
* @todo Add methods to manipulate the data, such as applyFormat, slice etc. | ||
*/ | ||
export function removeReservedCharacters(string: string): string; | ||
export class RichTextData { | ||
static empty(): RichTextData; | ||
static fromPlainText(text: any): RichTextData; | ||
static fromHTMLString(html: any): RichTextData; | ||
static fromHTMLElement(htmlElement: any, options?: {}): RichTextData; | ||
constructor(init?: { | ||
formats: never[]; | ||
replacements: never[]; | ||
text: string; | ||
}); | ||
toPlainText(): string; | ||
toHTMLString(): any; | ||
valueOf(): any; | ||
toString(): any; | ||
toJSON(): any; | ||
get length(): any; | ||
get formats(): any; | ||
get replacements(): any; | ||
get text(): any; | ||
} | ||
export type RichTextValue = import('./types').RichTextValue; | ||
//# sourceMappingURL=create.d.ts.map |
export { store } from './store'; | ||
export { applyFormat } from './apply-format'; | ||
export { concat } from './concat'; | ||
export { create } from './create'; | ||
export { RichTextData, create } from './create'; | ||
export { getActiveFormat } from './get-active-format'; | ||
@@ -6,0 +6,0 @@ export { getActiveFormats } from './get-active-formats'; |
@@ -79,5 +79,14 @@ "use strict"; | ||
_value.current = value; | ||
record.current = (0, _create.create)({ | ||
html: preserveWhiteSpace ? value : (0, _create.collapseWhiteSpace)(typeof value === 'string' ? value : '') | ||
}); | ||
record.current = value; | ||
if (!(value instanceof _create.RichTextData)) { | ||
record.current = value ? _create.RichTextData.fromHTMLString(value, { | ||
preserveWhiteSpace | ||
}) : _create.RichTextData.empty(); | ||
} | ||
// To do: make rich text internally work with RichTextData. | ||
record.current = { | ||
text: record.current.text, | ||
formats: record.current.formats, | ||
replacements: record.current.replacements | ||
}; | ||
if (disableFormats) { | ||
@@ -119,8 +128,14 @@ record.current.formats = Array(value.length); | ||
} else { | ||
_value.current = (0, _toHtmlString.toHTMLString)({ | ||
value: __unstableBeforeSerialize ? { | ||
...newRecord, | ||
formats: __unstableBeforeSerialize(newRecord) | ||
} : newRecord | ||
}); | ||
const newFormats = __unstableBeforeSerialize ? __unstableBeforeSerialize(newRecord) : newRecord.formats; | ||
newRecord = { | ||
...newRecord, | ||
formats: newFormats | ||
}; | ||
if (typeof value === 'string') { | ||
_value.current = (0, _toHtmlString.toHTMLString)({ | ||
value: newRecord | ||
}); | ||
} else { | ||
_value.current = new _create.RichTextData(newRecord); | ||
} | ||
} | ||
@@ -132,3 +147,3 @@ const { | ||
text | ||
} = newRecord; | ||
} = record.current; | ||
@@ -135,0 +150,0 @@ // Selection must be updated first, so it is recorded in history when |
@@ -6,3 +6,3 @@ "use strict"; | ||
}); | ||
exports.collapseWhiteSpace = collapseWhiteSpace; | ||
exports.RichTextData = void 0; | ||
exports.create = create; | ||
@@ -15,2 +15,4 @@ exports.removeReservedCharacters = removeReservedCharacters; | ||
var _specialCharacters = require("./special-characters"); | ||
var _toHtmlString = require("./to-html-string"); | ||
var _getTextContent = require("./get-text-content"); | ||
/** | ||
@@ -100,3 +102,90 @@ * WordPress dependencies | ||
// Ideally we use a private property. | ||
const RichTextInternalData = Symbol('RichTextInternalData'); | ||
/** | ||
* The RichTextData class is used to instantiate a wrapper around rich text | ||
* values, with methods that can be used to transform or manipulate the data. | ||
* | ||
* - Create an emtpy instance: `new RichTextData()`. | ||
* - Create one from an html string: `RichTextData.fromHTMLString( | ||
* '<em>hello</em>' )`. | ||
* - Create one from a wrapper HTMLElement: `RichTextData.fromHTMLElement( | ||
* document.querySelector( 'p' ) )`. | ||
* - Create one from plain text: `RichTextData.fromPlainText( '1\n2' )`. | ||
* - Create one from a rich text value: `new RichTextData( { text: '...', | ||
* formats: [ ... ] } )`. | ||
* | ||
* @todo Add methods to manipulate the data, such as applyFormat, slice etc. | ||
*/ | ||
class RichTextData { | ||
static empty() { | ||
return new RichTextData(); | ||
} | ||
static fromPlainText(text) { | ||
return new RichTextData(create({ | ||
text | ||
})); | ||
} | ||
static fromHTMLString(html) { | ||
return new RichTextData(create({ | ||
html | ||
})); | ||
} | ||
static fromHTMLElement(htmlElement, options = {}) { | ||
const { | ||
preserveWhiteSpace = false | ||
} = options; | ||
const element = preserveWhiteSpace ? htmlElement : collapseWhiteSpace(htmlElement); | ||
const richTextData = new RichTextData(create({ | ||
element | ||
})); | ||
Object.defineProperty(richTextData, 'originalHTML', { | ||
value: htmlElement.innerHTML | ||
}); | ||
return richTextData; | ||
} | ||
constructor(init = createEmptyValue()) { | ||
// Setting text, formats, and replacements as enumerable properties | ||
// unfortunately visualises these in the e2e tests. As long as the class | ||
// instance doesn't have any enumerable properties, it will be | ||
// visualised as a string. | ||
Object.defineProperty(this, RichTextInternalData, { | ||
value: init | ||
}); | ||
} | ||
toPlainText() { | ||
return (0, _getTextContent.getTextContent)(this[RichTextInternalData]); | ||
} | ||
// We could expose `toHTMLElement` at some point as well, but we'd only use | ||
// it internally. | ||
toHTMLString() { | ||
return this.originalHTML || (0, _toHtmlString.toHTMLString)({ | ||
value: this[RichTextInternalData] | ||
}); | ||
} | ||
valueOf() { | ||
return this.toHTMLString(); | ||
} | ||
toString() { | ||
return this.toHTMLString(); | ||
} | ||
toJSON() { | ||
return this.toHTMLString(); | ||
} | ||
get length() { | ||
return this.text.length; | ||
} | ||
get formats() { | ||
return this[RichTextInternalData].formats; | ||
} | ||
get replacements() { | ||
return this[RichTextInternalData].replacements; | ||
} | ||
get text() { | ||
return this[RichTextInternalData].text; | ||
} | ||
} | ||
/** | ||
* Create a RichText value from an `Element` tree (DOM), an HTML string or a | ||
@@ -133,5 +222,5 @@ * plain text string, with optionally a `Range` object to set the selection. If | ||
* @param {boolean} [$1.__unstableIsEditableTree] | ||
* | ||
* @return {RichTextValue} A rich text value. | ||
*/ | ||
exports.RichTextData = RichTextData; | ||
function create({ | ||
@@ -144,2 +233,9 @@ element, | ||
} = {}) { | ||
if (html instanceof RichTextData) { | ||
return { | ||
text: html.text, | ||
formats: html.formats, | ||
replacements: html.replacements | ||
}; | ||
} | ||
if (typeof text === 'string' && text.length > 0) { | ||
@@ -273,6 +369,30 @@ return { | ||
* | ||
* @param {string} string | ||
* @param {HTMLElement} element | ||
* @param {boolean} isRoot | ||
* | ||
* @return {HTMLElement} New element with collapsed whitespace. | ||
*/ | ||
function collapseWhiteSpace(string) { | ||
return string.replace(/[\n\r\t]+/g, ' '); | ||
function collapseWhiteSpace(element, isRoot = true) { | ||
const clone = element.cloneNode(true); | ||
clone.normalize(); | ||
Array.from(clone.childNodes).forEach((node, i, nodes) => { | ||
if (node.nodeType === node.TEXT_NODE) { | ||
let newNodeValue = node.nodeValue; | ||
if (/[\n\t\r\f]/.test(newNodeValue)) { | ||
newNodeValue = newNodeValue.replace(/[\n\t\r\f]+/g, ' '); | ||
} | ||
if (newNodeValue.indexOf(' ') !== -1) { | ||
newNodeValue = newNodeValue.replace(/ {2,}/g, ' '); | ||
} | ||
if (i === 0 && newNodeValue.startsWith(' ')) { | ||
newNodeValue = newNodeValue.slice(1); | ||
} else if (isRoot && i === nodes.length - 1 && newNodeValue.endsWith(' ')) { | ||
newNodeValue = newNodeValue.slice(0, -1); | ||
} | ||
node.nodeValue = newNodeValue; | ||
} else if (node.nodeType === node.ELEMENT_NODE) { | ||
collapseWhiteSpace(node, false); | ||
} | ||
}); | ||
return clone; | ||
} | ||
@@ -279,0 +399,0 @@ |
@@ -6,2 +6,8 @@ "use strict"; | ||
}); | ||
Object.defineProperty(exports, "RichTextData", { | ||
enumerable: true, | ||
get: function () { | ||
return _create.RichTextData; | ||
} | ||
}); | ||
Object.defineProperty(exports, "__experimentalRichText", { | ||
@@ -8,0 +14,0 @@ enumerable: true, |
@@ -5,2 +5,4 @@ <!-- Learn how to maintain this file at https://github.com/WordPress/gutenberg/tree/HEAD/packages#maintaining-changelogs. --> | ||
## 6.25.0 (2023-12-13) | ||
## 6.24.0 (2023-11-29) | ||
@@ -7,0 +9,0 @@ |
{ | ||
"name": "@wordpress/rich-text", | ||
"version": "6.24.0", | ||
"version": "6.25.0", | ||
"description": "Rich text value and manipulation API.", | ||
@@ -34,10 +34,10 @@ "author": "The WordPress Contributors", | ||
"@babel/runtime": "^7.16.0", | ||
"@wordpress/a11y": "^3.47.0", | ||
"@wordpress/compose": "^6.24.0", | ||
"@wordpress/data": "^9.17.0", | ||
"@wordpress/deprecated": "^3.47.0", | ||
"@wordpress/element": "^5.24.0", | ||
"@wordpress/escape-html": "^2.47.0", | ||
"@wordpress/i18n": "^4.47.0", | ||
"@wordpress/keycodes": "^3.47.0", | ||
"@wordpress/a11y": "^3.48.0", | ||
"@wordpress/compose": "^6.25.0", | ||
"@wordpress/data": "^9.18.0", | ||
"@wordpress/deprecated": "^3.48.0", | ||
"@wordpress/element": "^5.25.0", | ||
"@wordpress/escape-html": "^2.48.0", | ||
"@wordpress/i18n": "^4.48.0", | ||
"@wordpress/keycodes": "^3.48.0", | ||
"memize": "^2.1.0", | ||
@@ -52,3 +52,3 @@ "rememo": "^4.0.2" | ||
}, | ||
"gitHead": "d98dff8ea96f29cfea045bf964269f46f040d539" | ||
"gitHead": "fcf61b4beff747222c2c81d09d757ca1a0abd925" | ||
} |
@@ -358,2 +358,15 @@ # Rich Text | ||
### RichTextData | ||
The RichTextData class is used to instantiate a wrapper around rich text values, with methods that can be used to transform or manipulate the data. | ||
- Create an emtpy instance: `new RichTextData()`. | ||
- Create one from an html string: `RichTextData.fromHTMLString( | ||
'<em>hello</em>' )`. | ||
- Create one from a wrapper HTMLElement: `RichTextData.fromHTMLElement( | ||
document.querySelector( 'p' ) )`. | ||
- Create one from plain text: `RichTextData.fromPlainText( '1\n2' )`. | ||
- Create one from a rich text value: `new RichTextData( { text: '...', | ||
formats: [ ... ] } )`. | ||
### RichTextValue | ||
@@ -360,0 +373,0 @@ |
@@ -11,3 +11,3 @@ /** | ||
*/ | ||
import { collapseWhiteSpace, create } from '../create'; | ||
import { create, RichTextData } from '../create'; | ||
import { apply } from '../to-dom'; | ||
@@ -74,7 +74,14 @@ import { toHTMLString } from '../to-html-string'; | ||
_value.current = value; | ||
record.current = create( { | ||
html: preserveWhiteSpace | ||
? value | ||
: collapseWhiteSpace( typeof value === 'string' ? value : '' ), | ||
} ); | ||
record.current = value; | ||
if ( ! ( value instanceof RichTextData ) ) { | ||
record.current = value | ||
? RichTextData.fromHTMLString( value, { preserveWhiteSpace } ) | ||
: RichTextData.empty(); | ||
} | ||
// To do: make rich text internally work with RichTextData. | ||
record.current = { | ||
text: record.current.text, | ||
formats: record.current.formats, | ||
replacements: record.current.replacements, | ||
}; | ||
if ( disableFormats ) { | ||
@@ -122,13 +129,14 @@ record.current.formats = Array( value.length ); | ||
} else { | ||
_value.current = toHTMLString( { | ||
value: __unstableBeforeSerialize | ||
? { | ||
...newRecord, | ||
formats: __unstableBeforeSerialize( newRecord ), | ||
} | ||
: newRecord, | ||
} ); | ||
const newFormats = __unstableBeforeSerialize | ||
? __unstableBeforeSerialize( newRecord ) | ||
: newRecord.formats; | ||
newRecord = { ...newRecord, formats: newFormats }; | ||
if ( typeof value === 'string' ) { | ||
_value.current = toHTMLString( { value: newRecord } ); | ||
} else { | ||
_value.current = new RichTextData( newRecord ); | ||
} | ||
} | ||
const { start, end, formats, text } = newRecord; | ||
const { start, end, formats, text } = record.current; | ||
@@ -135,0 +143,0 @@ // Selection must be updated first, so it is recorded in history when |
@@ -13,2 +13,4 @@ /** | ||
import { OBJECT_REPLACEMENT_CHARACTER, ZWNBSP } from './special-characters'; | ||
import { toHTMLString } from './to-html-string'; | ||
import { getTextContent } from './get-text-content'; | ||
@@ -100,3 +102,83 @@ /** @typedef {import('./types').RichTextValue} RichTextValue */ | ||
// Ideally we use a private property. | ||
const RichTextInternalData = Symbol( 'RichTextInternalData' ); | ||
/** | ||
* The RichTextData class is used to instantiate a wrapper around rich text | ||
* values, with methods that can be used to transform or manipulate the data. | ||
* | ||
* - Create an emtpy instance: `new RichTextData()`. | ||
* - Create one from an html string: `RichTextData.fromHTMLString( | ||
* '<em>hello</em>' )`. | ||
* - Create one from a wrapper HTMLElement: `RichTextData.fromHTMLElement( | ||
* document.querySelector( 'p' ) )`. | ||
* - Create one from plain text: `RichTextData.fromPlainText( '1\n2' )`. | ||
* - Create one from a rich text value: `new RichTextData( { text: '...', | ||
* formats: [ ... ] } )`. | ||
* | ||
* @todo Add methods to manipulate the data, such as applyFormat, slice etc. | ||
*/ | ||
export class RichTextData { | ||
static empty() { | ||
return new RichTextData(); | ||
} | ||
static fromPlainText( text ) { | ||
return new RichTextData( create( { text } ) ); | ||
} | ||
static fromHTMLString( html ) { | ||
return new RichTextData( create( { html } ) ); | ||
} | ||
static fromHTMLElement( htmlElement, options = {} ) { | ||
const { preserveWhiteSpace = false } = options; | ||
const element = preserveWhiteSpace | ||
? htmlElement | ||
: collapseWhiteSpace( htmlElement ); | ||
const richTextData = new RichTextData( create( { element } ) ); | ||
Object.defineProperty( richTextData, 'originalHTML', { | ||
value: htmlElement.innerHTML, | ||
} ); | ||
return richTextData; | ||
} | ||
constructor( init = createEmptyValue() ) { | ||
// Setting text, formats, and replacements as enumerable properties | ||
// unfortunately visualises these in the e2e tests. As long as the class | ||
// instance doesn't have any enumerable properties, it will be | ||
// visualised as a string. | ||
Object.defineProperty( this, RichTextInternalData, { value: init } ); | ||
} | ||
toPlainText() { | ||
return getTextContent( this[ RichTextInternalData ] ); | ||
} | ||
// We could expose `toHTMLElement` at some point as well, but we'd only use | ||
// it internally. | ||
toHTMLString() { | ||
return ( | ||
this.originalHTML || | ||
toHTMLString( { value: this[ RichTextInternalData ] } ) | ||
); | ||
} | ||
valueOf() { | ||
return this.toHTMLString(); | ||
} | ||
toString() { | ||
return this.toHTMLString(); | ||
} | ||
toJSON() { | ||
return this.toHTMLString(); | ||
} | ||
get length() { | ||
return this.text.length; | ||
} | ||
get formats() { | ||
return this[ RichTextInternalData ].formats; | ||
} | ||
get replacements() { | ||
return this[ RichTextInternalData ].replacements; | ||
} | ||
get text() { | ||
return this[ RichTextInternalData ].text; | ||
} | ||
} | ||
/** | ||
* Create a RichText value from an `Element` tree (DOM), an HTML string or a | ||
@@ -133,3 +215,2 @@ * plain text string, with optionally a `Range` object to set the selection. If | ||
* @param {boolean} [$1.__unstableIsEditableTree] | ||
* | ||
* @return {RichTextValue} A rich text value. | ||
@@ -144,2 +225,10 @@ */ | ||
} = {} ) { | ||
if ( html instanceof RichTextData ) { | ||
return { | ||
text: html.text, | ||
formats: html.formats, | ||
replacements: html.replacements, | ||
}; | ||
} | ||
if ( typeof text === 'string' && text.length > 0 ) { | ||
@@ -275,6 +364,38 @@ return { | ||
* | ||
* @param {string} string | ||
* @param {HTMLElement} element | ||
* @param {boolean} isRoot | ||
* | ||
* @return {HTMLElement} New element with collapsed whitespace. | ||
*/ | ||
export function collapseWhiteSpace( string ) { | ||
return string.replace( /[\n\r\t]+/g, ' ' ); | ||
function collapseWhiteSpace( element, isRoot = true ) { | ||
const clone = element.cloneNode( true ); | ||
clone.normalize(); | ||
Array.from( clone.childNodes ).forEach( ( node, i, nodes ) => { | ||
if ( node.nodeType === node.TEXT_NODE ) { | ||
let newNodeValue = node.nodeValue; | ||
if ( /[\n\t\r\f]/.test( newNodeValue ) ) { | ||
newNodeValue = newNodeValue.replace( /[\n\t\r\f]+/g, ' ' ); | ||
} | ||
if ( newNodeValue.indexOf( ' ' ) !== -1 ) { | ||
newNodeValue = newNodeValue.replace( / {2,}/g, ' ' ); | ||
} | ||
if ( i === 0 && newNodeValue.startsWith( ' ' ) ) { | ||
newNodeValue = newNodeValue.slice( 1 ); | ||
} else if ( | ||
isRoot && | ||
i === nodes.length - 1 && | ||
newNodeValue.endsWith( ' ' ) | ||
) { | ||
newNodeValue = newNodeValue.slice( 0, -1 ); | ||
} | ||
node.nodeValue = newNodeValue; | ||
} else if ( node.nodeType === node.ELEMENT_NODE ) { | ||
collapseWhiteSpace( node, false ); | ||
} | ||
} ); | ||
return clone; | ||
} | ||
@@ -281,0 +402,0 @@ |
export { store } from './store'; | ||
export { applyFormat } from './apply-format'; | ||
export { concat } from './concat'; | ||
export { create } from './create'; | ||
export { RichTextData, create } from './create'; | ||
export { getActiveFormat } from './get-active-format'; | ||
@@ -6,0 +6,0 @@ export { getActiveFormats } from './get-active-formats'; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1010648
14702
490
Updated@wordpress/a11y@^3.48.0
Updated@wordpress/compose@^6.25.0
Updated@wordpress/data@^9.18.0
Updated@wordpress/element@^5.25.0
Updated@wordpress/i18n@^4.48.0
Updated@wordpress/keycodes@^3.48.0