diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist index c71ab7fe143..6209556d799 100644 --- a/.phpcs.xml.dist +++ b/.phpcs.xml.dist @@ -227,6 +227,12 @@ /inc/wpseo-functions-deprecated\.php$ + + + /src/deprecated/* + /inc/wpseo-functions-deprecated\.php$ + + diff --git a/admin/class-customizer.php b/admin/class-customizer.php deleted file mode 100644 index bc6b9e8c7c1..00000000000 --- a/admin/class-customizer.php +++ /dev/null @@ -1,249 +0,0 @@ - '', - 'type' => 'option', - 'transport' => 'refresh', - ]; - - /** - * Default arguments for the breadcrumbs customizer control object. - * - * @var array - */ - private $default_control_args = [ - 'label' => '', - 'type' => 'text', - 'section' => 'wpseo_breadcrumbs_customizer_section', - 'settings' => '', - 'context' => '', - ]; - - /** - * Construct Method. - */ - public function __construct() { - add_action( 'customize_register', [ $this, 'wpseo_customize_register' ] ); - } - - /** - * Function to support WordPress Customizer. - * - * @param WP_Customize_Manager $wp_customize Manager class instance. - * - * @return void - */ - public function wpseo_customize_register( $wp_customize ) { - if ( ! WPSEO_Capability_Utils::current_user_can( 'wpseo_manage_options' ) ) { - return; - } - - $this->wp_customize = $wp_customize; - - $this->breadcrumbs_section(); - $this->breadcrumbs_blog_show_setting(); - $this->breadcrumbs_separator_setting(); - $this->breadcrumbs_home_setting(); - $this->breadcrumbs_prefix_setting(); - $this->breadcrumbs_archiveprefix_setting(); - $this->breadcrumbs_searchprefix_setting(); - $this->breadcrumbs_404_setting(); - } - - /** - * Add the breadcrumbs section to the customizer. - * - * @return void - */ - private function breadcrumbs_section() { - $section_args = [ - /* translators: %s is the name of the plugin */ - 'title' => sprintf( __( '%s Breadcrumbs', 'wordpress-seo' ), 'Yoast SEO' ), - 'priority' => 999, - 'active_callback' => [ $this, 'breadcrumbs_active_callback' ], - ]; - - $this->wp_customize->add_section( 'wpseo_breadcrumbs_customizer_section', $section_args ); - } - - /** - * Returns whether or not the breadcrumbs are active. - * - * @return bool - */ - public function breadcrumbs_active_callback() { - return current_theme_supports( 'yoast-seo-breadcrumbs' ) || WPSEO_Options::get( 'breadcrumbs-enable' ); - } - - /** - * Adds the breadcrumbs show blog checkbox. - * - * @return void - */ - private function breadcrumbs_blog_show_setting() { - $index = 'breadcrumbs-display-blog-page'; - $control_args = [ - 'label' => __( 'Show blog page in breadcrumbs', 'wordpress-seo' ), - 'type' => 'checkbox', - 'active_callback' => [ $this, 'breadcrumbs_blog_show_active_cb' ], - ]; - - $this->add_setting_and_control( $index, $control_args ); - } - - /** - * Returns whether or not to show the breadcrumbs blog show option. - * - * @return bool - */ - public function breadcrumbs_blog_show_active_cb() { - return get_option( 'show_on_front' ) === 'page'; - } - - /** - * Adds the breadcrumbs separator text field. - * - * @return void - */ - private function breadcrumbs_separator_setting() { - $index = 'breadcrumbs-sep'; - $control_args = [ - 'label' => __( 'Breadcrumbs separator:', 'wordpress-seo' ), - ]; - $id = 'wpseo-breadcrumbs-separator'; - - $this->add_setting_and_control( $index, $control_args, $id ); - } - - /** - * Adds the breadcrumbs home anchor text field. - * - * @return void - */ - private function breadcrumbs_home_setting() { - $index = 'breadcrumbs-home'; - $control_args = [ - 'label' => __( 'Anchor text for the homepage:', 'wordpress-seo' ), - ]; - - $this->add_setting_and_control( $index, $control_args ); - } - - /** - * Adds the breadcrumbs prefix text field. - * - * @return void - */ - private function breadcrumbs_prefix_setting() { - $index = 'breadcrumbs-prefix'; - $control_args = [ - 'label' => __( 'Prefix for breadcrumbs:', 'wordpress-seo' ), - ]; - - $this->add_setting_and_control( $index, $control_args ); - } - - /** - * Adds the breadcrumbs archive prefix text field. - * - * @return void - */ - private function breadcrumbs_archiveprefix_setting() { - $index = 'breadcrumbs-archiveprefix'; - $control_args = [ - 'label' => __( 'Prefix for archive pages:', 'wordpress-seo' ), - ]; - - $this->add_setting_and_control( $index, $control_args ); - } - - /** - * Adds the breadcrumbs search prefix text field. - * - * @return void - */ - private function breadcrumbs_searchprefix_setting() { - $index = 'breadcrumbs-searchprefix'; - $control_args = [ - 'label' => __( 'Prefix for search result pages:', 'wordpress-seo' ), - ]; - - $this->add_setting_and_control( $index, $control_args ); - } - - /** - * Adds the breadcrumb 404 prefix text field. - * - * @return void - */ - private function breadcrumbs_404_setting() { - $index = 'breadcrumbs-404crumb'; - $control_args = [ - 'label' => __( 'Breadcrumb for 404 pages:', 'wordpress-seo' ), - ]; - - $this->add_setting_and_control( $index, $control_args ); - } - - /** - * Adds the customizer setting and control. - * - * @param string $index Array key index to use for the customizer setting. - * @param array $control_args Customizer control object arguments. - * Only those different from the default need to be passed. - * @param string|null $id Optional. Customizer control object ID. - * Will default to 'wpseo-' . $index. - * @param array $custom_settings Optional. Customizer setting arguments. - * Only those different from the default need to be passed. - * - * @return void - */ - private function add_setting_and_control( $index, $control_args, $id = null, $custom_settings = [] ) { - $setting = sprintf( $this->setting_template, $index ); - $control_args = array_merge( $this->default_control_args, $control_args ); - $control_args['settings'] = $setting; - - $settings_args = $this->default_setting_args; - if ( ! empty( $custom_settings ) ) { - $settings_args = array_merge( $settings_args, $custom_settings ); - } - - if ( ! isset( $id ) ) { - $id = 'wpseo-' . $index; - } - - $this->wp_customize->add_setting( $setting, $settings_args ); - - $control = new WP_Customize_Control( $this->wp_customize, $id, $control_args ); - $this->wp_customize->add_control( $control ); - } -} diff --git a/composer.json b/composer.json index fda1dd8e7f7..bd9aec02bd4 100644 --- a/composer.json +++ b/composer.json @@ -92,8 +92,8 @@ "Yoast\\WP\\SEO\\Composer\\Actions::check_coding_standards" ], "check-cs-thresholds": [ - "@putenv YOASTCS_THRESHOLD_ERRORS=2548", - "@putenv YOASTCS_THRESHOLD_WARNINGS=267", + "@putenv YOASTCS_THRESHOLD_ERRORS=2544", + "@putenv YOASTCS_THRESHOLD_WARNINGS=253", "Yoast\\WP\\SEO\\Composer\\Actions::check_cs_thresholds" ], "check-cs": [ diff --git a/package.json b/package.json index dae11309341..deee438179a 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "webpack-bundle-analyzer": "^4.9.1" }, "yoast": { - "pluginVersion": "22.7-RC3" + "pluginVersion": "22.7-RC5" }, "version": "0.0.0" } diff --git a/packages/js/src/initializers/post-scraper.js b/packages/js/src/initializers/post-scraper.js index 62ae0ed3d81..ecf210e63b1 100644 --- a/packages/js/src/initializers/post-scraper.js +++ b/packages/js/src/initializers/post-scraper.js @@ -248,7 +248,6 @@ export default function initPostScraper( $, store, editorData ) { marker: getApplyMarks( store ), contentAnalysisActive: isContentAnalysisActive(), keywordAnalysisActive: isKeywordAnalysisActive(), - hasSnippetPreview: false, debouncedRefresh: false, // eslint-disable-next-line new-cap researcher: new window.yoast.Researcher.default(), diff --git a/packages/js/src/initializers/term-scraper.js b/packages/js/src/initializers/term-scraper.js index 00478ca506e..4467fc7085f 100644 --- a/packages/js/src/initializers/term-scraper.js +++ b/packages/js/src/initializers/term-scraper.js @@ -289,7 +289,6 @@ export default function initTermScraper( $, store, editorData ) { locale: wpseoScriptData.metabox.contentLocale, contentAnalysisActive: isContentAnalysisActive(), keywordAnalysisActive: isKeywordAnalysisActive(), - hasSnippetPreview: false, debouncedRefresh: false, // eslint-disable-next-line new-cap researcher: new window.yoast.Researcher.default(), diff --git a/packages/js/src/shared-admin/helpers/search-appearance-description-mention.js b/packages/js/src/shared-admin/helpers/search-appearance-description-mention.js index 17050c6bee7..e01297fa4f6 100644 --- a/packages/js/src/shared-admin/helpers/search-appearance-description-mention.js +++ b/packages/js/src/shared-admin/helpers/search-appearance-description-mention.js @@ -66,7 +66,7 @@ const filterReplacementVariableEditorMentions = ( mentions, { fieldId } ) => { const isProduct = select( "yoast-seo/editor" ).getIsProduct(); const isPreviewField = fieldId === "yoast-google-preview-description-metabox" || fieldId === "yoast-google-preview-description-modal"; const dateCharacters = getDate().length; - const separatorCharacters = 3; + const emDashCharacters = 3; const newMentions = []; if ( ! isProduct && isPreviewField ) { newMentions.push( @@ -79,12 +79,13 @@ const filterReplacementVariableEditorMentions = ( mentions, { fieldId } ) => { /* translators: %s expands to the amount of characters */ _n( "The 'Date' variable is fixed and adds %s character to the length of your meta description.", "The 'Date' variable is fixed and adds %s characters to the length of your meta description.", dateCharacters, "wordpress-seo" ), dateCharacters ) } - - +   + { sprintf( /* translators: %s expands to the amount of characters */ - _n( "The 'Separator' variable is fixed and adds %s character to the length of your meta description.", "The 'Separator' variable is fixed and adds %s characters to the length of your meta description.", separatorCharacters, "wordpress-seo" ), separatorCharacters ) } + _n( "The em dash (—) is fixed and adds %s character to the length of your meta description.", "The em dash (—) is fixed and adds %s characters to the length of your meta description.", emDashCharacters, "wordpress-seo" ), emDashCharacters ) } +   ); diff --git a/packages/js/src/structured-data-blocks/faq/block.js b/packages/js/src/structured-data-blocks/faq/block.js index 337ae5b0d7b..ef25c847114 100644 --- a/packages/js/src/structured-data-blocks/faq/block.js +++ b/packages/js/src/structured-data-blocks/faq/block.js @@ -1,4 +1,5 @@ /* External dependencies */ +import { useBlockProps } from "@wordpress/block-editor"; import { registerBlockType } from "@wordpress/blocks"; /* Internal dependencies */ @@ -17,12 +18,16 @@ registerBlockType( block, { * @returns {Component} The editor component. */ edit: ( { attributes, setAttributes, className } ) => { + const blockProps = useBlockProps(); + // Because setAttributes is quite slow right after a block has been added we fake having a single step. if ( ! attributes.questions || attributes.questions.length === 0 ) { attributes.questions = [ { id: Faq.generateId( "faq-question" ), question: [], answer: [] } ]; } - return ; + return
+ +
; }, /** diff --git a/packages/js/src/structured-data-blocks/how-to/block.js b/packages/js/src/structured-data-blocks/how-to/block.js index f28b1f63d51..4418191a8bc 100644 --- a/packages/js/src/structured-data-blocks/how-to/block.js +++ b/packages/js/src/structured-data-blocks/how-to/block.js @@ -1,4 +1,5 @@ /* External dependencies */ +import { useBlockProps } from "@wordpress/block-editor"; import { registerBlockType } from "@wordpress/blocks"; /* Internal dependencies */ @@ -17,12 +18,16 @@ registerBlockType( block, { * @returns {Component} The editor component. */ edit: ( { attributes, setAttributes, className } ) => { + const blockProps = useBlockProps(); + // Because setAttributes is quite slow right after a block has been added we fake having a single step. if ( ! attributes.steps || attributes.steps.length === 0 ) { attributes.steps = [ { id: HowTo.generateId( "how-to-step" ), name: [], text: [] } ]; } - return ; + return
+ +
; }, /** diff --git a/packages/yoastseo/spec/appSpec.js b/packages/yoastseo/spec/appSpec.js index c7f23c768a6..ea9f8f4aad8 100644 --- a/packages/yoastseo/spec/appSpec.js +++ b/packages/yoastseo/spec/appSpec.js @@ -1,13 +1,10 @@ import MissingArgument from "../src/errors/missingArgument.js"; -import SnippetPreview from "../src/snippetPreview/snippetPreview.js"; import App from "../src/app.js"; // Mock these function to prevent us from needing an actual DOM in the tests. -App.prototype.createSnippetPreview = function() {}; App.prototype.showLoadingDialog = function() {}; App.prototype.updateLoadingDialog = function() {}; App.prototype.removeLoadingDialog = function() {}; -App.prototype.initSnippetPreview = function() {}; App.prototype.runAnalyzer = function() {}; // Makes lodash think this is a valid HTML element @@ -53,37 +50,6 @@ describe( "Creating an App", function() { } ).toThrowError( MissingArgument ); } ); - it( "throws on a missing snippet preview", function() { - expect( function() { - new App( { - targets: { - output: "outputID", - }, - callbacks: { - getData: () => { - return {}; - }, - }, - } ); - } ).toThrowError( MissingArgument ); - } ); - - it( "accepts a Snippet Preview object", function() { - new App( { - targets: { - output: "outputID", - }, - callbacks: { - getData: () => { - return {}; - }, - }, - snippetPreview: new SnippetPreview( { - targetElement: mockElement, - } ), - } ); - } ); - it( "should work without an output ID", function() { new App( { targets: { diff --git a/packages/yoastseo/spec/languageProcessing/helpers/word/countMetaDescriptionLengthSpec.js b/packages/yoastseo/spec/languageProcessing/helpers/word/countMetaDescriptionLengthSpec.js index 3dfbaa33c6e..3b18b2f3639 100644 --- a/packages/yoastseo/spec/languageProcessing/helpers/word/countMetaDescriptionLengthSpec.js +++ b/packages/yoastseo/spec/languageProcessing/helpers/word/countMetaDescriptionLengthSpec.js @@ -11,7 +11,12 @@ describe( "the meta description length research", function() { expect( result ).toBe( 44 ); } ); - it( "returns the length (0) of the description", function() { + it( "returns the length of the description when the meta description is empty", function() { + const result = metaDescriptionLength( "9 September 2021", "" ); + expect( result ).toBe( 19 ); + } ); + + it( "returns the length of the description when both fields are empty", function() { const result = metaDescriptionLength( "", "" ); expect( result ).toBe( 0 ); } ); diff --git a/packages/yoastseo/spec/snippetPreview/snippetPreviewSpec.js b/packages/yoastseo/spec/snippetPreview/snippetPreviewSpec.js deleted file mode 100644 index 1176941b544..00000000000 --- a/packages/yoastseo/spec/snippetPreview/snippetPreviewSpec.js +++ /dev/null @@ -1,243 +0,0 @@ -import SnippetPreview from "../../src/snippetPreview/snippetPreview.js"; -import "../../src/app.js"; -import Factory from "../../src/helpers/factory.js"; - -describe( "The snippet preview constructor", function() { - it( "accepts an App object as an opts property", function() { - var mockApp = { - rawData: { - snippetTitle: "", - snippetCite: "", - snippetMeta: "", - }, - }; - // Makes lodash think this is a valid HTML element - var mockElement = []; - mockElement.nodeType = 1; - - var snippetPreview = new SnippetPreview( { - analyzerApp: mockApp, - targetElement: mockElement, - } ); - - expect( snippetPreview.refObj ).toBe( mockApp ); - } ); -} ); - -describe( "The SnippetPreview format functions", function() { - it( "formats texts to use in the SnippetPreview", function() { - // Makes lodash think this is a valid HTML element - var mockElement = []; - mockElement.nodeType = 1; - - var mockApp = { - rawData: { - snippetTitle: "snippetTitle keyword", - snippetCite: "homeurl", - snippetMeta: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur lacinia eget neque ut porttitor. Quisque semper ligula leo. Nullam convallis ligula et dapibus dictum. Duis imperdiet nisl id orci hendrerit imperdiet. Praesent commodo ornare ante vitae placerat. Aliquam leo justo, imperdiet ut purus at, pharetra semper eros. In cursus faucibus efficitur.", - keyword: "keyword", - }, - pluggable: { - loaded: true, - /** - * A mock applyModifications function. - * - * @param {string} name The name of the modification to apply. - * @param {*} text Some data to apply modification to. - * - * @returns {*} The data to which the modification was applied. - */ - _applyModifications: function( name, text ) { - return text; - }, - }, - }; - - var snippetPreview = new SnippetPreview( { - analyzerApp: mockApp, - targetElement: mockElement, - } ); - - expect( snippetPreview.formatTitle() ).toBe( "snippetTitle keyword" ); - expect( snippetPreview.formatMeta() ).toBe( "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur lacinia eget neque ut porttitor. Quisque semper ligula leo. Nullam convallis ligula et da" ); - expect( snippetPreview.formatCite() ).toBe( "homeurl/" ); - expect( snippetPreview.formatKeyword( "a string with keyword" ) ).toBe( "a string with keyword" ); - - mockApp = { - rawData: { - snippetCite: "key-word", - keyword: "key word", - }, - pluggable: { - loaded: true, - /** - * A mock applyModifications function. - * - * @param {string} name The name of the modification to apply. - * @param {*} text Some data to apply modification to. - * - * @returns {*} The data to which the modification was applied. - */ - _applyModifications: function( name, text ) { - return text; - }, - }, - }; - - snippetPreview = new SnippetPreview( { - analyzerApp: mockApp, - targetElement: mockElement, - } ); - expect( snippetPreview.formatCite() ).toBe( "key-word/" ); - } ); - - describe( "#formatKeywordUrl", function() { - var snippetPreview, refObj, mockElement; - - beforeEach( function() { - mockElement = Factory.buildMockElement(); - - refObj = { - rawData: { keyword: "key'word" }, - }; - - snippetPreview = new SnippetPreview( { - analyzerApp: refObj, - targetElement: mockElement, - } ); - } ); - - it( "should highlight a keyword with an apostrophe", function() { - // Var keyword = "apo's"; - snippetPreview.data.urlPath = "keyword"; - - expect( snippetPreview.formatCite() ).toBe( "keyword/" ); - } ); - } ); - - describe( "The snippet preview format functions with special characters like periods", function() { - // Makes lodash think this is a valid HTML element - var mockElement = []; - mockElement.nodeType = 1; - - var mockApp = { - rawData: { - snippetMeta: "This is the Yoast SEO 3.9 release", - keyword: "Yoast SEO 3.9", - }, - pluggable: { - loaded: true, - /** - * A mock applyModifications function. - * - * @param {string} name The name of the modification to apply. - * @param {*} text Some data to apply modification to. - * - * @returns {*} The data to which the modification was applied. - */ - _applyModifications: function( name, text ) { - return text; - }, - }, - }; - - var snippetPreview = new SnippetPreview( { - analyzerApp: mockApp, - targetElement: mockElement, - } ); - - it( "should highlight the keywords", function() { - expect( snippetPreview.formatMeta() ).toBe( "This is the Yoast SEO 3.9 release" ); - } ); - } ); - describe( "The snippet preview format functions with special characters like periods", function() { - // Makes lodash think this is a valid HTML element - var mockElement = []; - mockElement.nodeType = 1; - - var mockApp = { - rawData: { - snippetMeta: "If you like Yoast SEO, please give a 5* rating", - keyword: "5* rating", - }, - pluggable: { - loaded: true, - /** - * A mock applyModifications function. - * - * @param {string} name The name of the modification to apply. - * @param {*} text Some data to apply modification to. - * - * @returns {*} The data to which the modification was applied. - */ - _applyModifications: function( name, text ) { - return text; - }, - }, - }; - - var snippetPreview = new SnippetPreview( { - analyzerApp: mockApp, - targetElement: mockElement, - } ); - - it( "should highlight the keywords", function() { - expect( snippetPreview.formatMeta() ).toBe( "If you like Yoast SEO, please give a 5* rating" ); - } ); - } ); -} ); - -describe( "Adds dashes to the keyword for highlighting in the snippet", function() { - it( "returns a keyword with strong tags", function() { - var mockApp = { - rawData: { - keyword: "keyword", - }, - }; - var mockElement = []; - mockElement.nodeType = 1; - - var snippetPreview = new SnippetPreview( { - analyzerApp: mockApp, - targetElement: mockElement, - } ); - - expect( snippetPreview.formatKeyword( "this is a keyword" ) ).toBe( "this is a keyword" ); - } ); -} ); - -describe( "Adds dashes to the keyword for highlighting in the snippet", function() { - it( "returns a keyword with strong tags", function() { - var mockApp = { - rawData: { - keyword: "key-word", - }, - }; - var mockElement = []; - mockElement.nodeType = 1; - - var snippetPreview = new SnippetPreview( { - analyzerApp: mockApp, - targetElement: mockElement, - } ); - expect( snippetPreview.formatKeyword( "this is a key-word with dash" ) ).toBe( "this is a key-word with dash" ); - } ); -} ); - -describe( "Formats the keyword in the title with diacritics", function() { - it( "returns a keyword with strong tags", function() { - var mockApp = { - rawData: { - keyword: "Slovníček pojmû", - }, - }; - var mockElement = []; - mockElement.nodeType = 1; - - var snippetPreview = new SnippetPreview( { - analyzerApp: mockApp, - targetElement: mockElement, - } ); - expect( snippetPreview.formatKeyword( "this is a Slovníček pojmû with diacritic" ) ).toBe( "this is a Slovníček pojmû with diacritic" ); - } ); -} ); diff --git a/packages/yoastseo/src/app.js b/packages/yoastseo/src/app.js index 17f63fce75a..da808c96d21 100644 --- a/packages/yoastseo/src/app.js +++ b/packages/yoastseo/src/app.js @@ -1,7 +1,7 @@ import { setLocaleData } from "@wordpress/i18n"; -import { debounce, defaultsDeep, forEach, isArray, isEmpty, isFunction, isObject, isString, isUndefined, merge, noop, throttle } from "lodash"; +import { debounce, defaultsDeep, forEach, isArray, isEmpty, isFunction, isObject, isUndefined, merge, noop, throttle } from "lodash"; import MissingArgument from "./errors/missingArgument"; -import { measureTextWidth } from "./helpers/createMeasurementElement.js"; +import { measureTextWidth } from "./helpers"; import removeHtmlBlocks from "./languageProcessing/helpers/html/htmlParser.js"; import Pluggable from "./pluggable.js"; @@ -11,7 +11,6 @@ import CornerstoneSEOAssessor from "./scoring/cornerstone/seoAssessor.js"; import AssessorPresenter from "./scoring/renderers/AssessorPresenter.js"; import SEOAssessor from "./scoring/seoAssessor.js"; -import SnippetPreview from "./snippetPreview/snippetPreview.js"; import Paper from "./values/Paper.js"; var inputDebounceDelay = 800; @@ -72,40 +71,9 @@ var defaults = { marker: noop, keywordAnalysisActive: true, contentAnalysisActive: true, - hasSnippetPreview: true, debounceRefresh: true, }; -/** - * Creates a default snippet preview, this can be used if no snippet preview has been passed. - * - * @private - * @this App - * - * @returns {SnippetPreview} The SnippetPreview object. - */ -function createDefaultSnippetPreview() { - var targetElement = document.getElementById( this.config.targets.snippet ); - - return new SnippetPreview( { - analyzerApp: this, - targetElement: targetElement, - callbacks: { - saveSnippetData: this.config.callbacks.saveSnippetData, - }, - } ); -} - -/** - * Returns whether or not the given argument is a valid SnippetPreview object. - * - * @param {*} snippetPreview The 'object' to check against. - * @returns {boolean} Whether or not it's a valid SnippetPreview object. - */ -function isValidSnippetPreview( snippetPreview ) { - return ! isUndefined( snippetPreview ) && Object.prototype.isPrototypeOf.call( SnippetPreview.prototype, snippetPreview ); -} - /** * Check arguments passed to the App to check if all necessary arguments are set. * @@ -121,15 +89,6 @@ function verifyArguments( args ) { if ( ! isObject( args.targets ) ) { throw new MissingArgument( "`targets` is a required App argument, `targets` is not an object." ); } - - // The args.targets.snippet argument is only required if not SnippetPreview object has been passed. - if ( - args.hasSnippetPreview && - ! isValidSnippetPreview( args.snippetPreview ) && - ! isString( args.targets.snippet ) ) { - throw new MissingArgument( "A snippet preview is required. When no SnippetPreview object isn't passed to " + - "the App, the `targets.snippet` is a required App argument. `targets.snippet` is not a string." ); - } } /** @@ -227,7 +186,6 @@ function verifyArguments( args ) { * @param {Function} args.callbacks.saveSnippetData Function called when the snippet data is changed. * @param {Function} args.marker The marker to use to apply the list of marks retrieved from an assessment. * - * @param {SnippetPreview} args.snippetPreview The SnippetPreview object to be used. * @param {boolean} [args.debouncedRefresh] Whether or not to debounce the * refresh function. Defaults to true. * @param {Researcher} args.researcher The Researcher object to be used. @@ -266,24 +224,10 @@ var App = function( args ) { this.showLoadingDialog(); } - if ( isValidSnippetPreview( args.snippetPreview ) ) { - this.snippetPreview = args.snippetPreview; - - /* Hack to make sure the snippet preview always has a reference to this App. This way we solve the circular - dependency issue. In the future this should be solved by the snippet preview not having a reference to the - app.*/ - if ( this.snippetPreview.refObj !== this ) { - this.snippetPreview.refObj = this; - } - } else if ( args.hasSnippetPreview ) { - this.snippetPreview = createDefaultSnippetPreview.call( this ); - } - this._assessorOptions = { useCornerStone: false, }; - this.initSnippetPreview(); this.initAssessorPresenters(); }; @@ -330,10 +274,7 @@ App.prototype.changeAssessorOptions = function( assessorOptions ) { */ App.prototype.getSeoAssessor = function() { const { useCornerStone } = this._assessorOptions; - - const assessor = useCornerStone ? this.cornerStoneSeoAssessor : this.defaultSeoAssessor; - - return assessor; + return useCornerStone ? this.cornerStoneSeoAssessor : this.defaultSeoAssessor; }; /** @@ -470,15 +411,6 @@ App.prototype.getData = function() { } ); } - if ( this.hasSnippetPreview() ) { - // Gets the data FOR the analyzer - var data = this.snippetPreview.getAnalyzerData(); - - this.rawData.metaTitle = data.title; - this.rawData.url = data.url; - this.rawData.meta = data.metaDesc; - } - if ( this.pluggable.loaded ) { this.rawData.metaTitle = this.pluggable._applyModifications( "data_page_title", this.rawData.metaTitle ); this.rawData.meta = this.pluggable._applyModifications( "data_meta_desc", this.rawData.meta ); @@ -515,29 +447,6 @@ App.prototype._pureRefresh = function() { this.runAnalyzer(); }; -/** - * Determines whether or not this app has a snippet preview. - * - * @returns {boolean} Whether or not this app has a snippet preview. - */ -App.prototype.hasSnippetPreview = function() { - return this.snippetPreview !== null && ! isUndefined( this.snippetPreview ); -}; - -/** - * Initializes the snippet preview for this App. - * - * @returns {void} - */ -App.prototype.initSnippetPreview = function() { - if ( this.hasSnippetPreview() ) { - this.snippetPreview.renderTemplate(); - this.snippetPreview.callRegisteredEventBinder(); - this.snippetPreview.bindEvents(); - this.snippetPreview.init(); - } -}; - /** * Initializes the assessor presenters for content and SEO. * @@ -565,29 +474,6 @@ App.prototype.initAssessorPresenters = function() { } }; -/** - * Binds the refresh function to the input of the targetElement on the page. - * - * @returns {void} - */ -App.prototype.bindInputEvent = function() { - for ( var i = 0; i < this.config.elementTarget.length; i++ ) { - var elem = document.getElementById( this.config.elementTarget[ i ] ); - elem.addEventListener( "input", this.refresh.bind( this ) ); - } -}; - -/** - * Runs the rerender function of the snippetPreview if that object is defined. - * - * @returns {void} - */ -App.prototype.reloadSnippetText = function() { - if ( this.hasSnippetPreview() ) { - this.snippetPreview.reRender(); - } -}; - /** * Sets the startTime timestamp. * @@ -628,19 +514,12 @@ App.prototype.runAnalyzer = function() { this.analyzerData = this.modifyData( this.rawData ); - if ( this.hasSnippetPreview() ) { - this.snippetPreview.refresh(); - } - let text = this.analyzerData.text; // Insert HTML stripping code text = removeHtmlBlocks( text ); - let titleWidth = this.analyzerData.titleWidth; - if ( this.hasSnippetPreview() ) { - titleWidth = this.snippetPreview.getTitleWidth(); - } + const titleWidth = this.analyzerData.titleWidth; // Create a paper object for the Researcher this.paper = new Paper( text, { @@ -665,10 +544,6 @@ App.prototype.runAnalyzer = function() { if ( this.config.dynamicDelay ) { this.endTime(); } - - if ( this.hasSnippetPreview() ) { - this.snippetPreview.reRender(); - } }; /** @@ -925,19 +800,6 @@ App.prototype.registerTest = function() { console.error( "This function is deprecated, please use registerAssessment" ); }; -/** - * Creates the elements for the snippetPreview - * - * @deprecated Don't create a snippet preview using this method, create it directly using the prototype and pass it as - * an argument instead. - * - * @returns {void} - */ -App.prototype.createSnippetPreview = function() { - this.snippetPreview = createDefaultSnippetPreview.call( this ); - this.initSnippetPreview(); -}; - /** * Switches between the cornerstone and default assessors. * diff --git a/packages/yoastseo/src/index.js b/packages/yoastseo/src/index.js index 166555183cb..f7d32ee13a7 100644 --- a/packages/yoastseo/src/index.js +++ b/packages/yoastseo/src/index.js @@ -13,7 +13,6 @@ import ContentAssessor from "./scoring/contentAssessor"; import SeoAssessor from "./scoring/seoAssessor"; import TaxonomyAssessor from "./scoring/taxonomyAssessor"; import Pluggable from "./pluggable"; -import SnippetPreview from "./snippetPreview/snippetPreview"; import Paper from "./values/Paper"; import AssessmentResult from "./values/AssessmentResult"; import Assessment from "./scoring/assessments/assessment"; @@ -31,7 +30,6 @@ export { SeoAssessor, TaxonomyAssessor, Pluggable, - SnippetPreview, Paper, AssessmentResult, @@ -65,7 +63,6 @@ export default { ContentAssessor, TaxonomyAssessor, Pluggable, - SnippetPreview, Paper, AssessmentResult, diff --git a/packages/yoastseo/src/languageProcessing/helpers/word/countMetaDescriptionLength.js b/packages/yoastseo/src/languageProcessing/helpers/word/countMetaDescriptionLength.js index 31035f3a525..749840d2956 100644 --- a/packages/yoastseo/src/languageProcessing/helpers/word/countMetaDescriptionLength.js +++ b/packages/yoastseo/src/languageProcessing/helpers/word/countMetaDescriptionLength.js @@ -1,16 +1,16 @@ /** - * Check the length of the description. + * Returns the total length of the meta description (including the date if present). * * @param {string} date The date. * @param {string} description The meta description. * - * @returns {number} The length of the description. + * @returns {number} The total length of the meta description. */ export default function( date, description ) { let descriptionLength = description.length; /* If the meta description is preceded by a date, two spaces and a hyphen (" - ") are added as well. Therefore, three needs to be added to the total length. */ - if ( date !== "" && descriptionLength > 0 ) { + if ( date !== "" ) { descriptionLength += date.length + 3; } diff --git a/packages/yoastseo/src/snippetPreview/snippetPreview.js b/packages/yoastseo/src/snippetPreview/snippetPreview.js deleted file mode 100644 index 347039cd659..00000000000 --- a/packages/yoastseo/src/snippetPreview/snippetPreview.js +++ /dev/null @@ -1,1305 +0,0 @@ -import { __ } from "@wordpress/i18n"; -import { clone, debounce, defaultsDeep, forEach, isElement, isEmpty, isUndefined } from "lodash"; - -import createWordRegex from "../languageProcessing/helpers/regex/createWordRegex"; -import { stripFullTags as stripHTMLTags } from "../languageProcessing/helpers/sanitize/stripHTMLTags"; -import stripSpaces from "../languageProcessing/helpers/sanitize/stripSpaces"; -import replaceDiacritics from "../languageProcessing/helpers/transliterate/replaceDiacritics"; -import transliterate from "../languageProcessing/helpers/transliterate/transliterate"; - -import SnippetPreviewToggler from "./snippetPreviewToggler"; -import domManipulation from "../helpers/domManipulation.js"; - -var defaults = { - data: { - title: "", - metaDesc: "", - urlPath: "", - titleWidth: 0, - metaHeight: 0, - }, - placeholder: { - title: "", - metaDesc: "", - urlPath: "example-post/", - }, - defaultValue: { - title: "", - metaDesc: "", - }, - baseURL: "http://example.com/", - callbacks: { - /** - * Empty function. - * @returns {void} - */ - saveSnippetData: function() {}, - }, - addTrailingSlash: true, - metaDescriptionDate: "", - previewMode: "desktop", - -}; - -var titleMaxLength = 600; -const maximumMetaDescriptionLength = 156; - -var inputPreviewBindings = [ - { - preview: "title_container", - inputField: "title", - }, - { - preview: "url_container", - inputField: "urlPath", - }, - { - preview: "meta_container", - inputField: "metaDesc", - }, -]; - -/** - * Get's the base URL for this instance of the Snippet Preview. - * - * @private - * @this SnippetPreview - * - * @returns {string} The base URL. - */ -var getBaseURL = function() { - var baseURL = this.opts.baseURL; - - /* - * For backwards compatibility, if no URL was passed to the snippet editor we try to retrieve the base URL from the - * rawData in the App. This is because the scrapers used to be responsible for retrieving the baseURL, but the base - * URL is static so we can just pass it to the snippet editor. - */ - if ( this.hasApp() && ! isEmpty( this.refObj.rawData.baseUrl ) && this.opts.baseURL === defaults.baseURL ) { - baseURL = this.refObj.rawData.baseUrl; - } - - return baseURL; -}; - -/** - * Retrieves unformatted text from the data object - * - * @private - * @this SnippetPreview - * - * @param {string} key The key to retrieve. - * - * @returns {string} The unformatted text. - */ -function retrieveUnformattedText( key ) { - return this.data[ key ]; -} - -/** - * Update data and DOM objects when the unformatted text is updated, here for backwards compatibility - * - * @private - * @this SnippetPreview - * - * @param {string} key The data key to update. - * @param {string} value The value to update. - * - * @returns {void} - */ -function updateUnformattedText( key, value ) { - this.element.input[ key ].value = value; - - this.data[ key ] = value; -} - -/** - * Returns if a url has a trailing slash or not. - * - * @param {string} url The url to check for a trailing slash. - * - * @returns {boolean} Whether or not the url contains a trailing slash. - */ -function hasTrailingSlash( url ) { - return url.indexOf( "/" ) === ( url.length - 1 ); -} - -/** - * Detects if this browser has support. Also serves as a poor man's HTML5shiv. - * - * @private - * - * @returns {boolean} Whether or not the browser supports a element - */ -function hasProgressSupport() { - var progressElement = document.createElement( "progress" ); - - return ! isUndefined( progressElement.max ); -} - -/** - * Returns a rating based on the length of the title - * - * @param {number} titleLength the length of the title. - * - * @returns {string} The rating given based on the title length. - */ -function rateTitleLength( titleLength ) { - var rating; - - switch ( true ) { - case titleLength > 0 && titleLength <= 399: - case titleLength > 600: - rating = "ok"; - break; - - case titleLength >= 400 && titleLength <= 600: - rating = "good"; - break; - - default: - rating = "bad"; - break; - } - - return rating; -} - -/** - * Returns a rating based on the length of the meta description - * - * @param {number} metaDescLength the length of the meta description. - * - * @returns {string} The rating given based on the description length. - */ -function rateMetaDescLength( metaDescLength ) { - var rating; - - switch ( true ) { - case metaDescLength > 0 && metaDescLength < 120: - case metaDescLength > maximumMetaDescriptionLength: - rating = "ok"; - break; - - case metaDescLength >= 120 && metaDescLength <= maximumMetaDescriptionLength: - rating = "good"; - break; - - default: - rating = "bad"; - break; - } - - return rating; -} - -/** - * Updates a progress bar - * - * @private - * @this SnippetPreview - * - * @param {HTMLElement} element The progress element that's rendered. - * @param {number} value The current value. - * @param {number} maximum The maximum allowed value. - * @param {string} rating The SEO score rating for this value. - * - * @returns {void} - */ -function updateProgressBar( element, value, maximum, rating ) { - var barElement, progress, - allClasses = [ - "snippet-editor__progress--bad", - "snippet-editor__progress--ok", - "snippet-editor__progress--good", - ]; - - element.value = value; - domManipulation.removeClasses( element, allClasses ); - domManipulation.addClass( element, "snippet-editor__progress--" + rating ); - - if ( ! this.hasProgressSupport ) { - barElement = element.getElementsByClassName( "snippet-editor__progress-bar" )[ 0 ]; - progress = ( value / maximum ) * 100; - - barElement.style.width = progress + "%"; - } -} - -/** - * @module snippetPreview - */ - -/** - * Defines the config and outputTarget for the SnippetPreview - * - * @param {Object} opts - Snippet preview options. - * @param {App} opts.analyzerApp - The app object the Snippet Preview is part of. - * @param {Object} opts.placeholder - The placeholder values for the fields, will be shown as - * actual placeholders in the inputs and as a fallback for the preview. - * @param {string} opts.placeholder.title - The placeholder title. - * @param {string} opts.placeholder.metaDesc - The placeholder meta description. - * @param {string} opts.placeholder.urlPath - The placeholder url. - * - * @param {Object} opts.defaultValue - The default value for the fields, if the user has not - * changed a field, this value will be used for the analyzer, preview and the progress bars. - * @param {string} opts.defaultValue.title - The default title. - * @param {string} opts.defaultValue.metaDesc - The default meta description. - * it. - * - * @param {string} opts.baseURL - The basic URL as it will be displayed in google. - * @param {HTMLElement} opts.targetElement - The target element that contains this snippet editor. - * - * @param {Object} opts.callbacks - Functions that are called on specific instances. - * @param {Function} opts.callbacks.saveSnippetData - Function called when the snippet data is changed. - * - * @param {boolean} opts.addTrailingSlash - Whether or not to add a trailing slash to the URL. - * @param {string} opts.metaDescriptionDate - The date to display before the meta description. - * - * @param {string} opts.previewMode - The current preview mode. - * - * @property {App} refObj - The connected app object. - * - * @property {HTMLElement} targetElement - The target element that contains this snippet editor. - * - * @property {Object} element - The elements for this snippet editor. - * @property {Object} element.rendered - The rendered elements. - * @property {HTMLElement} element.rendered.title - The rendered title element. - * @property {HTMLElement} element.rendered.urlPath - The rendered url path element. - * @property {HTMLElement} element.rendered.urlBase - The rendered url base element. - * @property {HTMLElement} element.rendered.metaDesc - The rendered meta description element. - * - * @property {HTMLElement} element.measurers.titleWidth - The rendered title width element. - * @property {HTMLElement} element.measurers.metaHeight - The rendered meta height element. - * - * @property {Object} element.input - The input elements. - * @property {HTMLElement} element.input.title - The title input element. - * @property {HTMLElement} element.input.urlPath - The url path input element. - * @property {HTMLElement} element.input.metaDesc - The meta description input element. - * - * @property {HTMLElement} element.container - The main container element. - * @property {HTMLElement} element.formContainer - The form container element. - * @property {HTMLElement} element.editToggle - The button that toggles the editor form. - * - * @property {Object} data - The data for this snippet editor. - * @property {string} data.title - The title. - * @property {string} data.urlPath - The url path. - * @property {string} data.metaDesc - The meta description. - * @property {int} data.titleWidth - The width of the title in pixels. - * @property {int} data.metaHeight - The height of the meta description in pixels. - * - * @property {string} baseURL - The basic URL as it will be displayed in google. - * - * @property {boolean} hasProgressSupport - Whether this browser supports the element. - * - * @constructor - */ -var SnippetPreview = function( opts ) { - defaultsDeep( opts, defaults ); - - this.data = opts.data; - - if ( ! isUndefined( opts.analyzerApp ) ) { - this.refObj = opts.analyzerApp; - - this.data = { - title: this.refObj.rawData.snippetTitle || "", - urlPath: this.refObj.rawData.snippetCite || "", - metaDesc: this.refObj.rawData.snippetMeta || "", - }; - - // For backwards compatibility set the metaTitle as placeholder. - if ( ! isEmpty( this.refObj.rawData.metaTitle ) ) { - opts.placeholder.title = this.refObj.rawData.metaTitle; - } - } - - if ( ! isElement( opts.targetElement ) ) { - throw new Error( "The snippet preview requires a valid target element" ); - } - - this.opts = opts; - this._currentFocus = null; - this._currentHover = null; - - // For backwards compatibility monitor the unformatted text for changes and reflect them in the preview - this.unformattedText = {}; - Object.defineProperty( this.unformattedText, "snippet_cite", { - get: retrieveUnformattedText.bind( this, "urlPath" ), - set: updateUnformattedText.bind( this, "urlPath" ), - } ); - Object.defineProperty( this.unformattedText, "snippet_meta", { - get: retrieveUnformattedText.bind( this, "metaDesc" ), - set: updateUnformattedText.bind( this, "metaDesc" ), - } ); - Object.defineProperty( this.unformattedText, "snippet_title", { - get: retrieveUnformattedText.bind( this, "title" ), - set: updateUnformattedText.bind( this, "title" ), - } ); -}; - -/** - * Renders snippet editor and adds it to the targetElement - * @returns {void} - */ -SnippetPreview.prototype.renderTemplate = function() { - var targetElement = this.opts.targetElement; - - this.element = { - measurers: { - metaHeight: null, - }, - rendered: { - title: document.getElementById( "snippet_title" ), - urlBase: document.getElementById( "snippet_citeBase" ), - urlPath: document.getElementById( "snippet_cite" ), - metaDesc: document.getElementById( "snippet_meta" ), - }, - input: { - title: targetElement.getElementsByClassName( "js-snippet-editor-title" )[ 0 ], - urlPath: targetElement.getElementsByClassName( "js-snippet-editor-slug" )[ 0 ], - metaDesc: targetElement.getElementsByClassName( "js-snippet-editor-meta-description" )[ 0 ], - }, - progress: { - title: targetElement.getElementsByClassName( "snippet-editor__progress-title" )[ 0 ], - metaDesc: targetElement.getElementsByClassName( "snippet-editor__progress-meta-description" )[ 0 ], - }, - container: document.getElementById( "snippet_preview" ), - formContainer: targetElement.getElementsByClassName( "snippet-editor__form" )[ 0 ], - editToggle: targetElement.getElementsByClassName( "snippet-editor__edit-button" )[ 0 ], - closeEditor: targetElement.getElementsByClassName( "snippet-editor__submit" )[ 0 ], - formFields: targetElement.getElementsByClassName( "snippet-editor__form-field" ), - }; - - this.element.label = { - title: this.element.input.title.parentNode, - urlPath: this.element.input.urlPath.parentNode, - metaDesc: this.element.input.metaDesc.parentNode, - }; - - this.element.preview = { - title: this.element.rendered.title.parentNode, - urlPath: this.element.rendered.urlPath.parentNode, - metaDesc: this.element.rendered.metaDesc.parentNode, - }; - - this.hasProgressSupport = hasProgressSupport(); - - if ( this.hasProgressSupport ) { - this.element.progress.title.max = titleMaxLength; - this.element.progress.metaDesc.max = maximumMetaDescriptionLength; - } else { - forEach( this.element.progress, function( progressElement ) { - domManipulation.addClass( progressElement, "snippet-editor__progress--fallback" ); - } ); - } - - this.initPreviewToggler(); - this.setInitialView(); - - this.opened = false; - this.createMeasurementElements(); - this.updateProgressBars(); -}; - -/** - * Initializes the Snippet Preview Toggler. - * - * @returns {void} - */ -SnippetPreview.prototype.initPreviewToggler = function() { - this.snippetPreviewToggle = new SnippetPreviewToggler( - this.opts.previewMode, this.opts.targetElement.getElementsByClassName( "snippet-editor__view-icon" ) - ); - this.snippetPreviewToggle.initialize(); - this.snippetPreviewToggle.bindEvents(); -}; - -/** - * Refreshes the snippet editor rendered HTML - * @returns {void} - */ -SnippetPreview.prototype.refresh = function() { - this.output = this.htmlOutput(); - this.renderOutput(); - this.renderSnippetStyle(); - this.measureTitle(); - this.measureMetaDescription(); - this.updateProgressBars(); -}; - -/** - * Returns the title as meant for the analyzer - * - * @private - * @this SnippetPreview - * - * @returns {string} The title that is meant for the analyzer. - */ -function getAnalyzerTitle() { - var title = this.data.title; - - if ( isEmpty( title ) ) { - title = this.opts.defaultValue.title; - } - - if ( this.hasPluggable() ) { - title = this.refObj.pluggable._applyModifications( "data_page_title", title ); - } - - return stripSpaces( title ); -} - -/** - * Returns the metaDescription, includes the date if it is set. - * - * @private - * @this SnippetPreview - * - * @returns {string} The meta data for the analyzer. - */ -var getAnalyzerMetaDesc = function() { - var metaDesc = this.data.metaDesc; - - if ( isEmpty( metaDesc ) ) { - metaDesc = this.opts.defaultValue.metaDesc; - } - - if ( this.hasPluggable() ) { - metaDesc = this.refObj.pluggable._applyModifications( "data_meta_desc", metaDesc ); - } - - if ( ! isEmpty( this.opts.metaDescriptionDate ) && ! isEmpty( metaDesc ) ) { - metaDesc = this.opts.metaDescriptionDate + " - " + this.data.metaDesc; - } - - return stripSpaces( metaDesc ); -}; - -/** - * Returns the data from the Snippet Preview. - * - * @returns {Object} The collected data for the analyzer. - */ -SnippetPreview.prototype.getAnalyzerData = function() { - return { - title: getAnalyzerTitle.call( this ), - url: this.data.urlPath, - metaDesc: getAnalyzerMetaDesc.call( this ), - }; -}; - -/** - * Calls the event binder that has been registered using the callbacks option in the arguments of the App. - * - * @returns {void} - */ -SnippetPreview.prototype.callRegisteredEventBinder = function() { - if ( this.hasApp() ) { - this.refObj.callbacks.bindElementEvents( this.refObj ); - } -}; - -/** - * Checks if title and url are set so they can be rendered in the Snippet Preview. - * - * @returns {void} - */ -SnippetPreview.prototype.init = function() { - if ( - this.hasApp() && - this.refObj.rawData.metaTitle !== null && - this.refObj.rawData.cite !== null - ) { - this.refresh(); - } -}; - -/** - * Creates html object to contain the strings for the Snippet Preview. - * - * @returns {Object} The HTML output of the collected data. - */ -SnippetPreview.prototype.htmlOutput = function() { - var html = {}; - html.title = this.formatTitle(); - html.cite = this.formatCite(); - html.meta = this.formatMeta(); - html.url = this.formatUrl(); - return html; -}; - -/** - * Formats the title for the Snippet Preview. If title and pageTitle are empty, sampletext is used. - * - * @returns {string} The correctly formatted title. - */ -SnippetPreview.prototype.formatTitle = function() { - var title = this.data.title; - - // Fallback to the default if the title is empty. - if ( isEmpty( title ) ) { - title = this.opts.defaultValue.title; - } - - // For rendering we can fallback to the placeholder as well. - if ( isEmpty( title ) ) { - title = this.opts.placeholder.title; - } - - // Apply modification to the title before showing it. - if ( this.hasPluggable() && this.refObj.pluggable.loaded ) { - title = this.refObj.pluggable._applyModifications( "data_page_title", title ); - } - - title = stripHTMLTags( title ); - - // As an ultimate fallback provide the user with a helpful message. - if ( isEmpty( title ) ) { - title = __( "Please provide an SEO title by editing the snippet below.", "wordpress-seo" ); - } - - return title; -}; - -/** - * Formats the base url for the Snippet Preview. Removes the protocol name from the URL. - * - * @returns {string} Formatted base url for the Snippet Preview. - */ -SnippetPreview.prototype.formatUrl = function() { - var url = getBaseURL.call( this ); - - // Removes the http part of the url, google displays https:// if the website supports it. - return url.replace( /http:\/\//ig, "" ); -}; - -/** - * Formats the url for the Snippet Preview. - * - * @returns {string} Formatted URL for the Snippet Preview. - */ -SnippetPreview.prototype.formatCite = function() { - var cite = this.data.urlPath; - - cite = replaceDiacritics( stripHTMLTags( cite ) ); - - // Fallback to the default if the cite is empty. - if ( isEmpty( cite ) ) { - cite = this.opts.placeholder.urlPath; - } - - if ( this.hasApp() && ! isEmpty( this.refObj.rawData.keyword ) ) { - cite = this.formatKeywordUrl( cite ); - } - - if ( this.opts.addTrailingSlash && ! hasTrailingSlash( cite ) ) { - cite = cite + "/"; - } - - // URL's cannot contain whitespace so replace it by dashes. - cite = cite.replace( /\s/g, "-" ); - // Strip out question mark and hash characters from the raw URL. - cite = cite.replace( /\?|#/g, "" ); - - return cite; -}; - -/** - * Formats the meta description for the Snippet Preview, if it's empty retrieves it using getMetaText. - * - * @returns {string} Formatted meta description. - */ -SnippetPreview.prototype.formatMeta = function() { - var meta = this.data.metaDesc; - - // If no meta has been set, generate one. - if ( isEmpty( meta ) ) { - meta = this.getMetaText(); - } - - // Apply modification to the desc before showing it. - if ( this.hasPluggable() && this.refObj.pluggable.loaded ) { - meta = this.refObj.pluggable._applyModifications( "data_meta_desc", meta ); - } - - meta = stripHTMLTags( meta ); - - // Cut-off the meta description according to the maximum length - meta = meta.substring( 0, maximumMetaDescriptionLength ); - - if ( this.hasApp() && ! isEmpty( this.refObj.rawData.keyword ) ) { - meta = this.formatKeyword( meta ); - } - - // As an ultimate fallback provide the user with a helpful message. - if ( isEmpty( meta ) ) { - meta = __( "Please provide a meta description by editing the snippet below.", "wordpress-seo" ); - } - - return meta; -}; - -/** - * Generates a meta description with an educated guess based on the passed text and excerpt. - * It uses the keyword to select an appropriate part of the text. If the keyword isn't present it takes the maximum - * meta description length of the text. If both the keyword, text and excerpt are empty this function returns the - * sample text. - * - * @returns {string} A generated meta description. - */ -SnippetPreview.prototype.getMetaText = function() { - var metaText = this.opts.defaultValue.metaDesc; - - if ( this.hasApp() && ! isUndefined( this.refObj.rawData.excerpt ) && isEmpty( metaText ) ) { - metaText = this.refObj.rawData.excerpt; - } - - if ( this.hasApp() && ! isUndefined( this.refObj.rawData.text ) && isEmpty( metaText ) ) { - metaText = this.refObj.rawData.text; - - if ( this.hasPluggable() && this.refObj.pluggable.loaded ) { - metaText = this.refObj.pluggable._applyModifications( "content", metaText ); - } - } - - metaText = stripHTMLTags( metaText ); - - return metaText.substring( 0, maximumMetaDescriptionLength ); -}; - -/** - * Builds an array with all indexes of the keyword. - * - * @returns {Array} Array with matches - */ -SnippetPreview.prototype.getIndexMatches = function() { - var indexMatches = []; - var i = 0; - - // Starts at 0, locates first match of the keyword. - var match = this.refObj.rawData.text.indexOf( - this.refObj.rawData.keyword, - i - ); - - // Runs the loop untill no more indexes are found, and match returns -1. - while ( match > -1 ) { - indexMatches.push( match ); - - // Pushes location to indexMatches and increase i with the length of keyword. - i = match + this.refObj.rawData.keyword.length; - match = this.refObj.rawData.text.indexOf( - this.refObj.rawData.keyword, - i - ); - } - return indexMatches; -}; - -/** - * Builds an array with indexes of all sentence ends (select on .). - * - * @returns {Array} Array with sentences. - */ -SnippetPreview.prototype.getPeriodMatches = function() { - var periodMatches = [ 0 ]; - var match; - var i = 0; - while ( ( match = this.refObj.rawData.text.indexOf( ".", i ) ) > -1 ) { - periodMatches.push( match ); - i = match + 1; - } - return periodMatches; -}; - -/** - * Formats the keyword for use in the snippetPreview by adding -tags - * strips unwanted characters that could break the regex or give unwanted results. - * - * @param {string} textString The keyword string that needs to be formatted. - * - * @returns {string} The formatted keyword. - */ -SnippetPreview.prototype.formatKeyword = function( textString ) { - // Removes characters from the keyword that could break the regex, or give unwanted results. - var keyword = this.refObj.rawData.keyword; - - // Match keyword case-insensitively. - var keywordRegex = createWordRegex( keyword, "", false ); - - textString = textString.replace( keywordRegex, function( str ) { - return "" + str + ""; - } ); - - // Transliterate the keyword for highlighting - var transliterateKeyword = transliterate( keyword, this.refObj.rawData.locale ); - if ( transliterateKeyword !== keyword ) { - keywordRegex = createWordRegex( transliterateKeyword, "", false ); - textString = textString.replace( keywordRegex, function( str ) { - return "" + str + ""; - } ); - } - return textString; -}; - -/** - * Formats the keyword for use in the URL by accepting - and _ instead of spaces and by adding -tags. - * Strips unwanted characters that could break the regex or give unwanted results. - * - * @param {string} textString The keyword string that needs to be formatted. - * - * @returns {XML|string|void} The formatted keyword string to be used in the URL. - */ -SnippetPreview.prototype.formatKeywordUrl = function( textString ) { - var keyword = this.refObj.rawData.keyword; - keyword = transliterate( keyword, this.refObj.rawData.locale ); - keyword = keyword.replace( /'/, "" ); - - var dashedKeyword = keyword.replace( /\s/g, "-" ); - - // Match keyword case-insensitively. - var keywordRegex = createWordRegex( dashedKeyword, "\\-" ); - - // Make the keyword bold in the textString. - return textString.replace( keywordRegex, function( str ) { - return "" + str + ""; - } ); -}; - -/** - * Renders the outputs to the elements on the page. - * - * @returns {void} - */ -SnippetPreview.prototype.renderOutput = function() { - this.element.rendered.title.innerHTML = this.output.title; - this.element.rendered.urlPath.innerHTML = this.output.cite; - this.element.rendered.urlBase.innerHTML = this.output.url; - this.element.rendered.metaDesc.innerHTML = this.output.meta; -}; - -/** - * Makes the rendered meta description gray if no meta description has been set by the user. - * - * @returns {void} - */ -SnippetPreview.prototype.renderSnippetStyle = function() { - var metaDescElement = this.element.rendered.metaDesc; - var metaDesc = getAnalyzerMetaDesc.call( this ); - - if ( isEmpty( metaDesc ) ) { - domManipulation.addClass( metaDescElement, "desc-render" ); - domManipulation.removeClass( metaDescElement, "desc-default" ); - } else { - domManipulation.addClass( metaDescElement, "desc-default" ); - domManipulation.removeClass( metaDescElement, "desc-render" ); - } -}; - -/** - * Function to call init, to rerender the Snippet Preview. - * - * @returns {void} - */ -SnippetPreview.prototype.reRender = function() { - this.init(); -}; - -/** - * Checks text length of the snippetmeta and snippet title, shortens it if it is too long. - * @param {Object} event The event to check the text length from. - * - * @returns {void} - */ -SnippetPreview.prototype.checkTextLength = function( event ) { - var text = event.currentTarget.textContent; - switch ( event.currentTarget.id ) { - case "snippet_meta": - event.currentTarget.className = "desc"; - if ( text.length > maximumMetaDescriptionLength ) { - /* eslint-disable */ - YoastSEO.app.snippetPreview.unformattedText.snippet_meta = event.currentTarget.textContent; - /* eslint-enable */ - event.currentTarget.textContent = text.substring( - 0, - maximumMetaDescriptionLength - ); - } - break; - case "snippet_title": - event.currentTarget.className = "title"; - if ( text.length > titleMaxLength ) { - /* eslint-disable */ - YoastSEO.app.snippetPreview.unformattedText.snippet_title = event.currentTarget.textContent; - /* eslint-enable */ - event.currentTarget.textContent = text.substring( 0, titleMaxLength ); - } - break; - default: - break; - } -}; - -/** - * Get the unformatted text. - * - * When clicking on an element in the Snippet Preview, this checks and fills the textContent with the data from the - * unformatted text. This removes the keyword highlighting and modified data so the original content can be editted. - * - * @param {Object} event The event to get the unformatted text from. - * - * @returns {void} - */ -SnippetPreview.prototype.getUnformattedText = function( event ) { - var currentElement = event.currentTarget.id; - if ( typeof this.unformattedText[ currentElement ] !== "undefined" ) { - event.currentTarget.textContent = this.unformattedText[ currentElement ]; - } -}; - -/** - * Sets the unformatted text. - * - * When text is entered into the snippetPreview elements, the text is set in the unformattedText object. - * This allows the visible data to be editted in the snippetPreview. - * - * @param {Object} event The event to set the unformatted text from. - * - * @returns {void} - */ -SnippetPreview.prototype.setUnformattedText = function( event ) { - var elem = event.currentTarget.id; - this.unformattedText[ elem ] = document.getElementById( elem ).textContent; -}; - -/** - * Validates all fields and highlights errors. - * - * @returns {void} - */ -SnippetPreview.prototype.validateFields = function() { - var metaDescription = getAnalyzerMetaDesc.call( this ); - var title = getAnalyzerTitle.call( this ); - - if ( metaDescription.length > maximumMetaDescriptionLength ) { - domManipulation.addClass( this.element.input.metaDesc, "snippet-editor__field--invalid" ); - } else { - domManipulation.removeClass( this.element.input.metaDesc, "snippet-editor__field--invalid" ); - } - - if ( title.length > titleMaxLength ) { - domManipulation.addClass( this.element.input.title, "snippet-editor__field--invalid" ); - } else { - domManipulation.removeClass( this.element.input.title, "snippet-editor__field--invalid" ); - } -}; - -/** - * Updates progress bars based on the available data. - * - * @returns {void} - */ -SnippetPreview.prototype.updateProgressBars = function() { - var metaDescriptionRating, titleRating, metaDescription; - - metaDescription = getAnalyzerMetaDesc.call( this ); - - titleRating = rateTitleLength( this.data.titleWidth ); - metaDescriptionRating = rateMetaDescLength( metaDescription.length ); - - updateProgressBar.call( - this, - this.element.progress.title, - this.data.titleWidth, - titleMaxLength, - titleRating - ); - - updateProgressBar.call( - this, - this.element.progress.metaDesc, - metaDescription.length, - maximumMetaDescriptionLength, - metaDescriptionRating - ); -}; - -/** - * Gets the width of the Snippet Preview to set its initial view to desktop or mobile. - * - * @returns {void} - */ -SnippetPreview.prototype.setInitialView = function() { - var previewWidth = document.getElementById( "snippet_preview" ).getBoundingClientRect().width; - this.snippetPreviewToggle.setVisibility( previewWidth ); -}; - -/** - * When the window is resized, gets the width of the Snippet Preview to set the Scroll Hint visibility. - * - * @returns {void} - */ -SnippetPreview.prototype.handleWindowResizing = debounce( function() { - var previewWidth = document.getElementById( "snippet_preview" ).getBoundingClientRect().width; - this.snippetPreviewToggle.setScrollHintVisibility( previewWidth ); -}, 25 ); - -/** - * Binds the reloadSnippetText function to the blur of the snippet inputs. - * - * @returns {void} - */ -SnippetPreview.prototype.bindEvents = function() { - var targetElement, - elems = [ "title", "slug", "meta-description" ]; - - forEach( elems, function( elem ) { - targetElement = document.getElementsByClassName( "js-snippet-editor-" + elem )[ 0 ]; - - targetElement.addEventListener( "keydown", this.changedInput.bind( this ) ); - targetElement.addEventListener( "keyup", this.changedInput.bind( this ) ); - - targetElement.addEventListener( "input", this.changedInput.bind( this ) ); - targetElement.addEventListener( "focus", this.changedInput.bind( this ) ); - targetElement.addEventListener( "blur", this.changedInput.bind( this ) ); - }.bind( this ) ); - - this.element.editToggle.addEventListener( "click", this.toggleEditor.bind( this ) ); - this.element.closeEditor.addEventListener( "click", this.closeEditor.bind( this ) ); - - // Note: `handleWindowResizing` is called also in Yoast SEO when the WP admin menu state changes. - window.addEventListener( "resize", this.handleWindowResizing.bind( this ) ); - - // Loop through the bindings and bind a click handler to the click to focus the focus element. - forEach( inputPreviewBindings, function( binding ) { - var previewElement = document.getElementById( binding.preview ); - var inputElement = this.element.input[ binding.inputField ]; - - // Make the preview element click open the editor and focus the correct input. - previewElement.addEventListener( "click", function() { - this.openEditor(); - inputElement.focus(); - }.bind( this ) ); - - // Make focusing an input, update the carets. - inputElement.addEventListener( "focus", function() { - this._currentFocus = binding.inputField; - - this._updateFocusCarets(); - }.bind( this ) ); - - // Make removing focus from an element, update the carets. - inputElement.addEventListener( "blur", function() { - this._currentFocus = null; - - this._updateFocusCarets(); - }.bind( this ) ); - - previewElement.addEventListener( "mouseover", function() { - this._currentHover = binding.inputField; - - this._updateHoverCarets(); - }.bind( this ) ); - - previewElement.addEventListener( "mouseout", function() { - this._currentHover = null; - - this._updateHoverCarets(); - }.bind( this ) ); - }.bind( this ) ); -}; - -/** - * Updates Snippet Preview on changed input. It's debounced so that we can call this function as much as we want. - * - * @returns {void} - */ -SnippetPreview.prototype.changedInput = debounce( function() { - this.updateDataFromDOM(); - this.validateFields(); - this.updateProgressBars(); - - this.refresh(); - - if ( this.hasApp() ) { - this.refObj.refresh(); - } -}, 25 ); - -/** - * Updates our data object from the DOM. - * - * @returns {void} - */ -SnippetPreview.prototype.updateDataFromDOM = function() { - this.data.title = this.element.input.title.value; - this.data.urlPath = this.element.input.urlPath.value; - this.data.metaDesc = this.element.input.metaDesc.value; - - // Clone so the data isn't changeable. - this.opts.callbacks.saveSnippetData( clone( this.data ) ); -}; - -/** - * Opens the snippet editor. - * - * @returns {void} - */ -SnippetPreview.prototype.openEditor = function() { - this.element.editToggle.setAttribute( "aria-expanded", "true" ); - - // Show these elements. - domManipulation.removeClass( this.element.formContainer, "snippet-editor--hidden" ); - - this.opened = true; -}; - -/** - * Closes the snippet editor. - * - * @returns {void} - */ -SnippetPreview.prototype.closeEditor = function() { - // Hide these elements. - domManipulation.addClass( this.element.formContainer, "snippet-editor--hidden" ); - - this.element.editToggle.setAttribute( "aria-expanded", "false" ); - this.element.editToggle.focus(); - - this.opened = false; -}; - -/** - * Toggles the snippet editor. - * - * @returns {void} - */ -SnippetPreview.prototype.toggleEditor = function() { - if ( this.opened ) { - this.closeEditor(); - } else { - this.openEditor(); - } -}; - -/** - * Updates carets before the preview and input fields. - * - * @private - * - * @returns {void} - */ -SnippetPreview.prototype._updateFocusCarets = function() { - var focusedLabel, focusedPreview; - - // Disable all carets on the labels. - forEach( this.element.label, function( element ) { - domManipulation.removeClass( element, "snippet-editor__label--focus" ); - } ); - - // Disable all carets on the previews. - forEach( this.element.preview, function( element ) { - domManipulation.removeClass( element, "snippet-editor__container--focus" ); - } ); - - if ( null !== this._currentFocus ) { - focusedLabel = this.element.label[ this._currentFocus ]; - focusedPreview = this.element.preview[ this._currentFocus ]; - - domManipulation.addClass( focusedLabel, "snippet-editor__label--focus" ); - domManipulation.addClass( focusedPreview, "snippet-editor__container--focus" ); - } -}; - -/** - * Updates hover carets before the input fields. - * - * @private - * - * @returns {void} - */ -SnippetPreview.prototype._updateHoverCarets = function() { - var hoveredLabel; - - forEach( this.element.label, function( element ) { - domManipulation.removeClass( element, "snippet-editor__label--hover" ); - } ); - - if ( null !== this._currentHover ) { - hoveredLabel = this.element.label[ this._currentHover ]; - - domManipulation.addClass( hoveredLabel, "snippet-editor__label--hover" ); - } -}; - -/** - * Updates the title data and the title input field. This also means the snippet editor view is updated. - * - * @param {string} title The title to use in the input field. - * - * @returns {void} - */ -SnippetPreview.prototype.setTitle = function( title ) { - this.element.input.title.value = title; - - this.changedInput(); -}; - -/** - * Updates the url path data and the url path input field. This also means the snippet editor view is updated. - * - * @param {string} urlPath the URL path to use in the input field. - * - * @returns {void} - */ -SnippetPreview.prototype.setUrlPath = function( urlPath ) { - this.element.input.urlPath.value = urlPath; - - this.changedInput(); -}; - -/** - * Updates the meta description data and the meta description input field. This also means the snippet editor view is updated. - * - * @param {string} metaDesc the meta description to use in the input field. - * - * @returns {void} - */ -SnippetPreview.prototype.setMetaDescription = function( metaDesc ) { - this.element.input.metaDesc.value = metaDesc; - - this.changedInput(); -}; - -/** - * Creates elements with the purpose to calculate the sizes of elements and puts these elements to the body. - * - * @returns {void} - */ -SnippetPreview.prototype.createMeasurementElements = function() { - var spanHolder; - - spanHolder = document.createElement( "div" ); - spanHolder.className = "yoast-measurement-elements-holder"; - - document.body.appendChild( spanHolder ); - - this.element.measurers.metaHeight = spanHolder.childNodes[ 0 ]; -}; - -/** - * Copies the title text to the title measure element to calculate the width in pixels. - * - * @returns {void} - */ -SnippetPreview.prototype.measureTitle = function() { - if ( this.element.rendered.title.offsetWidth !== 0 || this.element.rendered.title.textContent === "" ) { - this.data.titleWidth = this.element.rendered.title.offsetWidth; - } -}; - -/** - * Copies the metadescription text to the metadescription measure element to calculate the height in pixels. - * - * @returns {void} - */ -SnippetPreview.prototype.measureMetaDescription = function() { - var metaHeightElement = this.element.measurers.metaHeight; - - metaHeightElement.innerHTML = this.element.rendered.metaDesc.innerHTML; - - this.data.metaHeight = metaHeightElement.offsetHeight; -}; - -/** - * Returns the width of the title in pixels. - * - * @returns {Number} The width of the title in pixels. - */ -SnippetPreview.prototype.getTitleWidth = function() { - return this.data.titleWidth; -}; - -/** - * Allows to manually set the title width. - * - * This may be useful in setups where the title field will not always be rendered. - * - * @param {Number} titleWidth The width of the title in pixels. - * - * @returns {void} - */ -SnippetPreview.prototype.setTitleWidth = function( titleWidth ) { - this.data.titleWidth = titleWidth; -}; - -/** - * Returns whether or not an app object is present. - * - * @returns {boolean} Whether or not there is an App object present. - */ -SnippetPreview.prototype.hasApp = function() { - return ! isUndefined( this.refObj ); -}; - -/** - * Returns whether or not a pluggable object is present. - * - * @returns {boolean} Whether or not there is a Pluggable object present. - */ -SnippetPreview.prototype.hasPluggable = function() { - return ! isUndefined( this.refObj ) && ! isUndefined( this.refObj.pluggable ); -}; - -/* eslint-disable */ - -/** - * Disables Enter as input. - * - * Used to disable enter as input. Returns false to prevent enter, and preventDefault and - * cancelBubble to prevent other elements from capturing this event. - * - * @deprecated - * - * @param {KeyboardEvent} ev The keyboard event. - */ -SnippetPreview.prototype.disableEnter = function( ev ) {}; - -/** - * Adds and removes the tooLong class when a text is too long. - * - * @deprecated - * @param ev The event. - */ -SnippetPreview.prototype.textFeedback = function( ev ) {}; - -/** - * Shows the edit icon corresponding to the hovered element. - * - * @deprecated - * - * @param ev The event. - */ -SnippetPreview.prototype.showEditIcon = function( ev ) { - -}; - -/** - * Removes all editIcon-classes, sets to snippet_container. - * - * @deprecated - */ -SnippetPreview.prototype.hideEditIcon = function() {}; - -/** - * Sets focus on child element of the snippet_container that is clicked. Hides the editicon. - * - * @deprecated - * - * @param ev The event. - */ -SnippetPreview.prototype.setFocus = function( ev ) {}; - -/* eslint-disable */ -export default SnippetPreview; diff --git a/packages/yoastseo/src/snippetPreview/snippetPreviewToggler.js b/packages/yoastseo/src/snippetPreview/snippetPreviewToggler.js deleted file mode 100644 index e386338c924..00000000000 --- a/packages/yoastseo/src/snippetPreview/snippetPreviewToggler.js +++ /dev/null @@ -1,239 +0,0 @@ -import { forEach } from "lodash"; -import domManipulation from "../helpers/domManipulation.js"; - -var previewModes = { - desktop: "snippet-editor__view--desktop", - mobile: "snippet-editor__view--mobile", -}; - -var minimumDesktopWidth = 640; - -/** - * Constructs the snippet preview toggle. - * - * @param {string} previewMode The default preview mode. - * @param {Element[]} previewToggles Array with toggle elements. - * - * @property {string} previewMode The current preview mode. - * @property {Element[]} previewToggles The array with toggle elements. - * @property {Element} viewElement The target element. - * @constructor - */ -var SnippetPreviewToggler = function( previewMode, previewToggles ) { - this.previewMode = previewMode; - this.previewToggles = previewToggles; - this.viewElement = document.getElementById( "snippet-preview-view" ); -}; - -/** - * Initializes the object by setting the current previewMode as the active one. - * - * @returns {void} - */ -SnippetPreviewToggler.prototype.initialize = function() { - this._setPreviewMode( this.previewMode, this._findElementByMode( this.previewMode ) ); -}; - -/** - * Binds a function on the click event of the preview toggle. - * - * @param {string} previewToggle The previewToggle to bind the click event on. - * - * @returns {void} - */ -SnippetPreviewToggler.prototype.bindClickEvent = function( previewToggle ) { - previewToggle.addEventListener( "click", function() { - this._setPreviewMode( previewToggle.getAttribute( "data-type" ), previewToggle ); - this.removeTooltipAbility( previewToggle ); - }.bind( this ) ); -}; - -/** - * Binds a function on the mouseleave event of the preview toggle. - * - * @param {string} previewToggle The previewToggle to bind the mouseleave event on. - * - * @returns {void} - */ -SnippetPreviewToggler.prototype.bindMouseleaveEvent = function( previewToggle ) { - previewToggle.addEventListener( "mouseleave", function() { - this.removeTooltipAbility( previewToggle ); - }.bind( this ) ); -}; - -/** - * Binds a function on the blur event of the preview toggle. - * - * @param {string} previewToggle The previewToggle to bind the blur event on. - * - * @returns {void} - */ -SnippetPreviewToggler.prototype.bindBlurEvent = function( previewToggle ) { - previewToggle.addEventListener( "blur", function() { - this.restoreTooltipAbility( previewToggle ); - }.bind( this ) ); -}; - -/** - * Binds a function on the mouseenter event of the preview toggle. - * - * @param {string} previewToggle The previewToggle to bind the mouseenter event on. - * - * @returns {void} - */ -SnippetPreviewToggler.prototype.bindMouseenterEvent = function( previewToggle ) { - previewToggle.addEventListener( "mouseenter", function() { - this.restoreTooltipAbility( previewToggle ); - }.bind( this ) ); -}; - -/** - * Sets the events for the preview toggles to switch between the preview modes and handle the tooltips. - * - * @returns {void} - */ -SnippetPreviewToggler.prototype.bindEvents = function() { - forEach( this.previewToggles, function( previewToggle ) { - this.bindClickEvent( previewToggle ); - this.bindMouseleaveEvent( previewToggle ); - this.bindBlurEvent( previewToggle ); - this.bindMouseenterEvent( previewToggle ); - }.bind( this ) ); -}; - -/** - * Returns the element by given mode. - * - * @param {string} previewMode The mode used to find the element. - * @returns {Element} The found element. - * @private - */ -SnippetPreviewToggler.prototype._findElementByMode = function( previewMode ) { - return document.getElementsByClassName( "snippet-editor__view-icon-" + previewMode )[ 0 ]; -}; - -/** - * Sets the preview mode. - * - * @param {string} previewMode The preview mode that has to be set. - * @param {Element} toggleElement The element that has been triggered. - * - * @returns {void} - * @private - */ -SnippetPreviewToggler.prototype._setPreviewMode = function( previewMode, toggleElement ) { - this._removeActiveStates(); - this._setActiveState( toggleElement ); - - domManipulation.removeClass( this.viewElement, previewModes[ this.previewMode ] ); - domManipulation.addClass( this.viewElement, previewModes[ previewMode ] ); - - this.previewMode = previewMode; -}; - -/** - * Sets the Snippet Preview Toggler to desktop mode. - * - * @returns {void} - */ -SnippetPreviewToggler.prototype.setDesktopMode = function() { - this._setPreviewMode( "desktop", this._findElementByMode( "desktop" ) ); -}; - -/** - * Sets the Snippet Preview Toggler to mobile mode. - * - * @returns {void} - */ -SnippetPreviewToggler.prototype.setMobileMode = function() { - this._setPreviewMode( "mobile", this._findElementByMode( "mobile" ) ); -}; - -/** - * Sets the initial view to desktop or mobile based on the width of the Snippet Preview container. - * - * @param {number} previewWidth the width of the Snippet Preview container. - * - * @returns {void} - */ -SnippetPreviewToggler.prototype.setVisibility = function( previewWidth ) { - if ( previewWidth < minimumDesktopWidth ) { - this.setMobileMode(); - // At this point the desktop view is scrollable: set a CSS class to show the Scroll Hint message. - domManipulation.addClass( this.viewElement, "snippet-editor__view--desktop-has-scroll" ); - } else { - this.setDesktopMode(); - } -}; - -/** - * When the window is resized, sets the visibility of the Scroll Hint message. - * - * @param {number} previewWidth the width of the Snippet Preview container. - * - * @returns {void} - */ -SnippetPreviewToggler.prototype.setScrollHintVisibility = function( previewWidth ) { - domManipulation.removeClass( this.viewElement, "snippet-editor__view--desktop-has-scroll" ); - if ( previewWidth < minimumDesktopWidth ) { - domManipulation.addClass( this.viewElement, "snippet-editor__view--desktop-has-scroll" ); - } -}; - -/** - * Removes all active state for the preview toggles. - * - * @returns {void} - * @private - */ -SnippetPreviewToggler.prototype._removeActiveStates = function() { - forEach( this.previewToggles, this._removeActiveState.bind( this ) ); -}; - -/** - * Removes the active state for the given element. - * - * @param {Element} previewToggle The element to remove its state for. - * @returns {void} - * @private - */ -SnippetPreviewToggler.prototype._removeActiveState = function( previewToggle ) { - domManipulation.removeClass( previewToggle, "snippet-editor__view-icon-" + previewToggle.getAttribute( "data-type" ) + "--active" ); - domManipulation.removeClass( previewToggle, "snippet-editor__view-icon--active" ); - previewToggle.setAttribute( "aria-pressed", "false" ); -}; - -/** - * Makes an element tooltip hidden. - * - * @param {Element} previewToggle The element on which the tooltip should be hidden. - * @returns {void} - */ -SnippetPreviewToggler.prototype.removeTooltipAbility = function( previewToggle ) { - domManipulation.addClass( previewToggle, "yoast-tooltip-hidden" ); -}; - -/** - * Makes an element tooltip visible. - * - * @param {Element} previewToggle The element on which the tooltip should be visible. - * @returns {void} - */ -SnippetPreviewToggler.prototype.restoreTooltipAbility = function( previewToggle ) { - domManipulation.removeClass( previewToggle, "yoast-tooltip-hidden" ); -}; - -/** - * Adds active state to the given element. - * - * @param {Element} elementToActivate The element that will be activated. - * @returns {void} - * @private - */ -SnippetPreviewToggler.prototype._setActiveState = function( elementToActivate ) { - domManipulation.addClass( elementToActivate, "snippet-editor__view-icon-" + elementToActivate.getAttribute( "data-type" ) + "--active" ); - domManipulation.addClass( elementToActivate, "snippet-editor__view-icon--active" ); - elementToActivate.setAttribute( "aria-pressed", "true" ); -}; - -export default SnippetPreviewToggler; diff --git a/readme.txt b/readme.txt index 2aecf3d826f..306c65812a8 100644 --- a/readme.txt +++ b/readme.txt @@ -327,6 +327,7 @@ Release date: 2024-05-14 #### Bugfixes * Fixes a bug where a PHP deprecation error would be thrown when trying to convert a relative URL to an absolute one, with the provided value being `null`. +* Fixes a bug where the character count of the meta description field would not include the automatically added date and separator when no additional content was provided. #### Other diff --git a/src/deprecated/admin/class-customizer.php b/src/deprecated/admin/class-customizer.php new file mode 100644 index 00000000000..57b68c317ac --- /dev/null +++ b/src/deprecated/admin/class-customizer.php @@ -0,0 +1,72 @@ +