@aglyn/plugins-email 1.0.0-beta.250 → 1.0.0-beta.253

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/plugins-email",
3
- "version": "1.0.0-beta.250",
3
+ "version": "1.0.0-beta.253",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,21 +25,21 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.0.0-beta.250",
29
- "@aglyn/shared-data-enums": "1.0.0-beta.250",
30
- "@aglyn/shared-data-mdi": "1.0.0-beta.250",
31
- "@aglyn/shared-data-types": "1.0.0-beta.250",
32
- "@aglyn/shared-ui-jsx": "1.0.0-beta.250",
33
- "@aglyn/shared-ui-jsx-forms": "1.0.0-beta.250",
34
- "@aglyn/shared-ui-next": "1.0.0-beta.250",
35
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.250",
36
- "@aglyn/shared-ui-theme": "1.0.0-beta.250",
37
- "@aglyn/shared-util-email": "1.0.0-beta.250",
38
- "@aglyn/shared-util-http": "1.0.0-beta.250",
39
- "@aglyn/shared-util-timestamp": "1.0.0-beta.250",
40
- "@aglyn/shared-util-tools": "1.0.0-beta.250",
41
- "@aglyn/tenant-data-admin": "1.0.0-beta.250",
42
- "@aglyn/tenant-feature-instance": "1.0.0-beta.250",
28
+ "@aglyn/aglyn": "1.0.0-beta.253",
29
+ "@aglyn/shared-data-enums": "1.0.0-beta.253",
30
+ "@aglyn/shared-data-mdi": "1.0.0-beta.253",
31
+ "@aglyn/shared-data-types": "1.0.0-beta.253",
32
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.253",
33
+ "@aglyn/shared-ui-jsx-forms": "1.0.0-beta.253",
34
+ "@aglyn/shared-ui-next": "1.0.0-beta.253",
35
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.253",
36
+ "@aglyn/shared-ui-theme": "1.0.0-beta.253",
37
+ "@aglyn/shared-util-email": "1.0.0-beta.253",
38
+ "@aglyn/shared-util-http": "1.0.0-beta.253",
39
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.253",
40
+ "@aglyn/shared-util-tools": "1.0.0-beta.253",
41
+ "@aglyn/tenant-data-admin": "1.0.0-beta.253",
42
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.253",
43
43
  "@swc/helpers": "0.5.23"
44
44
  },
45
45
  "peerDependencies": {
@@ -19,6 +19,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
19
  */ import * as Aglyn from "@aglyn/aglyn";
20
20
  import { mdiCardTextOutline, mdiEmailOutline, mdiFormatText, mdiImageOutline, mdiMinus, mdiPackageVariantClosed, mdiPageLayoutFooter, mdiPageLayoutHeader, mdiArrowExpandVertical, mdiGestureTapButton, mdiCodeTags } from "@aglyn/shared-data-mdi";
21
21
  import { sanitizeAuthorHtml } from "@aglyn/aglyn/app-utils/author-html";
22
+ import { EMAIL_BLOCK_STARTER_TREES } from "@aglyn/aglyn/app-utils/email-block-starters";
22
23
  import { mergeSxProps } from "@aglyn/shared-ui-theme";
23
24
  import Box from "@mui/material/Box";
24
25
  import Typography from "@mui/material/Typography";
@@ -732,46 +733,9 @@ export const emailHtmlSchema = {
732
733
  color: '#f57c00'
733
734
  }
734
735
  },
735
- data: {
736
- $id: null,
737
- componentId: SECTION_ID,
738
- pluginId: BUNDLE_ID,
739
- // Marks the band as the email's header, so the sender's own header is
740
- // not drawn above it (AGL-3372).
741
- props: {
742
- align: 'center',
743
- padding: 24,
744
- emailRole: 'header'
745
- },
746
- nodes: [
747
- {
748
- $id: null,
749
- componentId: IMAGE_ID,
750
- pluginId: BUNDLE_ID,
751
- props: {
752
- alt: 'Your company logo',
753
- width: 160,
754
- align: 'center'
755
- }
756
- },
757
- {
758
- $id: null,
759
- componentId: TEXT_ID,
760
- pluginId: BUNDLE_ID,
761
- props: {
762
- children: 'Your company',
763
- variant: 'subheading',
764
- align: 'center'
765
- }
766
- }
767
- ]
768
- }
736
+ // The same tree the Components page and the native apps start a block from.
737
+ data: EMAIL_BLOCK_STARTER_TREES.header
769
738
  };
770
- /**
771
- * The Footer's small print, a muted gray that still reads at 5:1 on white.
772
- * A literal on purpose: it is written into the sent mail's own HTML, where no
773
- * theme token resolves.
774
- */ const FOOTER_TEXT_COLOR = '#6b6b6b';
775
739
  /** See {@link EMAIL_HEADER_PRESET}. */ export const EMAIL_FOOTER_PRESET = {
776
740
  $id: Aglyn.REUSABLE_EMAIL_BLOCK_STARTER_PRESET_IDS.footer,
777
741
  type: Aglyn.NodeType.PRESET,
@@ -791,48 +755,8 @@ export const emailHtmlSchema = {
791
755
  color: '#f57c00'
792
756
  }
793
757
  },
794
- data: {
795
- $id: null,
796
- componentId: SECTION_ID,
797
- pluginId: BUNDLE_ID,
798
- // Marks the band as the email's footer, so the sender's own footer is
799
- // not drawn under it (AGL-3372).
800
- props: {
801
- align: 'center',
802
- padding: 24,
803
- emailRole: 'footer'
804
- },
805
- nodes: [
806
- {
807
- $id: null,
808
- componentId: DIVIDER_ID,
809
- pluginId: BUNDLE_ID,
810
- props: {}
811
- },
812
- {
813
- $id: null,
814
- componentId: TEXT_ID,
815
- pluginId: BUNDLE_ID,
816
- props: {
817
- children: 'Your company · 123 Main St, City',
818
- variant: 'caption',
819
- align: 'center',
820
- color: FOOTER_TEXT_COLOR
821
- }
822
- },
823
- {
824
- $id: null,
825
- componentId: TEXT_ID,
826
- pluginId: BUNDLE_ID,
827
- props: {
828
- children: "You're receiving this because you're a customer of Your company.",
829
- variant: 'caption',
830
- align: 'center',
831
- color: FOOTER_TEXT_COLOR
832
- }
833
- }
834
- ]
835
- }
758
+ // The same tree the Components page and the native apps start a block from.
759
+ data: EMAIL_BLOCK_STARTER_TREES.footer
836
760
  };
837
761
  export const emailPresets = [
838
762
  {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/email/src/lib/components/email-blocks.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 mdiCardTextOutline,\n mdiEmailOutline,\n mdiFormatText,\n mdiImageOutline,\n mdiMinus,\n mdiPackageVariantClosed,\n mdiPageLayoutFooter,\n mdiPageLayoutHeader,\n mdiArrowExpandVertical,\n mdiGestureTapButton,\n mdiCodeTags,\n} from '@aglyn/shared-data-mdi'\nimport { sanitizeAuthorHtml } from '@aglyn/aglyn/app-utils/author-html'\nimport { mergeSxProps } from '@aglyn/shared-ui-theme'\nimport Box from '@mui/material/Box'\nimport type { SxProps, Theme } from '@mui/material/styles'\nimport Typography from '@mui/material/Typography'\nimport { forwardRef } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n/**\n * Email-safe blocks (AGL-346): the besigner canvas renders these web\n * previews; the render pipeline (email-render.ts in app-utils) converts\n * the same nodes to inline-styled table HTML for email clients. Merge\n * tokens like {{contact.firstName}} substitute at send time.\n *\n * Component ids are persisted in screen documents; never rename.\n *\n * Every block pulls `sx` out of its props and composes it AFTER its own\n * defaults with MUI array composition (AGL-587): the renderer's Leaf\n * passes the node-level sx (styles-panel output) through props.sx, and a\n * literal `sx={{...}}` after `{...rest}` used to silently discard it.\n */\n\ninterface EmailBlockStyleProps {\n /** Node-level styles merged by the renderer; wins over block defaults. */\n sx?: SxProps<Theme>\n}\n\n// ---------------------------------------------------------------- section\nexport const SECTION_ID: Aglyn.ComponentId = 'emailSection'\n\nexport interface EmailSectionProps extends EmailBlockStyleProps {\n backgroundColor?: string\n /**\n * `header` or `footer` when the section is the email's own band of that\n * kind (AGL-3372): the send then leaves the sender's matching band out.\n * Set by the Header and Footer presets; draws nothing itself.\n */\n emailRole?: 'header' | 'footer'\n /** Inner padding in px (default 24). */\n padding?: number\n align?: 'left' | 'center' | 'right'\n children?: JSX.Children\n}\n\nexport const EmailSection = forwardRef<HTMLDivElement, EmailSectionProps>(\n (props, ref) => {\n // `emailRole` is read at send time and must not reach the DOM.\n const { backgroundColor, padding, align, children, sx, emailRole: _emailRole, ...rest } =\n props\n return (\n <Box\n ref={ref}\n {...rest}\n sx={mergeSxProps(\n {\n maxWidth: 600,\n mx: 'auto',\n backgroundColor: backgroundColor || '#ffffff',\n p: `${padding ?? 24}px`,\n textAlign: align ?? 'left',\n },\n sx,\n )}\n >\n {children}\n </Box>\n )\n },\n)\nEmailSection.displayName = 'EmailSection'\n\nexport const emailSectionSchema: Aglyn.ComponentSchema<EmailSectionProps> = {\n $id: SECTION_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Email section',\n description: 'An email-safe band with its own background color and padding.',\n category: Aglyn.ComponentCategory.LAYOUT,\n icon: { path: mdiEmailOutline.path, sx: { color: '#f57c00' } },\n attributes: [\n {\n name: 'backgroundColor',\n label: 'Background color',\n description: 'Solid colors only — gradients are unreliable in email.',\n component: Aglyn.FieldComponentType.COLOR_PICKER,\n },\n {\n name: 'padding',\n label: 'Padding (px)',\n description: 'Inner spacing around the section content.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'align',\n label: 'Content alignment',\n description:\n 'How every block inside this section lines up. Each block can still ' +\n 'override it with its own alignment.',\n component: Aglyn.FieldComponentType.SELECT,\n options: [\n { value: 'left', label: 'Left' },\n { value: 'center', label: 'Center' },\n { value: 'right', label: 'Right' },\n ],\n },\n ],\n}\n\n// ------------------------------------------------------------------- text\nexport const TEXT_ID: Aglyn.ComponentId = 'emailText'\n\nexport interface EmailTextProps extends EmailBlockStyleProps {\n children?: string\n variant?: 'heading' | 'subheading' | 'body' | 'caption'\n color?: string\n align?: 'left' | 'center' | 'right'\n}\n\nconst TEXT_PRESETS: Record<\n NonNullable<EmailTextProps['variant']>,\n { fontSize: number; fontWeight: number; lineHeight: number }\n> = {\n heading: { fontSize: 28, fontWeight: 700, lineHeight: 1.25 },\n subheading: { fontSize: 20, fontWeight: 600, lineHeight: 1.3 },\n body: { fontSize: 15, fontWeight: 400, lineHeight: 1.55 },\n caption: { fontSize: 12, fontWeight: 400, lineHeight: 1.4 },\n}\n\nexport const EmailText = forwardRef<HTMLDivElement, EmailTextProps>(\n (props, ref) => {\n const { children, variant, color, align, sx, ...rest } = props\n const preset = TEXT_PRESETS[variant ?? 'body']\n return (\n <Typography\n ref={ref}\n component=\"div\"\n {...rest}\n sx={mergeSxProps(\n {\n fontSize: preset.fontSize,\n fontWeight: preset.fontWeight,\n lineHeight: preset.lineHeight,\n color: color || '#1a1a1a',\n textAlign: align ?? 'left',\n fontFamily: 'Helvetica, Arial, sans-serif',\n mb: 1,\n },\n sx,\n )}\n >\n {children ?? 'Email text'}\n </Typography>\n )\n },\n)\nEmailText.displayName = 'EmailText'\n\nexport const emailTextSchema: Aglyn.ComponentSchema<EmailTextProps> = {\n $id: TEXT_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Email text',\n description: 'A paragraph of email copy in one of the preset styles.',\n category: Aglyn.ComponentCategory.DATA_DISPLAY,\n icon: { path: mdiFormatText.path, sx: { color: '#f57c00' } },\n flags: { textEditable: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'children',\n label: 'Text',\n description:\n 'Supports merge tokens: {{contact.firstName}}, {{contact.email}}, ' +\n '{{org.name}}, {{unsubscribeUrl}}.',\n component: Aglyn.FieldComponentType.TEXTAREA,\n },\n {\n name: 'variant',\n label: 'Style',\n description:\n 'Size and weight of this text — Heading and Subheading also give it ' +\n 'the spacing above and below that a heading needs.',\n component: Aglyn.FieldComponentType.SELECT,\n options: [\n { value: 'heading', label: 'Heading' },\n { value: 'subheading', label: 'Subheading' },\n { value: 'body', label: 'Body' },\n { value: 'caption', label: 'Caption' },\n ],\n },\n {\n name: 'color',\n label: 'Color',\n description:\n 'Text color. Keep it dark on a light background: many clients ' +\n 'ignore dark mode overrides and show your color as-is.',\n component: Aglyn.FieldComponentType.COLOR_PICKER,\n },\n {\n name: 'align',\n label: 'Alignment',\n description: 'How this block of text lines up within the section.',\n component: Aglyn.FieldComponentType.SELECT,\n options: [\n { value: 'left', label: 'Left' },\n { value: 'center', label: 'Center' },\n { value: 'right', label: 'Right' },\n ],\n },\n ],\n}\n\n// --------------------------------------------------------------- richtext\nexport const RICHTEXT_ID: Aglyn.ComponentId = 'emailRichtext'\n\nexport interface EmailRichtextProps extends EmailBlockStyleProps {\n /** HTML fragment; sanitized with the custom-html policy (AGL-320). */\n html?: string\n}\n\nexport const EmailRichtext = forwardRef<HTMLDivElement, EmailRichtextProps>(\n (props, ref) => {\n const { html, sx, ...rest } = props\n if (!html?.trim()) {\n return (\n <Box\n ref={ref}\n {...rest}\n sx={mergeSxProps(\n {\n p: 2,\n border: '1px dashed',\n borderColor: 'divider',\n color: 'text.secondary',\n fontSize: 13,\n },\n sx,\n )}\n >\n {'Rich text — add HTML in the attributes panel'}\n </Box>\n )\n }\n return (\n <Box\n ref={ref}\n {...rest}\n sx={mergeSxProps(\n { fontFamily: 'Helvetica, Arial, sans-serif', fontSize: 15 },\n sx,\n )}\n // Sanitized on every render — same policy as the custom HTML block.\n // The `typeof window` guard this replaced existed only because that\n // policy was DOMPurify, which needs a DOM; it is now a pure function\n // (AGL-1901), so the body survives a server render instead of being\n // blanked — which for an email is the render that gets SENT.\n dangerouslySetInnerHTML={{ __html: sanitizeAuthorHtml(html) }}\n />\n )\n },\n)\nEmailRichtext.displayName = 'EmailRichtext'\n\nexport const emailRichtextSchema: Aglyn.ComponentSchema<EmailRichtextProps> = {\n $id: RICHTEXT_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Email rich text',\n description: 'An HTML fragment in an email, sanitised on render.',\n category: Aglyn.ComponentCategory.DATA_DISPLAY,\n icon: { path: mdiCardTextOutline.path, sx: { color: '#f57c00' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'html',\n label: 'HTML',\n description:\n 'Formatted content (p, b, i, a, ul/ol, h1-h4). Scripts and forms ' +\n 'are stripped; keep styling simple for email clients. Merge ' +\n 'tokens work here too.',\n component: Aglyn.FieldComponentType.TEXTAREA,\n },\n ],\n}\n\n// ------------------------------------------------------------------ image\nexport const IMAGE_ID: Aglyn.ComponentId = 'emailImage'\n\nexport interface EmailImageProps extends EmailBlockStyleProps {\n src?: string\n alt?: string\n /** Rendered width in px (max 600). */\n width?: number\n href?: string\n align?: 'left' | 'center' | 'right'\n}\n\nexport const EmailImage = forwardRef<HTMLDivElement, EmailImageProps>(\n (props, ref) => {\n const { src: storedSrc, alt, width, href, align, sx, ...rest } = props\n // A media-picker target, so it can hold a media reference (AGL-1215).\n // This is the CANVAS preview only — the sent HTML is built by\n // `email-render`, which needs an absolute URL and cannot resolve either\n // a reference or the relative CDN path it replaces.\n const src = Aglyn.resolveMediaSrc(storedSrc)\n return (\n <Box\n ref={ref}\n {...rest}\n sx={mergeSxProps({ textAlign: align ?? 'center', my: 1 }, sx)}\n >\n {src ? (\n <Box\n component=\"img\"\n src={src}\n alt={alt ?? ''}\n sx={{ maxWidth: '100%', width: width ? `${width}px` : '100%' }}\n />\n ) : (\n <Box\n sx={{\n height: 120,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n border: '1px dashed',\n borderColor: 'divider',\n color: 'text.secondary',\n fontSize: 13,\n }}\n >\n {'Image — pick from the media library'}\n </Box>\n )}\n </Box>\n )\n },\n)\nEmailImage.displayName = 'EmailImage'\n\nexport const emailImageSchema: Aglyn.ComponentSchema<EmailImageProps> = {\n $id: IMAGE_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Email image',\n description: 'An email-safe image, capped at 600px wide, optionally linked.',\n category: Aglyn.ComponentCategory.DATA_DISPLAY,\n icon: { path: mdiImageOutline.path, sx: { color: '#f57c00' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'src',\n label: 'Source URL',\n description: 'Use an absolute URL — email clients cannot resolve relative paths.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'alt',\n label: 'Alt text',\n // AGL-1896: the same \"Browse media\" default the site Image element\n // gets. It matters MORE here — most clients block images by default,\n // so for most recipients the alt text IS the block.\n description:\n 'Shown while images are blocked — most clients block by default. ' +\n 'Filled in from the media library when you pick a file that has ' +\n 'alt text.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'width',\n label: 'Width (px)',\n description:\n 'How wide the image renders. Blank fills the section — which is ' +\n 'usually right, since a phone is narrower than any number you pick.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'href',\n label: 'Link URL',\n description:\n 'Where the image goes when it is clicked. Blank leaves it as a ' +\n 'plain picture.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'align',\n label: 'Alignment',\n description: 'Where the image sits when it is narrower than the section.',\n component: Aglyn.FieldComponentType.SELECT,\n options: [\n { value: 'left', label: 'Left' },\n { value: 'center', label: 'Center' },\n { value: 'right', label: 'Right' },\n ],\n },\n ],\n}\n\n// ----------------------------------------------------------------- button\nexport const BUTTON_ID: Aglyn.ComponentId = 'emailButton'\n\nexport interface EmailButtonProps extends EmailBlockStyleProps {\n children?: string\n href?: string\n backgroundColor?: string\n color?: string\n align?: 'left' | 'center' | 'right'\n}\n\nexport const EmailButton = forwardRef<HTMLDivElement, EmailButtonProps>(\n (props, ref) => {\n const { children, href, backgroundColor, color, align, sx, ...rest } =\n props\n return (\n <Box\n ref={ref}\n {...rest}\n sx={mergeSxProps({ textAlign: align ?? 'center', my: 1.5 }, sx)}\n >\n <Box\n component=\"span\"\n sx={{\n display: 'inline-block',\n px: 3,\n py: 1.25,\n borderRadius: 1,\n // The theme's accent when nothing was picked, as the sent email\n // resolves it (AGL-3370): the canvas and the inbox agree.\n backgroundColor: backgroundColor || 'primary.main',\n color: color || 'primary.contrastText',\n fontFamily: 'Helvetica, Arial, sans-serif',\n fontSize: 15,\n fontWeight: 600,\n }}\n >\n {children ?? 'Call to action'}\n </Box>\n </Box>\n )\n },\n)\nEmailButton.displayName = 'EmailButton'\n\nexport const emailButtonSchema: Aglyn.ComponentSchema<EmailButtonProps> = {\n $id: BUTTON_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Email button',\n description: 'A call-to-action button that survives every mail client.',\n category: Aglyn.ComponentCategory.INPUT,\n icon: { path: mdiGestureTapButton.path, sx: { color: '#f57c00' } },\n flags: { textEditable: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'children',\n label: 'Label',\n description:\n 'The words on the button. Say what happens next — \"See the report\" ' +\n 'reads as a destination where \"Click here\" does not.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'href',\n label: 'Link URL',\n description: 'Absolute URL (https://…). Merge tokens allowed.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'backgroundColor',\n label: 'Button color',\n description:\n 'The fill behind the label. Check it against the label color — a ' +\n 'button nobody can read is a link nobody clicks.',\n component: Aglyn.FieldComponentType.COLOR_PICKER,\n },\n {\n name: 'color',\n label: 'Label color',\n description: 'The color of the words on the button.',\n component: Aglyn.FieldComponentType.COLOR_PICKER,\n },\n {\n name: 'align',\n label: 'Alignment',\n description: 'Where the button sits across the width of the section.',\n component: Aglyn.FieldComponentType.SELECT,\n options: [\n { value: 'left', label: 'Left' },\n { value: 'center', label: 'Center' },\n { value: 'right', label: 'Right' },\n ],\n },\n ],\n}\n\n// ---------------------------------------------------------------- divider\nexport const DIVIDER_ID: Aglyn.ComponentId = 'emailDivider'\n\nexport interface EmailDividerProps extends EmailBlockStyleProps {\n color?: string\n}\n\nexport const EmailDivider = forwardRef<HTMLHRElement, EmailDividerProps>(\n (props, ref) => {\n const { color, sx, ...rest } = props\n return (\n <Box\n ref={ref}\n component=\"hr\"\n {...rest}\n sx={mergeSxProps(\n {\n border: 0,\n // Split shorthand so a palette token path from the theme-\n // reference color picker (AGL-588) resolves through the sx\n // system instead of dying inside a template string.\n borderTopStyle: 'solid',\n borderTopWidth: 1,\n borderTopColor: color || '#e0e0e0',\n my: 2,\n },\n sx,\n )}\n />\n )\n },\n)\nEmailDivider.displayName = 'EmailDivider'\n\nexport const emailDividerSchema: Aglyn.ComponentSchema<EmailDividerProps> = {\n $id: DIVIDER_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Email divider',\n description: 'A horizontal rule between email sections.',\n category: Aglyn.ComponentCategory.LAYOUT,\n icon: { path: mdiMinus.path, sx: { color: '#f57c00' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'color',\n label: 'Line color',\n description:\n 'The color of the rule. A light grey separates sections without ' +\n 'drawing the eye to the line itself.',\n component: Aglyn.FieldComponentType.COLOR_PICKER,\n },\n ],\n}\n\n// ----------------------------------------------------------------- spacer\nexport const SPACER_ID: Aglyn.ComponentId = 'emailSpacer'\n\nexport interface EmailSpacerProps extends EmailBlockStyleProps {\n /** Height in px (default 24). */\n height?: number\n}\n\nexport const EmailSpacer = forwardRef<HTMLDivElement, EmailSpacerProps>(\n (props, ref) => {\n const { height, sx, ...rest } = props\n return (\n <Box\n ref={ref}\n {...rest}\n sx={mergeSxProps({ height: height ?? 24 }, sx)}\n />\n )\n },\n)\nEmailSpacer.displayName = 'EmailSpacer'\n\nexport const emailSpacerSchema: Aglyn.ComponentSchema<EmailSpacerProps> = {\n $id: SPACER_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Email spacer',\n description: 'Fixed vertical whitespace between email blocks.',\n category: Aglyn.ComponentCategory.LAYOUT,\n icon: { path: mdiArrowExpandVertical.path, sx: { color: '#f57c00' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'height',\n label: 'Height (px)',\n description:\n 'How much empty vertical space this block adds. Email clients drop ' +\n 'CSS margins, so a spacer is how you get reliable breathing room.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n ],\n}\n\n// ---------------------------------------------------------------- product\nexport const PRODUCT_ID: Aglyn.ComponentId = 'emailProduct'\n\nexport interface EmailProductProps extends EmailBlockStyleProps {\n /** Product id — resolved server-side at render (rename-safe). */\n productId?: string\n buttonLabel?: string\n}\n\nexport const EmailProduct = forwardRef<HTMLDivElement, EmailProductProps>(\n (props, ref) => {\n const { productId, buttonLabel, sx, ...rest } = props\n return (\n <Box\n ref={ref}\n {...rest}\n sx={mergeSxProps(\n {\n border: '1px solid',\n borderColor: 'divider',\n borderRadius: 1,\n p: 2,\n my: 1,\n textAlign: 'center',\n color: 'text.secondary',\n fontSize: 13,\n },\n sx,\n )}\n >\n {productId\n ? `Product card (${productId}) — name, price, image fill in at send`\n : 'Product card — pick a product in the attributes panel'}\n <Box sx={{ mt: 1, fontWeight: 600 }}>\n {buttonLabel ?? 'Shop now'}\n </Box>\n </Box>\n )\n },\n)\nEmailProduct.displayName = 'EmailProduct'\n\nexport const emailProductSchema: Aglyn.ComponentSchema<EmailProductProps> = {\n $id: PRODUCT_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Email product',\n description: 'A product card resolved at send time, so it never goes stale.',\n category: Aglyn.ComponentCategory.DATA_DISPLAY,\n icon: { path: mdiPackageVariantClosed.path, sx: { color: '#f57c00' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'productId',\n label: 'Product',\n description:\n 'Stored by id — the current name, price, and image render at send.',\n component: Aglyn.FieldComponentType.PRODUCT_SELECT,\n },\n {\n name: 'buttonLabel',\n label: 'Button label',\n description:\n 'The words on the card’s button. Blank uses the block’s default, ' +\n 'and the link always points at this product’s page.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n ],\n}\n\n// ------------------------------------------------------------ custom html\nexport const HTML_ID: Aglyn.ComponentId = 'emailHtml'\n\nexport interface EmailHtmlProps extends EmailBlockStyleProps {\n html?: string\n}\n\nexport const EmailHtml = forwardRef<HTMLDivElement, EmailHtmlProps>(\n (props, ref) => {\n const { html, sx, ...rest } = props\n return (\n <Box\n ref={ref}\n {...rest}\n sx={mergeSxProps({ fontFamily: 'Helvetica, Arial, sans-serif' }, sx)}\n // No `typeof window` guard any more (AGL-1901): the sanitizer needs\n // no DOM, so a server-rendered email keeps its custom HTML.\n dangerouslySetInnerHTML={{\n __html: html ? sanitizeAuthorHtml(html) : '',\n }}\n />\n )\n },\n)\nEmailHtml.displayName = 'EmailHtml'\n\nexport const emailHtmlSchema: Aglyn.ComponentSchema<EmailHtmlProps> = {\n $id: HTML_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Email custom HTML',\n description: 'Hand-written HTML for an email, used as-is.',\n category: Aglyn.ComponentCategory.DATA_DISPLAY,\n icon: { path: mdiCodeTags.path, sx: { color: '#f57c00' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'html',\n label: 'HTML',\n description:\n 'Raw table markup for advanced layouts. Sanitized with the ' +\n 'custom-html policy; the render pipeline passes it through as-is ' +\n 'after sanitization.',\n component: Aglyn.FieldComponentType.TEXTAREA,\n },\n ],\n}\n\n/**\n * The two bands most emails a site sends begin and end with (AGL-3287).\n *\n * Each drops into any email like the blocks above, and the Components page\n * starts a reusable Header or Footer email block from these same trees, by\n * the ids core names in `REUSABLE_EMAIL_BLOCK_STARTER_PRESET_IDS` — one tree\n * each, wherever it is started from.\n *\n * The words are placeholders to replace. No merge token names the company in\n * both kinds of email — a campaign supplies `{{org.name}}`, and no token is\n * supplied by every one of a site's own emails — while a token an email does\n * not supply is blanked before sending, which would leave the header with no\n * name at all.\n *\n * The footer carries no unsubscribe link: a campaign send appends its own,\n * and a second one would do the same thing twice.\n */\nexport const EMAIL_HEADER_PRESET: Aglyn.PresetSchema = {\n $id: Aglyn.REUSABLE_EMAIL_BLOCK_STARTER_PRESET_IDS.header,\n type: Aglyn.NodeType.PRESET,\n displayName: 'Header',\n description:\n 'Your logo above your company name, for the top of an email. Pick the ' +\n 'logo from your media library — until you do, the picture shows nothing ' +\n 'in a sent email.',\n pluginId: BUNDLE_ID,\n category: Aglyn.ComponentCategory.BLOCKS,\n tags: ['logo', 'masthead', 'top', 'brand'],\n icon: { path: mdiPageLayoutHeader.path, sx: { color: '#f57c00' } },\n data: {\n $id: null,\n componentId: SECTION_ID,\n pluginId: BUNDLE_ID,\n // Marks the band as the email's header, so the sender's own header is\n // not drawn above it (AGL-3372).\n props: { align: 'center', padding: 24, emailRole: 'header' },\n nodes: [\n {\n $id: null,\n componentId: IMAGE_ID,\n pluginId: BUNDLE_ID,\n props: { alt: 'Your company logo', width: 160, align: 'center' },\n },\n {\n $id: null,\n componentId: TEXT_ID,\n pluginId: BUNDLE_ID,\n props: {\n children: 'Your company',\n variant: 'subheading',\n align: 'center',\n },\n },\n ],\n },\n}\n\n/**\n * The Footer's small print, a muted gray that still reads at 5:1 on white.\n * A literal on purpose: it is written into the sent mail's own HTML, where no\n * theme token resolves.\n */\nconst FOOTER_TEXT_COLOR = '#6b6b6b'\n\n/** See {@link EMAIL_HEADER_PRESET}. */\nexport const EMAIL_FOOTER_PRESET: Aglyn.PresetSchema = {\n $id: Aglyn.REUSABLE_EMAIL_BLOCK_STARTER_PRESET_IDS.footer,\n type: Aglyn.NodeType.PRESET,\n displayName: 'Footer',\n description:\n 'Your company name and address, and why the reader gets your emails, for ' +\n 'the bottom of an email. Campaign emails add the unsubscribe link for you.',\n pluginId: BUNDLE_ID,\n category: Aglyn.ComponentCategory.BLOCKS,\n tags: ['address', 'bottom', 'contact', 'legal'],\n icon: { path: mdiPageLayoutFooter.path, sx: { color: '#f57c00' } },\n data: {\n $id: null,\n componentId: SECTION_ID,\n pluginId: BUNDLE_ID,\n // Marks the band as the email's footer, so the sender's own footer is\n // not drawn under it (AGL-3372).\n props: { align: 'center', padding: 24, emailRole: 'footer' },\n nodes: [\n {\n $id: null,\n componentId: DIVIDER_ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n {\n $id: null,\n componentId: TEXT_ID,\n pluginId: BUNDLE_ID,\n props: {\n children: 'Your company · 123 Main St, City',\n variant: 'caption',\n align: 'center',\n color: FOOTER_TEXT_COLOR,\n },\n },\n {\n $id: null,\n componentId: TEXT_ID,\n pluginId: BUNDLE_ID,\n props: {\n children:\n \"You're receiving this because you're a customer of Your company.\",\n variant: 'caption',\n align: 'center',\n color: FOOTER_TEXT_COLOR,\n },\n },\n ],\n },\n}\n\nexport const emailPresets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(SECTION_ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Email section',\n pluginId: BUNDLE_ID,\n category: Aglyn.ComponentCategory.LAYOUT,\n icon: { path: mdiEmailOutline.path, sx: { color: '#f57c00' } },\n data: {\n $id: null,\n componentId: SECTION_ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n {\n $id: generatePresetId(TEXT_ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Email text',\n pluginId: BUNDLE_ID,\n category: Aglyn.ComponentCategory.DATA_DISPLAY,\n icon: { path: mdiFormatText.path, sx: { color: '#f57c00' } },\n data: {\n $id: null,\n componentId: TEXT_ID,\n pluginId: BUNDLE_ID,\n props: { children: 'Hello {{contact.firstName}},', variant: 'body' },\n },\n },\n {\n $id: generatePresetId(RICHTEXT_ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Email rich text',\n pluginId: BUNDLE_ID,\n category: Aglyn.ComponentCategory.DATA_DISPLAY,\n icon: { path: mdiCardTextOutline.path, sx: { color: '#f57c00' } },\n data: {\n $id: null,\n componentId: RICHTEXT_ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n {\n $id: generatePresetId(IMAGE_ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Email image',\n pluginId: BUNDLE_ID,\n category: Aglyn.ComponentCategory.DATA_DISPLAY,\n icon: { path: mdiImageOutline.path, sx: { color: '#f57c00' } },\n data: {\n $id: null,\n componentId: IMAGE_ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n {\n $id: generatePresetId(BUTTON_ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Email button',\n pluginId: BUNDLE_ID,\n category: Aglyn.ComponentCategory.INPUT,\n icon: { path: mdiGestureTapButton.path, sx: { color: '#f57c00' } },\n data: {\n $id: null,\n componentId: BUTTON_ID,\n pluginId: BUNDLE_ID,\n props: { children: 'Shop now' },\n },\n },\n {\n $id: generatePresetId(DIVIDER_ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Email divider',\n pluginId: BUNDLE_ID,\n category: Aglyn.ComponentCategory.LAYOUT,\n icon: { path: mdiMinus.path, sx: { color: '#f57c00' } },\n data: {\n $id: null,\n componentId: DIVIDER_ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n {\n $id: generatePresetId(SPACER_ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Email spacer',\n pluginId: BUNDLE_ID,\n category: Aglyn.ComponentCategory.LAYOUT,\n icon: { path: mdiArrowExpandVertical.path, sx: { color: '#f57c00' } },\n data: {\n $id: null,\n componentId: SPACER_ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n {\n $id: generatePresetId(PRODUCT_ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Email product',\n pluginId: BUNDLE_ID,\n category: Aglyn.ComponentCategory.DATA_DISPLAY,\n icon: { path: mdiPackageVariantClosed.path, sx: { color: '#f57c00' } },\n data: {\n $id: null,\n componentId: PRODUCT_ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n {\n $id: generatePresetId(HTML_ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Email custom HTML',\n pluginId: BUNDLE_ID,\n category: Aglyn.ComponentCategory.DATA_DISPLAY,\n icon: { path: mdiCodeTags.path, sx: { color: '#f57c00' } },\n data: {\n $id: null,\n componentId: HTML_ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n EMAIL_HEADER_PRESET,\n EMAIL_FOOTER_PRESET,\n]\n"],"names":["Aglyn","mdiCardTextOutline","mdiEmailOutline","mdiFormatText","mdiImageOutline","mdiMinus","mdiPackageVariantClosed","mdiPageLayoutFooter","mdiPageLayoutHeader","mdiArrowExpandVertical","mdiGestureTapButton","mdiCodeTags","sanitizeAuthorHtml","mergeSxProps","Box","Typography","forwardRef","BUNDLE_ID","generatePresetId","SECTION_ID","EmailSection","props","ref","backgroundColor","padding","align","children","sx","emailRole","_emailRole","rest","maxWidth","mx","p","textAlign","displayName","emailSectionSchema","$id","pluginId","description","category","ComponentCategory","LAYOUT","icon","path","color","attributes","name","label","component","FieldComponentType","COLOR_PICKER","TEXT_FIELD","type","SELECT","options","value","TEXT_ID","TEXT_PRESETS","heading","fontSize","fontWeight","lineHeight","subheading","body","caption","EmailText","variant","preset","fontFamily","mb","emailTextSchema","DATA_DISPLAY","flags","textEditable","FEATURE_FLAG","ENABLED","TEXTAREA","RICHTEXT_ID","EmailRichtext","html","trim","border","borderColor","dangerouslySetInnerHTML","__html","emailRichtextSchema","selfClosing","IMAGE_ID","EmailImage","src","storedSrc","alt","width","href","resolveMediaSrc","my","height","display","alignItems","justifyContent","emailImageSchema","BUTTON_ID","EmailButton","px","py","borderRadius","emailButtonSchema","INPUT","DIVIDER_ID","EmailDivider","borderTopStyle","borderTopWidth","borderTopColor","emailDividerSchema","SPACER_ID","EmailSpacer","emailSpacerSchema","PRODUCT_ID","EmailProduct","productId","buttonLabel","mt","emailProductSchema","PRODUCT_SELECT","HTML_ID","EmailHtml","emailHtmlSchema","EMAIL_HEADER_PRESET","REUSABLE_EMAIL_BLOCK_STARTER_PRESET_IDS","header","NodeType","PRESET","BLOCKS","tags","data","componentId","nodes","FOOTER_TEXT_COLOR","EMAIL_FOOTER_PRESET","footer","emailPresets"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/email/src/lib/components/email-blocks.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 mdiCardTextOutline,\n mdiEmailOutline,\n mdiFormatText,\n mdiImageOutline,\n mdiMinus,\n mdiPackageVariantClosed,\n mdiPageLayoutFooter,\n mdiPageLayoutHeader,\n mdiArrowExpandVertical,\n mdiGestureTapButton,\n mdiCodeTags,\n} from '@aglyn/shared-data-mdi'\nimport { sanitizeAuthorHtml } from '@aglyn/aglyn/app-utils/author-html'\nimport { EMAIL_BLOCK_STARTER_TREES } from '@aglyn/aglyn/app-utils/email-block-starters'\nimport { mergeSxProps } from '@aglyn/shared-ui-theme'\nimport Box from '@mui/material/Box'\nimport type { SxProps, Theme } from '@mui/material/styles'\nimport Typography from '@mui/material/Typography'\nimport { forwardRef } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n/**\n * Email-safe blocks (AGL-346): the besigner canvas renders these web\n * previews; the render pipeline (email-render.ts in app-utils) converts\n * the same nodes to inline-styled table HTML for email clients. Merge\n * tokens like {{contact.firstName}} substitute at send time.\n *\n * Component ids are persisted in screen documents; never rename.\n *\n * Every block pulls `sx` out of its props and composes it AFTER its own\n * defaults with MUI array composition (AGL-587): the renderer's Leaf\n * passes the node-level sx (styles-panel output) through props.sx, and a\n * literal `sx={{...}}` after `{...rest}` used to silently discard it.\n */\n\ninterface EmailBlockStyleProps {\n /** Node-level styles merged by the renderer; wins over block defaults. */\n sx?: SxProps<Theme>\n}\n\n// ---------------------------------------------------------------- section\nexport const SECTION_ID: Aglyn.ComponentId = 'emailSection'\n\nexport interface EmailSectionProps extends EmailBlockStyleProps {\n backgroundColor?: string\n /**\n * `header` or `footer` when the section is the email's own band of that\n * kind (AGL-3372): the send then leaves the sender's matching band out.\n * Set by the Header and Footer presets; draws nothing itself.\n */\n emailRole?: 'header' | 'footer'\n /** Inner padding in px (default 24). */\n padding?: number\n align?: 'left' | 'center' | 'right'\n children?: JSX.Children\n}\n\nexport const EmailSection = forwardRef<HTMLDivElement, EmailSectionProps>(\n (props, ref) => {\n // `emailRole` is read at send time and must not reach the DOM.\n const { backgroundColor, padding, align, children, sx, emailRole: _emailRole, ...rest } =\n props\n return (\n <Box\n ref={ref}\n {...rest}\n sx={mergeSxProps(\n {\n maxWidth: 600,\n mx: 'auto',\n backgroundColor: backgroundColor || '#ffffff',\n p: `${padding ?? 24}px`,\n textAlign: align ?? 'left',\n },\n sx,\n )}\n >\n {children}\n </Box>\n )\n },\n)\nEmailSection.displayName = 'EmailSection'\n\nexport const emailSectionSchema: Aglyn.ComponentSchema<EmailSectionProps> = {\n $id: SECTION_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Email section',\n description: 'An email-safe band with its own background color and padding.',\n category: Aglyn.ComponentCategory.LAYOUT,\n icon: { path: mdiEmailOutline.path, sx: { color: '#f57c00' } },\n attributes: [\n {\n name: 'backgroundColor',\n label: 'Background color',\n description: 'Solid colors only — gradients are unreliable in email.',\n component: Aglyn.FieldComponentType.COLOR_PICKER,\n },\n {\n name: 'padding',\n label: 'Padding (px)',\n description: 'Inner spacing around the section content.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'align',\n label: 'Content alignment',\n description:\n 'How every block inside this section lines up. Each block can still ' +\n 'override it with its own alignment.',\n component: Aglyn.FieldComponentType.SELECT,\n options: [\n { value: 'left', label: 'Left' },\n { value: 'center', label: 'Center' },\n { value: 'right', label: 'Right' },\n ],\n },\n ],\n}\n\n// ------------------------------------------------------------------- text\nexport const TEXT_ID: Aglyn.ComponentId = 'emailText'\n\nexport interface EmailTextProps extends EmailBlockStyleProps {\n children?: string\n variant?: 'heading' | 'subheading' | 'body' | 'caption'\n color?: string\n align?: 'left' | 'center' | 'right'\n}\n\nconst TEXT_PRESETS: Record<\n NonNullable<EmailTextProps['variant']>,\n { fontSize: number; fontWeight: number; lineHeight: number }\n> = {\n heading: { fontSize: 28, fontWeight: 700, lineHeight: 1.25 },\n subheading: { fontSize: 20, fontWeight: 600, lineHeight: 1.3 },\n body: { fontSize: 15, fontWeight: 400, lineHeight: 1.55 },\n caption: { fontSize: 12, fontWeight: 400, lineHeight: 1.4 },\n}\n\nexport const EmailText = forwardRef<HTMLDivElement, EmailTextProps>(\n (props, ref) => {\n const { children, variant, color, align, sx, ...rest } = props\n const preset = TEXT_PRESETS[variant ?? 'body']\n return (\n <Typography\n ref={ref}\n component=\"div\"\n {...rest}\n sx={mergeSxProps(\n {\n fontSize: preset.fontSize,\n fontWeight: preset.fontWeight,\n lineHeight: preset.lineHeight,\n color: color || '#1a1a1a',\n textAlign: align ?? 'left',\n fontFamily: 'Helvetica, Arial, sans-serif',\n mb: 1,\n },\n sx,\n )}\n >\n {children ?? 'Email text'}\n </Typography>\n )\n },\n)\nEmailText.displayName = 'EmailText'\n\nexport const emailTextSchema: Aglyn.ComponentSchema<EmailTextProps> = {\n $id: TEXT_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Email text',\n description: 'A paragraph of email copy in one of the preset styles.',\n category: Aglyn.ComponentCategory.DATA_DISPLAY,\n icon: { path: mdiFormatText.path, sx: { color: '#f57c00' } },\n flags: { textEditable: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'children',\n label: 'Text',\n description:\n 'Supports merge tokens: {{contact.firstName}}, {{contact.email}}, ' +\n '{{org.name}}, {{unsubscribeUrl}}.',\n component: Aglyn.FieldComponentType.TEXTAREA,\n },\n {\n name: 'variant',\n label: 'Style',\n description:\n 'Size and weight of this text — Heading and Subheading also give it ' +\n 'the spacing above and below that a heading needs.',\n component: Aglyn.FieldComponentType.SELECT,\n options: [\n { value: 'heading', label: 'Heading' },\n { value: 'subheading', label: 'Subheading' },\n { value: 'body', label: 'Body' },\n { value: 'caption', label: 'Caption' },\n ],\n },\n {\n name: 'color',\n label: 'Color',\n description:\n 'Text color. Keep it dark on a light background: many clients ' +\n 'ignore dark mode overrides and show your color as-is.',\n component: Aglyn.FieldComponentType.COLOR_PICKER,\n },\n {\n name: 'align',\n label: 'Alignment',\n description: 'How this block of text lines up within the section.',\n component: Aglyn.FieldComponentType.SELECT,\n options: [\n { value: 'left', label: 'Left' },\n { value: 'center', label: 'Center' },\n { value: 'right', label: 'Right' },\n ],\n },\n ],\n}\n\n// --------------------------------------------------------------- richtext\nexport const RICHTEXT_ID: Aglyn.ComponentId = 'emailRichtext'\n\nexport interface EmailRichtextProps extends EmailBlockStyleProps {\n /** HTML fragment; sanitized with the custom-html policy (AGL-320). */\n html?: string\n}\n\nexport const EmailRichtext = forwardRef<HTMLDivElement, EmailRichtextProps>(\n (props, ref) => {\n const { html, sx, ...rest } = props\n if (!html?.trim()) {\n return (\n <Box\n ref={ref}\n {...rest}\n sx={mergeSxProps(\n {\n p: 2,\n border: '1px dashed',\n borderColor: 'divider',\n color: 'text.secondary',\n fontSize: 13,\n },\n sx,\n )}\n >\n {'Rich text — add HTML in the attributes panel'}\n </Box>\n )\n }\n return (\n <Box\n ref={ref}\n {...rest}\n sx={mergeSxProps(\n { fontFamily: 'Helvetica, Arial, sans-serif', fontSize: 15 },\n sx,\n )}\n // Sanitized on every render — same policy as the custom HTML block.\n // The `typeof window` guard this replaced existed only because that\n // policy was DOMPurify, which needs a DOM; it is now a pure function\n // (AGL-1901), so the body survives a server render instead of being\n // blanked — which for an email is the render that gets SENT.\n dangerouslySetInnerHTML={{ __html: sanitizeAuthorHtml(html) }}\n />\n )\n },\n)\nEmailRichtext.displayName = 'EmailRichtext'\n\nexport const emailRichtextSchema: Aglyn.ComponentSchema<EmailRichtextProps> = {\n $id: RICHTEXT_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Email rich text',\n description: 'An HTML fragment in an email, sanitised on render.',\n category: Aglyn.ComponentCategory.DATA_DISPLAY,\n icon: { path: mdiCardTextOutline.path, sx: { color: '#f57c00' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'html',\n label: 'HTML',\n description:\n 'Formatted content (p, b, i, a, ul/ol, h1-h4). Scripts and forms ' +\n 'are stripped; keep styling simple for email clients. Merge ' +\n 'tokens work here too.',\n component: Aglyn.FieldComponentType.TEXTAREA,\n },\n ],\n}\n\n// ------------------------------------------------------------------ image\nexport const IMAGE_ID: Aglyn.ComponentId = 'emailImage'\n\nexport interface EmailImageProps extends EmailBlockStyleProps {\n src?: string\n alt?: string\n /** Rendered width in px (max 600). */\n width?: number\n href?: string\n align?: 'left' | 'center' | 'right'\n}\n\nexport const EmailImage = forwardRef<HTMLDivElement, EmailImageProps>(\n (props, ref) => {\n const { src: storedSrc, alt, width, href, align, sx, ...rest } = props\n // A media-picker target, so it can hold a media reference (AGL-1215).\n // This is the CANVAS preview only — the sent HTML is built by\n // `email-render`, which needs an absolute URL and cannot resolve either\n // a reference or the relative CDN path it replaces.\n const src = Aglyn.resolveMediaSrc(storedSrc)\n return (\n <Box\n ref={ref}\n {...rest}\n sx={mergeSxProps({ textAlign: align ?? 'center', my: 1 }, sx)}\n >\n {src ? (\n <Box\n component=\"img\"\n src={src}\n alt={alt ?? ''}\n sx={{ maxWidth: '100%', width: width ? `${width}px` : '100%' }}\n />\n ) : (\n <Box\n sx={{\n height: 120,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n border: '1px dashed',\n borderColor: 'divider',\n color: 'text.secondary',\n fontSize: 13,\n }}\n >\n {'Image — pick from the media library'}\n </Box>\n )}\n </Box>\n )\n },\n)\nEmailImage.displayName = 'EmailImage'\n\nexport const emailImageSchema: Aglyn.ComponentSchema<EmailImageProps> = {\n $id: IMAGE_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Email image',\n description: 'An email-safe image, capped at 600px wide, optionally linked.',\n category: Aglyn.ComponentCategory.DATA_DISPLAY,\n icon: { path: mdiImageOutline.path, sx: { color: '#f57c00' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'src',\n label: 'Source URL',\n description: 'Use an absolute URL — email clients cannot resolve relative paths.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'alt',\n label: 'Alt text',\n // AGL-1896: the same \"Browse media\" default the site Image element\n // gets. It matters MORE here — most clients block images by default,\n // so for most recipients the alt text IS the block.\n description:\n 'Shown while images are blocked — most clients block by default. ' +\n 'Filled in from the media library when you pick a file that has ' +\n 'alt text.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'width',\n label: 'Width (px)',\n description:\n 'How wide the image renders. Blank fills the section — which is ' +\n 'usually right, since a phone is narrower than any number you pick.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'href',\n label: 'Link URL',\n description:\n 'Where the image goes when it is clicked. Blank leaves it as a ' +\n 'plain picture.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'align',\n label: 'Alignment',\n description: 'Where the image sits when it is narrower than the section.',\n component: Aglyn.FieldComponentType.SELECT,\n options: [\n { value: 'left', label: 'Left' },\n { value: 'center', label: 'Center' },\n { value: 'right', label: 'Right' },\n ],\n },\n ],\n}\n\n// ----------------------------------------------------------------- button\nexport const BUTTON_ID: Aglyn.ComponentId = 'emailButton'\n\nexport interface EmailButtonProps extends EmailBlockStyleProps {\n children?: string\n href?: string\n backgroundColor?: string\n color?: string\n align?: 'left' | 'center' | 'right'\n}\n\nexport const EmailButton = forwardRef<HTMLDivElement, EmailButtonProps>(\n (props, ref) => {\n const { children, href, backgroundColor, color, align, sx, ...rest } =\n props\n return (\n <Box\n ref={ref}\n {...rest}\n sx={mergeSxProps({ textAlign: align ?? 'center', my: 1.5 }, sx)}\n >\n <Box\n component=\"span\"\n sx={{\n display: 'inline-block',\n px: 3,\n py: 1.25,\n borderRadius: 1,\n // The theme's accent when nothing was picked, as the sent email\n // resolves it (AGL-3370): the canvas and the inbox agree.\n backgroundColor: backgroundColor || 'primary.main',\n color: color || 'primary.contrastText',\n fontFamily: 'Helvetica, Arial, sans-serif',\n fontSize: 15,\n fontWeight: 600,\n }}\n >\n {children ?? 'Call to action'}\n </Box>\n </Box>\n )\n },\n)\nEmailButton.displayName = 'EmailButton'\n\nexport const emailButtonSchema: Aglyn.ComponentSchema<EmailButtonProps> = {\n $id: BUTTON_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Email button',\n description: 'A call-to-action button that survives every mail client.',\n category: Aglyn.ComponentCategory.INPUT,\n icon: { path: mdiGestureTapButton.path, sx: { color: '#f57c00' } },\n flags: { textEditable: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'children',\n label: 'Label',\n description:\n 'The words on the button. Say what happens next — \"See the report\" ' +\n 'reads as a destination where \"Click here\" does not.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'href',\n label: 'Link URL',\n description: 'Absolute URL (https://…). Merge tokens allowed.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'backgroundColor',\n label: 'Button color',\n description:\n 'The fill behind the label. Check it against the label color — a ' +\n 'button nobody can read is a link nobody clicks.',\n component: Aglyn.FieldComponentType.COLOR_PICKER,\n },\n {\n name: 'color',\n label: 'Label color',\n description: 'The color of the words on the button.',\n component: Aglyn.FieldComponentType.COLOR_PICKER,\n },\n {\n name: 'align',\n label: 'Alignment',\n description: 'Where the button sits across the width of the section.',\n component: Aglyn.FieldComponentType.SELECT,\n options: [\n { value: 'left', label: 'Left' },\n { value: 'center', label: 'Center' },\n { value: 'right', label: 'Right' },\n ],\n },\n ],\n}\n\n// ---------------------------------------------------------------- divider\nexport const DIVIDER_ID: Aglyn.ComponentId = 'emailDivider'\n\nexport interface EmailDividerProps extends EmailBlockStyleProps {\n color?: string\n}\n\nexport const EmailDivider = forwardRef<HTMLHRElement, EmailDividerProps>(\n (props, ref) => {\n const { color, sx, ...rest } = props\n return (\n <Box\n ref={ref}\n component=\"hr\"\n {...rest}\n sx={mergeSxProps(\n {\n border: 0,\n // Split shorthand so a palette token path from the theme-\n // reference color picker (AGL-588) resolves through the sx\n // system instead of dying inside a template string.\n borderTopStyle: 'solid',\n borderTopWidth: 1,\n borderTopColor: color || '#e0e0e0',\n my: 2,\n },\n sx,\n )}\n />\n )\n },\n)\nEmailDivider.displayName = 'EmailDivider'\n\nexport const emailDividerSchema: Aglyn.ComponentSchema<EmailDividerProps> = {\n $id: DIVIDER_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Email divider',\n description: 'A horizontal rule between email sections.',\n category: Aglyn.ComponentCategory.LAYOUT,\n icon: { path: mdiMinus.path, sx: { color: '#f57c00' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'color',\n label: 'Line color',\n description:\n 'The color of the rule. A light grey separates sections without ' +\n 'drawing the eye to the line itself.',\n component: Aglyn.FieldComponentType.COLOR_PICKER,\n },\n ],\n}\n\n// ----------------------------------------------------------------- spacer\nexport const SPACER_ID: Aglyn.ComponentId = 'emailSpacer'\n\nexport interface EmailSpacerProps extends EmailBlockStyleProps {\n /** Height in px (default 24). */\n height?: number\n}\n\nexport const EmailSpacer = forwardRef<HTMLDivElement, EmailSpacerProps>(\n (props, ref) => {\n const { height, sx, ...rest } = props\n return (\n <Box\n ref={ref}\n {...rest}\n sx={mergeSxProps({ height: height ?? 24 }, sx)}\n />\n )\n },\n)\nEmailSpacer.displayName = 'EmailSpacer'\n\nexport const emailSpacerSchema: Aglyn.ComponentSchema<EmailSpacerProps> = {\n $id: SPACER_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Email spacer',\n description: 'Fixed vertical whitespace between email blocks.',\n category: Aglyn.ComponentCategory.LAYOUT,\n icon: { path: mdiArrowExpandVertical.path, sx: { color: '#f57c00' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'height',\n label: 'Height (px)',\n description:\n 'How much empty vertical space this block adds. Email clients drop ' +\n 'CSS margins, so a spacer is how you get reliable breathing room.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n ],\n}\n\n// ---------------------------------------------------------------- product\nexport const PRODUCT_ID: Aglyn.ComponentId = 'emailProduct'\n\nexport interface EmailProductProps extends EmailBlockStyleProps {\n /** Product id — resolved server-side at render (rename-safe). */\n productId?: string\n buttonLabel?: string\n}\n\nexport const EmailProduct = forwardRef<HTMLDivElement, EmailProductProps>(\n (props, ref) => {\n const { productId, buttonLabel, sx, ...rest } = props\n return (\n <Box\n ref={ref}\n {...rest}\n sx={mergeSxProps(\n {\n border: '1px solid',\n borderColor: 'divider',\n borderRadius: 1,\n p: 2,\n my: 1,\n textAlign: 'center',\n color: 'text.secondary',\n fontSize: 13,\n },\n sx,\n )}\n >\n {productId\n ? `Product card (${productId}) — name, price, image fill in at send`\n : 'Product card — pick a product in the attributes panel'}\n <Box sx={{ mt: 1, fontWeight: 600 }}>\n {buttonLabel ?? 'Shop now'}\n </Box>\n </Box>\n )\n },\n)\nEmailProduct.displayName = 'EmailProduct'\n\nexport const emailProductSchema: Aglyn.ComponentSchema<EmailProductProps> = {\n $id: PRODUCT_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Email product',\n description: 'A product card resolved at send time, so it never goes stale.',\n category: Aglyn.ComponentCategory.DATA_DISPLAY,\n icon: { path: mdiPackageVariantClosed.path, sx: { color: '#f57c00' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'productId',\n label: 'Product',\n description:\n 'Stored by id — the current name, price, and image render at send.',\n component: Aglyn.FieldComponentType.PRODUCT_SELECT,\n },\n {\n name: 'buttonLabel',\n label: 'Button label',\n description:\n 'The words on the card’s button. Blank uses the block’s default, ' +\n 'and the link always points at this product’s page.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n ],\n}\n\n// ------------------------------------------------------------ custom html\nexport const HTML_ID: Aglyn.ComponentId = 'emailHtml'\n\nexport interface EmailHtmlProps extends EmailBlockStyleProps {\n html?: string\n}\n\nexport const EmailHtml = forwardRef<HTMLDivElement, EmailHtmlProps>(\n (props, ref) => {\n const { html, sx, ...rest } = props\n return (\n <Box\n ref={ref}\n {...rest}\n sx={mergeSxProps({ fontFamily: 'Helvetica, Arial, sans-serif' }, sx)}\n // No `typeof window` guard any more (AGL-1901): the sanitizer needs\n // no DOM, so a server-rendered email keeps its custom HTML.\n dangerouslySetInnerHTML={{\n __html: html ? sanitizeAuthorHtml(html) : '',\n }}\n />\n )\n },\n)\nEmailHtml.displayName = 'EmailHtml'\n\nexport const emailHtmlSchema: Aglyn.ComponentSchema<EmailHtmlProps> = {\n $id: HTML_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Email custom HTML',\n description: 'Hand-written HTML for an email, used as-is.',\n category: Aglyn.ComponentCategory.DATA_DISPLAY,\n icon: { path: mdiCodeTags.path, sx: { color: '#f57c00' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'html',\n label: 'HTML',\n description:\n 'Raw table markup for advanced layouts. Sanitized with the ' +\n 'custom-html policy; the render pipeline passes it through as-is ' +\n 'after sanitization.',\n component: Aglyn.FieldComponentType.TEXTAREA,\n },\n ],\n}\n\n/**\n * The two bands most emails a site sends begin and end with (AGL-3287).\n *\n * Each drops into any email like the blocks above, and the Components page\n * starts a reusable Header or Footer email block from these same trees, by\n * the ids core names in `REUSABLE_EMAIL_BLOCK_STARTER_PRESET_IDS` — one tree\n * each, wherever it is started from.\n *\n * The words are placeholders to replace. No merge token names the company in\n * both kinds of email — a campaign supplies `{{org.name}}`, and no token is\n * supplied by every one of a site's own emails — while a token an email does\n * not supply is blanked before sending, which would leave the header with no\n * name at all.\n *\n * The footer carries no unsubscribe link: a campaign send appends its own,\n * and a second one would do the same thing twice.\n */\nexport const EMAIL_HEADER_PRESET: Aglyn.PresetSchema = {\n $id: Aglyn.REUSABLE_EMAIL_BLOCK_STARTER_PRESET_IDS.header,\n type: Aglyn.NodeType.PRESET,\n displayName: 'Header',\n description:\n 'Your logo above your company name, for the top of an email. Pick the ' +\n 'logo from your media library — until you do, the picture shows nothing ' +\n 'in a sent email.',\n pluginId: BUNDLE_ID,\n category: Aglyn.ComponentCategory.BLOCKS,\n tags: ['logo', 'masthead', 'top', 'brand'],\n icon: { path: mdiPageLayoutHeader.path, sx: { color: '#f57c00' } },\n // The same tree the Components page and the native apps start a block from.\n data: EMAIL_BLOCK_STARTER_TREES.header as Aglyn.PresetSchema['data'],\n}\n\n/** See {@link EMAIL_HEADER_PRESET}. */\nexport const EMAIL_FOOTER_PRESET: Aglyn.PresetSchema = {\n $id: Aglyn.REUSABLE_EMAIL_BLOCK_STARTER_PRESET_IDS.footer,\n type: Aglyn.NodeType.PRESET,\n displayName: 'Footer',\n description:\n 'Your company name and address, and why the reader gets your emails, for ' +\n 'the bottom of an email. Campaign emails add the unsubscribe link for you.',\n pluginId: BUNDLE_ID,\n category: Aglyn.ComponentCategory.BLOCKS,\n tags: ['address', 'bottom', 'contact', 'legal'],\n icon: { path: mdiPageLayoutFooter.path, sx: { color: '#f57c00' } },\n // The same tree the Components page and the native apps start a block from.\n data: EMAIL_BLOCK_STARTER_TREES.footer as Aglyn.PresetSchema['data'],\n}\n\nexport const emailPresets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(SECTION_ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Email section',\n pluginId: BUNDLE_ID,\n category: Aglyn.ComponentCategory.LAYOUT,\n icon: { path: mdiEmailOutline.path, sx: { color: '#f57c00' } },\n data: {\n $id: null,\n componentId: SECTION_ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n {\n $id: generatePresetId(TEXT_ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Email text',\n pluginId: BUNDLE_ID,\n category: Aglyn.ComponentCategory.DATA_DISPLAY,\n icon: { path: mdiFormatText.path, sx: { color: '#f57c00' } },\n data: {\n $id: null,\n componentId: TEXT_ID,\n pluginId: BUNDLE_ID,\n props: { children: 'Hello {{contact.firstName}},', variant: 'body' },\n },\n },\n {\n $id: generatePresetId(RICHTEXT_ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Email rich text',\n pluginId: BUNDLE_ID,\n category: Aglyn.ComponentCategory.DATA_DISPLAY,\n icon: { path: mdiCardTextOutline.path, sx: { color: '#f57c00' } },\n data: {\n $id: null,\n componentId: RICHTEXT_ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n {\n $id: generatePresetId(IMAGE_ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Email image',\n pluginId: BUNDLE_ID,\n category: Aglyn.ComponentCategory.DATA_DISPLAY,\n icon: { path: mdiImageOutline.path, sx: { color: '#f57c00' } },\n data: {\n $id: null,\n componentId: IMAGE_ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n {\n $id: generatePresetId(BUTTON_ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Email button',\n pluginId: BUNDLE_ID,\n category: Aglyn.ComponentCategory.INPUT,\n icon: { path: mdiGestureTapButton.path, sx: { color: '#f57c00' } },\n data: {\n $id: null,\n componentId: BUTTON_ID,\n pluginId: BUNDLE_ID,\n props: { children: 'Shop now' },\n },\n },\n {\n $id: generatePresetId(DIVIDER_ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Email divider',\n pluginId: BUNDLE_ID,\n category: Aglyn.ComponentCategory.LAYOUT,\n icon: { path: mdiMinus.path, sx: { color: '#f57c00' } },\n data: {\n $id: null,\n componentId: DIVIDER_ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n {\n $id: generatePresetId(SPACER_ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Email spacer',\n pluginId: BUNDLE_ID,\n category: Aglyn.ComponentCategory.LAYOUT,\n icon: { path: mdiArrowExpandVertical.path, sx: { color: '#f57c00' } },\n data: {\n $id: null,\n componentId: SPACER_ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n {\n $id: generatePresetId(PRODUCT_ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Email product',\n pluginId: BUNDLE_ID,\n category: Aglyn.ComponentCategory.DATA_DISPLAY,\n icon: { path: mdiPackageVariantClosed.path, sx: { color: '#f57c00' } },\n data: {\n $id: null,\n componentId: PRODUCT_ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n {\n $id: generatePresetId(HTML_ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Email custom HTML',\n pluginId: BUNDLE_ID,\n category: Aglyn.ComponentCategory.DATA_DISPLAY,\n icon: { path: mdiCodeTags.path, sx: { color: '#f57c00' } },\n data: {\n $id: null,\n componentId: HTML_ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n EMAIL_HEADER_PRESET,\n EMAIL_FOOTER_PRESET,\n]\n"],"names":["Aglyn","mdiCardTextOutline","mdiEmailOutline","mdiFormatText","mdiImageOutline","mdiMinus","mdiPackageVariantClosed","mdiPageLayoutFooter","mdiPageLayoutHeader","mdiArrowExpandVertical","mdiGestureTapButton","mdiCodeTags","sanitizeAuthorHtml","EMAIL_BLOCK_STARTER_TREES","mergeSxProps","Box","Typography","forwardRef","BUNDLE_ID","generatePresetId","SECTION_ID","EmailSection","props","ref","backgroundColor","padding","align","children","sx","emailRole","_emailRole","rest","maxWidth","mx","p","textAlign","displayName","emailSectionSchema","$id","pluginId","description","category","ComponentCategory","LAYOUT","icon","path","color","attributes","name","label","component","FieldComponentType","COLOR_PICKER","TEXT_FIELD","type","SELECT","options","value","TEXT_ID","TEXT_PRESETS","heading","fontSize","fontWeight","lineHeight","subheading","body","caption","EmailText","variant","preset","fontFamily","mb","emailTextSchema","DATA_DISPLAY","flags","textEditable","FEATURE_FLAG","ENABLED","TEXTAREA","RICHTEXT_ID","EmailRichtext","html","trim","border","borderColor","dangerouslySetInnerHTML","__html","emailRichtextSchema","selfClosing","IMAGE_ID","EmailImage","src","storedSrc","alt","width","href","resolveMediaSrc","my","height","display","alignItems","justifyContent","emailImageSchema","BUTTON_ID","EmailButton","px","py","borderRadius","emailButtonSchema","INPUT","DIVIDER_ID","EmailDivider","borderTopStyle","borderTopWidth","borderTopColor","emailDividerSchema","SPACER_ID","EmailSpacer","emailSpacerSchema","PRODUCT_ID","EmailProduct","productId","buttonLabel","mt","emailProductSchema","PRODUCT_SELECT","HTML_ID","EmailHtml","emailHtmlSchema","EMAIL_HEADER_PRESET","REUSABLE_EMAIL_BLOCK_STARTER_PRESET_IDS","header","NodeType","PRESET","BLOCKS","tags","data","EMAIL_FOOTER_PRESET","footer","emailPresets","componentId"],"mappings":"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"}
@@ -41,7 +41,7 @@
41
41
  * Those are different acts with different records, and a console that let them
42
42
  * blur is a console where "remove" is quietly relied on as an unsubscribe.
43
43
  */
44
- import { type ConsentGroup, type DynamicListRule } from '@aglyn/aglyn';
44
+ import { type ConsentGroup, type DynamicListRule, type MarketingConsentRecord } from '@aglyn/aglyn';
45
45
  export interface ListMembersPanelProps {
46
46
  hostId: string;
47
47
  /**
@@ -102,6 +102,83 @@ export interface ListMembersFilter {
102
102
  }>;
103
103
  };
104
104
  }
105
+ /**
106
+ * One member row, as stored.
107
+ *
108
+ * The consent fields are deliberately NOT enumerated here. They live one level
109
+ * down, in an entry per site under `marketingConsentByHost`, and a row type
110
+ * that named them at the top would be an invitation to read them there — which
111
+ * is exactly what this table did while `enrollListMember` wrote them nested,
112
+ * so every membership on every list reported "No basis on record" while the
113
+ * send path mailed the same people on a basis it could see perfectly well. The
114
+ * index signature is what {@link readMarketingBasis} takes; it is the only
115
+ * reader of those fields on this surface.
116
+ */
117
+ interface MemberRow {
118
+ $id: string;
119
+ email?: string;
120
+ name?: string;
121
+ source?: string;
122
+ via?: 'manual' | 'rule';
123
+ addedAt?: {
124
+ toDate?: () => Date;
125
+ };
126
+ [field: string]: unknown;
127
+ }
128
+ /**
129
+ * How the basis reads on screen, for the group this console is viewed as.
130
+ *
131
+ * ## Read through `readMarketingBasis`, never off the row
132
+ *
133
+ * This picked `marketingConsent`, `marketingConsentBasis` and
134
+ * `marketingConsentAtMs` off the top of the membership document, which is
135
+ * where `enrollListMember` used to put them. They now live in an entry per
136
+ * site under `marketingConsentByHost`, because a basis runs to the brand it
137
+ * was given to rather than to the account that holds the row. Nothing here
138
+ * moved with them, so every top-level read answered `undefined` and every
139
+ * member of every list — including people carrying a perfectly good attested
140
+ * basis — was reported as having no basis on record, on the one screen an
141
+ * operator consults before mailing them. `performCampaignSend` reads the same
142
+ * documents through the function below and mailed them, correctly, at the same
143
+ * time.
144
+ *
145
+ * The fix is not a second field name. It is that the send path's reader is the
146
+ * only reader: a surface that parses the stored shape itself agrees with the
147
+ * sender only until the shape moves, and it moves without anybody thinking to
148
+ * look here.
149
+ *
150
+ * ## Five states, because the model carries five
151
+ *
152
+ * An attestation and an opt-in are not the same fact — one is a person's own
153
+ * decision, the other a claim an account made on their behalf — and a table
154
+ * rendering both as a tick is the conflation `list-members.ts` stores the
155
+ * basis to prevent. The three that are not a grant are not one state either.
156
+ * Absence is not refusal, and neither of them is "opted in, to somebody else
157
+ * in this account": that third one is a person the sender deliberately leaves
158
+ * alone, and an operator who cannot see why reads it as the product having
159
+ * lost their subscriber.
160
+ */
161
+ type ConsentKey = 'opted-out' | 'attested' | 'opted-in' | 'other-site' | 'no-site' | 'none';
162
+ /**
163
+ * A rule-enrolled member's basis as their CONTACT records it, as
164
+ * `email/list-member-consent` answers it.
165
+ *
166
+ * A row a list rule wrote never holds a basis of its own — matching a rule is
167
+ * not an opt-in — so `performCampaignSend` reads those people's contacts, and
168
+ * this column shows what it reads. Fetched, because the browser may not read
169
+ * the org's contacts by address.
170
+ */
171
+ export interface ContactBasis {
172
+ basis: MarketingConsentRecord['basis'];
173
+ assertedBy: MarketingConsentRecord['assertedBy'];
174
+ basisAtMs: number | null;
175
+ otherGrant: MarketingConsentRecord['otherGrant'];
176
+ }
177
+ export declare function consentLabel(member: MemberRow, group: ConsentGroup, fromContact?: ContactBasis | null): {
178
+ key: ConsentKey;
179
+ label: string;
180
+ color: 'default' | 'warning' | 'error';
181
+ };
105
182
  /** Free text — a paste, a typed address, commas or newlines — to addresses. */
106
183
  export declare function splitAddresses(value: string): string[];
107
184
  export declare function ListMembersPanel(props: ListMembersPanelProps): import("react").JSX.Element;
@@ -77,8 +77,12 @@ import { useListQuery } from "@aglyn/tenant-feature-instance/hooks/use-list-quer
77
77
  import { useListColumnSort } from "@aglyn/shared-ui-jsx/hooks/use-list-column-sort";
78
78
  import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
79
79
  import { LIST_MEMBER_FILTER_HEADERS, LIST_MEMBER_FILTER_OPTIONS, LIST_MEMBER_QUERY } from "../constants/list-queries.js";
80
- function consentLabel(member, group) {
81
- const record = readMarketingBasis(member, group);
80
+ /** The key a member row and the consent route both use for an address. */ const memberAddress = (member)=>typeof member.email === 'string' ? member.email.trim().toLowerCase() : '';
81
+ export function consentLabel(member, group, fromContact) {
82
+ const own = readMarketingBasis(member, group);
83
+ // The contact answers only where the row itself is silent and a rule put it
84
+ // there — the same overlay the send applies.
85
+ const record = fromContact && member.via === 'rule' && own.basis === 'unrecorded' ? fromContact : own;
82
86
  const when = record.basisAtMs ? ` · ${new Date(record.basisAtMs).toLocaleDateString()}` : '';
83
87
  if (record.basis === 'declined') {
84
88
  return {
@@ -192,10 +196,15 @@ export function ListMembersPanel(props) {
192
196
  ],
193
197
  idField: '$id'
194
198
  });
199
+ /** Rule members' contact bases, by address — see {@link ContactBasis}. */ const [contactConsent, setContactConsent] = useState({});
200
+ const labelFor = useCallback((row)=>consentLabel(row, consentGroup, contactConsent[memberAddress(row)]), [
201
+ consentGroup,
202
+ contactConsent
203
+ ]);
195
204
  const pageSorts = useMemo(()=>({
196
- consent: (row)=>consentLabel(row, consentGroup).label
205
+ consent: (row)=>labelFor(row).label
197
206
  }), [
198
- consentGroup
207
+ labelFor
199
208
  ]);
200
209
  const columnSort = useListColumnSort({
201
210
  sorts: LIST_MEMBER_QUERY.sorts,
@@ -283,6 +292,37 @@ export function ListMembersPanel(props) {
283
292
  hostId,
284
293
  listId
285
294
  ]);
295
+ /*
296
+ * The page's rule rows that say nothing about consent themselves, asked of
297
+ * their contacts once per page. Rows already answered are not asked again;
298
+ * a refused or failed read leaves the row's own label, which is the honest
299
+ * fallback — it is what the row records.
300
+ */ const unansweredRuleRows = members.filter((row)=>row.via === 'rule' && consentLabel(row, consentGroup).key === 'none' && !(memberAddress(row) in contactConsent)).map(memberAddress).filter(Boolean);
301
+ const unansweredKey = unansweredRuleRows.join('\n');
302
+ useEffect(()=>{
303
+ if (!unansweredKey || !user) return;
304
+ let current = true;
305
+ const asked = unansweredKey.split('\n');
306
+ void post('list-member-consent', {
307
+ emails: asked
308
+ }).then(({ ok, payload })=>{
309
+ var _ref, _answered_email;
310
+ if (!current || !ok) return;
311
+ const answered = (_ref = payload == null ? void 0 : payload.consent) != null ? _ref : {};
312
+ // Every address asked is settled — `null` where the route had nothing
313
+ // to add — so a page is asked about once.
314
+ const settled = {};
315
+ for (const email of asked)settled[email] = (_answered_email = answered[email]) != null ? _answered_email : null;
316
+ setContactConsent((before)=>_extends({}, before, settled));
317
+ }).catch(()=>undefined);
318
+ return ()=>{
319
+ current = false;
320
+ };
321
+ }, [
322
+ unansweredKey,
323
+ user,
324
+ post
325
+ ]);
286
326
  const handlePreview = useCallback(async ()=>{
287
327
  if (busy || !addresses.length) return;
288
328
  setBusy(true);
@@ -518,9 +558,9 @@ export function ListMembersPanel(props) {
518
558
  field: 'consent',
519
559
  headerName: 'Consent',
520
560
  width: 190,
521
- valueGetter: (_value, row)=>consentLabel(row, consentGroup).label,
561
+ valueGetter: (_value, row)=>labelFor(row).label,
522
562
  renderCell: ({ row })=>{
523
- const consent = consentLabel(row, consentGroup);
563
+ const consent = labelFor(row);
524
564
  return /*#__PURE__*/ _jsx(Chip, {
525
565
  size: "small",
526
566
  variant: "outlined",