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