PluginProbe ʕ •ᴥ•ʔ
Secure Custom Fields / 6.9.5
Secure Custom Fields v6.9.5
6.9.5 6.9.4 6.9.3 6.9.2 6.9.1 6.9.0 6.8.9 6.8.7 6.8.8 6.8.6 6.8.4 6.8.5 trunk 6.4.0-beta1 6.4.0-beta2 6.4.1 6.4.1-beta3 6.4.1-beta4 6.4.1-beta5 6.4.1-beta6 6.4.1-beta7 6.4.2 6.5.0 6.5.1 6.5.2 6.5.3 6.5.4 6.5.5 6.5.6 6.5.7 6.6.0 6.7.0 6.7.1 6.8.0 6.8.1 6.8.2 6.8.3
secure-custom-fields / assets / build / js / pro / acf-datastore.js
secure-custom-fields / assets / build / js / pro Last commit date
acf-datastore.asset.php 2 months ago acf-datastore.js 2 months ago acf-datastore.js.map 2 months ago acf-datastore.min.asset.php 2 months ago acf-datastore.min.js 2 months ago acf-field-bindings.asset.php 2 months ago acf-field-bindings.js 2 months ago acf-field-bindings.js.map 2 months ago acf-field-bindings.min.asset.php 2 months ago acf-field-bindings.min.js 2 months ago acf-pro-blocks.asset.php 2 weeks ago acf-pro-blocks.js 2 weeks ago acf-pro-blocks.js.map 2 weeks ago acf-pro-blocks.min.asset.php 2 weeks ago acf-pro-blocks.min.js 2 weeks ago acf-pro-field-group.asset.php 11 months ago acf-pro-field-group.js 11 months ago acf-pro-field-group.js.map 8 months ago acf-pro-field-group.min.asset.php 8 months ago acf-pro-field-group.min.js 8 months ago acf-pro-input.asset.php 3 days ago acf-pro-input.js 3 days ago acf-pro-input.js.map 3 days ago acf-pro-input.min.asset.php 3 days ago acf-pro-input.min.js 3 days ago acf-pro-ui-options-page.asset.php 8 months ago acf-pro-ui-options-page.js 8 months ago acf-pro-ui-options-page.js.map 8 months ago acf-pro-ui-options-page.min.asset.php 8 months ago acf-pro-ui-options-page.min.js 8 months ago index.php 1 year ago
acf-datastore.js
1016 lines
1 /******/ (() => { // webpackBootstrap
2 /*!********************************************!*\
3 !*** ./assets/src/js/pro/acf-datastore.js ***!
4 \********************************************/
5 /* global acf, ajaxurl, jQuery */
6 (() => {
7 'use strict';
8
9 const STORE_NAME = 'acf/fields';
10 const COMPLEX_FIELD_TYPES = ['repeater', 'group', 'flexible_content', 'clone'];
11 const AJAX_LOOKUP_FIELD_TYPES = new Set(['post_object', 'page_link', 'relationship', 'taxonomy', 'user']);
12 const REPEATER_ROW_STATUS_NAME_PATTERN = new RegExp(`\\[acf_(${['added', 'changed', 'deleted', 'reordered', 'inserted'].join('|')})]$`);
13 const DEFAULT_STATE = {
14 context: {
15 postId: 0,
16 postType: ''
17 },
18 fields: {},
19 values: {},
20 savedValues: {},
21 nameToKey: {},
22 fieldGroups: [],
23 initialized: false,
24 syncing: false
25 };
26 const cloneValue = value => {
27 if (null === value || 'object' !== typeof value) {
28 return value;
29 }
30 return JSON.parse(JSON.stringify(value));
31 };
32 const isComplexFieldType = type => COMPLEX_FIELD_TYPES.includes(type);
33 const buildNameToKeyMap = fields => {
34 const nameToKey = {};
35 for (const fieldKey of Object.keys(fields)) {
36 const field = fields[fieldKey];
37 if (field.name) {
38 nameToKey[field.name] = fieldKey;
39 }
40 }
41 return nameToKey;
42 };
43 const resolveFieldKey = (state, keyOrName) => {
44 if (!keyOrName) {
45 return undefined;
46 }
47 return state.fields[keyOrName] ? keyOrName : state.nameToKey[keyOrName];
48 };
49 const getNestedValue = (value, path) => {
50 let currentValue = value;
51 for (const pathPart of path) {
52 if (null === currentValue || undefined === currentValue) {
53 return undefined;
54 }
55 currentValue = currentValue[pathPart];
56 }
57 return currentValue;
58 };
59 const setNestedValue = (value, path, nextValue) => {
60 if (0 === path.length) {
61 return nextValue;
62 }
63 const pathPart = path[0];
64 const currentValue = value ?? {};
65 const clonedValue = Array.isArray(currentValue) ? [...currentValue] : {
66 ...currentValue
67 };
68 clonedValue[pathPart] = setNestedValue(clonedValue[pathPart], path.slice(1), nextValue);
69 return clonedValue;
70 };
71 const reducer = (state = DEFAULT_STATE, action) => {
72 switch (action.type) {
73 case 'INITIALIZE_STORE':
74 {
75 const fields = action.fields ?? {};
76 const values = action.values ?? {};
77 return {
78 ...state,
79 context: action.context ?? state.context,
80 fields,
81 values,
82 savedValues: cloneValue(values),
83 nameToKey: buildNameToKeyMap(fields),
84 fieldGroups: action.fieldGroups ?? [],
85 initialized: true
86 };
87 }
88 case 'REGISTER_FIELD_GROUP':
89 {
90 const fields = {
91 ...state.fields,
92 ...(action.fields ?? {})
93 };
94 const incomingValues = action.values ?? {};
95 const values = {
96 ...state.values
97 };
98 const savedValues = {
99 ...state.savedValues
100 };
101 for (const fieldKey of Object.keys(incomingValues)) {
102 if (!(fieldKey in values)) {
103 values[fieldKey] = incomingValues[fieldKey];
104 }
105 if (!(fieldKey in savedValues)) {
106 savedValues[fieldKey] = cloneValue(incomingValues[fieldKey]);
107 }
108 }
109 const existingGroupKeys = new Set(state.fieldGroups.map(fieldGroup => fieldGroup.key));
110 const fieldGroups = (action.fieldGroups ?? []).filter(fieldGroup => !existingGroupKeys.has(fieldGroup.key));
111 const context = 0 === state.context.postId && action.context ? action.context : state.context;
112 return {
113 ...state,
114 context,
115 fields,
116 values,
117 savedValues,
118 nameToKey: buildNameToKeyMap(fields),
119 fieldGroups: [...state.fieldGroups, ...fieldGroups],
120 initialized: true
121 };
122 }
123 case 'SET_FIELD_VALUE':
124 {
125 const fieldKey = resolveFieldKey(state, action.fieldKey);
126 if (!fieldKey) {
127 return state;
128 }
129 return {
130 ...state,
131 values: {
132 ...state.values,
133 [fieldKey]: action.value
134 }
135 };
136 }
137 case 'SET_VALUES':
138 {
139 const values = {};
140 for (const keyOrName of Object.keys(action.values)) {
141 const fieldKey = resolveFieldKey(state, keyOrName);
142 if (fieldKey) {
143 values[fieldKey] = action.values[keyOrName];
144 }
145 }
146 return {
147 ...state,
148 values: {
149 ...state.values,
150 ...values
151 }
152 };
153 }
154 case 'SET_SUB_FIELD_VALUE':
155 {
156 const parentKey = resolveFieldKey(state, action.parentKey);
157 if (!parentKey) {
158 return state;
159 }
160 const parentValue = state.values[parentKey] ?? {};
161 const nextParentValue = setNestedValue(parentValue, action.path, action.value);
162 return {
163 ...state,
164 values: {
165 ...state.values,
166 [parentKey]: nextParentValue
167 }
168 };
169 }
170 case 'ADD_REPEATER_ROW':
171 {
172 const fieldKey = resolveFieldKey(state, action.fieldKey);
173 if (!fieldKey) {
174 return state;
175 }
176 const currentRows = Array.isArray(state.values[fieldKey]) ? state.values[fieldKey] : [];
177 const rows = [...currentRows];
178 const index = Math.max(0, Math.min(action.index ?? rows.length, rows.length));
179 rows.splice(index, 0, action.rowData ?? {});
180 return {
181 ...state,
182 values: {
183 ...state.values,
184 [fieldKey]: rows
185 }
186 };
187 }
188 case 'REMOVE_REPEATER_ROW':
189 {
190 const fieldKey = resolveFieldKey(state, action.fieldKey);
191 if (!fieldKey) {
192 return state;
193 }
194 const rows = state.values[fieldKey];
195 if (!Array.isArray(rows)) {
196 return state;
197 }
198 if (action.rowIndex < 0 || action.rowIndex >= rows.length) {
199 return state;
200 }
201 const nextRows = [...rows];
202 nextRows.splice(action.rowIndex, 1);
203 return {
204 ...state,
205 values: {
206 ...state.values,
207 [fieldKey]: nextRows
208 }
209 };
210 }
211 case 'MOVE_REPEATER_ROW':
212 {
213 const fieldKey = resolveFieldKey(state, action.fieldKey);
214 if (!fieldKey) {
215 return state;
216 }
217 const rows = state.values[fieldKey];
218 if (!Array.isArray(rows)) {
219 return state;
220 }
221 if (action.fromIndex < 0 || action.fromIndex >= rows.length || action.toIndex < 0 || action.toIndex >= rows.length) {
222 return state;
223 }
224 const nextRows = [...rows];
225 const [movedRow] = nextRows.splice(action.fromIndex, 1);
226 nextRows.splice(action.toIndex, 0, movedRow);
227 return {
228 ...state,
229 values: {
230 ...state.values,
231 [fieldKey]: nextRows
232 }
233 };
234 }
235 case 'MARK_AS_SAVED':
236 return {
237 ...state,
238 savedValues: cloneValue(state.values)
239 };
240 case 'SET_SYNCING':
241 return {
242 ...state,
243 syncing: action.isSyncing
244 };
245 default:
246 return state;
247 }
248 };
249 const selectors = {
250 getFieldValue: (state, keyOrName) => {
251 const fieldKey = resolveFieldKey(state, keyOrName);
252 return fieldKey ? state.values[fieldKey] : undefined;
253 },
254 getFieldValueByKey: (state, fieldKey) => state.values[fieldKey],
255 getFieldValueByName: (state, fieldName) => {
256 const fieldKey = state.nameToKey[fieldName];
257 return fieldKey ? state.values[fieldKey] : undefined;
258 },
259 getAllValues: state => state.values,
260 getAllValuesByName: state => {
261 const valuesByName = {};
262 for (const fieldName of Object.keys(state.nameToKey)) {
263 const fieldKey = state.nameToKey[fieldName];
264 if (fieldKey in state.values) {
265 valuesByName[fieldName] = state.values[fieldKey];
266 }
267 }
268 return valuesByName;
269 },
270 getChangedValues: state => {
271 const changedValues = {};
272 for (const fieldKey of Object.keys(state.values)) {
273 if (JSON.stringify(state.values[fieldKey]) !== JSON.stringify(state.savedValues[fieldKey])) {
274 changedValues[fieldKey] = state.values[fieldKey];
275 }
276 }
277 return changedValues;
278 },
279 getField: (state, keyOrName) => {
280 const fieldKey = resolveFieldKey(state, keyOrName);
281 return fieldKey ? state.fields[fieldKey] : undefined;
282 },
283 getFields: state => state.fields,
284 getFieldsByGroup: (state, fieldGroupKey) => {
285 const fields = {};
286 for (const fieldKey of Object.keys(state.fields)) {
287 if (state.fields[fieldKey].fieldGroupKey === fieldGroupKey) {
288 fields[fieldKey] = state.fields[fieldKey];
289 }
290 }
291 return fields;
292 },
293 getFieldKeyByName: (state, fieldName) => state.nameToKey[fieldName],
294 getSubFieldValue: (state, parentKey, ...path) => {
295 const fieldKey = resolveFieldKey(state, parentKey);
296 return fieldKey ? getNestedValue(state.values[fieldKey], path) : undefined;
297 },
298 isInitialized: state => state.initialized,
299 isSyncing: state => state.syncing,
300 hasChanges: state => JSON.stringify(state.values) !== JSON.stringify(state.savedValues),
301 isDirty: (state, keyOrName) => {
302 const fieldKey = resolveFieldKey(state, keyOrName);
303 return !!(fieldKey && JSON.stringify(state.values[fieldKey]) !== JSON.stringify(state.savedValues[fieldKey]));
304 },
305 getContext: state => state.context,
306 getFieldGroups: state => state.fieldGroups
307 };
308 const actions = {
309 initializeStore: storeData => ({
310 type: 'INITIALIZE_STORE',
311 context: storeData.context,
312 fields: storeData.fields,
313 values: storeData.values,
314 fieldGroups: storeData.fieldGroups
315 }),
316 registerFieldGroup: storeData => ({
317 type: 'REGISTER_FIELD_GROUP',
318 context: storeData.context,
319 fields: storeData.fields,
320 values: storeData.values,
321 fieldGroups: storeData.fieldGroups
322 }),
323 setFieldValue: (fieldKey, value) => ({
324 type: 'SET_FIELD_VALUE',
325 fieldKey,
326 value
327 }),
328 setValues: values => ({
329 type: 'SET_VALUES',
330 values
331 }),
332 setSubFieldValue: (parentKey, ...pathAndValue) => {
333 const value = pathAndValue.pop();
334 return {
335 type: 'SET_SUB_FIELD_VALUE',
336 parentKey,
337 path: pathAndValue,
338 value
339 };
340 },
341 addRepeaterRow: (fieldKey, rowData, index) => ({
342 type: 'ADD_REPEATER_ROW',
343 fieldKey,
344 rowData,
345 index
346 }),
347 removeRepeaterRow: (fieldKey, rowIndex) => ({
348 type: 'REMOVE_REPEATER_ROW',
349 fieldKey,
350 rowIndex
351 }),
352 moveRepeaterRow: (fieldKey, fromIndex, toIndex) => ({
353 type: 'MOVE_REPEATER_ROW',
354 fieldKey,
355 fromIndex,
356 toIndex
357 }),
358 markAsSaved: () => ({
359 type: 'MARK_AS_SAVED'
360 }),
361 setSyncing: isSyncing => ({
362 type: 'SET_SYNCING',
363 isSyncing
364 })
365 };
366 if (window.wp?.data?.createReduxStore) {
367 wp.data.register(wp.data.createReduxStore(STORE_NAME, {
368 reducer,
369 selectors,
370 actions
371 }));
372 }
373 const normalizeLookupValue = value => {
374 if (null === value || undefined === value || '' === value || false === value) {
375 return [];
376 }
377 if (Array.isArray(value)) {
378 return value.filter(item => null !== item && undefined !== item && '' !== item);
379 }
380 return [value];
381 };
382 const getMissingLookupValues = (field, value) => {
383 const fieldType = field.get('type');
384 if (!AJAX_LOOKUP_FIELD_TYPES.has(fieldType)) {
385 return [];
386 }
387 const values = normalizeLookupValue(value);
388 if (!values.length) {
389 return [];
390 }
391 if ('relationship' === fieldType) {
392 const renderedIds = new Set();
393 field.$el.find('.choices-list .acf-rel-item').each(function () {
394 renderedIds.add(String(jQuery(this).data('id')));
395 });
396 return values.filter(item => !renderedIds.has(String(item)));
397 }
398 const $select = field.$el.find('select').first();
399 if (!$select.length) {
400 return [];
401 }
402 const renderedIds = new Set();
403 $select.find('option').each(function () {
404 renderedIds.add(String(jQuery(this).val()));
405 });
406 return values.filter(item => !renderedIds.has(String(item)));
407 };
408 const appendLookupOptions = (field, options) => {
409 if (!options.length) {
410 return;
411 }
412 if ('relationship' === field.get('type')) {
413 const $choicesList = field.$el.find('.choices-list').first();
414 if (!$choicesList.length) {
415 return;
416 }
417 for (const option of options) {
418 const id = String(option.id);
419 const alreadyExists = $choicesList.find('.acf-rel-item').toArray().some(element => String(jQuery(element).data('id')) === id);
420 if (alreadyExists) {
421 continue;
422 }
423 const $item = jQuery('<li><span tabindex="0" class="acf-rel-item acf-rel-item-add"></span></li>');
424 $item.find('.acf-rel-item').attr('data-id', id).text(option.text);
425 $choicesList.append($item);
426 }
427 return;
428 }
429 const $select = field.$el.find('select').first();
430 if (!$select.length) {
431 return;
432 }
433 for (const option of options) {
434 const id = String(option.id);
435 const alreadyExists = $select.find('option').toArray().some(element => String(element.value) === id);
436 if (!alreadyExists) {
437 $select.append(jQuery('<option></option>').attr('value', id).text(option.text));
438 }
439 }
440 };
441 const extractOptionFromAjaxResponse = (response, requestedId) => {
442 const results = response?.results;
443 if (!Array.isArray(results)) {
444 return null;
445 }
446 const requestedIdString = String(requestedId);
447 for (const result of results) {
448 if (!result || 'object' !== typeof result) {
449 continue;
450 }
451 if (Array.isArray(result.children)) {
452 for (const child of result.children) {
453 if (!child || 'object' !== typeof child) {
454 continue;
455 }
456 if (String(child.id) === requestedIdString && 'string' === typeof child.text) {
457 return {
458 id: child.id,
459 text: child.text
460 };
461 }
462 }
463 } else if (String(result.id) === requestedIdString && 'string' === typeof result.text) {
464 return {
465 id: result.id,
466 text: result.text
467 };
468 }
469 }
470 return null;
471 };
472 const fetchLookupOption = async (field, value) => {
473 const fieldType = field.get('type');
474 const fieldKey = field.get('key');
475 const nonce = field.get('nonce');
476 if (!fieldType || !fieldKey || !nonce || !window.ajaxurl) {
477 return null;
478 }
479 const body = new FormData();
480 body.append('action', `acf/fields/${fieldType}/query`);
481 body.append('field_key', fieldKey);
482 body.append('nonce', nonce);
483 body.append('include', String(value));
484 try {
485 const response = await fetch(ajaxurl, {
486 method: 'POST',
487 credentials: 'same-origin',
488 body
489 });
490 if (!response.ok) {
491 return null;
492 }
493 return extractOptionFromAjaxResponse(await response.json(), value);
494 } catch (error) {
495 return null;
496 }
497 };
498 const readComplexValue = field => {
499 const fieldType = field.get('type');
500 if ('group' === fieldType || 'clone' === fieldType) {
501 return readGroupValue(field);
502 }
503 if ('repeater' === fieldType) {
504 return readRepeaterValue(field);
505 }
506 if ('flexible_content' === fieldType) {
507 return readFlexibleContentValue(field);
508 }
509 return undefined;
510 };
511 const readGroupValue = field => {
512 const value = {};
513 const childFields = acf.getFields({
514 parent: field.$el
515 });
516 for (const childField of childFields) {
517 const childKey = childField.get('key');
518 const childType = childField.get('type');
519 value[childKey] = isComplexFieldType(childType) ? readComplexValue(childField) : childField.val();
520 }
521 return value;
522 };
523 const readRepeaterValue = field => {
524 const value = [];
525 field.$el.find('> .acf-input > .acf-repeater > table > tbody > tr.acf-row:not(.acf-clone)').each(function () {
526 const $row = jQuery(this);
527 const rowValue = {};
528 const childFields = acf.getFields({
529 parent: $row
530 });
531 for (const childField of childFields) {
532 const childKey = childField.get('key');
533 const childType = childField.get('type');
534 rowValue[childKey] = isComplexFieldType(childType) ? readComplexValue(childField) : childField.val();
535 }
536 value.push(rowValue);
537 });
538 return value;
539 };
540 const readFlexibleContentValue = field => {
541 const value = [];
542 field.$el.find('> .acf-input > .acf-flexible-content > .values > .layout:not(.acf-clone)').each(function () {
543 const $layout = jQuery(this);
544 const layoutValue = {
545 acf_fc_layout: $layout.data('layout')
546 };
547 const childFields = acf.getFields({
548 parent: $layout
549 });
550 for (const childField of childFields) {
551 const childKey = childField.get('key');
552 const childType = childField.get('type');
553 layoutValue[childKey] = isComplexFieldType(childType) ? readComplexValue(childField) : childField.val();
554 }
555 value.push(layoutValue);
556 });
557 return value;
558 };
559 const removeLayoutOrRow = $element => {
560 acf.doAction('remove', $element);
561 $element.remove();
562 };
563 const writeFieldValue = (field, value) => {
564 const fieldType = field.get('type');
565 if (!isComplexFieldType(fieldType)) {
566 field.val(value);
567 return;
568 }
569 if ('group' === fieldType || 'clone' === fieldType) {
570 writeGroupValue(field, value);
571 } else if ('repeater' === fieldType) {
572 writeRepeaterValue(field, value);
573 } else if ('flexible_content' === fieldType) {
574 writeFlexibleContentValue(field, value);
575 }
576 };
577 const writeGroupValue = (field, value) => {
578 if (value && 'object' === typeof value && !Array.isArray(value)) {
579 writeChildValues(field.$el, value);
580 }
581 };
582 const writeRepeaterValue = (field, value) => {
583 if (field.get('pagination')) {
584 return;
585 }
586 const rows = Array.isArray(value) ? value : [];
587 const getRows = () => field.$el.find('> .acf-input > .acf-repeater > table > tbody > tr.acf-row:not(.acf-clone)');
588 const currentRowCount = getRows().length;
589 for (let index = currentRowCount - 1; index >= rows.length; index--) {
590 removeLayoutOrRow(getRows().eq(index));
591 }
592 for (let index = currentRowCount; index < rows.length; index++) {
593 field.add();
594 }
595 getRows().each(function (index) {
596 const rowValue = rows[index];
597 if (rowValue && 'object' === typeof rowValue) {
598 writeChildValues(jQuery(this), rowValue);
599 }
600 });
601 };
602 const writeFlexibleContentValue = (field, value) => {
603 const layouts = Array.isArray(value) ? value : [];
604 const $currentLayouts = field.$layouts();
605 const layoutShapeChanged = $currentLayouts.length !== layouts.length || !$currentLayouts.toArray().every((element, index) => {
606 return jQuery(element).data('layout') === layouts[index]?.acf_fc_layout;
607 });
608 if (layoutShapeChanged) {
609 $currentLayouts.each(function () {
610 removeLayoutOrRow(jQuery(this));
611 });
612 for (const layoutValue of layouts) {
613 const layoutName = layoutValue?.acf_fc_layout;
614 if ('string' === typeof layoutName && layoutName) {
615 field.add({
616 layout: layoutName
617 });
618 }
619 }
620 }
621 field.$layouts().each(function (index) {
622 const layoutValue = layouts[index];
623 if (layoutValue && 'object' === typeof layoutValue) {
624 writeChildValues(jQuery(this), layoutValue);
625 }
626 });
627 };
628 const writeChildValues = ($parent, values) => {
629 for (const childField of acf.getFields({
630 parent: $parent
631 })) {
632 const childKey = childField.get('key');
633 if (childKey in values) {
634 writeFieldValue(childField, values[childKey]);
635 }
636 }
637 };
638 if (window.wp?.data?.select && window.wp?.data?.dispatch) {
639 let isSyncingDomAndStore = false;
640 let previousStoreValues = null;
641 const getTopLevelFieldKey = field => {
642 const parents = field.parents();
643 return parents.length ? parents[parents.length - 1].get('key') : field.get('key');
644 };
645 const writeStoreValueToField = (fieldKey, value) => {
646 const $field = acf.findField(fieldKey);
647 if (!$field.length) {
648 return;
649 }
650 const field = acf.getField($field);
651 if (field) {
652 writeFieldValue(field, value);
653 }
654 };
655 const fetchMissingLookupOptions = async (changedKeys, values) => {
656 const optionsByFieldKey = {};
657 const requests = [];
658 for (const fieldKey of changedKeys) {
659 const $field = acf.findField(fieldKey);
660 if (!$field.length) {
661 continue;
662 }
663 const field = acf.getField($field);
664 if (!field || !AJAX_LOOKUP_FIELD_TYPES.has(field.get('type'))) {
665 continue;
666 }
667 const missingValues = getMissingLookupValues(field, values[fieldKey]);
668 if (!missingValues.length) {
669 continue;
670 }
671 optionsByFieldKey[fieldKey] = [];
672 for (const missingValue of missingValues) {
673 requests.push(fetchLookupOption(field, missingValue).then(option => {
674 if (option) {
675 optionsByFieldKey[fieldKey].push(option);
676 }
677 }));
678 }
679 }
680 await Promise.all(requests);
681 return optionsByFieldKey;
682 };
683 const syncDomFromStore = () => {
684 if (isSyncingDomAndStore) {
685 return;
686 }
687 const store = wp.data.select(STORE_NAME);
688 if (!store.isInitialized()) {
689 return;
690 }
691 const values = store.getAllValues();
692 if (values === previousStoreValues) {
693 return;
694 }
695 const previousValues = previousStoreValues ?? {};
696 const changedKeys = Object.keys(values).filter(fieldKey => values[fieldKey] !== previousValues[fieldKey]);
697 previousStoreValues = values;
698 if (!changedKeys.length) {
699 return;
700 }
701 let needsLookupOptions = false;
702 for (const fieldKey of changedKeys) {
703 const $field = acf.findField(fieldKey);
704 if (!$field.length) {
705 continue;
706 }
707 const field = acf.getField($field);
708 if (field && AJAX_LOOKUP_FIELD_TYPES.has(field.get('type')) && getMissingLookupValues(field, values[fieldKey]).length) {
709 needsLookupOptions = true;
710 break;
711 }
712 }
713 if (needsLookupOptions) {
714 isSyncingDomAndStore = true;
715 fetchMissingLookupOptions(changedKeys, values).then(optionsByFieldKey => {
716 try {
717 for (const [fieldKey, options] of Object.entries(optionsByFieldKey)) {
718 const $field = acf.findField(fieldKey);
719 if (!$field.length) {
720 continue;
721 }
722 const field = acf.getField($field);
723 if (field) {
724 appendLookupOptions(field, options);
725 }
726 }
727 for (const fieldKey of changedKeys) {
728 writeStoreValueToField(fieldKey, values[fieldKey]);
729 }
730 } finally {
731 isSyncingDomAndStore = false;
732 previousStoreValues = values;
733 if (wp.data.select(STORE_NAME).getAllValues() !== values) {
734 queueMicrotask(syncDomFromStore);
735 }
736 }
737 }).catch(() => {
738 isSyncingDomAndStore = false;
739 });
740 return;
741 }
742 isSyncingDomAndStore = true;
743 try {
744 for (const fieldKey of changedKeys) {
745 writeStoreValueToField(fieldKey, values[fieldKey]);
746 }
747 } finally {
748 isSyncingDomAndStore = false;
749 }
750 };
751 new acf.Model({
752 id: 'datastoreSync',
753 wait: 'prepare',
754 initialize() {
755 if (!acf.isGutenbergPostEditor()) {
756 return;
757 }
758 this.initializeStore();
759 this.subscribeToStore();
760 this.listenToDOM();
761 this.setupConvenienceAPI();
762 },
763 initializeStore() {
764 const storeData = acf.get('storeData');
765 if (!storeData) {
766 return;
767 }
768 wp.data.dispatch(STORE_NAME).initializeStore(storeData);
769 this.reconcileWithDOM();
770 previousStoreValues = wp.data.select(STORE_NAME).getAllValues();
771 },
772 reconcileWithDOM() {
773 const fields = acf.getFields();
774 if (!fields?.length) {
775 return;
776 }
777 const store = wp.data.select(STORE_NAME);
778 const values = {};
779 for (const field of fields) {
780 const fieldKey = field.get('key');
781 if (!fieldKey || !store.getField(fieldKey)) {
782 continue;
783 }
784 const storeValue = store.getFieldValue(fieldKey);
785 if (undefined === storeValue) {
786 continue;
787 }
788 const fieldType = field.get('type');
789 const domValue = isComplexFieldType(fieldType) ? readComplexValue(field) : field.val();
790 if (JSON.stringify(domValue) !== JSON.stringify(storeValue)) {
791 values[fieldKey] = domValue;
792 }
793 }
794 if (Object.keys(values).length) {
795 wp.data.dispatch(STORE_NAME).setValues(values);
796 }
797 },
798 subscribeToStore() {
799 this.storeUnsubscribe = wp.data.subscribe(syncDomFromStore);
800 },
801 listenToDOM() {
802 acf.addAction('change_field', this.onFieldChange, 10, this);
803 acf.addAction('append_field', this.onFieldStructureChange, 10, this);
804 acf.addAction('remove_field', this.onFieldStructureChange, 10, this);
805 acf.addAction('sortstop_field', this.onFieldStructureChange, 10, this);
806 acf.addAction('refresh_post_screen', this.onRefreshPostScreen, 10, this);
807 },
808 onFieldChange(field) {
809 if (isSyncingDomAndStore) {
810 return;
811 }
812 if (!wp.data.select(STORE_NAME).isInitialized()) {
813 return;
814 }
815 const fieldKey = field.get('key');
816 if (!fieldKey) {
817 return;
818 }
819 isSyncingDomAndStore = true;
820 try {
821 const parentField = field.parent();
822 if (parentField && isComplexFieldType(parentField.get('type'))) {
823 const topLevelKey = getTopLevelFieldKey(field);
824 if (topLevelKey) {
825 const $topLevelField = acf.findField(topLevelKey);
826 if ($topLevelField.length) {
827 const topLevelField = acf.getField($topLevelField);
828 if (topLevelField) {
829 wp.data.dispatch(STORE_NAME).setFieldValue(topLevelKey, readComplexValue(topLevelField));
830 }
831 }
832 }
833 } else if (isComplexFieldType(field.get('type'))) {
834 wp.data.dispatch(STORE_NAME).setFieldValue(fieldKey, readComplexValue(field));
835 } else {
836 wp.data.dispatch(STORE_NAME).setFieldValue(fieldKey, field.val());
837 }
838 } finally {
839 isSyncingDomAndStore = false;
840 previousStoreValues = wp.data.select(STORE_NAME).getAllValues();
841 }
842 },
843 onFieldStructureChange(field) {
844 if (isSyncingDomAndStore) {
845 return;
846 }
847 if (!wp.data.select(STORE_NAME).isInitialized()) {
848 return;
849 }
850 const topLevelKey = getTopLevelFieldKey(field);
851 if (!topLevelKey) {
852 return;
853 }
854 const $topLevelField = acf.findField(topLevelKey);
855 if (!$topLevelField.length) {
856 return;
857 }
858 const topLevelField = acf.getField($topLevelField);
859 if (!topLevelField || !isComplexFieldType(topLevelField.get('type'))) {
860 return;
861 }
862 isSyncingDomAndStore = true;
863 try {
864 wp.data.dispatch(STORE_NAME).setFieldValue(topLevelKey, readComplexValue(topLevelField));
865 } finally {
866 isSyncingDomAndStore = false;
867 previousStoreValues = wp.data.select(STORE_NAME).getAllValues();
868 }
869 },
870 onRefreshPostScreen(response) {
871 if (!response?.storeData) {
872 return;
873 }
874 isSyncingDomAndStore = true;
875 try {
876 wp.data.dispatch(STORE_NAME).registerFieldGroup(response.storeData);
877 this.reconcileWithDOM();
878 } finally {
879 isSyncingDomAndStore = false;
880 previousStoreValues = wp.data.select(STORE_NAME).getAllValues();
881 }
882 },
883 setupConvenienceAPI() {
884 acf.store = {
885 get(fieldKey) {
886 if (wp.data.select(STORE_NAME).isInitialized()) {
887 return wp.data.select(STORE_NAME).getFieldValue(fieldKey);
888 }
889 return undefined;
890 },
891 set(fieldKey, value) {
892 if (wp.data.select(STORE_NAME).isInitialized()) {
893 wp.data.dispatch(STORE_NAME).setFieldValue(fieldKey, value);
894 }
895 },
896 subscribe(fieldKey, callback) {
897 let previousValue = wp.data.select(STORE_NAME).getFieldValue(fieldKey);
898 return wp.data.subscribe(() => {
899 const nextValue = wp.data.select(STORE_NAME).getFieldValue(fieldKey);
900 if (nextValue !== previousValue) {
901 const oldValue = previousValue;
902 previousValue = nextValue;
903 callback(nextValue, oldValue);
904 }
905 });
906 }
907 };
908 }
909 });
910 }
911 const collectPaginatedRepeaterRowsForSave = fieldKey => {
912 const $field = acf.findField(fieldKey);
913 if (!$field.length) {
914 return {};
915 }
916 const $rows = $field.find('> .acf-input > .acf-repeater > table > tbody > tr.acf-row:not(.acf-clone)');
917 const rowsById = {};
918 $rows.each(function () {
919 const $row = jQuery(this);
920 const rowId = $row.data('id');
921 if (!rowId) {
922 return;
923 }
924 const rowValue = {};
925 for (const childField of acf.getFields({
926 parent: $row
927 })) {
928 const childKey = childField.get('key');
929 const childType = childField.get('type');
930 rowValue[childKey] = isComplexFieldType(childType) ? readComplexValue(childField) : childField.val();
931 }
932 $row.find('input.acf-row-status').each(function () {
933 const matches = (jQuery(this).attr('name') ?? '').match(REPEATER_ROW_STATUS_NAME_PATTERN);
934 if (matches) {
935 rowValue[`acf_${matches[1]}`] = jQuery(this).val();
936 }
937 });
938 rowsById[rowId] = rowValue;
939 });
940 return rowsById;
941 };
942 let lastPostedAcfJson = null;
943 let lastObservedAcfJson = null;
944 let isApplyingEditorMetaToStore = false;
945 acf.gutenbergEditPost = function () {
946 if (!acf.isGutenbergPostEditor()) {
947 return;
948 }
949 const store = wp.data.select(STORE_NAME);
950 if (!store || !store.isInitialized()) {
951 return;
952 }
953 const values = {
954 ...store.getAllValues()
955 };
956 for (const fieldKey of Object.keys(values)) {
957 const field = store.getField(fieldKey);
958 if ('repeater' === field?.type && field.pagination) {
959 values[fieldKey] = collectPaginatedRepeaterRowsForSave(fieldKey);
960 }
961 }
962 const acfJson = JSON.stringify(values);
963 lastPostedAcfJson = acfJson;
964 lastObservedAcfJson = acfJson;
965 wp.data.dispatch('core/editor').editPost({
966 meta: {
967 _acf: acfJson
968 }
969 });
970 };
971 if (window.wp?.data?.subscribe) {
972 wp.data.subscribe(() => {
973 if (isApplyingEditorMetaToStore) {
974 return;
975 }
976 const store = wp.data.select(STORE_NAME);
977 if (!store?.isInitialized()) {
978 return;
979 }
980 const editor = wp.data.select('core/editor');
981 if (!editor?.getEditedPostAttribute) {
982 return;
983 }
984 const meta = editor.getEditedPostAttribute('meta');
985 const acfJson = meta && 'string' === typeof meta._acf ? meta._acf : null;
986 if (!acfJson || acfJson === lastObservedAcfJson) {
987 return;
988 }
989 if (acfJson === lastPostedAcfJson) {
990 lastObservedAcfJson = acfJson;
991 return;
992 }
993 let values;
994 try {
995 values = JSON.parse(acfJson);
996 } catch (error) {
997 lastObservedAcfJson = acfJson;
998 return;
999 }
1000 if (values && 'object' === typeof values && !Array.isArray(values)) {
1001 lastObservedAcfJson = acfJson;
1002 isApplyingEditorMetaToStore = true;
1003 try {
1004 wp.data.dispatch(STORE_NAME).setValues(values);
1005 } finally {
1006 isApplyingEditorMetaToStore = false;
1007 }
1008 } else {
1009 lastObservedAcfJson = acfJson;
1010 }
1011 });
1012 }
1013 })();
1014 /******/ })()
1015 ;
1016 //# sourceMappingURL=acf-datastore.js.map