Skip to content

Commit

Permalink
Merge pull request #20910 from Yoast/4125-highlighting-in-elementor-2
Browse files Browse the repository at this point in the history
Reimplement proof-of-concept for highlighting in Elementor#4125
  • Loading branch information
mhkuu committed Dec 12, 2023
2 parents df53130 + 9ae7c67 commit 807265d
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 11 deletions.
14 changes: 14 additions & 0 deletions packages/js/src/helpers/elementorHook.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,20 @@ export function registerElementorUIHookAfter( hook, id, callback ) {
$e.hooks.registerUIAfter( new ElementorUIHook( hook, id, callback ) );
}


/**
* Initializes the Elementor UI before hooks and registers them.
*
* @param {string} hook The hook to register to.
* @param {string} id The id to register our callback behind.
* @param {function} callback The function to call when the hook is fired.
*
* @returns {void}
*/
export function registerElementorUIHookBefore( hook, id, callback ) {
$e.hooks.registerUIBefore( new ElementorUIHook( hook, id, callback ) );
}

/**
* Initializes the Elementor Data hooks and registers them.
*
Expand Down
96 changes: 85 additions & 11 deletions packages/js/src/watchers/elementorWatcher.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { dispatch } from "@wordpress/data";
import { get, debounce } from "lodash";
import { dispatch, select } from "@wordpress/data";
import { debounce, get } from "lodash";
import firstImageUrlInContent from "../helpers/firstImageUrlInContent";
import { registerElementorUIHookAfter } from "../helpers/elementorHook";
import { registerElementorUIHookAfter, registerElementorUIHookBefore } from "../helpers/elementorHook";
import { markers, Paper } from "yoastseo";

const editorData = {
content: "",
Expand All @@ -11,6 +12,39 @@ const editorData = {
imageUrl: "",
};

const MARK_TAG = "yoastmark";

/**
* Checks whether the given Elementor widget has Yoast marks.
*
* @param {Object} widget The widget.
* @returns {boolean} Whether there are marks in the HTML of the widget.
*/
function widgetHasMarks( widget ) {
return widget.innerHTML.indexOf( "<" + MARK_TAG ) !== -1;
}

/**
* Retrieves all Elementor widget containers.
* @returns {jQuery[]} Elementor widget containers.
*/
function getWidgetContainers() {
const currentDocument = window.elementor.documents.getCurrent();
return currentDocument.$element.find( ".elementor-widget-container" );
}

/**
* Removes all marks from Elementor widgets.
*
* @returns {void}
*/
function removeMarks() {
getWidgetContainers().each( ( index, element ) => {
if ( widgetHasMarks( element ) ) {
element.innerHTML = markers.removeMarks( element.innerHTML );
}
} );
}
/**
* Gets the post content.
*
Expand All @@ -22,7 +56,12 @@ function getContent( editorDocument ) {
const content = [];

editorDocument.$element.find( ".elementor-widget-container" ).each( ( index, element ) => {
content.push( element.innerHTML.trim() );
// We remove \n and \t from the HTML as Elementor formats the HTML after saving.
// As this spacing is purely cosmetic, we can remove it for analysis purposes.
// We also convert &nbsp; elements to regular spaces.
// When we apply the marks, we do need to make the same amendments.
const rawHtml = element.innerHTML.replace( /[\n\t]/g, "" ).replace( /&nbsp;/g, " " ).trim();
content.push( rawHtml );
} );

return content.join( "" );
Expand Down Expand Up @@ -64,6 +103,7 @@ function getEditorData( editorDocument ) {
};
}

/* eslint-disable complexity */
/**
* Dispatches new data when the editor is dirty.
*
Expand All @@ -72,14 +112,17 @@ function getEditorData( editorDocument ) {
function handleEditorChange() {
const currentDocument = window.elementor.documents.getCurrent();

/*
Quit early if the change was caused by switching out of the wp-post/page document.
This can happen when users go to Site Settings, for example.
*/
// Quit early if the change was caused by switching out of the wp-post/page document.
// This can happen when users go to Site Settings, for example.
if ( ! [ "wp-post", "wp-page" ].includes( currentDocument.config.type ) ) {
return;
}

// Quit early if the highlighting functionality is on.
if ( select( "yoast-seo/editor" ).getActiveMarker() ) {
return;
}

const data = getEditorData( currentDocument );

if ( data.content !== editorData.content ) {
Expand All @@ -102,17 +145,48 @@ function handleEditorChange() {
dispatch( "yoast-seo/editor" ).setEditorDataImageUrl( editorData.imageUrl );
}
}
/* eslint-enable complexity */

const debouncedHandleEditorChange = debounce( handleEditorChange, 500 );
/**
* Removes highlighting from Elementor widgets and reset the highlighting button.
*
* @returns {void}
*/
function resetMarks() {
removeMarks();

dispatch( "yoast-seo/editor" ).setActiveMarker( null );
dispatch( "yoast-seo/editor" ).setMarkerPauseStatus( false );

window.YoastSEO.analysis.applyMarks( new Paper( "", {} ), [] );
}

const debouncedHandleEditorChange = debounce( handleEditorChange, 1500 );

/**
* Observes changes to the whole document through a MutationObserver.
*
* @returns {void}
*/
function observeChanges() {
const observer = new MutationObserver( debouncedHandleEditorChange );
observer.observe( window.document, { attributes: true, childList: true, subtree: true, characterData: true } );
}

/**
* Initializes the watcher by coupling the change handler to the change event.
* Initializes the watcher by coupling the change handlers to the change events.
*
* @returns {void}
*/
export default function initialize() {
// This hook will fire 500ms after a widget is edited -- this allows Elementor to set the cursor at the end of the widget.
registerElementorUIHookBefore( "panel/editor/open", "yoast-seo-reset-marks-edit", debounce( resetMarks, 500 ) );
// This hook will fire just before the document is saved.
registerElementorUIHookBefore( "document/save/save", "yoast-seo-reset-marks-save", resetMarks );

// This hook will fire when the Elementor preview becomes available.
registerElementorUIHookAfter( "editor/documents/attach-preview", "yoast-seo-content-scraper-attach-preview", debouncedHandleEditorChange );
registerElementorUIHookAfter( "editor/documents/attach-preview", "yoast-seo-content-scraper-initial", debouncedHandleEditorChange );
registerElementorUIHookAfter( "editor/documents/attach-preview", "yoast-seo-content-scraper", debounce( observeChanges, 500 ) );

// This hook will fire when the contents of the editor are modified.
registerElementorUIHookAfter( "document/save/set-is-modified", "yoast-seo-content-scraper-on-modified", debouncedHandleEditorChange );
Expand Down

0 comments on commit 807265d

Please sign in to comment.