From bc21b60768ab084a01924ccd03a5109bd3d31c5f Mon Sep 17 00:00:00 2001 From: Jeffrey Carandang Date: Fri, 7 Feb 2020 22:30:34 +0800 Subject: [PATCH] Add option to add text color to specific text inside RichText (#16014) Co-authored-by: Jorge Costa --- package-lock.json | 1 + .../rich-text/format-toolbar/index.js | 14 +- packages/format-library/package.json | 1 + .../format-library/src/default-formats.js | 12 +- packages/format-library/src/style.scss | 1 + .../format-library/src/text-color/index.js | 94 ++++++++++++ .../format-library/src/text-color/inline.js | 137 ++++++++++++++++++ .../format-library/src/text-color/style.scss | 43 ++++++ 8 files changed, 296 insertions(+), 7 deletions(-) create mode 100644 packages/format-library/src/text-color/index.js create mode 100644 packages/format-library/src/text-color/inline.js create mode 100644 packages/format-library/src/text-color/style.scss diff --git a/package-lock.json b/package-lock.json index 12e24e5195f3d..a9142e287a6aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10533,6 +10533,7 @@ "@babel/runtime": "^7.8.3", "@wordpress/block-editor": "file:packages/block-editor", "@wordpress/components": "file:packages/components", + "@wordpress/data": "file:packages/data", "@wordpress/dom": "file:packages/dom", "@wordpress/element": "file:packages/element", "@wordpress/html-entities": "file:packages/html-entities", diff --git a/packages/block-editor/src/components/rich-text/format-toolbar/index.js b/packages/block-editor/src/components/rich-text/format-toolbar/index.js index 1667bc2a06b04..ee9cd480fc40a 100644 --- a/packages/block-editor/src/components/rich-text/format-toolbar/index.js +++ b/packages/block-editor/src/components/rich-text/format-toolbar/index.js @@ -19,12 +19,14 @@ const FormatToolbar = () => { return (
- { [ 'bold', 'italic', 'link' ].map( ( format ) => ( - - ) ) } + { [ 'bold', 'italic', 'link', 'text-color' ].map( + ( format ) => ( + + ) + ) } { ( fills ) => fills.length !== 0 && ( diff --git a/packages/format-library/package.json b/packages/format-library/package.json index a970f595c36ea..e106f3abdd17f 100644 --- a/packages/format-library/package.json +++ b/packages/format-library/package.json @@ -24,6 +24,7 @@ "@babel/runtime": "^7.8.3", "@wordpress/block-editor": "file:../block-editor", "@wordpress/components": "file:../components", + "@wordpress/data": "file:../data", "@wordpress/dom": "file:../dom", "@wordpress/element": "file:../element", "@wordpress/html-entities": "file:../html-entities", diff --git a/packages/format-library/src/default-formats.js b/packages/format-library/src/default-formats.js index 889998635d394..ce6a9b5f73679 100644 --- a/packages/format-library/src/default-formats.js +++ b/packages/format-library/src/default-formats.js @@ -8,5 +8,15 @@ import { italic } from './italic'; import { link } from './link'; import { strikethrough } from './strikethrough'; import { underline } from './underline'; +import { textColor } from './text-color'; -export default [ bold, code, image, italic, link, strikethrough, underline ]; +export default [ + bold, + code, + image, + italic, + link, + strikethrough, + underline, + textColor, +]; diff --git a/packages/format-library/src/style.scss b/packages/format-library/src/style.scss index a9ab600a7ad7d..9aae705658e94 100644 --- a/packages/format-library/src/style.scss +++ b/packages/format-library/src/style.scss @@ -1,2 +1,3 @@ @import "./image/style.scss"; @import "./link/style.scss"; +@import "./text-color/style.scss"; diff --git a/packages/format-library/src/text-color/index.js b/packages/format-library/src/text-color/index.js new file mode 100644 index 0000000000000..467467553f05c --- /dev/null +++ b/packages/format-library/src/text-color/index.js @@ -0,0 +1,94 @@ +/** + * External dependencies + */ +import { get } from 'lodash'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { useSelect } from '@wordpress/data'; +import { useCallback, useMemo, useState } from '@wordpress/element'; +import { RichTextToolbarButton } from '@wordpress/block-editor'; +import { Dashicon } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import { default as InlineColorUI, getActiveColor } from './inline'; + +const name = 'core/text-color'; +const title = __( 'Text Color' ); + +const EMPTY_ARRAY = []; + +function TextColorEdit( { value, onChange, isActive, activeAttributes } ) { + const colors = useSelect( ( select ) => { + const { getSettings } = select( 'core/block-editor' ); + if ( getSettings ) { + return get( getSettings(), [ 'colors' ], EMPTY_ARRAY ); + } + return EMPTY_ARRAY; + } ); + const [ isAddingColor, setIsAddingColor ] = useState( false ); + const enableIsAddingColor = useCallback( () => setIsAddingColor( true ), [ + setIsAddingColor, + ] ); + const disableIsAddingColor = useCallback( () => setIsAddingColor( false ), [ + setIsAddingColor, + ] ); + const colorIndicatorStyle = useMemo( () => { + const activeColor = getActiveColor( name, value, colors ); + if ( ! activeColor ) { + return undefined; + } + return { + backgroundColor: activeColor, + }; + }, [ value, colors ] ); + return ( + <> + + + { isActive && ( + + ) } + + } + title={ title } + onClick={ enableIsAddingColor } + /> + { isAddingColor && ( + + ) } + + ); +} + +export const textColor = { + name, + title, + tagName: 'span', + className: 'has-inline-color', + attributes: { + style: 'style', + class: 'class', + }, + edit: TextColorEdit, +}; diff --git a/packages/format-library/src/text-color/inline.js b/packages/format-library/src/text-color/inline.js new file mode 100644 index 0000000000000..db40c8367687f --- /dev/null +++ b/packages/format-library/src/text-color/inline.js @@ -0,0 +1,137 @@ +/** + * External dependencies + */ +import { get } from 'lodash'; + +/** + * WordPress dependencies + */ +import { useCallback, useMemo } from '@wordpress/element'; +import { useSelect } from '@wordpress/data'; +import { withSpokenMessages } from '@wordpress/components'; +import { getRectangleFromRange } from '@wordpress/dom'; +import { + applyFormat, + removeFormat, + getActiveFormat, +} from '@wordpress/rich-text'; +import { + ColorPalette, + URLPopover, + getColorClassName, + getColorObjectByColorValue, + getColorObjectByAttributeValues, +} from '@wordpress/block-editor'; + +export function getActiveColor( formatName, formatValue, colors ) { + const activeColorFormat = getActiveFormat( formatValue, formatName ); + if ( ! activeColorFormat ) { + return; + } + const styleColor = activeColorFormat.attributes.style; + if ( styleColor ) { + return styleColor.replace( new RegExp( `^color:\\s*` ), '' ); + } + const currentClass = activeColorFormat.attributes.class; + if ( currentClass ) { + const colorSlug = currentClass.replace( /.*has-(.*?)-color.*/, '$1' ); + return getColorObjectByAttributeValues( colors, colorSlug ).color; + } +} + +const ColorPopoverAtLink = ( { isActive, addingColor, value, ...props } ) => { + const anchorRect = useMemo( () => { + const selection = window.getSelection(); + const range = + selection.rangeCount > 0 ? selection.getRangeAt( 0 ) : null; + if ( ! range ) { + return; + } + + if ( addingColor ) { + return getRectangleFromRange( range ); + } + + let element = range.startContainer; + + // If the caret is right before the element, select the next element. + element = element.nextElementSibling || element; + + while ( element.nodeType !== window.Node.ELEMENT_NODE ) { + element = element.parentNode; + } + + const closest = element.closest( 'span' ); + if ( closest ) { + return closest.getBoundingClientRect(); + } + }, [ isActive, addingColor, value.start, value.end ] ); + + if ( ! anchorRect ) { + return null; + } + + return ; +}; + +const ColorPicker = ( { name, value, onChange } ) => { + const colors = useSelect( ( select ) => { + const { getSettings } = select( 'core/block-editor' ); + return get( getSettings(), [ 'colors' ], [] ); + } ); + const onColorChange = useCallback( + ( color ) => { + if ( color ) { + const colorObject = getColorObjectByColorValue( colors, color ); + onChange( + applyFormat( value, { + type: name, + attributes: colorObject + ? { + class: getColorClassName( + 'color', + colorObject.slug + ), + } + : { + style: `color:${ color }`, + }, + } ) + ); + } else { + onChange( removeFormat( value, name ) ); + } + }, + [ colors, onChange ] + ); + const activeColor = useMemo( () => getActiveColor( name, value, colors ), [ + name, + value, + colors, + ] ); + + return ; +}; + +const InlineColorUI = ( { + name, + value, + onChange, + onClose, + isActive, + addingColor, +} ) => { + return ( + + + + ); +}; + +export default withSpokenMessages( InlineColorUI ); diff --git a/packages/format-library/src/text-color/style.scss b/packages/format-library/src/text-color/style.scss new file mode 100644 index 0000000000000..0a988ae9b313f --- /dev/null +++ b/packages/format-library/src/text-color/style.scss @@ -0,0 +1,43 @@ +.components-inline-color__indicator { + position: absolute; + background: #000; + height: 3px; + width: 20px; + bottom: 6px; + left: auto; + right: auto; + margin: 0 5px; +} + +.components-inline-color-popover { + + .components-popover__content { + padding: 20px 18px; + + .components-color-palette { + margin-top: 0.6rem; + } + + .components-base-control__title { + display: block; + margin-bottom: 16px; + font-weight: 600; + color: #191e23; + } + + .component-color-indicator { + vertical-align: text-bottom; + } + } +} + +.format-library-text-color-button { + position: relative; +} +.format-library-text-color-button__indicator { + height: 4px; + width: 20px; + position: absolute; + bottom: 6px; + left: 8px; +}