@rippling/rippling-sdk 0.2.0-alpha.44 → 0.2.0-alpha.46
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/examples/manifest/dental-practice-management/dental-practice-management.ts +0 -7
- package/examples/manifest/dental-practice-management/manifest.json +0 -7
- package/examples/manifest/existing-manifest-mutations/existing-manifest-mutations.ts +6 -7
- package/examples/manifest/gym-membership-management/gym-membership-management.ts +0 -7
- package/examples/manifest/gym-membership-management/manifest.json +0 -7
- package/lib/manifest/app.d.mts +2 -2
- package/lib/manifest/app.d.mts.map +1 -1
- package/lib/manifest/app.d.ts +2 -2
- package/lib/manifest/app.d.ts.map +1 -1
- package/lib/manifest/app.js +15 -4
- package/lib/manifest/app.js.map +1 -1
- package/lib/manifest/app.mjs +15 -4
- package/lib/manifest/app.mjs.map +1 -1
- package/lib/manifest/custom-object-field-section.d.mts +2 -2
- package/lib/manifest/custom-object-field-section.d.ts +2 -2
- package/lib/manifest/custom-object-field-section.js +2 -2
- package/lib/manifest/custom-object-field-section.mjs +2 -2
- package/lib/manifest/custom-object-page-layout.d.mts +86 -40
- package/lib/manifest/custom-object-page-layout.d.mts.map +1 -1
- package/lib/manifest/custom-object-page-layout.d.ts +86 -40
- package/lib/manifest/custom-object-page-layout.d.ts.map +1 -1
- package/lib/manifest/custom-object-page-layout.js +369 -79
- package/lib/manifest/custom-object-page-layout.js.map +1 -1
- package/lib/manifest/custom-object-page-layout.mjs +369 -79
- package/lib/manifest/custom-object-page-layout.mjs.map +1 -1
- package/lib/manifest/custom-object.d.mts +6 -0
- package/lib/manifest/custom-object.d.mts.map +1 -1
- package/lib/manifest/custom-object.d.ts +6 -0
- package/lib/manifest/custom-object.d.ts.map +1 -1
- package/lib/manifest/custom-object.js +6 -0
- package/lib/manifest/custom-object.js.map +1 -1
- package/lib/manifest/custom-object.mjs +6 -0
- package/lib/manifest/custom-object.mjs.map +1 -1
- package/lib/manifest/existing-manifest-context.d.mts +0 -1
- package/lib/manifest/existing-manifest-context.d.mts.map +1 -1
- package/lib/manifest/existing-manifest-context.d.ts +0 -1
- package/lib/manifest/existing-manifest-context.d.ts.map +1 -1
- package/lib/manifest/existing-manifest-context.js +5 -10
- package/lib/manifest/existing-manifest-context.js.map +1 -1
- package/lib/manifest/existing-manifest-context.mjs +5 -10
- package/lib/manifest/existing-manifest-context.mjs.map +1 -1
- package/lib/manifest/field.d.mts +6 -3
- package/lib/manifest/field.d.mts.map +1 -1
- package/lib/manifest/field.d.ts +6 -3
- package/lib/manifest/field.d.ts.map +1 -1
- package/lib/manifest/field.js +26 -6
- package/lib/manifest/field.js.map +1 -1
- package/lib/manifest/field.mjs +26 -6
- package/lib/manifest/field.mjs.map +1 -1
- package/package.json +1 -1
- package/src/lib/manifest/app.ts +19 -5
- package/src/lib/manifest/custom-object-field-section.ts +2 -2
- package/src/lib/manifest/custom-object-page-layout.ts +599 -109
- package/src/lib/manifest/custom-object.ts +6 -0
- package/src/lib/manifest/existing-manifest-context.ts +5 -12
- package/src/lib/manifest/field.ts +45 -9
- package/src/version.ts +1 -1
- package/version.d.mts +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.mjs +1 -1
|
@@ -14,6 +14,35 @@ function isFieldRef(value) {
|
|
|
14
14
|
const candidate = value;
|
|
15
15
|
return candidate != null && typeof candidate === 'object' && isField(candidate.field);
|
|
16
16
|
}
|
|
17
|
+
const FIELD_SECTION_ID = Symbol('CustomObjectPageLayout.fieldSectionId');
|
|
18
|
+
function isCustomObjectFieldSection(value) {
|
|
19
|
+
return value instanceof CustomObjectFieldSection;
|
|
20
|
+
}
|
|
21
|
+
function attachFieldSectionId(section, sectionId) {
|
|
22
|
+
Object.defineProperty(section, FIELD_SECTION_ID, {
|
|
23
|
+
value: sectionId,
|
|
24
|
+
enumerable: false,
|
|
25
|
+
configurable: true,
|
|
26
|
+
});
|
|
27
|
+
return section;
|
|
28
|
+
}
|
|
29
|
+
function layoutSectionKey(section) {
|
|
30
|
+
return section[FIELD_SECTION_ID];
|
|
31
|
+
}
|
|
32
|
+
function layoutSectionMatches(section, sectionKey) {
|
|
33
|
+
return layoutSectionKey(section) === sectionKey;
|
|
34
|
+
}
|
|
35
|
+
function inputFieldsSectionHasKey(input) {
|
|
36
|
+
const record = input;
|
|
37
|
+
return Object.prototype.hasOwnProperty.call(record, 'key') && record['key'] != null;
|
|
38
|
+
}
|
|
39
|
+
function assertNoSerializedSectionKey(section) {
|
|
40
|
+
const record = section;
|
|
41
|
+
if (Object.prototype.hasOwnProperty.call(record, 'key') && record['key'] != null) {
|
|
42
|
+
throw new Error('CustomObjectPageLayout loaded fields_section key is not supported. ' +
|
|
43
|
+
'Remove key and target the section with CustomObjectFieldSection metadata.');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
17
46
|
function assertSameObject(customObject, field, sectionKey) {
|
|
18
47
|
if (field.getCustomObjectApiName() !== customObject.getApiName()) {
|
|
19
48
|
throw new Error(`CustomObjectPageLayout fields_section "${sectionKey}" references field "${field.getApiName()}" ` +
|
|
@@ -26,6 +55,21 @@ function assertSectionBelongsTo(customObject, section) {
|
|
|
26
55
|
`but the layout belongs to "${customObject.getApiName()}".`);
|
|
27
56
|
}
|
|
28
57
|
}
|
|
58
|
+
function requireFieldSectionOption(value, label) {
|
|
59
|
+
if (isCustomObjectFieldSection(value))
|
|
60
|
+
return value;
|
|
61
|
+
if (typeof value === 'string') {
|
|
62
|
+
throw new TypeError(`${label} must be a CustomObjectFieldSection object; received raw section id "${value}". ` +
|
|
63
|
+
'Load/include the existing field section first.');
|
|
64
|
+
}
|
|
65
|
+
throw new TypeError(`${label} must be a CustomObjectFieldSection object.`);
|
|
66
|
+
}
|
|
67
|
+
function assertOptionAbsent(options, key, message) {
|
|
68
|
+
if (options == null || typeof options !== 'object')
|
|
69
|
+
return;
|
|
70
|
+
if (Object.prototype.hasOwnProperty.call(options, key))
|
|
71
|
+
throw new TypeError(message);
|
|
72
|
+
}
|
|
29
73
|
function toSerializedSectionField(fieldRqlName, options = {}) {
|
|
30
74
|
const normalized = { fieldRqlName };
|
|
31
75
|
if (options.hidden != null)
|
|
@@ -52,17 +96,19 @@ function normalizeSectionField(customObject, section, sectionKey, entry) {
|
|
|
52
96
|
}
|
|
53
97
|
function normalizeFieldsSection(customObject, input) {
|
|
54
98
|
assertSectionBelongsTo(customObject, input.section);
|
|
55
|
-
const
|
|
99
|
+
const sectionId = input.section.getSectionId();
|
|
100
|
+
if (inputFieldsSectionHasKey(input)) {
|
|
101
|
+
throw new Error('CustomObjectPageLayout fields_section key is not supported. Omit key.');
|
|
102
|
+
}
|
|
56
103
|
const name = input.name ?? input.section.getName();
|
|
57
104
|
const normalized = {
|
|
58
|
-
key,
|
|
59
105
|
name,
|
|
60
106
|
type: 'fields_section',
|
|
61
|
-
fields: input.fields.map((field) => normalizeSectionField(customObject, input.section,
|
|
107
|
+
fields: input.fields.map((field) => normalizeSectionField(customObject, input.section, sectionId, field)),
|
|
62
108
|
};
|
|
63
109
|
if (input.layout != null)
|
|
64
110
|
normalized.layout = input.layout;
|
|
65
|
-
return normalized;
|
|
111
|
+
return attachFieldSectionId(normalized, sectionId);
|
|
66
112
|
}
|
|
67
113
|
function normalizeTab(customObject, tab) {
|
|
68
114
|
if (tab.type === 'custom_tab')
|
|
@@ -115,6 +161,68 @@ function cloneJson(value) {
|
|
|
115
161
|
return value;
|
|
116
162
|
return JSON.parse(JSON.stringify(value));
|
|
117
163
|
}
|
|
164
|
+
const HEADER_EDIT_KEYS = new Set([
|
|
165
|
+
'newTitleField',
|
|
166
|
+
'titleFieldDeleted',
|
|
167
|
+
'newDescriptionField',
|
|
168
|
+
'descriptionFieldDeleted',
|
|
169
|
+
'systemFieldEdits',
|
|
170
|
+
'newFields',
|
|
171
|
+
'headerFieldsOrder',
|
|
172
|
+
'buttons',
|
|
173
|
+
]);
|
|
174
|
+
const SYSTEM_HEADER_FIELD_EDIT_KEYS = new Set(['newRqlField', 'deleted']);
|
|
175
|
+
const NEW_HEADER_FIELD_KEYS = new Set(['rqlName', 'key', 'canBeDeleted', 'canBeChanged', 'canBeMoved']);
|
|
176
|
+
function assertKnownKeys(record, allowedKeys, label, hint) {
|
|
177
|
+
for (const key of Object.keys(record)) {
|
|
178
|
+
if (!allowedKeys.has(key)) {
|
|
179
|
+
throw new Error(`${label} contains unsupported key "${key}". ${hint}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function normalizeHeaderEdits(headerEdits) {
|
|
184
|
+
assertKnownKeys(headerEdits, HEADER_EDIT_KEYS, 'CustomObjectPageLayout headerEdits', 'Use addHeaderField(), setTitleField(), setDescriptionField(), or the HeaderEdits keys documented by the SDK.');
|
|
185
|
+
for (const [key, edit] of Object.entries(headerEdits.systemFieldEdits ?? {})) {
|
|
186
|
+
assertKnownKeys(edit, SYSTEM_HEADER_FIELD_EDIT_KEYS, `CustomObjectPageLayout headerEdits.systemFieldEdits.${key}`, 'Use newRqlField and deleted for system header field edits.');
|
|
187
|
+
}
|
|
188
|
+
for (const [index, field] of (headerEdits.newFields ?? []).entries()) {
|
|
189
|
+
assertKnownKeys(field, NEW_HEADER_FIELD_KEYS, `CustomObjectPageLayout headerEdits.newFields[${index}]`, 'Use { rqlName, key } entries, or call addHeaderField(field).');
|
|
190
|
+
}
|
|
191
|
+
if (headerEdits.headerFieldsOrder == null)
|
|
192
|
+
return headerEdits;
|
|
193
|
+
return {
|
|
194
|
+
...headerEdits,
|
|
195
|
+
headerFieldsOrder: normalizeHeaderFieldsOrder(headerEdits),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function addUniqueHeaderOrderField(order, field) {
|
|
199
|
+
if (typeof field !== 'string' || field.length === 0)
|
|
200
|
+
return;
|
|
201
|
+
if (!order.includes(field))
|
|
202
|
+
order.push(field);
|
|
203
|
+
}
|
|
204
|
+
function normalizeHeaderFieldsOrder(headerEdits) {
|
|
205
|
+
const customFieldRqlByKey = new Map((headerEdits.newFields ?? []).map((field) => [field.key, field.rqlName]));
|
|
206
|
+
const systemFieldRqlByKey = new Map(Object.entries(headerEdits.systemFieldEdits ?? {})
|
|
207
|
+
.filter((entry) => {
|
|
208
|
+
const [, edit] = entry;
|
|
209
|
+
return typeof edit.newRqlField === 'string' && edit.newRqlField.length > 0;
|
|
210
|
+
})
|
|
211
|
+
.map(([key, edit]) => [key, edit.newRqlField]));
|
|
212
|
+
const order = [];
|
|
213
|
+
for (const entry of headerEdits.headerFieldsOrder ?? []) {
|
|
214
|
+
addUniqueHeaderOrderField(order, customFieldRqlByKey.get(entry) ?? systemFieldRqlByKey.get(entry) ?? entry);
|
|
215
|
+
}
|
|
216
|
+
addUniqueHeaderOrderField(order, headerEdits.newTitleField);
|
|
217
|
+
addUniqueHeaderOrderField(order, headerEdits.newDescriptionField);
|
|
218
|
+
for (const field of headerEdits.newFields ?? []) {
|
|
219
|
+
addUniqueHeaderOrderField(order, field.rqlName);
|
|
220
|
+
}
|
|
221
|
+
for (const systemEdit of Object.values(headerEdits.systemFieldEdits ?? {})) {
|
|
222
|
+
addUniqueHeaderOrderField(order, systemEdit.newRqlField);
|
|
223
|
+
}
|
|
224
|
+
return order;
|
|
225
|
+
}
|
|
118
226
|
function isTabWithSections(tab) {
|
|
119
227
|
return tab.type === 'tab_with_sections';
|
|
120
228
|
}
|
|
@@ -131,6 +239,11 @@ function insertionIndex(length, position) {
|
|
|
131
239
|
function insertAt(items, item, position) {
|
|
132
240
|
items.splice(insertionIndex(items.length, position), 0, item);
|
|
133
241
|
}
|
|
242
|
+
function insertUniqueAt(items, item, position) {
|
|
243
|
+
if (item == null || item.length === 0 || items.includes(item))
|
|
244
|
+
return;
|
|
245
|
+
insertAt(items, item, position);
|
|
246
|
+
}
|
|
134
247
|
function fieldRqlNameOf(entry) {
|
|
135
248
|
if (typeof entry === 'string')
|
|
136
249
|
return entry;
|
|
@@ -140,6 +253,48 @@ function fieldRqlNameOf(entry) {
|
|
|
140
253
|
const fieldRqlName = record['fieldRqlName'] ?? record['field_rql_name'];
|
|
141
254
|
return typeof fieldRqlName === 'string' ? fieldRqlName : undefined;
|
|
142
255
|
}
|
|
256
|
+
function explicitFieldSectionId(entry) {
|
|
257
|
+
if (entry == null || typeof entry !== 'object')
|
|
258
|
+
return undefined;
|
|
259
|
+
const record = entry;
|
|
260
|
+
const fieldSectionId = record['fieldSection'] ?? record['field_section'];
|
|
261
|
+
return typeof fieldSectionId === 'string' ? fieldSectionId : undefined;
|
|
262
|
+
}
|
|
263
|
+
function inferredSectionIdFromFields(section, resolveFieldSectionId) {
|
|
264
|
+
const sectionIds = new Set();
|
|
265
|
+
for (const field of section.fields ?? []) {
|
|
266
|
+
const explicitSectionId = explicitFieldSectionId(field);
|
|
267
|
+
if (explicitSectionId != null) {
|
|
268
|
+
sectionIds.add(explicitSectionId);
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
const fieldApiName = fieldRqlNameOf(field);
|
|
272
|
+
if (fieldApiName == null || resolveFieldSectionId == null)
|
|
273
|
+
continue;
|
|
274
|
+
const resolvedSectionId = resolveFieldSectionId(fieldApiName);
|
|
275
|
+
if (resolvedSectionId != null)
|
|
276
|
+
sectionIds.add(resolvedSectionId);
|
|
277
|
+
}
|
|
278
|
+
return sectionIds.size === 1 ? [...sectionIds][0] : undefined;
|
|
279
|
+
}
|
|
280
|
+
function annotateLoadedFieldsSection(section, resolveFieldSectionId) {
|
|
281
|
+
assertNoSerializedSectionKey(section);
|
|
282
|
+
const sectionId = layoutSectionKey(section) ?? inferredSectionIdFromFields(section, resolveFieldSectionId);
|
|
283
|
+
return sectionId == null ? section : attachFieldSectionId(section, sectionId);
|
|
284
|
+
}
|
|
285
|
+
function annotateLoadedTabEdits(tabEdits, resolveFieldSectionId) {
|
|
286
|
+
for (const tab of tabEdits.newTabs ?? []) {
|
|
287
|
+
if (!isTabWithSections(tab))
|
|
288
|
+
continue;
|
|
289
|
+
tab.sections = tab.sections.map((section) => annotateLoadedFieldsSection(section, resolveFieldSectionId));
|
|
290
|
+
}
|
|
291
|
+
for (const tabEdit of Object.values(tabEdits.systemTabEdits ?? {})) {
|
|
292
|
+
if (tabEdit.newSections != null) {
|
|
293
|
+
tabEdit.newSections = tabEdit.newSections.map((section) => annotateLoadedFieldsSection(section, resolveFieldSectionId));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return tabEdits;
|
|
297
|
+
}
|
|
143
298
|
function removeIndexes(items, indexes) {
|
|
144
299
|
for (const index of [...indexes].sort((a, b) => b - a)) {
|
|
145
300
|
items.splice(index, 1);
|
|
@@ -213,7 +368,6 @@ function ensureArrayProperty(record, key, label) {
|
|
|
213
368
|
* newTabs: [{
|
|
214
369
|
* key: 'member', name: 'Member', type: 'tab_with_sections',
|
|
215
370
|
* sections: [{
|
|
216
|
-
* key: 'profile',
|
|
217
371
|
* name: 'Profile',
|
|
218
372
|
* type: 'fields_section',
|
|
219
373
|
* section: profileSection,
|
|
@@ -252,7 +406,7 @@ export class CustomObjectPageLayout {
|
|
|
252
406
|
this._customObjectApiName = customObject.getApiName();
|
|
253
407
|
this._name = props.name;
|
|
254
408
|
this._tabEdits = normalizeTabEdits(customObject, props.tabEdits ?? {});
|
|
255
|
-
this._headerEdits = props.headerEdits ?? {};
|
|
409
|
+
this._headerEdits = normalizeHeaderEdits(props.headerEdits ?? {});
|
|
256
410
|
this._visibilityConditions = props.visibilityConditions ?? {};
|
|
257
411
|
this._blueprintKey = props.blueprintKey;
|
|
258
412
|
customObject._register(this);
|
|
@@ -268,14 +422,14 @@ export class CustomObjectPageLayout {
|
|
|
268
422
|
return getExistingManifestContext(options).loadPageLayout(layoutIdOrApiName, options);
|
|
269
423
|
}
|
|
270
424
|
/** @internal Hydrates a page layout from existing manifest wire JSON. */
|
|
271
|
-
static _fromExistingComponent(customObject, component) {
|
|
425
|
+
static _fromExistingComponent(customObject, component, resolveFieldSectionId) {
|
|
272
426
|
const layout = Object.create(CustomObjectPageLayout.prototype);
|
|
273
427
|
Object.assign(layout, {
|
|
274
428
|
_layoutId: requireStringComponentField(component, 'layout_id', CustomObjectPageLayout.componentType),
|
|
275
429
|
_apiName: requireStringComponentField(component, 'api_name', CustomObjectPageLayout.componentType),
|
|
276
430
|
_customObjectApiName: requireStringComponentField(component, 'object_rql_name', CustomObjectPageLayout.componentType),
|
|
277
431
|
_name: requireStringComponentField(component, 'name', CustomObjectPageLayout.componentType),
|
|
278
|
-
_tabEdits: cloneJson(component['tab_edits'] ?? {}),
|
|
432
|
+
_tabEdits: annotateLoadedTabEdits(cloneJson(component['tab_edits'] ?? {}), resolveFieldSectionId),
|
|
279
433
|
_headerEdits: cloneJson(component['header_edits'] ?? {}),
|
|
280
434
|
_visibilityConditions: cloneJson(component['visibility_conditions'] ?? {}),
|
|
281
435
|
_blueprintKey: component['blueprint_key'],
|
|
@@ -313,17 +467,32 @@ export class CustomObjectPageLayout {
|
|
|
313
467
|
/**
|
|
314
468
|
* Adds multiple fields to an existing layout section.
|
|
315
469
|
*
|
|
316
|
-
* Fields are inserted in the order provided.
|
|
317
|
-
*
|
|
318
|
-
* `CUSTOM_OBJECT_FIELD_SECTION
|
|
470
|
+
* Fields are inserted in the order provided. For custom layout sections,
|
|
471
|
+
* pass `options.section` so TypeScript and runtime validation agree on the
|
|
472
|
+
* target `CUSTOM_OBJECT_FIELD_SECTION`.
|
|
319
473
|
*/
|
|
320
474
|
addFields(fields, options) {
|
|
321
475
|
if (fields.length === 0)
|
|
322
476
|
return this;
|
|
477
|
+
let fieldSectionForMutation;
|
|
323
478
|
const container = options.systemSection === true ?
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
479
|
+
(() => {
|
|
480
|
+
assertOptionAbsent(options, 'section', 'CustomObjectPageLayout addField section is only supported for custom layout sections.');
|
|
481
|
+
assertOptionAbsent(options, 'fieldSection', 'CustomObjectPageLayout addField fieldSection is no longer supported. ' +
|
|
482
|
+
'Use sectionKey only for systemSection: true.');
|
|
483
|
+
return this.ensureSystemSectionFieldContainer(options.tabKey, options.sectionKey);
|
|
484
|
+
})()
|
|
485
|
+
: (() => {
|
|
486
|
+
assertOptionAbsent(options, 'sectionKey', 'CustomObjectPageLayout addField sectionKey is only supported with systemSection: true. ' +
|
|
487
|
+
'Pass section: CustomObjectFieldSection for custom layout sections.');
|
|
488
|
+
assertOptionAbsent(options, 'fieldSection', 'CustomObjectPageLayout addField fieldSection is no longer supported. ' +
|
|
489
|
+
'Pass section: CustomObjectFieldSection instead.');
|
|
490
|
+
const section = requireFieldSectionOption(options.section, 'CustomObjectPageLayout addField section');
|
|
491
|
+
this.assertFieldSectionBelongsToLayout(section);
|
|
492
|
+
fieldSectionForMutation = section;
|
|
493
|
+
return this.requireOneFieldContainer(options.tabKey, section.getSectionId(), section);
|
|
494
|
+
})();
|
|
495
|
+
const entries = fields.map((field) => this.serializeFieldForMutation(field, container.sectionKey ?? container.label, options, fieldSectionForMutation));
|
|
327
496
|
if (options.allowDuplicate !== true) {
|
|
328
497
|
const existing = new Set(container.fields.map((entry) => fieldRqlNameOf(entry)).filter(Boolean));
|
|
329
498
|
const seen = new Set();
|
|
@@ -349,9 +518,23 @@ export class CustomObjectPageLayout {
|
|
|
349
518
|
const fieldApiName = typeof field === 'string' ? field : field.getApiName();
|
|
350
519
|
if (isField(field))
|
|
351
520
|
this.assertFieldBelongsToLayout(field);
|
|
521
|
+
let sectionKey;
|
|
522
|
+
if (options.systemSection === true) {
|
|
523
|
+
assertOptionAbsent(options, 'section', 'CustomObjectPageLayout removeField section is only supported for custom layout sections.');
|
|
524
|
+
sectionKey = options.sectionKey;
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
assertOptionAbsent(options, 'sectionKey', 'CustomObjectPageLayout removeField sectionKey is only supported with systemSection: true. ' +
|
|
528
|
+
'Pass section: CustomObjectFieldSection for custom layout sections.');
|
|
529
|
+
if (options.section != null) {
|
|
530
|
+
const section = requireFieldSectionOption(options.section, 'CustomObjectPageLayout removeField section');
|
|
531
|
+
this.assertFieldSectionBelongsToLayout(section);
|
|
532
|
+
sectionKey = section.getSectionId();
|
|
533
|
+
}
|
|
534
|
+
}
|
|
352
535
|
const containers = this.findFieldContainers({
|
|
353
536
|
tabKey: options.tabKey,
|
|
354
|
-
sectionKey
|
|
537
|
+
sectionKey,
|
|
355
538
|
systemSectionOnly: options.systemSection === true,
|
|
356
539
|
});
|
|
357
540
|
const matches = [];
|
|
@@ -400,10 +583,10 @@ export class CustomObjectPageLayout {
|
|
|
400
583
|
if (options.systemTab === true) {
|
|
401
584
|
const tabEdit = this.ensureSystemTabEdit(options.tabKey);
|
|
402
585
|
const sections = ensureArrayProperty(tabEdit, 'newSections', `systemTabEdits.${options.tabKey}.newSections`);
|
|
403
|
-
this.assertSectionKeyAvailable(sections, normalized
|
|
586
|
+
this.assertSectionKeyAvailable(sections, layoutSectionKey(normalized), options.tabKey, options.allowDuplicate);
|
|
587
|
+
const sectionsOrder = this.ensureSystemSectionsOrder(tabEdit);
|
|
404
588
|
insertAt(sections, normalized, options.position);
|
|
405
|
-
|
|
406
|
-
insertAt(tabEdit.sectionsOrder, normalized.key, options.position);
|
|
589
|
+
insertUniqueAt(sectionsOrder, layoutSectionKey(normalized), options.position);
|
|
407
590
|
return this;
|
|
408
591
|
}
|
|
409
592
|
const tab = this.findNewTabWithSections(options.tabKey);
|
|
@@ -411,26 +594,27 @@ export class CustomObjectPageLayout {
|
|
|
411
594
|
throw new Error(`CustomObjectPageLayout could not find new tab "${options.tabKey}". ` +
|
|
412
595
|
'Pass systemTab: true to add a section to a built-in system tab.');
|
|
413
596
|
}
|
|
414
|
-
this.assertSectionKeyAvailable(tab.sections, normalized
|
|
597
|
+
this.assertSectionKeyAvailable(tab.sections, layoutSectionKey(normalized), options.tabKey, options.allowDuplicate);
|
|
415
598
|
insertAt(tab.sections, normalized, options.position);
|
|
416
599
|
return this;
|
|
417
600
|
}
|
|
418
|
-
|
|
419
|
-
* Removes a section from `newTabs` / `systemTabEdits.newSections`.
|
|
420
|
-
*
|
|
421
|
-
* Pass `systemSection: true` with `tabKey` to delete a built-in section via
|
|
422
|
-
* `systemSectionEdits[sectionKey].deleted`.
|
|
423
|
-
*/
|
|
424
|
-
removeSection(sectionKey, options = {}) {
|
|
601
|
+
removeSection(sectionOrKey, options = {}) {
|
|
425
602
|
if (options.systemSection === true) {
|
|
603
|
+
if (typeof sectionOrKey !== 'string') {
|
|
604
|
+
throw new TypeError('CustomObjectPageLayout removeSection with systemSection: true requires a system section key.');
|
|
605
|
+
}
|
|
426
606
|
if (options.tabKey == null) {
|
|
427
607
|
throw new Error('CustomObjectPageLayout removeSection with systemSection: true requires tabKey.');
|
|
428
608
|
}
|
|
429
|
-
const sectionEdit = this.ensureSystemSectionEdit(options.tabKey,
|
|
609
|
+
const sectionEdit = this.ensureSystemSectionEdit(options.tabKey, sectionOrKey);
|
|
430
610
|
sectionEdit.deleted = true;
|
|
431
|
-
this.
|
|
611
|
+
removeKeyFromOrder(this._tabEdits.systemTabEdits?.[options.tabKey]?.sectionsOrder, sectionOrKey);
|
|
612
|
+
this.removeSectionVisibility(sectionOrKey);
|
|
432
613
|
return this;
|
|
433
614
|
}
|
|
615
|
+
const section = requireFieldSectionOption(sectionOrKey, 'CustomObjectPageLayout removeSection section');
|
|
616
|
+
this.assertFieldSectionBelongsToLayout(section);
|
|
617
|
+
const sectionKey = section.getSectionId();
|
|
434
618
|
const matches = [];
|
|
435
619
|
for (const tab of this._tabEdits.newTabs ?? []) {
|
|
436
620
|
if (!isTabWithSections(tab))
|
|
@@ -438,16 +622,16 @@ export class CustomObjectPageLayout {
|
|
|
438
622
|
if (options.tabKey != null && tab.key !== options.tabKey)
|
|
439
623
|
continue;
|
|
440
624
|
tab.sections.forEach((section, index) => {
|
|
441
|
-
if (section
|
|
442
|
-
matches.push({ sections: tab.sections, index, tabKey: tab.key });
|
|
625
|
+
if (layoutSectionMatches(section, sectionKey))
|
|
626
|
+
matches.push({ sections: tab.sections, section, index, tabKey: tab.key });
|
|
443
627
|
});
|
|
444
628
|
}
|
|
445
629
|
for (const [tabKey, tabEdit] of Object.entries(this._tabEdits.systemTabEdits ?? {})) {
|
|
446
630
|
if (options.tabKey != null && tabKey !== options.tabKey)
|
|
447
631
|
continue;
|
|
448
632
|
(tabEdit.newSections ?? []).forEach((section, index) => {
|
|
449
|
-
if (section
|
|
450
|
-
matches.push({ sections: tabEdit.newSections, index, tabEdit, tabKey });
|
|
633
|
+
if (layoutSectionMatches(section, sectionKey))
|
|
634
|
+
matches.push({ sections: tabEdit.newSections, section, index, tabEdit, tabKey });
|
|
451
635
|
});
|
|
452
636
|
}
|
|
453
637
|
if (matches.length === 0) {
|
|
@@ -466,11 +650,11 @@ export class CustomObjectPageLayout {
|
|
|
466
650
|
byArray.set(match.sections, indexes);
|
|
467
651
|
if (match.tabEdit != null)
|
|
468
652
|
removeKeyFromOrder(match.tabEdit.sectionsOrder, sectionKey);
|
|
653
|
+
this.removeSectionVisibility(sectionKey);
|
|
469
654
|
}
|
|
470
655
|
for (const [sections, indexes] of byArray.entries()) {
|
|
471
656
|
removeIndexes(sections, indexes);
|
|
472
657
|
}
|
|
473
|
-
this.removeSectionVisibility(sectionKey);
|
|
474
658
|
return this;
|
|
475
659
|
}
|
|
476
660
|
/** Adds a new tab to `tab_edits.newTabs`. */
|
|
@@ -481,10 +665,9 @@ export class CustomObjectPageLayout {
|
|
|
481
665
|
if (options.allowDuplicate !== true && newTabs.some((existing) => existing.key === normalized.key)) {
|
|
482
666
|
throw new Error(`CustomObjectPageLayout already contains tab "${normalized.key}".`);
|
|
483
667
|
}
|
|
668
|
+
const tabsOrder = this.ensureTabsOrder();
|
|
484
669
|
insertAt(newTabs, normalized, options.position);
|
|
485
|
-
|
|
486
|
-
insertAt(this._tabEdits.tabsOrder, normalized.key, options.position);
|
|
487
|
-
}
|
|
670
|
+
insertUniqueAt(tabsOrder, normalized.key, options.position);
|
|
488
671
|
return this;
|
|
489
672
|
}
|
|
490
673
|
/**
|
|
@@ -517,10 +700,9 @@ export class CustomObjectPageLayout {
|
|
|
517
700
|
if (options.allowDuplicate !== true && newFields.some((existing) => existing.key === headerField.key)) {
|
|
518
701
|
throw new Error(`CustomObjectPageLayout header already contains field key "${headerField.key}".`);
|
|
519
702
|
}
|
|
703
|
+
const headerFieldsOrder = this.ensureHeaderFieldsOrder();
|
|
520
704
|
insertAt(newFields, headerField, options.position);
|
|
521
|
-
|
|
522
|
-
insertAt(this._headerEdits.headerFieldsOrder, headerField.key, options.position);
|
|
523
|
-
}
|
|
705
|
+
insertUniqueAt(headerFieldsOrder, headerField.rqlName, options.position);
|
|
524
706
|
return this;
|
|
525
707
|
}
|
|
526
708
|
/** Removes custom header fields from `header_edits.newFields`. */
|
|
@@ -530,50 +712,104 @@ export class CustomObjectPageLayout {
|
|
|
530
712
|
this.assertFieldBelongsToLayout(fieldOrKey);
|
|
531
713
|
const key = typeof fieldOrKey === 'string' ? fieldOrKey : fieldApiName;
|
|
532
714
|
const newFields = this._headerEdits.newFields ?? [];
|
|
533
|
-
const
|
|
715
|
+
const removedFields = [];
|
|
534
716
|
const removed = removeFromArray(newFields, (field) => {
|
|
535
717
|
const matched = field.key === key || (fieldApiName != null && field.rqlName === fieldApiName);
|
|
536
718
|
if (matched)
|
|
537
|
-
|
|
719
|
+
removedFields.push(field);
|
|
538
720
|
return matched;
|
|
539
721
|
}, `CustomObjectPageLayout header field "${key}"`, options.removeAll !== false);
|
|
540
722
|
if (removed === 0) {
|
|
541
723
|
throw new Error(`CustomObjectPageLayout header does not contain field "${key}".`);
|
|
542
724
|
}
|
|
543
|
-
for (const
|
|
544
|
-
|
|
545
|
-
}
|
|
725
|
+
for (const removedField of removedFields)
|
|
726
|
+
this.removeHeaderOrderFieldIfUnused(removedField);
|
|
546
727
|
return this;
|
|
547
728
|
}
|
|
548
729
|
/** Sets the record title field in `header_edits`. */
|
|
549
730
|
setTitleField(field) {
|
|
550
|
-
this._headerEdits.newTitleField
|
|
731
|
+
const previousTitleField = this._headerEdits.newTitleField;
|
|
732
|
+
const nextTitleField = this.headerFieldApiName(field);
|
|
733
|
+
this._headerEdits.newTitleField = nextTitleField;
|
|
551
734
|
if (field != null)
|
|
552
735
|
this._headerEdits.titleFieldDeleted = false;
|
|
736
|
+
insertUniqueAt(this.ensureHeaderFieldsOrder(), nextTitleField, 'end');
|
|
737
|
+
this.removeHeaderOrderFieldIfUnused(previousTitleField);
|
|
553
738
|
return this;
|
|
554
739
|
}
|
|
555
740
|
/** Removes the record title field from the header. */
|
|
556
741
|
removeTitleField() {
|
|
742
|
+
const previousTitleField = this._headerEdits.newTitleField;
|
|
557
743
|
this._headerEdits.newTitleField = null;
|
|
558
744
|
this._headerEdits.titleFieldDeleted = true;
|
|
745
|
+
this.removeHeaderOrderFieldIfUnused(previousTitleField);
|
|
559
746
|
return this;
|
|
560
747
|
}
|
|
561
748
|
/** Sets the record description field in `header_edits`. */
|
|
562
749
|
setDescriptionField(field) {
|
|
563
|
-
this._headerEdits.newDescriptionField
|
|
750
|
+
const previousDescriptionField = this._headerEdits.newDescriptionField;
|
|
751
|
+
const nextDescriptionField = this.headerFieldApiName(field);
|
|
752
|
+
this._headerEdits.newDescriptionField = nextDescriptionField;
|
|
564
753
|
if (field != null)
|
|
565
754
|
this._headerEdits.descriptionFieldDeleted = false;
|
|
755
|
+
insertUniqueAt(this.ensureHeaderFieldsOrder(), nextDescriptionField, 'end');
|
|
756
|
+
this.removeHeaderOrderFieldIfUnused(previousDescriptionField);
|
|
566
757
|
return this;
|
|
567
758
|
}
|
|
568
759
|
/** Removes the record description field from the header. */
|
|
569
760
|
removeDescriptionField() {
|
|
761
|
+
const previousDescriptionField = this._headerEdits.newDescriptionField;
|
|
570
762
|
this._headerEdits.newDescriptionField = null;
|
|
571
763
|
this._headerEdits.descriptionFieldDeleted = true;
|
|
764
|
+
this.removeHeaderOrderFieldIfUnused(previousDescriptionField);
|
|
572
765
|
return this;
|
|
573
766
|
}
|
|
574
|
-
|
|
767
|
+
ensureTabsOrder() {
|
|
768
|
+
var _a;
|
|
769
|
+
const order = ((_a = this._tabEdits).tabsOrder ?? (_a.tabsOrder = []));
|
|
770
|
+
for (const tab of this._tabEdits.newTabs ?? [])
|
|
771
|
+
insertUniqueAt(order, tab.key, 'end');
|
|
772
|
+
for (const [tabKey, tabEdit] of Object.entries(this._tabEdits.systemTabEdits ?? {})) {
|
|
773
|
+
if (tabEdit.deleted !== true)
|
|
774
|
+
insertUniqueAt(order, tabKey, 'end');
|
|
775
|
+
}
|
|
776
|
+
return order;
|
|
777
|
+
}
|
|
778
|
+
ensureSystemSectionsOrder(tabEdit) {
|
|
779
|
+
const order = (tabEdit.sectionsOrder ?? (tabEdit.sectionsOrder = []));
|
|
780
|
+
for (const section of tabEdit.newSections ?? [])
|
|
781
|
+
insertUniqueAt(order, layoutSectionKey(section), 'end');
|
|
782
|
+
for (const [sectionKey, sectionEdit] of Object.entries(tabEdit.systemSectionEdits ?? {})) {
|
|
783
|
+
if (sectionEdit.deleted !== true)
|
|
784
|
+
insertUniqueAt(order, sectionKey, 'end');
|
|
785
|
+
}
|
|
786
|
+
return order;
|
|
787
|
+
}
|
|
788
|
+
ensureHeaderFieldsOrder() {
|
|
789
|
+
this._headerEdits.headerFieldsOrder = normalizeHeaderFieldsOrder({
|
|
790
|
+
...this._headerEdits,
|
|
791
|
+
headerFieldsOrder: this._headerEdits.headerFieldsOrder ?? [],
|
|
792
|
+
});
|
|
793
|
+
return this._headerEdits.headerFieldsOrder;
|
|
794
|
+
}
|
|
795
|
+
headerOrderFieldIsUsed(rqlName) {
|
|
796
|
+
return (this._headerEdits.newTitleField === rqlName ||
|
|
797
|
+
this._headerEdits.newDescriptionField === rqlName ||
|
|
798
|
+
(this._headerEdits.newFields ?? []).some((field) => field.rqlName === rqlName) ||
|
|
799
|
+
Object.values(this._headerEdits.systemFieldEdits ?? {}).some((edit) => edit.newRqlField === rqlName));
|
|
800
|
+
}
|
|
801
|
+
removeHeaderOrderFieldIfUnused(field) {
|
|
802
|
+
const rqlName = typeof field === 'string' ? field : field?.rqlName;
|
|
803
|
+
if (field != null && typeof field !== 'string' && field.key !== rqlName) {
|
|
804
|
+
removeKeyFromOrder(this._headerEdits.headerFieldsOrder, field.key);
|
|
805
|
+
}
|
|
806
|
+
if (rqlName == null || this.headerOrderFieldIsUsed(rqlName))
|
|
807
|
+
return;
|
|
808
|
+
removeKeyFromOrder(this._headerEdits.headerFieldsOrder, rqlName);
|
|
809
|
+
}
|
|
810
|
+
serializeFieldForMutation(field, fallbackSectionKey, options, fieldSection) {
|
|
575
811
|
this.assertFieldBelongsToLayout(field);
|
|
576
|
-
this.assignFieldSectionForMutation(field, fallbackSectionKey,
|
|
812
|
+
this.assignFieldSectionForMutation(field, fallbackSectionKey, fieldSection);
|
|
577
813
|
return toSerializedSectionField(field.getApiName(), options);
|
|
578
814
|
}
|
|
579
815
|
normalizeSectionFieldForMutation(section, sectionKey, entry) {
|
|
@@ -591,21 +827,20 @@ export class CustomObjectPageLayout {
|
|
|
591
827
|
'pass a Field instance or { field }.');
|
|
592
828
|
}
|
|
593
829
|
normalizeSectionForMutation(input) {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
830
|
+
this.assertFieldSectionBelongsToLayout(input.section);
|
|
831
|
+
const sectionId = input.section.getSectionId();
|
|
832
|
+
if (inputFieldsSectionHasKey(input)) {
|
|
833
|
+
throw new Error('CustomObjectPageLayout fields_section key is not supported. Omit key.');
|
|
597
834
|
}
|
|
598
|
-
const key = input.key ?? input.section.getSectionId();
|
|
599
835
|
const name = input.name ?? input.section.getName();
|
|
600
836
|
const normalized = {
|
|
601
|
-
key,
|
|
602
837
|
name,
|
|
603
838
|
type: 'fields_section',
|
|
604
|
-
fields: input.fields.map((field) => this.normalizeSectionFieldForMutation(input.section,
|
|
839
|
+
fields: input.fields.map((field) => this.normalizeSectionFieldForMutation(input.section, sectionId, field)),
|
|
605
840
|
};
|
|
606
841
|
if (input.layout != null)
|
|
607
842
|
normalized.layout = input.layout;
|
|
608
|
-
return normalized;
|
|
843
|
+
return attachFieldSectionId(normalized, sectionId);
|
|
609
844
|
}
|
|
610
845
|
normalizeTabForMutation(tab) {
|
|
611
846
|
if (tab.type === 'custom_tab')
|
|
@@ -619,7 +854,7 @@ export class CustomObjectPageLayout {
|
|
|
619
854
|
const rawFieldSection = fieldSection;
|
|
620
855
|
if (rawFieldSection != null) {
|
|
621
856
|
if (typeof rawFieldSection === 'string') {
|
|
622
|
-
throw new TypeError(`CustomObjectPageLayout
|
|
857
|
+
throw new TypeError(`CustomObjectPageLayout section must be a CustomObjectFieldSection object; ` +
|
|
623
858
|
`received raw section id "${rawFieldSection}". Load/include the existing field section first.`);
|
|
624
859
|
}
|
|
625
860
|
if (rawFieldSection.getModelApiName() !== this._customObjectApiName) {
|
|
@@ -631,7 +866,7 @@ export class CustomObjectPageLayout {
|
|
|
631
866
|
}
|
|
632
867
|
if (field.getSectionId() == null) {
|
|
633
868
|
throw new Error(`CustomObjectPageLayout field "${field.getApiName()}" is missing field-section metadata. ` +
|
|
634
|
-
`Construct the field with a CustomObjectFieldSection or pass
|
|
869
|
+
`Construct the field with a CustomObjectFieldSection or pass section when adding it to ` +
|
|
635
870
|
`section "${fallbackSectionKey}".`);
|
|
636
871
|
}
|
|
637
872
|
}
|
|
@@ -641,17 +876,45 @@ export class CustomObjectPageLayout {
|
|
|
641
876
|
`"${field.getCustomObjectApiName()}", but the layout belongs to "${this._customObjectApiName}".`);
|
|
642
877
|
}
|
|
643
878
|
}
|
|
644
|
-
|
|
879
|
+
assertFieldSectionBelongsToLayout(section) {
|
|
880
|
+
if (section.getModelApiName() !== this._customObjectApiName) {
|
|
881
|
+
throw new Error(`CustomObjectPageLayout field section "${section.getSectionId()}" belongs to ` +
|
|
882
|
+
`"${section.getModelApiName()}", but the layout belongs to "${this._customObjectApiName}".`);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
requireOneFieldContainer(tabKey, sectionKey, fallbackFieldSection) {
|
|
645
886
|
const containers = this.findFieldContainers({
|
|
646
887
|
tabKey,
|
|
647
888
|
sectionKey,
|
|
648
889
|
systemSectionOnly: false,
|
|
649
890
|
});
|
|
650
891
|
if (containers.length === 0) {
|
|
651
|
-
|
|
652
|
-
|
|
892
|
+
if (sectionKey != null && fallbackFieldSection != null) {
|
|
893
|
+
const unkeyedContainers = this.findFieldContainers({
|
|
894
|
+
tabKey,
|
|
895
|
+
sectionKey: undefined,
|
|
896
|
+
systemSectionOnly: false,
|
|
897
|
+
}).filter((container) => container.section != null && container.sectionKey == null);
|
|
898
|
+
if (unkeyedContainers.length === 1) {
|
|
899
|
+
if (fallbackFieldSection.getModelApiName() !== this._customObjectApiName) {
|
|
900
|
+
throw new Error(`CustomObjectPageLayout field section "${fallbackFieldSection.getSectionId()}" belongs to ` +
|
|
901
|
+
`"${fallbackFieldSection.getModelApiName()}", but the layout belongs to "${this._customObjectApiName}".`);
|
|
902
|
+
}
|
|
903
|
+
const container = unkeyedContainers[0];
|
|
904
|
+
attachFieldSectionId(container.section, fallbackFieldSection.getSectionId());
|
|
905
|
+
container.sectionKey = fallbackFieldSection.getSectionId();
|
|
906
|
+
container.label = `${container.tabKey}.${container.sectionKey}`;
|
|
907
|
+
return container;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
const scope = sectionKey == null ? 'a field section' : (`section "${tabKey != null ? `${tabKey}.` : ''}${sectionKey}"`);
|
|
911
|
+
throw new Error(`CustomObjectPageLayout could not find ${scope}.`);
|
|
653
912
|
}
|
|
654
913
|
if (containers.length > 1) {
|
|
914
|
+
if (sectionKey == null) {
|
|
915
|
+
throw new Error(`CustomObjectPageLayout matched multiple sections: ` +
|
|
916
|
+
`${containers.map((container) => container.label).join(', ')}. Pass section to choose one.`);
|
|
917
|
+
}
|
|
655
918
|
throw new Error(`CustomObjectPageLayout section "${sectionKey}" matched multiple sections: ` +
|
|
656
919
|
`${containers.map((container) => container.label).join(', ')}. Pass tabKey to choose one.`);
|
|
657
920
|
}
|
|
@@ -665,28 +928,34 @@ export class CustomObjectPageLayout {
|
|
|
665
928
|
continue;
|
|
666
929
|
if (filter.tabKey != null && tab.key !== filter.tabKey)
|
|
667
930
|
continue;
|
|
668
|
-
for (const section of tab.sections) {
|
|
669
|
-
|
|
931
|
+
for (const [index, section] of tab.sections.entries()) {
|
|
932
|
+
const sectionKey = layoutSectionKey(section);
|
|
933
|
+
if (filter.sectionKey != null && !layoutSectionMatches(section, filter.sectionKey))
|
|
670
934
|
continue;
|
|
935
|
+
const label = sectionKey == null ? `${tab.key}.sections[${index}]` : `${tab.key}.${sectionKey}`;
|
|
671
936
|
containers.push({
|
|
672
937
|
tabKey: tab.key,
|
|
673
|
-
sectionKey
|
|
674
|
-
|
|
675
|
-
|
|
938
|
+
sectionKey,
|
|
939
|
+
section,
|
|
940
|
+
fields: ensureArrayProperty(section, 'fields', `newTabs.${label}.fields`),
|
|
941
|
+
label,
|
|
676
942
|
});
|
|
677
943
|
}
|
|
678
944
|
}
|
|
679
945
|
for (const [tabKey, tabEdit] of Object.entries(this._tabEdits.systemTabEdits ?? {})) {
|
|
680
946
|
if (filter.tabKey != null && tabKey !== filter.tabKey)
|
|
681
947
|
continue;
|
|
682
|
-
for (const section of tabEdit.newSections ?? []) {
|
|
683
|
-
|
|
948
|
+
for (const [index, section] of (tabEdit.newSections ?? []).entries()) {
|
|
949
|
+
const sectionKey = layoutSectionKey(section);
|
|
950
|
+
if (filter.sectionKey != null && !layoutSectionMatches(section, filter.sectionKey))
|
|
684
951
|
continue;
|
|
952
|
+
const label = sectionKey == null ? `${tabKey}.newSections[${index}]` : `${tabKey}.${sectionKey}`;
|
|
685
953
|
containers.push({
|
|
686
954
|
tabKey,
|
|
687
|
-
sectionKey
|
|
688
|
-
|
|
689
|
-
|
|
955
|
+
sectionKey,
|
|
956
|
+
section,
|
|
957
|
+
fields: ensureArrayProperty(section, 'fields', `systemTabEdits.${label}.fields`),
|
|
958
|
+
label,
|
|
690
959
|
});
|
|
691
960
|
}
|
|
692
961
|
}
|
|
@@ -717,6 +986,9 @@ export class CustomObjectPageLayout {
|
|
|
717
986
|
if (tabKey == null) {
|
|
718
987
|
throw new Error('CustomObjectPageLayout addField with systemSection: true requires tabKey.');
|
|
719
988
|
}
|
|
989
|
+
if (sectionKey == null) {
|
|
990
|
+
throw new Error('CustomObjectPageLayout addField with systemSection: true requires sectionKey.');
|
|
991
|
+
}
|
|
720
992
|
const sectionEdit = this.ensureSystemSectionEdit(tabKey, sectionKey);
|
|
721
993
|
if (sectionEdit.deleted === true) {
|
|
722
994
|
throw new Error(`CustomObjectPageLayout system section "${tabKey}.${sectionKey}" is marked deleted.`);
|
|
@@ -755,7 +1027,7 @@ export class CustomObjectPageLayout {
|
|
|
755
1027
|
assertSectionKeyAvailable(sections, sectionKey, tabKey, allowDuplicate) {
|
|
756
1028
|
if (allowDuplicate === true)
|
|
757
1029
|
return;
|
|
758
|
-
if (sections.some((section) => section
|
|
1030
|
+
if (sectionKey != null && sections.some((section) => layoutSectionMatches(section, sectionKey))) {
|
|
759
1031
|
throw new Error(`CustomObjectPageLayout tab "${tabKey}" already contains section "${sectionKey}".`);
|
|
760
1032
|
}
|
|
761
1033
|
}
|
|
@@ -827,12 +1099,13 @@ export class CustomObjectPageLayout {
|
|
|
827
1099
|
* tabs or sections. Always uses `apiName: 'default'`.
|
|
828
1100
|
*
|
|
829
1101
|
* @param customObject - The custom object to create the layout for.
|
|
830
|
-
* @param props - Optional. Pass `fields` to populate the General section.
|
|
1102
|
+
* @param props - Optional. Pass `section` and `fields` to populate the General section.
|
|
831
1103
|
* @returns A new `CustomObjectPageLayout` registered with the manifest.
|
|
832
1104
|
*
|
|
833
1105
|
* @example
|
|
834
1106
|
* ```ts
|
|
835
1107
|
* CustomObjectPageLayout.basic(memberObj, {
|
|
1108
|
+
* section: profileSection,
|
|
836
1109
|
* fields: [memberFirstName, memberEmail, memberStatusField],
|
|
837
1110
|
* });
|
|
838
1111
|
* ```
|
|
@@ -840,11 +1113,29 @@ export class CustomObjectPageLayout {
|
|
|
840
1113
|
static basic(customObject, props = {}) {
|
|
841
1114
|
const coApiName = customObject.getApiName();
|
|
842
1115
|
const coName = customObject.getName();
|
|
843
|
-
const
|
|
844
|
-
const generalSection =
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
1116
|
+
const fields = props.fields ?? [];
|
|
1117
|
+
const generalSection = props.section ??
|
|
1118
|
+
(() => {
|
|
1119
|
+
const alreadySectionedField = fields.find((field) => field.getSectionId() != null);
|
|
1120
|
+
if (alreadySectionedField != null) {
|
|
1121
|
+
throw new Error(`CustomObjectPageLayout.basic field "${alreadySectionedField.getApiName()}" already belongs to ` +
|
|
1122
|
+
`field section "${alreadySectionedField.getSectionId()}". Pass that CustomObjectFieldSection ` +
|
|
1123
|
+
'as section so basic() does not reassign the field.');
|
|
1124
|
+
}
|
|
1125
|
+
const sectionPrefix = coApiName.replace(/__c$/, '').replace(/[^a-zA-Z0-9]+/g, '_');
|
|
1126
|
+
return new CustomObjectFieldSection(customObject, {
|
|
1127
|
+
sectionId: `sec_${sectionPrefix}_general`,
|
|
1128
|
+
name: 'General',
|
|
1129
|
+
});
|
|
1130
|
+
})();
|
|
1131
|
+
assertSectionBelongsTo(customObject, generalSection);
|
|
1132
|
+
for (const field of fields) {
|
|
1133
|
+
const fieldSectionId = field.getSectionId();
|
|
1134
|
+
if (fieldSectionId != null && fieldSectionId !== generalSection.getSectionId()) {
|
|
1135
|
+
throw new Error(`CustomObjectPageLayout.basic field "${field.getApiName()}" belongs to field section ` +
|
|
1136
|
+
`"${fieldSectionId}", but basic() was given section "${generalSection.getSectionId()}".`);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
848
1139
|
return new CustomObjectPageLayout(customObject, {
|
|
849
1140
|
apiName: 'default',
|
|
850
1141
|
name: `${coName} Layout`,
|
|
@@ -856,11 +1147,10 @@ export class CustomObjectPageLayout {
|
|
|
856
1147
|
type: 'tab_with_sections',
|
|
857
1148
|
sections: [
|
|
858
1149
|
{
|
|
859
|
-
key: 'general',
|
|
860
1150
|
name: 'General',
|
|
861
1151
|
type: 'fields_section',
|
|
862
1152
|
section: generalSection,
|
|
863
|
-
fields
|
|
1153
|
+
fields,
|
|
864
1154
|
},
|
|
865
1155
|
],
|
|
866
1156
|
},
|