From 7e0c850fb25a34182c54f27734641b9194e067a2 Mon Sep 17 00:00:00 2001 From: iseulde Date: Sat, 13 Oct 2018 17:39:15 +0200 Subject: [PATCH] WIP: create format lib --- docs/manifest.json | 6 + lib/client-assets.php | 28 +++- package-lock.json | 19 ++- package.json | 1 + packages/edit-post/package.json | 1 + packages/edit-post/src/index.js | 2 + .../rich-text/format-controls/index.js | 15 -- .../src/components/rich-text/format-edit.js | 10 +- .../editor/src/components/rich-text/index.js | 26 ++++ packages/editor/src/style.scss | 1 - packages/editor/src/utils/index.js | 1 + packages/format-library/.npmrc | 1 + packages/format-library/README.md | 23 +++ packages/format-library/package.json | 36 +++++ .../src}/bold/index.js | 7 +- .../src}/code/index.js | 8 +- .../src}/image/index.js | 14 +- packages/format-library/src/index.js | 25 ++++ .../src}/italic/index.js | 7 +- .../src}/link/index.js | 12 +- .../src}/link/inline.js | 4 +- .../src}/link/positioned-at-selection.js | 0 .../src}/link/style.scss | 0 .../src}/strikethrough/index.js | 7 +- packages/format-library/src/style.scss | 1 + packages/rich-text/package.json | 4 +- .../rich-text/src/deregister-format-type.js | 27 ++++ packages/rich-text/src/get-format-type.js | 15 ++ packages/rich-text/src/get-format-types.js | 13 ++ packages/rich-text/src/index.js | 5 + .../rich-text/src/register-format-type.js | 137 ++++++++++++++++++ packages/rich-text/src/store/actions.js | 33 +++++ packages/rich-text/src/store/index.js | 13 ++ packages/rich-text/src/store/reducer.js | 33 +++++ packages/rich-text/src/store/selectors.js | 30 ++++ packages/rich-text/src/store/test/reducer.js | 47 ++++++ webpack.config.js | 1 + 37 files changed, 562 insertions(+), 51 deletions(-) delete mode 100644 packages/editor/src/components/rich-text/format-controls/index.js create mode 100644 packages/format-library/.npmrc create mode 100644 packages/format-library/README.md create mode 100644 packages/format-library/package.json rename packages/{editor/src/components/rich-text/format-controls => format-library/src}/bold/index.js (90%) rename packages/{editor/src/components/rich-text/format-controls => format-library/src}/code/index.js (79%) rename packages/{editor/src/components/rich-text/format-controls => format-library/src}/image/index.js (90%) create mode 100644 packages/format-library/src/index.js rename packages/{editor/src/components/rich-text/format-controls => format-library/src}/italic/index.js (90%) rename packages/{editor/src/components/rich-text/format-controls => format-library/src}/link/index.js (95%) rename packages/{editor/src/components/rich-text/format-controls => format-library/src}/link/inline.js (97%) rename packages/{editor/src/components/rich-text/format-controls => format-library/src}/link/positioned-at-selection.js (100%) rename packages/{editor/src/components/rich-text/format-controls => format-library/src}/link/style.scss (100%) rename packages/{editor/src/components/rich-text/format-controls => format-library/src}/strikethrough/index.js (89%) create mode 100644 packages/format-library/src/style.scss create mode 100644 packages/rich-text/src/deregister-format-type.js create mode 100644 packages/rich-text/src/get-format-type.js create mode 100644 packages/rich-text/src/get-format-types.js create mode 100644 packages/rich-text/src/register-format-type.js create mode 100644 packages/rich-text/src/store/actions.js create mode 100644 packages/rich-text/src/store/index.js create mode 100644 packages/rich-text/src/store/reducer.js create mode 100644 packages/rich-text/src/store/selectors.js create mode 100644 packages/rich-text/src/store/test/reducer.js diff --git a/docs/manifest.json b/docs/manifest.json index 95de517d25076a..b8daea5f7bbc6c 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -401,6 +401,12 @@ "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/escape-html/README.md", "parent": "packages" }, + { + "title": "@wordpress/format-library", + "slug": "packages-format-library", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/format-library/README.md", + "parent": "packages" + }, { "title": "@wordpress/hooks", "slug": "packages-hooks", diff --git a/lib/client-assets.php b/lib/client-assets.php index d48eaa69ac392d..dd993b232eaee6 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -463,6 +463,23 @@ function gutenberg_register_scripts_and_styles() { filemtime( gutenberg_dir_path() . 'build/block-library/index.js' ), true ); + wp_register_script( + 'wp-format-library', + gutenberg_url( 'build/format-library/index.js' ), + array( + 'wp-components', + 'wp-dom', + 'wp-editor', + 'wp-element', + 'wp-i18n', + 'wp-keycodes', + 'wp-polyfill', + 'wp-rich-text', + 'wp-url', + ), + filemtime( gutenberg_dir_path() . 'build/format-library/index.js' ), + true + ); wp_register_script( 'wp-nux', gutenberg_url( 'build/nux/index.js' ), @@ -647,6 +664,7 @@ function gutenberg_register_scripts_and_styles() { 'wp-editor', 'wp-element', 'wp-embed', + 'wp-format-library', 'wp-i18n', 'wp-keycodes', 'wp-nux', @@ -711,7 +729,7 @@ function gutenberg_register_scripts_and_styles() { wp_register_style( 'wp-edit-post', gutenberg_url( 'build/edit-post/style.css' ), - array( 'wp-components', 'wp-editor', 'wp-edit-blocks', 'wp-block-library', 'wp-nux' ), + array( 'wp-components', 'wp-editor', 'wp-edit-blocks', 'wp-block-library', 'wp-format-library', 'wp-nux' ), filemtime( gutenberg_dir_path() . 'build/edit-post/style.css' ) ); wp_style_add_data( 'wp-edit-post', 'rtl', 'replace' ); @@ -732,6 +750,14 @@ function gutenberg_register_scripts_and_styles() { ); wp_style_add_data( 'wp-block-library', 'rtl', 'replace' ); + wp_register_style( + 'wp-format-library', + gutenberg_url( 'build/format-library/style.css' ), + array(), + filemtime( gutenberg_dir_path() . 'build/format-library/style.css' ) + ); + wp_style_add_data( 'wp-format-library', 'rtl', 'replace' ); + wp_register_style( 'wp-edit-blocks', gutenberg_url( 'build/block-library/editor.css' ), diff --git a/package-lock.json b/package-lock.json index dcaccadef29655..0e9883c403730c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2235,6 +2235,7 @@ "@wordpress/deprecated": "file:packages/deprecated", "@wordpress/editor": "file:packages/editor", "@wordpress/element": "file:packages/element", + "@wordpress/format-library": "file:packages/format-library", "@wordpress/hooks": "file:packages/hooks", "@wordpress/i18n": "file:packages/i18n", "@wordpress/keycodes": "file:packages/keycodes", @@ -2306,6 +2307,20 @@ "@babel/runtime": "^7.0.0" } }, + "@wordpress/format-library": { + "version": "file:packages/format-library", + "requires": { + "@babel/runtime": "^7.0.0", + "@wordpress/components": "file:packages/components", + "@wordpress/dom": "file:packages/dom", + "@wordpress/editor": "file:packages/editor", + "@wordpress/element": "file:packages/element", + "@wordpress/i18n": "file:packages/i18n", + "@wordpress/keycodes": "file:packages/keycodes", + "@wordpress/rich-text": "file:packages/rich-text", + "@wordpress/url": "file:packages/url" + } + }, "@wordpress/hooks": { "version": "file:packages/hooks", "requires": { @@ -2417,8 +2432,10 @@ "version": "file:packages/rich-text", "requires": { "@babel/runtime": "^7.0.0", + "@wordpress/data": "file:packages/data", "@wordpress/escape-html": "file:packages/escape-html", - "lodash": "^4.17.10" + "lodash": "^4.17.10", + "rememo": "^3.0.0" } }, "@wordpress/scripts": { diff --git a/package.json b/package.json index 7a31c837258466..1e6ddf58845846 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@wordpress/editor": "file:packages/editor", "@wordpress/element": "file:packages/element", "@wordpress/escape-html": "file:packages/escape-html", + "@wordpress/format-library": "file:packages/format-library", "@wordpress/hooks": "file:packages/hooks", "@wordpress/html-entities": "file:packages/html-entities", "@wordpress/i18n": "file:packages/i18n", diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index a91b0a76185fd1..a9a0be10fa5ae1 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -31,6 +31,7 @@ "@wordpress/deprecated": "file:../deprecated", "@wordpress/editor": "file:../editor", "@wordpress/element": "file:../element", + "@wordpress/format-library": "file:../format-library", "@wordpress/hooks": "file:../hooks", "@wordpress/i18n": "file:../i18n", "@wordpress/keycodes": "file:../keycodes", diff --git a/packages/edit-post/src/index.js b/packages/edit-post/src/index.js index 9797f877b6c8fa..d1a8194e1821e7 100644 --- a/packages/edit-post/src/index.js +++ b/packages/edit-post/src/index.js @@ -6,6 +6,7 @@ import '@wordpress/editor'; import '@wordpress/nux'; import '@wordpress/viewport'; import { registerCoreBlocks } from '@wordpress/block-library'; +import { registerCoreFormats } from '@wordpress/format-library'; import { render, unmountComponentAtNode } from '@wordpress/element'; import { dispatch } from '@wordpress/data'; import deprecated from '@wordpress/deprecated'; @@ -59,6 +60,7 @@ export function initializeEditor( id, postType, postId, settings, overridePost ) const reboot = reinitializeEditor.bind( null, postType, postId, target, settings, overridePost ); registerCoreBlocks(); + registerCoreFormats(); dispatch( 'core/nux' ).triggerGuide( [ 'core/editor.inserter', diff --git a/packages/editor/src/components/rich-text/format-controls/index.js b/packages/editor/src/components/rich-text/format-controls/index.js deleted file mode 100644 index 390a2cd5ce2893..00000000000000 --- a/packages/editor/src/components/rich-text/format-controls/index.js +++ /dev/null @@ -1,15 +0,0 @@ -import { bold } from './bold'; -import { code } from './code'; -import { image } from './image'; -import { italic } from './italic'; -import { link } from './link'; -import { strikethrough } from './strikethrough'; - -export const formatControls = [ - bold, - code, - image, - italic, - link, - strikethrough, -]; diff --git a/packages/editor/src/components/rich-text/format-edit.js b/packages/editor/src/components/rich-text/format-edit.js index 983d644b4a6056..dc261b7c798e14 100644 --- a/packages/editor/src/components/rich-text/format-edit.js +++ b/packages/editor/src/components/rich-text/format-edit.js @@ -4,20 +4,16 @@ import { Fragment } from '@wordpress/element'; import { getActiveFormat, + getFormatTypes, } from '@wordpress/rich-text'; -/** - * Internal dependencies - */ -import { formatControls } from './format-controls'; - const FormatEdit = ( { value, onChange } ) => { return ( - { formatControls.map( ( { selector, edit: Edit }, i ) => + { getFormatTypes().map( ( { format, edit: Edit }, i ) => Edit && diff --git a/packages/editor/src/components/rich-text/index.js b/packages/editor/src/components/rich-text/index.js index d585802a159a94..8d344a6c0e2111 100644 --- a/packages/editor/src/components/rich-text/index.js +++ b/packages/editor/src/components/rich-text/index.js @@ -10,6 +10,8 @@ import { noop, isEqual, omit, + // mapValues, + // mapKeys, } from 'lodash'; import memize from 'memize'; @@ -72,6 +74,30 @@ const { Node, getSelection } = window; */ const TINYMCE_ZWSP = '\uFEFF'; +// function toFormat( { type, attributes } ) { +// const { format, attributes: attributesDefinition } = +// find( formatControls, ( { match } ) => type === match.tagName ); + +// return { +// type: format, +// attributes: mapValues( attributesDefinition, ( name ) => +// attributes ? attributes[ name ] : undefined +// ), +// }; +// } + +// function fromFormat( { type, attributes } ) { +// const { match, attributes: attributesDefinition } = +// find( formatControls, ( { format } ) => type === format ); + +// return { +// type: match.tagName, +// attributes: mapKeys( attributes, ( value, key ) => +// attributesDefinition[ key ] +// ), +// }; +// } + export class RichText extends Component { constructor( { value, onReplace, multiline } ) { super( ...arguments ); diff --git a/packages/editor/src/style.scss b/packages/editor/src/style.scss index 7e29865d8c1334..5bde3ed2abe620 100644 --- a/packages/editor/src/style.scss +++ b/packages/editor/src/style.scss @@ -39,7 +39,6 @@ @import "./components/post-title/style.scss"; @import "./components/post-trash/style.scss"; @import "./components/rich-text/core-tokens/image/style.scss"; -@import "./components/rich-text/format-controls/link/style.scss"; @import "./components/rich-text/format-toolbar/style.scss"; @import "./components/rich-text/style.scss"; @import "./components/rich-text/tokens/ui/style.scss"; diff --git a/packages/editor/src/utils/index.js b/packages/editor/src/utils/index.js index 3a49f66b86c902..a1ba6322bc96ef 100644 --- a/packages/editor/src/utils/index.js +++ b/packages/editor/src/utils/index.js @@ -4,3 +4,4 @@ import mediaUpload from './media-upload'; export { mediaUpload }; +export { filterURLForDisplay } from './url'; diff --git a/packages/format-library/.npmrc b/packages/format-library/.npmrc new file mode 100644 index 00000000000000..43c97e719a5a82 --- /dev/null +++ b/packages/format-library/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/packages/format-library/README.md b/packages/format-library/README.md new file mode 100644 index 00000000000000..aa89390c4d99db --- /dev/null +++ b/packages/format-library/README.md @@ -0,0 +1,23 @@ +# Block library + +Format library for the WordPress editor. + +## Installation + +Install the module + +```bash +npm install @wordpress/format-library --save +``` + +_This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. Learn more about it in [Babel docs](https://babeljs.io/docs/en/next/caveats)._ + +## Usage + +```js +import { registerCoreFormats } from '@wordpress/format-library'; + +registerCoreFormats(); +``` + +

Code is Poetry.

diff --git a/packages/format-library/package.json b/packages/format-library/package.json new file mode 100644 index 00000000000000..deb690173e6e33 --- /dev/null +++ b/packages/format-library/package.json @@ -0,0 +1,36 @@ +{ + "name": "@wordpress/format-library", + "version": "2.1.0", + "description": "Format library for the WordPress editor.", + "author": "The WordPress Contributors", + "license": "GPL-2.0-or-later", + "keywords": [ + "wordpress", + "formats" + ], + "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/format-library/README.md", + "repository": { + "type": "git", + "url": "https://github.com/WordPress/gutenberg.git" + }, + "bugs": { + "url": "https://github.com/WordPress/gutenberg/issues" + }, + "main": "build/index.js", + "module": "build-module/index.js", + "react-native": "src/index", + "dependencies": { + "@babel/runtime": "^7.0.0", + "@wordpress/components": "file:../components", + "@wordpress/dom": "file:../dom", + "@wordpress/editor": "file:../editor", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/rich-text": "file:../rich-text", + "@wordpress/url": "file:../url" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/editor/src/components/rich-text/format-controls/bold/index.js b/packages/format-library/src/bold/index.js similarity index 90% rename from packages/editor/src/components/rich-text/format-controls/bold/index.js rename to packages/format-library/src/bold/index.js index daec7066ca726d..e577ce288fe2fb 100644 --- a/packages/editor/src/components/rich-text/format-controls/bold/index.js +++ b/packages/format-library/src/bold/index.js @@ -9,8 +9,11 @@ import { toggleFormat } from '@wordpress/rich-text'; const Shortcut = () => null; export const bold = { - format: 'bold', - selector: 'strong', + name: 'core/bold', + title: __( 'Bold' ), + match: { + tagName: 'strong', + }, edit( { isActive, value, onChange } ) { const onToggle = () => onChange( toggleFormat( value, { type: 'strong' } ) ); diff --git a/packages/editor/src/components/rich-text/format-controls/code/index.js b/packages/format-library/src/code/index.js similarity index 79% rename from packages/editor/src/components/rich-text/format-controls/code/index.js rename to packages/format-library/src/code/index.js index aac254f900bd68..96567da7fb85f2 100644 --- a/packages/editor/src/components/rich-text/format-controls/code/index.js +++ b/packages/format-library/src/code/index.js @@ -1,14 +1,18 @@ /** * WordPress dependencies */ +import { __ } from '@wordpress/i18n'; import { Fragment } from '@wordpress/element'; import { toggleFormat } from '@wordpress/rich-text'; const Shortcut = () => null; export const code = { - format: 'code', - selector: 'code', + name: 'core/code', + title: __( 'Code' ), + match: { + tagName: 'code', + }, edit( { value, onChange } ) { const onToggle = () => onChange( toggleFormat( value, { type: 'code' } ) ); diff --git a/packages/editor/src/components/rich-text/format-controls/image/index.js b/packages/format-library/src/image/index.js similarity index 90% rename from packages/editor/src/components/rich-text/format-controls/image/index.js rename to packages/format-library/src/image/index.js index 6d555a41bead69..4957df10066715 100644 --- a/packages/editor/src/components/rich-text/format-controls/image/index.js +++ b/packages/format-library/src/image/index.js @@ -5,18 +5,16 @@ import { __ } from '@wordpress/i18n'; import { Fragment, Component } from '@wordpress/element'; import { Fill } from '@wordpress/components'; import { insertObject } from '@wordpress/rich-text'; - -/** - * Internal dependencies - */ -import InserterListItem from '../../../inserter-list-item'; -import MediaUpload from '../../../media-upload'; +import { InserterListItem, MediaUpload } from '@wordpress/editor'; const ALLOWED_MEDIA_TYPES = [ 'image' ]; export const image = { - format: 'image', - selector: 'img', + name: 'core/image', + title: __( 'Image' ), + match: { + tagName: 'img', + }, edit: class ImageEdit extends Component { constructor() { super( ...arguments ); diff --git a/packages/format-library/src/index.js b/packages/format-library/src/index.js new file mode 100644 index 00000000000000..0d80d58138f17a --- /dev/null +++ b/packages/format-library/src/index.js @@ -0,0 +1,25 @@ +import { bold } from './bold'; +import { code } from './code'; +import { image } from './image'; +import { italic } from './italic'; +import { link } from './link'; +import { strikethrough } from './strikethrough'; + +/** + * WordPress dependencies + */ +import { + registerFormatType, +} from '@wordpress/rich-text'; + +export const registerCoreFormats = () => { + [ + bold, + code, + image, + italic, + link, + strikethrough, + ].forEach( registerFormatType ); +}; + diff --git a/packages/editor/src/components/rich-text/format-controls/italic/index.js b/packages/format-library/src/italic/index.js similarity index 90% rename from packages/editor/src/components/rich-text/format-controls/italic/index.js rename to packages/format-library/src/italic/index.js index 1c6cb7df1be53e..58c9d268167cd5 100644 --- a/packages/editor/src/components/rich-text/format-controls/italic/index.js +++ b/packages/format-library/src/italic/index.js @@ -9,8 +9,11 @@ import { toggleFormat } from '@wordpress/rich-text'; const Shortcut = () => null; export const italic = { - format: 'italic', - selector: 'em', + name: 'core/italic', + title: __( 'Italic' ), + match: { + tagName: 'em', + }, edit( { isActive, value, onChange } ) { const onToggle = () => onChange( toggleFormat( value, { type: 'em' } ) ); diff --git a/packages/editor/src/components/rich-text/format-controls/link/index.js b/packages/format-library/src/link/index.js similarity index 95% rename from packages/editor/src/components/rich-text/format-controls/link/index.js rename to packages/format-library/src/link/index.js index 17e081a39511d0..bcf7a8332faf4b 100644 --- a/packages/editor/src/components/rich-text/format-controls/link/index.js +++ b/packages/format-library/src/link/index.js @@ -21,13 +21,13 @@ import InlineLinkUI from './inline'; const Shortcut = () => null; export const link = { - format: 'link', - selector: 'a', + name: 'core/link', + title: __( 'Link' ), + match: { + tagName: 'a', + }, attributes: { - url: { - source: 'attribute', - attribute: 'href', - }, + url: 'href', }, edit: class LinkEdit extends Component { constructor() { diff --git a/packages/editor/src/components/rich-text/format-controls/link/inline.js b/packages/format-library/src/link/inline.js similarity index 97% rename from packages/editor/src/components/rich-text/format-controls/link/inline.js rename to packages/format-library/src/link/inline.js index 0741dbdb8b0161..070f75b6a3ec63 100644 --- a/packages/editor/src/components/rich-text/format-controls/link/inline.js +++ b/packages/format-library/src/link/inline.js @@ -18,14 +18,12 @@ import { isCollapsed, applyFormat, } from '@wordpress/rich-text'; +import { URLInput, filterURLForDisplay, URLPopover } from '@wordpress/editor'; /** * Internal dependencies */ import PositionedAtSelection from './positioned-at-selection'; -import URLInput from '../../../url-input'; -import { filterURLForDisplay } from '../../../../utils/url'; -import URLPopover from '../../../url-popover'; const stopKeyPropagation = ( event ) => event.stopPropagation(); diff --git a/packages/editor/src/components/rich-text/format-controls/link/positioned-at-selection.js b/packages/format-library/src/link/positioned-at-selection.js similarity index 100% rename from packages/editor/src/components/rich-text/format-controls/link/positioned-at-selection.js rename to packages/format-library/src/link/positioned-at-selection.js diff --git a/packages/editor/src/components/rich-text/format-controls/link/style.scss b/packages/format-library/src/link/style.scss similarity index 100% rename from packages/editor/src/components/rich-text/format-controls/link/style.scss rename to packages/format-library/src/link/style.scss diff --git a/packages/editor/src/components/rich-text/format-controls/strikethrough/index.js b/packages/format-library/src/strikethrough/index.js similarity index 89% rename from packages/editor/src/components/rich-text/format-controls/strikethrough/index.js rename to packages/format-library/src/strikethrough/index.js index 984d72c9186a9b..bc2a5f222387de 100644 --- a/packages/editor/src/components/rich-text/format-controls/strikethrough/index.js +++ b/packages/format-library/src/strikethrough/index.js @@ -9,8 +9,11 @@ import { toggleFormat } from '@wordpress/rich-text'; const Shortcut = () => null; export const strikethrough = { - format: 'strikethrough', - selector: 'del', + name: 'core/strikethrough', + title: __( 'Strikethrough' ), + match: { + tagName: 'del', + }, edit( { isActive, value, onChange } ) { const onToggle = () => onChange( toggleFormat( value, { type: 'del' } ) ); diff --git a/packages/format-library/src/style.scss b/packages/format-library/src/style.scss new file mode 100644 index 00000000000000..f421ddc28f9283 --- /dev/null +++ b/packages/format-library/src/style.scss @@ -0,0 +1 @@ +@import "./link/style.scss"; diff --git a/packages/rich-text/package.json b/packages/rich-text/package.json index 0c800ee2298f91..fb97882ce780ed 100644 --- a/packages/rich-text/package.json +++ b/packages/rich-text/package.json @@ -21,8 +21,10 @@ "react-native": "src/index", "dependencies": { "@babel/runtime": "^7.0.0", + "@wordpress/data": "file:../data", "@wordpress/escape-html": "file:../escape-html", - "lodash": "^4.17.10" + "lodash": "^4.17.10", + "rememo": "^3.0.0" }, "devDependencies": { "deep-freeze": "^0.0.1", diff --git a/packages/rich-text/src/deregister-format-type.js b/packages/rich-text/src/deregister-format-type.js new file mode 100644 index 00000000000000..3f46566fb7102d --- /dev/null +++ b/packages/rich-text/src/deregister-format-type.js @@ -0,0 +1,27 @@ +/** + * WordPress dependencies + */ +import { select, dispatch } from '@wordpress/data'; + +/** + * Deregisters a format. + * + * @param {string} name Format name. + * + * @return {?WPFormat} The previous format value, if it has been successfully + * unregistered; otherwise `undefined`. + */ +export function deregisterBlockType( name ) { + const oldFormat = select( 'core/formats' ).getBlockType( name ); + + if ( ! oldFormat ) { + window.console.error( + 'Format "' + name + '" is not registered.' + ); + return; + } + + dispatch( 'core/formats' ).removeBlockTypes( name ); + + return oldFormat; +} diff --git a/packages/rich-text/src/get-format-type.js b/packages/rich-text/src/get-format-type.js new file mode 100644 index 00000000000000..39420e1024972c --- /dev/null +++ b/packages/rich-text/src/get-format-type.js @@ -0,0 +1,15 @@ +/** + * WordPress dependencies + */ +import { select } from '@wordpress/data'; + +/** + * Returns a registered format type. + * + * @param {string} name Format name. + * + * @return {?Object} Format type. + */ +export function getFormatType( name ) { + return select( 'core/formats' ).getFormatType( name ); +} diff --git a/packages/rich-text/src/get-format-types.js b/packages/rich-text/src/get-format-types.js new file mode 100644 index 00000000000000..4a1513b881facd --- /dev/null +++ b/packages/rich-text/src/get-format-types.js @@ -0,0 +1,13 @@ +/** + * WordPress dependencies + */ +import { select } from '@wordpress/data'; + +/** + * Returns all registered formats. + * + * @return {Array} Format settings. + */ +export function getFormatTypes() { + return select( 'core/formats' ).getFormatTypes(); +} diff --git a/packages/rich-text/src/index.js b/packages/rich-text/src/index.js index 10d71c3187abb8..e84a56d028989f 100644 --- a/packages/rich-text/src/index.js +++ b/packages/rich-text/src/index.js @@ -1,11 +1,16 @@ +import './store'; + export { applyFormat } from './apply-format'; export { concat } from './concat'; export { create } from './create'; export { getActiveFormat } from './get-active-format'; +export { getFormatType } from './get-format-type'; +export { getFormatTypes } from './get-format-types'; export { getTextContent } from './get-text-content'; export { isCollapsed } from './is-collapsed'; export { isEmpty, isEmptyLine } from './is-empty'; export { join } from './join'; +export { registerFormatType } from './register-format-type'; export { removeFormat } from './remove-format'; export { remove } from './remove'; export { replace } from './replace'; diff --git a/packages/rich-text/src/register-format-type.js b/packages/rich-text/src/register-format-type.js new file mode 100644 index 00000000000000..6f88e1d9dd9777 --- /dev/null +++ b/packages/rich-text/src/register-format-type.js @@ -0,0 +1,137 @@ +/** + * External dependencies + */ +import { isFunction, isString } from 'lodash'; +import { default as tinycolor, mostReadable } from 'tinycolor2'; + +/** + * WordPress dependencies + */ +import { select, dispatch } from '@wordpress/data'; +import { Component, isValidElement } from '@wordpress/element'; + +/** + * Array of icon colors containing a color to be used if the icon color + * was not explicitly set but the icon background color was. + * + * @type {Object} + */ +const ICON_COLORS = [ '#191e23', '#f8f9f9' ]; + +/** + * Function that checks if the parameter is a valid icon. + * + * @param {*} icon Parameter to be checked. + * + * @return {boolean} True if the parameter is a valid icon and false otherwise. + */ + +export function isValidIcon( icon ) { + return !! icon && ( + isString( icon ) || + isValidElement( icon ) || + isFunction( icon ) || + icon instanceof Component + ); +} + +/** + * Function that receives an icon as set by the formats during the registration + * and returns a new icon object that is normalized so we can rely on just on possible icon structure + * in the codebase. + * + * @param {(Object|string|WPElement)} icon Slug of the Dashicon to be shown + * as the icon for the format in the + * inserter, or element or an object describing the icon. + * + * @return {Object} Object describing the icon. + */ +export function normalizeIconObject( icon ) { + if ( ! icon ) { + return { src: }; + } + if ( isValidIcon( icon ) ) { + return { src: icon }; + } + + if ( icon.background ) { + const tinyBgColor = tinycolor( icon.background ); + if ( ! icon.foreground ) { + const foreground = mostReadable( + tinyBgColor, + ICON_COLORS, + { includeFallbackColors: true, level: 'AA', size: 'large' } + ).toHexString(); + icon.foreground = foreground; + } + icon.shadowColor = tinyBgColor.setAlpha( 0.3 ).toRgbString(); + } + return icon; +} + +/** + * Registers a new format provided a unique name and an object defining its + * behavior. + * + * @param {Object} settings Format settings. + * + * @return {?WPFormat} The format, if it has been successfully registered; + * otherwise `undefined`. + */ +export function registerFormatType( settings ) { + if ( typeof settings.name !== 'string' ) { + window.console.error( + 'Format names must be strings.' + ); + return; + } + + if ( ! /^[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*$/.test( settings.name ) ) { + window.console.error( + 'Format names must contain a namespace prefix, include only lowercase alphanumeric characters or dashes, and start with a letter. Example: my-plugin/my-custom-format' + ); + return; + } + + if ( select( 'core/formats' ).getFormatType( settings.name ) ) { + window.console.error( + 'Format "' + settings.name + '" is already registered.' + ); + return; + } + + if ( ! settings || ! isFunction( settings.edit ) ) { + window.console.error( + 'The "edit" property must be specified and must be a valid function.' + ); + return; + } + + if ( ! ( 'title' in settings ) || settings.title === '' ) { + window.console.error( + 'The format "' + settings.name + '" must have a title.' + ); + return; + } + + if ( typeof settings.title !== 'string' ) { + window.console.error( + 'Format titles must be strings.' + ); + return; + } + + settings.icon = normalizeIconObject( settings.icon ); + + if ( ! isValidIcon( settings.icon.src ) ) { + window.console.error( + 'The icon passed is invalid. ' + + 'The icon should be a string, an element, a function, or an object following the specifications documented in https://wordpress.org/gutenberg/handbook/format-api/#icon-optional' + ); + return; + } + + dispatch( 'core/formats' ).addFormatTypes( settings ); + + return settings; +} diff --git a/packages/rich-text/src/store/actions.js b/packages/rich-text/src/store/actions.js new file mode 100644 index 00000000000000..9b14ac26bca5fc --- /dev/null +++ b/packages/rich-text/src/store/actions.js @@ -0,0 +1,33 @@ +/** + * External dependencies + */ +import { castArray } from 'lodash'; + +/** + * Returns an action object used in signalling that format types have been + * added. + * + * @param {Array|Object} formatTypes Format types received. + * + * @return {Object} Action object. + */ +export function addFormatTypes( formatTypes ) { + return { + type: 'ADD_FORMAT_TYPES', + formatTypes: castArray( formatTypes ), + }; +} + +/** + * Returns an action object used to remove a registered format type. + * + * @param {string|Array} names Format name. + * + * @return {Object} Action object. + */ +export function removeFormatTypes( names ) { + return { + type: 'REMOVE_FORMAT_TYPES', + names: castArray( names ), + }; +} diff --git a/packages/rich-text/src/store/index.js b/packages/rich-text/src/store/index.js new file mode 100644 index 00000000000000..4e736c99fb4e1e --- /dev/null +++ b/packages/rich-text/src/store/index.js @@ -0,0 +1,13 @@ +/** + * WordPress dependencies + */ +import { registerStore } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import reducer from './reducer'; +import * as selectors from './selectors'; +import * as actions from './actions'; + +registerStore( 'core/formats', { reducer, selectors, actions } ); diff --git a/packages/rich-text/src/store/reducer.js b/packages/rich-text/src/store/reducer.js new file mode 100644 index 00000000000000..6002b1f55c34d5 --- /dev/null +++ b/packages/rich-text/src/store/reducer.js @@ -0,0 +1,33 @@ +/** + * External dependencies + */ +import { keyBy, omit } from 'lodash'; + +/** + * WordPress dependencies + */ +import { combineReducers } from '@wordpress/data'; + +/** + * Reducer managing the format types + * + * @param {Object} state Current state. + * @param {Object} action Dispatched action. + * + * @return {Object} Updated state. + */ +export function formatTypes( state = {}, action ) { + switch ( action.type ) { + case 'ADD_FORMAT_TYPES': + return { + ...state, + ...keyBy( action.formatTypes, 'name' ), + }; + case 'REMOVE_FORMAT_TYPES': + return omit( state, action.names ); + } + + return state; +} + +export default combineReducers( { formatTypes } ); diff --git a/packages/rich-text/src/store/selectors.js b/packages/rich-text/src/store/selectors.js new file mode 100644 index 00000000000000..c5a28baf0abc22 --- /dev/null +++ b/packages/rich-text/src/store/selectors.js @@ -0,0 +1,30 @@ +/** + * External dependencies + */ +import createSelector from 'rememo'; + +/** + * Returns all the available format types. + * + * @param {Object} state Data state. + * + * @return {Array} Format types. + */ +export const getFormatTypes = createSelector( + ( state ) => Object.values( state.formatTypes ), + ( state ) => [ + state.formatTypes, + ] +); + +/** + * Returns a format type by name. + * + * @param {Object} state Data state. + * @param {string} name Format type name. + * + * @return {Object?} Format type. + */ +export function getFormatType( state, name ) { + return state.formatTypes[ name ]; +} diff --git a/packages/rich-text/src/store/test/reducer.js b/packages/rich-text/src/store/test/reducer.js new file mode 100644 index 00000000000000..4738453bc12eb7 --- /dev/null +++ b/packages/rich-text/src/store/test/reducer.js @@ -0,0 +1,47 @@ +/** + * External dependencies + */ +import deepFreeze from 'deep-freeze'; + +/** + * Internal dependencies + */ +import { formatTypes } from '../reducer'; + +describe( 'formatTypes', () => { + it( 'should return an empty object as default state', () => { + expect( formatTypes( undefined, {} ) ).toEqual( {} ); + } ); + + it( 'should add add a new block type', () => { + const original = deepFreeze( { + 'core/bold': { name: 'core/bold' }, + } ); + + const state = formatTypes( original, { + type: 'ADD_FORMAT_TYPES', + formatTypes: [ { name: 'core/code' } ], + } ); + + expect( state ).toEqual( { + 'core/bold': { name: 'core/bold' }, + 'core/code': { name: 'core/code' }, + } ); + } ); + + it( 'should remove block types', () => { + const original = deepFreeze( { + 'core/bold': { name: 'core/bold' }, + 'core/code': { name: 'core/code' }, + } ); + + const state = formatTypes( original, { + type: 'REMOVE_FORMAT_TYPES', + names: [ 'core/code' ], + } ); + + expect( state ).toEqual( { + 'core/bold': { name: 'core/bold' }, + } ); + } ); +} ); diff --git a/webpack.config.js b/webpack.config.js index 3d19a5aa0dc1c8..9359186defbf53 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -84,6 +84,7 @@ const gutenbergPackages = [ 'editor', 'element', 'escape-html', + 'format-library', 'hooks', 'html-entities', 'i18n',