diff --git a/.changeset/strong-icons-hang.md b/.changeset/strong-icons-hang.md new file mode 100644 index 0000000..769d633 --- /dev/null +++ b/.changeset/strong-icons-hang.md @@ -0,0 +1,5 @@ +--- +'@coldwired/actions': minor +--- + +implement refresh action diff --git a/packages/actions/src/actions.ts b/packages/actions/src/actions.ts index 834ae34..a6f43a2 100644 --- a/packages/actions/src/actions.ts +++ b/packages/actions/src/actions.ts @@ -9,6 +9,7 @@ import { isFormElement, isFormInputElement, nextAnimationFrame, + parseHTMLDocument, parseHTMLFragment, partition, wait, @@ -21,7 +22,16 @@ import { morph } from './morph'; import type { Plugin } from './plugin'; import { type Schema, defaultSchema } from './schema'; -const voidActionNames = ['remove', 'focus', 'enable', 'disable', 'hide', 'show', 'reset'] as const; +const voidActionNames = [ + 'remove', + 'focus', + 'enable', + 'disable', + 'hide', + 'show', + 'reset', + 'refresh', +] as const; const fragmentActionNames = ['after', 'before', 'append', 'prepend', 'replace', 'update'] as const; const actionNames = [...voidActionNames, ...fragmentActionNames]; @@ -71,6 +81,7 @@ export class Actions { #metadata = new Metadata(); #controller = new AbortController(); #plugins: Plugin[] = []; + #refreshRequestController?: AbortController; #pending = new Set>(); #pinned = new Map(); @@ -211,6 +222,10 @@ export class Actions { this.applyActions([{ action: 'reset', ...params }]); } + refresh(params: Omit | Omit) { + this.applyActions([{ action: 'refresh', ...params }]); + } + morph( from: Element | Document, to: string | Element | Document | DocumentFragment, @@ -457,6 +472,50 @@ export class Actions { } } + private _refresh() { + const currentPath = () => `${window.location.pathname}${window.location.search}`; + + this.#refreshRequestController?.abort(); + const controller = new AbortController(); + this.#refreshRequestController = controller; + + const path = currentPath(); + fetch(path, { + method: 'get', + signal: controller.signal, + headers: { accept: 'text/html,application/xhtml+xml' }, + credentials: 'same-origin', + redirect: 'follow', + }) + .then( + (response) => { + // only apply the result if we are still on the same page + if (currentPath() == path) { + if (response.redirected) { + window.location.href = response.url; + } else { + response.text().then((html) => { + const newDocument = parseHTMLDocument(html); + if (response.ok) { + this._morph(document.body, newDocument.body); + } else { + document.body.innerHTML = newDocument.body.innerHTML; + } + }); + } + } + }, + (error) => { + if (error.name != 'AbortError') { + console.error(`Failed to refresh the page: ${path}`, error); + } + }, + ) + .finally(() => { + this.#refreshRequestController = undefined; + }); + } + private handleEvent(event: Event) { const target = (event.composedPath && event.composedPath()[0]) || event.target;