Try client-side routing for all sx-get link clicks, not just boost links
bind-event now checks tryClientRoute before executeRequest for GET clicks on links. Previously only boost links (inside [sx-boost] containers) attempted client routing — explicit sx-get links like ~nav-link always hit the network. Now essay/doc nav links render client-side when possible. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -512,92 +512,6 @@
|
|||||||
return NIL;
|
return NIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
|
||||||
// Platform: deps module — component dependency analysis
|
|
||||||
// =========================================================================
|
|
||||||
|
|
||||||
function componentDeps(c) {
|
|
||||||
return c.deps ? c.deps.slice() : [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function componentSetDeps(c, deps) {
|
|
||||||
c.deps = deps;
|
|
||||||
}
|
|
||||||
|
|
||||||
function componentCssClasses(c) {
|
|
||||||
return c.cssClasses ? c.cssClasses.slice() : [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function envComponents(env) {
|
|
||||||
var names = [];
|
|
||||||
for (var k in env) {
|
|
||||||
var v = env[k];
|
|
||||||
if (v && (v._component || v._macro)) names.push(k);
|
|
||||||
}
|
|
||||||
return names;
|
|
||||||
}
|
|
||||||
|
|
||||||
function regexFindAll(pattern, source) {
|
|
||||||
var re = new RegExp(pattern, "g");
|
|
||||||
var results = [];
|
|
||||||
var m;
|
|
||||||
while ((m = re.exec(source)) !== null) {
|
|
||||||
if (m[1] !== undefined) results.push(m[1]);
|
|
||||||
else results.push(m[0]);
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
function scanCssClasses(source) {
|
|
||||||
var classes = {};
|
|
||||||
var result = [];
|
|
||||||
var m;
|
|
||||||
var re1 = /:class\s+"([^"]*)"/g;
|
|
||||||
while ((m = re1.exec(source)) !== null) {
|
|
||||||
var parts = m[1].split(/\s+/);
|
|
||||||
for (var i = 0; i < parts.length; i++) {
|
|
||||||
if (parts[i] && !classes[parts[i]]) {
|
|
||||||
classes[parts[i]] = true;
|
|
||||||
result.push(parts[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var re2 = /:class\s+\(str\s+((?:"[^"]*"\s*)+)\)/g;
|
|
||||||
while ((m = re2.exec(source)) !== null) {
|
|
||||||
var re3 = /"([^"]*)"/g;
|
|
||||||
var m2;
|
|
||||||
while ((m2 = re3.exec(m[1])) !== null) {
|
|
||||||
var parts2 = m2[1].split(/\s+/);
|
|
||||||
for (var j = 0; j < parts2.length; j++) {
|
|
||||||
if (parts2[j] && !classes[parts2[j]]) {
|
|
||||||
classes[parts2[j]] = true;
|
|
||||||
result.push(parts2[j]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var re4 = /;;\s*@css\s+(.+)/g;
|
|
||||||
while ((m = re4.exec(source)) !== null) {
|
|
||||||
var parts3 = m[1].split(/\s+/);
|
|
||||||
for (var k = 0; k < parts3.length; k++) {
|
|
||||||
if (parts3[k] && !classes[parts3[k]]) {
|
|
||||||
classes[parts3[k]] = true;
|
|
||||||
result.push(parts3[k]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function componentIoRefs(c) {
|
|
||||||
return c.ioRefs ? c.ioRefs.slice() : [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function componentSetIoRefs(c, refs) {
|
|
||||||
c.ioRefs = refs;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// Platform interface — Parser
|
// Platform interface — Parser
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
@@ -1935,7 +1849,7 @@ return postSwap(target); });
|
|||||||
return (isSxTruthy((val == lastVal)) ? (shouldFire = false) : (lastVal = val));
|
return (isSxTruthy((val == lastVal)) ? (shouldFire = false) : (lastVal = val));
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
return (isSxTruthy(shouldFire) ? ((isSxTruthy(sxOr((eventName == "submit"), (isSxTruthy((eventName == "click")) && domHasAttr(el, "href")))) ? preventDefault_(e) : NIL), (isSxTruthy(get(mods, "delay")) ? (clearTimeout_(timer), (timer = setTimeout_(function() { return executeRequest(el, verbInfo, NIL); }, get(mods, "delay")))) : executeRequest(el, verbInfo, NIL))) : NIL);
|
return (isSxTruthy(shouldFire) ? ((isSxTruthy(sxOr((eventName == "submit"), (isSxTruthy((eventName == "click")) && domHasAttr(el, "href")))) ? preventDefault_(e) : NIL), (isSxTruthy((isSxTruthy((eventName == "click")) && isSxTruthy((get(verbInfo, "method") == "GET")) && isSxTruthy(domHasAttr(el, "href")) && isSxTruthy(!get(mods, "delay")) && tryClientRoute(urlPathname(get(verbInfo, "url"))))) ? (browserPushState(get(verbInfo, "url")), browserScrollTo(0, 0)) : (isSxTruthy(get(mods, "delay")) ? (clearTimeout_(timer), (timer = setTimeout_(function() { return executeRequest(el, verbInfo, NIL); }, get(mods, "delay")))) : executeRequest(el, verbInfo, NIL)))) : NIL);
|
||||||
})(); }, (isSxTruthy(get(mods, "once")) ? {["once"]: true} : NIL)) : NIL);
|
})(); }, (isSxTruthy(get(mods, "once")) ? {["once"]: true} : NIL)) : NIL);
|
||||||
})(); };
|
})(); };
|
||||||
|
|
||||||
@@ -2438,189 +2352,6 @@ callExpr.push(dictGet(kwargs, k)); } }
|
|||||||
var bootInit = function() { return (initCssTracking(), initStyleDict(), processSxScripts(NIL), processPageScripts(), sxHydrateElements(NIL), processElements(NIL)); };
|
var bootInit = function() { return (initCssTracking(), initStyleDict(), processSxScripts(NIL), processPageScripts(), sxHydrateElements(NIL), processElements(NIL)); };
|
||||||
|
|
||||||
|
|
||||||
// === Transpiled from deps (component dependency analysis) ===
|
|
||||||
|
|
||||||
// scan-refs
|
|
||||||
var scanRefs = function(node) { return (function() {
|
|
||||||
var refs = [];
|
|
||||||
scanRefsWalk(node, refs);
|
|
||||||
return refs;
|
|
||||||
})(); };
|
|
||||||
|
|
||||||
// scan-refs-walk
|
|
||||||
var scanRefsWalk = function(node, refs) { return (isSxTruthy((typeOf(node) == "symbol")) ? (function() {
|
|
||||||
var name = symbolName(node);
|
|
||||||
return (isSxTruthy(startsWith(name, "~")) ? (isSxTruthy(!contains(refs, name)) ? append_b(refs, name) : NIL) : NIL);
|
|
||||||
})() : (isSxTruthy((typeOf(node) == "list")) ? forEach(function(item) { return scanRefsWalk(item, refs); }, node) : (isSxTruthy((typeOf(node) == "dict")) ? forEach(function(key) { return scanRefsWalk(dictGet(node, key), refs); }, keys(node)) : NIL))); };
|
|
||||||
|
|
||||||
// transitive-deps-walk
|
|
||||||
var transitiveDepsWalk = function(n, seen, env) { return (isSxTruthy(!contains(seen, n)) ? (append_b(seen, n), (function() {
|
|
||||||
var val = envGet(env, n);
|
|
||||||
return (isSxTruthy((typeOf(val) == "component")) ? forEach(function(ref) { return transitiveDepsWalk(ref, seen, env); }, scanRefs(componentBody(val))) : (isSxTruthy((typeOf(val) == "macro")) ? forEach(function(ref) { return transitiveDepsWalk(ref, seen, env); }, scanRefs(macroBody(val))) : NIL));
|
|
||||||
})()) : NIL); };
|
|
||||||
|
|
||||||
// transitive-deps
|
|
||||||
var transitiveDeps = function(name, env) { return (function() {
|
|
||||||
var seen = [];
|
|
||||||
var key = (isSxTruthy(startsWith(name, "~")) ? name : (String("~") + String(name)));
|
|
||||||
transitiveDepsWalk(key, seen, env);
|
|
||||||
return filter(function(x) { return !(x == key); }, seen);
|
|
||||||
})(); };
|
|
||||||
|
|
||||||
// compute-all-deps
|
|
||||||
var computeAllDeps = function(env) { return forEach(function(name) { return (function() {
|
|
||||||
var val = envGet(env, name);
|
|
||||||
return (isSxTruthy((typeOf(val) == "component")) ? componentSetDeps(val, transitiveDeps(name, env)) : NIL);
|
|
||||||
})(); }, envComponents(env)); };
|
|
||||||
|
|
||||||
// scan-components-from-source
|
|
||||||
var scanComponentsFromSource = function(source) { return (function() {
|
|
||||||
var matches = regexFindAll("\\(~([a-zA-Z_][a-zA-Z0-9_\\-]*)", source);
|
|
||||||
return map(function(m) { return (String("~") + String(m)); }, matches);
|
|
||||||
})(); };
|
|
||||||
|
|
||||||
// components-needed
|
|
||||||
var componentsNeeded = function(pageSource, env) { return (function() {
|
|
||||||
var direct = scanComponentsFromSource(pageSource);
|
|
||||||
var allNeeded = [];
|
|
||||||
{ var _c = direct; for (var _i = 0; _i < _c.length; _i++) { var name = _c[_i]; if (isSxTruthy(!contains(allNeeded, name))) {
|
|
||||||
allNeeded.push(name);
|
|
||||||
}
|
|
||||||
(function() {
|
|
||||||
var val = envGet(env, name);
|
|
||||||
return (function() {
|
|
||||||
var deps = (isSxTruthy((isSxTruthy((typeOf(val) == "component")) && !isEmpty(componentDeps(val)))) ? componentDeps(val) : transitiveDeps(name, env));
|
|
||||||
return forEach(function(dep) { return (isSxTruthy(!contains(allNeeded, dep)) ? append_b(allNeeded, dep) : NIL); }, deps);
|
|
||||||
})();
|
|
||||||
})(); } }
|
|
||||||
return allNeeded;
|
|
||||||
})(); };
|
|
||||||
|
|
||||||
// page-component-bundle
|
|
||||||
var pageComponentBundle = function(pageSource, env) { return componentsNeeded(pageSource, env); };
|
|
||||||
|
|
||||||
// page-css-classes
|
|
||||||
var pageCssClasses = function(pageSource, env) { return (function() {
|
|
||||||
var needed = componentsNeeded(pageSource, env);
|
|
||||||
var classes = [];
|
|
||||||
{ var _c = needed; for (var _i = 0; _i < _c.length; _i++) { var name = _c[_i]; (function() {
|
|
||||||
var val = envGet(env, name);
|
|
||||||
return (isSxTruthy((typeOf(val) == "component")) ? forEach(function(cls) { return (isSxTruthy(!contains(classes, cls)) ? append_b(classes, cls) : NIL); }, componentCssClasses(val)) : NIL);
|
|
||||||
})(); } }
|
|
||||||
{ var _c = scanCssClasses(pageSource); for (var _i = 0; _i < _c.length; _i++) { var cls = _c[_i]; if (isSxTruthy(!contains(classes, cls))) {
|
|
||||||
classes.push(cls);
|
|
||||||
} } }
|
|
||||||
return classes;
|
|
||||||
})(); };
|
|
||||||
|
|
||||||
// scan-io-refs-walk
|
|
||||||
var scanIoRefsWalk = function(node, ioNames, refs) { return (isSxTruthy((typeOf(node) == "symbol")) ? (function() {
|
|
||||||
var name = symbolName(node);
|
|
||||||
return (isSxTruthy(contains(ioNames, name)) ? (isSxTruthy(!contains(refs, name)) ? append_b(refs, name) : NIL) : NIL);
|
|
||||||
})() : (isSxTruthy((typeOf(node) == "list")) ? forEach(function(item) { return scanIoRefsWalk(item, ioNames, refs); }, node) : (isSxTruthy((typeOf(node) == "dict")) ? forEach(function(key) { return scanIoRefsWalk(dictGet(node, key), ioNames, refs); }, keys(node)) : NIL))); };
|
|
||||||
|
|
||||||
// scan-io-refs
|
|
||||||
var scanIoRefs = function(node, ioNames) { return (function() {
|
|
||||||
var refs = [];
|
|
||||||
scanIoRefsWalk(node, ioNames, refs);
|
|
||||||
return refs;
|
|
||||||
})(); };
|
|
||||||
|
|
||||||
// transitive-io-refs-walk
|
|
||||||
var transitiveIoRefsWalk = function(n, seen, allRefs, env, ioNames) { return (isSxTruthy(!contains(seen, n)) ? (append_b(seen, n), (function() {
|
|
||||||
var val = envGet(env, n);
|
|
||||||
return (isSxTruthy((typeOf(val) == "component")) ? (forEach(function(ref) { return (isSxTruthy(!contains(allRefs, ref)) ? append_b(allRefs, ref) : NIL); }, scanIoRefs(componentBody(val), ioNames)), forEach(function(dep) { return transitiveIoRefsWalk(dep, seen, allRefs, env, ioNames); }, scanRefs(componentBody(val)))) : (isSxTruthy((typeOf(val) == "macro")) ? (forEach(function(ref) { return (isSxTruthy(!contains(allRefs, ref)) ? append_b(allRefs, ref) : NIL); }, scanIoRefs(macroBody(val), ioNames)), forEach(function(dep) { return transitiveIoRefsWalk(dep, seen, allRefs, env, ioNames); }, scanRefs(macroBody(val)))) : NIL));
|
|
||||||
})()) : NIL); };
|
|
||||||
|
|
||||||
// transitive-io-refs
|
|
||||||
var transitiveIoRefs = function(name, env, ioNames) { return (function() {
|
|
||||||
var allRefs = [];
|
|
||||||
var seen = [];
|
|
||||||
var key = (isSxTruthy(startsWith(name, "~")) ? name : (String("~") + String(name)));
|
|
||||||
transitiveIoRefsWalk(key, seen, allRefs, env, ioNames);
|
|
||||||
return allRefs;
|
|
||||||
})(); };
|
|
||||||
|
|
||||||
// compute-all-io-refs
|
|
||||||
var computeAllIoRefs = function(env, ioNames) { return forEach(function(name) { return (function() {
|
|
||||||
var val = envGet(env, name);
|
|
||||||
return (isSxTruthy((typeOf(val) == "component")) ? componentSetIoRefs(val, transitiveIoRefs(name, env, ioNames)) : NIL);
|
|
||||||
})(); }, envComponents(env)); };
|
|
||||||
|
|
||||||
// component-pure?
|
|
||||||
var componentPure_p = function(name, env, ioNames) { return isEmpty(transitiveIoRefs(name, env, ioNames)); };
|
|
||||||
|
|
||||||
|
|
||||||
// === Transpiled from router (client-side route matching) ===
|
|
||||||
|
|
||||||
// split-path-segments
|
|
||||||
var splitPathSegments = function(path) { return (function() {
|
|
||||||
var trimmed = (isSxTruthy(startsWith(path, "/")) ? slice(path, 1) : path);
|
|
||||||
return (function() {
|
|
||||||
var trimmed2 = (isSxTruthy((isSxTruthy(!isEmpty(trimmed)) && endsWith(trimmed, "/"))) ? slice(trimmed, 0, (length(trimmed) - 1)) : trimmed);
|
|
||||||
return (isSxTruthy(isEmpty(trimmed2)) ? [] : split(trimmed2, "/"));
|
|
||||||
})();
|
|
||||||
})(); };
|
|
||||||
|
|
||||||
// make-route-segment
|
|
||||||
var makeRouteSegment = function(seg) { return (isSxTruthy((isSxTruthy(startsWith(seg, "<")) && endsWith(seg, ">"))) ? (function() {
|
|
||||||
var paramName = slice(seg, 1, (length(seg) - 1));
|
|
||||||
return (function() {
|
|
||||||
var d = {};
|
|
||||||
d["type"] = "param";
|
|
||||||
d["value"] = paramName;
|
|
||||||
return d;
|
|
||||||
})();
|
|
||||||
})() : (function() {
|
|
||||||
var d = {};
|
|
||||||
d["type"] = "literal";
|
|
||||||
d["value"] = seg;
|
|
||||||
return d;
|
|
||||||
})()); };
|
|
||||||
|
|
||||||
// parse-route-pattern
|
|
||||||
var parseRoutePattern = function(pattern) { return (function() {
|
|
||||||
var segments = splitPathSegments(pattern);
|
|
||||||
return map(makeRouteSegment, segments);
|
|
||||||
})(); };
|
|
||||||
|
|
||||||
// match-route-segments
|
|
||||||
var matchRouteSegments = function(pathSegs, parsedSegs) { return (isSxTruthy(!(length(pathSegs) == length(parsedSegs))) ? NIL : (function() {
|
|
||||||
var params = {};
|
|
||||||
var matched = true;
|
|
||||||
forEachIndexed(function(i, parsedSeg) { return (isSxTruthy(matched) ? (function() {
|
|
||||||
var pathSeg = nth(pathSegs, i);
|
|
||||||
var segType = get(parsedSeg, "type");
|
|
||||||
return (isSxTruthy((segType == "literal")) ? (isSxTruthy(!(pathSeg == get(parsedSeg, "value"))) ? (matched = false) : NIL) : (isSxTruthy((segType == "param")) ? dictSet(params, get(parsedSeg, "value"), pathSeg) : (matched = false)));
|
|
||||||
})() : NIL); }, parsedSegs);
|
|
||||||
return (isSxTruthy(matched) ? params : NIL);
|
|
||||||
})()); };
|
|
||||||
|
|
||||||
// match-route
|
|
||||||
var matchRoute = function(path, pattern) { return (function() {
|
|
||||||
var pathSegs = splitPathSegments(path);
|
|
||||||
var parsedSegs = parseRoutePattern(pattern);
|
|
||||||
return matchRouteSegments(pathSegs, parsedSegs);
|
|
||||||
})(); };
|
|
||||||
|
|
||||||
// find-matching-route
|
|
||||||
var findMatchingRoute = function(path, routes) { return (function() {
|
|
||||||
var pathSegs = splitPathSegments(path);
|
|
||||||
var result = NIL;
|
|
||||||
{ var _c = routes; for (var _i = 0; _i < _c.length; _i++) { var route = _c[_i]; if (isSxTruthy(isNil(result))) {
|
|
||||||
(function() {
|
|
||||||
var params = matchRouteSegments(pathSegs, get(route, "parsed"));
|
|
||||||
return (isSxTruthy(!isNil(params)) ? (function() {
|
|
||||||
var matched = merge(route, {});
|
|
||||||
matched["params"] = params;
|
|
||||||
return (result = matched);
|
|
||||||
})() : NIL);
|
|
||||||
})();
|
|
||||||
} } }
|
|
||||||
return result;
|
|
||||||
})(); };
|
|
||||||
|
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// Platform interface — DOM adapter (browser-only)
|
// Platform interface — DOM adapter (browser-only)
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
@@ -3753,20 +3484,6 @@ callExpr.push(dictGet(kwargs, k)); } }
|
|||||||
renderComponent: typeof sxRenderComponent === "function" ? sxRenderComponent : null,
|
renderComponent: typeof sxRenderComponent === "function" ? sxRenderComponent : null,
|
||||||
getEnv: function() { return componentEnv; },
|
getEnv: function() { return componentEnv; },
|
||||||
init: typeof bootInit === "function" ? bootInit : null,
|
init: typeof bootInit === "function" ? bootInit : null,
|
||||||
scanRefs: scanRefs,
|
|
||||||
transitiveDeps: transitiveDeps,
|
|
||||||
computeAllDeps: computeAllDeps,
|
|
||||||
componentsNeeded: componentsNeeded,
|
|
||||||
pageComponentBundle: pageComponentBundle,
|
|
||||||
pageCssClasses: pageCssClasses,
|
|
||||||
scanIoRefs: scanIoRefs,
|
|
||||||
transitiveIoRefs: transitiveIoRefs,
|
|
||||||
computeAllIoRefs: computeAllIoRefs,
|
|
||||||
componentPure_p: componentPure_p,
|
|
||||||
splitPathSegments: splitPathSegments,
|
|
||||||
parseRoutePattern: parseRoutePattern,
|
|
||||||
matchRoute: matchRoute,
|
|
||||||
findMatchingRoute: findMatchingRoute,
|
|
||||||
_version: "ref-2.0 (boot+cssx+dom+engine+html+orchestration+parser+sx, bootstrap-compiled)"
|
_version: "ref-2.0 (boot+cssx+dom+engine+html+orchestration+parser+sx, bootstrap-compiled)"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -388,15 +388,25 @@
|
|||||||
(dom-has-attr? el "href")))
|
(dom-has-attr? el "href")))
|
||||||
(prevent-default e))
|
(prevent-default e))
|
||||||
|
|
||||||
;; Delay modifier
|
;; For GET clicks on links, try client-side routing first
|
||||||
(if (get mods "delay")
|
(if (and (= event-name "click")
|
||||||
|
(= (get verbInfo "method") "GET")
|
||||||
|
(dom-has-attr? el "href")
|
||||||
|
(not (get mods "delay"))
|
||||||
|
(try-client-route (url-pathname (get verbInfo "url"))))
|
||||||
|
;; Client route succeeded — push state, scroll to top
|
||||||
(do
|
(do
|
||||||
(clear-timeout timer)
|
(browser-push-state (get verbInfo "url"))
|
||||||
(set! timer
|
(browser-scroll-to 0 0))
|
||||||
(set-timeout
|
;; Fall through to server fetch
|
||||||
(fn () (execute-request el verbInfo nil))
|
(if (get mods "delay")
|
||||||
(get mods "delay"))))
|
(do
|
||||||
(execute-request el verbInfo nil)))))
|
(clear-timeout timer)
|
||||||
|
(set! timer
|
||||||
|
(set-timeout
|
||||||
|
(fn () (execute-request el verbInfo nil))
|
||||||
|
(get mods "delay"))))
|
||||||
|
(execute-request el verbInfo nil))))))
|
||||||
(if (get mods "once") (dict "once" true) nil))))))
|
(if (get mods "once") (dict "once" true) nil))))))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user