Fix title of data section when editing conversation label
[GitHub/WoltLab/com.woltlab.wcf.conversation.git] / files / js / WCF.Conversation.js
CommitLineData
7f584dca
AE
1/**
2 * Namespace for conversations.
5e279c42
AE
3 *
4 * @author Alexander Ebert
ca7ac0d3 5 * @copyright 2001-2018 WoltLab GmbH
5e279c42 6 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
7f584dca
AE
7 */
8WCF.Conversation = { };
9
5e279c42
AE
10/**
11 * Core editor handler for conversations.
5e279c42 12 */
7f584dca 13WCF.Conversation.EditorHandler = Class.extend({
5e279c42
AE
14 /**
15 * list of attributes per conversation
16 * @var object
17 */
7f584dca 18 _attributes: { },
5e279c42
AE
19
20 /**
21 * list of conversations
22 * @var object
23 */
7f584dca 24 _conversations: { },
5e279c42
AE
25
26 /**
27 * list of permissions per conversation
28 * @var object
29 */
7f584dca
AE
30 _permissions: { },
31
5e279c42
AE
32 /**
33 * Initializes the core editor handler for conversations.
5e279c42 34 */
2e0bc870 35 init: function(availableLabels) {
7f584dca
AE
36 this._conversations = { };
37
38 var self = this;
39 $('.conversation').each(function(index, conversation) {
40 var $conversation = $(conversation);
41 var $conversationID = $conversation.data('conversationID');
42
43 if (!self._conversations[$conversationID]) {
44 self._conversations[$conversationID] = $conversation;
b89bd2bc 45 var $labelIDs = eval($conversation.data('labelIDs'));
7f584dca
AE
46
47 // set attributes
48 self._attributes[$conversationID] = {
b89bd2bc
AE
49 isClosed: ($conversation.data('isClosed') ? true : false),
50 labelIDs: $labelIDs
7f584dca
AE
51 };
52
53 // set permissions
54 self._permissions[$conversationID] = {
65b37bf6 55 canAddParticipants: ($conversation.data('canAddParticipants') ? true : false),
7f584dca
AE
56 canCloseConversation: ($conversation.data('canCloseConversation') ? true : false)
57 };
58 }
59 });
60 },
61
5e279c42
AE
62 /**
63 * Returns a permission's value for given conversation id.
64 *
65 * @param integer conversationID
66 * @param string permission
67 * @return boolean
68 */
7f584dca
AE
69 getPermission: function(conversationID, permission) {
70 if (this._permissions[conversationID][permission] === undefined) {
71 return false;
72 }
73
74 return (this._permissions[conversationID][permission]) ? true : false;
75 },
76
5e279c42
AE
77 /**
78 * Returns an attribute's value for given conversation id.
79 *
80 * @param integer conversationID
81 * @param string key
82 * @return mixed
83 */
7f584dca
AE
84 getValue: function(conversationID, key) {
85 switch (key) {
86 case 'labelIDs':
87 if (this._attributes[conversationID].labelIDs === undefined) {
7f584dca
AE
88 this._attributes[conversationID].labelIDs = [ ];
89 }
90
91 return this._attributes[conversationID].labelIDs;
92 break;
93
94 case 'isClosed':
95 return (this._attributes[conversationID].isClosed) ? true : false;
96 break;
97 }
98 },
99
5e279c42
AE
100 /**
101 * Counts available labels.
102 *
103 * @return integer
104 */
7f584dca 105 countAvailableLabels: function() {
b89bd2bc
AE
106 return (this.getAvailableLabels()).length;
107 },
108
db864366
MS
109 /**
110 * Returns a list with the data of the available labels.
111 *
112 * @return array<object>
113 */
b89bd2bc
AE
114 getAvailableLabels: function() {
115 var $labels = [ ];
116
00eda4be 117 WCF.Dropdown.getDropdownMenu('conversationLabelFilter').children('.scrollableDropdownMenu').children('li').each(function(index, listItem) {
b89bd2bc
AE
118 var $listItem = $(listItem);
119 if ($listItem.hasClass('dropdownDivider')) {
120 return false;
121 }
122
123 var $span = $listItem.find('span');
124 $labels.push({
125 cssClassName: $span.data('cssClassName'),
126 labelID: $span.data('labelID'),
127 label: $span.text()
128 });
129 });
130
131 return $labels;
3990815c
AE
132 },
133
134 /**
135 * Updates conversation data.
136 *
137 * @param integer conversationID
138 * @param object data
139 */
140 update: function(conversationID, key, data) {
141 if (!this._conversations[conversationID]) {
142 console.debug("[WCF.Conversation.EditorHandler] Unknown conversation id '" + conversationID + "'");
143 return;
144 }
145 var $conversation = this._conversations[conversationID];
146
147 switch (key) {
c78d591e 148 case 'close':
7cd36813 149 $('<li><span class="icon icon16 fa-lock jsTooltip jsIconLock" title="' + WCF.Language.get('wcf.global.state.closed') + '" /></li>').prependTo($conversation.find('.statusIcons'));
c78d591e
AE
150
151 this._attributes[conversationID].isClosed = 1;
152 break;
153
3990815c
AE
154 case 'labelIDs':
155 var $labels = { };
443fbc56 156 WCF.Dropdown.getDropdownMenu('conversationLabelFilter').find('li > a > span').each(function(index, span) {
3990815c
AE
157 var $span = $(span);
158
159 $labels[$span.data('labelID')] = {
160 cssClassName: $span.data('cssClassName'),
9bbe55ef
AE
161 label: $span.text(),
162 url: $span.parent().attr('href')
3990815c
AE
163 };
164 });
165
9cd031f5 166 var $labelList = $conversation.find('.columnSubject > .labelList');
3990815c
AE
167 if (!data.length) {
168 if ($labelList.length) $labelList.remove();
169 }
170 else {
171 // create label list if missing
172 if (!$labelList.length) {
9cd031f5 173 $labelList = $('<ul class="labelList" />').prependTo($conversation.find('.columnSubject'));
3990815c
AE
174 }
175
176 // remove all existing labels
177 $labelList.empty();
178
179 // insert labels
180 for (var $i = 0, $length = data.length; $i < $length; $i++) {
181 var $label = $labels[data[$i]];
f61e621c 182 $('<li><a href="' + $label.url + '" class="badge label' + ($label.cssClassName ? " " + $label.cssClassName : "") + '">' + WCF.String.escapeHTML($label.label) + '</a></li>').appendTo($labelList);
3990815c
AE
183 }
184 }
185 break;
c78d591e
AE
186
187 case 'open':
188 $conversation.find('.statusIcons li').each(function(index, listItem) {
189 var $listItem = $(listItem);
caaf1cde 190 if ($listItem.children('span.jsIconLock').length) {
c78d591e
AE
191 $listItem.remove();
192 return false;
193 }
194 });
195
196 this._attributes[conversationID].isClosed = 0;
197 break;
3990815c 198 }
ae0cf915
AE
199
200 WCF.Clipboard.reload();
7f584dca
AE
201 }
202});
203
2e0bc870
AE
204/**
205 * Conversation editor handler for conversation page.
206 *
207 * @see WCF.Conversation.EditorHandler
208 * @param array<object> availableLabels
209 */
210WCF.Conversation.EditorHandlerConversation = WCF.Conversation.EditorHandler.extend({
211 /**
212 * list of available labels
213 * @var array<object>
214 */
215 _availableLabels: null,
216
217 /**
218 * @see WCF.Conversation.EditorHandler.init()
219 *
220 * @param array<object> availableLabels
221 */
222 init: function(availableLabels) {
223 this._availableLabels = availableLabels || [ ];
224
225 this._super();
226 },
227
228 /**
229 * @see WCF.Conversation.EditorHandler.getAvailableLabels()
230 */
231 getAvailableLabels: function() {
232 return this._availableLabels;
233 },
234
235 /**
236 * @see WCF.Conversation.EditorHandler.update()
237 */
238 update: function(conversationID, key, data) {
239 if (!this._conversations[conversationID]) {
240 console.debug("[WCF.Conversation.EditorHandler] Unknown conversation id '" + conversationID + "'");
241 return;
242 }
2e0bc870 243
f61e621c
AE
244 var container = $('.contentHeaderTitle > .contentHeaderMetaData');
245
2e0bc870 246 switch (key) {
a132491c 247 case 'close':
f61e621c 248 $('<li><span class="icon icon16 fa-lock jsIconLock" /> ' + WCF.Language.get('wcf.global.state.closed') + '</li>').appendTo(container);
a132491c
AE
249
250 this._attributes[conversationID].isClosed = 1;
251 break;
252
2e0bc870 253 case 'labelIDs':
f61e621c 254 var labelList = container.find('.labelList');
2e0bc870 255 if (!data.length) {
f61e621c 256 labelList.parent().remove();
2e0bc870
AE
257 }
258 else {
f61e621c 259 var availableLabels = this.getAvailableLabels();
2e0bc870 260
f61e621c
AE
261 if (!labelList.length) {
262 labelList = $('<li><span class="icon icon16 fa-tags"></span> <ul class="labelList"></ul></li>').prependTo(container);
263 labelList = labelList.children('ul');
264 }
2e0bc870 265
f61e621c
AE
266 var html = '';
267 data.forEach(function(labelId) {
268 availableLabels.forEach(function(label) {
269 if (label.labelID == labelId) {
270 html += '<li><span class="label badge' + (label.cssClassName ? ' ' + label.cssClassName : '') + '">' + label.label + '</span></li>';
2e0bc870 271 }
f61e621c
AE
272 });
273 });
274
275 labelList[0].innerHTML = html;
2e0bc870
AE
276 }
277 break;
a132491c
AE
278
279 case 'open':
f61e621c 280 container.find('.jsIconLock').parent().remove();
a132491c
AE
281
282 this._attributes[conversationID].isClosed = 0;
283 break;
2e0bc870
AE
284 }
285 }
286});
287
6f1bbea0 288/**
559c6734 289 * Provides extended actions for conversation clipboard actions.
6f1bbea0
AE
290 */
291WCF.Conversation.Clipboard = Class.extend({
292 /**
293 * editor handler
294 * @var WCF.Conversation.EditorHandler
295 */
296 _editorHandler: null,
297
298 /**
299 * Initializes a new WCF.Conversation.Clipboard object.
300 *
f61e621c 301 * @param {WCF.Conversation.EditorHandler} editorHandler
6f1bbea0
AE
302 */
303 init: function(editorHandler) {
304 this._editorHandler = editorHandler;
305
f61e621c
AE
306 WCF.System.Event.addListener('com.woltlab.wcf.clipboard', 'com.woltlab.wcf.conversation.conversation', (function (data) {
307 if (data.responseData === null) {
308 this._execute(data.data.actionName, data.data.parameters);
309 }
310 else {
311 this._evaluateResponse(data.data.actionName, data.responseData);
6f1bbea0 312 }
f61e621c 313 }).bind(this));
6f1bbea0
AE
314 },
315
316 /**
317 * Handles clipboard actions.
318 *
f61e621c
AE
319 * @param {string} actionName
320 * @param {Object} parameters
6f1bbea0 321 */
f61e621c
AE
322 _execute: function(actionName, parameters) {
323 if (actionName === 'com.woltlab.wcf.conversation.conversation.assignLabel') {
4981a270 324 new WCF.Conversation.Label.Editor(this._editorHandler, null, parameters.objectIDs);
6f1bbea0 325 }
e8fe47c2
AE
326 },
327
328 /**
329 * Evaluates AJAX responses.
330 *
f61e621c
AE
331 * @param {Object} data
332 * @param {string} actionName
e8fe47c2 333 */
f61e621c 334 _evaluateResponse: function(actionName, data) {
e8fe47c2 335 switch (actionName) {
d78f31a6
MS
336 case 'com.woltlab.wcf.conversation.conversation.leave':
337 case 'com.woltlab.wcf.conversation.conversation.leavePermanently':
4ca3137c 338 case 'com.woltlab.wcf.conversation.conversation.markAsRead':
d78f31a6 339 case 'com.woltlab.wcf.conversation.conversation.restore':
e8fe47c2
AE
340 window.location.reload();
341 break;
3d0a3f74 342
d78f31a6
MS
343 case 'com.woltlab.wcf.conversation.conversation.close':
344 case 'com.woltlab.wcf.conversation.conversation.open':
f61e621c
AE
345 //noinspection JSUnresolvedVariable
346 for (var conversationId in data.returnValues.conversationData) {
347 //noinspection JSUnresolvedVariable
348 if (data.returnValues.conversationData.hasOwnProperty(conversationId)) {
349 //noinspection JSUnresolvedVariable
350 var $data = data.returnValues.conversationData[conversationId];
351
352 this._editorHandler.update(conversationId, ($data.isClosed ? 'close' : 'open'), $data);
353 }
3d0a3f74
AE
354 }
355 break;
e8fe47c2 356 }
6f1bbea0
AE
357 }
358});
359
5e279c42
AE
360/**
361 * Inline editor implementation for conversations.
362 *
363 * @see WCF.Inline.Editor
364 */
7f584dca 365WCF.Conversation.InlineEditor = WCF.InlineEditor.extend({
5e279c42
AE
366 /**
367 * editor handler object
368 * @var WCF.Conversation.EditorHandler
369 */
7f584dca
AE
370 _editorHandler: null,
371
c78d591e
AE
372 /**
373 * execution environment
374 * @var string
375 */
376 _environment: 'conversation',
377
7f584dca
AE
378 /**
379 * @see WCF.InlineEditor._setOptions()
380 */
381 _setOptions: function() {
382 this._options = [
4251df82
AE
383 // edit title
384 { label: WCF.Language.get('wcf.conversation.edit.subject'), optionName: 'editSubject' },
385
7f584dca
AE
386 // isClosed
387 { label: WCF.Language.get('wcf.conversation.edit.close'), optionName: 'close' },
388 { label: WCF.Language.get('wcf.conversation.edit.open'), optionName: 'open' },
389
390 // assign labels
391 { label: WCF.Language.get('wcf.conversation.edit.assignLabel'), optionName: 'assignLabel' },
392
393 // divider
394 { optionName: 'divider' },
395
65b37bf6
AE
396 // add participants
397 { label: WCF.Language.get('wcf.conversation.edit.addParticipants'), optionName: 'addParticipants' },
398
7f584dca 399 // leave conversation
7d269a63
MW
400 { label: WCF.Language.get('wcf.conversation.edit.leave'), optionName: 'leave' },
401
402 // edit draft
403 { label: WCF.Language.get('wcf.global.button.edit'), optionName: 'edit', isQuickOption: true }
7f584dca
AE
404 ];
405 },
406
5e279c42
AE
407 /**
408 * Sets editor handler object.
409 *
410 * @param WCF.Conversation.EditorHandler editorHandler
c78d591e 411 * @param string environment
5e279c42 412 */
c78d591e 413 setEditorHandler: function(editorHandler, environment) {
7f584dca 414 this._editorHandler = editorHandler;
c78d591e 415 this._environment = (environment == 'list') ? 'list' : 'conversation';
7f584dca
AE
416 },
417
418 /**
419 * @see WCF.InlineEditor._getTriggerElement()
420 */
421 _getTriggerElement: function(element) {
1a3cec9c 422 return element.find('.jsConversationInlineEditor');
7f584dca
AE
423 },
424
425 /**
426 * @see WCF.InlineEditor._validate()
427 */
428 _validate: function(elementID, optionName) {
429 var $conversationID = $('#' + elementID).data('conversationID');
430
431 switch (optionName) {
65b37bf6
AE
432 case 'addParticipants':
433 return (this._editorHandler.getPermission($conversationID, 'canAddParticipants'));
434 break;
435
7f584dca
AE
436 case 'assignLabel':
437 return (this._editorHandler.countAvailableLabels()) ? true : false;
438 break;
439
4251df82
AE
440 case 'editSubject':
441 return (!!this._editorHandler.getPermission($conversationID, 'canCloseConversation'));
442 break;
443
7f584dca
AE
444 case 'close':
445 case 'open':
446 if (!this._editorHandler.getPermission($conversationID, 'canCloseConversation')) {
447 return false;
448 }
449
5cb8c349
AE
450 if (optionName === 'close') return !(this._editorHandler.getValue($conversationID, 'isClosed'));
451 else return (this._editorHandler.getValue($conversationID, 'isClosed'));
7f584dca
AE
452 break;
453
454 case 'leave':
455 return true;
456 break;
7d269a63
MW
457
458 case 'edit':
459 return ($('#' + elementID).data('isDraft') ? true : false);
460 break;
7f584dca
AE
461 }
462
463 return false;
3990815c
AE
464 },
465
466 /**
467 * @see WCF.InlineEditor._execute()
468 */
469 _execute: function(elementID, optionName) {
470 // abort if option is invalid or not accessible
471 if (!this._validate(elementID, optionName)) {
472 return false;
473 }
474
475 switch (optionName) {
65b37bf6 476 case 'addParticipants':
38340e3b 477 require(['WoltLabSuite/Core/Conversation/Ui/Participant/Add'], function(UiParticipantAdd) {
4251df82 478 new UiParticipantAdd(elData(elById(elementID), 'conversation-id'));
368540c1 479 });
65b37bf6
AE
480 break;
481
3990815c
AE
482 case 'assignLabel':
483 new WCF.Conversation.Label.Editor(this._editorHandler, elementID);
484 break;
50cd21a1 485
4251df82
AE
486 case 'editSubject':
487 require(['WoltLabSuite/Core/Conversation/Ui/Subject/Editor'], function (UiSubjectEditor) {
488 UiSubjectEditor.beginEdit(elData(elById(elementID), 'conversation-id'));
489 });
490 break;
491
2e0bc870
AE
492 case 'close':
493 case 'open':
494 this._updateConversation(elementID, optionName, { isClosed: (optionName === 'close' ? 1 : 0) });
495 break;
496
50cd21a1 497 case 'leave':
b2e0a2ad 498 new WCF.Conversation.Leave([ $('#' + elementID).data('conversationID') ], this._environment);
50cd21a1 499 break;
7d269a63
MW
500
501 case 'edit':
502 window.location = this._getTriggerElement($('#' + elementID)).prop('href');
503 break;
50cd21a1 504 }
2e0bc870
AE
505 },
506
507 /**
508 * Updates conversation properties.
509 *
510 * @param string elementID
511 * @param string optionName
512 * @param object data
513 */
514 _updateConversation: function(elementID, optionName, data) {
515 var $conversationID = this._elements[elementID].data('conversationID');
516
517 switch (optionName) {
518 case 'close':
4251df82 519 case 'editSubject':
2e0bc870 520 case 'open':
c78d591e
AE
521 this._updateData.push({
522 elementID: elementID,
523 optionName: optionName,
524 data: data
2e0bc870 525 });
c78d591e
AE
526
527 this._proxy.setOption('data', {
528 actionName: optionName,
529 className: 'wcf\\data\\conversation\\ConversationAction',
530 objectIDs: [ $conversationID ]
531 });
532 this._proxy.sendRequest();
2e0bc870
AE
533 break;
534 }
c78d591e
AE
535 },
536
537 /**
538 * @see WCF.InlineEditor._updateState()
539 */
540 _updateState: function() {
541 for (var $i = 0, $length = this._updateData.length; $i < $length; $i++) {
542 var $data = this._updateData[$i];
543 var $conversationID = this._elements[$data.elementID].data('conversationID');
544
545 switch ($data.optionName) {
546 case 'close':
4251df82 547 case 'editSubject':
c78d591e 548 case 'open':
4251df82 549 this._editorHandler.update($conversationID, $data.optionName, $data.data);
c78d591e
AE
550 break;
551 }
552 }
50cd21a1
AE
553 }
554});
555
556/**
557 * Provides a dialog for leaving or restoring conversation.
558 *
559 * @param array<integer> conversationIDs
560 */
561WCF.Conversation.Leave = Class.extend({
562 /**
563 * list of conversation ids
564 * @var array<integer>
565 */
566 _conversationIDs: [ ],
567
568 /**
569 * dialog overlay
570 * @var jQuery
571 */
572 _dialog: null,
573
b2e0a2ad
AE
574 /**
575 * environment name
576 * @var string
577 */
578 _environment: '',
579
50cd21a1
AE
580 /**
581 * action proxy
582 * @var WCF.Action.Proxy
583 */
584 _proxy: null,
585
586 /**
587 * Initializes the leave/restore dialog for conversations.
588 *
589 * @param array<integer> conversationIDs
b2e0a2ad 590 * @param string environment
50cd21a1 591 */
b2e0a2ad 592 init: function(conversationIDs, environment) {
50cd21a1
AE
593 this._conversationIDs = conversationIDs;
594 this._dialog = null;
b2e0a2ad 595 this._environment = environment;
50cd21a1
AE
596
597 this._proxy = new WCF.Action.Proxy({
598 success: $.proxy(this._success, this)
599 });
600
601 this._loadDialog();
602 },
603
604 /**
605 * Loads the dialog overlay.
606 */
a480521b 607 _loadDialog: function() {
50cd21a1
AE
608 this._proxy.setOption('data', {
609 actionName: 'getLeaveForm',
610 className: 'wcf\\data\\conversation\\ConversationAction',
2f5b4859 611 objectIDs: this._conversationIDs
50cd21a1
AE
612 });
613 this._proxy.sendRequest();
614 },
615
616 /**
617 * Handles successful AJAX requests.
618 *
619 * @param object data
620 * @param string textStatus
621 * @param jQuery jqXHR
622 */
623 _success: function(data, textStatus, jqXHR) {
624 switch (data.returnValues.actionName) {
625 case 'getLeaveForm':
626 this._showDialog(data);
627 break;
628
629 case 'hideConversation':
b2e0a2ad
AE
630 if (this._environment === 'conversation') {
631 window.location = data.returnValues.redirectURL;
632 }
633 else {
634 window.location.reload();
635 }
50cd21a1
AE
636 break;
637 }
638 },
639
640 /**
641 * Displays the leave/restore conversation dialog overlay.
642 *
643 * @param object data
644 */
645 _showDialog: function(data) {
646 if (this._dialog === null) {
647 this._dialog = $('#leaveDialog');
648 if (!this._dialog.length) {
649 this._dialog = $('<div id="leaveDialog" />').hide().appendTo(document.body);
650 }
651 }
652
653 // render dialog
654 this._dialog.html(data.returnValues.template);
655 this._dialog.wcfDialog({
656 title: WCF.Language.get('wcf.conversation.leave.title')
657 });
658
659 this._dialog.wcfDialog('render');
660
661 // bind event listener
662 this._dialog.find('#hideConversation').click($.proxy(this._click, this));
663 },
664
665 /**
666 * Handles conversation state changes.
667 */
668 _click: function() {
669 var $input = this._dialog.find('input[type=radio]:checked');
670 if ($input.length === 1) {
671 this._proxy.setOption('data', {
672 actionName: 'hideConversation',
673 className: 'wcf\\data\\conversation\\ConversationAction',
2f5b4859 674 objectIDs: this._conversationIDs,
50cd21a1 675 parameters: {
50cd21a1
AE
676 hideConversation: $input.val()
677 }
678 });
679 this._proxy.sendRequest();
3990815c
AE
680 }
681 }
682});
683
a208d1f4
AE
684/**
685 * Provides methods to remove participants from conversations.
686 *
687 * @see WCF.Action.Delete
688 */
689WCF.Conversation.RemoveParticipant = WCF.Action.Delete.extend({
690 /**
691 * conversation id
692 * @var integer
693 */
694 _conversationID: 0,
695
696 /**
697 * @see WCF.Action.Delete.init()
698 */
699 init: function(conversationID) {
700 this._conversationID = conversationID;
701 this._super('wcf\\data\\conversation\\ConversationAction', '.jsParticipant');
702 },
703
704 /**
705 * @see WCF.Action.Delete._sendRequest()
706 */
707 _sendRequest: function(object) {
708 this.proxy.setOption('data', {
709 actionName: 'removeParticipant',
710 className: this._className,
711 objectIDs: [ this._conversationID ],
712 parameters: {
713 userID: $(object).data('objectID')
714 }
715 });
716
717 this.proxy.sendRequest();
718 },
719
720 /**
721 * @see WCF.Action.Delete._success()
722 */
723 _success: function(data, textStatus, jqXHR) {
e829b862
MW
724 var $userID = data.returnValues.userID;
725
726 for (var $index in this._containers) {
727 var $container = $('#' + this._containers[$index]);
728 if ($container.find('.jsDeleteButton').data('objectID') == $userID) {
729 $container.find('.userLink').addClass('conversationLeft');
730 $container.find('.jsDeleteButton').remove();
731 }
732 }
a208d1f4
AE
733 }
734});
735
3990815c
AE
736/**
737 * Namespace for label-related classes.
738 */
739WCF.Conversation.Label = { };
740
741/**
742 * Providers an editor for conversation labels.
743 *
744 * @param WCF.Conversation.EditorHandler editorHandler
745 * @param string elementID
f26185f2 746 * @param array<integer> conversationIDs
3990815c
AE
747 */
748WCF.Conversation.Label.Editor = Class.extend({
749 /**
f26185f2
AE
750 * list of conversation id
751 * @var array<integer>
3990815c 752 */
f26185f2 753 _conversationIDs: 0,
3990815c
AE
754
755 /**
756 * dialog object
757 * @var jQuery
758 */
759 _dialog: null,
760
761 /**
762 * editor handler object
763 * @var WCF.Conversation.EditorHandler
764 */
765 _editorHandler: null,
766
767 /**
768 * system notification object
769 * @var WCF.System.Notification
770 */
771 _notification: null,
772
773 /**
774 * action proxy object
775 * @var WCF.Action.Proxy
776 */
777 _proxy: null,
778
779 /**
780 * Initializes the label editor for given conversation.
781 *
782 * @param WCF.Conversation.EditorHandler editorHandler
783 * @param string elementID
f26185f2 784 * @param array<integer> conversationIDs
3990815c 785 */
f26185f2
AE
786 init: function(editorHandler, elementID, conversationIDs) {
787 if (elementID) {
788 this._conversationIDs = [ $('#' + elementID).data('conversationID') ];
789 }
790 else {
791 this._conversationIDs = conversationIDs;
792 }
793
3990815c
AE
794 this._dialog = null;
795 this._editorHandler = editorHandler;
796
8ba68f57 797 this._notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success.edit'));
3990815c
AE
798 this._proxy = new WCF.Action.Proxy({
799 success: $.proxy(this._success, this)
800 });
801
802 this._loadDialog();
803 },
804
805 /**
806 * Loads label assignment dialog.
807 */
808 _loadDialog: function() {
809 this._proxy.setOption('data', {
810 actionName: 'getLabelForm',
811 className: 'wcf\\data\\conversation\\label\\ConversationLabelAction',
812 parameters: {
f26185f2 813 conversationIDs: this._conversationIDs
3990815c
AE
814 }
815 });
816 this._proxy.sendRequest();
817 },
818
819 /**
820 * Handles successful AJAX requests.
821 *
822 * @param object data
823 * @param string textStatus
824 * @param jQuery jqXHR
825 */
826 _success: function(data, textStatus, jqXHR) {
827 switch (data.returnValues.actionName) {
828 case 'assignLabel':
829 this._assignLabels(data);
830 break;
831
832 case 'getLabelForm':
833 this._renderDialog(data);
834 break;
835 }
836 },
837
838 /**
839 * Renders the label assignment form overlay.
840 *
841 * @param object data
842 */
843 _renderDialog: function(data) {
844 if (this._dialog === null) {
845 this._dialog = $('#conversationLabelForm');
846 if (!this._dialog.length) {
847 this._dialog = $('<div id="conversationLabelForm" />').hide().appendTo(document.body);
848 }
849 }
850
851 this._dialog.html(data.returnValues.template);
852 this._dialog.wcfDialog({
853 title: WCF.Language.get('wcf.conversation.label.assignLabels')
854 });
855 this._dialog.wcfDialog('render');
856
857 $('#assignLabels').click($.proxy(this._save, this));
858 },
859
860 /**
861 * Saves label assignments for current conversation id.
862 */
863 _save: function() {
864 var $labelIDs = [ ];
865 this._dialog.find('input').each(function(index, checkbox) {
866 var $checkbox = $(checkbox);
867 if ($checkbox.is(':checked')) {
868 $labelIDs.push($checkbox.data('labelID'));
869 }
870 });
871
872 this._proxy.setOption('data', {
873 actionName: 'assignLabel',
874 className: 'wcf\\data\\conversation\\label\\ConversationLabelAction',
875 parameters: {
f26185f2 876 conversationIDs: this._conversationIDs,
3990815c
AE
877 labelIDs: $labelIDs
878 }
879 });
880 this._proxy.sendRequest();
881 },
882
883 /**
884 * Updates conversation labels.
885 *
886 * @param object data
887 */
888 _assignLabels: function(data) {
889 // update conversation
4981a270 890 for (var $i = 0, $length = this._conversationIDs.length; $i < $length; $i++) {
f26185f2
AE
891 this._editorHandler.update(this._conversationIDs[$i], 'labelIDs', data.returnValues.labelIDs);
892 }
3990815c
AE
893
894 // close dialog and show a 'success' notice
895 this._dialog.wcfDialog('close');
896 this._notification.show();
7f584dca 897 }
5e279c42
AE
898});
899
900/**
901 * Label manager for conversations.
902 *
903 * @param string link
904 */
3990815c 905WCF.Conversation.Label.Manager = Class.extend({
d4e2a0cc
AE
906 /**
907 * deleted label id
908 * @var integer
909 */
910 _deletedLabelID: 0,
911
5e279c42
AE
912 /**
913 * dialog object
914 * @var jQuery
915 */
916 _dialog: null,
917
918 /**
919 * list of labels
920 * @var jQuery
921 */
922 _labels: null,
923
924 /**
925 * parsed label link
926 * @var string
927 */
928 _link: '',
929
72f75266
AE
930 /**
931 * system notification object
932 * @var WCF.System.Notification
933 */
934 _notification: '',
935
5e279c42
AE
936 /**
937 * action proxy object
938 * @var WCF.Action.Proxy
939 */
940 _proxy: null,
941
d8c52eb0
MS
942 /**
943 * maximum number of labels the user may create
944 * @var integer
945 */
946 _maxLabels: 0,
947
948 /**
949 * number of labels the user created
950 * @var integer
951 */
952 _labelCount: 0,
953
5e279c42
AE
954 /**
955 * Initializes the label manager for conversations.
956 *
957 * @param string link
958 */
959 init: function(link) {
d4e2a0cc 960 this._deletedLabelID = 0;
d8c52eb0
MS
961 this._maxLabels = 0;
962 this._labelCount = 0;
5e279c42
AE
963 this._link = link;
964
e3fca11b 965 this._labels = WCF.Dropdown.getDropdownMenu('conversationLabelFilter').children('.scrollableDropdownMenu');
5e279c42
AE
966 $('#manageLabel').click($.proxy(this._click, this));
967
72f75266 968 this._notification = new WCF.System.Notification(WCF.Language.get('wcf.conversation.label.management.addLabel.success'));
5e279c42
AE
969 this._proxy = new WCF.Action.Proxy({
970 success: $.proxy(this._success, this)
971 });
972 },
973
974 /**
975 * Handles clicks on the 'manage labels' button.
976 */
977 _click: function() {
978 this._proxy.setOption('data', {
979 actionName: 'getLabelManagement',
980 className: 'wcf\\data\\conversation\\ConversationAction'
981 });
982 this._proxy.sendRequest();
983 },
984
985 /**
986 * Handles successful AJAX requests.
987 *
988 * @param object data
989 * @param string textStatus
990 * @param jQuery jqXHR
991 */
992 _success: function(data, textStatus, jqXHR) {
993 if (this._dialog === null) {
994 this._dialog = $('<div id="labelManagement" />').hide().appendTo(document.body);
995 }
996
d4e2a0cc
AE
997 if (data.returnValues && data.returnValues.actionName) {
998 switch (data.returnValues.actionName) {
999 case 'add':
1000 this._insertLabel(data);
1001 break;
5e279c42 1002
d4e2a0cc 1003 case 'getLabelManagement':
d8c52eb0
MS
1004 this._maxLabels = parseInt(data.returnValues.maxLabels);
1005 this._labelCount = parseInt(data.returnValues.labelCount);
1006
d4e2a0cc
AE
1007 // render dialog
1008 this._dialog.empty().html(data.returnValues.template);
1009 this._dialog.wcfDialog({
1010 title: WCF.Language.get('wcf.conversation.label.management')
1011 });
1012 this._dialog.wcfDialog('render');
1013
1014 // bind action listeners
1015 this._bindListener();
1016 break;
1017 }
1018 }
1019 else {
1020 // check if delete label id is present within URL (causing an IllegalLinkException if reloading)
1021 if (this._deletedLabelID) {
1022 var $regex = new RegExp('(\\?|&)labelID=' + this._deletedLabelID);
1023 window.location = window.location.toString().replace($regex, '');
1024 }
1025 else {
72f75266
AE
1026 // reload page
1027 window.location.reload();
d4e2a0cc 1028 }
5e279c42
AE
1029 }
1030 },
1031
1032 /**
1033 * Inserts a previously created label.
1034 *
1035 * @param object data
1036 */
72f75266 1037 _insertLabel: function(data) {
350145ef 1038 var $listItem = $('<li><a href="' + this._link + '&labelID=' + data.returnValues.labelID + '"><span class="badge label' + (data.returnValues.cssClassName ? ' ' + data.returnValues.cssClassName : '') + '">' + data.returnValues.label + '</span></a></li>');
9e1794f9 1039 $listItem.find('a > span').data('labelID', data.returnValues.labelID).data('cssClassName', data.returnValues.cssClassName);
5e279c42 1040
e3fca11b 1041 this._labels.append($listItem);
72f75266
AE
1042
1043 this._notification.show();
d8c52eb0
MS
1044
1045 this._labelCount++;
1046
1047 if (this._labelCount >= this._maxLabels) {
1048 $('#conversationLabelManagementForm').hide();
1049 }
5e279c42
AE
1050 },
1051
1052 /**
1053 * Binds event listener for label management.
1054 */
1055 _bindListener: function() {
5740132f 1056 $('#labelName').on('keyup keydown keypress', $.proxy(this._updateLabels, this));
67f16163
MS
1057 if ($.browser.mozilla && $.browser.touch) {
1058 $('#labelName').on('input', $.proxy(this._updateLabels, this));
1059 }
1060
d8c52eb0
MS
1061 if (this._labelCount >= this._maxLabels) {
1062 $('#conversationLabelManagementForm').hide();
1063 this._dialog.wcfDialog('render');
1064 }
1065
d4e2a0cc
AE
1066 $('#addLabel').disable().click($.proxy(this._addLabel, this));
1067 $('#editLabel').disable();
72f75266 1068
d4e2a0cc 1069 this._dialog.find('.conversationLabelList a.label').click($.proxy(this._edit, this));
72f75266
AE
1070 },
1071
1072 /**
1073 * Prepares a label for editing.
1074 *
1075 * @param object event
1076 */
1077 _edit: function(event) {
d8c52eb0
MS
1078 if (this._labelCount >= this._maxLabels) {
1079 $('#conversationLabelManagementForm').show();
1080 this._dialog.wcfDialog('render');
1081 }
1082
72f75266
AE
1083 var $label = $(event.currentTarget);
1084
1085 // replace legends
3e5330c0 1086 var $legend = WCF.Language.get('wcf.conversation.label.management.editLabel').replace(/#labelName#/, WCF.String.escapeHTML($label.text()));
e2b79659 1087 $('#conversationLabelManagementForm').data('labelID', $label.data('labelID')).children('.sectionTitle').html($legend);
72f75266
AE
1088
1089 // update text input
1090 $('#labelName').val($label.text()).trigger('keyup');
1091
1092 // select css class name
1093 var $cssClassName = $label.data('cssClassName');
1094 $('#labelManagementList input').each(function(index, input) {
1095 var $input = $(input);
1096
1097 if ($input.val() == $cssClassName) {
1098 $input.attr('checked', 'checked');
1099 }
1100 });
1101
1102 // toggle buttons
1103 $('#addLabel').hide();
1104 $('#editLabel').show().click($.proxy(this._editLabel, this));
1105 $('#deleteLabel').show().click($.proxy(this._deleteLabel, this));
1106 },
1107
1108 /**
1109 * Edits a label.
1110 */
1111 _editLabel: function() {
1112 this._proxy.setOption('data', {
1113 actionName: 'update',
1114 className: 'wcf\\data\\conversation\\label\\ConversationLabelAction',
d4e2a0cc 1115 objectIDs: [ $('#conversationLabelManagementForm').data('labelID') ],
72f75266
AE
1116 parameters: {
1117 data: {
1118 cssClassName: $('#labelManagementList input:checked').val(),
d4e2a0cc 1119 label: $('#labelName').val()
72f75266
AE
1120 }
1121 }
1122 });
1123 this._proxy.sendRequest();
1124 },
1125
1126 /**
1127 * Deletes a label.
1128 */
1129 _deleteLabel: function() {
1130 var $title = WCF.Language.get('wcf.conversation.label.management.deleteLabel.confirmMessage').replace(/#labelName#/, $('#labelName').val());
d4e2a0cc 1131 WCF.System.Confirmation.show($title, $.proxy(function(action) {
72f75266
AE
1132 if (action === 'confirm') {
1133 this._proxy.setOption('data', {
1134 actionName: 'delete',
1135 className: 'wcf\\data\\conversation\\label\\ConversationLabelAction',
d4e2a0cc 1136 objectIDs: [ $('#conversationLabelManagementForm').data('labelID') ]
72f75266
AE
1137 });
1138 this._proxy.sendRequest();
d4e2a0cc
AE
1139
1140 this._deletedLabelID = $('#conversationLabelManagementForm').data('labelID');
72f75266 1141 }
b21bc4a1 1142 }, this), undefined, undefined, true);
5e279c42
AE
1143 },
1144
1145 /**
1146 * Updates label text within label management.
1147 */
1148 _updateLabels: function() {
5740132f 1149 var $value = $.trim($('#labelName').val());
5e279c42 1150 if ($value) {
72f75266 1151 $('#addLabel, #editLabel').enable();
5e279c42
AE
1152 }
1153 else {
1c942355 1154 $('#addLabel, #editLabel').disable();
5e279c42
AE
1155 $value = WCF.Language.get('wcf.conversation.label.placeholder');
1156 }
1157
1158 $('#labelManagementList').find('span.label').text($value);
1159 },
1160
1161 /**
1162 * Sends an AJAX request to add a new label.
1163 */
1164 _addLabel: function() {
1165 var $labelName = $('#labelName').val();
72f75266 1166 var $cssClassName = $('#labelManagementList input:checked').val();
5e279c42
AE
1167
1168 this._proxy.setOption('data', {
1169 actionName: 'add',
1170 className: 'wcf\\data\\conversation\\label\\ConversationLabelAction',
1171 parameters: {
1172 data: {
1173 cssClassName: $cssClassName,
1174 labelName: $labelName
1175 }
1176 }
1177 });
1178 this._proxy.sendRequest();
72f75266
AE
1179
1180 // close dialog
1181 this._dialog.wcfDialog('close');
5e279c42
AE
1182 }
1183});
e2196ce0
MW
1184
1185/**
1186 * Provides a flexible conversation preview.
1187 *
1188 * @see WCF.Popover
1189 */
1190WCF.Conversation.Preview = WCF.Popover.extend({
1191 /**
1192 * action proxy
1193 * @var WCF.Action.Proxy
1194 */
1195 _proxy: null,
1196
1197 /**
1198 * @see WCF.Popover.init()
1199 */
1200 init: function() {
1201 this._super('.conversationLink');
1202
1203 // init proxy
1204 this._proxy = new WCF.Action.Proxy({
1205 showLoadingOverlay: false
1206 });
1207
1208 WCF.DOMNodeInsertedHandler.addCallback('WCF.Conversation.Preview', $.proxy(this._initContainers, this));
1209 },
1210
1211 /**
1212 * @see WCF.Popover._loadContent()
1213 */
1214 _loadContent: function() {
1215 var $link = $('#' + this._activeElementID);
1216
1217 this._proxy.setOption('data', {
1218 actionName: 'getMessagePreview',
1219 className: 'wcf\\data\\conversation\\ConversationAction',
1220 objectIDs: [ $link.data('conversationID') ]
1221 });
1222
1223 var $elementID = this._activeElementID;
1224 var self = this;
1225 this._proxy.setOption('success', function(data, textStatus, jqXHR) {
1226 self._insertContent($elementID, data.returnValues.template, true);
1227 });
1228 this._proxy.sendRequest();
1229 }
8b49562e 1230});
742b8736 1231
c61f0f25
AE
1232/**
1233 * User Panel implementation for conversations.
1234 *
1235 * @see WCF.User.Panel.Abstract
1236 */
1237WCF.User.Panel.Conversation = WCF.User.Panel.Abstract.extend({
1238 /**
1239 * @see WCF.User.Panel.Abstract.init()
1240 */
1241 init: function(options) {
1242 options.enableMarkAsRead = true;
1243
1244 this._super($('#unreadConversations'), 'unreadConversations', options);
c7953aa4
AE
1245
1246 WCF.System.Event.addListener('com.woltlab.wcf.conversation.userPanel', 'reset', (function() {
1247 this.resetItems();
1248 this.updateBadge(0);
1249 this._loadData = true;
1250 }).bind(this));
48f2f886
AE
1251
1252 require(['EventHandler'], (function(EventHandler) {
1253 EventHandler.add('com.woltlab.wcf.UserMenuMobile', 'more', (function(data) {
1254 if (data.identifier === 'com.woltlab.wcf.conversation') {
1255 this.toggle();
1256 }
1257 }).bind(this));
1258 }).bind(this));
c61f0f25
AE
1259 },
1260
1261 /**
1262 * @see WCF.User.Panel.Abstract._initDropdown()
1263 */
1264 _initDropdown: function() {
1265 var $dropdown = this._super();
1266
0d511ff2
AE
1267 if (this._options.canStartConversation) {
1268 $('<li><a href="' + this._options.newConversationLink + '" title="' + this._options.newConversation + '" class="jsTooltip"><span class="icon icon24 fa-plus" /></a></li>').appendTo($dropdown.getLinkList());
1269 }
c61f0f25
AE
1270
1271 return $dropdown;
1272 },
1273
1274 /**
1275 * @see WCF.User.Panel.Abstract._load()
1276 */
1277 _load: function() {
1278 this._proxy.setOption('data', {
1279 actionName: 'getMixedConversationList',
1280 className: 'wcf\\data\\conversation\\ConversationAction'
1281 });
1282 this._proxy.sendRequest();
1283 },
1284
1285 /**
1286 * @see WCF.User.Panel.Abstract._markAsRead()
1287 */
1288 _markAsRead: function(event, objectID) {
1289 this._proxy.setOption('data', {
1290 actionName: 'markAsRead',
1291 className: 'wcf\\data\\conversation\\ConversationAction',
1292 objectIDs: [ objectID ]
1293 });
1294 this._proxy.sendRequest();
1295 },
1296
1297 /**
1298 * @see WCF.User.Panel.Abstract._markAllAsRead()
1299 */
1300 _markAllAsRead: function(event) {
1301 this._proxy.setOption('data', {
1302 actionName: 'markAllAsRead',
1303 className: 'wcf\\data\\conversation\\ConversationAction'
1304 });
1305 this._proxy.sendRequest();
c61f0f25
AE
1306 }
1307});
1308
7f75cde5
MW
1309/**
1310 * Marks one conversation as read.
1311 */
1312WCF.Conversation.MarkAsRead = Class.extend({
1313 /**
1314 * action proxy
1315 * @var WCF.Action.Proxy
1316 */
1317 _proxy: null,
1318
1319 /**
1320 * Initializes the mark as read for conversations.
1321 */
1322 init: function() {
1323 this._proxy = new WCF.Action.Proxy({
1324 success: $.proxy(this._success, this)
1325 });
1326
02ce3a8a 1327 $(document).on('dblclick', '.conversationList .new .columnAvatar', $.proxy(this._dblclick, this));
7f75cde5
MW
1328 },
1329
1330 /**
1331 * Handles double clicks on avatar.
1332 *
1333 * @param object event
1334 */
1335 _dblclick: function(event) {
1336 this._proxy.setOption('data', {
1337 actionName: 'markAsRead',
1338 className: 'wcf\\data\\conversation\\ConversationAction',
d7c80056 1339 objectIDs: [ $(event.currentTarget).parents('ol:eq(0)').data('conversationID') ]
7f75cde5
MW
1340 });
1341 this._proxy.sendRequest();
1342 },
1343
1344 /**
1345 * Handles successful AJAX requests.
1346 *
1347 * @param object data
1348 * @param string textStatus
1349 * @param jQuery jqXHR
1350 */
1351 _success: function(data, textStatus, jqXHR) {
1352 $('.conversationList .new').each(function(index, element) {
1353 var $element = $(element);
1354 if (WCF.inArray($element.data('conversationID'), data.objectIDs)) {
1355 // remove new class
1356 $element.removeClass('new');
1357
1358 // hide arrows
1359 $element.find('.firstNewPost').parent().remove();
1360
1361 // remove event
1362 $element.find('.columnAvatar').off('dblclick');
1363 }
1364 });
1365 }
1366});
1367
38cc68ad
MW
1368/**
1369 * Marks all conversations as read.
1370 */
1371WCF.Conversation.MarkAllAsRead = Class.extend({
1372 /**
1373 * action proxy
1374 * @var WCF.Action.Proxy
1375 */
1376 _proxy: null,
1377
1378 /**
1379 * Initializes the WCF.Conversation.MarkAllAsRead class.
1380 */
1381 init: function() {
1382 this._proxy = new WCF.Action.Proxy({
1383 success: $.proxy(this._success, this)
1384 });
1385
6e576a66 1386 $('.markAllAsReadButton').click($.proxy(this._click, this));
38cc68ad
MW
1387 },
1388
1389 /**
6e576a66 1390 * Handles clicks.
38cc68ad
MW
1391 *
1392 * @param object event
1393 */
6e576a66
MW
1394 _click: function(event) {
1395 event.preventDefault();
1396
38cc68ad
MW
1397 this._proxy.setOption('data', {
1398 actionName: 'markAllAsRead',
1399 className: 'wcf\\data\\conversation\\ConversationAction'
1400 });
1401 this._proxy.sendRequest();
1402 },
1403
1404 /**
1405 * Marks all conversations as read.
1406 *
1407 * @param object data
1408 * @param string textStatus
1409 * @param jQuery jqXHR
1410 */
1411 _success: function(data, textStatus, jqXHR) {
1412 // fix dropdown
c7953aa4 1413 WCF.System.Event.fireEvent('com.woltlab.wcf.conversation.userPanel', 'reset');
38cc68ad
MW
1414
1415 // fix conversation list
1416 var $conversationList = $('.conversationList');
1417 $conversationList.find('.new').removeClass('new');
1418 $conversationList.find('.columnAvatar').off('dblclick');
1419 }
1420});
1421
a5bacc02
AE
1422/**
1423 * Namespace for conversation messages.
1424 */
1425WCF.Conversation.Message = { };
1426
1427/**
1428 * Provides an inline editor for conversation messages.
1429 *
1430 * @see WCF.Message.InlineEditor
1431 */
1432WCF.Conversation.Message.InlineEditor = WCF.Message.InlineEditor.extend({
b5ffaf76
AE
1433 /**
1434 * @see WCF.Message.InlineEditor.init()
1435 */
0109b481
AE
1436 init: function(containerID, quoteManager) {
1437 this._super(containerID, true, quoteManager);
b5ffaf76
AE
1438 },
1439
a5bacc02
AE
1440 /**
1441 * @see WCF.Message.InlineEditor._getClassName()
1442 */
1443 _getClassName: function() {
1444 return 'wcf\\data\\conversation\\message\\ConversationMessageAction';
1445 }
1446});
19caa577
AE
1447
1448/**
1449 * Provides the quote manager for conversation messages.
1450 *
1451 * @param WCF.Message.Quote.Manager quoteManager
1452 * @see WCF.Message.Quote.Handler
1453 */
1454WCF.Conversation.Message.QuoteHandler = WCF.Message.Quote.Handler.extend({
1455 /**
1456 * @see WCF.Message.QuoteManager.init()
1457 */
1458 init: function(quoteManager) {
9ac36595
AE
1459 this._super(
1460 quoteManager,
1461 'wcf\\data\\conversation\\message\\ConversationMessageAction',
1462 'com.woltlab.wcf.conversation.message',
1463 '.message',
1464 '.messageBody',
1465 '.messageBody > div > div.messageText',
1466 true
1467 );
19caa577
AE
1468 }
1469});