Adding a space before newly created badges
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / js / WCF.User.js
1 "use strict";
2
3 /**
4 * User-related classes.
5 *
6 * @author Alexander Ebert
7 * @copyright 2001-2014 WoltLab GmbH
8 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
9 */
10
11 /**
12 * User login
13 *
14 * @param boolean isQuickLogin
15 */
16 WCF.User.Login = Class.extend({
17 /**
18 * login button
19 * @var jQuery
20 */
21 _loginSubmitButton: null,
22
23 /**
24 * password input
25 * @var jQuery
26 */
27 _password: null,
28
29 /**
30 * password input container
31 * @var jQuery
32 */
33 _passwordContainer: null,
34
35 /**
36 * cookie input
37 * @var jQuery
38 */
39 _useCookies: null,
40
41 /**
42 * cookie input container
43 * @var jQuery
44 */
45 _useCookiesContainer: null,
46
47 /**
48 * Initializes the user login
49 *
50 * @param boolean isQuickLogin
51 */
52 init: function(isQuickLogin) {
53 this._loginSubmitButton = $('#loginSubmitButton');
54 this._password = $('#password'),
55 this._passwordContainer = this._password.parents('dl');
56 this._useCookies = $('#useCookies');
57 this._useCookiesContainer = this._useCookies.parents('dl');
58
59 var $loginForm = $('#loginForm');
60 $loginForm.find('input[name=action]').change($.proxy(this._change, this));
61
62 if (isQuickLogin) {
63 WCF.User.QuickLogin.init();
64 }
65 },
66
67 /**
68 * Handle toggle between login and register.
69 *
70 * @param object event
71 */
72 _change: function(event) {
73 if ($(event.currentTarget).val() === 'register') {
74 this._setState(false, WCF.Language.get('wcf.user.button.register'));
75 }
76 else {
77 this._setState(true, WCF.Language.get('wcf.user.button.login'));
78 }
79 },
80
81 /**
82 * Sets form states.
83 *
84 * @param boolean enable
85 * @param string buttonTitle
86 */
87 _setState: function(enable, buttonTitle) {
88 if (enable) {
89 this._password.enable();
90 this._passwordContainer.removeClass('disabled');
91 this._useCookies.enable();
92 this._useCookiesContainer.removeClass('disabled');
93 }
94 else {
95 this._password.disable();
96 this._passwordContainer.addClass('disabled');
97 this._useCookies.disable();
98 this._useCookiesContainer.addClass('disabled');
99 }
100
101 this._loginSubmitButton.val(buttonTitle);
102 }
103 });
104
105 /**
106 * Namespace for User Panel classes.
107 */
108 WCF.User.Panel = { };
109
110 /**
111 * Abstract implementation for user panel items providing an interactive dropdown.
112 *
113 * @param jQuery triggerElement
114 * @param string identifier
115 * @param object options
116 */
117 WCF.User.Panel.Abstract = Class.extend({
118 /**
119 * counter badge
120 * @var jQuery
121 */
122 _badge: null,
123
124 /**
125 * interactive dropdown instance
126 * @var WCF.Dropdown.Interactive.Instance
127 */
128 _dropdown: null,
129
130 /**
131 * dropdown identifier
132 * @var string
133 */
134 _identifier: '',
135
136 /**
137 * true if data should be loaded using an AJAX request
138 * @var boolean
139 */
140 _loadData: true,
141
142 /**
143 * header icon to mark all items belonging to this user panel item as read
144 * @var jQuery
145 */
146 _markAllAsReadLink: null,
147
148 /**
149 * list of options required to dropdown initialization and UI features
150 * @var object
151 */
152 _options: { },
153
154 /**
155 * action proxy
156 * @var WCF.Action.Proxy
157 */
158 _proxy: null,
159
160 /**
161 * trigger element
162 * @var jQuery
163 */
164 _triggerElement: null,
165
166 /**
167 * Initializes the WCF.User.Panel.Abstract class.
168 *
169 * @param jQuery triggerElement
170 * @param string identifier
171 * @param object options
172 */
173 init: function(triggerElement, identifier, options) {
174 this._dropdown = null;
175 this._identifier = identifier;
176 this._triggerElement = triggerElement;
177 this._options = options;
178
179 this._proxy = new WCF.Action.Proxy({
180 showLoadingOverlay: false,
181 success: $.proxy(this._success, this)
182 });
183
184 this._triggerElement.click($.proxy(this.toggle, this));
185 if (this._options.showAllLink) {
186 this._triggerElement.dblclick($.proxy(this._dblClick, this));
187 }
188
189 var $badge = this._triggerElement.find('span.badge');
190 if ($badge.length) {
191 this._badge = $badge;
192 }
193 },
194
195 /**
196 * Toggles the interactive dropdown.
197 *
198 * @param object event
199 * @return boolean
200 */
201 toggle: function(event) {
202 event.preventDefault();
203
204 if (this._dropdown === null) {
205 this._dropdown = this._initDropdown();
206 }
207
208 if (this._dropdown.toggle()) {
209 if (!this._loadData) {
210 // check if there are outstanding items but there are no outstanding ones in the current list
211 if (this._badge !== null) {
212 var $count = parseInt(this._badge.text()) || 0;
213 if ($count && !this._dropdown.getItemList().children('.interactiveDropdownItemOutstanding').length) {
214 this._loadData = true;
215 }
216 }
217 }
218
219 if (this._loadData) {
220 this._loadData = false;
221 this._load();
222 }
223 }
224
225 return false;
226 },
227
228 /**
229 * Forward to original URL by double clicking the trigger element.
230 *
231 * @param object event
232 * @return boolean
233 */
234 _dblClick: function(event) {
235 event.preventDefault();
236
237 window.location = this._options.showAllLink;
238
239 return false;
240 },
241
242 /**
243 * Initializes the dropdown on first usage.
244 *
245 * @return WCF.Dropdown.Interactive.Instance
246 */
247 _initDropdown: function() {
248 var $dropdown = WCF.Dropdown.Interactive.Handler.create(this._triggerElement, this._identifier, this._options);
249 $('<li class="loading"><span class="icon icon24 fa-spinner" /> <span>' + WCF.Language.get('wcf.global.loading') + '</span></li>').appendTo($dropdown.getItemList());
250
251 return $dropdown;
252 },
253
254 /**
255 * Loads item list data via AJAX.
256 */
257 _load: function() {
258 // override this in your own implementation to fetch display data
259 },
260
261 /**
262 * Handles successful AJAX requests.
263 *
264 * @param object data
265 */
266 _success: function(data) {
267 if (data.returnValues.template !== undefined) {
268 var $itemList = this._dropdown.getItemList().empty();
269 $(data.returnValues.template).appendTo($itemList);
270
271 if (!$itemList.children().length) {
272 $('<li class="noItems">' + this._options.noItems + '</li>').appendTo($itemList);
273 }
274
275 if (this._options.enableMarkAsRead) {
276 var $outstandingItems = this._dropdown.getItemList().children('.interactiveDropdownItemOutstanding');
277 if (this._markAllAsReadLink === null && $outstandingItems.length && this._options.markAllAsReadConfirmMessage) {
278 var $button = this._markAllAsReadLink = $('<li class="interactiveDropdownItemMarkAllAsRead"><a href="#" title="' + WCF.Language.get('wcf.user.panel.markAllAsRead') + '" class="jsTooltip"><span class="icon icon16 fa-check" /></a></li>').appendTo(this._dropdown.getLinkList());
279 $button.click((function(event) {
280 this._dropdown.close();
281
282 WCF.System.Confirmation.show(this._options.markAllAsReadConfirmMessage, (function(action) {
283 if (action === 'confirm') {
284 this._markAllAsRead();
285 }
286 }).bind(this));
287
288 return false;
289 }).bind(this));
290 }
291
292 $outstandingItems.each((function(index, item) {
293 var $item = $(item).addClass('interactiveDropdownItemOutstandingIcon');
294 var $objectID = $item.data('objectID');
295
296 var $button = $('<div class="interactiveDropdownItemMarkAsRead"><a href="#" title="' + WCF.Language.get('wcf.user.panel.markAsRead') + '" class="jsTooltip"><span class="icon icon16 fa-check" /></a></div>').appendTo($item);
297 $button.click((function(event) {
298 this._markAsRead(event, $objectID);
299
300 return false;
301 }).bind(this));
302 }).bind(this));
303 }
304
305 this._dropdown.getItemList().children().each(function(index, item) {
306 var $item = $(item);
307 var $link = $item.data('link');
308
309 if ($link) {
310 if ($.browser.msie) {
311 $item.click(function(event) {
312 if (event.target.tagName !== 'A') {
313 window.location = $link;
314
315 return false;
316 }
317 });
318 }
319 else {
320 $item.addClass('interactiveDropdownItemShadow');
321 $('<a href="' + $link + '" class="interactiveDropdownItemShadowLink" />').appendTo($item);
322 }
323
324 if ($item.data('linkReplaceAll')) {
325 $item.find('> .box32 a:not(.userLink)').prop('href', $link);
326 }
327 }
328 });
329
330 this._dropdown.rebuildScrollbar();
331 }
332
333 if (data.returnValues.totalCount !== undefined) {
334 this.updateBadge(data.returnValues.totalCount);
335 }
336
337 if (this._options.enableMarkAsRead) {
338 if (data.returnValues.markAsRead) {
339 var $item = this._dropdown.getItemList().children('li[data-object-id=' + data.returnValues.markAsRead + ']');
340 if ($item.length) {
341 $item.removeClass('interactiveDropdownItemOutstanding').data('isRead', true);
342 $item.children('.interactiveDropdownItemMarkAsRead').remove();
343 }
344 }
345 else if (data.returnValues.markAllAsRead) {
346 this.resetItems();
347 this.updateBadge(0);
348 }
349 }
350 },
351
352 /**
353 * Marks an item as read.
354 *
355 * @param object event
356 * @param integer objectID
357 */
358 _markAsRead: function(event, objectID) {
359 // override this in your own implementation to mark an item as read
360 },
361
362 /**
363 * Marks all items as read.
364 */
365 _markAllAsRead: function() {
366 // override this in your own implementation to mark all items as read
367 },
368
369 /**
370 * Updates the badge's count or removes it if count reaches zero. Passing a negative number is undefined.
371 *
372 * @param integer count
373 */
374 updateBadge: function(count) {
375 count = parseInt(count) || 0;
376
377 if (count) {
378 if (this._badge === null) {
379 this._badge = $('<span class="badge badgeInverse" />').appendTo(this._triggerElement.children('a'));
380 this._badge.before(' ');
381 }
382
383 this._badge.text(count);
384 }
385 else if (this._badge !== null) {
386 this._badge.remove();
387 }
388
389 if (this._options.enableMarkAsRead) {
390 if (!count && this._markAllAsReadLink !== null) {
391 this._markAllAsReadLink.remove();
392 this._markAllAsReadLink = null;
393 }
394 }
395 },
396
397 /**
398 * Resets the dropdown's inner item list.
399 */
400 resetItems: function() {
401 this._dropdown.resetItems();
402 }
403 });
404
405 /**
406 * User Panel implementation for user notifications.
407 *
408 * @see WCF.User.Panel.Abstract
409 */
410 WCF.User.Panel.Notification = WCF.User.Panel.Abstract.extend({
411 /**
412 * favico instance
413 * @var Favico
414 */
415 _favico: null,
416
417 /**
418 * @see WCF.User.Panel.Abstract.init()
419 */
420 init: function(options) {
421 options.enableMarkAsRead = true;
422
423 this._super($('#userNotifications'), 'userNotifications', options);
424
425 try {
426 this._favico = new Favico({
427 animation: 'none',
428 type: 'circle'
429 });
430
431 if (this._badge !== null) {
432 var $count = parseInt(this._badge.text()) || 0;
433 this._favico.badge($count);
434 }
435 }
436 catch (e) {
437 console.debug("[WCF.User.Panel.Notification] Failed to initialized Favico: " + e.message);
438 }
439
440 WCF.System.PushNotification.addCallback('userNotificationCount', $.proxy(this.updateUserNotificationCount, this));
441 },
442
443 /**
444 * @see WCF.User.Panel.Abstract._initDropdown()
445 */
446 _initDropdown: function() {
447 var $dropdown = this._super();
448
449 $('<li><a href="' + this._options.settingsLink + '" title="' + WCF.Language.get('wcf.user.panel.settings') + '" class="jsTooltip"><span class="icon icon16 fa-cog" /></a></li>').appendTo($dropdown.getLinkList());
450
451 return $dropdown;
452 },
453
454 /**
455 * @see WCF.User.Panel.Abstract._load()
456 */
457 _load: function() {
458 this._proxy.setOption('data', {
459 actionName: 'getOutstandingNotifications',
460 className: 'wcf\\data\\user\\notification\\UserNotificationAction'
461 });
462 this._proxy.sendRequest();
463 },
464
465 /**
466 * @see WCF.User.Panel.Abstract._markAsRead()
467 */
468 _markAsRead: function(event, objectID) {
469 this._proxy.setOption('data', {
470 actionName: 'markAsConfirmed',
471 className: 'wcf\\data\\user\\notification\\UserNotificationAction',
472 objectIDs: [ objectID ]
473 });
474 this._proxy.sendRequest();
475 },
476
477 /**
478 * @see WCF.User.Panel.Abstract._markAllAsRead()
479 */
480 _markAllAsRead: function(event) {
481 this._proxy.setOption('data', {
482 actionName: 'markAllAsConfirmed',
483 className: 'wcf\\data\\user\\notification\\UserNotificationAction'
484 });
485 this._proxy.sendRequest();
486 },
487
488 /**
489 * @see WCF.User.Panel.Abstract.resetItems()
490 */
491 resetItems: function() {
492 this._super();
493
494 if (this._markAllAsReadLink) {
495 this._markAllAsReadLink.remove();
496 this._markAllAsReadLink = null;
497 }
498 },
499
500 /**
501 * @see WCF.User.Panel.Abstract.updateBadge()
502 */
503 updateBadge: function(count) {
504 count = parseInt(count) || 0;
505
506 if (this._favico !== null) {
507 this._favico.badge(count);
508 }
509
510 this._super(count);
511 },
512
513 /**
514 * Updates the badge counter and resets the dropdown's item list.
515 *
516 * @param integer count
517 */
518 updateUserNotificationCount: function(count) {
519 if (this._dropdown !== null) {
520 this._dropdown.resetItems();
521 }
522
523 this.updateBadge(count);
524 }
525 });
526
527 /**
528 * Quick login box
529 */
530 WCF.User.QuickLogin = {
531 /**
532 * dialog overlay
533 * @var jQuery
534 */
535 _dialog: null,
536
537 /**
538 * login message container
539 * @var jQuery
540 */
541 _loginMessage: null,
542
543 /**
544 * Initializes the quick login box
545 */
546 init: function() {
547 $('.loginLink').click($.proxy(this._render, this));
548
549 // prepend protocol and hostname
550 $('#loginForm input[name=url]').val(function(index, value) {
551 return window.location.protocol + '//' + window.location.host + value;
552 });
553 },
554
555 /**
556 * Displays the quick login box with a info message
557 *
558 * @param string message
559 */
560 show: function(message) {
561 if (message) {
562 if (this._loginMessage === null) {
563 this._loginMessage = $('<p class="info" />').hide().prependTo($('#loginForm > form'));
564 }
565
566 this._loginMessage.show().text(message);
567 }
568 else if (this._loginMessage !== null) {
569 this._loginMessage.hide();
570 }
571
572 this._render();
573 },
574
575 /**
576 * Renders the dialog
577 *
578 * @param jQuery.Event event
579 */
580 _render: function(event) {
581 if (event !== undefined) {
582 event.preventDefault();
583 }
584
585 if (this._dialog === null) {
586 this._dialog = $('#loginForm').wcfDialog({
587 title: WCF.Language.get('wcf.user.login')
588 });
589 this._dialog.find('#username').focus();
590 }
591 else {
592 this._dialog.wcfDialog('open');
593 }
594 }
595 };
596
597 /**
598 * UserProfile namespace
599 */
600 WCF.User.Profile = {};
601
602 /**
603 * Shows the activity point list for users.
604 */
605 WCF.User.Profile.ActivityPointList = {
606 /**
607 * list of cached templates
608 * @var object
609 */
610 _cache: { },
611
612 /**
613 * dialog overlay
614 * @var jQuery
615 */
616 _dialog: null,
617
618 /**
619 * initialization state
620 * @var boolean
621 */
622 _didInit: false,
623
624 /**
625 * action proxy
626 * @var WCF.Action.Proxy
627 */
628 _proxy: null,
629
630 /**
631 * Initializes the WCF.User.Profile.ActivityPointList class.
632 */
633 init: function() {
634 if (this._didInit) {
635 return;
636 }
637
638 this._cache = { };
639 this._dialog = null;
640 this._proxy = new WCF.Action.Proxy({
641 success: $.proxy(this._success, this)
642 });
643
644 this._init();
645
646 WCF.DOMNodeInsertedHandler.addCallback('WCF.User.Profile.ActivityPointList', $.proxy(this._init, this));
647
648 this._didInit = true;
649 },
650
651 /**
652 * Initializes display for activity points.
653 */
654 _init: function() {
655 $('.activityPointsDisplay').removeClass('activityPointsDisplay').click($.proxy(this._click, this));
656 },
657
658 /**
659 * Shows or loads the activity point for selected user.
660 *
661 * @param object event
662 */
663 _click: function(event) {
664 event.preventDefault();
665 var $userID = $(event.currentTarget).data('userID');
666
667 if (this._cache[$userID] === undefined) {
668 this._proxy.setOption('data', {
669 actionName: 'getDetailedActivityPointList',
670 className: 'wcf\\data\\user\\UserProfileAction',
671 objectIDs: [ $userID ]
672 });
673 this._proxy.sendRequest();
674 }
675 else {
676 this._show($userID);
677 }
678 },
679
680 /**
681 * Displays activity points for given user.
682 *
683 * @param integer userID
684 */
685 _show: function(userID) {
686 if (this._dialog === null) {
687 this._dialog = $('<div>' + this._cache[userID] + '</div>').hide().appendTo(document.body);
688 this._dialog.wcfDialog({
689 title: WCF.Language.get('wcf.user.activityPoint')
690 });
691 }
692 else {
693 this._dialog.html(this._cache[userID]);
694 this._dialog.wcfDialog('open');
695 }
696 },
697
698 /**
699 * Handles successful AJAX requests.
700 *
701 * @param object data
702 * @param string textStatus
703 * @param jQuery jqXHR
704 */
705 _success: function(data, textStatus, jqXHR) {
706 this._cache[data.returnValues.userID] = data.returnValues.template;
707 this._show(data.returnValues.userID);
708 }
709 };
710
711 /**
712 * Provides methods to follow an user.
713 *
714 * @param integer userID
715 * @param boolean following
716 */
717 WCF.User.Profile.Follow = Class.extend({
718 /**
719 * follow button
720 * @var jQuery
721 */
722 _button: null,
723
724 /**
725 * true if following current user
726 * @var boolean
727 */
728 _following: false,
729
730 /**
731 * action proxy object
732 * @var WCF.Action.Proxy
733 */
734 _proxy: null,
735
736 /**
737 * user id
738 * @var integer
739 */
740 _userID: 0,
741
742 /**
743 * Creates a new follow object.
744 *
745 * @param integer userID
746 * @param boolean following
747 */
748 init: function (userID, following) {
749 this._following = following;
750 this._userID = userID;
751 this._proxy = new WCF.Action.Proxy({
752 success: $.proxy(this._success, this)
753 });
754
755 this._createButton();
756 this._showButton();
757 },
758
759 /**
760 * Creates the (un-)follow button
761 */
762 _createButton: function () {
763 this._button = $('<li id="followUser"><a href="#" class="button jsTooltip" title="'+WCF.Language.get('wcf.user.button.'+(this._following ? 'un' : '')+'follow')+'"><span class="icon icon16 icon-plus"></span> <span class="invisible">'+WCF.Language.get('wcf.user.button.'+(this._following ? 'un' : '')+'follow')+'</span></a></li>').prependTo($('#profileButtonContainer'));
764 this._button.click($.proxy(this._execute, this));
765 },
766
767 /**
768 * Follows or unfollows an user.
769 */
770 _execute: function (event) {
771 event.preventDefault();
772 var $actionName = (this._following) ? 'unfollow' : 'follow';
773 this._proxy.setOption('data', {
774 'actionName': $actionName,
775 'className': 'wcf\\data\\user\\follow\\UserFollowAction',
776 'parameters': {
777 data: {
778 userID: this._userID
779 }
780 }
781 });
782 this._proxy.sendRequest();
783 },
784
785 /**
786 * Displays current follow state.
787 */
788 _showButton: function () {
789 if (this._following) {
790 this._button.find('.button').data('tooltip', WCF.Language.get('wcf.user.button.unfollow')).addClass('active').children('.icon').removeClass('icon-plus').addClass('icon-minus');
791 }
792 else {
793 this._button.find('.button').data('tooltip', WCF.Language.get('wcf.user.button.follow')).removeClass('active').children('.icon').removeClass('icon-minus').addClass('icon-plus');
794 }
795 },
796
797 /**
798 * Update object state on success.
799 *
800 * @param object data
801 * @param string textStatus
802 * @param jQuery jqXHR
803 */
804 _success: function (data, textStatus, jqXHR) {
805 this._following = data.returnValues.following;
806 this._showButton();
807
808 var $notification = new WCF.System.Notification();
809 $notification.show();
810 }
811 });
812
813 /**
814 * Provides methods to manage ignored users.
815 *
816 * @param integer userID
817 * @param boolean isIgnoredUser
818 */
819 WCF.User.Profile.IgnoreUser = Class.extend({
820 /**
821 * ignore button
822 * @var jQuery
823 */
824 _button: null,
825
826 /**
827 * ignore state
828 * @var boolean
829 */
830 _isIgnoredUser: false,
831
832 /**
833 * ajax proxy object
834 * @var WCF.Action.Proxy
835 */
836 _proxy: null,
837
838 /**
839 * target user id
840 * @var integer
841 */
842 _userID: 0,
843
844 /**
845 * Initializes methods to manage an ignored user.
846 *
847 * @param integer userID
848 * @param boolean isIgnoredUser
849 */
850 init: function(userID, isIgnoredUser) {
851 this._userID = userID;
852 this._isIgnoredUser = isIgnoredUser;
853
854 // initialize proxy
855 this._proxy = new WCF.Action.Proxy({
856 success: $.proxy(this._success, this)
857 });
858
859 // handle button
860 this._updateButton();
861 this._button.click($.proxy(this._click, this));
862 },
863
864 /**
865 * Handle clicks, might cause 'ignore' or 'unignore' to be triggered.
866 */
867 _click: function(event) {
868 event.preventDefault();
869 var $action = (this._isIgnoredUser) ? 'unignore' : 'ignore';
870
871 this._proxy.setOption('data', {
872 actionName: $action,
873 className: 'wcf\\data\\user\\ignore\\UserIgnoreAction',
874 parameters: {
875 data: {
876 ignoreUserID: this._userID
877 }
878 }
879 });
880
881 this._proxy.sendRequest();
882 },
883
884 /**
885 * Updates button label and function upon successful request.
886 *
887 * @param object data
888 * @param string textStatus
889 * @param jQuery jqXHR
890 */
891 _success: function(data, textStatus, jqXHR) {
892 this._isIgnoredUser = data.returnValues.isIgnoredUser;
893 this._updateButton();
894
895 var $notification = new WCF.System.Notification();
896 $notification.show();
897 },
898
899 /**
900 * Updates button label and inserts it if not exists.
901 */
902 _updateButton: function() {
903 if (this._button === null) {
904 this._button = $('<li id="ignoreUser"><a href="#" class="button jsTooltip" title="'+WCF.Language.get('wcf.user.button.'+(this._isIgnoredUser ? 'un' : '')+'ignore')+'"><span class="icon icon16 icon-ban-circle"></span> <span class="invisible">'+WCF.Language.get('wcf.user.button.'+(this._isIgnoredUser ? 'un' : '')+'ignore')+'</span></a></li>').prependTo($('#profileButtonContainer'));
905 }
906
907 this._button.find('.button').data('tooltip', WCF.Language.get('wcf.user.button.' + (this._isIgnoredUser ? 'un' : '') + 'ignore'));
908 if (this._isIgnoredUser) this._button.find('.button').addClass('active').children('.icon').removeClass('icon-ban-circle').addClass('icon-circle-blank');
909 else this._button.find('.button').removeClass('active').children('.icon').removeClass('icon-circle-blank').addClass('icon-ban-circle');
910 }
911 });
912
913 /**
914 * Provides methods to load tab menu content upon request.
915 */
916 WCF.User.Profile.TabMenu = Class.extend({
917 /**
918 * list of containers
919 * @var object
920 */
921 _hasContent: { },
922
923 /**
924 * profile content
925 * @var jQuery
926 */
927 _profileContent: null,
928
929 /**
930 * action proxy
931 * @var WCF.Action.Proxy
932 */
933 _proxy: null,
934
935 /**
936 * target user id
937 * @var integer
938 */
939 _userID: 0,
940
941 /**
942 * Initializes the tab menu loader.
943 *
944 * @param integer userID
945 */
946 init: function(userID) {
947 this._profileContent = $('#profileContent');
948 this._userID = userID;
949
950 var $activeMenuItem = this._profileContent.data('active');
951 var $enableProxy = false;
952
953 // fetch content state
954 this._profileContent.find('div.tabMenuContent').each($.proxy(function(index, container) {
955 var $containerID = $(container).wcfIdentify();
956
957 if ($activeMenuItem === $containerID) {
958 this._hasContent[$containerID] = true;
959 }
960 else {
961 this._hasContent[$containerID] = false;
962 $enableProxy = true;
963 }
964 }, this));
965
966 // enable loader if at least one container is empty
967 if ($enableProxy) {
968 this._proxy = new WCF.Action.Proxy({
969 success: $.proxy(this._success, this)
970 });
971
972 this._profileContent.on('wcftabsbeforeactivate', $.proxy(this._loadContent, this));
973
974 // check which tab is selected
975 this._profileContent.find('> nav.tabMenu > ul > li').each($.proxy(function(index, listItem) {
976 var $listItem = $(listItem);
977 if ($listItem.hasClass('ui-state-active')) {
978 if (index) {
979 this._loadContent(null, {
980 newPanel: $('#' + $listItem.attr('aria-controls'))
981 });
982 }
983
984 return false;
985 }
986 }, this));
987 }
988 },
989
990 /**
991 * Prepares to load content once tabs are being switched.
992 *
993 * @param object event
994 * @param object ui
995 */
996 _loadContent: function(event, ui) {
997 var $panel = $(ui.newPanel);
998 var $containerID = $panel.attr('id');
999
1000 if (!this._hasContent[$containerID]) {
1001 this._proxy.setOption('data', {
1002 actionName: 'getContent',
1003 className: 'wcf\\data\\user\\profile\\menu\\item\\UserProfileMenuItemAction',
1004 parameters: {
1005 data: {
1006 containerID: $containerID,
1007 menuItem: $panel.data('menuItem'),
1008 userID: this._userID
1009 }
1010 }
1011 });
1012 this._proxy.sendRequest();
1013 }
1014 },
1015
1016 /**
1017 * Shows previously requested content.
1018 *
1019 * @param object data
1020 * @param string textStatus
1021 * @param jQuery jqXHR
1022 */
1023 _success: function(data, textStatus, jqXHR) {
1024 var $containerID = data.returnValues.containerID;
1025 this._hasContent[$containerID] = true;
1026
1027 // insert content
1028 var $content = this._profileContent.find('#' + $containerID);
1029 $('<div>' + data.returnValues.template + '</div>').hide().appendTo($content);
1030
1031 // slide in content
1032 $content.children('div').wcfBlindIn();
1033 }
1034 });
1035
1036 /**
1037 * User profile inline editor.
1038 *
1039 * @param integer userID
1040 * @param boolean editOnInit
1041 */
1042 WCF.User.Profile.Editor = Class.extend({
1043 /**
1044 * current action
1045 * @var string
1046 */
1047 _actionName: '',
1048
1049 /**
1050 * list of interface buttons
1051 * @var object
1052 */
1053 _buttons: { },
1054
1055 /**
1056 * cached tab content
1057 * @var string
1058 */
1059 _cachedTemplate: '',
1060
1061 /**
1062 * action proxy
1063 * @var WCF.Action.Proxy
1064 */
1065 _proxy: null,
1066
1067 /**
1068 * tab object
1069 * @var jQuery
1070 */
1071 _tab: null,
1072
1073 /**
1074 * target user id
1075 * @var integer
1076 */
1077 _userID: 0,
1078
1079 /**
1080 * Initializes the WCF.User.Profile.Editor object.
1081 *
1082 * @param integer userID
1083 * @param boolean editOnInit
1084 */
1085 init: function(userID, editOnInit) {
1086 this._actionName = '';
1087 this._cachedTemplate = '';
1088 this._tab = $('#about');
1089 this._userID = userID;
1090 this._proxy = new WCF.Action.Proxy({
1091 success: $.proxy(this._success, this)
1092 });
1093
1094 this._initButtons();
1095
1096 // begin editing on page load
1097 if (editOnInit) {
1098 this._beginEdit();
1099 }
1100 },
1101
1102 /**
1103 * Initializes interface buttons.
1104 */
1105 _initButtons: function() {
1106 var $buttonContainer = $('#profileButtonContainer');
1107
1108 // create buttons
1109 this._buttons = {
1110 beginEdit: $('<li><a class="button"><span class="icon icon16 icon-pencil" /> <span>' + WCF.Language.get('wcf.user.editProfile') + '</span></a></li>').click($.proxy(this._beginEdit, this)).appendTo($buttonContainer)
1111 };
1112 },
1113
1114 /**
1115 * Begins editing.
1116 */
1117 _beginEdit: function() {
1118 this._actionName = 'beginEdit';
1119 this._buttons.beginEdit.hide();
1120 $('#profileContent').wcfTabs('select', 'about');
1121
1122 // load form
1123 this._proxy.setOption('data', {
1124 actionName: 'beginEdit',
1125 className: 'wcf\\data\\user\\UserProfileAction',
1126 objectIDs: [ this._userID ]
1127 });
1128 this._proxy.sendRequest();
1129 },
1130
1131 /**
1132 * Saves input values.
1133 */
1134 _save: function() {
1135 this._actionName = 'save';
1136
1137 // collect values
1138 var $regExp = /values\[([a-zA-Z0-9._-]+)\]/;
1139 var $values = { };
1140 this._tab.find('input, textarea, select').each(function(index, element) {
1141 var $element = $(element);
1142 var $value = null;
1143
1144 switch ($element.getTagName()) {
1145 case 'input':
1146 var $type = $element.attr('type');
1147
1148 if (($type === 'radio' || $type === 'checkbox') && !$element.prop('checked')) {
1149 return;
1150 }
1151 break;
1152
1153 case 'textarea':
1154 if ($element.data('redactor')) {
1155 $value = $element.redactor('wutil.getText');
1156 }
1157 break;
1158 }
1159
1160 var $name = $element.attr('name');
1161 if ($regExp.test($name)) {
1162 $values[RegExp.$1] = ($value === null) ? $element.val() : $value;
1163 }
1164 });
1165
1166 this._proxy.setOption('data', {
1167 actionName: 'save',
1168 className: 'wcf\\data\\user\\UserProfileAction',
1169 objectIDs: [ this._userID ],
1170 parameters: {
1171 values: $values
1172 }
1173 });
1174 this._proxy.sendRequest();
1175 },
1176
1177 /**
1178 * Restores back to default view.
1179 */
1180 _restore: function() {
1181 this._actionName = 'restore';
1182 this._buttons.beginEdit.show();
1183
1184 this._destroyEditor();
1185
1186 this._tab.html(this._cachedTemplate).children().css({ height: 'auto' });
1187 },
1188
1189 /**
1190 * Handles successful AJAX requests.
1191 *
1192 * @param object data
1193 * @param string textStatus
1194 * @param jQuery jqXHR
1195 */
1196 _success: function(data, textStatus, jqXHR) {
1197 switch (this._actionName) {
1198 case 'beginEdit':
1199 this._prepareEdit(data);
1200 break;
1201
1202 case 'save':
1203 // save was successful, show parsed template
1204 if (data.returnValues.success) {
1205 this._cachedTemplate = data.returnValues.template;
1206 this._restore();
1207 }
1208 else {
1209 this._prepareEdit(data, true);
1210 }
1211 break;
1212 }
1213 },
1214
1215 /**
1216 * Prepares editing mode.
1217 *
1218 * @param object data
1219 * @param boolean disableCache
1220 */
1221 _prepareEdit: function(data, disableCache) {
1222 this._destroyEditor();
1223
1224 // update template
1225 var self = this;
1226 this._tab.html(function(index, oldHTML) {
1227 if (disableCache !== true) {
1228 self._cachedTemplate = oldHTML;
1229 }
1230
1231 return data.returnValues.template;
1232 });
1233
1234 // block autocomplete
1235 this._tab.find('input[type=text]').attr('autocomplete', 'off');
1236
1237 // bind event listener
1238 this._tab.find('.formSubmit > button[data-type=save]').click($.proxy(this._save, this));
1239 this._tab.find('.formSubmit > button[data-type=restore]').click($.proxy(this._restore, this));
1240 this._tab.find('input').keyup(function(event) {
1241 if (event.which === $.ui.keyCode.ENTER) {
1242 self._save();
1243
1244 event.preventDefault();
1245 return false;
1246 }
1247 });
1248 },
1249
1250 /**
1251 * Destroys all editor instances within current tab.
1252 */
1253 _destroyEditor: function() {
1254 // destroy all editor instances
1255 this._tab.find('textarea').each(function(index, container) {
1256 var $container = $(container);
1257 if ($container.data('redactor')) {
1258 $container.redactor('core.destroy');
1259 }
1260 });
1261 }
1262 });
1263
1264 /**
1265 * Namespace for registration functions.
1266 */
1267 WCF.User.Registration = {};
1268
1269 /**
1270 * Validates the password.
1271 *
1272 * @param jQuery element
1273 * @param jQuery confirmElement
1274 * @param object options
1275 */
1276 WCF.User.Registration.Validation = Class.extend({
1277 /**
1278 * action name
1279 * @var string
1280 */
1281 _actionName: '',
1282
1283 /**
1284 * class name
1285 * @var string
1286 */
1287 _className: '',
1288
1289 /**
1290 * confirmation input element
1291 * @var jQuery
1292 */
1293 _confirmElement: null,
1294
1295 /**
1296 * input element
1297 * @var jQuery
1298 */
1299 _element: null,
1300
1301 /**
1302 * list of error messages
1303 * @var object
1304 */
1305 _errorMessages: { },
1306
1307 /**
1308 * list of additional options
1309 * @var object
1310 */
1311 _options: { },
1312
1313 /**
1314 * AJAX proxy
1315 * @var WCF.Action.Proxy
1316 */
1317 _proxy: null,
1318
1319 /**
1320 * Initializes the validation.
1321 *
1322 * @param jQuery element
1323 * @param jQuery confirmElement
1324 * @param object options
1325 */
1326 init: function(element, confirmElement, options) {
1327 this._element = element;
1328 this._element.blur($.proxy(this._blur, this));
1329 this._confirmElement = confirmElement || null;
1330
1331 if (this._confirmElement !== null) {
1332 this._confirmElement.blur($.proxy(this._blurConfirm, this));
1333 }
1334
1335 options = options || { };
1336 this._setOptions(options);
1337
1338 this._proxy = new WCF.Action.Proxy({
1339 success: $.proxy(this._success, this),
1340 showLoadingOverlay: false
1341 });
1342
1343 this._setErrorMessages();
1344 },
1345
1346 /**
1347 * Sets additional options
1348 */
1349 _setOptions: function(options) { },
1350
1351 /**
1352 * Sets error messages.
1353 */
1354 _setErrorMessages: function() {
1355 this._errorMessages = {
1356 ajaxError: '',
1357 notEqual: ''
1358 };
1359 },
1360
1361 /**
1362 * Validates once focus on input is lost.
1363 *
1364 * @param object event
1365 */
1366 _blur: function(event) {
1367 var $value = this._element.val();
1368 if (!$value) {
1369 return this._showError(this._element, WCF.Language.get('wcf.global.form.error.empty'));
1370 }
1371
1372 if (this._confirmElement !== null) {
1373 var $confirmValue = this._confirmElement.val();
1374 if ($confirmValue != '' && $value != $confirmValue) {
1375 return this._showError(this._confirmElement, this._errorMessages.notEqual);
1376 }
1377 }
1378
1379 if (!this._validateOptions()) {
1380 return;
1381 }
1382
1383 this._proxy.setOption('data', {
1384 actionName: this._actionName,
1385 className: this._className,
1386 parameters: this._getParameters()
1387 });
1388 this._proxy.sendRequest();
1389 },
1390
1391 /**
1392 * Returns a list of parameters.
1393 *
1394 * @return object
1395 */
1396 _getParameters: function() {
1397 return { };
1398 },
1399
1400 /**
1401 * Validates input by options.
1402 *
1403 * @return boolean
1404 */
1405 _validateOptions: function() {
1406 return true;
1407 },
1408
1409 /**
1410 * Validates value once confirmation input focus is lost.
1411 *
1412 * @param object event
1413 */
1414 _blurConfirm: function(event) {
1415 var $value = this._confirmElement.val();
1416 if (!$value) {
1417 return this._showError(this._confirmElement, WCF.Language.get('wcf.global.form.error.empty'));
1418 }
1419
1420 this._blur(event);
1421 },
1422
1423 /**
1424 * Handles AJAX responses.
1425 *
1426 * @param object data
1427 * @param string textStatus
1428 * @param jQuery jqXHR
1429 */
1430 _success: function(data, textStatus, jqXHR) {
1431 if (data.returnValues.isValid) {
1432 this._showSuccess(this._element);
1433 if (this._confirmElement !== null && this._confirmElement.val()) {
1434 this._showSuccess(this._confirmElement);
1435 }
1436 }
1437 else {
1438 this._showError(this._element, WCF.Language.get(this._errorMessages.ajaxError + data.returnValues.error));
1439 }
1440 },
1441
1442 /**
1443 * Shows an error message.
1444 *
1445 * @param jQuery element
1446 * @param string message
1447 */
1448 _showError: function(element, message) {
1449 element.parent().parent().addClass('formError').removeClass('formSuccess');
1450
1451 var $innerError = element.parent().find('small.innerError');
1452 if (!$innerError.length) {
1453 $innerError = $('<small />').addClass('innerError').insertAfter(element);
1454 }
1455
1456 $innerError.text(message);
1457 },
1458
1459 /**
1460 * Displays a success message.
1461 *
1462 * @param jQuery element
1463 */
1464 _showSuccess: function(element) {
1465 element.parent().parent().addClass('formSuccess').removeClass('formError');
1466 element.next('small.innerError').remove();
1467 }
1468 });
1469
1470 /**
1471 * Username validation for registration.
1472 *
1473 * @see WCF.User.Registration.Validation
1474 */
1475 WCF.User.Registration.Validation.Username = WCF.User.Registration.Validation.extend({
1476 /**
1477 * @see WCF.User.Registration.Validation._actionName
1478 */
1479 _actionName: 'validateUsername',
1480
1481 /**
1482 * @see WCF.User.Registration.Validation._className
1483 */
1484 _className: 'wcf\\data\\user\\UserRegistrationAction',
1485
1486 /**
1487 * @see WCF.User.Registration.Validation._setOptions()
1488 */
1489 _setOptions: function(options) {
1490 this._options = $.extend(true, {
1491 minlength: 3,
1492 maxlength: 25
1493 }, options);
1494 },
1495
1496 /**
1497 * @see WCF.User.Registration.Validation._setErrorMessages()
1498 */
1499 _setErrorMessages: function() {
1500 this._errorMessages = {
1501 ajaxError: 'wcf.user.username.error.'
1502 };
1503 },
1504
1505 /**
1506 * @see WCF.User.Registration.Validation._validateOptions()
1507 */
1508 _validateOptions: function() {
1509 var $value = this._element.val();
1510 if ($value.length < this._options.minlength || $value.length > this._options.maxlength) {
1511 this._showError(this._element, WCF.Language.get('wcf.user.username.error.notValid'));
1512 return false;
1513 }
1514
1515 return true;
1516 },
1517
1518 /**
1519 * @see WCF.User.Registration.Validation._getParameters()
1520 */
1521 _getParameters: function() {
1522 return {
1523 username: this._element.val()
1524 };
1525 }
1526 });
1527
1528 /**
1529 * Email validation for registration.
1530 *
1531 * @see WCF.User.Registration.Validation
1532 */
1533 WCF.User.Registration.Validation.EmailAddress = WCF.User.Registration.Validation.extend({
1534 /**
1535 * @see WCF.User.Registration.Validation._actionName
1536 */
1537 _actionName: 'validateEmailAddress',
1538
1539 /**
1540 * @see WCF.User.Registration.Validation._className
1541 */
1542 _className: 'wcf\\data\\user\\UserRegistrationAction',
1543
1544 /**
1545 * @see WCF.User.Registration.Validation._getParameters()
1546 */
1547 _getParameters: function() {
1548 return {
1549 email: this._element.val()
1550 };
1551 },
1552
1553 /**
1554 * @see WCF.User.Registration.Validation._setErrorMessages()
1555 */
1556 _setErrorMessages: function() {
1557 this._errorMessages = {
1558 ajaxError: 'wcf.user.email.error.',
1559 notEqual: WCF.Language.get('wcf.user.confirmEmail.error.notEqual')
1560 };
1561 }
1562 });
1563
1564 /**
1565 * Password validation for registration.
1566 *
1567 * @see WCF.User.Registration.Validation
1568 */
1569 WCF.User.Registration.Validation.Password = WCF.User.Registration.Validation.extend({
1570 /**
1571 * @see WCF.User.Registration.Validation._actionName
1572 */
1573 _actionName: 'validatePassword',
1574
1575 /**
1576 * @see WCF.User.Registration.Validation._className
1577 */
1578 _className: 'wcf\\data\\user\\UserRegistrationAction',
1579
1580 /**
1581 * @see WCF.User.Registration.Validation._getParameters()
1582 */
1583 _getParameters: function() {
1584 return {
1585 password: this._element.val()
1586 };
1587 },
1588
1589 /**
1590 * @see WCF.User.Registration.Validation._setErrorMessages()
1591 */
1592 _setErrorMessages: function() {
1593 this._errorMessages = {
1594 ajaxError: 'wcf.user.password.error.',
1595 notEqual: WCF.Language.get('wcf.user.confirmPassword.error.notEqual')
1596 };
1597 }
1598 });
1599
1600 /**
1601 * Toggles input fields for lost password form.
1602 */
1603 WCF.User.Registration.LostPassword = Class.extend({
1604 /**
1605 * email input
1606 * @var jQuery
1607 */
1608 _email: null,
1609
1610 /**
1611 * username input
1612 * @var jQuery
1613 */
1614 _username: null,
1615
1616 /**
1617 * Initializes LostPassword-form class.
1618 */
1619 init: function() {
1620 // bind input fields
1621 this._email = $('#emailInput');
1622 this._username = $('#usernameInput');
1623
1624 // bind event listener
1625 this._email.keyup($.proxy(this._checkEmail, this));
1626 this._username.keyup($.proxy(this._checkUsername, this));
1627
1628 if ($.browser.mozilla && $.browser.touch) {
1629 this._email.on('input', $.proxy(this._checkEmail, this));
1630 this._username.on('input', $.proxy(this._checkUsername, this));
1631 }
1632
1633 // toggle fields on init
1634 this._checkEmail();
1635 this._checkUsername();
1636 },
1637
1638 /**
1639 * Checks for content in email field and toggles username.
1640 */
1641 _checkEmail: function() {
1642 if (this._email.val() == '') {
1643 this._username.enable();
1644 this._username.parents('dl:eq(0)').removeClass('disabled');
1645 }
1646 else {
1647 this._username.disable();
1648 this._username.parents('dl:eq(0)').addClass('disabled');
1649 }
1650 },
1651
1652 /**
1653 * Checks for content in username field and toggles email.
1654 */
1655 _checkUsername: function() {
1656 if (this._username.val() == '') {
1657 this._email.enable();
1658 this._email.parents('dl:eq(0)').removeClass('disabled');
1659 }
1660 else {
1661 this._email.disable();
1662 this._email.parents('dl:eq(0)').addClass('disabled');
1663 }
1664 }
1665 });
1666
1667 /**
1668 * Notification system for WCF.
1669 *
1670 * @author Alexander Ebert
1671 * @copyright 2001-2014 WoltLab GmbH
1672 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
1673 */
1674 WCF.Notification = { };
1675
1676 /**
1677 * Handles the notification list.
1678 */
1679 WCF.Notification.List = Class.extend({
1680 /**
1681 * action proxy
1682 * @var WCF.Action.Proxy
1683 */
1684 _proxy: null,
1685
1686 /**
1687 * Initializes the WCF.Notification.List object.
1688 */
1689 init: function() {
1690 this._proxy = new WCF.Action.Proxy({
1691 success: $.proxy(this._success, this)
1692 });
1693
1694 // handle 'mark all as confirmed' buttons
1695 $('.contentNavigation .jsMarkAllAsConfirmed').click(function() {
1696 WCF.System.Confirmation.show(WCF.Language.get('wcf.user.notification.markAllAsConfirmed.confirmMessage'), function(action) {
1697 if (action === 'confirm') {
1698 new WCF.Action.Proxy({
1699 autoSend: true,
1700 data: {
1701 actionName: 'markAllAsConfirmed',
1702 className: 'wcf\\data\\user\\notification\\UserNotificationAction'
1703 },
1704 success: function() { window.location.reload(); }
1705 });
1706 }
1707 });
1708 });
1709
1710 // handle regular items
1711 this._convertList();
1712 },
1713
1714 /**
1715 * Converts the notification item list to be in sync with the notification dropdown.
1716 */
1717 _convertList: function() {
1718 $('.userNotificationItemList > .notificationItem').each((function(index, item) {
1719 var $item = $(item);
1720
1721 if (!$item.data('isRead')) {
1722 $item.find('a:not(.userLink)').prop('href', $item.data('link'));
1723
1724 var $markAsConfirmed = $('<a href="#" class="icon icon24 fa-check notificationItemMarkAsConfirmed jsTooltip" title="' + WCF.Language.get('wcf.user.notification.markAsConfirmed') + '" />').appendTo($item);
1725 $markAsConfirmed.click($.proxy(this._markAsConfirmed, this));
1726 }
1727 }).bind(this));
1728
1729 WCF.DOMNodeInsertedHandler.execute();
1730 },
1731
1732 /**
1733 * Marks a single notification as confirmed.
1734 *
1735 * @param object event
1736 */
1737 _markAsConfirmed: function(event) {
1738 event.preventDefault();
1739
1740 var $notificationID = $(event.currentTarget).parents('.notificationItem:eq(0)').data('objectID');
1741
1742 this._proxy.setOption('data', {
1743 actionName: 'markAsConfirmed',
1744 className: 'wcf\\data\\user\\notification\\UserNotificationAction',
1745 objectIDs: [ $notificationID ]
1746 });
1747 this._proxy.sendRequest();
1748
1749 return false;
1750 },
1751
1752 /**
1753 * Handles successful AJAX requests.
1754 *
1755 * @param object data
1756 * @param string textStatus
1757 * @param jQuery jqXHR
1758 */
1759 _success: function(data, textStatus, jqXHR) {
1760 var $item = $('.userNotificationItemList > .notificationItem[data-object-id=' + data.returnValues.markAsRead + ']');
1761
1762 $item.data('isRead', true);
1763 $item.find('.newContentBadge').remove();
1764 $item.find('.notificationItemMarkAsConfirmed').remove();
1765 $item.removeClass('notificationUnconfirmed');
1766 }
1767 });
1768
1769 /**
1770 * Signature preview.
1771 *
1772 * @see WCF.Message.Preview
1773 */
1774 WCF.User.SignaturePreview = WCF.Message.Preview.extend({
1775 /**
1776 * @see WCF.Message.Preview._handleResponse()
1777 */
1778 _handleResponse: function(data) {
1779 // get preview container
1780 var $preview = $('#previewContainer');
1781 if (!$preview.length) {
1782 $preview = $('<fieldset id="previewContainer"><legend>' + WCF.Language.get('wcf.global.preview') + '</legend><div></div></fieldset>').insertBefore($('#signatureContainer')).wcfFadeIn();
1783 }
1784
1785 $preview.children('div').first().html(data.returnValues.message);
1786 }
1787 });
1788
1789 /**
1790 * Loads recent activity events once the user scrolls to the very bottom.
1791 *
1792 * @param integer userID
1793 */
1794 WCF.User.RecentActivityLoader = Class.extend({
1795 /**
1796 * container object
1797 * @var jQuery
1798 */
1799 _container: null,
1800
1801 /**
1802 * true if list should be filtered by followed users
1803 * @var boolean
1804 */
1805 _filteredByFollowedUsers: false,
1806
1807 /**
1808 * button to load next events
1809 * @var jQuery
1810 */
1811 _loadButton: null,
1812
1813 /**
1814 * action proxy
1815 * @var WCF.Action.Proxy
1816 */
1817 _proxy: null,
1818
1819 /**
1820 * user id
1821 * @var integer
1822 */
1823 _userID: 0,
1824
1825 /**
1826 * Initializes a new RecentActivityLoader object.
1827 *
1828 * @param integer userID
1829 * @param boolean filteredByFollowedUsers
1830 */
1831 init: function(userID, filteredByFollowedUsers) {
1832 this._container = $('#recentActivities');
1833 this._filteredByFollowedUsers = (filteredByFollowedUsers === true);
1834 this._userID = userID;
1835
1836 if (this._userID !== null && !this._userID) {
1837 console.debug("[WCF.User.RecentActivityLoader] Invalid parameter 'userID' given.");
1838 return;
1839 }
1840
1841 this._proxy = new WCF.Action.Proxy({
1842 success: $.proxy(this._success, this)
1843 });
1844
1845 if (this._container.children('li').length) {
1846 this._loadButton = $('<li class="recentActivitiesMore"><button class="small">' + WCF.Language.get('wcf.user.recentActivity.more') + '</button></li>').appendTo(this._container);
1847 this._loadButton = this._loadButton.children('button').click($.proxy(this._click, this));
1848 }
1849 else {
1850 $('<li class="recentActivitiesMore"><small>' + WCF.Language.get('wcf.user.recentActivity.noMoreEntries') + '</small></li>').appendTo(this._container);
1851 }
1852
1853 if (WCF.User.userID) {
1854 $('.jsRecentActivitySwitchContext .button').click($.proxy(this._switchContext, this));
1855 }
1856 },
1857
1858 /**
1859 * Loads next activity events.
1860 */
1861 _click: function() {
1862 this._loadButton.enable();
1863
1864 var $parameters = {
1865 lastEventID: this._container.data('lastEventID'),
1866 lastEventTime: this._container.data('lastEventTime')
1867 };
1868 if (this._userID) {
1869 $parameters.userID = this._userID;
1870 }
1871 else if (this._filteredByFollowedUsers) {
1872 $parameters.filteredByFollowedUsers = 1;
1873 }
1874
1875 this._proxy.setOption('data', {
1876 actionName: 'load',
1877 className: 'wcf\\data\\user\\activity\\event\\UserActivityEventAction',
1878 parameters: $parameters
1879 });
1880 this._proxy.sendRequest();
1881 },
1882
1883 /**
1884 * Switches recent activity context.
1885 */
1886 _switchContext: function(event) {
1887 event.preventDefault();
1888
1889 if (!$(event.currentTarget).hasClass('active')) {
1890 new WCF.Action.Proxy({
1891 autoSend: true,
1892 data: {
1893 actionName: 'switchContext',
1894 className: 'wcf\\data\\user\\activity\\event\\UserActivityEventAction'
1895 },
1896 success: function() {
1897 window.location.hash = '#dashboardBoxRecentActivity';
1898 window.location.reload();
1899 }
1900 });
1901 }
1902 },
1903
1904 /**
1905 * Handles successful AJAX requests.
1906 *
1907 * @param object data
1908 * @param string textStatus
1909 * @param jQuery jqXHR
1910 */
1911 _success: function(data, textStatus, jqXHR) {
1912 if (data.returnValues.template) {
1913 $(data.returnValues.template).insertBefore(this._loadButton.parent());
1914
1915 this._container.data('lastEventTime', data.returnValues.lastEventTime);
1916 this._container.data('lastEventID', data.returnValues.lastEventID);
1917 this._loadButton.enable();
1918 }
1919 else {
1920 $('<small>' + WCF.Language.get('wcf.user.recentActivity.noMoreEntries') + '</small>').appendTo(this._loadButton.parent());
1921 this._loadButton.remove();
1922 }
1923 }
1924 });
1925
1926 /**
1927 * Loads likes once the user scrolls to the very bottom.
1928 *
1929 * @param integer userID
1930 */
1931 WCF.User.LikeLoader = Class.extend({
1932 /**
1933 * container object
1934 * @var jQuery
1935 */
1936 _container: null,
1937
1938 /**
1939 * like type
1940 * @var string
1941 */
1942 _likeType: 'received',
1943
1944 /**
1945 * like value
1946 * @var integer
1947 */
1948 _likeValue: 1,
1949
1950 /**
1951 * button to load next events
1952 * @var jQuery
1953 */
1954 _loadButton: null,
1955
1956 /**
1957 * 'no more entries' element
1958 * @var jQuery
1959 */
1960 _noMoreEntries: null,
1961
1962 /**
1963 * action proxy
1964 * @var WCF.Action.Proxy
1965 */
1966 _proxy: null,
1967
1968 /**
1969 * user id
1970 * @var integer
1971 */
1972 _userID: 0,
1973
1974 /**
1975 * Initializes a new RecentActivityLoader object.
1976 *
1977 * @param integer userID
1978 * @param boolean filteredByFollowedUsers
1979 */
1980 init: function(userID) {
1981 this._container = $('#likeList');
1982 this._userID = userID;
1983
1984 if (!this._userID) {
1985 console.debug("[WCF.User.RecentActivityLoader] Invalid parameter 'userID' given.");
1986 return;
1987 }
1988
1989 this._proxy = new WCF.Action.Proxy({
1990 success: $.proxy(this._success, this)
1991 });
1992
1993 var $container = $('<li class="likeListMore recentActivitiesMore"><button class="small">' + WCF.Language.get('wcf.like.likes.more') + '</button><small>' + WCF.Language.get('wcf.like.likes.noMoreEntries') + '</small></li>').appendTo(this._container);
1994 this._loadButton = $container.children('button').click($.proxy(this._click, this));
1995 this._noMoreEntries = $container.children('small').hide();
1996
1997 if (this._container.find('> li').length == 2) {
1998 this._loadButton.hide();
1999 this._noMoreEntries.show();
2000 }
2001
2002 $('#likeType .button').click($.proxy(this._clickLikeType, this));
2003 $('#likeValue .button').click($.proxy(this._clickLikeValue, this));
2004 },
2005
2006 /**
2007 * Handles like type change.
2008 */
2009 _clickLikeType: function(event) {
2010 var $button = $(event.currentTarget);
2011 if (this._likeType != $button.data('likeType')) {
2012 this._likeType = $button.data('likeType');
2013 $('#likeType .button').removeClass('active');
2014 $button.addClass('active');
2015 this._reload();
2016 }
2017 },
2018
2019 /**
2020 * Handles like value change.
2021 */
2022 _clickLikeValue: function(event) {
2023 var $button = $(event.currentTarget);
2024 if (this._likeValue != $button.data('likeValue')) {
2025 this._likeValue = $button.data('likeValue');
2026 $('#likeValue .button').removeClass('active');
2027 $button.addClass('active');
2028
2029 // change button labels
2030 $('#likeType > li:first-child > .button').text(WCF.Language.get('wcf.like.' + (this._likeValue == -1 ? 'dis' : '') + 'likesReceived'));
2031 $('#likeType > li:last-child > .button').text(WCF.Language.get('wcf.like.' + (this._likeValue == -1 ? 'dis' : '') + 'likesGiven'));
2032
2033 this._reload();
2034 }
2035 },
2036
2037 /**
2038 * Handles reload.
2039 */
2040 _reload: function() {
2041 this._container.find('> li:not(:first-child):not(:last-child)').remove();
2042 this._container.data('lastLikeTime', 0);
2043 this._click();
2044 },
2045
2046 /**
2047 * Loads next likes.
2048 */
2049 _click: function() {
2050 this._loadButton.enable();
2051
2052 var $parameters = {
2053 lastLikeTime: this._container.data('lastLikeTime'),
2054 userID: this._userID,
2055 likeType: this._likeType,
2056 likeValue: this._likeValue
2057 };
2058
2059 this._proxy.setOption('data', {
2060 actionName: 'load',
2061 className: 'wcf\\data\\like\\LikeAction',
2062 parameters: $parameters
2063 });
2064 this._proxy.sendRequest();
2065 },
2066
2067 /**
2068 * Handles successful AJAX requests.
2069 *
2070 * @param object data
2071 * @param string textStatus
2072 * @param jQuery jqXHR
2073 */
2074 _success: function(data, textStatus, jqXHR) {
2075 if (data.returnValues.template) {
2076 $(data.returnValues.template).insertBefore(this._loadButton.parent());
2077
2078 this._container.data('lastLikeTime', data.returnValues.lastLikeTime);
2079 this._noMoreEntries.hide();
2080 this._loadButton.show().enable();
2081 }
2082 else {
2083 this._noMoreEntries.show();
2084 this._loadButton.hide();
2085 }
2086 }
2087 });
2088
2089 /**
2090 * Loads user profile previews.
2091 *
2092 * @see WCF.Popover
2093 */
2094 WCF.User.ProfilePreview = WCF.Popover.extend({
2095 /**
2096 * action proxy
2097 * @var WCF.Action.Proxy
2098 */
2099 _proxy: null,
2100
2101 /**
2102 * list of user profiles
2103 * @var object
2104 */
2105 _userProfiles: { },
2106
2107 /**
2108 * @see WCF.Popover.init()
2109 */
2110 init: function() {
2111 this._super('.userLink');
2112
2113 this._proxy = new WCF.Action.Proxy({
2114 showLoadingOverlay: false
2115 });
2116
2117 // register instance
2118 WCF.System.ObjectStore.add('WCF.User.ProfilePreview', this);
2119 },
2120
2121 /**
2122 * @see WCF.Popover._loadContent()
2123 */
2124 _loadContent: function() {
2125 var $element = $('#' + this._activeElementID);
2126 var $userID = $element.data('userID');
2127
2128 if (this._userProfiles[$userID]) {
2129 // use cached user profile
2130 this._insertContent(this._activeElementID, this._userProfiles[$userID], true);
2131 }
2132 else {
2133 this._proxy.setOption('data', {
2134 actionName: 'getUserProfile',
2135 className: 'wcf\\data\\user\\UserProfileAction',
2136 objectIDs: [ $userID ]
2137 });
2138
2139 var $elementID = this._activeElementID;
2140 var self = this;
2141 this._proxy.setOption('success', function(data, textStatus, jqXHR) {
2142 // cache user profile
2143 self._userProfiles[$userID] = data.returnValues.template;
2144
2145 // show user profile
2146 self._insertContent($elementID, data.returnValues.template, true);
2147 });
2148 this._proxy.setOption('failure', function(data, jqXHR, textStatus, errorThrown) {
2149 // cache user profile
2150 self._userProfiles[$userID] = data.message;
2151
2152 // show user profile
2153 self._insertContent($elementID, data.message, true);
2154
2155 return false;
2156 });
2157 this._proxy.sendRequest();
2158 }
2159 },
2160
2161 /**
2162 * Purages a cached user profile.
2163 *
2164 * @param integer userID
2165 */
2166 purge: function(userID) {
2167 delete this._userProfiles[userID];
2168
2169 // purge content cache
2170 this._data = { };
2171 }
2172 });
2173
2174 /**
2175 * Initalizes WCF.User.Action namespace.
2176 */
2177 WCF.User.Action = {};
2178
2179 /**
2180 * Handles user follow and unfollow links.
2181 */
2182 WCF.User.Action.Follow = Class.extend({
2183 /**
2184 * list with elements containing follow and unfollow buttons
2185 * @var array
2186 */
2187 _containerList: null,
2188
2189 /**
2190 * CSS selector for follow buttons
2191 * @var string
2192 */
2193 _followButtonSelector: '.jsFollowButton',
2194
2195 /**
2196 * id of the user that is currently being followed/unfollowed
2197 * @var integer
2198 */
2199 _userID: 0,
2200
2201 /**
2202 * Initializes new WCF.User.Action.Follow object.
2203 *
2204 * @param array containerList
2205 * @param string followButtonSelector
2206 */
2207 init: function(containerList, followButtonSelector) {
2208 if (!containerList.length) {
2209 return;
2210 }
2211 this._containerList = containerList;
2212
2213 if (followButtonSelector) {
2214 this._followButtonSelector = followButtonSelector;
2215 }
2216
2217 // initialize proxy
2218 this._proxy = new WCF.Action.Proxy({
2219 success: $.proxy(this._success, this)
2220 });
2221
2222 // bind event listeners
2223 this._containerList.each($.proxy(function(index, container) {
2224 $(container).find(this._followButtonSelector).click($.proxy(this._click, this));
2225 }, this));
2226 },
2227
2228 /**
2229 * Handles a click on a follow or unfollow button.
2230 *
2231 * @param object event
2232 */
2233 _click: function(event) {
2234 event.preventDefault();
2235 var link = $(event.target);
2236 if (!link.is('a')) {
2237 link = link.closest('a');
2238 }
2239 this._userID = link.data('objectID');
2240
2241 this._proxy.setOption('data', {
2242 'actionName': link.data('following') ? 'unfollow' : 'follow',
2243 'className': 'wcf\\data\\user\\follow\\UserFollowAction',
2244 'parameters': {
2245 data: {
2246 userID: this._userID
2247 }
2248 }
2249 });
2250 this._proxy.sendRequest();
2251 },
2252
2253 /**
2254 * Handles the successful (un)following of a user.
2255 *
2256 * @param object data
2257 * @param string textStatus
2258 * @param jQuery jqXHR
2259 */
2260 _success: function(data, textStatus, jqXHR) {
2261 this._containerList.each($.proxy(function(index, container) {
2262 var button = $(container).find(this._followButtonSelector).get(0);
2263
2264 if (button && $(button).data('objectID') == this._userID) {
2265 button = $(button);
2266
2267 // toogle icon title
2268 if (data.returnValues.following) {
2269 button.data('tooltip', WCF.Language.get('wcf.user.button.unfollow')).children('.icon').removeClass('icon-plus').addClass('icon-minus');
2270 }
2271 else {
2272 button.data('tooltip', WCF.Language.get('wcf.user.button.follow')).children('.icon').removeClass('icon-minus').addClass('icon-plus');
2273 }
2274
2275 button.data('following', data.returnValues.following);
2276
2277 return false;
2278 }
2279 }, this));
2280
2281 var $notification = new WCF.System.Notification();
2282 $notification.show();
2283
2284 // force rebuilding of popover cache
2285 var self = this;
2286 WCF.System.ObjectStore.invoke('WCF.User.ProfilePreview', function(profilePreview) {
2287 profilePreview.purge(self._userID);
2288 });
2289 }
2290 });
2291
2292 /**
2293 * Handles user ignore and unignore links.
2294 */
2295 WCF.User.Action.Ignore = Class.extend({
2296 /**
2297 * list with elements containing ignore and unignore buttons
2298 * @var array
2299 */
2300 _containerList: null,
2301
2302 /**
2303 * CSS selector for ignore buttons
2304 * @var string
2305 */
2306 _ignoreButtonSelector: '.jsIgnoreButton',
2307
2308 /**
2309 * id of the user that is currently being ignored/unignored
2310 * @var integer
2311 */
2312 _userID: 0,
2313
2314 /**
2315 * Initializes new WCF.User.Action.Ignore object.
2316 *
2317 * @param array containerList
2318 * @param string ignoreButtonSelector
2319 */
2320 init: function(containerList, ignoreButtonSelector) {
2321 if (!containerList.length) {
2322 return;
2323 }
2324 this._containerList = containerList;
2325
2326 if (ignoreButtonSelector) {
2327 this._ignoreButtonSelector = ignoreButtonSelector;
2328 }
2329
2330 // initialize proxy
2331 this._proxy = new WCF.Action.Proxy({
2332 success: $.proxy(this._success, this)
2333 });
2334
2335 // bind event listeners
2336 this._containerList.each($.proxy(function(index, container) {
2337 $(container).find(this._ignoreButtonSelector).click($.proxy(this._click, this));
2338 }, this));
2339 },
2340
2341 /**
2342 * Handles a click on a ignore or unignore button.
2343 *
2344 * @param object event
2345 */
2346 _click: function(event) {
2347 event.preventDefault();
2348 var link = $(event.target);
2349 if (!link.is('a')) {
2350 link = link.closest('a');
2351 }
2352 this._userID = link.data('objectID');
2353
2354 this._proxy.setOption('data', {
2355 'actionName': link.data('ignored') ? 'unignore' : 'ignore',
2356 'className': 'wcf\\data\\user\\ignore\\UserIgnoreAction',
2357 'parameters': {
2358 data: {
2359 ignoreUserID: this._userID
2360 }
2361 }
2362 });
2363 this._proxy.sendRequest();
2364 },
2365
2366 /**
2367 * Handles the successful (un)ignoring of a user.
2368 *
2369 * @param object data
2370 * @param string textStatus
2371 * @param jQuery jqXHR
2372 */
2373 _success: function(data, textStatus, jqXHR) {
2374 this._containerList.each($.proxy(function(index, container) {
2375 var button = $(container).find(this._ignoreButtonSelector).get(0);
2376
2377 if (button && $(button).data('objectID') == this._userID) {
2378 button = $(button);
2379
2380 // toogle icon title
2381 if (data.returnValues.isIgnoredUser) {
2382 button.data('tooltip', WCF.Language.get('wcf.user.button.unignore')).children('.icon').removeClass('icon-ban-circle').addClass('icon-circle-blank');
2383 }
2384 else {
2385 button.data('tooltip', WCF.Language.get('wcf.user.button.ignore')).children('.icon').removeClass('icon-circle-blank').addClass('icon-ban-circle');
2386 }
2387
2388 button.data('ignored', data.returnValues.isIgnoredUser);
2389
2390 return false;
2391 }
2392 }, this));
2393
2394 var $notification = new WCF.System.Notification();
2395 $notification.show();
2396
2397 // force rebuilding of popover cache
2398 var self = this;
2399 WCF.System.ObjectStore.invoke('WCF.User.ProfilePreview', function(profilePreview) {
2400 profilePreview.purge(self._userID);
2401 });
2402 }
2403 });
2404
2405 /**
2406 * Namespace for avatar functions.
2407 */
2408 WCF.User.Avatar = {};
2409
2410 /**
2411 * Handles cropping an avatar.
2412 */
2413 WCF.User.Avatar.Crop = Class.extend({
2414 /**
2415 * current crop setting in x-direction
2416 * @var integer
2417 */
2418 _cropX: 0,
2419
2420 /**
2421 * current crop setting in y-direction
2422 * @var integer
2423 */
2424 _cropY: 0,
2425
2426 /**
2427 * avatar crop dialog
2428 * @var jQuery
2429 */
2430 _dialog: null,
2431
2432 /**
2433 * action proxy to send the crop AJAX requests
2434 * @var WCF.Action.Proxy
2435 */
2436 _proxy: null,
2437
2438 /**
2439 * maximum size of thumbnails
2440 * @var integer
2441 */
2442 MAX_THUMBNAIL_SIZE: 128,
2443
2444 /**
2445 * Creates a new instance of WCF.User.Avatar.Crop.
2446 *
2447 * @param integer avatarID
2448 */
2449 init: function(avatarID) {
2450 this._avatarID = avatarID;
2451
2452 if (this._dialog) {
2453 this.destroy();
2454 }
2455 this._dialog = null;
2456
2457 // check if object already had been initialized
2458 if (!this._proxy) {
2459 this._proxy = new WCF.Action.Proxy({
2460 success: $.proxy(this._success, this)
2461 });
2462 }
2463
2464 $('.userAvatarCrop').click($.proxy(this._showCropDialog, this));
2465 },
2466
2467 /**
2468 * Destroys the avatar crop interface.
2469 */
2470 destroy: function() {
2471 this._dialog.remove();
2472 },
2473
2474 /**
2475 * Sends AJAX request to crop avatar.
2476 *
2477 * @param object event
2478 */
2479 _crop: function(event) {
2480 this._proxy.setOption('data', {
2481 actionName: 'cropAvatar',
2482 className: 'wcf\\data\\user\\avatar\\UserAvatarAction',
2483 objectIDs: [ this._avatarID ],
2484 parameters: {
2485 cropX: this._cropX,
2486 cropY: this._cropY
2487 }
2488 });
2489 this._proxy.sendRequest();
2490 },
2491
2492 /**
2493 * Initializes the dialog after a successful 'getCropDialog' request.
2494 *
2495 * @param object data
2496 */
2497 _getCropDialog: function(data) {
2498 if (!this._dialog) {
2499 this._dialog = $('<div />').hide().appendTo(document.body);
2500 this._dialog.wcfDialog({
2501 title: WCF.Language.get('wcf.user.avatar.type.custom.crop')
2502 });
2503 }
2504
2505 this._dialog.html(data.returnValues.template);
2506 this._dialog.find('button[data-type="save"]').click($.proxy(this._crop, this));
2507
2508 this._cropX = data.returnValues.cropX;
2509 this._cropY = data.returnValues.cropY;
2510
2511 var $image = $('#userAvatarCropSelection > img');
2512 $('#userAvatarCropSelection').css({
2513 height: $image.height() + 'px',
2514 width: $image.width() + 'px'
2515 });
2516 $('#userAvatarCropOverlaySelection').css({
2517 'background-image': 'url(' + $image.attr('src') + ')',
2518 'background-position': -this._cropX + 'px ' + -this._cropY + 'px',
2519 'left': this._cropX + 'px',
2520 'top': this._cropY + 'px'
2521 }).draggable({
2522 containment: 'parent',
2523 drag : $.proxy(this._updateSelection, this),
2524 stop : $.proxy(this._updateSelection, this)
2525 });
2526
2527 this._dialog.find('button[data-type="save"]').click($.proxy(this._save, this));
2528
2529 this._dialog.wcfDialog('render');
2530 },
2531
2532 /**
2533 * Shows the cropping dialog.
2534 */
2535 _showCropDialog: function() {
2536 if (!this._dialog) {
2537 this._proxy.setOption('data', {
2538 actionName: 'getCropDialog',
2539 className: 'wcf\\data\\user\\avatar\\UserAvatarAction',
2540 objectIDs: [ this._avatarID ]
2541 });
2542 this._proxy.sendRequest();
2543 }
2544 else {
2545 this._dialog.wcfDialog('open');
2546 }
2547 },
2548
2549 /**
2550 * Handles successful AJAX request.
2551 *
2552 * @param object data
2553 * @param string textStatus
2554 * @param jQuery jqXHR
2555 */
2556 _success: function(data, textStatus, jqXHR) {
2557 switch (data.actionName) {
2558 case 'getCropDialog':
2559 this._getCropDialog(data);
2560 break;
2561
2562 case 'cropAvatar':
2563 $('#avatarUpload > dt > img').replaceWith($('<img src="' + data.returnValues.url + '" alt="" class="userAvatarCrop jsTooltip" title="' + WCF.Language.get('wcf.user.avatar.type.custom.crop') + '" />').css({
2564 width: '96px',
2565 height: '96px'
2566 }).click($.proxy(this._showCropDialog, this)));
2567
2568 WCF.DOMNodeInsertedHandler.execute();
2569
2570 this._dialog.wcfDialog('close');
2571
2572 var $notification = new WCF.System.Notification();
2573 $notification.show();
2574 break;
2575 }
2576 },
2577
2578 /**
2579 * Updates the current crop selection if the selection overlay is dragged.
2580 *
2581 * @param object event
2582 * @param object ui
2583 */
2584 _updateSelection: function(event, ui) {
2585 this._cropX = ui.position.left;
2586 this._cropY = ui.position.top;
2587
2588 $('#userAvatarCropOverlaySelection').css({
2589 'background-position': -ui.position.left + 'px ' + -ui.position.top + 'px'
2590 });
2591 }
2592 });
2593
2594 /**
2595 * Avatar upload function
2596 *
2597 * @see WCF.Upload
2598 */
2599 WCF.User.Avatar.Upload = WCF.Upload.extend({
2600 /**
2601 * handles cropping the avatar
2602 * @var WCF.User.Avatar.Crop
2603 */
2604 _avatarCrop: null,
2605
2606 /**
2607 * user id of avatar owner
2608 * @var integer
2609 */
2610 _userID: 0,
2611
2612 /**
2613 * Initalizes a new WCF.User.Avatar.Upload object.
2614 *
2615 * @param integer userID
2616 * @param WCF.User.Avatar.Crop avatarCrop
2617 */
2618 init: function(userID, avatarCrop) {
2619 this._super($('#avatarUpload > dd > div'), undefined, 'wcf\\data\\user\\avatar\\UserAvatarAction');
2620 this._userID = userID || 0;
2621 this._avatarCrop = avatarCrop;
2622
2623 $('#avatarForm input[type=radio]').change(function() {
2624 if ($(this).val() == 'custom') {
2625 $('#avatarUpload > dd > div').show();
2626 }
2627 else {
2628 $('#avatarUpload > dd > div').hide();
2629 }
2630 });
2631 if (!$('#avatarForm input[type=radio][value=custom]:checked').length) {
2632 $('#avatarUpload > dd > div').hide();
2633 }
2634 },
2635
2636 /**
2637 * @see WCF.Upload._initFile()
2638 */
2639 _initFile: function(file) {
2640 return $('#avatarUpload > dt > img');
2641 },
2642
2643 /**
2644 * @see WCF.Upload._success()
2645 */
2646 _success: function(uploadID, data) {
2647 if (data.returnValues.url) {
2648 this._updateImage(data.returnValues.url, data.returnValues.canCrop);
2649
2650 if (data.returnValues.canCrop) {
2651 if (!this._avatarCrop) {
2652 this._avatarCrop = new WCF.User.Avatar.Crop(data.returnValues.avatarID);
2653 }
2654 else {
2655 this._avatarCrop.init(data.returnValues.avatarID);
2656 }
2657 }
2658 else if (this._avatarCrop) {
2659 this._avatarCrop.destroy();
2660 this._avatarCrop = null;
2661 }
2662
2663 // hide error
2664 $('#avatarUpload > dd > .innerError').remove();
2665
2666 // show success message
2667 var $notification = new WCF.System.Notification(WCF.Language.get('wcf.user.avatar.upload.success'));
2668 $notification.show();
2669 }
2670 else if (data.returnValues.errorType) {
2671 // show error
2672 this._getInnerErrorElement().text(WCF.Language.get('wcf.user.avatar.upload.error.' + data.returnValues.errorType));
2673 }
2674 },
2675
2676 /**
2677 * Updates the displayed avatar image.
2678 *
2679 * @param string url
2680 * @param boolean canCrop
2681 */
2682 _updateImage: function(url, canCrop) {
2683 $('#avatarUpload > dt > img').remove();
2684 var $image = $('<img src="' + url + '" alt="" />').css({
2685 'height': 'auto',
2686 'max-height': '96px',
2687 'max-width': '96px',
2688 'width': 'auto'
2689 });
2690 if (canCrop) {
2691 $image.addClass('userAvatarCrop').addClass('jsTooltip');
2692 $image.attr('title', WCF.Language.get('wcf.user.avatar.type.custom.crop'));
2693 }
2694
2695 $('#avatarUpload > dt').prepend($image);
2696
2697 WCF.DOMNodeInsertedHandler.execute();
2698 },
2699
2700 /**
2701 * Returns the inner error element.
2702 *
2703 * @return jQuery
2704 */
2705 _getInnerErrorElement: function() {
2706 var $span = $('#avatarUpload > dd > .innerError');
2707 if (!$span.length) {
2708 $span = $('<small class="innerError"></span>');
2709 $('#avatarUpload > dd').append($span);
2710 }
2711
2712 return $span;
2713 },
2714
2715 /**
2716 * @see WCF.Upload._getParameters()
2717 */
2718 _getParameters: function() {
2719 return {
2720 userID: this._userID
2721 };
2722 },
2723 });
2724
2725 /**
2726 * Generic implementation for grouped user lists.
2727 *
2728 * @param string className
2729 * @param string dialogTitle
2730 * @param object additionalParameters
2731 */
2732 WCF.User.List = Class.extend({
2733 /**
2734 * list of additional parameters
2735 * @var object
2736 */
2737 _additionalParameters: { },
2738
2739 /**
2740 * list of cached pages
2741 * @var object
2742 */
2743 _cache: { },
2744
2745 /**
2746 * action class name
2747 * @var string
2748 */
2749 _className: '',
2750
2751 /**
2752 * dialog overlay
2753 * @var jQuery
2754 */
2755 _dialog: null,
2756
2757 /**
2758 * dialog title
2759 * @var string
2760 */
2761 _dialogTitle: '',
2762
2763 /**
2764 * page count
2765 * @var integer
2766 */
2767 _pageCount: 0,
2768
2769 /**
2770 * current page no
2771 * @var integer
2772 */
2773 _pageNo: 1,
2774
2775 /**
2776 * action proxy
2777 * @var WCF.Action.Proxy
2778 */
2779 _proxy: null,
2780
2781 /**
2782 * Initializes a new grouped user list.
2783 *
2784 * @param string className
2785 * @param string dialogTitle
2786 * @param object additionalParameters
2787 */
2788 init: function(className, dialogTitle, additionalParameters) {
2789 this._additionalParameters = additionalParameters || { };
2790 this._cache = { };
2791 this._className = className;
2792 this._dialog = null;
2793 this._dialogTitle = dialogTitle;
2794 this._pageCount = 0;
2795 this._pageNo = 1;
2796
2797 this._proxy = new WCF.Action.Proxy({
2798 success: $.proxy(this._success, this)
2799 });
2800 },
2801
2802 /**
2803 * Opens the dialog overlay.
2804 */
2805 open: function() {
2806 this._pageNo = 1;
2807 this._showPage();
2808 },
2809
2810 /**
2811 * Displays the specified page.
2812 *
2813 * @param object event
2814 * @param object data
2815 */
2816 _showPage: function(event, data) {
2817 if (data && data.activePage) {
2818 this._pageNo = data.activePage;
2819 }
2820
2821 if (this._pageCount != 0 && (this._pageNo < 1 || this._pageNo > this._pageCount)) {
2822 console.debug("[WCF.User.List] Cannot access page " + this._pageNo + " of " + this._pageCount);
2823 return;
2824 }
2825
2826 if (this._cache[this._pageNo]) {
2827 var $dialogCreated = false;
2828 if (this._dialog === null) {
2829 this._dialog = $('<div id="userList' + this._className.hashCode() + '" />').hide().appendTo(document.body);
2830 $dialogCreated = true;
2831 }
2832
2833 // remove current view
2834 this._dialog.empty();
2835
2836 // insert HTML
2837 this._dialog.html(this._cache[this._pageNo]);
2838
2839 // add pagination
2840 if (this._pageCount > 1) {
2841 this._dialog.find('.jsPagination').wcfPages({
2842 activePage: this._pageNo,
2843 maxPage: this._pageCount
2844 }).on('wcfpagesswitched', $.proxy(this._showPage, this));
2845 }
2846
2847 // show dialog
2848 if ($dialogCreated) {
2849 this._dialog.wcfDialog({
2850 title: this._dialogTitle
2851 });
2852 }
2853 else {
2854 this._dialog.wcfDialog('open').wcfDialog('render');
2855 }
2856 }
2857 else {
2858 this._additionalParameters.pageNo = this._pageNo;
2859
2860 // load template via AJAX
2861 this._proxy.setOption('data', {
2862 actionName: 'getGroupedUserList',
2863 className: this._className,
2864 interfaceName: 'wcf\\data\\IGroupedUserListAction',
2865 parameters: this._additionalParameters
2866 });
2867 this._proxy.sendRequest();
2868 }
2869 },
2870
2871 /**
2872 * Handles successful AJAX requests.
2873 *
2874 * @param object data
2875 * @param string textStatus
2876 * @param jQuery jqXHR
2877 */
2878 _success: function(data, textStatus, jqXHR) {
2879 if (data.returnValues.pageCount) {
2880 this._pageCount = data.returnValues.pageCount;
2881 }
2882
2883 this._cache[this._pageNo] = data.returnValues.template;
2884 this._showPage();
2885 }
2886 });
2887
2888 /**
2889 * Namespace for object watch functions.
2890 */
2891 WCF.User.ObjectWatch = {};
2892
2893 /**
2894 * Handles subscribe/unsubscribe links.
2895 */
2896 WCF.User.ObjectWatch.Subscribe = Class.extend({
2897 /**
2898 * CSS selector for subscribe buttons
2899 * @var string
2900 */
2901 _buttonSelector: '.jsSubscribeButton',
2902
2903 /**
2904 * list of buttons
2905 * @var object
2906 */
2907 _buttons: { },
2908
2909 /**
2910 * dialog overlay
2911 * @var object
2912 */
2913 _dialog: null,
2914
2915 /**
2916 * system notification
2917 * @var WCF.System.Notification
2918 */
2919 _notification: null,
2920
2921 /**
2922 * reload page on unsubscribe
2923 * @var boolean
2924 */
2925 _reloadOnUnsubscribe: false,
2926
2927 /**
2928 * WCF.User.ObjectWatch.Subscribe object.
2929 *
2930 * @param boolean reloadOnUnsubscribe
2931 */
2932 init: function(reloadOnUnsubscribe) {
2933 this._buttons = { };
2934 this._notification = null;
2935 this._reloadOnUnsubscribe = (reloadOnUnsubscribe === true);
2936
2937 // initialize proxy
2938 this._proxy = new WCF.Action.Proxy({
2939 success: $.proxy(this._success, this)
2940 });
2941
2942 // bind event listeners
2943 $(this._buttonSelector).each($.proxy(function(index, button) {
2944 var $button = $(button);
2945 var $objectID = $button.data('objectID');
2946 this._buttons[$objectID] = $button.click($.proxy(this._click, this));
2947 }, this));
2948
2949 WCF.System.Event.addListener('com.woltlab.wcf.objectWatch', 'update', $.proxy(this._updateSubscriptionStatus, this));
2950 },
2951
2952 /**
2953 * Handles a click on a subscribe button.
2954 *
2955 * @param object event
2956 */
2957 _click: function(event) {
2958 event.preventDefault();
2959 var $button = $(event.currentTarget);
2960
2961 this._proxy.setOption('data', {
2962 actionName: 'manageSubscription',
2963 className: 'wcf\\data\\user\\object\\watch\\UserObjectWatchAction',
2964 parameters: {
2965 objectID: $button.data('objectID'),
2966 objectType: $button.data('objectType')
2967 }
2968 });
2969 this._proxy.sendRequest();
2970 },
2971
2972 /**
2973 * Handles successful AJAX requests.
2974 *
2975 * @param object data
2976 * @param string textStatus
2977 * @param jQuery jqXHR
2978 */
2979 _success: function(data, textStatus, jqXHR) {
2980 if (data.actionName === 'manageSubscription') {
2981 if (this._dialog === null) {
2982 this._dialog = $('<div>' + data.returnValues.template + '</div>').hide().appendTo(document.body);
2983 this._dialog.wcfDialog({
2984 title: WCF.Language.get('wcf.user.objectWatch.manageSubscription')
2985 });
2986 }
2987 else {
2988 this._dialog.html(data.returnValues.template);
2989 this._dialog.wcfDialog('open');
2990 }
2991
2992 // bind event listener
2993 this._dialog.find('.formSubmit > .jsButtonSave').data('objectID', data.returnValues.objectID).click($.proxy(this._save, this));
2994 var $enableNotification = this._dialog.find('input[name=enableNotification]').disable();
2995
2996 // toggle subscription
2997 this._dialog.find('input[name=subscribe]').change(function(event) {
2998 var $input = $(event.currentTarget);
2999 if ($input.val() == 1) {
3000 $enableNotification.enable();
3001 }
3002 else {
3003 $enableNotification.disable();
3004 }
3005 });
3006
3007 // setup
3008 var $selectedOption = this._dialog.find('input[name=subscribe]:checked');
3009 if ($selectedOption.length && $selectedOption.val() == 1) {
3010 $enableNotification.enable();
3011 }
3012 }
3013 else if (data.actionName === 'saveSubscription' && this._dialog.is(':visible')) {
3014 this._dialog.wcfDialog('close');
3015
3016 this._updateSubscriptionStatus({
3017 isSubscribed: data.returnValues.subscribe,
3018 objectID: data.returnValues.objectID
3019 });
3020
3021
3022 // show notification
3023 if (this._notification === null) {
3024 this._notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success.edit'));
3025 }
3026
3027 this._notification.show();
3028 }
3029 },
3030
3031 /**
3032 * Saves the subscription.
3033 *
3034 * @param object event
3035 */
3036 _save: function(event) {
3037 var $button = this._buttons[$(event.currentTarget).data('objectID')];
3038 var $subscribe = this._dialog.find('input[name=subscribe]:checked').val();
3039 var $enableNotification = (this._dialog.find('input[name=enableNotification]').is(':checked')) ? 1 : 0;
3040
3041 this._proxy.setOption('data', {
3042 actionName: 'saveSubscription',
3043 className: 'wcf\\data\\user\\object\\watch\\UserObjectWatchAction',
3044 parameters: {
3045 enableNotification: $enableNotification,
3046 objectID: $button.data('objectID'),
3047 objectType: $button.data('objectType'),
3048 subscribe: $subscribe
3049 }
3050 });
3051 this._proxy.sendRequest();
3052 },
3053
3054 /**
3055 * Updates subscription status and icon.
3056 *
3057 * @param object data
3058 */
3059 _updateSubscriptionStatus: function(data) {
3060 var $button = $(this._buttonSelector + '[data-object-id=' + data.objectID + ']');
3061 var $icon = $button.children('.icon');
3062 if (data.isSubscribed) {
3063 $icon.removeClass('icon-bookmark-empty').addClass('icon-bookmark');
3064 $button.data('isSubscribed', true);
3065 }
3066 else {
3067 $icon.removeClass('icon-bookmark').addClass('icon-bookmark-empty');
3068 $button.data('isSubscribed', false);
3069
3070 if (this._reloadOnUnsubscribe) {
3071 window.location.reload();
3072 return;
3073 }
3074 }
3075
3076 WCF.System.Event.fireEvent('com.woltlab.wcf.objectWatch', 'updatedSubscription', data);
3077 }
3078 });
3079
3080 /**
3081 * Handles inline editing of users.
3082 */
3083 WCF.User.InlineEditor = WCF.InlineEditor.extend({
3084 /**
3085 * data of additional options
3086 * @var object
3087 */
3088 _additionalOptions: { },
3089
3090 /**
3091 * dialog object
3092 * @var jQuery
3093 */
3094 _dialog: null,
3095
3096 /**
3097 * list of permissions
3098 * @var object
3099 */
3100 _permissions: { },
3101
3102 /**
3103 * @see WCF.InlineEditor.init()
3104 */
3105 init: function(elementSelector) {
3106 this._super(elementSelector);
3107
3108 WCF.System.ObjectStore.add('WCF.User.InlineEditor', this);
3109 },
3110
3111 /**
3112 * @see WCF.InlineEditor._execute()
3113 */
3114 _execute: function(elementID, optionName) {
3115 if (!this._validate(elementID, optionName)) {
3116 return false;
3117 }
3118
3119 var $data = { };
3120 var $element = $('#' + elementID);
3121 if (this._additionalOptions[optionName] !== undefined) {
3122 this._additionalOptions[optionName].callback(elementID);
3123 }
3124 else {
3125 switch (optionName) {
3126 case 'unban':
3127 case 'enableAvatar':
3128 case 'enableSignature':
3129 switch (optionName) {
3130 case 'unban':
3131 $data.banned = 0;
3132 break;
3133
3134 case 'enableAvatar':
3135 $data.disableAvatar = 0;
3136 break;
3137
3138 case 'enableSignature':
3139 $data.disableSignature = 0;
3140 break;
3141 }
3142
3143 this._proxy.setOption('data', {
3144 actionName: optionName,
3145 className: 'wcf\\data\\user\\UserAction',
3146 objectIDs: [ $element.data('objectID') ]
3147 });
3148 this._proxy.sendRequest();
3149 break;
3150
3151 case 'ban':
3152 case 'disableAvatar':
3153 case 'disableSignature':
3154 if (optionName == 'ban') {
3155 $data.banned = 1;
3156 }
3157 else {
3158 $data[optionName] = 1;
3159 }
3160
3161 this._showReasonDialog($element.data('objectID'), optionName);
3162 break;
3163
3164 case 'advanced':
3165 window.location = this._getTriggerElement($element).attr('href');
3166 break;
3167 }
3168 }
3169
3170 if ($.getLength($data)) {
3171 this._updateData.push({
3172 data: $data,
3173 elementID: elementID,
3174 });
3175 }
3176 },
3177
3178 /**
3179 * Executes an action with a reason.
3180 *
3181 * @param integer userID
3182 * @param string optionName
3183 * @param string reason
3184 */
3185 _executeReasonAction: function(userID, optionName, reason) {
3186 var $optionName = this._dialog.data('optionName');
3187
3188 this._dialog.find('.innerError').remove();
3189
3190 var $banExpires = '';
3191 if (!$('#' + $optionName + 'NeverExpires').is(':checked')) {
3192 $banExpires = $('#' + $optionName + 'ExpiresDatePicker').val();
3193 if (!$banExpires) {
3194 this._dialog.find('#' + $optionName + 'ExpiresSettings > dd > small').prepend($('<small class="innerError" />').text(WCF.Language.get('wcf.global.form.error.empty')));
3195 return
3196 }
3197 }
3198
3199 var $parameters = { };
3200 $parameters[$optionName + 'Reason'] = $('#' + $optionName + 'Reason').val();
3201 $parameters[$optionName + 'Expires'] = $banExpires;
3202
3203 this._proxy.setOption('data', {
3204 actionName: $optionName,
3205 className: 'wcf\\data\\user\\UserAction',
3206 objectIDs: [ this._dialog.data('userID') ],
3207 parameters: $parameters
3208 });
3209 this._proxy.sendRequest();
3210 },
3211
3212 /**
3213 * Returns a specific permission.
3214 *
3215 * @param string permission
3216 * @return integer
3217 */
3218 _getPermission: function(permission) {
3219 if (this._permissions[permission]) {
3220 return this._permissions[permission];
3221 }
3222
3223 return 0;
3224 },
3225
3226 /**
3227 * @see WCF.InlineEditor._getTriggerElement()
3228 */
3229 _getTriggerElement: function(element) {
3230 return element.find('.jsUserInlineEditor');
3231 },
3232
3233 /**
3234 * @see WCF.InlineEditor._setOptions()
3235 */
3236 _setOptions: function() {
3237 this._options = [
3238 // banning
3239 { label: WCF.Language.get('wcf.user.ban'), optionName: 'ban' },
3240 { label: WCF.Language.get('wcf.user.unban'), optionName: 'unban' },
3241
3242 // disabling avatar
3243 { label: WCF.Language.get('wcf.user.disableAvatar'), optionName: 'disableAvatar' },
3244 { label: WCF.Language.get('wcf.user.enableAvatar'), optionName: 'enableAvatar' },
3245
3246 // disabling signature
3247 { label: WCF.Language.get('wcf.user.disableSignature'), optionName: 'disableSignature' },
3248 { label: WCF.Language.get('wcf.user.enableSignature'), optionName: 'enableSignature' }
3249 ];
3250
3251 for (var $optionName in this._additionalOptions) {
3252 this._options.push({ label: this._additionalOptions[$optionName].label, optionName: $optionName });
3253 }
3254
3255 // divider
3256 this._options.push({ optionName: 'divider' });
3257
3258 // redirect to ACP
3259 this._options.push({ label: WCF.Language.get('wcf.user.edit'), optionName: 'advanced' });
3260 },
3261
3262 /**
3263 * @see WCF.InlineEditor._show()
3264 */
3265 _show: function(event) {
3266 var $element = $(event.currentTarget);
3267 var $elementID = $element.data('elementID');
3268
3269 if (!this._dropdowns[$elementID]) {
3270 var $dropdownMenu = $element.next('.dropdownMenu');
3271
3272 if ($dropdownMenu) {
3273 this._dropdowns[$elementID] = $dropdownMenu;
3274 WCF.Dropdown.initDropdown(this._getTriggerElement(this._elements[$elementID]), true);
3275 }
3276 }
3277
3278 return this._super(event);
3279 },
3280
3281 /**
3282 * Shows the dialog to enter a reason for executing the option with the
3283 * given name.
3284 *
3285 * @param string optionName
3286 */
3287 _showReasonDialog: function(userID, optionName) {
3288 if (this._dialog) {
3289 this._dialog.remove();
3290 }
3291
3292 // create dialog
3293 this._dialog = $('<div />').hide().appendTo(document.body);
3294 this._dialog.append($('<fieldset><dl><dt><label for="' + optionName + 'Reason">' + WCF.Language.get('wcf.global.reason') + '</label></dt><dd><textarea id="' + optionName + 'Reason" cols="40" rows="3" />' + (WCF.Language.get('wcf.user.' + optionName + '.reason.description') != 'wcf.user.' + optionName + '.reason.description' ? '<small>' + WCF.Language.get('wcf.user.' + optionName + '.reason.description') + '</small>' : '') + '</dd></dl><dl><dt></dt><dd><label for="' + optionName + 'NeverExpires"><input type="checkbox" name="' + optionName + 'NeverExpires" id="' + optionName + 'NeverExpires" checked="checked" /> ' + WCF.Language.get('wcf.user.' + optionName + '.neverExpires') + '</label></dd></dl><dl id="' + optionName + 'ExpiresSettings" style="display: none;"><dt><label for="' + optionName + 'Expires">' + WCF.Language.get('wcf.user.' + optionName + '.expires') + '</label></dt><dd><input type="date" name="' + optionName + 'Expires" id="' + optionName + 'Expires" class="medium" min="' + new Date(TIME_NOW * 1000).toISOString() + '" data-ignore-timezone="true" /><small>' + WCF.Language.get('wcf.user.' + optionName + '.expires.description') + '</small></dd></dl></fieldset>'));
3295 this._dialog.append($('<div class="formSubmit"><button class="buttonPrimary" accesskey="s">' + WCF.Language.get('wcf.global.button.submit') + '</button></div>'));
3296
3297 this._dialog.data('optionName', optionName).data('userID', userID);
3298
3299 this._dialog.find('#' + optionName + 'NeverExpires').change(function() {
3300 $('#' + optionName + 'ExpiresSettings').toggle();
3301 });
3302
3303 this._dialog.find('button').click($.proxy(this._executeReasonAction, this));
3304
3305 this._dialog.wcfDialog({
3306 title: WCF.Language.get('wcf.user.' + optionName + '.confirmMessage')
3307 });
3308 },
3309
3310 /**
3311 * @see WCF.InlineEditor._updateState()
3312 */
3313 _updateState: function(data) {
3314 this._notification.show();
3315
3316 for (var $i = 0, $length = this._updateData.length; $i < $length; $i++) {
3317 var $data = this._updateData[$i];
3318 var $element = $('#' + $data.elementID);
3319
3320 for (var $property in $data.data) {
3321 $element.data($property, $data.data[$property]);
3322 }
3323 }
3324
3325 if (data.actionName == 'ban' || data.actionName == 'disableAvatar' || data.actionName == 'disableSignature') {
3326 this._dialog.wcfDialog('close');
3327 }
3328
3329 if (data.actionName == 'unban') {
3330 $('.userHeadline .jsUserBanned').remove();
3331 }
3332 },
3333
3334 /**
3335 * @see WCF.InlineEditor._validate()
3336 */
3337 _validate: function(elementID, optionName) {
3338 var $user = $('#' + elementID);
3339
3340 if (this._additionalOptions[optionName] !== undefined) {
3341 return true;
3342 }
3343
3344 switch (optionName) {
3345 case 'ban':
3346 case 'unban':
3347 if (!this._getPermission('canBanUser')) {
3348 return false;
3349 }
3350
3351 if (optionName == 'ban') {
3352 return !$user.data('banned');
3353 }
3354 else {
3355 return $user.data('banned');
3356 }
3357 break;
3358
3359 case 'disableAvatar':
3360 case 'enableAvatar':
3361 if (!this._getPermission('canDisableAvatar')) {
3362 return false;
3363 }
3364
3365 if (optionName == 'disableAvatar') {
3366 return !$user.data('disableAvatar');
3367 }
3368 else {
3369 return $user.data('disableAvatar');
3370 }
3371 break;
3372
3373 case 'disableSignature':
3374 case 'enableSignature':
3375 if (!this._getPermission('canDisableSignature')) {
3376 return false;
3377 }
3378
3379 if (optionName == 'disableSignature') {
3380 return !$user.data('disableSignature');
3381 }
3382 else {
3383 return $user.data('disableSignature');
3384 }
3385 break;
3386
3387 case 'advanced':
3388 return this._getPermission('canEditUser');
3389 break;
3390 }
3391
3392 return false;
3393 },
3394
3395 /**
3396 * Adds an additional option.
3397 *
3398 * @param string label
3399 * @param string optionName
3400 * @param function callback
3401 */
3402 addOption: function(label, optionName, callback) {
3403 if (!$.isFunction(callback)) {
3404 console.debug('[WCF.User.InlineEditor] Missing callback');
3405 }
3406 if (this._additionalOptions[optionName] !== undefined) {
3407 console.debug('[WCF.User.InlineEditor] Additional option with name "' + optionName + "' already exists");
3408 }
3409
3410 this._additionalOptions[optionName] = {
3411 callback: callback,
3412 label: label
3413 };
3414
3415 this._setOptions();
3416 },
3417
3418 /**
3419 * Sets a permission.
3420 *
3421 * @param string permission
3422 * @param integer value
3423 */
3424 setPermission: function(permission, value) {
3425 this._permissions[permission] = value;
3426 },
3427
3428 /**
3429 * Sets permissions.
3430 *
3431 * @param object permissions
3432 */
3433 setPermissions: function(permissions) {
3434 for (var $permission in permissions) {
3435 this.setPermission($permission, permissions[$permission]);
3436 }
3437 }
3438 });