306739faf1020873012e9ba901c6c3f91f894cb1
[civicrm-core.git] / ext / msgtplui / ang / msgtplui / Edit.js
1 (function(angular, $, _) {
2
3 var TRANSLATED = ['msg_subject', 'msg_html', 'msg_text'];
4
5 /**
6 * Create an APIv4 request to replace a series of translations.
7 *
8 * @param id
9 * The ID of the translated entity.
10 * @param lang
11 * The language of the translation ('fr_CA', 'en_GB').
12 * @param status
13 * The status of the translation ('active', 'draft').
14 * @param values
15 * Key-value pairs. ({msg_title: 'Cheerio'})
16 * @returns []
17 * An API call which replaces the translations ([entity,action,params]).
18 */
19 function reqReplaceTranslations(id, lang, status, values) {
20 if (!values._exists) {
21 return reqDeleteTranslations(id, lang, status);
22 }
23 var records = [];
24 angular.forEach(TRANSLATED, function(key) {
25 records.push({"entity_field":key, "string":values[key]});
26 });
27 return ['Translation', 'replace', {
28 records: records,
29 where: [
30 ["entity_table", "=", "civicrm_msg_template"],
31 ["entity_id", "=", id],
32 ["language", "=", lang],
33 ["status_id:name", "=", status]
34 ]
35 }];
36 }
37
38 function reqDeleteTranslations(id, lang, status) {
39 return ['Translation', 'delete', {
40 where: [
41 ["entity_table", "=", "civicrm_msg_template"],
42 ["entity_id", "=", id],
43 ["language", "=", lang],
44 ["status_id:name", "=", status]
45 ]
46 }];
47 }
48
49 /**
50 * Create an APIv4 request to read a series of translations.
51 *
52 * @param lang
53 * The language of the translation ('fr_CA', 'en_GB').
54 * @param status
55 * The status of the translation ('active', 'draft').
56 * @returns []
57 * An API call which replaces the translations ([entity,action,params]).
58 */
59 function reqChainTranslations(lang, status) {
60 return ["Translation", "get", {
61 "select": ['id', 'entity_field', 'string'],
62 "where": [
63 ['entity_table', '=', 'civicrm_msg_template'],
64 ['entity_id', '=', '$id'],
65 ['language', '=', lang],
66 ['status_id:name', '=', status]
67 ]
68 }];
69 }
70
71 function respMergeTranslations(prefetch) {
72 angular.forEach(prefetch, function(results, queryName) {
73 var forceExists = (queryName === 'txActive');
74 angular.forEach(results, function(result) {
75 if (result.translations) {
76 angular.forEach(result.translations, function(tx) {
77 result[tx.entity_field] = tx.string;
78 });
79 result._exists = forceExists || (result.translations.length > 0);
80 }
81 });
82 });
83 return prefetch;
84 }
85
86 function pickFirsts(prefetch) {
87 return _.reduce(prefetch, function(all, record, key){
88 all[key] = record[0] || undefined;
89 return all;
90 }, {});
91 }
92
93 function copyTranslations(src, dest) {
94 dest._exists = false; // Starting assumption - prove otherwise in a moment.
95 TRANSLATED.forEach(function(fld) {
96 if (src[fld] !== undefined) {
97 dest._exists = true;
98 dest[fld] = src[fld];
99 }
100 else {
101 delete dest[fld];
102 }
103 });
104 }
105
106 angular.module('msgtplui').config(function($routeProvider) {
107 $routeProvider.when('/edit', {
108 controller: 'MsgtpluiEdit',
109 controllerAs: '$ctrl',
110 templateUrl: '~/msgtplui/Edit.html',
111
112 // If you need to look up data when opening the page, list it out
113 // under "resolve".
114 resolve: {
115 prefetch: function(crmApi4, crmStatus, $location) {
116 var args = $location.search();
117 var requests = {};
118
119 requests.main = ['MessageTemplate', 'get', {
120 where: [['id', '=', args.id]],
121 }];
122
123 requests.original = ['MessageTemplate', 'get', {
124 join: [["MessageTemplate AS other", "INNER", null, ["workflow_name", "=", "other.workflow_name"]]],
125 where: [["other.id", "=", args.id], ["is_reserved", "=", true]],
126 limit: 25
127 }];
128
129 if (args.lang) {
130 requests.txActive = ['MessageTemplate', 'get', {
131 select: TRANSLATED,
132 where: [['id', '=', args.id]],
133 chain: {translations: reqChainTranslations(args.lang, 'active')}
134 }];
135
136 requests.txDraft = ['MessageTemplate', 'get', {
137 select: TRANSLATED,
138 where: [['id', '=', args.id]],
139 chain: {translations: reqChainTranslations(args.lang, 'draft')}
140 }];
141 }
142
143 return crmStatus({start: ts('Loading...'), success: ''}, crmApi4(requests).then(respMergeTranslations).then(pickFirsts));
144 },
145 tokenList: function () {
146 return CRM.crmMailing.mailTokens;
147 }
148 }
149 });
150 }
151 );
152
153 angular.module('msgtplui').controller('MsgtpluiEdit', function($q, $scope, crmApi4, crmBlocker, crmStatus, crmUiAlert, crmUiHelp, $location, prefetch, tokenList, $rootScope, dialogService) {
154 var block = $scope.block = crmBlocker();
155 var ts = $scope.ts = CRM.ts('msgtplui');
156 var hs = $scope.hs = crmUiHelp({file: 'CRM/Msgtplui/Edit'}); // See: templates/CRM/Msgtplui/Edit.hlp
157 var $ctrl = this;
158 var args = $location.search();
159
160 $ctrl.locales = CRM.msgtplui.uiLanguages;
161 $ctrl.records = prefetch;
162 $ctrl.tokenList = tokenList;
163 if (args.lang) {
164 $ctrl.lang = args.lang;
165 $ctrl.tab = (args.status === 'draft' && $ctrl.records.txDraft && $ctrl.records.txDraft._exists) ? 'txDraft' : 'txActive';
166 }
167 else {
168 $ctrl.lang = null;
169 $ctrl.tab = 'main';
170 }
171
172 var revisionTypes = [
173 {name: 'original', label: ts('Original')},
174 {name: 'main', label: ts('Standard')},
175 {name: 'txActive', label: ts('%1 - Current translation', {1: $ctrl.locales[$ctrl.lang]})},
176 {name: 'txDraft', label: ts('%1 - Draft translation', {1: $ctrl.locales[$ctrl.lang]})}
177 ];
178
179 $ctrl.switchTab = function switchTab(tgt) {
180 $ctrl.tab = tgt;
181 // Experimenting with action buttons in the tab-bar. This makes the scroll unnecessary.
182 // $('html, body').animate({scrollTop: $("a[name=msgtplui-tabs]").offset().top - $('#civicrm-menu').height()}, 200);
183 };
184 $ctrl.allowDraft = function allowDraft() {
185 return !!$ctrl.lang;
186 };
187 $ctrl.hasDraft = function hasDraft() {
188 return $ctrl.allowDraft() && $ctrl.records.txDraft && $ctrl.records.txDraft._exists;
189 };
190 $ctrl.hasRevType = function hasRevType(name) {
191 switch (name) {
192 case 'txDraft': return $ctrl.hasDraft();
193 case 'txActive': return !!$ctrl.lang;
194 case 'original': return !!$ctrl.records.original;
195 case 'main': return !$ctrl.lang; // !!$ctrl.records.main;
196 }
197 };
198 $ctrl.createDraft = function createDraft(src) {
199 copyTranslations(src, $ctrl.records.txDraft);
200 $ctrl.switchTab('txDraft');
201 crmStatus({success: ts('Beginning draft...')}, $q.resolve());
202 };
203 $ctrl.deleteDraft = function deleteDraft() {
204 copyTranslations({}, $ctrl.records.txDraft);
205 $ctrl.switchTab('txActive');
206 crmStatus({error: ts('Abandoned draft.')}, $q.reject()).then(function(){},function(){});
207 };
208 $ctrl.activateDraft = function activateDraft() {
209 copyTranslations($ctrl.records.txDraft, $ctrl.records.txActive);
210 copyTranslations({}, $ctrl.records.txDraft);
211 $ctrl.switchTab('txActive');
212 crmStatus({success: ts('Activated draft.')}, $q.resolve());
213 };
214
215 $ctrl.save = function save() {
216 var requests = {};
217 if ($ctrl.lang) {
218 requests.txActive = reqReplaceTranslations($ctrl.records.main.id, $ctrl.lang, 'active', $ctrl.records.txActive);
219 requests.txDraft = reqReplaceTranslations($ctrl.records.main.id, $ctrl.lang, 'draft', $ctrl.records.txDraft);
220 }
221 else {
222 requests.main = ['MessageTemplate', 'update', {
223 where: [['id', '=', $ctrl.records.main.id]],
224 values: $ctrl.records.main
225 }];
226 }
227 return block(crmStatus({start: ts('Saving...'), success: ts('Saved')}, crmApi4(requests)));
228 };
229 $ctrl.cancel = function() {
230 window.location = '#/workflow';
231 };
232 $ctrl.delete = function() {
233 var requests = {};
234 if ($ctrl.lang) {
235 requests.txActive = reqDeleteTranslations($ctrl.records.main.id, $ctrl.lang, 'active');
236 requests.txDraft = reqDeleteTranslations($ctrl.records.main.id, $ctrl.lang, 'draft');
237 }
238 else {
239 requests.main = ['MessageTemplate', 'delete', {where: [['id', '=', $ctrl.records.main.id]]}];
240 }
241 return block(crmStatus({start: ts('Deleting...'), success: ts('Deleted')}, crmApi4(requests)))
242 .then($ctrl.cancel);
243 };
244
245 // Ex: $rootScope.$emit('previewMsgTpl', {revisionName: 'txDraft', formatName: 'msg_text'})
246 function onPreview(event, args) {
247 var defaults = {
248 // exampleName: 'fix-this-example',
249 // examples: [
250 // {id: 0, name: 'fix-this-example', title: ts('Fix this example')},
251 // {id: 1, name: 'another-example', title: ts('Another example')}
252 // ],
253 formatName: 'msg_html',
254 formats: [
255 {id: 0, name: 'msg_html', label: ts('HTML')},
256 {id: 1, name: 'msg_text', label: ts('Text')}
257 ],
258 revisionName: $ctrl.tab,
259 revisions: _.reduce(revisionTypes, function(acc, revType){
260 if ($ctrl.hasRevType(revType.name)) {
261 acc.push(angular.extend({id: acc.length, rec: $ctrl.records[revType.name]}, revType));
262 }
263 return acc;
264 }, []),
265 title: ts('Preview')
266 };
267
268 crmApi4('WorkflowMessageExample', 'get', {
269 // FIXME: workflow name
270 where: [["tags", "CONTAINS", "preview"], ["workflow", "=", $ctrl.records.main.workflow_name]],
271 limit: 25
272 }).then(function(workflowMessageExamples) {
273 defaults.exampleName = workflowMessageExamples.length > 0 ? workflowMessageExamples[0].name : null;
274 var i = 0;
275 angular.forEach(workflowMessageExamples, function(ex) {
276 ex.id = i++;
277 });
278 defaults.examples = workflowMessageExamples;
279
280 var model = angular.extend({}, defaults, args);
281 var options = CRM.utils.adjustDialogDefaults({
282 dialogClass: 'msgtplui-dialog',
283 autoOpen: false,
284 height: '90%',
285 width: '90%'
286 });
287 return dialogService.open('previewMsgDlg', '~/msgtplui/Preview.html', model, options)
288 // Nothing to do but hide warnings. The field was edited live.
289 .then(function(){}, function(){});
290 }, function(failure) {
291 // handle failure
292 });
293
294 }
295 $rootScope.$on('previewMsgTpl', onPreview);
296 $rootScope.$on('$destroy', function (){
297 $rootScope.$off('previewMsgTpl', onPreview);
298 });
299 });
300
301 })(angular, CRM.$, CRM._);