8a14dfb346b856597c9348708bb4e84f7c83e59e
[Packages/TYPO3.CMS.git] / typo3 / sysext / extensionmanager / Resources / Public / JavaScript / Main.js
1 /*
2 * This file is part of the TYPO3 CMS project.
3 *
4 * It is free software; you can redistribute it and/or modify it under
5 * the terms of the GNU General Public License, either version 2
6 * of the License, or any later version.
7 *
8 * For the full copyright and license information, please read the
9 * LICENSE.txt file that was distributed with this source code.
10 *
11 * The TYPO3 project - inspiring people to share!
12 */
13 /**
14 * Module: TYPO3/CMS/Extensionmanager/Main
15 * main logic holding everything together, consists of multiple parts
16 * ExtensionManager => Various functions for displaying the extension list / sorting
17 * Repository => Various AJAX functions for TER downloads
18 * ExtensionManager.Update => Various AJAX functions to display updates
19 * ExtensionManager.uploadForm => helper to show the upload form
20 */
21 define(['jquery', 'nprogress', 'TYPO3/CMS/Backend/Modal', 'datatables', 'TYPO3/CMS/Backend/jquery.clearable'], function($, NProgress, Modal) {
22
23 /**
24 *
25 * @type {{identifier: {extensionlist: string, searchField: string, extensionManager: string}}}
26 * @exports TYPO3/CMS/Extensionmanager/Main
27 */
28 var ExtensionManager = {
29 identifier: {
30 extensionlist: '#typo3-extension-list',
31 searchField: '#Tx_Extensionmanager_extensionkey',
32 extensionManager: '.typo3-extension-manager'
33 }
34 };
35
36 /**
37 *
38 * @returns {Object}
39 */
40 ExtensionManager.manageExtensionListing = function() {
41 var $searchField = $(this.identifier.searchField),
42 dataTable = $(this.identifier.extensionlist).DataTable({
43 paging: false,
44 dom: 'lrtip',
45 lengthChange: false,
46 pageLength: 15,
47 stateSave: true,
48 drawCallback: this.bindExtensionListActions,
49 columns: [
50 null,
51 null,
52 null,
53 null,
54 {
55 type: 'version'
56 }, {
57 orderable: false
58 },
59 null
60 ]
61 });
62
63 $searchField.parents('form').on('submit', function() {
64 return false;
65 });
66
67 var getVars = ExtensionManager.getUrlVars();
68
69 // restore filter
70 var currentSearch = (getVars['search'] ? getVars['search'] : dataTable.search());
71 $searchField.val(currentSearch);
72
73 $searchField.on('input', function(e) {
74 dataTable.search($(this).val()).draw();
75 });
76
77 return dataTable;
78 };
79
80 /**
81 *
82 */
83 ExtensionManager.bindExtensionListActions = function() {
84 $('.removeExtension').not('.transformed').each(function() {
85 var $me = $(this);
86 $me.data('href', $me.attr('href'));
87 $me.attr('href', '#');
88 $me.addClass('transformed');
89 $me.click(function() {
90 Modal.confirm(
91 TYPO3.lang['extensionList.removalConfirmation.title'],
92 TYPO3.lang['extensionList.removalConfirmation.question'],
93 top.TYPO3.Severity.error,
94 [
95 {
96 text: TYPO3.lang['button.cancel'],
97 active: true,
98 btnClass: 'btn-default',
99 trigger: function() {
100 Modal.dismiss();
101 }
102 }, {
103 text: TYPO3.lang['button.remove'],
104 btnClass: 'btn-danger',
105 trigger: function() {
106 ExtensionManager.removeExtensionFromDisk($me);
107 Modal.dismiss();
108 }
109 }
110 ]
111 );
112 });
113 });
114 };
115
116 /**
117 *
118 * @param {Object} $extension
119 */
120 ExtensionManager.removeExtensionFromDisk = function($extension) {
121 var $extManager = $(Repository.identifier.extensionManager);
122 $extManager.mask();
123 $.ajax({
124 url: $extension.data('href'),
125 beforeSend: function() {
126 NProgress.start();
127 },
128 success: function() {
129 location.reload();
130 },
131 error: function() {
132 $extManager.unmask();
133 },
134 complete: function() {
135 NProgress.done();
136 }
137 });
138 };
139
140 /**
141 *
142 * @returns {Array}
143 */
144 ExtensionManager.getUrlVars = function() {
145 var vars = [], hash;
146 var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
147 for (var i = 0; i < hashes.length; i++) {
148 hash = hashes[i].split('=');
149 vars.push(hash[0]);
150 vars[hash[0]] = hash[1];
151 }
152 return vars;
153 };
154
155 $.fn.dataTableExt.oSort['version-asc'] = function(a, b) {
156 var result = ExtensionManager.compare(a, b);
157 return result * -1;
158 };
159
160 $.fn.dataTableExt.oSort['version-desc'] = function(a, b) {
161 return ExtensionManager.compare(a, b);
162 };
163
164 /**
165 *
166 * @param {String} a
167 * @param {String} b
168 * @returns {Number}
169 */
170 ExtensionManager.compare = function(a, b) {
171 if (a === b) {
172 return 0;
173 }
174
175 var a_components = a.split(".");
176 var b_components = b.split(".");
177
178 var len = Math.min(a_components.length, b_components.length);
179
180 // loop while the components are equal
181 for (var i = 0; i < len; i++) {
182 // A bigger than B
183 if (parseInt(a_components[i]) > parseInt(b_components[i])) {
184 return 1;
185 }
186
187 // B bigger than A
188 if (parseInt(a_components[i]) < parseInt(b_components[i])) {
189 return -1;
190 }
191 }
192
193 // If one's a prefix of the other, the longer one is greaRepository.
194 if (a_components.length > b_components.length) {
195 return 1;
196 }
197
198 if (a_components.length < b_components.length) {
199 return -1;
200 }
201 // Otherwise they are the same.
202 return 0;
203 };
204
205 /**
206 *
207 * @param {Object} data
208 */
209 ExtensionManager.updateExtension = function(data) {
210 var message = '<h1>' + TYPO3.lang['extensionList.updateConfirmation.title'] + '</h1>';
211 message += '<h2>' + TYPO3.lang['extensionList.updateConfirmation.message'] + '</h2>';
212 message += '<form>';
213 var i = 0;
214 $.each(data.updateComments, function(version, comment) {
215 message += '<h3><input type="radio" ' + (i == 0 ? 'checked="checked" ' : '') + 'name="version" value="' + version + '" /> ' + version + '</h3>';
216 message += '<div>' + comment + '</div>';
217 i++;
218 });
219 message += '</form>';
220
221 var $extManager = $(ExtensionManager.identifier.extensionManager);
222 NProgress.done();
223 $extManager.unmask();
224
225 Modal.confirm(
226 TYPO3.lang['extensionList.updateConfirmation.questionVersionComments'],
227 message,
228 top.TYPO3.Severity.warning,
229 [
230 {
231 text: TYPO3.lang['button.cancel'],
232 active: true,
233 btnClass: 'btn-default',
234 trigger: function() {
235 Modal.dismiss();
236 }
237 }, {
238 text: TYPO3.lang['button.updateExtension'],
239 btnClass: 'btn-warning',
240 trigger: function() {
241 $.ajax({
242 url: data.url,
243 data: {
244 tx_extensionmanager_tools_extensionmanagerextensionmanager: {
245 version: $('input:radio[name=version]:checked', Modal.currentModal).val()
246 }
247 },
248 dataType: 'json',
249 beforeSend: function() {
250 $extManager.mask();
251 NProgress.start();
252 },
253 complete: function() {
254 location.reload();
255 }
256 });
257 Modal.dismiss();
258 }
259 }
260 ]
261 );
262 };
263
264 /**
265 * configuration properties
266 */
267 ExtensionManager.configurationFieldSupport = function() {
268 $('.t3js-emconf-offset').each(function() {
269 var $me = $(this),
270 $parent = $me.parent(),
271 id = $me.attr('id'),
272 val = $me.attr('value'),
273 valArr = val.split(',');
274
275 $me.attr('data-offsetfield-x', '#' + id + '_offset_x')
276 .attr('data-offsetfield-y', '#' + id + '_offset_y')
277 .wrap('<div class="hidden"></div>');
278
279 var elementX = '' +
280 '<div class="form-multigroup-item">' +
281 '<div class="input-group">' +
282 '<div class="input-group-addon">x</div>' +
283 '<input id="' + id + '_offset_x" class="form-control t3js-emconf-offsetfield" data-target="#' + id + '" value="' + $.trim(valArr[0]) + '">' +
284 '</div>' +
285 '</div>';
286 var elementY = '' +
287 '<div class="form-multigroup-item">' +
288 '<div class="input-group">' +
289 '<div class="input-group-addon">y</div>' +
290 '<input id="' + id + '_offset_y" class="form-control t3js-emconf-offsetfield" data-target="#' + id + '" value="' + $.trim(valArr[1]) + '">' +
291 '</div>' +
292 '</div>';
293
294 var offsetGroup = '<div class="form-multigroup-wrap">' + elementX + elementY + '</div>';
295 $parent.append(offsetGroup);
296 $parent.find('.t3js-emconf-offset').keyup(function() {
297 var $target = $($(this).data('target'));
298 $target.attr(
299 'value',
300 $($target.data('offsetfield-x')).val() + ',' + $($target.data('offsetfield-y')).val()
301 );
302 });
303 });
304
305 $('.t3js-emconf-wrap').each(function() {
306 var $me = $(this),
307 $parent = $me.parent(),
308 id = $me.attr('id'),
309 val = $me.attr('value'),
310 valArr = val.split('|');
311
312 $me.attr('data-wrapfield-start', '#' + id + '_wrap_start')
313 .attr('data-wrapfield-end', '#' + id + '_wrap_end')
314 .wrap('<div class="hidden"></div>');
315
316 var elementStart = '' +
317 '<div class="form-multigroup-item">' +
318 '<input id="' + id + '_wrap_start" class="form-control t3js-emconf-wrapfield" data-target="#' + id + '" value="' + $.trim(valArr[0]) + '">' +
319 '</div>';
320 var elementEnd = '' +
321 '<div class="form-multigroup-item">' +
322 '<input id="' + id + '_wrap_end" class="form-control t3js-emconf-wrapfield" data-target="#' + id + '" value="' + $.trim(valArr[1]) + '">' +
323 '</div>';
324
325 var wrapGroup = '<div class="form-multigroup-wrap">' + elementStart + elementEnd + '</div>';
326 $parent.append(wrapGroup);
327 $parent.find('.t3js-emconf-wrapfield').keyup(function() {
328 var $target = $($(this).data('target'));
329 $target.attr(
330 'value',
331 $($target.data('wrapfield-start')).val() + '|' + $($target.data('wrapfield-end')).val()
332 );
333 });
334 });
335 };
336
337 /**
338 *
339 * @type {{downloadPath: string, identifier: {extensionManager: string}}}
340 */
341 var Repository = {
342 downloadPath: '',
343 identifier: {
344 extensionManager: '.typo3-extension-manager'
345 }
346 };
347
348 /**
349 *
350 */
351 Repository.initDom = function() {
352 NProgress.configure({parent: '.t3js-module-body', showSpinner: false});
353
354 $('#terTable').DataTable({
355 lengthChange: false,
356 pageLength: 15,
357 stateSave: false,
358 info: false,
359 paging: false,
360 searching: false,
361 ordering: false,
362 drawCallback: Repository.bindDownload
363 });
364
365 $('#terVersionTable').DataTable({
366 lengthChange: false,
367 pageLength: 15,
368 stateSave: false,
369 info: false,
370 paging: false,
371 searching: false,
372 drawCallback: Repository.bindDownload,
373 order: [
374 [2, 'asc']
375 ],
376 columns: [
377 {orderable: false},
378 null,
379 {type: 'version'},
380 null,
381 null,
382 null
383 ]
384 });
385
386 $('#terSearchTable').DataTable({
387 paging: false,
388 lengthChange: false,
389 stateSave: false,
390 searching: false,
391 language: {
392 search: 'Filter results:'
393 },
394 ordering: false,
395 drawCallback: Repository.bindDownload
396 });
397
398 Repository.bindDownload();
399 Repository.bindSearchFieldResetter();
400 };
401
402 /**
403 *
404 */
405 Repository.bindDownload = function() {
406 var installButtons = $('.downloadFromTer form.download button[type=submit]');
407 installButtons.off('click');
408 installButtons.on('click', function(event) {
409 event.preventDefault();
410 var url = $(event.currentTarget.form).attr('data-href');
411 Repository.downloadPath = $(event.currentTarget.form).find('input.downloadPath:checked').val();
412 $.ajax({
413 url: url,
414 dataType: 'json',
415 beforeSend: function() {
416 $(Repository.identifier.extensionManager).mask();
417 NProgress.start();
418 },
419 success: Repository.getDependencies
420 });
421 });
422 };
423
424 /**
425 *
426 * @param {Object} data
427 * @returns {Boolean}
428 */
429 Repository.getDependencies = function(data) {
430 var $extManager = $(Repository.identifier.extensionManager);
431 NProgress.done();
432 $extManager.unmask();
433 if (data.hasDependencies) {
434 Modal.confirm(data.title, data.message, top.TYPO3.Severity.info, [
435 {
436 text: TYPO3.lang['button.cancel'],
437 active: true,
438 btnClass: 'btn-default',
439 trigger: function() {
440 Modal.dismiss();
441 }
442 }, {
443 text: TYPO3.lang['button.resolveDependencies'],
444 btnClass: 'btn-info',
445 trigger: function() {
446 Repository.getResolveDependenciesAndInstallResult(data.url + '&tx_extensionmanager_tools_extensionmanagerextensionmanager[downloadPath]=' + Repository.downloadPath);
447 Modal.dismiss();
448 }
449 }
450 ]);
451 } else {
452 if(data.hasErrors) {
453 top.TYPO3.Notification.error(data.title, data.message, 15);
454 } else {
455 Repository.getResolveDependenciesAndInstallResult(data.url + '&tx_extensionmanager_tools_extensionmanagerextensionmanager[downloadPath]=' + Repository.downloadPath);
456 }
457 }
458 return false;
459 };
460
461 /**
462 *
463 * @param {String} url
464 */
465 Repository.getResolveDependenciesAndInstallResult = function(url) {
466 var $extManager = $(Repository.identifier.extensionManager);
467 $.ajax({
468 url: url,
469 dataType: 'json',
470 beforeSend: function() {
471 $extManager.mask();
472 NProgress.start();
473 },
474 success: function (data) {
475 if (data.errorCount > 0) {
476 Modal.confirm(data.errorTitle, data.errorMessage, top.TYPO3.Severity.error, [
477 {
478 text: TYPO3.lang['button.cancel'],
479 active: true,
480 btnClass: 'btn-default',
481 trigger: function() {
482 Modal.dismiss();
483 }
484 }, {
485 text: TYPO3.lang['button.resolveDependenciesIgnore'],
486 btnClass: 'btn-danger disabled t3js-dependencies',
487 trigger: function() {
488 Repository.getResolveDependenciesAndInstallResult(data.skipDependencyUri);
489 Modal.dismiss();
490 }
491 }
492 ]);
493 Modal.currentModal.on('shown.bs.modal', function() {
494 var $actionButton = Modal.currentModal.find('.t3js-dependencies');
495 top.TYPO3.jQuery('input[name=unlockDependencyIgnoreButton]').on('change', function() {
496 $actionButton.toggleClass('disabled', !$(this).prop('checked'));
497 });
498 });
499 } else {
500 var successMessage = TYPO3.lang['extensionList.dependenciesResolveDownloadSuccess.message' + data.installationTypeLanguageKey].replace(/\{0\}/g, data.extension) + ' <br />';
501 successMessage += '<br /><h3>' + TYPO3.lang['extensionList.dependenciesResolveDownloadSuccess.header'] + ':</h3>';
502 $.each(data.result, function(index, value) {
503 successMessage += TYPO3.lang['extensionList.dependenciesResolveDownloadSuccess.item'] + ' ' + index + ':<br /><ul>';
504 $.each(value, function(extkey, extdata) {
505 successMessage += '<li>' + extkey + '</li>';
506 });
507 successMessage += '</ul>';
508 });
509 top.TYPO3.Notification.info(TYPO3.lang['extensionList.dependenciesResolveFlashMessage.title' + data.installationTypeLanguageKey].replace(/\{0\}/g, data.extension), successMessage, 15);
510 top.TYPO3.ModuleMenu.App.refreshMenu();
511 }
512 },
513 complete: function() {
514 NProgress.done();
515 $extManager.unmask();
516 }
517 });
518 };
519
520 /**
521 *
522 */
523 Repository.bindSearchFieldResetter = function() {
524 var $searchFields = $('.typo3-extensionmanager-searchTerForm input[type="text"]');
525 var searchResultShown = ('' !== $searchFields.first().val());
526
527 $searchFields.clearable(
528 {
529 onClear: function() {
530 if (searchResultShown) {
531 $(this).closest('form').submit();
532 }
533 }
534 }
535 );
536 };
537
538 /**
539 *
540 * @type {{identifier: {extensionTable: string, terUpdateAction: string, pagination: string, splashscreen: string, terTableWrapper: string, terTableDataTableWrapper: string}}}
541 */
542 ExtensionManager.Update = {
543 identifier: {
544 extensionTable: '#terTable',
545 terUpdateAction: '.update-from-ter',
546 pagination: '.pagination-wrap',
547 splashscreen: '.splash-receivedata',
548 terTableWrapper: '#terTableWrapper',
549 terTableDataTableWrapper: '#terTableWrapper .dataTables_wrapper'
550 }
551 };
552
553 /**
554 * Register "update from ter" action
555 */
556 ExtensionManager.Update.initializeEvents = function() {
557 $(ExtensionManager.Update.identifier.terUpdateAction).each(function() {
558
559 // "this" is the form which updates the extension list from
560 // TER on submit
561 var $me = $(this),
562 updateURL = $(this).attr('action');
563
564 $me.attr('action', '#');
565 $me.submit(function() {
566 // Force update on click.
567 ExtensionManager.Update.updateFromTer(updateURL, 1);
568
569 // Prevent normal submit action.
570 return false;
571 });
572
573 // This might give problems when there are more "update"-buttons,
574 // each one would trigger a TER-ExtensionManager.Update.
575 ExtensionManager.Update.updateFromTer(updateURL, 0);
576 });
577 };
578
579 /**
580 *
581 * @param {String} url
582 * @param {Number} forceUpdate
583 */
584 ExtensionManager.Update.updateFromTer = function(url, forceUpdate) {
585 if (forceUpdate == 1) {
586 url = url + '&tx_extensionmanager_tools_extensionmanagerextensionmanager%5BforceUpdateCheck%5D=1';
587 }
588
589 // Hide triggers for TER update
590 $(ExtensionManager.Update.identifier.terUpdateAction).addClass('is-hidden');
591
592 // Hide extension table
593 $(ExtensionManager.Update.identifier.extensionTable).hide();
594
595 // Show loaders
596 $(ExtensionManager.Update.identifier.splashscreen).addClass('is-shown');
597 $(ExtensionManager.Update.identifier.terTableDataTableWrapper).addClass('is-loading');
598 $(ExtensionManager.Update.identifier.pagination).addClass('is-loading');
599
600 $.ajax({
601 url: url,
602 dataType: 'json',
603 cache: false,
604 beforeSend: function() {
605 NProgress.start();
606 },
607 success: function(data) {
608 // Something went wrong, show message
609 if (data.errorMessage.length) {
610 top.TYPO3.Notification.error(TYPO3.lang['extensionList.updateFromTerFlashMessage.title'], data.errorMessage, 10);
611 }
612
613 // Message with latest updates
614 var $lastUpdate = $(ExtensionManager.Update.identifier.terUpdateAction + ' .time-since-last-update');
615 $lastUpdate.text(data.timeSinceLastUpdate);
616 $lastUpdate.attr(
617 'title',
618 TYPO3.lang['extensionList.updateFromTer.lastUpdate.timeOfLastUpdate'] + data.lastUpdateTime
619 );
620
621 if (data.updated) {
622 $.ajax({
623 url: window.location.href + '&tx_extensionmanager_tools_extensionmanagerextensionmanager%5Bformat%5D=json',
624 dataType: 'json',
625 success: function(data) {
626 $(ExtensionManager.Update.identifier.terTableWrapper).html(data);
627 ExtensionManager.Update.transformPaginatorToAjax();
628 }
629 });
630 }
631 },
632 error: function(jqXHR, textStatus, errorThrown) {
633 // Create an error message with diagnosis info.
634 var errorMessage = textStatus + '(' + errorThrown + '): ' + jqXHR.responseText;
635
636 top.TYPO3.Notification.warning(
637 TYPO3.lang['extensionList.updateFromTerFlashMessage.title'],
638 errorMessage,
639 10
640 );
641 },
642 complete: function() {
643 NProgress.done();
644
645 // Hide loaders
646 $(ExtensionManager.Update.identifier.splashscreen).removeClass('is-shown');
647 $(ExtensionManager.Update.identifier.terTableDataTableWrapper).removeClass('is-loading');
648 $(ExtensionManager.Update.identifier.pagination).removeClass('is-loading');
649
650 // Show triggers for TER-update
651 $(ExtensionManager.Update.identifier.terUpdateAction).removeClass('is-hidden');
652
653 // Show extension table
654 $(ExtensionManager.Update.identifier.extensionTable).show();
655 }
656 });
657 };
658
659 /**
660 *
661 */
662 ExtensionManager.Update.transformPaginatorToAjax = function () {
663 $(ExtensionManager.Update.identifier.pagination + ' a').each(function() {
664 var $me = $(this);
665 $me.data('href', $(this).attr('href'));
666 $me.attr('href', '#');
667 $me.click(function() {
668 var $terTableWrapper = $(ExtensionManager.Update.identifier.terTableWrapper);
669 $terTableWrapper.mask();
670 NProgress.start();
671 $.ajax({
672 url: $(this).data('href'),
673 dataType: 'json',
674 success: function(data) {
675 $terTableWrapper.html(data);
676 ExtensionManager.Update.transformPaginatorToAjax();
677 },
678 complete: function() {
679 NProgress.done();
680 $terTableWrapper.unmask();
681 }
682 });
683 });
684 });
685 };
686
687 /**
688 * show the uploading form
689 */
690 ExtensionManager.UploadForm = {
691 expandedUploadFormClass: 'transformed'
692 };
693
694 /**
695 *
696 */
697 ExtensionManager.UploadForm.initializeEvents = function() {
698 // Show upload form
699 $(document).on('click', '.t3js-upload', function(event) {
700 var $me = $(this),
701 $uploadForm = $('.uploadForm');
702
703 event.preventDefault();
704 if($me.hasClass(ExtensionManager.UploadForm.expandedUploadFormClass)) {
705 $uploadForm.stop().slideUp();
706 $me.removeClass(ExtensionManager.UploadForm.expandedUploadFormClass);
707 } else {
708 $me.addClass(ExtensionManager.UploadForm.expandedUploadFormClass);
709 $uploadForm.stop().slideDown();
710
711 $.ajax({
712 url: $me.attr('href'),
713 dataType: 'html',
714 success: function (data) {
715 $uploadForm.html(data);
716 }
717 });
718 }
719 });
720 };
721
722 $(function() {
723 var dataTable = ExtensionManager.manageExtensionListing();
724
725 $(document).on('click', '.onClickMaskExtensionManager', function() {
726 $(ExtensionManager.identifier.extensionManager).mask();
727 NProgress.start();
728 }).on('click', 'a[data-action=update-extension]', function(e) {
729 e.preventDefault();
730 $.ajax({
731 url: $(this).attr('href'),
732 dataType: 'json',
733 beforeSend: function() {
734 $(ExtensionManager.identifier.extensionManager).mask();
735 NProgress.start();
736 },
737 success: ExtensionManager.updateExtension
738 });
739 }).on('change', 'input[name=unlockDependencyIgnoreButton]', function() {
740 var $actionButton = TYPO3.jQuery('.t3js-dependencies');
741 $actionButton.toggleClass('disabled', !$(this).prop('checked'));
742 });
743
744 $(ExtensionManager.identifier.searchField).clearable({
745 onClear: function() {
746 dataTable.search('').draw();
747 }
748 });
749
750 $('.expandable').expander({
751 expandEffect: 'slideDown',
752 collapseEffect: 'slideUp',
753 beforeExpand: function() {
754 $(this).parent().css('z-index', 199);
755 },
756 afterCollapse: function() {
757 $(this).parent().css('z-index', 1);
758 }
759 });
760
761 $(document).on('click', '.t3-button-action-installdistribution', function() {
762 $(ExtensionManager.identifier.extensionManager).mask();
763 });
764
765 ExtensionManager.configurationFieldSupport();
766 var $validate = $('.validate');
767 $validate.validate();
768 $(document).on('click', '.t3js-save-close', function() {
769 $validate.append($('<input />', {type: 'hidden', name: 'tx_extensionmanager_tools_extensionmanagerextensionmanager[action]', value: 'saveAndClose'})).submit();
770 });
771
772 // initialize the repository
773 Repository.initDom();
774
775 ExtensionManager.Update.initializeEvents();
776 ExtensionManager.UploadForm.initializeEvents();
777 });
778
779 if (typeof TYPO3.ExtensionManager === 'undefined') {
780 TYPO3.ExtensionManager = ExtensionManager;
781 }
782
783 return ExtensionManager;
784 });