commit e5f78550b431ffd6e06b6e8fdb2d4996aacd231d
parent 108cbea0ec6188c08e093d02bd9d48e17bbe990a
Author: Damian Myrda <damian@prime8.dev>
Date: Thu, 22 Oct 2026 01:31:00 +0000
web: links that leave the app open in a new tab
Diffstat:
1 file changed, 27 insertions(+), 3 deletions(-)
diff --git a/internal/web/scripts/modules/header.js b/internal/web/scripts/modules/header.js
@@ -13,6 +13,21 @@ const APP_HOSTS = new Set(["prime8.dev", "www.prime8.dev"]);
// static assets) is a real load.
const APP_PATH = /^\/(?:(?:projects|experience)(?:\/[^/]+)?)?\/?$/;
+// opensOut reports whether an anchor should open in a new tab.
+function opensOut(a) {
+ if (a.hasAttribute("download") || a.hasAttribute("hx-get")) return false;
+ let url;
+ try {
+ url = new URL(a.href, window.location.href);
+ } catch {
+ return false;
+ }
+ if (!/^https?:$/.test(url.protocol)) return false;
+ const here = new URL(window.location.href);
+ if (url.origin === here.origin && url.pathname === here.pathname && url.hash) return false;
+ return true;
+}
+
// appPath returns the in-app path an anchor points at, or "" when clicking
// it should be left to the browser.
function appPath(a) {
@@ -79,9 +94,18 @@ export function initHeader() {
const a = e.target.closest?.("a[href]");
if (!a || !window.htmx) return;
const path = appPath(a);
- if (!path) return;
- e.preventDefault();
- navigate(path);
+ if (path) {
+ e.preventDefault();
+ navigate(path);
+ return;
+ }
+ // Everything else (other sites, raw files, links in project READMEs)
+ // opens in a new tab so the app tab stays put. In-page #anchors and
+ // non-http links (mailto:) are left to the browser.
+ if (opensOut(a)) {
+ e.preventDefault();
+ window.open(a.href, "_blank", "noopener,noreferrer");
+ }
};
document.addEventListener("click", headerHandlers.linkClick);