From 2825ad04e95f7ca7abed55fb73f1abaaaff2a361 Mon Sep 17 00:00:00 2001 From: Feoktist Shovchko Date: Tue, 5 Sep 2023 21:56:46 +0300 Subject: [PATCH 1/8] feat(gh-pages): service worker --- pages/.eleventy.js | 3 +- pages/serviceWorker/register.ts | 7 ++++ pages/serviceWorker/serviceWorker.js | 58 ++++++++++++++++++++++++++++ pages/src/localdev.ts | 4 ++ 4 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 pages/serviceWorker/register.ts create mode 100644 pages/serviceWorker/serviceWorker.js diff --git a/pages/.eleventy.js b/pages/.eleventy.js index 094b3bd44..1e11e055c 100644 --- a/pages/.eleventy.js +++ b/pages/.eleventy.js @@ -23,7 +23,8 @@ module.exports = (config) => { config.addPassthroughCopy({ '../docs/images': 'assets/docs-images', 'static/assets': 'assets', - 'static/tools': '.' + 'static/tools': '.', + 'serviceWorker/serviceWorker.js': '.' }); config.setServerOptions({ diff --git a/pages/serviceWorker/register.ts b/pages/serviceWorker/register.ts new file mode 100644 index 000000000..93531c620 --- /dev/null +++ b/pages/serviceWorker/register.ts @@ -0,0 +1,7 @@ +export const registerServiceWorker = async () => { + const {origin} = location; + if (/localhost/.test(origin) || !navigator.serviceWorker) return; + + navigator.serviceWorker.register(origin + '/serviceWorker.js', {scope: '/'}) + .catch((err) => console.log(err)); +}; diff --git a/pages/serviceWorker/serviceWorker.js b/pages/serviceWorker/serviceWorker.js new file mode 100644 index 000000000..5df4a837e --- /dev/null +++ b/pages/serviceWorker/serviceWorker.js @@ -0,0 +1,58 @@ +const CACHE = 'esl-sw-cache'; + +const addResourcesToCache = async (resources) => { + const cache = await caches.open(CACHE); + await cache.addAll(resources); +}; + +const putInCache = async (request, response) => { + const cache = await caches.open(CACHE); + await cache.put(request, response); +}; + +const cacheFirst = async ({ request, preloadResponsePromise }) => { + // Try to get the resource from the cache + const responseFromCache = await caches.match(request); + if (responseFromCache) return responseFromCache; + + // Try to use the preloaded response, if it's there + const preloadResponse = await preloadResponsePromise; + if (preloadResponse) { + console.info('using preload response', preloadResponse); + putInCache(request, preloadResponse.clone()); + return preloadResponse; + } + + // Try to get the resource from the network + try { + const responseFromNetwork = await fetch(request.clone()); + putInCache(request, responseFromNetwork.clone()); + return responseFromNetwork; + } catch (error) { + return new Response('Network error happened', { + status: 408, + headers: { 'Content-Type': 'text/plain' }, + }); + } +}; + +const enableNavigationPreload = async () => { + if (self.registration.navigationPreload) await self.registration.navigationPreload.enable(); +}; + +self.addEventListener('install', (event) => { + event.waitUntil(addResourcesToCache(['/'])); +}); + +self.addEventListener('activate', (event) => { + event.waitUntil(enableNavigationPreload()); +}); + +self.addEventListener('fetch', (event) => { + event.respondWith( + cacheFirst({ + request: event.request, + preloadResponsePromise: event.preloadResponse + }) + ); +}); diff --git a/pages/src/localdev.ts b/pages/src/localdev.ts index 512a5464c..7c6b3ea8e 100644 --- a/pages/src/localdev.ts +++ b/pages/src/localdev.ts @@ -53,6 +53,8 @@ import '../../src/modules/esl-share/actions/print-action'; import './esl-media-demo/test-media'; import './esl-media-demo/test-media-source'; +import {registerServiceWorker} from '../serviceWorker/register'; + import {ESLDemoBackLink} from './back-link/back-link'; import {ESLDemoMarquee} from './landing/landing'; import {ESLDemoSearchBox} from './navigation/header/header-search'; @@ -61,6 +63,8 @@ import {ESLDemoSidebar} from './navigation/navigation'; import {ESLDemoAnchorLink} from './anchor/anchor-link'; import {ESLDemoBanner} from './banner/banner'; +registerServiceWorker(); + ESLVSizeCSSProxy.observe(); // Register Demo components From c7aa4bf2426b51c7716837452981ff587052b6f1 Mon Sep 17 00:00:00 2001 From: Feoktist Shovchko Date: Tue, 12 Sep 2023 14:54:29 +0300 Subject: [PATCH 2/8] chore(gh-pages): add replace links --- pages/.eleventy.js | 3 +- pages/serviceWorker/register.ts | 7 --- pages/serviceWorker/serviceWorker.js | 58 ------------------- pages/src/link/link.ts | 39 +++++++++++++ pages/src/localdev.ts | 6 +- pages/src/navigation/sidebar/sidebar.ts | 31 +++++++++- .../_includes/navigation/sidebar-item.njk | 4 +- pages/views/_layouts/content.njk | 2 +- pages/views/_layouts/forward.njk | 2 +- 9 files changed, 74 insertions(+), 78 deletions(-) delete mode 100644 pages/serviceWorker/register.ts delete mode 100644 pages/serviceWorker/serviceWorker.js create mode 100644 pages/src/link/link.ts diff --git a/pages/.eleventy.js b/pages/.eleventy.js index 1e11e055c..094b3bd44 100644 --- a/pages/.eleventy.js +++ b/pages/.eleventy.js @@ -23,8 +23,7 @@ module.exports = (config) => { config.addPassthroughCopy({ '../docs/images': 'assets/docs-images', 'static/assets': 'assets', - 'static/tools': '.', - 'serviceWorker/serviceWorker.js': '.' + 'static/tools': '.' }); config.setServerOptions({ diff --git a/pages/serviceWorker/register.ts b/pages/serviceWorker/register.ts deleted file mode 100644 index 93531c620..000000000 --- a/pages/serviceWorker/register.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const registerServiceWorker = async () => { - const {origin} = location; - if (/localhost/.test(origin) || !navigator.serviceWorker) return; - - navigator.serviceWorker.register(origin + '/serviceWorker.js', {scope: '/'}) - .catch((err) => console.log(err)); -}; diff --git a/pages/serviceWorker/serviceWorker.js b/pages/serviceWorker/serviceWorker.js deleted file mode 100644 index 5df4a837e..000000000 --- a/pages/serviceWorker/serviceWorker.js +++ /dev/null @@ -1,58 +0,0 @@ -const CACHE = 'esl-sw-cache'; - -const addResourcesToCache = async (resources) => { - const cache = await caches.open(CACHE); - await cache.addAll(resources); -}; - -const putInCache = async (request, response) => { - const cache = await caches.open(CACHE); - await cache.put(request, response); -}; - -const cacheFirst = async ({ request, preloadResponsePromise }) => { - // Try to get the resource from the cache - const responseFromCache = await caches.match(request); - if (responseFromCache) return responseFromCache; - - // Try to use the preloaded response, if it's there - const preloadResponse = await preloadResponsePromise; - if (preloadResponse) { - console.info('using preload response', preloadResponse); - putInCache(request, preloadResponse.clone()); - return preloadResponse; - } - - // Try to get the resource from the network - try { - const responseFromNetwork = await fetch(request.clone()); - putInCache(request, responseFromNetwork.clone()); - return responseFromNetwork; - } catch (error) { - return new Response('Network error happened', { - status: 408, - headers: { 'Content-Type': 'text/plain' }, - }); - } -}; - -const enableNavigationPreload = async () => { - if (self.registration.navigationPreload) await self.registration.navigationPreload.enable(); -}; - -self.addEventListener('install', (event) => { - event.waitUntil(addResourcesToCache(['/'])); -}); - -self.addEventListener('activate', (event) => { - event.waitUntil(enableNavigationPreload()); -}); - -self.addEventListener('fetch', (event) => { - event.respondWith( - cacheFirst({ - request: event.request, - preloadResponsePromise: event.preloadResponse - }) - ); -}); diff --git a/pages/src/link/link.ts b/pages/src/link/link.ts new file mode 100644 index 000000000..e67b8ea21 --- /dev/null +++ b/pages/src/link/link.ts @@ -0,0 +1,39 @@ +import {memoize, listen, prop} from '../../../src/modules/esl-utils/decorators'; +import {ESLMixinElement} from '../../../src/modules/esl-mixin-element/core'; +import {ESLDemoSidebar} from '../navigation/navigation'; + +export class ESLDemoReplaceLink extends ESLMixinElement { + static override is = 'esl-d-replace-link'; + + @prop() public contentSelector = '#content-replaceable'; + + @memoize() + protected get sidebar(): ESLDemoSidebar { + return document.body.querySelector(ESLDemoSidebar.is)!; + } + + @listen('click') + protected _onClick(e: Event): void { + e.preventDefault(); + const pathname = this.$host.getAttribute('href'); + if (!pathname) return; + this.$host.setAttribute(this.sidebar.activeCls, ''); + fetch(pathname, {method: 'GET'}) + .then((response) => response.text()) + .then((text) => { + const $content = new DOMParser().parseFromString(text, 'text/html').querySelector(this.contentSelector); + const $currentContent = document.body.querySelector(this.contentSelector); + + if (!$content || !$currentContent) { + history.pushState(null, '', pathname); + location.reload(); + return; + } + + $currentContent.replaceWith($content); + history.pushState(null, '', pathname); + this.sidebar.setActive(this.$host); + }) + .catch(() => console.error(`[ESL] Failed to fetch resource: ${pathname}`)); + } +} diff --git a/pages/src/localdev.ts b/pages/src/localdev.ts index 7c6b3ea8e..eaf5f26da 100644 --- a/pages/src/localdev.ts +++ b/pages/src/localdev.ts @@ -53,8 +53,6 @@ import '../../src/modules/esl-share/actions/print-action'; import './esl-media-demo/test-media'; import './esl-media-demo/test-media-source'; -import {registerServiceWorker} from '../serviceWorker/register'; - import {ESLDemoBackLink} from './back-link/back-link'; import {ESLDemoMarquee} from './landing/landing'; import {ESLDemoSearchBox} from './navigation/header/header-search'; @@ -62,8 +60,7 @@ import {ESLDemoSearchPageWrapper} from './search/search'; import {ESLDemoSidebar} from './navigation/navigation'; import {ESLDemoAnchorLink} from './anchor/anchor-link'; import {ESLDemoBanner} from './banner/banner'; - -registerServiceWorker(); +import {ESLDemoReplaceLink} from './link/link'; ESLVSizeCSSProxy.observe(); @@ -75,6 +72,7 @@ ESLDemoSearchPageWrapper.register(); ESLDemoAnchorLink.register(); ESLDemoBackLink.register(); ESLDemoBanner.register(); +ESLDemoReplaceLink.register(); // Register ESL Components ESLImage.register(); diff --git a/pages/src/navigation/sidebar/sidebar.ts b/pages/src/navigation/sidebar/sidebar.ts index efe3f8f07..54fd9d30b 100644 --- a/pages/src/navigation/sidebar/sidebar.ts +++ b/pages/src/navigation/sidebar/sidebar.ts @@ -1,6 +1,7 @@ import {boolAttr, listen, prop, ready} from '../../../../src/modules/esl-utils/decorators'; import {ESLToggleable} from '../../../../src/modules/esl-toggleable/core/esl-toggleable'; import {ESLMediaQuery} from '../../../../src/modules/esl-media-query/core/esl-media-query'; +import {ESLTraversingQuery} from '../../../../src/modules/esl-traversing-query/core/esl-traversing-query'; import type {ESLToggleableActionParams} from '../../../../src/modules/esl-toggleable/core/esl-toggleable'; @@ -17,6 +18,7 @@ export class ESLDemoSidebar extends ESLToggleable { @prop() public submenus: string = '.sidebar-nav-secondary'; @prop() public activeMenuAttr: string = 'data-open'; + @prop() public activeCls = 'active'; @boolAttr({name: 'animation'}) protected _animation: boolean; @@ -24,6 +26,10 @@ export class ESLDemoSidebar extends ESLToggleable { return Array.from(this.querySelectorAll(this.submenus)); } + public get active(): ESLToggleable | null { + return this.$submenus.find((menu) => menu.hasAttribute(this.activeMenuAttr)) || null; + } + @ready protected override connectedCallback(): void { super.connectedCallback(); @@ -39,14 +45,33 @@ export class ESLDemoSidebar extends ESLToggleable { this.toggle(isDesktop && isStoredOpen, {force: true, initiator: 'init', immediate: true}); } + public setActive(link: Element): void { + this.removeActive(); + const $newActive = this.$submenus.filter((menu) => menu.contains(link) || menu.previousElementSibling?.contains(link))[0]; + if (!$newActive) return; + + link.parentElement!.classList.add(this.activeCls); + $newActive.previousElementSibling?.classList.add(this.activeCls); + $newActive.classList.add(this.activeCls); + $newActive.setAttribute(this.activeMenuAttr, ''); + this.expandActive(); + } + + public removeActive(): void { + if (!this.active) return; + + ESLTraversingQuery.all(`::find(.sidebar-nav-secondary-item.${this.activeCls}), ::prev`, this.active, this) + .forEach((element) => element.classList.remove(this.activeCls)); + this.active.classList.remove(this.activeCls); + this.active.removeAttribute(this.activeMenuAttr); + } + public collapseAll(): void { this.$submenus.forEach((menu) => menu.hide({activator: this})); } public expandActive(noAnimate: boolean = false): void { - this.$submenus - .filter((menu) => menu.hasAttribute('data-open')) - .forEach((menu) => menu.show({noAnimate, activator: this})); + this.active?.show({noAnimate, activator: this}); } protected override updateA11y(): void { diff --git a/pages/views/_includes/navigation/sidebar-item.njk b/pages/views/_includes/navigation/sidebar-item.njk index 55cc4653d..c5eaa37e6 100644 --- a/pages/views/_includes/navigation/sidebar-item.njk +++ b/pages/views/_includes/navigation/sidebar-item.njk @@ -5,7 +5,7 @@ {% if items.length %}