@uniweb/core 0.38.0 → 0.38.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/core",
3
- "version": "0.38.0",
3
+ "version": "0.38.2",
4
4
  "description": "Core classes for the Uniweb platform - Uniweb, Website, Page, Block",
5
5
  "type": "module",
6
6
  "exports": {
@@ -49,8 +49,8 @@
49
49
  "vitest": "^4.1.7"
50
50
  },
51
51
  "dependencies": {
52
- "@uniweb/semantic-parser": "^1.5.1",
53
- "@uniweb/theming": "^0.2.0"
52
+ "@uniweb/semantic-parser": "~1.5.1",
53
+ "@uniweb/theming": "~0.2.0"
54
54
  },
55
55
  "scripts": {
56
56
  "test": "vitest run"
package/src/index.js CHANGED
@@ -91,6 +91,7 @@ export {
91
91
  localeOfPath,
92
92
  localeUrl,
93
93
  landingRoute,
94
+ carryQuery,
94
95
  RESOLUTION_STATUS,
95
96
  } from './resolve-route.js'
96
97
  // ⛔ No `isRichSchema` (removed 2026-10-05). It told a form-shaped `data:` value from a field
@@ -330,6 +330,10 @@ export function landingRoute(site, page, seen = new Set()) {
330
330
  *
331
331
  * `status` is what a host that sends one sends. A static host has its own ways to say each kind.
332
332
  *
333
+ * ⭐ **A redirect's `location` names nothing of the request's query or fragment** — a path in, a
334
+ * location out. `carryQuery` adds them, as the runtime does before it navigates and as a host that
335
+ * answers with an HTTP redirect should, so a link's parameters reach the page the visitor lands on.
336
+ *
333
337
  * @param {Object} site - one locale's site content (`{ pages, config, notFound? }`), or its `routeIndex`
334
338
  * @param {string} path - the request path, with any deployment base removed
335
339
  * @param {{ activeLocale?: string, defaultLocale?: string }} [context] - defaults to the content's
@@ -415,6 +419,46 @@ function redirect(location, status, reason) {
415
419
  return { kind: 'redirect', status, location, reason }
416
420
  }
417
421
 
422
+ /**
423
+ * ⭐ **WHAT THE VISITOR ASKED WITH RIDES ALONG.** A redirect's `location` with the query and the
424
+ * fragment of the URL the visitor arrived at, as an HTTP redirect keeps them: the request's query
425
+ * unless the location names its own, and its fragment unless the location names one. So a link's
426
+ * parameters reach the page the visitor lands on — a campaign's tags, or a token a site's backend
427
+ * mails to the site's root (`/?verify=…`).
428
+ *
429
+ * ⛔ **Never onto another origin.** A query can carry a token minted for this site, so a location
430
+ * on another origin — another site, a locale's own domain — goes as written. Without an `origin`,
431
+ * every location with a scheme, or protocol-relative, is taken to be elsewhere.
432
+ *
433
+ * ⛔ Until 2026-10-11 the runtime navigated to the location alone, so on a root that redirects — an
434
+ * authored `redirect:`, a page with no content of its own — a mailed link lost its token before any
435
+ * code could read it.
436
+ *
437
+ * @param {string} location - a redirect's `location`
438
+ * @param {{ search?: string, hash?: string }} [request] - the request's query (`?…`) and fragment
439
+ * (`#…`) — a `Location` will do
440
+ * @param {string|null} [origin] - the site's origin (`https://example.com`)
441
+ * @returns {string}
442
+ */
443
+ export function carryQuery(location, { search = '', hash = '' } = {}, origin = null) {
444
+ const query = search && search !== '?' ? (search.startsWith('?') ? search : `?${search}`) : ''
445
+ const fragment = hash && hash !== '#' ? (hash.startsWith('#') ? hash : `#${hash}`) : ''
446
+ if (typeof location !== 'string' || !location || (!query && !fragment)) return location
447
+ if (location.startsWith('//') || HAS_SCHEME.test(location)) {
448
+ let target = null
449
+ try {
450
+ target = origin ? new URL(location, origin) : null
451
+ } catch {
452
+ target = null
453
+ }
454
+ if (!target || target.origin !== origin) return location
455
+ }
456
+ const cut = location.indexOf('#')
457
+ const path = cut < 0 ? location : location.slice(0, cut)
458
+ const own = cut < 0 ? '' : location.slice(cut)
459
+ return path + (path.includes('?') ? '' : query) + (own || fragment)
460
+ }
461
+
418
462
  const HAS_SCHEME = /^[a-z][a-z0-9+.-]*:/i
419
463
 
420
464
  /**