@builder.io/sdk-svelte 3.0.3 → 3.0.6
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/lib/browser/blocks/columns/columns.svelte +3 -3
- package/lib/browser/blocks/form/form/form.svelte +2 -15
- package/lib/browser/blocks/image/image.svelte +1 -3
- package/lib/browser/blocks/personalization-container/component-info.d.ts +2 -0
- package/lib/browser/blocks/personalization-container/component-info.js +36 -0
- package/lib/browser/blocks/personalization-container/helpers/inlined-fns.d.ts +8 -0
- package/lib/browser/blocks/personalization-container/helpers/inlined-fns.js +87 -0
- package/lib/browser/blocks/personalization-container/helpers.d.ts +27 -0
- package/lib/browser/blocks/personalization-container/helpers.js +58 -0
- package/lib/browser/blocks/personalization-container/index.d.ts +1 -0
- package/lib/browser/blocks/personalization-container/index.js +1 -0
- package/lib/browser/blocks/personalization-container/personalization-container.svelte +118 -0
- package/lib/browser/blocks/personalization-container/personalization-container.svelte.d.ts +21 -0
- package/lib/browser/blocks/personalization-container/personalization-container.types.d.ts +14 -0
- package/lib/browser/blocks/personalization-container/personalization-container.types.js +1 -0
- package/lib/browser/blocks/video/video.svelte +0 -1
- package/lib/browser/components/blocks/blocks-wrapper.svelte +6 -1
- package/lib/browser/constants/builder-registered-components.js +6 -1
- package/lib/browser/constants/sdk-version.d.ts +1 -1
- package/lib/browser/constants/sdk-version.js +1 -1
- package/lib/browser/helpers/canTrack.js +6 -1
- package/lib/browser/helpers/user-attributes.d.ts +17 -0
- package/lib/browser/helpers/user-attributes.js +46 -0
- package/lib/browser/index-helpers/blocks-exports.d.ts +1 -0
- package/lib/browser/index-helpers/blocks-exports.js +1 -0
- package/lib/browser/scripts/init-editing.js +2 -1
- package/lib/browser/types/components.d.ts +2 -2
- package/lib/browser/types/input.d.ts +1 -0
- package/lib/edge/blocks/columns/columns.svelte +3 -3
- package/lib/edge/blocks/form/form/form.svelte +2 -15
- package/lib/edge/blocks/image/image.svelte +1 -3
- package/lib/edge/blocks/personalization-container/component-info.d.ts +2 -0
- package/lib/edge/blocks/personalization-container/component-info.js +36 -0
- package/lib/edge/blocks/personalization-container/helpers/inlined-fns.d.ts +8 -0
- package/lib/edge/blocks/personalization-container/helpers/inlined-fns.js +87 -0
- package/lib/edge/blocks/personalization-container/helpers.d.ts +27 -0
- package/lib/edge/blocks/personalization-container/helpers.js +58 -0
- package/lib/edge/blocks/personalization-container/index.d.ts +1 -0
- package/lib/edge/blocks/personalization-container/index.js +1 -0
- package/lib/edge/blocks/personalization-container/personalization-container.svelte +118 -0
- package/lib/edge/blocks/personalization-container/personalization-container.svelte.d.ts +21 -0
- package/lib/edge/blocks/personalization-container/personalization-container.types.d.ts +14 -0
- package/lib/edge/blocks/personalization-container/personalization-container.types.js +1 -0
- package/lib/edge/blocks/video/video.svelte +0 -1
- package/lib/edge/components/blocks/blocks-wrapper.svelte +6 -1
- package/lib/edge/constants/builder-registered-components.js +6 -1
- package/lib/edge/constants/sdk-version.d.ts +1 -1
- package/lib/edge/constants/sdk-version.js +1 -1
- package/lib/edge/helpers/canTrack.js +6 -1
- package/lib/edge/helpers/user-attributes.d.ts +17 -0
- package/lib/edge/helpers/user-attributes.js +46 -0
- package/lib/edge/index-helpers/blocks-exports.d.ts +1 -0
- package/lib/edge/index-helpers/blocks-exports.js +1 -0
- package/lib/edge/scripts/init-editing.js +2 -1
- package/lib/edge/types/components.d.ts +2 -2
- package/lib/edge/types/input.d.ts +1 -0
- package/lib/node/blocks/columns/columns.svelte +3 -3
- package/lib/node/blocks/form/form/form.svelte +2 -15
- package/lib/node/blocks/image/image.svelte +1 -3
- package/lib/node/blocks/personalization-container/component-info.d.ts +2 -0
- package/lib/node/blocks/personalization-container/component-info.js +36 -0
- package/lib/node/blocks/personalization-container/helpers/inlined-fns.d.ts +8 -0
- package/lib/node/blocks/personalization-container/helpers/inlined-fns.js +87 -0
- package/lib/node/blocks/personalization-container/helpers.d.ts +27 -0
- package/lib/node/blocks/personalization-container/helpers.js +58 -0
- package/lib/node/blocks/personalization-container/index.d.ts +1 -0
- package/lib/node/blocks/personalization-container/index.js +1 -0
- package/lib/node/blocks/personalization-container/personalization-container.svelte +118 -0
- package/lib/node/blocks/personalization-container/personalization-container.svelte.d.ts +21 -0
- package/lib/node/blocks/personalization-container/personalization-container.types.d.ts +14 -0
- package/lib/node/blocks/personalization-container/personalization-container.types.js +1 -0
- package/lib/node/blocks/video/video.svelte +0 -1
- package/lib/node/components/blocks/blocks-wrapper.svelte +6 -1
- package/lib/node/constants/builder-registered-components.js +6 -1
- package/lib/node/constants/sdk-version.d.ts +1 -1
- package/lib/node/constants/sdk-version.js +1 -1
- package/lib/node/helpers/canTrack.js +6 -1
- package/lib/node/helpers/user-attributes.d.ts +17 -0
- package/lib/node/helpers/user-attributes.js +46 -0
- package/lib/node/index-helpers/blocks-exports.d.ts +1 -0
- package/lib/node/index-helpers/blocks-exports.js +1 -0
- package/lib/node/scripts/init-editing.js +2 -1
- package/lib/node/types/components.d.ts +2 -2
- package/lib/node/types/input.d.ts +1 -0
- package/package.json +2 -2
- package/src/blocks/columns/columns.svelte +3 -3
- package/src/blocks/form/form/form.svelte +3 -15
- package/src/blocks/image/image.svelte +0 -4
- package/src/blocks/personalization-container/component-info.ts +37 -0
- package/src/blocks/personalization-container/helpers/inlined-fns.ts +88 -0
- package/src/blocks/personalization-container/helpers.ts +84 -0
- package/src/blocks/personalization-container/index.ts +1 -0
- package/src/blocks/personalization-container/personalization-container.svelte +155 -0
- package/src/blocks/personalization-container/personalization-container.types.ts +14 -0
- package/src/blocks/video/video.svelte +0 -1
- package/src/components/blocks/blocks-wrapper.svelte +6 -1
- package/src/constants/builder-registered-components.ts +6 -1
- package/src/constants/sdk-version.ts +1 -1
- package/src/helpers/canTrack.ts +6 -1
- package/src/helpers/user-attributes.ts +49 -0
- package/src/index-helpers/blocks-exports.ts +2 -1
- package/src/scripts/init-editing.ts +2 -1
- package/src/types/components.ts +2 -2
- package/src/types/input.ts +1 -0
|
@@ -122,7 +122,7 @@ export interface ComponentInfo {
|
|
|
122
122
|
/**
|
|
123
123
|
* Whether or not the component should receive the `builderBlock` prop, containing the current Builder block being rendered.
|
|
124
124
|
*
|
|
125
|
-
* Defaults to `
|
|
125
|
+
* Defaults to `false`.
|
|
126
126
|
*/
|
|
127
127
|
builderBlock?: boolean;
|
|
128
128
|
/**
|
|
@@ -130,7 +130,7 @@ export interface ComponentInfo {
|
|
|
130
130
|
* The Builder Context contains a lot of data relevant to the current `Content` render.
|
|
131
131
|
* You can see more information [here](https://github.com/BuilderIO/builder/blob/main/packages/sdks/src/context/types.ts).
|
|
132
132
|
*
|
|
133
|
-
* Defaults to `
|
|
133
|
+
* Defaults to `false`.
|
|
134
134
|
*/
|
|
135
135
|
builderContext?: boolean;
|
|
136
136
|
/**
|
|
@@ -170,13 +170,13 @@ $: flexDir = () => {
|
|
|
170
170
|
><Blocks
|
|
171
171
|
path={`columns.${index}.blocks`}
|
|
172
172
|
parent={builderBlock.id}
|
|
173
|
-
styleProp={{
|
|
174
|
-
flexGrow: "1",
|
|
175
|
-
}}
|
|
176
173
|
context={builderContext}
|
|
177
174
|
registeredComponents={builderComponents}
|
|
178
175
|
linkComponent={builderLinkComponent}
|
|
179
176
|
blocks={column.blocks}
|
|
177
|
+
styleProp={{
|
|
178
|
+
flexGrow: "1",
|
|
179
|
+
}}
|
|
180
180
|
/></DynamicRenderer
|
|
181
181
|
>
|
|
182
182
|
{/each}
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
</script>
|
|
5
5
|
|
|
6
|
-
<script>import
|
|
7
|
-
import Blocks from "../../../components/blocks/blocks.svelte";
|
|
6
|
+
<script>import Blocks from "../../../components/blocks/blocks.svelte";
|
|
8
7
|
import { getEnv } from "../../../functions/get-env";
|
|
9
8
|
import { get } from "../../../functions/get";
|
|
10
9
|
import { isEditing } from "../../../functions/is-editing";
|
|
@@ -27,9 +26,6 @@ export let resetFormOnSubmit = undefined;
|
|
|
27
26
|
export let successUrl = undefined;
|
|
28
27
|
export let validate = undefined;
|
|
29
28
|
export let attributes = undefined;
|
|
30
|
-
export let builderBlock;
|
|
31
|
-
export let builderComponents;
|
|
32
|
-
export let builderLinkComponent = undefined;
|
|
33
29
|
export let errorMessage = undefined;
|
|
34
30
|
export let sendingMessage = undefined;
|
|
35
31
|
export let successMessage = undefined;
|
|
@@ -254,16 +250,7 @@ let formErrorMessage = "";
|
|
|
254
250
|
{...filterAttrs(attributes, "on:", false)}
|
|
255
251
|
use:setAttrs={filterAttrs(attributes, "on:", true)}
|
|
256
252
|
>
|
|
257
|
-
|
|
258
|
-
{#each builderBlock?.children as block, idx (`form-block-${idx}`)}
|
|
259
|
-
<Block
|
|
260
|
-
{block}
|
|
261
|
-
context={builderContext}
|
|
262
|
-
registeredComponents={builderComponents}
|
|
263
|
-
linkComponent={builderLinkComponent}
|
|
264
|
-
/>
|
|
265
|
-
{/each}
|
|
266
|
-
{/if}
|
|
253
|
+
<slot />
|
|
267
254
|
{#if submissionState() === "error"}
|
|
268
255
|
<Blocks
|
|
269
256
|
path="errorMessage"
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
<script>import {
|
|
2
|
-
import { getSrcSet } from "./image.helpers";
|
|
1
|
+
<script>import { getSrcSet } from "./image.helpers";
|
|
3
2
|
export let image;
|
|
4
3
|
export let src = undefined;
|
|
5
4
|
export let srcset = undefined;
|
|
@@ -66,7 +65,6 @@ $: aspectRatioCss = () => {
|
|
|
66
65
|
const out = aspectRatio ? aspectRatioStyles : undefined;
|
|
67
66
|
return out;
|
|
68
67
|
};
|
|
69
|
-
onMount(() => { });
|
|
70
68
|
</script>
|
|
71
69
|
|
|
72
70
|
<picture>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export const componentInfo = {
|
|
2
|
+
name: 'PersonalizationContainer',
|
|
3
|
+
shouldReceiveBuilderProps: {
|
|
4
|
+
builderBlock: true,
|
|
5
|
+
builderContext: true
|
|
6
|
+
},
|
|
7
|
+
noWrap: true,
|
|
8
|
+
image: 'https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F37229ed30d8c41dfb10b8cca1992053a',
|
|
9
|
+
canHaveChildren: true,
|
|
10
|
+
inputs: [{
|
|
11
|
+
name: 'variants',
|
|
12
|
+
defaultValue: [],
|
|
13
|
+
behavior: 'personalizationVariantList',
|
|
14
|
+
type: 'list',
|
|
15
|
+
subFields: [{
|
|
16
|
+
name: 'name',
|
|
17
|
+
type: 'text'
|
|
18
|
+
}, {
|
|
19
|
+
name: 'query',
|
|
20
|
+
friendlyName: 'Targeting rules',
|
|
21
|
+
type: 'BuilderQuery',
|
|
22
|
+
defaultValue: []
|
|
23
|
+
}, {
|
|
24
|
+
name: 'startDate',
|
|
25
|
+
type: 'date'
|
|
26
|
+
}, {
|
|
27
|
+
name: 'endDate',
|
|
28
|
+
type: 'date'
|
|
29
|
+
}, {
|
|
30
|
+
name: 'blocks',
|
|
31
|
+
type: 'uiBlocks',
|
|
32
|
+
hideFromUI: true,
|
|
33
|
+
defaultValue: []
|
|
34
|
+
}]
|
|
35
|
+
}]
|
|
36
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WARNING: This file contains functions that get stringified and inlined into the HTML at build-time.
|
|
3
|
+
* They cannot import anything.
|
|
4
|
+
*/
|
|
5
|
+
import type { Query, UserAttributes } from '../helpers';
|
|
6
|
+
export declare function filterWithCustomTargeting(userAttributes: UserAttributes, query: Query[], startDate?: string, endDate?: string): boolean;
|
|
7
|
+
export declare const PERSONALIZATION_SCRIPT = "function getPersonalizedVariant(variants, blockId, locale) {\n if (!navigator.cookieEnabled) {\n return;\n }\n function getCookie(name) {\n const nameEQ = name + '=';\n const ca = document.cookie.split(';');\n for (let i = 0; i < ca.length; i++) {\n let c = ca[i];\n while (c.charAt(0) == ' ') c = c.substring(1, c.length);\n if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);\n }\n return null;\n }\n function removeVariants() {\n variants?.forEach(function (_, index) {\n document.querySelector('template[data-variant-id=\"' + blockId + '-' + index + '\"]')?.remove();\n });\n document.querySelector('script[data-id=\"variants-script-' + blockId + '\"]')?.remove();\n document.querySelector('style[data-id=\"variants-styles-' + blockId + '\"]')?.remove();\n }\n const attributes = JSON.parse(getCookie('builder.userAttributes') || '{}');\n if (locale) {\n attributes.locale = locale;\n }\n const winningVariantIndex = variants?.findIndex(function (variant) {\n return filterWithCustomTargeting(attributes, variant.query, variant.startDate, variant.endDate);\n });\n const isDebug = location.href.includes('builder.debug=true');\n if (isDebug) {\n console.debug('PersonalizationContainer', {\n attributes,\n variants,\n winningVariantIndex\n });\n }\n if (winningVariantIndex !== -1) {\n const winningVariant = document.querySelector('template[data-variant-id=\"' + blockId + '-' + winningVariantIndex + '\"]');\n if (winningVariant) {\n const parentNode = winningVariant.parentNode;\n if (parentNode) {\n const newParent = parentNode.cloneNode(false);\n newParent.appendChild(winningVariant.content.firstChild);\n newParent.appendChild(winningVariant.content.lastChild);\n parentNode.parentNode?.replaceChild(newParent, parentNode);\n }\n if (isDebug) {\n console.debug('PersonalizationContainer', 'Winning variant Replaced:', winningVariant);\n }\n }\n } else if (variants && variants.length > 0) {\n removeVariants();\n }\n}";
|
|
8
|
+
export declare const FILTER_WITH_CUSTOM_TARGETING_SCRIPT = "function filterWithCustomTargeting(userAttributes, query, startDate, endDate) {\n function isString(val) {\n return typeof val === 'string';\n }\n function isNumber(val) {\n return typeof val === 'number';\n }\n function objectMatchesQuery(userattr, query) {\n const result = (() => {\n const property = query.property;\n const operator = query.operator;\n let testValue = query.value;\n if (query && query.property === 'urlPath' && query.value && typeof query.value === 'string' && query.value !== '/' && query.value.endsWith('/')) {\n testValue = query.value.slice(0, -1);\n }\n if (!(property && operator)) {\n return true;\n }\n if (Array.isArray(testValue)) {\n if (operator === 'isNot') {\n return testValue.every(val => objectMatchesQuery(userattr, {\n property,\n operator,\n value: val\n }));\n }\n return !!testValue.find(val => objectMatchesQuery(userattr, {\n property,\n operator,\n value: val\n }));\n }\n const value = userattr[property];\n if (Array.isArray(value)) {\n return value.includes(testValue);\n }\n switch (operator) {\n case 'is':\n return value === testValue;\n case 'isNot':\n return value !== testValue;\n case 'contains':\n return (isString(value) || Array.isArray(value)) && value.includes(String(testValue));\n case 'startsWith':\n return isString(value) && value.startsWith(String(testValue));\n case 'endsWith':\n return isString(value) && value.endsWith(String(testValue));\n case 'greaterThan':\n return isNumber(value) && isNumber(testValue) && value > testValue;\n case 'lessThan':\n return isNumber(value) && isNumber(testValue) && value < testValue;\n case 'greaterThanOrEqualTo':\n return isNumber(value) && isNumber(testValue) && value >= testValue;\n case 'lessThanOrEqualTo':\n return isNumber(value) && isNumber(testValue) && value <= testValue;\n default:\n return false;\n }\n })();\n return result;\n }\n const item = {\n query,\n startDate,\n endDate\n };\n const now = userAttributes.date && new Date(userAttributes.date) || new Date();\n if (item.startDate && new Date(item.startDate) > now) {\n return false;\n } else if (item.endDate && new Date(item.endDate) < now) {\n return false;\n }\n if (!item.query || !item.query.length) {\n return true;\n }\n return item.query.every(filter => {\n return objectMatchesQuery(userAttributes, filter);\n });\n}";
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WARNING: This file contains functions that get stringified and inlined into the HTML at build-time.
|
|
3
|
+
* They cannot import anything.
|
|
4
|
+
*/
|
|
5
|
+
import '../personalization-container.types';
|
|
6
|
+
export function filterWithCustomTargeting(userAttributes, query, startDate, endDate) {
|
|
7
|
+
function isString(val) {
|
|
8
|
+
return typeof val === 'string';
|
|
9
|
+
}
|
|
10
|
+
function isNumber(val) {
|
|
11
|
+
return typeof val === 'number';
|
|
12
|
+
}
|
|
13
|
+
function objectMatchesQuery(userattr, query) {
|
|
14
|
+
const result = (() => {
|
|
15
|
+
const property = query.property;
|
|
16
|
+
const operator = query.operator;
|
|
17
|
+
let testValue = query.value;
|
|
18
|
+
if (query && query.property === 'urlPath' && query.value && typeof query.value === 'string' && query.value !== '/' && query.value.endsWith('/')) {
|
|
19
|
+
testValue = query.value.slice(0, -1);
|
|
20
|
+
}
|
|
21
|
+
if (!(property && operator)) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
if (Array.isArray(testValue)) {
|
|
25
|
+
if (operator === 'isNot') {
|
|
26
|
+
return testValue.every(val => objectMatchesQuery(userattr, {
|
|
27
|
+
property,
|
|
28
|
+
operator,
|
|
29
|
+
value: val
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
32
|
+
return !!testValue.find(val => objectMatchesQuery(userattr, {
|
|
33
|
+
property,
|
|
34
|
+
operator,
|
|
35
|
+
value: val
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
const value = userattr[property];
|
|
39
|
+
if (Array.isArray(value)) {
|
|
40
|
+
return value.includes(testValue);
|
|
41
|
+
}
|
|
42
|
+
switch (operator) {
|
|
43
|
+
case 'is':
|
|
44
|
+
return value === testValue;
|
|
45
|
+
case 'isNot':
|
|
46
|
+
return value !== testValue;
|
|
47
|
+
case 'contains':
|
|
48
|
+
return (isString(value) || Array.isArray(value)) && value.includes(String(testValue));
|
|
49
|
+
case 'startsWith':
|
|
50
|
+
return isString(value) && value.startsWith(String(testValue));
|
|
51
|
+
case 'endsWith':
|
|
52
|
+
return isString(value) && value.endsWith(String(testValue));
|
|
53
|
+
case 'greaterThan':
|
|
54
|
+
return isNumber(value) && isNumber(testValue) && value > testValue;
|
|
55
|
+
case 'lessThan':
|
|
56
|
+
return isNumber(value) && isNumber(testValue) && value < testValue;
|
|
57
|
+
case 'greaterThanOrEqualTo':
|
|
58
|
+
return isNumber(value) && isNumber(testValue) && value >= testValue;
|
|
59
|
+
case 'lessThanOrEqualTo':
|
|
60
|
+
return isNumber(value) && isNumber(testValue) && value <= testValue;
|
|
61
|
+
default:
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
})();
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
const item = {
|
|
68
|
+
query,
|
|
69
|
+
startDate,
|
|
70
|
+
endDate
|
|
71
|
+
};
|
|
72
|
+
const now = userAttributes.date && new Date(userAttributes.date) || new Date();
|
|
73
|
+
if (item.startDate && new Date(item.startDate) > now) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
else if (item.endDate && new Date(item.endDate) < now) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
if (!item.query || !item.query.length) {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
return item.query.every((filter) => {
|
|
83
|
+
return objectMatchesQuery(userAttributes, filter);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
export const PERSONALIZATION_SCRIPT = "function getPersonalizedVariant(variants, blockId, locale) {\n if (!navigator.cookieEnabled) {\n return;\n }\n function getCookie(name) {\n const nameEQ = name + '=';\n const ca = document.cookie.split(';');\n for (let i = 0; i < ca.length; i++) {\n let c = ca[i];\n while (c.charAt(0) == ' ') c = c.substring(1, c.length);\n if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);\n }\n return null;\n }\n function removeVariants() {\n variants?.forEach(function (_, index) {\n document.querySelector('template[data-variant-id=\"' + blockId + '-' + index + '\"]')?.remove();\n });\n document.querySelector('script[data-id=\"variants-script-' + blockId + '\"]')?.remove();\n document.querySelector('style[data-id=\"variants-styles-' + blockId + '\"]')?.remove();\n }\n const attributes = JSON.parse(getCookie('builder.userAttributes') || '{}');\n if (locale) {\n attributes.locale = locale;\n }\n const winningVariantIndex = variants?.findIndex(function (variant) {\n return filterWithCustomTargeting(attributes, variant.query, variant.startDate, variant.endDate);\n });\n const isDebug = location.href.includes('builder.debug=true');\n if (isDebug) {\n console.debug('PersonalizationContainer', {\n attributes,\n variants,\n winningVariantIndex\n });\n }\n if (winningVariantIndex !== -1) {\n const winningVariant = document.querySelector('template[data-variant-id=\"' + blockId + '-' + winningVariantIndex + '\"]');\n if (winningVariant) {\n const parentNode = winningVariant.parentNode;\n if (parentNode) {\n const newParent = parentNode.cloneNode(false);\n newParent.appendChild(winningVariant.content.firstChild);\n newParent.appendChild(winningVariant.content.lastChild);\n parentNode.parentNode?.replaceChild(newParent, parentNode);\n }\n if (isDebug) {\n console.debug('PersonalizationContainer', 'Winning variant Replaced:', winningVariant);\n }\n }\n } else if (variants && variants.length > 0) {\n removeVariants();\n }\n}";
|
|
87
|
+
export const FILTER_WITH_CUSTOM_TARGETING_SCRIPT = "function filterWithCustomTargeting(userAttributes, query, startDate, endDate) {\n function isString(val) {\n return typeof val === 'string';\n }\n function isNumber(val) {\n return typeof val === 'number';\n }\n function objectMatchesQuery(userattr, query) {\n const result = (() => {\n const property = query.property;\n const operator = query.operator;\n let testValue = query.value;\n if (query && query.property === 'urlPath' && query.value && typeof query.value === 'string' && query.value !== '/' && query.value.endsWith('/')) {\n testValue = query.value.slice(0, -1);\n }\n if (!(property && operator)) {\n return true;\n }\n if (Array.isArray(testValue)) {\n if (operator === 'isNot') {\n return testValue.every(val => objectMatchesQuery(userattr, {\n property,\n operator,\n value: val\n }));\n }\n return !!testValue.find(val => objectMatchesQuery(userattr, {\n property,\n operator,\n value: val\n }));\n }\n const value = userattr[property];\n if (Array.isArray(value)) {\n return value.includes(testValue);\n }\n switch (operator) {\n case 'is':\n return value === testValue;\n case 'isNot':\n return value !== testValue;\n case 'contains':\n return (isString(value) || Array.isArray(value)) && value.includes(String(testValue));\n case 'startsWith':\n return isString(value) && value.startsWith(String(testValue));\n case 'endsWith':\n return isString(value) && value.endsWith(String(testValue));\n case 'greaterThan':\n return isNumber(value) && isNumber(testValue) && value > testValue;\n case 'lessThan':\n return isNumber(value) && isNumber(testValue) && value < testValue;\n case 'greaterThanOrEqualTo':\n return isNumber(value) && isNumber(testValue) && value >= testValue;\n case 'lessThanOrEqualTo':\n return isNumber(value) && isNumber(testValue) && value <= testValue;\n default:\n return false;\n }\n })();\n return result;\n }\n const item = {\n query,\n startDate,\n endDate\n };\n const now = userAttributes.date && new Date(userAttributes.date) || new Date();\n if (item.startDate && new Date(item.startDate) > now) {\n return false;\n } else if (item.endDate && new Date(item.endDate) < now) {\n return false;\n }\n if (!item.query || !item.query.length) {\n return true;\n }\n return item.query.every(filter => {\n return objectMatchesQuery(userAttributes, filter);\n });\n}";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { BuilderBlock } from '../../types/builder-block';
|
|
2
|
+
import type { PersonalizationContainerProps } from './personalization-container.types';
|
|
3
|
+
export type UserAttributes = {
|
|
4
|
+
date?: string | Date;
|
|
5
|
+
urlPath?: string;
|
|
6
|
+
[key: string]: any;
|
|
7
|
+
};
|
|
8
|
+
type QueryOperator = 'is' | 'isNot' | 'contains' | 'startsWith' | 'endsWith' | 'greaterThan' | 'lessThan' | 'greaterThanOrEqualTo' | 'lessThanOrEqualTo';
|
|
9
|
+
type QueryValue = string | number | boolean | Array<string | number | boolean>;
|
|
10
|
+
export type Query = {
|
|
11
|
+
property: string;
|
|
12
|
+
operator: QueryOperator;
|
|
13
|
+
value: QueryValue;
|
|
14
|
+
};
|
|
15
|
+
export declare function checkShouldRenderVariants(variants: PersonalizationContainerProps['variants'], canTrack: boolean): boolean;
|
|
16
|
+
export declare function getBlocksToRender({ variants, previewingIndex, isHydrated, filteredVariants, fallbackBlocks }: {
|
|
17
|
+
variants: PersonalizationContainerProps['variants'];
|
|
18
|
+
previewingIndex?: number | null;
|
|
19
|
+
isHydrated: boolean;
|
|
20
|
+
filteredVariants: PersonalizationContainerProps['variants'];
|
|
21
|
+
fallbackBlocks?: BuilderBlock[];
|
|
22
|
+
}): {
|
|
23
|
+
blocks: BuilderBlock[];
|
|
24
|
+
path: string | undefined;
|
|
25
|
+
};
|
|
26
|
+
export declare const getPersonalizationScript: (variants: PersonalizationContainerProps['variants'], blockId: string, locale?: string) => string;
|
|
27
|
+
export { filterWithCustomTargeting } from './helpers/inlined-fns';
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { TARGET } from '../../constants/target';
|
|
2
|
+
import { isBrowser } from '../../functions/is-browser';
|
|
3
|
+
import { isEditing } from '../../functions/is-editing';
|
|
4
|
+
import { FILTER_WITH_CUSTOM_TARGETING_SCRIPT, PERSONALIZATION_SCRIPT } from './helpers/inlined-fns';
|
|
5
|
+
export function checkShouldRenderVariants(variants, canTrack) {
|
|
6
|
+
const hasVariants = variants && variants.length > 0;
|
|
7
|
+
if (TARGET === 'reactNative')
|
|
8
|
+
return false;
|
|
9
|
+
if (!hasVariants)
|
|
10
|
+
return false;
|
|
11
|
+
if (!canTrack)
|
|
12
|
+
return false;
|
|
13
|
+
if (TARGET === 'vue' || TARGET === 'svelte')
|
|
14
|
+
return true;
|
|
15
|
+
if (isBrowser())
|
|
16
|
+
return false;
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
export function getBlocksToRender({ variants, previewingIndex, isHydrated, filteredVariants, fallbackBlocks }) {
|
|
20
|
+
const fallback = {
|
|
21
|
+
blocks: fallbackBlocks ?? [],
|
|
22
|
+
path: 'this.children'
|
|
23
|
+
};
|
|
24
|
+
if (isHydrated && isEditing()) {
|
|
25
|
+
// If editing a specific variant
|
|
26
|
+
if (typeof previewingIndex === 'number' && previewingIndex < (variants?.length ?? 0)) {
|
|
27
|
+
const variant = variants[previewingIndex];
|
|
28
|
+
return {
|
|
29
|
+
blocks: variant.blocks,
|
|
30
|
+
path: `component.options.variants.${previewingIndex}.blocks`
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
// Otherwise we're editing the default variant
|
|
34
|
+
return fallback;
|
|
35
|
+
}
|
|
36
|
+
// If we're on the browser, check if there's a winning variant
|
|
37
|
+
if (isBrowser()) {
|
|
38
|
+
const winningVariant = filteredVariants?.[0];
|
|
39
|
+
if (winningVariant) {
|
|
40
|
+
return {
|
|
41
|
+
blocks: winningVariant.blocks,
|
|
42
|
+
path: `component.options.variants.${variants?.indexOf(winningVariant)}.blocks`
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// If no winning variant or we are on the server, return the default variant
|
|
47
|
+
return fallback;
|
|
48
|
+
}
|
|
49
|
+
export const getPersonalizationScript = (variants, blockId, locale) => {
|
|
50
|
+
return `
|
|
51
|
+
(function() {
|
|
52
|
+
${FILTER_WITH_CUSTOM_TARGETING_SCRIPT}
|
|
53
|
+
${PERSONALIZATION_SCRIPT}
|
|
54
|
+
getPersonalizedVariant(${JSON.stringify(variants)}, "${blockId}"${locale ? `, "${locale}"` : ''})
|
|
55
|
+
})();
|
|
56
|
+
`;
|
|
57
|
+
};
|
|
58
|
+
export { filterWithCustomTargeting } from './helpers/inlined-fns';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './personalization-container.svelte';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './personalization-container.svelte';
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
<script>import { onDestroy, onMount } from "svelte";
|
|
2
|
+
import Blocks from "../../components/blocks/blocks.svelte";
|
|
3
|
+
import InlinedScript from "../../components/inlined-script.svelte";
|
|
4
|
+
import InlinedStyles from "../../components/inlined-styles.svelte";
|
|
5
|
+
import { isEditing } from "../../functions/is-editing";
|
|
6
|
+
import { isPreviewing } from "../../functions/is-previewing";
|
|
7
|
+
import { getDefaultCanTrack } from "../../helpers/canTrack";
|
|
8
|
+
import { userAttributesService } from "../../helpers/user-attributes";
|
|
9
|
+
import { checkShouldRenderVariants, filterWithCustomTargeting, getBlocksToRender, getPersonalizationScript, } from "./helpers";
|
|
10
|
+
export let builderContext;
|
|
11
|
+
export let variants = undefined;
|
|
12
|
+
export let builderBlock;
|
|
13
|
+
export let previewingIndex = undefined;
|
|
14
|
+
export let attributes = undefined;
|
|
15
|
+
$: filteredVariants = () => {
|
|
16
|
+
return (variants || []).filter((variant) => {
|
|
17
|
+
return filterWithCustomTargeting({
|
|
18
|
+
...($builderContext?.rootState?.locale
|
|
19
|
+
? {
|
|
20
|
+
locale: $builderContext?.rootState?.locale,
|
|
21
|
+
}
|
|
22
|
+
: {}),
|
|
23
|
+
...userAttributes,
|
|
24
|
+
}, variant.query, variant.startDate, variant.endDate);
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
$: blocksToRender = () => {
|
|
28
|
+
return getBlocksToRender({
|
|
29
|
+
variants: variants,
|
|
30
|
+
fallbackBlocks: builderBlock?.children,
|
|
31
|
+
isHydrated: isHydrated,
|
|
32
|
+
filteredVariants: filteredVariants(),
|
|
33
|
+
previewingIndex: previewingIndex,
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
$: hideVariantsStyleString = () => {
|
|
37
|
+
return (variants || [])
|
|
38
|
+
.map((_, index) => `[data-variant-id="${builderBlock?.id}-${index}"] { display: none; } `)
|
|
39
|
+
.join("");
|
|
40
|
+
};
|
|
41
|
+
let rootRef;
|
|
42
|
+
let userAttributes = userAttributesService.getUserAttributes();
|
|
43
|
+
let scriptStr = getPersonalizationScript(variants, builderBlock?.id || "none", $builderContext?.rootState?.locale);
|
|
44
|
+
let unsubscribers = [];
|
|
45
|
+
let shouldRenderVariants = checkShouldRenderVariants(variants, getDefaultCanTrack($builderContext?.canTrack));
|
|
46
|
+
let isHydrated = false;
|
|
47
|
+
onMount(() => {
|
|
48
|
+
isHydrated = true;
|
|
49
|
+
const unsub = userAttributesService.subscribeOnUserAttributesChange((attrs) => {
|
|
50
|
+
userAttributes = attrs;
|
|
51
|
+
});
|
|
52
|
+
if (!(isEditing() || isPreviewing())) {
|
|
53
|
+
const variant = filteredVariants()[0];
|
|
54
|
+
if (rootRef) {
|
|
55
|
+
rootRef.dispatchEvent(new CustomEvent("builder.variantLoaded", {
|
|
56
|
+
detail: {
|
|
57
|
+
variant: variant || "default",
|
|
58
|
+
content: $builderContext?.content,
|
|
59
|
+
},
|
|
60
|
+
bubbles: true,
|
|
61
|
+
}));
|
|
62
|
+
const observer = new IntersectionObserver((entries) => {
|
|
63
|
+
entries.forEach((entry) => {
|
|
64
|
+
if (entry.isIntersecting && rootRef) {
|
|
65
|
+
rootRef.dispatchEvent(new CustomEvent("builder.variantDisplayed", {
|
|
66
|
+
detail: {
|
|
67
|
+
variant: variant || "default",
|
|
68
|
+
content: $builderContext?.content,
|
|
69
|
+
},
|
|
70
|
+
bubbles: true,
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
observer.observe(rootRef);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
unsubscribers.push(unsub);
|
|
79
|
+
});
|
|
80
|
+
onDestroy(() => {
|
|
81
|
+
unsubscribers.forEach((unsub) => unsub());
|
|
82
|
+
});
|
|
83
|
+
</script>
|
|
84
|
+
|
|
85
|
+
<div
|
|
86
|
+
bind:this={rootRef}
|
|
87
|
+
{...attributes}
|
|
88
|
+
class={`builder-personalization-container ${attributes?.className || ""}`}
|
|
89
|
+
>
|
|
90
|
+
{#if shouldRenderVariants}
|
|
91
|
+
{#each variants as variant, index (index)}
|
|
92
|
+
<svelte:element
|
|
93
|
+
this={"template"}
|
|
94
|
+
data-variant-id={`${builderBlock?.id}-${index}`}
|
|
95
|
+
><Blocks
|
|
96
|
+
blocks={variant.blocks}
|
|
97
|
+
parent={builderBlock?.id}
|
|
98
|
+
path={`component.options.variants.${index}.blocks`}
|
|
99
|
+
/></svelte:element
|
|
100
|
+
>
|
|
101
|
+
{/each}
|
|
102
|
+
|
|
103
|
+
<InlinedStyles
|
|
104
|
+
nonce={$builderContext?.nonce || ""}
|
|
105
|
+
styles={hideVariantsStyleString()}
|
|
106
|
+
id={`variants-styles-${builderBlock?.id}`}
|
|
107
|
+
/>
|
|
108
|
+
<InlinedScript
|
|
109
|
+
nonce={$builderContext?.nonce || ""}
|
|
110
|
+
{scriptStr}
|
|
111
|
+
id={`variants-script-${builderBlock?.id}`}
|
|
112
|
+
/>
|
|
113
|
+
{/if}<Blocks
|
|
114
|
+
blocks={blocksToRender().blocks}
|
|
115
|
+
parent={builderBlock?.id}
|
|
116
|
+
path={blocksToRender().path}
|
|
117
|
+
/>
|
|
118
|
+
</div>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { SvelteComponent } from "svelte";
|
|
2
|
+
import type { PersonalizationContainerProps } from "./personalization-container.types";
|
|
3
|
+
declare const __propDef: {
|
|
4
|
+
props: {
|
|
5
|
+
builderContext: PersonalizationContainerProps["builderContext"];
|
|
6
|
+
variants?: PersonalizationContainerProps["variants"];
|
|
7
|
+
builderBlock: PersonalizationContainerProps["builderBlock"];
|
|
8
|
+
previewingIndex?: PersonalizationContainerProps["previewingIndex"];
|
|
9
|
+
attributes?: PersonalizationContainerProps["attributes"];
|
|
10
|
+
};
|
|
11
|
+
events: {
|
|
12
|
+
[evt: string]: CustomEvent<any>;
|
|
13
|
+
};
|
|
14
|
+
slots: {};
|
|
15
|
+
};
|
|
16
|
+
type PersonalizationContainerProps_ = typeof __propDef.props;
|
|
17
|
+
export { PersonalizationContainerProps_ as PersonalizationContainerProps };
|
|
18
|
+
export type PersonalizationContainerEvents = typeof __propDef.events;
|
|
19
|
+
export type PersonalizationContainerSlots = typeof __propDef.slots;
|
|
20
|
+
export default class PersonalizationContainer extends SvelteComponent<PersonalizationContainerProps, PersonalizationContainerEvents, PersonalizationContainerSlots> {
|
|
21
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { BuilderBlock } from '../../types/builder-block';
|
|
2
|
+
import type { BuilderDataProps } from '../../types/builder-props';
|
|
3
|
+
import type { Query } from './helpers';
|
|
4
|
+
export type PersonalizationContainerProps = {
|
|
5
|
+
children?: any;
|
|
6
|
+
attributes?: any;
|
|
7
|
+
previewingIndex?: number | null;
|
|
8
|
+
variants?: Array<{
|
|
9
|
+
blocks: BuilderBlock[];
|
|
10
|
+
query: Query[];
|
|
11
|
+
startDate?: string;
|
|
12
|
+
endDate?: string;
|
|
13
|
+
}>;
|
|
14
|
+
} & BuilderDataProps;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -50,8 +50,13 @@ $: dataPath = () => {
|
|
|
50
50
|
if (!path) {
|
|
51
51
|
return undefined;
|
|
52
52
|
}
|
|
53
|
+
const thisPrefix = "this.";
|
|
53
54
|
const pathPrefix = "component.options.";
|
|
54
|
-
return path.startsWith(
|
|
55
|
+
return path.startsWith(thisPrefix)
|
|
56
|
+
? path.replace(thisPrefix, "")
|
|
57
|
+
: path.startsWith(pathPrefix)
|
|
58
|
+
? path
|
|
59
|
+
: `${pathPrefix}${path || ""}`;
|
|
55
60
|
};
|
|
56
61
|
let blocksWrapperRef;
|
|
57
62
|
onMount(() => { });
|
|
@@ -8,6 +8,8 @@ import { componentInfo as fragmentComponentInfo } from '../blocks/fragment/compo
|
|
|
8
8
|
import { default as Fragment } from '../blocks/fragment/index';
|
|
9
9
|
import { componentInfo as imageComponentInfo } from '../blocks/image/component-info';
|
|
10
10
|
import { default as Image } from '../blocks/image/index';
|
|
11
|
+
import { componentInfo as personalizationContainerComponentInfo } from '../blocks/personalization-container/component-info';
|
|
12
|
+
import { default as PersonalizationContainer } from '../blocks/personalization-container/index';
|
|
11
13
|
import { componentInfo as sectionComponentInfo } from '../blocks/section/component-info';
|
|
12
14
|
import { default as Section } from '../blocks/section/index';
|
|
13
15
|
import { componentInfo as slotComponentInfo } from '../blocks/slot/component-info';
|
|
@@ -48,7 +50,10 @@ export const getDefaultRegisteredComponents = () => [{
|
|
|
48
50
|
}, {
|
|
49
51
|
component: Text,
|
|
50
52
|
...textComponentInfo
|
|
51
|
-
}, ...(TARGET === '
|
|
53
|
+
}, ...(TARGET === 'react' ? [{
|
|
54
|
+
component: PersonalizationContainer,
|
|
55
|
+
...personalizationContainerComponentInfo
|
|
56
|
+
}] : []), ...(TARGET === 'rsc' ? [] : [{
|
|
52
57
|
component: Tabs,
|
|
53
58
|
...tabsComponentInfo
|
|
54
59
|
}, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "3.0.
|
|
1
|
+
export declare const SDK_VERSION = "3.0.6";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const SDK_VERSION = "3.0.
|
|
1
|
+
export const SDK_VERSION = "3.0.6";
|
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
import { checkIsDefined } from './nullable';
|
|
2
|
-
|
|
2
|
+
import { userAttributesService } from './user-attributes';
|
|
3
|
+
export const getDefaultCanTrack = (canTrack) => {
|
|
4
|
+
const result = checkIsDefined(canTrack) ? canTrack : true;
|
|
5
|
+
userAttributesService.setCanTrack(result);
|
|
6
|
+
return result;
|
|
7
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface UserAttributes {
|
|
2
|
+
[key: string]: any;
|
|
3
|
+
}
|
|
4
|
+
export declare const USER_ATTRIBUTES_COOKIE_NAME = "builder.userAttributes";
|
|
5
|
+
export declare function createUserAttributesService(): {
|
|
6
|
+
setUserAttributes(newAttrs: UserAttributes): void;
|
|
7
|
+
getUserAttributes(): any;
|
|
8
|
+
subscribeOnUserAttributesChange(callback: (attrs: UserAttributes) => void): () => void;
|
|
9
|
+
setCanTrack(value: boolean): void;
|
|
10
|
+
};
|
|
11
|
+
export declare const userAttributesService: {
|
|
12
|
+
setUserAttributes(newAttrs: UserAttributes): void;
|
|
13
|
+
getUserAttributes(): any;
|
|
14
|
+
subscribeOnUserAttributesChange(callback: (attrs: UserAttributes) => void): () => void;
|
|
15
|
+
setCanTrack(value: boolean): void;
|
|
16
|
+
};
|
|
17
|
+
export declare const setClientUserAttributes: (attributes: UserAttributes) => void;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { isBrowser } from '../functions/is-browser';
|
|
2
|
+
import { getCookieSync, setCookie } from './cookie';
|
|
3
|
+
export const USER_ATTRIBUTES_COOKIE_NAME = 'builder.userAttributes';
|
|
4
|
+
export function createUserAttributesService() {
|
|
5
|
+
let canTrack = true;
|
|
6
|
+
const subscribers = new Set();
|
|
7
|
+
return {
|
|
8
|
+
setUserAttributes(newAttrs) {
|
|
9
|
+
if (!isBrowser()) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const userAttributes = {
|
|
13
|
+
...this.getUserAttributes(),
|
|
14
|
+
...newAttrs
|
|
15
|
+
};
|
|
16
|
+
setCookie({
|
|
17
|
+
name: USER_ATTRIBUTES_COOKIE_NAME,
|
|
18
|
+
value: JSON.stringify(userAttributes),
|
|
19
|
+
canTrack
|
|
20
|
+
});
|
|
21
|
+
subscribers.forEach(callback => callback(userAttributes));
|
|
22
|
+
},
|
|
23
|
+
getUserAttributes() {
|
|
24
|
+
if (!isBrowser()) {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
return JSON.parse(getCookieSync({
|
|
28
|
+
name: USER_ATTRIBUTES_COOKIE_NAME,
|
|
29
|
+
canTrack
|
|
30
|
+
}) || '{}');
|
|
31
|
+
},
|
|
32
|
+
subscribeOnUserAttributesChange(callback) {
|
|
33
|
+
subscribers.add(callback);
|
|
34
|
+
return () => {
|
|
35
|
+
subscribers.delete(callback);
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
setCanTrack(value) {
|
|
39
|
+
canTrack = value;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export const userAttributesService = createUserAttributesService();
|
|
44
|
+
export const setClientUserAttributes = (attributes) => {
|
|
45
|
+
userAttributesService.setUserAttributes(attributes);
|
|
46
|
+
};
|