@aglyn/tenant-runtime 1.0.0-beta.143
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/LICENSE +201 -0
- package/README.md +26 -0
- package/package.json +39 -0
- package/src/index.d.ts +21 -0
- package/src/index.js +22 -0
- package/src/index.js.map +1 -0
- package/src/lib/apply-publish-schedule.d.ts +34 -0
- package/src/lib/apply-publish-schedule.js +285 -0
- package/src/lib/apply-publish-schedule.js.map +1 -0
- package/src/lib/assign-contact-owner.d.ts +162 -0
- package/src/lib/assign-contact-owner.js +322 -0
- package/src/lib/assign-contact-owner.js.map +1 -0
- package/src/lib/associate-company-by-domain.d.ts +70 -0
- package/src/lib/associate-company-by-domain.js +150 -0
- package/src/lib/associate-company-by-domain.js.map +1 -0
- package/src/lib/author-page-nodes.d.ts +36 -0
- package/src/lib/author-page-nodes.js +296 -0
- package/src/lib/author-page-nodes.js.map +1 -0
- package/src/lib/built-in-page-layout.d.ts +48 -0
- package/src/lib/built-in-page-layout.js +107 -0
- package/src/lib/built-in-page-layout.js.map +1 -0
- package/src/lib/capture-host-contact.d.ts +95 -0
- package/src/lib/capture-host-contact.js +137 -0
- package/src/lib/capture-host-contact.js.map +1 -0
- package/src/lib/collection-fallback-nodes.d.ts +90 -0
- package/src/lib/collection-fallback-nodes.js +602 -0
- package/src/lib/collection-fallback-nodes.js.map +1 -0
- package/src/lib/compose-author-page.d.ts +109 -0
- package/src/lib/compose-author-page.js +225 -0
- package/src/lib/compose-author-page.js.map +1 -0
- package/src/lib/compose-collection-page.d.ts +94 -0
- package/src/lib/compose-collection-page.js +256 -0
- package/src/lib/compose-collection-page.js.map +1 -0
- package/src/lib/compose-screen-nodes.d.ts +156 -0
- package/src/lib/compose-screen-nodes.js +547 -0
- package/src/lib/compose-screen-nodes.js.map +1 -0
- package/src/lib/compose-search-page.d.ts +39 -0
- package/src/lib/compose-search-page.js +57 -0
- package/src/lib/compose-search-page.js.map +1 -0
- package/src/lib/convert-host-lead.d.ts +189 -0
- package/src/lib/convert-host-lead.js +443 -0
- package/src/lib/convert-host-lead.js.map +1 -0
- package/src/lib/defer-lazy-panels.d.ts +58 -0
- package/src/lib/defer-lazy-panels.js +171 -0
- package/src/lib/defer-lazy-panels.js.map +1 -0
- package/src/lib/element-animation-assets.d.ts +62 -0
- package/src/lib/element-animation-assets.js +290 -0
- package/src/lib/element-animation-assets.js.map +1 -0
- package/src/lib/emit-host-event.d.ts +29 -0
- package/src/lib/emit-host-event.js +30 -0
- package/src/lib/emit-host-event.js.map +1 -0
- package/src/lib/enrich-gated-page.d.ts +67 -0
- package/src/lib/enrich-gated-page.js +134 -0
- package/src/lib/enrich-gated-page.js.map +1 -0
- package/src/lib/entry-link-routes.d.ts +61 -0
- package/src/lib/entry-link-routes.js +169 -0
- package/src/lib/entry-link-routes.js.map +1 -0
- package/src/lib/get-author-content.d.ts +119 -0
- package/src/lib/get-author-content.js +288 -0
- package/src/lib/get-author-content.js.map +1 -0
- package/src/lib/get-collection-content.d.ts +290 -0
- package/src/lib/get-collection-content.js +565 -0
- package/src/lib/get-collection-content.js.map +1 -0
- package/src/lib/get-components.d.ts +24 -0
- package/src/lib/get-components.js +102 -0
- package/src/lib/get-components.js.map +1 -0
- package/src/lib/get-datasets.d.ts +45 -0
- package/src/lib/get-datasets.js +169 -0
- package/src/lib/get-datasets.js.map +1 -0
- package/src/lib/get-forms.d.ts +24 -0
- package/src/lib/get-forms.js +116 -0
- package/src/lib/get-forms.js.map +1 -0
- package/src/lib/get-layout-version.d.ts +26 -0
- package/src/lib/get-layout-version.js +99 -0
- package/src/lib/get-layout-version.js.map +1 -0
- package/src/lib/get-media-asset-facts.d.ts +74 -0
- package/src/lib/get-media-asset-facts.js +116 -0
- package/src/lib/get-media-asset-facts.js.map +1 -0
- package/src/lib/get-plugin-installs.d.ts +29 -0
- package/src/lib/get-plugin-installs.js +94 -0
- package/src/lib/get-plugin-installs.js.map +1 -0
- package/src/lib/get-screen-version.d.ts +27 -0
- package/src/lib/get-screen-version.js +66 -0
- package/src/lib/get-screen-version.js.map +1 -0
- package/src/lib/get-screen.d.ts +39 -0
- package/src/lib/get-screen.js +126 -0
- package/src/lib/get-screen.js.map +1 -0
- package/src/lib/get-variables.d.ts +41 -0
- package/src/lib/get-variables.js +128 -0
- package/src/lib/get-variables.js.map +1 -0
- package/src/lib/get-video-asset-facts.d.ts +22 -0
- package/src/lib/get-video-asset-facts.js +22 -0
- package/src/lib/get-video-asset-facts.js.map +1 -0
- package/src/lib/host-event-listeners.d.ts +83 -0
- package/src/lib/host-event-listeners.js +91 -0
- package/src/lib/host-event-listeners.js.map +1 -0
- package/src/lib/org-permissions.d.ts +63 -0
- package/src/lib/org-permissions.js +187 -0
- package/src/lib/org-permissions.js.map +1 -0
- package/src/lib/repeat-record-pages.d.ts +48 -0
- package/src/lib/repeat-record-pages.js +64 -0
- package/src/lib/repeat-record-pages.js.map +1 -0
- package/src/lib/required-site-plugins.d.ts +114 -0
- package/src/lib/required-site-plugins.js +121 -0
- package/src/lib/required-site-plugins.js.map +1 -0
- package/src/lib/resolve-dataset.d.ts +40 -0
- package/src/lib/resolve-dataset.js +64 -0
- package/src/lib/resolve-dataset.js.map +1 -0
- package/src/lib/search-results-nodes.d.ts +48 -0
- package/src/lib/search-results-nodes.js +302 -0
- package/src/lib/search-results-nodes.js.map +1 -0
- package/src/lib/social-image-facts.d.ts +107 -0
- package/src/lib/social-image-facts.js +133 -0
- package/src/lib/social-image-facts.js.map +1 -0
- package/src/lib/stamp-form-dataset-bindings.d.ts +32 -0
- package/src/lib/stamp-form-dataset-bindings.js +59 -0
- package/src/lib/stamp-form-dataset-bindings.js.map +1 -0
- package/src/lib/template-screens.d.ts +227 -0
- package/src/lib/template-screens.js +364 -0
- package/src/lib/template-screens.js.map +1 -0
|
@@ -0,0 +1,256 @@
|
|
|
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 * as Aglyn from "@aglyn/aglyn/server";
|
|
18
|
+
import buildCollectionFallbackNodes from "./collection-fallback-nodes.js";
|
|
19
|
+
import composeScreenNodes, { composeNodesWithChrome } from "./compose-screen-nodes.js";
|
|
20
|
+
import { resolveBuiltInPageLayoutId } from "./built-in-page-layout.js";
|
|
21
|
+
import getScreen from "./get-screen.js";
|
|
22
|
+
import { collectSocialImageFacts } from "./social-image-facts.js";
|
|
23
|
+
/**
|
|
24
|
+
* Which template screen a collection route renders through (AGL-551):
|
|
25
|
+
* `/{collection}` uses `listScreenId`, `/{collection}/{entry}` uses
|
|
26
|
+
* `entryScreenId` — falling back to the legacy AGL-105 `templateScreenId`
|
|
27
|
+
* so existing blogs keep rendering. `undefined` means no template is set
|
|
28
|
+
* and the designed built-in fallback applies.
|
|
29
|
+
*/ export function resolveCollectionTemplateScreenId(collection, kind) {
|
|
30
|
+
if (kind === 'list') return collection.listScreenId || undefined;
|
|
31
|
+
return collection.entryScreenId || collection.templateScreenId || undefined;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Page-level `{{collection.*}}` tokens for template screens (AGL-551), plus
|
|
35
|
+
* the routed category (AGL-1321) so a list template can name what it is
|
|
36
|
+
* showing — "Guides" rather than a heading that says "Blog" on every filtered
|
|
37
|
+
* URL. Both category tokens resolve to the empty string on the unfiltered
|
|
38
|
+
* listing, which is what makes them safe to bind unconditionally.
|
|
39
|
+
*
|
|
40
|
+
* `{{pagination.*}}` follows the same design (AGL-1386). One static list
|
|
41
|
+
* screen serves the bare listing, every `/page/{n}` and every
|
|
42
|
+
* `/category/{slug}`, so a hand-built pager on it renders identically on all
|
|
43
|
+
* of them: "Older →" on a category that has one page, pointing at a URL that
|
|
44
|
+
* dropped the filter. The URLs come from `collectionPaginationLinks`, which
|
|
45
|
+
* builds them through the shared listing-URL builder (so they carry the
|
|
46
|
+
* category) and resolves the EDGES TO THE EMPTY STRING — no previous page,
|
|
47
|
+
* no `prevUrl`. There is no runtime conditional to hide a link with (the
|
|
48
|
+
* `condition` field on nodes is editor-side field visibility, not a render
|
|
49
|
+
* gate), so the empty string is what makes an unconditional binding correct
|
|
50
|
+
* on every route: a link whose href does not resolve renders as an inert
|
|
51
|
+
* placeholder of the same element (AGL-1268/1357).
|
|
52
|
+
*
|
|
53
|
+
* This SURFACES what the platform already computes — the built-in fallback
|
|
54
|
+
* pager reads the same function, so the two cannot drift.
|
|
55
|
+
*/ export function collectionTokens(collection, category, pagination) {
|
|
56
|
+
var _ref, _ref1;
|
|
57
|
+
// An unpaginated listing is page 1 of 1 — both URLs empty, which reads as
|
|
58
|
+
// the honest "nowhere to page to" rather than a broken link.
|
|
59
|
+
const pager = Aglyn.collectionPaginationLinks(_extends({
|
|
60
|
+
collectionSlug: collection.slug
|
|
61
|
+
}, (category == null ? void 0 : category.slug) ? {
|
|
62
|
+
categorySlug: category.slug
|
|
63
|
+
} : {}, {
|
|
64
|
+
page: pagination == null ? void 0 : pagination.page,
|
|
65
|
+
totalPages: pagination == null ? void 0 : pagination.totalPages
|
|
66
|
+
}));
|
|
67
|
+
return {
|
|
68
|
+
'collection.name': collection.displayName,
|
|
69
|
+
'collection.slug': collection.slug,
|
|
70
|
+
'collection.category': (_ref = category == null ? void 0 : category.name) != null ? _ref : '',
|
|
71
|
+
'collection.categorySlug': (_ref1 = category == null ? void 0 : category.slug) != null ? _ref1 : '',
|
|
72
|
+
'pagination.page': String(pager.page),
|
|
73
|
+
'pagination.totalPages': String(pager.totalPages),
|
|
74
|
+
'pagination.prevUrl': pager.prevUrl,
|
|
75
|
+
'pagination.nextUrl': pager.nextUrl
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Renders a collection route through its designated template screen
|
|
80
|
+
* (AGL-551), the same mechanism as commerce PDP/collection templates: the
|
|
81
|
+
* screen composes through the NORMAL published pipeline — theme, shared
|
|
82
|
+
* layout, reusable components — with `{{entry.*}}`/`{{collection.*}}`
|
|
83
|
+
* tokens substituted and Collection entries blocks expanded. Returns null
|
|
84
|
+
* when no template is designated (or it fails to compose) so the caller
|
|
85
|
+
* falls through to the designed built-in fallback.
|
|
86
|
+
*/ export async function composeCollectionTemplatePage(options) {
|
|
87
|
+
var _templateRes_screen_seo;
|
|
88
|
+
var _templateRes_screen_seo1, _options_host_seo, _options_host, _content_pagination;
|
|
89
|
+
const { hostId, content } = options;
|
|
90
|
+
const collection = content.collection;
|
|
91
|
+
if (!collection) return null;
|
|
92
|
+
const kind = content.entry ? 'entry' : 'list';
|
|
93
|
+
const screenId = resolveCollectionTemplateScreenId(collection, kind);
|
|
94
|
+
if (!screenId) return null;
|
|
95
|
+
// The one read that WANTS a template document (AGL-1400): this is the
|
|
96
|
+
// composition it exists for, with `{{entry.*}}` substituted against the
|
|
97
|
+
// routed entry. Every path-resolving caller leaves the flag off and gets the
|
|
98
|
+
// 404 a template deserves at an address of its own.
|
|
99
|
+
const templateRes = await getScreen({
|
|
100
|
+
hostId,
|
|
101
|
+
screenId,
|
|
102
|
+
allowTemplate: true
|
|
103
|
+
});
|
|
104
|
+
if (!templateRes.screen) return null;
|
|
105
|
+
const entry = content.entry;
|
|
106
|
+
const tokens = entry ? _extends({}, collectionTokens(collection), Aglyn.collectionEntryTokens(entry, collection.slug, collection.categories)) : collectionTokens(collection, content.category, content.pagination);
|
|
107
|
+
// The head's card on this page, in its order: the entry's cover, the
|
|
108
|
+
// template's own image, then the site default (AGL-2850).
|
|
109
|
+
const card = collectSocialImageFacts([
|
|
110
|
+
entry == null ? void 0 : entry.coverImage,
|
|
111
|
+
(_templateRes_screen_seo1 = templateRes.screen.seo) == null ? void 0 : _templateRes_screen_seo1.image,
|
|
112
|
+
(_options_host = options.host) == null ? void 0 : (_options_host_seo = _options_host.seo) == null ? void 0 : _options_host_seo.image
|
|
113
|
+
]);
|
|
114
|
+
const nodes = await composeScreenNodes({
|
|
115
|
+
hostId,
|
|
116
|
+
screenId,
|
|
117
|
+
screen: templateRes.screen,
|
|
118
|
+
socialImages: card.socialImages,
|
|
119
|
+
// The site the template renders for, so its host variables — and its
|
|
120
|
+
// layout's — fill in as they do on every other page (AGL-2883).
|
|
121
|
+
host: options.host,
|
|
122
|
+
tokens,
|
|
123
|
+
// List pages hand their already-fetched entries to the Collection
|
|
124
|
+
// entries block; entry pages carry the routed entry (AGL-582, Related
|
|
125
|
+
// posts) and let blocks fetch entry lists on demand (e.g. a "More
|
|
126
|
+
// posts" section on the article template). The category taxonomy
|
|
127
|
+
// rides along so `{{entry.category}}` resolves inside the blocks.
|
|
128
|
+
collection: entry ? {
|
|
129
|
+
slug: collection.slug,
|
|
130
|
+
entry,
|
|
131
|
+
categories: collection.categories
|
|
132
|
+
} : _extends({
|
|
133
|
+
slug: collection.slug,
|
|
134
|
+
// Already filtered to the routed category (AGL-1321) — the block
|
|
135
|
+
// repeats what the ROUTE resolved, so a designer-pinned
|
|
136
|
+
// `filterCategory` on the block narrows it further rather than
|
|
137
|
+
// fighting it.
|
|
138
|
+
entries: content.entries,
|
|
139
|
+
categories: collection.categories
|
|
140
|
+
}, ((_content_pagination = content.pagination) == null ? void 0 : _content_pagination.page) ? {
|
|
141
|
+
page: content.pagination.page
|
|
142
|
+
} : {}, content.category ? {
|
|
143
|
+
categorySlug: content.category.slug
|
|
144
|
+
} : {}, content.entriesReachedBound ? {
|
|
145
|
+
entriesReachedBound: true
|
|
146
|
+
} : {})
|
|
147
|
+
});
|
|
148
|
+
if (!nodes) return null;
|
|
149
|
+
const screenSeo = (_templateRes_screen_seo = templateRes.screen.seo) != null ? _templateRes_screen_seo : {};
|
|
150
|
+
const seo = entry ? _extends({}, screenSeo, {
|
|
151
|
+
title: entry.seoTitle || entry.title,
|
|
152
|
+
description: entry.seoDescription || entry.excerpt || undefined
|
|
153
|
+
}, entry.coverImage ? {
|
|
154
|
+
image: entry.coverImage,
|
|
155
|
+
imageWidth: undefined,
|
|
156
|
+
imageHeight: undefined,
|
|
157
|
+
imageAlt: entry.coverImageAlt || undefined
|
|
158
|
+
} : {
|
|
159
|
+
image: screenSeo.image || undefined
|
|
160
|
+
}) : //
|
|
161
|
+
// This used to default `title` to `collection.displayName`, which reads
|
|
162
|
+
// like a harmless fallback and is not: it made an authored title
|
|
163
|
+
// indistinguishable from a generated one by the time the head was built.
|
|
164
|
+
// The title rule (AGL-1341) turns on exactly that distinction — an
|
|
165
|
+
// authored title renders VERBATIM, a name joins the site title — so a
|
|
166
|
+
// consumer reading this could only choose between dropping the site
|
|
167
|
+
// title off every untitled list ("Changelog") or ignoring the author's
|
|
168
|
+
// title on every titled one ("Changelog – Acme" over the sentence they
|
|
169
|
+
// wrote). The collection name is still the fallback; it just belongs to
|
|
170
|
+
// the title resolver, as the page's `name`, alongside every other
|
|
171
|
+
// surface's fallback rather than baked into stored SEO here.
|
|
172
|
+
screenSeo;
|
|
173
|
+
return _extends({
|
|
174
|
+
screen: _extends({}, templateRes.screen, {
|
|
175
|
+
seo
|
|
176
|
+
}),
|
|
177
|
+
nodes
|
|
178
|
+
}, card.collected());
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* The designed built-in rendering (AGL-551): when a collection has no
|
|
182
|
+
* template screen, its routes still compose through the site's theme and
|
|
183
|
+
* the host's default shared layout (the home screen's layout) instead of
|
|
184
|
+
* the old unthemed article. Fail-open — any error returns null and the
|
|
185
|
+
* caller keeps the legacy plain rendering.
|
|
186
|
+
*/ export async function composeCollectionFallbackPage(options) {
|
|
187
|
+
const { hostId, host, content } = options;
|
|
188
|
+
const collection = content.collection;
|
|
189
|
+
if (!collection) return null;
|
|
190
|
+
try {
|
|
191
|
+
var _content_entry, _host_seo, _content_pagination;
|
|
192
|
+
// The layout for a page the platform composed rather than the author
|
|
193
|
+
// (AGL-2513). Still the home screen's layout by default — the rule this
|
|
194
|
+
// branch has always followed — but the host can now name a different one,
|
|
195
|
+
// and site search reads the same setting so the two built-in pages of a
|
|
196
|
+
// site cannot end up in different chrome.
|
|
197
|
+
const layoutId = await resolveBuiltInPageLayoutId({
|
|
198
|
+
hostId,
|
|
199
|
+
host
|
|
200
|
+
});
|
|
201
|
+
const screenNodes = buildCollectionFallbackNodes({
|
|
202
|
+
collection,
|
|
203
|
+
entries: content.entries,
|
|
204
|
+
entry: content.entry,
|
|
205
|
+
pagination: content.pagination,
|
|
206
|
+
category: content.category,
|
|
207
|
+
// The cover resolves through `resolveMediaSrc` (AGL-1407), and an
|
|
208
|
+
// org-scoped reference has to name the site asking or a site-restricted
|
|
209
|
+
// asset will not serve.
|
|
210
|
+
hostId
|
|
211
|
+
});
|
|
212
|
+
// The head's card on a page with no template: the entry's cover, then the
|
|
213
|
+
// site default (AGL-2850).
|
|
214
|
+
const card = collectSocialImageFacts([
|
|
215
|
+
(_content_entry = content.entry) == null ? void 0 : _content_entry.coverImage,
|
|
216
|
+
host == null ? void 0 : (_host_seo = host.seo) == null ? void 0 : _host_seo.image
|
|
217
|
+
]);
|
|
218
|
+
const nodes = await composeNodesWithChrome({
|
|
219
|
+
hostId,
|
|
220
|
+
layoutId,
|
|
221
|
+
screenNodes,
|
|
222
|
+
socialImages: card.socialImages,
|
|
223
|
+
// The layout's host variables fill in from this site (AGL-2883).
|
|
224
|
+
host,
|
|
225
|
+
// Entry routes resolve with an EMPTY entries list (the loader only
|
|
226
|
+
// fetched the one entry), so hand the routed entry over and let the
|
|
227
|
+
// Related posts block fetch the list on demand (AGL-582); list
|
|
228
|
+
// routes keep their already-fetched entries. Categories ride along
|
|
229
|
+
// for `categoryId` → name resolution.
|
|
230
|
+
collection: content.entry ? {
|
|
231
|
+
slug: collection.slug,
|
|
232
|
+
entry: content.entry,
|
|
233
|
+
categories: collection.categories
|
|
234
|
+
} : _extends({
|
|
235
|
+
slug: collection.slug,
|
|
236
|
+
entries: content.entries,
|
|
237
|
+
categories: collection.categories
|
|
238
|
+
}, ((_content_pagination = content.pagination) == null ? void 0 : _content_pagination.page) ? {
|
|
239
|
+
page: content.pagination.page
|
|
240
|
+
} : {}, content.category ? {
|
|
241
|
+
categorySlug: content.category.slug
|
|
242
|
+
} : {}, content.entriesReachedBound ? {
|
|
243
|
+
entriesReachedBound: true
|
|
244
|
+
} : {})
|
|
245
|
+
});
|
|
246
|
+
return nodes ? _extends({
|
|
247
|
+
nodes
|
|
248
|
+
}, card.collected()) : null;
|
|
249
|
+
} catch (error) {
|
|
250
|
+
console.error(error);
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
export default composeCollectionTemplatePage;
|
|
255
|
+
|
|
256
|
+
//# sourceMappingURL=compose-collection-page.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/tenant/runtime/src/lib/compose-collection-page.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 */\n\nimport * as Aglyn from '@aglyn/aglyn/server'\nimport buildCollectionFallbackNodes from './collection-fallback-nodes'\nimport composeScreenNodes, {\n composeNodesWithChrome,\n} from './compose-screen-nodes'\nimport { resolveBuiltInPageLayoutId } from './built-in-page-layout'\nimport type { CollectionContent } from './get-collection-content'\nimport getScreen from './get-screen'\nimport { collectSocialImageFacts } from './social-image-facts'\n\ntype CollectionDoc = NonNullable<CollectionContent['collection']>\n\n/**\n * Which template screen a collection route renders through (AGL-551):\n * `/{collection}` uses `listScreenId`, `/{collection}/{entry}` uses\n * `entryScreenId` — falling back to the legacy AGL-105 `templateScreenId`\n * so existing blogs keep rendering. `undefined` means no template is set\n * and the designed built-in fallback applies.\n */\nexport function resolveCollectionTemplateScreenId(\n collection: Pick<\n CollectionDoc,\n 'listScreenId' | 'entryScreenId' | 'templateScreenId'\n >,\n kind: 'list' | 'entry',\n): string | undefined {\n if (kind === 'list') return collection.listScreenId || undefined\n return collection.entryScreenId || collection.templateScreenId || undefined\n}\n\n/**\n * Page-level `{{collection.*}}` tokens for template screens (AGL-551), plus\n * the routed category (AGL-1321) so a list template can name what it is\n * showing — \"Guides\" rather than a heading that says \"Blog\" on every filtered\n * URL. Both category tokens resolve to the empty string on the unfiltered\n * listing, which is what makes them safe to bind unconditionally.\n *\n * `{{pagination.*}}` follows the same design (AGL-1386). One static list\n * screen serves the bare listing, every `/page/{n}` and every\n * `/category/{slug}`, so a hand-built pager on it renders identically on all\n * of them: \"Older →\" on a category that has one page, pointing at a URL that\n * dropped the filter. The URLs come from `collectionPaginationLinks`, which\n * builds them through the shared listing-URL builder (so they carry the\n * category) and resolves the EDGES TO THE EMPTY STRING — no previous page,\n * no `prevUrl`. There is no runtime conditional to hide a link with (the\n * `condition` field on nodes is editor-side field visibility, not a render\n * gate), so the empty string is what makes an unconditional binding correct\n * on every route: a link whose href does not resolve renders as an inert\n * placeholder of the same element (AGL-1268/1357).\n *\n * This SURFACES what the platform already computes — the built-in fallback\n * pager reads the same function, so the two cannot drift.\n */\nexport function collectionTokens(\n collection: Pick<CollectionDoc, 'displayName' | 'slug'>,\n category?: CollectionContent['category'],\n pagination?: CollectionContent['pagination'],\n): Record<string, string> {\n // An unpaginated listing is page 1 of 1 — both URLs empty, which reads as\n // the honest \"nowhere to page to\" rather than a broken link.\n const pager = Aglyn.collectionPaginationLinks({\n collectionSlug: collection.slug,\n ...(category?.slug ? { categorySlug: category.slug } : {}),\n page: pagination?.page,\n totalPages: pagination?.totalPages,\n })\n return {\n 'collection.name': collection.displayName,\n 'collection.slug': collection.slug,\n 'collection.category': category?.name ?? '',\n 'collection.categorySlug': category?.slug ?? '',\n 'pagination.page': String(pager.page),\n 'pagination.totalPages': String(pager.totalPages),\n 'pagination.prevUrl': pager.prevUrl,\n 'pagination.nextUrl': pager.nextUrl,\n }\n}\n\nexport interface ComposedCollectionPage {\n /** Template screen doc with the entry/collection SEO merged in. */\n screen: Record<string, any>\n nodes: Record<string, any>\n /**\n * The current pair of each asset the head's card may name (AGL-2850): the\n * entry's cover, the template's image, the site default. Read in the\n * composition's batch, and absent when it answered for none of them.\n */\n socialImageFacts?: Aglyn.SocialImageAssetFacts\n}\n\n/**\n * Renders a collection route through its designated template screen\n * (AGL-551), the same mechanism as commerce PDP/collection templates: the\n * screen composes through the NORMAL published pipeline — theme, shared\n * layout, reusable components — with `{{entry.*}}`/`{{collection.*}}`\n * tokens substituted and Collection entries blocks expanded. Returns null\n * when no template is designated (or it fails to compose) so the caller\n * falls through to the designed built-in fallback.\n */\nexport async function composeCollectionTemplatePage(options: {\n hostId: string\n content: CollectionContent\n /**\n * The site, whose default card the head falls back to after the entry's\n * cover and the template's image (AGL-2850). Its document is read in this\n * page's batch with theirs.\n */\n host?: Aglyn.AglynHost | null\n}): Promise<ComposedCollectionPage | null> {\n const { hostId, content } = options\n const collection = content.collection\n if (!collection) return null\n const kind = content.entry ? 'entry' : 'list'\n const screenId = resolveCollectionTemplateScreenId(collection, kind)\n if (!screenId) return null\n\n // The one read that WANTS a template document (AGL-1400): this is the\n // composition it exists for, with `{{entry.*}}` substituted against the\n // routed entry. Every path-resolving caller leaves the flag off and gets the\n // 404 a template deserves at an address of its own.\n const templateRes = await getScreen({ hostId, screenId, allowTemplate: true })\n if (!templateRes.screen) return null\n\n const entry = content.entry\n const tokens = entry\n ? {\n ...collectionTokens(collection),\n // Category names resolve against the collection's taxonomy\n // (AGL-582): `categoryId` lookup first, legacy string fallback.\n ...Aglyn.collectionEntryTokens(\n entry,\n collection.slug,\n collection.categories,\n ),\n }\n : collectionTokens(collection, content.category, content.pagination)\n // The head's card on this page, in its order: the entry's cover, the\n // template's own image, then the site default (AGL-2850).\n const card = collectSocialImageFacts([\n entry?.coverImage,\n (templateRes.screen as any).seo?.image,\n options.host?.seo?.image,\n ])\n const nodes = await composeScreenNodes({\n hostId,\n screenId,\n screen: templateRes.screen,\n socialImages: card.socialImages,\n // The site the template renders for, so its host variables — and its\n // layout's — fill in as they do on every other page (AGL-2883).\n host: options.host,\n tokens,\n // List pages hand their already-fetched entries to the Collection\n // entries block; entry pages carry the routed entry (AGL-582, Related\n // posts) and let blocks fetch entry lists on demand (e.g. a \"More\n // posts\" section on the article template). The category taxonomy\n // rides along so `{{entry.category}}` resolves inside the blocks.\n collection: entry\n ? { slug: collection.slug, entry, categories: collection.categories }\n : {\n slug: collection.slug,\n // Already filtered to the routed category (AGL-1321) — the block\n // repeats what the ROUTE resolved, so a designer-pinned\n // `filterCategory` on the block narrows it further rather than\n // fighting it.\n entries: content.entries,\n categories: collection.categories,\n ...(content.pagination?.page\n ? { page: content.pagination.page }\n : {}),\n ...(content.category ? { categorySlug: content.category.slug } : {}),\n // The read's own bound (AGL-1516), which has to travel WITH the\n // filtered entries above: once `content.entries` has been narrowed\n // to a category, nothing downstream can tell a complete read from a\n // truncated one by counting it.\n ...(content.entriesReachedBound\n ? { entriesReachedBound: true }\n : {}),\n },\n })\n if (!nodes) return null\n\n const screenSeo = (templateRes.screen as any).seo ?? {}\n const seo = entry\n ? // Entry metadata drives the head (AGL-117 merge; AGL-582 overrides).\n {\n ...screenSeo,\n title: entry.seoTitle || entry.title,\n description: entry.seoDescription || entry.excerpt || undefined,\n // The image and its companions move as ONE group (AGL-2417). The\n // spread above carries the SCREEN's `imageWidth`/`imageHeight`/\n // `imageAlt`, so an entry that supplies its own cover was describing\n // it with the screen default's size and — once alts existed — with\n // the screen default's DESCRIPTION: a sentence about a picture this\n // card does not show, delivered to the reader least able to check.\n // When the entry wins, its own companions win with it.\n ...(entry.coverImage\n ? {\n image: entry.coverImage,\n imageWidth: undefined,\n imageHeight: undefined,\n imageAlt: entry.coverImageAlt || undefined,\n }\n : { image: screenSeo.image || undefined }),\n }\n : // A LIST passes its screen's own SEO through UNTOUCHED (AGL-1345).\n //\n // This used to default `title` to `collection.displayName`, which reads\n // like a harmless fallback and is not: it made an authored title\n // indistinguishable from a generated one by the time the head was built.\n // The title rule (AGL-1341) turns on exactly that distinction — an\n // authored title renders VERBATIM, a name joins the site title — so a\n // consumer reading this could only choose between dropping the site\n // title off every untitled list (\"Changelog\") or ignoring the author's\n // title on every titled one (\"Changelog – Acme\" over the sentence they\n // wrote). The collection name is still the fallback; it just belongs to\n // the title resolver, as the page's `name`, alongside every other\n // surface's fallback rather than baked into stored SEO here.\n screenSeo\n return {\n screen: { ...(templateRes.screen as any), seo },\n nodes,\n ...card.collected(),\n }\n}\n\n/**\n * The designed built-in rendering (AGL-551): when a collection has no\n * template screen, its routes still compose through the site's theme and\n * the host's default shared layout (the home screen's layout) instead of\n * the old unthemed article. Fail-open — any error returns null and the\n * caller keeps the legacy plain rendering.\n */\nexport async function composeCollectionFallbackPage(options: {\n hostId: string\n host: Aglyn.AglynHost\n content: CollectionContent\n}): Promise<Omit<ComposedCollectionPage, 'screen'> | null> {\n const { hostId, host, content } = options\n const collection = content.collection\n if (!collection) return null\n try {\n // The layout for a page the platform composed rather than the author\n // (AGL-2513). Still the home screen's layout by default — the rule this\n // branch has always followed — but the host can now name a different one,\n // and site search reads the same setting so the two built-in pages of a\n // site cannot end up in different chrome.\n const layoutId = await resolveBuiltInPageLayoutId({ hostId, host })\n const screenNodes = buildCollectionFallbackNodes({\n collection,\n entries: content.entries,\n entry: content.entry,\n pagination: content.pagination,\n category: content.category,\n // The cover resolves through `resolveMediaSrc` (AGL-1407), and an\n // org-scoped reference has to name the site asking or a site-restricted\n // asset will not serve.\n hostId,\n })\n // The head's card on a page with no template: the entry's cover, then the\n // site default (AGL-2850).\n const card = collectSocialImageFacts([\n content.entry?.coverImage,\n host?.seo?.image,\n ])\n const nodes = await composeNodesWithChrome({\n hostId,\n layoutId,\n screenNodes,\n socialImages: card.socialImages,\n // The layout's host variables fill in from this site (AGL-2883).\n host,\n // Entry routes resolve with an EMPTY entries list (the loader only\n // fetched the one entry), so hand the routed entry over and let the\n // Related posts block fetch the list on demand (AGL-582); list\n // routes keep their already-fetched entries. Categories ride along\n // for `categoryId` → name resolution.\n collection: content.entry\n ? {\n slug: collection.slug,\n entry: content.entry,\n categories: collection.categories,\n }\n : {\n slug: collection.slug,\n entries: content.entries,\n categories: collection.categories,\n ...(content.pagination?.page\n ? { page: content.pagination.page }\n : {}),\n ...(content.category\n ? { categorySlug: content.category.slug }\n : {}),\n // Same fact, same reason as the template path above (AGL-1516).\n ...(content.entriesReachedBound\n ? { entriesReachedBound: true }\n : {}),\n },\n })\n return nodes ? { nodes, ...card.collected() } : null\n } catch (error) {\n console.error(error)\n return null\n }\n}\n\nexport default composeCollectionTemplatePage\n"],"names":["Aglyn","buildCollectionFallbackNodes","composeScreenNodes","composeNodesWithChrome","resolveBuiltInPageLayoutId","getScreen","collectSocialImageFacts","resolveCollectionTemplateScreenId","collection","kind","listScreenId","undefined","entryScreenId","templateScreenId","collectionTokens","category","pagination","pager","collectionPaginationLinks","collectionSlug","slug","categorySlug","page","totalPages","displayName","name","String","prevUrl","nextUrl","composeCollectionTemplatePage","options","content","hostId","entry","screenId","templateRes","allowTemplate","screen","tokens","collectionEntryTokens","categories","card","coverImage","seo","image","host","nodes","socialImages","entries","entriesReachedBound","screenSeo","title","seoTitle","description","seoDescription","excerpt","imageWidth","imageHeight","imageAlt","coverImageAlt","collected","composeCollectionFallbackPage","layoutId","screenNodes","error","console"],"mappings":"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"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import * as Aglyn from '@aglyn/aglyn/server';
|
|
18
|
+
import { type ComposeSocialImages } from './social-image-facts';
|
|
19
|
+
/**
|
|
20
|
+
* Content-collection context for a compose (AGL-551): the collection the
|
|
21
|
+
* route resolved (list/entry template screens). `entries` rides along when
|
|
22
|
+
* the route already fetched them (list pages); blocks bound to other
|
|
23
|
+
* collections — or to this one when `entries` is absent — fetch on demand.
|
|
24
|
+
*/
|
|
25
|
+
export interface ComposeCollectionContext {
|
|
26
|
+
slug: string;
|
|
27
|
+
entries?: Aglyn.CollectionEntryRecord[];
|
|
28
|
+
/**
|
|
29
|
+
* The entry being rendered (AGL-582, entry-template screens / entry
|
|
30
|
+
* fallback) — the Related posts block resolves against it.
|
|
31
|
+
*/
|
|
32
|
+
entry?: Aglyn.CollectionEntryRecord | null;
|
|
33
|
+
/**
|
|
34
|
+
* The routed collection's category taxonomy (AGL-582): entry
|
|
35
|
+
* `categoryId`s resolve to display names against it during expansion.
|
|
36
|
+
*/
|
|
37
|
+
categories?: Aglyn.CollectionCategory[];
|
|
38
|
+
/**
|
|
39
|
+
* The list page the URL asked for (AGL-1321). Fills in an entries block
|
|
40
|
+
* that declares `perPage` but no `page` — design time cannot know which
|
|
41
|
+
* page a visitor is on.
|
|
42
|
+
*/
|
|
43
|
+
page?: number;
|
|
44
|
+
/**
|
|
45
|
+
* The category segment the URL filtered on (AGL-1321); marks the current
|
|
46
|
+
* pill in a Category Pills block. `entries` arrives already filtered.
|
|
47
|
+
*/
|
|
48
|
+
categorySlug?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Whether the read behind `entries` stopped at its `.limit()` (AGL-1516).
|
|
51
|
+
* Travels with the entries because the search boxes downstream have to say
|
|
52
|
+
* what they actually searched, and — with the liveness gate and the route's
|
|
53
|
+
* category filter both in between — `entries.length` no longer tells them.
|
|
54
|
+
*/
|
|
55
|
+
entriesReachedBound?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Whether {@link slug} is a cache KEY rather than an address (AGL-2524).
|
|
58
|
+
*
|
|
59
|
+
* The author page mixes collections, and the compose pipeline keys entry
|
|
60
|
+
* sources by collection slug — so it hands over a synthetic one
|
|
61
|
+
* (`AUTHOR_ENTRIES_SOURCE_SLUG`) with its entries already in hand. That is
|
|
62
|
+
* harmless for the entries block, whose every row carries its OWN
|
|
63
|
+
* `collectionSlug` and builds `entry.url` from it, and for the search box,
|
|
64
|
+
* whose index is built the same way.
|
|
65
|
+
*
|
|
66
|
+
* It is NOT harmless for anything that builds a URL from the source's slug
|
|
67
|
+
* itself. Set this and such a block resolves nothing rather than pointing
|
|
68
|
+
* readers at `/{synthetic}/…`.
|
|
69
|
+
*/
|
|
70
|
+
routeless?: boolean;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Shared post-version composition (AGL-551, extracted from
|
|
74
|
+
* `composeScreenNodes`): layout chrome, reusable components, repeatables,
|
|
75
|
+
* collection entries, bindings, function definitions, plugin installs,
|
|
76
|
+
* named tokens, denormalize, and last the current facts of each placed image
|
|
77
|
+
* and film, and of the social card the page is shared as. The screen path and
|
|
78
|
+
* the collection-fallback
|
|
79
|
+
* path (which has no screen doc) build identical trees through this one
|
|
80
|
+
* pipeline.
|
|
81
|
+
*/
|
|
82
|
+
export declare function composeNodesWithChrome(options: {
|
|
83
|
+
hostId: string;
|
|
84
|
+
/**
|
|
85
|
+
* The layout binding, or a PROMISE of it.
|
|
86
|
+
*
|
|
87
|
+
* The unresolved form exists for the same reason `screenNodes` accepts one
|
|
88
|
+
* (AGL-1428): the binding may live on the version document (key-present
|
|
89
|
+
* wins over the screen's), and awaiting the version before this call would
|
|
90
|
+
* put every host-scoped read back behind it. Only the layout-chain walk
|
|
91
|
+
* consumes the binding, so it alone waits; the rest of the chrome bundle
|
|
92
|
+
* still starts immediately.
|
|
93
|
+
*/
|
|
94
|
+
layoutId?: string | null | Promise<string | null | undefined>;
|
|
95
|
+
/**
|
|
96
|
+
* The screen's values for the properties of the layouts it renders inside
|
|
97
|
+
* (AGL-2893), keyed by layout id — or a PROMISE of them, for the reason
|
|
98
|
+
* `layoutId` accepts one: they live on the version document beside the
|
|
99
|
+
* binding. Absent, every layout renders its properties' defaults.
|
|
100
|
+
*/
|
|
101
|
+
layoutPropValues?: Aglyn.AglynScreenVersion['layoutPropValues'] | null | Promise<Aglyn.AglynScreenVersion['layoutPropValues'] | null | undefined>;
|
|
102
|
+
/**
|
|
103
|
+
* The screen's own nodes, or a PROMISE of them (AGL-1428).
|
|
104
|
+
*
|
|
105
|
+
* Accepting the unresolved form is what lets `composeScreenNodes` hand the
|
|
106
|
+
* version read over before it has finished, so the host-scoped reads below
|
|
107
|
+
* — none of which look at these nodes — overlap it instead of queueing
|
|
108
|
+
* behind it. Passing a resolved value behaves exactly as before, which is
|
|
109
|
+
* what every other caller does.
|
|
110
|
+
*/
|
|
111
|
+
screenNodes: Record<string, any> | Promise<Record<string, any>>;
|
|
112
|
+
/** Entry-template tokens (AGL-105) substituted before denormalize. */
|
|
113
|
+
tokens?: Record<string, string>;
|
|
114
|
+
/** Routed content collection (AGL-551) for Collection entries blocks. */
|
|
115
|
+
collection?: ComposeCollectionContext;
|
|
116
|
+
/**
|
|
117
|
+
* The host document, for `host.*` tokens (AGL-1022).
|
|
118
|
+
*
|
|
119
|
+
* Passed in rather than read here: every caller already holds it, and
|
|
120
|
+
* `composeScreenNodes` was the largest single render phase before AGL-1225
|
|
121
|
+
* cut it to one round trip — adding a read back would spend that win on
|
|
122
|
+
* data we already have in hand.
|
|
123
|
+
*/
|
|
124
|
+
host?: Aglyn.HostTokenSource | null;
|
|
125
|
+
/**
|
|
126
|
+
* The social card the head shares this page as (AGL-2850). The documents of
|
|
127
|
+
* the assets it names are read in the same batch as the images and films
|
|
128
|
+
* the tree places.
|
|
129
|
+
*/
|
|
130
|
+
socialImages?: ComposeSocialImages;
|
|
131
|
+
}): Promise<Record<string, any>>;
|
|
132
|
+
/**
|
|
133
|
+
* Full published-render composition for one screen (extracted for AGL-87 so
|
|
134
|
+
* the SSG path and the password-unlock API build identical trees): applies
|
|
135
|
+
* a due publish schedule, loads the version, composes the shared layout
|
|
136
|
+
* chrome, grafts reusable components, and denormalizes.
|
|
137
|
+
*/
|
|
138
|
+
export declare function composeScreenNodes(options: {
|
|
139
|
+
hostId: string;
|
|
140
|
+
screenId: string;
|
|
141
|
+
screen: Aglyn.AglynScreen;
|
|
142
|
+
/** Entry-template tokens (AGL-105) substituted before denormalize. */
|
|
143
|
+
tokens?: Record<string, string>;
|
|
144
|
+
/** Routed content collection (AGL-551) for Collection entries blocks. */
|
|
145
|
+
collection?: ComposeCollectionContext;
|
|
146
|
+
/**
|
|
147
|
+
* Compose a specific version instead of the published one (AGL-253):
|
|
148
|
+
* experiment variants point at versions; schedules don't apply.
|
|
149
|
+
*/
|
|
150
|
+
versionId?: string;
|
|
151
|
+
/** The host document, for `host.*` tokens (AGL-1022). */
|
|
152
|
+
host?: Aglyn.HostTokenSource | null;
|
|
153
|
+
/** The social card the head shares this page as (AGL-2850). */
|
|
154
|
+
socialImages?: ComposeSocialImages;
|
|
155
|
+
}): Promise<Record<string, any> | null>;
|
|
156
|
+
export default composeScreenNodes;
|