From 7c76253b755571ef3df05fa844bb37058af20b27 Mon Sep 17 00:00:00 2001 From: Mykola Shlyakhtun Date: Tue, 23 Apr 2024 11:12:09 +0300 Subject: [PATCH 01/19] Cleanup: Investigate if the files within yoastseo/src/snippetPreview can be removed #93 Remove files and unused functions and tests. --- packages/yoastseo/spec/appSpec.js | 8 +- .../spec/snippetPreview/snippetPreviewSpec.js | 243 --- packages/yoastseo/src/app.js | 136 +- packages/yoastseo/src/index.js | 3 - .../src/snippetPreview/snippetPreview.js | 1305 ----------------- .../snippetPreview/snippetPreviewToggler.js | 239 --- 6 files changed, 2 insertions(+), 1932 deletions(-) delete mode 100644 packages/yoastseo/spec/snippetPreview/snippetPreviewSpec.js delete mode 100644 packages/yoastseo/src/snippetPreview/snippetPreview.js delete mode 100644 packages/yoastseo/src/snippetPreview/snippetPreviewToggler.js diff --git a/packages/yoastseo/spec/appSpec.js b/packages/yoastseo/spec/appSpec.js index c7f23c768a6..fc7fb0d84bc 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 @@ -77,10 +74,7 @@ describe( "Creating an App", function() { getData: () => { return {}; }, - }, - snippetPreview: new SnippetPreview( { - targetElement: mockElement, - } ), + } } ); } ); 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 fece3aa9037..37716145228 100644 --- a/packages/yoastseo/src/app.js +++ b/packages/yoastseo/src/app.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,10 @@ var defaults = { marker: noop, keywordAnalysisActive: true, contentAnalysisActive: true, - hasSnippetPreview: true, + hasSnippetPreview: false, 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 +90,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 +187,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 +225,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(); }; @@ -470,15 +415,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 +451,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 +478,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 +518,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(); - } // Create a paper object for the Researcher this.paper = new Paper( text, { @@ -665,10 +548,6 @@ App.prototype.runAnalyzer = function() { if ( this.config.dynamicDelay ) { this.endTime(); } - - if ( this.hasSnippetPreview() ) { - this.snippetPreview.reRender(); - } }; /** @@ -925,19 +804,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/snippetPreview/snippetPreview.js b/packages/yoastseo/src/snippetPreview/snippetPreview.js deleted file mode 100644 index f8a3578117f..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-es"; - -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 3bf605529a7..00000000000 --- a/packages/yoastseo/src/snippetPreview/snippetPreviewToggler.js +++ /dev/null @@ -1,239 +0,0 @@ -import { forEach } from "lodash-es"; -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; From 57ff833f483bfec160b763eba0aa51f3df7668be Mon Sep 17 00:00:00 2001 From: Mykola Shlyakhtun Date: Tue, 23 Apr 2024 11:43:51 +0300 Subject: [PATCH 02/19] Cleanup: Investigate if the files within yoastseo/src/snippetPreview can be removed #93 Fix linting issues. --- packages/yoastseo/src/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/yoastseo/src/app.js b/packages/yoastseo/src/app.js index 37716145228..e203e67e69e 100644 --- a/packages/yoastseo/src/app.js +++ b/packages/yoastseo/src/app.js @@ -1,5 +1,5 @@ import { setLocaleData } from "@wordpress/i18n"; -import { debounce, defaultsDeep, forEach, isArray, isEmpty, isFunction, isObject, isString, isUndefined, merge, noop, throttle } from "lodash-es"; +import { debounce, defaultsDeep, forEach, isArray, isEmpty, isFunction, isObject, isUndefined, merge, noop, throttle } from "lodash-es"; import MissingArgument from "./errors/missingArgument"; import { measureTextWidth } from "./helpers/createMeasurementElement.js"; @@ -523,7 +523,7 @@ App.prototype.runAnalyzer = function() { // Insert HTML stripping code text = removeHtmlBlocks( text ); - let titleWidth = this.analyzerData.titleWidth; + const titleWidth = this.analyzerData.titleWidth; // Create a paper object for the Researcher this.paper = new Paper( text, { From f018a9fa10b9cfecd509c1e1463eaf1ff91e7300 Mon Sep 17 00:00:00 2001 From: Mykola Shlyakhtun Date: Tue, 23 Apr 2024 12:07:06 +0300 Subject: [PATCH 03/19] Cleanup: Investigate if the files within yoastseo/src/snippetPreview can be removed #93 Fix linting issues. --- packages/yoastseo/spec/appSpec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoastseo/spec/appSpec.js b/packages/yoastseo/spec/appSpec.js index fc7fb0d84bc..ad8f339e2a5 100644 --- a/packages/yoastseo/spec/appSpec.js +++ b/packages/yoastseo/spec/appSpec.js @@ -74,7 +74,7 @@ describe( "Creating an App", function() { getData: () => { return {}; }, - } + }, } ); } ); From 33905a567c29de96542ada20da211f532a8f40c5 Mon Sep 17 00:00:00 2001 From: Mykola Shlyakhtun Date: Tue, 23 Apr 2024 14:38:26 +0300 Subject: [PATCH 04/19] Cleanup: Investigate if the files within yoastseo/src/snippetPreview can be removed #93 Fix tests. --- packages/yoastseo/spec/appSpec.js | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/packages/yoastseo/spec/appSpec.js b/packages/yoastseo/spec/appSpec.js index ad8f339e2a5..ea9f8f4aad8 100644 --- a/packages/yoastseo/spec/appSpec.js +++ b/packages/yoastseo/spec/appSpec.js @@ -50,34 +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 {}; - }, - }, - } ); - } ); - it( "should work without an output ID", function() { new App( { targets: { From 3a6120412de46bcc0c940845180163b67c6eaf75 Mon Sep 17 00:00:00 2001 From: Enrico Battocchi Date: Thu, 2 May 2024 15:30:39 +0200 Subject: [PATCH 05/19] Don't initializer the WPSEO_Customizer class anymore --- wp-seo-main.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/wp-seo-main.php b/wp-seo-main.php index ed650de23a7..8f90ba013ff 100644 --- a/wp-seo-main.php +++ b/wp-seo-main.php @@ -363,9 +363,6 @@ function wpseo_init() { require_once WPSEO_PATH . 'inc/wpseo-non-ajax-functions.php'; } - // Init it here because the filter must be present on the frontend as well or it won't work in the customizer. - new WPSEO_Customizer(); - $integrations = []; $integrations[] = new WPSEO_Slug_Change_Watcher(); From 689fc1aefa8ac11670422f42cbb307b9be317dc9 Mon Sep 17 00:00:00 2001 From: Enrico Battocchi Date: Thu, 2 May 2024 15:30:58 +0200 Subject: [PATCH 06/19] Deprecate the WPSEO_Customizer class --- admin/class-customizer.php | 249 ---------------------- src/deprecated/admin/class-customizer.php | 72 +++++++ 2 files changed, 72 insertions(+), 249 deletions(-) delete mode 100644 admin/class-customizer.php create mode 100644 src/deprecated/admin/class-customizer.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/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 @@ + Date: Thu, 2 May 2024 15:54:07 +0200 Subject: [PATCH 07/19] Decrease the error threshold --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 5bad5b6086a..1265c5bd5fb 100644 --- a/composer.json +++ b/composer.json @@ -92,7 +92,7 @@ "Yoast\\WP\\SEO\\Composer\\Actions::check_coding_standards" ], "check-cs-thresholds": [ - "@putenv YOASTCS_THRESHOLD_ERRORS=2549", + "@putenv YOASTCS_THRESHOLD_ERRORS=2545", "@putenv YOASTCS_THRESHOLD_WARNINGS=267", "Yoast\\WP\\SEO\\Composer\\Actions::check_cs_thresholds" ], From 72c241ee2221776ba61e4e2bfe1df6f97e9acb9a Mon Sep 17 00:00:00 2001 From: Enrico Battocchi Date: Fri, 3 May 2024 08:33:17 +0200 Subject: [PATCH 08/19] Exclude `VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable` for deprecated code --- .phpcs.xml.dist | 6 ++++++ composer.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) 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/composer.json b/composer.json index 1265c5bd5fb..cc188b0e052 100644 --- a/composer.json +++ b/composer.json @@ -93,7 +93,7 @@ ], "check-cs-thresholds": [ "@putenv YOASTCS_THRESHOLD_ERRORS=2545", - "@putenv YOASTCS_THRESHOLD_WARNINGS=267", + "@putenv YOASTCS_THRESHOLD_WARNINGS=253", "Yoast\\WP\\SEO\\Composer\\Actions::check_cs_thresholds" ], "check-cs": [ From a998866abf7fbb9b26590bdf2d3a8dde78fb1ad1 Mon Sep 17 00:00:00 2001 From: Martijn van der Klis Date: Mon, 6 May 2024 12:09:04 +0200 Subject: [PATCH 09/19] Adds   after and between mentions to prevent interactions between spans --- .../helpers/search-appearance-description-mention.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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..c241a9b15b8 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 @@ -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 ) } +   ); From 0e7be5c6c91708f937f0df849d45e6083d2b0aaf Mon Sep 17 00:00:00 2001 From: Martijn van der Klis Date: Mon, 6 May 2024 12:12:11 +0200 Subject: [PATCH 10/19] Adds the date to the meta description length when the description is empty --- .../helpers/word/countMetaDescriptionLengthSpec.js | 7 ++++++- .../helpers/word/countMetaDescriptionLength.js | 6 +++--- 2 files changed, 9 insertions(+), 4 deletions(-) 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/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; } From 9f3dfd5490e4c6946920dc8f401a50cac4110388 Mon Sep 17 00:00:00 2001 From: Martijn van der Klis Date: Mon, 6 May 2024 15:20:10 +0200 Subject: [PATCH 11/19] Clears up the confusion between our separator and Google's separator --- .../helpers/search-appearance-description-mention.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 c241a9b15b8..b2dc5eb6ef9 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 @@ -80,10 +80,10 @@ const filterReplacementVariableEditorMentions = ( mentions, { fieldId } ) => { _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.", separatorCharacters, "wordpress-seo" ), separatorCharacters ) }   From 6f0fd8cfb1fd6ed8bf515f20a292eb78d0366307 Mon Sep 17 00:00:00 2001 From: Enrico Battocchi Date: Mon, 6 May 2024 17:58:27 +0200 Subject: [PATCH 12/19] Improve the blocks behaviour in the editor --- packages/js/src/structured-data-blocks/faq/block.js | 7 ++++++- packages/js/src/structured-data-blocks/how-to/block.js | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) 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
+ +
; }, /** From ca7bf3affaec91ce2c9f2d25e66a6000ec076bbf Mon Sep 17 00:00:00 2001 From: Martijn van der Klis Date: Mon, 6 May 2024 20:21:16 +0200 Subject: [PATCH 13/19] Removes the hasSnippetPreview option from App --- packages/js/src/initializers/post-scraper.js | 1 - packages/js/src/initializers/term-scraper.js | 1 - packages/yoastseo/src/app.js | 8 ++------ 3 files changed, 2 insertions(+), 8 deletions(-) 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/yoastseo/src/app.js b/packages/yoastseo/src/app.js index f69319d30c9..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, 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"; @@ -71,7 +71,6 @@ var defaults = { marker: noop, keywordAnalysisActive: true, contentAnalysisActive: true, - hasSnippetPreview: false, debounceRefresh: true, }; @@ -275,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; }; /** From 4392da220d3757de81eeaffd5dd78afc1f9c3def Mon Sep 17 00:00:00 2001 From: YoastBot Date: Tue, 7 May 2024 07:40:49 +0000 Subject: [PATCH 14/19] Add changelog --- readme.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.txt b/readme.txt index 2aecf3d826f..4b6e972fa6d 100644 --- a/readme.txt +++ b/readme.txt @@ -322,6 +322,7 @@ Release date: 2024-05-14 #### Enhancements * Converts the Yoast _How-to_ and _FAQ_ blocks to use the Blocks V3 API. +* Improves how the How-To and the FAQ block can be moved and managed in the block editor. * Introduces a `date` and a `separator` snippet variable to the meta description text field, to make clear that both the date and a separator are automatically added to the character count of the meta description. Hovering over the variables reveals a tooltip with more explanation. #### Bugfixes From e5091bd5a49f96b2db76b8fe824345382a997ca4 Mon Sep 17 00:00:00 2001 From: YoastBot Date: Tue, 7 May 2024 07:40:55 +0000 Subject: [PATCH 15/19] Bump version to 22.7-RC4 --- package.json | 2 +- wp-seo-main.php | 2 +- wp-seo.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index dae11309341..2f207a1c0c1 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-RC4" }, "version": "0.0.0" } diff --git a/wp-seo-main.php b/wp-seo-main.php index ed650de23a7..322137ceb4a 100644 --- a/wp-seo-main.php +++ b/wp-seo-main.php @@ -15,7 +15,7 @@ * {@internal Nobody should be able to overrule the real version number as this can cause * serious issues with the options, so no if ( ! defined() ).}} */ -define( 'WPSEO_VERSION', '22.7-RC3' ); +define( 'WPSEO_VERSION', '22.7-RC4' ); if ( ! defined( 'WPSEO_PATH' ) ) { diff --git a/wp-seo.php b/wp-seo.php index c913fa220de..a0aa2fb15f0 100644 --- a/wp-seo.php +++ b/wp-seo.php @@ -8,7 +8,7 @@ * * @wordpress-plugin * Plugin Name: Yoast SEO - * Version: 22.7-RC3 + * Version: 22.7-RC4 * Plugin URI: https://yoa.st/1uj * Description: The first true all-in-one SEO solution for WordPress, including on-page content analysis, XML sitemaps and much more. * Author: Team Yoast From ffdfadb2ff1fde89a0eece5f1c923e51c3657f12 Mon Sep 17 00:00:00 2001 From: Enrico Battocchi Date: Tue, 7 May 2024 09:56:37 +0200 Subject: [PATCH 16/19] Remove non-user-facing changelog line --- readme.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/readme.txt b/readme.txt index 4b6e972fa6d..2aecf3d826f 100644 --- a/readme.txt +++ b/readme.txt @@ -322,7 +322,6 @@ Release date: 2024-05-14 #### Enhancements * Converts the Yoast _How-to_ and _FAQ_ blocks to use the Blocks V3 API. -* Improves how the How-To and the FAQ block can be moved and managed in the block editor. * Introduces a `date` and a `separator` snippet variable to the meta description text field, to make clear that both the date and a separator are automatically added to the character count of the meta description. Hovering over the variables reveals a tooltip with more explanation. #### Bugfixes From 24efe4b64bd6681a4c0e1d22b6fe02484a478da0 Mon Sep 17 00:00:00 2001 From: Jordi Prats Verdu Date: Tue, 7 May 2024 10:42:08 +0200 Subject: [PATCH 17/19] Renames the em dash variable --- .../helpers/search-appearance-description-mention.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 b2dc5eb6ef9..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( @@ -83,7 +83,7 @@ const filterReplacementVariableEditorMentions = ( mentions, { fieldId } ) => { { sprintf( /* translators: %s expands to the amount of characters */ - _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.", 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 ) }   From 68dac22f825f910589596a66f1bca54f89b261d9 Mon Sep 17 00:00:00 2001 From: YoastBot Date: Tue, 7 May 2024 09:46:45 +0000 Subject: [PATCH 18/19] Add changelog --- readme.txt | 1 + 1 file changed, 1 insertion(+) 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 From 71323b9c70edb22a083147bf6bd1383915dd6936 Mon Sep 17 00:00:00 2001 From: YoastBot Date: Tue, 7 May 2024 09:46:51 +0000 Subject: [PATCH 19/19] Bump version to 22.7-RC5 --- package.json | 2 +- wp-seo-main.php | 2 +- wp-seo.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 2f207a1c0c1..deee438179a 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "webpack-bundle-analyzer": "^4.9.1" }, "yoast": { - "pluginVersion": "22.7-RC4" + "pluginVersion": "22.7-RC5" }, "version": "0.0.0" } diff --git a/wp-seo-main.php b/wp-seo-main.php index 322137ceb4a..aa560212d2a 100644 --- a/wp-seo-main.php +++ b/wp-seo-main.php @@ -15,7 +15,7 @@ * {@internal Nobody should be able to overrule the real version number as this can cause * serious issues with the options, so no if ( ! defined() ).}} */ -define( 'WPSEO_VERSION', '22.7-RC4' ); +define( 'WPSEO_VERSION', '22.7-RC5' ); if ( ! defined( 'WPSEO_PATH' ) ) { diff --git a/wp-seo.php b/wp-seo.php index a0aa2fb15f0..6002ed1bd96 100644 --- a/wp-seo.php +++ b/wp-seo.php @@ -8,7 +8,7 @@ * * @wordpress-plugin * Plugin Name: Yoast SEO - * Version: 22.7-RC4 + * Version: 22.7-RC5 * Plugin URI: https://yoa.st/1uj * Description: The first true all-in-one SEO solution for WordPress, including on-page content analysis, XML sitemaps and much more. * Author: Team Yoast