Merge branch 'origin/formBuilder' into reactions
[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 this._actionName = 'save';
1065
1066 // collect values
1067 var $regExp = /values\[([a-zA-Z0-9._-]+)\]/;
1068 var $values = {};
1069 this._tab.find('input, textarea, select').each(function (index, element) {
1070 var $element = $(element);
1071 var $value = null;
1072
1073 switch ($element.getTagName()) {
1074 case 'input':
1075 var $type = $element.attr('type');
1076
1077 if (($type === 'radio' || $type === 'checkbox') && !$element.prop('checked')) {
1078 return;
1079 }
1080 break;
1081
1082 case 'textarea':
1083 if ($element.data('redactor')) {
1084 $value = $element.redactor('code.get');
1085 }
1086 break;
1087 }
1088
1089 var $name = $element.attr('name');
1090 if ($regExp.test($name)) {
1091 var $fieldName = RegExp.$1;
1092 if ($value === null) $value = $element.val();
1093
1094 // check for checkboxes
1095 if ($element.attr('type') === 'checkbox' && /\[\]$/.test($name)) {
1096 if (!Array.isArray($values[$fieldName])) {
1097 $values[$fieldName] = [];
1098 }
1099
1100 $values[$fieldName].push($value);
1101 }
1102 else {
1103 $values[$fieldName] = $value;
1104 }
1105 }
1106 });
1107
1108 this._proxy.setOption('data', {
1109 actionName: 'save',
1110 className: 'wcf\\data\\user\\UserProfileAction',
1111 objectIDs: [this._userID],
1112 parameters: {
1113 values: $values
1114 }
1115 });
1116 this._proxy.sendRequest();
1117 },
1118
1119 /**
1120 * Restores back to default view.
1121 */
1122 _restore: function () {
1123 this._actionName = 'restore';
1124 this._active = false;
1125 this._buttons.beginEdit.parent().removeClass('active');
1126
1127 this._destroyEditor();
1128
1129 this._tab.html(this._cachedTemplate).children().css({height: 'auto'});
1130 },
1131
1132 /**
1133 * Handles successful AJAX requests.
1134 *
1135 * @param object data
1136 * @param string textStatus
1137 * @param jQuery jqXHR
1138 */
1139 _success: function (data, textStatus, jqXHR) {
1140 switch (this._actionName) {
1141 case 'beginEdit':
1142 this._prepareEdit(data);
1143 break;
1144
1145 case 'save':
1146 // save was successful, show parsed template
1147 if (data.returnValues.success) {
1148 this._cachedTemplate = data.returnValues.template;
1149 this._restore();
1150 }
1151 else {
1152 this._prepareEdit(data, true);
1153 }
1154 break;
1155 }
1156 },
1157
1158 /**
1159 * Prepares editing mode.
1160 *
1161 * @param object data
1162 * @param boolean disableCache
1163 */
1164 _prepareEdit: function (data, disableCache) {
1165 this._destroyEditor();
1166
1167 // update template
1168 var self = this;
1169 this._tab.html(function (index, oldHTML) {
1170 if (disableCache !== true) {
1171 self._cachedTemplate = oldHTML;
1172 }
1173
1174 return data.returnValues.template;
1175 });
1176
1177 // block autocomplete
1178 this._tab.find('input[type=text]').attr('autocomplete', 'off');
1179
1180 // bind event listener
1181 this._tab.find('.formSubmit > button[data-type=save]').click($.proxy(this._save, this));
1182 this._tab.find('.formSubmit > button[data-type=restore]').click($.proxy(this._restore, this));
1183 this._tab.find('input').keyup(function (event) {
1184 if (event.which === $.ui.keyCode.ENTER) {
1185 self._save();
1186
1187 event.preventDefault();
1188 return false;
1189 }
1190 });
1191 },
1192
1193 /**
1194 * Destroys all editor instances within current tab.
1195 */
1196 _destroyEditor: function () {
1197 // destroy all editor instances
1198 this._tab.find('textarea').each(function (index, container) {
1199 var $container = $(container);
1200 if ($container.data('redactor')) {
1201 $container.redactor('core.destroy');
1202 }
1203 });
1204 }
1205 });
1206 }
1207 else {
1208 WCF.User.Profile.Editor = Class.extend({
1209 _actionName: "",
1210 _active: false,
1211 _buttons: {},
1212 _cachedTemplate: "",
1213 _proxy: {},
1214 _tab: {},
1215 _userID: 0,
1216 init: function() {},
1217 _initButtons: function() {},
1218 _beginEdit: function() {},
1219 _save: function() {},
1220 _restore: function() {},
1221 _success: function() {},
1222 _prepareEdit: function() {},
1223 _destroyEditor: function() {}
1224 });
1225 }
1226
1227 /**
1228 * Namespace for registration functions.
1229 */
1230 WCF.User.Registration = {};
1231
1232 /**
1233 * Validates the password.
1234 *
1235 * @param jQuery element
1236 * @param jQuery confirmElement
1237 * @param object options
1238 */
1239 WCF.User.Registration.Validation = Class.extend({
1240 /**
1241 * action name
1242 * @var string
1243 */
1244 _actionName: '',
1245
1246 /**
1247 * class name
1248 * @var string
1249 */
1250 _className: '',
1251
1252 /**
1253 * confirmation input element
1254 * @var jQuery
1255 */
1256 _confirmElement: null,
1257
1258 /**
1259 * input element
1260 * @var jQuery
1261 */
1262 _element: null,
1263
1264 /**
1265 * list of error messages
1266 * @var object
1267 */
1268 _errorMessages: { },
1269
1270 /**
1271 * list of additional options
1272 * @var object
1273 */
1274 _options: { },
1275
1276 /**
1277 * AJAX proxy
1278 * @var WCF.Action.Proxy
1279 */
1280 _proxy: null,
1281
1282 /**
1283 * Initializes the validation.
1284 *
1285 * @param jQuery element
1286 * @param jQuery confirmElement
1287 * @param object options
1288 */
1289 init: function(element, confirmElement, options) {
1290 this._element = element;
1291 this._element.blur($.proxy(this._blur, this));
1292 this._confirmElement = confirmElement || null;
1293
1294 if (this._confirmElement !== null) {
1295 this._confirmElement.blur($.proxy(this._blurConfirm, this));
1296 }
1297
1298 options = options || { };
1299 this._setOptions(options);
1300
1301 this._proxy = new WCF.Action.Proxy({
1302 success: $.proxy(this._success, this),
1303 showLoadingOverlay: false
1304 });
1305
1306 this._setErrorMessages();
1307 },
1308
1309 /**
1310 * Sets additional options
1311 */
1312 _setOptions: function(options) { },
1313
1314 /**
1315 * Sets error messages.
1316 */
1317 _setErrorMessages: function() {
1318 this._errorMessages = {
1319 ajaxError: '',
1320 notEqual: ''
1321 };
1322 },
1323
1324 /**
1325 * Validates once focus on input is lost.
1326 *
1327 * @param object event
1328 */
1329 _blur: function(event) {
1330 var $value = this._element.val();
1331 if (!$value) {
1332 return this._showError(this._element, WCF.Language.get('wcf.global.form.error.empty'));
1333 }
1334
1335 if (this._confirmElement !== null) {
1336 var $confirmValue = this._confirmElement.val();
1337 if ($confirmValue != '' && $value != $confirmValue) {
1338 return this._showError(this._confirmElement, this._errorMessages.notEqual);
1339 }
1340 }
1341
1342 if (!this._validateOptions()) {
1343 return;
1344 }
1345
1346 this._proxy.setOption('data', {
1347 actionName: this._actionName,
1348 className: this._className,
1349 parameters: this._getParameters()
1350 });
1351 this._proxy.sendRequest();
1352 },
1353
1354 /**
1355 * Returns a list of parameters.
1356 *
1357 * @return object
1358 */
1359 _getParameters: function() {
1360 return { };
1361 },
1362
1363 /**
1364 * Validates input by options.
1365 *
1366 * @return boolean
1367 */
1368 _validateOptions: function() {
1369 return true;
1370 },
1371
1372 /**
1373 * Validates value once confirmation input focus is lost.
1374 *
1375 * @param object event
1376 */
1377 _blurConfirm: function(event) {
1378 var $value = this._confirmElement.val();
1379 if (!$value) {
1380 return this._showError(this._confirmElement, WCF.Language.get('wcf.global.form.error.empty'));
1381 }
1382
1383 this._blur(event);
1384 },
1385
1386 /**
1387 * Handles AJAX responses.
1388 *
1389 * @param object data
1390 * @param string textStatus
1391 * @param jQuery jqXHR
1392 */
1393 _success: function(data, textStatus, jqXHR) {
1394 if (data.returnValues.isValid) {
1395 this._showSuccess(this._element);
1396 if (this._confirmElement !== null && this._confirmElement.val()) {
1397 this._showSuccess(this._confirmElement);
1398 }
1399 }
1400 else {
1401 this._showError(this._element, WCF.Language.get(this._errorMessages.ajaxError + data.returnValues.error));
1402 }
1403 },
1404
1405 /**
1406 * Shows an error message.
1407 *
1408 * @param jQuery element
1409 * @param string message
1410 */
1411 _showError: function(element, message) {
1412 element.parent().parent().addClass('formError').removeClass('formSuccess');
1413
1414 var $innerError = element.parent().find('small.innerError');
1415 if (!$innerError.length) {
1416 $innerError = $('<small />').addClass('innerError').insertAfter(element);
1417 }
1418
1419 $innerError.text(message);
1420 },
1421
1422 /**
1423 * Displays a success message.
1424 *
1425 * @param jQuery element
1426 */
1427 _showSuccess: function(element) {
1428 element.parent().parent().addClass('formSuccess').removeClass('formError');
1429 element.next('small.innerError').remove();
1430 }
1431 });
1432
1433 /**
1434 * Username validation for registration.
1435 *
1436 * @see WCF.User.Registration.Validation
1437 */
1438 WCF.User.Registration.Validation.Username = WCF.User.Registration.Validation.extend({
1439 /**
1440 * @see WCF.User.Registration.Validation._actionName
1441 */
1442 _actionName: 'validateUsername',
1443
1444 /**
1445 * @see WCF.User.Registration.Validation._className
1446 */
1447 _className: 'wcf\\data\\user\\UserRegistrationAction',
1448
1449 /**
1450 * @see WCF.User.Registration.Validation._setOptions()
1451 */
1452 _setOptions: function(options) {
1453 this._options = $.extend(true, {
1454 minlength: 3,
1455 maxlength: 25
1456 }, options);
1457 },
1458
1459 /**
1460 * @see WCF.User.Registration.Validation._setErrorMessages()
1461 */
1462 _setErrorMessages: function() {
1463 this._errorMessages = {
1464 ajaxError: 'wcf.user.username.error.'
1465 };
1466 },
1467
1468 /**
1469 * @see WCF.User.Registration.Validation._validateOptions()
1470 */
1471 _validateOptions: function() {
1472 var $value = this._element.val();
1473 if ($value.length < this._options.minlength || $value.length > this._options.maxlength) {
1474 this._showError(this._element, WCF.Language.get('wcf.user.username.error.invalid'));
1475 return false;
1476 }
1477
1478 return true;
1479 },
1480
1481 /**
1482 * @see WCF.User.Registration.Validation._getParameters()
1483 */
1484 _getParameters: function() {
1485 return {
1486 username: this._element.val()
1487 };
1488 }
1489 });
1490
1491 /**
1492 * Email validation for registration.
1493 *
1494 * @see WCF.User.Registration.Validation
1495 */
1496 WCF.User.Registration.Validation.EmailAddress = WCF.User.Registration.Validation.extend({
1497 /**
1498 * @see WCF.User.Registration.Validation._actionName
1499 */
1500 _actionName: 'validateEmailAddress',
1501
1502 /**
1503 * @see WCF.User.Registration.Validation._className
1504 */
1505 _className: 'wcf\\data\\user\\UserRegistrationAction',
1506
1507 /**
1508 * @see WCF.User.Registration.Validation._getParameters()
1509 */
1510 _getParameters: function() {
1511 return {
1512 email: this._element.val()
1513 };
1514 },
1515
1516 /**
1517 * @see WCF.User.Registration.Validation._setErrorMessages()
1518 */
1519 _setErrorMessages: function() {
1520 this._errorMessages = {
1521 ajaxError: 'wcf.user.email.error.',
1522 notEqual: WCF.Language.get('wcf.user.confirmEmail.error.notEqual')
1523 };
1524 }
1525 });
1526
1527 /**
1528 * Password validation for registration.
1529 *
1530 * @see WCF.User.Registration.Validation
1531 */
1532 WCF.User.Registration.Validation.Password = WCF.User.Registration.Validation.extend({
1533 /**
1534 * @see WCF.User.Registration.Validation._actionName
1535 */
1536 _actionName: 'validatePassword',
1537
1538 /**
1539 * @see WCF.User.Registration.Validation._className
1540 */
1541 _className: 'wcf\\data\\user\\UserRegistrationAction',
1542
1543 /**
1544 * @see WCF.User.Registration.Validation._getParameters()
1545 */
1546 _getParameters: function() {
1547 return {
1548 password: this._element.val()
1549 };
1550 },
1551
1552 /**
1553 * @see WCF.User.Registration.Validation._setErrorMessages()
1554 */
1555 _setErrorMessages: function() {
1556 this._errorMessages = {
1557 ajaxError: 'wcf.user.password.error.',
1558 notEqual: WCF.Language.get('wcf.user.confirmPassword.error.notEqual')
1559 };
1560 }
1561 });
1562
1563 /**
1564 * Toggles input fields for lost password form.
1565 */
1566 WCF.User.Registration.LostPassword = Class.extend({
1567 /**
1568 * email input
1569 * @var jQuery
1570 */
1571 _email: null,
1572
1573 /**
1574 * username input
1575 * @var jQuery
1576 */
1577 _username: null,
1578
1579 /**
1580 * Initializes LostPassword-form class.
1581 */
1582 init: function() {
1583 // bind input fields
1584 this._email = $('#emailInput');
1585 this._username = $('#usernameInput');
1586
1587 // bind event listener
1588 this._email.keyup($.proxy(this._checkEmail, this));
1589 this._username.keyup($.proxy(this._checkUsername, this));
1590
1591 if ($.browser.mozilla && $.browser.touch) {
1592 this._email.on('input', $.proxy(this._checkEmail, this));
1593 this._username.on('input', $.proxy(this._checkUsername, this));
1594 }
1595
1596 // toggle fields on init
1597 this._checkEmail();
1598 this._checkUsername();
1599 },
1600
1601 /**
1602 * Checks for content in email field and toggles username.
1603 */
1604 _checkEmail: function() {
1605 if (this._email.val() == '') {
1606 this._username.enable();
1607 this._username.parents('dl:eq(0)').removeClass('disabled');
1608 }
1609 else {
1610 this._username.disable();
1611 this._username.parents('dl:eq(0)').addClass('disabled');
1612 this._username.val('');
1613 }
1614 },
1615
1616 /**
1617 * Checks for content in username field and toggles email.
1618 */
1619 _checkUsername: function() {
1620 if (this._username.val() == '') {
1621 this._email.enable();
1622 this._email.parents('dl:eq(0)').removeClass('disabled');
1623 }
1624 else {
1625 this._email.disable();
1626 this._email.parents('dl:eq(0)').addClass('disabled');
1627 this._email.val('');
1628 }
1629 }
1630 });
1631
1632 /**
1633 * Notification system for WCF.
1634 *
1635 * @author Alexander Ebert
1636 * @copyright 2001-2018 WoltLab GmbH
1637 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
1638 */
1639 WCF.Notification = { };
1640
1641 if (COMPILER_TARGET_DEFAULT) {
1642 /**
1643 * Handles the notification list.
1644 */
1645 WCF.Notification.List = Class.extend({
1646 /**
1647 * action proxy
1648 * @var WCF.Action.Proxy
1649 */
1650 _proxy: null,
1651
1652 /**
1653 * Initializes the WCF.Notification.List object.
1654 */
1655 init: function () {
1656 this._proxy = new WCF.Action.Proxy({
1657 success: $.proxy(this._success, this)
1658 });
1659
1660 // handle 'mark all as confirmed' buttons
1661 $('.contentHeaderNavigation .jsMarkAllAsConfirmed').click(function () {
1662 WCF.System.Confirmation.show(WCF.Language.get('wcf.user.notification.markAllAsConfirmed.confirmMessage'), function (action) {
1663 if (action === 'confirm') {
1664 new WCF.Action.Proxy({
1665 autoSend: true,
1666 data: {
1667 actionName: 'markAllAsConfirmed',
1668 className: 'wcf\\data\\user\\notification\\UserNotificationAction'
1669 },
1670 success: function () {
1671 window.location.reload();
1672 }
1673 });
1674 }
1675 });
1676 });
1677
1678 // handle regular items
1679 this._convertList();
1680 },
1681
1682 /**
1683 * Converts the notification item list to be in sync with the notification dropdown.
1684 */
1685 _convertList: function () {
1686 $('.userNotificationItemList > .notificationItem').each((function (index, item) {
1687 var $item = $(item);
1688
1689 if (!$item.data('isRead')) {
1690 $item.find('a:not(.userLink)').prop('href', $item.data('link'));
1691
1692 var $markAsConfirmed = $('<a href="#" class="icon icon24 fa-check notificationItemMarkAsConfirmed jsTooltip" title="' + WCF.Language.get('wcf.user.notification.markAsConfirmed') + '" />').appendTo($item);
1693 $markAsConfirmed.click($.proxy(this._markAsConfirmed, this));
1694 }
1695
1696 // work-around for legacy notifications
1697 if (!$item.find('a:not(.notificationItemMarkAsConfirmed)').length) {
1698 $item.find('.details > p:eq(0)').html(function (index, oldHTML) {
1699 return '<a href="' + $item.data('link') + '">' + oldHTML + '</a>';
1700 });
1701 }
1702 }).bind(this));
1703
1704 WCF.DOMNodeInsertedHandler.execute();
1705 },
1706
1707 /**
1708 * Marks a single notification as confirmed.
1709 *
1710 * @param object event
1711 */
1712 _markAsConfirmed: function (event) {
1713 event.preventDefault();
1714
1715 var $notificationID = $(event.currentTarget).parents('.notificationItem:eq(0)').data('objectID');
1716
1717 this._proxy.setOption('data', {
1718 actionName: 'markAsConfirmed',
1719 className: 'wcf\\data\\user\\notification\\UserNotificationAction',
1720 objectIDs: [$notificationID]
1721 });
1722 this._proxy.sendRequest();
1723
1724 return false;
1725 },
1726
1727 /**
1728 * Handles successful AJAX requests.
1729 *
1730 * @param object data
1731 * @param string textStatus
1732 * @param jQuery jqXHR
1733 */
1734 _success: function (data, textStatus, jqXHR) {
1735 var $item = $('.userNotificationItemList > .notificationItem[data-object-id=' + data.returnValues.markAsRead + ']');
1736
1737 $item.data('isRead', true);
1738 $item.find('.newContentBadge').remove();
1739 $item.find('.notificationItemMarkAsConfirmed').remove();
1740 $item.removeClass('notificationUnconfirmed');
1741 }
1742 });
1743
1744 /**
1745 * Signature preview.
1746 *
1747 * @see WCF.Message.Preview
1748 */
1749 WCF.User.SignaturePreview = WCF.Message.Preview.extend({
1750 /**
1751 * @see WCF.Message.Preview._handleResponse()
1752 */
1753 _handleResponse: function (data) {
1754 // get preview container
1755 var $preview = $('#previewContainer');
1756 if (!$preview.length) {
1757 $preview = $('<section class="section" id="previewContainer"><h2 class="sectionTitle">' + WCF.Language.get('wcf.global.preview') + '</h2><div class="htmlContent"></div></section>').insertBefore($('#signatureContainer')).wcfFadeIn();
1758 }
1759
1760 $preview.children('div').first().html(data.returnValues.message);
1761 }
1762 });
1763 }
1764 else {
1765 WCF.Notification.List = Class.extend({
1766 _proxy: {},
1767 init: function() {},
1768 _convertList: function() {},
1769 _markAsConfirmed: function() {},
1770 _success: function() {}
1771 });
1772
1773 WCF.User.SignaturePreview = WCF.Message.Preview.extend({
1774 _handleResponse: function() {},
1775 _className: "",
1776 _messageFieldID: "",
1777 _messageField: {},
1778 _proxy: {},
1779 _previewButton: {},
1780 _previewButtonLabel: "",
1781 init: function() {},
1782 _click: function() {},
1783 _getParameters: function() {},
1784 _getMessage: function() {},
1785 _success: function() {},
1786 _failure: function() {}
1787 });
1788 }
1789
1790 /**
1791 * Loads recent activity events once the user scrolls to the very bottom.
1792 *
1793 * @param integer userID
1794 */
1795 WCF.User.RecentActivityLoader = Class.extend({
1796 /**
1797 * container object
1798 * @var jQuery
1799 */
1800 _container: null,
1801
1802 /**
1803 * true if list should be filtered by followed users
1804 * @var boolean
1805 */
1806 _filteredByFollowedUsers: false,
1807
1808 /**
1809 * button to load next events
1810 * @var jQuery
1811 */
1812 _loadButton: null,
1813
1814 /**
1815 * action proxy
1816 * @var WCF.Action.Proxy
1817 */
1818 _proxy: null,
1819
1820 /**
1821 * user id
1822 * @var integer
1823 */
1824 _userID: 0,
1825
1826 /**
1827 * Initializes a new RecentActivityLoader object.
1828 *
1829 * @param integer userID
1830 * @param boolean filteredByFollowedUsers
1831 */
1832 init: function(userID, filteredByFollowedUsers) {
1833 this._container = $('#recentActivities');
1834 this._filteredByFollowedUsers = (filteredByFollowedUsers === true);
1835 this._userID = userID;
1836
1837 if (this._userID !== null && !this._userID) {
1838 console.debug("[WCF.User.RecentActivityLoader] Invalid parameter 'userID' given.");
1839 return;
1840 }
1841
1842 this._proxy = new WCF.Action.Proxy({
1843 success: $.proxy(this._success, this)
1844 });
1845
1846 if (this._container.children('li').length) {
1847 this._loadButton = $('<li class="showMore"><button class="small">' + WCF.Language.get('wcf.user.recentActivity.more') + '</button></li>').appendTo(this._container);
1848 this._loadButton = this._loadButton.children('button').click($.proxy(this._click, this));
1849 }
1850 else {
1851 $('<li class="showMore"><small>' + WCF.Language.get('wcf.user.recentActivity.noMoreEntries') + '</small></li>').appendTo(this._container);
1852 }
1853
1854 if (WCF.User.userID) {
1855 $('.jsRecentActivitySwitchContext .button').click($.proxy(this._switchContext, this));
1856 }
1857 },
1858
1859 /**
1860 * Loads next activity events.
1861 */
1862 _click: function() {
1863 this._loadButton.enable();
1864
1865 var $parameters = {
1866 lastEventID: this._container.data('lastEventID'),
1867 lastEventTime: this._container.data('lastEventTime')
1868 };
1869 if (this._userID) {
1870 $parameters.userID = this._userID;
1871 }
1872 else if (this._filteredByFollowedUsers) {
1873 $parameters.filteredByFollowedUsers = 1;
1874 }
1875
1876 this._proxy.setOption('data', {
1877 actionName: 'load',
1878 className: 'wcf\\data\\user\\activity\\event\\UserActivityEventAction',
1879 parameters: $parameters
1880 });
1881 this._proxy.sendRequest();
1882 },
1883
1884 /**
1885 * Switches recent activity context.
1886 */
1887 _switchContext: function(event) {
1888 event.preventDefault();
1889
1890 if (!$(event.currentTarget).hasClass('active')) {
1891 new WCF.Action.Proxy({
1892 autoSend: true,
1893 data: {
1894 actionName: 'switchContext',
1895 className: 'wcf\\data\\user\\activity\\event\\UserActivityEventAction'
1896 },
1897 success: function() {
1898 window.location.hash = '#dashboardBoxRecentActivity';
1899 window.location.reload();
1900 }
1901 });
1902 }
1903 },
1904
1905 /**
1906 * Handles successful AJAX requests.
1907 *
1908 * @param object data
1909 * @param string textStatus
1910 * @param jQuery jqXHR
1911 */
1912 _success: function(data, textStatus, jqXHR) {
1913 if (data.returnValues.template) {
1914 $(data.returnValues.template).insertBefore(this._loadButton.parent());
1915
1916 this._container.data('lastEventTime', data.returnValues.lastEventTime);
1917 this._container.data('lastEventID', data.returnValues.lastEventID);
1918 this._loadButton.enable();
1919 }
1920 else {
1921 $('<small>' + WCF.Language.get('wcf.user.recentActivity.noMoreEntries') + '</small>').appendTo(this._loadButton.parent());
1922 this._loadButton.remove();
1923 }
1924 }
1925 });
1926
1927 /**
1928 * Loads likes once the user scrolls to the very bottom.
1929 *
1930 * @param integer userID
1931 */
1932 WCF.User.LikeLoader = Class.extend({
1933 /**
1934 * container object
1935 * @var jQuery
1936 */
1937 _container: null,
1938
1939 /**
1940 * like type
1941 * @var string
1942 */
1943 _likeType: 'received',
1944
1945 /**
1946 * like value
1947 * @var integer
1948 */
1949 _likeValue: 1,
1950
1951 /**
1952 * button to load next events
1953 * @var jQuery
1954 */
1955 _loadButton: null,
1956
1957 /**
1958 * 'no more entries' element
1959 * @var jQuery
1960 */
1961 _noMoreEntries: null,
1962
1963 /**
1964 * action proxy
1965 * @var WCF.Action.Proxy
1966 */
1967 _proxy: null,
1968
1969 /**
1970 * user id
1971 * @var integer
1972 */
1973 _userID: 0,
1974
1975 /**
1976 * Initializes a new RecentActivityLoader object.
1977 *
1978 * @param integer userID
1979 * @param boolean filteredByFollowedUsers
1980 */
1981 init: function(userID) {
1982 this._container = $('#likeList');
1983 this._userID = userID;
1984
1985 if (!this._userID) {
1986 console.debug("[WCF.User.RecentActivityLoader] Invalid parameter 'userID' given.");
1987 return;
1988 }
1989
1990 this._proxy = new WCF.Action.Proxy({
1991 success: $.proxy(this._success, this)
1992 });
1993
1994 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);
1995 this._loadButton = $container.children('button').click($.proxy(this._click, this));
1996 this._noMoreEntries = $container.children('small').hide();
1997
1998 if (this._container.find('> li').length == 2) {
1999 this._loadButton.hide();
2000 this._noMoreEntries.show();
2001 }
2002
2003 $('#likeType .button').click($.proxy(this._clickLikeType, this));
2004 $('#likeValue .button').click($.proxy(this._clickLikeValue, this));
2005 },
2006
2007 /**
2008 * Handles like type change.
2009 */
2010 _clickLikeType: function(event) {
2011 var $button = $(event.currentTarget);
2012 if (this._likeType != $button.data('likeType')) {
2013 this._likeType = $button.data('likeType');
2014 $('#likeType .button').removeClass('active');
2015 $button.addClass('active');
2016 this._reload();
2017 }
2018 },
2019
2020 /**
2021 * Handles like value change.
2022 */
2023 _clickLikeValue: function(event) {
2024 var $button = $(event.currentTarget);
2025 if (this._likeValue != $button.data('likeValue')) {
2026 this._likeValue = $button.data('likeValue');
2027 $('#likeValue .button').removeClass('active');
2028 $button.addClass('active');
2029
2030 // change button labels
2031 $('#likeType > li:first-child > .button').text(WCF.Language.get('wcf.like.' + (this._likeValue == -1 ? 'dis' : '') + 'likesReceived'));
2032 $('#likeType > li:last-child > .button').text(WCF.Language.get('wcf.like.' + (this._likeValue == -1 ? 'dis' : '') + 'likesGiven'));
2033
2034 this._container.find('> li.likeListMore button').text(WCF.Language.get('wcf.like.' + (this._likeValue == -1 ? 'dis' : '') + 'likes.more'));
2035 this._container.find('> li.likeListMore small').text(WCF.Language.get('wcf.like.' + (this._likeValue == -1 ? 'dis' : '') + 'likes.noMoreEntries'));
2036
2037 this._reload();
2038 }
2039 },
2040
2041 /**
2042 * Handles reload.
2043 */
2044 _reload: function() {
2045 this._container.find('> li:not(:first-child):not(:last-child)').remove();
2046 this._container.data('lastLikeTime', 0);
2047 this._click();
2048 },
2049
2050 /**
2051 * Loads next likes.
2052 */
2053 _click: function() {
2054 this._loadButton.enable();
2055
2056 var $parameters = {
2057 lastLikeTime: this._container.data('lastLikeTime'),
2058 userID: this._userID,
2059 likeType: this._likeType,
2060 likeValue: this._likeValue
2061 };
2062
2063 this._proxy.setOption('data', {
2064 actionName: 'load',
2065 className: 'wcf\\data\\like\\LikeAction',
2066 parameters: $parameters
2067 });
2068 this._proxy.sendRequest();
2069 },
2070
2071 /**
2072 * Handles successful AJAX requests.
2073 *
2074 * @param object data
2075 * @param string textStatus
2076 * @param jQuery jqXHR
2077 */
2078 _success: function(data, textStatus, jqXHR) {
2079 if (data.returnValues.template) {
2080 $(data.returnValues.template).insertBefore(this._loadButton.parent());
2081
2082 this._container.data('lastLikeTime', data.returnValues.lastLikeTime);
2083 this._noMoreEntries.hide();
2084 this._loadButton.show().enable();
2085 }
2086 else {
2087 this._noMoreEntries.show();
2088 this._loadButton.hide();
2089 }
2090 }
2091 });
2092
2093 /**
2094 * Loads user profile previews.
2095 *
2096 * @see WCF.Popover
2097 */
2098 WCF.User.ProfilePreview = WCF.Popover.extend({
2099 /**
2100 * action proxy
2101 * @var WCF.Action.Proxy
2102 */
2103 _proxy: null,
2104
2105 /**
2106 * list of user profiles
2107 * @var object
2108 */
2109 _userProfiles: { },
2110
2111 /**
2112 * @see WCF.Popover.init()
2113 */
2114 init: function() {
2115 this._super('.userLink');
2116
2117 this._proxy = new WCF.Action.Proxy({
2118 showLoadingOverlay: false
2119 });
2120
2121 // register instance
2122 WCF.System.ObjectStore.add('WCF.User.ProfilePreview', this);
2123 },
2124
2125 /**
2126 * @see WCF.Popover._loadContent()
2127 */
2128 _loadContent: function() {
2129 var $element = $('#' + this._activeElementID);
2130 var $userID = $element.data('userID');
2131
2132 if (this._userProfiles[$userID]) {
2133 // use cached user profile
2134 this._insertContent(this._activeElementID, this._userProfiles[$userID], true);
2135 }
2136 else {
2137 this._proxy.setOption('data', {
2138 actionName: 'getUserProfile',
2139 className: 'wcf\\data\\user\\UserProfileAction',
2140 objectIDs: [ $userID ]
2141 });
2142
2143 var $elementID = this._activeElementID;
2144 var self = this;
2145 this._proxy.setOption('success', function(data, textStatus, jqXHR) {
2146 // cache user profile
2147 self._userProfiles[$userID] = data.returnValues.template;
2148
2149 // show user profile
2150 self._insertContent($elementID, data.returnValues.template, true);
2151 });
2152 this._proxy.setOption('failure', function(data, jqXHR, textStatus, errorThrown) {
2153 // cache user profile
2154 self._userProfiles[$userID] = data.message;
2155
2156 // show user profile
2157 self._insertContent($elementID, data.message, true);
2158
2159 return false;
2160 });
2161 this._proxy.sendRequest();
2162 }
2163 },
2164
2165 /**
2166 * Purages a cached user profile.
2167 *
2168 * @param integer userID
2169 */
2170 purge: function(userID) {
2171 delete this._userProfiles[userID];
2172
2173 // purge content cache
2174 this._data = { };
2175 }
2176 });
2177
2178 /**
2179 * Initializes WCF.User.Action namespace.
2180 */
2181 WCF.User.Action = {};
2182
2183 if (COMPILER_TARGET_DEFAULT) {
2184 /**
2185 * Handles user follow and unfollow links.
2186 */
2187 WCF.User.Action.Follow = Class.extend({
2188 /**
2189 * list with elements containing follow and unfollow buttons
2190 * @var array
2191 */
2192 _containerList: null,
2193
2194 /**
2195 * CSS selector for follow buttons
2196 * @var string
2197 */
2198 _followButtonSelector: '.jsFollowButton',
2199
2200 /**
2201 * id of the user that is currently being followed/unfollowed
2202 * @var integer
2203 */
2204 _userID: 0,
2205
2206 /**
2207 * Initializes new WCF.User.Action.Follow object.
2208 *
2209 * @param array containerList
2210 * @param string followButtonSelector
2211 */
2212 init: function (containerList, followButtonSelector) {
2213 if (!containerList.length) {
2214 return;
2215 }
2216 this._containerList = containerList;
2217
2218 if (followButtonSelector) {
2219 this._followButtonSelector = followButtonSelector;
2220 }
2221
2222 // initialize proxy
2223 this._proxy = new WCF.Action.Proxy({
2224 success: $.proxy(this._success, this)
2225 });
2226
2227 // bind event listeners
2228 this._containerList.each($.proxy(function (index, container) {
2229 $(container).find(this._followButtonSelector).click($.proxy(this._click, this));
2230 }, this));
2231 },
2232
2233 /**
2234 * Handles a click on a follow or unfollow button.
2235 *
2236 * @param object event
2237 */
2238 _click: function (event) {
2239 event.preventDefault();
2240 var link = $(event.target);
2241 if (!link.is('a')) {
2242 link = link.closest('a');
2243 }
2244 this._userID = link.data('objectID');
2245
2246 this._proxy.setOption('data', {
2247 'actionName': link.data('following') ? 'unfollow' : 'follow',
2248 'className': 'wcf\\data\\user\\follow\\UserFollowAction',
2249 'parameters': {
2250 data: {
2251 userID: this._userID
2252 }
2253 }
2254 });
2255 this._proxy.sendRequest();
2256 },
2257
2258 /**
2259 * Handles the successful (un)following of a user.
2260 *
2261 * @param object data
2262 * @param string textStatus
2263 * @param jQuery jqXHR
2264 */
2265 _success: function (data, textStatus, jqXHR) {
2266 this._containerList.each($.proxy(function (index, container) {
2267 var button = $(container).find(this._followButtonSelector).get(0);
2268
2269 if (button && $(button).data('objectID') == this._userID) {
2270 button = $(button);
2271
2272 // toogle icon title
2273 if (data.returnValues.following) {
2274 button.attr('data-tooltip', WCF.Language.get('wcf.user.button.unfollow')).children('.icon').removeClass('fa-plus').addClass('fa-minus');
2275 button.children('.invisible').text(WCF.Language.get('wcf.user.button.unfollow'));
2276 }
2277 else {
2278 button.attr('data-tooltip', WCF.Language.get('wcf.user.button.follow')).children('.icon').removeClass('fa-minus').addClass('fa-plus');
2279 button.children('.invisible').text(WCF.Language.get('wcf.user.button.follow'));
2280 }
2281
2282 button.data('following', data.returnValues.following);
2283
2284 return false;
2285 }
2286 }, this));
2287
2288 var $notification = new WCF.System.Notification();
2289 $notification.show();
2290 }
2291 });
2292
2293 /**
2294 * Handles user ignore and unignore links.
2295 */
2296 WCF.User.Action.Ignore = Class.extend({
2297 /**
2298 * list with elements containing ignore and unignore buttons
2299 * @var array
2300 */
2301 _containerList: null,
2302
2303 /**
2304 * CSS selector for ignore buttons
2305 * @var string
2306 */
2307 _ignoreButtonSelector: '.jsIgnoreButton',
2308
2309 /**
2310 * id of the user that is currently being ignored/unignored
2311 * @var integer
2312 */
2313 _userID: 0,
2314
2315 /**
2316 * Initializes new WCF.User.Action.Ignore object.
2317 *
2318 * @param array containerList
2319 * @param string ignoreButtonSelector
2320 */
2321 init: function (containerList, ignoreButtonSelector) {
2322 if (!containerList.length) {
2323 return;
2324 }
2325 this._containerList = containerList;
2326
2327 if (ignoreButtonSelector) {
2328 this._ignoreButtonSelector = ignoreButtonSelector;
2329 }
2330
2331 // initialize proxy
2332 this._proxy = new WCF.Action.Proxy({
2333 success: $.proxy(this._success, this)
2334 });
2335
2336 // bind event listeners
2337 this._containerList.each($.proxy(function (index, container) {
2338 $(container).find(this._ignoreButtonSelector).click($.proxy(this._click, this));
2339 }, this));
2340 },
2341
2342 /**
2343 * Handles a click on a ignore or unignore button.
2344 *
2345 * @param object event
2346 */
2347 _click: function (event) {
2348 event.preventDefault();
2349 var link = $(event.target);
2350 if (!link.is('a')) {
2351 link = link.closest('a');
2352 }
2353 this._userID = link.data('objectID');
2354
2355 this._proxy.setOption('data', {
2356 'actionName': link.data('ignored') ? 'unignore' : 'ignore',
2357 'className': 'wcf\\data\\user\\ignore\\UserIgnoreAction',
2358 'parameters': {
2359 data: {
2360 userID: this._userID
2361 }
2362 }
2363 });
2364 this._proxy.sendRequest();
2365 },
2366
2367 /**
2368 * Handles the successful (un)ignoring of a user.
2369 *
2370 * @param object data
2371 * @param string textStatus
2372 * @param jQuery jqXHR
2373 */
2374 _success: function (data, textStatus, jqXHR) {
2375 this._containerList.each($.proxy(function (index, container) {
2376 var button = $(container).find(this._ignoreButtonSelector).get(0);
2377
2378 if (button && $(button).data('objectID') == this._userID) {
2379 button = $(button);
2380
2381 // toogle icon title
2382 if (data.returnValues.isIgnoredUser) {
2383 button.attr('data-tooltip', WCF.Language.get('wcf.user.button.unignore')).children('.icon').removeClass('fa-ban').addClass('fa-circle-o');
2384 button.children('.invisible').text(WCF.Language.get('wcf.user.button.unignore'));
2385 }
2386 else {
2387 button.attr('data-tooltip', WCF.Language.get('wcf.user.button.ignore')).children('.icon').removeClass('fa-circle-o').addClass('fa-ban');
2388 button.children('.invisible').text(WCF.Language.get('wcf.user.button.ignore'));
2389 }
2390
2391 button.data('ignored', data.returnValues.isIgnoredUser);
2392
2393 return false;
2394 }
2395 }, this));
2396
2397 var $notification = new WCF.System.Notification();
2398 $notification.show();
2399
2400 // force rebuilding of popover cache
2401 var self = this;
2402 WCF.System.ObjectStore.invoke('WCF.User.ProfilePreview', function (profilePreview) {
2403 profilePreview.purge(self._userID);
2404 });
2405 }
2406 });
2407 }
2408 else {
2409 WCF.User.Action.Follow = Class.extend({
2410 _containerList: {},
2411 _followButtonSelector: "",
2412 _userID: 0,
2413 init: function() {},
2414 _click: function() {},
2415 _success: function() {}
2416 });
2417
2418 WCF.User.Action.Ignore = Class.extend({
2419 _containerList: {},
2420 _ignoreButtonSelector: "",
2421 _userID: 0,
2422 init: function() {},
2423 _click: function() {},
2424 _success: function() {}
2425 });
2426 }
2427
2428 /**
2429 * Namespace for avatar functions.
2430 */
2431 WCF.User.Avatar = {};
2432
2433 if (COMPILER_TARGET_DEFAULT) {
2434 /**
2435 * Avatar upload function
2436 *
2437 * @see WCF.Upload
2438 */
2439 WCF.User.Avatar.Upload = WCF.Upload.extend({
2440 /**
2441 * user id of avatar owner
2442 * @var integer
2443 */
2444 _userID: 0,
2445
2446 /**
2447 * Initializes a new WCF.User.Avatar.Upload object.
2448 *
2449 * @param integer userID
2450 */
2451 init: function (userID) {
2452 this._super($('#avatarUpload > dd > div'), undefined, 'wcf\\data\\user\\avatar\\UserAvatarAction');
2453 this._userID = userID || 0;
2454
2455 $('#avatarForm input[type=radio]').change(function () {
2456 if ($(this).val() == 'custom') {
2457 $('#avatarUpload > dd > div').show();
2458 }
2459 else {
2460 $('#avatarUpload > dd > div').hide();
2461 }
2462 });
2463 if (!$('#avatarForm input[type=radio][value=custom]:checked').length) {
2464 $('#avatarUpload > dd > div').hide();
2465 }
2466 },
2467
2468 /**
2469 * @see WCF.Upload._initFile()
2470 */
2471 _initFile: function (file) {
2472 return $('#avatarUpload > dt > img');
2473 },
2474
2475 /**
2476 * @see WCF.Upload._success()
2477 */
2478 _success: function (uploadID, data) {
2479 if (data.returnValues.url) {
2480 this._updateImage(data.returnValues.url);
2481
2482 // hide error
2483 $('#avatarUpload > dd > .innerError').remove();
2484
2485 // show success message
2486 var $notification = new WCF.System.Notification(WCF.Language.get('wcf.user.avatar.upload.success'));
2487 $notification.show();
2488 }
2489 else if (data.returnValues.errorType) {
2490 // show error
2491 this._getInnerErrorElement().text(WCF.Language.get('wcf.user.avatar.upload.error.' + data.returnValues.errorType));
2492 }
2493 },
2494
2495 /**
2496 * Updates the displayed avatar image.
2497 *
2498 * @param string url
2499 */
2500 _updateImage: function (url) {
2501 $('#avatarUpload > dt > img').remove();
2502 var $image = $('<img src="' + url + '" class="userAvatarImage" alt="" />').css({
2503 'height': 'auto',
2504 'max-height': '96px',
2505 'max-width': '96px',
2506 'width': 'auto'
2507 });
2508
2509 $('#avatarUpload > dt').prepend($image);
2510
2511 WCF.DOMNodeInsertedHandler.execute();
2512 },
2513
2514 /**
2515 * Returns the inner error element.
2516 *
2517 * @return jQuery
2518 */
2519 _getInnerErrorElement: function () {
2520 var $span = $('#avatarUpload > dd > .innerError');
2521 if (!$span.length) {
2522 $span = $('<small class="innerError"></span>');
2523 $('#avatarUpload > dd').append($span);
2524 }
2525
2526 return $span;
2527 },
2528
2529 /**
2530 * @see WCF.Upload._getParameters()
2531 */
2532 _getParameters: function () {
2533 return {
2534 userID: this._userID
2535 };
2536 }
2537 });
2538 }
2539 else {
2540 WCF.User.Avatar.Upload = WCF.Upload.extend({
2541 _userID: 0,
2542 init: function() {},
2543 _initFile: function() {},
2544 _success: function() {},
2545 _updateImage: function() {},
2546 _getInnerErrorElement: function() {},
2547 _getParameters: function() {},
2548 _name: "",
2549 _buttonSelector: {},
2550 _fileListSelector: {},
2551 _fileUpload: {},
2552 _className: "",
2553 _iframe: {},
2554 _internalFileID: 0,
2555 _options: {},
2556 _uploadMatrix: {},
2557 _supportsAJAXUpload: true,
2558 _overlay: {},
2559 _createButton: function() {},
2560 _insertButton: function() {},
2561 _removeButton: function() {},
2562 _upload: function() {},
2563 _createUploadMatrix: function() {},
2564 _error: function() {},
2565 _progress: function() {},
2566 _showOverlay: function() {},
2567 _evaluateResponse: function() {},
2568 _getFilename: function() {}
2569 });
2570 }
2571
2572 /**
2573 * Generic implementation for grouped user lists.
2574 *
2575 * @param string className
2576 * @param string dialogTitle
2577 * @param object additionalParameters
2578 */
2579 WCF.User.List = Class.extend({
2580 /**
2581 * list of additional parameters
2582 * @var object
2583 */
2584 _additionalParameters: { },
2585
2586 /**
2587 * list of cached pages
2588 * @var object
2589 */
2590 _cache: { },
2591
2592 /**
2593 * action class name
2594 * @var string
2595 */
2596 _className: '',
2597
2598 /**
2599 * dialog overlay
2600 * @var jQuery
2601 */
2602 _dialog: null,
2603
2604 /**
2605 * dialog title
2606 * @var string
2607 */
2608 _dialogTitle: '',
2609
2610 /**
2611 * page count
2612 * @var integer
2613 */
2614 _pageCount: 0,
2615
2616 /**
2617 * current page no
2618 * @var integer
2619 */
2620 _pageNo: 1,
2621
2622 /**
2623 * action proxy
2624 * @var WCF.Action.Proxy
2625 */
2626 _proxy: null,
2627
2628 /**
2629 * Initializes a new grouped user list.
2630 *
2631 * @param string className
2632 * @param string dialogTitle
2633 * @param object additionalParameters
2634 */
2635 init: function(className, dialogTitle, additionalParameters) {
2636 this._additionalParameters = additionalParameters || { };
2637 this._cache = { };
2638 this._className = className;
2639 this._dialog = null;
2640 this._dialogTitle = dialogTitle;
2641 this._pageCount = 0;
2642 this._pageNo = 1;
2643
2644 this._proxy = new WCF.Action.Proxy({
2645 success: $.proxy(this._success, this)
2646 });
2647 },
2648
2649 /**
2650 * Opens the dialog overlay.
2651 */
2652 open: function() {
2653 this._pageNo = 1;
2654 this._showPage();
2655 },
2656
2657 /**
2658 * Displays the specified page.
2659 *
2660 * @param object event
2661 * @param object data
2662 */
2663 _showPage: function(event, data) {
2664 if (data && data.activePage) {
2665 this._pageNo = data.activePage;
2666 }
2667
2668 if (this._pageCount != 0 && (this._pageNo < 1 || this._pageNo > this._pageCount)) {
2669 console.debug("[WCF.User.List] Cannot access page " + this._pageNo + " of " + this._pageCount);
2670 return;
2671 }
2672
2673 if (this._cache[this._pageNo]) {
2674 var $dialogCreated = false;
2675 if (this._dialog === null) {
2676 this._dialog = $('#userList' + this._className.hashCode());
2677 if (this._dialog.length === 0) {
2678 this._dialog = $('<div id="userList' + this._className.hashCode() + '" />').hide().appendTo(document.body);
2679 $dialogCreated = true;
2680 }
2681 }
2682
2683 // remove current view
2684 this._dialog.empty();
2685
2686 // insert HTML
2687 this._dialog.html(this._cache[this._pageNo]);
2688
2689 // add pagination
2690 if (this._pageCount > 1) {
2691 this._dialog.find('.jsPagination').wcfPages({
2692 activePage: this._pageNo,
2693 maxPage: this._pageCount
2694 }).on('wcfpagesswitched', $.proxy(this._showPage, this));
2695 }
2696 else {
2697 this._dialog.find('.jsPagination').hide();
2698 }
2699
2700 // show dialog
2701 if ($dialogCreated) {
2702 this._dialog.wcfDialog({
2703 title: this._dialogTitle
2704 });
2705 }
2706 else {
2707 this._dialog.wcfDialog('option', 'title', this._dialogTitle);
2708 this._dialog.wcfDialog('open').wcfDialog('render');
2709 }
2710
2711 WCF.DOMNodeInsertedHandler.execute();
2712 }
2713 else {
2714 this._additionalParameters.pageNo = this._pageNo;
2715
2716 // load template via AJAX
2717 this._proxy.setOption('data', {
2718 actionName: 'getGroupedUserList',
2719 className: this._className,
2720 interfaceName: 'wcf\\data\\IGroupedUserListAction',
2721 parameters: this._additionalParameters
2722 });
2723 this._proxy.sendRequest();
2724 }
2725 },
2726
2727 /**
2728 * Handles successful AJAX requests.
2729 *
2730 * @param object data
2731 * @param string textStatus
2732 * @param jQuery jqXHR
2733 */
2734 _success: function(data, textStatus, jqXHR) {
2735 if (data.returnValues.pageCount) {
2736 this._pageCount = data.returnValues.pageCount;
2737 }
2738
2739 this._cache[this._pageNo] = data.returnValues.template;
2740 this._showPage();
2741 }
2742 });
2743
2744 /**
2745 * Namespace for object watch functions.
2746 */
2747 WCF.User.ObjectWatch = {};
2748
2749 if (COMPILER_TARGET_DEFAULT) {
2750 /**
2751 * Handles subscribe/unsubscribe links.
2752 */
2753 WCF.User.ObjectWatch.Subscribe = Class.extend({
2754 /**
2755 * CSS selector for subscribe buttons
2756 * @var string
2757 */
2758 _buttonSelector: '.jsSubscribeButton',
2759
2760 /**
2761 * list of buttons
2762 * @var object
2763 */
2764 _buttons: {},
2765
2766 /**
2767 * dialog overlay
2768 * @var object
2769 */
2770 _dialog: null,
2771
2772 /**
2773 * system notification
2774 * @var WCF.System.Notification
2775 */
2776 _notification: null,
2777
2778 /**
2779 * reload page on unsubscribe
2780 * @var boolean
2781 */
2782 _reloadOnUnsubscribe: false,
2783
2784 /**
2785 * WCF.User.ObjectWatch.Subscribe object.
2786 *
2787 * @param boolean reloadOnUnsubscribe
2788 */
2789 init: function (reloadOnUnsubscribe) {
2790 this._buttons = {};
2791 this._notification = null;
2792 this._reloadOnUnsubscribe = (reloadOnUnsubscribe === true);
2793
2794 // initialize proxy
2795 this._proxy = new WCF.Action.Proxy({
2796 success: $.proxy(this._success, this)
2797 });
2798
2799 // bind event listeners
2800 $(this._buttonSelector).each($.proxy(function (index, button) {
2801 var $button = $(button);
2802 $button.addClass('pointer');
2803 var $objectType = $button.data('objectType');
2804 var $objectID = $button.data('objectID');
2805
2806 if (this._buttons[$objectType] === undefined) {
2807 this._buttons[$objectType] = {};
2808 }
2809
2810 this._buttons[$objectType][$objectID] = $button.click($.proxy(this._click, this));
2811 }, this));
2812
2813 WCF.System.Event.addListener('com.woltlab.wcf.objectWatch', 'update', $.proxy(this._updateSubscriptionStatus, this));
2814 },
2815
2816 /**
2817 * Handles a click on a subscribe button.
2818 *
2819 * @param object event
2820 */
2821 _click: function (event) {
2822 event.preventDefault();
2823 var $button = $(event.currentTarget);
2824
2825 this._proxy.setOption('data', {
2826 actionName: 'manageSubscription',
2827 className: 'wcf\\data\\user\\object\\watch\\UserObjectWatchAction',
2828 parameters: {
2829 objectID: $button.data('objectID'),
2830 objectType: $button.data('objectType')
2831 }
2832 });
2833 this._proxy.sendRequest();
2834 },
2835
2836 /**
2837 * Handles successful AJAX requests.
2838 *
2839 * @param object data
2840 * @param string textStatus
2841 * @param jQuery jqXHR
2842 */
2843 _success: function (data, textStatus, jqXHR) {
2844 if (data.actionName === 'manageSubscription') {
2845 if (this._dialog === null) {
2846 this._dialog = $('<div>' + data.returnValues.template + '</div>').hide().appendTo(document.body);
2847 this._dialog.wcfDialog({
2848 title: WCF.Language.get('wcf.user.objectWatch.manageSubscription')
2849 });
2850 }
2851 else {
2852 this._dialog.html(data.returnValues.template);
2853 this._dialog.wcfDialog('open');
2854 }
2855
2856 // bind event listener
2857 this._dialog.find('.formSubmit > .jsButtonSave').data('objectID', data.returnValues.objectID).data('objectType', data.returnValues.objectType).click($.proxy(this._save, this));
2858 var $enableNotification = this._dialog.find('input[name=enableNotification]').disable();
2859
2860 // toggle subscription
2861 this._dialog.find('input[name=subscribe]').change(function (event) {
2862 var $input = $(event.currentTarget);
2863 if ($input.val() == 1) {
2864 $enableNotification.enable();
2865 }
2866 else {
2867 $enableNotification.disable();
2868 }
2869 });
2870
2871 // setup
2872 var $selectedOption = this._dialog.find('input[name=subscribe]:checked');
2873 if ($selectedOption.length && $selectedOption.val() == 1) {
2874 $enableNotification.enable();
2875 }
2876 }
2877 else if (data.actionName === 'saveSubscription' && this._dialog.is(':visible')) {
2878 this._dialog.wcfDialog('close');
2879
2880 this._updateSubscriptionStatus({
2881 isSubscribed: data.returnValues.subscribe,
2882 objectID: data.returnValues.objectID
2883 });
2884
2885
2886 // show notification
2887 if (this._notification === null) {
2888 this._notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success.edit'));
2889 }
2890
2891 this._notification.show();
2892 }
2893 },
2894
2895 /**
2896 * Saves the subscription.
2897 *
2898 * @param object event
2899 */
2900 _save: function (event) {
2901 var $button = this._buttons[$(event.currentTarget).data('objectType')][$(event.currentTarget).data('objectID')];
2902 var $subscribe = this._dialog.find('input[name=subscribe]:checked').val();
2903 var $enableNotification = (this._dialog.find('input[name=enableNotification]').is(':checked')) ? 1 : 0;
2904
2905 this._proxy.setOption('data', {
2906 actionName: 'saveSubscription',
2907 className: 'wcf\\data\\user\\object\\watch\\UserObjectWatchAction',
2908 parameters: {
2909 enableNotification: $enableNotification,
2910 objectID: $button.data('objectID'),
2911 objectType: $button.data('objectType'),
2912 subscribe: $subscribe
2913 }
2914 });
2915 this._proxy.sendRequest();
2916 },
2917
2918 /**
2919 * Updates subscription status and icon.
2920 *
2921 * @param object data
2922 */
2923 _updateSubscriptionStatus: function (data) {
2924 var $button = $(this._buttonSelector + '[data-object-id=' + data.objectID + ']');
2925 var $icon = $button.children('.icon');
2926 if (data.isSubscribed) {
2927 $icon.removeClass('fa-bookmark-o').addClass('fa-bookmark');
2928 $button.data('isSubscribed', true);
2929 }
2930 else {
2931 if ($button.data('removeOnUnsubscribe')) {
2932 $button.parent().remove();
2933 }
2934 else {
2935 $icon.removeClass('fa-bookmark').addClass('fa-bookmark-o');
2936 $button.data('isSubscribed', false);
2937 }
2938
2939 if (this._reloadOnUnsubscribe) {
2940 window.location.reload();
2941 return;
2942 }
2943 }
2944
2945 WCF.System.Event.fireEvent('com.woltlab.wcf.objectWatch', 'updatedSubscription', data);
2946 }
2947 });
2948 }
2949 else {
2950 WCF.User.ObjectWatch.Subscribe = Class.extend({
2951 _buttonSelector: "",
2952 _buttons: {},
2953 _dialog: {},
2954 _notification: {},
2955 _reloadOnUnsubscribe: false,
2956 init: function() {},
2957 _click: function() {},
2958 _success: function() {},
2959 _save: function() {},
2960 _updateSubscriptionStatus: function() {}
2961 });
2962 }