UNPKG

7.38 kBJavaScriptView Raw
1/**
2 * External dependencies
3 */
4import classnames from 'classnames';
5
6/**
7 * WordPress dependencies
8 */
9import { __, _x } from '@wordpress/i18n';
10import {
11 Component,
12 Fragment,
13} from '@wordpress/element';
14import {
15 PanelBody,
16 ToggleControl,
17 Toolbar,
18 withFallbackStyles,
19} from '@wordpress/components';
20import {
21 withColors,
22 AlignmentToolbar,
23 BlockControls,
24 ContrastChecker,
25 FontSizePicker,
26 InspectorControls,
27 PanelColorSettings,
28 RichText,
29 withFontSizes,
30} from '@wordpress/block-editor';
31import { createBlock } from '@wordpress/blocks';
32import { compose } from '@wordpress/compose';
33import { withSelect } from '@wordpress/data';
34
35const { getComputedStyle } = window;
36
37const name = 'core/paragraph';
38
39const applyFallbackStyles = withFallbackStyles( ( node, ownProps ) => {
40 const { textColor, backgroundColor, fontSize, customFontSize } = ownProps.attributes;
41 const editableNode = node.querySelector( '[contenteditable="true"]' );
42 //verify if editableNode is available, before using getComputedStyle.
43 const computedStyles = editableNode ? getComputedStyle( editableNode ) : null;
44 return {
45 fallbackBackgroundColor: backgroundColor || ! computedStyles ? undefined : computedStyles.backgroundColor,
46 fallbackTextColor: textColor || ! computedStyles ? undefined : computedStyles.color,
47 fallbackFontSize: fontSize || customFontSize || ! computedStyles ? undefined : parseInt( computedStyles.fontSize ) || undefined,
48 };
49} );
50
51class ParagraphBlock extends Component {
52 constructor() {
53 super( ...arguments );
54
55 this.onReplace = this.onReplace.bind( this );
56 this.toggleDropCap = this.toggleDropCap.bind( this );
57 this.splitBlock = this.splitBlock.bind( this );
58 }
59
60 onReplace( blocks ) {
61 const { attributes, onReplace } = this.props;
62 onReplace( blocks.map( ( block, index ) => (
63 index === 0 && block.name === name ?
64 { ...block,
65 attributes: {
66 ...attributes,
67 ...block.attributes,
68 },
69 } :
70 block
71 ) ) );
72 }
73
74 toggleDropCap() {
75 const { attributes, setAttributes } = this.props;
76 setAttributes( { dropCap: ! attributes.dropCap } );
77 }
78
79 getDropCapHelp( checked ) {
80 return checked ? __( 'Showing large initial letter.' ) : __( 'Toggle to show a large initial letter.' );
81 }
82
83 /**
84 * Split handler for RichText value, namely when content is pasted or the
85 * user presses the Enter key.
86 *
87 * @param {?Array} before Optional before value, to be used as content
88 * in place of what exists currently for the
89 * block. If undefined, the block is deleted.
90 * @param {?Array} after Optional after value, to be appended in a new
91 * paragraph block to the set of blocks passed
92 * as spread.
93 * @param {...WPBlock} blocks Optional blocks inserted between the before
94 * and after value blocks.
95 */
96 splitBlock( before, after, ...blocks ) {
97 const {
98 attributes,
99 insertBlocksAfter,
100 setAttributes,
101 onReplace,
102 } = this.props;
103
104 if ( after !== null ) {
105 // Append "After" content as a new paragraph block to the end of
106 // any other blocks being inserted after the current paragraph.
107 blocks.push( createBlock( name, { content: after } ) );
108 }
109
110 if ( blocks.length && insertBlocksAfter ) {
111 insertBlocksAfter( blocks );
112 }
113
114 const { content } = attributes;
115 if ( before === null ) {
116 // If before content is omitted, treat as intent to delete block.
117 onReplace( [] );
118 } else if ( content !== before ) {
119 // Only update content if it has in-fact changed. In case that user
120 // has created a new paragraph at end of an existing one, the value
121 // of before will be strictly equal to the current content.
122 setAttributes( { content: before } );
123 }
124 }
125
126 render() {
127 const {
128 attributes,
129 setAttributes,
130 mergeBlocks,
131 onReplace,
132 className,
133 backgroundColor,
134 textColor,
135 setBackgroundColor,
136 setTextColor,
137 fallbackBackgroundColor,
138 fallbackTextColor,
139 fallbackFontSize,
140 fontSize,
141 setFontSize,
142 isRTL,
143 } = this.props;
144
145 const {
146 align,
147 content,
148 dropCap,
149 placeholder,
150 direction,
151 } = attributes;
152
153 return (
154 <Fragment>
155 <BlockControls>
156 <AlignmentToolbar
157 value={ align }
158 onChange={ ( nextAlign ) => {
159 setAttributes( { align: nextAlign } );
160 } }
161 />
162 { isRTL && (
163 <Toolbar
164 controls={ [
165 {
166 icon: 'editor-ltr',
167 title: _x( 'Left to right', 'editor button' ),
168 isActive: direction === 'ltr',
169 onClick() {
170 const nextDirection = direction === 'ltr' ? undefined : 'ltr';
171 setAttributes( {
172 direction: nextDirection,
173 } );
174 },
175 },
176 ] }
177 />
178 ) }
179 </BlockControls>
180 <InspectorControls>
181 <PanelBody title={ __( 'Text Settings' ) } className="blocks-font-size">
182 <FontSizePicker
183 fallbackFontSize={ fallbackFontSize }
184 value={ fontSize.size }
185 onChange={ setFontSize }
186 />
187 <ToggleControl
188 label={ __( 'Drop Cap' ) }
189 checked={ !! dropCap }
190 onChange={ this.toggleDropCap }
191 help={ this.getDropCapHelp }
192 />
193 </PanelBody>
194 <PanelColorSettings
195 title={ __( 'Color Settings' ) }
196 initialOpen={ false }
197 colorSettings={ [
198 {
199 value: backgroundColor.color,
200 onChange: setBackgroundColor,
201 label: __( 'Background Color' ),
202 },
203 {
204 value: textColor.color,
205 onChange: setTextColor,
206 label: __( 'Text Color' ),
207 },
208 ] }
209 >
210 <ContrastChecker
211 { ...{
212 textColor: textColor.color,
213 backgroundColor: backgroundColor.color,
214 fallbackTextColor,
215 fallbackBackgroundColor,
216 } }
217 fontSize={ fontSize.size }
218 />
219 </PanelColorSettings>
220 </InspectorControls>
221 <RichText
222 identifier="content"
223 tagName="p"
224 className={ classnames( 'wp-block-paragraph', className, {
225 'has-text-color': textColor.color,
226 'has-background': backgroundColor.color,
227 'has-drop-cap': dropCap,
228 [ backgroundColor.class ]: backgroundColor.class,
229 [ textColor.class ]: textColor.class,
230 [ fontSize.class ]: fontSize.class,
231 } ) }
232 style={ {
233 backgroundColor: backgroundColor.color,
234 color: textColor.color,
235 fontSize: fontSize.size ? fontSize.size + 'px' : undefined,
236 textAlign: align,
237 direction,
238 } }
239 value={ content }
240 onChange={ ( nextContent ) => {
241 setAttributes( {
242 content: nextContent,
243 } );
244 } }
245 unstableOnSplit={ this.splitBlock }
246 onMerge={ mergeBlocks }
247 onReplace={ this.onReplace }
248 onRemove={ () => onReplace( [] ) }
249 aria-label={ content ? __( 'Paragraph block' ) : __( 'Empty block; start writing or type forward slash to choose a block' ) }
250 placeholder={ placeholder || __( 'Start writing or type / to choose a block' ) }
251 />
252 </Fragment>
253 );
254 }
255}
256
257const ParagraphEdit = compose( [
258 withColors( 'backgroundColor', { textColor: 'color' } ),
259 withFontSizes( 'fontSize' ),
260 applyFallbackStyles,
261 withSelect( ( select ) => {
262 const { getSettings } = select( 'core/block-editor' );
263
264 return {
265 isRTL: getSettings().isRTL,
266 };
267 } ),
268] )( ParagraphBlock );
269
270export default ParagraphEdit;