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

Yoast / wordpress-seo / 41e0a1d1f695fd44d9952bb2417fefb0e94d089e

13 Dec 2024 05:03PM UTC coverage: 54.386%. Remained the same
41e0a1d1f695fd44d9952bb2417fefb0e94d089e

Pull #21918

github

web-flow
Merge 2072c8ea7 into b602854da
Pull Request #21918: Upgrade eslint to 9

7678 of 13701 branches covered (56.04%)

Branch coverage included in aggregate %.

4 of 10 new or added lines in 10 files covered. (40.0%)

4 existing lines in 4 files now uncovered.

29938 of 55464 relevant lines covered (53.98%)

41379.69 hits per line

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

25.74
/packages/yoastseo/src/app.js
1
import { setLocaleData } from "@wordpress/i18n";
2
import { debounce, defaultsDeep, forEach, isArray, isEmpty, isFunction, isObject, isUndefined, merge, noop, throttle } from "lodash";
3

4
import AssessorPresenter from "./scoring/renderers/AssessorPresenter.js";
5
import { measureTextWidth } from "./helpers";
6
import MissingArgument from "./errors/missingArgument.js";
7
import Paper from "./values/Paper.js";
8
import Pluggable from "./pluggable.js";
9
import removeHtmlBlocks from "./languageProcessing/helpers/html/htmlParser.js";
10

11
// Assessors.
12
import ContentAssessor from "./scoring/assessors/contentAssessor.js";
13
import CornerstoneContentAssessor from "./scoring/assessors/cornerstone/contentAssessor.js";
14
import CornerstoneSEOAssessor from "./scoring/assessors/cornerstone/seoAssessor.js";
15
import SEOAssessor from "./scoring/assessors/seoAssessor.js";
16

17
const inputDebounceDelay = 800;
518✔
18

19
/**
20
 * Default config for YoastSEO.js
21
 *
22
 * @type {Object}
23
 */
24
const defaults = {
518✔
25
        callbacks: {
26
                bindElementEvents: noop,
27
                updateSnippetValues: noop,
28
                saveScores: noop,
29
                saveContentScore: noop,
30
                updatedContentResults: noop,
31
                updatedKeywordsResults: noop,
32
        },
33
        sampleText: {
34
                baseUrl: "example.org/",
35
                snippetCite: "example-post/",
36
                title: "",
37
                keyword: "Choose a focus keyword",
38
                meta: "",
39
                text: "Start writing your text!",
40
        },
41
        queue: [ "wordCount",
42
                "keywordDensity",
43
                "subHeadings",
44
                "stopwords",
45
                "fleschReading",
46
                "linkCount",
47
                "imageCount",
48
                "slugKeyword",
49
                "urlLength",
50
                "metaDescription",
51
                "pageTitleKeyword",
52
                "pageTitleWidth",
53
                "firstParagraph",
54
                "'keywordDoubles" ],
55
        typeDelay: 3000,
56
        typeDelayStep: 1500,
57
        maxTypeDelay: 5000,
58
        dynamicDelay: true,
59
        locale: "en_US",
60
        translations: {
61
                domain: "wordpress-seo",
62
                // eslint-disable-next-line camelcase
63
                locale_data: {
64
                        "wordpress-seo": {
65
                                "": {},
66
                        },
67
                },
68
        },
69
        replaceTarget: [],
70
        resetTarget: [],
71
        elementTarget: [],
72
        marker: noop,
73
        keywordAnalysisActive: true,
74
        contentAnalysisActive: true,
75
        debounceRefresh: true,
76
};
77

78
/**
79
 * Check arguments passed to the App to check if all necessary arguments are set.
80
 *
81
 * @private
82
 * @param {Object} args The arguments object passed to the App.
83
 * @returns {void}
84
 */
85
function verifyArguments( args ) {
86
        if ( ! isObject( args.callbacks.getData ) ) {
14✔
87
                throw new MissingArgument( "The app requires an object with a getdata callback." );
8✔
88
        }
89

90
        if ( ! isObject( args.targets ) ) {
6✔
91
                throw new MissingArgument( "`targets` is a required App argument, `targets` is not an object." );
2✔
92
        }
93
}
94

95
/**
96
 * This should return an object with the given properties.
97
 *
98
 * @callback YoastSEO.App~getData
99
 * @returns {Object} data. The data object containing the following properties: keyword, meta, text, metaTitle, title, url, excerpt.
100
 * @returns {String} data.keyword The keyword that should be used.
101
 * @returns {String} data.meta The meta description to analyze.
102
 * @returns {String} data.text The text to analyze.
103
 * @returns {String} data.metaTitle The text in the HTML title tag.
104
 * @returns {String} data.title The title to analyze.
105
 * @returns {String} data.url The URL for the given page
106
 * @returns {String} data.excerpt Excerpt for the pages
107
 */
108

109
/**
110
 * @callback YoastSEO.App~getAnalyzerInput
111
 *
112
 * @returns {Array} An array containing the analyzer queue.
113
 */
114

115
/**
116
 * @callback YoastSEO.App~bindElementEvents
117
 *
118
 * @param {YoastSEO.App} app A reference to the YoastSEO.App from where this is called.
119
 */
120

121
/**
122
 * @callback YoastSEO.App~updateSnippetValues
123
 *
124
 * @param {Object} ev The event emitted from the DOM.
125
 */
126

127
/**
128
 * @callback YoastSEO.App~saveScores
129
 *
130
 * @param {int} score The overall keyword score as determined by the assessor.
131
 * @param {AssessorPresenter} assessorPresenter The assessor presenter that will be used to render the keyword score.
132
 */
133

134
/**
135
 * @callback YoastSEO.App~saveContentScore
136
 *
137
 * @param {int} score The overall content score as determined by the assessor.
138
 * @param {AssessorPresenter} assessorPresenter The assessor presenter that will be used to render the content score.
139
 */
140

141
/**
142
 * @callback YoastSEO.App~updatedContentResults
143
 *
144
 * @param {Object[]} result The updated content analysis results.
145
 * @param {number} result[].score The SEO score.
146
 * @param {string} result[].rating String representation of the SEO score.
147
 * @param {string} result[].text Textual explanation of the score.
148
 * @param {number} overallContentScore The overall content SEO score.
149
 */
150

151
/**
152
 * @callback YoastSEO.App~updatedKeywordsResults
153
 *
154
 * @param {Object[]} result The updated keywords analysis results.
155
 * @param {number} result[].score The SEO score.
156
 * @param {string} result[].rating String representation of the SEO score.
157
 * @param {string} result[].text Textual explanation of the score.
158
 * @param {number} overallContentScore The overall keywords SEO score.
159
 */
160

161
/**
162
 * Represents the main YoastSEO App.
163
 */
164
class App {
165
        /**
166
         * Loader for the analyzer, loads the eventbinder and the elementdefiner
167
         *
168
         * @param {Object} args The arguments passed to the loader.
169
         * @param {Object} args.translations Jed compatible translations.
170
         * @param {Object} args.targets Targets to retrieve or set on.
171
         * @param {String} args.targets.snippet ID for the snippet preview element.
172
         * @param {String} args.targets.output ID for the element to put the output of the analyzer in.
173
         * @param {int} args.typeDelay Number of milliseconds to wait between typing to refresh the analyzer output.
174
         * @param {boolean} args.dynamicDelay   Whether to enable dynamic delay, will ignore type delay if the analyzer takes a long time. Applicable on slow devices.
175
         * @param {int} args.maxTypeDelay The maximum amount of type delay even if dynamic delay is on.
176
         * @param {int} args.typeDelayStep The amount with which to increase the typeDelay on each step when dynamic delay is enabled.
177
         * @param {Object} args.callbacks The callbacks that the app requires.
178
         * @param {Object} args.assessor The Assessor to use instead of the default assessor.
179
         * @param {YoastSEO.App~getData} args.callbacks.getData Called to retrieve input data
180
         * @param {YoastSEO.App~getAnalyzerInput} args.callbacks.getAnalyzerInput Called to retrieve input for the analyzer.
181
         * @param {YoastSEO.App~bindElementEvents} args.callbacks.bindElementEvents Called to bind events to the DOM elements.
182
         * @param {YoastSEO.App~updateSnippetValues} args.callbacks.updateSnippetValues Called when the snippet values need to be updated.
183
         * @param {YoastSEO.App~saveScores} args.callbacks.saveScores Called when the score has been determined by the analyzer.
184
         * @param {YoastSEO.App~saveContentScore} args.callback.saveContentScore Called when the content score has been determined by the assessor.
185
         * @param {YoastSEO.App~updatedContentResults} args.callbacks.updatedContentResults Called when the score has been determined by the analyzer.
186
         * @param {YoastSEO.App~updatedKeywordsResults} args.callback.updatedKeywordsResults Called when the content score has been determined by the assessor.
187
         * @param {Function} args.callbacks.saveSnippetData Function called when the snippet data is changed.
188
         * @param {Function} args.marker The marker to use to apply the list of marks retrieved from an assessment.
189
         *
190
         * @param {boolean} [args.debouncedRefresh=true] Whether or not to debounce the refresh function. Defaults to true.
191
         * @param {Researcher} args.researcher The Researcher object to be used.
192
         *
193
         * @constructor
194
         */
195
        constructor( args ) {
196
                if ( ! isObject( args ) ) {
14✔
197
                        args = {};
2✔
198
                }
199

200
                defaultsDeep( args, defaults );
14✔
201

202
                verifyArguments( args );
14✔
203

204
                this.config = args;
4✔
205

206
                if ( args.debouncedRefresh === true ) {
4!
207
                        this.refresh = debounce( this.refresh.bind( this ), inputDebounceDelay );
×
208
                }
209
                this._pureRefresh = throttle( this._pureRefresh.bind( this ), this.config.typeDelay );
4✔
210

211
                this.callbacks = this.config.callbacks;
4✔
212

213
                this.researcher = this.config.researcher;
4✔
214

215
                setLocaleData( this.config.translations.locale_data[ "wordpress-seo" ], "wordpress-seo" );
4✔
216

217
                this.initializeAssessors( args );
4✔
218

219
                this.pluggable = new Pluggable( this );
4✔
220

221
                this.getData();
4✔
222

223
                this.defaultOutputElement = this.getDefaultOutputElement( args );
4✔
224

225
                if ( this.defaultOutputElement !== "" ) {
4!
226
                        this.showLoadingDialog();
4✔
227
                }
228

229
                this._assessorOptions = {
4✔
230
                        useCornerStone: false,
231
                };
232

233
                this.initAssessorPresenters();
4✔
234
        }
235

236
        /**
237
         * Returns the default output element based on which analyses are active.
238
         *
239
         * @param {Object} args The arguments passed to the App.
240
         * @returns {string} The ID of the target that is active.
241
         */
242
        getDefaultOutputElement( args ) {
243
                if ( args.keywordAnalysisActive ) {
4!
244
                        return args.targets.output;
4✔
245
                }
246

247
                if ( args.contentAnalysisActive ) {
×
248
                        return args.targets.contentOutput;
×
249
                }
250

251
                return "";
×
252
        }
253

254
        /**
255
         * Sets the assessors based on the assessor options and refreshes them.
256
         *
257
         * @param {Object} assessorOptions The specific options.
258
         * @returns {void}
259
         */
260
        changeAssessorOptions( assessorOptions ) {
261
                this._assessorOptions = merge( this._assessorOptions, assessorOptions );
×
262

263
                // Set the assessors based on the new assessor options.
264
                this.seoAssessor = this.getSeoAssessor();
×
265
                this.contentAssessor = this.getContentAssessor();
×
266

267
                // Refresh everything so the user sees the changes.
268
                this.initAssessorPresenters();
×
269
                this.refresh();
×
270
        }
271

272
        /**
273
         * Returns an instance of the seo assessor to use.
274
         *
275
         * @returns {Assessor} The assessor instance.
276
         */
277
        getSeoAssessor() {
278
                const { useCornerStone } = this._assessorOptions;
×
279
                return useCornerStone ? this.cornerStoneSeoAssessor : this.defaultSeoAssessor;
×
280
        }
281

282
        /**
283
         * Returns an instance of the content assessor to use.
284
         *
285
         * @returns {Assessor} The assessor instance.
286
         */
287
        getContentAssessor() {
288
                const { useCornerStone } = this._assessorOptions;
×
289
                return useCornerStone ? this.cornerStoneContentAssessor : this.defaultContentAssessor;
×
290
        }
291

292
        /**
293
         * Initializes assessors based on whether the respective analysis is active.
294
         *
295
         * @param {Object} args The arguments passed to the App.
296
         * @returns {void}
297
         */
298
        initializeAssessors( args ) {
299
                this.initializeSEOAssessor( args );
4✔
300
                this.initializeContentAssessor( args );
4✔
301
        }
302

303
        /**
304
         * Initializes the SEO assessor.
305
         *
306
         * @param {Object} args The arguments passed to the App.
307
         * @returns {void}
308
         */
309
        initializeSEOAssessor( args ) {
310
                if ( ! args.keywordAnalysisActive ) {
4!
311
                        return;
×
312
                }
313

314
                this.defaultSeoAssessor = new SEOAssessor( this.researcher, { marker: this.config.marker } );
4✔
315
                this.cornerStoneSeoAssessor = new CornerstoneSEOAssessor( this.researcher, { marker: this.config.marker } );
4✔
316

317
                // Set the assessor
318
                if ( isUndefined( args.seoAssessor ) ) {
4!
319
                        this.seoAssessor = this.defaultSeoAssessor;
4✔
320
                } else {
321
                        this.seoAssessor = args.seoAssessor;
×
322
                }
323
        }
324

325
        /**
326
         * Initializes the content assessor.
327
         *
328
         * @param {Object} args The arguments passed to the App.
329
         * @returns {void}
330
         */
331
        initializeContentAssessor( args ) {
332
                if ( ! args.contentAnalysisActive ) {
4!
333
                        return;
×
334
                }
335

336
                this.defaultContentAssessor = new ContentAssessor( this.researcher, { marker: this.config.marker, locale: this.config.locale } );
4✔
337
                this.cornerStoneContentAssessor = new CornerstoneContentAssessor( this.researcher,
4✔
338
                        { marker: this.config.marker, locale: this.config.locale }
339
                );
340

341
                // Set the content assessor
342
                if ( isUndefined( args._contentAssessor ) ) {
4!
343
                        this.contentAssessor = this.defaultContentAssessor;
4✔
344
                } else {
345
                        this.contentAssessor = args._contentAssessor;
×
346
                }
347
        }
348

349
        /**
350
         * Extends the config with defaults.
351
         *
352
         * @param {Object} args The arguments to be extended.
353
         *
354
         * @returns {Object} The extended arguments.
355
         */
356
        extendConfig( args ) {
357
                args.sampleText = this.extendSampleText( args.sampleText );
×
358
                args.locale = args.locale || "en_US";
×
359

360
                return args;
×
361
        }
362

363
        /**
364
         * Extends sample text config with defaults.
365
         *
366
         * @param {Object} sampleText The sample text to be extended.
367
         * @returns {Object} The extended sample text.
368
         */
369
        extendSampleText( sampleText ) {
370
                const defaultSampleText = defaults.sampleText;
×
371

372
                if ( isUndefined( sampleText ) ) {
×
373
                        return defaultSampleText;
×
374
                }
375

376
                for ( const key in sampleText ) {
×
377
                        if ( isUndefined( sampleText[ key ] ) ) {
×
378
                                sampleText[ key ] = defaultSampleText[ key ];
×
379
                        }
380
                }
381

382
                return sampleText;
×
383
        }
384

385
        /**
386
         * Registers a custom data callback.
387
         *
388
         * @param {Function} callback The callback to register.
389
         *
390
         * @returns {void}
391
         */
392
        registerCustomDataCallback( callback ) {
393
                if ( ! this.callbacks.custom ) {
×
394
                        this.callbacks.custom = [];
×
395
                }
396

397
                if ( isFunction( callback ) ) {
×
398
                        this.callbacks.custom.push( callback );
×
399
                }
400
        }
401

402
        /**
403
         * Retrieves data from the callbacks.getData and applies modification to store these in this.rawData.
404
         *
405
         * @returns {void}
406
         */
407
        getData() {
408
                this.rawData = this.callbacks.getData();
4✔
409

410
                // Add the custom data to the raw data.
411
                if ( isArray( this.callbacks.custom ) ) {
4!
412
                        this.callbacks.custom.forEach( ( customCallback ) => {
×
413
                                const customData = customCallback();
×
414

415
                                this.rawData = merge( this.rawData, customData );
×
416
                        } );
417
                }
418

419
                if ( this.pluggable.loaded ) {
4!
420
                        this.rawData.metaTitle = this.pluggable._applyModifications( "data_page_title", this.rawData.metaTitle );
×
421
                        this.rawData.meta = this.pluggable._applyModifications( "data_meta_desc", this.rawData.meta );
×
422
                }
423

424
                this.rawData.titleWidth = measureTextWidth( this.rawData.metaTitle );
4✔
425

426
                this.rawData.locale = this.config.locale;
4✔
427
        }
428

429
        /**
430
         * Refreshes the analyzer and output of the analyzer, is debounced for a better experience.
431
         *
432
         * @returns {void}
433
         */
434
        refresh() {
435
                // Until all plugins are loaded, do not trigger a refresh.
436
                if ( ! this.pluggable.loaded ) {
×
437
                        return;
×
438
                }
439

440
                this._pureRefresh();
×
441
        }
442

443
        /**
444
         * Refreshes the analyzer and output of the analyzer, is throttled to prevent performance issues.
445
         *
446
         * @returns {void}
447
         *
448
         * @private
449
         */
450
        _pureRefresh() {
451
                this.getData();
×
452
                this.runAnalyzer();
×
453
        }
454

455
        /**
456
         * Initializes the assessor presenters for content and SEO analysis.
457
         *
458
         * @returns {void}
459
         */
460
        initAssessorPresenters() {
461
                // Pass the assessor result through to the formatter.
462
                if ( ! isUndefined( this.config.targets.output ) ) {
4✔
463
                        this.seoAssessorPresenter = new AssessorPresenter( {
2✔
464
                                targets: {
465
                                        output: this.config.targets.output,
466
                                },
467
                                assessor: this.seoAssessor,
468
                        } );
469
                }
470

471
                if ( ! isUndefined( this.config.targets.contentOutput ) ) {
4!
472
                        // Pass the assessor result through to the formatter.
473
                        this.contentAssessorPresenter = new AssessorPresenter( {
×
474
                                targets: {
475
                                        output: this.config.targets.contentOutput,
476
                                },
477
                                assessor: this.contentAssessor,
478
                        } );
479
                }
480
        }
481

482
        /**
483
         * Sets the startTime timestamp.
484
         *
485
         * @returns {void}
486
         */
487
        startTime() {
488
                this.startTimestamp = new Date().getTime();
×
489
        }
490

491
        /**
492
         * Sets the endTime timestamp and compares with startTime to determine typeDelayincrease.
493
         *
494
         * @returns {void}
495
         */
496
        endTime() {
497
                this.endTimestamp = new Date().getTime();
×
498
                if ( this.endTimestamp - this.startTimestamp > this.config.typeDelay ) {
×
499
                        if ( this.config.typeDelay < ( this.config.maxTypeDelay - this.config.typeDelayStep ) ) {
×
500
                                this.config.typeDelay += this.config.typeDelayStep;
×
501
                        }
502
                }
503
        }
504

505
        /**
506
         * Inits a new pageAnalyzer with the inputs from the getInput function and calls the scoreFormatter to format outputs.
507
         *
508
         * @returns {void}
509
         */
510
        runAnalyzer() {
511
                if ( this.pluggable.loaded === false ) {
×
512
                        return;
×
513
                }
514

515
                if ( this.config.dynamicDelay ) {
×
516
                        this.startTime();
×
517
                }
518

519
                this.analyzerData = this.modifyData( this.rawData );
×
520

521

522
                let text = this.analyzerData.text;
×
523

524
                // Insert HTML stripping code.
525
                text = removeHtmlBlocks( text );
×
526

527
                const titleWidth = this.analyzerData.titleWidth;
×
528

529
                // Create a paper object for the Researcher.
530
                this.paper = new Paper( text, {
×
531
                        keyword: this.analyzerData.keyword,
532
                        synonyms: this.analyzerData.synonyms,
533
                        description: this.analyzerData.meta,
534
                        slug: this.analyzerData.slug,
535
                        title: this.analyzerData.metaTitle,
536
                        titleWidth: titleWidth,
537
                        locale: this.config.locale,
538
                        permalink: this.analyzerData.permalink,
539
                } );
540

541
                this.config.researcher.setPaper( this.paper );
×
542

543
                this.runKeywordAnalysis();
×
544

545
                this.runContentAnalysis();
×
546

547
                this._renderAnalysisResults();
×
548

549
                if ( this.config.dynamicDelay ) {
×
550
                        this.endTime();
×
551
                }
552
        }
553

554
        /**
555
         * Runs the keyword analysis and calls the appropriate callbacks.
556
         *
557
         * @returns {void}
558
         */
559
        runKeywordAnalysis() {
560
                if ( this.config.keywordAnalysisActive ) {
×
561
                        this.seoAssessor.assess( this.paper );
×
562
                        const overallSeoScore = this.seoAssessor.calculateOverallScore();
×
563

564
                        if ( ! isUndefined( this.callbacks.updatedKeywordsResults ) ) {
×
565
                                this.callbacks.updatedKeywordsResults( this.seoAssessor.results, overallSeoScore );
×
566
                        }
567

568
                        if ( ! isUndefined( this.callbacks.saveScores ) ) {
×
569
                                this.callbacks.saveScores( overallSeoScore, this.seoAssessorPresenter );
×
570
                        }
571
                }
572
        }
573

574
        /**
575
         * Runs the content analysis and calls the appropriate callbacks.
576
         *
577
         * @returns {void}
578
         */
579
        runContentAnalysis() {
580
                if ( this.config.contentAnalysisActive ) {
×
581
                        this.contentAssessor.assess( this.paper );
×
582
                        const overallContentScore = this.contentAssessor.calculateOverallScore();
×
583

584
                        if ( ! isUndefined( this.callbacks.updatedContentResults ) ) {
×
585
                                this.callbacks.updatedContentResults( this.contentAssessor.results, overallContentScore );
×
586
                        }
587

588
                        if ( ! isUndefined( this.callbacks.saveContentScore ) ) {
×
589
                                this.callbacks.saveContentScore( overallContentScore, this.contentAssessorPresenter );
×
590
                        }
591
                }
592
        }
593

594
        /**
595
         * Modifies the data with plugins before it is sent to the analyzer.
596
         *
597
         * @param   {Object}  data      The data to be modified.
598
         * @returns {Object}            The data with the applied modifications.
599
         */
600
        modifyData( data ) {
601
                // Copy rawdata to lose object reference.
602
                data = JSON.parse( JSON.stringify( data ) );
×
603

604
                data.text = this.pluggable._applyModifications( "content", data.text );
×
605
                data.metaTitle = this.pluggable._applyModifications( "title", data.metaTitle );
×
606

607
                return data;
×
608
        }
609

610
        /**
611
         * Removes the loading dialog and fires the analyzer when all plugins are loaded.
612
         *
613
         * @returns {void}
614
         */
615
        pluginsLoaded() {
616
                this.removeLoadingDialog();
×
617
                this.refresh();
×
618
        }
619

620
        /**
621
         * Shows the loading dialog which shows the loading of the plugins.
622
         *
623
         * @returns {void}
624
         */
625
        showLoadingDialog() {
626
                const outputElement = document.getElementById( this.defaultOutputElement );
×
627

628
                if ( this.defaultOutputElement !== "" && ! isEmpty( outputElement ) ) {
×
629
                        const dialogDiv = document.createElement( "div" );
×
630
                        dialogDiv.className = "YoastSEO_msg";
×
631
                        dialogDiv.id = "YoastSEO-plugin-loading";
×
632
                        document.getElementById( this.defaultOutputElement ).appendChild( dialogDiv );
×
633
                }
634
        }
635

636
        /**
637
         * Updates the loading plugins. Uses the plugins as arguments to show which plugins are loading.
638
         *
639
         * @param   {Object}  plugins   The plugins to be parsed into the dialog.
640
         * @returns {void}
641
         */
642
        updateLoadingDialog( plugins ) {
643
                const outputElement = document.getElementById( this.defaultOutputElement );
×
644

645
                if ( this.defaultOutputElement === "" || isEmpty( outputElement ) ) {
×
646
                        return;
×
647
                }
648

649
                const dialog = document.getElementById( "YoastSEO-plugin-loading" );
×
650
                dialog.textContent = "";
×
651

652
                forEach( plugins, function( plugin, pluginName ) {
×
653
                        dialog.innerHTML += "<span class=left>" + pluginName + "</span><span class=right " +
×
654
                                plugin.status + ">" + plugin.status + "</span><br />";
655
                } );
656

657
                dialog.innerHTML += "<span class=bufferbar></span>";
×
658
        }
659

660
        /**
661
         * Removes the plugin load dialog.
662
         *
663
         * @returns {void}
664
         */
665
        removeLoadingDialog() {
666
                const outputElement = document.getElementById( this.defaultOutputElement );
×
667
                const loadingDialog = document.getElementById( "YoastSEO-plugin-loading" );
×
668

669
                if ( ( this.defaultOutputElement !== "" && ! isEmpty( outputElement ) ) && ! isEmpty( loadingDialog ) ) {
×
670
                        document.getElementById( this.defaultOutputElement ).removeChild( document.getElementById( "YoastSEO-plugin-loading" ) );
×
671
                }
672
        }
673

674
        // ***** PLUGGABLE PUBLIC DSL ***** //
675
        /**
676
         * Delegates to `YoastSEO.app.pluggable.registerPlugin`
677
         *
678
         * @param {string}  pluginName      The name of the plugin to be registered.
679
         * @param {object}  options         The options object.
680
         * @param {string}  options.status  The status of the plugin being registered. Can either be "loading" or "ready".
681
         * @returns {boolean}               Whether or not it was successfully registered.
682
         */
683
        registerPlugin( pluginName, options ) {
684
                return this.pluggable._registerPlugin( pluginName, options );
×
685
        }
686

687
        /**
688
         * Delegates to `YoastSEO.app.pluggable.ready`
689
         *
690
         * @param {string}  pluginName  The name of the plugin to check.
691
         * @returns {boolean}           Whether or not the plugin is ready.
692
         */
693
        pluginReady( pluginName ) {
694
                return this.pluggable._ready( pluginName );
×
695
        }
696

697
        /**
698
         * Delegates to `YoastSEO.app.pluggable.reloaded`
699
         *
700
         * @param {string} pluginName   The name of the plugin to reload
701
         * @returns {boolean}           Whether or not the plugin was reloaded.
702
         */
703
        pluginReloaded( pluginName ) {
704
                return this.pluggable._reloaded( pluginName );
×
705
        }
706

707
        /**
708
         * Delegates to `YoastSEO.app.pluggable.registerModification`.
709
         *
710
         * @param {string}   modification   The name of the filter.
711
         * @param {function} callable       The callable function.
712
         * @param {string}   pluginName     The plugin that is registering the modification.
713
         * @param {number}   [priority]     Used to specify the order in which the callables associated with a particular filter are called.
714
         *                                  Lower numbers correspond with earlier execution.
715
         *
716
         * @returns {boolean} Whether or not the modification was successfully registered.
717
         */
718
        registerModification( modification, callable, pluginName, priority ) {
719
                return this.pluggable._registerModification( modification, callable, pluginName, priority );
×
720
        }
721

722
        /**
723
         * Registers a custom assessment for use in the analyzer, this will result in a new line in the analyzer results.
724
         * The function needs to use the assessment result to return a result based on the contents of the page/posts.
725
         *
726
         * Score 0 results in a grey circle if it is not explicitly set by using setscore
727
         * Scores 0, 1, 2, 3 and 4 result in a red circle
728
         * Scores 6 and 7 result in a yellow circle
729
         * Scores 8, 9 and 10 result in a green circle
730
         *
731
         * @param {string} name Name of the test.
732
         * @param {function} assessment The assessment to run.
733
         * @param {string}   pluginName The plugin that is registering the test.
734
         * @returns {boolean} Whether or not the test was successfully registered.
735
         */
736
        registerAssessment( name, assessment, pluginName ) {
737
                if ( ! isUndefined( this.seoAssessor ) ) {
×
738
                        return this.pluggable._registerAssessment( this.defaultSeoAssessor, name, assessment, pluginName ) &&
×
739
                                this.pluggable._registerAssessment( this.cornerStoneSeoAssessor, name, assessment, pluginName );
740
                }
741
        }
742

743
        /**
744
         * Disables markers visually in the UI.
745
         *
746
         * @returns {void}
747
         */
748
        disableMarkers() {
749
                if ( ! isUndefined( this.seoAssessorPresenter ) ) {
×
750
                        this.seoAssessorPresenter.disableMarker();
×
751
                }
752

753
                if ( ! isUndefined( this.contentAssessorPresenter ) ) {
×
754
                        this.contentAssessorPresenter.disableMarker();
×
755
                }
756
        }
757

758
        /**
759
         * Renders the content and keyword analysis results.
760
         *
761
         * @returns {void}
762
         */
763
        _renderAnalysisResults() {
764
                if ( this.config.contentAnalysisActive && ! isUndefined( this.contentAssessorPresenter ) ) {
×
765
                        this.contentAssessorPresenter.renderIndividualRatings();
×
766
                }
767
                if ( this.config.keywordAnalysisActive && ! isUndefined( this.seoAssessorPresenter ) ) {
×
768
                        this.seoAssessorPresenter.setKeyword( this.paper.getKeyword() );
×
769
                        this.seoAssessorPresenter.render();
×
770
                }
771
        }
772

773
        // Deprecated functions
774
        /**
775
         * The analyzeTimer calls the checkInputs function with a delay, so the function won't be executed
776
         * at every keystroke checks the reference object, so this function can be called from anywhere,
777
         * without problems with different scopes.
778
         *
779
         * @deprecated: 1.3 - Use this.refresh() instead.
780
         *
781
         * @returns {void}
782
         */
783
        analyzeTimer() {
784
                this.refresh();
×
785
        }
786

787
        /**
788
         * Registers a custom test for use in the analyzer, this will result in a new line in the analyzer results. The function
789
         * has to return a result based on the contents of the page/posts.
790
         *
791
         * The scoring object is a special object with definitions about how to translate a result from your analysis function
792
         * to a SEO score.
793
         *
794
         * Negative scores result in a red circle
795
         * Scores 1, 2, 3, 4 and 5 result in an orange circle
796
         * Scores 6 and 7 result in a yellow circle
797
         * Scores 8, 9 and 10 result in a red circle
798
         *
799
         * @returns {void}
800
         *
801
         * @deprecated since version 1.2
802
         */
803
        registerTest() {
804
                console.error( "This function is deprecated, please use registerAssessment" );
×
805
        }
806

807
        /**
808
         * Switches between the cornerstone and default assessors.
809
         *
810
         * @deprecated 1.35.0 - Use changeAssessorOption instead.
811
         *
812
         * @param {boolean} useCornerStone True when cornerstone should be used.
813
         *
814
         * @returns {void}
815
         */
816
        switchAssessors( useCornerStone ) {
UNCOV
817
                console.warn( "Switch assessor is deprecated since YoastSEO.js version 1.35.0" );
×
818

819
                this.changeAssessorOptions( {
×
820
                        useCornerStone,
821
                } );
822
        }
823
}
824

825

826
export default App;
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

© 2025 Coveralls, Inc