|
@@ -1,103 +1,193 @@
|
|
|
import { replaceUrl, pushUrl, fromSessionStorage } from './history'
|
|
|
import { swapPage } from './page'
|
|
|
+import { endProgressBar, startProgressBar } from './progressBar'
|
|
|
|
|
|
export default function (Alpine) {
|
|
|
- // Create a history state entry for the initial page load.
|
|
|
- // (This is so later hitting back can restore this page).
|
|
|
- let url = new URL(window.location.href, document.baseURI)
|
|
|
- replaceUrl(url, document.documentElement.outerHTML)
|
|
|
+ setInitialPageUsingHistoryReplaceStateForFutureBackButtons()
|
|
|
|
|
|
// Listen for back button presses...
|
|
|
- window.addEventListener('popstate', event => {
|
|
|
- let { html } = fromSessionStorage(event)
|
|
|
+ window.addEventListener('popstate', e => handleBackButtonPress(e, Alpine))
|
|
|
|
|
|
- html && swapPage(Alpine, html, () => {
|
|
|
- document.dispatchEvent(new CustomEvent('alpine:navigated', { bubbles: true }))
|
|
|
+ // Listen for any <a> tag click...
|
|
|
+ Array.from(document.links).forEach(el => {
|
|
|
+ el.addEventListener('mouseenter', () => handleLinkHover(el))
|
|
|
+ el.addEventListener('click', e => handleLinkClick(el, e))
|
|
|
+ })
|
|
|
+
|
|
|
+ document.addEventListener('alpine:navigated', () => {
|
|
|
+ Array.from(document.links).forEach(el => {
|
|
|
+ el.addEventListener('mouseenter', () => handleLinkHover(el))
|
|
|
+ el.addEventListener('click', e => handleLinkClick(el, e))
|
|
|
})
|
|
|
})
|
|
|
|
|
|
- // Listen for any <a> tag click...
|
|
|
- document.addEventListener('click', e => {
|
|
|
- if (e.target.tagName.toLowerCase() !== 'a') return
|
|
|
+ // Alpine.magic('history', (el, { interceptor }) => {
|
|
|
+ // let alias
|
|
|
|
|
|
- let url = new URL(window.location.href, document.baseURI)
|
|
|
- replaceUrl(url, document.documentElement.outerHTML)
|
|
|
+ // return interceptor((initialValue, getter, setter, path, key) => {
|
|
|
+ // let pause = false
|
|
|
+ // let queryKey = alias || path
|
|
|
|
|
|
- let destination = new URL(e.target.getAttribute('href'), document.baseURI)
|
|
|
+ // let value = initialValue
|
|
|
+ // let url = new URL(window.location.href)
|
|
|
|
|
|
- document.dispatchEvent(new CustomEvent('alpine:navigating', { bubbles: true }))
|
|
|
+ // if (url.searchParams.has(queryKey)) {
|
|
|
+ // value = url.searchParams.get(queryKey)
|
|
|
+ // }
|
|
|
|
|
|
- fetch(destination.pathname).then(i => i.text()).then(html => {
|
|
|
- swapPage(Alpine, html, () => {
|
|
|
- pushUrl(destination, html)
|
|
|
+ // setter(value)
|
|
|
|
|
|
- document.dispatchEvent(new CustomEvent('alpine:navigated', { bubbles: true }))
|
|
|
- })
|
|
|
- })
|
|
|
+ // let object = { value }
|
|
|
|
|
|
- e.preventDefault()
|
|
|
- })
|
|
|
+ // url.searchParams.set(queryKey, value)
|
|
|
|
|
|
- Alpine.magic('history', (el, { interceptor }) => {
|
|
|
- let alias
|
|
|
+ // replace(url.toString(), path, object)
|
|
|
|
|
|
- return interceptor((initialValue, getter, setter, path, key) => {
|
|
|
- let pause = false
|
|
|
- let queryKey = alias || path
|
|
|
+ // window.addEventListener('popstate', (e) => {
|
|
|
+ // if (! e.state) return
|
|
|
+ // if (! e.state.alpine) return
|
|
|
|
|
|
- let value = initialValue
|
|
|
- let url = new URL(window.location.href)
|
|
|
+ // Object.entries(e.state.alpine).forEach(([newKey, { value }]) => {
|
|
|
+ // if (newKey !== key) return
|
|
|
|
|
|
- if (url.searchParams.has(queryKey)) {
|
|
|
- value = url.searchParams.get(queryKey)
|
|
|
- }
|
|
|
+ // pause = true
|
|
|
|
|
|
- setter(value)
|
|
|
+ // Alpine.disableEffectScheduling(() => {
|
|
|
+ // setter(value)
|
|
|
+ // })
|
|
|
|
|
|
- let object = { value }
|
|
|
+ // pause = false
|
|
|
+ // })
|
|
|
+ // })
|
|
|
|
|
|
- url.searchParams.set(queryKey, value)
|
|
|
+ // Alpine.effect(() => {
|
|
|
+ // let value = getter()
|
|
|
|
|
|
- replace(url.toString(), path, object)
|
|
|
+ // if (pause) return
|
|
|
|
|
|
- window.addEventListener('popstate', (e) => {
|
|
|
- if (! e.state) return
|
|
|
- if (! e.state.alpine) return
|
|
|
+ // let object = { value }
|
|
|
|
|
|
- Object.entries(e.state.alpine).forEach(([newKey, { value }]) => {
|
|
|
- if (newKey !== key) return
|
|
|
+ // let url = new URL(window.location.href)
|
|
|
|
|
|
- pause = true
|
|
|
+ // url.searchParams.set(queryKey, value)
|
|
|
|
|
|
- Alpine.disableEffectScheduling(() => {
|
|
|
- setter(value)
|
|
|
- })
|
|
|
+ // push(url.toString(), path, object)
|
|
|
+ // })
|
|
|
|
|
|
- pause = false
|
|
|
- })
|
|
|
- })
|
|
|
+ // return value
|
|
|
+ // }, func => {
|
|
|
+ // func.as = key => { alias = key; return func }
|
|
|
+ // })
|
|
|
+ // })
|
|
|
+}
|
|
|
|
|
|
- Alpine.effect(() => {
|
|
|
- let value = getter()
|
|
|
+function setInitialPageUsingHistoryReplaceStateForFutureBackButtons() {
|
|
|
+ // Create a history state entry for the initial page load.
|
|
|
+ // (This is so later hitting back can restore this page).
|
|
|
+ let url = new URL(window.location.href, document.baseURI)
|
|
|
|
|
|
- if (pause) return
|
|
|
+ replaceUrl(url, document.documentElement.outerHTML)
|
|
|
+}
|
|
|
|
|
|
- let object = { value }
|
|
|
+function handleBackButtonPress(e, Alpine) {
|
|
|
+ let { html } = fromSessionStorage(e)
|
|
|
|
|
|
- let url = new URL(window.location.href)
|
|
|
+ document.dispatchEvent(new CustomEvent('alpine:navigating', { bubbles: true }))
|
|
|
|
|
|
- url.searchParams.set(queryKey, value)
|
|
|
+ html && swapPage(Alpine, html, () => {
|
|
|
+ document.dispatchEvent(new CustomEvent('alpine:navigated', { bubbles: true }))
|
|
|
+ })
|
|
|
|
|
|
- push(url.toString(), path, object)
|
|
|
- })
|
|
|
+ restoreScroll()
|
|
|
+}
|
|
|
+
|
|
|
+// Warning: this could cause some memory leaks
|
|
|
+let prefetches = new Map
|
|
|
+
|
|
|
+function handleLinkHover(el) {
|
|
|
+ if (prefetches.has(el)) return
|
|
|
+
|
|
|
+ let destination = new URL(el.getAttribute('href'), document.baseURI)
|
|
|
|
|
|
- return value
|
|
|
- }, func => {
|
|
|
- func.as = key => { alias = key; return func }
|
|
|
+ prefetches.set(el, { finished: false, html: null, whenFinished: () => {} })
|
|
|
+
|
|
|
+ fetch(destination.pathname).then(i => i.text()).then(html => {
|
|
|
+ let state = prefetches.get(el)
|
|
|
+ state.html = html
|
|
|
+ state.finished = true
|
|
|
+ state.whenFinished()
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+function handleLinkClick(el, e) {
|
|
|
+ let destination = new URL(el.getAttribute('href'), document.baseURI)
|
|
|
+
|
|
|
+ let handleHtml = html => {
|
|
|
+ let url = new URL(window.location.href, document.baseURI)
|
|
|
+
|
|
|
+ storeScrollRestorationDataInHTML()
|
|
|
+
|
|
|
+ replaceUrl(url, document.documentElement.outerHTML)
|
|
|
+
|
|
|
+ swapPage(Alpine, html, () => {
|
|
|
+ pushUrl(destination, html)
|
|
|
+
|
|
|
+ document.dispatchEvent(new CustomEvent('alpine:navigated', { bubbles: true }))
|
|
|
})
|
|
|
+ }
|
|
|
+
|
|
|
+ document.dispatchEvent(new CustomEvent('alpine:navigating', { bubbles: true }))
|
|
|
+
|
|
|
+ if (prefetches.has(el)) {
|
|
|
+ let state = prefetches.get(el)
|
|
|
+ if (! state.finished) {
|
|
|
+ startProgressBar()
|
|
|
+
|
|
|
+ state.whenFinished = () => {
|
|
|
+ endProgressBar(() => {
|
|
|
+ handleHtml(state.html)
|
|
|
+ prefetches.delete(el)
|
|
|
+ })
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ handleHtml(state.html)
|
|
|
+ prefetches.delete(el)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ startProgressBar()
|
|
|
+
|
|
|
+ fetch(destination.pathname).then(i => i.text()).then(html => {
|
|
|
+ endProgressBar(() => {
|
|
|
+ handleHtml(html)
|
|
|
+ })
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ e.preventDefault()
|
|
|
+}
|
|
|
+
|
|
|
+function storeScrollRestorationDataInHTML() {
|
|
|
+ document.body.setAttribute('data-scroll-x', document.body.scrollLeft)
|
|
|
+ document.body.setAttribute('data-scroll-y', document.body.scrollTop)
|
|
|
+
|
|
|
+ document.querySelectorAll('[x-navigate\\:scroll]').forEach(el => {
|
|
|
+ el.setAttribute('data-scroll-x', el.scrollLeft)
|
|
|
+ el.setAttribute('data-scroll-y', el.scrollTop)
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+function restoreScroll() {
|
|
|
+ let scroll = el => {
|
|
|
+ el.scrollTo(Number(el.getAttribute('data-scroll-x')), Number(el.getAttribute('data-scroll-y')))
|
|
|
+ el.removeAttribute('data-scroll-x')
|
|
|
+ el.removeAttribute('data-scroll-y')
|
|
|
+ }
|
|
|
+
|
|
|
+ scroll(document.body)
|
|
|
+
|
|
|
+ document.querySelectorAll('[x-navigate\\:scroll]').forEach(scroll)
|
|
|
+}
|
|
|
+
|
|
|
function replace(url, key, object) {
|
|
|
let state = window.history.state || {}
|
|
|
|