UNPKG

yoastseo-dep

Version:

Yoast clientside page analysis

229 lines (209 loc) 7.55 kB
import { __, _n, sprintf } from "@wordpress/i18n"; import { merge } from "lodash-es"; import { getSubheadingsTopLevel } from "../../../languageProcessing/helpers/html/getSubheadings"; import Assessment from "../assessment"; import { createAnchorOpeningTag } from "../../../helpers/shortlinker"; import { inRangeStartEndInclusive } from "../../helpers/assessments/inRange.js"; import AssessmentResult from "../../../values/AssessmentResult"; /** * Represents the assessment that checks if the keyword is present in one of the subheadings. */ export default class SubHeadingsKeywordAssessment extends Assessment { /** * Sets the identifier and the config. * * @param {object} config The configuration to use. * * @returns {void} */ constructor( config = {} ) { super(); const defaultConfig = { parameters: { lowerBoundary: 0.3, upperBoundary: 0.75, }, scores: { noMatches: 3, tooFewMatches: 3, goodNumberOfMatches: 9, tooManyMatches: 3, }, urlTitle: createAnchorOpeningTag( "https://yoa.st/33m" ), urlCallToAction: createAnchorOpeningTag( "https://yoa.st/33n" ), }; this.identifier = "subheadingsKeyword"; this._config = merge( defaultConfig, config ); } /** * Runs the matchKeywordInSubheadings research and based on this returns an assessment result. * * @param {Paper} paper The paper to use for the assessment. * @param {Researcher} researcher The researcher used for calling research. * * @returns {AssessmentResult} The assessment result. */ getResult( paper, researcher ) { this._subHeadings = researcher.getResearch( "matchKeywordInSubheadings" ); const assessmentResult = new AssessmentResult(); this._minNumberOfSubheadings = Math.ceil( this._subHeadings.count * this._config.parameters.lowerBoundary ); this._maxNumberOfSubheadings = Math.floor( this._subHeadings.count * this._config.parameters.upperBoundary ); const calculatedResult = this.calculateResult(); assessmentResult.setScore( calculatedResult.score ); assessmentResult.setText( calculatedResult.resultText ); return assessmentResult; } /** * Checks whether the paper has a subheadings. * * @param {Paper} paper The paper to use for the check. * * @returns {boolean} True when there is at least one subheading. */ hasSubheadings( paper ) { const subheadings = getSubheadingsTopLevel( paper.getText() ); return subheadings.length > 0; } /** * Checks whether the paper has a text and a keyword. * * @param {Paper} paper The paper to use for the assessment. * * @returns {boolean} True when there is text and a keyword. */ isApplicable( paper ) { return paper.hasText() && paper.hasKeyword() && this.hasSubheadings( paper ); } /** * Checks whether there are too few subheadings with the keyphrase. * * This is the case if the number of subheadings with the keyphrase is more than 0 but less than the specified * recommended minimum. * * @returns {boolean} Returns true if the keyphrase is included in too few subheadings. */ hasTooFewMatches() { return this._subHeadings.matches > 0 && this._subHeadings.matches < this._minNumberOfSubheadings; } /** * Checks whether there are too many subheadings with the keyphrase. * * The upper limit is only applicable if there is more than one subheading. If there is only one subheading with * the keyphrase this would otherwise always lead to a 100% match rate. * * @returns {boolean} Returns true if there is more than one subheading and if the keyphrase is included in less * subheadings than the recommended maximum. */ hasTooManyMatches() { return this._subHeadings.count > 1 && this._subHeadings.matches > this._maxNumberOfSubheadings; } /** * Checks whether there is only one higher-level subheading and this subheading includes the keyphrase. * * @returns {boolean} Returns true if there is exactly one higher-level subheading and this * subheading has a keyphrase match. */ isOneOfOne() { return this._subHeadings.count === 1 && this._subHeadings.matches === 1; } /** * Checks whether there is a good number of subheadings with the keyphrase. * * This is the case if there is only one subheading and that subheading includes the keyphrase or if the number of * subheadings with the keyphrase is within the specified recommended range. * * @returns {boolean} Returns true if the keyphrase is included in a sufficient number of subheadings. */ hasGoodNumberOfMatches() { return inRangeStartEndInclusive( this._subHeadings.matches, this._minNumberOfSubheadings, this._maxNumberOfSubheadings ); } /** * Determines the score and the Result text for the subheadings. * * @returns {Object} The object with the calculated score and the result text. */ calculateResult() { if ( this.hasTooFewMatches() ) { return { score: this._config.scores.tooFewMatches, resultText: sprintf( /* translators: %1$s and %2$s expand to a link on yoast.com, %3$s expands to the anchor end tag. */ __( "%1$sKeyphrase in subheading%3$s: %2$sUse more keyphrases or synonyms in your H2 and H3 subheadings%3$s!", "wordpress-seo" ), this._config.urlTitle, this._config.urlCallToAction, "</a>" ), }; } if ( this.hasTooManyMatches() ) { return { score: this._config.scores.tooManyMatches, resultText: sprintf( /* translators: %1$s and %2$s expand to a link on yoast.com, %3$s expands to the anchor end tag. */ __( // eslint-disable-next-line max-len "%1$sKeyphrase in subheading%3$s: More than 75%% of your H2 and H3 subheadings reflect the topic of your copy. That's too much. %2$sDon't over-optimize%3$s!", "wordpress-seo" ), this._config.urlTitle, this._config.urlCallToAction, "</a>" ), }; } if ( this.isOneOfOne() ) { return { score: this._config.scores.goodNumberOfMatches, resultText: sprintf( /* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag, %3$d expands to the number of subheadings containing the keyphrase. */ __( "%1$sKeyphrase in subheading%2$s: Your H2 or H3 subheading reflects the topic of your copy. Good job!", "wordpress-seo" ), this._config.urlTitle, "</a>", this._subHeadings.matches ), }; } if ( this.hasGoodNumberOfMatches() ) { return { score: this._config.scores.goodNumberOfMatches, resultText: sprintf( /* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag, %3$d expands to the number of subheadings containing the keyphrase. */ _n( "%1$sKeyphrase in subheading%2$s: %3$s of your H2 and H3 subheadings reflects the topic of your copy. Good job!", "%1$sKeyphrase in subheading%2$s: %3$s of your H2 and H3 subheadings reflect the topic of your copy. Good job!", this._subHeadings.matches, "wordpress-seo" ), this._config.urlTitle, "</a>", this._subHeadings.matches ), }; } return { score: this._config.scores.noMatches, resultText: sprintf( /* translators: %1$s and %2$s expand to a link on yoast.com, %3$s expands to the anchor end tag. */ __( "%1$sKeyphrase in subheading%3$s: %2$sUse more keyphrases or synonyms in your H2 and H3 subheadings%3$s!", "wordpress-seo" ), this._config.urlTitle, this._config.urlCallToAction, "</a>" ), }; } }