Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | 1x 1x 9x 6x 3x 3x 1x 3x 3x 1x 8x 7x 7x 5x 2x 3x 8x 8x 6x 2x | // The city a visitor picks (a home hero city tab, or one City filter value on
// /pros or /projects) becomes the default City filter when they next open a
// bare /pros or /projects. The URL stays the truth: the remembered city only
// fills in a listing URL that has no filters at all, and removing the City
// filter forgets it. Crawlers keep no storage, so they never see a redirect.
const KEY = "ir-city";
const LISTINGS = new Set(["/pros", "/projects"]);
function cityIds(url: URL): string[] {
return url.searchParams
.getAll("cityIds")
.flatMap((v) => v.split(","))
.filter(Boolean);
}
export function rememberedCity(): string | null {
try {
return localStorage.getItem(KEY);
} catch {
return null;
}
}
export function rememberCity(id: string | null): void {
try {
if (id) localStorage.setItem(KEY, id);
else localStorage.removeItem(KEY);
} catch {
// Storage blocked (private mode): no default, nothing else changes.
}
}
/**
* What arriving at `to` says about the visitor's city: an id (one city
* picked), null (the City filter was just removed on the same listing), or
* undefined (nothing to change).
*/
export function cityChoice(from: URL, to: URL): string | null | undefined {
if (!LISTINGS.has(to.pathname)) return undefined;
const ids = cityIds(to);
if (ids.length === 1) return ids[0];
if (
ids.length === 0 &&
from.pathname === to.pathname &&
cityIds(from).length > 0
) {
return null;
}
return undefined;
}
/** The bare listing URL with the remembered city filled in, or null. */
export function withDefaultCity(
href: string,
city: string | null,
allowed: string[],
): string | null {
const url = new URL(href);
if (
!LISTINGS.has(url.pathname) ||
url.search ||
!city ||
!allowed.includes(city)
) {
return null;
}
return `${url.pathname}?cityIds=${encodeURIComponent(city)}`;
}
/* v8 ignore start -- browser wiring; the decisions above are unit-tested */
if (typeof document !== "undefined") {
// Hero city tabs carry data-city-id.
document.addEventListener(
"click",
(e) => {
const tab = (e.target as Element | null)?.closest?.("a[data-city-id]");
if (tab) rememberCity(tab.getAttribute("data-city-id"));
},
true,
);
document.addEventListener("astro:before-preparation", (e) => {
const { from, to } = e as Event & { from: URL; to: URL };
const choice = cityChoice(from, to);
if (choice !== undefined) rememberCity(choice);
});
document.addEventListener("astro:page-load", () => {
const here = new URL(location.href);
const choice = cityChoice(here, here);
if (choice) rememberCity(choice);
const allowed = Array.from(
document.querySelectorAll(
'details[data-facet-param="cityIds"] [data-facet-option]',
),
).map((el) => el.getAttribute("data-facet-option") ?? "");
const target = withDefaultCity(location.href, rememberedCity(), allowed);
// replace(): the bare URL never becomes a Back stop that bounces again.
if (target) location.replace(target);
});
}
/* v8 ignore stop */
|