| @@ -1,26 +1,171 @@ | ||
| 1 | 1 | "use strict"; |
| 2 | 2 | |
| 3 | +function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); } | |
| 4 | +function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } | |
| 5 | +function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } | |
| 6 | +function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } | |
| 7 | +function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } } | |
| 8 | +function _arrayWithHoles(r) { if (Array.isArray(r)) return r; } | |
| 9 | +function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } | |
| 3 | 10 | ; |
| 4 | 11 | (function ($, window, document, undefined) { |
| 5 | 12 | 'use strict'; |
| 6 | 13 | |
| 7 | 14 | var merchant = merchant || {}; |
| 15 | + var params = new URLSearchParams(window.location.search); | |
| 16 | + var currentModule = params.get('module'); | |
| 8 | 17 | $(document).ready(function () { |
| 9 | 18 | // AjaxSave |
| 10 | 19 | var $ajaxForm = $('.merchant-module-page-ajax-form'); |
| 11 | 20 | var $ajaxHeader = $('.merchant-module-page-ajax-header'); |
| 12 | 21 | var $ajaxSaveBtn = $('.merchant-module-save-button'); |
| 13 | - $('.merchant-module-page-content').on('change keypress change.merchant', ':input:not(.merchant-module-question-answer-textarea):not(.merchant-license-code-input)', function () { | |
| 14 | - if (!merchant.show_save) { | |
| 15 | - $ajaxHeader.addClass('merchant-show'); | |
| 16 | - $ajaxHeader.removeClass('merchant-saving'); | |
| 17 | - merchant.show_save = true; | |
| 22 | + $('.merchant-module-page-content').on('change keypress change.merchant', function (e) { | |
| 23 | + if ($(e.target).hasClass('merchant-backup-file') || $(e.target).hasClass('merchant-search-field')) { | |
| 24 | + return; | |
| 18 | 25 | } |
| 26 | + | |
| 27 | + // Nothing in the preview is a setting. Its controls are there to be played | |
| 28 | + // with, so a module whose mock-up has working fields cannot dirty the form. | |
| 29 | + if ($(e.target).closest('.merchant-module-page-preview').length) { | |
| 30 | + return; | |
| 31 | + } | |
| 32 | + | |
| 33 | + // The licence key and the module feedback textarea save through their | |
| 34 | + // own handlers, so editing them leaves the save bar alone. | |
| 35 | + if (!$(e.target).is('.merchant-module-question-answer-textarea, .merchant-license-code-input')) { | |
| 36 | + if (!merchant.show_save) { | |
| 37 | + $ajaxHeader.addClass('merchant-show'); | |
| 38 | + $ajaxHeader.removeClass('merchant-saving'); | |
| 39 | + merchant.show_save = true; | |
| 40 | + } | |
| 41 | + } | |
| 42 | + GroubField.initFlag(); | |
| 19 | 43 | }); |
| 44 | + | |
| 45 | + // Warn the user before leaving the page with unsaved changes. | |
| 46 | + window.addEventListener('beforeunload', function (e) { | |
| 47 | + if (merchant.show_save) { | |
| 48 | + e.preventDefault(); | |
| 49 | + } | |
| 50 | + }); | |
| 20 | 51 | $ajaxForm.ajaxForm({ |
| 21 | - beforeSubmit: function beforeSubmit() { | |
| 52 | + beforeSubmit: function beforeSubmit(arr) { | |
| 22 | 53 | $ajaxHeader.addClass('merchant-saving'); |
| 54 | + | |
| 55 | + // Serialize merchant[] fields into a single JSON payload | |
| 56 | + // to bypass PHP's max_input_vars limit (Issue #608). | |
| 57 | + var merchantData = {}; | |
| 58 | + var keepIndexes = []; | |
| 59 | + arr.forEach(function (field, index) { | |
| 60 | + if (field.name === 'merchant-search-field') { | |
| 61 | + keepIndexes.push(index); | |
| 62 | + return; | |
| 63 | + } | |
| 64 | + if (field.name && field.name.indexOf('merchant[') === 0) { | |
| 65 | + var keys = field.name.match(/\[([^\]]*)\]/g); | |
| 66 | + if (!keys) { | |
| 67 | + return; | |
| 68 | + } | |
| 69 | + var obj = merchantData; | |
| 70 | + for (var i = 0; i < keys.length - 1; i++) { | |
| 71 | + var key = keys[i].slice(1, -1); | |
| 72 | + if (obj[key] === undefined) { | |
| 73 | + var nextKey = keys[i + 1] ? keys[i + 1].slice(1, -1) : ''; | |
| 74 | + obj[key] = /^\d+$/.test(nextKey) ? [] : {}; | |
| 75 | + } | |
| 76 | + obj = obj[key]; | |
| 77 | + } | |
| 78 | + var lastKey = keys[keys.length - 1].slice(1, -1); | |
| 79 | + if (lastKey === '') { | |
| 80 | + // Array notation (e.g. name="...[show_pages][]") — push into parent array. | |
| 81 | + var parentKey = keys[keys.length - 2].slice(1, -1); | |
| 82 | + var parentObj = merchantData; | |
| 83 | + for (var j = 0; j < keys.length - 2; j++) { | |
| 84 | + parentObj = parentObj[keys[j].slice(1, -1)]; | |
| 85 | + } | |
| 86 | + if (!Array.isArray(parentObj[parentKey])) { | |
| 87 | + parentObj[parentKey] = []; | |
| 88 | + } | |
| 89 | + parentObj[parentKey].push(field.value); | |
| 90 | + } else { | |
| 91 | + obj[lastKey] = field.value; | |
| 92 | + } | |
| 93 | + } else { | |
| 94 | + keepIndexes.push(index); | |
| 95 | + } | |
| 96 | + }); | |
| 97 | + | |
| 98 | + // Ensure unchecked checkboxes inside hydrated FC rows are saved as 0. | |
| 99 | + // serializeArray() omits unchecked checkboxes entirely, which would | |
| 100 | + // silently drop the field from the JSON payload and lose the "off" state. | |
| 101 | + $('.merchant-flexible-content .layout:not([data-deferred])').each(function () { | |
| 102 | + $(this).find('.layout-body input[type="checkbox"]:not(:checked)').each(function () { | |
| 103 | + var name = $(this).attr('name') || ''; | |
| 104 | + if (name.indexOf('merchant[') !== 0) { | |
| 105 | + return; | |
| 106 | + } | |
| 107 | + | |
| 108 | + // Skip checkbox_multiple (name ends with []) — absence = empty array. | |
| 109 | + if (/\[\]$/.test(name)) { | |
| 110 | + return; | |
| 111 | + } | |
| 112 | + var keys = name.match(/\[([^\]]*)\]/g); | |
| 113 | + if (!keys) { | |
| 114 | + return; | |
| 115 | + } | |
| 116 | + var obj = merchantData; | |
| 117 | + for (var i = 0; i < keys.length - 1; i++) { | |
| 118 | + var key = keys[i].slice(1, -1); | |
| 119 | + if (obj[key] === undefined) { | |
| 120 | + obj[key] = {}; | |
| 121 | + } | |
| 122 | + obj = obj[key]; | |
| 123 | + } | |
| 124 | + var lastKey = keys[keys.length - 1].slice(1, -1); | |
| 125 | + if (obj[lastKey] === undefined) { | |
| 126 | + obj[lastKey] = 0; | |
| 127 | + } | |
| 128 | + }); | |
| 129 | + }); | |
| 130 | + | |
| 131 | + // Merge deferred (non-hydrated) layout row data into merchantData. | |
| 132 | + // These rows have no form inputs, only a data-fields-json attribute. | |
| 133 | + $('.merchant-flexible-content .layout[data-deferred="1"]').each(function () { | |
| 134 | + var jsonStr = $(this).attr('data-fields-json'); | |
| 135 | + if (!jsonStr) { | |
| 136 | + return; | |
| 137 | + } | |
| 138 | + var deferredData; | |
| 139 | + try { | |
| 140 | + deferredData = JSON.parse(jsonStr); | |
| 141 | + } catch (e) { | |
| 142 | + return; | |
| 143 | + } | |
| 144 | + var $control = $(this).closest('.merchant-flexible-content-control'); | |
| 145 | + var fcFieldId = $control.attr('data-id'); | |
| 146 | + var rowIndex = parseInt($(this).find('.layout-count').text(), 10) - 1; | |
| 147 | + if (!merchantData[fcFieldId]) { | |
| 148 | + merchantData[fcFieldId] = {}; | |
| 149 | + } | |
| 150 | + | |
| 151 | + // Build the row data from deferred values + layout/flexible_id from hidden inputs. | |
| 152 | + var rowData = $.extend({}, deferredData.values || {}); | |
| 153 | + rowData.layout = $(this).attr('data-type') || ''; | |
| 154 | + rowData.flexible_id = $(this).find('.flexible-id').val() || ''; | |
| 155 | + merchantData[fcFieldId][rowIndex] = rowData; | |
| 156 | + }); | |
| 157 | + var kept = keepIndexes.map(function (i) { | |
| 158 | + return arr[i]; | |
| 159 | + }); | |
| 160 | + kept.push({ | |
| 161 | + name: 'merchant_json_payload', | |
| 162 | + value: JSON.stringify(merchantData) | |
| 163 | + }); | |
| 164 | + arr.length = 0; | |
| 165 | + kept.forEach(function (item) { | |
| 166 | + arr.push(item); | |
| 167 | + }); | |
| 23 | 168 | }, |
| 24 | 169 | success: function success() { |
| 25 | 170 | $ajaxHeader.removeClass('merchant-show'); |
| 26 | 171 | merchant.show_save = false; |
| @@ -36,19 +181,29 @@ | ||
| 36 | 181 | $(document).off('click.merchant-alert-close'); |
| 37 | 182 | } |
| 38 | 183 | }); |
| 39 | 184 | } |
| 185 | + $(document).trigger('save.merchant', [currentModule]); | |
| 40 | 186 | } |
| 41 | 187 | }); |
| 42 | - $('.merchant-module-question-answer-button').on('click', function (e) { | |
| 188 | + var $disableModuleSubmitBtn = $('.merchant-module-question-answer-button'); | |
| 189 | + var $disableModuleTextField = $('.merchant-module-question-answer-textarea'); | |
| 190 | + $disableModuleTextField.on('input', function () { | |
| 191 | + $disableModuleSubmitBtn.prop('disabled', $(this).val().trim() === ''); | |
| 192 | + }); | |
| 193 | + $disableModuleSubmitBtn.on('click', function (e) { | |
| 43 | 194 | e.preventDefault(); |
| 195 | + var message = $disableModuleTextField.val(); | |
| 196 | + if (!message.trim()) { | |
| 197 | + alert('Please provide the required information.'); | |
| 198 | + return; | |
| 199 | + } | |
| 44 | 200 | var $button = $(this); |
| 45 | - var $textarea = $('.merchant-module-question-answer-textarea'); | |
| 46 | 201 | $('.merchant-module-question-answer-dropdown').removeClass('merchant-show'); |
| 47 | 202 | $('.merchant-module-question-thank-you-dropdown').addClass('merchant-show'); |
| 48 | 203 | window.wp.ajax.post('merchant_module_feedback', { |
| 49 | - subject: $textarea.data('subject'), | |
| 50 | - message: $textarea.val(), | |
| 204 | + subject: $disableModuleTextField.attr('data-subject'), | |
| 205 | + message: message, | |
| 51 | 206 | module: $button.closest('.merchant-module-action').find('.merchant-module-page-button-action-activate').data('module'), |
| 52 | 207 | nonce: window.merchant.nonce |
| 53 | 208 | }); |
| 54 | 209 | }); |
| @@ -53,8 +208,11 @@ | ||
| 53 | 208 | }); |
| 54 | 209 | }); |
| 55 | 210 | $('.merchant-module-page-button-action-activate').on('click', function (e) { |
| 56 | 211 | e.preventDefault(); |
| 212 | + if ($(this).hasClass('merchant-module-deactivated-by-bp')) { | |
| 213 | + return false; | |
| 214 | + } | |
| 57 | 215 | $('.merchant-module-question-list-dropdown').removeClass('merchant-show'); |
| 58 | 216 | $('.merchant-module-question-answer-dropdown').removeClass('merchant-show'); |
| 59 | 217 | $('.merchant-module-question-answer-form').removeClass('merchant-show'); |
| 60 | 218 | $('.merchant-module-question-answer-title').removeClass('merchant-show'); |
| @@ -79,8 +237,9 @@ | ||
| 79 | 237 | $('.merchant-module-question-list-dropdown').addClass('merchant-show'); |
| 80 | 238 | }); |
| 81 | 239 | }); |
| 82 | 240 | $('.merchant-module-question-list-dropdown li').on('click', function (e) { |
| 241 | + $disableModuleSubmitBtn.prop('disabled', $disableModuleTextField.val().trim() === ''); | |
| 83 | 242 | var $question = $(this); |
| 84 | 243 | var target = $question.data('answer-target'); |
| 85 | 244 | var $answer = $('[data-answer-title="' + target + '"]'); |
| 86 | 245 | if ($answer.length) { |
| @@ -110,31 +269,302 @@ | ||
| 110 | 269 | $(document).off('click.merchant-close'); |
| 111 | 270 | } |
| 112 | 271 | }); |
| 113 | 272 | }); |
| 114 | - $('.merchant-range').each(function () { | |
| 115 | - var $range = $(this); | |
| 116 | - var $rangeInput = $range.find('.merchant-range-input'); | |
| 117 | - var $numberInput = $range.find('.merchant-range-number-input'); | |
| 118 | - $rangeInput.on('change input merchant.range merchant-init.range', function (e) { | |
| 273 | + | |
| 274 | + // Create a function that initializes a single range or all ranges in a context for range field | |
| 275 | + function initMerchantRange() { | |
| 276 | + var rangeFields = $(document).find('.merchant-range'); | |
| 277 | + if (rangeFields.length === 0) { | |
| 278 | + return; | |
| 279 | + } | |
| 280 | + rangeFields.each(function () { | |
| 119 | 281 | var $range = $(this); |
| 120 | - var value = (e.type === 'merchant' ? $numberInput.val() : $range.val()) || 0; | |
| 121 | - var min = $range.attr('min') || 0; | |
| 122 | - var max = $range.attr('max') || 1; | |
| 123 | - var percentage = (value - min) / (max - min) * 100; | |
| 124 | - $range.css({ | |
| 125 | - 'background': 'linear-gradient(to right, #3858E9 0%, #3858E9 ' + percentage + '%, #ddd ' + percentage + '%, #ddd 100%)' | |
| 282 | + var $rangeInput = $range.find('.merchant-range-input'); | |
| 283 | + var $numberInput = $range.find('.merchant-range-number-input'); | |
| 284 | + $rangeInput.on('change input merchant.range merchant-init.range', function (e) { | |
| 285 | + var $range = $(this); | |
| 286 | + var value = (e.type === 'merchant' ? $numberInput.val() : $range.val()) || 0; | |
| 287 | + var min = $range.attr('min') || 0; | |
| 288 | + var max = $range.attr('max') || 1; | |
| 289 | + var percentage = (value - min) / (max - min) * 100; | |
| 290 | + if ($('body').hasClass('rtl')) { | |
| 291 | + $range.css({ | |
| 292 | + 'background': 'linear-gradient(to left, #3858E9 0%, #3858E9 ' + percentage + '%, #ddd ' + percentage + '%, #ddd 100%)' | |
| 293 | + }); | |
| 294 | + } else { | |
| 295 | + $range.css({ | |
| 296 | + 'background': 'linear-gradient(to right, #3858E9 0%, #3858E9 ' + percentage + '%, #ddd ' + percentage + '%, #ddd 100%)' | |
| 297 | + }); | |
| 298 | + } | |
| 299 | + $rangeInput.val(value); | |
| 300 | + $numberInput.val(value); | |
| 301 | + }).trigger('merchant-init.range'); | |
| 302 | + $numberInput.on('change input blur', function () { | |
| 303 | + if ($rangeInput.hasClass('merchant-range-input')) { | |
| 304 | + $rangeInput.val($(this).val()).trigger('merchant.range'); | |
| 305 | + } | |
| 126 | 306 | }); |
| 127 | - $rangeInput.val(value); | |
| 128 | - $numberInput.val(value); | |
| 129 | - }).trigger('merchant-init.range'); | |
| 130 | - $numberInput.on('change input blur', function () { | |
| 131 | - if ($rangeInput.hasClass('merchant-range-input')) { | |
| 132 | - $rangeInput.val($(this).val()).trigger('merchant.range'); | |
| 133 | - } | |
| 134 | 307 | }); |
| 308 | + } | |
| 309 | + | |
| 310 | + // 1. Initialize on DOM ready (existing fields) | |
| 311 | + initMerchantRange(); | |
| 312 | + $(document).on('click', '.merchant-module-page-setting-field-hidden-desc-trigger', function () { | |
| 313 | + var $trigger = $(this); | |
| 314 | + $trigger.toggleClass('expanded'); | |
| 315 | + var showText = $trigger.attr('data-show-text'); | |
| 316 | + var hiddenText = $trigger.attr('data-hidden-text'); | |
| 317 | + $(this).find('span:first').text($trigger.text() === showText ? hiddenText : showText); | |
| 318 | + $(this).closest('.merchant-module-page-setting-field').find('.merchant-module-page-setting-field-hidden-desc').stop(true, true).slideToggle('fast'); | |
| 135 | 319 | }); |
| 320 | + var moduleBackup = { | |
| 321 | + init: function init() { | |
| 322 | + this.events(); | |
| 323 | + }, | |
| 324 | + events: function events() { | |
| 325 | + var self = this; | |
| 326 | + $(document).on('click', '#download-backup-button', this.download.bind(this)); | |
| 136 | 327 | |
| 328 | + // Browse button opens file picker. | |
| 329 | + $(document).on('click', '.merchant-dropzone__browse', function (e) { | |
| 330 | + e.preventDefault(); | |
| 331 | + e.stopPropagation(); | |
| 332 | + $(this).closest('.merchant-dropzone').find('.merchant-backup-file').trigger('click'); | |
| 333 | + }); | |
| 334 | + | |
| 335 | + // Auto-restore when a file is selected (via browse or drop). | |
| 336 | + $(document).on('change', '#merchant-backup-file', function () { | |
| 337 | + var file = this.files[0]; | |
| 338 | + if (file) { | |
| 339 | + self.restoreFile(file); | |
| 340 | + } | |
| 341 | + }); | |
| 342 | + | |
| 343 | + // Drag-and-drop visual feedback. | |
| 344 | + var $dropzone = $('#merchant-restore-dropzone'); | |
| 345 | + $dropzone.on('dragover dragenter', function (e) { | |
| 346 | + e.preventDefault(); | |
| 347 | + e.stopPropagation(); | |
| 348 | + $(this).addClass('drag-over'); | |
| 349 | + }); | |
| 350 | + $dropzone.on('dragleave drop', function (e) { | |
| 351 | + e.preventDefault(); | |
| 352 | + e.stopPropagation(); | |
| 353 | + $(this).removeClass('drag-over'); | |
| 354 | + }); | |
| 355 | + $dropzone.on('drop', function (e) { | |
| 356 | + var file = e.originalEvent.dataTransfer.files[0]; | |
| 357 | + if (file) { | |
| 358 | + self.restoreFile(file); | |
| 359 | + } | |
| 360 | + }); | |
| 361 | + }, | |
| 362 | + download: function download(e) { | |
| 363 | + var self = this; | |
| 364 | + e.preventDefault(); | |
| 365 | + var container = $('.merchant-module-page-setting-fields .backup-section'); | |
| 366 | + var $status = container.find('.merchant-backup-status'); | |
| 367 | + $status.removeAttr('class').addClass('merchant-backup-status').text(''); | |
| 368 | + var module_id = $(e.target).attr('data-module-id'); | |
| 369 | + $.ajax({ | |
| 370 | + url: merchant_admin_options.ajaxurl, | |
| 371 | + type: 'GET', | |
| 372 | + data: { | |
| 373 | + action: 'merchant_get_module_settings', | |
| 374 | + nonce: merchant_admin_options.ajaxnonce, | |
| 375 | + module_id: module_id | |
| 376 | + }, | |
| 377 | + beforeSend: function beforeSend() { | |
| 378 | + self.showLoadingIndicator(container); | |
| 379 | + }, | |
| 380 | + success: function success(response) { | |
| 381 | + self.hideLoadingIndicator(container); | |
| 382 | + if (response.success) { | |
| 383 | + var moduleSettings = response.data; | |
| 384 | + var fileName = 'merchant-' + module_id + '-backup-' + new Date().toISOString().slice(0, 10) + '-' + new Date().getHours() + '-' + new Date().getMinutes() + '-' + new Date().getSeconds() + '.json'; | |
| 385 | + self.downloadJson(moduleSettings, fileName); | |
| 386 | + $status.addClass('is-success').text(merchant_admin_options.backup_success || 'Downloaded!'); | |
| 387 | + } else { | |
| 388 | + $status.addClass('is-error').text(response.data.message); | |
| 389 | + } | |
| 390 | + setTimeout(function () { | |
| 391 | + $status.removeAttr('class').addClass('merchant-backup-status').text(''); | |
| 392 | + }, 3000); | |
| 393 | + }, | |
| 394 | + error: function error(xhr, status, _error) { | |
| 395 | + self.hideLoadingIndicator(container); | |
| 396 | + $status.addClass('is-error').text(_error); | |
| 397 | + setTimeout(function () { | |
| 398 | + $status.removeAttr('class').addClass('merchant-backup-status').text(''); | |
| 399 | + }, 3000); | |
| 400 | + } | |
| 401 | + }); | |
| 402 | + }, | |
| 403 | + restoreFile: function restoreFile(file) { | |
| 404 | + var self = this; | |
| 405 | + var container = $('.merchant-module-page-setting-fields .restore-section'); | |
| 406 | + var module_id = $('#merchant-restore-dropzone').data('module-id'); | |
| 407 | + self.hideError(); | |
| 408 | + if (!file || file.type !== 'application/json') { | |
| 409 | + self.displayError(merchant_admin_options.invalid_file); | |
| 410 | + return; | |
| 411 | + } | |
| 412 | + var reader = new FileReader(); | |
| 413 | + reader.onload = function (e) { | |
| 414 | + var moduleSettings = e.target.result; | |
| 415 | + $.ajax({ | |
| 416 | + url: merchant_admin_options.ajaxurl, | |
| 417 | + type: 'POST', | |
| 418 | + data: { | |
| 419 | + action: 'merchant_restore_module_settings', | |
| 420 | + nonce: merchant_admin_options.ajaxnonce, | |
| 421 | + module_id: module_id, | |
| 422 | + module_settings: moduleSettings | |
| 423 | + }, | |
| 424 | + beforeSend: function beforeSend() { | |
| 425 | + $('#merchant-restore-dropzone').addClass('is-loading'); | |
| 426 | + self.showLoadingIndicator(container); | |
| 427 | + }, | |
| 428 | + success: function success(response) { | |
| 429 | + if (response.success) { | |
| 430 | + merchant.show_save = false; | |
| 431 | + var $dz = $('#merchant-restore-dropzone'); | |
| 432 | + $dz.removeClass('is-loading').addClass('is-success'); | |
| 433 | + self.hideLoadingIndicator(container); | |
| 434 | + setTimeout(function () { | |
| 435 | + window.location.href = response.data.redirect_url; | |
| 436 | + }, 1500); | |
| 437 | + } else { | |
| 438 | + self.displayError(response.data.message); | |
| 439 | + } | |
| 440 | + }, | |
| 441 | + error: function error(xhr, status, _error2) { | |
| 442 | + self.displayError(_error2); | |
| 443 | + } | |
| 444 | + }); | |
| 445 | + }; | |
| 446 | + reader.readAsText(file); | |
| 447 | + }, | |
| 448 | + downloadJson: function downloadJson(data, filename) { | |
| 449 | + if (_typeof(data) === 'object') { | |
| 450 | + data = JSON.stringify(data); | |
| 451 | + } | |
| 452 | + var blob = new Blob([data], { | |
| 453 | + type: "application/json" | |
| 454 | + }); | |
| 455 | + var url = URL.createObjectURL(blob); | |
| 456 | + var a = document.createElement('a'); | |
| 457 | + a.href = url; | |
| 458 | + a.download = filename; | |
| 459 | + a.click(); | |
| 460 | + URL.revokeObjectURL(url); | |
| 461 | + }, | |
| 462 | + displayError: function displayError(errorMsg) { | |
| 463 | + var $dz = $('#merchant-restore-dropzone'); | |
| 464 | + $dz.find('.merchant-dropzone__error p').text(errorMsg); | |
| 465 | + $dz.removeClass('is-loading').addClass('is-error'); | |
| 466 | + this.hideLoadingIndicator($('.merchant-module-page-setting-fields .restore-section')); | |
| 467 | + setTimeout(function () { | |
| 468 | + $dz.removeClass('is-error'); | |
| 469 | + // Reset file input so same file can be re-selected. | |
| 470 | + $dz.find('.merchant-backup-file').val(''); | |
| 471 | + }, 3000); | |
| 472 | + }, | |
| 473 | + hideError: function hideError() { | |
| 474 | + $('#merchant-restore-dropzone').removeClass('is-error'); | |
| 475 | + }, | |
| 476 | + showLoadingIndicator: function showLoadingIndicator(container) { | |
| 477 | + container.find('.merchant-loading-spinner').addClass('show'); | |
| 478 | + }, | |
| 479 | + hideLoadingIndicator: function hideLoadingIndicator(container) { | |
| 480 | + container.find('.merchant-loading-spinner').removeClass('show'); | |
| 481 | + } | |
| 482 | + }; | |
| 483 | + moduleBackup.init(); | |
| 484 | + | |
| 485 | + // Add support for toggle field inside flexible content. | |
| 486 | + var flexibleToggleField = { | |
| 487 | + init: function init(field) { | |
| 488 | + this.events(); | |
| 489 | + }, | |
| 490 | + events: function events() { | |
| 491 | + $(document).on('click', '.merchant-flexible-content .merchant-toggle-switch .toggle-switch-label span', function () { | |
| 492 | + var checkBox = $(this).closest('.merchant-toggle-switch').find('.toggle-switch-checkbox'); | |
| 493 | + checkBox.prop('checked', !checkBox.prop('checked')); | |
| 494 | + }).trigger('merchant.change'); | |
| 495 | + } | |
| 496 | + }; | |
| 497 | + var dateTimePickerField = { | |
| 498 | + init: function init() { | |
| 499 | + this.initiate_datepicker(); | |
| 500 | + this.events(); | |
| 501 | + }, | |
| 502 | + // Date() cannot parse the bare 'H:i' a time-only picker stores, so pin it to today. | |
| 503 | + parseInitialDate: function parseInitialDate(value, fieldOptions) { | |
| 504 | + if (!value) { | |
| 505 | + return []; | |
| 506 | + } | |
| 507 | + if (fieldOptions.onlyTimepicker && /^\d{1,2}:\d{2}$/.test(value)) { | |
| 508 | + var _value$split = value.split(':'), | |
| 509 | + _value$split2 = _slicedToArray(_value$split, 2), | |
| 510 | + hours = _value$split2[0], | |
| 511 | + minutes = _value$split2[1]; | |
| 512 | + return [new Date(new Date().setHours(Number(hours), Number(minutes), 0, 0))]; | |
| 513 | + } | |
| 514 | + var date = new Date(value); | |
| 515 | + return isNaN(date) ? [] : [date]; | |
| 516 | + }, | |
| 517 | + initiate_datepicker: function initiate_datepicker() { | |
| 518 | + var self = this; | |
| 519 | + var elements = $('.merchant-module-page-setting-field .merchant-datetime-field'); | |
| 520 | + if (elements.length === 0) { | |
| 521 | + return; | |
| 522 | + } | |
| 523 | + elements.each(function (index) { | |
| 524 | + var input = $(this).find('input'), | |
| 525 | + fieldOptions = $(this).data('options') || {}, | |
| 526 | + options = { | |
| 527 | + locale: JSON.parse(merchant_datepicker_locale), | |
| 528 | + selectedDates: self.parseInitialDate(input.val(), fieldOptions), | |
| 529 | + onSelect: function onSelect(_ref) { | |
| 530 | + var date = _ref.date, | |
| 531 | + formattedDate = _ref.formattedDate, | |
| 532 | + datepicker = _ref.datepicker; | |
| 533 | + if (typeof formattedDate === "undefined") { | |
| 534 | + // allow removing date | |
| 535 | + // input.val(''); | |
| 536 | + datepicker.$el.value = ''; | |
| 537 | + } | |
| 538 | + input.trigger('change.merchant'); | |
| 539 | + input.trigger('change.merchant-datepicker', [formattedDate, input, options, index]); | |
| 540 | + } | |
| 541 | + }; | |
| 542 | + // add buttons to fieldOptions | |
| 543 | + fieldOptions.buttons = ['clear']; | |
| 544 | + if (fieldOptions.minDate !== undefined && fieldOptions.minDate === 'today') { | |
| 545 | + fieldOptions.minDate = new Date(); | |
| 546 | + if (fieldOptions.timeZone !== undefined && fieldOptions.timeZone !== '') { | |
| 547 | + fieldOptions.minDate = new Date(fieldOptions.minDate.toLocaleString('en-US', { | |
| 548 | + timeZone: fieldOptions.timeZone | |
| 549 | + })); | |
| 550 | + } | |
| 551 | + } | |
| 552 | + options = Object.assign(options, fieldOptions); | |
| 553 | + var datepickerObj = new AirDatepicker(input.getPath(), options); | |
| 554 | + input.attr('readonly', true); | |
| 555 | + $(document).trigger('initiated.merchant-datepicker', [datepickerObj, input, options, index]); | |
| 556 | + }); | |
| 557 | + }, | |
| 558 | + events: function events() { | |
| 559 | + var self = this; | |
| 560 | + $(document).on('merchant-flexible-content-added', function () { | |
| 561 | + self.initiate_datepicker(); | |
| 562 | + }); | |
| 563 | + } | |
| 564 | + }; | |
| 565 | + dateTimePickerField.init(); | |
| 566 | + | |
| 137 | 567 | // Sortable. |
| 138 | 568 | var SortableField = { |
| 139 | 569 | init: function init(field) { |
| 140 | 570 | this.events(); |
| @@ -175,11 +605,19 @@ | ||
| 175 | 605 | return JSON.stringify(newVal); |
| 176 | 606 | } |
| 177 | 607 | }; |
| 178 | 608 | |
| 179 | - // Intialize Sortable. | |
| 609 | + // Initialize Sortable. | |
| 180 | 610 | SortableField.init(); |
| 611 | + flexibleToggleField.init(); | |
| 181 | 612 | |
| 613 | + // When adding/duplicating new item, refresh sorting | |
| 614 | + $(document).on('merchant-flexible-content-added', function (e, $layout) { | |
| 615 | + var $sortableWrapper = $layout.find('.merchant-sortable-repeater-control'); | |
| 616 | + var $sortableElement = $sortableWrapper.find('.merchant-sortable-repeater.sortable'); | |
| 617 | + SortableRepeaterField.makeFieldsSortable($sortableElement); | |
| 618 | + }); | |
| 619 | + | |
| 182 | 620 | // Sortable Repeater. |
| 183 | 621 | var SortableRepeaterField = { |
| 184 | 622 | init: function init(field) { |
| 185 | 623 | var self = this; |
| @@ -194,23 +632,24 @@ | ||
| 194 | 632 | $(this).find('.repeater-input').val(defaultValuesArray[0]); |
| 195 | 633 | |
| 196 | 634 | // Create a new row for each new value |
| 197 | 635 | if (numRepeaterItems > 1) { |
| 198 | - var i; | |
| 199 | - for (i = 1; i < numRepeaterItems; ++i) { | |
| 636 | + // var i; | |
| 637 | + for (var i = 1; i < numRepeaterItems; ++i) { | |
| 200 | 638 | self.appendRow($(this), defaultValuesArray[i]); |
| 201 | 639 | } |
| 202 | 640 | } |
| 203 | 641 | } |
| 204 | 642 | |
| 205 | - // Make our Repeater fields sortable. | |
| 206 | - if (!$(this).hasClass('disable-sorting')) { | |
| 207 | - $(this).find('.merchant-sortable-repeater.sortable').sortable({ | |
| 208 | - update: function update(event, ui) { | |
| 209 | - self.getAllInputs($(this).parent()); | |
| 210 | - } | |
| 211 | - }); | |
| 212 | - } | |
| 643 | + // Todo: remove | |
| 644 | + // Make our Repeater fields sortable. Doesn't work with flexible content | |
| 645 | + // if (!$(this).hasClass('disable-sorting')) { | |
| 646 | + // $(this).find('.merchant-sortable-repeater.sortable').sortable({ | |
| 647 | + // update: function (event, ui) { | |
| 648 | + // self.getAllInputs($(this).parent()); | |
| 649 | + // } | |
| 650 | + // }); | |
| 651 | + // } | |
| 213 | 652 | }); |
| 214 | 653 | |
| 215 | 654 | // Events. |
| 216 | 655 | this.events(); |
| @@ -217,26 +656,21 @@ | ||
| 217 | 656 | }, |
| 218 | 657 | events: function events() { |
| 219 | 658 | var self = this; |
| 220 | 659 | |
| 221 | - // Remove item starting from it's parent element | |
| 222 | - $('.merchant-sortable-repeater.sortable').on('click', '.customize-control-sortable-repeater-delete', function (event) { | |
| 660 | + // Remove item starting from its parent element | |
| 661 | + $(document).on('click', '.merchant-sortable-repeater.sortable .customize-control-sortable-repeater-delete', function (event) { | |
| 223 | 662 | event.preventDefault(); |
| 224 | - var numItems = $(this).parent().parent().find('.repeater').length; | |
| 225 | - if (numItems > 1) { | |
| 226 | - $(this).parent().slideUp('fast', function () { | |
| 227 | - var parentContainer = $(this).parent().parent(); | |
| 228 | - $(this).remove(); | |
| 229 | - self.getAllInputs(parentContainer); | |
| 230 | - }); | |
| 231 | - } else { | |
| 232 | - // $(this).parent().find('.repeater-input').val(''); | |
| 233 | - self.getAllInputs($(this).parent().parent().parent()); | |
| 234 | - } | |
| 663 | + $(this).parent().slideUp('fast', function () { | |
| 664 | + var parentContainer = $(this).parent().parent(); | |
| 665 | + $(this).remove(); | |
| 666 | + self.getAllInputs(parentContainer); | |
| 667 | + }); | |
| 668 | + $(document).trigger('merchant-sortable-repeater-item-deleted'); | |
| 235 | 669 | }); |
| 236 | 670 | |
| 237 | 671 | // Add new item |
| 238 | - $('.customize-control-sortable-repeater-add').click(function (event) { | |
| 672 | + $(document).on('click', '.customize-control-sortable-repeater-add', function (event) { | |
| 239 | 673 | event.preventDefault(); |
| 240 | 674 | self.appendRow($(this).parent()); |
| 241 | 675 | self.getAllInputs($(this).parent()); |
| 242 | 676 | }); |
| @@ -241,12 +675,12 @@ | ||
| 241 | 675 | self.getAllInputs($(this).parent()); |
| 242 | 676 | }); |
| 243 | 677 | |
| 244 | 678 | // Refresh our hidden field if any fields change |
| 245 | - $('.merchant-sortable-repeater.sortable').change(function () { | |
| 679 | + $(document).on('change', '.merchant-sortable-repeater.sortable', function () { | |
| 246 | 680 | self.getAllInputs($(this).parent()); |
| 247 | 681 | }); |
| 248 | - $('.merchant-sortable-repeater.sortable').on('focusout', '.repeater-input', function () { | |
| 682 | + $(document).on('focusout', '.merchant-sortable-repeater.sortable .repeater-input', function () { | |
| 249 | 683 | self.getAllInputs($(this).parent()); |
| 250 | 684 | }); |
| 251 | 685 | }, |
| 252 | 686 | /** |
| @@ -256,12 +690,26 @@ | ||
| 256 | 690 | appendRow: function appendRow($element) { |
| 257 | 691 | var defaultValue = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; |
| 258 | 692 | var newRow = '<div class="repeater" style="display:none"><input type="text" value="' + defaultValue + '" class="repeater-input" /><span class="dashicons dashicons-menu"></span><a class="customize-control-sortable-repeater-delete" href="#"><span class="dashicons dashicons-no-alt"></span></a></div>'; |
| 259 | 693 | $element.find('.sortable').append(newRow); |
| 260 | - $element.find('.sortable').find('.repeater:last').slideDown('slow', function () { | |
| 694 | + var $newItem = $element.find('.sortable').find('.repeater:last'); | |
| 695 | + $newItem.slideDown('slow', function () { | |
| 261 | 696 | $(this).find('input').focus(); |
| 262 | 697 | }); |
| 698 | + | |
| 699 | + // Make Repeater fields sortable; Putting here works better with flexible content | |
| 700 | + this.makeFieldsSortable($element.find('.sortable')); | |
| 701 | + $(document).trigger('merchant-sortable-repeater-item-added', [$newItem, $element.find('.sortable')]); | |
| 263 | 702 | }, |
| 703 | + makeFieldsSortable: function makeFieldsSortable($sortableElement) { | |
| 704 | + if (!$sortableElement.hasClass('disable-sorting')) { | |
| 705 | + $sortableElement.sortable({ | |
| 706 | + update: function update(event, ui) { | |
| 707 | + SortableRepeaterField.getAllInputs($sortableElement.parent()); | |
| 708 | + } | |
| 709 | + }); | |
| 710 | + } | |
| 711 | + }, | |
| 264 | 712 | /** |
| 265 | 713 | * Get the values from the repeater input fields and add to our hidden field. |
| 266 | 714 | * |
| 267 | 715 | */ |
| @@ -269,12 +717,18 @@ | ||
| 269 | 717 | var inputValues = $element.find('.repeater-input').map(function () { |
| 270 | 718 | return $(this).val(); |
| 271 | 719 | }).toArray(); |
| 272 | 720 | |
| 721 | + // Keep one empty item if all deleted. | |
| 722 | + if (!inputValues.length) { | |
| 723 | + inputValues.push(''); | |
| 724 | + } | |
| 725 | + | |
| 273 | 726 | // Add all the values from our repeater fields to the hidden field (which is the one that actually gets saved) |
| 274 | 727 | $element.find('.merchant-sortable-repeater-input').val(JSON.stringify(inputValues)); |
| 275 | 728 | // Important! Make sure to trigger change event so Customizer knows it has to save the field |
| 276 | 729 | $element.find('.merchant-sortable-repeater-input').trigger('change'); |
| 730 | + $element.find('.merchant-sortable-repeater-input').trigger('sortable.repeater.change'); | |
| 277 | 731 | } |
| 278 | 732 | }; |
| 279 | 733 | |
| 280 | 734 | // Initialize Sortable Repeater. |
| @@ -279,8 +733,590 @@ | ||
| 279 | 733 | |
| 280 | 734 | // Initialize Sortable Repeater. |
| 281 | 735 | SortableRepeaterField.init(); |
| 282 | 736 | |
| 737 | + // Sortable Repeater Icons. | |
| 738 | + var SortableRepeaterIconsField = { | |
| 739 | + CONTROL: '.merchant-sortable-repeater-icons-control', | |
| 740 | + init: function init() { | |
| 741 | + var self = this; | |
| 742 | + $(self.CONTROL).each(function () { | |
| 743 | + self.populate($(this)); | |
| 744 | + }); | |
| 745 | + this.events(); | |
| 746 | + }, | |
| 747 | + /** | |
| 748 | + * Populate rows from the hidden JSON input. | |
| 749 | + */ | |
| 750 | + populate: function populate($control) { | |
| 751 | + var self = this; | |
| 752 | + var items = []; | |
| 753 | + try { | |
| 754 | + items = JSON.parse($control.find('.merchant-sortable-repeater-input').val()); | |
| 755 | + } catch (e) { | |
| 756 | + items = []; | |
| 757 | + } | |
| 758 | + if (!Array.isArray(items) || items.length === 0) { | |
| 759 | + return; | |
| 760 | + } | |
| 761 | + var $firstRow = $control.find('.repeater').first(); | |
| 762 | + | |
| 763 | + // First item populates the existing row. | |
| 764 | + var firstItem = self.normalizeItem(items[0]); | |
| 765 | + $firstRow.find('.repeater-input').val(firstItem.text); | |
| 766 | + self.setRowIcon($firstRow, firstItem.icon); | |
| 767 | + | |
| 768 | + // Remaining items get new rows (silent = true to avoid triggering save bar). | |
| 769 | + for (var i = 1; i < items.length; i++) { | |
| 770 | + var item = self.normalizeItem(items[i]); | |
| 771 | + self.appendRow($control, item.text, item.icon, true); | |
| 772 | + } | |
| 773 | + }, | |
| 774 | + events: function events() { | |
| 775 | + var self = this; | |
| 776 | + | |
| 777 | + // Toggle icon picker dropdown. | |
| 778 | + $(document).on('click', self.CONTROL + ' .merchant-icon-picker-toggle', function (e) { | |
| 779 | + e.preventDefault(); | |
| 780 | + e.stopPropagation(); | |
| 781 | + var $dropdown = $(this).siblings('.merchant-icon-picker-dropdown'); | |
| 782 | + | |
| 783 | + // Close all other dropdowns first. | |
| 784 | + $(self.CONTROL + ' .merchant-icon-picker-dropdown').not($dropdown).hide(); | |
| 785 | + $dropdown.toggle(); | |
| 786 | + }); | |
| 787 | + | |
| 788 | + // Select icon from dropdown. | |
| 789 | + $(document).on('click', self.CONTROL + ' .merchant-icon-option', function (e) { | |
| 790 | + e.preventDefault(); | |
| 791 | + e.stopPropagation(); | |
| 792 | + var $repeater = $(this).closest('.repeater'); | |
| 793 | + var iconKey = $(this).data('icon') || ''; | |
| 794 | + self.setRowIcon($repeater, iconKey); | |
| 795 | + $(this).closest('.merchant-icon-picker-dropdown').hide(); | |
| 796 | + self.syncHiddenInput($(this).closest(self.CONTROL)); | |
| 797 | + }); | |
| 798 | + | |
| 799 | + // Close dropdown on outside click. | |
| 800 | + $(document).on('click', function () { | |
| 801 | + $(SortableRepeaterIconsField.CONTROL + ' .merchant-icon-picker-dropdown').hide(); | |
| 802 | + }); | |
| 803 | + | |
| 804 | + // Prevent dropdown clicks from closing. | |
| 805 | + $(document).on('click', self.CONTROL + ' .merchant-icon-picker-dropdown', function (e) { | |
| 806 | + e.stopPropagation(); | |
| 807 | + }); | |
| 808 | + | |
| 809 | + // Delete row. | |
| 810 | + $(document).on('click', self.CONTROL + ' .merchant-sortable-repeater-icons.sortable .customize-control-sortable-repeater-delete', function (e) { | |
| 811 | + e.preventDefault(); | |
| 812 | + var $control = $(this).closest(self.CONTROL); | |
| 813 | + $(this).parent().slideUp('fast', function () { | |
| 814 | + $(this).remove(); | |
| 815 | + self.syncHiddenInput($control); | |
| 816 | + }); | |
| 817 | + $(document).trigger('merchant-sortable-repeater-item-deleted'); | |
| 818 | + }); | |
| 819 | + | |
| 820 | + // Add new row. | |
| 821 | + $(document).on('click', self.CONTROL + ' .customize-control-sortable-repeater-icons-add', function (e) { | |
| 822 | + e.preventDefault(); | |
| 823 | + var $control = $(this).closest(self.CONTROL); | |
| 824 | + self.appendRow($control, '', ''); | |
| 825 | + self.syncHiddenInput($control); | |
| 826 | + }); | |
| 827 | + | |
| 828 | + // Text change → sync. | |
| 829 | + $(document).on('focusout', self.CONTROL + ' .merchant-sortable-repeater-icons.sortable .repeater-input', function () { | |
| 830 | + self.syncHiddenInput($(this).closest(self.CONTROL)); | |
| 831 | + }); | |
| 832 | + | |
| 833 | + // Sort change → sync. | |
| 834 | + $(document).on('change', self.CONTROL + ' .merchant-sortable-repeater-icons.sortable', function () { | |
| 835 | + self.syncHiddenInput($(this).closest(self.CONTROL)); | |
| 836 | + }); | |
| 837 | + | |
| 838 | + // Re-init on flexible content add. | |
| 839 | + $(document).on('merchant-flexible-content-added', function (e, $layout) { | |
| 840 | + var $control = $layout.find(self.CONTROL); | |
| 841 | + if ($control.length) { | |
| 842 | + self.populate($control); | |
| 843 | + self.makeFieldsSortable($control.find('.sortable')); | |
| 844 | + } | |
| 845 | + }); | |
| 846 | + }, | |
| 847 | + /** | |
| 848 | + * Append a new repeater row. | |
| 849 | + */ | |
| 850 | + appendRow: function appendRow($control, text, icon, silent) { | |
| 851 | + var self = this; | |
| 852 | + var iconLibrary = self.getIconLibrary($control); | |
| 853 | + var dropdownHtml = '<div class="merchant-icon-picker-dropdown" style="display:none;">'; | |
| 854 | + dropdownHtml += '<button type="button" class="merchant-icon-option" data-icon="" title="Use campaign icon"><span class="dashicons dashicons-no-alt"></span></button>'; | |
| 855 | + Object.keys(iconLibrary).forEach(function (key) { | |
| 856 | + dropdownHtml += '<button type="button" class="merchant-icon-option" data-icon="' + key + '" title="' + key + '">' + iconLibrary[key] + '</button>'; | |
| 857 | + }); | |
| 858 | + dropdownHtml += '</div>'; | |
| 859 | + var escapedText = $('<div>').text(text).html(); | |
| 860 | + var newRow = $('<div class="repeater" style="display:none">' + '<button type="button" class="merchant-icon-picker-toggle" data-icon="" title="Select icon"><span class="dashicons dashicons-plus-alt2"></span></button>' + dropdownHtml + '<input type="text" value="' + escapedText + '" class="repeater-input" />' + '<span class="dashicons dashicons-menu"></span>' + '<a class="customize-control-sortable-repeater-delete" href="#"><span class="dashicons dashicons-no-alt"></span></a>' + '</div>'); | |
| 861 | + $control.find('.sortable').append(newRow); | |
| 862 | + if (silent) { | |
| 863 | + newRow.show(); | |
| 864 | + } else { | |
| 865 | + newRow.slideDown('slow', function () { | |
| 866 | + $(this).find('input').focus(); | |
| 867 | + }); | |
| 868 | + } | |
| 869 | + if (icon) { | |
| 870 | + self.setRowIcon(newRow, icon); | |
| 871 | + } | |
| 872 | + self.makeFieldsSortable($control.find('.sortable')); | |
| 873 | + $(document).trigger('merchant-sortable-repeater-item-added', [newRow, $control.find('.sortable')]); | |
| 874 | + }, | |
| 875 | + /** | |
| 876 | + * Set the icon on a repeater row. | |
| 877 | + */ | |
| 878 | + setRowIcon: function setRowIcon($row, iconKey) { | |
| 879 | + var $toggle = $row.find('.merchant-icon-picker-toggle'); | |
| 880 | + $toggle.attr('data-icon', iconKey); | |
| 881 | + if (iconKey) { | |
| 882 | + var $control = $row.closest(SortableRepeaterIconsField.CONTROL); | |
| 883 | + var iconLibrary = this.getIconLibrary($control); | |
| 884 | + var svg = iconLibrary[iconKey] || ''; | |
| 885 | + if (svg) { | |
| 886 | + $toggle.html(svg).addClass('has-icon'); | |
| 887 | + } else { | |
| 888 | + $toggle.html('<span class="dashicons dashicons-plus-alt2"></span>').removeClass('has-icon'); | |
| 889 | + } | |
| 890 | + } else { | |
| 891 | + $toggle.html('<span class="dashicons dashicons-plus-alt2"></span>').removeClass('has-icon'); | |
| 892 | + } | |
| 893 | + | |
| 894 | + // Mark selected option in dropdown. | |
| 895 | + $row.find('.merchant-icon-option').removeClass('selected'); | |
| 896 | + $row.find('.merchant-icon-option[data-icon="' + iconKey + '"]').addClass('selected'); | |
| 897 | + }, | |
| 898 | + /** | |
| 899 | + * Sync all rows back to the hidden JSON input. | |
| 900 | + */ | |
| 901 | + syncHiddenInput: function syncHiddenInput($control) { | |
| 902 | + var items = []; | |
| 903 | + $control.find('.merchant-sortable-repeater-icons .repeater').each(function () { | |
| 904 | + var text = $(this).find('.repeater-input').val(); | |
| 905 | + var icon = $(this).find('.merchant-icon-picker-toggle').attr('data-icon') || ''; | |
| 906 | + items.push({ | |
| 907 | + text: text, | |
| 908 | + icon: icon | |
| 909 | + }); | |
| 910 | + }); | |
| 911 | + if (!items.length) { | |
| 912 | + items.push({ | |
| 913 | + text: '', | |
| 914 | + icon: '' | |
| 915 | + }); | |
| 916 | + } | |
| 917 | + $control.find('.merchant-sortable-repeater-input').val(JSON.stringify(items)); | |
| 918 | + $control.find('.merchant-sortable-repeater-input').trigger('change'); | |
| 919 | + $control.find('.merchant-sortable-repeater-input').trigger('sortable.repeater.change'); | |
| 920 | + }, | |
| 921 | + makeFieldsSortable: function makeFieldsSortable($sortable) { | |
| 922 | + var self = this; | |
| 923 | + if (!$sortable.hasClass('disable-sorting')) { | |
| 924 | + $sortable.sortable({ | |
| 925 | + handle: '.dashicons-menu', | |
| 926 | + update: function update() { | |
| 927 | + self.syncHiddenInput($sortable.closest(self.CONTROL)); | |
| 928 | + } | |
| 929 | + }); | |
| 930 | + } | |
| 931 | + }, | |
| 932 | + getIconLibrary: function getIconLibrary($control) { | |
| 933 | + if (!$control.data('_iconLibrary')) { | |
| 934 | + try { | |
| 935 | + $control.data('_iconLibrary', JSON.parse($control.attr('data-icons')) || {}); | |
| 936 | + } catch (e) { | |
| 937 | + $control.data('_iconLibrary', {}); | |
| 938 | + } | |
| 939 | + } | |
| 940 | + return $control.data('_iconLibrary'); | |
| 941 | + }, | |
| 942 | + normalizeItem: function normalizeItem(item) { | |
| 943 | + if (typeof item === 'string') { | |
| 944 | + return { | |
| 945 | + text: item, | |
| 946 | + icon: '' | |
| 947 | + }; | |
| 948 | + } | |
| 949 | + return { | |
| 950 | + text: item.text || '', | |
| 951 | + icon: item.icon || '' | |
| 952 | + }; | |
| 953 | + } | |
| 954 | + }; | |
| 955 | + | |
| 956 | + // Initialize Sortable Repeater Icons. | |
| 957 | + SortableRepeaterIconsField.init(); | |
| 958 | + var GroubField = { | |
| 959 | + init: function init() { | |
| 960 | + var self = this; | |
| 961 | + self.initAccordion(); | |
| 962 | + self.initFlag(); | |
| 963 | + }, | |
| 964 | + initAccordion: function initAccordion() { | |
| 965 | + var self = this; | |
| 966 | + $('.merchant-group-field.has-accordion').each(function () { | |
| 967 | + var element = $(this); | |
| 968 | + element.accordion({ | |
| 969 | + collapsible: true, | |
| 970 | + header: "> .title-area", | |
| 971 | + heightStyle: "content", | |
| 972 | + active: element.hasClass('open') ? 0 : false | |
| 973 | + }); | |
| 974 | + }); | |
| 975 | + }, | |
| 976 | + initFlag: function initFlag() { | |
| 977 | + $('.merchant-group-field.has-flag').each(function () { | |
| 978 | + var element = $(this); | |
| 979 | + var field_id = element.data('id'); | |
| 980 | + var status_field = element.find(".merchant-field-".concat(field_id, "_status select")); | |
| 981 | + var selected_value = status_field.val(); | |
| 982 | + var selected_label = status_field.find('option:selected').text(); | |
| 983 | + var status_element = element.find('.field-status'); | |
| 984 | + status_element.removeClass('hidden active inactive').text(selected_label).addClass(selected_value); | |
| 985 | + }); | |
| 986 | + } | |
| 987 | + }; | |
| 988 | + var ReviewsSelector = { | |
| 989 | + accordion: null, | |
| 990 | + activePopupContainer: null, | |
| 991 | + // Initialize the Reviews Selector | |
| 992 | + init: function init() { | |
| 993 | + var self = this; | |
| 994 | + self.initAccordion(); | |
| 995 | + self.events(); | |
| 996 | + // Skip the flexible content hidden elements that will be cloned when initialize the sortable reviews functionality | |
| 997 | + self.makeSelectedReviewsSortable($('.merchant-reviews-selector').not('.layouts .merchant-reviews-selector')); | |
| 998 | + }, | |
| 999 | + // Set up event listeners | |
| 1000 | + events: function events() { | |
| 1001 | + var self = this; | |
| 1002 | + $(document).on('input', '.merchant-reviews-selector .products-search', self.ajaxSearch.bind(self)); | |
| 1003 | + $(document).on('change', '.merchant-reviews-selector .product-review input[type="checkbox"]', self.toggleReview.bind(self)); | |
| 1004 | + $(document).on('click', '.merchant-reviews-selector .review-photo img', self.requestReviewImages.bind(self)); | |
| 1005 | + $(document).on('click', '.review-photos-popup .overlay', self.dismissImagesGallery.bind(self)); | |
| 1006 | + $(document).on('click', '.merchant-reviews-selector .product-reviews-load-more button', self.loadMoreReviews.bind(self)); | |
| 1007 | + $(document).on('click', '.merchant-reviews-selector .popup-trigger', self.initPopUp.bind(self)); | |
| 1008 | + $(document).on('click', '.merchant-reviews-selector .popup-header .close, .merchant-reviews-selector > .overlay', self.dismissPopUp.bind(self)); | |
| 1009 | + $(document).on('click', '.merchant-reviews-selector .product-review-delete', self.deleteSelectedReview.bind(self)); | |
| 1010 | + // Listen to escape key | |
| 1011 | + $(document).on('keyup', function (e) { | |
| 1012 | + if (e.key === "Escape") { | |
| 1013 | + var isGallery = self.dismissImagesGallery(); | |
| 1014 | + if (isGallery) { | |
| 1015 | + return; | |
| 1016 | + } | |
| 1017 | + self.dismissPopUp(self); | |
| 1018 | + } | |
| 1019 | + }); | |
| 1020 | + $(document).on('merchant-flexible-content-added', function (e, layout) { | |
| 1021 | + self.makeSelectedReviewsSortable(); | |
| 1022 | + self.initAccordion(); | |
| 1023 | + }); | |
| 1024 | + }, | |
| 1025 | + // Make selected reviews sortable | |
| 1026 | + makeSelectedReviewsSortable: function makeSelectedReviewsSortable() { | |
| 1027 | + var container = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : $(document).find('.merchant-reviews-selector').not('.layouts .merchant-reviews-selector'); | |
| 1028 | + var self = this; | |
| 1029 | + var selectedReviews = container.find('.selected-reviews .product-reviews'); | |
| 1030 | + selectedReviews.sortable({ | |
| 1031 | + axis: 'y', | |
| 1032 | + cursor: 'move', | |
| 1033 | + helper: 'original', | |
| 1034 | + handle: '.product-review-move', | |
| 1035 | + cancel: '', | |
| 1036 | + placeholder: 'placeholder', | |
| 1037 | + // Add a class for styling | |
| 1038 | + update: function update(event, ui) { | |
| 1039 | + var container = ui.item.closest('.merchant-reviews-selector'); | |
| 1040 | + self.saveModifications(container); | |
| 1041 | + }, | |
| 1042 | + start: function start(event, ui) { | |
| 1043 | + ui.placeholder.height(ui.item.height()); // Match height | |
| 1044 | + } | |
| 1045 | + }); | |
| 1046 | + // $('.product-reviews').sortable('refresh'); | |
| 1047 | + // console.log('selectedReviews',selectedReviews.sortable('instance')) | |
| 1048 | + }, | |
| 1049 | + // Delete selected review | |
| 1050 | + deleteSelectedReview: function deleteSelectedReview(e) { | |
| 1051 | + var self = this; | |
| 1052 | + var review = $(e.target).closest('.product-review'); | |
| 1053 | + var container = review.closest('.merchant-reviews-selector'); | |
| 1054 | + review.remove(); | |
| 1055 | + self.saveModifications(container); | |
| 1056 | + self.updateSelectedReviewsCheckboxState(container); | |
| 1057 | + self.countSelectedReviewsInProduct(container); | |
| 1058 | + }, | |
| 1059 | + // Handle AJAX search for reviews | |
| 1060 | + ajaxSearch: function ajaxSearch(e) { | |
| 1061 | + var self = this; | |
| 1062 | + var searchField = $(e.target); | |
| 1063 | + var container = searchField.closest('.merchant-reviews-selector'); | |
| 1064 | + var header = container.find('.popup-header'); | |
| 1065 | + | |
| 1066 | + // check value length > 3 | |
| 1067 | + if (searchField.val().length === 0 || searchField.val().length >= 3) { | |
| 1068 | + $.ajax({ | |
| 1069 | + url: merchant_admin_options.ajaxurl, | |
| 1070 | + type: 'POST', | |
| 1071 | + data: { | |
| 1072 | + action: 'merchant_search_reviews', | |
| 1073 | + search: searchField.val(), | |
| 1074 | + nonce: merchant_admin_options.ajaxnonce | |
| 1075 | + }, | |
| 1076 | + beforeSend: function beforeSend() { | |
| 1077 | + header.addClass('loading'); | |
| 1078 | + self.destroyAccordion(container); | |
| 1079 | + }, | |
| 1080 | + success: function success(response) { | |
| 1081 | + if (response.success) { | |
| 1082 | + header.removeClass('popup-error'); | |
| 1083 | + container.find('.products-search-results').html(response.data); | |
| 1084 | + self.initAccordion(container); | |
| 1085 | + } else { | |
| 1086 | + header.addClass('popup-error'); | |
| 1087 | + } | |
| 1088 | + }, | |
| 1089 | + complete: function complete() { | |
| 1090 | + header.removeClass('loading'); | |
| 1091 | + }, | |
| 1092 | + error: function error() { | |
| 1093 | + header.addClass('popup-error'); | |
| 1094 | + } | |
| 1095 | + }); | |
| 1096 | + } | |
| 1097 | + }, | |
| 1098 | + // Load more reviews | |
| 1099 | + loadMoreReviews: function loadMoreReviews(e) { | |
| 1100 | + var self = this; | |
| 1101 | + var product = $(e.target).closest('.product-item'); | |
| 1102 | + var offset = product.find('.product-review').length; | |
| 1103 | + $.ajax({ | |
| 1104 | + url: merchant_admin_options.ajaxurl, | |
| 1105 | + type: 'POST', | |
| 1106 | + data: { | |
| 1107 | + action: 'merchant_load_more_reviews', | |
| 1108 | + product_id: product.attr('data-id'), | |
| 1109 | + offset: offset, | |
| 1110 | + nonce: merchant_admin_options.ajaxnonce | |
| 1111 | + }, | |
| 1112 | + beforeSend: function beforeSend() { | |
| 1113 | + product.addClass('loading'); | |
| 1114 | + }, | |
| 1115 | + success: function success(response) { | |
| 1116 | + if (response.success) { | |
| 1117 | + product.find('.product-reviews .reviews-wrapper').append(response.data.reviews); | |
| 1118 | + if (response.data.load_more === '') { | |
| 1119 | + self.hideLoadMoreButton(product); | |
| 1120 | + } | |
| 1121 | + self.updateSelectedReviewsCheckboxState(product.closest('.merchant-reviews-selector')); | |
| 1122 | + self.countSelectedReviewsInProduct(product.closest('.merchant-reviews-selector')); | |
| 1123 | + } | |
| 1124 | + }, | |
| 1125 | + complete: function complete() { | |
| 1126 | + product.removeClass('loading'); | |
| 1127 | + } | |
| 1128 | + }); | |
| 1129 | + }, | |
| 1130 | + // Hide load more button | |
| 1131 | + hideLoadMoreButton: function hideLoadMoreButton(product) { | |
| 1132 | + product.find('.product-reviews-load-more').remove(); | |
| 1133 | + }, | |
| 1134 | + // Initialize the popup | |
| 1135 | + initPopUp: function initPopUp(e) { | |
| 1136 | + var self = this; | |
| 1137 | + var container = $(e.target).closest('.merchant-reviews-selector'); | |
| 1138 | + var popup = container.find('.selector-popup'); | |
| 1139 | + var overlay = container.find('.overlay'); | |
| 1140 | + popup.addClass('active'); | |
| 1141 | + overlay.addClass('active'); | |
| 1142 | + self.activePopupContainer = container; | |
| 1143 | + }, | |
| 1144 | + // Dismiss the popup | |
| 1145 | + dismissPopUp: function dismissPopUp() { | |
| 1146 | + var self = this; | |
| 1147 | + var container = self.activePopupContainer; | |
| 1148 | + if (container === null) { | |
| 1149 | + return; | |
| 1150 | + } | |
| 1151 | + var popup = container.find('.selector-popup'); | |
| 1152 | + var overlay = container.find('.overlay'); | |
| 1153 | + popup.removeClass('active'); | |
| 1154 | + overlay.removeClass('active'); | |
| 1155 | + self.activePopupContainer = null; | |
| 1156 | + }, | |
| 1157 | + // Initialize the accordion | |
| 1158 | + initAccordion: function initAccordion() { | |
| 1159 | + var container = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : $(document).find('.merchant-reviews-selector'); | |
| 1160 | + var self = this; | |
| 1161 | + container.each(function () { | |
| 1162 | + var elements = $(this).find('.popup-content .product-item.product-item-has-reviews'); | |
| 1163 | + elements.each(function () { | |
| 1164 | + elements.accordion({ | |
| 1165 | + collapsible: true, | |
| 1166 | + heightStyle: "content", | |
| 1167 | + icons: false, | |
| 1168 | + // Disable icons | |
| 1169 | + active: elements.hasClass('opened') ? 0 : false, | |
| 1170 | + activate: function activate(event, ui) { | |
| 1171 | + // Check if a new panel is being activated | |
| 1172 | + if (ui.newPanel.length) { | |
| 1173 | + // Add 'opened' class to the parent product item | |
| 1174 | + ui.newPanel.parent().addClass('opened'); | |
| 1175 | + } else { | |
| 1176 | + // Remove 'opened' class from all product items | |
| 1177 | + elements.removeClass('opened'); | |
| 1178 | + } | |
| 1179 | + } | |
| 1180 | + }); | |
| 1181 | + }); | |
| 1182 | + elements.find('a').on('click', function (e) { | |
| 1183 | + e.stopPropagation(); // Prevent event from bubbling up to the accordion header | |
| 1184 | + }); | |
| 1185 | + self.updateSelectedReviewsCheckboxState($(this)); | |
| 1186 | + self.countSelectedReviewsInProduct($(this)); | |
| 1187 | + }); | |
| 1188 | + }, | |
| 1189 | + // Destroy the accordion | |
| 1190 | + destroyAccordion: function destroyAccordion(container) { | |
| 1191 | + var elements = container.find('.selector-popup .product-item.product-item-has-reviews'); | |
| 1192 | + elements.each(function () { | |
| 1193 | + if ($(this).hasClass('ui-accordion')) { | |
| 1194 | + // Check if it has the accordion class | |
| 1195 | + $(this).accordion('destroy'); | |
| 1196 | + } | |
| 1197 | + }); | |
| 1198 | + }, | |
| 1199 | + // Toggle review selection | |
| 1200 | + toggleReview: function toggleReview(e) { | |
| 1201 | + var self = this; | |
| 1202 | + var checked = $(e.target).prop('checked'); | |
| 1203 | + var reviewsFieldContainer = $(e.target).closest('.merchant-reviews-selector'); | |
| 1204 | + var review = $(e.target).closest('.product-review'); | |
| 1205 | + var reviewId = review.attr('data-id'); | |
| 1206 | + var selectedReviews = reviewsFieldContainer.find('.selected-reviews .product-reviews'); | |
| 1207 | + var selectedReview = selectedReviews.find(".product-review[data-id=\"".concat(reviewId, "\"]")); | |
| 1208 | + if (checked) { | |
| 1209 | + if (selectedReview.length === 0) { | |
| 1210 | + var newReview = review.clone(); | |
| 1211 | + selectedReviews.append(newReview); | |
| 1212 | + self.makeSelectedReviewsSortable(); | |
| 1213 | + } | |
| 1214 | + } else { | |
| 1215 | + selectedReview.remove(); | |
| 1216 | + } | |
| 1217 | + setTimeout(function () { | |
| 1218 | + self.saveModifications(reviewsFieldContainer); | |
| 1219 | + }, 150); | |
| 1220 | + }, | |
| 1221 | + // Save modifications to the selected reviews | |
| 1222 | + saveModifications: function saveModifications(container) { | |
| 1223 | + var self = this; | |
| 1224 | + var reviewsSavedIdsField = container.find('.review-saved-ids'); | |
| 1225 | + var selectedReviews = container.find('.selected-reviews .product-reviews'); | |
| 1226 | + var reviews = selectedReviews.find('.product-review'); | |
| 1227 | + var reviewsIds = []; | |
| 1228 | + reviews.each(function () { | |
| 1229 | + var id = $(this).attr('data-id'); | |
| 1230 | + reviewsIds.push(id); | |
| 1231 | + }); | |
| 1232 | + reviewsSavedIdsField.val(reviewsIds.join(',')); | |
| 1233 | + reviewsSavedIdsField.trigger('change'); | |
| 1234 | + // reviewsSavedIds; | |
| 1235 | + self.updateSelectedReviewsCheckboxState(container); | |
| 1236 | + self.countSelectedReviewsInProduct(container); | |
| 1237 | + }, | |
| 1238 | + // Mark selected reviews checkbox | |
| 1239 | + updateSelectedReviewsCheckboxState: function updateSelectedReviewsCheckboxState(container) { | |
| 1240 | + var popupContent = container.find('.selector-popup'); | |
| 1241 | + var reviewsSavedIds = container.find('.review-saved-ids'); | |
| 1242 | + var reviews = popupContent.find('.product-review'); | |
| 1243 | + var reviewsIds = reviewsSavedIds.val().split(','); | |
| 1244 | + reviews.each(function () { | |
| 1245 | + var reviewId = $(this).attr('data-id'); | |
| 1246 | + var checkbox = container.find(".product-review[data-id=\"".concat(reviewId, "\"] input[type=\"checkbox\"]")); | |
| 1247 | + if (reviewsIds.includes(reviewId)) { | |
| 1248 | + checkbox.prop('checked', true); | |
| 1249 | + } else { | |
| 1250 | + checkbox.prop('checked', false); | |
| 1251 | + } | |
| 1252 | + }); | |
| 1253 | + }, | |
| 1254 | + // Count selected reviews in product | |
| 1255 | + countSelectedReviewsInProduct: function countSelectedReviewsInProduct(container) { | |
| 1256 | + var self = this; | |
| 1257 | + var products = container.find('.selector-popup .product-item'); | |
| 1258 | + var reviewsSavedIds = container.find('.review-saved-ids'); | |
| 1259 | + var reviewsIds = reviewsSavedIds.val().split(','); | |
| 1260 | + products.each(function () { | |
| 1261 | + var selectedReviews = 0; | |
| 1262 | + var product = $(this); | |
| 1263 | + var productReviews = product.find('.product-review'); | |
| 1264 | + productReviews.each(function () { | |
| 1265 | + var review = $(this); | |
| 1266 | + var reviewId = review.attr('data-id'); | |
| 1267 | + if (reviewsIds.includes(reviewId)) { | |
| 1268 | + selectedReviews++; | |
| 1269 | + } | |
| 1270 | + }); | |
| 1271 | + product.find('.selected-reviews-count .counter').text(selectedReviews); | |
| 1272 | + }); | |
| 1273 | + }, | |
| 1274 | + // Request review images | |
| 1275 | + requestReviewImages: function requestReviewImages(e) { | |
| 1276 | + var reviewContainer = $(e.target).closest('.product-review'); | |
| 1277 | + var image = $(e.target); | |
| 1278 | + var reviewId = reviewContainer.attr('data-id'); | |
| 1279 | + $.ajax({ | |
| 1280 | + url: merchant_admin_options.ajaxurl, | |
| 1281 | + type: 'POST', | |
| 1282 | + data: { | |
| 1283 | + action: 'merchant_get_review_images', | |
| 1284 | + review_id: reviewId, | |
| 1285 | + nonce: merchant_admin_options.ajaxnonce | |
| 1286 | + }, | |
| 1287 | + beforeSend: function beforeSend() { | |
| 1288 | + // Show loading spinner | |
| 1289 | + image.addClass('loading'); | |
| 1290 | + }, | |
| 1291 | + success: function success(response) { | |
| 1292 | + if (response.success) { | |
| 1293 | + $('body').append(response.data); | |
| 1294 | + setTimeout(function () { | |
| 1295 | + $('body').find('.review-photos-popup').addClass('active'); | |
| 1296 | + }, 300); | |
| 1297 | + } | |
| 1298 | + }, | |
| 1299 | + complete: function complete() { | |
| 1300 | + // Hide loading spinner | |
| 1301 | + image.removeClass('loading'); | |
| 1302 | + } | |
| 1303 | + }); | |
| 1304 | + }, | |
| 1305 | + // Dismiss the images gallery | |
| 1306 | + dismissImagesGallery: function dismissImagesGallery() { | |
| 1307 | + var gallery = $('body').find('.review-photos-popup'); | |
| 1308 | + if (gallery.length) { | |
| 1309 | + gallery.removeClass('active'); | |
| 1310 | + setTimeout(function () { | |
| 1311 | + gallery.remove(); | |
| 1312 | + }, 300); | |
| 1313 | + return true; | |
| 1314 | + } | |
| 1315 | + return false; | |
| 1316 | + } | |
| 1317 | + }; | |
| 1318 | + | |
| 283 | 1319 | // Flexible Content. |
| 284 | 1320 | var FlexibleContentField = { |
| 285 | 1321 | init: function init(field) { |
| 286 | 1322 | var self = this; |
| @@ -286,32 +1322,294 @@ | ||
| 286 | 1322 | var self = this; |
| 287 | 1323 | |
| 288 | 1324 | // Update the values for all our input fields and initialise the sortable repeater. |
| 289 | 1325 | $('.merchant-flexible-content-control').each(function () { |
| 290 | - var $content = $(this).find('.merchant-flexible-content'); | |
| 291 | - $content.sortable({ | |
| 292 | - axis: 'y', | |
| 293 | - cursor: 'move', | |
| 294 | - helper: 'original', | |
| 295 | - handle: '.customize-control-flexible-content-move', | |
| 296 | - stop: function stop(event, ui) { | |
| 297 | - $content.trigger('merchant.sorted'); | |
| 298 | - self.refreshNumbers($content); | |
| 1326 | + var hasAccordion = $(this).hasClass('has-accordion'), | |
| 1327 | + $content = $(this).find('.merchant-flexible-content'); | |
| 1328 | + var campaignId = params.get('campaign_id'); | |
| 1329 | + var campaignIndex = 0; | |
| 1330 | + | |
| 1331 | + // Find the campaignIndex based on campaignId | |
| 1332 | + if (campaignId) { | |
| 1333 | + $content.find('input.flexible-id').each(function (index) { | |
| 1334 | + if ($(this).val() === campaignId) { | |
| 1335 | + campaignIndex = index; | |
| 1336 | + return false; | |
| 1337 | + } | |
| 1338 | + }); | |
| 1339 | + } | |
| 1340 | + if (hasAccordion) { | |
| 1341 | + $content.accordion({ | |
| 1342 | + active: campaignIndex, | |
| 1343 | + // Open the campaign with campaign index | |
| 1344 | + collapsible: true, | |
| 1345 | + //header: "> div > .layout-header", | |
| 1346 | + header: function header(elem) { | |
| 1347 | + return elem.find('.layout__inner > .layout-header'); | |
| 1348 | + }, | |
| 1349 | + heightStyle: "content", | |
| 1350 | + beforeActivate: function beforeActivate(event, ui) { | |
| 1351 | + // Prevent accordion toggle when clicking the status badge. | |
| 1352 | + if (event.originalEvent && $(event.originalEvent.target).hasClass('layout-status')) { | |
| 1353 | + event.preventDefault(); | |
| 1354 | + return; | |
| 1355 | + } | |
| 1356 | + | |
| 1357 | + // Hydrate deferred layout before the accordion animation | |
| 1358 | + // so the panel opens with content already rendered. | |
| 1359 | + if (ui.newPanel.length) { | |
| 1360 | + var $layout = ui.newPanel.closest('.layout'); | |
| 1361 | + if ($layout.attr('data-deferred') === '1') { | |
| 1362 | + self.hydrateLayout($layout); | |
| 1363 | + } | |
| 1364 | + } | |
| 1365 | + } | |
| 1366 | + }).sortable({ | |
| 1367 | + axis: 'y', | |
| 1368 | + cursor: 'move', | |
| 1369 | + helper: 'original', | |
| 1370 | + handle: '.customize-control-flexible-content-move', | |
| 1371 | + stop: function stop(event, ui) { | |
| 1372 | + $content.trigger('merchant.sorted'); | |
| 1373 | + self.refreshNumbers($content); | |
| 1374 | + $content.accordion("refresh"); | |
| 1375 | + } | |
| 1376 | + }); | |
| 1377 | + } else { | |
| 1378 | + $content.sortable({ | |
| 1379 | + axis: 'y', | |
| 1380 | + cursor: 'move', | |
| 1381 | + helper: 'original', | |
| 1382 | + handle: '.customize-control-flexible-content-move', | |
| 1383 | + stop: function stop(event, ui) { | |
| 1384 | + $content.trigger('merchant.sorted'); | |
| 1385 | + self.refreshNumbers($content); | |
| 1386 | + $content.accordion("refresh"); | |
| 1387 | + } | |
| 1388 | + }); | |
| 1389 | + } | |
| 1390 | + }); | |
| 1391 | + this.updateLayoutTitle(); | |
| 1392 | + this.updateLayoutStatus(); | |
| 1393 | + this.updateDiscountPercentMaxVal(); | |
| 1394 | + // Events. | |
| 1395 | + this.events(); | |
| 1396 | + }, | |
| 1397 | + updateLayoutTitle: function updateLayoutTitle() { | |
| 1398 | + // Update the title for all layout header. | |
| 1399 | + $('.merchant-flexible-content .layout').each(function () { | |
| 1400 | + var title = $(this).find('.layout-title[data-title-field]'); | |
| 1401 | + if (title.length) { | |
| 1402 | + var input = $(this).find('.layout-body .merchant-field-' + title.data('title-field') + ' input'); | |
| 1403 | + input.on('change keyup', function () { | |
| 1404 | + title.text($(this).val()); | |
| 1405 | + }); | |
| 1406 | + title.text(input.val()); | |
| 1407 | + } | |
| 1408 | + }); | |
| 1409 | + }, | |
| 1410 | + /** | |
| 1411 | + * Resolve a status field inside a layout, returning a unified adapter | |
| 1412 | + * that works with select, radio, and switcher field types. | |
| 1413 | + * | |
| 1414 | + * @param {jQuery} $layout The .layout element. | |
| 1415 | + * @param {string} fieldId The field identifier (e.g. 'campaign_status'). | |
| 1416 | + * @returns {object|null} Adapter with getValue, getLabel, getOptions, setNext, onChange — or null. | |
| 1417 | + */ | |
| 1418 | + resolveStatusField: function resolveStatusField($layout, fieldId) { | |
| 1419 | + var $wrapper = $layout.find('.layout-body .merchant-field-' + fieldId); | |
| 1420 | + if (!$wrapper.length) return null; | |
| 1421 | + var $select = $wrapper.find('select'); | |
| 1422 | + var $radios = $wrapper.find('input[type="radio"]'); | |
| 1423 | + var $checkbox = $wrapper.find('input[type="checkbox"]'); | |
| 1424 | + | |
| 1425 | + // Select field adapter. | |
| 1426 | + if ($select.length) { | |
| 1427 | + return { | |
| 1428 | + getValue: function getValue() { | |
| 1429 | + return $select.val() || 'active'; | |
| 1430 | + }, | |
| 1431 | + getLabel: function getLabel() { | |
| 1432 | + return $select.find('option:selected').text() || this.getValue(); | |
| 1433 | + }, | |
| 1434 | + getOptions: function getOptions() { | |
| 1435 | + return $select.find('option').map(function () { | |
| 1436 | + return $(this).val(); | |
| 1437 | + }).get(); | |
| 1438 | + }, | |
| 1439 | + setNext: function setNext() { | |
| 1440 | + var opts = $select.find('option'); | |
| 1441 | + var idx = opts.index(opts.filter(':selected')); | |
| 1442 | + var next = (idx + 1) % opts.length; | |
| 1443 | + $select.val(opts.eq(next).val()).trigger('change'); | |
| 1444 | + }, | |
| 1445 | + onChange: function onChange(fn) { | |
| 1446 | + $select.off('change.statusBadge').on('change.statusBadge', fn); | |
| 299 | 1447 | } |
| 300 | - }); | |
| 1448 | + }; | |
| 1449 | + } | |
| 1450 | + | |
| 1451 | + // Radio field adapter. | |
| 1452 | + if ($radios.length) { | |
| 1453 | + return { | |
| 1454 | + getValue: function getValue() { | |
| 1455 | + return $radios.filter(':checked').val() || 'active'; | |
| 1456 | + }, | |
| 1457 | + getLabel: function getLabel() { | |
| 1458 | + var $checked = $radios.filter(':checked'); | |
| 1459 | + var $label = $wrapper.find('label[for="' + $checked.attr('id') + '"]'); | |
| 1460 | + return $label.length ? $label.text() : this.getValue(); | |
| 1461 | + }, | |
| 1462 | + getOptions: function getOptions() { | |
| 1463 | + return $radios.map(function () { | |
| 1464 | + return $(this).val(); | |
| 1465 | + }).get(); | |
| 1466 | + }, | |
| 1467 | + setNext: function setNext() { | |
| 1468 | + var vals = this.getOptions(); | |
| 1469 | + var idx = vals.indexOf(this.getValue()); | |
| 1470 | + var next = (idx + 1) % vals.length; | |
| 1471 | + $radios.filter('[value="' + vals[next] + '"]').prop('checked', true).trigger('change'); | |
| 1472 | + }, | |
| 1473 | + onChange: function onChange(fn) { | |
| 1474 | + $radios.off('change.statusBadge').on('change.statusBadge', fn); | |
| 1475 | + } | |
| 1476 | + }; | |
| 1477 | + } | |
| 1478 | + | |
| 1479 | + // Switcher (checkbox) field adapter — on/off. | |
| 1480 | + if ($checkbox.length) { | |
| 1481 | + return { | |
| 1482 | + getValue: function getValue() { | |
| 1483 | + return $checkbox.is(':checked') ? 'active' : 'inactive'; | |
| 1484 | + }, | |
| 1485 | + getLabel: function getLabel() { | |
| 1486 | + return $checkbox.is(':checked') ? 'Active' : 'Inactive'; | |
| 1487 | + }, | |
| 1488 | + getOptions: function getOptions() { | |
| 1489 | + return ['active', 'inactive']; | |
| 1490 | + }, | |
| 1491 | + setNext: function setNext() { | |
| 1492 | + $checkbox.prop('checked', !$checkbox.is(':checked')).trigger('change'); | |
| 1493 | + }, | |
| 1494 | + onChange: function onChange(fn) { | |
| 1495 | + $checkbox.off('change.statusBadge').on('change.statusBadge', fn); | |
| 1496 | + } | |
| 1497 | + }; | |
| 1498 | + } | |
| 1499 | + return null; | |
| 1500 | + }, | |
| 1501 | + updateLayoutStatus: function updateLayoutStatus() { | |
| 1502 | + var self = this; | |
| 1503 | + | |
| 1504 | + // Sync status badge on each layout header with the field value. | |
| 1505 | + $('.merchant-flexible-content .layout').each(function () { | |
| 1506 | + var $badge = $(this).find('.layout-header .layout-status[data-status-field]'); | |
| 1507 | + if (!$badge.length) return; | |
| 1508 | + var fieldId = $badge.data('status-field'); | |
| 1509 | + var adapter = self.resolveStatusField($(this), fieldId); | |
| 1510 | + if (!adapter) return; | |
| 1511 | + function syncBadge() { | |
| 1512 | + var val = adapter.getValue(); | |
| 1513 | + var label = adapter.getLabel(); | |
| 1514 | + $badge.removeClass(function (i, cls) { | |
| 1515 | + return (cls.match(/layout-status--\S+/g) || []).join(' '); | |
| 1516 | + }).addClass('layout-status--' + val).text(label.trim()); | |
| 1517 | + } | |
| 1518 | + adapter.onChange(syncBadge); | |
| 1519 | + syncBadge(); | |
| 301 | 1520 | }); |
| 302 | 1521 | |
| 303 | - // Events. | |
| 304 | - this.events(); | |
| 1522 | + // Delegated click handler for badge toggle — works on all rows including deferred. | |
| 1523 | + $(document).off('click.statusToggle').on('click.statusToggle', '.layout-status[data-status-field]', function (e) { | |
| 1524 | + e.stopPropagation(); | |
| 1525 | + e.preventDefault(); | |
| 1526 | + var $badge = $(this); | |
| 1527 | + var fieldId = $badge.data('status-field'); | |
| 1528 | + var $layout = $badge.closest('.layout'); | |
| 1529 | + var adapter = self.resolveStatusField($layout, fieldId); | |
| 1530 | + if (adapter) { | |
| 1531 | + // Hydrated row: toggle via the field adapter. | |
| 1532 | + adapter.setNext(); | |
| 1533 | + } else { | |
| 1534 | + // Deferred row: cycle through options stored in data-status-options, | |
| 1535 | + // falling back to active/inactive. | |
| 1536 | + var optionsAttr = $badge.attr('data-status-options'); | |
| 1537 | + var options = optionsAttr ? optionsAttr.split(',') : ['active', 'inactive']; | |
| 1538 | + var currentVal = $badge.attr('class').match(/layout-status--(\S+)/); | |
| 1539 | + currentVal = currentVal ? currentVal[1] : options[0]; | |
| 1540 | + var idx = options.indexOf(currentVal); | |
| 1541 | + var nextIdx = (idx + 1) % options.length; | |
| 1542 | + var newVal = options[nextIdx]; | |
| 1543 | + var newLabel = newVal.charAt(0).toUpperCase() + newVal.slice(1); | |
| 1544 | + $badge.removeClass('layout-status--' + currentVal).addClass('layout-status--' + newVal).text(newLabel); | |
| 1545 | + | |
| 1546 | + // Update data-fields-json so the value persists when hydrated. | |
| 1547 | + var jsonAttr = $layout.attr('data-fields-json'); | |
| 1548 | + if (jsonAttr) { | |
| 1549 | + try { | |
| 1550 | + var data = JSON.parse(jsonAttr); | |
| 1551 | + if (!data.values) data.values = {}; | |
| 1552 | + data.values[fieldId] = newVal; | |
| 1553 | + $layout.attr('data-fields-json', JSON.stringify(data)); | |
| 1554 | + } catch (ex) {/* ignore parse errors */} | |
| 1555 | + } | |
| 1556 | + | |
| 1557 | + // Mark the form as changed. | |
| 1558 | + $('.merchant-module-page-content').trigger('change.merchant'); | |
| 1559 | + } | |
| 1560 | + }); | |
| 305 | 1561 | }, |
| 1562 | + updateDiscountPercentMaxVal: function updateDiscountPercentMaxVal() { | |
| 1563 | + $('.merchant-flexible-content .layout').each(function () { | |
| 1564 | + var $layout = $(this); | |
| 1565 | + var $discountType = $layout.find('.merchant-module-page-setting-field[data-id="discount_type"]'); | |
| 1566 | + var $discountVal = $layout.find('.merchant-module-page-setting-field[data-id="discount_value"]'); | |
| 1567 | + $discountVal = $discountVal.length ? $discountVal : $layout.find('.merchant-module-page-setting-field[data-id="discount"]'); | |
| 1568 | + $discountVal = $discountVal.length ? $discountVal : $layout.find('.merchant-module-page-setting-field[data-id="discount_amount"]'); | |
| 1569 | + var checkedValue = $discountType.find('input:checked').val(); | |
| 1570 | + | |
| 1571 | + // Set/Remove max value based on the discount type. | |
| 1572 | + checkedValue === 'percentage_discount' || checkedValue === 'percentage' ? $discountVal.find('input').attr('max', 100) : $discountVal.find('input').removeAttr('max'); | |
| 1573 | + }); | |
| 1574 | + $('.merchant-module-page-setting-fields'); | |
| 1575 | + }, | |
| 1576 | + generateUUID: function generateUUID() { | |
| 1577 | + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { | |
| 1578 | + var r = Math.random() * 16 | 0; | |
| 1579 | + var v = c === 'x' ? r : r & 0x3 | 0x8; | |
| 1580 | + return v.toString(16); | |
| 1581 | + }); | |
| 1582 | + }, | |
| 306 | 1583 | events: function events() { |
| 307 | 1584 | var self = this; |
| 308 | - $('.customize-control-flexible-content-add-button').click(function (event) { | |
| 1585 | + $(document).on('change', '.merchant-module-page-setting-field[data-id="discount_type"] input', function () { | |
| 1586 | + self.updateDiscountPercentMaxVal(); | |
| 1587 | + }); | |
| 1588 | + | |
| 1589 | + // Update "selected" attribute on select. It's Required for duplicating layouts. | |
| 1590 | + $(document).on('change', 'select', function () { | |
| 1591 | + var selectedValue = $(this).val(); | |
| 1592 | + var isMultiple = $(this).prop('multiple'); | |
| 1593 | + $(this).find('option').each(function () { | |
| 1594 | + var currentValue = $(this).val(); | |
| 1595 | + var isSelected = isMultiple ? selectedValue.includes(currentValue) : currentValue === selectedValue; | |
| 1596 | + $(this).attr('selected', isSelected); | |
| 1597 | + }); | |
| 1598 | + }); | |
| 1599 | + $('.customize-control-flexible-content-add-button').on('click', function (event) { | |
| 309 | 1600 | event.preventDefault(); |
| 310 | 1601 | event.stopImmediatePropagation(); |
| 1602 | + if ($(this).parent().find('.customize-control-flexible-content-add-list a').length === 1) { | |
| 1603 | + // If there is only one layout, trigger click on it. | |
| 1604 | + $(this).parent().find('.customize-control-flexible-content-add-list a').trigger('click'); | |
| 1605 | + return; | |
| 1606 | + } | |
| 311 | 1607 | $(this).parent().find('.customize-control-flexible-content-add-list').toggleClass('active'); |
| 312 | 1608 | }); |
| 313 | - $('.customize-control-flexible-content-add').click(function (event) { | |
| 1609 | + | |
| 1610 | + // Add new item | |
| 1611 | + $(document).on('click', '.customize-control-flexible-content-add', function (event) { | |
| 314 | 1612 | event.preventDefault(); |
| 315 | 1613 | event.stopImmediatePropagation(); |
| 316 | 1614 | var $field = $('.merchant-flexible-content-control[data-id=' + $(this).data('id') + ']'); |
| 317 | 1615 | var $layouts = $field.find('.layouts'); |
| @@ -318,14 +1616,20 @@ | ||
| 318 | 1616 | var $selected = $(this).data('layout'); |
| 319 | 1617 | var $layout = $layouts.find('.layout[data-type=' + $selected + ']').clone(true); |
| 320 | 1618 | var $content = $field.find('.merchant-flexible-content'); |
| 321 | 1619 | var $items = $content.find('.layout'); |
| 322 | - $layout.find('input, select').each(function () { | |
| 1620 | + var uuid = self.generateUUID(); | |
| 1621 | + $layout.find('input, select, textarea').each(function () { | |
| 323 | 1622 | if ($(this).data('name')) { |
| 324 | 1623 | $(this).attr('name', $(this).data('name').replace('0', $items.length)); |
| 325 | 1624 | } |
| 1625 | + if ($(this).is(':checkbox, :radio') && $(this).attr('checked')) { | |
| 1626 | + $(this).prop('checked', true); | |
| 1627 | + } | |
| 326 | 1628 | }); |
| 1629 | + $layout.attr('data-layout-id', uuid); | |
| 327 | 1630 | $layout.find('.layout-count').text($items.length + 1); |
| 1631 | + $layout.find('.flexible-id').val(uuid); | |
| 328 | 1632 | $content.append($layout); |
| 329 | 1633 | $content.removeClass('empty'); |
| 330 | 1634 | $(this).parent().removeClass('active'); |
| 331 | 1635 | if ($layout.find('.merchant-module-page-setting-field-upload').length) { |
| @@ -333,12 +1637,101 @@ | ||
| 333 | 1637 | } |
| 334 | 1638 | if ($layout.find('.merchant-module-page-setting-field-select_ajax').length) { |
| 335 | 1639 | initSelectAjax($layout.find('.merchant-module-page-setting-field-select_ajax')); |
| 336 | 1640 | } |
| 1641 | + var parentDiv = $(this).closest('.merchant-flexible-content-control'), | |
| 1642 | + hasAccordion = parentDiv.hasClass('has-accordion'); | |
| 1643 | + if (hasAccordion) { | |
| 1644 | + parentDiv.find('.merchant-flexible-content').accordion("refresh"); | |
| 1645 | + // Expand the accordion last added | |
| 1646 | + parentDiv.find('.merchant-flexible-content').accordion("option", "active", -1); | |
| 1647 | + } | |
| 1648 | + GroubField.init(); | |
| 337 | 1649 | $(document).trigger('merchant-flexible-content-added', [$layout]); |
| 1650 | + initMerchantRange(); | |
| 1651 | + self.updateLayoutTitle(); | |
| 1652 | + self.updateLayoutStatus(); | |
| 1653 | + self.updateDiscountPercentMaxVal(); | |
| 1654 | + $('.merchant-module-page-content').trigger('change.merchant'); | |
| 338 | 1655 | }); |
| 339 | - $('.customize-control-flexible-content-delete').click(function (event) { | |
| 1656 | + | |
| 1657 | + // Duplicate item | |
| 1658 | + $(document).on('click', '.customize-control-flexible-content-duplicate', function (event) { | |
| 340 | 1659 | event.preventDefault(); |
| 1660 | + event.stopImmediatePropagation(); | |
| 1661 | + var $duplicateBtn = $(this); | |
| 1662 | + var $flexibleContentWrapper = $duplicateBtn.closest('.merchant-flexible-content-control[data-id=' + $duplicateBtn.data('id') + ']'); | |
| 1663 | + var $flexibleContent = $flexibleContentWrapper === null || $flexibleContentWrapper === void 0 ? void 0 : $flexibleContentWrapper.find('.merchant-flexible-content'); | |
| 1664 | + if (!$flexibleContentWrapper.length || !$flexibleContent.length) { | |
| 1665 | + return; | |
| 1666 | + } | |
| 1667 | + var $sourceLayout = $duplicateBtn.closest('.layout'); | |
| 1668 | + if (!$sourceLayout.length) { | |
| 1669 | + return; | |
| 1670 | + } | |
| 1671 | + $sourceLayout.find('.layout-actions__inner').hide(); | |
| 1672 | + | |
| 1673 | + // Clone the layout without data & events. | |
| 1674 | + var $clonedLayout = $sourceLayout.clone(); | |
| 1675 | + var $items = $flexibleContent.find('.layout'); | |
| 1676 | + var index = $sourceLayout.find('.layout-count').text(); | |
| 1677 | + var uuid = self.generateUUID(); | |
| 1678 | + $clonedLayout.attr('data-layout-id', uuid); | |
| 1679 | + $clonedLayout.find('.flexible-id').val(uuid); | |
| 1680 | + $clonedLayout.find('input, select, textarea').each(function () { | |
| 1681 | + var $input = $(this); | |
| 1682 | + var inputName = $input.attr('name'); | |
| 1683 | + if (inputName) { | |
| 1684 | + var prefix = inputName.split('[')[0]; | |
| 1685 | + var indexPart = inputName.match(/\[(.*?)\]/g); | |
| 1686 | + if (indexPart && indexPart.length > 1) { | |
| 1687 | + indexPart[1] = '[' + index + ']'; | |
| 1688 | + var newName = "".concat(prefix).concat(indexPart.join('')); | |
| 1689 | + $input.attr('name', newName); | |
| 1690 | + } | |
| 1691 | + } | |
| 1692 | + }); | |
| 1693 | + | |
| 1694 | + // Find select2, Remove and Re-init again. select.select2('destroy') doesn't work. | |
| 1695 | + $clonedLayout.find('select').each(function () { | |
| 1696 | + if ($(this).hasClass('select2-hidden-accessible')) { | |
| 1697 | + $(this).removeClass('select2-hidden-accessible').removeAttr('data-live-search').removeAttr('data-select2-id').removeAttr('aria-hidden').removeAttr('tabindex'); | |
| 1698 | + | |
| 1699 | + // Remove the existing dropdown | |
| 1700 | + $(this).nextAll('.select2-container').remove(); | |
| 1701 | + | |
| 1702 | + // Re-init | |
| 1703 | + $(this).select2(); | |
| 1704 | + } | |
| 1705 | + }); | |
| 1706 | + | |
| 1707 | + // Removing copied style to make the accordion work properly. | |
| 1708 | + $clonedLayout.find('.layout-body').removeAttr('style'); | |
| 1709 | + | |
| 1710 | + // Append the cloned layout right after the source one with fadeIn effect. | |
| 1711 | + $clonedLayout.hide(); | |
| 1712 | + $clonedLayout.insertAfter($sourceLayout); | |
| 1713 | + $clonedLayout.fadeIn(); | |
| 1714 | + if ($clonedLayout.find('.merchant-module-page-setting-field-upload').length) { | |
| 1715 | + initUploadField($clonedLayout.find('.merchant-module-page-setting-field-upload')); | |
| 1716 | + } | |
| 1717 | + if ($clonedLayout.find('.merchant-module-page-setting-field-select_ajax').length) { | |
| 1718 | + initSelectAjax($clonedLayout.find('.merchant-module-page-setting-field-select_ajax')); | |
| 1719 | + } | |
| 1720 | + self.refreshNumbers($flexibleContent); | |
| 1721 | + $(document).trigger('merchant-flexible-content-added', [$clonedLayout]); | |
| 1722 | + if ($flexibleContentWrapper.hasClass('has-accordion')) { | |
| 1723 | + $flexibleContent.accordion('refresh'); | |
| 1724 | + } | |
| 1725 | + self.updateLayoutTitle(); | |
| 1726 | + self.updateLayoutStatus(); | |
| 1727 | + GroubField.init(); | |
| 1728 | + $('.merchant-module-page-content').trigger('change.merchant'); | |
| 1729 | + }); | |
| 1730 | + | |
| 1731 | + // Delete item | |
| 1732 | + $(document).on('click', '.customize-control-flexible-content-delete', function (event) { | |
| 1733 | + event.preventDefault(); | |
| 341 | 1734 | var $item = $(this).closest('.layout'); |
| 342 | 1735 | var $content = $item.parent(); |
| 343 | 1736 | $item.remove(); |
| 344 | 1737 | if ($content.find('.layout').length === 0) { |
| @@ -345,16 +1738,343 @@ | ||
| 345 | 1738 | $content.addClass('empty'); |
| 346 | 1739 | } |
| 347 | 1740 | self.refreshNumbers($content); |
| 348 | 1741 | $(document).trigger('merchant-flexible-content-deleted', [$item]); |
| 1742 | + var parentDiv = $(this).closest('.merchant-flexible-content-control'), | |
| 1743 | + hasAccordion = parentDiv.hasClass('has-accordion'); | |
| 1744 | + if (hasAccordion) { | |
| 1745 | + parentDiv.find('.merchant-flexible-content').accordion("refresh"); | |
| 1746 | + } | |
| 1747 | + $('.merchant-module-page-content').trigger('change.merchant'); | |
| 349 | 1748 | }); |
| 1749 | + | |
| 1750 | + // Toggle Actions(delete/duplicate) | |
| 1751 | + $(document).on('click', '.layout-actions__toggle', function (e) { | |
| 1752 | + e.preventDefault(); | |
| 1753 | + | |
| 1754 | + // Hide other opened elements | |
| 1755 | + hideOtherActions($(this).closest('.layout')); | |
| 1756 | + | |
| 1757 | + // Toggle the current element | |
| 1758 | + $(this).closest('.layout-actions').find('.layout-actions__inner').stop().slideToggle(300); | |
| 1759 | + }); | |
| 1760 | + | |
| 1761 | + // Hide Actions when collapse/open | |
| 1762 | + $(document).on('click', '.layout-header', function () { | |
| 1763 | + hideOtherActions($(this).closest('.layout')); | |
| 1764 | + }); | |
| 1765 | + $(document).on('merchant-flexible-content-added', function (e, $layout) { | |
| 1766 | + hideOtherActions($layout); | |
| 1767 | + }); | |
| 1768 | + function hideOtherActions($layout) { | |
| 1769 | + if ($layout && $layout.length) { | |
| 1770 | + $layout.siblings().find('.layout-actions__inner').slideUp(300); | |
| 1771 | + } | |
| 1772 | + } | |
| 1773 | + | |
| 1774 | + // Dismiss actions menu on click outside or Escape. | |
| 1775 | + $(document).on('click', function (e) { | |
| 1776 | + if (!$(e.target).closest('.layout-actions').length) { | |
| 1777 | + $('.layout-actions__inner').slideUp(300); | |
| 1778 | + } | |
| 1779 | + }); | |
| 1780 | + $(document).on('keydown', function (e) { | |
| 1781 | + if (e.key === 'Escape') { | |
| 1782 | + $('.layout-actions__inner').slideUp(300); | |
| 1783 | + } | |
| 1784 | + }); | |
| 350 | 1785 | }, |
| 1786 | + /** | |
| 1787 | + * Hydrate a deferred layout row. | |
| 1788 | + * | |
| 1789 | + * Clones the hidden template for the layout type, populates field values | |
| 1790 | + * from the data-fields-json attribute, initialises all widgets, and | |
| 1791 | + * replaces the empty layout-body content with the fully rendered fields. | |
| 1792 | + * | |
| 1793 | + * @param {jQuery} $layout The .layout element with data-deferred="1". | |
| 1794 | + */ | |
| 1795 | + hydrateLayout: function hydrateLayout($layout) { | |
| 1796 | + var jsonStr = $layout.attr('data-fields-json'); | |
| 1797 | + if (!jsonStr) { | |
| 1798 | + return; | |
| 1799 | + } | |
| 1800 | + var data; | |
| 1801 | + try { | |
| 1802 | + data = JSON.parse(jsonStr); | |
| 1803 | + } catch (e) { | |
| 1804 | + return; | |
| 1805 | + } | |
| 1806 | + var layoutType = $layout.attr('data-type'); | |
| 1807 | + var $control = $layout.closest('.merchant-flexible-content-control'); | |
| 1808 | + var $template = $control.find('.layouts .layout[data-type="' + layoutType + '"]'); | |
| 1809 | + if (!$template.length) { | |
| 1810 | + return; | |
| 1811 | + } | |
| 1812 | + | |
| 1813 | + // Clone the template's layout-body content (the rendered fields). | |
| 1814 | + var $clonedBody = $template.find('.layout-body').clone(); | |
| 1815 | + | |
| 1816 | + // Determine the correct row index from layout-count. | |
| 1817 | + var rowIndex = parseInt($layout.find('.layout-count').text(), 10) - 1; | |
| 1818 | + var fieldId = $control.attr('data-id'); | |
| 1819 | + | |
| 1820 | + // Fix name attributes: replace data-name with name, and index [0] with [rowIndex]. | |
| 1821 | + $clonedBody.find('input, select, textarea').each(function () { | |
| 1822 | + var dataName = $(this).attr('data-name'); | |
| 1823 | + if (dataName) { | |
| 1824 | + $(this).attr('name', dataName.replace('[0]', '[' + rowIndex + ']')); | |
| 1825 | + $(this).removeAttr('data-name'); | |
| 1826 | + } | |
| 1827 | + }); | |
| 1828 | + | |
| 1829 | + // Populate field values from the resolved data. | |
| 1830 | + this.populateFieldValues($clonedBody, data, fieldId, rowIndex); | |
| 1831 | + | |
| 1832 | + // Sync color picker preview boxes to reflect populated values. | |
| 1833 | + $clonedBody.find('.merchant-color').each(function () { | |
| 1834 | + var colorVal = $(this).find('.merchant-color-input').val(); | |
| 1835 | + if (colorVal) { | |
| 1836 | + $(this).find('.merchant-color-picker').css('background-color', colorVal); | |
| 1837 | + } | |
| 1838 | + }); | |
| 1839 | + | |
| 1840 | + // Sync range slider values from their sibling number inputs. | |
| 1841 | + // populateFieldValues only sets the number input (it has the name attribute); | |
| 1842 | + // the range slider (no name) still holds the template default. | |
| 1843 | + $clonedBody.find('.merchant-range').each(function () { | |
| 1844 | + var numVal = $(this).find('.merchant-range-number-input').val(); | |
| 1845 | + if (numVal !== undefined && numVal !== '') { | |
| 1846 | + $(this).find('.merchant-range-input').val(numVal); | |
| 1847 | + } | |
| 1848 | + }); | |
| 1849 | + | |
| 1850 | + // Swap the content of the layout-body (not the element itself) | |
| 1851 | + // to preserve jQuery UI accordion's internal panel reference. | |
| 1852 | + var $existingBody = $layout.find('.layout-body'); | |
| 1853 | + $existingBody.empty().append($clonedBody.children()); | |
| 1854 | + | |
| 1855 | + // Initialise widgets within the hydrated layout. | |
| 1856 | + if ($existingBody.find('.merchant-module-page-setting-field-upload').length) { | |
| 1857 | + initUploadField($existingBody.find('.merchant-module-page-setting-field-upload')); | |
| 1858 | + } | |
| 1859 | + if ($existingBody.find('.merchant-module-page-setting-field-select_ajax').length) { | |
| 1860 | + initSelectAjax($existingBody.find('.merchant-module-page-setting-field-select_ajax')); | |
| 1861 | + } | |
| 1862 | + | |
| 1863 | + // Trigger events to re-initialise fields_group, color pickers, conditions, etc. | |
| 1864 | + $(document).trigger('merchant-flexible-content-added', [$layout]); | |
| 1865 | + GroubField.init(); | |
| 1866 | + initMerchantRange(); | |
| 1867 | + | |
| 1868 | + // Init layout title binding. | |
| 1869 | + var $title = $layout.find('.layout-title[data-title-field]'); | |
| 1870 | + if ($title.length) { | |
| 1871 | + var titleFieldId = $title.attr('data-title-field'); | |
| 1872 | + var $titleInput = $layout.find('.layout-body .merchant-field-' + titleFieldId + ' input'); | |
| 1873 | + $titleInput.on('change keyup', function () { | |
| 1874 | + $title.text($(this).val()); | |
| 1875 | + }); | |
| 1876 | + } | |
| 1877 | + | |
| 1878 | + // Init status badge binding for hydrated row. | |
| 1879 | + var $badge = $layout.find('.layout-header .layout-status[data-status-field]'); | |
| 1880 | + if ($badge.length) { | |
| 1881 | + var statusFieldId = $badge.data('status-field'); | |
| 1882 | + var statusAdapter = this.resolveStatusField($layout, statusFieldId); | |
| 1883 | + if (statusAdapter) { | |
| 1884 | + var syncHydratedBadge = function syncHydratedBadge() { | |
| 1885 | + var val = statusAdapter.getValue(); | |
| 1886 | + var label = statusAdapter.getLabel(); | |
| 1887 | + $badge.removeClass(function (i, cls) { | |
| 1888 | + return (cls.match(/layout-status--\S+/g) || []).join(' '); | |
| 1889 | + }).addClass('layout-status--' + val).text(label.trim()); | |
| 1890 | + }; | |
| 1891 | + statusAdapter.onChange(syncHydratedBadge); | |
| 1892 | + syncHydratedBadge(); | |
| 1893 | + } | |
| 1894 | + } | |
| 1895 | + | |
| 1896 | + // Init discount max val. | |
| 1897 | + this.updateDiscountPercentMaxVal(); | |
| 1898 | + | |
| 1899 | + // Remove the deferred flag and clean up. | |
| 1900 | + $layout.removeAttr('data-deferred'); | |
| 1901 | + $layout.removeAttr('data-fields-json'); | |
| 1902 | + | |
| 1903 | + // Refresh accordion to re-sync panel references after DOM change. | |
| 1904 | + var $content = $control.find('.merchant-flexible-content'); | |
| 1905 | + if ($content.data('ui-accordion')) { | |
| 1906 | + $content.accordion('refresh'); | |
| 1907 | + } | |
| 1908 | + | |
| 1909 | + // Trigger condition checks for the newly hydrated panel. | |
| 1910 | + $(document).trigger('merchant-admin-check-fields'); | |
| 1911 | + $(document).trigger('merchant-admin-check-color-fields'); | |
| 1912 | + }, | |
| 1913 | + /** | |
| 1914 | + * Populate field values from deferred data into a cloned template body. | |
| 1915 | + * | |
| 1916 | + * @param {jQuery} $body The cloned .layout-body element. | |
| 1917 | + * @param {Object} data The parsed data-fields-json object. | |
| 1918 | + * @param {string} fieldId The flexible content field ID. | |
| 1919 | + * @param {number} rowIndex The row index for name attribute construction. | |
| 1920 | + */ | |
| 1921 | + populateFieldValues: function populateFieldValues($body, data, fieldId, rowIndex) { | |
| 1922 | + var values = data.values || {}; | |
| 1923 | + var selectOptions = data.select_options || {}; | |
| 1924 | + var productOptions = data.product_options || {}; | |
| 1925 | + var reviewOptions = data.review_options || {}; | |
| 1926 | + | |
| 1927 | + // Populate simple field values. | |
| 1928 | + $body.find('input, select, textarea').each(function () { | |
| 1929 | + var $el = $(this); | |
| 1930 | + var name = $el.attr('name') || ''; | |
| 1931 | + | |
| 1932 | + // Handle checkbox_multiple: name ends with [] (e.g. merchant[fc][0][show_pages][]). | |
| 1933 | + var arrayMatch = name.match(/\[([^\]]+)\]\[\]$/); | |
| 1934 | + if (arrayMatch) { | |
| 1935 | + var arrayKey = arrayMatch[1]; | |
| 1936 | + var arrVal = values[arrayKey]; | |
| 1937 | + if (Array.isArray(arrVal) && $el.is(':checkbox')) { | |
| 1938 | + $el.prop('checked', arrVal.indexOf($el.val()) !== -1); | |
| 1939 | + } | |
| 1940 | + return; | |
| 1941 | + } | |
| 1942 | + | |
| 1943 | + // Extract the field key from name="merchant[fieldId][rowIndex][fieldKey]". | |
| 1944 | + var match = name.match(/\[([^\]]+)\]$/); | |
| 1945 | + if (!match) { | |
| 1946 | + return; | |
| 1947 | + } | |
| 1948 | + var fieldKey = match[1]; | |
| 1949 | + var val = values[fieldKey]; | |
| 1950 | + if (val === undefined || val === null) { | |
| 1951 | + return; | |
| 1952 | + } | |
| 1953 | + if ($el.is(':checkbox') && !$el.is(':radio')) { | |
| 1954 | + $el.prop('checked', !!parseInt(val, 10)); | |
| 1955 | + } else if ($el.is(':radio')) { | |
| 1956 | + $el.prop('checked', $el.val() === String(val)); | |
| 1957 | + } else if ($el.is('select')) { | |
| 1958 | + $el.val(val); | |
| 1959 | + } else { | |
| 1960 | + $el.val(_typeof(val) === 'object' && val !== null ? JSON.stringify(val) : val); | |
| 1961 | + } | |
| 1962 | + }); | |
| 1963 | + | |
| 1964 | + // Populate select_ajax pre-selected options. | |
| 1965 | + $.each(selectOptions, function (selectFieldId, options) { | |
| 1966 | + var $select = $body.find('[data-id="' + selectFieldId + '"] select'); | |
| 1967 | + if (!$select.length) { | |
| 1968 | + return; | |
| 1969 | + } | |
| 1970 | + | |
| 1971 | + // Clear any existing options and add resolved ones. | |
| 1972 | + $select.empty(); | |
| 1973 | + $.each(options, function (_, opt) { | |
| 1974 | + $select.append($('<option>').val(opt.id).text(opt.text).prop('selected', true)); | |
| 1975 | + }); | |
| 1976 | + }); | |
| 1977 | + | |
| 1978 | + // Populate products_selector pre-selected products. | |
| 1979 | + $.each(productOptions, function (prodFieldId, products) { | |
| 1980 | + var $container = $body.find('[data-id="' + prodFieldId + '"] .merchant-products-search-container'); | |
| 1981 | + if (!$container.length) { | |
| 1982 | + return; | |
| 1983 | + } | |
| 1984 | + var $preview = $container.find('.merchant-selected-products-preview ul'); | |
| 1985 | + var ids = []; | |
| 1986 | + $preview.empty(); | |
| 1987 | + $.each(products, function (_, product) { | |
| 1988 | + ids.push(product.id); | |
| 1989 | + | |
| 1990 | + // Build <li> matching the structure from Merchant_Admin_Ajax::product_data_li(). | |
| 1991 | + var $li = $('<li>').addClass('product-item').attr('data-id', product.id).attr('data-name', product.title); | |
| 1992 | + if (product.image) { | |
| 1993 | + $li.append($('<span>').addClass('img').append($('<img>').attr('src', product.image).attr('alt', product.title).attr('width', 30).attr('height', 30))); | |
| 1994 | + } | |
| 1995 | + $li.append($('<span>').addClass('data').append($('<span>').addClass('name').text(product.title), $('<span>').addClass('info').html(product.price))); | |
| 1996 | + if (product.type || product.id) { | |
| 1997 | + var typeHtml = (product.type || '') + '<br>#' + product.id; | |
| 1998 | + var $typeSpan = $('<span>').addClass('type'); | |
| 1999 | + if (product.edit_url) { | |
| 2000 | + $typeSpan.append($('<a>').attr('href', product.edit_url).attr('target', '_blank').html(typeHtml)); | |
| 2001 | + } else { | |
| 2002 | + $typeSpan.html(typeHtml); | |
| 2003 | + } | |
| 2004 | + $li.append($typeSpan); | |
| 2005 | + } | |
| 2006 | + $li.append($('<span>').addClass('remove hint--left').attr('aria-label', 'Remove').html('×')); | |
| 2007 | + $preview.append($li); | |
| 2008 | + }); | |
| 2009 | + $container.find('.merchant-selected-products').val(ids.join(',')); | |
| 2010 | + }); | |
| 2011 | + | |
| 2012 | + // Populate reviews_selector pre-selected reviews. | |
| 2013 | + $.each(reviewOptions, function (reviewFieldId, reviews) { | |
| 2014 | + var $container = $body.find('[data-id="' + reviewFieldId + '"] .merchant-reviews-selector'); | |
| 2015 | + if (!$container.length) { | |
| 2016 | + return; | |
| 2017 | + } | |
| 2018 | + var $selectedList = $container.find('.selected-reviews .product-reviews'); | |
| 2019 | + var ids = []; | |
| 2020 | + $selectedList.empty(); | |
| 2021 | + $.each(reviews, function (_, review) { | |
| 2022 | + ids.push(review.id); | |
| 2023 | + var $reviewEl = $('<div>').addClass('product-review').attr('data-review-id', review.id); | |
| 2024 | + $reviewEl.append($('<span>').addClass('review-author').text(review.author)); | |
| 2025 | + $reviewEl.append($('<span>').addClass('review-content').text(review.content)); | |
| 2026 | + $reviewEl.append($('<span>').addClass('review-rating').text('★'.repeat(parseInt(review.rating) || 0))); | |
| 2027 | + $reviewEl.append($('<span>').addClass('review-product').text(review.product_name)); | |
| 2028 | + $reviewEl.append($('<span>').addClass('product-review-delete').html('×')); | |
| 2029 | + $reviewEl.append($('<span>').addClass('product-review-move').html('⋮⋮')); | |
| 2030 | + $selectedList.append($reviewEl); | |
| 2031 | + }); | |
| 2032 | + $container.find('.merchant-selected-reviews').val(ids.join(',')); | |
| 2033 | + }); | |
| 2034 | + | |
| 2035 | + // Handle fields_group and compound (e.g. hook_select) nested values. | |
| 2036 | + // Recursively walks the value tree to find and set inputs | |
| 2037 | + // at any nesting depth (e.g. fields_group → hook_select → hook_name). | |
| 2038 | + var _setNestedValues = function setNestedValues(obj, namePrefix) { | |
| 2039 | + $.each(obj, function (key, val) { | |
| 2040 | + if (_typeof(val) === 'object' && val !== null && !Array.isArray(val)) { | |
| 2041 | + // Recurse into nested objects. | |
| 2042 | + _setNestedValues(val, namePrefix + '[' + key + ']'); | |
| 2043 | + } else if (Array.isArray(val)) { | |
| 2044 | + // checkbox_multiple inside a nested object: check boxes whose value is in the array. | |
| 2045 | + $body.find('[name="' + namePrefix + '[' + key + '][]"]').each(function () { | |
| 2046 | + if ($(this).is(':checkbox')) { | |
| 2047 | + $(this).prop('checked', val.indexOf($(this).val()) !== -1); | |
| 2048 | + } | |
| 2049 | + }); | |
| 2050 | + } else { | |
| 2051 | + var $field = $body.find('[name="' + namePrefix + '[' + key + ']"]'); | |
| 2052 | + if (!$field.length) { | |
| 2053 | + return; | |
| 2054 | + } | |
| 2055 | + if ($field.is(':checkbox') && !$field.is(':radio')) { | |
| 2056 | + $field.prop('checked', !!parseInt(val, 10)); | |
| 2057 | + } else if ($field.is(':radio')) { | |
| 2058 | + $field.prop('checked', $field.val() === String(val)); | |
| 2059 | + } else { | |
| 2060 | + $field.val(val); | |
| 2061 | + } | |
| 2062 | + } | |
| 2063 | + }); | |
| 2064 | + }; | |
| 2065 | + $.each(values, function (key, val) { | |
| 2066 | + if (_typeof(val) === 'object' && val !== null && !Array.isArray(val)) { | |
| 2067 | + _setNestedValues(val, 'merchant[' + fieldId + '][' + rowIndex + '][' + key + ']'); | |
| 2068 | + } | |
| 2069 | + }); | |
| 2070 | + }, | |
| 351 | 2071 | refreshNumbers: function refreshNumbers($content) { |
| 352 | 2072 | $content.find('.layout').each(function (index) { |
| 353 | 2073 | var $count = $(this).find('.layout-count').text(); |
| 354 | 2074 | var $inputIndex = parseInt($count) - 1; |
| 355 | 2075 | $(this).find('.layout-count').text(index + 1); |
| 356 | - $(this).find('input, select').each(function () { | |
| 2076 | + $(this).find('input, select, textarea').each(function () { | |
| 357 | 2077 | if ($(this).attr('name')) { |
| 358 | 2078 | $(this).attr('name', $(this).attr('name').replace('[' + $inputIndex + ']', '[*refreshed*' + index + ']')); |
| 359 | 2079 | } |
| 360 | 2080 | }); |
| @@ -359,12 +2079,16 @@ | ||
| 359 | 2079 | } |
| 360 | 2080 | }); |
| 361 | 2081 | }); |
| 362 | 2082 | $content.find('.layout').each(function (index) { |
| 363 | - // We've added *refreshed* tp the attribute name in the prior loop as refreshing the numbers in the attribute can cause | |
| 364 | - // checked boxes to be unchecked due to similar attribute names during the change while sorting, within this loop we remove them | |
| 365 | - $(this).find('input, select').each(function () { | |
| 366 | - $(this).attr('name', $(this).attr('name').replace('*refreshed*', '')); | |
| 2083 | + $(this).find('input, select, textarea').each(function () { | |
| 2084 | + // We've added *refreshed* to the attribute name in the prior loop as refreshing the numbers in the attribute can cause | |
| 2085 | + // checked boxes to be unchecked due to similar attribute names during the change while sorting, within this loop we remove them | |
| 2086 | + var nameAttr = $(this).attr('name'); | |
| 2087 | + if (nameAttr) { | |
| 2088 | + // Check if name attribute exists | |
| 2089 | + $(this).attr('name', nameAttr.replace('*refreshed*', '')); | |
| 2090 | + } | |
| 367 | 2091 | }); |
| 368 | 2092 | }); |
| 369 | 2093 | $content.parent().find('input').trigger('change.merchant'); |
| 370 | 2094 | } |
| @@ -371,8 +2095,112 @@ | ||
| 371 | 2095 | }; |
| 372 | 2096 | |
| 373 | 2097 | // Initialize Flexible Content. |
| 374 | 2098 | FlexibleContentField.init(); |
| 2099 | + GroubField.init(); | |
| 2100 | + ReviewsSelector.init(); | |
| 2101 | + | |
| 2102 | + // Products selector. | |
| 2103 | + // Handle keyup event for the search input | |
| 2104 | + var debounceTimer; | |
| 2105 | + $(document).on('keyup', '.merchant-module-page-setting-field-products_selector .merchant-search-field', function () { | |
| 2106 | + clearTimeout(debounceTimer); | |
| 2107 | + var categories = []; | |
| 2108 | + var $excluded = $(this).closest('[data-id="excluded_products"]'); | |
| 2109 | + if ($excluded.length) { | |
| 2110 | + var $layout = $(this).closest('.layout'); | |
| 2111 | + var rules = $layout.find('.merchant-field-rules_to_apply select').val() || $layout.find('.merchant-field-rules_to_display select').val() || $layout.find('.merchant-field-display_rules select').val(); | |
| 2112 | + if (rules === 'categories' || rules === 'by_category') { | |
| 2113 | + categories = $layout.find('.merchant-field-category_slugs select').val() || $layout.find('.merchant-field-product_cats select').val(); | |
| 2114 | + } | |
| 2115 | + } | |
| 2116 | + var parent = $(this).closest('.merchant-products-search-container'); | |
| 2117 | + if ($(this).val() !== '') { | |
| 2118 | + parent.find('.merchant-searching').addClass('active'); | |
| 2119 | + var data = { | |
| 2120 | + action: 'merchant_admin_products_search', | |
| 2121 | + nonce: merchant_admin_options.ajaxnonce, | |
| 2122 | + keyword: $(this).val(), | |
| 2123 | + product_types: $(this).data('allowed-types'), | |
| 2124 | + ids: parent.find('.merchant-selected-products').val(), | |
| 2125 | + categories: categories | |
| 2126 | + }; | |
| 2127 | + debounceTimer = setTimeout(function () { | |
| 2128 | + $.post(merchant_admin_options.ajaxurl, data, function (response) { | |
| 2129 | + var results = parent.find('.merchant-selections-products-preview'); | |
| 2130 | + results.show(); | |
| 2131 | + results.html(response); | |
| 2132 | + parent.find('.merchant-searching').removeClass('active'); | |
| 2133 | + }); | |
| 2134 | + }, 250); | |
| 2135 | + } else { | |
| 2136 | + parent.find('.merchant-selections-products-preview').html('').hide(); | |
| 2137 | + } | |
| 2138 | + }); | |
| 2139 | + | |
| 2140 | + // Products selector: dismiss search results on click outside or Escape. | |
| 2141 | + $(document).on('click touch', function (e) { | |
| 2142 | + if (!$(e.target).closest('.merchant-products-search-container').length) { | |
| 2143 | + $('.merchant-selections-products-preview').html('').hide(); | |
| 2144 | + $('.merchant-search-field').val(''); | |
| 2145 | + } | |
| 2146 | + }); | |
| 2147 | + $(document).on('keydown', '.merchant-search-field', function (e) { | |
| 2148 | + if (e.key === 'Escape') { | |
| 2149 | + var parent = $(this).closest('.merchant-products-search-container'); | |
| 2150 | + parent.find('.merchant-selections-products-preview').html('').hide(); | |
| 2151 | + $(this).val('').blur(); | |
| 2152 | + } | |
| 2153 | + }); | |
| 2154 | + | |
| 2155 | + // Products selector. | |
| 2156 | + // Handle click/touch event for the search results | |
| 2157 | + $(document).on('click touch', '.merchant-module-page-setting-field-products_selector .merchant-selections-products-preview li', function () { | |
| 2158 | + var parent = $(this).closest('.merchant-products-search-container'), | |
| 2159 | + valueField = parent.find('.merchant-selected-products'), | |
| 2160 | + oldValue = valueField.val(), | |
| 2161 | + multiple = parent.data('multiple') === 'multiple'; | |
| 2162 | + if (parent.find('.merchant-selected-products-preview ul li').length > 0 && !multiple) { | |
| 2163 | + // replace the first item | |
| 2164 | + parent.find('.merchant-selected-products-preview ul li').remove(); | |
| 2165 | + valueField.val('').change(); | |
| 2166 | + } | |
| 2167 | + $(this).children('.remove').attr('aria-label', 'Remove').html('×'); | |
| 2168 | + parent.find('.merchant-selected-products-preview ul').append($(this)); | |
| 2169 | + parent.find('.merchant-selections-products-preview').html('').hide(); | |
| 2170 | + parent.find('.merchant-search-field').val('').change(); | |
| 2171 | + if (oldValue === '') { | |
| 2172 | + valueField.val($(this).data('id')).change(); | |
| 2173 | + } else { | |
| 2174 | + if (!multiple) { | |
| 2175 | + valueField.val($(this).data('id')).change(); | |
| 2176 | + } else { | |
| 2177 | + var newValue = oldValue.split(','); | |
| 2178 | + newValue.push($(this).data('id')); | |
| 2179 | + valueField.val(newValue.join(',')).change(); | |
| 2180 | + } | |
| 2181 | + } | |
| 2182 | + }); | |
| 2183 | + | |
| 2184 | + // Products selector. | |
| 2185 | + // Handle click/touch event for the remove button. | |
| 2186 | + $(document).on('click touch', '.merchant-selected-products-preview .remove', function () { | |
| 2187 | + var removeButton = $(this); | |
| 2188 | + var parent = removeButton.closest('.merchant-products-search-container'), | |
| 2189 | + valueField = parent.find('.merchant-selected-products'), | |
| 2190 | + id = removeButton.parent().data('id'); | |
| 2191 | + removeButton.parent().remove(); | |
| 2192 | + var currentValue = valueField.val().split(','); | |
| 2193 | + if (currentValue.length > 0) { | |
| 2194 | + for (var key in currentValue) { | |
| 2195 | + if (parseInt(currentValue[key]) === parseInt(id)) { | |
| 2196 | + currentValue.splice(key, 1); | |
| 2197 | + } | |
| 2198 | + } | |
| 2199 | + } | |
| 2200 | + valueField.val(currentValue.join(',')).change(); | |
| 2201 | + valueField.trigger('change.merchant'); | |
| 2202 | + }); | |
| 375 | 2203 | $(document).on('merchant-admin-check-fields merchant-flexible-content-added', function () { |
| 376 | 2204 | $('.merchant-module-page-setting-field').each(function () { |
| 377 | 2205 | var $field = $(this); |
| 378 | 2206 | if ($field.data('condition') && $field.data('condition').length) { |
| @@ -385,9 +2213,10 @@ | ||
| 385 | 2213 | var passed = false; |
| 386 | 2214 | switch (condition[1]) { |
| 387 | 2215 | case '==': |
| 388 | 2216 | if ($target.attr('type') === 'radio' || $target.attr('type') === 'checkbox') { |
| 389 | - if ($target.is(':checked') && $target.val() == condition[2]) { | |
| 2217 | + var checked = $target.parent().find('input:checked'); | |
| 2218 | + if (checked.length && checked.val() === condition[2]) { | |
| 390 | 2219 | passed = true; |
| 391 | 2220 | } |
| 392 | 2221 | } |
| 393 | 2222 | if ($target.is('select') && $target.val() == condition[2]) { |
| @@ -395,9 +2224,10 @@ | ||
| 395 | 2224 | } |
| 396 | 2225 | break; |
| 397 | 2226 | case 'any': |
| 398 | 2227 | if ($target.attr('type') === 'radio' || $target.attr('type') === 'checkbox') { |
| 399 | - if ($target.is(':checked') && condition[2].split('|').includes($target.val())) { | |
| 2228 | + var _checked = $target.parent().find('input:checked'); | |
| 2229 | + if (_checked.length && condition[2].split('|').includes(_checked.val())) { | |
| 400 | 2230 | passed = true; |
| 401 | 2231 | } |
| 402 | 2232 | } |
| 403 | 2233 | if ($target.is('select') && condition[2].split('|').includes($target.val())) { |
| @@ -413,8 +2243,22 @@ | ||
| 413 | 2243 | } |
| 414 | 2244 | } |
| 415 | 2245 | }); |
| 416 | 2246 | }).trigger('merchant.change'); |
| 2247 | + $(document).on('merchant-admin-check-fields merchant-flexible-content-added change keyup', function () { | |
| 2248 | + $(document).find('.merchant-module-page-setting-field').each(function () { | |
| 2249 | + var $field = $(this); | |
| 2250 | + if ($field.data('conditions')) { | |
| 2251 | + var conditions = $field.data('conditions'), | |
| 2252 | + passed = evaluateConditions(conditions, $field); | |
| 2253 | + if (passed) { | |
| 2254 | + $field.removeClass('merchant-hide').addClass('merchant-show'); | |
| 2255 | + } else { | |
| 2256 | + $field.removeClass('merchant-show').addClass('merchant-hide'); | |
| 2257 | + } | |
| 2258 | + } | |
| 2259 | + }); | |
| 2260 | + }).trigger('merchant.change'); | |
| 417 | 2261 | $(document).on('change', '.merchant-module-page-setting-field', function () { |
| 418 | 2262 | $(document).trigger('merchant-admin-check-fields'); |
| 419 | 2263 | }).trigger('merchant.change'); |
| 420 | 2264 | $(document).trigger('merchant-admin-check-fields'); |
| @@ -467,8 +2311,9 @@ | ||
| 467 | 2311 | }); |
| 468 | 2312 | if ($input.val() !== colorCode) { |
| 469 | 2313 | $input.val(colorCode).trigger('change.merchant'); |
| 470 | 2314 | } |
| 2315 | + $(document).trigger('merchant-color-picker-updated', [colorCode, $input]); | |
| 471 | 2316 | }); |
| 472 | 2317 | pickr.on('clear', function () { |
| 473 | 2318 | var defaultColor = $picker.data('default-color'); |
| 474 | 2319 | if (defaultColor) { |
| @@ -545,11 +2390,13 @@ | ||
| 545 | 2390 | }); |
| 546 | 2391 | }, |
| 547 | 2392 | ajaxResponseHandler: function ajaxResponseHandler(response, $this, $success_message, $create_message) { |
| 548 | 2393 | if ('success' === response.status) { |
| 549 | - var href = $success_message.find('a').attr('href'), | |
| 550 | - newhref = href.replace('?post=&', '?post=' + response.page_id + '&'); | |
| 551 | - $success_message.find('a').attr('href', newhref); | |
| 2394 | + var $editLink = $success_message.find('a').first(), | |
| 2395 | + href = $editLink.attr('href'); | |
| 2396 | + if (href) { | |
| 2397 | + $editLink.attr('href', href.replace('?post=&', '?post=' + response.page_id + '&')); | |
| 2398 | + } | |
| 552 | 2399 | $success_message.css('display', 'block'); |
| 553 | 2400 | $create_message.remove(); |
| 554 | 2401 | $this.remove(); |
| 555 | 2402 | } |
| @@ -654,14 +2501,17 @@ | ||
| 654 | 2501 | wpMediaFrame.on('select', function () { |
| 655 | 2502 | $wrapper.empty(); |
| 656 | 2503 | var item = wpMediaFrame.state().get('selection').first().attributes; |
| 657 | 2504 | var thumb = item.sizes && item.sizes.thumbnail && item.sizes.thumbnail.url ? item.sizes.thumbnail.url : item.url; |
| 658 | - $wrapper.append('<div class="merchant-upload-image"><i class="merchant-upload-remove dashicons dashicons-no-alt"></i><img src="' + thumb + '" /></div>'); | |
| 2505 | + var sizes = item.sizes ? JSON.stringify(item.sizes) : ''; | |
| 2506 | + $wrapper.append('<div class="merchant-upload-image" data-sizes=\'' + sizes + '\'><i class="merchant-upload-remove dashicons dashicons-no-alt"></i><img src="' + thumb + '" /></div>'); | |
| 659 | 2507 | $input.val(item.id).trigger('change'); |
| 2508 | + $this.find('.merchant-upload-button-drag-drop').hide(); | |
| 660 | 2509 | $this.find('.merchant-upload-remove').on('click', function (e) { |
| 661 | 2510 | e.preventDefault(); |
| 662 | 2511 | $(this).parent().remove(); |
| 663 | 2512 | $input.val('').trigger('change'); |
| 2513 | + $this.find('.merchant-upload-button-drag-drop').show(); | |
| 664 | 2514 | }); |
| 665 | 2515 | }); |
| 666 | 2516 | }); |
| 667 | 2517 | }; |
| @@ -667,16 +2517,39 @@ | ||
| 667 | 2517 | }; |
| 668 | 2518 | $('.merchant-module-page-setting-field-upload:not(.template)').each(function () { |
| 669 | 2519 | initUploadField($(this)); |
| 670 | 2520 | }); |
| 671 | - var initSelectAjax = function initSelectAjax($selectAjax) { | |
| 2521 | + | |
| 2522 | + // Drag & Drag | |
| 2523 | + var events = ['dragenter', 'dragover', 'dragleave', 'drop']; | |
| 2524 | + jQuery.each(events, function (index, eventName) { | |
| 2525 | + $(document).on(eventName, '.merchant-upload-button-drag-drop', function (e) { | |
| 2526 | + e.preventDefault(); | |
| 2527 | + e.stopPropagation(); | |
| 2528 | + }); | |
| 2529 | + }); | |
| 2530 | + $(document).on('dragenter', '.merchant-upload-button-drag-drop', function (e) { | |
| 2531 | + $(this).closest('.merchant-module-page-setting-field-upload').find('.merchant-upload-button').click(); | |
| 2532 | + }); | |
| 2533 | + | |
| 2534 | + // Callers pass a collection, and reading the source off a collection would give | |
| 2535 | + // every select the first field's source, so each field is set up on its own. | |
| 2536 | + var initSelectAjax = function initSelectAjax($selectAjaxFields) { | |
| 2537 | + $selectAjaxFields.each(function () { | |
| 2538 | + initSelectAjaxField($(this)); | |
| 2539 | + }); | |
| 2540 | + }; | |
| 2541 | + var initSelectAjaxField = function initSelectAjaxField($selectAjax) { | |
| 672 | 2542 | var $select = $selectAjax.find('select'); |
| 673 | 2543 | var $source = $select.data('source'); |
| 674 | 2544 | var $config = window.merchant_admin_options; |
| 675 | 2545 | var $object = { |
| 676 | - width: '100%' | |
| 2546 | + width: '100%', | |
| 2547 | + templateSelection: function templateSelection(category) { | |
| 2548 | + return category.text.replace(/ -*\s*/g, '').trim(); | |
| 2549 | + } | |
| 677 | 2550 | }; |
| 678 | - if ($source === 'post' || $source === 'product') { | |
| 2551 | + if ($source === 'post' || $source === 'product' || $source === 'user') { | |
| 679 | 2552 | $object.minimumInputLength = 1; |
| 680 | 2553 | $object.ajax = { |
| 681 | 2554 | url: $config.ajaxurl, |
| 682 | 2555 | dataType: 'json', |
| @@ -770,8 +2643,26 @@ | ||
| 770 | 2643 | } |
| 771 | 2644 | }); |
| 772 | 2645 | } |
| 773 | 2646 | }); |
| 2647 | + | |
| 2648 | + // Hide fixed changelog items | |
| 2649 | + var merchantNotificationsContent = $('.merchant-notification-content'); | |
| 2650 | + merchantNotificationsContent.each(function () { | |
| 2651 | + var notificationContentItem = $(this); | |
| 2652 | + // search for all spans that contains class "changelog-fixed" and then go to the parent li of that span and add class hidden | |
| 2653 | + var fixedItems = notificationContentItem.find('span.changelog-fixed'); | |
| 2654 | + fixedItems.each(function () { | |
| 2655 | + var fixedItem = $(this); | |
| 2656 | + fixedItem.closest('li').remove(); | |
| 2657 | + }); | |
| 2658 | + // now check if all li inside this notificationContentItem are hidden, so we need to look for the parent .merchant-notification and remove it | |
| 2659 | + var allItems = notificationContentItem.find('li'); | |
| 2660 | + var hiddenItems = notificationContentItem.find('li.hidden'); | |
| 2661 | + if (allItems.length === hiddenItems.length) { | |
| 2662 | + notificationContentItem.closest('.merchant-notification').remove(); | |
| 2663 | + } | |
| 2664 | + }); | |
| 774 | 2665 | $(window).on('scroll', function () { |
| 775 | 2666 | if (window.pageYOffset > 60) { |
| 776 | 2667 | $notificationsSidebar.addClass('closing'); |
| 777 | 2668 | setTimeout(function () { |
| @@ -824,6 +2715,252 @@ | ||
| 824 | 2715 | $moduleAlert.removeClass('merchant-show'); |
| 825 | 2716 | $(document).off('click.merchant-alert-close'); |
| 826 | 2717 | }); |
| 827 | 2718 | } |
| 2719 | + | |
| 2720 | + // AI Prompt Examples Modal | |
| 2721 | + var $aiPromptsModal = $('#merchant-ai-prompts-modal'); | |
| 2722 | + if ($aiPromptsModal.length) { | |
| 2723 | + var $aiPromptsList = $aiPromptsModal.find('.merchant-ai-prompts-modal-list'); | |
| 2724 | + var copyHintText = window.merchant.ai_prompt_copy_hint || 'Click to copy'; | |
| 2725 | + var copiedLabelText = window.merchant.ai_prompt_copied_label || 'Copied!'; | |
| 2726 | + var copyIconSvg = '<svg width="14" height="14" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="5" y="5" width="8" height="8" rx="1.5" stroke="currentColor" stroke-width="1.3"/><path d="M3 10V3.5C3 2.67157 3.67157 2 4.5 2H10.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>'; | |
| 2727 | + var checkIconSvg = '<svg width="14" height="14" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M13.5 4.5L6 12L2.5 8.5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>'; | |
| 2728 | + var copyTextToClipboard = function copyTextToClipboard(text, onCopied) { | |
| 2729 | + if (navigator.clipboard && navigator.clipboard.writeText) { | |
| 2730 | + navigator.clipboard.writeText(text).then(onCopied); | |
| 2731 | + return; | |
| 2732 | + } | |
| 2733 | + var $temp = $('<textarea readonly></textarea>').val(text).css({ | |
| 2734 | + position: 'fixed', | |
| 2735 | + top: '-1000px' | |
| 2736 | + }); | |
| 2737 | + $('body').append($temp); | |
| 2738 | + $temp[0].select(); | |
| 2739 | + document.execCommand('copy'); | |
| 2740 | + $temp.remove(); | |
| 2741 | + onCopied(); | |
| 2742 | + }; | |
| 2743 | + $(document).on('click', '.merchant-ai-badge', function (e) { | |
| 2744 | + e.preventDefault(); | |
| 2745 | + e.stopPropagation(); | |
| 2746 | + var prompts = $(this).data('aiPrompts'); | |
| 2747 | + $aiPromptsModal.find('.merchant-ai-prompts-modal-title-text').text($(this).data('aiTitle') || ''); | |
| 2748 | + $aiPromptsModal.find('.merchant-ai-prompts-modal-blurb').text($(this).data('aiBlurb') || ''); | |
| 2749 | + $aiPromptsList.empty(); | |
| 2750 | + if (Array.isArray(prompts)) { | |
| 2751 | + prompts.forEach(function (prompt) { | |
| 2752 | + var $row = $('<button type="button" class="merchant-ai-prompt-row"></button>').attr('data-prompt', prompt); | |
| 2753 | + $row.append($('<span class="merchant-ai-prompt-text"></span>').text(prompt)); | |
| 2754 | + $row.append($('<span class="merchant-ai-prompt-icon"></span>').html(copyIconSvg)); | |
| 2755 | + $row.attr('aria-label', copyHintText); | |
| 2756 | + $aiPromptsList.append($row); | |
| 2757 | + }); | |
| 2758 | + } | |
| 2759 | + $aiPromptsModal.addClass('merchant-show'); | |
| 2760 | + var $badgeIcon = $aiPromptsModal.find('.merchant-ai-prompts-modal-badge svg'); | |
| 2761 | + $badgeIcon.removeClass('merchant-ai-badge-spin'); | |
| 2762 | + void $badgeIcon[0].offsetWidth; | |
| 2763 | + $badgeIcon.addClass('merchant-ai-badge-spin'); | |
| 2764 | + }); | |
| 2765 | + $(document).on('keydown', '.merchant-ai-badge', function (e) { | |
| 2766 | + if (e.key === 'Enter' || e.key === ' ') { | |
| 2767 | + e.preventDefault(); | |
| 2768 | + $(this).trigger('click'); | |
| 2769 | + } | |
| 2770 | + }); | |
| 2771 | + $aiPromptsList.on('click', '.merchant-ai-prompt-row', function () { | |
| 2772 | + var $row = $(this); | |
| 2773 | + if ($row.hasClass('is-copied')) { | |
| 2774 | + return; | |
| 2775 | + } | |
| 2776 | + copyTextToClipboard($row.attr('data-prompt'), function () { | |
| 2777 | + $row.addClass('is-copied').attr('aria-label', copiedLabelText); | |
| 2778 | + $row.find('.merchant-ai-prompt-icon').html(checkIconSvg); | |
| 2779 | + setTimeout(function () { | |
| 2780 | + $row.removeClass('is-copied').attr('aria-label', copyHintText); | |
| 2781 | + $row.find('.merchant-ai-prompt-icon').html(copyIconSvg); | |
| 2782 | + }, 1600); | |
| 2783 | + }); | |
| 2784 | + }); | |
| 2785 | + var closeAiPromptsModal = function closeAiPromptsModal() { | |
| 2786 | + $aiPromptsModal.removeClass('merchant-show'); | |
| 2787 | + $aiPromptsModal.find('.merchant-ai-prompts-modal-badge svg').removeClass('merchant-ai-badge-spin'); | |
| 2788 | + }; | |
| 2789 | + $(document).on('click', '.merchant-ai-prompts-modal-close', function () { | |
| 2790 | + closeAiPromptsModal(); | |
| 2791 | + }); | |
| 2792 | + $aiPromptsModal.on('click', function (e) { | |
| 2793 | + if ($(e.target).is($aiPromptsModal)) { | |
| 2794 | + closeAiPromptsModal(); | |
| 2795 | + } | |
| 2796 | + }); | |
| 2797 | + $(document).on('keydown', function (e) { | |
| 2798 | + if (e.key === 'Escape' && $aiPromptsModal.hasClass('merchant-show')) { | |
| 2799 | + closeAiPromptsModal(); | |
| 2800 | + } | |
| 2801 | + }); | |
| 2802 | + } | |
| 828 | 2803 | }); |
| 829 | -})(jQuery, window, document); | |
| 2804 | + | |
| 2805 | + /** | |
| 2806 | + * Check if a string is numeric. | |
| 2807 | + * | |
| 2808 | + * @param str the string to check | |
| 2809 | + * | |
| 2810 | + * @returns {boolean} true if numeric, false otherwise | |
| 2811 | + */ | |
| 2812 | + function isNumeric(str) { | |
| 2813 | + if (typeof str != "string") return false; // we only process strings! | |
| 2814 | + return !isNaN(str) && | |
| 2815 | + // use type coercion to parse the _entirety_ of the string (`parseFloat` alone does not do this)... | |
| 2816 | + !isNaN(parseFloat(str)); // ...and ensure strings of whitespace fail | |
| 2817 | + } | |
| 2818 | + | |
| 2819 | + /** | |
| 2820 | + * Evaluate conditional fields. | |
| 2821 | + * | |
| 2822 | + * @param conditions the array of conditions | |
| 2823 | + * @param field the field that is being evaluated | |
| 2824 | + * | |
| 2825 | + * @returns {boolean} | |
| 2826 | + */ | |
| 2827 | + function evaluateConditions(conditions) { | |
| 2828 | + var field = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; | |
| 2829 | + var passed = false; | |
| 2830 | + if ('relation' in conditions) { | |
| 2831 | + //loop through terms | |
| 2832 | + var relation = conditions.relation.toUpperCase(); | |
| 2833 | + // lowercase relation | |
| 2834 | + if (relation === 'OR') { | |
| 2835 | + for (var i = 0; i < conditions.terms.length; i++) { | |
| 2836 | + var term = conditions.terms[i]; | |
| 2837 | + passed = evaluateConditions(term, field); | |
| 2838 | + if (passed) { | |
| 2839 | + return true; | |
| 2840 | + } | |
| 2841 | + } | |
| 2842 | + } else if (relation === 'AND') { | |
| 2843 | + var n = 0; | |
| 2844 | + for (var _i = 0; _i < conditions.terms.length; _i++) { | |
| 2845 | + // check if inner terms are passed | |
| 2846 | + var _term = conditions.terms[_i]; | |
| 2847 | + if (evaluateConditions(_term, field)) { | |
| 2848 | + n++; | |
| 2849 | + } | |
| 2850 | + } | |
| 2851 | + if (n === conditions.terms.length) { | |
| 2852 | + passed = true; | |
| 2853 | + } | |
| 2854 | + } | |
| 2855 | + } else { | |
| 2856 | + var condition = ''; | |
| 2857 | + if ('terms' in conditions) { | |
| 2858 | + condition = conditions.terms[0]; | |
| 2859 | + } else { | |
| 2860 | + condition = conditions; | |
| 2861 | + } | |
| 2862 | + var $target = $('input[name="merchant[' + condition.field + ']"],select[name="merchant[' + condition.field + ']"]'); | |
| 2863 | + if (!$target.length) { | |
| 2864 | + // check if inside flexible content | |
| 2865 | + var flexibleContentParent = field.closest('.layout-body'); | |
| 2866 | + if (flexibleContentParent.length > 0) { | |
| 2867 | + $target = flexibleContentParent.find('.merchant-field-' + condition.field).find('input, select'); | |
| 2868 | + } | |
| 2869 | + } | |
| 2870 | + if (!$target.length) { | |
| 2871 | + // Maybe the field is a multiple field | |
| 2872 | + $target = $('input[name="merchant[' + condition.field + '][]"],select[name="merchant[' + condition.field + '][]"]'); | |
| 2873 | + } | |
| 2874 | + if (!$target.length) { | |
| 2875 | + // Maybe the field is inside fields group | |
| 2876 | + $target = $('.merchant-group-fields-container').find('.merchant-field-' + condition.field + ' input[name*="' + condition.field + '"],.merchant-field-' + condition.field + ' select[name*="' + condition.field + '"]'); | |
| 2877 | + } | |
| 2878 | + var value = $target.val(); | |
| 2879 | + if ($target.attr('type') === 'checkbox') { | |
| 2880 | + value = $target.is(':checked'); | |
| 2881 | + } | |
| 2882 | + if ($target.attr('type') === 'radio') { | |
| 2883 | + value = $target.filter(':checked').val(); | |
| 2884 | + } | |
| 2885 | + | |
| 2886 | + // check if the field is multiple checkbox | |
| 2887 | + if ($target.attr('type') === 'checkbox' && $target.length > 1) { | |
| 2888 | + value = []; | |
| 2889 | + $target.each(function () { | |
| 2890 | + if ($(this).is(':checked')) { | |
| 2891 | + value.push($(this).val()); | |
| 2892 | + } | |
| 2893 | + }); | |
| 2894 | + } | |
| 2895 | + | |
| 2896 | + // cast value as string if numeric | |
| 2897 | + if (isNumeric(value)) { | |
| 2898 | + value = Number(value); | |
| 2899 | + } | |
| 2900 | + | |
| 2901 | + // check if is array condition.value | |
| 2902 | + if (Array.isArray(condition.value)) { | |
| 2903 | + condition.value = condition.value.map(function (item) { | |
| 2904 | + if (isNumeric(item)) { | |
| 2905 | + return Number(item); | |
| 2906 | + } | |
| 2907 | + return item; | |
| 2908 | + }); | |
| 2909 | + } | |
| 2910 | + if (condition.operator === '===' && value === condition.value) { | |
| 2911 | + passed = true; | |
| 2912 | + } else if (condition.operator === '!==' && value !== condition.value) { | |
| 2913 | + passed = true; | |
| 2914 | + } else if (condition.operator === '>' && value > condition.value) { | |
| 2915 | + passed = true; | |
| 2916 | + } else if (condition.operator === '<' && value < condition.value) { | |
| 2917 | + passed = true; | |
| 2918 | + } else if (condition.operator === '>=' && value >= condition.value) { | |
| 2919 | + passed = true; | |
| 2920 | + } else if (condition.operator === '<=' && value <= condition.value) { | |
| 2921 | + passed = true; | |
| 2922 | + } else if (condition.operator === 'in' && condition.value.includes(value)) { | |
| 2923 | + passed = true; | |
| 2924 | + } else if (condition.operator === '!in' && !condition.value.includes(value)) { | |
| 2925 | + passed = true; | |
| 2926 | + } else if (condition.operator === 'contains' && Array.isArray(value) && value.includes(condition.value)) { | |
| 2927 | + passed = true; | |
| 2928 | + } else if (condition.operator === '!contains' && Array.isArray(value) && !value.includes(condition.value)) { | |
| 2929 | + passed = true; | |
| 2930 | + } | |
| 2931 | + } | |
| 2932 | + return passed; | |
| 2933 | + } | |
| 2934 | +})(jQuery, window, document); | |
| 2935 | + | |
| 2936 | +// Extend jQuery to add getPath func to get accurate dynamic selector to an element. | |
| 2937 | +jQuery.fn.extend({ | |
| 2938 | + getPath: function getPath() { | |
| 2939 | + var pathes = []; | |
| 2940 | + this.each(function (index, element) { | |
| 2941 | + var path, | |
| 2942 | + $node = jQuery(element); | |
| 2943 | + while ($node.length) { | |
| 2944 | + var realNode = $node.get(0), | |
| 2945 | + name = realNode.localName; | |
| 2946 | + if (!name) { | |
| 2947 | + break; | |
| 2948 | + } | |
| 2949 | + name = name.toLowerCase(); | |
| 2950 | + var parent = $node.parent(); | |
| 2951 | + var sameTagSiblings = parent.children(name); | |
| 2952 | + if (sameTagSiblings.length > 1) { | |
| 2953 | + var allSiblings = parent.children(); | |
| 2954 | + var index = allSiblings.index(realNode) + 1; | |
| 2955 | + if (index > 0) { | |
| 2956 | + name += ':nth-child(' + index + ')'; | |
| 2957 | + } | |
| 2958 | + } | |
| 2959 | + path = name + (path ? ' > ' + path : ''); | |
| 2960 | + $node = parent; | |
| 2961 | + } | |
| 2962 | + pathes.push(path); | |
| 2963 | + }); | |
| 2964 | + return pathes.join(','); | |
| 2965 | + } | |
| 2966 | +}); | |