2026-07-22 18:09:06 +02:00
|
|
|
//SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
|
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
2026-05-18 02:07:48 +02:00
|
|
|
|
|
|
|
|
// History / SPA routing. Thin C++ wrapper over the browser's
|
|
|
|
|
// `history.pushState` / `popstate` and `window.location.pathname` —
|
|
|
|
|
// exists so a single-page app written against this library can manage
|
|
|
|
|
// its own URL without going through the DOM partition. Re-homed under
|
|
|
|
|
// the `Crafter::Router` namespace (cleaner than CppDOM's bare free
|
|
|
|
|
// functions; symmetric with `Crafter::Gamepad`).
|
|
|
|
|
|
|
|
|
|
export module Crafter.Graphics:Router;
|
|
|
|
|
#ifdef CRAFTER_GRAPHICS_WINDOW_DOM
|
|
|
|
|
import std;
|
|
|
|
|
|
|
|
|
|
export namespace Crafter::Router {
|
|
|
|
|
|
|
|
|
|
// Push a new history entry. `data` is a JSON string serialized by
|
|
|
|
|
// the caller — the browser stores it on the entry but the popstate
|
|
|
|
|
// listener in V1 receives no payload (matches CppDOM's surface).
|
2026-08-05 04:08:13 +02:00
|
|
|
// `url` is browser-relative, e.g. "/blog/post-1". Cannot leave the
|
|
|
|
|
// origin; use Navigate for that.
|
2026-05-18 02:07:48 +02:00
|
|
|
void PushState(std::string_view data, std::string_view title, std::string_view url);
|
|
|
|
|
|
2026-08-05 04:08:13 +02:00
|
|
|
// As PushState, but rewrites the current entry instead of adding one.
|
|
|
|
|
// For URL changes that shouldn't become their own Back destination:
|
|
|
|
|
// filter/sort state, canonicalising a sloppy incoming URL.
|
|
|
|
|
void ReplaceState(std::string_view data, std::string_view title, std::string_view url);
|
|
|
|
|
|
2026-05-18 02:07:48 +02:00
|
|
|
// Subscribe to the browser's `popstate` event (back/forward button,
|
|
|
|
|
// programmatic history.go). Returns an opaque id usable with
|
|
|
|
|
// `RemovePopStateListener`. Multiple subscribers OK.
|
2026-08-05 04:08:13 +02:00
|
|
|
//
|
|
|
|
|
// The callback still receives no payload — re-read the location with
|
|
|
|
|
// the accessors below rather than relying on the pushState `data`.
|
2026-05-18 02:07:48 +02:00
|
|
|
std::int32_t AddPopStateListener(std::function<void()> callback);
|
|
|
|
|
void RemovePopStateListener(std::int32_t id);
|
|
|
|
|
|
2026-08-05 04:08:13 +02:00
|
|
|
// Location accessors. Each allocates a fresh string per call — cache
|
|
|
|
|
// the result if used in a hot path.
|
|
|
|
|
//
|
|
|
|
|
// GetSearch / GetHash include their leading '?' / '#', so an absent
|
|
|
|
|
// query and a bare "?" stay distinguishable and the result can be
|
|
|
|
|
// concatenated straight back onto a path.
|
|
|
|
|
std::string GetPath(); // window.location.pathname
|
|
|
|
|
std::string GetSearch(); // window.location.search, e.g. "?page=2&sort=new"
|
|
|
|
|
std::string GetHash(); // window.location.hash, e.g. "#reviews"
|
|
|
|
|
std::string GetHref(); // window.location.href, absolute
|
|
|
|
|
|
|
|
|
|
// Full-page navigation, cross-origin allowed. PushState deliberately
|
|
|
|
|
// cannot leave the origin, so this is the only way to hand the user off
|
|
|
|
|
// to an external URL (a hosted payment page, an OAuth consent screen).
|
|
|
|
|
// `replace == true` drops the current page from history, so Back won't
|
|
|
|
|
// return to a stale page.
|
|
|
|
|
void Navigate(std::string_view url, bool replace = false);
|
|
|
|
|
|
|
|
|
|
// ─── Query-string helpers ─────────────────────────────────────────
|
|
|
|
|
//
|
|
|
|
|
// Pure string functions — no JS round-trip, safe to call on any
|
|
|
|
|
// string, not just the live location. Provided here so callers don't
|
|
|
|
|
// each hand-roll percent decoding; `+` vs `%20` is the usual bug.
|
|
|
|
|
|
|
|
|
|
// Split a query string into decoded key/value pairs, in source order.
|
|
|
|
|
// Accepts an optional leading '?'. Duplicate keys are preserved as
|
|
|
|
|
// separate entries (?tag=a&tag=b yields two). A valueless key
|
|
|
|
|
// ("?debug") yields an empty value. Empty segments are skipped, so
|
|
|
|
|
// "a=1&&b=2" and a trailing '&' are tolerated.
|
|
|
|
|
std::vector<std::pair<std::string, std::string>> ParseQuery(std::string_view query);
|
|
|
|
|
|
|
|
|
|
// First value for `key`, or nullopt if absent. Distinguishes "missing"
|
|
|
|
|
// from "present but empty" — which ?debug and ?q= need.
|
|
|
|
|
std::optional<std::string> QueryGet(std::string_view query, std::string_view key);
|
|
|
|
|
|
|
|
|
|
// Percent-decode a single component, treating '+' as a space per
|
|
|
|
|
// application/x-www-form-urlencoded. A malformed escape is passed
|
|
|
|
|
// through literally rather than dropped.
|
|
|
|
|
std::string PercentDecode(std::string_view in);
|
2026-05-18 02:07:48 +02:00
|
|
|
}
|
|
|
|
|
#endif // CRAFTER_GRAPHICS_WINDOW_DOM
|