Support replacing existing media files
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / js / WoltLabSuite / Core / Media / Manager / Base.js
CommitLineData
59ab4d0f
MS
1/**
2 * Provides the media manager dialog.
3 *
4 * @author Matthias Schmidt
7b7b9764 5 * @copyright 2001-2019 WoltLab GmbH
59ab4d0f 6 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
58d7e8f8 7 * @module WoltLabSuite/Core/Media/Manager/Base
59ab4d0f
MS
8 */
9define(
10 [
036edbb4
MS
11 'Core', 'Dictionary', 'Dom/ChangeListener', 'Dom/Traverse',
12 'Dom/Util', 'EventHandler', 'Language', 'List',
58d7e8f8 13 'Permission', 'Ui/Dialog', 'Ui/Notification', 'WoltLabSuite/Core/Controller/Clipboard',
e2a882df 14 'WoltLabSuite/Core/Media/Editor', 'WoltLabSuite/Core/Media/Upload', 'WoltLabSuite/Core/Media/Manager/Search', 'StringUtil',
599b8699
MS
15 'WoltLabSuite/Core/Ui/Pagination',
16 'WoltLabSuite/Core/Media/Clipboard'
59ab4d0f
MS
17 ],
18 function(
036edbb4
MS
19 Core, Dictionary, DomChangeListener, DomTraverse,
20 DomUtil, EventHandler, Language, List,
21 Permission, UiDialog, UiNotification, Clipboard,
e2a882df 22 MediaEditor, MediaUpload, MediaManagerSearch, StringUtil,
599b8699
MS
23 UiPagination,
24 MediaClipboard
59ab4d0f
MS
25 )
26{
27 "use strict";
28
d5bbc780
AE
29 if (!COMPILER_TARGET_DEFAULT) {
30 var Fake = function() {};
31 Fake.prototype = {
32 _addButtonEventListeners: function() {},
33 _click: function() {},
d5bbc780
AE
34 _dialogClose: function() {},
35 _dialogInit: function() {},
36 _dialogSetup: function() {},
37 _dialogShow: function() {},
38 _editMedia: function() {},
39 _editorClose: function() {},
40 _editorSuccess: function() {},
41 _removeClipboardCheckboxes: function() {},
42 _setMedia: function() {},
43 addMedia: function() {},
599b8699 44 clipboardDeleteMedia: function() {},
d5bbc780
AE
45 getDialog: function() {},
46 getMode: function() {},
47 getOption: function() {},
48 removeMedia: function() {},
49 resetMedia: function() {},
50 setMedia: function() {},
51 setupMediaElement: function() {}
52 };
53 return Fake;
54 }
55
5c1cc5ff
MS
56 var _mediaManagerCounter = 0;
57
59ab4d0f
MS
58 /**
59 * @constructor
60 */
56eb7314
MS
61 function MediaManagerBase(options) {
62 this._options = Core.extend({
63 dialogTitle: Language.get('wcf.media.manager'),
dac0007f 64 imagesOnly: false,
a308057b 65 minSearchLength: 3
56eb7314
MS
66 }, options);
67
5c1cc5ff 68 this._id = 'mediaManager' + _mediaManagerCounter++;
7ed8dd25 69 this._listItems = new Dictionary();
59ab4d0f 70 this._media = new Dictionary();
59ab4d0f
MS
71 this._mediaManagerMediaList = null;
72 this._search = null;
2413a6e5 73 this._upload = null;
59a19269 74 this._forceClipboard = false;
5c1cc5ff 75 this._hadInitiallyMarkedItems = false;
e2a882df 76 this._pagination = null;
59ab4d0f
MS
77
78 if (Permission.get('admin.content.cms.canManageMedia')) {
79 this._mediaEditor = new MediaEditor(this);
80 }
81
58d7e8f8 82 DomChangeListener.add('WoltLabSuite/Core/Media/Manager', this._addButtonEventListeners.bind(this));
83e795ec
MS
83
84 EventHandler.add('com.woltlab.wcf.media.upload', 'success', this._openEditorAfterUpload.bind(this));
f0115fd9 85 }
56eb7314 86 MediaManagerBase.prototype = {
59ab4d0f
MS
87 /**
88 * Adds click event listeners to media buttons.
89 */
90 _addButtonEventListeners: function() {
91 if (!this._mediaManagerMediaList) return;
92
93 var listItems = DomTraverse.childrenByTag(this._mediaManagerMediaList, 'LI');
94 for (var i = 0, length = listItems.length; i < length; i++) {
95 var listItem = listItems[i];
96
97 if (Permission.get('admin.content.cms.canManageMedia')) {
a051b5a7 98 var editIcon = elByClass('jsMediaEditButton', listItem)[0];
59ab4d0f 99 if (editIcon) {
a051b5a7 100 editIcon.classList.remove('jsMediaEditButton');
ac188fc5 101 editIcon.addEventListener(WCF_CLICK_EVENT, this._editMedia.bind(this));
59ab4d0f
MS
102 }
103 }
59ab4d0f
MS
104 }
105 },
106
cf4e950c
MS
107 /**
108 * Is called when a new category is selected.
109 */
110 _categoryChange: function() {
111 this._search.search();
112 },
113
59ab4d0f
MS
114 /**
115 * Handles clicks on the media manager button.
116 *
117 * @param {object} event event object
118 */
119 _click: function(event) {
120 event.preventDefault();
121
122 UiDialog.open(this);
123 },
124
54ce3706
MS
125 /**
126 * Is called if the media manager dialog is closed.
127 */
128 _dialogClose: function() {
129 // only show media clipboard if editor is open
59a19269 130 if (Permission.get('admin.content.cms.canManageMedia') || this._forceClipboard) {
5eb9ad65
MS
131 Clipboard.hideEditor('com.woltlab.wcf.media');
132 }
54ce3706
MS
133 },
134
c4020813
MS
135 /**
136 * Initializes the dialog when first loaded.
137 *
138 * @param {string} content dialog content
139 * @param {object} data AJAX request's response data
140 */
141 _dialogInit: function(content, data) {
142 // store media data locally
143 var media = data.returnValues.media || { };
144 for (var mediaId in media) {
145 if (objOwns(media, mediaId)) {
7ed8dd25 146 this._media.set(~~mediaId, media[mediaId]);
c4020813
MS
147 }
148 }
149
e2a882df
MS
150 this._initPagination(~~data.returnValues.pageCount);
151
5c1cc5ff 152 this._hadInitiallyMarkedItems = data.returnValues.hasMarkedItems;
c4020813
MS
153 },
154
59ab4d0f
MS
155 /**
156 * Returns all data to setup the media manager dialog.
157 *
158 * @return {object} dialog setup data
159 */
160 _dialogSetup: function() {
161 return {
5c1cc5ff 162 id: this._id,
59ab4d0f 163 options: {
54ce3706
MS
164 onClose: this._dialogClose.bind(this),
165 onShow: this._dialogShow.bind(this),
56eb7314 166 title: this._options.dialogTitle
59ab4d0f
MS
167 },
168 source: {
c4020813 169 after: this._dialogInit.bind(this),
59ab4d0f
MS
170 data: {
171 actionName: 'getManagementDialog',
56eb7314
MS
172 className: 'wcf\\data\\media\\MediaAction',
173 parameters: {
174 mode: this.getMode(),
dac0007f 175 imagesOnly: this._options.imagesOnly
56eb7314 176 }
59ab4d0f
MS
177 }
178 }
179 };
180 },
181
54ce3706
MS
182 /**
183 * Is called if the media manager dialog is shown.
184 */
185 _dialogShow: function() {
5c1cc5ff 186 if (!this._mediaManagerMediaList) {
cf4e950c
MS
187 var dialog = this.getDialog();
188
189 this._mediaManagerMediaList = elByClass('mediaManagerMediaList', dialog)[0];
190
191 this._mediaCategorySelect = elBySel('.mediaManagerCategoryList > select', dialog);
192 if (this._mediaCategorySelect) {
193 this._mediaCategorySelect.addEventListener('change', this._categoryChange.bind(this));
194 }
5c1cc5ff
MS
195
196 // store list items locally
197 var listItems = DomTraverse.childrenByTag(this._mediaManagerMediaList, 'LI');
198 for (var i = 0, length = listItems.length; i < length; i++) {
199 var listItem = listItems[i];
200
201 this._listItems.set(~~elData(listItem, 'object-id'), listItem);
202 }
203
204 if (Permission.get('admin.content.cms.canManageMedia')) {
205 var uploadButton = elByClass('mediaManagerMediaUploadButton', UiDialog.getDialog(this).dialog)[0];
2413a6e5 206 this._upload = new MediaUpload(DomUtil.identify(uploadButton), DomUtil.identify(this._mediaManagerMediaList), {
5c1cc5ff
MS
207 mediaManager: this
208 });
209
210 var deleteAction = new WCF.Action.Delete('wcf\\data\\media\\MediaAction', '.mediaFile');
211 deleteAction._didTriggerEffect = function(element) {
e2a882df 212 this.removeMedia(elData(element[0], 'object-id'));
5c1cc5ff
MS
213 }.bind(this);
214 }
215
216 if (Permission.get('admin.content.cms.canManageMedia') || this._forceClipboard) {
599b8699
MS
217 MediaClipboard.init(
218 'menuManagerDialog-' + this.getMode(),
219 this._hadInitiallyMarkedItems ? true : false,
220 this
221 );
5c1cc5ff
MS
222 }
223 else {
224 this._removeClipboardCheckboxes();
225 }
226
227 this._search = new MediaManagerSearch(this);
228
229 if (!listItems.length) {
230 this._search.hideSearch();
231 }
232 }
c4020813 233
54ce3706 234 // only show media clipboard if editor is open
59a19269 235 if (Permission.get('admin.content.cms.canManageMedia') || this._forceClipboard) {
5eb9ad65
MS
236 Clipboard.showEditor('com.woltlab.wcf.media');
237 }
54ce3706
MS
238 },
239
59ab4d0f
MS
240 /**
241 * Opens the media editor for a media file.
242 *
243 * @param {Event} event event object for clicks on edit icons
244 */
245 _editMedia: function(event) {
246 if (!Permission.get('admin.content.cms.canManageMedia')) {
247 throw new Error("You are not allowed to edit media files.");
248 }
249
5c1cc5ff 250 UiDialog.close(this);
59ab4d0f 251
7ed8dd25 252 this._mediaEditor.edit(this._media.get(~~elData(event.currentTarget, 'object-id')));
59ab4d0f
MS
253 },
254
255 /**
256 * Re-opens the manager dialog after closing the editor dialog.
257 */
258 _editorClose: function() {
259 UiDialog.open(this);
260 },
261
262 /**
263 * Re-opens the manager dialog and updates the media data after
264 * successfully editing a media file.
265 *
266 * @param {object} media updated media file data
cf4e950c 267 * @param {integer} oldCategoryId old category id
59ab4d0f 268 */
cf4e950c
MS
269 _editorSuccess: function(media, oldCategoryId) {
270 // if the category changed of media changed and category
271 // is selected, check if media list needs to be refreshed
272 if (this._mediaCategorySelect) {
273 var selectedCategoryId = ~~this._mediaCategorySelect.value;
274
275 if (selectedCategoryId) {
276 var newCategoryId = ~~media.categoryID;
277
278 if (oldCategoryId != newCategoryId && (oldCategoryId == selectedCategoryId || newCategoryId == selectedCategoryId)) {
279 this._search.search();
280 }
281 }
282 }
283
59ab4d0f
MS
284 UiDialog.open(this);
285
7ed8dd25 286 this._media.set(~~media.mediaID, media);
59ab4d0f 287
7ed8dd25 288 var listItem = this._listItems.get(~~media.mediaID);
59ab4d0f
MS
289 var p = elByClass('mediaTitle', listItem)[0];
290 if (media.isMultilingual) {
c2e9de94
MS
291 if (media.title && media.title[LANGUAGE_ID]) {
292 p.textContent = media.title[LANGUAGE_ID];
293 }
294 else {
295 p.textContent = media.filename;
296 }
59ab4d0f
MS
297 }
298 else {
c2e9de94
MS
299 if (media.title && media.title[media.languageID]) {
300 p.textContent = media.title[media.languageID];
301 }
302 else {
303 p.textContent = media.filename;
304 }
305 }
306
307 var thumbnail = elByClass('mediaThumbnail', listItem)[0];
308 thumbnail.innerHTML = media.elementTag;
309 // Bust browser cache by adding additional parameter.
310 var imgs = elByTag('img', thumbnail);
311 if (imgs.length) {
312 imgs[0].src += '&refresh=1';
59ab4d0f
MS
313 }
314 },
315
e2a882df
MS
316 /**
317 * Initializes the dialog pagination.
318 *
319 * @param {integer} pageCount
320 * @param {integer} pageNo
321 */
322 _initPagination: function(pageCount, pageNo) {
323 if (pageNo === undefined) pageNo = 1;
324
325 if (pageCount > 1) {
326 var newPagination = elCreate('div');
327 newPagination.className = 'paginationBottom jsPagination';
328 DomUtil.replaceElement(elBySel('.jsPagination', UiDialog.getDialog(this).content), newPagination);
329
330 this._pagination = new UiPagination(newPagination, {
331 activePage: pageNo,
332 callbackSwitch: this._search.search.bind(this._search),
333 maxPage: pageCount
334 });
335 }
336 else if (this._pagination) {
337 elHide(this._pagination.getElement());
338 }
339 },
340
59a19269
MS
341 /**
342 * Removes all media clipboard checkboxes.
343 */
344 _removeClipboardCheckboxes: function() {
345 var checkboxes = elByClass('mediaCheckbox', this._mediaManagerMediaList);
346 while (checkboxes.length) {
347 elRemove(checkboxes[0]);
348 }
349 },
350
83e795ec
MS
351 /**
352 * Opens the media editor after uploading a single file.
353 *
354 * @param {object} data upload event data
dd2d8c0c 355 * @since 5.2
83e795ec
MS
356 */
357 _openEditorAfterUpload: function(data) {
0ebfe8d4 358 if (data.upload === this._upload && !data.isMultiFileUpload && !this._upload.hasPendingUploads()) {
83e795ec
MS
359 var keys = Object.keys(data.media);
360
361 if (keys.length) {
362 UiDialog.close(this);
363
364 this._mediaEditor.edit(this._media.get(~~data.media[keys[0]].mediaID));
365 }
366 }
367 },
368
56eb7314
MS
369 /**
370 * Sets the displayed media (after a search).
371 *
372 * @param {Dictionary} media media to be set as active
373 */
374 _setMedia: function(media) {
59ab4d0f
MS
375 if (Core.isPlainObject(media)) {
376 this._media = Dictionary.fromObject(media);
377 }
378 else {
379 this._media = media;
380 }
381
382 var info = DomTraverse.nextByClass(this._mediaManagerMediaList, 'info');
383
384 if (this._media.size) {
385 if (info) {
386 elHide(info);
387 }
388 }
389 else {
390 if (info === null) {
391 info = elCreate('p');
392 info.className = 'info';
393 info.textContent = Language.get('wcf.media.search.noResults');
394 }
395
396 elShow(info);
397 DomUtil.insertAfter(info, this._mediaManagerMediaList);
398 }
399
400 var mediaListItems = DomTraverse.childrenByTag(this._mediaManagerMediaList, 'LI');
401 for (var i = 0, length = mediaListItems.length; i < length; i++) {
402 var listItem = mediaListItems[i];
403
404 if (!this._media.has(elData(listItem, 'object-id'))) {
405 elHide(listItem);
406 }
407 else {
408 elShow(listItem);
409 }
410 }
411
412 DomChangeListener.trigger();
413
59a19269
MS
414 if (Permission.get('admin.content.cms.canManageMedia') || this._forceClipboard) {
415 Clipboard.reload();
416 }
417 else {
418 this._removeClipboardCheckboxes();
419 }
59ab4d0f
MS
420 },
421
422 /**
423 * Adds a media file to the manager.
424 *
425 * @param {object} media data of the media file
426 * @param {Element} listItem list item representing the file
427 */
428 addMedia: function(media, listItem) {
429 if (!media.languageID) media.isMultilingual = 1;
430
7ed8dd25
MS
431 this._media.set(~~media.mediaID, media);
432 this._listItems.set(~~media.mediaID, listItem);
59ab4d0f 433
7ed8dd25 434 if (this._listItems.size === 1) {
59ab4d0f 435 this._search.showSearch();
59ab4d0f
MS
436 }
437 },
56eb7314 438
599b8699
MS
439 /**
440 * Is called after the media files with the given ids have been deleted via clipboard.
441 *
442 * @param {int[]} mediaIds ids of deleted media files
443 */
444 clipboardDeleteMedia: function(mediaIds) {
445 for (var i = 0, length = mediaIds.length; i < length; i++) {
446 this.removeMedia(~~mediaIds[i], true);
447 }
448
449 UiNotification.show();
450 },
451
cf4e950c
MS
452 /**
453 * Returns the id of the currently selected category or `0` if no category is selected.
454 *
455 * @return {integer}
456 */
457 getCategoryId: function() {
458 if (this._mediaCategorySelect) {
459 return this._mediaCategorySelect.value;
460 }
461
462 return 0;
463 },
464
5c1cc5ff
MS
465 /**
466 * Returns the media manager dialog element.
467 *
468 * @return {Element} media manager dialog
469 */
470 getDialog: function() {
471 return UiDialog.getDialog(this).dialog;
472 },
473
56eb7314
MS
474 /**
475 * Returns the mode of the media manager.
476 *
477 * @return {string}
478 */
479 getMode: function() {
480 return '';
481 },
482
483 /**
484 * Returns the media manager option with the given name.
485 *
486 * @param {string} name option name
487 * @return {mixed} option value or null
488 */
489 getOption: function(name) {
490 if (this._options[name]) {
491 return this._options[name];
492 }
493
494 return null;
495 },
496
59ab4d0f
MS
497 /**
498 * Removes a media file.
499 *
500 * @param {int} mediaId id of the removed media file
56eb7314 501 */
e2a882df 502 removeMedia: function(mediaId) {
7ed8dd25 503 if (this._listItems.has(mediaId)) {
59ab4d0f 504 // remove list item
2b11f76d 505 try {
7ed8dd25 506 elRemove(this._listItems.get(mediaId));
2b11f76d
MS
507 }
508 catch (e) {
509 // ignore errors if item has already been removed like by WCF.Action.Delete
510 }
56eb7314 511
7ed8dd25 512 this._listItems.delete(mediaId);
59ab4d0f 513 this._media.delete(mediaId);
59ab4d0f 514 }
59ab4d0f
MS
515 },
516
517 /**
518 * Changes the displayed media to the previously displayed media.
519 */
520 resetMedia: function() {
e2a882df
MS
521 // calling WoltLabSuite/Core/Media/Manager/Search.search() reloads the first page of the dialog
522 this._search.search();
59ab4d0f
MS
523 },
524
525 /**
526 * Sets the media files currently displayed.
527 *
528 * @param {object} media media data
529 * @param {string} template
e2a882df 530 * @param {object} additionalData
59ab4d0f 531 */
e2a882df 532 setMedia: function(media, template, additionalData) {
59ab4d0f
MS
533 var hasMedia = false;
534 for (var mediaId in media) {
f5336f4f 535 if (objOwns(media, mediaId)) {
59ab4d0f
MS
536 hasMedia = true;
537 }
538 }
539
540 var newListItems = [];
541 if (hasMedia) {
542 var ul = elCreate('ul');
543 ul.innerHTML = template;
544
545 var listItems = DomTraverse.childrenByTag(ul, 'LI');
546 for (var i = 0, length = listItems.length; i < length; i++) {
547 var listItem = listItems[i];
7ed8dd25
MS
548 if (!this._listItems.has(~~elData(listItem, 'object-id'))) {
549 this._listItems.set(elData(listItem, 'object-id'), listItem);
59ab4d0f
MS
550
551 this._mediaManagerMediaList.appendChild(listItem);
552 }
553 }
554 }
555
e2a882df
MS
556 this._initPagination(additionalData.pageCount, additionalData.pageNo);
557
59ab4d0f 558 this._setMedia(media);
56eb7314
MS
559 },
560
561 /**
562 * Sets up a new media element.
563 *
564 * @param {object} media data of the media file
565 * @param {HTMLElement} mediaElement element representing the media file
566 */
567 setupMediaElement: function(media, mediaElement) {
568 var mediaInformation = DomTraverse.childByClass(mediaElement, 'mediaInformation');
569
570 var buttonGroupNavigation = elCreate('nav');
a051b5a7 571 buttonGroupNavigation.className = 'jsMobileNavigation buttonGroupNavigation';
56eb7314
MS
572 mediaInformation.parentNode.appendChild(buttonGroupNavigation);
573
a051b5a7
MS
574 var buttons = elCreate('ul');
575 buttons.className = 'buttonList iconList';
576 buttonGroupNavigation.appendChild(buttons);
56eb7314
MS
577
578 var listItem = elCreate('li');
c5dd767e 579 listItem.className = 'mediaCheckbox';
a051b5a7
MS
580 buttons.appendChild(listItem);
581
582 var a = elCreate('a');
583 listItem.appendChild(a);
584
585 var label = elCreate('label');
586 a.appendChild(label);
56eb7314
MS
587
588 var checkbox = elCreate('input');
74a6f0de 589 checkbox.className = 'jsClipboardItem';
56eb7314
MS
590 elAttr(checkbox, 'type', 'checkbox');
591 elData(checkbox, 'object-id', media.mediaID);
a051b5a7 592 label.appendChild(checkbox);
56eb7314
MS
593
594 if (Permission.get('admin.content.cms.canManageMedia')) {
595 listItem = elCreate('li');
a051b5a7
MS
596 listItem.className = 'jsMediaEditButton';
597 elData(listItem, 'object-id', media.mediaID);
598 buttons.appendChild(listItem);
56eb7314 599
a051b5a7 600 listItem.innerHTML = '<a><span class="icon icon16 fa-pencil jsTooltip" title="' + Language.get('wcf.global.button.edit') + '"></span> <span class="invisible">' + Language.get('wcf.global.button.edit') + '</span></a>';
56eb7314
MS
601
602 listItem = elCreate('li');
a051b5a7
MS
603 listItem.className = 'jsDeleteButton';
604 elData(listItem, 'object-id', media.mediaID);
b0969180
MS
605
606 // use temporary title to not unescape html in filename
607 var uuid = Core.getUuid();
608 elData(listItem, 'confirm-message-html', StringUtil.unescapeHTML(Language.get('wcf.media.delete.confirmMessage', {
609 title: uuid
610 })).replace(uuid, StringUtil.escapeHTML(media.filename)));
a051b5a7 611 buttons.appendChild(listItem);
56eb7314 612
a051b5a7 613 listItem.innerHTML = '<a><span class="icon icon16 fa-times jsTooltip" title="' + Language.get('wcf.global.button.delete') + '"></span> <span class="invisible">' + Language.get('wcf.global.button.delete') + '</span></a>';
56eb7314 614 }
59ab4d0f
MS
615 }
616 };
617
56eb7314 618 return MediaManagerBase;
59ab4d0f 619});