From c28ad8d35f2ac4ac079af58a5aee5a853d553860 Mon Sep 17 00:00:00 2001 From: Armando Liccardo Date: Wed, 23 Oct 2024 15:28:18 +0100 Subject: [PATCH 01/66] added link prefetch options component, LinkPrefetch class and js for frontend --- assets/js/linkPrefetch.js | 219 ++++++++++++++++++++++++++ assets/js/linkPrefetch.min.js | 1 + components/linkPrefetch/index.js | 136 ++++++++++++++++ components/performance/defaultText.js | 19 +++ components/performance/index.js | 9 +- includes/LinkPrefetch.php | 71 +++++++++ includes/Performance.php | 6 +- 7 files changed, 459 insertions(+), 2 deletions(-) create mode 100644 assets/js/linkPrefetch.js create mode 100644 assets/js/linkPrefetch.min.js create mode 100644 components/linkPrefetch/index.js create mode 100644 includes/LinkPrefetch.php diff --git a/assets/js/linkPrefetch.js b/assets/js/linkPrefetch.js new file mode 100644 index 0000000..47bd6a8 --- /dev/null +++ b/assets/js/linkPrefetch.js @@ -0,0 +1,219 @@ +window.addEventListener( 'load', () => { + /* check if the browser supports Prefetch */ + const testlink = document.createElement("link"), + supportsPrefetchCheck = testlink.relList && testlink.relList.supports && testlink.relList.supports("prefetch"), + /* check if the user has set a reduced data usage option on the user agent or if the current connection effective type is 2g */ + navigatorConnectionCheck = navigator.connection && (navigator.connection.saveData || (navigator.connection.effectiveType || "").includes("2g")), + intersectionObserverCheck = window.IntersectionObserver && "isIntersecting" in IntersectionObserverEntry.prototype; + + if ( ! supportsPrefetchCheck || navigatorConnectionCheck ) { + return; + } else { + class LP_APP { + constructor(config) { + this.config = config; + this.activeOnDesktop = config.activeOnDesktop; + this.behavior = config.behavior; + this.hoverDelay = config.hoverDelay; + this.ignoreKeywords = config.ignoreKeywords.split(','); + this.instantClick = config.instantClick; + this.mobileActive = config.mobileActive; + this.isMobile = config.isMobile; + this.mobileBehavior = config.mobileBehavior; + this.prefetchedUrls = new Set(); + this.timerIdentifier; + this.eventListenerOptions = { capture: !0, passive: !0 }; + } + /** + * Init + * @returns {void} + */ + init() { + const isChrome = navigator.userAgent.indexOf("Chrome/") > -1, + chromeVersion = isChrome && parseInt(navigator.userAgent.substring(navigator.userAgent.indexOf("Chrome/") + "Chrome/".length)); + + if ( isChrome && chromeVersion < 110 ) {return;} + if ( this.isMobile && ! this.mobileActive ) {return;} + if ( ! this.isMobile && ! this.activeOnDesktop ) {return;} + + if ( ! this.isMobile ) { + if ( 'mouseHover' === this.behavior ) { + let hoverDelay = parseInt(this.hoverDelay); + hoverDelay = isNaN(hoverDelay) ? 60 : hoverDelay; + document.addEventListener("mouseover", this.mouseHover.bind(this), this.eventListenerOptions); + } else if ( 'mouseDown' === this.behavior ) { + if ( this.instantClick ) { + document.addEventListener("mousedown", this.mouseDownToClick.bind(this), this.eventListenerOptions); + } else { + document.addEventListener("mousedown", this.mouseDown.bind(this), this.eventListenerOptions) + } + } + } + + if ( this.mobileActive ) { + if ( 'touchstart' === this.mobileBehavior ) { + document.addEventListener("touchstart", this.touchstart.bind(this), this.eventListenerOptions); + } else if ( 'viewport' && intersectionObserverCheck ) { + this.viewport(); + } + } + } + /** + * Viewport handler + * @returns {void} + */ + viewport() { + const io = new IntersectionObserver((e) => { + e.forEach((e) => { + if (e.isIntersecting) { + const n = e.target; + io.unobserve(n); + this.canPrefetch(n) && this.prefetchIt(n.href); + } + }); + }); + let requestIdleCallback = window.requestIdleCallback || + function (cb) { + var start = Date.now(); + return setTimeout(function () { + cb({ + didTimeout: false, + timeRemaining: function () { + return Math.max(0, 50 - (Date.now() - start)); + } + }); + }, 1); + }; + requestIdleCallback( () => { + return setTimeout(function () { + return document.querySelectorAll("a").forEach(function (a) { + return io.observe(a); + }); + }, 1000); + }, { timeout: 1000 }); + } + /** + * Mouse Down handler + * @param {Event} e - listener event + * @returns {void} + */ + mouseDown(e) { + const el = e.target.closest("a"); + this.canPrefetch(el) && this.prefetchIt(el.href); + } + + /** + * Mouse Down handler for instant click + * @param {Event} e - listener event + * @returns {void} + */ + mouseDownToClick(e) { + //if (performance.now() - o < r) return; + const el = e.target.closest("a"); + if (e.which > 1 || e.metaKey || e.ctrlKey) return; + if (!el) return; + el.addEventListener( + "click", + function (t) { + 'lpappinstantclick' != t.detail && t.preventDefault(); + }, + { capture: !0, passive: !1, once: !0 } + ); + const n = new MouseEvent("click", { view: window, bubbles: !0, cancelable: !1, detail: 'lpappinstantclick' }); + el.dispatchEvent(n); + } + + touchstart(e) { + const el = e.target.closest("a"); + this.canPrefetch(el) && this.prefetchIt(el.href); + } + + /** + * Clean Timers + * @param {Event} t - listener event + * @returns {void} + */ + clean(t) { + if ( t.relatedTarget && t.target.closest("a") == t.relatedTarget.closest("a") || this.timerIdentifier ) { + clearTimeout( this.timerIdentifier ); + this.timerIdentifier = void(0); + } + } + + /** + * Mouse hover function + * @param {Event} e - listener event + * @returns {void} + */ + mouseHover(e) { + if ( !("closest" in e.target) ) return; + const link = e.target.closest("a"); + if ( this.canPrefetch( link ) ) { + link.addEventListener("mouseout", this.clean.bind(this), { passive: !0 }); + this.timerIdentifier = setTimeout(()=> { + this.prefetchIt( link.href ); + this.timerIdentifier = void(0); + }, this.hoverDelay); + } + } + + /** + * Can the url be prefetched or not + * @param {Element} el - link element + * @returns {boolean} - if it can be prefetched + */ + canPrefetch( el ) { + if ( el && el.href ) { + /* it has been just prefetched before */ + if (this.prefetchedUrls.has(el.href)) { + return false; + } + + /* avoid if it is the same url as the actual location */ + if ( el.href.replace(/\/$/, "") !== location.origin.replace(/\/$/, "") && el.href.replace(/\/$/, "") !== location.href.replace(/\/$/, "") ) { + return true; + } + + /* checking exclusions */ + const exclude = this.ignoreKeywords.filter( k => { + if ( el.href.indexOf (k) > -1) { + return k; + } + }) + if ( exclude.length > 0 ) { return false; } + + } + + return false; + } + + /** + * Append link rel=prefetch to the head + * @param {string} url - url to prefetch + * @returns {void} + */ + prefetchIt(url) { + const toPrefechLink = document.createElement("link"); + + toPrefechLink.rel = "prefetch"; + toPrefechLink.href = url; + toPrefechLink.as = "document"; + + document.head.appendChild(toPrefechLink); + this.prefetchedUrls.add(url); + } + } + /* + default config: + 'activeOnDesktop' => true, + 'behavior' =>'mouseHover', + 'hoverDelay' => 60, + 'instantClick' => true , + 'activeOnMobile' => true , + 'mobileBehavior' => 'viewport', + 'ignoreKeywords' =>'wp-admin,#,?', + */ + const lpapp = new LP_APP( window.LP_CONFIG ); + lpapp.init(); + } +}); diff --git a/assets/js/linkPrefetch.min.js b/assets/js/linkPrefetch.min.js new file mode 100644 index 0000000..fe07dc1 --- /dev/null +++ b/assets/js/linkPrefetch.min.js @@ -0,0 +1 @@ +window.addEventListener("load",(()=>{const e=document.createElement("link"),t=e.relList&&e.relList.supports&&e.relList.supports("prefetch"),i=navigator.connection&&(navigator.connection.saveData||(navigator.connection.effectiveType||"").includes("2g")),n=window.IntersectionObserver&&"isIntersecting"in IntersectionObserverEntry.prototype;if(t&&!i){class e{constructor(e){this.config=e,this.activeOnDesktop=e.activeOnDesktop,this.behavior=e.behavior,this.hoverDelay=e.hoverDelay,this.ignoreKeywords=e.ignoreKeywords.split(","),this.instantClick=e.instantClick,this.mobileActive=e.mobileActive,this.isMobile=e.isMobile,this.mobileBehavior=e.mobileBehavior,this.prefetchedUrls=new Set,this.timerIdentifier,this.eventListenerOptions={capture:!0,passive:!0}}init(){const e=navigator.userAgent.indexOf("Chrome/")>-1,t=e&&parseInt(navigator.userAgent.substring(navigator.userAgent.indexOf("Chrome/")+7));if(!(e&&t<110)&&(!this.isMobile||this.mobileActive)&&(this.isMobile||this.activeOnDesktop)){if(!this.isMobile)if("mouseHover"===this.behavior){let e=parseInt(this.hoverDelay);e=isNaN(e)?60:e,document.addEventListener("mouseover",this.mouseHover.bind(this),this.eventListenerOptions)}else"mouseDown"===this.behavior&&(this.instantClick?document.addEventListener("mousedown",this.mouseDownToClick.bind(this),this.eventListenerOptions):document.addEventListener("mousedown",this.mouseDown.bind(this),this.eventListenerOptions));this.mobileActive&&("touchstart"===this.mobileBehavior?document.addEventListener("touchstart",this.touchstart.bind(this),this.eventListenerOptions):n&&this.viewport())}}viewport(){const e=new IntersectionObserver((t=>{t.forEach((t=>{if(t.isIntersecting){const i=t.target;e.unobserve(i),this.canPrefetch(i)&&this.prefetchIt(i.href)}}))}));(window.requestIdleCallback||function(e){var t=Date.now();return setTimeout((function(){e({didTimeout:!1,timeRemaining:function(){return Math.max(0,50-(Date.now()-t))}})}),1)})((()=>setTimeout((function(){return document.querySelectorAll("a").forEach((function(t){return e.observe(t)}))}),1e3)),{timeout:1e3})}mouseDown(e){const t=e.target.closest("a");this.canPrefetch(t)&&this.prefetchIt(t.href)}mouseDownToClick(e){const t=e.target.closest("a");if(e.which>1||e.metaKey||e.ctrlKey)return;if(!t)return;t.addEventListener("click",(function(e){"lpappinstantclick"!=e.detail&&e.preventDefault()}),{capture:!0,passive:!1,once:!0});const i=new MouseEvent("click",{view:window,bubbles:!0,cancelable:!1,detail:"lpappinstantclick"});t.dispatchEvent(i)}touchstart(e){const t=e.target.closest("a");this.canPrefetch(t)&&this.prefetchIt(t.href)}clean(e){(e.relatedTarget&&e.target.closest("a")==e.relatedTarget.closest("a")||this.timerIdentifier)&&(clearTimeout(this.timerIdentifier),this.timerIdentifier=void 0)}mouseHover(e){if(!("closest"in e.target))return;const t=e.target.closest("a");this.canPrefetch(t)&&(t.addEventListener("mouseout",this.clean.bind(this),{passive:!0}),this.timerIdentifier=setTimeout((()=>{this.prefetchIt(t.href),this.timerIdentifier=void 0}),this.hoverDelay))}canPrefetch(e){if(e&&e.href){if(this.prefetchedUrls.has(e.href))return!1;if(e.href.replace(/\/$/,"")!==location.origin.replace(/\/$/,"")&&e.href.replace(/\/$/,"")!==location.href.replace(/\/$/,""))return!0;if(this.ignoreKeywords.filter((t=>{if(e.href.indexOf(t)>-1)return t})).length>0)return!1}return!1}prefetchIt(e){const t=document.createElement("link");t.rel="prefetch",t.href=e,t.as="document",document.head.appendChild(t),this.prefetchedUrls.add(e)}}new e(window.LP_CONFIG).init()}})); \ No newline at end of file diff --git a/components/linkPrefetch/index.js b/components/linkPrefetch/index.js new file mode 100644 index 0000000..c545a33 --- /dev/null +++ b/components/linkPrefetch/index.js @@ -0,0 +1,136 @@ +import { ToggleField, TextField, TextareaField, SelectField, Container } from "@newfold/ui-component-library"; +const LinkPrefetch = ({methods, constants}) => { + const [settings, setSettings] = methods.useState(constants.store.linkPrefetch) + + const handleChangeOption = ( option, value ) => { + if ( option in settings ) { + const updatedSettings = settings; + updatedSettings[option] = value; + methods.newfoldSettingsApiFetch( + { linkPrefetch: updatedSettings }, + methods.setError, (response) => { + setSettings( (prev)=> { + return { + ...prev, + [option]: value + } + }); + } + ); + } + } + + methods.useUpdateEffect(() => { + methods.setStore({ + ...constants.store, + linkPrefetch: settings, + }); + + methods.makeNotice( + "link-prefetch-change-notice", + constants.text.linkPrefetchTitle, + constants.text.linkPrefetchNoticeTitle, + "success", + 5000 + ); + }, [settings]); + + return( + + handleChangeOption( 'activeOnDesktop', !settings.activeOnDesktop) } + description={constants.text.linkPrefetchActivateOnDekstopDescription} + /> + { settings.activeOnDesktop && ( + <> + handleChangeOption( 'behavior', v) } + description={constants.text.linkPrefetchBehaviorDescription} + > + + + + { + 'mouseDown' === settings.behavior && ( + handleChangeOption( 'instantClick', !settings.instantClick) } + description={constants.text.linkPrefetchInstantClickDescription} + /> + ) + } + { + 'mouseHover' === settings.behavior && ( + handleChangeOption( 'hoverDelay', '' === e.target.value ? 60 : e.target.value )} + type="number" + value={settings.hoverDelay} + description={constants.text.linkPrefetchHoverDelayDescription} + /> + ) + } + + ) + } + handleChangeOption( 'activeOnMobile', !settings.activeOnMobile) } + description={constants.text.linkPrefetchActivateOnMobileDescription} + /> + { settings.activeOnMobile && ( + handleChangeOption( 'mobileBehavior', v) } + description={constants.text.linkPrefetchBehaviorDescription} + > + + + + ) + } + { ( settings.activeOnMobile || settings.activeOnDesktop ) && + handleChangeOption('ignoreKeywords', e.target.value)} + defaultValue={settings.ignoreKeywords} + /> + } + + + ) +} + +export default LinkPrefetch; \ No newline at end of file diff --git a/components/performance/defaultText.js b/components/performance/defaultText.js index 2603b96..a1b9d69 100644 --- a/components/performance/defaultText.js +++ b/components/performance/defaultText.js @@ -22,6 +22,25 @@ const defaultText = { clearCacheDescription: __('We automatically clear your cache as you work (creating content, changing settings, installing plugins and more). But you can manually clear it here to be confident it is fresh.', 'wp-module-performance'), clearCacheNoticeTitle: __('Cache cleared', 'wp-module-performance'), clearCacheTitle: __('Clear Cache', 'wp-module-performance'), + linkPrefetchDescription: __('Some description text here','wp-module-performance'), + linkPrefetchNoticeTitle: __('Link prefetching setting saved','wp-module-performance'), + linkPrefetchTitle: __('Link Prefetch','wp-module-performance'), + linkPrefetchActivateOnDekstopDescription: __('Enable link prefetching on desktop','wp-module-performance'), + linkPrefetchActivateOnDekstopLabel: __('Activate on desktop','wp-module-performance'), + linkPrefetchBehaviorDescription: __('Behavior of the prefetch','wp-module-performance'), + linkPrefetchBehaviorLabel: __('Behavior','wp-module-performance'), + linkPrefetchInstantClickDescription: __('If active the click is triggered on mouse down','wp-module-performance'), + linkPrefetchInstantClickLabel: __('Instant click','wp-module-performance'), + linkPrefetchHoverDelayDescription: __('Set the hover delay in ms','wp-module-performance'), + linkPrefetchHoverDelayLabel: __('Hover delay','wp-module-performance'), + linkPrefetchBehaviorMouseDownLabel: __('Mouse down','wp-module-performance'), + linkPrefetchBehaviorMouseHoverLabel: __('Mouse hover','wp-module-performance'), + linkPrefetchActivateOnMobileDescription: __('Enable link prefetching on Mobile','wp-module-performance'), + linkPrefetchActivateOnMobileLabel: __('Activate on mobile','wp-module-performance'), + linkPrefetchBehaviorMobileTouchstartLabel: __('Touchstart','wp-module-performance'), + linkPrefetchBehaviorMobileViewportLabel: __('Viewport','wp-module-performance'), + linkPrefetchIgnoreKeywordsDescription: __('The list of keywords that should be ignored from prefetching','wp-module-performance'), + linkPrefetchIgnoreKeywordsLabel: __('Ignore keywords','wp-module-performance'), }; export default defaultText; \ No newline at end of file diff --git a/components/performance/index.js b/components/performance/index.js index e6d7483..76672dd 100644 --- a/components/performance/index.js +++ b/components/performance/index.js @@ -2,6 +2,7 @@ import { Container } from '@newfold/ui-component-library'; import { default as CacheSettings } from '../cacheSettings/'; import { default as ClearCache } from '../clearCache/'; import { default as defaultText } from './defaultText'; +import { default as LinkPrefetch } from '../linkPrefetch/'; /** * Performance Module @@ -45,12 +46,18 @@ const Performance = ({methods, constants, Components, ...props}) => { Components={Components} /> - + + + + ); diff --git a/includes/LinkPrefetch.php b/includes/LinkPrefetch.php new file mode 100644 index 0000000..5299287 --- /dev/null +++ b/includes/LinkPrefetch.php @@ -0,0 +1,71 @@ +container = $container; + + add_action( 'wp_enqueue_scripts', array( $this, 'enqueueScripts') ); + } + /** + * Enqueue de script. + * + * return void + */ + public function enqueueScripts() { + $plugin_url = $this->container->plugin()->url . $this->getScriptPath(); + $settings = get_option( 'nfd_linkPrefetch', $this->getDefaultSettings() ); + + if ( ! $settings['activeOnDesktop'] && ! $settings['activeOnMobile'] ) { return; } + + $settings['isMobile'] = wp_is_mobile(); + + wp_enqueue_script( 'linkprefetcher', $plugin_url, array(), $this->container->plugin()->version, true ); + wp_add_inline_script( 'linkprefetcher', 'window.LP_CONFIG = ' . json_encode( $settings ), 'before' ); + } + + /** + * Get js script path. + * + * return string + */ + public function getScriptPath() { + $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; + return 'vendor/newfold-labs/wp-module-performance/assets/js/linkPrefetch' . $suffix . '.js'; + } + + /** + * Get link prefetch default settings. + * + * return array + */ + public function getDefaultSettings() { + return array( + 'activeOnDesktop' => false, + 'behavior' => 'mouseHover', + 'hoverDelay' => 60, + 'instantClick' => false , + 'activeOnMobile' => false , + 'mobileBehavior' => 'viewport', + 'ignoreKeywords' => 'wp-admin,#,?', + ); + } +} \ No newline at end of file diff --git a/includes/Performance.php b/includes/Performance.php index ac50c2f..c7e961c 100644 --- a/includes/Performance.php +++ b/includes/Performance.php @@ -72,13 +72,17 @@ public function __construct( Container $container ) { $cacheManager = new CacheManager( $container ); $cachePurger = new CachePurgingService( $cacheManager->getInstances() ); + $linkPrefetch = new LinkPrefetch( $container ); + // Ensure that purgeable cache types are enabled before showing the UI. if ( $cachePurger->canPurge() ) { add_action( 'admin_bar_menu', array( $this, 'adminBarMenu' ), 100 ); } $container->set( 'cachePurger', $cachePurger ); - + + $container->set( 'linkPrefetch', $linkPrefetch ); + $container->set( 'hasMustUsePlugin', file_exists( WPMU_PLUGIN_DIR . '/endurance-page-cache.php' ) ); } From 03256c3b671ad618f7423126f548ecd7fef1a1c5 Mon Sep 17 00:00:00 2001 From: Armando Liccardo Date: Fri, 25 Oct 2024 12:19:38 +0100 Subject: [PATCH 02/66] added defer attribute to script tag --- includes/LinkPrefetch.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/includes/LinkPrefetch.php b/includes/LinkPrefetch.php index 5299287..7e4a80e 100644 --- a/includes/LinkPrefetch.php +++ b/includes/LinkPrefetch.php @@ -24,6 +24,7 @@ public function __construct( Container $container ) { $this->container = $container; add_action( 'wp_enqueue_scripts', array( $this, 'enqueueScripts') ); + add_filter('script_loader_tag', array( $this, 'addDefer' ), 10, 2 ); } /** * Enqueue de script. @@ -40,6 +41,7 @@ public function enqueueScripts() { wp_enqueue_script( 'linkprefetcher', $plugin_url, array(), $this->container->plugin()->version, true ); wp_add_inline_script( 'linkprefetcher', 'window.LP_CONFIG = ' . json_encode( $settings ), 'before' ); + } /** @@ -68,4 +70,19 @@ public function getDefaultSettings() { 'ignoreKeywords' => 'wp-admin,#,?', ); } + + /** + * Add defer attribute to the script. + * + * @param string $tag html tag. + * @param string $handle handle of the script. + * + * return string + */ + public function addDefer( $tag, $handle ) { + if ('linkprefetcher' === $handle && false === strpos($tag, 'defer')) { + $tag = preg_replace(':(?=>):', ' defer', $tag); + } + return $tag; + } } \ No newline at end of file From 414553400da9503c177d5ec2ab3788c2204a9d52 Mon Sep 17 00:00:00 2001 From: Armando Liccardo Date: Fri, 25 Oct 2024 12:58:51 +0100 Subject: [PATCH 03/66] phpcs on new class --- includes/LinkPrefetch.php | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/includes/LinkPrefetch.php b/includes/LinkPrefetch.php index 7e4a80e..de1a281 100644 --- a/includes/LinkPrefetch.php +++ b/includes/LinkPrefetch.php @@ -23,8 +23,8 @@ class LinkPrefetch { public function __construct( Container $container ) { $this->container = $container; - add_action( 'wp_enqueue_scripts', array( $this, 'enqueueScripts') ); - add_filter('script_loader_tag', array( $this, 'addDefer' ), 10, 2 ); + add_action( 'wp_enqueue_scripts', array( $this, 'enqueueScripts' ) ); + add_filter( 'script_loader_tag', array( $this, 'addDefer' ), 10, 2 ); } /** * Enqueue de script. @@ -40,8 +40,7 @@ public function enqueueScripts() { $settings['isMobile'] = wp_is_mobile(); wp_enqueue_script( 'linkprefetcher', $plugin_url, array(), $this->container->plugin()->version, true ); - wp_add_inline_script( 'linkprefetcher', 'window.LP_CONFIG = ' . json_encode( $settings ), 'before' ); - + wp_add_inline_script( 'linkprefetcher', 'window.LP_CONFIG = ' . wp_json_encode( $settings ), 'before' ); } /** @@ -64,8 +63,8 @@ public function getDefaultSettings() { 'activeOnDesktop' => false, 'behavior' => 'mouseHover', 'hoverDelay' => 60, - 'instantClick' => false , - 'activeOnMobile' => false , + 'instantClick' => false, + 'activeOnMobile' => false, 'mobileBehavior' => 'viewport', 'ignoreKeywords' => 'wp-admin,#,?', ); @@ -76,13 +75,13 @@ public function getDefaultSettings() { * * @param string $tag html tag. * @param string $handle handle of the script. - * + * * return string */ public function addDefer( $tag, $handle ) { - if ('linkprefetcher' === $handle && false === strpos($tag, 'defer')) { - $tag = preg_replace(':(?=>):', ' defer', $tag); + if ( 'linkprefetcher' === $handle && false === strpos( $tag, 'defer' ) ) { + $tag = preg_replace( ':(?=>):', ' defer', $tag ); } return $tag; } -} \ No newline at end of file +} From ba1198a8ec7546ca79b790ff2ca08dd03a3d5cc6 Mon Sep 17 00:00:00 2001 From: Armando Liccardo Date: Mon, 4 Nov 2024 11:26:56 +0000 Subject: [PATCH 04/66] added copy text and changed toggle style --- components/linkPrefetch/index.js | 119 ++++++++++++++------------ components/performance/defaultText.js | 22 ++--- includes/LinkPrefetch.php | 2 +- 3 files changed, 74 insertions(+), 69 deletions(-) diff --git a/components/linkPrefetch/index.js b/components/linkPrefetch/index.js index c545a33..8f85ed6 100644 --- a/components/linkPrefetch/index.js +++ b/components/linkPrefetch/index.js @@ -1,6 +1,13 @@ -import { ToggleField, TextField, TextareaField, SelectField, Container } from "@newfold/ui-component-library"; +import { Toggle, TextField, TextareaField, SelectField, Container } from "@newfold/ui-component-library"; + const LinkPrefetch = ({methods, constants}) => { - const [settings, setSettings] = methods.useState(constants.store.linkPrefetch) + const [settings, setSettings] = methods.useState(constants.store?.linkPrefetch ? constants.store?.linkPrefetch : null) + + if ( !settings ) { + return; + } + + console.log(methods.NewfoldRuntime) const handleChangeOption = ( option, value ) => { if ( option in settings ) { @@ -36,69 +43,66 @@ const LinkPrefetch = ({methods, constants}) => { }, [settings]); return( + <> - handleChangeOption( 'activeOnDesktop', !settings.activeOnDesktop) } - description={constants.text.linkPrefetchActivateOnDekstopDescription} - /> +
+
+ +
+ {constants.text.linkPrefetchActivateOnDekstopDescription} +
+
+ handleChangeOption( 'activeOnDesktop', !settings.activeOnDesktop) } + /> +
{ settings.activeOnDesktop && ( - <> - handleChangeOption( 'behavior', v) } - description={constants.text.linkPrefetchBehaviorDescription} - > - - - - { - 'mouseDown' === settings.behavior && ( - handleChangeOption( 'instantClick', !settings.instantClick) } - description={constants.text.linkPrefetchInstantClickDescription} - /> - ) - } - { - 'mouseHover' === settings.behavior && ( - handleChangeOption( 'hoverDelay', '' === e.target.value ? 60 : e.target.value )} - type="number" - value={settings.hoverDelay} - description={constants.text.linkPrefetchHoverDelayDescription} - /> - ) - } - + handleChangeOption( 'behavior', v) } + description={ 'mouseDown' === settings.behavior ? constants.text.linkPrefetchBehaviorMouseDownDescription : constants.text.linkPrefetchBehaviorMouseHoverDescription} + > + + + ) } - +
+ +
+ {constants.text.linkPrefetchActivateOnMobileDescription} +
+
+ handleChangeOption('activeOnMobile', !settings.activeOnMobile) } + /> + +{/* handleChangeOption( 'activeOnMobile', !settings.activeOnMobile) } + onChange={() => handleChangeOption('activeOnMobile', !settings.activeOnMobile) } description={constants.text.linkPrefetchActivateOnMobileDescription} - /> + /> */} { settings.activeOnMobile && ( { value={settings.mobileBehavior} selectedLabel={'touchstart' === settings.mobileBehavior ? constants.text.linkPrefetchBehaviorMobileTouchstartLabel : constants.text.linkPrefetchBehaviorMobileViewportLabel} onChange={(v) => handleChangeOption( 'mobileBehavior', v) } - description={constants.text.linkPrefetchBehaviorDescription} + description={'touchstart' === settings.mobileBehavior ? constants.text.linkPrefetchBehaviorMobileTouchstartDescription : constants.text.linkPrefetchBehaviorMobileViewportDescription} > { ) } { ( settings.activeOnMobile || settings.activeOnDesktop ) && - handleChangeOption('ignoreKeywords', e.target.value)} - defaultValue={settings.ignoreKeywords} + value={settings.ignoreKeywords} /> }
+ ) } diff --git a/components/performance/defaultText.js b/components/performance/defaultText.js index a1b9d69..8ce5d23 100644 --- a/components/performance/defaultText.js +++ b/components/performance/defaultText.js @@ -22,25 +22,25 @@ const defaultText = { clearCacheDescription: __('We automatically clear your cache as you work (creating content, changing settings, installing plugins and more). But you can manually clear it here to be confident it is fresh.', 'wp-module-performance'), clearCacheNoticeTitle: __('Cache cleared', 'wp-module-performance'), clearCacheTitle: __('Clear Cache', 'wp-module-performance'), - linkPrefetchDescription: __('Some description text here','wp-module-performance'), + linkPrefetchDescription: __('Asks the browser to download and cache links on the page ahead of them being clicked on, so that when they are clicked they load almost instantly. ','wp-module-performance'), linkPrefetchNoticeTitle: __('Link prefetching setting saved','wp-module-performance'), linkPrefetchTitle: __('Link Prefetch','wp-module-performance'), linkPrefetchActivateOnDekstopDescription: __('Enable link prefetching on desktop','wp-module-performance'), linkPrefetchActivateOnDekstopLabel: __('Activate on desktop','wp-module-performance'), linkPrefetchBehaviorDescription: __('Behavior of the prefetch','wp-module-performance'), linkPrefetchBehaviorLabel: __('Behavior','wp-module-performance'), - linkPrefetchInstantClickDescription: __('If active the click is triggered on mouse down','wp-module-performance'), - linkPrefetchInstantClickLabel: __('Instant click','wp-module-performance'), - linkPrefetchHoverDelayDescription: __('Set the hover delay in ms','wp-module-performance'), - linkPrefetchHoverDelayLabel: __('Hover delay','wp-module-performance'), - linkPrefetchBehaviorMouseDownLabel: __('Mouse down','wp-module-performance'), - linkPrefetchBehaviorMouseHoverLabel: __('Mouse hover','wp-module-performance'), + linkPrefetchBehaviorMouseDownLabel: __('Prefetch on Mouse down','wp-module-performance'), + linkPrefetchBehaviorMouseDownDescription: __('Prefetch on Mouse Down: Starts loading the page as soon as you click down, for faster response when you release the click.','wp-module-performance'), + linkPrefetchBehaviorMouseHoverLabel: __('Prefetch on Mouse Hover (Recommended)','wp-module-performance'), + linkPrefetchBehaviorMouseHoverDescription: __('Prefetch on Mouse Hover: Begins loading the page the moment your cursor hovers over a link','wp-module-performance'), linkPrefetchActivateOnMobileDescription: __('Enable link prefetching on Mobile','wp-module-performance'), linkPrefetchActivateOnMobileLabel: __('Activate on mobile','wp-module-performance'), - linkPrefetchBehaviorMobileTouchstartLabel: __('Touchstart','wp-module-performance'), - linkPrefetchBehaviorMobileViewportLabel: __('Viewport','wp-module-performance'), - linkPrefetchIgnoreKeywordsDescription: __('The list of keywords that should be ignored from prefetching','wp-module-performance'), - linkPrefetchIgnoreKeywordsLabel: __('Ignore keywords','wp-module-performance'), + linkPrefetchBehaviorMobileTouchstartLabel: __('Prefetch on Touchstart (Recommended)','wp-module-performance'), + linkPrefetchBehaviorMobileTouchstartDescription: __('Prefetch on Touch Start: Instantly starts loading the page as soon as you tap the screen, ensuring a quicker response when you lift your finger.','wp-module-performance'), + linkPrefetchBehaviorMobileViewportLabel: __('Prefetch Above the Fold','wp-module-performance'), + linkPrefetchBehaviorMobileViewportDescription: __('Prefetch Above the Fold: Loads links in your current view instantly, ensuring they\'re ready when you need them.','wp-module-performance'), + linkPrefetchIgnoreKeywordsDescription: __('Exclude Keywords: A comma separated list of words or strings that will exclude a link from being prefetched. For example, excluding "app" will prevent https://example.com/apple from being prefetched.','wp-module-performance'), + linkPrefetchIgnoreKeywordsLabel: __('Exclude keywords','wp-module-performance'), }; export default defaultText; \ No newline at end of file diff --git a/includes/LinkPrefetch.php b/includes/LinkPrefetch.php index de1a281..f06f40e 100644 --- a/includes/LinkPrefetch.php +++ b/includes/LinkPrefetch.php @@ -65,7 +65,7 @@ public function getDefaultSettings() { 'hoverDelay' => 60, 'instantClick' => false, 'activeOnMobile' => false, - 'mobileBehavior' => 'viewport', + 'mobileBehavior' => 'touchstart', 'ignoreKeywords' => 'wp-admin,#,?', ); } From 3f9047010081d902f0eb922ce6c101a86801ad76 Mon Sep 17 00:00:00 2001 From: Armando Liccardo Date: Mon, 4 Nov 2024 15:01:04 +0000 Subject: [PATCH 05/66] added restapi to manage the settings, adding bottom margin to fields --- assets/js/linkPrefetch.js | 6 +- components/linkPrefetch/index.js | 57 +++++----- includes/LinkPrefetch.php | 40 +++++++ includes/RestApi/LinkPrefetchController.php | 112 ++++++++++++++++++++ 4 files changed, 181 insertions(+), 34 deletions(-) create mode 100644 includes/RestApi/LinkPrefetchController.php diff --git a/assets/js/linkPrefetch.js b/assets/js/linkPrefetch.js index 47bd6a8..2e0d338 100644 --- a/assets/js/linkPrefetch.js +++ b/assets/js/linkPrefetch.js @@ -17,7 +17,7 @@ window.addEventListener( 'load', () => { this.hoverDelay = config.hoverDelay; this.ignoreKeywords = config.ignoreKeywords.split(','); this.instantClick = config.instantClick; - this.mobileActive = config.mobileActive; + this.mobileActive = config.activeOnMobile; this.isMobile = config.isMobile; this.mobileBehavior = config.mobileBehavior; this.prefetchedUrls = new Set(); @@ -35,7 +35,7 @@ window.addEventListener( 'load', () => { if ( isChrome && chromeVersion < 110 ) {return;} if ( this.isMobile && ! this.mobileActive ) {return;} if ( ! this.isMobile && ! this.activeOnDesktop ) {return;} - + if ( ! this.isMobile ) { if ( 'mouseHover' === this.behavior ) { let hoverDelay = parseInt(this.hoverDelay); @@ -49,7 +49,7 @@ window.addEventListener( 'load', () => { } } } - + if ( this.mobileActive ) { if ( 'touchstart' === this.mobileBehavior ) { document.addEventListener("touchstart", this.touchstart.bind(this), this.eventListenerOptions); diff --git a/components/linkPrefetch/index.js b/components/linkPrefetch/index.js index 8f85ed6..7480a31 100644 --- a/components/linkPrefetch/index.js +++ b/components/linkPrefetch/index.js @@ -1,29 +1,29 @@ -import { Toggle, TextField, TextareaField, SelectField, Container } from "@newfold/ui-component-library"; +import { Toggle, TextField, SelectField, Container } from "@newfold/ui-component-library"; const LinkPrefetch = ({methods, constants}) => { - const [settings, setSettings] = methods.useState(constants.store?.linkPrefetch ? constants.store?.linkPrefetch : null) - - if ( !settings ) { - return; - } - - console.log(methods.NewfoldRuntime) + const [settings, setSettings] = methods.useState(methods.NewfoldRuntime.sdk.linkPrefetch.settings); + const [isError, setIsError] = methods.useState(false); + const apiUrl = methods.NewfoldRuntime.createApiUrl("/newfold-ecommerce/v1/linkprefetch/update"); const handleChangeOption = ( option, value ) => { if ( option in settings ) { const updatedSettings = settings; updatedSettings[option] = value; - methods.newfoldSettingsApiFetch( - { linkPrefetch: updatedSettings }, - methods.setError, (response) => { - setSettings( (prev)=> { - return { - ...prev, - [option]: value - } - }); - } - ); + methods.apiFetch({ + url: apiUrl, + method: "POST", + data: {settings: updatedSettings} + }).then((result)=>{ + console.log('updating settings',result) + setSettings( (prev)=> { + return { + ...prev, + [option]: value + } + }); + }).catch((error) => { + setIsError(error.message); + }); } } @@ -36,11 +36,11 @@ const LinkPrefetch = ({methods, constants}) => { methods.makeNotice( "link-prefetch-change-notice", constants.text.linkPrefetchTitle, - constants.text.linkPrefetchNoticeTitle, - "success", + !isError ? constants.text.linkPrefetchNoticeTitle : isError, + !isError ? "success" : "error", 5000 ); - }, [settings]); + }, [settings, isError]); return( <> @@ -48,7 +48,7 @@ const LinkPrefetch = ({methods, constants}) => { title={constants.text.linkPrefetchTitle} description={constants.text.linkPrefetchDescription} > -
+
@@ -70,6 +70,7 @@ const LinkPrefetch = ({methods, constants}) => { selectedLabel={'mouseDown' === settings.behavior ? constants.text.linkPrefetchBehaviorMouseDownLabel : constants.text.linkPrefetchBehaviorMouseHoverLabel} onChange={(v) => handleChangeOption( 'behavior', v) } description={ 'mouseDown' === settings.behavior ? constants.text.linkPrefetchBehaviorMouseDownDescription : constants.text.linkPrefetchBehaviorMouseHoverDescription} + className='nfd-mb-6' > { ) } -
+
@@ -96,13 +97,6 @@ const LinkPrefetch = ({methods, constants}) => { onChange={() => handleChangeOption('activeOnMobile', !settings.activeOnMobile) } />
-{/* handleChangeOption('activeOnMobile', !settings.activeOnMobile) } - description={constants.text.linkPrefetchActivateOnMobileDescription} - /> */} { settings.activeOnMobile && ( { selectedLabel={'touchstart' === settings.mobileBehavior ? constants.text.linkPrefetchBehaviorMobileTouchstartLabel : constants.text.linkPrefetchBehaviorMobileViewportLabel} onChange={(v) => handleChangeOption( 'mobileBehavior', v) } description={'touchstart' === settings.mobileBehavior ? constants.text.linkPrefetchBehaviorMobileTouchstartDescription : constants.text.linkPrefetchBehaviorMobileViewportDescription} + className='nfd-mb-6' > container = $container; + add_action( 'rest_api_init', array( $this, 'register_routes' ) ); + add_filter( 'newfold-runtime', array( $this, 'add_to_runtime' ) ); + add_action( 'wp_enqueue_scripts', array( $this, 'enqueueScripts' ) ); add_filter( 'script_loader_tag', array( $this, 'addDefer' ), 10, 2 ); } + + /** + * Register API routes. + */ + public function register_routes() { + foreach ( $this->controllers as $Controller ) { + /** + * Get an instance of the WP_REST_Controller. + * + * @var $instance \WP_REST_Controller + */ + $instance = new $Controller( $this->container ); + $instance->register_routes(); + } + } + /** + * Add values to the runtime object. + * + * @param array $sdk The runtime object. + * + * @return array + */ + public function add_to_runtime( $sdk ) { + $values = array( + 'settings' => get_option( 'nfd_linkPrefetch', $this->getDefaultSettings() ), + ); + return array_merge( $sdk, array( 'linkPrefetch' => $values ) ); + } /** * Enqueue de script. * diff --git a/includes/RestApi/LinkPrefetchController.php b/includes/RestApi/LinkPrefetchController.php new file mode 100644 index 0000000..d5504e6 --- /dev/null +++ b/includes/RestApi/LinkPrefetchController.php @@ -0,0 +1,112 @@ +container = $container; + } + + /** + * Registers rest routes for PluginsController class. + * + * @return void + */ + public function register_routes() { + \register_rest_route( + $this->namespace, + $this->rest_base . '/settings', + array( + array( + 'methods' => \WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_settings' ), + 'permission_callback' => array( Permissions::class, 'rest_is_authorized_admin' ), + ), + ) + ); + \register_rest_route( + $this->namespace, + $this->rest_base . '/update', + array( + array( + 'methods' => \WP_REST_Server::CREATABLE, + 'callback' => array( $this, 'update_settings' ), + 'permission_callback' => array( Permissions::class, 'rest_is_authorized_admin' ), + ), + ) + ); + } + + /** + * Get the settings + * + * @return \WP_REST_Response + */ + public function get_settings() { + return new \WP_REST_Response( + array( + 'settings' => get_option( 'nfd_linkPrefetch', $this->container->get( 'linkPrefetch' )->getDefaultSettings() ), + ), + 200 + ); + } + + /** + * Update the settings + * + * @param \WP_REST_Request $request the request. + * @return \WP_REST_Response + */ + public function update_settings( \WP_REST_Request $request ) { + $settings = $request->get_param( 'settings' ); + if ( is_array( $settings ) && update_option( 'nfd_linkPrefetch', $settings ) ) { + return new \WP_REST_Response( + array( + 'result' => true, + ), + 200 + ); + } + + return new \WP_REST_Response( + array( + 'result' => false, + ), + 400 + ); + } +} From f7801ae9012854393b9be863c8baec2378b61504 Mon Sep 17 00:00:00 2001 From: Alessio Torrisi Date: Mon, 4 Nov 2024 17:11:25 +0100 Subject: [PATCH 06/66] New branch Jetpack Boost --- .../InstallActivatePluginButton.js | 63 + .../JetpackBoost/SingleOption.js | 120 ++ .../advancedSettings/JetpackBoost/index.js | 156 ++ components/advancedSettings/index.js | 16 + components/cacheSettings/index.js | 4 +- components/performance/defaultText.js | 20 + components/performance/index.js | 12 +- includes/Performance.php | 35 +- includes/RestApi/JetpackController.php | 82 + package-lock.json | 1870 ++++++++++++++++- package.json | 3 +- src/css/style.css | 32 + 12 files changed, 2362 insertions(+), 51 deletions(-) create mode 100644 components/advancedSettings/JetpackBoost/InstallActivatePluginButton.js create mode 100644 components/advancedSettings/JetpackBoost/SingleOption.js create mode 100644 components/advancedSettings/JetpackBoost/index.js create mode 100644 components/advancedSettings/index.js create mode 100644 includes/RestApi/JetpackController.php create mode 100644 src/css/style.css diff --git a/components/advancedSettings/JetpackBoost/InstallActivatePluginButton.js b/components/advancedSettings/JetpackBoost/InstallActivatePluginButton.js new file mode 100644 index 0000000..35f669e --- /dev/null +++ b/components/advancedSettings/JetpackBoost/InstallActivatePluginButton.js @@ -0,0 +1,63 @@ +import React, { useState } from 'react'; +import apiFetch from '@wordpress/api-fetch'; +import { Spinner } from "@newfold/ui-component-library"; + +const InstallActivatePluginButton = ( { methods, constants, setModuleStatus } ) => { + const [isLoading, setIsLoading] = useState(false); + const [isActive, setIsActive] = useState(false); + const [message, setMessage] = useState(null); + + const handleInstallActivate = async () => { + setIsLoading(true); + setMessage( constants.text.jetpackBoostInstalling ); + + const apiUrl = methods.NewfoldRuntime.createApiUrl("/newfold-installer/v1/plugins/install"); + const INSTALL_TOKEN = methods.NewfoldRuntime.ecommerce.install_token; + const plugin = 'jetpack-boost'; + + try { + await methods.apiFetch({ + url: apiUrl, + method: "POST", + headers: { "X-NFD-INSTALLER": INSTALL_TOKEN }, + data: { plugin, activate: true, queue: false }, + }); + setIsActive(true); + setModuleStatus(true); + setMessage( constants.text.jetpackBoostInstalling ); + } catch (error) { + console.log(error.message); + setMessage( constants.text.jetpackBoostActivationFailed ); + } finally { + setIsLoading(false); + } + }; + + return ( +
+ +
+ ); +}; + +export default InstallActivatePluginButton; \ No newline at end of file diff --git a/components/advancedSettings/JetpackBoost/SingleOption.js b/components/advancedSettings/JetpackBoost/SingleOption.js new file mode 100644 index 0000000..b35ff8f --- /dev/null +++ b/components/advancedSettings/JetpackBoost/SingleOption.js @@ -0,0 +1,120 @@ +import React, { useState, useEffect, useRef } from 'react'; +import apiFetch from '@wordpress/api-fetch'; +import { ToggleField, Textarea, Notifications } from '@newfold/ui-component-library'; + +const SingleOption = ( {params, isChild, methods, constants } ) => { + + const [ optionDetails, setOptionDetails ] = useState({ + id: params.id, + label: params.label, + description: params.description, + value: params.value ? String(params.value) : '', + type: params.type, + externalLink: params.externalLink, + children: params.children + }); + + + const [ isShown, setIsShown ] = useState( false ); + + const debounceTimeout = useRef(null); // Mantiene il timeout tra i render + + + const handleChangeOption = ( value, id ) => { + + if( typeof value === 'object' ){ + value = value.target.value; + } + + setOptionDetails({ ...optionDetails, value: value }); + + // Cancella il timeout precedente se l'utente digita di nuovo + if (debounceTimeout.current) { + clearTimeout(debounceTimeout.current); + } + + // Imposta un nuovo timeout di 2 secondi + debounceTimeout.current = setTimeout(() => { + apiFetch({ + path: 'newfold-performance/v1/jetpack/set_options', + method: 'POST', + data: { + field: { + id: id, + value: value + }, + } + }).then((response) => { + methods.makeNotice( + "cache-level-change-notice", + constants.text.jetpackOptionSaved, + '', + "success", + 5000 + ); + }).catch((error) => { + + }); + }, 1000); + + + } + + const handleTextInputChange = ( value, id ) => { + + }; + + + const displayOption = ( params ) => { + switch( params.type ){ + case 'toggle': + return ( + <> + { handleChangeOption( value, params.id ) } } + /> + { params.externalLink ?

{ constants.text.jetpackBoostDicoverMore } { __( 'here', 'newfold-module-performance' ) }

: '' } + + ); + + case 'textarea': + return ( + <> +

{params.label}

+