• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Yoast / wordpress-seo / 5c4bcab7435f4e59f18137cffef98dc313dc7b7e

07 Feb 2024 04:45PM UTC coverage: 56.066% (-0.006%) from 56.072%
5c4bcab7435f4e59f18137cffef98dc313dc7b7e

Pull #21111

github

web-flow
Merge 3d657e00a into fb7b792eb
Pull Request #21111: Add-tooltip-to-UI-library

7564 of 13903 branches covered (0.0%)

Branch coverage included in aggregate %.

13486 of 23642 relevant lines covered (57.04%)

93599.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/packages/js/src/structured-data-blocks/how-to/components/HowToStep.js
1
/* External dependencies */
2
import PropTypes from "prop-types";
3
import { __ } from "@wordpress/i18n";
4
import appendSpace from "../../../components/higherorder/appendSpace";
5
import { isShallowEqualObjects } from "@wordpress/is-shallow-equal";
6
import { Component } from "@wordpress/element";
7
import { Button } from "@wordpress/components";
8
import { RichText, MediaUpload } from "@wordpress/block-editor";
9

10
const RichTextContentWithAppendedSpace = appendSpace( RichText.Content );
×
11

12
/**
13
 * A How-to step within a How-to block.
14
 */
15
export default class HowToStep extends Component {
16
        /**
17
         * Constructs a HowToStep editor component.
18
         *
19
         * @param {Object} props This component's properties.
20
         *
21
         * @returns {void}
22
         */
23
        constructor( props ) {
24
                super( props );
×
25

26
                this.onSelectImage  = this.onSelectImage.bind( this );
×
27
                this.onInsertStep   = this.onInsertStep.bind( this );
×
28
                this.onRemoveStep   = this.onRemoveStep.bind( this );
×
29
                this.onMoveStepUp   = this.onMoveStepUp.bind( this );
×
30
                this.onMoveStepDown = this.onMoveStepDown.bind( this );
×
31
                this.onFocusText    = this.onFocusText.bind( this );
×
32
                this.onFocusTitle   = this.onFocusTitle.bind( this );
×
33
                this.onChangeTitle  = this.onChangeTitle.bind( this );
×
34
                this.onChangeText   = this.onChangeText.bind( this );
×
35
        }
36

37
        /**
38
         * Handles the insert step button action.
39
         *
40
         * @returns {void}
41
         */
42
        onInsertStep() {
43
                this.props.insertStep( this.props.index );
×
44
        }
45

46
        /**
47
         * Handles the remove step button action.
48
         *
49
         * @returns {void}
50
         */
51
        onRemoveStep() {
52
                this.props.removeStep( this.props.index );
×
53
        }
54

55
        /**
56
         * Handles the move step up button action.
57
         *
58
         * @returns {void}
59
         */
60
        onMoveStepUp() {
61
                if ( this.props.isFirst ) {
×
62
                        return;
×
63
                }
64
                this.props.onMoveUp( this.props.index );
×
65
        }
66

67
        /**
68
         * Handles the move step down button action.
69
         *
70
         * @returns {void}
71
         */
72
        onMoveStepDown() {
73
                if ( this.props.isLast ) {
×
74
                        return;
×
75
                }
76
                this.props.onMoveDown( this.props.index );
×
77
        }
78

79
        /**
80
         * Handles the focus event on the title editor.
81
         *
82
         * @returns {void}
83
         */
84
        onFocusTitle() {
85
                this.props.onFocus( this.props.index, "name" );
×
86
        }
87

88
        /**
89
         * Handles the focus event on the text editor.
90
         *
91
         * @returns {void}
92
         */
93
        onFocusText() {
94
                this.props.onFocus( this.props.index, "text" );
×
95
        }
96

97
        /**
98
         * Handles the on change event on the title editor.
99
         *
100
         * @param {string} value The new title.
101
         *
102
         * @returns {void}
103
         */
104
        onChangeTitle( value ) {
105
                const {
106
                        onChange,
107
                        index,
108
                        step: {
109
                                text,
110
                                name,
111
                        },
112
                } = this.props;
×
113

114
                onChange( value, text, name, text, index );
×
115
        }
116

117
        /**
118
         * Handles the on change event on the text editor.
119
         *
120
         * @param {string} value The new text.
121
         *
122
         * @returns {void}
123
         */
124
        onChangeText( value ) {
125
                const {
126
                        onChange,
127
                        index,
128
                        step: {
129
                                text,
130
                                name,
131
                        },
132
                } = this.props;
×
133

134
                onChange( name, value, name, text, index );
×
135
        }
136

137
        /**
138
         * Renders the media upload button.
139
         *
140
         * @param {object} props      The receive props.
141
         * @param {func}   props.open Opens the media upload dialog.
142
         *
143
         * @returns {wp.Element} The media upload button.
144
         */
145
        getMediaUploadButton( props ) {
146
                return (
×
147
                        <Button
148
                                className="schema-how-to-step-button how-to-step-add-media"
149
                                icon="insert"
150
                                onClick={ props.open }
151
                        >
152
                                { __( "Add image", "wordpress-seo" ) }
153
                        </Button>
154
                );
155
        }
156

157
        /**
158
         * The insert and remove step buttons.
159
         *
160
         * @returns {wp.Element} The buttons.
161
         */
162
        getButtons() {
163
                const {
164
                        step,
165
                } = this.props;
×
166

167
                return <div className="schema-how-to-step-button-container">
×
168
                        { ! HowToStep.getImageSrc( step.text ) &&
×
169
                        <MediaUpload
170
                                onSelect={ this.onSelectImage }
171
                                allowedTypes={ [ "image" ] }
172
                                value={ step.id }
173
                                render={ this.getMediaUploadButton }
174
                        />
175
                        }
176
                        <Button
177
                                className="schema-how-to-step-button"
178
                                icon="trash"
179
                                label={ __( "Delete step", "wordpress-seo" ) }
180
                                onClick={ this.onRemoveStep }
181
                        />
182
                        <Button
183
                                className="schema-how-to-step-button"
184
                                icon="insert"
185
                                label={ __( "Insert step", "wordpress-seo" ) }
186
                                onClick={ this.onInsertStep }
187
                        />
188
                </div>;
189
        }
190

191
        /**
192
         * The mover buttons.
193
         *
194
         * @returns {Component} the buttons.
195
         */
196
        getMover() {
197
                return <div className="schema-how-to-step-mover">
×
198
                        <Button
199
                                className="editor-block-mover__control"
200
                                onClick={ this.onMoveStepUp }
201
                                icon="arrow-up-alt2"
202
                                label={ __( "Move step up", "wordpress-seo" ) }
203
                                aria-disabled={ this.props.isFirst }
204
                        />
205
                        <Button
206
                                className="editor-block-mover__control"
207
                                onClick={ this.onMoveStepDown }
208
                                icon="arrow-down-alt2"
209
                                label={ __( "Move step down", "wordpress-seo" ) }
210
                                aria-disabled={ this.props.isLast }
211
                        />
212
                </div>;
213
        }
214

215
        /**
216
         * Callback when an image from the media library has been selected.
217
         *
218
         * @param {Object} media The selected image.
219
         *
220
         * @returns {void}
221
         */
222
        onSelectImage( media ) {
223
                const {
224
                        index,
225
                        step: {
226
                                name,
227
                                text,
228
                        },
229
                } = this.props;
×
230

231
                let newText = text.slice();
×
232
                const image = <img className={ `wp-image-${ media.id }` } alt={ media.alt } src={ media.url } style="max-width:100%;" />;
×
233

234
                if ( newText.push ) {
×
235
                        newText.push( image );
×
236
                } else {
237
                        newText = [ newText, image ];
×
238
                }
239

240
                this.props.onChange( name, newText, name, text, index );
×
241
        }
242

243
        /**
244
         * Returns the image src from step contents.
245
         *
246
         * @param {array} contents The step contents.
247
         *
248
         * @returns {string|boolean} The image src or false if none is found.
249
         */
250
        static getImageSrc( contents ) {
251
                if ( ! contents || ! contents.filter ) {
×
252
                        return false;
×
253
                }
254

255
                const image = contents.filter( ( node ) => node && node.type && node.type === "img" )[ 0 ];
×
256

257
                if ( ! image ) {
×
258
                        return false;
×
259
                }
260

261
                return image.props.src;
×
262
        }
263

264
        /**
265
         * Perform a shallow equal to prevent every step from being rerendered.
266
         *
267
         * @param {object} nextProps The next props the component will receive.
268
         *
269
         * @returns {boolean} Whether or not the component should perform an update.
270
         */
271
        shouldComponentUpdate( nextProps ) {
272
                return ! isShallowEqualObjects( nextProps, this.props );
×
273
        }
274

275
        /**
276
         * Returns the component of the given How-to step to be rendered in a WordPress post
277
         * (e.g. not in the editor).
278
         *
279
         * @param {object} step The how-to step.
280
         *
281
         * @returns {wp.Element} The component to be rendered.
282
         */
283
        static Content( step ) {
284
                return (
×
285
                        <li className={ "schema-how-to-step" } id={ step.id } key={ step.id }>
286
                                <RichTextContentWithAppendedSpace
287
                                        tagName="strong"
288
                                        className="schema-how-to-step-name"
289
                                        key={ step.id + "-name" }
290
                                        value={ step.name }
291
                                />
292
                                <RichTextContentWithAppendedSpace
293
                                        tagName="p"
294
                                        className="schema-how-to-step-text"
295
                                        key={ step.id + "-text" }
296
                                        value={ step.text }
297
                                />
298
                        </li>
299
                );
300
        }
301

302
        /**
303
         * Renders this component.
304
         *
305
         * @returns {wp.Element} The how-to step editor.
306
         */
307
        render() {
308
                const {
309
                        index,
310
                        step,
311
                        isSelected,
312
                        isUnorderedList,
313
                } = this.props;
×
314

315
                const { id, name, text } = step;
×
316

317
                return (
×
318
                        <li className="schema-how-to-step" key={ id }>
319
                                <span className="schema-how-to-step-number">
320
                                        { isUnorderedList
×
321
                                                ? "•"
322
                                                : ( index + 1 ) + "."
323
                                        }
324
                                </span>
325
                                <RichText
326
                                        identifier={ `${ id }-name` }
327
                                        className="schema-how-to-step-name"
328
                                        tagName="p"
329
                                        key={ `${ id }-name` }
330
                                        value={ name }
331
                                        onChange={ this.onChangeTitle }
332
                                        onFocus={ this.onFocusTitle }
333
                                        // The unstableOnFocus prop is added for backwards compatibility with Gutenberg versions <= 15.1 (WordPress 6.2).
334
                                        unstableOnFocus={ this.onFocusTitle }
335
                                        placeholder={ __( "Enter a step title", "wordpress-seo" ) }
336
                                        allowedFormats={ [ "core/italic", "core/strikethrough", "core/link", "core/annotation" ] }
337
                                />
338
                                <RichText
339
                                        identifier={ `${ id }-text` }
340
                                        className="schema-how-to-step-text"
341
                                        tagName="p"
342
                                        key={ `${ id }-text` }
343
                                        value={ text }
344
                                        onChange={ this.onChangeText }
345
                                        onFocus={ this.onFocusText }
346
                                        // The unstableOnFocus prop is added for backwards compatibility with Gutenberg versions <= 15.1 (WordPress 6.2).
347
                                        unstableOnFocus={ this.onFocusText }
348
                                        placeholder={ __( "Enter a step description", "wordpress-seo" ) }
349
                                />
350
                                { isSelected &&
×
351
                                        <div className="schema-how-to-step-controls-container">
352
                                                { this.getMover() }
353
                                                { this.getButtons() }
354
                                        </div>
355
                                }
356
                        </li>
357
                );
358
        }
359
}
360

361
HowToStep.propTypes = {
×
362
        index: PropTypes.number.isRequired,
363
        step: PropTypes.object.isRequired,
364
        onChange: PropTypes.func.isRequired,
365
        insertStep: PropTypes.func.isRequired,
366
        removeStep: PropTypes.func.isRequired,
367
        onFocus: PropTypes.func.isRequired,
368
        onMoveUp: PropTypes.func.isRequired,
369
        onMoveDown: PropTypes.func.isRequired,
370
        isSelected: PropTypes.bool.isRequired,
371
        isFirst: PropTypes.bool.isRequired,
372
        isLast: PropTypes.bool.isRequired,
373
        isUnorderedList: PropTypes.bool,
374
};
375

376
HowToStep.defaultProps = {
×
377
        isUnorderedList: false,
378
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc