fix www generation

This commit is contained in:
Carson Gross 2023-04-11 10:28:35 -06:00
parent d1ffd58ae6
commit cb52f71cd2
8 changed files with 194 additions and 3794 deletions

View File

@ -14,9 +14,9 @@ fs.copySync("node_modules/sinon/pkg/sinon.js", currentReleaseRoot + "/node_modul
fs.copySync("node_modules/mock-socket/dist/mock-socket.js", currentReleaseRoot + "/node_modules/mock-socket/dist/mock-socket.js");
fs.copySync("test/", currentReleaseRoot + "/test");
fs.copySync("src/", currentReleaseRoot + "/src");
fs.copySync("src/htmx.js", "www/js/htmx.js");
fs.copySync("src/ext/class-tools.js", "www/js/class-tools.js");
fs.copySync("src/ext/preload.js", "www/js/preload.js");
fs.copySync("src/htmx.js", "www/themes/htmx-theme/static/js/htmx.js");
fs.copySync("src/ext/class-tools.js", "www/themes/htmx-theme/static/js/class-tools.js");
fs.copySync("src/ext/preload.js", "www/themes/htmx-theme/static/js/preload.js");
var testHTML = "<html><body style='font-family: sans-serif'><h1>HTMX TESTS</h1><ul>\n"
fs.readdirSync(testRoot).reverse().forEach(function (file) {

View File

@ -1,92 +0,0 @@
(function () {
function splitOnWhitespace(trigger) {
return trigger.split(/\s+/);
}
function parseClassOperation(trimmedValue) {
var split = splitOnWhitespace(trimmedValue);
if (split.length > 1) {
var operation = split[0];
var classDef = split[1].trim();
var cssClass;
var delay;
if (classDef.indexOf(":") > 0) {
var splitCssClass = classDef.split(':');
cssClass = splitCssClass[0];
delay = htmx.parseInterval(splitCssClass[1]);
} else {
cssClass = classDef;
delay = 100;
}
return {
operation: operation,
cssClass: cssClass,
delay: delay
}
} else {
return null;
}
}
function performOperation(elt, classOperation, classList, currentRunTime) {
setTimeout(function () {
elt.classList[classOperation.operation].call(elt.classList, classOperation.cssClass);
}, currentRunTime)
}
function toggleOperation(elt, classOperation, classList, currentRunTime) {
setTimeout(function () {
setInterval(function () {
elt.classList[classOperation.operation].call(elt.classList, classOperation.cssClass);
}, classOperation.delay);
}, currentRunTime)
}
function processClassList(elt, classList) {
var runs = classList.split("&");
for (var i = 0; i < runs.length; i++) {
var run = runs[i];
var currentRunTime = 0;
var classOperations = run.split(",");
for (var j = 0; j < classOperations.length; j++) {
var value = classOperations[j];
var trimmedValue = value.trim();
var classOperation = parseClassOperation(trimmedValue);
if (classOperation) {
if (classOperation.operation === "toggle") {
toggleOperation(elt, classOperation, classList, currentRunTime);
currentRunTime = currentRunTime + classOperation.delay;
} else {
currentRunTime = currentRunTime + classOperation.delay;
performOperation(elt, classOperation, classList, currentRunTime);
}
}
}
}
}
function maybeProcessClasses(elt) {
if (elt.getAttribute) {
var classList = elt.getAttribute("classes") || elt.getAttribute("data-classes");
if (classList) {
processClassList(elt, classList);
}
}
}
htmx.defineExtension('class-tools', {
onEvent: function (name, evt) {
if (name === "htmx:afterProcessNode") {
var elt = evt.detail.elt;
maybeProcessClasses(elt);
if (elt.querySelectorAll) {
var children = elt.querySelectorAll("[classes], [data-classes]");
for (var i = 0; i < children.length; i++) {
maybeProcessClasses(children[i]);
}
}
}
}
});
})();

File diff suppressed because it is too large Load Diff

View File

@ -1,144 +0,0 @@
// This adds the "preload" extension to htmx. By default, this will
// preload the targets of any tags with `href` or `hx-get` attributes
// if they also have a `preload` attribute as well. See documentation
// for more details
htmx.defineExtension("preload", {
onEvent: function(name, event) {
// Only take actions on "htmx:afterProcessNode"
if (name !== "htmx:afterProcessNode") {
return;
}
// SOME HELPER FUNCTIONS WE'LL NEED ALONG THE WAY
// attr gets the closest non-empty value from the attribute.
var attr = function(node, property) {
if (node == undefined) {return undefined;}
return node.getAttribute(property) || node.getAttribute("data-" + property) || attr(node.parentElement, property)
}
// load handles the actual HTTP fetch, and uses htmx.ajax in cases where we're
// preloading an htmx resource (this sends the same HTTP headers as a regular htmx request)
var load = function(node) {
// Called after a successful AJAX request, to mark the
// content as loaded (and prevent additional AJAX calls.)
var done = function(html) {
if (!node.preloadAlways) {
node.preloadState = "DONE"
}
if (attr(node, "preload-images") == "true") {
document.createElement("div").innerHTML = html // create and populate a node to load linked resources, too.
}
}
return function() {
// If this value has already been loaded, then do not try again.
if (node.preloadState !== "READY") {
return;
}
// Special handling for HX-GET - use built-in htmx.ajax function
// so that headers match other htmx requests, then set
// node.preloadState = TRUE so that requests are not duplicated
// in the future
var hxGet = node.getAttribute("hx-get") || node.getAttribute("data-hx-get")
if (hxGet) {
htmx.ajax("GET", hxGet, {handler:function(elt, info) {
done(info.xhr.responseText);
}});
return;
}
// Otherwise, perform a standard xhr request, then set
// node.preloadState = TRUE so that requests are not duplicated
// in the future.
if (node.getAttribute("href")) {
var r = new XMLHttpRequest();
r.open("GET", node.getAttribute("href"));
r.onload = function() {done(r.responseText);};
r.send();
return;
}
}
}
// This function processes a specific node and sets up event handlers.
// We'll search for nodes and use it below.
var init = function(node) {
// If this node DOES NOT include a "GET" transaction, then there's nothing to do here.
if (node.getAttribute("href") + node.getAttribute("hx-get") + node.getAttribute("data-hx-get") == "") {
return;
}
// Guarantee that we only initialize each node once.
if (node.preloadState !== undefined) {
return;
}
// Get event name from config.
var on = attr(node, "preload") || "mousedown"
const always = on.indexOf("always") !== -1
if (always) {
on = on.replace('always', '').trim()
}
// FALL THROUGH to here means we need to add an EventListener
// Apply the listener to the node
node.addEventListener(on, function(evt) {
if (node.preloadState === "PAUSE") { // Only add one event listener
node.preloadState = "READY"; // Requred for the `load` function to trigger
// Special handling for "mouseover" events. Wait 100ms before triggering load.
if (on === "mouseover") {
window.setTimeout(load(node), 100);
} else {
load(node)() // all other events trigger immediately.
}
}
})
// Special handling for certain built-in event handlers
switch (on) {
case "mouseover":
// Mirror `touchstart` events (fires immediately)
node.addEventListener("touchstart", load(node));
// WHhen the mouse leaves, immediately disable the preload
node.addEventListener("mouseout", function(evt) {
if ((evt.target === node) && (node.preloadState === "READY")) {
node.preloadState = "PAUSE";
}
})
break;
case "mousedown":
// Mirror `touchstart` events (fires immediately)
node.addEventListener("touchstart", load(node));
break;
}
// Mark the node as ready to run.
node.preloadState = "PAUSE";
node.preloadAlways = always;
htmx.trigger(node, "preload:init") // This event can be used to load content immediately.
}
// Search for all child nodes that have a "preload" attribute
event.target.querySelectorAll("[preload]").forEach(function(node) {
// Initialize the node with the "preload" attribute
init(node)
// Initialize all child elements that are anchors or have `hx-get` (use with care)
node.querySelectorAll("a,[hx-get],[data-hx-get]").forEach(init)
})
}
})

View File

@ -156,6 +156,10 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
}
};
source.onopen = function (evt) {
api.triggerEvent(elt, "htmx::sseOpen", {source: source});
}
// Add message handlers for every `sse-swap` attribute
queryAttributeOnThisOrChildren(elt, "sse-swap").forEach(function(child) {

View File

@ -433,6 +433,23 @@ return (function () {
}
}
function normalizePath(path) {
try {
var url = new URL(path);
if (url) {
path = url.pathname + url.search;
}
// remove trailing slash, unless index page
if (!path.match('^/$')) {
path = path.replace(/\/+$/, '');
}
return path;
} catch (e) {
// be kind to IE11, which doesn't support URL()
return path;
}
}
//==========================================================================================
// public API
//==========================================================================================
@ -1301,7 +1318,7 @@ return (function () {
var verb, path;
if (elt.tagName === "A") {
verb = "get";
path = getRawAttribute(elt, 'href');
path = elt.href; // DOM property gives the fully resolved href of a relative link
} else {
var rawAttribute = getRawAttribute(elt, "method");
verb = rawAttribute ? rawAttribute.toLowerCase() : "get";
@ -2037,6 +2054,8 @@ return (function () {
return;
}
url = normalizePath(url);
var historyCache = parseJSON(localStorage.getItem("htmx-history-cache")) || [];
for (var i = 0; i < historyCache.length; i++) {
if (historyCache[i].url === url) {
@ -2066,6 +2085,8 @@ return (function () {
return null;
}
url = normalizePath(url);
var historyCache = parseJSON(localStorage.getItem("htmx-history-cache")) || [];
for (var i = 0; i < historyCache.length; i++) {
if (historyCache[i].url === url) {

View File

@ -100,7 +100,8 @@ describe("hx-disinherit attribute", function() {
var div = make('<div hx-boost="true" hx-disinherit="false"><a id="a1" href="/test">Click me</a></div>');
var link = byId("a1");
link.click();
should.equal(request.detail.requestConfig.path, '/test');
// should match the fully resolved href of the boosted element
should.equal(request.detail.requestConfig.path, request.detail.elt.href);
should.equal(request.detail.elt["htmx-internal-data"].boosted, true);
} finally {
htmx.off("htmx:beforeRequest", handler);

View File

@ -70,6 +70,7 @@ return (function () {
scrollBehavior: 'smooth',
defaultFocusScroll: false,
getCacheBusterParam: false,
globalViewTransitions: false,
},
parseInterval:parseInterval,
_:internalEval,
@ -81,7 +82,7 @@ return (function () {
sock.binaryType = htmx.config.wsBinaryType;
return sock;
},
version: "1.8.6"
version: "1.9.0"
};
/** @type {import("./htmx").HtmxInternalApi} */
@ -265,13 +266,18 @@ return (function () {
return responseNode;
}
function aFullPageResponse(resp) {
return resp.match(/<body/);
}
/**
*
* @param {string} resp
* @returns {Element}
*/
function makeFragment(resp) {
if (htmx.config.useTemplateFragments) {
var partialResponse = !aFullPageResponse(resp);
if (htmx.config.useTemplateFragments && partialResponse) {
var documentFragment = parseHTML("<body><template>" + resp + "</template></body>", 0);
// @ts-ignore type mismatch between DocumentFragment and Element.
// TODO: Are these close enough for htmx to use interchangably?
@ -427,6 +433,23 @@ return (function () {
}
}
function normalizePath(path) {
try {
var url = new URL(path);
if (url) {
path = url.pathname + url.search;
}
// remove trailing slash, unless index page
if (!path.match('^/$')) {
path = path.replace(/\/+$/, '');
}
return path;
} catch (e) {
// be kind to IE11, which doesn't support URL()
return path;
}
}
//==========================================================================================
// public API
//==========================================================================================
@ -471,7 +494,10 @@ return (function () {
function removeElement(elt, delay) {
elt = resolveTarget(elt);
if (delay) {
setTimeout(function(){removeElement(elt);}, delay)
setTimeout(function(){
removeElement(elt);
elt = null;
}, delay);
} else {
elt.parentElement.removeChild(elt);
}
@ -480,7 +506,10 @@ return (function () {
function addClassToElement(elt, clazz, delay) {
elt = resolveTarget(elt);
if (delay) {
setTimeout(function(){addClassToElement(elt, clazz);}, delay)
setTimeout(function(){
addClassToElement(elt, clazz);
elt = null;
}, delay);
} else {
elt.classList && elt.classList.add(clazz);
}
@ -489,7 +518,10 @@ return (function () {
function removeClassFromElement(elt, clazz, delay) {
elt = resolveTarget(elt);
if (delay) {
setTimeout(function(){removeClassFromElement(elt, clazz);}, delay)
setTimeout(function(){
removeClassFromElement(elt, clazz);
elt = null;
}, delay);
} else {
if (elt.classList) {
elt.classList.remove(clazz);
@ -530,21 +562,30 @@ return (function () {
}
}
function normalizeSelector(selector) {
var trimmedSelector = selector.trim();
if (trimmedSelector.startsWith("<") && trimmedSelector.endsWith("/>")) {
return trimmedSelector.substring(1, trimmedSelector.length - 2);
} else {
return trimmedSelector;
}
}
function querySelectorAllExt(elt, selector) {
if (selector.indexOf("closest ") === 0) {
return [closest(elt, selector.substr(8))];
return [closest(elt, normalizeSelector(selector.substr(8)))];
} else if (selector.indexOf("find ") === 0) {
return [find(elt, selector.substr(5))];
return [find(elt, normalizeSelector(selector.substr(5)))];
} else if (selector.indexOf("next ") === 0) {
return [scanForwardQuery(elt, selector.substr(5))];
return [scanForwardQuery(elt, normalizeSelector(selector.substr(5)))];
} else if (selector.indexOf("previous ") === 0) {
return [scanBackwardsQuery(elt, selector.substr(9))];
return [scanBackwardsQuery(elt, normalizeSelector(selector.substr(9)))];
} else if (selector === 'document') {
return [document];
} else if (selector === 'window') {
return [window];
} else {
return getDocument().querySelectorAll(selector);
return getDocument().querySelectorAll(normalizeSelector(selector));
}
}
@ -760,7 +801,7 @@ return (function () {
var oobSelectValues = oobSelects.split(",");
for (let i = 0; i < oobSelectValues.length; i++) {
var oobSelectValue = oobSelectValues[i].split(":", 2);
var id = oobSelectValue[0];
var id = oobSelectValue[0].trim();
if (id.indexOf("#") === 0) {
id = id.substring(1);
}
@ -793,7 +834,8 @@ return (function () {
forEach(fragment.querySelectorAll("[id]"), function (newNode) {
if (newNode.id && newNode.id.length > 0) {
var normalizedId = newNode.id.replace("'", "\\'");
var oldNode = parentNode.querySelector(newNode.tagName + "[id='" + normalizedId + "']");
var normalizedTag = newNode.tagName.replace(':', '\\:');
var oldNode = parentNode.querySelector(normalizedTag + "[id='" + normalizedId + "']");
if (oldNode && oldNode !== parentNode) {
var newAttributes = newNode.cloneNode();
cloneAttributes(newNode, oldNode);
@ -862,6 +904,9 @@ return (function () {
function deInitNode(element) {
var internalData = getInternalData(element);
if (internalData.timeout) {
clearTimeout(internalData.timeout);
}
if (internalData.webSocket) {
internalData.webSocket.close();
}
@ -875,6 +920,11 @@ return (function () {
}
});
}
if (internalData.onHandlers) {
for (var eventName of internalData.onHandlers) {
element.removeEventListener(eventName, internalData.onHandlers[eventName]);
}
}
}
function cleanUpElement(element) {
@ -1268,7 +1318,7 @@ return (function () {
var verb, path;
if (elt.tagName === "A") {
verb = "get";
path = getRawAttribute(elt, 'href');
path = elt.href; // DOM property gives the fully resolved href of a relative link
} else {
var rawAttribute = getRawAttribute(elt, "method");
verb = rawAttribute ? rawAttribute.toLowerCase() : "get";
@ -1396,6 +1446,7 @@ return (function () {
} else if (triggerSpec.delay) {
elementData.delayed = setTimeout(function() { handler(elt, evt) }, triggerSpec.delay);
} else {
triggerEvent(elt, 'htmx:trigger')
handler(elt, evt);
}
}
@ -1688,6 +1739,13 @@ return (function () {
});
}
});
if (!explicitAction && hasAttribute(elt, 'hx-trigger')) {
explicitAction = true
triggerSpecs.forEach(function(triggerSpec) {
// For "naked" triggers, don't do anything at all
addTriggerHandler(elt, triggerSpec, nodeData, function () { })
})
}
return explicitAction;
}
@ -1772,7 +1830,7 @@ return (function () {
if (elt.querySelectorAll) {
var boostedElts = hasChanceOfBeingBoosted() ? ", a, form" : "";
var results = elt.querySelectorAll(VERB_SELECTOR + boostedElts + ", [hx-sse], [data-hx-sse], [hx-ws]," +
" [data-hx-ws], [hx-ext], [data-hx-ext]");
" [data-hx-ws], [hx-ext], [data-hx-ext], [hx-trigger], [data-hx-trigger], [hx-on], [data-hx-on]");
return results;
} else {
return [];
@ -1800,6 +1858,57 @@ return (function () {
})
}
function countCurlies(line) {
var tokens = tokenizeString(line);
var netCurlies = 0;
for (let i = 0; i < tokens.length; i++) {
const token = tokens[i];
if (token === "{") {
netCurlies++;
} else if (token === "}") {
netCurlies--;
}
}
return netCurlies;
}
function addHxOnEventHandler(elt, eventName, code) {
var nodeData = getInternalData(elt);
nodeData.onHandlers ||= {};
var func = new Function("event", code + "; return;");
var listener = elt.addEventListener(eventName, function (e) {
return func.call(elt, e);
});
nodeData.onHandlers[eventName] = listener;
return {nodeData, code, func, listener};
}
function processHxOn(elt) {
var hxOnValue = getAttributeValue(elt, 'hx-on');
if (hxOnValue) {
var handlers = {}
var lines = hxOnValue.split("\n");
var currentEvent = null;
var curlyCount = 0;
while (lines.length > 0) {
var line = lines.shift();
var match = line.match(/^\s*([a-zA-Z:\-]+:)(.*)/);
if (curlyCount === 0 && match) {
line.split(":")
currentEvent = match[1].slice(0, -1); // strip last colon
handlers[currentEvent] = match[2];
} else {
handlers[currentEvent] += line;
}
curlyCount += countCurlies(line);
}
for (var eventName in handlers) {
addHxOnEventHandler(elt, eventName, handlers[eventName]);
}
}
}
function initNode(elt) {
if (elt.closest && elt.closest(htmx.config.disableSelector)) {
return;
@ -1812,6 +1921,8 @@ return (function () {
// clean up any previously processed info
deInitNode(elt);
processHxOn(elt);
triggerEvent(elt, "htmx:beforeProcessNode")
if (elt.value) {
@ -1943,6 +2054,8 @@ return (function () {
return;
}
url = normalizePath(url);
var historyCache = parseJSON(localStorage.getItem("htmx-history-cache")) || [];
for (var i = 0; i < historyCache.length; i++) {
if (historyCache[i].url === url) {
@ -1972,6 +2085,8 @@ return (function () {
return null;
}
url = normalizePath(url);
var historyCache = parseJSON(localStorage.getItem("htmx-history-cache")) || [];
for (var i = 0; i < historyCache.length; i++) {
if (historyCache[i].url === url) {
@ -2396,6 +2511,9 @@ return (function () {
if (modifier.indexOf("settle:") === 0) {
swapSpec["settleDelay"] = parseInterval(modifier.substr(7));
}
if (modifier.indexOf("transition:") === 0) {
swapSpec["transition"] = modifier.substr(11) === "true";
}
if (modifier.indexOf("scroll:") === 0) {
var scrollSpec = modifier.substr(7);
var splitSpec = scrollSpec.split(":");
@ -3127,9 +3245,13 @@ return (function () {
var swapSpec = getSwapSpecification(elt, swapOverride);
target.classList.add(htmx.config.swappingClass);
// optional transition API promise callbacks
var settleResolve = null;
var settleReject = null;
var doSwap = function () {
try {
var activeElt = document.activeElement;
var selectionInfo = {};
try {
@ -3228,6 +3350,7 @@ return (function () {
}
handleTrigger(xhr, "HX-Trigger-After-Settle", finalElt);
}
maybeCall(settleResolve);
}
if (swapSpec.settleDelay > 0) {
@ -3237,10 +3360,34 @@ return (function () {
}
} catch (e) {
triggerErrorEvent(elt, 'htmx:swapError', responseInfo);
maybeCall(settleReject);
throw e;
}
};
var shouldTransition = htmx.config.globalViewTransitions
if(swapSpec.hasOwnProperty('transition')){
shouldTransition = swapSpec.transition;
}
if(shouldTransition &&
triggerEvent(elt, 'htmx:beforeTransition', responseInfo) &&
typeof Promise !== "undefined" && document.startViewTransition){
var settlePromise = new Promise(function (_resolve, _reject) {
settleResolve = _resolve;
settleReject = _reject;
});
// wrap the original doSwap() in a call to startViewTransition()
var innerDoSwap = doSwap;
doSwap = function() {
document.startViewTransition(function () {
innerDoSwap();
return settlePromise;
});
}
}
if (swapSpec.swapDelay > 0) {
setTimeout(doSwap, swapSpec.swapDelay)
} else {
@ -3402,6 +3549,7 @@ return (function () {
};
setTimeout(function () {
triggerEvent(body, 'htmx:load', {}); // give ready handlers a chance to load up before firing this event
body = null; // kill reference for gc
}, 0);
})