@sanity/personalization-plugin 2.5.0-field-level-personalization.1 → 2.5.0
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 +1 -1
- package/README.md +2 -1
- package/dist/growthbook/index.js +3 -3
- package/dist/growthbook/index.js.map +1 -1
- package/dist/growthbook/index.mjs +1 -1
- package/dist/index.d.mts +12 -33
- package/dist/index.d.ts +12 -33
- package/dist/index.js +280 -157
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +280 -159
- package/dist/index.mjs.map +1 -1
- package/dist/launchDarkly/index.d.mts +12 -0
- package/dist/launchDarkly/index.d.ts +12 -0
- package/dist/launchDarkly/index.js +103 -0
- package/dist/launchDarkly/index.js.map +1 -0
- package/dist/launchDarkly/index.mjs +107 -0
- package/dist/launchDarkly/index.mjs.map +1 -0
- package/package.json +10 -5
- package/src/components/{experiment/Array.tsx → Array.tsx} +2 -2
- package/src/components/{experiment/Context.tsx → ExperimentContext.tsx} +2 -2
- package/src/components/{experiment/Field.tsx → ExperimentField.tsx} +8 -11
- package/src/components/{experiment/Input.tsx → ExperimentInput.tsx} +4 -4
- package/src/components/{ArrayItem.tsx → ExperimentItem.tsx} +2 -1
- package/src/components/Select.tsx +1 -1
- package/src/components/{experiment/VariantPreview.tsx → VariantPreview.tsx} +2 -2
- package/src/fieldExperiments.tsx +13 -43
- package/src/index.ts +0 -1
- package/src/launchDarkly/components/LaunchDarklyContext.tsx +36 -0
- package/src/launchDarkly/components/Secrets.tsx +46 -0
- package/src/launchDarkly/index.ts +52 -0
- package/src/launchDarkly/types.ts +193 -0
- package/src/launchDarkly/utils.ts +54 -0
- package/src/types.ts +2 -20
- package/dist/_chunks-cjs/fieldExperiments.js +0 -507
- package/dist/_chunks-cjs/fieldExperiments.js.map +0 -1
- package/dist/_chunks-es/fieldExperiments.mjs +0 -511
- package/dist/_chunks-es/fieldExperiments.mjs.map +0 -1
- package/src/components/experiment/index.ts +0 -6
- package/src/components/personalization/Array.tsx +0 -59
- package/src/components/personalization/Context.tsx +0 -61
- package/src/components/personalization/Field.tsx +0 -134
- package/src/components/personalization/SegmentInput.tsx +0 -19
- package/src/components/personalization/SegmentPreview.tsx +0 -71
- package/src/components/personalization/index.ts +0 -5
- package/src/fieldPersonalization.tsx +0 -254
- package/src/utils/clearChildGroups.ts +0 -33
- /package/src/components/{experiment/VariantInput.tsx → VariantInput.tsx} +0 -0
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import {Button, Inline, Stack} from '@sanity/ui'
|
|
2
|
-
import {uuid} from '@sanity/uuid'
|
|
3
|
-
import {useCallback} from 'react'
|
|
4
|
-
|
|
5
|
-
import {PersonalizationArrayInputProps, VariantType} from '../../types'
|
|
6
|
-
import {usePersonalizationContext} from './Context'
|
|
7
|
-
|
|
8
|
-
export const ArrayInput = (props: PersonalizationArrayInputProps) => {
|
|
9
|
-
const {onItemAppend, segmentName, segmentId} = props
|
|
10
|
-
|
|
11
|
-
const {segments} = usePersonalizationContext()
|
|
12
|
-
|
|
13
|
-
const handleClick = useCallback(
|
|
14
|
-
async (segment: VariantType) => {
|
|
15
|
-
const item = {
|
|
16
|
-
_key: uuid(),
|
|
17
|
-
[segmentId]: segment.id,
|
|
18
|
-
_type: segmentName,
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Patch the document
|
|
22
|
-
onItemAppend(item)
|
|
23
|
-
},
|
|
24
|
-
[segmentId, segmentName, onItemAppend],
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
type Value = {
|
|
28
|
-
value?: unknown
|
|
29
|
-
[key: string]: string | unknown
|
|
30
|
-
segmentId: string
|
|
31
|
-
_key: string
|
|
32
|
-
_type: string
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// there is probably some better was of getting the type of this?
|
|
36
|
-
const values = (props.value as Value[]) || []
|
|
37
|
-
|
|
38
|
-
const usedSegments = values?.map((segment) => segment[segmentId])
|
|
39
|
-
|
|
40
|
-
return (
|
|
41
|
-
<Stack space={3}>
|
|
42
|
-
{props.renderDefault({...props, arrayFunctions: () => null})}
|
|
43
|
-
|
|
44
|
-
<Inline space={1}>
|
|
45
|
-
{segments.map((segment) => {
|
|
46
|
-
return (
|
|
47
|
-
<Button
|
|
48
|
-
key={`${segment.id}`}
|
|
49
|
-
text={`Add ${segment.label}`}
|
|
50
|
-
mode="ghost"
|
|
51
|
-
disabled={usedSegments?.includes(segment.id)}
|
|
52
|
-
onClick={() => handleClick(segment)}
|
|
53
|
-
/>
|
|
54
|
-
)
|
|
55
|
-
})}
|
|
56
|
-
</Inline>
|
|
57
|
-
</Stack>
|
|
58
|
-
)
|
|
59
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import equal from 'fast-deep-equal'
|
|
2
|
-
import {createContext, useContext, useMemo} from 'react'
|
|
3
|
-
import {ObjectInputProps, useClient, useWorkspace} from 'sanity'
|
|
4
|
-
import {suspend} from 'suspend-react'
|
|
5
|
-
|
|
6
|
-
import {PersonalizationContextProps, PersonalizationFieldPluginConfig} from '../../types'
|
|
7
|
-
|
|
8
|
-
export const CONFIG_DEFAULT = {
|
|
9
|
-
fields: [],
|
|
10
|
-
apiVersion: '2024-11-07',
|
|
11
|
-
personalizationNameOverride: 'personalization',
|
|
12
|
-
segmentNameOverride: 'segment',
|
|
13
|
-
segmentId: 'segmentId',
|
|
14
|
-
segmentArrayName: 'segments',
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export const PersonalizationContext = createContext<PersonalizationContextProps>({
|
|
18
|
-
...CONFIG_DEFAULT,
|
|
19
|
-
segments: [],
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
export function usePersonalizationContext() {
|
|
23
|
-
return useContext(PersonalizationContext)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
type PersonalizationProps = ObjectInputProps & {
|
|
27
|
-
personalizationFieldPluginConfig: Required<PersonalizationFieldPluginConfig>
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function PersonalizationProvider(props: PersonalizationProps) {
|
|
31
|
-
const {personalizationFieldPluginConfig} = props
|
|
32
|
-
|
|
33
|
-
const client = useClient({apiVersion: personalizationFieldPluginConfig.apiVersion})
|
|
34
|
-
const workspace = useWorkspace()
|
|
35
|
-
|
|
36
|
-
// Fetch or return experiments
|
|
37
|
-
const segments = Array.isArray(personalizationFieldPluginConfig.segments)
|
|
38
|
-
? personalizationFieldPluginConfig.segments
|
|
39
|
-
: suspend(
|
|
40
|
-
// eslint-disable-next-line require-await
|
|
41
|
-
async () => {
|
|
42
|
-
if (typeof personalizationFieldPluginConfig.segments === 'function') {
|
|
43
|
-
return personalizationFieldPluginConfig.segments(client)
|
|
44
|
-
}
|
|
45
|
-
return personalizationFieldPluginConfig.segments
|
|
46
|
-
},
|
|
47
|
-
[workspace],
|
|
48
|
-
{equal},
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
const context = useMemo(
|
|
52
|
-
() => ({...personalizationFieldPluginConfig, segments}),
|
|
53
|
-
[personalizationFieldPluginConfig, segments],
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
return (
|
|
57
|
-
<PersonalizationContext.Provider value={context}>
|
|
58
|
-
{props.renderDefault(props)}
|
|
59
|
-
</PersonalizationContext.Provider>
|
|
60
|
-
)
|
|
61
|
-
}
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import {CloseIcon} from '@sanity/icons'
|
|
2
|
-
import React, {useCallback, useMemo} from 'react'
|
|
3
|
-
import {IoMdPeople} from 'react-icons/io'
|
|
4
|
-
import {
|
|
5
|
-
defineDocumentFieldAction,
|
|
6
|
-
DocumentFieldActionItem,
|
|
7
|
-
DocumentFieldActionProps,
|
|
8
|
-
FormPatch,
|
|
9
|
-
ObjectFieldProps,
|
|
10
|
-
PatchEvent,
|
|
11
|
-
set,
|
|
12
|
-
unset,
|
|
13
|
-
} from 'sanity'
|
|
14
|
-
|
|
15
|
-
import {clearChildrenGroups} from '../../utils/clearChildGroups'
|
|
16
|
-
|
|
17
|
-
type PatchStuff = {onChange: (patch: FormPatch | FormPatch[] | PatchEvent) => void; inputId: string}
|
|
18
|
-
|
|
19
|
-
const useAddExperimentAction = (
|
|
20
|
-
props: DocumentFieldActionProps &
|
|
21
|
-
PatchStuff & {personalizationNameOverride: string; active: boolean},
|
|
22
|
-
): DocumentFieldActionItem => {
|
|
23
|
-
const {onChange, active, personalizationNameOverride} = props
|
|
24
|
-
|
|
25
|
-
const handleAddAction = useCallback(() => {
|
|
26
|
-
onChange([set(!active, ['active'])])
|
|
27
|
-
}, [onChange, active])
|
|
28
|
-
|
|
29
|
-
return {
|
|
30
|
-
title: `Add ${personalizationNameOverride}`,
|
|
31
|
-
type: 'action',
|
|
32
|
-
icon: IoMdPeople,
|
|
33
|
-
onAction: handleAddAction,
|
|
34
|
-
renderAsButton: true,
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const useRemoveExperimentAction = (
|
|
39
|
-
props: DocumentFieldActionProps &
|
|
40
|
-
PatchStuff & {
|
|
41
|
-
personalizationNameOverride: string
|
|
42
|
-
active: boolean
|
|
43
|
-
segmentNameOverride: string
|
|
44
|
-
},
|
|
45
|
-
): DocumentFieldActionItem => {
|
|
46
|
-
const {onChange, active, personalizationNameOverride, segmentNameOverride} = props
|
|
47
|
-
const handleClearAction = useCallback(() => {
|
|
48
|
-
const activeId = ['active']
|
|
49
|
-
const segments = [`${segmentNameOverride}s`]
|
|
50
|
-
onChange([set(!active, activeId), unset(segments)])
|
|
51
|
-
}, [onChange, active, segmentNameOverride])
|
|
52
|
-
|
|
53
|
-
return {
|
|
54
|
-
title: `Remove ${personalizationNameOverride}`,
|
|
55
|
-
type: 'action',
|
|
56
|
-
icon: CloseIcon,
|
|
57
|
-
onAction: handleClearAction,
|
|
58
|
-
renderAsButton: true,
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const createActions = ({
|
|
63
|
-
onChange,
|
|
64
|
-
inputId,
|
|
65
|
-
active,
|
|
66
|
-
personalizationNameOverride,
|
|
67
|
-
segmentNameOverride,
|
|
68
|
-
}: PatchStuff & {
|
|
69
|
-
active?: boolean
|
|
70
|
-
personalizationNameOverride: string
|
|
71
|
-
segmentNameOverride: string
|
|
72
|
-
}) => {
|
|
73
|
-
const removeAction = defineDocumentFieldAction({
|
|
74
|
-
name: `Remove ${personalizationNameOverride}`,
|
|
75
|
-
useAction: (props) =>
|
|
76
|
-
useRemoveExperimentAction({
|
|
77
|
-
...props,
|
|
78
|
-
active: true,
|
|
79
|
-
onChange,
|
|
80
|
-
inputId,
|
|
81
|
-
personalizationNameOverride,
|
|
82
|
-
segmentNameOverride,
|
|
83
|
-
}),
|
|
84
|
-
})
|
|
85
|
-
const addAction = defineDocumentFieldAction({
|
|
86
|
-
name: `Add ${personalizationNameOverride}`,
|
|
87
|
-
useAction: (props) =>
|
|
88
|
-
useAddExperimentAction({
|
|
89
|
-
...props,
|
|
90
|
-
active: false,
|
|
91
|
-
onChange,
|
|
92
|
-
inputId,
|
|
93
|
-
personalizationNameOverride,
|
|
94
|
-
}),
|
|
95
|
-
})
|
|
96
|
-
return active ? removeAction : addAction
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export const Field = (
|
|
100
|
-
props: ObjectFieldProps & {
|
|
101
|
-
personalizationNameOverride: string
|
|
102
|
-
segmentNameOverride: string
|
|
103
|
-
},
|
|
104
|
-
): React.ReactElement => {
|
|
105
|
-
const {onChange} = props.inputProps
|
|
106
|
-
const {inputId, personalizationNameOverride, segmentNameOverride} = props
|
|
107
|
-
const active = props.value?.active as boolean | undefined
|
|
108
|
-
|
|
109
|
-
const actionProps = useMemo(
|
|
110
|
-
() => ({
|
|
111
|
-
onChange,
|
|
112
|
-
inputId,
|
|
113
|
-
active,
|
|
114
|
-
personalizationNameOverride,
|
|
115
|
-
segmentNameOverride,
|
|
116
|
-
}),
|
|
117
|
-
[onChange, inputId, active, personalizationNameOverride, segmentNameOverride],
|
|
118
|
-
)
|
|
119
|
-
|
|
120
|
-
const memoizedActions = useMemo(() => {
|
|
121
|
-
const oldActions = props.actions || []
|
|
122
|
-
return [createActions(actionProps), ...oldActions]
|
|
123
|
-
}, [actionProps, props.actions])
|
|
124
|
-
|
|
125
|
-
const enhancedProps = useMemo(() => {
|
|
126
|
-
const propsWithClearedGroups = clearChildrenGroups(props)
|
|
127
|
-
return {
|
|
128
|
-
...propsWithClearedGroups,
|
|
129
|
-
actions: memoizedActions,
|
|
130
|
-
}
|
|
131
|
-
}, [props, memoizedActions])
|
|
132
|
-
|
|
133
|
-
return props.renderDefault(enhancedProps)
|
|
134
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import {Button, Inline, Stack} from '@sanity/ui'
|
|
2
|
-
import {ObjectInputProps, set, useFormValue} from 'sanity'
|
|
3
|
-
|
|
4
|
-
export const SegmentInput = (props: ObjectInputProps) => {
|
|
5
|
-
const personalizationPath = props.path.slice(0, -2)
|
|
6
|
-
const defaultValue = useFormValue([...personalizationPath, 'default'])
|
|
7
|
-
const handleClick = () => {
|
|
8
|
-
props.onChange(set(defaultValue, ['value']))
|
|
9
|
-
}
|
|
10
|
-
return (
|
|
11
|
-
<Stack space={3}>
|
|
12
|
-
{props.renderDefault(props)}
|
|
13
|
-
|
|
14
|
-
<Inline space={1}>
|
|
15
|
-
<Button text="Copy default" mode="ghost" onClick={() => handleClick()} />
|
|
16
|
-
</Inline>
|
|
17
|
-
</Stack>
|
|
18
|
-
)
|
|
19
|
-
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import {useEffect, useState} from 'react'
|
|
2
|
-
import {
|
|
3
|
-
isImage,
|
|
4
|
-
isReference,
|
|
5
|
-
ObjectSchemaType,
|
|
6
|
-
PreviewProps,
|
|
7
|
-
ReferenceSchemaType,
|
|
8
|
-
useClient,
|
|
9
|
-
} from 'sanity'
|
|
10
|
-
|
|
11
|
-
import {VariantPreviewProps} from '../../types'
|
|
12
|
-
import {usePersonalizationContext} from './Context'
|
|
13
|
-
|
|
14
|
-
export const SegmentPreview = (props: PreviewProps) => {
|
|
15
|
-
const [subtitle, setSubtitle] = useState<string | undefined>(undefined)
|
|
16
|
-
const [title, setTitle] = useState<string | undefined>(undefined)
|
|
17
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
18
|
-
const [media, setMedia] = useState<any>(undefined)
|
|
19
|
-
const client = useClient({apiVersion: '2025-01-01'})
|
|
20
|
-
const {segments} = usePersonalizationContext()
|
|
21
|
-
|
|
22
|
-
const {segment, value} = props as VariantPreviewProps
|
|
23
|
-
|
|
24
|
-
const selectedSegment = segments.find((segmentItem) => {
|
|
25
|
-
return segmentItem.id === segment
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
useEffect(() => {
|
|
29
|
-
const getSubtitle = async () => {
|
|
30
|
-
setTitle(`${selectedSegment?.label}`)
|
|
31
|
-
if (typeof value === 'string') {
|
|
32
|
-
return setSubtitle(value)
|
|
33
|
-
}
|
|
34
|
-
if (isReference(value)) {
|
|
35
|
-
const doc = await client.getDocument(value._ref)
|
|
36
|
-
const type = props.schemaType as ObjectSchemaType
|
|
37
|
-
const valueField = type.fields.find((field) => field.name === 'value') as ObjectSchemaType
|
|
38
|
-
const referenceField = valueField?.type as ReferenceSchemaType
|
|
39
|
-
const referenceType = referenceField.to.find((field) => field.type?.name === doc?._type)
|
|
40
|
-
|
|
41
|
-
const selectFields = {} as Record<string, unknown>
|
|
42
|
-
const previewFields = referenceType?.preview?.select || {}
|
|
43
|
-
Object.keys(previewFields).forEach((key) => {
|
|
44
|
-
const valueKey = referenceType?.preview?.select?.[key]
|
|
45
|
-
selectFields[key] =
|
|
46
|
-
valueKey && doc
|
|
47
|
-
? valueKey?.split('.').reduce((acc, index) => acc[index], doc)
|
|
48
|
-
: undefined
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
const previewContent = referenceType?.preview?.prepare?.(selectFields)
|
|
52
|
-
setMedia(previewContent?.media || selectFields.media)
|
|
53
|
-
return setSubtitle(previewContent?.title || (selectFields?.title as string))
|
|
54
|
-
}
|
|
55
|
-
if (isImage(value)) {
|
|
56
|
-
setMedia(value)
|
|
57
|
-
}
|
|
58
|
-
return ''
|
|
59
|
-
}
|
|
60
|
-
getSubtitle()
|
|
61
|
-
}, [value, client, selectedSegment?.label, props.schemaType])
|
|
62
|
-
|
|
63
|
-
const previewProps = {
|
|
64
|
-
...props,
|
|
65
|
-
title: title,
|
|
66
|
-
subtitle: subtitle,
|
|
67
|
-
media: media,
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return props.renderDefault(previewProps)
|
|
71
|
-
}
|
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ArrayOfObjectsInputProps,
|
|
3
|
-
defineField,
|
|
4
|
-
definePlugin,
|
|
5
|
-
defineType,
|
|
6
|
-
FieldDefinition,
|
|
7
|
-
isObjectInputProps,
|
|
8
|
-
} from 'sanity'
|
|
9
|
-
|
|
10
|
-
import {ArrayItem} from './components/ArrayItem'
|
|
11
|
-
import {
|
|
12
|
-
ArrayInput,
|
|
13
|
-
CONFIG_DEFAULT,
|
|
14
|
-
Field,
|
|
15
|
-
PersonalizationProvider,
|
|
16
|
-
SegmentInput,
|
|
17
|
-
SegmentPreview,
|
|
18
|
-
} from './components/personalization'
|
|
19
|
-
import {PersonalizationFieldPluginConfig} from './types'
|
|
20
|
-
import {flattenSchemaType} from './utils/flattenSchemaType'
|
|
21
|
-
|
|
22
|
-
const createPersonalizationType = ({
|
|
23
|
-
field,
|
|
24
|
-
personalizationNameOverride,
|
|
25
|
-
segmentNameOverride,
|
|
26
|
-
segmentId,
|
|
27
|
-
segmentArrayName,
|
|
28
|
-
}: {
|
|
29
|
-
field: string | FieldDefinition
|
|
30
|
-
personalizationNameOverride: string
|
|
31
|
-
segmentNameOverride: string
|
|
32
|
-
segmentId: string
|
|
33
|
-
segmentArrayName: string
|
|
34
|
-
}) => {
|
|
35
|
-
const typeName = typeof field === `string` ? field : field.name
|
|
36
|
-
const usedName = String(typeName[0]).toUpperCase() + String(typeName).slice(1)
|
|
37
|
-
const segmentName = `${segmentNameOverride}${usedName}`
|
|
38
|
-
|
|
39
|
-
return defineType({
|
|
40
|
-
name: `${personalizationNameOverride}${usedName}`,
|
|
41
|
-
type: 'object',
|
|
42
|
-
groups: [
|
|
43
|
-
{
|
|
44
|
-
name: 'default',
|
|
45
|
-
title: 'Default',
|
|
46
|
-
hidden: ({parent}) => {
|
|
47
|
-
return !Array.isArray(parent)
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
name: 'personalization',
|
|
52
|
-
title: 'Personalization options',
|
|
53
|
-
hidden: ({parent}) => {
|
|
54
|
-
return !Array.isArray(parent)
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
name: 'all-fields',
|
|
59
|
-
title: 'All fields',
|
|
60
|
-
hidden: ({parent}) => {
|
|
61
|
-
return Array.isArray(parent)
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
],
|
|
65
|
-
components: {
|
|
66
|
-
field: (props) => (
|
|
67
|
-
<Field
|
|
68
|
-
{...props}
|
|
69
|
-
personalizationNameOverride={personalizationNameOverride}
|
|
70
|
-
segmentNameOverride={segmentNameOverride}
|
|
71
|
-
/>
|
|
72
|
-
),
|
|
73
|
-
item: ArrayItem,
|
|
74
|
-
},
|
|
75
|
-
fields: [
|
|
76
|
-
typeof field === `string`
|
|
77
|
-
? // Define a simple field if all we have is the name as a string
|
|
78
|
-
defineField({
|
|
79
|
-
name: 'default',
|
|
80
|
-
type: field,
|
|
81
|
-
group: 'default',
|
|
82
|
-
})
|
|
83
|
-
: // Pass in the configured options, but overwrite the name
|
|
84
|
-
{
|
|
85
|
-
...field,
|
|
86
|
-
name: 'default',
|
|
87
|
-
group: 'default',
|
|
88
|
-
},
|
|
89
|
-
defineField({
|
|
90
|
-
name: 'active',
|
|
91
|
-
type: 'boolean',
|
|
92
|
-
hidden: true,
|
|
93
|
-
initialValue: false,
|
|
94
|
-
}),
|
|
95
|
-
defineField({
|
|
96
|
-
name: segmentArrayName,
|
|
97
|
-
type: 'array',
|
|
98
|
-
hidden: ({parent}) => {
|
|
99
|
-
return !parent?.active
|
|
100
|
-
},
|
|
101
|
-
group: 'personalization',
|
|
102
|
-
components: {
|
|
103
|
-
input: (props: ArrayOfObjectsInputProps) => (
|
|
104
|
-
<ArrayInput {...props} segmentName={segmentName} segmentId={segmentId} />
|
|
105
|
-
),
|
|
106
|
-
},
|
|
107
|
-
of: [
|
|
108
|
-
defineField({
|
|
109
|
-
name: segmentName,
|
|
110
|
-
type: segmentName,
|
|
111
|
-
}),
|
|
112
|
-
],
|
|
113
|
-
}),
|
|
114
|
-
],
|
|
115
|
-
preview: {
|
|
116
|
-
select: {
|
|
117
|
-
base: 'default',
|
|
118
|
-
},
|
|
119
|
-
prepare: ({base}) => {
|
|
120
|
-
const title = base?.title || base?.name || typeof base === 'string' ? base : ''
|
|
121
|
-
const media = base?.image || base?.photo || base?.media || ''
|
|
122
|
-
return {
|
|
123
|
-
title: title,
|
|
124
|
-
media,
|
|
125
|
-
}
|
|
126
|
-
},
|
|
127
|
-
},
|
|
128
|
-
})
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const createSegmentType = ({
|
|
132
|
-
field,
|
|
133
|
-
segmentNameOverride,
|
|
134
|
-
segmentId,
|
|
135
|
-
}: {
|
|
136
|
-
field: string | FieldDefinition
|
|
137
|
-
segmentNameOverride: string
|
|
138
|
-
segmentId: string
|
|
139
|
-
}) => {
|
|
140
|
-
const typeName = typeof field === `string` ? field : field.name
|
|
141
|
-
const usedName = String(typeName[0]).toUpperCase() + String(typeName).slice(1)
|
|
142
|
-
return defineType({
|
|
143
|
-
name: `${segmentNameOverride}${usedName}`,
|
|
144
|
-
title: `${segmentNameOverride} array ${usedName}`,
|
|
145
|
-
type: 'object',
|
|
146
|
-
components: {
|
|
147
|
-
preview: SegmentPreview,
|
|
148
|
-
input: SegmentInput,
|
|
149
|
-
},
|
|
150
|
-
fields: [
|
|
151
|
-
{
|
|
152
|
-
type: 'string',
|
|
153
|
-
name: segmentId,
|
|
154
|
-
readOnly: true,
|
|
155
|
-
},
|
|
156
|
-
typeof field === `string`
|
|
157
|
-
? // Define a simple field if all we have is the name as a string
|
|
158
|
-
defineField({
|
|
159
|
-
name: 'value',
|
|
160
|
-
type: field,
|
|
161
|
-
// hidden: ({parent}) => !parent?.[`${objectNameOverride}Id`],
|
|
162
|
-
})
|
|
163
|
-
: // Pass in the configured options, but overwrite the name
|
|
164
|
-
{
|
|
165
|
-
...field,
|
|
166
|
-
name: 'value',
|
|
167
|
-
// hidden: ({parent}) => !parent?.[`${objectNameOverride}Id`],
|
|
168
|
-
},
|
|
169
|
-
],
|
|
170
|
-
preview: {
|
|
171
|
-
select: {
|
|
172
|
-
segment: segmentId,
|
|
173
|
-
value: 'value',
|
|
174
|
-
},
|
|
175
|
-
},
|
|
176
|
-
})
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const fieldSchema = ({
|
|
180
|
-
fields,
|
|
181
|
-
personalizationNameOverride,
|
|
182
|
-
segmentNameOverride,
|
|
183
|
-
segmentId,
|
|
184
|
-
segmentArrayName,
|
|
185
|
-
}: Required<Omit<PersonalizationFieldPluginConfig, 'apiVersion' | 'segments'>>) => {
|
|
186
|
-
return [
|
|
187
|
-
...fields.map((field) => createSegmentType({field, segmentNameOverride, segmentId})),
|
|
188
|
-
...fields.map((field) =>
|
|
189
|
-
createPersonalizationType({
|
|
190
|
-
field,
|
|
191
|
-
personalizationNameOverride,
|
|
192
|
-
segmentNameOverride,
|
|
193
|
-
segmentId,
|
|
194
|
-
segmentArrayName,
|
|
195
|
-
}),
|
|
196
|
-
),
|
|
197
|
-
]
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
export const fieldLevelPersonalization = definePlugin<PersonalizationFieldPluginConfig>(
|
|
201
|
-
(config) => {
|
|
202
|
-
const pluginConfig = {...CONFIG_DEFAULT, ...config}
|
|
203
|
-
const {fields, personalizationNameOverride, segmentNameOverride} = pluginConfig
|
|
204
|
-
|
|
205
|
-
const segmentArrayName = `${segmentNameOverride}s`
|
|
206
|
-
const segmentId = `${segmentNameOverride}Id`
|
|
207
|
-
|
|
208
|
-
const fieldSchemaConfig = fieldSchema({
|
|
209
|
-
fields,
|
|
210
|
-
personalizationNameOverride,
|
|
211
|
-
segmentNameOverride,
|
|
212
|
-
segmentId,
|
|
213
|
-
segmentArrayName,
|
|
214
|
-
})
|
|
215
|
-
return {
|
|
216
|
-
name: 'sanity-personalistaion-plugin-field-level-personalization',
|
|
217
|
-
schema: {
|
|
218
|
-
types: fieldSchemaConfig,
|
|
219
|
-
},
|
|
220
|
-
form: {
|
|
221
|
-
components: {
|
|
222
|
-
input: (props) => {
|
|
223
|
-
const isRootInput = props.id === 'root' && isObjectInputProps(props)
|
|
224
|
-
|
|
225
|
-
if (!isRootInput) {
|
|
226
|
-
return props.renderDefault(props)
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const flatFields = flattenSchemaType(props.schemaType)
|
|
230
|
-
const hasPersonalization = flatFields.some(
|
|
231
|
-
(field) =>
|
|
232
|
-
field.type.name.startsWith(personalizationNameOverride) ||
|
|
233
|
-
field.name.startsWith(personalizationNameOverride),
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
if (!hasPersonalization) {
|
|
237
|
-
return props.renderDefault(props)
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const providerProps = {
|
|
241
|
-
...props,
|
|
242
|
-
personalizationFieldPluginConfig: {
|
|
243
|
-
...pluginConfig,
|
|
244
|
-
segmentId,
|
|
245
|
-
segmentArrayName,
|
|
246
|
-
},
|
|
247
|
-
}
|
|
248
|
-
return PersonalizationProvider(providerProps)
|
|
249
|
-
},
|
|
250
|
-
},
|
|
251
|
-
},
|
|
252
|
-
}
|
|
253
|
-
},
|
|
254
|
-
)
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import {ObjectFieldProps} from 'sanity'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Safely updates deeply nested children props to clear groups array
|
|
5
|
-
* This prevents field grouping UI conflicts in personalization mode
|
|
6
|
-
*/
|
|
7
|
-
export const clearChildrenGroups = (props: ObjectFieldProps): ObjectFieldProps => {
|
|
8
|
-
// Type assertion is needed here because Sanity's ObjectFieldProps children
|
|
9
|
-
// typing doesn't account for the nested structure we need to manipulate
|
|
10
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
-
const children = props.children as any
|
|
12
|
-
|
|
13
|
-
if (!children || typeof children !== 'object' || !children.props) {
|
|
14
|
-
return props
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return {
|
|
18
|
-
...props,
|
|
19
|
-
children: {
|
|
20
|
-
...children,
|
|
21
|
-
props: {
|
|
22
|
-
...children.props,
|
|
23
|
-
children: {
|
|
24
|
-
...children.props.children,
|
|
25
|
-
props: {
|
|
26
|
-
...children.props.children?.props,
|
|
27
|
-
groups: [],
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
}
|
|
33
|
-
}
|
|
File without changes
|