bbea867b8328c3a218f75c3074af34fc6bb803ab
[Packages/TYPO3.CMS.git] / typo3 / sysext / workspaces / Resources / Public / JavaScript / Backend.js
1 /*
2 /*
3 * This file is part of the TYPO3 CMS project.
4 *
5 * It is free software; you can redistribute it and/or modify it under
6 * the terms of the GNU General Public License, either version 2
7 * of the License, or any later version.
8 *
9 * For the full copyright and license information, please read the
10 * LICENSE.txt file that was distributed with this source code.
11 *
12 * The TYPO3 project - inspiring people to share!
13 */
14
15 /**
16 * RequireJS module for the workspace backend module
17 */
18 define([
19 'jquery',
20 'TYPO3/CMS/Workspaces/Workspaces',
21 'TYPO3/CMS/Backend/Tooltip',
22 'TYPO3/CMS/Backend/Severity',
23 'TYPO3/CMS/Backend/Modal',
24 'TYPO3/CMS/Backend/Wizard',
25 'nprogress',
26 'TYPO3/CMS/Backend/jquery.clearable'
27 ], function($, Workspaces, Tooltip, Severity, Modal, Wizard, NProgress) {
28 'use strict';
29
30 var Backend = {
31 workspaceTitle: '',
32 identifiers: {
33 searchForm: '#workspace-settings-form',
34 searchTextField: '#workspace-settings-form input[name="search-text"]',
35 searchSubmitBtn: '#workspace-settings-form button[type="submit"]',
36 depthSelector: '#workspace-settings-form [name="depth"]',
37 languageSelector: '#workspace-settings-form select[name="languages"]',
38 actionForm: '#workspace-actions-form',
39 chooseStageAction: '#workspace-actions-form [name="stage-action"]',
40 chooseSelectionAction: '#workspace-actions-form [name="selection-action"]',
41 chooseMassAction: '#workspace-actions-form [name="mass-action"]',
42 container: '#workspace-panel',
43 actionIcons: '#workspace-action-icons',
44 toggleAll: '.t3js-toggle-all',
45 previewLinksButton: '.t3js-preview-link',
46 pagination: '#workspace-pagination'
47 },
48 settings: {
49 depth: TYPO3.settings.Workspaces.depth,
50 dir: 'ASC',
51 id: TYPO3.settings.Workspaces.id,
52 language: TYPO3.settings.Workspaces.language,
53 limit: 30,
54 query: '',
55 sort: 'label_Live',
56 start: 0,
57 filterTxt: ''
58 },
59 paging: {
60 currentPage: 1,
61 totalPages: 1,
62 totalItems: 0
63 },
64 allToggled: false,
65 elements: {}, // filled in Backend.getElements()
66 latestPath: '',
67 markedRecordsForMassAction: []
68 };
69
70 Backend.initialize = function() {
71 Backend.getElements();
72 Backend.registerEvents();
73
74 if (TYPO3.settings.Workspaces.depth > 0) {
75 Backend.elements.$depthSelector.val(TYPO3.settings.Workspaces.depth);
76 }
77
78 Backend.loadWorkspaceComponents();
79 };
80
81 Backend.getElements = function() {
82 Backend.elements.$searchForm = $(Backend.identifiers.searchForm);
83 Backend.elements.$searchTextField = $(Backend.identifiers.searchTextField);
84 Backend.elements.$searchSubmitBtn = $(Backend.identifiers.searchSubmitBtn);
85 Backend.elements.$depthSelector = $(Backend.identifiers.depthSelector);
86 Backend.elements.$languageSelector = $(Backend.identifiers.languageSelector);
87 Backend.elements.$container = $(Backend.identifiers.container);
88 Backend.elements.$tableBody = Backend.elements.$container.find('tbody');
89 Backend.elements.$actionIcons = $(Backend.identifiers.actionIcons);
90 Backend.elements.$toggleAll = $(Backend.identifiers.toggleAll);
91 Backend.elements.$chooseStageAction = $(Backend.identifiers.chooseStageAction);
92 Backend.elements.$chooseSelectionAction = $(Backend.identifiers.chooseSelectionAction);
93 Backend.elements.$chooseMassAction = $(Backend.identifiers.chooseMassAction);
94 Backend.elements.$previewLinksButton = $(Backend.identifiers.previewLinksButton);
95 Backend.elements.$pagination = $(Backend.identifiers.pagination);
96 };
97
98 Backend.registerEvents = function() {
99 $(document).on('click', '[data-action="swap"]', function(e) {
100 var $tr = $(e.target).closest('tr');
101 Workspaces.checkIntegrity(
102 {
103 selection: [
104 {
105 liveId: $tr.data('uid'),
106 versionId: $tr.data('t3ver_oid'),
107 table: $tr.data('table')
108 }
109 ],
110 type: 'selection'
111 }
112 ).done(function(response) {
113 if (response[0].result.result === 'warning') {
114 Backend.addIntegrityCheckWarningToWizard();
115 }
116
117 Wizard.setup.forceSelection = false;
118 Wizard.addSlide(
119 'swap-confirm',
120 'Swap',
121 TYPO3.lang['window.swap.message'],
122 Severity.info
123 );
124 Wizard.addFinalProcessingSlide(function() {
125 // We passed this slide, swap the record now
126 Workspaces.sendExtDirectRequest(
127 Workspaces.generateExtDirectActionsPayload('swapSingleRecord', [
128 $tr.data('table'),
129 $tr.data('t3ver_oid'),
130 $tr.data('uid')
131 ])
132 ).done(function() {
133 Wizard.dismiss();
134 Backend.getWorkspaceInfos();
135 Backend.refreshPageTree();
136 });
137 }).done(function() {
138 Wizard.show();
139 });
140 });
141 }).on('click', '[data-action="prevstage"]', function(e) {
142 Backend.sendToStage($(e.target).closest('tr'), 'prev');
143 }).on('click', '[data-action="nextstage"]', function(e) {
144 Backend.sendToStage($(e.target).closest('tr'), 'next');
145 }).on('click', '[data-action="changes"]', Backend.viewChanges
146 ).on('click', '[data-action="preview"]', Backend.openPreview
147 ).on('click', '[data-action="open"]', function(e) {
148 var $tr = $(e.target).closest('tr'),
149 newUrl = TYPO3.settings.FormEngine.moduleUrl + '&returnUrl=' + encodeURIComponent(document.location.href) + '&id=' + TYPO3.settings.Workspaces.id + '&edit[' + $tr.data('table') + '][' + $tr.data('uid') + ']=edit';
150
151 // Append workspace of record in all-workspaces view
152 if (TYPO3.settings.Workspaces.allView) {
153 newUrl += '&workspace=' + $tr.data('t3ver_wsid');
154 }
155 window.location.href = newUrl;
156 }).on('click', '[data-action="version"]', function(e) {
157 var $tr = $(e.target).closest('tr');
158 if ($tr.data('table') === 'pages') {
159 top.loadEditId($tr.data('t3ver_oid'));
160 } else {
161 top.loadEditId($tr.data('pid'));
162 }
163 }).on('click', '[data-action="remove"]', Backend.confirmDeleteRecordFromWorkspace
164 ).on('click', '[data-action="expand"]', function(e) {
165 var $me = $(this),
166 $target = Backend.elements.$tableBody.find($me.data('target')),
167 iconIdentifier;
168
169 if ($target.first().attr('aria-expanded') === 'true') {
170 iconIdentifier = 'apps-pagetree-expand';
171 } else {
172 iconIdentifier = 'apps-pagetree-collapse';
173 }
174
175 $me.html(Backend.getPreRenderedIcon(iconIdentifier));
176 });
177
178 Backend.elements.$searchForm.on('submit', function(e) {
179 e.preventDefault();
180 Backend.settings.filterTxt = Backend.elements.$searchTextField.val();
181 Backend.getWorkspaceInfos();
182 });
183
184 Backend.elements.$searchTextField.on('keyup', function() {
185 var $me = $(this);
186
187 if ($me.val() !== '') {
188 Backend.elements.$searchSubmitBtn.removeClass('disabled');
189 } else {
190 Backend.elements.$searchSubmitBtn.addClass('disabled');
191 Backend.getWorkspaceInfos();
192 }
193 }).clearable(
194 {
195 onClear: function() {
196 Backend.elements.$searchSubmitBtn.addClass('disabled');
197 Backend.settings.filterTxt = '';
198 Backend.getWorkspaceInfos();
199 }
200 }
201 );
202
203 // checkboxes in the table
204 Backend.elements.$toggleAll.on('click', function() {
205 Backend.allToggled = !Backend.allToggled;
206 Backend.elements.$tableBody.find('input[type="checkbox"]').prop('checked', Backend.allToggled).trigger('change');
207 });
208 Backend.elements.$tableBody.on('change', 'tr input[type=checkbox]', Backend.handleCheckboxChange);
209
210 // Listen for depth changes
211 Backend.elements.$depthSelector.on('change', function(e) {
212 var $me = $(this);
213 Backend.settings.depth = $me.val();
214
215 Backend.getWorkspaceInfos();
216 });
217
218 // Generate preview links
219 Backend.elements.$previewLinksButton.on('click', Backend.generatePreviewLinks);
220
221 // Listen for language changes
222 Backend.elements.$languageSelector.on('change', function(e) {
223 var $me = $(this);
224 Backend.settings.language = $me.val();
225
226 Workspaces.sendExtDirectRequest([
227 Workspaces.generateExtDirectActionsPayload('saveLanguageSelection', [$me.val()]),
228 Workspaces.generateExtDirectPayload('getWorkspaceInfos', Backend.settings)
229 ]).done(function(response) {
230 Backend.elements.$languageSelector.prev().html($me.find(':selected').data('icon'));
231 Backend.renderWorkspaceInfos(response[1].result);
232 });
233 });
234
235 // Listen for actions
236 Backend.elements.$chooseStageAction.on('change', Backend.sendToSpecificStageAction);
237 Backend.elements.$chooseSelectionAction.on('change', Backend.runSelectionAction);
238 Backend.elements.$chooseMassAction.on('change', Backend.runMassAction);
239
240 // clicking an action in the paginator
241 Backend.elements.$pagination.on('click', 'a[data-action]', function(e) {
242 e.preventDefault();
243
244 var $el = $(this),
245 reload = false;
246
247 switch ($el.data('action')) {
248 case 'previous':
249 if (Backend.paging.currentPage > 1) {
250 Backend.paging.currentPage--;
251 reload = true;
252 }
253 break;
254 case 'next':
255 if (Backend.paging.currentPage < Backend.paging.totalPages) {
256 Backend.paging.currentPage++;
257 reload = true;
258 }
259 break;
260 case 'page':
261 Backend.paging.currentPage = parseInt($el.data('page'));
262 reload = true;
263 break;
264 }
265
266 if (reload) {
267 // Adjust settings
268 Backend.settings.start = Backend.settings.limit * (Backend.paging.currentPage - 1);
269 Backend.getWorkspaceInfos();
270 }
271 });
272 };
273
274 Backend.handleCheckboxChange = function(e) {
275 var $checkbox = $(this),
276 $tr = $checkbox.parents('tr'),
277 table = $tr.data('table'),
278 uid = $tr.data('uid'),
279 t3ver_oid = $tr.data('t3ver_oid'),
280 record = table + ':' + uid + ':' + t3ver_oid;
281
282 if ($checkbox.prop('checked')) {
283 Backend.markedRecordsForMassAction.push(record);
284 $tr.addClass('warning');
285 } else {
286 var index = Backend.markedRecordsForMassAction.indexOf(record);
287 if (index > -1) {
288 Backend.markedRecordsForMassAction.splice(index, 1);
289 }
290 $tr.removeClass('warning');
291 }
292
293 Backend.elements.$chooseStageAction.prop('disabled', Backend.markedRecordsForMassAction.length === 0);
294 Backend.elements.$chooseSelectionAction.prop('disabled', Backend.markedRecordsForMassAction.length === 0);
295 Backend.elements.$chooseMassAction.prop('disabled', Backend.markedRecordsForMassAction.length > 0);
296 };
297
298 /**
299 * Generates the diff view of a record
300 *
301 * @param {Object} diff
302 * @return {$}
303 */
304 Backend.generateDiffView = function(diff) {
305 var $diff = $('<div />', {class: 'diff'});
306
307 for (var i = 0; i < diff.length; ++i) {
308 $diff.append(
309 $('<div />', {class: 'diff-item'}).append(
310 $('<div />', {class: 'diff-item-title'}).text(diff[i].label),
311 $('<div />', {class: 'diff-item-result diff-item-result-inline'}).html(diff[i].content)
312 )
313 );
314 }
315 return $diff;
316 };
317
318 /**
319 * Generates the comments view of a record
320 *
321 * @param {Object} comments
322 * @return {$}
323 */
324 Backend.generateCommentView = function(comments) {
325 var $comments = $('<div />');
326
327 for (var i = 0; i < comments.length; ++i) {
328 var $panel = $('<div />', {class: 'panel panel-default'});
329
330 if (comments[i].user_comment.length > 0) {
331 $panel.append(
332 $('<div />', {class: 'panel-body'}).html(comments[i].user_comment)
333 );
334 }
335
336 $panel.append(
337 $('<div />', {class: 'panel-footer'}).append(
338 $('<span />', {class: 'label label-success'}).text(comments[i].stage_title),
339 $('<span />', {class: 'label label-info'}).text(comments[i].tstamp)
340 )
341 );
342
343 $comments.append(
344 $('<div />', {class: 'media'}).append(
345 $('<div />', {class: 'media-left text-center'}).text(comments[i].user_username).prepend(
346 $('<div />').html(comments[i].user_avatar)
347 ),
348 $('<div />', {class: 'media-body'}).append($panel)
349 )
350 );
351 }
352
353 return $comments;
354 };
355
356 /**
357 * Sends a record to a stage
358 *
359 * @param {Object} $row
360 * @param {String} direction
361 */
362 Backend.sendToStage = function($row, direction) {
363 var nextStage,
364 stageWindowAction,
365 stageExecuteAction;
366
367 if (direction === 'next') {
368 nextStage = $row.data('nextStage');
369 stageWindowAction = 'sendToNextStageWindow';
370 stageExecuteAction = 'sendToNextStageExecute';
371 } else if (direction === 'prev') {
372 nextStage = $row.data('prevStage');
373 stageWindowAction = 'sendToPrevStageWindow';
374 stageExecuteAction = 'sendToPrevStageExecute';
375 } else {
376 throw 'Invalid direction given.';
377 }
378
379 Workspaces.sendExtDirectRequest(
380 Workspaces.generateExtDirectActionsPayload(stageWindowAction, [
381 $row.data('uid'), $row.data('table'), $row.data('t3ver_oid')
382 ])
383 ).done(function(response) {
384 var $modal = Workspaces.renderSendToStageWindow(response);
385 $modal.on('button.clicked', function(e) {
386 if (e.target.name === 'ok') {
387 var $form = $(e.currentTarget).find('form'),
388 serializedForm = $form.serializeObject();
389
390 serializedForm.affects = {
391 table: $row.data('table'),
392 nextStage: nextStage,
393 t3ver_oid: $row.data('t3ver_oid'),
394 uid: $row.data('uid'),
395 elements: []
396 };
397
398 Workspaces.sendExtDirectRequest([
399 Workspaces.generateExtDirectActionsPayload(stageExecuteAction, [serializedForm]),
400 Workspaces.generateExtDirectPayload('getWorkspaceInfos', Backend.settings)
401 ]).done(function(response) {
402 $modal.modal('hide');
403 Backend.renderWorkspaceInfos(response[1].result);
404 Backend.refreshPageTree();
405 });
406 }
407 });
408 });
409 };
410
411 /**
412 * Loads the workspace components, like available stage actions and items of the workspace
413 */
414 Backend.loadWorkspaceComponents = function() {
415 Workspaces.sendExtDirectRequest([
416 Workspaces.generateExtDirectPayload('getWorkspaceInfos', Backend.settings),
417 Workspaces.generateExtDirectPayload('getStageActions', {}),
418 Workspaces.generateExtDirectMassActionsPayload('getMassStageActions', {}),
419 Workspaces.generateExtDirectPayload('getSystemLanguages', {})
420 ]).done(function(response) {
421 Backend.elements.$depthSelector.prop('disabled', false);
422
423 // Records
424 Backend.renderWorkspaceInfos(response[0].result);
425
426 // Stage actions
427 var stageActions = response[1].result.data,
428 i;
429 for (i = 0; i < stageActions.length; ++i) {
430 Backend.elements.$chooseStageAction.append(
431 $('<option />').val(stageActions[i].uid).text(stageActions[i].title)
432 );
433 }
434
435 // Mass actions
436 var massActions = response[2].result.data;
437 for (i = 0; i < massActions.length; ++i) {
438 Backend.elements.$chooseSelectionAction.append(
439 $('<option />').val(massActions[i].action).text(massActions[i].title)
440 );
441
442 Backend.elements.$chooseMassAction.append(
443 $('<option />').val(massActions[i].action).text(massActions[i].title)
444 );
445 }
446
447 // Languages
448 var languages = response[3].result.data;
449 for (i = 0; i < languages.length; ++i) {
450 var $option = $('<option />').val(languages[i].uid).text(languages[i].title).data('icon', languages[i].icon);
451 if (String(languages[i].uid) === String(TYPO3.settings.Workspaces.language)) {
452 $option.prop('selected', true);
453 Backend.elements.$languageSelector.prev().html(languages[i].icon);
454 }
455 Backend.elements.$languageSelector.append($option);
456 }
457 Backend.elements.$languageSelector.prop('disabled', false);
458 });
459 };
460
461 /**
462 * Gets the workspace infos
463 *
464 * @return {Promise}
465 * @protected
466 */
467 Backend.getWorkspaceInfos = function() {
468 Workspaces.sendExtDirectRequest(
469 Workspaces.generateExtDirectPayload('getWorkspaceInfos', Backend.settings)
470 ).done(function(response) {
471 Backend.renderWorkspaceInfos(response[0].result);
472 });
473 };
474
475 /**
476 * Renders fetched workspace informations
477 *
478 * @param {Object} result
479 */
480 Backend.renderWorkspaceInfos = function(result) {
481 Backend.elements.$tableBody.children().remove();
482 Backend.allToggled = false;
483 Backend.elements.$chooseStageAction.prop('disabled', true);
484 Backend.elements.$chooseSelectionAction.prop('disabled', true);
485 Backend.elements.$chooseMassAction.prop('disabled', result.data.length === 0);
486
487 Backend.buildPagination(result.total);
488
489 for (var i = 0; i < result.data.length; ++i) {
490 var item = result.data[i],
491 $actions = $('<div />', {class: 'btn-group'}),
492 $integrityIcon = '';
493
494 $actions.append(
495 Backend.getAction(item.Workspaces_CollectionChildren > 0 && item.Workspaces_CollectionCurrent !== '', 'expand', 'apps-pagetree-collapse').attr('title', TYPO3.lang['tooltip.swap']).attr('data-target', '[data-collection="' + item.Workspaces_CollectionCurrent + '"]').attr('data-toggle', 'collapse'),
496 $('<button />', {class: 'btn btn-default', 'data-action': 'changes', 'data-toggle': 'tooltip', title: TYPO3.lang['tooltip.showChanges']}).append(Backend.getPreRenderedIcon('actions-document-info')),
497 Backend.getAction(item.allowedAction_swap && item.Workspaces_CollectionParent === '', 'swap', 'actions-version-swap-version').attr('title', TYPO3.lang['tooltip.swap']),
498 Backend.getAction(item.allowedAction_view, 'preview', 'actions-version-workspace-preview').attr('title', TYPO3.lang['tooltip.viewElementAction']),
499 $('<button />', {class: 'btn btn-default', 'data-action': 'open', 'data-toggle': 'tooltip', title: TYPO3.lang['tooltip.editElementAction']}).append(Backend.getPreRenderedIcon('actions-open')),
500 $('<button />', {class: 'btn btn-default', 'data-action': 'version', 'data-toggle': 'tooltip', title: TYPO3.lang['tooltip.openPage']}).append(Backend.getPreRenderedIcon('actions-version-page-open')),
501 Backend.getAction(item.allowedAction_delete, 'remove', 'actions-version-document-remove').attr('title', TYPO3.lang['tooltip.discardVersion']),
502 $('<label />', {class: 'btn btn-default btn-checkbox'}).append(
503 $('<input />', {type: 'checkbox'}),
504 $('<span />', {class: 't3-icon fa'})
505 )
506 );
507
508 if (item.integrity.messages !== '') {
509 $integrityIcon = $(TYPO3.settings.Workspaces.icons[item.integrity.status]);
510 $integrityIcon
511 .attr('data-toggle', 'tooltip')
512 .attr('data-placement', 'top')
513 .attr('data-html', true)
514 .attr('title', item.integrity.messages);
515 }
516
517 if (Backend.latestPath !== item.path_Workspace) {
518 Backend.latestPath = item.path_Workspace;
519 Backend.elements.$tableBody.append(
520 $('<tr />').append(
521 $('<th />', {colspan: 6}).text(Backend.latestPath)
522 )
523 );
524 }
525
526 var rowConfiguration = {
527 'data-uid': item.uid,
528 'data-pid': item.livepid,
529 'data-t3ver_oid': item.t3ver_oid,
530 'data-t3ver_wsid': item.t3ver_wsid,
531 'data-table': item.table,
532 'data-next-stage': item.value_nextStage,
533 'data-prev-stage': item.value_prevStage,
534 'data-stage': item.stage
535 };
536
537 if (item.Workspaces_CollectionParent !== '') {
538 rowConfiguration['data-collection'] = item.Workspaces_CollectionParent;
539 rowConfiguration['class'] = 'collapse';
540 }
541
542 Backend.elements.$tableBody.append(
543 $('<tr />', rowConfiguration).append(
544 $('<td />', {class: 't3js-title-workspace'}).html(item.icon_Workspace + '&nbsp;' + '<a href="#" data-action="changes"><span class="item-state-' + item.state_Workspace + '">' + item.label_Workspace + '</span></a>'),
545 $('<td />', {class: 't3js-title-live'}).html(item.icon_Live + '&nbsp;' + item.label_Live),
546 $('<td />').text(item.label_Stage),
547 $('<td />').html($integrityIcon),
548 $('<td />').html(item.language.icon),
549 $('<td />', {class: 'text-right', nowrap: 'nowrap'}).append($actions)
550 )
551 );
552
553 Tooltip.initialize('[data-toggle="tooltip"]', {
554 delay: {
555 show: 500,
556 hide: 100
557 },
558 trigger: 'hover',
559 container: 'body'
560 });
561 }
562 };
563
564 /**
565 * Renders the pagination
566 *
567 * @param {Number} totalItems
568 */
569 Backend.buildPagination = function(totalItems) {
570 if (totalItems === 0) {
571 Backend.elements.$pagination.contents().remove();
572 return;
573 }
574
575 Backend.paging.totalItems = totalItems;
576 Backend.paging.totalPages = Math.ceil(totalItems / Backend.settings.limit);
577
578 if (Backend.paging.totalPages === 1) {
579 // early abort if only one page is available
580 Backend.elements.$pagination.contents().remove();
581 return;
582 }
583
584 var $ul = $('<ul />', {class: 'pagination pagination-block'}),
585 liElements = [],
586 $controlFirstPage = $('<li />').append(
587 $('<a />', {'data-action': 'previous'}).append(
588 $('<span />', {class: 't3-icon fa fa-arrow-left'})
589 )
590 ),
591 $controlLastPage = $('<li />').append(
592 $('<a />', {'data-action': 'next'}).append(
593 $('<span />', {class: 't3-icon fa fa-arrow-right'})
594 )
595 );
596
597 if (Backend.paging.currentPage === 1) {
598 $controlFirstPage.disablePagingAction();
599 }
600
601 if (Backend.paging.currentPage === Backend.paging.totalPages) {
602 $controlLastPage.disablePagingAction();
603 }
604
605 for (var i = 1; i <= Backend.paging.totalPages; i++) {
606 var $li = $('<li />', {class: Backend.paging.currentPage === i ? 'active' : ''});
607 $li.append(
608 $('<a />', {'data-action': 'page', 'data-page': i}).append(
609 $('<span />').text(i)
610 )
611 );
612 liElements.push($li);
613 }
614
615 $ul.append($controlFirstPage, liElements, $controlLastPage);
616 Backend.elements.$pagination.html($ul);
617 };
618
619 /**
620 * View changes of a record
621 *
622 * @param {Event} e
623 */
624 Backend.viewChanges = function(e) {
625 e.preventDefault();
626
627 var $tr = $(e.target).closest('tr');
628
629 Workspaces.sendExtDirectRequest(
630 Workspaces.generateExtDirectPayload('getRowDetails', {
631 stage: $tr.data('stage'),
632 t3ver_oid: $tr.data('t3ver_oid'),
633 table: $tr.data('table'),
634 uid: $tr.data('uid')
635 })
636 ).done(function(response) {
637 var item = response[0].result.data[0],
638 $content = $('<div />'),
639 $tabsNav = $('<ul />', {class: 'nav nav-tabs', role: 'tablist'}),
640 $tabsContent = $('<div />', {class: 'tab-content'}),
641 modalButtons = [];
642
643 $content.append(
644 $('<p />').html(TYPO3.lang['path'].replace('{0}', item.path_Live)),
645 $('<p />').html(TYPO3.lang['current_step'].replace('{0}', item.label_Stage).replace('{1}', item.stage_position).replace('{2}', item.stage_count))
646 );
647
648 if (item.diff.length > 0) {
649 $tabsNav.append(
650 $('<li />', {role: 'presentation'}).append(
651 $('<a />', {href: '#workspace-changes', 'aria-controls': 'workspace-changes', role: 'tab', 'data-toggle': 'tab'}).text(TYPO3.lang['window.recordChanges.tabs.changeSummary'])
652 )
653 );
654 $tabsContent.append(
655 $('<div />', {role: 'tabpanel', class: 'tab-pane', id: 'workspace-changes'}).append(
656 $('<div />', {class: 'form-section'}).append(
657 Backend.generateDiffView(item.diff)
658 )
659 )
660 );
661 }
662
663 if (item.comments.length > 0) {
664 $tabsNav.append(
665 $('<li />', {role: 'presentation'}).append(
666 $('<a />', {href: '#workspace-comments', 'aria-controls': 'workspace-comments', role: 'tab', 'data-toggle': 'tab'}).html(TYPO3.lang['window.recordChanges.tabs.comments'] + '&nbsp;').append(
667 $('<span />', {class: 'badge'}).text(item.comments.length)
668 )
669 )
670 );
671 $tabsContent.append(
672 $('<div />', {role: 'tabpanel', class: 'tab-pane', id: 'workspace-comments'}).append(
673 $('<div />', {class: 'form-section'}).append(
674 Backend.generateCommentView(item.comments)
675 )
676 )
677 );
678 }
679
680 if (item.history.total > 0) {
681 $tabsNav.append(
682 $('<li />', {role: 'presentation'}).append(
683 $('<a />', {href: '#workspace-history', 'aria-controls': 'workspace-history', role: 'tab', 'data-toggle': 'tab'}).text(TYPO3.lang['window.recordChanges.tabs.history'])
684 )
685 );
686
687 $tabsContent.append(
688 $('<div />', {role: 'tabpanel', class: 'tab-pane', id: 'workspace-history'}).append(
689 $('<div />', {class: 'form-section'}).append(
690 Backend.generateHistoryView(item.history.data)
691 )
692 )
693 );
694 }
695
696 // Mark the first tab and pane as active
697 $tabsNav.find('li').first().addClass('active');
698 $tabsContent.find('.tab-pane').first().addClass('active');
699
700 // Attach tabs
701 $content.append(
702 $('<div />').append(
703 $tabsNav,
704 $tabsContent
705 )
706 );
707
708 if ($tr.data('stage') !== $tr.data('prevStage')) {
709 modalButtons.push({
710 text: item.label_PrevStage.title,
711 active: true,
712 btnClass: 'btn-default',
713 name: 'prevstage',
714 trigger: function () {
715 Modal.currentModal.trigger('modal-dismiss');
716 Backend.sendToStage($(e.target).closest('tr'), 'prev');
717 }
718 });
719 }
720
721 modalButtons.push({
722 text: item.label_NextStage.title,
723 active: true,
724 btnClass: 'btn-default',
725 name: 'nextstage',
726 trigger: function () {
727 Modal.currentModal.trigger('modal-dismiss');
728 Backend.sendToStage($(e.target).closest('tr'), 'next');
729 }
730 });
731 modalButtons.push({
732 text: TYPO3.lang['close'],
733 active: true,
734 btnClass: 'btn-info',
735 name: 'cancel',
736 trigger: function () {
737 Modal.currentModal.trigger('modal-dismiss');
738 }
739 });
740
741 Modal.show(
742 TYPO3.lang['window.recordInformation'].replace('{0}', $.trim($tr.find('.t3js-title-live').text())),
743 $content,
744 Severity.info,
745 modalButtons
746 );
747 });
748 };
749
750 /**
751 * Opens a record in a preview window
752 *
753 * @param {Event} e
754 */
755 Backend.openPreview = function(e) {
756 var $tr = $(e.target).closest('tr');
757
758 Workspaces.sendExtDirectRequest(
759 Workspaces.generateExtDirectActionsPayload('viewSingleRecord', [
760 $tr.data('table'), $tr.data('uid')
761 ])
762 ).done(function(response) {
763 eval(response[0].result);
764 });
765 };
766
767 /**
768 * Renders the record's history
769 *
770 * @param {Object} data
771 */
772 Backend.generateHistoryView = function(data) {
773 var $history = $('<div />');
774
775 for (var i = 0; i < data.length; ++i) {
776 var $panel = $('<div />', {class: 'panel panel-default'}),
777 $diff;
778
779 if (typeof data[i].differences === 'object') {
780 if (data[i].differences.length === 0) {
781 // Somehow here are no differences. What a pity, skip that record
782 continue;
783 }
784 $diff = $('<div />', {class: 'diff'});
785
786 for (var j = 0; j < data[i].differences.length; ++j) {
787 $diff.append(
788 $('<div />', {class: 'diff-item'}).append(
789 $('<div />', {class: 'diff-item-title'}).text(data[i].differences[j].label),
790 $('<div />', {class: 'diff-item-result diff-item-result-inline'}).html(data[i].differences[j].html)
791 )
792 );
793 }
794
795 $panel.append(
796 $('<div />').append($diff)
797 );
798 } else {
799 $panel.append(
800 $('<div />', {class: 'panel-body'}).text(data[i].differences)
801 );
802 }
803 $panel.append(
804 $('<div />', {class: 'panel-footer'}).append(
805 $('<span />', {class: 'label label-info'}).text(data[i].datetime)
806 )
807 );
808
809 $history.append(
810 $('<div />', {class: 'media'}).append(
811 $('<div />', {class: 'media-left text-center'}).text(data[i].user).prepend(
812 $('<div />').html(data[i].user_avatar)
813 ),
814 $('<div />', {class: 'media-body'}).append($panel)
815 )
816 );
817 }
818
819 return $history;
820 };
821
822 /**
823 * Shows a confirmation modal and deletes the selected record from workspace.
824 *
825 * @param {Event} e
826 */
827 Backend.confirmDeleteRecordFromWorkspace = function(e) {
828 var $tr = $(e.target).closest('tr');
829 var $modal = Modal.confirm(
830 TYPO3.lang['window.discard.title'],
831 TYPO3.lang['window.discard.message'],
832 Severity.warning,
833 [
834 {
835 text: TYPO3.lang['cancel'],
836 active: true,
837 btnClass: 'btn-default',
838 name: 'cancel',
839 trigger: function() {
840 $modal.modal('hide');
841 }
842 }, {
843 text: TYPO3.lang['ok'],
844 btnClass: 'btn-warning',
845 name: 'ok'
846 }
847 ]
848 );
849 $modal.on('button.clicked', function(e) {
850 if (e.target.name === 'ok') {
851 Workspaces.sendExtDirectRequest([
852 Workspaces.generateExtDirectActionsPayload('deleteSingleRecord', [
853 $tr.data('table'),
854 $tr.data('uid')
855 ])
856 ]).done(function() {
857 $modal.modal('hide');
858 Backend.getWorkspaceInfos();
859 Backend.refreshPageTree();
860 });
861 }
862 });
863 };
864
865 /**
866 * Runs a mass action
867 */
868 Backend.runSelectionAction = function() {
869 var selectedAction = Backend.elements.$chooseSelectionAction.val(),
870 integrityCheckRequired = selectedAction !== 'discard';
871
872 if (selectedAction.length === 0) {
873 // Don't do anything if that value is empty
874 return;
875 }
876
877 var affectedRecords = [];
878 for (var i = 0; i < Backend.markedRecordsForMassAction.length; ++i) {
879 var affected = Backend.markedRecordsForMassAction[i].split(':');
880 affectedRecords.push({
881 table: affected[0],
882 liveId: affected[2],
883 versionId: affected[1]
884 });
885 }
886
887 if (!integrityCheckRequired) {
888 Wizard.setup.forceSelection = false;
889 Backend.renderSelectionActionWizard(selectedAction, affectedRecords);
890 } else {
891 Workspaces.checkIntegrity(
892 {
893 selection: affectedRecords,
894 type: 'selection'
895 }
896 ).done(function(response) {
897 Wizard.setup.forceSelection = false;
898 if (response[0].result.result === 'warning') {
899 Backend.addIntegrityCheckWarningToWizard();
900 }
901 Backend.renderSelectionActionWizard(selectedAction, affectedRecords);
902 });
903 }
904 };
905
906 /**
907 * Adds a slide to the wizard concerning an integrity check warning.
908 */
909 Backend.addIntegrityCheckWarningToWizard = function() {
910 Wizard.addSlide(
911 'integrity-warning',
912 'Warning',
913 TYPO3.lang['integrity.hasIssuesDescription'] + '<br>' + TYPO3.lang['integrity.hasIssuesQuestion'],
914 Severity.warning
915 );
916 };
917
918 /**
919 * Renders the wizard for selection actions
920 *
921 * @param {String} selectedAction
922 * @param {Object} affectedRecords
923 */
924 Backend.renderSelectionActionWizard = function(selectedAction, affectedRecords) {
925 Wizard.addSlide(
926 'mass-action-confirmation',
927 TYPO3.lang['window.selectionAction.title'],
928 $('<p />').text(TYPO3.lang['tooltip.' + selectedAction + 'Selected']),
929 Severity.warning
930 );
931 Wizard.addFinalProcessingSlide(function() {
932 Workspaces.sendExtDirectRequest(
933 Workspaces.generateExtDirectActionsPayload('executeSelectionAction', {
934 action: selectedAction,
935 selection: affectedRecords
936 })
937 ).done(function() {
938 Backend.getWorkspaceInfos();
939 Wizard.dismiss();
940 Backend.refreshPageTree();
941 });
942 }).done(function() {
943 Wizard.show();
944
945 Wizard.getComponent().on('wizard-dismissed', function() {
946 Backend.elements.$chooseSelectionAction.val('');
947 });
948 });
949 };
950
951 /**
952 * Runs a mass action
953 */
954 Backend.runMassAction = function() {
955 var selectedAction = Backend.elements.$chooseMassAction.val(),
956 integrityCheckRequired = selectedAction !== 'discard';
957
958 if (selectedAction.length === 0) {
959 // Don't do anything if that value is empty
960 return;
961 }
962
963 if (!integrityCheckRequired) {
964 Wizard.setup.forceSelection = false;
965 Backend.renderMassActionWizard(selectedAction);
966 } else {
967 Workspaces.checkIntegrity(
968 {
969 language: Backend.settings.language,
970 type: selectedAction
971 }
972 ).done(function(response) {
973 Wizard.setup.forceSelection = false;
974 if (response[0].result.result === 'warning') {
975 Backend.addIntegrityCheckWarningToWizard();
976 }
977 Backend.renderMassActionWizard(selectedAction);
978 });
979 }
980 };
981
982 /**
983 * Renders the wizard for mass actions
984 *
985 * @param {String} selectedAction
986 */
987 Backend.renderMassActionWizard = function(selectedAction) {
988 var massAction,
989 doSwap = false;
990
991 switch (selectedAction) {
992 case 'publish':
993 massAction = 'publishWorkspace';
994 break;
995 case 'swap':
996 massAction = 'publishWorkspace';
997 doSwap = true;
998 break;
999 case 'discard':
1000 massAction = 'flushWorkspace';
1001 break;
1002 }
1003
1004 if (massAction === null) {
1005 throw 'Invalid mass action ' + selectedAction + ' called.';
1006 }
1007
1008 Wizard.setup.forceSelection = false;
1009 Wizard.addSlide(
1010 'mass-action-confirmation',
1011 TYPO3.lang['window.massAction.title'],
1012 $('<p />').html(TYPO3.lang['tooltip.' + selectedAction + 'All'] + '<br><br>' + TYPO3.lang['tooltip.affectWholeWorkspace']),
1013 Severity.warning
1014 );
1015 Wizard.addFinalProcessingSlide(function() {
1016 Workspaces.sendExtDirectRequest(
1017 Workspaces.generateExtDirectMassActionsPayload(massAction, {
1018 init: true,
1019 total: 0,
1020 processed: 0,
1021 language: Backend.settings.language,
1022 swap: doSwap
1023 })
1024 ).done(function(response) {
1025 var payload = response[0].result;
1026 Workspaces.sendExtDirectRequest(
1027 Workspaces.generateExtDirectMassActionsPayload(massAction, payload)
1028 ).done(function() {
1029 Backend.getWorkspaceInfos();
1030 Wizard.dismiss();
1031 });
1032 });
1033 }).done(function() {
1034 Wizard.show();
1035
1036 Wizard.getComponent().on('wizard-dismissed', function() {
1037 Backend.elements.$chooseMassAction.val('');
1038 });
1039 });
1040 };
1041
1042 /**
1043 * Sends marked records to a stage
1044 *
1045 * @param {Event} e
1046 */
1047 Backend.sendToSpecificStageAction = function(e) {
1048 var affectedRecords = [],
1049 stage = $(e.currentTarget).val();
1050 for (var i = 0; i < Backend.markedRecordsForMassAction.length; ++i) {
1051 var affected = Backend.markedRecordsForMassAction[i].split(':');
1052 affectedRecords.push({
1053 table: affected[0],
1054 uid: affected[1],
1055 t3ver_oid: affected[2]
1056 });
1057 }
1058 Workspaces.sendExtDirectRequest(
1059 Workspaces.generateExtDirectActionsPayload('sendToSpecificStageWindow', [
1060 stage, affectedRecords
1061 ])
1062 ).done(function(response) {
1063 var $modal = Workspaces.renderSendToStageWindow(response);
1064 $modal.on('button.clicked', function(e) {
1065 if (e.target.name === 'ok') {
1066 var $form = $(e.currentTarget).find('form'),
1067 serializedForm = $form.serializeObject();
1068
1069 serializedForm.affects = {
1070 elements: affectedRecords,
1071 nextStage: stage
1072 };
1073
1074 Workspaces.sendExtDirectRequest([
1075 Workspaces.generateExtDirectActionsPayload('sendToSpecificStageExecute', [serializedForm]),
1076 Workspaces.generateExtDirectPayload('getWorkspaceInfos', Backend.settings)
1077 ]).done(function(response) {
1078 $modal.modal('hide');
1079 Backend.renderWorkspaceInfos(response[1].result);
1080 Backend.refreshPageTree();
1081 });
1082 }
1083 }).on('modal-destroyed', function() {
1084 Backend.elements.$chooseStageAction.val('');
1085 });
1086 });
1087 };
1088
1089 /**
1090 * Reloads the page tree
1091 */
1092 Backend.refreshPageTree = function() {
1093 if (top.TYPO3 && top.TYPO3.Backend && top.TYPO3.Backend.NavigationContainer && top.TYPO3.Backend.NavigationContainer.PageTree) {
1094 top.TYPO3.Backend.NavigationContainer.PageTree.refreshTree();
1095 }
1096 };
1097
1098 /**
1099 * Renders the action button based on the user's permission.
1100 * This method is intended to be dropped once we don't the ExtDirect stuff anymore.
1101 *
1102 * @returns {$}
1103 * @private
1104 */
1105 Backend.getAction = function(condition, action, iconIdentifier) {
1106 if (condition) {
1107 return $('<button />', {class: 'btn btn-default', 'data-action': action, 'data-toggle': 'tooltip'}).append(Backend.getPreRenderedIcon(iconIdentifier))
1108 }
1109 return $('<span />', {class: 'btn btn-default disabled'}).append(Backend.getPreRenderedIcon('empty-empty'));
1110 };
1111
1112 /**
1113 * Fetches and renders available preview links
1114 */
1115 Backend.generatePreviewLinks = function() {
1116 Workspaces.sendExtDirectRequest(
1117 Workspaces.generateExtDirectActionsPayload('generateWorkspacePreviewLinksForAllLanguages', [
1118 Backend.settings.id
1119 ])
1120 ).done(function(response) {
1121 var result = response[0].result,
1122 $list = $('<dl />');
1123
1124 $.each(result, function(language, url) {
1125 $list.append(
1126 $('<dt />').text(language),
1127 $('<dd />').append(
1128 $('<a />', {href: url, target: '_blank'}).text(url)
1129 )
1130 );
1131 });
1132
1133 Modal.show(
1134 TYPO3.lang['previewLink'],
1135 $list,
1136 Severity.info,
1137 [{
1138 text: TYPO3.lang['ok'],
1139 active: true,
1140 btnClass: 'btn-info',
1141 name: 'ok',
1142 trigger: function() {
1143 Modal.currentModal.trigger('modal-dismiss');
1144 }
1145 }]
1146 );
1147 });
1148 };
1149
1150 /**
1151 * Gets the pre-rendered icon
1152 * This method is intended to be dropped once we use Fluid's StandaloneView.
1153 *
1154 * @param {String} identifier
1155 * @returns {$}
1156 */
1157 Backend.getPreRenderedIcon = function(identifier) {
1158 return Backend.elements.$actionIcons.find('[data-identifier="' + identifier + '"]').clone();
1159 };
1160
1161 /**
1162 * Serialize a form to a JavaScript object
1163 *
1164 * @see http://stackoverflow.com/a/1186309/4828813
1165 * @return {Object}
1166 */
1167 $.fn.serializeObject = function() {
1168 var o = {};
1169 var a = this.serializeArray();
1170 $.each(a, function() {
1171 if (typeof o[this.name] !== 'undefined') {
1172 if (!o[this.name].push) {
1173 o[this.name] = [o[this.name]];
1174 }
1175 o[this.name].push(this.value || '');
1176 } else {
1177 o[this.name] = this.value || '';
1178 }
1179 });
1180 return o;
1181 };
1182
1183 /**
1184 * Changes the markup of a pagination action being disabled
1185 */
1186 $.fn.disablePagingAction = function() {
1187 $(this).addClass('disabled').find('.t3-icon').unwrap().wrap($('<span />'));
1188 };
1189
1190 $(Backend.initialize);
1191 });