@aglyn/plugins-mui 1.0.0-beta.215 → 1.0.0-beta.216
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 +7 -7
- package/src/lib/components/collection-author-links.d.ts +41 -0
- package/src/lib/components/collection-author-links.js +135 -0
- package/src/lib/components/collection-author-links.js.map +1 -0
- package/src/lib/components/collection-author-profile.d.ts +67 -0
- package/src/lib/components/collection-author-profile.js +269 -0
- package/src/lib/components/collection-author-profile.js.map +1 -0
- package/src/lib/components/collection-categories.d.ts +52 -0
- package/src/lib/components/collection-categories.js +163 -0
- package/src/lib/components/collection-categories.js.map +1 -0
- package/src/lib/components/collection-common.d.ts +104 -0
- package/src/lib/components/collection-common.js +95 -0
- package/src/lib/components/collection-common.js.map +1 -0
- package/src/lib/components/collection-entries.d.ts +137 -0
- package/src/lib/components/collection-entries.js +358 -0
- package/src/lib/components/collection-entries.js.map +1 -0
- package/src/lib/components/collection-entry-author.d.ts +81 -0
- package/src/lib/components/collection-entry-author.js +259 -0
- package/src/lib/components/collection-entry-author.js.map +1 -0
- package/src/lib/components/collection-entry-body.d.ts +44 -0
- package/src/lib/components/collection-entry-body.js +328 -0
- package/src/lib/components/collection-entry-body.js.map +1 -0
- package/src/lib/components/collection-entry-meta.d.ts +88 -0
- package/src/lib/components/collection-entry-meta.js +331 -0
- package/src/lib/components/collection-entry-meta.js.map +1 -0
- package/src/lib/components/collection-presets.d.ts +18 -0
- package/src/lib/components/collection-presets.js +328 -0
- package/src/lib/components/collection-presets.js.map +1 -0
- package/src/lib/components/collection-related.d.ts +101 -0
- package/src/lib/components/collection-related.js +536 -0
- package/src/lib/components/collection-related.js.map +1 -0
- package/src/lib/components/collection-search-box.d.ts +92 -0
- package/src/lib/components/collection-search-box.js +334 -0
- package/src/lib/components/collection-search-box.js.map +1 -0
- package/src/lib/components/collection-search-fuse.d.ts +28 -0
- package/src/lib/components/collection-search-fuse.js +28 -0
- package/src/lib/components/collection-search-fuse.js.map +1 -0
- package/src/lib/components/collection-search.d.ts +62 -0
- package/src/lib/components/collection-search.js +140 -0
- package/src/lib/components/collection-search.js.map +1 -0
- package/src/lib/components/collection-share.d.ts +31 -0
- package/src/lib/components/collection-share.js +154 -0
- package/src/lib/components/collection-share.js.map +1 -0
- package/src/lib/components/collection.d.ts +19 -497
- package/src/lib/components/collection.js +19 -2982
- package/src/lib/components/collection.js.map +1 -1
- package/src/lib/components/image-list.d.ts +2 -2
- package/src/lib/components/image-list.js +4 -6
- package/src/lib/components/image-list.js.map +1 -1
- package/src/lib/element-tier.d.ts +9 -1
- package/src/lib/element-tier.js +12 -1
- package/src/lib/element-tier.js.map +1 -1
- package/src/lib/plugin.js +20 -12
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/utils/to-count.d.ts +25 -0
- package/src/lib/utils/to-count.js +30 -0
- package/src/lib/utils/to-count.js.map +1 -0
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
|
|
3
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
4
|
+
/**
|
|
5
|
+
* @license
|
|
6
|
+
* Copyright 2026 Aglyn LLC
|
|
7
|
+
*
|
|
8
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
9
|
+
* you may not use this file except in compliance with the License.
|
|
10
|
+
* You may obtain a copy of the License at
|
|
11
|
+
*
|
|
12
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
13
|
+
*
|
|
14
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
15
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
16
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
17
|
+
* See the License for the specific language governing permissions and
|
|
18
|
+
* limitations under the License.
|
|
19
|
+
*/ import * as Aglyn from "@aglyn/aglyn";
|
|
20
|
+
import { mdiNewspaperVariantOutline } from "@aglyn/shared-data-mdi";
|
|
21
|
+
import { AppLink } from "@aglyn/shared-ui-jsx";
|
|
22
|
+
import Box from "@mui/material/Box";
|
|
23
|
+
import Chip from "@mui/material/Chip";
|
|
24
|
+
import MuiStack from "@mui/material/Stack";
|
|
25
|
+
import Typography from "@mui/material/Typography";
|
|
26
|
+
import { forwardRef, useContext } from "react";
|
|
27
|
+
import { BUNDLE_ID } from "../constants/bundle-common.js";
|
|
28
|
+
// One coercion for every authored count in this bundle (AGL-1457) — number
|
|
29
|
+
// fields round-trip as strings, and a second parser here would drift.
|
|
30
|
+
import { toCount } from "../utils/to-count.js";
|
|
31
|
+
import { RELATED_SAMPLE_PUBLISHED_AT, RELATED_SAMPLE_CATEGORY } from "./collection-common.js";
|
|
32
|
+
// The theme's own type steps (AGL-2486), shared with the Typography element
|
|
33
|
+
// rather than a second list here — one rung list, one place to extend.
|
|
34
|
+
import { typographyVariants } from "./typography.js";
|
|
35
|
+
// Persisted component id (AGL-551/582); the compose pipeline references it
|
|
36
|
+
// through the @aglyn/aglyn constant. Never rename.
|
|
37
|
+
export const RELATED_ID = Aglyn.COLLECTION_RELATED_COMPONENT_ID;
|
|
38
|
+
/** The frame's 3-up (Figma 170:242) when nothing usable is authored. */ const RELATED_DEFAULT_COLUMNS = 3;
|
|
39
|
+
/**
|
|
40
|
+
* The cover's PROPORTION in the card grid.
|
|
41
|
+
*
|
|
42
|
+
* ONE ratio for every card, rather than each record's own shape: the titles
|
|
43
|
+
* under the covers only line up when the covers are the same height, and a
|
|
44
|
+
* grid of honest shapes is a ragged grid.
|
|
45
|
+
*
|
|
46
|
+
* The ratio is the shape the console asks authors for — 1200 x 630, the same
|
|
47
|
+
* image that heads the entry and doubles as its share card — so a cover
|
|
48
|
+
* authored as instructed loses nothing to the crop. The frame's own card is
|
|
49
|
+
* 445 x 180 (Figma 170:243), which is 2.47 against that art's 1.90: held
|
|
50
|
+
* there, `objectFit: cover` discarded 23% of every card's picture at every
|
|
51
|
+
* width. Cover art is usually a composed lockup, so what it discards is the
|
|
52
|
+
* title set on it.
|
|
53
|
+
*
|
|
54
|
+
* Before the ratio it was a fixed 180px height, which was the frame's number
|
|
55
|
+
* without the frame's shape: a card is 445 wide there, the grid's columns
|
|
56
|
+
* grow with the viewport and 180 does not, so every extra pixel of width
|
|
57
|
+
* flattened the box further — 3.57:1 by a 2000px viewport. A ratio holds the
|
|
58
|
+
* shape at every width; this ratio holds the picture as well.
|
|
59
|
+
*/ const RELATED_COVER_ASPECT = '1200 / 630';
|
|
60
|
+
/**
|
|
61
|
+
* The card's category chip (AGL-1457). ONE fixed token pair for every
|
|
62
|
+
* category: colour-coding a chip per category needs conditional styling,
|
|
63
|
+
* which is not expressible yet (AGL-1307), and inventing a palette here
|
|
64
|
+
* would have to be unpicked when it is. Same shape as the tag chips Entry
|
|
65
|
+
* Meta already renders, so the two blocks read as one vocabulary.
|
|
66
|
+
*/ const relatedChipSx = {
|
|
67
|
+
alignSelf: 'flex-start',
|
|
68
|
+
borderColor: 'divider',
|
|
69
|
+
color: 'text.secondary',
|
|
70
|
+
'& .MuiChip-label': {
|
|
71
|
+
// 9px, the frame's own inset on a 69 x 24 chip.
|
|
72
|
+
px: 1.125,
|
|
73
|
+
// The frame sets this label in the same uppercase, letter-spaced step as
|
|
74
|
+
// the kicker above an article title, which is what `overline` IS in the
|
|
75
|
+
// site theme. Naming the step keeps the chip on the theme's scale instead
|
|
76
|
+
// of a hand-written size that drifts the moment the theme moves.
|
|
77
|
+
typography: 'overline',
|
|
78
|
+
// `size="small"` gives the chip the frame's 24px height, and `overline`
|
|
79
|
+
// carries a 2.66 line-height meant for a standalone kicker with room
|
|
80
|
+
// around it. Inside a fixed-height chip the box sets the height, so the
|
|
81
|
+
// line collapses to it and the label centres.
|
|
82
|
+
lineHeight: 1
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
/** The heading step the block has always emitted. */ const RELATED_DEFAULT_HEADING_VARIANT = 'h5';
|
|
86
|
+
/** The card-title step the block has always emitted. */ const RELATED_DEFAULT_TITLE_VARIANT = 'subtitle1';
|
|
87
|
+
/**
|
|
88
|
+
* Titles for the sample cards. Deliberately unmistakable as copy: the author
|
|
89
|
+
* is looking at a LAYOUT, and a plausible-looking headline here is one an
|
|
90
|
+
* author can mistake for a post that exists — or worse, design around.
|
|
91
|
+
*/ const RELATED_SAMPLE_TITLES = [
|
|
92
|
+
'Sample related post',
|
|
93
|
+
'A second sample post',
|
|
94
|
+
'A third sample post'
|
|
95
|
+
];
|
|
96
|
+
/** Excerpt text for the sample cards; one line, so the card keeps its shape. */ const RELATED_SAMPLE_EXCERPT = 'Each post’s own excerpt reads here, trimmed to a line or two.';
|
|
97
|
+
/**
|
|
98
|
+
* Stand-in posts for editing surfaces (AGL-2486).
|
|
99
|
+
*
|
|
100
|
+
* Related posts are resolved FROM the routed entry, and a besigner canvas has
|
|
101
|
+
* no routed entry — so the block used to draw a one-line dashed strip and the
|
|
102
|
+
* author styled a layout they could not see. The sample is the block's REAL
|
|
103
|
+
* markup, at the author's own settings, which is the only way the canvas can
|
|
104
|
+
* answer "what will this look like".
|
|
105
|
+
*
|
|
106
|
+
* It exists only where {@link ScreenLinkContext.suppressNavigation} is on —
|
|
107
|
+
* the besigner canvas and Preview. A published render reaches this function
|
|
108
|
+
* on no path at all, so no visitor can ever be shown a post that does not
|
|
109
|
+
* exist.
|
|
110
|
+
*
|
|
111
|
+
* The count follows the author's own `limit` so a 6-post rail at 3 columns
|
|
112
|
+
* previews as two rows, and is bounded by the same ceiling the server
|
|
113
|
+
* applies rather than a second one.
|
|
114
|
+
*/ const sampleRelatedEntries = (limit, dateFormat)=>{
|
|
115
|
+
const count = Math.min(// `|| default` also rejects 0, which `toCount` would otherwise accept and
|
|
116
|
+
// preview as an empty rail — the one thing the sample exists to avoid.
|
|
117
|
+
toCount(limit, Aglyn.COLLECTION_RELATED_DEFAULT_LIMIT) || Aglyn.COLLECTION_RELATED_DEFAULT_LIMIT, Aglyn.COLLECTION_RELATED_MAX);
|
|
118
|
+
const date = Aglyn.formatCollectionEntryDate(RELATED_SAMPLE_PUBLISHED_AT, dateFormat);
|
|
119
|
+
return Array.from({
|
|
120
|
+
length: count
|
|
121
|
+
}, (_, index)=>{
|
|
122
|
+
var _RELATED_SAMPLE_TITLES_index;
|
|
123
|
+
return {
|
|
124
|
+
title: (_RELATED_SAMPLE_TITLES_index = RELATED_SAMPLE_TITLES[index]) != null ? _RELATED_SAMPLE_TITLES_index : `Sample related post ${index + 1}`,
|
|
125
|
+
// Empty, not a plausible path: every surface that renders the sample
|
|
126
|
+
// suppresses navigation, so the title is text rather than an anchor and
|
|
127
|
+
// this is never read. A real-looking href would be a link to nowhere the
|
|
128
|
+
// moment that stopped being true.
|
|
129
|
+
url: '',
|
|
130
|
+
date,
|
|
131
|
+
category: RELATED_SAMPLE_CATEGORY,
|
|
132
|
+
excerpt: RELATED_SAMPLE_EXCERPT
|
|
133
|
+
};
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* Lists other entries of the same collection sharing the current entry's
|
|
138
|
+
* category or a tag (AGL-582). The tenant stamps `entries` at compose time
|
|
139
|
+
* on entry renders; without them the published site renders nothing and an
|
|
140
|
+
* editing surface renders sample cards (AGL-2486).
|
|
141
|
+
*
|
|
142
|
+
* Two layouts (AGL-1457). `list` is the plain-link list that has always
|
|
143
|
+
* shipped and stays the default — the block is live on every blog entry, so
|
|
144
|
+
* a new default would restyle published pages nobody asked about. `cards` is
|
|
145
|
+
* the article frame's grid: cover, category chip, title, at an author-set
|
|
146
|
+
* column count.
|
|
147
|
+
*
|
|
148
|
+
* The block owns its markup, so nothing inside a card is a node the Styles
|
|
149
|
+
* panel can select. Every part it renders therefore needs an attribute, or
|
|
150
|
+
* it is unauthorable by construction — which is what the type steps and the
|
|
151
|
+
* `show*` switches are (AGL-2486).
|
|
152
|
+
*/ const CollectionRelated = /*#__PURE__*/ forwardRef((props, ref)=>{
|
|
153
|
+
// `limit` and `dateFormat` are compose-time: the tenant resolves them
|
|
154
|
+
// while stamping `entries`; strip them so they never hit the DOM. The
|
|
155
|
+
// rest are read here, so they must not reach it either.
|
|
156
|
+
const { heading, limit, entries, showCover, layout, columns, headingVariant, titleVariant, showDate, dateFormat, showCategory, showExcerpt } = props, rest = _object_without_properties_loose(props, [
|
|
157
|
+
"heading",
|
|
158
|
+
"limit",
|
|
159
|
+
"entries",
|
|
160
|
+
"showCover",
|
|
161
|
+
"layout",
|
|
162
|
+
"columns",
|
|
163
|
+
"headingVariant",
|
|
164
|
+
"titleVariant",
|
|
165
|
+
"showDate",
|
|
166
|
+
"dateFormat",
|
|
167
|
+
"showCategory",
|
|
168
|
+
"showExcerpt"
|
|
169
|
+
]);
|
|
170
|
+
// Node styles ride the renderer-merged sx; recompose (stack.ts pattern).
|
|
171
|
+
const nodeSx = Array.isArray(props['sx']) ? props['sx'] : [
|
|
172
|
+
props['sx']
|
|
173
|
+
];
|
|
174
|
+
const { suppressNavigation } = useContext(Aglyn.ScreenLinkContext);
|
|
175
|
+
// The resolver every other surface shares (AGL-1215): a stamped `media:`
|
|
176
|
+
// reference becomes a CDN URL HERE, not in the document, so one
|
|
177
|
+
// reference keeps working across sites. Called before the early return —
|
|
178
|
+
// it is a hook.
|
|
179
|
+
const { hostId } = Aglyn.useSite();
|
|
180
|
+
// An editing surface has no routed entry, so nothing is stamped and the
|
|
181
|
+
// block used to be the one part of the page that was not WYSIWYG. The
|
|
182
|
+
// sample stands in for exactly that gap, and only there.
|
|
183
|
+
const sample = !(entries == null ? void 0 : entries.length) && Boolean(suppressNavigation);
|
|
184
|
+
const items = (entries == null ? void 0 : entries.length) ? entries : sample ? sampleRelatedEntries(limit, Aglyn.normalizeCollectionEntryDateFormat(dateFormat)) : [];
|
|
185
|
+
if (!items.length) return /*#__PURE__*/ _jsx(Box, _extends({
|
|
186
|
+
ref: ref
|
|
187
|
+
}, rest));
|
|
188
|
+
const title = heading != null ? heading : 'Related articles';
|
|
189
|
+
const headingNode = title ? /*#__PURE__*/ _jsx(Typography, {
|
|
190
|
+
variant: headingVariant || RELATED_DEFAULT_HEADING_VARIANT,
|
|
191
|
+
component: "h2",
|
|
192
|
+
children: title
|
|
193
|
+
}) : null;
|
|
194
|
+
/**
|
|
195
|
+
* The card title, as a HEADING in the site theme.
|
|
196
|
+
*
|
|
197
|
+
* It used to be a bare `AppLink`, which is MUI's `Link` — `primary.main`
|
|
198
|
+
* and `underline="always"` — so the one thing a related card is FOR read
|
|
199
|
+
* as a default browser link on a themed page, at no heading level, and
|
|
200
|
+
* with no attribute able to touch it. The type step now comes from the
|
|
201
|
+
* theme's own rungs and the anchor inherits the colour, exactly as the
|
|
202
|
+
* Entry Author card's byline link does.
|
|
203
|
+
*
|
|
204
|
+
* `linked` is false wherever the whole card is already the anchor: an
|
|
205
|
+
* `<a>` inside an `<a>` is invalid markup that browsers silently unnest.
|
|
206
|
+
*/ const titleNode = (entry, linked)=>/*#__PURE__*/ _jsx(Typography, {
|
|
207
|
+
variant: titleVariant || RELATED_DEFAULT_TITLE_VARIANT,
|
|
208
|
+
component: "h3",
|
|
209
|
+
children: linked && !suppressNavigation && entry.url ? /*#__PURE__*/ _jsx(AppLink, {
|
|
210
|
+
href: entry.url,
|
|
211
|
+
sx: {
|
|
212
|
+
color: 'inherit'
|
|
213
|
+
},
|
|
214
|
+
underline: "hover",
|
|
215
|
+
children: entry.title
|
|
216
|
+
}) : entry.title
|
|
217
|
+
});
|
|
218
|
+
const dateNode = (entry)=>showDate !== false && entry.date ? /*#__PURE__*/ _jsx(Typography, {
|
|
219
|
+
variant: "caption",
|
|
220
|
+
sx: {
|
|
221
|
+
color: 'text.secondary'
|
|
222
|
+
},
|
|
223
|
+
children: entry.date
|
|
224
|
+
}) : null;
|
|
225
|
+
const excerptNode = (entry)=>showExcerpt && entry.excerpt ? /*#__PURE__*/ _jsx(Typography, {
|
|
226
|
+
variant: "body2",
|
|
227
|
+
sx: {
|
|
228
|
+
color: 'text.secondary'
|
|
229
|
+
},
|
|
230
|
+
children: entry.excerpt
|
|
231
|
+
}) : null;
|
|
232
|
+
/**
|
|
233
|
+
* The cover, when the author asked for one AND the entry has one. An
|
|
234
|
+
* entry without a cover gets no box at all rather than a placeholder:
|
|
235
|
+
* the related list is a real feed, and a row of grey rectangles is worse
|
|
236
|
+
* than a row of titles.
|
|
237
|
+
*
|
|
238
|
+
* The sample cards are the exception, and for the opposite reason: they
|
|
239
|
+
* stand in for posts that do not exist yet, so the cover SLOT is what
|
|
240
|
+
* the author is looking at. A card grid previewing without its images is
|
|
241
|
+
* not the grid being styled.
|
|
242
|
+
*/ const coverNode = (entry)=>{
|
|
243
|
+
const src = showCover ? Aglyn.resolveMediaSrc(entry.coverImage, {
|
|
244
|
+
hostId
|
|
245
|
+
}) : undefined;
|
|
246
|
+
if (!src) {
|
|
247
|
+
if (!sample || !showCover) return null;
|
|
248
|
+
return /*#__PURE__*/ _jsx(Box, {
|
|
249
|
+
"aria-hidden": true,
|
|
250
|
+
sx: {
|
|
251
|
+
width: '100%',
|
|
252
|
+
aspectRatio: RELATED_COVER_ASPECT,
|
|
253
|
+
borderRadius: 1,
|
|
254
|
+
backgroundColor: 'action.hover'
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
return /*#__PURE__*/ _jsx(Box, _extends({
|
|
259
|
+
component: "img",
|
|
260
|
+
src: src,
|
|
261
|
+
// The author's own description wins, and the title stays the
|
|
262
|
+
// fallback (AGL-2418). Unlike the entry hero and the event
|
|
263
|
+
// thumbnail — which render empty when nothing is authored — this
|
|
264
|
+
// cover is the LINK's own content, so it must carry an accessible
|
|
265
|
+
// name rather than go silent.
|
|
266
|
+
alt: Aglyn.renderedMediaAlt(entry.coverImageAlt, entry.title),
|
|
267
|
+
sx: {
|
|
268
|
+
display: 'block',
|
|
269
|
+
width: '100%',
|
|
270
|
+
aspectRatio: RELATED_COVER_ASPECT,
|
|
271
|
+
objectFit: 'cover',
|
|
272
|
+
borderRadius: 1
|
|
273
|
+
}
|
|
274
|
+
}, Aglyn.DEFERRED_IMAGE_ATTRIBUTES));
|
|
275
|
+
};
|
|
276
|
+
/**
|
|
277
|
+
* The notice that says these cards are not posts (AGL-2486). Editing
|
|
278
|
+
* surfaces only, and outside the card markup rather than inside it, so
|
|
279
|
+
* the author is styling the same tree the site will render.
|
|
280
|
+
*/ const sampleNotice = sample ? /*#__PURE__*/ _jsx(Typography, {
|
|
281
|
+
variant: "caption",
|
|
282
|
+
sx: {
|
|
283
|
+
display: 'block',
|
|
284
|
+
p: 1,
|
|
285
|
+
border: '1px dashed',
|
|
286
|
+
borderColor: 'divider',
|
|
287
|
+
borderRadius: 1,
|
|
288
|
+
color: 'text.secondary',
|
|
289
|
+
fontFamily: 'system-ui, sans-serif'
|
|
290
|
+
},
|
|
291
|
+
children: 'Sample posts — entries sharing this entry’s category or tags ' + 'replace these on the published page'
|
|
292
|
+
}) : null;
|
|
293
|
+
if (layout === 'cards') {
|
|
294
|
+
// `toCount` rounds and rejects junk; `|| default` also rejects 0, which
|
|
295
|
+
// it would otherwise accept as a column count and emit `repeat(0, …)`.
|
|
296
|
+
const columnCount = toCount(columns, RELATED_DEFAULT_COLUMNS) || RELATED_DEFAULT_COLUMNS;
|
|
297
|
+
/**
|
|
298
|
+
* One card's contents, always with a PLAIN title: the caller wraps the
|
|
299
|
+
* whole card in the anchor, because a reader aiming at a 180px cover
|
|
300
|
+
* should not have to hit the line of text under it — and a second
|
|
301
|
+
* anchor around the title inside that one is markup browsers unnest.
|
|
302
|
+
*/ const card = (entry)=>// 12px between cover, chip and title — the frame's rhythm (170:243
|
|
303
|
+
// puts the chip at y=192 under a cover ending at 180, and the title
|
|
304
|
+
// at y=228 under a chip ending at 216).
|
|
305
|
+
/*#__PURE__*/ _jsxs(MuiStack, {
|
|
306
|
+
spacing: 1.5,
|
|
307
|
+
children: [
|
|
308
|
+
coverNode(entry),
|
|
309
|
+
showCategory !== false && entry.category ? /*#__PURE__*/ _jsx(Chip, {
|
|
310
|
+
label: entry.category,
|
|
311
|
+
size: "small",
|
|
312
|
+
variant: "outlined",
|
|
313
|
+
sx: relatedChipSx
|
|
314
|
+
}) : null,
|
|
315
|
+
titleNode(entry, false),
|
|
316
|
+
excerptNode(entry),
|
|
317
|
+
dateNode(entry)
|
|
318
|
+
]
|
|
319
|
+
});
|
|
320
|
+
return /*#__PURE__*/ _jsxs(Box, _extends({
|
|
321
|
+
ref: ref
|
|
322
|
+
}, rest, {
|
|
323
|
+
// MERGE, never replace (AGL-1450) — the node's slice arrives as the
|
|
324
|
+
// ARRAY `mergeSxProps` builds in leaf.tsx, so folding it into an
|
|
325
|
+
// object spreads numeric keys and discards every authored property
|
|
326
|
+
// while these defaults still apply. Defaults first, node's after.
|
|
327
|
+
sx: [
|
|
328
|
+
{
|
|
329
|
+
display: 'grid',
|
|
330
|
+
gridTemplateColumns: `repeat(${columnCount}, minmax(0, 1fr))`,
|
|
331
|
+
alignItems: 'start',
|
|
332
|
+
columnGap: 3,
|
|
333
|
+
rowGap: 4
|
|
334
|
+
},
|
|
335
|
+
...nodeSx
|
|
336
|
+
],
|
|
337
|
+
children: [
|
|
338
|
+
headingNode ? /*#__PURE__*/ _jsx(Box, {
|
|
339
|
+
sx: {
|
|
340
|
+
gridColumn: '1 / -1'
|
|
341
|
+
},
|
|
342
|
+
children: headingNode
|
|
343
|
+
}) : null,
|
|
344
|
+
sampleNotice ? /*#__PURE__*/ _jsx(Box, {
|
|
345
|
+
sx: {
|
|
346
|
+
gridColumn: '1 / -1'
|
|
347
|
+
},
|
|
348
|
+
children: sampleNotice
|
|
349
|
+
}) : null,
|
|
350
|
+
items.map((entry, index)=>{
|
|
351
|
+
const linked = !suppressNavigation && Boolean(entry.url);
|
|
352
|
+
return linked ? /*#__PURE__*/ _jsx(AppLink, {
|
|
353
|
+
href: entry.url,
|
|
354
|
+
underline: "none",
|
|
355
|
+
// A grid ITEM, so it has to be a block; `color: inherit`
|
|
356
|
+
// keeps the cover, chip and date reading as themed content
|
|
357
|
+
// rather than as the inside of a link.
|
|
358
|
+
sx: {
|
|
359
|
+
color: 'inherit',
|
|
360
|
+
display: 'block'
|
|
361
|
+
},
|
|
362
|
+
children: card(entry)
|
|
363
|
+
}, index) : /*#__PURE__*/ _jsx(Box, {
|
|
364
|
+
children: card(entry)
|
|
365
|
+
}, index);
|
|
366
|
+
})
|
|
367
|
+
]
|
|
368
|
+
}));
|
|
369
|
+
}
|
|
370
|
+
return /*#__PURE__*/ _jsxs(MuiStack, _extends({
|
|
371
|
+
ref: ref,
|
|
372
|
+
spacing: 1.5
|
|
373
|
+
}, rest, {
|
|
374
|
+
children: [
|
|
375
|
+
headingNode,
|
|
376
|
+
sampleNotice,
|
|
377
|
+
items.map((entry, index)=>{
|
|
378
|
+
const meta = [
|
|
379
|
+
showDate !== false ? entry.date : '',
|
|
380
|
+
showCategory !== false ? entry.category : ''
|
|
381
|
+
].filter(Boolean);
|
|
382
|
+
return /*#__PURE__*/ _jsxs(MuiStack, {
|
|
383
|
+
spacing: 0.25,
|
|
384
|
+
children: [
|
|
385
|
+
coverNode(entry),
|
|
386
|
+
titleNode(entry, true),
|
|
387
|
+
excerptNode(entry),
|
|
388
|
+
meta.length ? /*#__PURE__*/ _jsx(Typography, {
|
|
389
|
+
variant: "caption",
|
|
390
|
+
sx: {
|
|
391
|
+
color: 'text.secondary'
|
|
392
|
+
},
|
|
393
|
+
children: meta.join(' · ')
|
|
394
|
+
}) : null
|
|
395
|
+
]
|
|
396
|
+
}, index);
|
|
397
|
+
})
|
|
398
|
+
]
|
|
399
|
+
}));
|
|
400
|
+
});
|
|
401
|
+
CollectionRelated.displayName = 'AglynCollectionRelated';
|
|
402
|
+
export const collectionRelatedSchema = {
|
|
403
|
+
$id: RELATED_ID,
|
|
404
|
+
pluginId: BUNDLE_ID,
|
|
405
|
+
displayName: 'Related Posts',
|
|
406
|
+
description: "Other entries sharing this one's category or tags.",
|
|
407
|
+
category: Aglyn.ComponentCategory.DATA_DISPLAY,
|
|
408
|
+
icon: {
|
|
409
|
+
path: mdiNewspaperVariantOutline.path,
|
|
410
|
+
sx: {
|
|
411
|
+
color: 'secondary.main'
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
flags: {
|
|
415
|
+
selfClosing: Aglyn.FEATURE_FLAG.ENABLED
|
|
416
|
+
},
|
|
417
|
+
attributes: [
|
|
418
|
+
{
|
|
419
|
+
name: 'heading',
|
|
420
|
+
label: 'Heading',
|
|
421
|
+
description: 'Section heading (blank hides it).',
|
|
422
|
+
component: Aglyn.FieldComponentType.TEXT_FIELD
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
name: 'limit',
|
|
426
|
+
label: 'Limit',
|
|
427
|
+
description: 'Most related posts listed (default 3).',
|
|
428
|
+
component: Aglyn.FieldComponentType.TEXT_FIELD,
|
|
429
|
+
type: 'number'
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
name: 'layout',
|
|
433
|
+
label: 'Layout',
|
|
434
|
+
description: 'List keeps the plain links this block has always rendered. ' + 'Cards is the article layout: cover, category, title, in a grid.',
|
|
435
|
+
component: Aglyn.FieldComponentType.SELECT,
|
|
436
|
+
// Both values are REAL (AGL-1451/AGL-1453): `''` cannot survive a
|
|
437
|
+
// save, so an author who switched to Cards would have no route back.
|
|
438
|
+
// `list` is also the value the render falls back to, so naming it is
|
|
439
|
+
// the same choice, not a second one.
|
|
440
|
+
options: [
|
|
441
|
+
{
|
|
442
|
+
value: 'list',
|
|
443
|
+
label: 'List (default)'
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
value: 'cards',
|
|
447
|
+
label: 'Card grid'
|
|
448
|
+
}
|
|
449
|
+
]
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
name: 'columns',
|
|
453
|
+
label: 'Columns',
|
|
454
|
+
description: 'Cards per row in the card grid. Default 3.',
|
|
455
|
+
component: Aglyn.FieldComponentType.TEXT_FIELD,
|
|
456
|
+
type: 'number',
|
|
457
|
+
// Meaningless on the list, where each post is its own row.
|
|
458
|
+
condition: {
|
|
459
|
+
when: 'layout',
|
|
460
|
+
is: 'cards'
|
|
461
|
+
}
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
name: 'showCover',
|
|
465
|
+
label: 'Show cover',
|
|
466
|
+
description: 'Show each post’s cover image. Posts without one show their ' + 'title alone rather than an empty box.',
|
|
467
|
+
component: Aglyn.FieldComponentType.SWITCH
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
name: 'headingVariant',
|
|
471
|
+
label: 'Heading style',
|
|
472
|
+
description: 'Which type step from the site theme the section heading above ' + 'the posts reads at. Default Heading 5.',
|
|
473
|
+
component: Aglyn.FieldComponentType.SELECT,
|
|
474
|
+
// The theme's own rungs, shared with the Typography element rather
|
|
475
|
+
// than a second list here: two lists is how a step added to the
|
|
476
|
+
// theme becomes reachable in one place and not the other.
|
|
477
|
+
options: [
|
|
478
|
+
...typographyVariants
|
|
479
|
+
]
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
name: 'titleVariant',
|
|
483
|
+
label: 'Title style',
|
|
484
|
+
description: 'Which type step from the site theme each post’s title reads at. ' + 'Default Subtitle 1; the title takes the surrounding text color ' + 'rather than the link color.',
|
|
485
|
+
component: Aglyn.FieldComponentType.SELECT,
|
|
486
|
+
options: [
|
|
487
|
+
...typographyVariants
|
|
488
|
+
]
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
name: 'showDate',
|
|
492
|
+
label: 'Show date',
|
|
493
|
+
description: 'Off drops each post’s published date. The posts stay in ' + 'newest-first order either way — this is about the card, not the ' + 'ordering.',
|
|
494
|
+
component: Aglyn.FieldComponentType.SWITCH,
|
|
495
|
+
// Unset means SHOWN in the renderer above, so the switch has to
|
|
496
|
+
// OPEN in that position or it lies about the block in front of
|
|
497
|
+
// the author — and the first click, which writes the `true` that
|
|
498
|
+
// was already in effect, changes nothing and reads as a dead
|
|
499
|
+
// control (AGL-2506).
|
|
500
|
+
initialValue: true
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
name: 'dateFormat',
|
|
504
|
+
label: 'Date format',
|
|
505
|
+
description: 'How each post’s published date reads on its card. Site default ' + 'is the format this block has always used.',
|
|
506
|
+
component: Aglyn.FieldComponentType.SELECT,
|
|
507
|
+
// The formats the pure layer knows how to produce, so the list
|
|
508
|
+
// cannot offer a shape nothing renders. Every value is REAL,
|
|
509
|
+
// including the do-nothing one (AGL-1451/AGL-1453).
|
|
510
|
+
options: [
|
|
511
|
+
...Aglyn.COLLECTION_ENTRY_DATE_FORMAT_OPTIONS
|
|
512
|
+
]
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
name: 'showCategory',
|
|
516
|
+
label: 'Show category',
|
|
517
|
+
description: 'Off drops the category — the chip above each card title, or the ' + 'second half of the line under each link on the list layout.',
|
|
518
|
+
component: Aglyn.FieldComponentType.SWITCH,
|
|
519
|
+
// Unset means SHOWN in the renderer above, so the switch has to
|
|
520
|
+
// OPEN in that position or it lies about the block in front of
|
|
521
|
+
// the author — and the first click, which writes the `true` that
|
|
522
|
+
// was already in effect, changes nothing and reads as a dead
|
|
523
|
+
// control (AGL-2506).
|
|
524
|
+
initialValue: true
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
name: 'showExcerpt',
|
|
528
|
+
label: 'Show excerpt',
|
|
529
|
+
description: 'Adds each post’s excerpt under its title. Posts with no excerpt ' + 'are unchanged rather than leaving a gap.',
|
|
530
|
+
component: Aglyn.FieldComponentType.SWITCH
|
|
531
|
+
}
|
|
532
|
+
]
|
|
533
|
+
};
|
|
534
|
+
export { CollectionRelated };
|
|
535
|
+
|
|
536
|
+
//# sourceMappingURL=collection-related.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/collection-related.tsx"],"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'\nimport { mdiNewspaperVariantOutline } from '@aglyn/shared-data-mdi'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport Box from '@mui/material/Box'\nimport Chip from '@mui/material/Chip'\nimport MuiStack, { type StackProps } from '@mui/material/Stack'\nimport Typography from '@mui/material/Typography'\nimport { forwardRef, useContext } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\n// One coercion for every authored count in this bundle (AGL-1457) — number\n// fields round-trip as strings, and a second parser here would drift.\nimport { toCount } from '../utils/to-count'\nimport {\n RELATED_SAMPLE_PUBLISHED_AT,\n RELATED_SAMPLE_CATEGORY,\n} from './collection-common'\n// The theme's own type steps (AGL-2486), shared with the Typography element\n// rather than a second list here — one rung list, one place to extend.\nimport { typographyVariants } from './typography'\n\n// Persisted component id (AGL-551/582); the compose pipeline references it\n// through the @aglyn/aglyn constant. Never rename.\nexport const RELATED_ID: Aglyn.ComponentId =\n Aglyn.COLLECTION_RELATED_COMPONENT_ID\n\n/** How the related posts are laid out (AGL-1457). */\nexport type CollectionRelatedLayout = 'list' | 'cards'\n\n/** One related post as stamped by the compose pipeline (AGL-582). */\nexport interface CollectionRelatedProps extends StackProps {\n /** Section heading; empty string hides it. */\n heading?: string\n /** Compose-time: most related posts listed (default 3). */\n limit?: number | string\n /**\n * Emit each entry's cover image (AGL-1457). OFF is the default and the\n * shipped behaviour — the block is live on every blog entry, so turning\n * covers on by default would restyle published pages nobody asked about.\n */\n showCover?: boolean\n /**\n * `list` (default) is the plain-link list that has always shipped; `cards`\n * is the article frame's grid of cover + category chip + title.\n */\n layout?: CollectionRelatedLayout\n /** Columns in the `cards` grid (default 3, the frame's 3-up). */\n columns?: number | string\n /**\n * Type step the section heading reads at (default `h5`). One of the\n * theme's own rungs, shared with the Typography element — a heading sized\n * by hand in the Styles panel is the pixel-typing the tokens exist to stop.\n */\n headingVariant?: string\n /**\n * Type step each card title reads at (default `subtitle1`, what the block\n * has always emitted). The title is the block's own markup, so the Styles\n * panel — which edits the node's root — cannot reach it; this is the only\n * handle on it.\n */\n titleVariant?: string\n /** Show each post's published date. On is the shipped behaviour. */\n showDate?: boolean\n /**\n * How each card's published date reads (AGL-1459) — a COMPOSE-TIME prop\n * like {@link limit}: it is answered in `expandCollectionRelated`, where\n * the timestamp still exists, and never reaches the DOM. Blank, or\n * `default`, is the locale date the block has always emitted.\n *\n * Read here too, but only to date the SAMPLE cards: an editing surface has\n * no routed entry and therefore no server fill, so the picker would\n * otherwise preview nothing.\n */\n dateFormat?: Aglyn.CollectionEntryDateFormat\n /** Show each post's category. On is the shipped behaviour. */\n showCategory?: boolean\n /**\n * Show each post's excerpt. OFF is the default and the shipped behaviour:\n * the excerpt has been stamped onto every related item since AGL-582 and\n * rendered by nothing, so turning it on by default would add a paragraph\n * to every published entry nobody asked about.\n */\n showExcerpt?: boolean\n /**\n * Server-stamped related posts (`expandCollectionRelated`); never set by\n * hand — the tenant computes it from the current entry's category/tags.\n */\n entries?: Aglyn.CollectionRelatedItem[]\n}\n\n/** The frame's 3-up (Figma 170:242) when nothing usable is authored. */\nconst RELATED_DEFAULT_COLUMNS = 3\n\n/**\n * The cover's PROPORTION in the card grid.\n *\n * ONE ratio for every card, rather than each record's own shape: the titles\n * under the covers only line up when the covers are the same height, and a\n * grid of honest shapes is a ragged grid.\n *\n * The ratio is the shape the console asks authors for — 1200 x 630, the same\n * image that heads the entry and doubles as its share card — so a cover\n * authored as instructed loses nothing to the crop. The frame's own card is\n * 445 x 180 (Figma 170:243), which is 2.47 against that art's 1.90: held\n * there, `objectFit: cover` discarded 23% of every card's picture at every\n * width. Cover art is usually a composed lockup, so what it discards is the\n * title set on it.\n *\n * Before the ratio it was a fixed 180px height, which was the frame's number\n * without the frame's shape: a card is 445 wide there, the grid's columns\n * grow with the viewport and 180 does not, so every extra pixel of width\n * flattened the box further — 3.57:1 by a 2000px viewport. A ratio holds the\n * shape at every width; this ratio holds the picture as well.\n */\nconst RELATED_COVER_ASPECT = '1200 / 630'\n\n/**\n * The card's category chip (AGL-1457). ONE fixed token pair for every\n * category: colour-coding a chip per category needs conditional styling,\n * which is not expressible yet (AGL-1307), and inventing a palette here\n * would have to be unpicked when it is. Same shape as the tag chips Entry\n * Meta already renders, so the two blocks read as one vocabulary.\n */\nconst relatedChipSx = {\n alignSelf: 'flex-start',\n borderColor: 'divider',\n color: 'text.secondary',\n '& .MuiChip-label': {\n // 9px, the frame's own inset on a 69 x 24 chip.\n px: 1.125,\n // The frame sets this label in the same uppercase, letter-spaced step as\n // the kicker above an article title, which is what `overline` IS in the\n // site theme. Naming the step keeps the chip on the theme's scale instead\n // of a hand-written size that drifts the moment the theme moves.\n typography: 'overline',\n // `size=\"small\"` gives the chip the frame's 24px height, and `overline`\n // carries a 2.66 line-height meant for a standalone kicker with room\n // around it. Inside a fixed-height chip the box sets the height, so the\n // line collapses to it and the label centres.\n lineHeight: 1,\n },\n}\n\n/** The heading step the block has always emitted. */\nconst RELATED_DEFAULT_HEADING_VARIANT = 'h5'\n\n/** The card-title step the block has always emitted. */\nconst RELATED_DEFAULT_TITLE_VARIANT = 'subtitle1'\n\n/**\n * Titles for the sample cards. Deliberately unmistakable as copy: the author\n * is looking at a LAYOUT, and a plausible-looking headline here is one an\n * author can mistake for a post that exists — or worse, design around.\n */\nconst RELATED_SAMPLE_TITLES = [\n 'Sample related post',\n 'A second sample post',\n 'A third sample post',\n]\n\n/** Excerpt text for the sample cards; one line, so the card keeps its shape. */\nconst RELATED_SAMPLE_EXCERPT =\n 'Each post’s own excerpt reads here, trimmed to a line or two.'\n\n/**\n * Stand-in posts for editing surfaces (AGL-2486).\n *\n * Related posts are resolved FROM the routed entry, and a besigner canvas has\n * no routed entry — so the block used to draw a one-line dashed strip and the\n * author styled a layout they could not see. The sample is the block's REAL\n * markup, at the author's own settings, which is the only way the canvas can\n * answer \"what will this look like\".\n *\n * It exists only where {@link ScreenLinkContext.suppressNavigation} is on —\n * the besigner canvas and Preview. A published render reaches this function\n * on no path at all, so no visitor can ever be shown a post that does not\n * exist.\n *\n * The count follows the author's own `limit` so a 6-post rail at 3 columns\n * previews as two rows, and is bounded by the same ceiling the server\n * applies rather than a second one.\n */\nconst sampleRelatedEntries = (\n limit: number | string | undefined,\n dateFormat: Aglyn.CollectionEntryDateFormat,\n): Aglyn.CollectionRelatedItem[] => {\n const count = Math.min(\n // `|| default` also rejects 0, which `toCount` would otherwise accept and\n // preview as an empty rail — the one thing the sample exists to avoid.\n toCount(limit, Aglyn.COLLECTION_RELATED_DEFAULT_LIMIT) ||\n Aglyn.COLLECTION_RELATED_DEFAULT_LIMIT,\n Aglyn.COLLECTION_RELATED_MAX,\n )\n const date = Aglyn.formatCollectionEntryDate(\n RELATED_SAMPLE_PUBLISHED_AT,\n dateFormat,\n )\n return Array.from({ length: count }, (_, index) => ({\n title: RELATED_SAMPLE_TITLES[index] ?? `Sample related post ${index + 1}`,\n // Empty, not a plausible path: every surface that renders the sample\n // suppresses navigation, so the title is text rather than an anchor and\n // this is never read. A real-looking href would be a link to nowhere the\n // moment that stopped being true.\n url: '',\n date,\n category: RELATED_SAMPLE_CATEGORY,\n excerpt: RELATED_SAMPLE_EXCERPT,\n }))\n}\n\n/**\n * Lists other entries of the same collection sharing the current entry's\n * category or a tag (AGL-582). The tenant stamps `entries` at compose time\n * on entry renders; without them the published site renders nothing and an\n * editing surface renders sample cards (AGL-2486).\n *\n * Two layouts (AGL-1457). `list` is the plain-link list that has always\n * shipped and stays the default — the block is live on every blog entry, so\n * a new default would restyle published pages nobody asked about. `cards` is\n * the article frame's grid: cover, category chip, title, at an author-set\n * column count.\n *\n * The block owns its markup, so nothing inside a card is a node the Styles\n * panel can select. Every part it renders therefore needs an attribute, or\n * it is unauthorable by construction — which is what the type steps and the\n * `show*` switches are (AGL-2486).\n */\nconst CollectionRelated = forwardRef<HTMLDivElement, CollectionRelatedProps>(\n (props, ref) => {\n // `limit` and `dateFormat` are compose-time: the tenant resolves them\n // while stamping `entries`; strip them so they never hit the DOM. The\n // rest are read here, so they must not reach it either.\n const {\n heading,\n limit,\n entries,\n showCover,\n layout,\n columns,\n headingVariant,\n titleVariant,\n showDate,\n dateFormat,\n showCategory,\n showExcerpt,\n ...rest\n } = props\n // Node styles ride the renderer-merged sx; recompose (stack.ts pattern).\n const nodeSx = Array.isArray(props['sx']) ? props['sx'] : [props['sx']]\n const { suppressNavigation } = useContext(Aglyn.ScreenLinkContext)\n // The resolver every other surface shares (AGL-1215): a stamped `media:`\n // reference becomes a CDN URL HERE, not in the document, so one\n // reference keeps working across sites. Called before the early return —\n // it is a hook.\n const { hostId } = Aglyn.useSite()\n // An editing surface has no routed entry, so nothing is stamped and the\n // block used to be the one part of the page that was not WYSIWYG. The\n // sample stands in for exactly that gap, and only there.\n const sample = !entries?.length && Boolean(suppressNavigation)\n const items = entries?.length\n ? entries\n : sample\n ? sampleRelatedEntries(\n limit,\n Aglyn.normalizeCollectionEntryDateFormat(dateFormat),\n )\n : []\n if (!items.length) return <Box ref={ref} {...rest} />\n const title = heading ?? 'Related articles'\n const headingNode = title ? (\n <Typography\n variant={(headingVariant || RELATED_DEFAULT_HEADING_VARIANT) as 'h5'}\n component=\"h2\"\n >\n {title}\n </Typography>\n ) : null\n /**\n * The card title, as a HEADING in the site theme.\n *\n * It used to be a bare `AppLink`, which is MUI's `Link` — `primary.main`\n * and `underline=\"always\"` — so the one thing a related card is FOR read\n * as a default browser link on a themed page, at no heading level, and\n * with no attribute able to touch it. The type step now comes from the\n * theme's own rungs and the anchor inherits the colour, exactly as the\n * Entry Author card's byline link does.\n *\n * `linked` is false wherever the whole card is already the anchor: an\n * `<a>` inside an `<a>` is invalid markup that browsers silently unnest.\n */\n const titleNode = (entry: Aglyn.CollectionRelatedItem, linked: boolean) => (\n <Typography\n variant={(titleVariant || RELATED_DEFAULT_TITLE_VARIANT) as 'subtitle1'}\n component=\"h3\"\n >\n {linked && !suppressNavigation && entry.url ? (\n <AppLink href={entry.url} sx={{ color: 'inherit' }} underline=\"hover\">\n {entry.title}\n </AppLink>\n ) : (\n entry.title\n )}\n </Typography>\n )\n const dateNode = (entry: Aglyn.CollectionRelatedItem) =>\n showDate !== false && entry.date ? (\n <Typography variant=\"caption\" sx={{ color: 'text.secondary' }}>\n {entry.date}\n </Typography>\n ) : null\n const excerptNode = (entry: Aglyn.CollectionRelatedItem) =>\n showExcerpt && entry.excerpt ? (\n <Typography variant=\"body2\" sx={{ color: 'text.secondary' }}>\n {entry.excerpt}\n </Typography>\n ) : null\n /**\n * The cover, when the author asked for one AND the entry has one. An\n * entry without a cover gets no box at all rather than a placeholder:\n * the related list is a real feed, and a row of grey rectangles is worse\n * than a row of titles.\n *\n * The sample cards are the exception, and for the opposite reason: they\n * stand in for posts that do not exist yet, so the cover SLOT is what\n * the author is looking at. A card grid previewing without its images is\n * not the grid being styled.\n */\n const coverNode = (entry: Aglyn.CollectionRelatedItem) => {\n const src = showCover\n ? Aglyn.resolveMediaSrc(entry.coverImage, { hostId })\n : undefined\n if (!src) {\n if (!sample || !showCover) return null\n return (\n <Box\n aria-hidden\n sx={{\n width: '100%',\n aspectRatio: RELATED_COVER_ASPECT,\n borderRadius: 1,\n backgroundColor: 'action.hover',\n }}\n />\n )\n }\n return (\n <Box\n component=\"img\"\n src={src}\n // The author's own description wins, and the title stays the\n // fallback (AGL-2418). Unlike the entry hero and the event\n // thumbnail — which render empty when nothing is authored — this\n // cover is the LINK's own content, so it must carry an accessible\n // name rather than go silent.\n alt={Aglyn.renderedMediaAlt(entry.coverImageAlt, entry.title)}\n sx={{\n display: 'block',\n width: '100%',\n aspectRatio: RELATED_COVER_ASPECT,\n objectFit: 'cover',\n borderRadius: 1,\n }}\n // `lazy` ALONE was the bug in miniature (AGL-2486): a lazy image\n // at default priority still outranks a lazy image at `low`, so\n // this related-entries rail — which sits at the bottom of an entry\n // by construction — was beating the deferred Image elements in the\n // body above it. The hint only works as a set.\n {...Aglyn.DEFERRED_IMAGE_ATTRIBUTES}\n />\n )\n }\n\n /**\n * The notice that says these cards are not posts (AGL-2486). Editing\n * surfaces only, and outside the card markup rather than inside it, so\n * the author is styling the same tree the site will render.\n */\n const sampleNotice = sample ? (\n <Typography\n variant=\"caption\"\n sx={{\n display: 'block',\n p: 1,\n border: '1px dashed',\n borderColor: 'divider',\n borderRadius: 1,\n color: 'text.secondary',\n fontFamily: 'system-ui, sans-serif',\n }}\n >\n {'Sample posts — entries sharing this entry’s category or tags ' +\n 'replace these on the published page'}\n </Typography>\n ) : null\n\n if (layout === 'cards') {\n // `toCount` rounds and rejects junk; `|| default` also rejects 0, which\n // it would otherwise accept as a column count and emit `repeat(0, …)`.\n const columnCount =\n toCount(columns, RELATED_DEFAULT_COLUMNS) || RELATED_DEFAULT_COLUMNS\n /**\n * One card's contents, always with a PLAIN title: the caller wraps the\n * whole card in the anchor, because a reader aiming at a 180px cover\n * should not have to hit the line of text under it — and a second\n * anchor around the title inside that one is markup browsers unnest.\n */\n const card = (entry: Aglyn.CollectionRelatedItem) => (\n // 12px between cover, chip and title — the frame's rhythm (170:243\n // puts the chip at y=192 under a cover ending at 180, and the title\n // at y=228 under a chip ending at 216).\n <MuiStack spacing={1.5}>\n {coverNode(entry)}\n {showCategory !== false && entry.category ? (\n <Chip\n label={entry.category}\n size=\"small\"\n variant=\"outlined\"\n sx={relatedChipSx}\n />\n ) : null}\n {titleNode(entry, false)}\n {excerptNode(entry)}\n {dateNode(entry)}\n </MuiStack>\n )\n return (\n <Box\n ref={ref}\n {...rest}\n // MERGE, never replace (AGL-1450) — the node's slice arrives as the\n // ARRAY `mergeSxProps` builds in leaf.tsx, so folding it into an\n // object spreads numeric keys and discards every authored property\n // while these defaults still apply. Defaults first, node's after.\n sx={[\n {\n display: 'grid',\n gridTemplateColumns: `repeat(${columnCount}, minmax(0, 1fr))`,\n alignItems: 'start',\n columnGap: 3,\n rowGap: 4,\n },\n ...nodeSx,\n ]}\n >\n {headingNode ? (\n <Box sx={{ gridColumn: '1 / -1' }}>{headingNode}</Box>\n ) : null}\n {sampleNotice ? (\n <Box sx={{ gridColumn: '1 / -1' }}>{sampleNotice}</Box>\n ) : null}\n {items.map((entry, index) => {\n const linked = !suppressNavigation && Boolean(entry.url)\n return linked ? (\n <AppLink\n key={index}\n href={entry.url}\n underline=\"none\"\n // A grid ITEM, so it has to be a block; `color: inherit`\n // keeps the cover, chip and date reading as themed content\n // rather than as the inside of a link.\n sx={{ color: 'inherit', display: 'block' }}\n >\n {card(entry)}\n </AppLink>\n ) : (\n <Box key={index}>{card(entry)}</Box>\n )\n })}\n </Box>\n )\n }\n return (\n <MuiStack ref={ref} spacing={1.5} {...rest}>\n {headingNode}\n {sampleNotice}\n {items.map((entry, index) => {\n const meta = [\n showDate !== false ? entry.date : '',\n showCategory !== false ? entry.category : '',\n ].filter(Boolean)\n return (\n <MuiStack key={index} spacing={0.25}>\n {coverNode(entry)}\n {titleNode(entry, true)}\n {excerptNode(entry)}\n {meta.length ? (\n <Typography variant=\"caption\" sx={{ color: 'text.secondary' }}>\n {meta.join(' · ')}\n </Typography>\n ) : null}\n </MuiStack>\n )\n })}\n </MuiStack>\n )\n },\n)\nCollectionRelated.displayName = 'AglynCollectionRelated'\n\nexport const collectionRelatedSchema: Aglyn.ComponentSchema<CollectionRelatedProps> =\n {\n $id: RELATED_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Related Posts',\n description: \"Other entries sharing this one's category or tags.\",\n category: Aglyn.ComponentCategory.DATA_DISPLAY,\n icon: { path: mdiNewspaperVariantOutline.path, sx: { color: 'secondary.main' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'heading',\n label: 'Heading',\n description: 'Section heading (blank hides it).',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'limit',\n label: 'Limit',\n description: 'Most related posts listed (default 3).',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'layout',\n label: 'Layout',\n description:\n 'List keeps the plain links this block has always rendered. ' +\n 'Cards is the article layout: cover, category, title, in a grid.',\n component: Aglyn.FieldComponentType.SELECT,\n // Both values are REAL (AGL-1451/AGL-1453): `''` cannot survive a\n // save, so an author who switched to Cards would have no route back.\n // `list` is also the value the render falls back to, so naming it is\n // the same choice, not a second one.\n options: [\n { value: 'list', label: 'List (default)' },\n { value: 'cards', label: 'Card grid' },\n ],\n },\n {\n name: 'columns',\n label: 'Columns',\n description: 'Cards per row in the card grid. Default 3.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n // Meaningless on the list, where each post is its own row.\n condition: { when: 'layout', is: 'cards' },\n },\n {\n name: 'showCover',\n label: 'Show cover',\n description:\n 'Show each post’s cover image. Posts without one show their ' +\n 'title alone rather than an empty box.',\n component: Aglyn.FieldComponentType.SWITCH,\n },\n {\n name: 'headingVariant',\n label: 'Heading style',\n description:\n 'Which type step from the site theme the section heading above ' +\n 'the posts reads at. Default Heading 5.',\n component: Aglyn.FieldComponentType.SELECT,\n // The theme's own rungs, shared with the Typography element rather\n // than a second list here: two lists is how a step added to the\n // theme becomes reachable in one place and not the other.\n options: [...typographyVariants],\n },\n {\n name: 'titleVariant',\n label: 'Title style',\n description:\n 'Which type step from the site theme each post’s title reads at. ' +\n 'Default Subtitle 1; the title takes the surrounding text color ' +\n 'rather than the link color.',\n component: Aglyn.FieldComponentType.SELECT,\n options: [...typographyVariants],\n },\n {\n name: 'showDate',\n label: 'Show date',\n description:\n 'Off drops each post’s published date. The posts stay in ' +\n 'newest-first order either way — this is about the card, not the ' +\n 'ordering.',\n component: Aglyn.FieldComponentType.SWITCH,\n // Unset means SHOWN in the renderer above, so the switch has to\n // OPEN in that position or it lies about the block in front of\n // the author — and the first click, which writes the `true` that\n // was already in effect, changes nothing and reads as a dead\n // control (AGL-2506).\n initialValue: true,\n },\n {\n name: 'dateFormat',\n label: 'Date format',\n description:\n 'How each post’s published date reads on its card. Site default ' +\n 'is the format this block has always used.',\n component: Aglyn.FieldComponentType.SELECT,\n // The formats the pure layer knows how to produce, so the list\n // cannot offer a shape nothing renders. Every value is REAL,\n // including the do-nothing one (AGL-1451/AGL-1453).\n options: [...Aglyn.COLLECTION_ENTRY_DATE_FORMAT_OPTIONS],\n },\n {\n name: 'showCategory',\n label: 'Show category',\n description:\n 'Off drops the category — the chip above each card title, or the ' +\n 'second half of the line under each link on the list layout.',\n component: Aglyn.FieldComponentType.SWITCH,\n // Unset means SHOWN in the renderer above, so the switch has to\n // OPEN in that position or it lies about the block in front of\n // the author — and the first click, which writes the `true` that\n // was already in effect, changes nothing and reads as a dead\n // control (AGL-2506).\n initialValue: true,\n },\n {\n name: 'showExcerpt',\n label: 'Show excerpt',\n description:\n 'Adds each post’s excerpt under its title. Posts with no excerpt ' +\n 'are unchanged rather than leaving a gap.',\n component: Aglyn.FieldComponentType.SWITCH,\n },\n ],\n }\n\nexport { CollectionRelated }\n"],"names":["Aglyn","mdiNewspaperVariantOutline","AppLink","Box","Chip","MuiStack","Typography","forwardRef","useContext","BUNDLE_ID","toCount","RELATED_SAMPLE_PUBLISHED_AT","RELATED_SAMPLE_CATEGORY","typographyVariants","RELATED_ID","COLLECTION_RELATED_COMPONENT_ID","RELATED_DEFAULT_COLUMNS","RELATED_COVER_ASPECT","relatedChipSx","alignSelf","borderColor","color","px","typography","lineHeight","RELATED_DEFAULT_HEADING_VARIANT","RELATED_DEFAULT_TITLE_VARIANT","RELATED_SAMPLE_TITLES","RELATED_SAMPLE_EXCERPT","sampleRelatedEntries","limit","dateFormat","count","Math","min","COLLECTION_RELATED_DEFAULT_LIMIT","COLLECTION_RELATED_MAX","date","formatCollectionEntryDate","Array","from","length","_","index","title","url","category","excerpt","CollectionRelated","props","ref","heading","entries","showCover","layout","columns","headingVariant","titleVariant","showDate","showCategory","showExcerpt","rest","nodeSx","isArray","suppressNavigation","ScreenLinkContext","hostId","useSite","sample","Boolean","items","normalizeCollectionEntryDateFormat","headingNode","variant","component","titleNode","entry","linked","href","sx","underline","dateNode","excerptNode","coverNode","src","resolveMediaSrc","coverImage","undefined","aria-hidden","width","aspectRatio","borderRadius","backgroundColor","alt","renderedMediaAlt","coverImageAlt","display","objectFit","DEFERRED_IMAGE_ATTRIBUTES","sampleNotice","p","border","fontFamily","columnCount","card","spacing","label","size","gridTemplateColumns","alignItems","columnGap","rowGap","gridColumn","map","meta","filter","join","displayName","collectionRelatedSchema","$id","pluginId","description","ComponentCategory","DATA_DISPLAY","icon","path","flags","selfClosing","FEATURE_FLAG","ENABLED","attributes","name","FieldComponentType","TEXT_FIELD","type","SELECT","options","value","condition","when","is","SWITCH","initialValue","COLLECTION_ENTRY_DATE_FORMAT_OPTIONS"],"mappings":"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"}
|