@aglyn/plugins-mui 1.0.0-beta.215 → 1.0.0-beta.217
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/plugins-mui",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.217",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,12 +25,12 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/aglyn": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-data-enums": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
31
|
-
"@aglyn/shared-ui-jsx": "1.0.0-beta.
|
|
32
|
-
"@aglyn/shared-ui-theme": "1.0.0-beta.
|
|
33
|
-
"@aglyn/shared-util-vendor": "1.0.0-beta.
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.217",
|
|
29
|
+
"@aglyn/shared-data-enums": "1.0.0-beta.217",
|
|
30
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.217",
|
|
31
|
+
"@aglyn/shared-ui-jsx": "1.0.0-beta.217",
|
|
32
|
+
"@aglyn/shared-ui-theme": "1.0.0-beta.217",
|
|
33
|
+
"@aglyn/shared-util-vendor": "1.0.0-beta.217",
|
|
34
34
|
"@swc/helpers": "0.5.23",
|
|
35
35
|
"mobx": "^6.16.1",
|
|
36
36
|
"mobx-react-lite": "^4.1.1"
|
|
@@ -0,0 +1,41 @@
|
|
|
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';
|
|
18
|
+
/**
|
|
19
|
+
* The author's profile links as a row of marks (AGL-2516).
|
|
20
|
+
*
|
|
21
|
+
* Shared by the article's Entry Author card and the Author Profile block on
|
|
22
|
+
* the author's own page (AGL-2518) — the one part of the two that would
|
|
23
|
+
* genuinely drift, since an icon row is where the accessible-name rule and
|
|
24
|
+
* the `rel` on an off-site profile live.
|
|
25
|
+
*
|
|
26
|
+
* Renders nothing for an empty list, so a caller can drop it in
|
|
27
|
+
* unconditionally rather than guarding at every call site.
|
|
28
|
+
*/
|
|
29
|
+
export declare function AuthorLinkRows(props: {
|
|
30
|
+
links: readonly Aglyn.ContentAuthorLink[];
|
|
31
|
+
/** Glyph scale; the profile draws them a step larger than the card. */
|
|
32
|
+
size?: number;
|
|
33
|
+
}): import("react").JSX.Element;
|
|
34
|
+
/**
|
|
35
|
+
* The rows a card will actually draw, guarded at the boundary.
|
|
36
|
+
*
|
|
37
|
+
* These arrive as PROPS, so a link can reach a renderer without ever passing
|
|
38
|
+
* through the store's normalizer — the reason the byline's own `url` is
|
|
39
|
+
* checked twice as well.
|
|
40
|
+
*/
|
|
41
|
+
export declare function safeAuthorLinks(links: readonly Aglyn.ContentAuthorLink[] | undefined, show: boolean | undefined): Aglyn.ContentAuthorLink[];
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
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";
|
|
18
|
+
import { mdiEmail, mdiFacebook, mdiGithub, mdiInstagram, mdiLinkVariant, mdiLinkedin, mdiMastodon, mdiRss, mdiTwitter, mdiWeb, mdiYoutube } from "@aglyn/shared-data-mdi";
|
|
19
|
+
import { AppLink, MdiIcon } from "@aglyn/shared-ui-jsx";
|
|
20
|
+
import MuiStack from "@mui/material/Stack";
|
|
21
|
+
/**
|
|
22
|
+
* An author's profile links as a row of marks (AGL-2516), shared by the Entry
|
|
23
|
+
* Author card and the Author Profile block, and by nothing else — its own
|
|
24
|
+
* module so the other collection elements do not download ten platform marks
|
|
25
|
+
* (AGL-3401).
|
|
26
|
+
*/ /**
|
|
27
|
+
* The mark for each platform the author model knows (AGL-2516).
|
|
28
|
+
*
|
|
29
|
+
* Imported rather than resolved through the icon catalog, because this set is
|
|
30
|
+
* CLOSED and known at build time — the catalog is ~2.9 MB and only picker
|
|
31
|
+
* surfaces load it, so a renderer that reached for it to draw ten glyphs
|
|
32
|
+
* would ship the whole thing to every visitor of every article.
|
|
33
|
+
*
|
|
34
|
+
* Keyed by the registry's `icon`, so adding a platform is one entry there and
|
|
35
|
+
* one import here, and a platform whose mark was never imported falls through
|
|
36
|
+
* to the generic link glyph rather than rendering nothing.
|
|
37
|
+
*/ const AUTHOR_LINK_ICON_PATHS = {
|
|
38
|
+
twitter: mdiTwitter.path,
|
|
39
|
+
linkedin: mdiLinkedin.path,
|
|
40
|
+
github: mdiGithub.path,
|
|
41
|
+
mastodon: mdiMastodon.path,
|
|
42
|
+
youtube: mdiYoutube.path,
|
|
43
|
+
instagram: mdiInstagram.path,
|
|
44
|
+
facebook: mdiFacebook.path,
|
|
45
|
+
web: mdiWeb.path,
|
|
46
|
+
email: mdiEmail.path,
|
|
47
|
+
rss: mdiRss.path
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* What one link row draws.
|
|
51
|
+
*
|
|
52
|
+
* A known platform takes its mark from the table above. A CUSTOM link takes
|
|
53
|
+
* the path the console stored beside the picked id (AGL-1212) — and falls back
|
|
54
|
+
* to a generic link glyph rather than to nothing, because a row with a label
|
|
55
|
+
* and a url is still a working link whose icon simply failed to resolve.
|
|
56
|
+
*/ function authorLinkIconPath(link) {
|
|
57
|
+
var _link_iconPath;
|
|
58
|
+
const platform = Aglyn.authorLinkPlatform(link.platform);
|
|
59
|
+
if (platform) {
|
|
60
|
+
var _AUTHOR_LINK_ICON_PATHS_platform_icon;
|
|
61
|
+
return (_AUTHOR_LINK_ICON_PATHS_platform_icon = AUTHOR_LINK_ICON_PATHS[platform.icon]) != null ? _AUTHOR_LINK_ICON_PATHS_platform_icon : mdiLinkVariant.path;
|
|
62
|
+
}
|
|
63
|
+
return ((_link_iconPath = link.iconPath) != null ? _link_iconPath : '').trim() || mdiLinkVariant.path;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Schemes a rendered author link may use — the model's own guard, restated at
|
|
67
|
+
* the boundary that produces the `href`.
|
|
68
|
+
*
|
|
69
|
+
* `normalizeContentAuthorLinks` already drops everything else, and this is
|
|
70
|
+
* deliberately not trusting that: the props on this component are authorable,
|
|
71
|
+
* so a link can reach the renderer without ever passing through the store's
|
|
72
|
+
* normalizer. Two checks, because only one of them is on the path an attacker
|
|
73
|
+
* would use.
|
|
74
|
+
*/ const SAFE_AUTHOR_LINK_HREF = /^(https:\/\/|mailto:)/i;
|
|
75
|
+
/**
|
|
76
|
+
* The author's profile links as a row of marks (AGL-2516).
|
|
77
|
+
*
|
|
78
|
+
* Shared by the article's Entry Author card and the Author Profile block on
|
|
79
|
+
* the author's own page (AGL-2518) — the one part of the two that would
|
|
80
|
+
* genuinely drift, since an icon row is where the accessible-name rule and
|
|
81
|
+
* the `rel` on an off-site profile live.
|
|
82
|
+
*
|
|
83
|
+
* Renders nothing for an empty list, so a caller can drop it in
|
|
84
|
+
* unconditionally rather than guarding at every call site.
|
|
85
|
+
*/ export function AuthorLinkRows(props) {
|
|
86
|
+
if (!props.links.length) return null;
|
|
87
|
+
return /*#__PURE__*/ _jsx(MuiStack, {
|
|
88
|
+
direction: "row",
|
|
89
|
+
spacing: 0.5,
|
|
90
|
+
sx: {
|
|
91
|
+
flexWrap: 'wrap',
|
|
92
|
+
ml: -1
|
|
93
|
+
},
|
|
94
|
+
children: props.links.map((link, index)=>{
|
|
95
|
+
var _link_url, _props_size;
|
|
96
|
+
const label = Aglyn.authorLinkLabel(link);
|
|
97
|
+
return /*#__PURE__*/ _jsx(AppLink, {
|
|
98
|
+
componentVariant: "icon-button",
|
|
99
|
+
href: ((_link_url = link.url) != null ? _link_url : '').trim(),
|
|
100
|
+
// The name IS the icon here, so it has to be announced — a row of
|
|
101
|
+
// unlabelled icon buttons is a row a screen reader reads as
|
|
102
|
+
// "link, link, link".
|
|
103
|
+
"aria-label": label,
|
|
104
|
+
title: label,
|
|
105
|
+
size: "small",
|
|
106
|
+
sx: {
|
|
107
|
+
color: 'text.secondary'
|
|
108
|
+
},
|
|
109
|
+
// A profile lives off this site by definition, and `mailto:` must
|
|
110
|
+
// not replace the article either.
|
|
111
|
+
target: "_blank",
|
|
112
|
+
rel: "noopener noreferrer me",
|
|
113
|
+
children: /*#__PURE__*/ _jsx(MdiIcon, {
|
|
114
|
+
path: authorLinkIconPath(link),
|
|
115
|
+
size: (_props_size = props.size) != null ? _props_size : 0.85
|
|
116
|
+
})
|
|
117
|
+
}, index);
|
|
118
|
+
})
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The rows a card will actually draw, guarded at the boundary.
|
|
123
|
+
*
|
|
124
|
+
* These arrive as PROPS, so a link can reach a renderer without ever passing
|
|
125
|
+
* through the store's normalizer — the reason the byline's own `url` is
|
|
126
|
+
* checked twice as well.
|
|
127
|
+
*/ export function safeAuthorLinks(links, show) {
|
|
128
|
+
if (show === false) return [];
|
|
129
|
+
return (links != null ? links : []).filter((link)=>{
|
|
130
|
+
var _ref;
|
|
131
|
+
return SAFE_AUTHOR_LINK_HREF.test(((_ref = link == null ? void 0 : link.url) != null ? _ref : '').trim());
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
//# sourceMappingURL=collection-author-links.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/collection-author-links.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 {\n mdiEmail,\n mdiFacebook,\n mdiGithub,\n mdiInstagram,\n mdiLinkVariant,\n mdiLinkedin,\n mdiMastodon,\n mdiRss,\n mdiTwitter,\n mdiWeb,\n mdiYoutube,\n} from '@aglyn/shared-data-mdi'\nimport { AppLink, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport MuiStack from '@mui/material/Stack'\n\n/**\n * An author's profile links as a row of marks (AGL-2516), shared by the Entry\n * Author card and the Author Profile block, and by nothing else — its own\n * module so the other collection elements do not download ten platform marks\n * (AGL-3401).\n */\n\n/**\n * The mark for each platform the author model knows (AGL-2516).\n *\n * Imported rather than resolved through the icon catalog, because this set is\n * CLOSED and known at build time — the catalog is ~2.9 MB and only picker\n * surfaces load it, so a renderer that reached for it to draw ten glyphs\n * would ship the whole thing to every visitor of every article.\n *\n * Keyed by the registry's `icon`, so adding a platform is one entry there and\n * one import here, and a platform whose mark was never imported falls through\n * to the generic link glyph rather than rendering nothing.\n */\nconst AUTHOR_LINK_ICON_PATHS: Record<string, string> = {\n twitter: mdiTwitter.path,\n linkedin: mdiLinkedin.path,\n github: mdiGithub.path,\n mastodon: mdiMastodon.path,\n youtube: mdiYoutube.path,\n instagram: mdiInstagram.path,\n facebook: mdiFacebook.path,\n web: mdiWeb.path,\n email: mdiEmail.path,\n rss: mdiRss.path,\n}\n\n/**\n * What one link row draws.\n *\n * A known platform takes its mark from the table above. A CUSTOM link takes\n * the path the console stored beside the picked id (AGL-1212) — and falls back\n * to a generic link glyph rather than to nothing, because a row with a label\n * and a url is still a working link whose icon simply failed to resolve.\n */\nfunction authorLinkIconPath(link: Aglyn.ContentAuthorLink): string {\n const platform = Aglyn.authorLinkPlatform(link.platform)\n if (platform) {\n return AUTHOR_LINK_ICON_PATHS[platform.icon] ?? mdiLinkVariant.path\n }\n return (link.iconPath ?? '').trim() || mdiLinkVariant.path\n}\n\n/**\n * Schemes a rendered author link may use — the model's own guard, restated at\n * the boundary that produces the `href`.\n *\n * `normalizeContentAuthorLinks` already drops everything else, and this is\n * deliberately not trusting that: the props on this component are authorable,\n * so a link can reach the renderer without ever passing through the store's\n * normalizer. Two checks, because only one of them is on the path an attacker\n * would use.\n */\nconst SAFE_AUTHOR_LINK_HREF = /^(https:\\/\\/|mailto:)/i\n\n/**\n * The author's profile links as a row of marks (AGL-2516).\n *\n * Shared by the article's Entry Author card and the Author Profile block on\n * the author's own page (AGL-2518) — the one part of the two that would\n * genuinely drift, since an icon row is where the accessible-name rule and\n * the `rel` on an off-site profile live.\n *\n * Renders nothing for an empty list, so a caller can drop it in\n * unconditionally rather than guarding at every call site.\n */\nexport function AuthorLinkRows(props: {\n links: readonly Aglyn.ContentAuthorLink[]\n /** Glyph scale; the profile draws them a step larger than the card. */\n size?: number\n}) {\n if (!props.links.length) return null\n return (\n <MuiStack direction=\"row\" spacing={0.5} sx={{ flexWrap: 'wrap', ml: -1 }}>\n {props.links.map((link, index) => {\n const label = Aglyn.authorLinkLabel(link)\n return (\n <AppLink\n key={index}\n componentVariant=\"icon-button\"\n href={(link.url ?? '').trim()}\n // The name IS the icon here, so it has to be announced — a row of\n // unlabelled icon buttons is a row a screen reader reads as\n // \"link, link, link\".\n aria-label={label}\n title={label}\n size=\"small\"\n sx={{ color: 'text.secondary' }}\n // A profile lives off this site by definition, and `mailto:` must\n // not replace the article either.\n target=\"_blank\"\n rel=\"noopener noreferrer me\"\n >\n <MdiIcon path={authorLinkIconPath(link)} size={props.size ?? 0.85} />\n </AppLink>\n )\n })}\n </MuiStack>\n )\n}\n\n/**\n * The rows a card will actually draw, guarded at the boundary.\n *\n * These arrive as PROPS, so a link can reach a renderer without ever passing\n * through the store's normalizer — the reason the byline's own `url` is\n * checked twice as well.\n */\nexport function safeAuthorLinks(\n links: readonly Aglyn.ContentAuthorLink[] | undefined,\n show: boolean | undefined,\n): Aglyn.ContentAuthorLink[] {\n if (show === false) return []\n return (links ?? []).filter((link) =>\n SAFE_AUTHOR_LINK_HREF.test((link?.url ?? '').trim()),\n )\n}\n"],"names":["Aglyn","mdiEmail","mdiFacebook","mdiGithub","mdiInstagram","mdiLinkVariant","mdiLinkedin","mdiMastodon","mdiRss","mdiTwitter","mdiWeb","mdiYoutube","AppLink","MdiIcon","MuiStack","AUTHOR_LINK_ICON_PATHS","twitter","path","linkedin","github","mastodon","youtube","instagram","facebook","web","email","rss","authorLinkIconPath","link","platform","authorLinkPlatform","icon","iconPath","trim","SAFE_AUTHOR_LINK_HREF","AuthorLinkRows","props","links","length","direction","spacing","sx","flexWrap","ml","map","index","label","authorLinkLabel","componentVariant","href","url","aria-label","title","size","color","target","rel","safeAuthorLinks","show","filter","test"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,YAAYA,WAAW,eAAc;AACrC,SACEC,QAAQ,EACRC,WAAW,EACXC,SAAS,EACTC,YAAY,EACZC,cAAc,EACdC,WAAW,EACXC,WAAW,EACXC,MAAM,EACNC,UAAU,EACVC,MAAM,EACNC,UAAU,QACL,yBAAwB;AAC/B,SAASC,OAAO,EAAEC,OAAO,QAAQ,uBAAsB;AACvD,OAAOC,cAAc,sBAAqB;AAE1C;;;;;CAKC,GAED;;;;;;;;;;;CAWC,GACD,MAAMC,yBAAiD;IACrDC,SAASP,WAAWQ,IAAI;IACxBC,UAAUZ,YAAYW,IAAI;IAC1BE,QAAQhB,UAAUc,IAAI;IACtBG,UAAUb,YAAYU,IAAI;IAC1BI,SAASV,WAAWM,IAAI;IACxBK,WAAWlB,aAAaa,IAAI;IAC5BM,UAAUrB,YAAYe,IAAI;IAC1BO,KAAKd,OAAOO,IAAI;IAChBQ,OAAOxB,SAASgB,IAAI;IACpBS,KAAKlB,OAAOS,IAAI;AAClB;AAEA;;;;;;;CAOC,GACD,SAASU,mBAAmBC,IAA6B;QAK/CA;IAJR,MAAMC,WAAW7B,MAAM8B,kBAAkB,CAACF,KAAKC,QAAQ;IACvD,IAAIA,UAAU;YACLd;QAAP,QAAOA,wCAAAA,sBAAsB,CAACc,SAASE,IAAI,CAAC,YAArChB,wCAAyCV,eAAeY,IAAI;IACrE;IACA,OAAO,EAACW,iBAAAA,KAAKI,QAAQ,YAAbJ,iBAAiB,IAAIK,IAAI,MAAM5B,eAAeY,IAAI;AAC5D;AAEA;;;;;;;;;CASC,GACD,MAAMiB,wBAAwB;AAE9B;;;;;;;;;;CAUC,GACD,OAAO,SAASC,eAAeC,KAI9B;IACC,IAAI,CAACA,MAAMC,KAAK,CAACC,MAAM,EAAE,OAAO;IAChC,qBACE,KAACxB;QAASyB,WAAU;QAAMC,SAAS;QAAKC,IAAI;YAAEC,UAAU;YAAQC,IAAI,CAAC;QAAE;kBACpEP,MAAMC,KAAK,CAACO,GAAG,CAAC,CAAChB,MAAMiB;gBAMXjB,WAawCQ;YAlBnD,MAAMU,QAAQ9C,MAAM+C,eAAe,CAACnB;YACpC,qBACE,KAAChB;gBAECoC,kBAAiB;gBACjBC,MAAM,EAACrB,YAAAA,KAAKsB,GAAG,YAARtB,YAAY,IAAIK,IAAI;gBAC3B,kEAAkE;gBAClE,4DAA4D;gBAC5D,sBAAsB;gBACtBkB,cAAYL;gBACZM,OAAON;gBACPO,MAAK;gBACLZ,IAAI;oBAAEa,OAAO;gBAAiB;gBAC9B,kEAAkE;gBAClE,kCAAkC;gBAClCC,QAAO;gBACPC,KAAI;0BAEJ,cAAA,KAAC3C;oBAAQI,MAAMU,mBAAmBC;oBAAOyB,IAAI,GAAEjB,cAAAA,MAAMiB,IAAI,YAAVjB,cAAc;;eAfxDS;QAkBX;;AAGN;AAEA;;;;;;CAMC,GACD,OAAO,SAASY,gBACdpB,KAAqD,EACrDqB,IAAyB;IAEzB,IAAIA,SAAS,OAAO,OAAO,EAAE;IAC7B,OAAO,CAACrB,gBAAAA,QAAS,EAAE,EAAEsB,MAAM,CAAC,CAAC/B;;eAC3BM,sBAAsB0B,IAAI,CAAC,SAAChC,wBAAAA,KAAMsB,GAAG,mBAAI,IAAIjB,IAAI;;AAErD"}
|
|
@@ -0,0 +1,67 @@
|
|
|
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';
|
|
18
|
+
import { type StackProps } from '@mui/material/Stack';
|
|
19
|
+
export declare const AUTHOR_PROFILE_ID: Aglyn.ComponentId;
|
|
20
|
+
export interface ContentAuthorProfileProps extends StackProps {
|
|
21
|
+
/**
|
|
22
|
+
* The byline. Server-filled from the routed author on their own page
|
|
23
|
+
* (`expandContentAuthorProfile`); set it — or bind `{{author.name}}` —
|
|
24
|
+
* only to override.
|
|
25
|
+
*/
|
|
26
|
+
name?: string;
|
|
27
|
+
/** The blurb from their record; bind `{{author.bio}}` to override. */
|
|
28
|
+
bio?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Portrait or logo — a media reference (AGL-1215) or any URL, filled from
|
|
31
|
+
* the record so the face follows the person rather than the template.
|
|
32
|
+
*/
|
|
33
|
+
image?: string;
|
|
34
|
+
/** Person only — what they do; blank on an Organization. */
|
|
35
|
+
jobTitle?: string;
|
|
36
|
+
/** Person only — who they write for. */
|
|
37
|
+
worksFor?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Their OWN site. The name links here; blank renders it as plain text.
|
|
40
|
+
* This block already IS the author's page, so it does not link to itself.
|
|
41
|
+
*/
|
|
42
|
+
url?: string;
|
|
43
|
+
/** Their profile links, server-filled from the record (AGL-2516). */
|
|
44
|
+
links?: Aglyn.ContentAuthorLink[];
|
|
45
|
+
showBio?: boolean;
|
|
46
|
+
showAvatar?: boolean;
|
|
47
|
+
showRole?: boolean;
|
|
48
|
+
showLinks?: boolean;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Who an author page is about (AGL-2518) — portrait, name, role, bio, links.
|
|
52
|
+
*
|
|
53
|
+
* The Entry Author card renders the same person as a footnote under an
|
|
54
|
+
* article. This renders them as the SUBJECT of a page, which is why it has
|
|
55
|
+
* the two fields that card has no room for: `jobTitle` and `worksFor` are
|
|
56
|
+
* what tell a stranger who they are looking at, and they are already on the
|
|
57
|
+
* record because `schema.org/Person` defines them (AGL-2486).
|
|
58
|
+
*
|
|
59
|
+
* Every field is independently overridable and every one collapses when
|
|
60
|
+
* empty, exactly as the card's do — an author with no portrait renders the
|
|
61
|
+
* text alone rather than a gap where a face would be. On an editing surface
|
|
62
|
+
* the block previews its own shape with sample text, because a template is
|
|
63
|
+
* designed before any author is routed through it.
|
|
64
|
+
*/
|
|
65
|
+
declare const ContentAuthorProfile: import("react").ForwardRefExoticComponent<Omit<ContentAuthorProfileProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
|
|
66
|
+
export { ContentAuthorProfile };
|
|
67
|
+
export declare const contentAuthorProfileSchema: Aglyn.ComponentSchema<ContentAuthorProfileProps>;
|
|
@@ -0,0 +1,269 @@
|
|
|
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 { mdiAccountCircleOutline } from "@aglyn/shared-data-mdi";
|
|
21
|
+
import { AppLink } from "@aglyn/shared-ui-jsx";
|
|
22
|
+
import Box from "@mui/material/Box";
|
|
23
|
+
import MuiStack from "@mui/material/Stack";
|
|
24
|
+
import Typography from "@mui/material/Typography";
|
|
25
|
+
import { forwardRef, useContext } from "react";
|
|
26
|
+
import { BUNDLE_ID } from "../constants/bundle-common.js";
|
|
27
|
+
import { UNRESOLVED_TOKEN, ENTRY_SAMPLE_AUTHOR, ENTRY_SAMPLE_BIO, samplePortraitSx, metaValue, SAFE_AUTHOR_HREF, EXTERNAL_AUTHOR_HREF } from "./collection-common.js";
|
|
28
|
+
import { AuthorLinkRows, safeAuthorLinks } from "./collection-author-links.js";
|
|
29
|
+
// Persisted component id (AGL-551/582); the compose pipeline references it
|
|
30
|
+
// through the @aglyn/aglyn constant. Never rename.
|
|
31
|
+
export const AUTHOR_PROFILE_ID = Aglyn.CONTENT_AUTHOR_PROFILE_COMPONENT_ID;
|
|
32
|
+
/** The page subject's portrait — a step up from the card's 48px. */ const PROFILE_AVATAR_SIZE = 96;
|
|
33
|
+
/**
|
|
34
|
+
* Who an author page is about (AGL-2518) — portrait, name, role, bio, links.
|
|
35
|
+
*
|
|
36
|
+
* The Entry Author card renders the same person as a footnote under an
|
|
37
|
+
* article. This renders them as the SUBJECT of a page, which is why it has
|
|
38
|
+
* the two fields that card has no room for: `jobTitle` and `worksFor` are
|
|
39
|
+
* what tell a stranger who they are looking at, and they are already on the
|
|
40
|
+
* record because `schema.org/Person` defines them (AGL-2486).
|
|
41
|
+
*
|
|
42
|
+
* Every field is independently overridable and every one collapses when
|
|
43
|
+
* empty, exactly as the card's do — an author with no portrait renders the
|
|
44
|
+
* text alone rather than a gap where a face would be. On an editing surface
|
|
45
|
+
* the block previews its own shape with sample text, because a template is
|
|
46
|
+
* designed before any author is routed through it.
|
|
47
|
+
*/ const ContentAuthorProfile = /*#__PURE__*/ forwardRef((props, ref)=>{
|
|
48
|
+
// None of these are DOM attributes (`name` least of all), so they are
|
|
49
|
+
// destructured rather than spread — the stack.ts pattern.
|
|
50
|
+
const { name, bio, image, jobTitle, worksFor, url, links, showBio, showAvatar, showRole, showLinks } = props, rest = _object_without_properties_loose(props, [
|
|
51
|
+
"name",
|
|
52
|
+
"bio",
|
|
53
|
+
"image",
|
|
54
|
+
"jobTitle",
|
|
55
|
+
"worksFor",
|
|
56
|
+
"url",
|
|
57
|
+
"links",
|
|
58
|
+
"showBio",
|
|
59
|
+
"showAvatar",
|
|
60
|
+
"showRole",
|
|
61
|
+
"showLinks"
|
|
62
|
+
]);
|
|
63
|
+
const nodeSx = Array.isArray(props['sx']) ? props['sx'] : [
|
|
64
|
+
props['sx']
|
|
65
|
+
];
|
|
66
|
+
const { suppressNavigation } = useContext(Aglyn.ScreenLinkContext);
|
|
67
|
+
const { hostId } = Aglyn.useSite();
|
|
68
|
+
const nameValue = metaValue(name, suppressNavigation);
|
|
69
|
+
const bioValue = showBio !== false ? metaValue(bio, suppressNavigation) : '';
|
|
70
|
+
const jobValue = showRole !== false ? metaValue(jobTitle, suppressNavigation) : '';
|
|
71
|
+
const worksForValue = showRole !== false ? metaValue(worksFor, suppressNavigation) : '';
|
|
72
|
+
const urlRaw = metaValue(url, suppressNavigation);
|
|
73
|
+
const urlValue = SAFE_AUTHOR_HREF.test(urlRaw) ? urlRaw : '';
|
|
74
|
+
// An unresolved token empties on EVERY surface, as in the byline: a literal
|
|
75
|
+
// `{{…}}` in a src is a broken portrait in the canvas.
|
|
76
|
+
const imageRaw = (image != null ? image : '').trim();
|
|
77
|
+
const imageSrc = showAvatar !== false && imageRaw && !UNRESOLVED_TOKEN.test(imageRaw) ? Aglyn.resolveMediaSrc(imageRaw, {
|
|
78
|
+
hostId
|
|
79
|
+
}) : '';
|
|
80
|
+
const linkRows = safeAuthorLinks(links, showLinks);
|
|
81
|
+
// Nothing routed and nothing typed: an editing surface previews the block's
|
|
82
|
+
// own markup at its own settings, and a published page renders nothing at
|
|
83
|
+
// all. Every part still answers its own switch, so a block with Show bio
|
|
84
|
+
// off previews as portrait and name.
|
|
85
|
+
const sample = !nameValue && !bioValue && !jobValue && !imageSrc && !linkRows.length && Boolean(suppressNavigation);
|
|
86
|
+
const displayName = sample ? ENTRY_SAMPLE_AUTHOR : nameValue;
|
|
87
|
+
const displayBio = sample && showBio !== false ? ENTRY_SAMPLE_BIO : bioValue;
|
|
88
|
+
const samplePortrait = sample && showAvatar !== false;
|
|
89
|
+
/**
|
|
90
|
+
* "Founder at Aglyn" — one line, from two fields, and a middle dot would be
|
|
91
|
+
* wrong here: this is a phrase, not a list of facts like the byline's
|
|
92
|
+
* `name · date`. Either field alone still reads.
|
|
93
|
+
*/ const roleLine = [
|
|
94
|
+
jobValue,
|
|
95
|
+
worksForValue
|
|
96
|
+
].filter(Boolean).join(' at ');
|
|
97
|
+
if (!displayName && !displayBio && !roleLine && !imageSrc && !samplePortrait && !linkRows.length) {
|
|
98
|
+
return /*#__PURE__*/ _jsx(Box, _extends({
|
|
99
|
+
ref: ref
|
|
100
|
+
}, rest));
|
|
101
|
+
}
|
|
102
|
+
return /*#__PURE__*/ _jsxs(MuiStack, _extends({
|
|
103
|
+
ref: ref,
|
|
104
|
+
direction: "row",
|
|
105
|
+
spacing: 3
|
|
106
|
+
}, rest, {
|
|
107
|
+
// MERGE, never replace (AGL-1450): `rest.sx` is the array leaf.tsx
|
|
108
|
+
// builds, and spreading it into an object drops every authored value.
|
|
109
|
+
sx: [
|
|
110
|
+
{
|
|
111
|
+
alignItems: 'flex-start'
|
|
112
|
+
},
|
|
113
|
+
...nodeSx
|
|
114
|
+
],
|
|
115
|
+
children: [
|
|
116
|
+
imageSrc ? /*#__PURE__*/ _jsx(Box, _extends({
|
|
117
|
+
component: "img",
|
|
118
|
+
src: imageSrc,
|
|
119
|
+
// Decorative: the block names the author in text right beside it.
|
|
120
|
+
alt: "",
|
|
121
|
+
sx: {
|
|
122
|
+
display: 'block',
|
|
123
|
+
flexShrink: 0,
|
|
124
|
+
width: PROFILE_AVATAR_SIZE,
|
|
125
|
+
height: PROFILE_AVATAR_SIZE,
|
|
126
|
+
borderRadius: '50%',
|
|
127
|
+
objectFit: 'cover'
|
|
128
|
+
}
|
|
129
|
+
}, Aglyn.DEFERRED_IMAGE_ATTRIBUTES)) : samplePortrait ? /*#__PURE__*/ _jsx(Box, {
|
|
130
|
+
"aria-hidden": true,
|
|
131
|
+
sx: samplePortraitSx(PROFILE_AVATAR_SIZE)
|
|
132
|
+
}) : null,
|
|
133
|
+
/*#__PURE__*/ _jsxs(MuiStack, {
|
|
134
|
+
spacing: 1,
|
|
135
|
+
sx: {
|
|
136
|
+
minWidth: 0
|
|
137
|
+
},
|
|
138
|
+
children: [
|
|
139
|
+
displayName ? // `h1` because the author IS this page's subject — the one heading
|
|
140
|
+
// rule a built-in body has to get right, since the template around
|
|
141
|
+
// it cannot know what the block will be handed.
|
|
142
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
143
|
+
component: "h1",
|
|
144
|
+
variant: "h4",
|
|
145
|
+
children: urlValue ? /*#__PURE__*/ _jsx(AppLink, _extends({
|
|
146
|
+
href: urlValue,
|
|
147
|
+
sx: {
|
|
148
|
+
color: 'inherit'
|
|
149
|
+
},
|
|
150
|
+
underline: "hover"
|
|
151
|
+
}, EXTERNAL_AUTHOR_HREF.test(urlValue) ? {
|
|
152
|
+
target: '_blank',
|
|
153
|
+
rel: 'noopener noreferrer'
|
|
154
|
+
} : {}, {
|
|
155
|
+
children: displayName
|
|
156
|
+
})) : displayName
|
|
157
|
+
}) : null,
|
|
158
|
+
roleLine ? /*#__PURE__*/ _jsx(Typography, {
|
|
159
|
+
variant: "subtitle2",
|
|
160
|
+
sx: {
|
|
161
|
+
color: 'text.secondary'
|
|
162
|
+
},
|
|
163
|
+
children: roleLine
|
|
164
|
+
}) : null,
|
|
165
|
+
displayBio ? /*#__PURE__*/ _jsx(Typography, {
|
|
166
|
+
variant: "body1",
|
|
167
|
+
sx: {
|
|
168
|
+
color: 'text.secondary'
|
|
169
|
+
},
|
|
170
|
+
children: displayBio
|
|
171
|
+
}) : null,
|
|
172
|
+
/*#__PURE__*/ _jsx(AuthorLinkRows, {
|
|
173
|
+
links: linkRows,
|
|
174
|
+
size: 1
|
|
175
|
+
})
|
|
176
|
+
]
|
|
177
|
+
})
|
|
178
|
+
]
|
|
179
|
+
}));
|
|
180
|
+
});
|
|
181
|
+
ContentAuthorProfile.displayName = 'AglynContentAuthorProfile';
|
|
182
|
+
export { ContentAuthorProfile };
|
|
183
|
+
export const contentAuthorProfileSchema = {
|
|
184
|
+
$id: AUTHOR_PROFILE_ID,
|
|
185
|
+
pluginId: BUNDLE_ID,
|
|
186
|
+
displayName: 'Author Profile',
|
|
187
|
+
description: 'Who an author page is about — portrait, name, role, bio and links.',
|
|
188
|
+
category: Aglyn.ComponentCategory.TEXT,
|
|
189
|
+
icon: {
|
|
190
|
+
path: mdiAccountCircleOutline.path,
|
|
191
|
+
sx: {
|
|
192
|
+
color: 'secondary.main'
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
flags: {
|
|
196
|
+
selfClosing: Aglyn.FEATURE_FLAG.ENABLED
|
|
197
|
+
},
|
|
198
|
+
attributes: [
|
|
199
|
+
{
|
|
200
|
+
name: 'name',
|
|
201
|
+
label: 'Name',
|
|
202
|
+
description: 'Blank shows the name of the author whose page this is. Type here ' + '(or bind {{author.name}}) only to override it.',
|
|
203
|
+
component: Aglyn.FieldComponentType.TEXT_FIELD
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
name: 'bio',
|
|
207
|
+
label: 'Bio',
|
|
208
|
+
description: 'Blank shows the bio from the author’s record. Type here (or bind ' + '{{author.bio}}) only to override it.',
|
|
209
|
+
component: Aglyn.FieldComponentType.TEXTAREA
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
name: 'image',
|
|
213
|
+
label: 'Portrait',
|
|
214
|
+
description: 'Blank shows the portrait of the author whose page this is, at the ' + 'larger size a profile takes. Pick from your media library only to ' + 'override it.',
|
|
215
|
+
component: Aglyn.FieldComponentType.TEXT_FIELD
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
name: 'jobTitle',
|
|
219
|
+
label: 'Role',
|
|
220
|
+
description: 'Blank shows the job title from the author’s record. Only a person ' + 'has one — an author record set to Organization leaves it empty.',
|
|
221
|
+
component: Aglyn.FieldComponentType.TEXT_FIELD
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
name: 'worksFor',
|
|
225
|
+
label: 'Organization',
|
|
226
|
+
description: 'Blank shows who the author writes for, from their record. Shown ' + 'after the role as "Role at Organization".',
|
|
227
|
+
component: Aglyn.FieldComponentType.TEXT_FIELD
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
name: 'url',
|
|
231
|
+
label: 'Link',
|
|
232
|
+
description: 'Blank uses the author’s own url. The name links here; with no url ' + 'it renders as plain text. This block is already the author’s page ' + 'on this site, so it never links to itself.',
|
|
233
|
+
component: Aglyn.FieldComponentType.TEXT_FIELD
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
name: 'showLinks',
|
|
237
|
+
label: 'Show links',
|
|
238
|
+
description: 'Off hides the row of profile links under the bio — the place a ' + 'reader follows this author elsewhere. Turning it off on a page ' + 'about them is rarely what you want.',
|
|
239
|
+
component: Aglyn.FieldComponentType.SWITCH,
|
|
240
|
+
// Unset means SHOWN in the renderer above, so the switch has to open
|
|
241
|
+
// in that position or it lies about the block in front of the author
|
|
242
|
+
// (AGL-2506).
|
|
243
|
+
initialValue: true
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
name: 'showBio',
|
|
247
|
+
label: 'Show bio',
|
|
248
|
+
description: 'Off leaves the block as a portrait, a name and a role.',
|
|
249
|
+
component: Aglyn.FieldComponentType.SWITCH,
|
|
250
|
+
initialValue: true
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
name: 'showRole',
|
|
254
|
+
label: 'Show role',
|
|
255
|
+
description: 'Off hides the "Role at Organization" line under the name.',
|
|
256
|
+
component: Aglyn.FieldComponentType.SWITCH,
|
|
257
|
+
initialValue: true
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: 'showAvatar',
|
|
261
|
+
label: 'Show portrait',
|
|
262
|
+
description: 'Off drops the portrait and lets the name and bio run the full ' + 'width, with no space held for a picture.',
|
|
263
|
+
component: Aglyn.FieldComponentType.SWITCH,
|
|
264
|
+
initialValue: true
|
|
265
|
+
}
|
|
266
|
+
]
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
//# sourceMappingURL=collection-author-profile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/collection-author-profile.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 { mdiAccountCircleOutline } from '@aglyn/shared-data-mdi'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport Box from '@mui/material/Box'\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'\nimport {\n UNRESOLVED_TOKEN,\n ENTRY_SAMPLE_AUTHOR,\n ENTRY_SAMPLE_BIO,\n samplePortraitSx,\n metaValue,\n SAFE_AUTHOR_HREF,\n EXTERNAL_AUTHOR_HREF,\n} from './collection-common'\nimport { AuthorLinkRows, safeAuthorLinks } from './collection-author-links'\n\n// Persisted component id (AGL-551/582); the compose pipeline references it\n// through the @aglyn/aglyn constant. Never rename.\nexport const AUTHOR_PROFILE_ID: Aglyn.ComponentId =\n Aglyn.CONTENT_AUTHOR_PROFILE_COMPONENT_ID\n\nexport interface ContentAuthorProfileProps extends StackProps {\n /**\n * The byline. Server-filled from the routed author on their own page\n * (`expandContentAuthorProfile`); set it — or bind `{{author.name}}` —\n * only to override.\n */\n name?: string\n /** The blurb from their record; bind `{{author.bio}}` to override. */\n bio?: string\n /**\n * Portrait or logo — a media reference (AGL-1215) or any URL, filled from\n * the record so the face follows the person rather than the template.\n */\n image?: string\n /** Person only — what they do; blank on an Organization. */\n jobTitle?: string\n /** Person only — who they write for. */\n worksFor?: string\n /**\n * Their OWN site. The name links here; blank renders it as plain text.\n * This block already IS the author's page, so it does not link to itself.\n */\n url?: string\n /** Their profile links, server-filled from the record (AGL-2516). */\n links?: Aglyn.ContentAuthorLink[]\n showBio?: boolean\n showAvatar?: boolean\n showRole?: boolean\n showLinks?: boolean\n}\n\n/** The page subject's portrait — a step up from the card's 48px. */\nconst PROFILE_AVATAR_SIZE = 96\n\n/**\n * Who an author page is about (AGL-2518) — portrait, name, role, bio, links.\n *\n * The Entry Author card renders the same person as a footnote under an\n * article. This renders them as the SUBJECT of a page, which is why it has\n * the two fields that card has no room for: `jobTitle` and `worksFor` are\n * what tell a stranger who they are looking at, and they are already on the\n * record because `schema.org/Person` defines them (AGL-2486).\n *\n * Every field is independently overridable and every one collapses when\n * empty, exactly as the card's do — an author with no portrait renders the\n * text alone rather than a gap where a face would be. On an editing surface\n * the block previews its own shape with sample text, because a template is\n * designed before any author is routed through it.\n */\nconst ContentAuthorProfile = forwardRef<\n HTMLDivElement,\n ContentAuthorProfileProps\n>((props, ref) => {\n // None of these are DOM attributes (`name` least of all), so they are\n // destructured rather than spread — the stack.ts pattern.\n const {\n name,\n bio,\n image,\n jobTitle,\n worksFor,\n url,\n links,\n showBio,\n showAvatar,\n showRole,\n showLinks,\n ...rest\n } = props\n const nodeSx = Array.isArray(props['sx']) ? props['sx'] : [props['sx']]\n const { suppressNavigation } = useContext(Aglyn.ScreenLinkContext)\n const { hostId } = Aglyn.useSite()\n const nameValue = metaValue(name, suppressNavigation)\n const bioValue = showBio !== false ? metaValue(bio, suppressNavigation) : ''\n const jobValue = showRole !== false ? metaValue(jobTitle, suppressNavigation) : ''\n const worksForValue =\n showRole !== false ? metaValue(worksFor, suppressNavigation) : ''\n const urlRaw = metaValue(url, suppressNavigation)\n const urlValue = SAFE_AUTHOR_HREF.test(urlRaw) ? urlRaw : ''\n // An unresolved token empties on EVERY surface, as in the byline: a literal\n // `{{…}}` in a src is a broken portrait in the canvas.\n const imageRaw = (image ?? '').trim()\n const imageSrc =\n showAvatar !== false && imageRaw && !UNRESOLVED_TOKEN.test(imageRaw)\n ? Aglyn.resolveMediaSrc(imageRaw, { hostId })\n : ''\n const linkRows = safeAuthorLinks(links, showLinks)\n // Nothing routed and nothing typed: an editing surface previews the block's\n // own markup at its own settings, and a published page renders nothing at\n // all. Every part still answers its own switch, so a block with Show bio\n // off previews as portrait and name.\n const sample =\n !nameValue &&\n !bioValue &&\n !jobValue &&\n !imageSrc &&\n !linkRows.length &&\n Boolean(suppressNavigation)\n const displayName = sample ? ENTRY_SAMPLE_AUTHOR : nameValue\n const displayBio = sample && showBio !== false ? ENTRY_SAMPLE_BIO : bioValue\n const samplePortrait = sample && showAvatar !== false\n /**\n * \"Founder at Aglyn\" — one line, from two fields, and a middle dot would be\n * wrong here: this is a phrase, not a list of facts like the byline's\n * `name · date`. Either field alone still reads.\n */\n const roleLine = [jobValue, worksForValue].filter(Boolean).join(' at ')\n if (\n !displayName &&\n !displayBio &&\n !roleLine &&\n !imageSrc &&\n !samplePortrait &&\n !linkRows.length\n ) {\n return <Box ref={ref} {...rest} />\n }\n return (\n <MuiStack\n ref={ref}\n direction=\"row\"\n spacing={3}\n {...rest}\n // MERGE, never replace (AGL-1450): `rest.sx` is the array leaf.tsx\n // builds, and spreading it into an object drops every authored value.\n sx={[{ alignItems: 'flex-start' }, ...nodeSx]}\n >\n {imageSrc ? (\n <Box\n component=\"img\"\n src={imageSrc}\n // Decorative: the block names the author in text right beside it.\n alt=\"\"\n sx={{\n display: 'block',\n flexShrink: 0,\n width: PROFILE_AVATAR_SIZE,\n height: PROFILE_AVATAR_SIZE,\n borderRadius: '50%',\n objectFit: 'cover',\n }}\n {...Aglyn.DEFERRED_IMAGE_ATTRIBUTES}\n />\n ) : samplePortrait ? (\n <Box aria-hidden sx={samplePortraitSx(PROFILE_AVATAR_SIZE)} />\n ) : null}\n <MuiStack spacing={1} sx={{ minWidth: 0 }}>\n {displayName ? (\n // `h1` because the author IS this page's subject — the one heading\n // rule a built-in body has to get right, since the template around\n // it cannot know what the block will be handed.\n <Typography component=\"h1\" variant=\"h4\">\n {urlValue ? (\n <AppLink\n href={urlValue}\n sx={{ color: 'inherit' }}\n underline=\"hover\"\n {...(EXTERNAL_AUTHOR_HREF.test(urlValue)\n ? { target: '_blank', rel: 'noopener noreferrer' }\n : {})}\n >\n {displayName}\n </AppLink>\n ) : (\n displayName\n )}\n </Typography>\n ) : null}\n {roleLine ? (\n <Typography variant=\"subtitle2\" sx={{ color: 'text.secondary' }}>\n {roleLine}\n </Typography>\n ) : null}\n {displayBio ? (\n <Typography variant=\"body1\" sx={{ color: 'text.secondary' }}>\n {displayBio}\n </Typography>\n ) : null}\n <AuthorLinkRows links={linkRows} size={1} />\n </MuiStack>\n </MuiStack>\n )\n})\nContentAuthorProfile.displayName = 'AglynContentAuthorProfile'\n\nexport { ContentAuthorProfile }\n\nexport const contentAuthorProfileSchema: Aglyn.ComponentSchema<ContentAuthorProfileProps> =\n {\n $id: AUTHOR_PROFILE_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Author Profile',\n description:\n 'Who an author page is about — portrait, name, role, bio and links.',\n category: Aglyn.ComponentCategory.TEXT,\n icon: {\n path: mdiAccountCircleOutline.path,\n sx: { color: 'secondary.main' },\n },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'name',\n label: 'Name',\n description:\n 'Blank shows the name of the author whose page this is. Type here ' +\n '(or bind {{author.name}}) only to override it.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'bio',\n label: 'Bio',\n description:\n 'Blank shows the bio from the author’s record. Type here (or bind ' +\n '{{author.bio}}) only to override it.',\n component: Aglyn.FieldComponentType.TEXTAREA,\n },\n {\n name: 'image',\n label: 'Portrait',\n description:\n 'Blank shows the portrait of the author whose page this is, at the ' +\n 'larger size a profile takes. Pick from your media library only to ' +\n 'override it.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'jobTitle',\n label: 'Role',\n description:\n 'Blank shows the job title from the author’s record. Only a person ' +\n 'has one — an author record set to Organization leaves it empty.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'worksFor',\n label: 'Organization',\n description:\n 'Blank shows who the author writes for, from their record. Shown ' +\n 'after the role as \"Role at Organization\".',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'url',\n label: 'Link',\n description:\n 'Blank uses the author’s own url. The name links here; with no url ' +\n 'it renders as plain text. This block is already the author’s page ' +\n 'on this site, so it never links to itself.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'showLinks',\n label: 'Show links',\n description:\n 'Off hides the row of profile links under the bio — the place a ' +\n 'reader follows this author elsewhere. Turning it off on a page ' +\n 'about them is rarely what you want.',\n component: Aglyn.FieldComponentType.SWITCH,\n // Unset means SHOWN in the renderer above, so the switch has to open\n // in that position or it lies about the block in front of the author\n // (AGL-2506).\n initialValue: true,\n },\n {\n name: 'showBio',\n label: 'Show bio',\n description:\n 'Off leaves the block as a portrait, a name and a role.',\n component: Aglyn.FieldComponentType.SWITCH,\n initialValue: true,\n },\n {\n name: 'showRole',\n label: 'Show role',\n description:\n 'Off hides the \"Role at Organization\" line under the name.',\n component: Aglyn.FieldComponentType.SWITCH,\n initialValue: true,\n },\n {\n name: 'showAvatar',\n label: 'Show portrait',\n description:\n 'Off drops the portrait and lets the name and bio run the full ' +\n 'width, with no space held for a picture.',\n component: Aglyn.FieldComponentType.SWITCH,\n initialValue: true,\n },\n ],\n }\n"],"names":["Aglyn","mdiAccountCircleOutline","AppLink","Box","MuiStack","Typography","forwardRef","useContext","BUNDLE_ID","UNRESOLVED_TOKEN","ENTRY_SAMPLE_AUTHOR","ENTRY_SAMPLE_BIO","samplePortraitSx","metaValue","SAFE_AUTHOR_HREF","EXTERNAL_AUTHOR_HREF","AuthorLinkRows","safeAuthorLinks","AUTHOR_PROFILE_ID","CONTENT_AUTHOR_PROFILE_COMPONENT_ID","PROFILE_AVATAR_SIZE","ContentAuthorProfile","props","ref","name","bio","image","jobTitle","worksFor","url","links","showBio","showAvatar","showRole","showLinks","rest","nodeSx","Array","isArray","suppressNavigation","ScreenLinkContext","hostId","useSite","nameValue","bioValue","jobValue","worksForValue","urlRaw","urlValue","test","imageRaw","trim","imageSrc","resolveMediaSrc","linkRows","sample","length","Boolean","displayName","displayBio","samplePortrait","roleLine","filter","join","direction","spacing","sx","alignItems","component","src","alt","display","flexShrink","width","height","borderRadius","objectFit","DEFERRED_IMAGE_ATTRIBUTES","aria-hidden","minWidth","variant","href","color","underline","target","rel","size","contentAuthorProfileSchema","$id","pluginId","description","category","ComponentCategory","TEXT","icon","path","flags","selfClosing","FEATURE_FLAG","ENABLED","attributes","label","FieldComponentType","TEXT_FIELD","TEXTAREA","SWITCH","initialValue"],"mappings":"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"}
|