Compare commits

...

2 commits

Author SHA1 Message Date
d1fd1da54b
Fix a couple JavaScript errors 2024-01-06 01:30:53 +01:00
31a2719ca2
Update front-end dependencies 2024-01-06 01:30:38 +01:00
4 changed files with 133 additions and 100 deletions

File diff suppressed because one or more lines are too long

View file

@ -5,7 +5,7 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
*/ */
(function(){ (function() {
/** @type {import("../htmx").HtmxInternalApi} */ /** @type {import("../htmx").HtmxInternalApi} */
var api; var api;
@ -39,17 +39,19 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
switch (name) { switch (name) {
// Try to remove remove an EventSource when elements are removed case "htmx:beforeCleanupElement":
case "htmx:beforeCleanupElement": var internalData = api.getInternalData(evt.target)
var internalData = api.getInternalData(evt.target) // Try to remove remove an EventSource when elements are removed
if (internalData.sseEventSource) { if (internalData.sseEventSource) {
internalData.sseEventSource.close(); internalData.sseEventSource.close();
} }
return;
// Try to create EventSources when elements are processed return;
case "htmx:afterProcessNode":
createEventSourceOnElement(evt.target); // Try to create EventSources when elements are processed
case "htmx:afterProcessNode":
ensureEventSourceOnElement(evt.target);
registerSSE(evt.target);
} }
} }
}); });
@ -66,8 +68,8 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
* @param {string} url * @param {string} url
* @returns EventSource * @returns EventSource
*/ */
function createEventSource(url) { function createEventSource(url) {
return new EventSource(url, {withCredentials:true}); return new EventSource(url, { withCredentials: true });
} }
function splitOnWhitespace(trigger) { function splitOnWhitespace(trigger) {
@ -90,7 +92,7 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
function getLegacySSESwaps(elt) { function getLegacySSESwaps(elt) {
var legacySSEValue = api.getAttributeValue(elt, "hx-sse"); var legacySSEValue = api.getAttributeValue(elt, "hx-sse");
var returnArr = []; var returnArr = [];
if (legacySSEValue) { if (legacySSEValue != null) {
var values = splitOnWhitespace(legacySSEValue); var values = splitOnWhitespace(legacySSEValue);
for (var i = 0; i < values.length; i++) { for (var i = 0; i < values.length; i++) {
var value = values[i].split(/:(.+)/); var value = values[i].split(/:(.+)/);
@ -103,63 +105,24 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
} }
/** /**
* createEventSourceOnElement creates a new EventSource connection on the provided element. * registerSSE looks for attributes that can contain sse events, right
* If a usable EventSource already exists, then it is returned. If not, then a new EventSource * now hx-trigger and sse-swap and adds listeners based on these attributes too
* is created and stored in the element's internalData. * the closest event source
*
* @param {HTMLElement} elt * @param {HTMLElement} elt
* @param {number} retryCount
* @returns {EventSource | null}
*/ */
function createEventSourceOnElement(elt, retryCount) { function registerSSE(elt) {
// Find closest existing event source
if (elt == null) { var sourceElement = api.getClosestMatch(elt, hasEventSource);
return null; if (sourceElement == null) {
// api.triggerErrorEvent(elt, "htmx:noSSESourceError")
return null; // no eventsource in parentage, orphaned element
} }
var internalData = api.getInternalData(elt); // Set internalData and source
var internalData = api.getInternalData(sourceElement);
var source = internalData.sseEventSource;
// get URL from element's attribute
var sseURL = api.getAttributeValue(elt, "sse-connect");
if (sseURL == undefined) {
var legacyURL = getLegacySSEURL(elt)
if (legacyURL) {
sseURL = legacyURL;
} else {
return null;
}
}
// Connect to the EventSource
var source = htmx.createEventSource(sseURL);
internalData.sseEventSource = source;
// Create event handlers
source.onerror = function (err) {
// Log an error event
api.triggerErrorEvent(elt, "htmx:sseError", {error:err, source:source});
// If parent no longer exists in the document, then clean up this EventSource
if (maybeCloseSSESource(elt)) {
return;
}
// Otherwise, try to reconnect the EventSource
if (source.readyState === EventSource.CLOSED) {
retryCount = retryCount || 0;
var timeout = Math.random() * (2 ^ retryCount) * 500;
window.setTimeout(function() {
createEventSourceOnElement(elt, Math.min(7, retryCount+1));
}, timeout);
}
};
source.onopen = function (evt) {
api.triggerEvent(elt, "htmx::sseOpen", {source: source});
}
// Add message handlers for every `sse-swap` attribute // Add message handlers for every `sse-swap` attribute
queryAttributeOnThisOrChildren(elt, "sse-swap").forEach(function(child) { queryAttributeOnThisOrChildren(elt, "sse-swap").forEach(function(child) {
@ -170,23 +133,27 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
var sseEventNames = getLegacySSESwaps(child); var sseEventNames = getLegacySSESwaps(child);
} }
for (var i = 0 ; i < sseEventNames.length ; i++) { for (var i = 0; i < sseEventNames.length; i++) {
var sseEventName = sseEventNames[i].trim(); var sseEventName = sseEventNames[i].trim();
var listener = function(event) { var listener = function(event) {
// If the parent is missing then close SSE and remove listener // If the source is missing then close SSE
if (maybeCloseSSESource(elt)) { if (maybeCloseSSESource(sourceElement)) {
source.removeEventListener(sseEventName, listener);
return; return;
} }
// If the body no longer contains the element, remove the listener
if (!api.bodyContains(child)) {
source.removeEventListener(sseEventName, listener);
}
// swap the response into the DOM and trigger a notification // swap the response into the DOM and trigger a notification
swap(child, event.data); swap(child, event.data);
api.triggerEvent(elt, "htmx:sseMessage", event); api.triggerEvent(elt, "htmx:sseMessage", event);
}; };
// Register the new listener // Register the new listener
api.getInternalData(elt).sseEventListener = listener; api.getInternalData(child).sseEventListener = listener;
source.addEventListener(sseEventName, listener); source.addEventListener(sseEventName, listener);
} }
}); });
@ -203,24 +170,86 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
if (sseEventName.slice(0, 4) != "sse:") { if (sseEventName.slice(0, 4) != "sse:") {
return; return;
} }
// remove the sse: prefix from here on out
sseEventName = sseEventName.substr(4);
var listener = function(event) { var listener = function() {
if (maybeCloseSSESource(sourceElement)) {
// If parent is missing, then close SSE and remove listener return
if (maybeCloseSSESource(elt)) {
source.removeEventListener(sseEventName, listener);
return;
} }
// Trigger events to be handled by the rest of htmx if (!api.bodyContains(child)) {
htmx.trigger(child, sseEventName, event); source.removeEventListener(sseEventName, listener);
htmx.trigger(child, "htmx:sseMessage", event); }
}
});
}
/**
* ensureEventSourceOnElement creates a new EventSource connection on the provided element.
* If a usable EventSource already exists, then it is returned. If not, then a new EventSource
* is created and stored in the element's internalData.
* @param {HTMLElement} elt
* @param {number} retryCount
* @returns {EventSource | null}
*/
function ensureEventSourceOnElement(elt, retryCount) {
if (elt == null) {
return null;
}
// handle extension source creation attribute
queryAttributeOnThisOrChildren(elt, "sse-connect").forEach(function(child) {
var sseURL = api.getAttributeValue(child, "sse-connect");
if (sseURL == null) {
return;
} }
// Register the new listener ensureEventSource(child, sseURL, retryCount);
api.getInternalData(elt).sseEventListener = listener;
source.addEventListener(sseEventName.slice(4), listener);
}); });
// handle legacy sse, remove for HTMX2
queryAttributeOnThisOrChildren(elt, "hx-sse").forEach(function(child) {
var sseURL = getLegacySSEURL(child);
if (sseURL == null) {
return;
}
ensureEventSource(child, sseURL, retryCount);
});
}
function ensureEventSource(elt, url, retryCount) {
var source = htmx.createEventSource(url);
source.onerror = function(err) {
// Log an error event
api.triggerErrorEvent(elt, "htmx:sseError", { error: err, source: source });
// If parent no longer exists in the document, then clean up this EventSource
if (maybeCloseSSESource(elt)) {
return;
}
// Otherwise, try to reconnect the EventSource
if (source.readyState === EventSource.CLOSED) {
retryCount = retryCount || 0;
var timeout = Math.random() * (2 ^ retryCount) * 500;
window.setTimeout(function() {
ensureEventSourceOnElement(elt, Math.min(7, retryCount + 1));
}, timeout);
}
};
source.onopen = function(evt) {
api.triggerEvent(elt, "htmx:sseOpen", { source: source });
}
api.getInternalData(elt).sseEventSource = source;
} }
/** /**
@ -253,12 +282,12 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
var result = []; var result = [];
// If the parent element also contains the requested attribute, then add it to the results too. // If the parent element also contains the requested attribute, then add it to the results too.
if (api.hasAttribute(elt, attributeName) || api.hasAttribute(elt, "hx-sse")) { if (api.hasAttribute(elt, attributeName)) {
result.push(elt); result.push(elt);
} }
// Search all child nodes that match the requested attribute // Search all child nodes that match the requested attribute
elt.querySelectorAll("[" + attributeName + "], [data-" + attributeName + "], [hx-sse], [data-hx-sse]").forEach(function(node) { elt.querySelectorAll("[" + attributeName + "], [data-" + attributeName + "]").forEach(function(node) {
result.push(node); result.push(node);
}); });
@ -281,7 +310,7 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
api.selectAndSwap(swapSpec.swapStyle, target, elt, content, settleInfo); api.selectAndSwap(swapSpec.swapStyle, target, elt, content, settleInfo);
settleInfo.elts.forEach(function (elt) { settleInfo.elts.forEach(function(elt) {
if (elt.classList) { if (elt.classList) {
elt.classList.add(htmx.config.settlingClass); elt.classList.add(htmx.config.settlingClass);
} }
@ -306,11 +335,11 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
function doSettle(settleInfo) { function doSettle(settleInfo) {
return function() { return function() {
settleInfo.tasks.forEach(function (task) { settleInfo.tasks.forEach(function(task) {
task.call(); task.call();
}); });
settleInfo.elts.forEach(function (elt) { settleInfo.elts.forEach(function(elt) {
if (elt.classList) { if (elt.classList) {
elt.classList.remove(htmx.config.settlingClass); elt.classList.remove(htmx.config.settlingClass);
} }
@ -319,4 +348,8 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
} }
} }
})(); function hasEventSource(node) {
return api.getInternalData(node).sseEventSource != null;
}
})();

File diff suppressed because one or more lines are too long

View file

@ -30,15 +30,15 @@
</ul> </ul>
<script> <script>
const scrollCurrentSongIntoView = () => {
const hoveredSong = document.querySelector(".queue li:hover");
if (hoveredSong === null) {
const currentSong = document.querySelector(".queue li.playing");
currentSong.scrollIntoView({ block: "nearest" });
}
}
htmx.onLoad(() => { htmx.onLoad(() => {
const scrollCurrentSongIntoView = () => {
const hoveredSong = document.querySelector(".queue li:hover");
if (hoveredSong === null) {
const currentSong = document.querySelector(".queue li.playing");
currentSong?.scrollIntoView({ block: "nearest" });
}
}
const isReduced = window const isReduced = window
.matchMedia("(prefers-reduced-motion: reduce)") .matchMedia("(prefers-reduced-motion: reduce)")
.matches; .matches;