From 9b6e3b61cf7264de02f644eabb71770393c2a9cd Mon Sep 17 00:00:00 2001 From: Benedikt Straub Date: Tue, 10 Jun 2025 20:12:21 +0200 Subject: [PATCH] Make relative-time a self-maintaining custom element (#8134) Fixes #8124 Replaces #8130 Use a custom element for relative-time. Thanks to @Beowulf for suggesting this approach. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8134 Reviewed-by: Gusted Reviewed-by: Beowulf Co-authored-by: Benedikt Straub Co-committed-by: Benedikt Straub --- web_src/js/webcomponents/relative-time.js | 51 +++++++++++++------ .../js/webcomponents/relative-time.test.js | 1 + 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/web_src/js/webcomponents/relative-time.js b/web_src/js/webcomponents/relative-time.js index 5a126fd625..d247ced3ca 100644 --- a/web_src/js/webcomponents/relative-time.js +++ b/web_src/js/webcomponents/relative-time.js @@ -130,21 +130,42 @@ export function DoUpdateRelativeTime(object, now) { return HALF_MINUTE; } -/** Update the displayed text of one relative-time DOM element with its human-readable, localized relative time string. */ -function UpdateRelativeTime(object) { - const next = DoUpdateRelativeTime(object); - if (next !== null) setTimeout(() => { UpdateRelativeTime(object) }, next); -} +window.customElements.define('relative-time', class extends HTMLElement { + static observedAttributes = ['datetime']; -/** Update the displayed text of all relative-time DOM elements with their respective human-readable, localized relative time string. */ -function UpdateAllRelativeTimes() { - for (const object of document.querySelectorAll('relative-time')) UpdateRelativeTime(object); -} + alive = false; + contentSpan = null; -document.addEventListener('DOMContentLoaded', () => { - UpdateAllRelativeTimes(); - // Also update relative-time DOM elements after htmx swap events. - document.body.addEventListener('htmx:afterSwap', () => { - for (const object of document.querySelectorAll('relative-time')) DoUpdateRelativeTime(object); - }); + update = (recurring) => { + if (!this.alive) return; + + if (!this.shadowRoot) { + this.attachShadow({mode: 'open'}); + this.contentSpan = document.createElement('span'); + this.shadowRoot.append(this.contentSpan); + } + + const next = DoUpdateRelativeTime(this); + if (recurring && next !== null) setTimeout(() => { this.update(true) }, next); + }; + + connectedCallback() { + this.alive = true; + this.update(true); + } + + disconnectedCallback() { + this.alive = false; + } + + attributeChangedCallback(name, oldValue, newValue) { + if (name === 'datetime' && oldValue !== newValue) this.update(false); + } + + set textContent(value) { + if (this.contentSpan) this.contentSpan.textContent = value; + } + get textContent() { + return this.contentSpan?.textContent; + } }); diff --git a/web_src/js/webcomponents/relative-time.test.js b/web_src/js/webcomponents/relative-time.test.js index 8ffefd4139..5a8e2950e0 100644 --- a/web_src/js/webcomponents/relative-time.test.js +++ b/web_src/js/webcomponents/relative-time.test.js @@ -23,6 +23,7 @@ test('CalculateRelativeTimes', () => { 'relativetime.years': ['%d year ago', '%d years ago'], }; const mock = document.createElement('relative-time'); + document.body.append(mock); const now = Date.parse('2024-10-27T04:05:30+01:00'); // One hour after DST switchover, CET.