diff --git a/ang/crmMosaico/BlockDesign.html b/ang/crmMosaico/BlockDesign.html index f8b12ab649..d495f4fb62 100644 --- a/ang/crmMosaico/BlockDesign.html +++ b/ang/crmMosaico/BlockDesign.html @@ -1,20 +1,20 @@
- +
+ crm-mosaico-template-item="{state:'selected', title: ts('My Design'), subtitle: mosaicoCtrl.getTemplate().type, img: mosaicoCtrl.getTemplate().thumbnail}" + on-item-click="mosaicoCtrl.edit()" + on-item-reset="mosaicoCtrl.reset()" + ng-show="mosaicoCtrl.hasSelection()">
-
+
+ on-item-click="mosaicoCtrl.select(template)">
diff --git a/ang/crmMosaico/BlockDesign.js b/ang/crmMosaico/BlockDesign.js index 703d95c131..de5c90d4b4 100644 --- a/ang/crmMosaico/BlockDesign.js +++ b/ang/crmMosaico/BlockDesign.js @@ -1,21 +1,151 @@ (function(angular, $, _) { - angular.module('crmMosaico').directive('crmMosaicoBlockDesign', function($q, crmUiHelp) { + angular.module('crmMosaico').directive('crmMosaicoBlockDesign', function($q, crmUiHelp, dialogService, crmMosaicoTemplates, crmStatus, CrmMosaicoIframe, $timeout) { + return { scope: { - crmMosaicoCtrl: '@', - crmMailing: '@' + // crmMosaicoCtrl: '@', + crmMailing: '@', + crmMailingAttachments: '@', + crmMailingVariant: '@', }, templateUrl: '~/crmMosaico/BlockDesign.html', link: function (scope, elm, attr) { scope.$parent.$watch(attr.crmMailing, function(newValue){ scope.mailing = newValue; }); - scope.$parent.$watch(attr.crmMosaicoCtrl, function(newValue){ - scope.mosaicoCtrl = newValue; + scope.$parent.$watch(attr.crmMailingAttachments, function(newValue){ + scope.attachments = newValue; + }); + scope.$parent.$watch(attr.crmMailingVariant, function(newValue){ + scope.variantId = newValue; }); scope.crmMailingConst = CRM.crmMailing; scope.ts = CRM.ts(null); scope.hs = crmUiHelp({file: 'CRM/Mailing/MailingUI'}); + + function _object(prop) { + const mailing = scope.mailing; + mailing.template_options = mailing.template_options || {}; + + if (scope.variantId !== null) return mailing.template_options.variants[scope.variantId]; + return prop.match(/^mosaico/) ? mailing.template_options : mailing; + } + function getProp(prop) { return _object(prop)[prop]; } + function setProp(prop, value) { _object(prop)[prop] = value; } + function deleteProp(prop) { delete _object(prop)[prop]; } + + const $scope = scope; + var crmMosaicoIframe = null, activeDialogs = {}; + $scope.mosaicoCtrl = { + templates: [], + // Fill a given "mailing" which the chosen "template". + select: function(template) { + var promise = crmMosaicoTemplates.getFull(template).then(function(tplCtnt){ + setProp('mosaicoTemplate', template.id); + setProp('mosaicoMetadata', tplCtnt.metadata); + setProp('mosaicoContent', tplCtnt.content); + setProp('body_html', tplCtnt.html); + $scope.mosaicoCtrl.edit(); + }); + return crmStatus({start: ts('Loading...'), success: null}, promise); + }, + hasSelection: function() { + return !!getProp('mosaicoTemplate'); + }, + hasMarkup: function() { + return !!getProp('body_html'); + }, + // Figure out which "template" was previously used with a "mailing." + getTemplate: function() { + const mailing = scope.mailing; + if (!mailing || !getProp('mosaicoTemplate')) { + return null; + } + var matches = _.where($scope.mosaicoCtrl.templates, { + id: getProp('mosaicoTemplate') + }); + return matches.length > 0 ? matches[0] : null; + }, + // Reset all Mosaico data in a "mailing'. + reset: function() { + if (crmMosaicoIframe) crmMosaicoIframe.destroy(); + crmMosaicoIframe = null; + deleteProp('mosaicoTemplate'); + deleteProp('mosaicoMetadata'); + deleteProp('mosaicoContent'); + setProp('body_html', ''); + }, + // Edit a mailing in Mosaico. + edit: function() { + if (crmMosaicoIframe) { + crmMosaicoIframe.show(); + return; + } + + function syncModel(viewModel) { + setProp('body_html', viewModel.exportHTML()); + // Mosaico exports JSON. Keep their original encoding... or else the loader throws an error. + setProp('mosaicoMetadata', viewModel.exportMetadata()); + setProp('mosaicoContent', viewModel.exportJSON()); + } + + crmMosaicoIframe = new CrmMosaicoIframe({ + model: { + template: $scope.mosaicoCtrl.getTemplate().path, + metadata: getProp('mosaicoMetadata'), + content: getProp('mosaicoContent') + }, + actions: { + sync: function(ko, viewModel) { + syncModel(viewModel); + }, + close: function(ko, viewModel) { + viewModel.metadata.changed = Date.now(); + syncModel(viewModel); + // TODO: When autosave is better integrated, remove this. + $timeout(function(){ + $scope.$parent.$apply(attr.onSave); + }, 100); + crmMosaicoIframe.hide('crmMosaicoEditorDialog'); + }, + test: function(ko, viewModel) { + syncModel(viewModel); + + var model = {mailing: $scope.mailing, attachments: $scope.attachments, variantId: $scope.variantId}; + var options = CRM.utils.adjustDialogDefaults(angular.extend( + {autoOpen: false, title: ts('Preview / Test'), width: 550}, + options + )); + activeDialogs.crmMosaicoPreviewDialog = 1; + var pr = dialogService.open('crmMosaicoPreviewDialog', '~/crmMosaico/PreviewDialogCtrl.html', model, options) + .finally(function(){ delete activeDialogs.crmMosaicoPreviewDialog; }); + return pr; + } + } + }); + + return crmStatus({start: ts('Loading...'), success: null}, crmMosaicoIframe.open()); + } + }; + + crmMosaicoTemplates.whenLoaded().then(function(){ + $scope.mosaicoCtrl.templates = crmMosaicoTemplates.getAll(); + $scope.mosaicoCtrl.categoryFilters = _.transform(crmMosaicoTemplates.getCategories(), function(filters, category) { + filters.push({id: filters.length, text: category.label, filter: {category_id: category.value}}); + }, [{id: 0, text: ts('Base Template'), filter: {isBase: true}}]); + }); + + $scope.$on("$destroy", function() { + angular.forEach(activeDialogs, function(v,name){ + dialogService.cancel(name); + }); + if (crmMosaicoIframe) { + crmMosaicoIframe.destroy(); + crmMosaicoIframe = null; + } + }); + + } }; }); diff --git a/ang/crmMosaico/BlockMailing.html b/ang/crmMosaico/BlockMailing.html index 3300880a7e..4fcc7bfa8e 100644 --- a/ang/crmMosaico/BlockMailing.html +++ b/ang/crmMosaico/BlockMailing.html @@ -65,9 +65,8 @@
-
- - +
+ ({{ts('Define two options for the subject. We will use A/B testing to determine which is better.')}})
diff --git a/ang/crmMosaico/BlockMailing.js b/ang/crmMosaico/BlockMailing.js index 593c4132b9..80d884e139 100644 --- a/ang/crmMosaico/BlockMailing.js +++ b/ang/crmMosaico/BlockMailing.js @@ -1,5 +1,11 @@ (function(angular, $, _) { - angular.module('crmMosaico').directive('crmMosaicoBlockMailing', function(crmMailingSimpleDirective) { - return crmMailingSimpleDirective('crmMosaicoBlockMailing', '~/crmMosaico/BlockMailing.html'); + angular.module('crmMosaico').directive('crmMosaicoBlockMailing', function(crmMailingSimpleDirective, crmMosaicoVariants) { + const d = crmMailingSimpleDirective('crmMosaicoBlockMailing', '~/crmMosaico/BlockMailing.html'); + const link = d.link; + d.link = function(scope, elm, attr) { + link(scope, elm, attr); + scope.isMailingSplit = (mailing, field) => crmMosaicoVariants.isSplit(mailing, field); + }; + return d; }); })(angular, CRM.$, CRM._); diff --git a/ang/crmMosaico/BlockPreview.html b/ang/crmMosaico/BlockPreview.html index b59f564c9c..456a29d6ae 100644 --- a/ang/crmMosaico/BlockPreview.html +++ b/ang/crmMosaico/BlockPreview.html @@ -44,3 +44,7 @@
+ +
+ {{ts('The draft mailing allows two variations (A/B).')}}
{{ts('If you send a test now, it will use all available variations.')}}
+
diff --git a/ang/crmMosaico/BlockPreview.js b/ang/crmMosaico/BlockPreview.js index 1d5c663c89..92dc4d3aa1 100644 --- a/ang/crmMosaico/BlockPreview.js +++ b/ang/crmMosaico/BlockPreview.js @@ -2,7 +2,7 @@ // example:
// note: the directive defines a variable called "preview" with any inputs supplied by the user (e.g. the target recipient for an example mailing) - angular.module('crmMosaico').directive('crmMosaicoBlockPreview', function(crmUiHelp) { + angular.module('crmMosaico').directive('crmMosaicoBlockPreview', function(crmUiHelp, crmMosaicoVariants) { return { templateUrl: '~/crmMosaico/BlockPreview.html', link: function(scope, elm, attr) { @@ -25,6 +25,7 @@ return ($.inArray(false, validityArr) == -1); }; + scope.isSplit = crmMosaicoVariants.isSplit; scope.doPreview = function(mode) { scope.$eval(attr.onPreview, { diff --git a/ang/crmMosaico/BlockSchedule.html b/ang/crmMosaico/BlockSchedule.html index b70684daae..e22e9c7c90 100644 --- a/ang/crmMosaico/BlockSchedule.html +++ b/ang/crmMosaico/BlockSchedule.html @@ -1,5 +1,11 @@
+
+ +

{{ts('You have enabled A/B testing. We will send each test message to a subset of your subscribers. Later, you can decide the final mailing.')}}

+ +
+
diff --git a/ang/crmMosaico/EditMailingCtrl/bootstrap-single.html b/ang/crmMosaico/EditMailingCtrl/bootstrap-single.html index 019784766d..efea9b3fd3 100644 --- a/ang/crmMosaico/EditMailingCtrl/bootstrap-single.html +++ b/ang/crmMosaico/EditMailingCtrl/bootstrap-single.html @@ -7,12 +7,29 @@
{{ts('Mailing')}}
-
- {{ts('Design')}} +
+
+ + +
+ + {{design.title}}
-
-
+
+
diff --git a/ang/crmMosaico/EditMailingCtrl/bootstrap-wizard.html b/ang/crmMosaico/EditMailingCtrl/bootstrap-wizard.html index 9d41e0f605..31baf381d0 100644 --- a/ang/crmMosaico/EditMailingCtrl/bootstrap-wizard.html +++ b/ang/crmMosaico/EditMailingCtrl/bootstrap-wizard.html @@ -7,7 +7,43 @@
-
+ +
+
+ +
+ +
+
+ +
+

({{ts('Define two options for the design. We will use A/B testing to determine which is better.')}})

+ +
+
+
+ +
+ + {{design.title}} +
+
+
+
+
+
+
+
@@ -21,30 +57,30 @@
+ + {{ts('Back')}} + + + {{ts('Delete Draft')}} + + + {{ts('Save Draft')}} + + + {{ts('Continue')}} + + + {{ts('Submit Mailing')}} +
diff --git a/ang/crmMosaico/MixinCtrl.js b/ang/crmMosaico/MixinCtrl.js index 4aea10def7..3ce61f24f0 100644 --- a/ang/crmMosaico/MixinCtrl.js +++ b/ang/crmMosaico/MixinCtrl.js @@ -2,99 +2,31 @@ // This provides additional actions for editing a Mosaico mailing. // It coexists with crmMailing's EditMailingCtrl. - angular.module('crmMosaico').controller('CrmMosaicoMixinCtrl', function CrmMosaicoMixinCtrl($scope, dialogService, crmMosaicoTemplates, crmStatus, CrmMosaicoIframe, $timeout) { - // var ts = $scope.ts = CRM.ts(null); - - // Main data is in $scope.mailing, $scope.mosaicoCtrl.template - - var crmMosaicoIframe = null, activeDialogs = {}; - - // Hrm, would like `ng-controller="CrmMosaicoMixinCtrl as mosaicoCtrl`, but that's not working... - $scope.mosaicoCtrl = { - templates: [], - // Fill a given "mailing" which the chosen "template". - select: function(mailing, template) { - var topt = mailing.template_options = mailing.template_options || {}; - var promise = crmMosaicoTemplates.getFull(template).then(function(tplCtnt){ - topt.mosaicoTemplate = template.id; - topt.mosaicoMetadata = tplCtnt.metadata; - topt.mosaicoContent = tplCtnt.content; - mailing.body_html = tplCtnt.html; - // console.log('select', {isAr1: _.isArray(mailing.template_options), isAr2: _.isArray(topt), m: mailing, t: template}); - $scope.mosaicoCtrl.edit(mailing); - }); - return crmStatus({start: ts('Loading...'), success: null}, promise); - }, - // Figure out which "template" was previously used with a "mailing." - getTemplate: function(mailing) { - if (!mailing || !mailing.template_options || !mailing.template_options.mosaicoTemplate) { - return null; - } - var matches = _.where($scope.mosaicoCtrl.templates, { - id: mailing.template_options.mosaicoTemplate - }); - return matches.length > 0 ? matches[0] : null; - }, - // Reset all Mosaico data in a "mailing'. - reset: function(mailing) { - if (crmMosaicoIframe) crmMosaicoIframe.destroy(); - crmMosaicoIframe = null; - delete mailing.template_options.mosaicoTemplate; - delete mailing.template_options.mosaicoMetadata; - delete mailing.template_options.mosaicoContent; - mailing.body_html = ''; - }, - // Edit a mailing in Mosaico. - edit: function(mailing) { - if (crmMosaicoIframe) { - crmMosaicoIframe.show(); - return; - } - - function syncModel(viewModel) { - mailing.body_html = viewModel.exportHTML(); - mailing.template_options = mailing.template_options || {}; - // Mosaico exports JSON. Keep their original encoding... or else the loader throws an error. - mailing.template_options.mosaicoMetadata = viewModel.exportMetadata(); - mailing.template_options.mosaicoContent = viewModel.exportJSON(); - } - - crmMosaicoIframe = new CrmMosaicoIframe({ - model: { - template: $scope.mosaicoCtrl.getTemplate(mailing).path, - metadata: mailing.template_options.mosaicoMetadata, - content: mailing.template_options.mosaicoContent - }, - actions: { - sync: function(ko, viewModel) { - syncModel(viewModel); - }, - close: function(ko, viewModel) { - viewModel.metadata.changed = Date.now(); - syncModel(viewModel); - // TODO: When autosave is better integrated, remove this. - $timeout(function(){$scope.save();}, 100); - crmMosaicoIframe.hide('crmMosaicoEditorDialog'); - }, - test: function(ko, viewModel) { - syncModel(viewModel); + angular.module('crmMosaico').controller('CrmMosaicoMixinCtrl', function CrmMosaicoMixinCtrl($scope, dialogService, crmMosaicoVariants) { + const ts = $scope.ts = CRM.ts('mosaico'); + + const singleDesign = [ + {title: ts('Design'), vid: null, action: 'split'} + ]; + const abDesign = [ + {title: ts('Design (A)'), vid: 0, action: 'unsplit'}, + {title: ts('Design (B)'), vid: 1, action: 'unsplit'}, + ]; + + $scope.getDesigns = function(mailing) { + if (!mailing) return []; + return crmMosaicoVariants.isSplit(mailing, 'body_html') ? abDesign : singleDesign; + } + $scope.unsplitDesign = function(mailing, vid) { + crmMosaicoVariants.remove(mailing, ['mosaicoTemplate', 'mosaicoMetadata', 'mosaicoContent', 'body_html'], vid); + }; + $scope.splitDesign = function(mailing) { + crmMosaicoVariants.split(mailing, ['mosaicoTemplate', 'mosaicoMetadata', 'mosaicoContent', 'body_html']); + }; - var model = {mailing: $scope.mailing, attachments: $scope.attachments}; - var options = CRM.utils.adjustDialogDefaults(angular.extend( - {autoOpen: false, title: ts('Preview / Test'), width: 550}, - options - )); - activeDialogs.crmMosaicoPreviewDialog = 1; - var pr = dialogService.open('crmMosaicoPreviewDialog', '~/crmMosaico/PreviewDialogCtrl.html', model, options) - .finally(function(){ delete activeDialogs.crmMosaicoPreviewDialog; }); - return pr; - } - } - }); + $scope.isMailingSplit = (mailing, field) => crmMosaicoVariants.isSplit(mailing, field); - return crmStatus({start: ts('Loading...'), success: null}, crmMosaicoIframe.open()); - } - }; + var activeDialogs = {}; // Open a dialog of advanced options. $scope.openAdvancedOptions = function() { @@ -113,21 +45,10 @@ .finally(function(){ delete activeDialogs.crmMosaicoAdvancedDialog; }); }; - crmMosaicoTemplates.whenLoaded().then(function(){ - $scope.mosaicoCtrl.templates = crmMosaicoTemplates.getAll(); - $scope.mosaicoCtrl.categoryFilters = _.transform(crmMosaicoTemplates.getCategories(), function(filters, category) { - filters.push({id: filters.length, text: category.label, filter: {category_id: category.value}}); - }, [{id: 0, text: ts('Base Template'), filter: {isBase: true}}]); - }); - $scope.$on("$destroy", function() { angular.forEach(activeDialogs, function(v,name){ dialogService.cancel(name); }); - if (crmMosaicoIframe) { - crmMosaicoIframe.destroy(); - crmMosaicoIframe = null; - } }); }); diff --git a/ang/crmMosaico/PreviewDialogCtrl.html b/ang/crmMosaico/PreviewDialogCtrl.html index 8e8258f2ae..901016f935 100644 --- a/ang/crmMosaico/PreviewDialogCtrl.html +++ b/ang/crmMosaico/PreviewDialogCtrl.html @@ -1,5 +1,5 @@
-
+
diff --git a/ang/crmMosaico/PreviewDialogCtrl.js b/ang/crmMosaico/PreviewDialogCtrl.js index a42b2a74a4..2a363613e8 100644 --- a/ang/crmMosaico/PreviewDialogCtrl.js +++ b/ang/crmMosaico/PreviewDialogCtrl.js @@ -5,17 +5,18 @@ // - [input] "model": Object // - "mailing": Object, CiviMail mailing // - "attachments": Object, CrmAttachment - angular.module('crmMosaico').controller('CrmMosaicoPreviewDialogCtrl', function CrmMosaicoPreviewDialogCtrl($scope, crmMailingMgr, crmMailingPreviewMgr, crmBlocker, crmStatus) { + angular.module('crmMosaico').controller('CrmMosaicoPreviewDialogCtrl', function CrmMosaicoPreviewDialogCtrl($scope, crmMailingMgr, crmMailingPreviewMgr, crmBlocker, crmStatus, crmMosaicoVariants) { var ts = $scope.ts = CRM.ts(null); var block = $scope.block = crmBlocker(); // @return Promise - $scope.previewMailing = function previewMailing(mailing, mode) { - return crmMailingPreviewMgr.preview(mailing, mode); + $scope.previewMailing = function previewMailing(mailing, variantId, mode) { + const preview = crmMosaicoVariants.preview(mailing, variantId); + return crmMailingPreviewMgr.preview(preview, mode); }; // @return Promise - $scope.sendTest = function sendTest(mailing, attachments, recipient) { + $scope.sendTest = function sendTest(mailing, variantId, attachments, recipient) { var savePromise = crmMailingMgr.save(mailing) .then(function() { return attachments.save(); diff --git a/ang/crmMosaico/SubjectList.html b/ang/crmMosaico/SubjectList.html index 89b3e47f2a..538264d48d 100644 --- a/ang/crmMosaico/SubjectList.html +++ b/ang/crmMosaico/SubjectList.html @@ -1,4 +1,4 @@ -
+
-
+
({{labels[vid]}}) diff --git a/ang/crmMosaico/SubjectList.js b/ang/crmMosaico/SubjectList.js index d5ca3a7e72..bdbbc5c655 100644 --- a/ang/crmMosaico/SubjectList.js +++ b/ang/crmMosaico/SubjectList.js @@ -1,6 +1,6 @@ (function(angular, $, _) { // Example usage: - angular.module('crmMosaico').directive('crmMosaicoSubjectList', function(crmUiHelp) { + angular.module('crmMosaico').directive('crmMosaicoSubjectList', function(crmUiHelp, crmMosaicoVariants) { return { scope: { crmMailing: '@' @@ -14,23 +14,10 @@ scope.hs = crmUiHelp({file: 'CRM/Mailing/MailingUI'}); scope.checkPerm = CRM.checkPerm; - scope.addSubj = function addSubj() { - scope.mailing.template_options.variants = [ - {subject: scope.mailing.subject}, - {subject: scope.mailing.subject} - ] - }; - - scope.rmSubj = function rmSubj(vid) { - var m = scope.mailing; - m.template_options.variants.splice(vid, 1); - if (m.template_options.variants.length === 1) { - m.subject = m.template_options.variants[0].subject; - delete m.template_options.variants; - } - }; - - scope.labels = ['A', 'B']; + scope.addSubj = () => crmMosaicoVariants.split(scope.mailing, 'subject'); + scope.rmSubj = (vid) => crmMosaicoVariants.remove(scope.mailing, 'subject', vid); + scope.isSplit = () => crmMosaicoVariants.isSplit(scope.mailing, 'subject'); + scope.labels = crmMosaicoVariants.getLabels(); } }; diff --git a/ang/crmMosaico/Variants.js b/ang/crmMosaico/Variants.js new file mode 100644 index 0000000000..0fa3f79d4d --- /dev/null +++ b/ang/crmMosaico/Variants.js @@ -0,0 +1,91 @@ +(function(angular, $, _) { + angular.module('crmMosaico').service('crmMosaicoVariants', function() { + // This utility helps you manipulate the `variants` on a mailing. + + function angularObj(obj) { + return JSON.parse(angular.toJson(obj)); + } + + const mainPlaceholders = {subject: 'VARIANT SUBJECTS', body_html: 'VARIANT HTMLS', mosaicoTemplate: "", mosaicoMetadata: '{}', mosaicoContent: '{}'}; + + const self = { + getLabels: () => ['A', 'B'], + + // Enable variations for a particular field. (By default, old value is copied to A+B. But you can optionally override B.) + // Ex: crmMosaicoVariants.split(mymailing, 'subject') + // Ex: crmMosaicoVariants.split(mymailing, ['body_html', 'mosaicoContent']) + // Ex: crmMosaicoVariants.split(mymailing, 'subject', '') + // Ex: crmMosaicoVariants.split(mymailing, ['body_html', 'mosaicoContent'], {body_html: '', mosaicoContent: null}) + split: function split(mailing, field, bValue) { + if (_.isArray(field)) { + bValue = bValue || {}; + return angular.forEach(field, (f) => self.split(mailing, f, bValue[field])); + } + + mailing.template_options.variants = mailing.template_options.variants || []; + + const mainObj = field.match(/^mosaico/) ? mailing.template_options : mailing; + mailing.template_options.variants[0] = mailing.template_options.variants[0] || {}; + + mailing.template_options.variants[1] = mailing.template_options.variants[1] || {}; + mailing.template_options.variants[0][field] = mainObj[field]; + mailing.template_options.variants[1][field] = (bValue === undefined ? mainObj[field] : bValue); + + if (mainPlaceholders[field] !== undefined) { + mainObj[field] = mainPlaceholders[field]; + } + }, + + // Disable variations for a particular field. Remove a particular record. + // Ex: crmMosaicoVariants.remove(mymailing, 'subject', 0) + // Ex: crmMosaicoVariants.remove(mymailing, ['body_html', 'mosaicoContent'], 1) + remove: function remove(mailing, field, deleteVid) { + if (_.isArray(field)) { + return angular.forEach(field, (f) => self.remove(mailing, f, deleteVid)); + } + + const mainObj = field.match(/^mosaico/) ? mailing.template_options : mailing; + const deadVariant = mailing.template_options.variants[deleteVid]; + const liveVariant = mailing.template_options.variants[deleteVid ? 0 : 1]; + + mainObj[field] = liveVariant[field]; + delete liveVariant[field]; + delete deadVariant[field]; + + // If the variants are empty, then delete the variant objects + const nonEmpties = _.filter(mailing.template_options.variants, (v) => !_.isEmpty(angularObj(v))); + if (nonEmpties.length === 0) { + delete mailing.template_options.variants; + } + }, + + // Create a new, flat `mailing` record which includes overrides for a specific variant. + preview: function preview(mailing, vid) { + const preview = angular.copy(mailing, {}, 5); + if (vid !== null && vid !== undefined) { + angular.extend(preview, mailing.template_options.variants[vid]); + } + delete preview.id; + delete preview.template_options.variants; + delete preview.mosaicoTemplate; + delete preview.mosaicoMetadata; + delete preview.mosaicoContent; + return preview; + }, + + // isSplit(mailing): Determine if there are -any- split/variant fields. + // isSplit(mailing, field): Determine if a -specific- field has variations. + isSplit: function isSplit(mailing, field) { + if (!mailing.template_options || !mailing.template_options.variants) { + return false; + } + if (field) { + return (field in mailing.template_options.variants[0]); + } + const nonEmpties = _.filter(mailing.template_options.variants, (v) => !_.isEmpty(angularObj(v))); + return nonEmpties.length > 0; + } + }; + return self; + }); +})(angular, CRM.$, CRM._);