diff --git a/packages/components/src/index.js b/packages/components/src/index.js index 6a3578f58dfb27..fe11dcdb179574 100644 --- a/packages/components/src/index.js +++ b/packages/components/src/index.js @@ -53,6 +53,7 @@ export { default as TextControl } from './text-control'; export { default as TextareaControl } from './textarea-control'; export { default as ToggleControl } from './toggle-control'; export { default as Toolbar } from './toolbar'; +export { default as ToolbarButton } from './toolbar-button'; export { default as Tooltip } from './tooltip'; export { default as TreeSelect } from './tree-select'; export { createSlotFill, Slot, Fill, Provider as SlotFillProvider } from './slot-fill'; diff --git a/packages/components/src/style.scss b/packages/components/src/style.scss index 37c379d3819221..cd6783dee58cb3 100644 --- a/packages/components/src/style.scss +++ b/packages/components/src/style.scss @@ -36,4 +36,5 @@ @import "./textarea-control/style.scss"; @import "./toggle-control/style.scss"; @import "./toolbar/style.scss"; +@import "./toolbar-button/style.scss"; @import "./tooltip/style.scss"; diff --git a/packages/components/src/toolbar-button/index.js b/packages/components/src/toolbar-button/index.js new file mode 100644 index 00000000000000..8c2374b0284d1d --- /dev/null +++ b/packages/components/src/toolbar-button/index.js @@ -0,0 +1,50 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * Internal dependencies + */ +import IconButton from '../icon-button'; +import ToolbarButtonContainer from './toolbar-button-container'; + +function ToolbarButton( { + containerClassName, + icon, + title, + shortcut, + subscript, + onClick, + className, + isActive, + isDisabled, + extraProps, + children, +} ) { + return ( + + { + event.stopPropagation(); + onClick(); + } } + className={ classnames( + 'components-toolbar__control', + className, + { 'is-active': isActive } + ) } + aria-pressed={ isActive } + disabled={ isDisabled } + { ...extraProps } + /> + { children } + + ); +} + +export default ToolbarButton; diff --git a/packages/components/src/toolbar-button/style.scss b/packages/components/src/toolbar-button/style.scss new file mode 100644 index 00000000000000..65f3c0e56c11a5 --- /dev/null +++ b/packages/components/src/toolbar-button/style.scss @@ -0,0 +1,77 @@ +.components-toolbar__control.components-button { + display: inline-flex; + align-items: flex-end; + margin: 0; + padding: 3px; + outline: none; + cursor: pointer; + position: relative; + width: $icon-button-size; + height: $icon-button-size; + + // Unset icon button styles + &:active, + &:not([aria-disabled="true"]):hover, + &:not([aria-disabled="true"]):focus { + outline: none; + box-shadow: none; + background: none; + border: none; + } + + // Disabled + &:disabled { + cursor: default; + } + + & > svg { + padding: 5px; + border-radius: $radius-round-rectangle; + height: 30px; + width: 30px; + } + + // Subscript for numbered icon buttons, like headings + &[data-subscript] svg { + padding: 5px 10px 5px 0; + } + + &[data-subscript]::after { + content: attr(data-subscript); + font-family: $default-font; + font-size: $default-font-size; + font-weight: 600; + line-height: 12px; + position: absolute; + right: 8px; + bottom: 10px; + } + + // Assign hover style to child element, not the button itself + &:not(:disabled):not([aria-disabled="true"]):hover { + box-shadow: none; + } + + &:not(:disabled).is-active > svg, + &:not(:disabled):hover > svg { + @include formatting-button-style__hover; + } + + // Active & toggled style + &:not(:disabled).is-active > svg { + @include formatting-button-style__active; + } + + &:not(:disabled).is-active[data-subscript]::after { + color: $white; + } + + // Focus style + &:not(:disabled):focus > svg { + @include formatting-button-style__focus; + } +} + +.components-toolbar__control .dashicon { + display: block; +} diff --git a/packages/components/src/toolbar/toolbar-button-container.js b/packages/components/src/toolbar-button/toolbar-button-container.js similarity index 100% rename from packages/components/src/toolbar/toolbar-button-container.js rename to packages/components/src/toolbar-button/toolbar-button-container.js diff --git a/packages/components/src/toolbar/toolbar-button-container.native.js b/packages/components/src/toolbar-button/toolbar-button-container.native.js similarity index 100% rename from packages/components/src/toolbar/toolbar-button-container.native.js rename to packages/components/src/toolbar-button/toolbar-button-container.native.js diff --git a/packages/components/src/toolbar/index.js b/packages/components/src/toolbar/index.js index 548c5fb4affffc..863aca5a9b5d44 100644 --- a/packages/components/src/toolbar/index.js +++ b/packages/components/src/toolbar/index.js @@ -7,10 +7,9 @@ import { flatMap } from 'lodash'; /** * Internal dependencies */ -import IconButton from '../icon-button'; +import ToolbarButton from '../toolbar-button'; import DropdownMenu from '../dropdown-menu'; import ToolbarContainer from './toolbar-container'; -import ToolbarButtonContainer from './toolbar-button-container'; /** * Renders a toolbar with controls. @@ -71,28 +70,11 @@ function Toolbar( { controls = [], children, className, isCollapsed, icon, label { flatMap( controlSets, ( controlSet, indexOfSet ) => ( controlSet.map( ( control, indexOfControl ) => ( - 0 && indexOfControl === 0 ? 'has-left-divider' : null } - > - { - event.stopPropagation(); - control.onClick(); - } } - className={ classnames( 'components-toolbar__control', control.className, { - 'is-active': control.isActive, - } ) } - aria-pressed={ control.isActive } - disabled={ control.isDisabled } - { ...control.extraProps } - /> - { control.children } - + { ...control } + /> ) ) ) ) } { children } diff --git a/packages/components/src/toolbar/style.scss b/packages/components/src/toolbar/style.scss index 13d609e3d65584..1356b9d83d4afd 100644 --- a/packages/components/src/toolbar/style.scss +++ b/packages/components/src/toolbar/style.scss @@ -39,81 +39,3 @@ div.components-toolbar { } } } - -.components-toolbar__control.components-button { - display: inline-flex; - align-items: flex-end; - margin: 0; - padding: 3px; - outline: none; - cursor: pointer; - position: relative; - width: $icon-button-size; - height: $icon-button-size; - - // Unset icon button styles - &:active, - &:not([aria-disabled="true"]):hover, - &:not([aria-disabled="true"]):focus { - outline: none; - box-shadow: none; - background: none; - border: none; - } - - // Disabled - &:disabled { - cursor: default; - } - - & > svg { - padding: 5px; - border-radius: $radius-round-rectangle; - height: 30px; - width: 30px; - } - - // Subscript for numbered icon buttons, like headings - &[data-subscript] svg { - padding: 5px 10px 5px 0; - } - - &[data-subscript]::after { - content: attr(data-subscript); - font-family: $default-font; - font-size: $default-font-size; - font-weight: 600; - line-height: 12px; - position: absolute; - right: 8px; - bottom: 10px; - } - - // Assign hover style to child element, not the button itself - &:not(:disabled):not([aria-disabled="true"]):hover { - box-shadow: none; - } - - &:not(:disabled).is-active > svg, - &:not(:disabled):hover > svg { - @include formatting-button-style__hover; - } - - // Active & toggled style - &:not(:disabled).is-active > svg { - @include formatting-button-style__active; - } - - &:not(:disabled).is-active[data-subscript]::after { - color: $white; - } - - // Focus style - &:not(:disabled):focus > svg { - @include formatting-button-style__focus; - } -} - -.components-toolbar__control .dashicon { - display: block; -} diff --git a/packages/components/src/toolbar/test/index.js b/packages/components/src/toolbar/test/index.js index 08a9def41e5d50..d7a2735c5e8093 100644 --- a/packages/components/src/toolbar/test/index.js +++ b/packages/components/src/toolbar/test/index.js @@ -32,13 +32,12 @@ describe( 'Toolbar', () => { }, ]; const toolbar = shallow( ); - const listItem = toolbar.find( 'IconButton' ); + const listItem = toolbar.find( 'ToolbarButton' ); expect( listItem.props() ).toMatchObject( { icon: 'wordpress', - label: 'WordPress', - 'data-subscript': 'wp', - 'aria-pressed': false, - className: 'components-toolbar__control', + title: 'WordPress', + subscript: 'wp', + className: null, } ); } ); @@ -54,10 +53,9 @@ describe( 'Toolbar', () => { }, ]; const toolbar = shallow( ); - const listItem = toolbar.find( 'IconButton' ); + const listItem = toolbar.find( 'ToolbarButton' ); expect( listItem.props() ).toMatchObject( { - 'aria-pressed': true, - className: 'components-toolbar__control is-active', + className: null, } ); } ); @@ -96,10 +94,10 @@ describe( 'Toolbar', () => { }, ]; const toolbar = shallow( ); - const listItem = toolbar.find( 'IconButton' ); + const listItem = toolbar.find( 'ToolbarButton' ); listItem.simulate( 'click', event ); expect( clickHandler ).toHaveBeenCalledTimes( 1 ); - expect( clickHandler ).toHaveBeenCalledWith(); + expect( clickHandler ).toHaveBeenCalledWith( event ); } ); } ); } ); diff --git a/packages/editor/src/components/rich-text/format-controls/bold/index.js b/packages/editor/src/components/rich-text/format-controls/bold/index.js new file mode 100644 index 00000000000000..daec7066ca726d --- /dev/null +++ b/packages/editor/src/components/rich-text/format-controls/bold/index.js @@ -0,0 +1,35 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Fragment } from '@wordpress/element'; +import { Fill, ToolbarButton } from '@wordpress/components'; +import { toggleFormat } from '@wordpress/rich-text'; + +const Shortcut = () => null; + +export const bold = { + format: 'bold', + selector: 'strong', + edit( { isActive, value, onChange } ) { + const onToggle = () => onChange( toggleFormat( value, { type: 'strong' } ) ); + + return ( + + + + + + + ); + }, +}; diff --git a/packages/editor/src/components/rich-text/format-controls/code/index.js b/packages/editor/src/components/rich-text/format-controls/code/index.js new file mode 100644 index 00000000000000..aac254f900bd68 --- /dev/null +++ b/packages/editor/src/components/rich-text/format-controls/code/index.js @@ -0,0 +1,25 @@ +/** + * WordPress dependencies + */ +import { Fragment } from '@wordpress/element'; +import { toggleFormat } from '@wordpress/rich-text'; + +const Shortcut = () => null; + +export const code = { + format: 'code', + selector: 'code', + edit( { value, onChange } ) { + const onToggle = () => onChange( toggleFormat( value, { type: 'code' } ) ); + + return ( + + + + ); + }, +}; diff --git a/packages/editor/src/components/rich-text/format-controls/index.js b/packages/editor/src/components/rich-text/format-controls/index.js new file mode 100644 index 00000000000000..4601040652eb94 --- /dev/null +++ b/packages/editor/src/components/rich-text/format-controls/index.js @@ -0,0 +1,13 @@ +import { bold } from './bold'; +import { code } from './code'; +import { italic } from './italic'; +import { link } from './link'; +import { strikethrough } from './strikethrough'; + +export const formatControls = [ + bold, + code, + italic, + link, + strikethrough, +]; diff --git a/packages/editor/src/components/rich-text/format-controls/italic/index.js b/packages/editor/src/components/rich-text/format-controls/italic/index.js new file mode 100644 index 00000000000000..1c6cb7df1be53e --- /dev/null +++ b/packages/editor/src/components/rich-text/format-controls/italic/index.js @@ -0,0 +1,35 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Fragment } from '@wordpress/element'; +import { Fill, ToolbarButton } from '@wordpress/components'; +import { toggleFormat } from '@wordpress/rich-text'; + +const Shortcut = () => null; + +export const italic = { + format: 'italic', + selector: 'em', + edit( { isActive, value, onChange } ) { + const onToggle = () => onChange( toggleFormat( value, { type: 'em' } ) ); + + return ( + + + + + + + ); + }, +}; diff --git a/packages/editor/src/components/rich-text/format-controls/link/index.js b/packages/editor/src/components/rich-text/format-controls/link/index.js new file mode 100644 index 00000000000000..974e38e4f2f22a --- /dev/null +++ b/packages/editor/src/components/rich-text/format-controls/link/index.js @@ -0,0 +1,104 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Component, Fragment } from '@wordpress/element'; +import { Fill, ToolbarButton } from '@wordpress/components'; +import { + getTextContent, + applyFormat, + removeFormat, + slice, + getActiveFormat, +} from '@wordpress/rich-text'; +import { isURL } from '@wordpress/url'; + +/** + * Internal dependencies + */ +import InlineLinkUI from './inline'; + +const Shortcut = () => null; + +export const link = { + format: 'link', + selector: 'a', + attributes: { + url: { + source: 'attribute', + attribute: 'href', + }, + }, + edit: class FormatToolbar extends Component { + constructor() { + super( ...arguments ); + + this.addLink = this.addLink.bind( this ); + this.stopAddingLink = this.stopAddingLink.bind( this ); + this.state = { + addingLink: false, + }; + } + + addLink() { + const { value, onChange } = this.props; + const text = getTextContent( slice( value ) ); + + if ( text && isURL( text ) ) { + onChange( applyFormat( value, { type: 'a', attributes: { href: text } } ) ); + } else { + this.setState( { addingLink: true } ); + } + } + + stopAddingLink() { + this.setState( { addingLink: false } ); + } + + render() { + const { isActive, value, onChange } = this.props; + const onRemoveFormat = () => onChange( removeFormat( value, 'a' ) ); + + return ( + + + this.addLink() } + /> + this.addLink() } + /> + + { isActive && } + { ! isActive && this.addLink() } + isActive={ isActive } + /> } + + + + ); + } + }, +}; diff --git a/packages/editor/src/components/rich-text/format-toolbar/link-container.js b/packages/editor/src/components/rich-text/format-controls/link/inline.js similarity index 89% rename from packages/editor/src/components/rich-text/format-toolbar/link-container.js rename to packages/editor/src/components/rich-text/format-controls/link/inline.js index 80e5ee36989aae..b9ad9c2ba0640c 100644 --- a/packages/editor/src/components/rich-text/format-toolbar/link-container.js +++ b/packages/editor/src/components/rich-text/format-controls/link/inline.js @@ -24,8 +24,8 @@ import { * Internal dependencies */ import PositionedAtSelection from './positioned-at-selection'; -import URLInput from '../../url-input'; -import { filterURLForDisplay } from '../../../utils/url'; +import URLInput from '../../../url-input'; +import { filterURLForDisplay } from '../../../../utils/url'; const stopKeyPropagation = ( event ) => event.stopPropagation(); @@ -53,7 +53,7 @@ function isShowingInput( props, state ) { return props.addingLink || state.editLink; } -class LinkContainer extends Component { +class InlineLinkUI extends Component { constructor() { super( ...arguments ); @@ -106,12 +106,14 @@ class LinkContainer extends Component { } setLinkTarget( opensInNewWindow ) { + const { link, value, onChange } = this.props; + this.setState( { opensInNewWindow } ); // Apply now if URL is not being edited. if ( ! isShowingInput( this.props, this.state ) ) { - const { href } = getLinkAttributesFromFormat( this.props.link ); - this.props.applyFormat( createLinkFormat( { href, opensInNewWindow } ) ); + const { href } = getLinkAttributesFromFormat( link ); + onChange( applyFormat( value, createLinkFormat( { href, opensInNewWindow } ) ) ); } } @@ -121,22 +123,22 @@ class LinkContainer extends Component { } submitLink( event ) { - const { link, record } = this.props; + const { link, value, onChange, speak } = this.props; const { inputValue, opensInNewWindow } = this.state; const href = prependHTTP( inputValue ); const format = createLinkFormat( { href, opensInNewWindow } ); - if ( isCollapsed( record ) ) { + if ( isCollapsed( value ) ) { const toInsert = applyFormat( create( { text: href } ), format, 0, href.length ); - this.props.onChange( insert( record, toInsert ) ); + onChange( insert( value, toInsert ) ); } else { - this.props.applyFormat( format ); + onChange( applyFormat( value, format ) ); } this.resetState(); if ( ! link ) { - this.props.speak( __( 'Link added.' ), 'assertive' ); + speak( __( 'Link added.' ), 'assertive' ); } event.preventDefault(); @@ -148,7 +150,7 @@ class LinkContainer extends Component { } render() { - const { link, addingLink, record } = this.props; + const { link, addingLink, value } = this.props; if ( ! link && ! addingLink ) { return null; @@ -171,7 +173,7 @@ class LinkContainer extends Component { null; + +export const strikethrough = { + format: 'strikethrough', + selector: 'del', + edit( { isActive, value, onChange } ) { + const onToggle = () => onChange( toggleFormat( value, { type: 'del' } ) ); + + return ( + + + + + + + ); + }, +}; diff --git a/packages/editor/src/components/rich-text/format-edit.js b/packages/editor/src/components/rich-text/format-edit.js new file mode 100644 index 00000000000000..983d644b4a6056 --- /dev/null +++ b/packages/editor/src/components/rich-text/format-edit.js @@ -0,0 +1,29 @@ +/** + * WordPress dependencies + */ +import { Fragment } from '@wordpress/element'; +import { + getActiveFormat, +} from '@wordpress/rich-text'; + +/** + * Internal dependencies + */ +import { formatControls } from './format-controls'; + +const FormatEdit = ( { value, onChange } ) => { + return ( + + { formatControls.map( ( { selector, edit: Edit }, i ) => + Edit && + ) } + + ); +}; + +export default FormatEdit; diff --git a/packages/editor/src/components/rich-text/format-toolbar/index.js b/packages/editor/src/components/rich-text/format-toolbar/index.js index db06869771df00..d9f8ac8fd8de78 100644 --- a/packages/editor/src/components/rich-text/format-toolbar/index.js +++ b/packages/editor/src/components/rich-text/format-toolbar/index.js @@ -1,158 +1,18 @@ /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; -import { Toolbar } from '@wordpress/components'; -import { rawShortcut } from '@wordpress/keycodes'; -import { Component } from '@wordpress/element'; -import { - applyFormat, - removeFormat, - getActiveFormat, - getTextContent, - slice, -} from '@wordpress/rich-text'; -import { isURL } from '@wordpress/url'; - -/** - * Internal dependencies - */ -import { FORMATTING_CONTROLS } from '../formatting-controls'; -import LinkContainer from './link-container'; -import ToolbarContainer from './toolbar-container'; - -class FormatToolbar extends Component { - constructor( { toggleFormat, editor } ) { - super( ...arguments ); - - this.removeLink = this.removeLink.bind( this ); - this.addLink = this.addLink.bind( this ); - this.stopAddingLink = this.stopAddingLink.bind( this ); - this.applyFormat = this.applyFormat.bind( this ); - this.removeFormat = this.removeFormat.bind( this ); - this.getActiveFormat = this.getActiveFormat.bind( this ); - this.toggleFormat = this.toggleFormat.bind( this ); - - this.state = { - addingLink: false, - }; - - if ( editor ) { - editor.shortcuts.add( rawShortcut.primary( 'k' ), '', this.addLink ); - editor.shortcuts.add( rawShortcut.access( 'a' ), '', this.addLink ); - editor.shortcuts.add( rawShortcut.access( 's' ), '', this.removeLink ); - editor.shortcuts.add( rawShortcut.access( 'd' ), '', () => toggleFormat( { type: 'del' } ) ); - editor.shortcuts.add( rawShortcut.access( 'x' ), '', () => toggleFormat( { type: 'code' } ) ); - } - } - - removeLink() { - this.removeFormat( 'a' ); - } - - addLink() { - const text = getTextContent( slice( this.props.record ) ); - - if ( text && isURL( text ) ) { - this.applyFormat( { - type: 'a', - attributes: { - href: text, - }, - } ); - } else { - this.setState( { addingLink: true } ); - } - } - - stopAddingLink() { - this.setState( { addingLink: false } ); - } - - /** - * Apply a format with the current value and selection. - * - * @param {Object} format The format to apply. - */ - applyFormat( format ) { - this.props.onChange( applyFormat( this.props.record, format ) ); - } - - /** - * Remove a format from the current value with the current selection. - * - * @param {string} formatType The type of format to remove. - */ - removeFormat( formatType ) { - this.props.onChange( removeFormat( this.props.record, formatType ) ); - } - - /** - * Get the current format based on the selection - * - * @param {string} formatType The type of format to check. - * - * @return {boolean} Whether the format is active or not. - */ - getActiveFormat( formatType ) { - return getActiveFormat( this.props.record, formatType ); - } - - /** - * Toggle a format based on the selection. - * - * @param {Object} format The format to toggle. - */ - toggleFormat( format ) { - if ( this.getActiveFormat( format.type ) ) { - this.removeFormat( format.type ); - } else { - this.applyFormat( format ); - } - } - - render() { - const link = this.getActiveFormat( 'a' ); - const toolbarControls = FORMATTING_CONTROLS - .filter( ( control ) => this.props.enabledControls.indexOf( control.format ) !== -1 ) - .map( ( control ) => { - if ( control.format === 'link' ) { - const linkIsActive = link !== undefined; - - return { - ...control, - shortcut: linkIsActive ? control.activeShortcut : control.shortcut, - icon: linkIsActive ? 'editor-unlink' : 'admin-links', // TODO: Need proper unlink icon - title: linkIsActive ? __( 'Unlink' ) : __( 'Link' ), - onClick: linkIsActive ? this.removeLink : this.addLink, - isActive: !! linkIsActive, - }; - } - - return { - ...control, - onClick: () => this.toggleFormat( { type: control.selector } ), - isActive: this.getActiveFormat( control.selector ) !== undefined, - }; - } ); - - return ( - - - - - ); - } -} +import { Toolbar, Slot } from '@wordpress/components'; + +const FormatToolbar = ( { controls } ) => { + return ( +
+ + { controls.map( ( format, index ) => + + ) } + +
+ ); +}; export default FormatToolbar; diff --git a/packages/editor/src/components/rich-text/format-toolbar/style.scss b/packages/editor/src/components/rich-text/format-toolbar/style.scss index 1e1df05d7c99ae..a4607b24b34754 100644 --- a/packages/editor/src/components/rich-text/format-toolbar/style.scss +++ b/packages/editor/src/components/rich-text/format-toolbar/style.scss @@ -2,53 +2,3 @@ display: flex; flex-shrink: 0; } - -.editor-format-toolbar__link-container { - position: absolute; - transform: translateX(-50%); -} - -.editor-format-toolbar__link-modal-line { - display: flex; - flex-direction: row; - min-width: 0; - align-items: flex-start; - - .components-button { - flex-shrink: 0; - width: $icon-button-size; - height: $icon-button-size; - } - - .editor-url-input { - flex-grow: 1; - } -} - -.editor-format-toolbar__link-settings-toggle .dashicon { - transform: rotate(90deg); -} - -.editor-format-toolbar__link-value { - margin: $item-spacing - $border-width; - flex-grow: 1; - flex-shrink: 1; - overflow: hidden; - text-overflow: ellipsis; - position: relative; - white-space: nowrap; - min-width: 150px; - max-width: 500px; -} - -.editor-format-toolbar__link-settings { - padding: 7px 8px; - border-top: $border-width solid $light-gray-500; - padding-top: 7px + $border-width; - - .components-base-control { - margin: 0; - flex-grow: 1; - flex-shrink: 1; - } -} diff --git a/packages/editor/src/components/rich-text/format-toolbar/toolbar-container.js b/packages/editor/src/components/rich-text/format-toolbar/toolbar-container.js deleted file mode 100644 index effed4d9271811..00000000000000 --- a/packages/editor/src/components/rich-text/format-toolbar/toolbar-container.js +++ /dev/null @@ -1,8 +0,0 @@ -const ToolbarContainer = ( props ) => ( -
- { props.children } -
-); -export default ToolbarContainer; diff --git a/packages/editor/src/components/rich-text/formatting-controls.js b/packages/editor/src/components/rich-text/formatting-controls.js deleted file mode 100644 index 2699a17fbbe7ca..00000000000000 --- a/packages/editor/src/components/rich-text/formatting-controls.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; -import { displayShortcut } from '@wordpress/keycodes'; - -export const FORMATTING_CONTROLS = [ - { - icon: 'editor-bold', - title: __( 'Bold' ), - shortcut: displayShortcut.primary( 'b' ), - format: 'bold', - selector: 'strong', - }, - { - icon: 'editor-italic', - title: __( 'Italic' ), - shortcut: displayShortcut.primary( 'i' ), - format: 'italic', - selector: 'em', - }, - { - icon: 'admin-links', - title: __( 'Link' ), - shortcut: displayShortcut.primary( 'k' ), - activeShortcut: displayShortcut.access( 's' ), - format: 'link', - selector: 'a', - }, - { - icon: 'editor-strikethrough', - title: __( 'Strikethrough' ), - shortcut: displayShortcut.access( 'd' ), - format: 'strikethrough', - selector: 'del', - }, -]; diff --git a/packages/editor/src/components/rich-text/index.js b/packages/editor/src/components/rich-text/index.js index 38b2b0337a9a4f..4056f383c6f1f8 100644 --- a/packages/editor/src/components/rich-text/index.js +++ b/packages/editor/src/components/rich-text/index.js @@ -46,7 +46,7 @@ import { */ import Autocomplete from '../autocomplete'; import BlockFormatControls from '../block-format-controls'; -import { FORMATTING_CONTROLS } from './formatting-controls'; +import FormatEdit from './format-edit'; import FormatToolbar from './format-toolbar'; import TinyMCE from './tinymce'; import { pickAriaProps } from './aria'; @@ -853,15 +853,6 @@ export class RichText extends Component { const classes = classnames( wrapperClassName, 'editor-rich-text' ); const record = this.getRecord(); - const formatToolbar = this.editor && ( - - ); - return (
{ isSelected && ! inlineToolbar && ( - { formatToolbar } + ) } { isSelected && inlineToolbar && (
- { formatToolbar } +
) } { isSelected && @@ -921,6 +912,7 @@ export class RichText extends Component { } { isSelected && } + { isSelected && } ) } @@ -930,7 +922,7 @@ export class RichText extends Component { } RichText.defaultProps = { - formattingControls: FORMATTING_CONTROLS.map( ( { format } ) => format ), + formattingControls: [ 'bold', 'italic', 'link', 'strikethrough' ], format: 'rich-text', }; diff --git a/packages/editor/src/components/rich-text/index.native.js b/packages/editor/src/components/rich-text/index.native.js index 617f5f0ea04b34..6caee12d50a18d 100644 --- a/packages/editor/src/components/rich-text/index.native.js +++ b/packages/editor/src/components/rich-text/index.native.js @@ -15,11 +15,34 @@ import { Component, RawHTML } from '@wordpress/element'; import { withInstanceId, compose } from '@wordpress/compose'; import { toHTMLString } from '@wordpress/rich-text'; import { Toolbar } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import { FORMATTING_CONTROLS } from './formatting-controls'; + +const FORMATTING_CONTROLS = [ + { + icon: 'editor-bold', + title: __( 'Bold' ), + format: 'bold', + }, + { + icon: 'editor-italic', + title: __( 'Italic' ), + format: 'italic', + }, + { + icon: 'admin-links', + title: __( 'Link' ), + format: 'link', + }, + { + icon: 'editor-strikethrough', + title: __( 'Strikethrough' ), + format: 'strikethrough', + }, +]; const isRichTextValueEmpty = ( value ) => { return ! value || ! value.length; diff --git a/packages/editor/src/style.scss b/packages/editor/src/style.scss index f6e6b8e586e66a..218cc3c417e8bd 100644 --- a/packages/editor/src/style.scss +++ b/packages/editor/src/style.scss @@ -36,6 +36,7 @@ @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/rich-text-value/src/toggle-format.js b/packages/rich-text-value/src/toggle-format.js new file mode 100644 index 00000000000000..21278a8ccdaf6f --- /dev/null +++ b/packages/rich-text-value/src/toggle-format.js @@ -0,0 +1,26 @@ +/** + * Internal dependencies + */ + +import { getActiveFormat } from './get-active-format'; +import { removeFormat } from './remove-format'; +import { applyFormat } from './apply-format'; + +/** + * Toggle a format object to a Rich Text value at the current selection. + * + * @param {Object} value Value to modify. + * @param {Object} format Format to apply or remove. + * + * @return {Object} A new value with the format applied or removed. + */ +export function toggleFormat( + value, + format +) { + if ( getActiveFormat( value, format.type ) ) { + return removeFormat( value, format.type ); + } + + return applyFormat( value, format ); +} diff --git a/packages/rich-text/src/index.js b/packages/rich-text/src/index.js index 61f149cc047677..a6ff05ad1cc251 100644 --- a/packages/rich-text/src/index.js +++ b/packages/rich-text/src/index.js @@ -14,3 +14,4 @@ export { slice } from './slice'; export { split } from './split'; export { apply, toDom as unstableToDom } from './to-dom'; export { toHTMLString } from './to-html-string'; +export { toggleFormat } from './toggle-format'; diff --git a/packages/rich-text/src/toggle-format.js b/packages/rich-text/src/toggle-format.js new file mode 100644 index 00000000000000..21278a8ccdaf6f --- /dev/null +++ b/packages/rich-text/src/toggle-format.js @@ -0,0 +1,26 @@ +/** + * Internal dependencies + */ + +import { getActiveFormat } from './get-active-format'; +import { removeFormat } from './remove-format'; +import { applyFormat } from './apply-format'; + +/** + * Toggle a format object to a Rich Text value at the current selection. + * + * @param {Object} value Value to modify. + * @param {Object} format Format to apply or remove. + * + * @return {Object} A new value with the format applied or removed. + */ +export function toggleFormat( + value, + format +) { + if ( getActiveFormat( value, format.type ) ) { + return removeFormat( value, format.type ); + } + + return applyFormat( value, format ); +}