UNPKG

yoastseo

Version:

Yoast client-side content analysis

338 lines (320 loc) 18.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _i18n = require("@wordpress/i18n"); var _lodash = require("lodash"); var _addMark = _interopRequireDefault(require("../../../markers/addMark")); var _Mark = _interopRequireDefault(require("../../../values/Mark")); var _assessment = _interopRequireDefault(require("../assessment")); var _inRange = require("../../helpers/assessments/inRange"); var _helpers = require("../../../helpers"); var _getSubheadings = require("../../../languageProcessing/helpers/html/getSubheadings"); var _getWords = _interopRequireDefault(require("../../../languageProcessing/helpers/word/getWords")); var _AssessmentResult = _interopRequireDefault(require("../../../values/AssessmentResult")); var _stripHTMLTags = require("../../../languageProcessing/helpers/sanitize/stripHTMLTags"); var _htmlParser = _interopRequireDefault(require("../../../languageProcessing/helpers/html/htmlParser")); var _helpers2 = require("../../../languageProcessing/helpers"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } /** * @typedef {import("../../../languageProcessing/researches/getSubheadingTextLengths").SubheadingText } SubheadingText */ /** * @typedef {Object} SubheadingDistributionConfig The default configuration for the subheading distribution assessment. * @property {Object} parameters The parameters for the assessment. * @property {number} parameters.recommendedMaximumLength The maximum recommended value of the subheading text. * @property {number} parameters.slightlyTooMany The slightly too many value of the subheading text. * @property {number} parameters.farTooMany The far too many value of the subheading text. * @property {string} urlTitle The URL for the help article for subheading distribution assessment used in the assessment's feedback title. * @property {string} urlCallToAction The URL for the help article for subheading distribution assessment used in the assessment's feedback call-to-action. * @property {Object} scores The scores for the assessment. * @property {number} scores.goodShortTextNoSubheadings The score for a good short text without subheadings. * @property {number} scores.goodSubheadings The score for good subheading distribution. * @property {number} scores.okSubheadings The score for okay subheading distribution. * @property {number} scores.badSubheadings The score for bad subheading distribution. * @property {number} scores.badLongTextNoSubheadings The score for a bad long text without subheadings. * @property {boolean} cornerstoneContent Whether the text is cornerstone content. * @property {boolean} countCharacters Whether to count characters instead of words. */ /** * Represents the assessment that checks whether a text has a good distribution of subheadings. */ class SubheadingsDistributionTooLong extends _assessment.default { /** * Creates an instance of SubheadingsDistributionTooLong. * @constructor * * @param {Object} [config={}] The configuration to use. This configuration will be merged with the default configuration. */ constructor(config = {}) { super(); /** * The default configuration for the subheading distribution assessment. * @type {SubheadingDistributionConfig} */ const DEFAULT_CONFIG = { parameters: { // The maximum recommended value of the subheading text. recommendedMaximumLength: 300, slightlyTooMany: 300, farTooMany: 350 }, urlTitle: (0, _helpers.createAnchorOpeningTag)("https://yoa.st/34x"), urlCallToAction: (0, _helpers.createAnchorOpeningTag)("https://yoa.st/34y"), scores: { goodShortTextNoSubheadings: 9, goodSubheadings: 9, okSubheadings: 6, badSubheadings: 3, badLongTextNoSubheadings: 2 }, cornerstoneContent: false, countCharacters: false }; this.identifier = "subheadingsTooLong"; this._config = (0, _lodash.merge)(DEFAULT_CONFIG, config); } /** * Checks if the text before the first subheading is long or very long. * * @param {SubheadingText[]} foundSubheadings An array containing found subheading objects. * * @returns {{isVeryLong: boolean, isLong: boolean}} An object containing an information whether the text before the first subheading is long or very long. */ checkTextBeforeFirstSubheadingLength(foundSubheadings) { let textBeforeFirstSubheading = { isLong: false, isVeryLong: false }; // There is a text if the subheading string of the first object in foundSubheadings is empty and the text is not empty. if (foundSubheadings.length > 0 && foundSubheadings[0].subheading === "" && foundSubheadings[0].text !== "") { // Retrieve the length of the text before the first subheading. const textBeforeFirstSubheadingLength = foundSubheadings[0].countLength; textBeforeFirstSubheading = { isLong: (0, _inRange.inRangeEndInclusive)(textBeforeFirstSubheadingLength, this._config.parameters.slightlyTooMany, this._config.parameters.farTooMany), isVeryLong: textBeforeFirstSubheadingLength > this._config.parameters.farTooMany }; } return textBeforeFirstSubheading; } /** * Gets the text length from the paper. Remove unwanted element first before calculating. * * @param {Paper} paper The Paper object to analyse. * @param {Researcher} researcher The researcher to use. * @returns {number} The length of the text. */ getTextLength(paper, researcher) { // Give specific feedback for cases where the post starts with a long text without subheadings. const customCountLength = researcher.getHelper("customCountLength"); let text = paper.getText(); text = (0, _htmlParser.default)(text); text = (0, _helpers2.filterShortcodesFromHTML)(text, paper._attributes && paper._attributes.shortcodes); return customCountLength ? customCountLength(text) : (0, _getWords.default)(text).length; } /** * Runs the getSubheadingTextLength research and checks scores based on length. * * @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._subheadingTextsLength = researcher.getResearch("getSubheadingTextLengths"); if (researcher.getConfig("subheadingsTooLong")) { this._config = this.getLanguageSpecificConfig(researcher); } // The configuration to use for Japanese texts. this._config.countCharacters = !!researcher.getConfig("countCharacters"); this._hasSubheadings = this.hasSubheadings(paper); this._tooLongTextsNumber = this.getTooLongSubheadingTexts().length; this._textLength = this.getTextLength(paper, researcher); // First check if there is text before the first subheading and check its length. // It's important that this check is done before we sort the `this._subheadingTextsLength` array. const textBeforeFirstSubheading = this.checkTextBeforeFirstSubheadingLength(this._subheadingTextsLength); this._subheadingTextsLength = this._subheadingTextsLength.sort((a, b) => b.countLength - a.countLength); const calculatedResult = this.calculateResult(textBeforeFirstSubheading); const assessmentResult = new _AssessmentResult.default(); assessmentResult.setIdentifier(this.identifier); assessmentResult.setScore(calculatedResult.score); assessmentResult.setText(calculatedResult.resultText); assessmentResult.setHasMarks(calculatedResult.hasMarks); return assessmentResult; } /** * Checks if there is language-specific config, and if so, overwrite the current config with it. * * @param {Researcher} researcher The researcher to use. * * @returns {SubheadingDistributionConfig} The config that should be used. */ getLanguageSpecificConfig(researcher) { const currentConfig = this._config; const languageSpecificConfig = researcher.getConfig("subheadingsTooLong"); // Check if a language has a default cornerstone configuration. if (currentConfig.cornerstoneContent === true && Object.hasOwn(languageSpecificConfig, "cornerstoneParameters")) { return (0, _lodash.merge)(currentConfig, languageSpecificConfig.cornerstoneParameters); } // Use the default language-specific config for non-cornerstone condition. return (0, _lodash.merge)(currentConfig, languageSpecificConfig.defaultParameters); } /** * Checks whether the paper has subheadings. * * @param {Paper} paper The paper to use for the assessment. * * @returns {boolean} True when there is at least one subheading. */ hasSubheadings(paper) { const subheadings = (0, _getSubheadings.getSubheadings)(paper.getText()); return subheadings.length > 0; } /** * Creates a marker for each subheading that precedes a text that is too long. * * @returns {Mark[]} All markers for the current text. */ getMarks() { const marks = this.getTooLongSubheadingTexts().map(({ subheading }) => { subheading = (0, _stripHTMLTags.stripFullTags)(subheading); const marked = (0, _addMark.default)(subheading); return new _Mark.default({ original: subheading, marked: marked, fieldsToMark: ["heading"] }); }); // This is to ensure that an empty subheading doesn't receive marker tags. // If an empty subheading string receives marker tags, clicking on the eye icon next to the assessment will lead to page crashing. return marks.filter(mark => mark.getOriginal() !== ""); } /** * Counts the number of subheading texts that are too long. * * @returns {SubheadingText[]} The array containing subheading texts that are too long. */ getTooLongSubheadingTexts() { return this._subheadingTextsLength.filter(subheading => subheading.countLength > this._config.parameters.recommendedMaximumLength); } /** * Returns the feedback texts for the assessment when there is a long text without subheadings. * * @returns {{beginning: (function(boolean): string), nonBeginning: (function(boolean): string)}} The feedback texts. */ getFeedbackTexts() { return { beginning: useCharacter => { const wordFeedback = (0, _i18n.sprintf)(/* translators: %1$s and %3$s expand to links on yoast.com, %2$s expands to the anchor end tag, %4$s expands to the recommended maximum length of a text without subheading. */ (0, _i18n._n)("%1$sSubheading distribution%2$s: The beginning of your text is longer than %4$d word and is not separated by any subheadings. %3$sAdd subheadings to improve readability.%2$s", "%1$sSubheading distribution%2$s: The beginning of your text is longer than %4$d words and is not separated by any subheadings. %3$sAdd subheadings to improve readability.%2$s", this._config.parameters.recommendedMaximumLength, "wordpress-seo"), this._config.urlTitle, "</a>", this._config.urlCallToAction, this._config.parameters.recommendedMaximumLength); const characterFeedback = (0, _i18n.sprintf)(/* translators: %1$s and %3$s expand to links on yoast.com, %2$s expands to the anchor end tag, %4$s expands to the recommended maximum length of a text without subheading. */ (0, _i18n._n)("%1$sSubheading distribution%2$s: The beginning of your text is longer than %4$d character and is not separated by any subheadings. %3$sAdd subheadings to improve readability.%2$s", "%1$sSubheading distribution%2$s: The beginning of your text is longer than %4$d characters and is not separated by any subheadings. %3$sAdd subheadings to improve readability.%2$s", this._config.parameters.recommendedMaximumLength, "wordpress-seo"), this._config.urlTitle, "</a>", this._config.urlCallToAction, this._config.parameters.recommendedMaximumLength); return useCharacter ? characterFeedback : wordFeedback; }, nonBeginning: useCharacter => { const wordFeedback = (0, _i18n.sprintf)(/* translators: %1$s and %5$s expand to links on yoast.com, %2$s expands to the anchor end tag, %3$d expands to the number of sections that are too long, %4$s expands to the recommended maximum length of a text without subheading. */ (0, _i18n._n)("%1$sSubheading distribution%2$s: %3$d section of your text is longer than the recommended number of words (%4$d) and is not separated by any subheadings. %5$sAdd subheadings to improve readability%2$s.", "%1$sSubheading distribution%2$s: %3$d sections of your text are longer than the recommended number of words (%4$d) and are not separated by any subheadings. %5$sAdd subheadings to improve readability%2$s.", this._tooLongTextsNumber, "wordpress-seo"), this._config.urlTitle, "</a>", this._tooLongTextsNumber, this._config.parameters.recommendedMaximumLength, this._config.urlCallToAction); const characterFeedback = (0, _i18n.sprintf)(/* translators: %1$s and %5$s expand to links on yoast.com, %2$s expands to the anchor end tag, %3$d expands to the number of sections that are too long, %4$s expands to the recommended maximum length of a text without subheading. */ (0, _i18n._n)("%1$sSubheading distribution%2$s: %3$d section of your text is longer than the recommended number of characters (%4$d) and is not separated by any subheadings. %5$sAdd subheadings to improve readability%2$s.", "%1$sSubheading distribution%2$s: %3$d sections of your text are longer than the recommended number of characters (%4$d) and are not separated by any subheadings. %5$sAdd subheadings to improve readability%2$s.", this._tooLongTextsNumber, "wordpress-seo"), this._config.urlTitle, "</a>", this._tooLongTextsNumber, this._config.parameters.recommendedMaximumLength, this._config.urlCallToAction); return useCharacter ? characterFeedback : wordFeedback; } }; } /** * Calculates the score and creates a feedback string based on the subheading texts length for a long text without subheadings. * * @param {{isVeryLong: boolean, isLong: boolean}} textBeforeFirstSubheading An object containing information whether the text before the first subheading is long or very long. * @returns {{resultText: string, score: number, hasMarks: boolean}} The calculated result. */ calculateResultForLongTextWithoutSubheadings(textBeforeFirstSubheading) { const feedbackTexts = this.getFeedbackTexts(); if (this._hasSubheadings) { if (textBeforeFirstSubheading.isLong && this._tooLongTextsNumber < 2) { /* * Orange indicator. Returns this feedback if the text preceding the first subheading is long * and the total number of too long texts is less than 2. */ return { score: this._config.scores.okSubheadings, hasMarks: false, resultText: feedbackTexts.beginning(this._config.countCharacters) }; } if (textBeforeFirstSubheading.isVeryLong && this._tooLongTextsNumber < 2) { /* * Red indicator. Returns this feedback if the text preceding the first subheading is very long * and the total number of too long texts is less than 2. */ return { score: this._config.scores.badSubheadings, hasMarks: false, resultText: feedbackTexts.beginning(this._config.countCharacters) }; } const longestSubheadingTextLength = this._subheadingTextsLength[0].countLength; if (longestSubheadingTextLength <= this._config.parameters.slightlyTooMany) { // Green indicator. return { score: this._config.scores.goodSubheadings, hasMarks: false, resultText: (0, _i18n.sprintf)(/* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag */ (0, _i18n.__)("%1$sSubheading distribution%2$s: Great job!", "wordpress-seo"), this._config.urlTitle, "</a>") }; } if ((0, _inRange.inRangeEndInclusive)(longestSubheadingTextLength, this._config.parameters.slightlyTooMany, this._config.parameters.farTooMany)) { // Orange indicator. return { score: this._config.scores.okSubheadings, hasMarks: true, resultText: feedbackTexts.nonBeginning(this._config.countCharacters) }; } // Red indicator. return { score: this._config.scores.badSubheadings, hasMarks: true, resultText: feedbackTexts.nonBeginning(this._config.countCharacters) }; } // Red indicator, use '2' so we can differentiate in external analysis. return { score: this._config.scores.badLongTextNoSubheadings, hasMarks: false, resultText: (0, _i18n.sprintf)(/* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag */ (0, _i18n.__)("%1$sSubheading distribution%2$s: You are not using any subheadings, although your text is rather long. %3$sTry and add some subheadings%2$s.", "wordpress-seo"), this._config.urlTitle, "</a>", this._config.urlCallToAction) }; } /** * Calculates the score and creates a feedback string based on the subheading texts length. * * @param {{isVeryLong: boolean, isLong: boolean}} textBeforeFirstSubheading An object containing information whether the text before the first subheading is long or very long. * * @returns {{resultText: string, score: number, hasMarks: boolean}} The calculated result. */ calculateResult(textBeforeFirstSubheading) { if (this._textLength > this._config.parameters.recommendedMaximumLength) { return this.calculateResultForLongTextWithoutSubheadings(textBeforeFirstSubheading); } if (this._hasSubheadings) { // Green indicator. return { score: this._config.scores.goodSubheadings, hasMarks: false, resultText: (0, _i18n.sprintf)(/* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag */ (0, _i18n.__)("%1$sSubheading distribution%2$s: Great job!", "wordpress-seo"), this._config.urlTitle, "</a>") }; } // Green indicator. return { score: this._config.scores.goodShortTextNoSubheadings, hasMarks: false, resultText: (0, _i18n.sprintf)(/* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag */ (0, _i18n.__)("%1$sSubheading distribution%2$s: You are not using any subheadings, but your text is short enough and probably doesn't need them.", "wordpress-seo"), this._config.urlTitle, "</a>") }; } } var _default = exports.default = SubheadingsDistributionTooLong; //# sourceMappingURL=SubheadingDistributionTooLongAssessment.js.map