From ab5022366fad3c461d5b675aae8f4de94ae870af Mon Sep 17 00:00:00 2001 From: Natizyskunk <59120131+Natizyskunk@users.noreply.github.com> Date: Tue, 6 Apr 2021 01:39:01 +0200 Subject: [PATCH 1/8] Update flying-pages.js --- flying-pages.js | 357 ++++++++++++++++++++++++------------------------ 1 file changed, 182 insertions(+), 175 deletions(-) diff --git a/flying-pages.js b/flying-pages.js index 1cfed65..b93ef02 100644 --- a/flying-pages.js +++ b/flying-pages.js @@ -1,175 +1,182 @@ -function flyingPages() { - const toPrefetch = new Set(); - const alreadyPrefetched = new Set(); - - // Check browser support for native 'prefetch' - const prefetcher = document.createElement("link"); - const isSupported = - prefetcher.relList && - prefetcher.relList.supports && - prefetcher.relList.supports("prefetch") && - window.IntersectionObserver && - "isIntersecting" in IntersectionObserverEntry.prototype; - - // Checks if user is on slow connection or has enabled data saver - const isSlowConnection = - navigator.connection && - (navigator.connection.saveData || - (navigator.connection.effectiveType || "").includes("2g")); - - // Don't start prefetching if user is on a slow connection or not supported - if (isSlowConnection || !isSupported) return; - - // Prefetch the given url using native 'prefetch'. Fallback to 'xhr' if not supported - const prefetch = (url) => - new Promise((resolve, reject) => { - const link = document.createElement(`link`); - link.rel = `prefetch`; - link.href = url; - link.onload = resolve; - link.onerror = reject; - document.head.appendChild(link); - }); - - // Prefetch pages with a timeout - const prefetchWithTimeout = (url) => { - const timer = setTimeout(() => stopPrefetching(), 5000); - prefetch(url) - .catch(() => stopPrefetching()) - .finally(() => clearTimeout(timer)); - }; - - const addUrlToQueue = (url, processImmediately = false) => { - if (alreadyPrefetched.has(url) || toPrefetch.has(url)) return; - - // Prevent prefetching 3rd party domains - const origin = window.location.origin; - if (url.substring(0, origin.length) !== origin) return; - - // Prevent current page from prefetching - if (window.location.href === url) return; - - // Ignore keywords in the array, if matched to the url - for (let i = 0; i < window.FPConfig.ignoreKeywords.length; i++) { - if (url.includes(window.FPConfig.ignoreKeywords[i])) return; - } - - // If max RPS is 0 or is on mouse hover, process immediately (without queue) - if (processImmediately) { - prefetchWithTimeout(url); - alreadyPrefetched.add(url); - } else toPrefetch.add(url); - }; - - // Observe the links in viewport, add url to queue if found intersecting - const linksObserver = new IntersectionObserver((entries) => { - entries.forEach((entry) => { - if (entry.isIntersecting) { - const url = entry.target.href; - addUrlToQueue(url, !window.FPConfig.maxRPS); - } - }); - }); - - // Queue that process requests based on max RPS (requests per second) - const startQueue = () => - setInterval(() => { - Array.from(toPrefetch) - .slice(0, window.FPConfig.maxRPS) - .forEach((url) => { - prefetchWithTimeout(url); - alreadyPrefetched.add(url); - toPrefetch.delete(url); - }); - }, 1000); - - let hoverTimer = null; - - // Add URL to queue on mouse hover, after timeout - const mouseOverListener = (event) => { - const elm = event.target.closest("a"); - if (elm && elm.href && !alreadyPrefetched.has(elm.href)) { - hoverTimer = setTimeout(() => { - addUrlToQueue(elm.href, true); - }, window.FPConfig.hoverDelay); - } - }; - - // Prefetch on touchstart on mobile - const touchStartListener = (event) => { - const elm = event.target.closest("a"); - if (elm && elm.href && !alreadyPrefetched.has(elm.href)) - addUrlToQueue(elm.href, true); - }; - - // Clear timeout on mouse out if not already prefetched - const mouseOutListener = (event) => { - const elm = event.target.closest("a"); - if (elm && elm.href && !alreadyPrefetched.has(elm.href)) { - clearTimeout(hoverTimer); - } - }; - - // Fallback for requestIdleCallback https://caniuse.com/#search=requestIdleCallback - const requestIdleCallback = - window.requestIdleCallback || - function (cb) { - const start = Date.now(); - return setTimeout(function () { - cb({ - didTimeout: false, - timeRemaining: function () { - return Math.max(0, 50 - (Date.now() - start)); - }, - }); - }, 1); - }; - - // Stop prefetching in case server is responding slow/errors - const stopPrefetching = () => { - // Find all links are remove it from observer (viewport) - document.querySelectorAll("a").forEach((e) => linksObserver.unobserve(e)); - - // Clear pending links in queue - toPrefetch.clear(); - - // Remove event listeners for mouse hover and mobile touch - document.removeEventListener("mouseover", mouseOverListener, true); - document.removeEventListener("mouseout", mouseOutListener, true); - document.removeEventListener("touchstart", touchStartListener, true); - }; - - // Default options incase options is not set - const defaultOptions = { - delay: 0, - ignoreKeywords: [], - maxRPS: 3, - hoverDelay: 50, - }; - - // Combine default options with received options to create the new config and set the config in window for easy access - window.FPConfig = Object.assign(defaultOptions, window.FPConfig); - - // Start Queue - startQueue(); - - // Start prefetching links in viewport on idle callback, with a delay - requestIdleCallback(() => - setTimeout( - () => - document.querySelectorAll("a").forEach((e) => linksObserver.observe(e)), - window.FPConfig.delay * 1000 - ) - ); - - // Add event listeners to detect mouse hover and mobile touch - const listenerOptions = { - capture: true, - passive: true, - }; - document.addEventListener("mouseover", mouseOverListener, listenerOptions); - document.addEventListener("mouseout", mouseOutListener, listenerOptions); - document.addEventListener("touchstart", touchStartListener, listenerOptions); -} - -flyingPages(); +!function(e, n) { + if ("object" == typeof exports && "undefined" != typeof module) { + n(exports) + } else if ("function" == typeof define && define.amd) { + define(["exports"], n); + } else { + n((e = e || self).flyingPages = {}); + } +}(this, (e) => { + const toPrefetch = new Set(); + const alreadyPrefetched = new Set(); + + // Check browser support for native 'prefetch' + const prefetcher = document.createElement("link"); + const isSupported = + prefetcher.relList && + prefetcher.relList.supports && + prefetcher.relList.supports("prefetch") && + window.IntersectionObserver && + "isIntersecting" in IntersectionObserverEntry.prototype; + + // Checks if user is on slow connection or has enabled data saver + const isSlowConnection = + navigator.connection && + (navigator.connection.saveData || + (navigator.connection.effectiveType ||"").includes("2g")); + + // Don't start prefetching if user is on a slow connection or not supported + if (isSlowConnection || !isSupported) return; + + // Prefetch the given url using native 'prefetch'. Fallback to 'xhr' if not supported + const prefetch = (url) => + new Promise((resolve, reject) => { + const link = document.createElement(`link`); + link.rel = `prefetch`; + link.href = url; + link.onload = resolve; + link.onerror = reject; + document.head.appendChild(link); + }); + + // Prefetch pages with a timeout + const prefetchWithTimeout = (url) => { + const timer = setTimeout(() => stopPrefetching(), 5000); + prefetch(url) + .catch(() => stopPrefetching()) + .finally(() => clearTimeout(timer)); + }; + + const addUrlToQueue = (url, processImmediately = false) => { + if (alreadyPrefetched.has(url) || toPrefetch.has(url)) return; + + // Prevent prefetching 3rd party domains + const origin = window.location.origin; + if (url.substring(0, origin.length) !== origin) return; + + // Prevent current page from prefetching + if (window.location.href === url) return; + + // Ignore keywords in the array, if matched to the url + for (let i = 0; i < window.FPConfig.ignoreKeywords.length; i++) { + if (url.includes(window.FPConfig.ignoreKeywords[i])) return; + } + + // If max RPS is 0 or is on mouse hover, process immediately (without queue) + if (processImmediately) { + prefetchWithTimeout(url); + alreadyPrefetched.add(url); + } else toPrefetch.add(url); + }; + + // Observe the links in viewport, add url to queue if found intersecting + const linksObserver = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + const url = entry.target.href; + addUrlToQueue(url, !window.FPConfig.maxRPS); + } + }); + }); + + // Queue that process requests based on max RPS (requests per second) + const startQueue = () => + setInterval(() => { + Array.from(toPrefetch) + .slice(0, window.FPConfig.maxRPS) + .forEach((url) => { + prefetchWithTimeout(url); + alreadyPrefetched.add(url); + toPrefetch.delete(url); + }); + }, 1000); + + let hoverTimer = null; + + // Add URL to queue on mouse hover, after timeout + const mouseOverListener = (event) => { + const elm = event.target.closest("a"); + if (elm && elm.href && !alreadyPrefetched.has(elm.href)) { + hoverTimer = setTimeout(() => { + addUrlToQueue(elm.href, true); + }, window.FPConfig.hoverDelay); + } + }; + + // Prefetch on touchstart on mobile + const touchStartListener = (event) => { + const elm = event.target.closest("a"); + if (elm && elm.href && !alreadyPrefetched.has(elm.href)) { + addUrlToQueue(elm.href, true); + } + }; + + // Clear timeout on mouse out if not already prefetched + const mouseOutListener = (event) => { + const elm = event.target.closest("a"); + if (elm && elm.href && !alreadyPrefetched.has(elm.href)) { + clearTimeout(hoverTimer); + } + }; + + // Fallback for requestIdleCallback https://caniuse.com/#search=requestIdleCallback + const requestIdleCallback = + window.requestIdleCallback || + function (cb) { + const start = Date.now(); + return setTimeout(function () { + cb({ + didTimeout: false, + timeRemaining: function () { + return Math.max(0, 50 - (Date.now() - start)); + }, + }); + }, 1); + }; + + // Stop prefetching in case server is responding slow/errors + const stopPrefetching = () => { + // Find all links are remove it from observer (viewport) + document.querySelectorAll("a").forEach((e) => linksObserver.unobserve(e)); + + // Clear pending links in queue + toPrefetch.clear(); + + // Remove event listeners for mouse hover and mobile touch + document.removeEventListener("mouseover", mouseOverListener, true); + document.removeEventListener("mouseout", mouseOutListener, true); + document.removeEventListener("touchstart", touchStartListener, true); + }; + + + e.listen = (e) => { + e = e || {}; + window.FPConfig = { + delay: e.delay || 0, + ignoreKeywords: e.ignoreKeywords || [], + maxRPS: e.maxRPS || 3, + hoverDelay: e.hoverDelay || 50, + }; + + // Start Queue + startQueue(); + + // Start prefetching links in viewport on idle callback, with a delay + requestIdleCallback(() => + setTimeout( + () => + document.querySelectorAll("a").forEach((e) => linksObserver.observe(e)), + window.FPConfig.delay * 1000 + ) + ); + + // Add event listeners to detect mouse hover and mobile touch + const listenerOptions = { + capture: true, + passive: true, + }; + document.addEventListener("mouseover", mouseOverListener, listenerOptions); + document.addEventListener("mouseout", mouseOutListener, listenerOptions); + document.addEventListener("touchstart", touchStartListener, listenerOptions); + }; +}); From 1aba25e966e09e45485021204c431dab5eadbcb7 Mon Sep 17 00:00:00 2001 From: Natizyskunk <59120131+Natizyskunk@users.noreply.github.com> Date: Tue, 6 Apr 2021 01:39:28 +0200 Subject: [PATCH 2/8] Update flying-pages.min.js --- flying-pages.min.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flying-pages.min.js b/flying-pages.min.js index 69284e0..8705c26 100644 --- a/flying-pages.min.js +++ b/flying-pages.min.js @@ -1 +1 @@ -"use strict";function flyingPages(){var a=new Set,b=new Set,c=document.createElement("link"),d=c.relList&&c.relList.supports&&c.relList.supports("prefetch")&&window.IntersectionObserver&&"isIntersecting"in IntersectionObserverEntry.prototype,e=navigator.connection&&(navigator.connection.saveData||(navigator.connection.effectiveType||"").includes("2g"));if(!e&&d){var f=function(a){return new Promise(function(b,c){var d=document.createElement("link");d.rel="prefetch",d.href=a,d.onload=b,d.onerror=c,document.head.appendChild(d)})},g=function(a){var b=setTimeout(function(){return p()},5e3);f(a)["catch"](function(){return p()})["finally"](function(){return clearTimeout(b)})},h=function(c){var d=!!(1{const t=new Set,n=new Set,o=document.createElement("link"),r=o.relList&&o.relList.supports&&o.relList.supports("prefetch")&&window.IntersectionObserver&&"isIntersecting"in IntersectionObserverEntry.prototype;if(navigator.connection&&(navigator.connection.saveData||(navigator.connection.effectiveType||"").includes("2g"))||!r)return;const i=e=>{const t=setTimeout(()=>m(),5e3);(e=>new Promise((t,n)=>{const o=document.createElement("link");o.rel="prefetch",o.href=e,o.onload=t,o.onerror=n,document.head.appendChild(o)}))(e).catch(()=>m()).finally(()=>clearTimeout(t))},s=(e,o=!1)=>{if(n.has(e)||t.has(e))return;const r=window.location.origin;if(e.substring(0,r.length)===r&&window.location.href!==e){for(let t=0;t{e.forEach(e=>{if(e.isIntersecting){const t=e.target.href;s(t,!window.FPConfig.maxRPS)}})});let c=null;const d=e=>{const t=e.target.closest("a");t&&t.href&&!n.has(t.href)&&(c=setTimeout(()=>{s(t.href,!0)},window.FPConfig.hoverDelay))},l=e=>{const t=e.target.closest("a");t&&t.href&&!n.has(t.href)&&s(t.href,!0)},u=e=>{const t=e.target.closest("a");t&&t.href&&!n.has(t.href)&&clearTimeout(c)},f=window.requestIdleCallback||function(e){const t=Date.now();return setTimeout(function(){e({didTimeout:!1,timeRemaining:function(){return Math.max(0,50-(Date.now()-t))}})},1)},m=()=>{document.querySelectorAll("a").forEach(e=>a.unobserve(e)),t.clear(),document.removeEventListener("mouseover",d,!0),document.removeEventListener("mouseout",u,!0),document.removeEventListener("touchstart",l,!0)};e.listen=(e=>{e=e||{},window.FPConfig={delay:e.delay||0,ignoreKeywords:e.ignoreKeywords||[],maxRPS:e.maxRPS||3,hoverDelay:e.hoverDelay||50},(()=>setInterval(()=>{Array.from(t).slice(0,window.FPConfig.maxRPS).forEach(e=>{i(e),n.add(e),t.delete(e)})},1e3))(),f(()=>setTimeout(()=>document.querySelectorAll("a").forEach(e=>a.observe(e)),1e3*window.FPConfig.delay));const o={capture:!0,passive:!0};document.addEventListener("mouseover",d,o),document.addEventListener("mouseout",u,o),document.addEventListener("touchstart",l,o)})}); From 1b05da97536f8d22e80fcb642781678fecaccf4f Mon Sep 17 00:00:00 2001 From: Natizyskunk <59120131+Natizyskunk@users.noreply.github.com> Date: Tue, 6 Apr 2021 01:39:45 +0200 Subject: [PATCH 3/8] Create index.html --- index.html | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 index.html diff --git a/index.html b/index.html new file mode 100644 index 0000000..c7b34d8 --- /dev/null +++ b/index.html @@ -0,0 +1,36 @@ + + + + + + + test flying pages + + +
+
+ +
+
+ + + + From fe4f1b02a0386c2644bafe6b630bcfdd08d77bb6 Mon Sep 17 00:00:00 2001 From: Natizyskunk <59120131+Natizyskunk@users.noreply.github.com> Date: Tue, 6 Apr 2021 01:39:59 +0200 Subject: [PATCH 4/8] Update readme.md --- readme.md | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/readme.md b/readme.md index 10fc329..116e0b7 100644 --- a/readme.md +++ b/readme.md @@ -6,6 +6,7 @@ > Flying Pages prefetch pages before the user click on links, making them load instantly + ## Quick Links - Demo: Open [https://wpspeedmatters.com](https://wpspeedmatters.com) and click on any post @@ -13,7 +14,6 @@ - [Quicklink vs Instant page vs Flying Pages](https://wpspeedmatters.com/quicklink-vs-instant-page-vs-flying-pages/) - Join our [Facebook Group](https://www.facebook.com/groups/wpspeedmatters/), a community of WordPress speed enthusiasts -Buy Me A Coffee ## Usage @@ -21,20 +21,27 @@ Quickstart: ```html + ``` With options: ```html + - ``` - `delay`: Start prefetching after a delay (in seconds). Will be started when the browser becomes idle, using `requestIdleCallback`. Default to 0. @@ -42,6 +49,7 @@ With options: - `maxRPS`: Maximum requests per second the queue should process. Set to 0 to process all requests immediately (without queue). Default to 3. - `hoverDelay`: Delay in prefetching links on mouse hover (in milliseconds). Default 50. + ## How it Works? Flying Pages injects a tiny JavaScript code (1KB gzipped), waits until the browser becomes idle. Then it detects pages in the viewport and on mouse hover and prefetch them. @@ -57,3 +65,8 @@ Flying Pages is intelligent to make sure prefetching doesn't crash your server o - **Stops prefetching if the server is busy** - In case the server starts to respond slowly or return errors, prefetching will be stopped to reduce the server load. - **Understands user's connection and preferences** - Checks if the user is on a slow connection like 2G or has enabled data-saver. Flying Pages won't prefetch anything in this case. + + +# Changelog + +**2021-04-05**: mod to make the script work without defer and without having to set options before the script call. Replacing window.FPConfig{}; before the script call with flyingPages.listen(); after the script call (see [Usage](#Usage)). From 025e954bbea2f8c538f9806e631ce676e263e0d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natan=20FOURI=C3=89?= <59120131+Natizyskunk@users.noreply.github.com> Date: Fri, 28 May 2021 22:16:18 +0200 Subject: [PATCH 5/8] Update readme.md --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 116e0b7..22e59c3 100644 --- a/readme.md +++ b/readme.md @@ -14,6 +14,7 @@ - [Quicklink vs Instant page vs Flying Pages](https://wpspeedmatters.com/quicklink-vs-instant-page-vs-flying-pages/) - Join our [Facebook Group](https://www.facebook.com/groups/wpspeedmatters/), a community of WordPress speed enthusiasts +Buy Me A Coffee ## Usage From 25f0baf05438fa2f6c52528d1ae7e29ccc86e57e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natan=20FOURI=C3=89?= <59120131+Natizyskunk@users.noreply.github.com> Date: Fri, 28 May 2021 22:17:52 +0200 Subject: [PATCH 6/8] Update readme.md --- readme.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/readme.md b/readme.md index 22e59c3..ea4d853 100644 --- a/readme.md +++ b/readme.md @@ -6,7 +6,6 @@ > Flying Pages prefetch pages before the user click on links, making them load instantly - ## Quick Links - Demo: Open [https://wpspeedmatters.com](https://wpspeedmatters.com) and click on any post @@ -50,7 +49,6 @@ With options: - `maxRPS`: Maximum requests per second the queue should process. Set to 0 to process all requests immediately (without queue). Default to 3. - `hoverDelay`: Delay in prefetching links on mouse hover (in milliseconds). Default 50. - ## How it Works? Flying Pages injects a tiny JavaScript code (1KB gzipped), waits until the browser becomes idle. Then it detects pages in the viewport and on mouse hover and prefetch them. @@ -67,7 +65,6 @@ Flying Pages is intelligent to make sure prefetching doesn't crash your server o - **Understands user's connection and preferences** - Checks if the user is on a slow connection like 2G or has enabled data-saver. Flying Pages won't prefetch anything in this case. - -# Changelog +## Changelog **2021-04-05**: mod to make the script work without defer and without having to set options before the script call. Replacing window.FPConfig{}; before the script call with flyingPages.listen(); after the script call (see [Usage](#Usage)). From 121a837fe9ef0c5e54528ff4162d9689d2ff867d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natan=20FOURI=C3=89?= <59120131+Natizyskunk@users.noreply.github.com> Date: Fri, 28 May 2021 22:18:30 +0200 Subject: [PATCH 7/8] Update index.html --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index c7b34d8..04f3946 100644 --- a/index.html +++ b/index.html @@ -10,7 +10,7 @@
    -
  • flying-pages.test/hello.html
  • +
  • home
  • ---
  • Google
  • YouTube
  • From 9f926803be05899ce181d17df6df79ef8e73fb47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natan=20FOURI=C3=89?= <59120131+Natizyskunk@users.noreply.github.com> Date: Thu, 28 Sep 2023 01:13:06 +0200 Subject: [PATCH 8/8] Create CONTRIBUTING.md --- CONTRIBUTING.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b97a011 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,51 @@ +# Contributing to vscode-sftp + +After you've created a branch on your fork with your changes, [open a pull request][pr-link]. + +*Please follow the guidelines given below while making a Pull Request to the vscode-sftp* + +## Pull Request Guidelines + +* The Description should not exceed 100 characters. +* Make sure the PR title is in the format of `Add/Remove/Fix ` *for e.g.*: `Add OpenSSH` +* Use a short descriptive commit message. *for e.g.*: ❌`Update Readme.md` ✔ `Add OpenSSH connection Method` +* Search previous Pull Requests or Issues before making a new one, as yours may be a duplicate. +* Please make sure the feature has proper documentation. +* Please make sure you squash all commits together before opening a pull request. If your pull request requires changes upon review, please be sure to squash all additional commits as well. [This wiki page][squash-link] outlines the squash process. +* Target your Pull Request to the `master` branch of the `vscode-sftp` + +Once you've submitted a pull request, the collaborators can review your proposed changes and decide whether or not to incorporate (pull in) your changes. + +### Pull Request Pro Tips + +* [Fork][fork-link] the repository and [clone][clone-link] it locally. +Connect your local repository to the original `upstream` repository by adding it as a [remote][remote-link]. +Pull in changes from `upstream` often so that you stay up to date and so when you submit your pull request, +merge conflicts will be less likely. See more detailed instructions [here][syncing-link]. +* Create a [branch][branch-link] for your edits. +* Contribute in the style of the project as outlined above. This makes it easier for the collaborators to merge +and for others to understand and maintain in the future. + +### Open Pull Requests + +Once you've opened a pull request, a discussion will start around your proposed changes. + +Other contributors and users may chime in, but ultimately the decision is made by the collaborators. + +During the discussion, you may be asked to make some changes to your pull request. + +If so, add more commits to your branch and push them – they will automatically go into the existing pull request. But don't forget to squash them. + +Opening a pull request will trigger a build to check the validity of all links in the project. After the build completes, **please ensure that the build has passed**. If the build did not pass, please view the build logs and correct any errors that were found in your contribution. + +*Thanks for being a part of this project, and we look forward to hearing from you soon!* + +[branch-link]: +[clone-link]: +[fork-link]: +[oauth-link]: +[pr-link]: +[remote-link]: +[syncing-link]: +[squash-link]: +