@aglyn/aglyn 1.0.0-beta.252 → 1.0.0-beta.254
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 +11 -11
- package/src/lib/app-utils/docs-index.generated.js +4 -4
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/email-block-starters.d.ts +39 -0
- package/src/lib/app-utils/email-block-starters.js +122 -0
- package/src/lib/app-utils/email-block-starters.js.map +1 -0
- package/src/lib/app-utils/screen-route.d.ts +1 -130
- package/src/lib/app-utils/screen-route.js +2 -151
- package/src/lib/app-utils/screen-route.js.map +1 -1
- package/src/lib/app-utils/screen-slug.d.ts +146 -0
- package/src/lib/app-utils/screen-slug.js +173 -0
- package/src/lib/app-utils/screen-slug.js.map +1 -0
- package/src/lib/app-utils/site-theme.d.ts +11 -0
- package/src/lib/app-utils/site-theme.js +32 -1
- package/src/lib/app-utils/site-theme.js.map +1 -1
- package/src/lib/app-utils/template-slug.d.ts +101 -0
- package/src/lib/app-utils/template-slug.js +150 -0
- package/src/lib/app-utils/template-slug.js.map +1 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ import { normalizeScreenSlug, reservedScreenRouteMessage, reservedScreenRouteSegment, SCREEN_ROOT_PATH, SCREEN_SLUG_PATH_SEPARATOR_MESSAGE, screenSlugHasPathSeparator } from "./screen-slug.js";
|
|
18
|
+
/*
|
|
19
|
+
* Where a page made from a template lands, in a leaf of its own (it was
|
|
20
|
+
* `create-page-from-template.ts`, which reaches Firestore) so the native apps
|
|
21
|
+
* replay the same answers. The console file re-exports all three names.
|
|
22
|
+
*/ /**
|
|
23
|
+
* Why one template screen cannot be given the address it asks for, or
|
|
24
|
+
* `undefined` when it can (AGL-2588).
|
|
25
|
+
*
|
|
26
|
+
* The bundle appliers pass `slug` straight off a template document into
|
|
27
|
+
* {@link createPageFromTemplate}, so no field ever asks the two questions
|
|
28
|
+
* every typed slug field asks: is this ONE path segment (AGL-2572), and is
|
|
29
|
+
* the address one the published site can actually answer (AGL-2076)? The set
|
|
30
|
+
* of bundle slugs is safe today only because of where they come from —
|
|
31
|
+
* starter slugs are code-defined, and library-template slugs were captured
|
|
32
|
+
* off screens that already passed a guard. Provenance is not a guard, and it
|
|
33
|
+
* stops being true the first time a template carries an author-supplied slug.
|
|
34
|
+
*
|
|
35
|
+
* Answers over the address `resolveTemplateSlug` would REQUEST — the slug,
|
|
36
|
+
* or the display name when the slug sanitizes away, or `page` — because a
|
|
37
|
+
* screen whose slug is `###` and whose name is `Search` still lands on
|
|
38
|
+
* `search`. De-confliction only ever appends a numeric suffix, so it can
|
|
39
|
+
* neither create a reserved segment nor clear one.
|
|
40
|
+
*
|
|
41
|
+
* A refusal string rather than a boolean, so the caller can name to the
|
|
42
|
+
* author which rule the screen fell foul of.
|
|
43
|
+
*/ export function templateScreenAddressRefusal(input) {
|
|
44
|
+
var _ref, _normalizeScreenSlug;
|
|
45
|
+
if (screenSlugHasPathSeparator(input.slug)) {
|
|
46
|
+
return SCREEN_SLUG_PATH_SEPARATOR_MESSAGE;
|
|
47
|
+
}
|
|
48
|
+
const requested = (_ref = (_normalizeScreenSlug = normalizeScreenSlug(input.slug)) != null ? _normalizeScreenSlug : normalizeScreenSlug(input.displayName)) != null ? _ref : 'page';
|
|
49
|
+
const reserved = reservedScreenRouteSegment(requested);
|
|
50
|
+
return reserved ? reservedScreenRouteMessage(reserved) : undefined;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The address one template page claims (AGL-1575).
|
|
54
|
+
*
|
|
55
|
+
* This used to be a private `slugifyPageName` that stripped every character
|
|
56
|
+
* outside `[a-z0-9]`, so `'/'` and `''` both reduced to the empty string and
|
|
57
|
+
* fell through to the display name. A template screen asking for the SITE
|
|
58
|
+
* ROOT could therefore never get it: the shop starters declared `slug: ''`
|
|
59
|
+
* meaning home and were published at `/home`, and a site started from any
|
|
60
|
+
* template 404'd at its own URL while the console showed a success toast.
|
|
61
|
+
*
|
|
62
|
+
* The routing-map format has one canonical normalizer — `normalizeScreenSlug`
|
|
63
|
+
* — which is what the Screens page and the besigner already publish through,
|
|
64
|
+
* and which is the only place that knows `'/'` is the root path rather than
|
|
65
|
+
* punctuation. Template pages now go through it too, so all three surfaces
|
|
66
|
+
* agree on what a user-supplied address means.
|
|
67
|
+
*
|
|
68
|
+
* ## De-confliction (the invariant)
|
|
69
|
+
*
|
|
70
|
+
* `usedSlugs` holds every path already routed on the host plus everything
|
|
71
|
+
* claimed earlier in the same batch. A claim that collides FALLS BACK; it
|
|
72
|
+
* never overwrites, because the caller publishes into the host's routing map
|
|
73
|
+
* and a duplicate path would take a live page off the site.
|
|
74
|
+
*
|
|
75
|
+
* The root is the interesting case: `'/-2'` is not an address, so a second
|
|
76
|
+
* claimant on a host that already has a home page falls back to its NAME
|
|
77
|
+
* (`Home` → `home`), and only then to numeric suffixes. First claimant wins
|
|
78
|
+
* the root; everybody after it gets a real path of their own.
|
|
79
|
+
*
|
|
80
|
+
* Mutates `usedSlugs` with whatever it hands out.
|
|
81
|
+
*/ export function resolveTemplateSlug(input) {
|
|
82
|
+
var _normalizeScreenSlug, _normalizeScreenSlug1;
|
|
83
|
+
const { usedSlugs } = input;
|
|
84
|
+
// `normalizeScreenSlug` returns undefined for anything that sanitizes away
|
|
85
|
+
// (`''`, `'###'`) — the caller decides what that means, and here it means
|
|
86
|
+
// "no address authored", so the display name answers instead.
|
|
87
|
+
const named = (_normalizeScreenSlug = normalizeScreenSlug(input.displayName)) != null ? _normalizeScreenSlug : 'page';
|
|
88
|
+
const requested = (_normalizeScreenSlug1 = normalizeScreenSlug(input.slug)) != null ? _normalizeScreenSlug1 : named;
|
|
89
|
+
const base = requested === SCREEN_ROOT_PATH && usedSlugs.has(SCREEN_ROOT_PATH) ? named : requested;
|
|
90
|
+
let slug = base;
|
|
91
|
+
let attempt = 2;
|
|
92
|
+
while(usedSlugs.has(slug))slug = `${base}-${attempt++}`;
|
|
93
|
+
usedSlugs.add(slug);
|
|
94
|
+
return {
|
|
95
|
+
slug,
|
|
96
|
+
requestedSlug: requested
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Gives a multi-page bundle a home page when nothing in it claims one
|
|
101
|
+
* (AGL-1575).
|
|
102
|
+
*
|
|
103
|
+
* A starter is somebody's whole site, and a site whose root 404s is broken in
|
|
104
|
+
* the one place every visitor lands. When no screen in the bundle asks for
|
|
105
|
+
* `SCREEN_ROOT_PATH` and the host has no root route yet, the FIRST screen —
|
|
106
|
+
* authored order, which is the order the author meant them to be created in —
|
|
107
|
+
* takes it. That also repairs starters materialized before this fix, whose
|
|
108
|
+
* seeded documents dropped the home screen's `slug: ''` on the floor.
|
|
109
|
+
*
|
|
110
|
+
* Deliberately NOT applied to the single-template "Use template" dialog: the
|
|
111
|
+
* user types an address there, and silently re-pointing an explicit answer at
|
|
112
|
+
* the root would be a worse surprise than a site that needs a home page.
|
|
113
|
+
* Deliberately conditional on the host having no root: it never moves a live
|
|
114
|
+
* home page, which is the same invariant `resolveTemplateSlug` protects.
|
|
115
|
+
*/ export function withBundleRootScreen(screens, usedSlugs) {
|
|
116
|
+
const list = [
|
|
117
|
+
...screens
|
|
118
|
+
];
|
|
119
|
+
if (!list.length) return list;
|
|
120
|
+
if ([
|
|
121
|
+
...usedSlugs
|
|
122
|
+
].includes(SCREEN_ROOT_PATH)) return list;
|
|
123
|
+
const claimsRoot = list.some((screen)=>normalizeScreenSlug(screen.slug) === SCREEN_ROOT_PATH);
|
|
124
|
+
if (claimsRoot) return list;
|
|
125
|
+
list[0] = _extends({}, list[0], {
|
|
126
|
+
slug: SCREEN_ROOT_PATH
|
|
127
|
+
});
|
|
128
|
+
return list;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* {@link resolveTemplateSlug} over a list instead of a `Set` it mutates: the
|
|
132
|
+
* address a page claims, and the paths taken once it has. For a caller that
|
|
133
|
+
* cannot hold a `Set` (the native apps) and for the recorded cases.
|
|
134
|
+
*/ export function resolveTemplateSlugAmong(input) {
|
|
135
|
+
const used = new Set(input.taken);
|
|
136
|
+
const { slug, requestedSlug } = resolveTemplateSlug({
|
|
137
|
+
slug: input.slug,
|
|
138
|
+
displayName: input.displayName,
|
|
139
|
+
usedSlugs: used
|
|
140
|
+
});
|
|
141
|
+
return {
|
|
142
|
+
slug,
|
|
143
|
+
requestedSlug,
|
|
144
|
+
taken: [
|
|
145
|
+
...used
|
|
146
|
+
]
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
//# sourceMappingURL=template-slug.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/template-slug.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n normalizeScreenSlug,\n reservedScreenRouteMessage,\n reservedScreenRouteSegment,\n SCREEN_ROOT_PATH,\n SCREEN_SLUG_PATH_SEPARATOR_MESSAGE,\n screenSlugHasPathSeparator,\n} from './screen-slug'\n\n/*\n * Where a page made from a template lands, in a leaf of its own (it was\n * `create-page-from-template.ts`, which reaches Firestore) so the native apps\n * replay the same answers. The console file re-exports all three names.\n */\n\n/**\n * Why one template screen cannot be given the address it asks for, or\n * `undefined` when it can (AGL-2588).\n *\n * The bundle appliers pass `slug` straight off a template document into\n * {@link createPageFromTemplate}, so no field ever asks the two questions\n * every typed slug field asks: is this ONE path segment (AGL-2572), and is\n * the address one the published site can actually answer (AGL-2076)? The set\n * of bundle slugs is safe today only because of where they come from —\n * starter slugs are code-defined, and library-template slugs were captured\n * off screens that already passed a guard. Provenance is not a guard, and it\n * stops being true the first time a template carries an author-supplied slug.\n *\n * Answers over the address `resolveTemplateSlug` would REQUEST — the slug,\n * or the display name when the slug sanitizes away, or `page` — because a\n * screen whose slug is `###` and whose name is `Search` still lands on\n * `search`. De-confliction only ever appends a numeric suffix, so it can\n * neither create a reserved segment nor clear one.\n *\n * A refusal string rather than a boolean, so the caller can name to the\n * author which rule the screen fell foul of.\n */\nexport function templateScreenAddressRefusal(input: {\n slug?: string\n displayName?: string\n}): string | undefined {\n if (screenSlugHasPathSeparator(input.slug)) {\n return SCREEN_SLUG_PATH_SEPARATOR_MESSAGE\n }\n const requested =\n normalizeScreenSlug(input.slug) ??\n normalizeScreenSlug(input.displayName) ??\n 'page'\n const reserved = reservedScreenRouteSegment(requested)\n return reserved ? reservedScreenRouteMessage(reserved) : undefined\n}\n\n/**\n * The address one template page claims (AGL-1575).\n *\n * This used to be a private `slugifyPageName` that stripped every character\n * outside `[a-z0-9]`, so `'/'` and `''` both reduced to the empty string and\n * fell through to the display name. A template screen asking for the SITE\n * ROOT could therefore never get it: the shop starters declared `slug: ''`\n * meaning home and were published at `/home`, and a site started from any\n * template 404'd at its own URL while the console showed a success toast.\n *\n * The routing-map format has one canonical normalizer — `normalizeScreenSlug`\n * — which is what the Screens page and the besigner already publish through,\n * and which is the only place that knows `'/'` is the root path rather than\n * punctuation. Template pages now go through it too, so all three surfaces\n * agree on what a user-supplied address means.\n *\n * ## De-confliction (the invariant)\n *\n * `usedSlugs` holds every path already routed on the host plus everything\n * claimed earlier in the same batch. A claim that collides FALLS BACK; it\n * never overwrites, because the caller publishes into the host's routing map\n * and a duplicate path would take a live page off the site.\n *\n * The root is the interesting case: `'/-2'` is not an address, so a second\n * claimant on a host that already has a home page falls back to its NAME\n * (`Home` → `home`), and only then to numeric suffixes. First claimant wins\n * the root; everybody after it gets a real path of their own.\n *\n * Mutates `usedSlugs` with whatever it hands out.\n */\nexport function resolveTemplateSlug(input: {\n /** Preferred address as authored — `'/'` (or `SCREEN_ROOT_PATH`) for home. */\n slug?: string\n /** Falls back to a slug of this when no address is authored. */\n displayName: string\n /** Paths already taken. Mutated with whatever this page claims. */\n usedSlugs: Set<string>\n}): { slug: string; requestedSlug: string } {\n const { usedSlugs } = input\n // `normalizeScreenSlug` returns undefined for anything that sanitizes away\n // (`''`, `'###'`) — the caller decides what that means, and here it means\n // \"no address authored\", so the display name answers instead.\n const named = normalizeScreenSlug(input.displayName) ?? 'page'\n const requested = normalizeScreenSlug(input.slug) ?? named\n\n const base =\n requested === SCREEN_ROOT_PATH && usedSlugs.has(SCREEN_ROOT_PATH)\n ? named\n : requested\n let slug = base\n let attempt = 2\n while (usedSlugs.has(slug)) slug = `${base}-${attempt++}`\n usedSlugs.add(slug)\n return { slug, requestedSlug: requested }\n}\n\n/**\n * Gives a multi-page bundle a home page when nothing in it claims one\n * (AGL-1575).\n *\n * A starter is somebody's whole site, and a site whose root 404s is broken in\n * the one place every visitor lands. When no screen in the bundle asks for\n * `SCREEN_ROOT_PATH` and the host has no root route yet, the FIRST screen —\n * authored order, which is the order the author meant them to be created in —\n * takes it. That also repairs starters materialized before this fix, whose\n * seeded documents dropped the home screen's `slug: ''` on the floor.\n *\n * Deliberately NOT applied to the single-template \"Use template\" dialog: the\n * user types an address there, and silently re-pointing an explicit answer at\n * the root would be a worse surprise than a site that needs a home page.\n * Deliberately conditional on the host having no root: it never moves a live\n * home page, which is the same invariant `resolveTemplateSlug` protects.\n */\nexport function withBundleRootScreen<T extends { slug?: string }>(\n screens: readonly T[],\n usedSlugs: Iterable<string>,\n): T[] {\n const list = [...screens]\n if (!list.length) return list\n if ([...usedSlugs].includes(SCREEN_ROOT_PATH)) return list\n const claimsRoot = list.some(\n (screen) => normalizeScreenSlug(screen.slug) === SCREEN_ROOT_PATH,\n )\n if (claimsRoot) return list\n list[0] = { ...list[0], slug: SCREEN_ROOT_PATH }\n return list\n}\n\n\n/**\n * {@link resolveTemplateSlug} over a list instead of a `Set` it mutates: the\n * address a page claims, and the paths taken once it has. For a caller that\n * cannot hold a `Set` (the native apps) and for the recorded cases.\n */\nexport function resolveTemplateSlugAmong(input: {\n slug?: string\n displayName: string\n taken: readonly string[]\n}): { slug: string; requestedSlug: string; taken: string[] } {\n const used = new Set(input.taken)\n const { slug, requestedSlug } = resolveTemplateSlug({\n slug: input.slug,\n displayName: input.displayName,\n usedSlugs: used,\n })\n return { slug, requestedSlug, taken: [...used] }\n}\n"],"names":["normalizeScreenSlug","reservedScreenRouteMessage","reservedScreenRouteSegment","SCREEN_ROOT_PATH","SCREEN_SLUG_PATH_SEPARATOR_MESSAGE","screenSlugHasPathSeparator","templateScreenAddressRefusal","input","slug","requested","displayName","reserved","undefined","resolveTemplateSlug","usedSlugs","named","base","has","attempt","add","requestedSlug","withBundleRootScreen","screens","list","length","includes","claimsRoot","some","screen","resolveTemplateSlugAmong","used","Set","taken"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GACD,SACEA,mBAAmB,EACnBC,0BAA0B,EAC1BC,0BAA0B,EAC1BC,gBAAgB,EAChBC,kCAAkC,EAClCC,0BAA0B,QACrB,mBAAe;AAEtB;;;;CAIC,GAED;;;;;;;;;;;;;;;;;;;;;CAqBC,GACD,OAAO,SAASC,6BAA6BC,KAG5C;QAKGP,MAAAA;IAJF,IAAIK,2BAA2BE,MAAMC,IAAI,GAAG;QAC1C,OAAOJ;IACT;IACA,MAAMK,aACJT,QAAAA,uBAAAA,oBAAoBO,MAAMC,IAAI,aAA9BR,uBACAA,oBAAoBO,MAAMG,WAAW,aADrCV,OAEA;IACF,MAAMW,WAAWT,2BAA2BO;IAC5C,OAAOE,WAAWV,2BAA2BU,YAAYC;AAC3D;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BC,GACD,OAAO,SAASC,oBAAoBN,KAOnC;QAKeP,sBACIA;IALlB,MAAM,EAAEc,SAAS,EAAE,GAAGP;IACtB,2EAA2E;IAC3E,0EAA0E;IAC1E,8DAA8D;IAC9D,MAAMQ,SAAQf,uBAAAA,oBAAoBO,MAAMG,WAAW,aAArCV,uBAA0C;IACxD,MAAMS,aAAYT,wBAAAA,oBAAoBO,MAAMC,IAAI,aAA9BR,wBAAmCe;IAErD,MAAMC,OACJP,cAAcN,oBAAoBW,UAAUG,GAAG,CAACd,oBAC5CY,QACAN;IACN,IAAID,OAAOQ;IACX,IAAIE,UAAU;IACd,MAAOJ,UAAUG,GAAG,CAACT,MAAOA,OAAO,GAAGQ,KAAK,CAAC,EAAEE,WAAW;IACzDJ,UAAUK,GAAG,CAACX;IACd,OAAO;QAAEA;QAAMY,eAAeX;IAAU;AAC1C;AAEA;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,SAASY,qBACdC,OAAqB,EACrBR,SAA2B;IAE3B,MAAMS,OAAO;WAAID;KAAQ;IACzB,IAAI,CAACC,KAAKC,MAAM,EAAE,OAAOD;IACzB,IAAI;WAAIT;KAAU,CAACW,QAAQ,CAACtB,mBAAmB,OAAOoB;IACtD,MAAMG,aAAaH,KAAKI,IAAI,CAC1B,CAACC,SAAW5B,oBAAoB4B,OAAOpB,IAAI,MAAML;IAEnD,IAAIuB,YAAY,OAAOH;IACvBA,IAAI,CAAC,EAAE,GAAG,aAAKA,IAAI,CAAC,EAAE;QAAEf,MAAML;;IAC9B,OAAOoB;AACT;AAGA;;;;CAIC,GACD,OAAO,SAASM,yBAAyBtB,KAIxC;IACC,MAAMuB,OAAO,IAAIC,IAAIxB,MAAMyB,KAAK;IAChC,MAAM,EAAExB,IAAI,EAAEY,aAAa,EAAE,GAAGP,oBAAoB;QAClDL,MAAMD,MAAMC,IAAI;QAChBE,aAAaH,MAAMG,WAAW;QAC9BI,WAAWgB;IACb;IACA,OAAO;QAAEtB;QAAMY;QAAeY,OAAO;eAAIF;SAAK;IAAC;AACjD"}
|