@od-labs/payloadcms-dynamic-value-richtext 1.0.2 → 1.1.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/README.md +128 -96
- package/dist/exports/jsx.d.ts +9 -0
- package/dist/exports/jsx.js +73 -0
- package/dist/exports/jsx.js.map +1 -0
- package/dist/exports/react.d.ts +19 -0
- package/dist/exports/react.js +22 -0
- package/dist/exports/react.js.map +1 -0
- package/dist/exports/rsc.d.ts +1 -0
- package/dist/exports/rsc.js +3 -0
- package/dist/exports/rsc.js.map +1 -0
- package/dist/features/DynamicValue/feature.client.d.ts +4 -8
- package/dist/features/DynamicValue/feature.client.js +187 -89
- package/dist/features/DynamicValue/feature.client.js.map +1 -1
- package/dist/features/DynamicValue/feature.server.d.ts +16 -5
- package/dist/features/DynamicValue/feature.server.js +78 -39
- package/dist/features/DynamicValue/feature.server.js.map +1 -1
- package/dist/features/DynamicValue/types.d.ts +23 -23
- package/dist/features/DynamicValue/types.js.map +1 -1
- package/dist/icons/dynamicValue/bold.d.ts +2 -0
- package/dist/icons/dynamicValue/bold.js +6 -0
- package/dist/icons/dynamicValue/bold.js.map +1 -0
- package/dist/icons/dynamicValue/index.d.ts +3 -0
- package/dist/icons/dynamicValue/index.js +14 -0
- package/dist/icons/dynamicValue/index.js.map +1 -0
- package/dist/icons/dynamicValue/italic.d.ts +2 -0
- package/dist/icons/dynamicValue/italic.js +6 -0
- package/dist/icons/dynamicValue/italic.js.map +1 -0
- package/dist/icons/dynamicValue/link.d.ts +2 -0
- package/dist/icons/dynamicValue/link.js +6 -0
- package/dist/icons/dynamicValue/link.js.map +1 -0
- package/dist/icons/dynamicValue/strikethrough.d.ts +2 -0
- package/dist/icons/dynamicValue/strikethrough.js +6 -0
- package/dist/icons/dynamicValue/strikethrough.js.map +1 -0
- package/dist/icons/dynamicValue/types.d.ts +5 -0
- package/dist/icons/dynamicValue/types.js +3 -0
- package/dist/icons/dynamicValue/types.js.map +1 -0
- package/dist/icons/dynamicValue/underline.d.ts +2 -0
- package/dist/icons/dynamicValue/underline.js +6 -0
- package/dist/icons/dynamicValue/underline.js.map +1 -0
- package/dist/index.d.ts +6 -6
- package/dist/index.js +75 -3
- package/dist/index.js.map +1 -1
- package/dist/nodes/DynamicValueNode/index.d.ts +13 -13
- package/dist/nodes/DynamicValueNode/index.js +155 -83
- package/dist/nodes/DynamicValueNode/index.js.map +1 -1
- package/package.json +41 -1
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { createClientFeature } from '@payloadcms/richtext-lexical/client';
|
|
4
|
-
import { $insertNodes } from '@payloadcms/richtext-lexical/lexical';
|
|
4
|
+
import { $createTextNode, $insertNodes } from '@payloadcms/richtext-lexical/lexical';
|
|
5
5
|
import { useLexicalComposerContext } from '@payloadcms/richtext-lexical/lexical/react/LexicalComposerContext';
|
|
6
6
|
import { LexicalTypeaheadMenuPlugin } from '@payloadcms/richtext-lexical/lexical/react/LexicalTypeaheadMenuPlugin';
|
|
7
|
+
import { useForm } from '@payloadcms/ui';
|
|
7
8
|
import { AtSign, Hash, Variable } from 'lucide-react';
|
|
8
9
|
import React, { useCallback, useMemo, useState } from 'react';
|
|
9
10
|
import * as ReactDOM from 'react-dom';
|
|
10
11
|
import { $createDynamicValueNode, DynamicValueNode } from '../../nodes/DynamicValueNode/index.js';
|
|
12
|
+
export function $isDynamicValueNode(node) {
|
|
13
|
+
return node?.getType?.() === 'dynamic-value';
|
|
14
|
+
}
|
|
11
15
|
function useTriggerMatch(trigger) {
|
|
12
16
|
return useCallback((text)=>{
|
|
13
17
|
const triggerIndex = text.lastIndexOf(trigger);
|
|
@@ -31,98 +35,109 @@ function useTriggerMatch(trigger) {
|
|
|
31
35
|
trigger
|
|
32
36
|
]);
|
|
33
37
|
}
|
|
38
|
+
const SelectIcon = (trigger)=>{
|
|
39
|
+
const IconToUse = trigger === '@' ? AtSign : trigger === '#' ? Hash : Variable;
|
|
40
|
+
return ()=>/*#__PURE__*/ _jsx("div", {
|
|
41
|
+
style: {
|
|
42
|
+
alignItems: 'center',
|
|
43
|
+
display: 'flex',
|
|
44
|
+
justifyContent: 'center',
|
|
45
|
+
opacity: 0.8
|
|
46
|
+
},
|
|
47
|
+
children: /*#__PURE__*/ _jsx(IconToUse, {
|
|
48
|
+
className: "icon",
|
|
49
|
+
focusable: "false",
|
|
50
|
+
size: 14,
|
|
51
|
+
strokeWidth: 1.5,
|
|
52
|
+
style: {
|
|
53
|
+
color: 'currentColor'
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
});
|
|
57
|
+
};
|
|
34
58
|
const DynamicValuePlugin = ({ anchorElem, options: allOptions, trigger })=>{
|
|
35
59
|
const [editor] = useLexicalComposerContext();
|
|
60
|
+
const { fields } = useForm();
|
|
36
61
|
const [queryString, setQueryString] = useState(null);
|
|
37
62
|
const checkForTriggerMatch = useTriggerMatch(trigger);
|
|
38
63
|
const options = useMemo(()=>{
|
|
39
64
|
const searchString = queryString?.toLowerCase() || '';
|
|
40
|
-
return (allOptions || []).filter((option)=>
|
|
65
|
+
return (allOptions || []).filter((option)=>{
|
|
66
|
+
const val = fields[option.value]?.value;
|
|
67
|
+
const hasValue = val !== undefined && val !== null && val !== '';
|
|
68
|
+
return hasValue && (option.label.toLowerCase().includes(searchString) || option.value.toLowerCase().includes(searchString));
|
|
69
|
+
}).map((opt)=>({
|
|
41
70
|
...opt,
|
|
42
71
|
key: opt.value
|
|
43
72
|
}));
|
|
44
73
|
}, [
|
|
45
74
|
queryString,
|
|
46
|
-
allOptions
|
|
75
|
+
allOptions,
|
|
76
|
+
fields
|
|
47
77
|
]);
|
|
48
|
-
const TypeaheadMenu = LexicalTypeaheadMenuPlugin;
|
|
49
78
|
const IconToUse = trigger === '@' ? AtSign : trigger === '#' ? Hash : Variable;
|
|
50
|
-
return /*#__PURE__*/ _jsx(
|
|
51
|
-
|
|
79
|
+
return /*#__PURE__*/ _jsx(LexicalTypeaheadMenuPlugin, {
|
|
80
|
+
...{
|
|
81
|
+
anchorElem: anchorElem || (typeof document !== 'undefined' ? document.body : undefined)
|
|
82
|
+
},
|
|
52
83
|
menuRenderFn: (anchorElementRef, { selectedIndex, selectOptionAndCleanUp, setHighlightedIndex })=>{
|
|
53
84
|
const anchor = anchorElementRef?.current || anchorElem || (typeof document !== 'undefined' ? document.body : null);
|
|
54
85
|
if (!anchor || !options.length) {
|
|
55
86
|
return null;
|
|
56
87
|
}
|
|
57
|
-
return /*#__PURE__*/ ReactDOM.createPortal(/*#__PURE__*/
|
|
58
|
-
className: "
|
|
88
|
+
return /*#__PURE__*/ ReactDOM.createPortal(/*#__PURE__*/ _jsx("div", {
|
|
89
|
+
className: "slash-menu-popup",
|
|
59
90
|
style: {
|
|
60
|
-
|
|
61
|
-
border: '1px solid var(--theme-elevation-250)',
|
|
62
|
-
borderRadius: '8px',
|
|
63
|
-
boxShadow: '0 8px 32px rgba(0,0,0,0.2)',
|
|
91
|
+
maxHeight: '400px',
|
|
64
92
|
minWidth: '260px',
|
|
65
|
-
|
|
66
|
-
padding: '
|
|
67
|
-
zIndex: 100000
|
|
93
|
+
overflowY: 'auto',
|
|
94
|
+
padding: '8px'
|
|
68
95
|
},
|
|
69
|
-
children:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
marginBottom: '4px',
|
|
78
|
-
opacity: 0.6,
|
|
79
|
-
padding: '10px 14px 6px',
|
|
80
|
-
textTransform: 'uppercase'
|
|
81
|
-
},
|
|
82
|
-
children: "Merge Fields"
|
|
83
|
-
}),
|
|
84
|
-
/*#__PURE__*/ _jsx("div", {
|
|
85
|
-
style: {
|
|
86
|
-
maxHeight: '300px',
|
|
87
|
-
overflowY: 'auto'
|
|
88
|
-
},
|
|
89
|
-
children: options.map((option, index)=>{
|
|
96
|
+
children: /*#__PURE__*/ _jsxs("div", {
|
|
97
|
+
className: "slash-menu-popup__group",
|
|
98
|
+
children: [
|
|
99
|
+
/*#__PURE__*/ _jsx("div", {
|
|
100
|
+
className: "slash-menu-popup__group-title",
|
|
101
|
+
children: "Dynamic Values"
|
|
102
|
+
}),
|
|
103
|
+
options.map((option, index)=>{
|
|
90
104
|
const isSelected = selectedIndex === index;
|
|
91
105
|
return /*#__PURE__*/ _jsxs("button", {
|
|
106
|
+
"aria-selected": isSelected,
|
|
107
|
+
className: `slash-menu-popup__item ${isSelected ? 'slash-menu-popup__item--selected' : ''}`,
|
|
92
108
|
onClick: ()=>selectOptionAndCleanUp(option),
|
|
93
109
|
onMouseEnter: ()=>setHighlightedIndex(index),
|
|
110
|
+
role: "option",
|
|
94
111
|
style: {
|
|
95
|
-
|
|
96
|
-
background: isSelected ? 'var(--theme-primary)' : 'transparent',
|
|
97
|
-
border: 'none',
|
|
98
|
-
borderRadius: '6px',
|
|
99
|
-
color: isSelected ? 'white' : 'var(--theme-text)',
|
|
100
|
-
cursor: 'pointer',
|
|
101
|
-
display: 'flex',
|
|
102
|
-
gap: '10px',
|
|
103
|
-
padding: '8px 12px',
|
|
104
|
-
textAlign: 'left',
|
|
105
|
-
transition: 'all 0.15s ease',
|
|
106
|
-
width: '100%'
|
|
112
|
+
minHeight: '50px'
|
|
107
113
|
},
|
|
114
|
+
tabIndex: -1,
|
|
108
115
|
type: "button",
|
|
109
116
|
children: [
|
|
110
117
|
/*#__PURE__*/ _jsx(IconToUse, {
|
|
111
|
-
|
|
118
|
+
className: "icon",
|
|
119
|
+
size: 20,
|
|
120
|
+
strokeWidth: 1.5,
|
|
112
121
|
style: {
|
|
113
|
-
|
|
122
|
+
flexShrink: 0
|
|
114
123
|
}
|
|
115
124
|
}),
|
|
116
|
-
/*#__PURE__*/ _jsxs("
|
|
125
|
+
/*#__PURE__*/ _jsxs("span", {
|
|
126
|
+
className: "slash-menu-popup__item-text",
|
|
117
127
|
style: {
|
|
128
|
+
alignItems: 'flex-start',
|
|
118
129
|
display: 'flex',
|
|
119
|
-
|
|
130
|
+
flex: '1',
|
|
131
|
+
flexDirection: 'column',
|
|
132
|
+
marginLeft: '8px'
|
|
120
133
|
},
|
|
121
134
|
children: [
|
|
122
135
|
/*#__PURE__*/ _jsx("span", {
|
|
136
|
+
className: "text",
|
|
123
137
|
style: {
|
|
124
138
|
fontSize: '13px',
|
|
125
|
-
fontWeight: 500
|
|
139
|
+
fontWeight: '500',
|
|
140
|
+
lineHeight: 1.2
|
|
126
141
|
},
|
|
127
142
|
children: option.label
|
|
128
143
|
}),
|
|
@@ -130,6 +145,8 @@ const DynamicValuePlugin = ({ anchorElem, options: allOptions, trigger })=>{
|
|
|
130
145
|
style: {
|
|
131
146
|
fontFamily: 'var(--font-mono)',
|
|
132
147
|
fontSize: '11px',
|
|
148
|
+
lineHeight: 1.2,
|
|
149
|
+
marginTop: '2px',
|
|
133
150
|
opacity: 0.5
|
|
134
151
|
},
|
|
135
152
|
children: option.value
|
|
@@ -139,8 +156,8 @@ const DynamicValuePlugin = ({ anchorElem, options: allOptions, trigger })=>{
|
|
|
139
156
|
]
|
|
140
157
|
}, option.value);
|
|
141
158
|
})
|
|
142
|
-
|
|
143
|
-
|
|
159
|
+
]
|
|
160
|
+
})
|
|
144
161
|
}), anchor);
|
|
145
162
|
},
|
|
146
163
|
onQueryChange: setQueryString,
|
|
@@ -151,7 +168,8 @@ const DynamicValuePlugin = ({ anchorElem, options: allOptions, trigger })=>{
|
|
|
151
168
|
}
|
|
152
169
|
const node = $createDynamicValueNode(option.value, option.label);
|
|
153
170
|
$insertNodes([
|
|
154
|
-
node
|
|
171
|
+
node,
|
|
172
|
+
$createTextNode(' ')
|
|
155
173
|
]);
|
|
156
174
|
closeMenu();
|
|
157
175
|
});
|
|
@@ -160,10 +178,120 @@ const DynamicValuePlugin = ({ anchorElem, options: allOptions, trigger })=>{
|
|
|
160
178
|
triggerFn: checkForTriggerMatch
|
|
161
179
|
});
|
|
162
180
|
};
|
|
163
|
-
|
|
181
|
+
const DropdownItemComponent = ({ editor, field, item, trigger })=>{
|
|
182
|
+
const { fields } = useForm();
|
|
183
|
+
const val = fields[field.value]?.value;
|
|
184
|
+
const hasValue = val !== undefined && val !== null && val !== '';
|
|
185
|
+
if (!hasValue) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
const IconToUse = trigger === '@' ? AtSign : trigger === '#' ? Hash : Variable;
|
|
189
|
+
return /*#__PURE__*/ _jsx("button", {
|
|
190
|
+
"aria-label": field.label,
|
|
191
|
+
className: "btn toolbar-popup__dropdown-item btn--icon btn--icon-style-none btn--size-medium btn--icon-position-left btn--style-none",
|
|
192
|
+
"data-item-key": field.value,
|
|
193
|
+
onClick: (e)=>{
|
|
194
|
+
e.preventDefault();
|
|
195
|
+
item.onSelect({
|
|
196
|
+
editor,
|
|
197
|
+
isActive: false
|
|
198
|
+
});
|
|
199
|
+
},
|
|
200
|
+
style: {
|
|
201
|
+
minHeight: '50px',
|
|
202
|
+
whiteSpace: 'nowrap'
|
|
203
|
+
},
|
|
204
|
+
title: field.label,
|
|
205
|
+
type: "button",
|
|
206
|
+
children: /*#__PURE__*/ _jsxs("span", {
|
|
207
|
+
className: "btn__content",
|
|
208
|
+
style: {
|
|
209
|
+
alignItems: 'center',
|
|
210
|
+
display: 'flex'
|
|
211
|
+
},
|
|
212
|
+
children: [
|
|
213
|
+
/*#__PURE__*/ _jsxs("span", {
|
|
214
|
+
className: "btn__label",
|
|
215
|
+
style: {
|
|
216
|
+
alignItems: 'flex-start',
|
|
217
|
+
display: 'flex',
|
|
218
|
+
flex: 1,
|
|
219
|
+
flexDirection: 'column',
|
|
220
|
+
marginLeft: '8px',
|
|
221
|
+
marginRight: '8px'
|
|
222
|
+
},
|
|
223
|
+
children: [
|
|
224
|
+
/*#__PURE__*/ _jsx("span", {
|
|
225
|
+
className: "text",
|
|
226
|
+
style: {
|
|
227
|
+
fontSize: '13px',
|
|
228
|
+
fontWeight: 500,
|
|
229
|
+
lineHeight: '1.2'
|
|
230
|
+
},
|
|
231
|
+
children: field.label
|
|
232
|
+
}),
|
|
233
|
+
/*#__PURE__*/ _jsx("span", {
|
|
234
|
+
style: {
|
|
235
|
+
fontFamily: 'var(--font-mono)',
|
|
236
|
+
fontSize: '11px',
|
|
237
|
+
lineHeight: '1.2',
|
|
238
|
+
marginTop: '2px',
|
|
239
|
+
opacity: 0.5
|
|
240
|
+
},
|
|
241
|
+
children: field.value
|
|
242
|
+
})
|
|
243
|
+
]
|
|
244
|
+
}),
|
|
245
|
+
/*#__PURE__*/ _jsx("span", {
|
|
246
|
+
className: "btn__icon",
|
|
247
|
+
style: {
|
|
248
|
+
flexShrink: 0
|
|
249
|
+
},
|
|
250
|
+
children: /*#__PURE__*/ _jsx(IconToUse, {
|
|
251
|
+
size: 20,
|
|
252
|
+
strokeWidth: 1.5,
|
|
253
|
+
style: {
|
|
254
|
+
opacity: 0.4
|
|
255
|
+
}
|
|
256
|
+
})
|
|
257
|
+
})
|
|
258
|
+
]
|
|
259
|
+
})
|
|
260
|
+
});
|
|
261
|
+
};
|
|
262
|
+
export const DynamicValueFeatureClient = createClientFeature((args)=>{
|
|
263
|
+
const props = args?.clientFeatureProps || args?.props || {};
|
|
164
264
|
const options = props?.options || [];
|
|
165
265
|
const trigger = props?.trigger || '@';
|
|
166
|
-
const
|
|
266
|
+
const groupItems = options.map((field)=>({
|
|
267
|
+
Component: ({ editor, item })=>/*#__PURE__*/ _jsx(DropdownItemComponent, {
|
|
268
|
+
editor: editor,
|
|
269
|
+
field: field,
|
|
270
|
+
item: item,
|
|
271
|
+
trigger: trigger
|
|
272
|
+
}),
|
|
273
|
+
isActive: ()=>false,
|
|
274
|
+
isEnabled: ()=>true,
|
|
275
|
+
key: `dv-item-${field.value}`,
|
|
276
|
+
label: field.label,
|
|
277
|
+
onSelect: ({ editor })=>{
|
|
278
|
+
editor.update(()=>{
|
|
279
|
+
const node = $createDynamicValueNode(field.value, field.label);
|
|
280
|
+
$insertNodes([
|
|
281
|
+
node,
|
|
282
|
+
$createTextNode(' ')
|
|
283
|
+
]);
|
|
284
|
+
});
|
|
285
|
+
},
|
|
286
|
+
order: 1
|
|
287
|
+
}));
|
|
288
|
+
const toolbarGroup = {
|
|
289
|
+
type: 'dropdown',
|
|
290
|
+
ChildComponent: SelectIcon(trigger),
|
|
291
|
+
items: groupItems,
|
|
292
|
+
key: 'dynamic-value-toolbar-group',
|
|
293
|
+
order: 100
|
|
294
|
+
};
|
|
167
295
|
return {
|
|
168
296
|
nodes: [
|
|
169
297
|
DynamicValueNode
|
|
@@ -181,37 +309,7 @@ export const DynamicValueFeatureClient = createClientFeature(({ props })=>{
|
|
|
181
309
|
sanitizedClientFeatureProps: props,
|
|
182
310
|
toolbarFixed: {
|
|
183
311
|
groups: [
|
|
184
|
-
|
|
185
|
-
type: 'dropdown',
|
|
186
|
-
ChildComponent: ()=>/*#__PURE__*/ _jsx("div", {
|
|
187
|
-
style: {
|
|
188
|
-
alignItems: 'center',
|
|
189
|
-
display: 'flex',
|
|
190
|
-
gap: '4px'
|
|
191
|
-
},
|
|
192
|
-
children: /*#__PURE__*/ _jsx(IconToUse, {
|
|
193
|
-
size: 14,
|
|
194
|
-
style: {
|
|
195
|
-
color: 'var(--theme-text)',
|
|
196
|
-
opacity: 0.7
|
|
197
|
-
}
|
|
198
|
-
})
|
|
199
|
-
}),
|
|
200
|
-
items: options.map((field)=>({
|
|
201
|
-
key: field.value,
|
|
202
|
-
label: field.label,
|
|
203
|
-
onSelect: ({ editor })=>{
|
|
204
|
-
editor.update(()=>{
|
|
205
|
-
const node = $createDynamicValueNode(field.value, field.label);
|
|
206
|
-
$insertNodes([
|
|
207
|
-
node
|
|
208
|
-
]);
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
})),
|
|
212
|
-
key: 'dynamicValue',
|
|
213
|
-
order: 99
|
|
214
|
-
}
|
|
312
|
+
toolbarGroup
|
|
215
313
|
]
|
|
216
314
|
}
|
|
217
315
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/features/DynamicValue/feature.client.tsx"],"sourcesContent":["'use client'\r\nimport { createClientFeature } from '@payloadcms/richtext-lexical/client'\r\nimport { $insertNodes } from '@payloadcms/richtext-lexical/lexical'\r\nimport { useLexicalComposerContext } from '@payloadcms/richtext-lexical/lexical/react/LexicalComposerContext'\r\nimport { LexicalTypeaheadMenuPlugin } from '@payloadcms/richtext-lexical/lexical/react/LexicalTypeaheadMenuPlugin'\r\nimport { AtSign, Hash, Variable } from 'lucide-react'\r\nimport React, { useCallback, useMemo, useState } from 'react'\r\nimport * as ReactDOM from 'react-dom'\r\n\r\nimport type { DynamicValueOption } from './types.js'\r\n\r\nimport { $createDynamicValueNode, DynamicValueNode } from '../../nodes/DynamicValueNode/index.js'\r\n\r\nfunction useTriggerMatch(trigger: string) {\r\n return useCallback(\r\n (text: string) => {\r\n const triggerIndex = text.lastIndexOf(trigger)\r\n if (triggerIndex === -1) {\r\n return null\r\n }\r\n\r\n const charBeforeTrigger = triggerIndex > 0 ? text[triggerIndex - 1] : null\r\n if (charBeforeTrigger && !/[\\s(]/.test(charBeforeTrigger)) {\r\n return null\r\n }\r\n\r\n const matchingString = text.slice(triggerIndex + 1)\r\n if (/\\s/.test(matchingString)) {\r\n return null\r\n }\r\n\r\n return {\r\n leadOffset: triggerIndex,\r\n matchingString,\r\n replaceableString: trigger + matchingString,\r\n }\r\n },\r\n [trigger],\r\n )\r\n}\r\n\r\nconst DynamicValuePlugin: React.FC<{\r\n anchorElem?: HTMLElement\r\n options: DynamicValueOption[]\r\n trigger: string\r\n}> = ({ anchorElem, options: allOptions, trigger }) => {\r\n const [editor] = useLexicalComposerContext()\r\n const [queryString, setQueryString] = useState<null | string>(null)\r\n\r\n const checkForTriggerMatch = useTriggerMatch(trigger)\r\n\r\n const options = useMemo(() => {\r\n const searchString = queryString?.toLowerCase() || ''\r\n return (allOptions || [])\r\n .filter(\r\n (option: DynamicValueOption) =>\r\n option.label.toLowerCase().includes(searchString) ||\r\n option.value.toLowerCase().includes(searchString),\r\n )\r\n .map((opt: DynamicValueOption) => ({\r\n ...opt,\r\n key: opt.value,\r\n }))\r\n }, [queryString, allOptions])\r\n\r\n const TypeaheadMenu = LexicalTypeaheadMenuPlugin as any\r\n\r\n const IconToUse = trigger === '@' ? AtSign : trigger === '#' ? Hash : Variable\r\n\r\n return (\r\n <TypeaheadMenu\r\n anchorElem={anchorElem || (typeof document !== 'undefined' ? document.body : undefined)}\r\n menuRenderFn={(\r\n anchorElementRef: any,\r\n { selectedIndex, selectOptionAndCleanUp, setHighlightedIndex }: any,\r\n ) => {\r\n const anchor =\r\n anchorElementRef?.current ||\r\n anchorElem ||\r\n (typeof document !== 'undefined' ? document.body : null)\r\n if (!anchor || !options.length) {\r\n return null\r\n }\r\n\r\n return ReactDOM.createPortal(\r\n <div\r\n className=\"dynamic-value-popup\"\r\n style={{\r\n background: 'var(--theme-elevation-100)',\r\n border: '1px solid var(--theme-elevation-250)',\r\n borderRadius: '8px',\r\n boxShadow: '0 8px 32px rgba(0,0,0,0.2)',\r\n minWidth: '260px',\r\n overflow: 'hidden',\r\n padding: '4px',\r\n zIndex: 100000,\r\n }}\r\n >\r\n <div\r\n style={{\r\n borderBottom: '1px solid var(--theme-elevation-150)',\r\n color: 'var(--theme-text)',\r\n fontSize: '10px',\r\n fontWeight: 700,\r\n letterSpacing: '0.05em',\r\n marginBottom: '4px',\r\n opacity: 0.6,\r\n padding: '10px 14px 6px',\r\n textTransform: 'uppercase',\r\n }}\r\n >\r\n Merge Fields\r\n </div>\r\n <div style={{ maxHeight: '300px', overflowY: 'auto' }}>\r\n {options.map((option, index) => {\r\n const isSelected = selectedIndex === index\r\n return (\r\n <button\r\n key={option.value}\r\n onClick={() => selectOptionAndCleanUp(option)}\r\n onMouseEnter={() => setHighlightedIndex(index)}\r\n style={{\r\n alignItems: 'center',\r\n background: isSelected ? 'var(--theme-primary)' : 'transparent',\r\n border: 'none',\r\n borderRadius: '6px',\r\n color: isSelected ? 'white' : 'var(--theme-text)',\r\n cursor: 'pointer',\r\n display: 'flex',\r\n gap: '10px',\r\n padding: '8px 12px',\r\n textAlign: 'left',\r\n transition: 'all 0.15s ease',\r\n width: '100%',\r\n }}\r\n type=\"button\"\r\n >\r\n <IconToUse size={14} style={{ opacity: isSelected ? 1 : 0.4 }} />\r\n <div style={{ display: 'flex', flexDirection: 'column' }}>\r\n <span style={{ fontSize: '13px', fontWeight: 500 }}>{option.label}</span>\r\n <span\r\n style={{ fontFamily: 'var(--font-mono)', fontSize: '11px', opacity: 0.5 }}\r\n >\r\n {option.value}\r\n </span>\r\n </div>\r\n </button>\r\n )\r\n })}\r\n </div>\r\n </div>,\r\n anchor,\r\n )\r\n }}\r\n onQueryChange={setQueryString}\r\n onSelectOption={(option: any, textNodeToReplace: any, closeMenu: any) => {\r\n editor.update(() => {\r\n if (textNodeToReplace) {\r\n textNodeToReplace.remove()\r\n }\r\n const node = $createDynamicValueNode(option.value, option.label)\r\n $insertNodes([node])\r\n closeMenu()\r\n })\r\n }}\r\n options={options}\r\n triggerFn={checkForTriggerMatch}\r\n />\r\n )\r\n}\r\n\r\nexport const DynamicValueFeatureClient = createClientFeature<\r\n { options: DynamicValueOption[]; trigger: string },\r\n { options: DynamicValueOption[]; trigger: string }\r\n>(({ props }) => {\r\n const options = props?.options || []\r\n const trigger = props?.trigger || '@'\r\n const IconToUse = trigger === '@' ? AtSign : trigger === '#' ? Hash : Variable\r\n\r\n return {\r\n nodes: [DynamicValueNode],\r\n plugins: [\r\n {\r\n Component: (pluginProps) => (\r\n <DynamicValuePlugin {...pluginProps} options={options} trigger={trigger} />\r\n ),\r\n position: 'floatingAnchorElem',\r\n },\r\n ],\r\n sanitizedClientFeatureProps: props,\r\n toolbarFixed: {\r\n groups: [\r\n {\r\n type: 'dropdown',\r\n ChildComponent: () => (\r\n <div style={{ alignItems: 'center', display: 'flex', gap: '4px' }}>\r\n <IconToUse size={14} style={{ color: 'var(--theme-text)', opacity: 0.7 }} />\r\n </div>\r\n ),\r\n items: options.map((field: DynamicValueOption) => ({\r\n key: field.value,\r\n label: field.label,\r\n onSelect: ({ editor }: { editor: any }) => {\r\n editor.update(() => {\r\n const node = $createDynamicValueNode(field.value, field.label)\r\n $insertNodes([node])\r\n })\r\n },\r\n })),\r\n key: 'dynamicValue',\r\n order: 99,\r\n },\r\n ],\r\n },\r\n }\r\n})\r\n"],"names":["createClientFeature","$insertNodes","useLexicalComposerContext","LexicalTypeaheadMenuPlugin","AtSign","Hash","Variable","React","useCallback","useMemo","useState","ReactDOM","$createDynamicValueNode","DynamicValueNode","useTriggerMatch","trigger","text","triggerIndex","lastIndexOf","charBeforeTrigger","test","matchingString","slice","leadOffset","replaceableString","DynamicValuePlugin","anchorElem","options","allOptions","editor","queryString","setQueryString","checkForTriggerMatch","searchString","toLowerCase","filter","option","label","includes","value","map","opt","key","TypeaheadMenu","IconToUse","document","body","undefined","menuRenderFn","anchorElementRef","selectedIndex","selectOptionAndCleanUp","setHighlightedIndex","anchor","current","length","createPortal","div","className","style","background","border","borderRadius","boxShadow","minWidth","overflow","padding","zIndex","borderBottom","color","fontSize","fontWeight","letterSpacing","marginBottom","opacity","textTransform","maxHeight","overflowY","index","isSelected","button","onClick","onMouseEnter","alignItems","cursor","display","gap","textAlign","transition","width","type","size","flexDirection","span","fontFamily","onQueryChange","onSelectOption","textNodeToReplace","closeMenu","update","remove","node","triggerFn","DynamicValueFeatureClient","props","nodes","plugins","Component","pluginProps","position","sanitizedClientFeatureProps","toolbarFixed","groups","ChildComponent","items","field","onSelect","order"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/features/DynamicValue/feature.client.tsx"],"sourcesContent":["'use client'\r\n\r\nimport { createClientFeature } from '@payloadcms/richtext-lexical/client'\r\nimport {\r\n $createTextNode,\r\n $insertNodes,\r\n type LexicalNode,\r\n} from '@payloadcms/richtext-lexical/lexical'\r\nimport { useLexicalComposerContext } from '@payloadcms/richtext-lexical/lexical/react/LexicalComposerContext'\r\nimport { LexicalTypeaheadMenuPlugin } from '@payloadcms/richtext-lexical/lexical/react/LexicalTypeaheadMenuPlugin'\r\nimport { useForm } from '@payloadcms/ui'\r\nimport { AtSign, Hash, Variable } from 'lucide-react'\r\nimport React, { useCallback, useMemo, useState } from 'react'\r\nimport * as ReactDOM from 'react-dom'\r\n\r\nimport { $createDynamicValueNode, DynamicValueNode } from '../../nodes/DynamicValueNode/index.js'\r\n\r\nexport function $isDynamicValueNode(\r\n node: LexicalNode | null | undefined,\r\n): node is DynamicValueNode {\r\n return node?.getType?.() === 'dynamic-value'\r\n}\r\n\r\nfunction useTriggerMatch(trigger: string) {\r\n return useCallback(\r\n (text: string) => {\r\n const triggerIndex = text.lastIndexOf(trigger)\r\n if (triggerIndex === -1) {\r\n return null\r\n }\r\n\r\n const charBeforeTrigger = triggerIndex > 0 ? text[triggerIndex - 1] : null\r\n if (charBeforeTrigger && !/[\\s(]/.test(charBeforeTrigger)) {\r\n return null\r\n }\r\n\r\n const matchingString = text.slice(triggerIndex + 1)\r\n if (/\\s/.test(matchingString)) {\r\n return null\r\n }\r\n\r\n return {\r\n leadOffset: triggerIndex,\r\n matchingString,\r\n replaceableString: trigger + matchingString,\r\n }\r\n },\r\n [trigger],\r\n )\r\n}\r\n\r\nconst SelectIcon = (trigger: string) => {\r\n const IconToUse = trigger === '@' ? AtSign : trigger === '#' ? Hash : Variable\r\n return () => (\r\n <div style={{ alignItems: 'center', display: 'flex', justifyContent: 'center', opacity: 0.8 }}>\r\n <IconToUse\r\n className=\"icon\"\r\n focusable=\"false\"\r\n size={14}\r\n strokeWidth={1.5}\r\n style={{ color: 'currentColor' }}\r\n />\r\n </div>\r\n )\r\n}\r\n\r\nconst DynamicValuePlugin = ({\r\n anchorElem,\r\n options: allOptions,\r\n trigger,\r\n}: {\r\n anchorElem?: HTMLElement\r\n options: any[]\r\n trigger: string\r\n}) => {\r\n const [editor] = useLexicalComposerContext()\r\n const { fields } = useForm()\r\n const [queryString, setQueryString] = useState<null | string>(null)\r\n const checkForTriggerMatch = useTriggerMatch(trigger)\r\n\r\n const options = useMemo(() => {\r\n const searchString = queryString?.toLowerCase() || ''\r\n return (allOptions || [])\r\n .filter((option) => {\r\n const val = fields[option.value]?.value\r\n const hasValue = val !== undefined && val !== null && val !== ''\r\n return (\r\n hasValue &&\r\n (option.label.toLowerCase().includes(searchString) ||\r\n option.value.toLowerCase().includes(searchString))\r\n )\r\n })\r\n .map((opt) => ({\r\n ...opt,\r\n key: opt.value,\r\n }))\r\n }, [queryString, allOptions, fields])\r\n\r\n const IconToUse = trigger === '@' ? AtSign : trigger === '#' ? Hash : Variable\r\n\r\n return (\r\n <LexicalTypeaheadMenuPlugin\r\n {...({\r\n anchorElem: anchorElem || (typeof document !== 'undefined' ? document.body : undefined),\r\n } as any)}\r\n menuRenderFn={(\r\n anchorElementRef,\r\n { selectedIndex, selectOptionAndCleanUp, setHighlightedIndex },\r\n ) => {\r\n const anchor =\r\n anchorElementRef?.current ||\r\n anchorElem ||\r\n (typeof document !== 'undefined' ? document.body : null)\r\n if (!anchor || !options.length) {\r\n return null\r\n }\r\n\r\n return ReactDOM.createPortal(\r\n <div\r\n className=\"slash-menu-popup\"\r\n style={{\r\n maxHeight: '400px',\r\n minWidth: '260px',\r\n overflowY: 'auto',\r\n padding: '8px',\r\n }}\r\n >\r\n <div className=\"slash-menu-popup__group\">\r\n <div className=\"slash-menu-popup__group-title\">Dynamic Values</div>\r\n {options.map((option, index) => {\r\n const isSelected = selectedIndex === index\r\n return (\r\n <button\r\n aria-selected={isSelected}\r\n className={`slash-menu-popup__item ${isSelected ? 'slash-menu-popup__item--selected' : ''}`}\r\n key={option.value}\r\n onClick={() => selectOptionAndCleanUp(option)}\r\n onMouseEnter={() => setHighlightedIndex(index)}\r\n role=\"option\"\r\n style={{ minHeight: '50px' }}\r\n tabIndex={-1}\r\n type=\"button\"\r\n >\r\n <IconToUse\r\n className=\"icon\"\r\n size={20}\r\n strokeWidth={1.5}\r\n style={{ flexShrink: 0 }}\r\n />\r\n <span\r\n className=\"slash-menu-popup__item-text\"\r\n style={{\r\n alignItems: 'flex-start',\r\n display: 'flex',\r\n flex: '1',\r\n flexDirection: 'column',\r\n marginLeft: '8px',\r\n }}\r\n >\r\n <span\r\n className=\"text\"\r\n style={{\r\n fontSize: '13px',\r\n fontWeight: '500',\r\n lineHeight: 1.2,\r\n }}\r\n >\r\n {option.label}\r\n </span>\r\n <span\r\n style={{\r\n fontFamily: 'var(--font-mono)',\r\n fontSize: '11px',\r\n lineHeight: 1.2,\r\n marginTop: '2px',\r\n opacity: 0.5,\r\n }}\r\n >\r\n {option.value}\r\n </span>\r\n </span>\r\n </button>\r\n )\r\n })}\r\n </div>\r\n </div>,\r\n anchor,\r\n )\r\n }}\r\n onQueryChange={setQueryString}\r\n onSelectOption={(option: any, textNodeToReplace, closeMenu) => {\r\n editor.update(() => {\r\n if (textNodeToReplace) {\r\n textNodeToReplace.remove()\r\n }\r\n const node = $createDynamicValueNode(option.value, option.label)\r\n $insertNodes([node, $createTextNode(' ')])\r\n closeMenu()\r\n })\r\n }}\r\n options={options as any}\r\n triggerFn={checkForTriggerMatch as any}\r\n />\r\n )\r\n}\r\n\r\nconst DropdownItemComponent = ({ editor, field, item, trigger }: any) => {\r\n const { fields } = useForm()\r\n const val = fields[field.value]?.value\r\n const hasValue = val !== undefined && val !== null && val !== ''\r\n\r\n if (!hasValue) {\r\n return null\r\n }\r\n\r\n const IconToUse = trigger === '@' ? AtSign : trigger === '#' ? Hash : Variable\r\n\r\n return (\r\n <button\r\n aria-label={field.label}\r\n className=\"btn toolbar-popup__dropdown-item btn--icon btn--icon-style-none btn--size-medium btn--icon-position-left btn--style-none\"\r\n data-item-key={field.value}\r\n onClick={(e) => {\r\n e.preventDefault()\r\n item.onSelect({ editor, isActive: false })\r\n }}\r\n style={{ minHeight: '50px', whiteSpace: 'nowrap' }}\r\n title={field.label}\r\n type=\"button\"\r\n >\r\n <span className=\"btn__content\" style={{ alignItems: 'center', display: 'flex' }}>\r\n <span\r\n className=\"btn__label\"\r\n style={{\r\n alignItems: 'flex-start',\r\n display: 'flex',\r\n flex: 1,\r\n flexDirection: 'column',\r\n marginLeft: '8px',\r\n marginRight: '8px',\r\n }}\r\n >\r\n <span className=\"text\" style={{ fontSize: '13px', fontWeight: 500, lineHeight: '1.2' }}>\r\n {field.label}\r\n </span>\r\n <span\r\n style={{\r\n fontFamily: 'var(--font-mono)',\r\n fontSize: '11px',\r\n lineHeight: '1.2',\r\n marginTop: '2px',\r\n opacity: 0.5,\r\n }}\r\n >\r\n {field.value}\r\n </span>\r\n </span>\r\n <span className=\"btn__icon\" style={{ flexShrink: 0 }}>\r\n <IconToUse size={20} strokeWidth={1.5} style={{ opacity: 0.4 }} />\r\n </span>\r\n </span>\r\n </button>\r\n )\r\n}\r\n\r\nexport const DynamicValueFeatureClient = createClientFeature((args: any) => {\r\n const props = args?.clientFeatureProps || args?.props || {}\r\n const options = props?.options || []\r\n const trigger = props?.trigger || '@'\r\n\r\n const groupItems = options.map((field: any) => ({\r\n Component: ({ editor, item }: any) => (\r\n <DropdownItemComponent editor={editor} field={field} item={item} trigger={trigger} />\r\n ),\r\n isActive: () => false,\r\n isEnabled: () => true,\r\n key: `dv-item-${field.value}`,\r\n label: field.label,\r\n onSelect: ({ editor }: any) => {\r\n editor.update(() => {\r\n const node = $createDynamicValueNode(field.value, field.label)\r\n $insertNodes([node, $createTextNode(' ')])\r\n })\r\n },\r\n order: 1,\r\n }))\r\n\r\n const toolbarGroup = {\r\n type: 'dropdown' as const,\r\n ChildComponent: SelectIcon(trigger),\r\n items: groupItems,\r\n key: 'dynamic-value-toolbar-group', // Unique key to avoid Link feature collision\r\n order: 100,\r\n }\r\n\r\n return {\r\n nodes: [DynamicValueNode],\r\n plugins: [\r\n {\r\n Component: (pluginProps) => (\r\n <DynamicValuePlugin {...pluginProps} options={options} trigger={trigger} />\r\n ),\r\n position: 'floatingAnchorElem',\r\n },\r\n ],\r\n sanitizedClientFeatureProps: props,\r\n toolbarFixed: {\r\n groups: [toolbarGroup],\r\n },\r\n }\r\n})\r\n"],"names":["createClientFeature","$createTextNode","$insertNodes","useLexicalComposerContext","LexicalTypeaheadMenuPlugin","useForm","AtSign","Hash","Variable","React","useCallback","useMemo","useState","ReactDOM","$createDynamicValueNode","DynamicValueNode","$isDynamicValueNode","node","getType","useTriggerMatch","trigger","text","triggerIndex","lastIndexOf","charBeforeTrigger","test","matchingString","slice","leadOffset","replaceableString","SelectIcon","IconToUse","div","style","alignItems","display","justifyContent","opacity","className","focusable","size","strokeWidth","color","DynamicValuePlugin","anchorElem","options","allOptions","editor","fields","queryString","setQueryString","checkForTriggerMatch","searchString","toLowerCase","filter","option","val","value","hasValue","undefined","label","includes","map","opt","key","document","body","menuRenderFn","anchorElementRef","selectedIndex","selectOptionAndCleanUp","setHighlightedIndex","anchor","current","length","createPortal","maxHeight","minWidth","overflowY","padding","index","isSelected","button","aria-selected","onClick","onMouseEnter","role","minHeight","tabIndex","type","flexShrink","span","flex","flexDirection","marginLeft","fontSize","fontWeight","lineHeight","fontFamily","marginTop","onQueryChange","onSelectOption","textNodeToReplace","closeMenu","update","remove","triggerFn","DropdownItemComponent","field","item","aria-label","data-item-key","e","preventDefault","onSelect","isActive","whiteSpace","title","marginRight","DynamicValueFeatureClient","args","props","clientFeatureProps","groupItems","Component","isEnabled","order","toolbarGroup","ChildComponent","items","nodes","plugins","pluginProps","position","sanitizedClientFeatureProps","toolbarFixed","groups"],"mappings":"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"}
|
|
@@ -1,5 +1,16 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import type { Field } from 'payload';
|
|
2
|
+
export type DynamicValueFeatureProps = {
|
|
3
|
+
collections?: string[];
|
|
4
|
+
fields?: Field[];
|
|
5
|
+
globals?: string[];
|
|
6
|
+
trigger?: string;
|
|
7
|
+
};
|
|
8
|
+
type DynamicValueOption = {
|
|
9
|
+
label: string;
|
|
10
|
+
value: string;
|
|
11
|
+
};
|
|
12
|
+
export declare const DynamicValueFeature: import("@payloadcms/richtext-lexical").FeatureProviderProviderServer<DynamicValueFeatureProps, DynamicValueFeatureProps, {
|
|
13
|
+
options: DynamicValueOption[];
|
|
14
|
+
trigger: string;
|
|
15
|
+
}>;
|
|
16
|
+
export {};
|
|
@@ -1,62 +1,101 @@
|
|
|
1
1
|
import { createNode, createServerFeature } from '@payloadcms/richtext-lexical';
|
|
2
|
-
import { DynamicValueNode } from '../../nodes/DynamicValueNode/index.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
field.tabs.forEach((tab)=>{
|
|
21
|
-
flattenFields(tab.fields, prefix);
|
|
22
|
-
});
|
|
23
|
-
}
|
|
2
|
+
import { DYNAMIC_VALUE_NODE_TYPE, DynamicValueNode, LEGACY_DYNAMIC_VALUE_NODE_TYPE } from '../../nodes/DynamicValueNode/index.js';
|
|
3
|
+
const blockedFieldTypes = new Set([
|
|
4
|
+
'array',
|
|
5
|
+
'blocks',
|
|
6
|
+
'richText',
|
|
7
|
+
'ui',
|
|
8
|
+
'upload'
|
|
9
|
+
]);
|
|
10
|
+
const flattenFields = (fields, options, prefix = '')=>{
|
|
11
|
+
for (const field of fields){
|
|
12
|
+
const fieldName = 'name' in field ? field.name : undefined;
|
|
13
|
+
const hasFieldName = typeof fieldName === 'string' && fieldName.length > 0;
|
|
14
|
+
const nextPrefix = hasFieldName ? prefix ? `${prefix}.${fieldName}` : fieldName : prefix;
|
|
15
|
+
if (hasFieldName && typeof field.type === 'string' && !blockedFieldTypes.has(field.type) && nextPrefix) {
|
|
16
|
+
const fieldLabel = 'label' in field && typeof field.label === 'string' ? field.label : fieldName;
|
|
17
|
+
options.push({
|
|
18
|
+
label: fieldLabel,
|
|
19
|
+
value: nextPrefix
|
|
24
20
|
});
|
|
21
|
+
}
|
|
22
|
+
if ('fields' in field && Array.isArray(field.fields)) {
|
|
23
|
+
flattenFields(field.fields, options, nextPrefix);
|
|
24
|
+
}
|
|
25
|
+
if (field.type === 'tabs' && Array.isArray(field.tabs)) {
|
|
26
|
+
for (const tab of field.tabs){
|
|
27
|
+
if (tab.fields) {
|
|
28
|
+
flattenFields(tab.fields, options, prefix);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
const mapToEnabledSlugs = (value)=>{
|
|
35
|
+
if (Array.isArray(value)) {
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
if (!value || typeof value !== 'object') {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
return Object.entries(value).filter(([, enabled])=>Boolean(enabled)).map(([slug])=>slug);
|
|
42
|
+
};
|
|
43
|
+
export const DynamicValueFeature = createServerFeature({
|
|
44
|
+
feature: ({ config: payloadConfig, props: featureProps })=>{
|
|
45
|
+
const pluginOptions = payloadConfig.custom?.dynamicValue || {};
|
|
46
|
+
const props = {
|
|
47
|
+
...pluginOptions,
|
|
48
|
+
...featureProps || {}
|
|
25
49
|
};
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
50
|
+
const options = [];
|
|
51
|
+
const trigger = props.trigger || '@';
|
|
52
|
+
if (props.fields) {
|
|
53
|
+
flattenFields(props.fields, options);
|
|
29
54
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const collection = payloadConfig.collections
|
|
55
|
+
if (props.collections && payloadConfig.collections) {
|
|
56
|
+
const collectionSlugs = mapToEnabledSlugs(props.collections);
|
|
57
|
+
for (const slug of collectionSlugs){
|
|
58
|
+
const collection = payloadConfig.collections.find((item)=>item.slug === slug);
|
|
34
59
|
if (collection) {
|
|
35
|
-
flattenFields(collection.fields, slug);
|
|
60
|
+
flattenFields(collection.fields, options, slug);
|
|
36
61
|
}
|
|
37
|
-
}
|
|
62
|
+
}
|
|
38
63
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const global = payloadConfig.globals
|
|
64
|
+
if (props.globals && payloadConfig.globals) {
|
|
65
|
+
const globalSlugs = mapToEnabledSlugs(props.globals);
|
|
66
|
+
for (const slug of globalSlugs){
|
|
67
|
+
const global = payloadConfig.globals.find((item)=>item.slug === slug);
|
|
43
68
|
if (global) {
|
|
44
|
-
flattenFields(global.fields, slug);
|
|
69
|
+
flattenFields(global.fields, options, slug);
|
|
45
70
|
}
|
|
46
|
-
}
|
|
71
|
+
}
|
|
47
72
|
}
|
|
48
73
|
return {
|
|
49
74
|
ClientFeature: '@od-labs/payloadcms-dynamic-value-richtext/client#DynamicValueFeatureClient',
|
|
50
|
-
|
|
75
|
+
clientFeatureProps: {
|
|
51
76
|
options,
|
|
52
77
|
trigger
|
|
53
78
|
},
|
|
54
79
|
key: 'dynamicValue',
|
|
55
80
|
nodes: [
|
|
56
81
|
createNode({
|
|
82
|
+
converters: {
|
|
83
|
+
html: {
|
|
84
|
+
converter: ({ node })=>{
|
|
85
|
+
const field = node.field || node.value || '';
|
|
86
|
+
const label = node.label || field;
|
|
87
|
+
return `<span data-payload-dynamic-value="true" data-payload-dynamic-field="${field}">${label}</span>`;
|
|
88
|
+
},
|
|
89
|
+
nodeTypes: [
|
|
90
|
+
DYNAMIC_VALUE_NODE_TYPE,
|
|
91
|
+
LEGACY_DYNAMIC_VALUE_NODE_TYPE
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
},
|
|
57
95
|
node: DynamicValueNode
|
|
58
96
|
})
|
|
59
|
-
]
|
|
97
|
+
],
|
|
98
|
+
sanitizedServerFeatureProps: props
|
|
60
99
|
};
|
|
61
100
|
},
|
|
62
101
|
key: 'dynamicValue'
|