Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

link prefetch new feature #26

Merged
merged 28 commits into from
Dec 14, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
c28ad8d
added link prefetch options component, LinkPrefetch class and js for …
geckod22 Oct 23, 2024
03256c3
added defer attribute to script tag
geckod22 Oct 25, 2024
4145534
phpcs on new class
geckod22 Oct 25, 2024
ba1198a
added copy text and changed toggle style
geckod22 Nov 4, 2024
3f90470
added restapi to manage the settings, adding bottom margin to fields
geckod22 Nov 4, 2024
fe39f82
removed console.log
geckod22 Nov 7, 2024
61fa41f
fixed check for mobile
geckod22 Nov 11, 2024
8374c5f
added fix for minified js
geckod22 Nov 11, 2024
49feb01
Update includes/RestApi/LinkPrefetchController.php
geckod22 Nov 18, 2024
24dff12
Update includes/RestApi/LinkPrefetchController.php
geckod22 Nov 18, 2024
ecbdb47
updated LinkPrefetchController with more fix
geckod22 Nov 18, 2024
f743bfe
adding suggested improvements to LinkPrefetchController.php
geckod22 Nov 18, 2024
b7f0abd
removed unused Permissions class and removed LinkPrefetch new and con…
geckod22 Nov 18, 2024
1427769
defaultText.js fix
geckod22 Nov 18, 2024
62999b1
Update assets/js/linkPrefetch.js
geckod22 Nov 18, 2024
b5a5284
Update components/linkPrefetch/index.js
geckod22 Nov 18, 2024
c9c5775
moved non-react scripts to scripts folder
geckod22 Nov 18, 2024
2202638
added RestApi.php
geckod22 Nov 18, 2024
5b5c22d
applied suggestion changes to component index.js
geckod22 Nov 18, 2024
5f18dca
other fixies for RestApi and LinkPrefetch Starting
geckod22 Nov 18, 2024
af2d843
moved inline styles to file and load it
geckod22 Nov 18, 2024
d29a258
some phpcs fixiews
geckod22 Nov 18, 2024
e010497
phpcs
geckod22 Nov 18, 2024
e2170d6
linkPrefetch.js lint
geckod22 Nov 18, 2024
96f9b80
Lint js files
arunshenoy99 Nov 20, 2024
a78673c
fix for mousedown event
geckod22 Nov 27, 2024
cd5d1d8
adding minified js
geckod22 Nov 27, 2024
9fc34ae
Merge branch 'release/v2.1.0' of https://github.com/newfold-labs/wp-m…
arunshenoy99 Dec 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
219 changes: 219 additions & 0 deletions assets/js/linkPrefetch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
window.addEventListener( 'load', () => {
geckod22 marked this conversation as resolved.
Show resolved Hide resolved
/* 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.activeOnMobile;
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 && this.isMobile ) {
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();
}
});
geckod22 marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions assets/js/linkPrefetch.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

135 changes: 135 additions & 0 deletions components/linkPrefetch/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { Toggle, TextField, SelectField, Container } from "@newfold/ui-component-library";

const LinkPrefetch = ({methods, constants}) => {
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");
geckod22 marked this conversation as resolved.
Show resolved Hide resolved

const handleChangeOption = ( option, value ) => {
if ( option in settings ) {
const updatedSettings = settings;
updatedSettings[option] = value;
methods.apiFetch({
url: apiUrl,
method: "POST",
data: {settings: updatedSettings}
}).then((result)=>{
setSettings( (prev)=> {
return {
...prev,
[option]: value
}
});
}).catch((error) => {
setIsError(error.message);
});
}
}

methods.useUpdateEffect(() => {
methods.setStore({
...constants.store,
linkPrefetch: settings,
});

methods.makeNotice(
"link-prefetch-change-notice",
constants.text.linkPrefetchTitle,
!isError ? constants.text.linkPrefetchNoticeTitle : isError,
!isError ? "success" : "error",
5000
);
}, [settings, isError]);

return(
<>
<Container.SettingsField
title={constants.text.linkPrefetchTitle}
description={constants.text.linkPrefetchDescription}
>
<div className="nfd-toggle-field nfd-mb-6" style={{display: 'flex', flexDirection:'row'}}>
geckod22 marked this conversation as resolved.
Show resolved Hide resolved
<div >
<label className="nfd-label" htmlFor="link-prefetch-active-desktop">{constants.text.linkPrefetchActivateOnDekstopLabel}</label>
<div className="nfd-select-field__description">
{constants.text.linkPrefetchActivateOnDekstopDescription}
</div>
</div>
<Toggle
id='link-prefetch-active-desktop'
screenReaderLabel={constants.text.linkPrefetchActivateOnDekstopLabel}
checked={settings.activeOnDesktop}
onChange={() => handleChangeOption( 'activeOnDesktop', !settings.activeOnDesktop) }
/>
</div>
{ settings.activeOnDesktop && (
<SelectField
id="link-prefetch-behavior"
label={constants.text.linkPrefetchBehaviorLabel}
value={settings.behavior}
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'
>
<SelectField.Option
label={constants.text.linkPrefetchBehaviorMouseHoverLabel}
value="mouseHover"
/>
<SelectField.Option
label={constants.text.linkPrefetchBehaviorMouseDownLabel}
value="mouseDown"
/>
</SelectField>
)
}
<div className="nfd-toggle-field nfd-mb-6" style={{display: 'flex', flexDirection:'row'}}>
<div >
<label className="nfd-label" htmlFor="link-prefetch-active-mobile">{constants.text.linkPrefetchActivateOnMobileLabel}</label>
<div className="nfd-select-field__description">
{constants.text.linkPrefetchActivateOnMobileDescription}
</div>
</div>
<Toggle
id='link-prefetch-active-mobile'
screenReaderLabel={constants.text.linkPrefetchActivateOnMobileLabel}
checked={settings.activeOnMobile}
onChange={() => handleChangeOption('activeOnMobile', !settings.activeOnMobile) }
/>
</div>
{ settings.activeOnMobile && (
<SelectField
id="link-prefetch-behavior-mobile"
label={constants.text.linkPrefetchBehaviorLabel}
value={settings.mobileBehavior}
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'
>
<SelectField.Option
label={constants.text.linkPrefetchBehaviorMobileTouchstartLabel}
value="touchstart"
/>
<SelectField.Option
label={constants.text.linkPrefetchBehaviorMobileViewportLabel}
value="viewport"
/>
</SelectField>
)
}
{ ( settings.activeOnMobile || settings.activeOnDesktop ) &&
<TextField
id="link-prefetch-ignore-keywords"
label={constants.text.linkPrefetchIgnoreKeywordsLabel}
description={constants.text.linkPrefetchIgnoreKeywordsDescription}
onChange={(e) => handleChangeOption('ignoreKeywords', e.target.value)}
value={settings.ignoreKeywords}
/>
}

</Container.SettingsField>
</>
)
}

export default LinkPrefetch;
geckod22 marked this conversation as resolved.
Show resolved Hide resolved
19 changes: 19 additions & 0 deletions components/performance/defaultText.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: __('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'),
geckod22 marked this conversation as resolved.
Show resolved Hide resolved
linkPrefetchActivateOnDekstopLabel: __('Activate on desktop','wp-module-performance'),
geckod22 marked this conversation as resolved.
Show resolved Hide resolved
linkPrefetchBehaviorDescription: __('Behavior of the prefetch','wp-module-performance'),
linkPrefetchBehaviorLabel: __('Behavior','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: __('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;
Loading
Loading