From 787df43ac025889372ad9411291bef3076107682 Mon Sep 17 00:00:00 2001 From: Alex Turchyn Date: Sun, 2 Jul 2023 17:56:14 +0300 Subject: [PATCH] add instantclick --- app/javascript/application.js | 9 ++- app/javascript/lib/turbo_instant_click.js | 98 +++++++++++++++++++++++ package.json | 1 + yarn.lock | 5 +- 4 files changed, 109 insertions(+), 4 deletions(-) create mode 100644 app/javascript/lib/turbo_instant_click.js diff --git a/app/javascript/application.js b/app/javascript/application.js index eb9d9cf0..e6306d80 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -1,4 +1,5 @@ -import '@hotwired/turbo-rails' +import '@hotwired/turbo' +import { encodeMethodIntoRequestBody } from '@hotwired/turbo-rails/app/javascript/turbo/fetch_requests' import { createApp, reactive } from 'vue' import TemplateBuilder from './template_builder/builder' @@ -15,6 +16,10 @@ import SetOriginUrl from './elements/set_origin_url' import SetTimezone from './elements/set_timezone' import AutoresizeTextarea from './elements/autoresize_textarea' +import * as TurboInstantClick from './lib/turbo_instant_click' + +TurboInstantClick.start() + document.addEventListener('turbo:before-cache', () => { window.flash?.remove() }) @@ -37,6 +42,8 @@ window.customElements.define('set-origin-url', SetOriginUrl) window.customElements.define('set-timezone', SetTimezone) window.customElements.define('autoresize-textarea', AutoresizeTextarea) +document.addEventListener('turbo:before-fetch-request', encodeMethodIntoRequestBody) + window.customElements.define('template-builder', class extends HTMLElement { connectedCallback () { this.appElem = document.createElement('div') diff --git a/app/javascript/lib/turbo_instant_click.js b/app/javascript/lib/turbo_instant_click.js new file mode 100644 index 00000000..01789e95 --- /dev/null +++ b/app/javascript/lib/turbo_instant_click.js @@ -0,0 +1,98 @@ +const requestCache = new Map() +const cacheTtl = 10 * 1000 + +function isPreloadable (linkElement) { + const href = linkElement.getAttribute('href') + + if (!href || href === '#' || linkElement.dataset.turbo === 'false' || linkElement.dataset.prefetch === 'false') { + return + } + + if (linkElement.origin !== document.location.origin) { + return + } + + if (!['http:', 'https:'].includes(linkElement.protocol)) { + return + } + + if (linkElement.pathname + linkElement.search === document.location.pathname + document.location.search) { + return + } + + if (linkElement.dataset.turboMethod && linkElement.dataset.turboMethod !== 'get') { + return + } + + return true +} + +function mouseoverListener (event) { + let linkElement + + if (event.target.tagName === 'A') { + linkElement = event.target + } else { + linkElement = event.target.closest('a') + } + + if (!linkElement) { + return + } + + if (!isPreloadable(linkElement)) { + return + } + + const url = linkElement.getAttribute('href') + const loc = new URL(url, location.protocol + '//' + location.host) + const absoluteUrl = loc.toString() + + const cached = requestCache.get(absoluteUrl) + + if (cached && cached.ttl > new Date()) { + return + } + + const requestOptions = { + credentials: 'same-origin', + headers: { Accept: 'text/html, application/xhtml+xml', 'VND.PREFETCH': 'true' }, + method: 'GET', + redirect: 'follow' + } + + if (linkElement.dataset.turboFrame && linkElement.dataset.turboFrame !== '_top') { + requestOptions.headers['Turbo-Frame'] = linkElement.dataset.turboFrame + } else if (linkElement.dataset.turboFrame !== '_top') { + const turboFrame = linkElement.closest('turbo-frame') + + if (turboFrame) { + requestOptions.headers['Turbo-Frame'] = turboFrame.id + } + } + + requestCache.set(absoluteUrl, { request: fetch(absoluteUrl, requestOptions), ttl: new Date(new Date().getTime() + cacheTtl) }) +} + +function turboBeforeFetchRequest (event) { + if (event.target.tagName !== 'FORM' && event.detail.fetchOptions.method === 'GET') { + const cached = requestCache.get(event.detail.url.toString()) + + if (cached && cached.ttl > new Date()) { + event.detail.response = cached.request + } + } + + requestCache.clear() +} + +function start () { + if (!window.turboInstantClickEnabled) { + document.addEventListener('turbo:before-fetch-request', turboBeforeFetchRequest) + document.addEventListener('mouseover', mouseoverListener, { capture: true, passive: true }) + } + + window.turboInstantClickEnabled = true +} + +export { start } diff --git a/package.json b/package.json index ec3d124c..6863f283 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "@babel/preset-env": "7.21.5", "@babel/runtime": "7.21.5", "@github/catalyst": "^2.0.0-beta", + "@hotwired/turbo": "https://github.com/docusealco/turbo#main", "@hotwired/turbo-rails": "^7.3.0", "@rails/activestorage": "^7.0.0", "@tabler/icons-vue": "^2.20.0", diff --git a/yarn.lock b/yarn.lock index 093d67fa..e7d5a1c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -988,10 +988,9 @@ "@hotwired/turbo" "^7.3.0" "@rails/actioncable" "^7.0" -"@hotwired/turbo@^7.3.0": +"@hotwired/turbo@^7.3.0", "@hotwired/turbo@https://github.com/docusealco/turbo#main": version "7.3.0" - resolved "https://registry.yarnpkg.com/@hotwired/turbo/-/turbo-7.3.0.tgz#2226000fff1aabda9fd9587474565c9929dbf15d" - integrity sha512-Dcu+NaSvHLT7EjrDrkEmH4qET2ZJZ5IcCWmNXxNQTBwlnE5tBZfN6WxZ842n5cHV52DH/AKNirbPBtcEXDLW4g== + resolved "https://github.com/docusealco/turbo#64d286a14b0d11383719a9a09852a775863d621a" "@humanwhocodes/config-array@^0.11.8": version "0.11.8"