@getdraft/plugin 1.17.5 → 1.19.0-alpha.1

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/src/plugin.ts CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  HandlerParams,
15
15
  MessageData,
16
16
  ExportAsFileParams,
17
+ RenderSectionPersonalizationGroupInput,
17
18
  } from './types';
18
19
  import {
19
20
  getImageParamsFromFileUrl,
@@ -22,6 +23,8 @@ import {
22
23
  } from './utils';
23
24
  import pkg from '../package.json';
24
25
 
26
+ const UNKNOWN_ERROR_MESSAGE = 'Unknown error';
27
+
25
28
  const getDeployLink = () => {
26
29
  const regex = new RegExp(`(^| )seplugin-dev=([^;]+)`);
27
30
  const match = document.cookie.match(regex);
@@ -170,7 +173,7 @@ export class PluginInstance {
170
173
  reject(
171
174
  new DraftError(
172
175
  code || 'unknown_error',
173
- message || 'Unknown error',
176
+ message || UNKNOWN_ERROR_MESSAGE,
174
177
  ),
175
178
  );
176
179
  } else {
@@ -221,7 +224,7 @@ export class PluginInstance {
221
224
  }
222
225
  };
223
226
 
224
- private onLoadPersonalization = async () => {
227
+ private onLoadPersonalization = async (requestId?: string) => {
225
228
  try {
226
229
  if (!this.config.on.loadPersonalization) {
227
230
  throw new Error('No personalization loader provided');
@@ -232,9 +235,62 @@ export class PluginInstance {
232
235
 
233
236
  this.postEvent('loadPersonalization', {
234
237
  items: personalizationDictionary,
238
+ requestId,
235
239
  });
236
240
  } catch (e) {
237
- this.postEvent('loadPersonalization', {});
241
+ this.postEvent('loadPersonalization', { requestId });
242
+ }
243
+ };
244
+
245
+ private onLoadSectionPersonalizationCatalog = async (requestId?: string) => {
246
+ try {
247
+ const handler = this.config.on.loadSectionPersonalizationCatalog;
248
+
249
+ if (!handler) {
250
+ throw new Error('No section personalization catalog loader provided');
251
+ }
252
+
253
+ const catalog = await handler();
254
+
255
+ this.postEvent('loadSectionPersonalizationCatalog', {
256
+ ok: true,
257
+ requestId,
258
+ catalog,
259
+ });
260
+ } catch (error) {
261
+ this.postEvent('loadSectionPersonalizationCatalog', {
262
+ ok: false,
263
+ requestId,
264
+ message: error instanceof Error ? error.message : UNKNOWN_ERROR_MESSAGE,
265
+ });
266
+ }
267
+ };
268
+
269
+ private onRenderSectionPersonalizationGroup = async (
270
+ data: RenderSectionPersonalizationGroupInput & { requestId?: string },
271
+ ) => {
272
+ const { requestId, ...input } = data;
273
+
274
+ try {
275
+ const handler = this.config.on.renderSectionPersonalizationGroup;
276
+
277
+ if (!handler) {
278
+ throw new Error('No section personalization renderer provided');
279
+ }
280
+
281
+ const renderedHtml = await handler(input);
282
+
283
+ this.postEvent('renderSectionPersonalizationGroup', {
284
+ ok: true,
285
+ requestId,
286
+ html: renderedHtml,
287
+ });
288
+ } catch (error) {
289
+ this.postEvent('renderSectionPersonalizationGroup', {
290
+ ok: false,
291
+ requestId,
292
+ message: error instanceof Error ? error.message : UNKNOWN_ERROR_MESSAGE,
293
+ });
238
294
  }
239
295
  };
240
296
 
@@ -284,7 +340,17 @@ export class PluginInstance {
284
340
  } else if (event === 'uploadImage') {
285
341
  this.onImageUpload(data as UploadImageParams & { id: string });
286
342
  } else if (event === 'loadPersonalization') {
287
- this.onLoadPersonalization();
343
+ this.onLoadPersonalization((data as { requestId?: string }).requestId);
344
+ } else if (event === 'loadSectionPersonalizationCatalog') {
345
+ this.onLoadSectionPersonalizationCatalog(
346
+ (data as { requestId?: string }).requestId,
347
+ );
348
+ } else if (event === 'renderSectionPersonalizationGroup') {
349
+ this.onRenderSectionPersonalizationGroup(
350
+ data as RenderSectionPersonalizationGroupInput & {
351
+ requestId?: string;
352
+ },
353
+ );
288
354
  } else {
289
355
  this.triggerEvent(
290
356
  this.config.on[event],
@@ -326,12 +392,21 @@ export class PluginInstance {
326
392
  apiUrl,
327
393
  pluginId,
328
394
  authData,
395
+ sectionPersonalization,
329
396
  __config,
330
397
  } = this.config;
331
398
 
332
399
  verifyConfig(this.config);
333
400
 
334
401
  if (this.iframe && this.iframe.contentWindow) {
402
+ const resolvedSectionPersonalization = {
403
+ ...sectionPersonalization,
404
+ enabled:
405
+ sectionPersonalization?.enabled === true &&
406
+ !!on.loadSectionPersonalizationCatalog &&
407
+ !!on.renderSectionPersonalizationGroup,
408
+ };
409
+
335
410
  this.postEvent('init', {
336
411
  container,
337
412
  locale,
@@ -340,6 +415,7 @@ export class PluginInstance {
340
415
  token,
341
416
  pluginId,
342
417
  authData,
418
+ sectionPersonalization: resolvedSectionPersonalization,
343
419
  apiUrl,
344
420
  __config,
345
421
  enabledListeners: Object.keys(on),
package/src/types.ts CHANGED
@@ -23,6 +23,130 @@ type PersonalizationDictionaryEntry =
23
23
 
24
24
  export type PersonalizationDictionaryItems = PersonalizationDictionaryEntry[];
25
25
 
26
+ export type SectionPersonalizationScalarValue = string | number | boolean;
27
+
28
+ export interface SectionPersonalizationRangeValue {
29
+ from: string | number | null;
30
+ to: string | number | null;
31
+ }
32
+
33
+ export type SectionPersonalizationValue =
34
+ | SectionPersonalizationScalarValue
35
+ | SectionPersonalizationScalarValue[]
36
+ | SectionPersonalizationRangeValue
37
+ | null;
38
+
39
+ export interface SectionPersonalizationField {
40
+ id: string;
41
+ }
42
+
43
+ export interface SectionPersonalizationCondition {
44
+ operatorId: string;
45
+ value: SectionPersonalizationValue;
46
+ }
47
+
48
+ export type SectionPersonalizationConditions = Record<
49
+ string,
50
+ SectionPersonalizationCondition
51
+ >;
52
+
53
+ export interface SectionPersonalizationCatalogOption {
54
+ label: string;
55
+ value: SectionPersonalizationScalarValue;
56
+ }
57
+
58
+ export type SectionPersonalizationRangeEndpointEditor =
59
+ | {
60
+ type: 'number';
61
+ integer?: boolean;
62
+ min?: number;
63
+ max?: number;
64
+ }
65
+ | { type: 'date' };
66
+
67
+ export type SectionPersonalizationValueEditor =
68
+ | { type: 'none' }
69
+ | { type: 'text'; maxLength?: number }
70
+ | {
71
+ type: 'number';
72
+ integer?: boolean;
73
+ min?: number;
74
+ max?: number;
75
+ }
76
+ | { type: 'boolean' }
77
+ | { type: 'date' }
78
+ | { type: 'select'; options: SectionPersonalizationCatalogOption[] }
79
+ | { type: 'multi-select'; options: SectionPersonalizationCatalogOption[] }
80
+ | {
81
+ type: 'range';
82
+ valueEditor: SectionPersonalizationRangeEndpointEditor;
83
+ };
84
+
85
+ export interface SectionPersonalizationCatalogOperator {
86
+ id: string;
87
+ label: string;
88
+ /**
89
+ * Optional abbreviation for compact summaries, e.g. a math symbol. The
90
+ * integration owns it, so Draft never derives one from `id` or `label`.
91
+ */
92
+ shortLabel?: string;
93
+ valueEditor: SectionPersonalizationValueEditor;
94
+ }
95
+
96
+ export interface SectionPersonalizationCatalogGroup {
97
+ type: 'group';
98
+ id: string;
99
+ label: string;
100
+ children: SectionPersonalizationCatalogNode[];
101
+ }
102
+
103
+ export interface SectionPersonalizationCatalogField {
104
+ type: 'field';
105
+ id: string;
106
+ label: string;
107
+ defaultOperatorId: string;
108
+ operators: SectionPersonalizationCatalogOperator[];
109
+ }
110
+
111
+ export type SectionPersonalizationCatalogNode =
112
+ | SectionPersonalizationCatalogGroup
113
+ | SectionPersonalizationCatalogField;
114
+
115
+ export interface SectionPersonalizationConfig {
116
+ enabled: boolean;
117
+ aboutUrl?: string;
118
+ }
119
+
120
+ /**
121
+ * One audience the whole group is hidden from. Conditions inside are joined
122
+ * with AND, exclusions between themselves with OR.
123
+ */
124
+ export interface SectionPersonalizationExclusion {
125
+ id: string;
126
+ conditions: SectionPersonalizationConditions;
127
+ }
128
+
129
+ export interface RenderSectionPersonalizationGroupInput {
130
+ groupId: string;
131
+ fields: SectionPersonalizationField[];
132
+ variants: Array<{
133
+ id: string;
134
+ conditions: SectionPersonalizationConditions;
135
+ html: string;
136
+ }>;
137
+ fallback?: {
138
+ id: string;
139
+ html: string;
140
+ };
141
+ /**
142
+ * Audiences the whole group must be hidden from, already stripped of unfilled
143
+ * conditions. Empty exclusions are dropped by Draft, so every entry carries at
144
+ * least one condition and the renderer never has to guard against a rule that
145
+ * would match everyone.
146
+ */
147
+ hiddenFor?: SectionPersonalizationExclusion[];
148
+ }
149
+
26
150
  export interface SerializedTemplate {
27
151
  [key: string]: unknown;
28
152
  parentId: string | null;
@@ -55,6 +179,7 @@ export type MessageData = {
55
179
  code?: string;
56
180
  message?: string;
57
181
  action?: string;
182
+ requestId?: string;
58
183
  };
59
184
 
60
185
  export interface ExportAsFileParams {
@@ -266,6 +391,14 @@ export interface Handlers {
266
391
  openGallery: EventHandler<(url?: string) => Promise<void>>;
267
392
  uploadImage: AsyncEventHandler<UploadImageParams, string>;
268
393
  loadPersonalization: AsyncEventHandler<void, PersonalizationDictionary>;
394
+ loadSectionPersonalizationCatalog: AsyncEventHandler<
395
+ void,
396
+ SectionPersonalizationCatalogNode[]
397
+ >;
398
+ renderSectionPersonalizationGroup: AsyncEventHandler<
399
+ RenderSectionPersonalizationGroupInput,
400
+ string
401
+ >;
269
402
  }
270
403
 
271
404
  export type HandlerParams<H> = H extends
@@ -290,6 +423,7 @@ export interface PluginConfig {
290
423
  pluginId: string;
291
424
  apiUrl: string;
292
425
  disableHotkeysPassing?: boolean;
426
+ sectionPersonalization?: SectionPersonalizationConfig;
293
427
  __config?: object;
294
428
  on: Partial<Handlers>;
295
429
  }