@aglyn/plugins-marketing 1.0.0-beta.143
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +7 -0
- package/package.json +56 -0
- package/src/index.d.ts +19 -0
- package/src/index.js +20 -0
- package/src/index.js.map +1 -0
- package/src/lib/components/announcement-bar-card.component.d.ts +20 -0
- package/src/lib/components/announcement-bar-card.component.js +292 -0
- package/src/lib/components/announcement-bar-card.component.js.map +1 -0
- package/src/lib/components/campaign-composer.d.ts +84 -0
- package/src/lib/components/campaign-composer.js +1643 -0
- package/src/lib/components/campaign-composer.js.map +1 -0
- package/src/lib/components/campaign-conversions-card.d.ts +22 -0
- package/src/lib/components/campaign-conversions-card.js +642 -0
- package/src/lib/components/campaign-conversions-card.js.map +1 -0
- package/src/lib/components/campaign-detail-card.d.ts +55 -0
- package/src/lib/components/campaign-detail-card.js +771 -0
- package/src/lib/components/campaign-detail-card.js.map +1 -0
- package/src/lib/components/campaign-edit-drawer.d.ts +60 -0
- package/src/lib/components/campaign-edit-drawer.js +252 -0
- package/src/lib/components/campaign-edit-drawer.js.map +1 -0
- package/src/lib/components/campaign-email-zones.d.ts +111 -0
- package/src/lib/components/campaign-email-zones.js +54 -0
- package/src/lib/components/campaign-email-zones.js.map +1 -0
- package/src/lib/components/campaign-glance-card.component.d.ts +18 -0
- package/src/lib/components/campaign-glance-card.component.js +151 -0
- package/src/lib/components/campaign-glance-card.component.js.map +1 -0
- package/src/lib/components/campaign-members-section.d.ts +87 -0
- package/src/lib/components/campaign-members-section.js +499 -0
- package/src/lib/components/campaign-members-section.js.map +1 -0
- package/src/lib/components/campaign-reach-sections.d.ts +137 -0
- package/src/lib/components/campaign-reach-sections.js +694 -0
- package/src/lib/components/campaign-reach-sections.js.map +1 -0
- package/src/lib/components/campaign-report-card.d.ts +91 -0
- package/src/lib/components/campaign-report-card.js +667 -0
- package/src/lib/components/campaign-report-card.js.map +1 -0
- package/src/lib/components/campaign-test-send-drawer.d.ts +45 -0
- package/src/lib/components/campaign-test-send-drawer.js +238 -0
- package/src/lib/components/campaign-test-send-drawer.js.map +1 -0
- package/src/lib/components/campaigns-card.d.ts +31 -0
- package/src/lib/components/campaigns-card.js +667 -0
- package/src/lib/components/campaigns-card.js.map +1 -0
- package/src/lib/components/conversion-attribution.component.d.ts +62 -0
- package/src/lib/components/conversion-attribution.component.js +127 -0
- package/src/lib/components/conversion-attribution.component.js.map +1 -0
- package/src/lib/components/email-compose-card.d.ts +12 -0
- package/src/lib/components/email-compose-card.js +157 -0
- package/src/lib/components/email-compose-card.js.map +1 -0
- package/src/lib/components/email-detail.d.ts +40 -0
- package/src/lib/components/email-detail.js +1180 -0
- package/src/lib/components/email-detail.js.map +1 -0
- package/src/lib/components/email-edit-drawer.d.ts +54 -0
- package/src/lib/components/email-edit-drawer.js +172 -0
- package/src/lib/components/email-edit-drawer.js.map +1 -0
- package/src/lib/components/email-messages-widget.d.ts +22 -0
- package/src/lib/components/email-messages-widget.js +49 -0
- package/src/lib/components/email-messages-widget.js.map +1 -0
- package/src/lib/components/email-recipients-card.d.ts +50 -0
- package/src/lib/components/email-recipients-card.js +338 -0
- package/src/lib/components/email-recipients-card.js.map +1 -0
- package/src/lib/components/emails-list-card.d.ts +36 -0
- package/src/lib/components/emails-list-card.js +544 -0
- package/src/lib/components/emails-list-card.js.map +1 -0
- package/src/lib/components/experiment-zones.d.ts +78 -0
- package/src/lib/components/experiment-zones.js +65 -0
- package/src/lib/components/experiment-zones.js.map +1 -0
- package/src/lib/components/host-experiments-card.component.d.ts +17 -0
- package/src/lib/components/host-experiments-card.component.js +887 -0
- package/src/lib/components/host-experiments-card.component.js.map +1 -0
- package/src/lib/components/host-marketing-summary-card.component.d.ts +15 -0
- package/src/lib/components/host-marketing-summary-card.component.js +161 -0
- package/src/lib/components/host-marketing-summary-card.component.js.map +1 -0
- package/src/lib/components/host-overlays-card.component.d.ts +19 -0
- package/src/lib/components/host-overlays-card.component.js +783 -0
- package/src/lib/components/host-overlays-card.component.js.map +1 -0
- package/src/lib/components/marketing-console-page.d.ts +28 -0
- package/src/lib/components/marketing-console-page.js +166 -0
- package/src/lib/components/marketing-console-page.js.map +1 -0
- package/src/lib/components/marketing-console-sections.d.ts +34 -0
- package/src/lib/components/marketing-console-sections.js +77 -0
- package/src/lib/components/marketing-console-sections.js.map +1 -0
- package/src/lib/components/overlay-stats-row.component.d.ts +15 -0
- package/src/lib/components/overlay-stats-row.component.js +78 -0
- package/src/lib/components/overlay-stats-row.component.js.map +1 -0
- package/src/lib/components/popup-card.component.d.ts +18 -0
- package/src/lib/components/popup-card.component.js +462 -0
- package/src/lib/components/popup-card.component.js.map +1 -0
- package/src/lib/components/record-attribution-widget.d.ts +35 -0
- package/src/lib/components/record-attribution-widget.js +33 -0
- package/src/lib/components/record-attribution-widget.js.map +1 -0
- package/src/lib/components/site-runtime.d.ts +9 -0
- package/src/lib/components/site-runtime.js +1081 -0
- package/src/lib/components/site-runtime.js.map +1 -0
- package/src/lib/components/use-campaign-send-api.d.ts +35 -0
- package/src/lib/components/use-campaign-send-api.js +99 -0
- package/src/lib/components/use-campaign-send-api.js.map +1 -0
- package/src/lib/components/use-campaign-topic-options.d.ts +20 -0
- package/src/lib/components/use-campaign-topic-options.js +44 -0
- package/src/lib/components/use-campaign-topic-options.js.map +1 -0
- package/src/lib/components/use-emails-hub-path.d.ts +39 -0
- package/src/lib/components/use-emails-hub-path.js +70 -0
- package/src/lib/components/use-emails-hub-path.js.map +1 -0
- package/src/lib/components/use-marketing-hub-path.d.ts +21 -0
- package/src/lib/components/use-marketing-hub-path.js +50 -0
- package/src/lib/components/use-marketing-hub-path.js.map +1 -0
- package/src/lib/components/use-overlay-copy-editor.d.ts +79 -0
- package/src/lib/components/use-overlay-copy-editor.js +110 -0
- package/src/lib/components/use-overlay-copy-editor.js.map +1 -0
- package/src/lib/constants/bundle-common.d.ts +8 -0
- package/src/lib/constants/bundle-common.js +9 -0
- package/src/lib/constants/bundle-common.js.map +1 -0
- package/src/lib/model/campaign-membership-figures.d.ts +109 -0
- package/src/lib/model/campaign-membership-figures.js +113 -0
- package/src/lib/model/campaign-membership-figures.js.map +1 -0
- package/src/lib/model/compile-client-automations.d.ts +65 -0
- package/src/lib/model/compile-client-automations.js +95 -0
- package/src/lib/model/compile-client-automations.js.map +1 -0
- package/src/lib/model/experiments.d.ts +130 -0
- package/src/lib/model/experiments.js +186 -0
- package/src/lib/model/experiments.js.map +1 -0
- package/src/lib/model/index.d.ts +26 -0
- package/src/lib/model/index.js +26 -0
- package/src/lib/model/index.js.map +1 -0
- package/src/lib/model/marketing-record-routes.d.ts +6 -0
- package/src/lib/model/marketing-record-routes.js +46 -0
- package/src/lib/model/marketing-record-routes.js.map +1 -0
- package/src/lib/model/overlay-copy.d.ts +62 -0
- package/src/lib/model/overlay-copy.js +78 -0
- package/src/lib/model/overlay-copy.js.map +1 -0
- package/src/lib/model/overlays.d.ts +113 -0
- package/src/lib/model/overlays.js +93 -0
- package/src/lib/model/overlays.js.map +1 -0
- package/src/lib/model/site-contract.d.ts +100 -0
- package/src/lib/model/site-contract.js +23 -0
- package/src/lib/model/site-contract.js.map +1 -0
- package/src/lib/plugin.d.ts +27 -0
- package/src/lib/plugin.js +168 -0
- package/src/lib/plugin.js.map +1 -0
- package/src/lib/preview-props.d.ts +31 -0
- package/src/lib/preview-props.js +54 -0
- package/src/lib/preview-props.js.map +1 -0
- package/src/lib/server/campaign-manage.d.ts +62 -0
- package/src/lib/server/campaign-manage.js +829 -0
- package/src/lib/server/campaign-manage.js.map +1 -0
- package/src/lib/server/campaign-process-scheduled.d.ts +26 -0
- package/src/lib/server/campaign-process-scheduled.js +198 -0
- package/src/lib/server/campaign-process-scheduled.js.map +1 -0
- package/src/lib/server/campaign-recipients.d.ts +55 -0
- package/src/lib/server/campaign-recipients.js +219 -0
- package/src/lib/server/campaign-recipients.js.map +1 -0
- package/src/lib/server/campaign-send.d.ts +487 -0
- package/src/lib/server/campaign-send.js +3157 -0
- package/src/lib/server/campaign-send.js.map +1 -0
- package/src/lib/server/email-campaign-reach.d.ts +174 -0
- package/src/lib/server/email-campaign-reach.js +254 -0
- package/src/lib/server/email-campaign-reach.js.map +1 -0
- package/src/lib/server/email-events.d.ts +27 -0
- package/src/lib/server/email-events.js +714 -0
- package/src/lib/server/email-events.js.map +1 -0
- package/src/lib/server/get-client-automations.d.ts +60 -0
- package/src/lib/server/get-client-automations.js +87 -0
- package/src/lib/server/get-client-automations.js.map +1 -0
- package/src/lib/server/get-overlays.d.ts +28 -0
- package/src/lib/server/get-overlays.js +65 -0
- package/src/lib/server/get-overlays.js.map +1 -0
- package/src/lib/server/get-screen-experiments.d.ts +42 -0
- package/src/lib/server/get-screen-experiments.js +82 -0
- package/src/lib/server/get-screen-experiments.js.map +1 -0
- package/src/lib/server/lists-materialize.d.ts +18 -0
- package/src/lib/server/lists-materialize.js +130 -0
- package/src/lib/server/lists-materialize.js.map +1 -0
- package/src/lib/server/marketing-figures.d.ts +37 -0
- package/src/lib/server/marketing-figures.js +248 -0
- package/src/lib/server/marketing-figures.js.map +1 -0
- package/src/lib/server/site-page-enricher.d.ts +26 -0
- package/src/lib/server/site-page-enricher.js +210 -0
- package/src/lib/server/site-page-enricher.js.map +1 -0
- package/src/lib/server.d.ts +20 -0
- package/src/lib/server.js +152 -0
- package/src/lib/server.js.map +1 -0
- package/src/lib/site.d.ts +22 -0
- package/src/lib/site.js +48 -0
- package/src/lib/site.js.map +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/model/overlay-copy.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n type HostTokenSource,\n resolveHostTokens,\n} from '@aglyn/aglyn/app-utils/host-tokens'\nimport {\n type HostVariable,\n resolveBindings,\n} from '@aglyn/aglyn/app-utils/variables'\n\n/**\n * The fields of a bar or popup that hold copy: a bar's `text`, a popup's\n * `headline` and `body`. Links, labels and colors are not copy and are never\n * resolved.\n */\nexport const OVERLAY_COPY_FIELDS = ['text', 'headline', 'body'] as const\n\n/**\n * Overlay copy as a visitor reads it: an announcement bar's text, and a\n * popup's headline and body.\n *\n * Every path that puts overlay copy in front of a visitor goes through this\n * one function — the bar and popup a page shows on load, and a bar or popup an\n * automation shows later — so the same overlay cannot read one way when it\n * matches the page and another way when an automation opens it.\n *\n * The order is the one a page's own copy is resolved in: site variables\n * (`{{var:id}}`) first, then the site's details (`{{host.*}}`). A variable\n * whose document is gone renders as nothing, and so does a detail the site has\n * not set; neither ever reaches a visitor as the token.\n *\n * Deep imports rather than either `@aglyn/aglyn` barrel: the server enricher\n * and the console editors both import this module, and the client barrel\n * carries React contexts a server graph must not load.\n *\n * @param variables - the site's variables keyed by document id, as\n * `getVariables` returns them.\n * @param host - the site document the copy is published on.\n */\nexport function resolveOverlayCopy(\n text: string,\n variables: Record<string, HostVariable>,\n host: HostTokenSource | null | undefined,\n): string {\n if (typeof text !== 'string' || !text.includes('{{')) return text\n return resolveHostTokens(resolveBindings(text, variables), host)\n}\n\n/** Any `{{…}}` token, whatever it names. */\nconst ANY_TOKEN_PATTERN = /\\{\\{[^{}]*\\}\\}/g\n\n/**\n * The tokens in stored overlay copy that a visitor would read exactly as\n * typed: every token {@link resolveOverlayCopy} leaves in place.\n *\n * A bare `{{name}}` is one — nothing on a published page resolves that form,\n * which is why an editor rewrites a name it recognizes to `{{var:id}}` before\n * saving — and so is any token overlay copy has no source for, such as a\n * function call or an entry field.\n *\n * Each token is put through the published page's own resolution rather than\n * matched against a list of forms, so what an editor warns about cannot drift\n * from what the page does. A variable whose document is gone and a detail the\n * site has not set both render as nothing, and neither is listed.\n */\nexport function overlayCopyTokensShownAsTyped(\n text: string | undefined,\n variables: Record<string, HostVariable>,\n host: HostTokenSource | null | undefined,\n): string[] {\n if (typeof text !== 'string' || !text.includes('{{')) return []\n const shown = new Set<string>()\n for (const token of text.match(ANY_TOKEN_PATTERN) ?? []) {\n if (resolveOverlayCopy(token, variables, host) === token) shown.add(token)\n }\n return [...shown]\n}\n"],"names":["resolveHostTokens","resolveBindings","OVERLAY_COPY_FIELDS","resolveOverlayCopy","text","variables","host","includes","ANY_TOKEN_PATTERN","overlayCopyTokensShownAsTyped","shown","Set","token","match","add"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAEEA,iBAAiB,QACZ,qCAAoC;AAC3C,SAEEC,eAAe,QACV,mCAAkC;AAEzC;;;;CAIC,GACD,OAAO,MAAMC,sBAAsB;IAAC;IAAQ;IAAY;CAAO,CAAS;AAExE;;;;;;;;;;;;;;;;;;;;;CAqBC,GACD,OAAO,SAASC,mBACdC,IAAY,EACZC,SAAuC,EACvCC,IAAwC;IAExC,IAAI,OAAOF,SAAS,YAAY,CAACA,KAAKG,QAAQ,CAAC,OAAO,OAAOH;IAC7D,OAAOJ,kBAAkBC,gBAAgBG,MAAMC,YAAYC;AAC7D;AAEA,0CAA0C,GAC1C,MAAME,oBAAoB;AAE1B;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASC,8BACdL,IAAwB,EACxBC,SAAuC,EACvCC,IAAwC;QAIpBF;IAFpB,IAAI,OAAOA,SAAS,YAAY,CAACA,KAAKG,QAAQ,CAAC,OAAO,OAAO,EAAE;IAC/D,MAAMG,QAAQ,IAAIC;IAClB,KAAK,MAAMC,UAASR,cAAAA,KAAKS,KAAK,CAACL,8BAAXJ,cAAiC,EAAE,CAAE;QACvD,IAAID,mBAAmBS,OAAOP,WAAWC,UAAUM,OAAOF,MAAMI,GAAG,CAACF;IACtE;IACA,OAAO;WAAIF;KAAM;AACnB"}
|
|
@@ -0,0 +1,113 @@
|
|
|
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 type { HostAnnouncementBar, HostPopup } from '@aglyn/aglyn';
|
|
18
|
+
/**
|
|
19
|
+
* Marketing hub overlays (AGL-251): multiple announcement bars and
|
|
20
|
+
* promotional popups per host at `hosts/{hostId}/overlays/{overlayId}`,
|
|
21
|
+
* each with its own schedule window and page targeting. The legacy single
|
|
22
|
+
* `host.announcementBar` / `host.popup` fields keep working as fallbacks
|
|
23
|
+
* when no overlay doc matches. Pure types + resolution helpers here; the
|
|
24
|
+
* tenant render picks the first active match per kind.
|
|
25
|
+
*/
|
|
26
|
+
export interface HostOverlay {
|
|
27
|
+
kind: 'bar' | 'popup';
|
|
28
|
+
/** Display name in the console list (e.g. "Black Friday bar"). */
|
|
29
|
+
name?: string;
|
|
30
|
+
/** Disabled overlays never render; new overlays default enabled. */
|
|
31
|
+
enabled?: boolean;
|
|
32
|
+
/** Bar payload when `kind: 'bar'` (the legacy shape, minus enabled). */
|
|
33
|
+
bar?: Omit<HostAnnouncementBar, 'enabled'>;
|
|
34
|
+
/** Popup payload when `kind: 'popup'` (the legacy shape, minus enabled). */
|
|
35
|
+
popup?: Omit<HostPopup, 'enabled' | 'startAtMs' | 'endAtMs'>;
|
|
36
|
+
/** Showing window (epoch millis; simple to serialize). */
|
|
37
|
+
startAtMs?: number;
|
|
38
|
+
endAtMs?: number;
|
|
39
|
+
/**
|
|
40
|
+
* Path patterns the overlay shows on; empty/absent = every page.
|
|
41
|
+
* Patterns are exact paths or prefix globs (`/pricing`, `/blog/*`).
|
|
42
|
+
*/
|
|
43
|
+
pathPatterns?: string[];
|
|
44
|
+
/** Path patterns the overlay never shows on; wins over includes. */
|
|
45
|
+
excludePathPatterns?: string[];
|
|
46
|
+
/** List order; lower renders first when several match. */
|
|
47
|
+
order?: number;
|
|
48
|
+
/**
|
|
49
|
+
* Aggregate engagement counters (AGL-271), incremented server-side by
|
|
50
|
+
* the tenant beacon — never written from the console.
|
|
51
|
+
*/
|
|
52
|
+
stats?: {
|
|
53
|
+
impressions?: number;
|
|
54
|
+
clicks?: number;
|
|
55
|
+
dismissals?: number;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** Exact-or-prefix-glob path match: `/blog/*` matches `/blog/anything`. */
|
|
59
|
+
export declare function overlayPatternMatches(pattern: string, path: string): boolean;
|
|
60
|
+
/** True when the overlay targets the given path (include/exclude rules). */
|
|
61
|
+
export declare function overlayMatchesPath(overlay: Pick<HostOverlay, 'pathPatterns' | 'excludePathPatterns'>, path: string): boolean;
|
|
62
|
+
/** True when `nowMs` falls inside the overlay's schedule window. */
|
|
63
|
+
export declare function overlayActiveAt(overlay: Pick<HostOverlay, 'startAtMs' | 'endAtMs'>, nowMs: number): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Picks the overlays to render for a page: enabled, inside their window,
|
|
66
|
+
* targeting the path — sorted by `order` then name, first of each kind
|
|
67
|
+
* wins (one bar + one popup per page render).
|
|
68
|
+
*/
|
|
69
|
+
export declare function resolveActiveOverlays(overlays: Array<HostOverlay & {
|
|
70
|
+
$id?: string;
|
|
71
|
+
}>, context: {
|
|
72
|
+
path: string;
|
|
73
|
+
nowMs?: number;
|
|
74
|
+
}): {
|
|
75
|
+
bar: (HostOverlay & {
|
|
76
|
+
$id?: string;
|
|
77
|
+
}) | null;
|
|
78
|
+
popup: (HostOverlay & {
|
|
79
|
+
$id?: string;
|
|
80
|
+
}) | null;
|
|
81
|
+
};
|
|
82
|
+
/** What a popup's frequency cap reads and writes. */
|
|
83
|
+
export type PopupCapStore = 'session' | 'local';
|
|
84
|
+
/**
|
|
85
|
+
* Which store a popup's frequency cap uses (AGL-2174).
|
|
86
|
+
*
|
|
87
|
+
* `sessionStorage` for a per-session cap, because that IS the lifetime the
|
|
88
|
+
* setting names: it clears when the tab closes. `frequencyDays: 1` cannot
|
|
89
|
+
* express it — a visitor who dismissed at 23:59 would be suppressed for
|
|
90
|
+
* one minute and then shown the popup again.
|
|
91
|
+
*
|
|
92
|
+
* The read and the write must agree. Stamping `localStorage` for a
|
|
93
|
+
* session-capped popup leaves a timestamp nothing reads, which outlives
|
|
94
|
+
* the cap it was meant to record.
|
|
95
|
+
*/
|
|
96
|
+
export declare function popupCapStore(popup: {
|
|
97
|
+
oncePerSession?: boolean;
|
|
98
|
+
}): PopupCapStore;
|
|
99
|
+
/**
|
|
100
|
+
* Whether a popup is currently suppressed by its own frequency cap.
|
|
101
|
+
*
|
|
102
|
+
* `sessionFlag` is whatever `sessionStorage` holds for it; `lastShownMs`
|
|
103
|
+
* is the `localStorage` timestamp. Exactly one is consulted — the two caps
|
|
104
|
+
* are alternative answers to the same question, and honouring both would
|
|
105
|
+
* suppress a popup for the session AND for a week afterwards.
|
|
106
|
+
*/
|
|
107
|
+
export declare function popupSuppressed(popup: {
|
|
108
|
+
oncePerSession?: boolean;
|
|
109
|
+
frequencyDays?: number;
|
|
110
|
+
}, state: {
|
|
111
|
+
sessionFlag?: string | null;
|
|
112
|
+
lastShownMs?: number | null;
|
|
113
|
+
}, nowMs: number): boolean;
|
|
@@ -0,0 +1,93 @@
|
|
|
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
|
+
*/ /** Exact-or-prefix-glob path match: `/blog/*` matches `/blog/anything`. */ export function overlayPatternMatches(pattern, path) {
|
|
17
|
+
const cleaned = pattern.trim();
|
|
18
|
+
if (!cleaned) return false;
|
|
19
|
+
const normalizedPath = path === '' ? '/' : path;
|
|
20
|
+
if (cleaned.endsWith('/*')) {
|
|
21
|
+
const prefix = cleaned.slice(0, -2);
|
|
22
|
+
if (!prefix || prefix === '/') return normalizedPath.startsWith('/');
|
|
23
|
+
return normalizedPath === prefix || normalizedPath.startsWith(`${prefix}/`);
|
|
24
|
+
}
|
|
25
|
+
return normalizedPath === cleaned;
|
|
26
|
+
}
|
|
27
|
+
/** True when the overlay targets the given path (include/exclude rules). */ export function overlayMatchesPath(overlay, path) {
|
|
28
|
+
var _overlay_excludePathPatterns, _overlay_pathPatterns;
|
|
29
|
+
const excludes = (_overlay_excludePathPatterns = overlay.excludePathPatterns) != null ? _overlay_excludePathPatterns : [];
|
|
30
|
+
if (excludes.some((pattern)=>overlayPatternMatches(pattern, path))) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
const includes = ((_overlay_pathPatterns = overlay.pathPatterns) != null ? _overlay_pathPatterns : []).filter((pattern)=>pattern.trim());
|
|
34
|
+
if (!includes.length) return true;
|
|
35
|
+
return includes.some((pattern)=>overlayPatternMatches(pattern, path));
|
|
36
|
+
}
|
|
37
|
+
/** True when `nowMs` falls inside the overlay's schedule window. */ export function overlayActiveAt(overlay, nowMs) {
|
|
38
|
+
if (overlay.startAtMs && nowMs < overlay.startAtMs) return false;
|
|
39
|
+
if (overlay.endAtMs && nowMs > overlay.endAtMs) return false;
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Picks the overlays to render for a page: enabled, inside their window,
|
|
44
|
+
* targeting the path — sorted by `order` then name, first of each kind
|
|
45
|
+
* wins (one bar + one popup per page render).
|
|
46
|
+
*/ export function resolveActiveOverlays(overlays, context) {
|
|
47
|
+
var _context_nowMs, _active_find, _active_find1;
|
|
48
|
+
const nowMs = (_context_nowMs = context.nowMs) != null ? _context_nowMs : Date.now();
|
|
49
|
+
const active = overlays.filter((overlay)=>overlay.enabled !== false && overlayActiveAt(overlay, nowMs) && overlayMatchesPath(overlay, context.path)).sort((a, b)=>{
|
|
50
|
+
var _a_order, _b_order, _a_name, _b_name;
|
|
51
|
+
return ((_a_order = a.order) != null ? _a_order : 0) - ((_b_order = b.order) != null ? _b_order : 0) || String((_a_name = a.name) != null ? _a_name : '').localeCompare(String((_b_name = b.name) != null ? _b_name : ''));
|
|
52
|
+
});
|
|
53
|
+
return {
|
|
54
|
+
bar: (_active_find = active.find((overlay)=>overlay.kind === 'bar' && overlay.bar)) != null ? _active_find : null,
|
|
55
|
+
popup: (_active_find1 = active.find((overlay)=>overlay.kind === 'popup' && overlay.popup)) != null ? _active_find1 : null
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Which store a popup's frequency cap uses (AGL-2174).
|
|
60
|
+
*
|
|
61
|
+
* `sessionStorage` for a per-session cap, because that IS the lifetime the
|
|
62
|
+
* setting names: it clears when the tab closes. `frequencyDays: 1` cannot
|
|
63
|
+
* express it — a visitor who dismissed at 23:59 would be suppressed for
|
|
64
|
+
* one minute and then shown the popup again.
|
|
65
|
+
*
|
|
66
|
+
* The read and the write must agree. Stamping `localStorage` for a
|
|
67
|
+
* session-capped popup leaves a timestamp nothing reads, which outlives
|
|
68
|
+
* the cap it was meant to record.
|
|
69
|
+
*/ export function popupCapStore(popup) {
|
|
70
|
+
return (popup == null ? void 0 : popup.oncePerSession) ? 'session' : 'local';
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Whether a popup is currently suppressed by its own frequency cap.
|
|
74
|
+
*
|
|
75
|
+
* `sessionFlag` is whatever `sessionStorage` holds for it; `lastShownMs`
|
|
76
|
+
* is the `localStorage` timestamp. Exactly one is consulted — the two caps
|
|
77
|
+
* are alternative answers to the same question, and honouring both would
|
|
78
|
+
* suppress a popup for the session AND for a week afterwards.
|
|
79
|
+
*/ export function popupSuppressed(popup, state, nowMs) {
|
|
80
|
+
var _state_lastShownMs, _ref;
|
|
81
|
+
if (popup == null ? void 0 : popup.oncePerSession) return Boolean(state.sessionFlag);
|
|
82
|
+
const stamp = Number((_state_lastShownMs = state.lastShownMs) != null ? _state_lastShownMs : 0);
|
|
83
|
+
if (!stamp) return false;
|
|
84
|
+
// A missing or zero `frequencyDays` means "no day cap configured", and
|
|
85
|
+
// the comparison already says so: `now - stamp` is positive for any
|
|
86
|
+
// real stamp, so it is never below zero. Stated rather than guarded —
|
|
87
|
+
// an `if (!days) return false` above this line reads like a guard while
|
|
88
|
+
// being unreachable, which is the kind of line a later reader trusts.
|
|
89
|
+
const days = Number((_ref = popup == null ? void 0 : popup.frequencyDays) != null ? _ref : 0);
|
|
90
|
+
return nowMs - stamp < days * 86400000;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
//# sourceMappingURL=overlays.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/model/overlays.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type {\n HostAnnouncementBar,\n HostPopup,\n} from '@aglyn/aglyn'\n\n/**\n * Marketing hub overlays (AGL-251): multiple announcement bars and\n * promotional popups per host at `hosts/{hostId}/overlays/{overlayId}`,\n * each with its own schedule window and page targeting. The legacy single\n * `host.announcementBar` / `host.popup` fields keep working as fallbacks\n * when no overlay doc matches. Pure types + resolution helpers here; the\n * tenant render picks the first active match per kind.\n */\nexport interface HostOverlay {\n kind: 'bar' | 'popup'\n /** Display name in the console list (e.g. \"Black Friday bar\"). */\n name?: string\n /** Disabled overlays never render; new overlays default enabled. */\n enabled?: boolean\n /** Bar payload when `kind: 'bar'` (the legacy shape, minus enabled). */\n bar?: Omit<HostAnnouncementBar, 'enabled'>\n /** Popup payload when `kind: 'popup'` (the legacy shape, minus enabled). */\n popup?: Omit<HostPopup, 'enabled' | 'startAtMs' | 'endAtMs'>\n /** Showing window (epoch millis; simple to serialize). */\n startAtMs?: number\n endAtMs?: number\n /**\n * Path patterns the overlay shows on; empty/absent = every page.\n * Patterns are exact paths or prefix globs (`/pricing`, `/blog/*`).\n */\n pathPatterns?: string[]\n /** Path patterns the overlay never shows on; wins over includes. */\n excludePathPatterns?: string[]\n /** List order; lower renders first when several match. */\n order?: number\n /**\n * Aggregate engagement counters (AGL-271), incremented server-side by\n * the tenant beacon — never written from the console.\n */\n stats?: {\n impressions?: number\n clicks?: number\n dismissals?: number\n }\n}\n\n/** Exact-or-prefix-glob path match: `/blog/*` matches `/blog/anything`. */\nexport function overlayPatternMatches(pattern: string, path: string): boolean {\n const cleaned = pattern.trim()\n if (!cleaned) return false\n const normalizedPath = path === '' ? '/' : path\n if (cleaned.endsWith('/*')) {\n const prefix = cleaned.slice(0, -2)\n if (!prefix || prefix === '/') return normalizedPath.startsWith('/')\n return (\n normalizedPath === prefix || normalizedPath.startsWith(`${prefix}/`)\n )\n }\n return normalizedPath === cleaned\n}\n\n/** True when the overlay targets the given path (include/exclude rules). */\nexport function overlayMatchesPath(\n overlay: Pick<HostOverlay, 'pathPatterns' | 'excludePathPatterns'>,\n path: string,\n): boolean {\n const excludes = overlay.excludePathPatterns ?? []\n if (excludes.some((pattern) => overlayPatternMatches(pattern, path))) {\n return false\n }\n const includes = (overlay.pathPatterns ?? []).filter((pattern) =>\n pattern.trim(),\n )\n if (!includes.length) return true\n return includes.some((pattern) => overlayPatternMatches(pattern, path))\n}\n\n/** True when `nowMs` falls inside the overlay's schedule window. */\nexport function overlayActiveAt(\n overlay: Pick<HostOverlay, 'startAtMs' | 'endAtMs'>,\n nowMs: number,\n): boolean {\n if (overlay.startAtMs && nowMs < overlay.startAtMs) return false\n if (overlay.endAtMs && nowMs > overlay.endAtMs) return false\n return true\n}\n\n/**\n * Picks the overlays to render for a page: enabled, inside their window,\n * targeting the path — sorted by `order` then name, first of each kind\n * wins (one bar + one popup per page render).\n */\nexport function resolveActiveOverlays(\n overlays: Array<HostOverlay & { $id?: string }>,\n context: { path: string; nowMs?: number },\n): {\n bar: (HostOverlay & { $id?: string }) | null\n popup: (HostOverlay & { $id?: string }) | null\n} {\n const nowMs = context.nowMs ?? Date.now()\n const active = overlays\n .filter(\n (overlay) =>\n overlay.enabled !== false &&\n overlayActiveAt(overlay, nowMs) &&\n overlayMatchesPath(overlay, context.path),\n )\n .sort(\n (a, b) =>\n (a.order ?? 0) - (b.order ?? 0) ||\n String(a.name ?? '').localeCompare(String(b.name ?? '')),\n )\n return {\n bar: active.find((overlay) => overlay.kind === 'bar' && overlay.bar) ?? null,\n popup:\n active.find((overlay) => overlay.kind === 'popup' && overlay.popup) ??\n null,\n }\n}\n\n/** What a popup's frequency cap reads and writes. */\nexport type PopupCapStore = 'session' | 'local'\n\n/**\n * Which store a popup's frequency cap uses (AGL-2174).\n *\n * `sessionStorage` for a per-session cap, because that IS the lifetime the\n * setting names: it clears when the tab closes. `frequencyDays: 1` cannot\n * express it — a visitor who dismissed at 23:59 would be suppressed for\n * one minute and then shown the popup again.\n *\n * The read and the write must agree. Stamping `localStorage` for a\n * session-capped popup leaves a timestamp nothing reads, which outlives\n * the cap it was meant to record.\n */\nexport function popupCapStore(popup: {\n oncePerSession?: boolean\n}): PopupCapStore {\n return popup?.oncePerSession ? 'session' : 'local'\n}\n\n/**\n * Whether a popup is currently suppressed by its own frequency cap.\n *\n * `sessionFlag` is whatever `sessionStorage` holds for it; `lastShownMs`\n * is the `localStorage` timestamp. Exactly one is consulted — the two caps\n * are alternative answers to the same question, and honouring both would\n * suppress a popup for the session AND for a week afterwards.\n */\nexport function popupSuppressed(\n popup: { oncePerSession?: boolean; frequencyDays?: number },\n state: { sessionFlag?: string | null; lastShownMs?: number | null },\n nowMs: number,\n): boolean {\n if (popup?.oncePerSession) return Boolean(state.sessionFlag)\n const stamp = Number(state.lastShownMs ?? 0)\n if (!stamp) return false\n // A missing or zero `frequencyDays` means \"no day cap configured\", and\n // the comparison already says so: `now - stamp` is positive for any\n // real stamp, so it is never below zero. Stated rather than guarded —\n // an `if (!days) return false` above this line reads like a guard while\n // being unreachable, which is the kind of line a later reader trusts.\n const days = Number(popup?.frequencyDays ?? 0)\n return nowMs - stamp < days * 86_400_000\n}\n"],"names":["overlayPatternMatches","pattern","path","cleaned","trim","normalizedPath","endsWith","prefix","slice","startsWith","overlayMatchesPath","overlay","excludes","excludePathPatterns","some","includes","pathPatterns","filter","length","overlayActiveAt","nowMs","startAtMs","endAtMs","resolveActiveOverlays","overlays","context","active","Date","now","enabled","sort","a","b","order","String","name","localeCompare","bar","find","kind","popup","popupCapStore","oncePerSession","popupSuppressed","state","Boolean","sessionFlag","stamp","Number","lastShownMs","days","frequencyDays"],"mappings":"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"}
|
|
@@ -0,0 +1,100 @@
|
|
|
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
|
+
/**
|
|
18
|
+
* The marketing plugin's site wire contract (AGL-419): shapes its server
|
|
19
|
+
* page-enricher writes into the page props and its client site-runtime
|
|
20
|
+
* reads back. Pure types — shared by the /server graph and the client
|
|
21
|
+
* runtime without pulling either's dependencies.
|
|
22
|
+
*/
|
|
23
|
+
import type { HostActionStep, SiteEventType } from '@aglyn/aglyn';
|
|
24
|
+
import type { ExperimentStatus } from './experiments';
|
|
25
|
+
export interface ClientAutomation {
|
|
26
|
+
id: string;
|
|
27
|
+
event: SiteEventType;
|
|
28
|
+
selector?: string;
|
|
29
|
+
threshold?: number;
|
|
30
|
+
/** Fire at most once per visitor (AGL-266). */
|
|
31
|
+
oncePerVisitor?: boolean;
|
|
32
|
+
/** Fire at most once per browser session (AGL-274). */
|
|
33
|
+
oncePerSession?: boolean;
|
|
34
|
+
/** Minimum minutes between fires for the same visitor (AGL-274). */
|
|
35
|
+
cooldownMinutes?: number;
|
|
36
|
+
/** Fire on every occurrence, not once per pageview (AGL-562). */
|
|
37
|
+
everyTime?: boolean;
|
|
38
|
+
steps: HostActionStep[];
|
|
39
|
+
hasServerSteps: boolean;
|
|
40
|
+
}
|
|
41
|
+
export interface ScreenExperiment {
|
|
42
|
+
id: string;
|
|
43
|
+
target: 'screen' | 'section';
|
|
44
|
+
status: ExperimentStatus;
|
|
45
|
+
nodeId?: string;
|
|
46
|
+
winnerVariantId?: string;
|
|
47
|
+
/** Past this the client runner serves the default (AGL-273). */
|
|
48
|
+
endAtMs?: number;
|
|
49
|
+
goal?: {
|
|
50
|
+
event: string;
|
|
51
|
+
filter?: string;
|
|
52
|
+
};
|
|
53
|
+
variants: Array<{
|
|
54
|
+
id: string;
|
|
55
|
+
weight?: number;
|
|
56
|
+
versionId?: string;
|
|
57
|
+
}>;
|
|
58
|
+
payloads: Record<string, Record<string, any> | null>;
|
|
59
|
+
}
|
|
60
|
+
export interface AnnouncementBarData {
|
|
61
|
+
text: string;
|
|
62
|
+
href?: string;
|
|
63
|
+
backgroundColor?: string;
|
|
64
|
+
textColor?: string;
|
|
65
|
+
dismissible: boolean;
|
|
66
|
+
contentHash: string;
|
|
67
|
+
overlayId?: string;
|
|
68
|
+
}
|
|
69
|
+
export interface PopupData {
|
|
70
|
+
headline?: string;
|
|
71
|
+
body: string;
|
|
72
|
+
imageUrl?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Alt text for {@link imageUrl} (AGL-1896).
|
|
75
|
+
*
|
|
76
|
+
* The popup image used to render with a hardcoded `alt=""` and there was
|
|
77
|
+
* no field anywhere that could have changed it — an author-chosen banner,
|
|
78
|
+
* on a paying customer's published site, invisible to a screen reader by
|
|
79
|
+
* construction. Defaults from the DAM asset's own alt when the author
|
|
80
|
+
* picks one through "Browse media"; an empty value still renders `alt=""`,
|
|
81
|
+
* which is the right markup for a genuinely decorative banner.
|
|
82
|
+
*/
|
|
83
|
+
imageAlt?: string;
|
|
84
|
+
ctaLabel?: string;
|
|
85
|
+
ctaHref?: string;
|
|
86
|
+
trigger: string;
|
|
87
|
+
triggerValue: number;
|
|
88
|
+
frequencyDays: number;
|
|
89
|
+
/**
|
|
90
|
+
* Cap the popup to one showing per browser session (AGL-2174). When
|
|
91
|
+
* set, `frequencyDays` is not consulted — the two are alternative
|
|
92
|
+
* answers to the same question.
|
|
93
|
+
*/
|
|
94
|
+
oncePerSession?: boolean;
|
|
95
|
+
collectEmail?: boolean;
|
|
96
|
+
startAtMs?: number;
|
|
97
|
+
endAtMs?: number;
|
|
98
|
+
contentHash: string;
|
|
99
|
+
overlayId?: string;
|
|
100
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
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
|
+
* The marketing plugin's site wire contract (AGL-419): shapes its server
|
|
18
|
+
* page-enricher writes into the page props and its client site-runtime
|
|
19
|
+
* reads back. Pure types — shared by the /server graph and the client
|
|
20
|
+
* runtime without pulling either's dependencies.
|
|
21
|
+
*/ export { };
|
|
22
|
+
|
|
23
|
+
//# sourceMappingURL=site-contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/model/site-contract.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The marketing plugin's site wire contract (AGL-419): shapes its server\n * page-enricher writes into the page props and its client site-runtime\n * reads back. Pure types — shared by the /server graph and the client\n * runtime without pulling either's dependencies.\n */\n\nimport type { HostActionStep, SiteEventType } from '@aglyn/aglyn'\nimport type { ExperimentStatus } from './experiments'\n\nexport interface ClientAutomation {\n id: string\n event: SiteEventType\n selector?: string\n threshold?: number\n /** Fire at most once per visitor (AGL-266). */\n oncePerVisitor?: boolean\n /** Fire at most once per browser session (AGL-274). */\n oncePerSession?: boolean\n /** Minimum minutes between fires for the same visitor (AGL-274). */\n cooldownMinutes?: number\n /** Fire on every occurrence, not once per pageview (AGL-562). */\n everyTime?: boolean\n steps: HostActionStep[]\n hasServerSteps: boolean\n}\n\nexport interface ScreenExperiment {\n id: string\n target: 'screen' | 'section'\n status: ExperimentStatus\n nodeId?: string\n winnerVariantId?: string\n /** Past this the client runner serves the default (AGL-273). */\n endAtMs?: number\n goal?: { event: string; filter?: string }\n variants: Array<{ id: string; weight?: number; versionId?: string }>\n payloads: Record<string, Record<string, any> | null>\n}\n\nexport interface AnnouncementBarData {\n text: string\n href?: string\n backgroundColor?: string\n textColor?: string\n dismissible: boolean\n contentHash: string\n overlayId?: string\n}\n\nexport interface PopupData {\n headline?: string\n body: string\n imageUrl?: string\n /**\n * Alt text for {@link imageUrl} (AGL-1896).\n *\n * The popup image used to render with a hardcoded `alt=\"\"` and there was\n * no field anywhere that could have changed it — an author-chosen banner,\n * on a paying customer's published site, invisible to a screen reader by\n * construction. Defaults from the DAM asset's own alt when the author\n * picks one through \"Browse media\"; an empty value still renders `alt=\"\"`,\n * which is the right markup for a genuinely decorative banner.\n */\n imageAlt?: string\n ctaLabel?: string\n ctaHref?: string\n trigger: string\n triggerValue: number\n frequencyDays: number\n /**\n * Cap the popup to one showing per browser session (AGL-2174). When\n * set, `frequencyDays` is not consulted — the two are alternative\n * answers to the same question.\n */\n oncePerSession?: boolean\n collectEmail?: boolean\n startAtMs?: number\n endAtMs?: number\n contentHash: string\n overlayId?: string\n}\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;CAKC,GA6CD,WA+BC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
/**
|
|
18
|
+
* Marketing feature plugin (AGL-395). Console-only — overlays and popups
|
|
19
|
+
* render on published sites through the tenant runtime, not a canvas
|
|
20
|
+
* element of their own, so there is no UI bundle. The console half declares
|
|
21
|
+
* the Marketing nav + page through the ConsoleExtension registry (always-on;
|
|
22
|
+
* the surface itself is not release-flagged — its overlays/A-B cards run
|
|
23
|
+
* their own per-plan checks off the passed `org`). The popup image picker
|
|
24
|
+
* uses the shell's media browser via `useMediaPicker`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function registerMarketingConsole(): void;
|
|
27
|
+
export * from './site';
|
|
@@ -0,0 +1,168 @@
|
|
|
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
|
+
*/ import * as Aglyn from "@aglyn/aglyn";
|
|
17
|
+
import { registerPluginZone } from "@aglyn/aglyn/plugin-manager/plugin-zones";
|
|
18
|
+
import { mdiBullhornOutline } from "@aglyn/shared-data-mdi";
|
|
19
|
+
import { lazy } from "react";
|
|
20
|
+
import { CAMPAIGN_DESIGN_CREATE_ZONE, CAMPAIGN_DESIGN_PREVIEW_ZONE, CAMPAIGN_SENDER_EDITOR_ZONE, CAMPAIGN_TOPIC_OPTIONS_ZONE, CAMPAIGN_TOPIC_SELECT_ZONE } from "./components/campaign-email-zones.js";
|
|
21
|
+
import { EXPERIMENT_RESULT_ZONE, EXPERIMENT_VARIANTS_ZONE } from "./components/experiment-zones.js";
|
|
22
|
+
import { MARKETING_CONSOLE_SECTIONS } from "./components/marketing-console-sections.js";
|
|
23
|
+
import { BUNDLE_ID } from "./constants/bundle-common.js";
|
|
24
|
+
import { registerMarketingRecordRoutes } from "./model/marketing-record-routes.js";
|
|
25
|
+
/** Code-split: the Marketing console page only loads when opened. */ const MarketingConsolePage = lazy(()=>import("./components/marketing-console-page.js"));
|
|
26
|
+
/** Dashboard glance card, loaded only where the shell renders the slot. */ const CampaignGlanceCard = lazy(()=>import("./components/campaign-glance-card.component.js"));
|
|
27
|
+
/** The site's campaigns, drawn in the Inbox's Campaigns section. */ const HostCampaignsCard = lazy(()=>import("./components/campaigns-card.js"));
|
|
28
|
+
/** The Emails page's Messages section: the list, one message, its composer. */ const EmailMessagesWidget = lazy(()=>import("./components/email-messages-widget.js"));
|
|
29
|
+
/** Who received the sends built from one template, on the template's page. */ const EmailRecipientsCard = lazy(()=>import("./components/email-recipients-card.js"));
|
|
30
|
+
/** Loaded where a record page draws one of the attribution zones. */ const RecordAttributionWidget = lazy(()=>import("./components/record-attribution-widget.js"));
|
|
31
|
+
/**
|
|
32
|
+
* Marketing feature plugin (AGL-395). Console-only — overlays and popups
|
|
33
|
+
* render on published sites through the tenant runtime, not a canvas
|
|
34
|
+
* element of their own, so there is no UI bundle. The console half declares
|
|
35
|
+
* the Marketing nav + page through the ConsoleExtension registry (always-on;
|
|
36
|
+
* the surface itself is not release-flagged — its overlays/A-B cards run
|
|
37
|
+
* their own per-plan checks off the passed `org`). The popup image picker
|
|
38
|
+
* uses the shell's media browser via `useMediaPicker`.
|
|
39
|
+
*/ export function registerMarketingConsole() {
|
|
40
|
+
/*
|
|
41
|
+
* The two positions the A/B testing card hosts (AGL-2914), declared before
|
|
42
|
+
* the extension that draws the card. An id another plugin has already taken
|
|
43
|
+
* is refused, naming both. What each zone hands a widget is carried on its
|
|
44
|
+
* token, in `components/experiment-zones`, so a widget elsewhere is written
|
|
45
|
+
* against the same shape without importing this plugin.
|
|
46
|
+
*/ registerPluginZone({
|
|
47
|
+
zone: EXPERIMENT_VARIANTS_ZONE,
|
|
48
|
+
label: 'A/B test variants',
|
|
49
|
+
surface: 'console',
|
|
50
|
+
description: 'In the experiment editor, beside the variants. A widget here proposes copy for the variants the editor holds; the dialog’s Save is the write, and nothing a widget returns starts or changes a test.'
|
|
51
|
+
}, // Named rather than left to the loader's marker, because this function is
|
|
52
|
+
// also called directly — by a spec, and by an app that loads the plugin
|
|
53
|
+
// without the loader — and a zone with no owner is refused.
|
|
54
|
+
{
|
|
55
|
+
pluginId: BUNDLE_ID
|
|
56
|
+
});
|
|
57
|
+
registerPluginZone({
|
|
58
|
+
zone: EXPERIMENT_RESULT_ZONE,
|
|
59
|
+
label: 'A/B test result',
|
|
60
|
+
surface: 'console',
|
|
61
|
+
description: 'Below the results of one test. A widget here explains what the figures show. There is nothing to apply, and a widget may not declare a winner the card has not.'
|
|
62
|
+
}, {
|
|
63
|
+
pluginId: BUNDLE_ID
|
|
64
|
+
});
|
|
65
|
+
/*
|
|
66
|
+
* What a campaign email asks of whichever plugin keeps the mail itself —
|
|
67
|
+
* the topic catalog, the sending identities, the design document and its
|
|
68
|
+
* renderer. Each is a zone the composer or a message's page hosts; see
|
|
69
|
+
* `components/campaign-email-zones` for what each hands a widget. All are
|
|
70
|
+
* `bare`: a widget there is a field, a button, a drawer or nothing at all,
|
|
71
|
+
* and the stack's spacing around it would be a gap in a form.
|
|
72
|
+
*/ const bareZone = (zone, label, description)=>registerPluginZone({
|
|
73
|
+
zone,
|
|
74
|
+
label,
|
|
75
|
+
description,
|
|
76
|
+
surface: 'console',
|
|
77
|
+
layout: 'bare'
|
|
78
|
+
}, {
|
|
79
|
+
pluginId: BUNDLE_ID
|
|
80
|
+
});
|
|
81
|
+
bareZone(CAMPAIGN_TOPIC_SELECT_ZONE, 'An email’s topic', 'In the campaign composer’s field stack. A widget here is the picker for the stream the email belongs to: it is handed the chosen id and reports a new one through `onChange`. The composer records it; the widget writes nothing.');
|
|
82
|
+
bareZone(CAMPAIGN_TOPIC_OPTIONS_ZONE, 'The topics a campaign can open on', 'Beside the drawers that create and edit a campaign. A widget here draws nothing: while `enabled` it reads the streams a recipient can still leave and answers through `onTopics`.');
|
|
83
|
+
bareZone(CAMPAIGN_SENDER_EDITOR_ZONE, 'Add a sender while composing', 'Mounted by the campaign composer once its author asks to add a sender. A widget here is that editor, open in its add mode; `onSaved` names the sender it created.');
|
|
84
|
+
bareZone(CAMPAIGN_DESIGN_CREATE_ZONE, 'A design for one email', 'In the campaign composer, beside the design picker. A widget here is the control that mints a design document and names it through `onCreated`; the composer records the choice on the campaign and opens the editor.');
|
|
85
|
+
bareZone(CAMPAIGN_DESIGN_PREVIEW_ZONE, 'A sent email as an inbox receives it', 'On one message’s page. A widget here renders the stored design, or the plain-text body, through the renderer the send path uses.');
|
|
86
|
+
registerMarketingRecordRoutes();
|
|
87
|
+
Aglyn.registerConsoleExtension({
|
|
88
|
+
pluginId: BUNDLE_ID,
|
|
89
|
+
displayName: 'Marketing',
|
|
90
|
+
/*
|
|
91
|
+
* The host dashboard's `Last campaign` card (AGL-433). A widget rather
|
|
92
|
+
* than a direct import by the dashboard page, so it appears only on
|
|
93
|
+
* workspaces that have the plugin that owns the campaign history — a card
|
|
94
|
+
* about campaigns on a console with no campaigns page could only ever be
|
|
95
|
+
* blank, and its header links straight into `/marketing/campaigns`.
|
|
96
|
+
*/ widgets: [
|
|
97
|
+
{
|
|
98
|
+
slot: Aglyn.CONSOLE_WIDGET_SLOTS.hostDashboard,
|
|
99
|
+
// The id a reader's dashboard preferences already name — it is what
|
|
100
|
+
// `isDashboardWidgetHidden` and the customize order are keyed on, so
|
|
101
|
+
// it is persisted and does not follow the plugin.
|
|
102
|
+
widgetId: 'email-campaign-glance',
|
|
103
|
+
title: 'Last campaign',
|
|
104
|
+
Component: CampaignGlanceCard
|
|
105
|
+
},
|
|
106
|
+
// Which campaign or link brought a person, drawn where the plugins that
|
|
107
|
+
// keep and list people host a zone for it. They hand over what the
|
|
108
|
+
// record is and its id; neither imports this plugin.
|
|
109
|
+
{
|
|
110
|
+
slot: 'crmRecordAttribution',
|
|
111
|
+
widgetId: 'marketing-crm-record-attribution',
|
|
112
|
+
title: 'Campaign attribution',
|
|
113
|
+
Component: RecordAttributionWidget
|
|
114
|
+
},
|
|
115
|
+
// The Emails page hosts its Messages section as a zone: a message is
|
|
116
|
+
// one send of a campaign, and every action on it is this plugin's route.
|
|
117
|
+
{
|
|
118
|
+
slot: 'emailMessages',
|
|
119
|
+
widgetId: 'marketing-email-messages',
|
|
120
|
+
title: 'Messages',
|
|
121
|
+
Component: EmailMessagesWidget
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
slot: 'emailTemplateRecipients',
|
|
125
|
+
widgetId: 'marketing-email-template-recipients',
|
|
126
|
+
title: 'Recipients',
|
|
127
|
+
Component: EmailRecipientsCard
|
|
128
|
+
},
|
|
129
|
+
// The Inbox's Campaigns tab is a zone; this is the card that fills it.
|
|
130
|
+
{
|
|
131
|
+
slot: 'inboxCampaigns',
|
|
132
|
+
widgetId: 'marketing-inbox-campaigns',
|
|
133
|
+
title: 'Campaigns',
|
|
134
|
+
Component: HostCampaignsCard
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
slot: 'inboxRecordAttribution',
|
|
138
|
+
widgetId: 'marketing-inbox-record-attribution',
|
|
139
|
+
title: 'Campaign attribution',
|
|
140
|
+
Component: RecordAttributionWidget
|
|
141
|
+
}
|
|
142
|
+
],
|
|
143
|
+
navItems: [
|
|
144
|
+
{
|
|
145
|
+
label: 'Marketing',
|
|
146
|
+
href: '/marketing',
|
|
147
|
+
// Sections as ROUTES (AGL-2501): each is a real URL the shell
|
|
148
|
+
// resolves and gates, so the page mounts the one being read.
|
|
149
|
+
sections: MARKETING_CONSOLE_SECTIONS,
|
|
150
|
+
navTabId: 'nav-tab-marketing',
|
|
151
|
+
icon: {
|
|
152
|
+
path: mdiBullhornOutline.path
|
|
153
|
+
},
|
|
154
|
+
header: {
|
|
155
|
+
title: 'Marketing',
|
|
156
|
+
icon: {
|
|
157
|
+
path: mdiBullhornOutline.path
|
|
158
|
+
},
|
|
159
|
+
docsTopic: 'marketingOverlays'
|
|
160
|
+
},
|
|
161
|
+
Component: MarketingConsolePage
|
|
162
|
+
}
|
|
163
|
+
]
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
export * from "./site.js";
|
|
167
|
+
|
|
168
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/plugins/marketing/src/lib/plugin.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport {\n registerPluginZone,\n type PluginZone,\n} from '@aglyn/aglyn/plugin-manager/plugin-zones'\nimport { mdiBullhornOutline } from '@aglyn/shared-data-mdi'\nimport { lazy } from 'react'\nimport {\n CAMPAIGN_DESIGN_CREATE_ZONE,\n CAMPAIGN_DESIGN_PREVIEW_ZONE,\n CAMPAIGN_SENDER_EDITOR_ZONE,\n CAMPAIGN_TOPIC_OPTIONS_ZONE,\n CAMPAIGN_TOPIC_SELECT_ZONE,\n} from './components/campaign-email-zones'\nimport {\n EXPERIMENT_RESULT_ZONE,\n EXPERIMENT_VARIANTS_ZONE,\n} from './components/experiment-zones'\nimport { MARKETING_CONSOLE_SECTIONS } from './components/marketing-console-sections'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { registerMarketingRecordRoutes } from './model/marketing-record-routes'\n\n/** Code-split: the Marketing console page only loads when opened. */\nconst MarketingConsolePage = lazy(\n () => import('./components/marketing-console-page'),\n)\n\n/** Dashboard glance card, loaded only where the shell renders the slot. */\nconst CampaignGlanceCard = lazy(\n () => import('./components/campaign-glance-card.component'),\n)\n\n/** The site's campaigns, drawn in the Inbox's Campaigns section. */\nconst HostCampaignsCard = lazy(() => import('./components/campaigns-card'))\n\n/** The Emails page's Messages section: the list, one message, its composer. */\nconst EmailMessagesWidget = lazy(\n () => import('./components/email-messages-widget'),\n)\n\n/** Who received the sends built from one template, on the template's page. */\nconst EmailRecipientsCard = lazy(\n () => import('./components/email-recipients-card'),\n)\n\n/** Loaded where a record page draws one of the attribution zones. */\nconst RecordAttributionWidget = lazy(\n () => import('./components/record-attribution-widget'),\n)\n\n/**\n * Marketing feature plugin (AGL-395). Console-only — overlays and popups\n * render on published sites through the tenant runtime, not a canvas\n * element of their own, so there is no UI bundle. The console half declares\n * the Marketing nav + page through the ConsoleExtension registry (always-on;\n * the surface itself is not release-flagged — its overlays/A-B cards run\n * their own per-plan checks off the passed `org`). The popup image picker\n * uses the shell's media browser via `useMediaPicker`.\n */\nexport function registerMarketingConsole(): void {\n /*\n * The two positions the A/B testing card hosts (AGL-2914), declared before\n * the extension that draws the card. An id another plugin has already taken\n * is refused, naming both. What each zone hands a widget is carried on its\n * token, in `components/experiment-zones`, so a widget elsewhere is written\n * against the same shape without importing this plugin.\n */\n registerPluginZone(\n {\n zone: EXPERIMENT_VARIANTS_ZONE,\n label: 'A/B test variants',\n surface: 'console',\n description:\n 'In the experiment editor, beside the variants. A widget here proposes copy for the variants the editor holds; the dialog’s Save is the write, and nothing a widget returns starts or changes a test.',\n },\n // Named rather than left to the loader's marker, because this function is\n // also called directly — by a spec, and by an app that loads the plugin\n // without the loader — and a zone with no owner is refused.\n { pluginId: BUNDLE_ID },\n )\n registerPluginZone(\n {\n zone: EXPERIMENT_RESULT_ZONE,\n label: 'A/B test result',\n surface: 'console',\n description:\n 'Below the results of one test. A widget here explains what the figures show. There is nothing to apply, and a widget may not declare a winner the card has not.',\n },\n { pluginId: BUNDLE_ID },\n )\n /*\n * What a campaign email asks of whichever plugin keeps the mail itself —\n * the topic catalog, the sending identities, the design document and its\n * renderer. Each is a zone the composer or a message's page hosts; see\n * `components/campaign-email-zones` for what each hands a widget. All are\n * `bare`: a widget there is a field, a button, a drawer or nothing at all,\n * and the stack's spacing around it would be a gap in a form.\n */\n const bareZone = <Props,>(\n zone: PluginZone<Props>,\n label: string,\n description: string,\n ) =>\n registerPluginZone(\n { zone, label, description, surface: 'console', layout: 'bare' },\n { pluginId: BUNDLE_ID },\n )\n bareZone(\n CAMPAIGN_TOPIC_SELECT_ZONE,\n 'An email’s topic',\n 'In the campaign composer’s field stack. A widget here is the picker for the stream the email belongs to: it is handed the chosen id and reports a new one through `onChange`. The composer records it; the widget writes nothing.',\n )\n bareZone(\n CAMPAIGN_TOPIC_OPTIONS_ZONE,\n 'The topics a campaign can open on',\n 'Beside the drawers that create and edit a campaign. A widget here draws nothing: while `enabled` it reads the streams a recipient can still leave and answers through `onTopics`.',\n )\n bareZone(\n CAMPAIGN_SENDER_EDITOR_ZONE,\n 'Add a sender while composing',\n 'Mounted by the campaign composer once its author asks to add a sender. A widget here is that editor, open in its add mode; `onSaved` names the sender it created.',\n )\n bareZone(\n CAMPAIGN_DESIGN_CREATE_ZONE,\n 'A design for one email',\n 'In the campaign composer, beside the design picker. A widget here is the control that mints a design document and names it through `onCreated`; the composer records the choice on the campaign and opens the editor.',\n )\n bareZone(\n CAMPAIGN_DESIGN_PREVIEW_ZONE,\n 'A sent email as an inbox receives it',\n 'On one message’s page. A widget here renders the stored design, or the plain-text body, through the renderer the send path uses.',\n )\n registerMarketingRecordRoutes()\n Aglyn.registerConsoleExtension({\n pluginId: BUNDLE_ID,\n displayName: 'Marketing',\n /*\n * The host dashboard's `Last campaign` card (AGL-433). A widget rather\n * than a direct import by the dashboard page, so it appears only on\n * workspaces that have the plugin that owns the campaign history — a card\n * about campaigns on a console with no campaigns page could only ever be\n * blank, and its header links straight into `/marketing/campaigns`.\n */\n widgets: [\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.hostDashboard,\n // The id a reader's dashboard preferences already name — it is what\n // `isDashboardWidgetHidden` and the customize order are keyed on, so\n // it is persisted and does not follow the plugin.\n widgetId: 'email-campaign-glance',\n title: 'Last campaign',\n Component: CampaignGlanceCard,\n },\n // Which campaign or link brought a person, drawn where the plugins that\n // keep and list people host a zone for it. They hand over what the\n // record is and its id; neither imports this plugin.\n {\n slot: 'crmRecordAttribution',\n widgetId: 'marketing-crm-record-attribution',\n title: 'Campaign attribution',\n Component: RecordAttributionWidget,\n },\n // The Emails page hosts its Messages section as a zone: a message is\n // one send of a campaign, and every action on it is this plugin's route.\n {\n slot: 'emailMessages',\n widgetId: 'marketing-email-messages',\n title: 'Messages',\n Component: EmailMessagesWidget,\n },\n {\n slot: 'emailTemplateRecipients',\n widgetId: 'marketing-email-template-recipients',\n title: 'Recipients',\n Component: EmailRecipientsCard,\n },\n // The Inbox's Campaigns tab is a zone; this is the card that fills it.\n {\n slot: 'inboxCampaigns',\n widgetId: 'marketing-inbox-campaigns',\n title: 'Campaigns',\n Component: HostCampaignsCard,\n },\n {\n slot: 'inboxRecordAttribution',\n widgetId: 'marketing-inbox-record-attribution',\n title: 'Campaign attribution',\n Component: RecordAttributionWidget,\n },\n ],\n navItems: [\n {\n label: 'Marketing',\n href: '/marketing',\n // Sections as ROUTES (AGL-2501): each is a real URL the shell\n // resolves and gates, so the page mounts the one being read.\n sections: MARKETING_CONSOLE_SECTIONS,\n navTabId: 'nav-tab-marketing',\n icon: { path: mdiBullhornOutline.path },\n header: {\n title: 'Marketing',\n icon: { path: mdiBullhornOutline.path },\n docsTopic: 'marketingOverlays',\n },\n Component: MarketingConsolePage,\n },\n ],\n })\n}\n\nexport * from './site'\n"],"names":["Aglyn","registerPluginZone","mdiBullhornOutline","lazy","CAMPAIGN_DESIGN_CREATE_ZONE","CAMPAIGN_DESIGN_PREVIEW_ZONE","CAMPAIGN_SENDER_EDITOR_ZONE","CAMPAIGN_TOPIC_OPTIONS_ZONE","CAMPAIGN_TOPIC_SELECT_ZONE","EXPERIMENT_RESULT_ZONE","EXPERIMENT_VARIANTS_ZONE","MARKETING_CONSOLE_SECTIONS","BUNDLE_ID","registerMarketingRecordRoutes","MarketingConsolePage","CampaignGlanceCard","HostCampaignsCard","EmailMessagesWidget","EmailRecipientsCard","RecordAttributionWidget","registerMarketingConsole","zone","label","surface","description","pluginId","bareZone","layout","registerConsoleExtension","displayName","widgets","slot","CONSOLE_WIDGET_SLOTS","hostDashboard","widgetId","title","Component","navItems","href","sections","navTabId","icon","path","header","docsTopic"],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
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 type { SiteRuntimePreviewContext } from '@aglyn/aglyn';
|
|
18
|
+
/**
|
|
19
|
+
* The marketing site-runtime's client-side page-props for the besigner
|
|
20
|
+
* Preview (AGL-830). No server enricher runs there, so this reads the host's
|
|
21
|
+
* `actions` with the client SDK and compiles them the same way the enricher
|
|
22
|
+
* does — the Preview then mounts the SAME MarketingSiteRuntime the tenant
|
|
23
|
+
* uses, and hover-to-open menus/drawers behave exactly like the live site.
|
|
24
|
+
*
|
|
25
|
+
* Compiled with `actionsEntitled: false`, so only basic PRESENTATIONAL steps
|
|
26
|
+
* (menu/drawer/show-hide/class/nav) run and `hasServerSteps` stays false —
|
|
27
|
+
* preview never fires analytics, webhooks, or server events. `matchAllPaths`
|
|
28
|
+
* because the Preview renders one composed screen and scopes relevance by
|
|
29
|
+
* selector match, not page path.
|
|
30
|
+
*/
|
|
31
|
+
export declare function loadMarketingPreviewProps(ctx: SiteRuntimePreviewContext): Promise<Record<string, unknown>>;
|