Merge pull request #2759 from derpierre65/patch-4
[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 * @deprecated since 3.2
1953 */
1954 WCF.User.LikeLoader = Class.extend({
1955 /**
1956 * container object
1957 * @var jQuery
1958 */
1959 _container: null,
1960
1961 /**
1962 * like type
1963 * @var string
1964 */
1965 _likeType: 'received',
1966
1967 /**
1968 * like value
1969 * @var integer
1970 */
1971 _likeValue: 1,
1972
1973 /**
1974 * button to load next events
1975 * @var jQuery
1976 */
1977 _loadButton: null,
1978
1979 /**
1980 * 'no more entries' element
1981 * @var jQuery
1982 */
1983 _noMoreEntries: null,
1984
1985 /**
1986 * action proxy
1987 * @var WCF.Action.Proxy
1988 */
1989 _proxy: null,
1990
1991 /**
1992 * user id
1993 * @var integer
1994 */
1995 _userID: 0,
1996
1997 /**
1998 * Initializes a new RecentActivityLoader object.
1999 *
2000 * @param integer userID
2001 * @param boolean filteredByFollowedUsers
2002 */
2003 init: function(userID) {
2004 this._container = $('#likeList');
2005 this._userID = userID;
2006
2007 if (!this._userID) {
2008 console.debug("[WCF.User.RecentActivityLoader] Invalid parameter 'userID' given.");
2009 return;
2010 }
2011
2012 this._proxy = new WCF.Action.Proxy({
2013 success: $.proxy(this._success, this)
2014 });
2015
2016 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);
2017 this._loadButton = $container.children('button').click($.proxy(this._click, this));
2018 this._noMoreEntries = $container.children('small').hide();
2019
2020 if (this._container.find('> li').length == 2) {
2021 this._loadButton.hide();
2022 this._noMoreEntries.show();
2023 }
2024
2025 $('#likeType .button').click($.proxy(this._clickLikeType, this));
2026 $('#likeValue .button').click($.proxy(this._clickLikeValue, this));
2027 },
2028
2029 /**
2030 * Handles like type change.
2031 */
2032 _clickLikeType: function(event) {
2033 var $button = $(event.currentTarget);
2034 if (this._likeType != $button.data('likeType')) {
2035 this._likeType = $button.data('likeType');
2036 $('#likeType .button').removeClass('active');
2037 $button.addClass('active');
2038 this._reload();
2039 }
2040 },
2041
2042 /**
2043 * Handles like value change.
2044 */
2045 _clickLikeValue: function(event) {
2046 var $button = $(event.currentTarget);
2047 if (this._likeValue != $button.data('likeValue')) {
2048 this._likeValue = $button.data('likeValue');
2049 $('#likeValue .button').removeClass('active');
2050 $button.addClass('active');
2051
2052 // change button labels
2053 $('#likeType > li:first-child > .button').text(WCF.Language.get('wcf.like.' + (this._likeValue == -1 ? 'dis' : '') + 'likesReceived'));
2054 $('#likeType > li:last-child > .button').text(WCF.Language.get('wcf.like.' + (this._likeValue == -1 ? 'dis' : '') + 'likesGiven'));
2055
2056 this._container.find('> li.likeListMore button').text(WCF.Language.get('wcf.like.' + (this._likeValue == -1 ? 'dis' : '') + 'likes.more'));
2057 this._container.find('> li.likeListMore small').text(WCF.Language.get('wcf.like.' + (this._likeValue == -1 ? 'dis' : '') + 'likes.noMoreEntries'));
2058
2059 this._reload();
2060 }
2061 },
2062
2063 /**
2064 * Handles reload.
2065 */
2066 _reload: function() {
2067 this._container.find('> li:not(:first-child):not(:last-child)').remove();
2068 this._container.data('lastLikeTime', 0);
2069 this._click();
2070 },
2071
2072 /**
2073 * Loads next likes.
2074 */
2075 _click: function() {
2076 this._loadButton.enable();
2077
2078 var $parameters = {
2079 lastLikeTime: this._container.data('lastLikeTime'),
2080 userID: this._userID,
2081 likeType: this._likeType,
2082 likeValue: this._likeValue
2083 };
2084
2085 this._proxy.setOption('data', {
2086 actionName: 'load',
2087 className: 'wcf\\data\\like\\LikeAction',
2088 parameters: $parameters
2089 });
2090 this._proxy.sendRequest();
2091 },
2092
2093 /**
2094 * Handles successful AJAX requests.
2095 *
2096 * @param object data
2097 * @param string textStatus
2098 * @param jQuery jqXHR
2099 */
2100 _success: function(data, textStatus, jqXHR) {
2101 if (data.returnValues.template) {
2102 $(data.returnValues.template).insertBefore(this._loadButton.parent());
2103
2104 this._container.data('lastLikeTime', data.returnValues.lastLikeTime);
2105 this._noMoreEntries.hide();
2106 this._loadButton.show().enable();
2107 }
2108 else {
2109 this._noMoreEntries.show();
2110 this._loadButton.hide();
2111 }
2112 }
2113 });
2114
2115 /**
2116 * Loads user profile previews.
2117 *
2118 * @see WCF.Popover
2119 */
2120 WCF.User.ProfilePreview = WCF.Popover.extend({
2121 /**
2122 * action proxy
2123 * @var WCF.Action.Proxy
2124 */
2125 _proxy: null,
2126
2127 /**
2128 * list of user profiles
2129 * @var object
2130 */
2131 _userProfiles: { },
2132
2133 /**
2134 * @see WCF.Popover.init()
2135 */
2136 init: function() {
2137 this._super('.userLink');
2138
2139 this._proxy = new WCF.Action.Proxy({
2140 showLoadingOverlay: false
2141 });
2142
2143 // register instance
2144 WCF.System.ObjectStore.add('WCF.User.ProfilePreview', this);
2145 },
2146
2147 /**
2148 * @see WCF.Popover._loadContent()
2149 */
2150 _loadContent: function() {
2151 var $element = $('#' + this._activeElementID);
2152 var $userID = $element.data('userID');
2153
2154 if (this._userProfiles[$userID]) {
2155 // use cached user profile
2156 this._insertContent(this._activeElementID, this._userProfiles[$userID], true);
2157 }
2158 else {
2159 this._proxy.setOption('data', {
2160 actionName: 'getUserProfile',
2161 className: 'wcf\\data\\user\\UserProfileAction',
2162 objectIDs: [ $userID ]
2163 });
2164
2165 var $elementID = this._activeElementID;
2166 var self = this;
2167 this._proxy.setOption('success', function(data, textStatus, jqXHR) {
2168 // cache user profile
2169 self._userProfiles[$userID] = data.returnValues.template;
2170
2171 // show user profile
2172 self._insertContent($elementID, data.returnValues.template, true);
2173 });
2174 this._proxy.setOption('failure', function(data, jqXHR, textStatus, errorThrown) {
2175 // cache user profile
2176 self._userProfiles[$userID] = data.message;
2177
2178 // show user profile
2179 self._insertContent($elementID, data.message, true);
2180
2181 return false;
2182 });
2183 this._proxy.sendRequest();
2184 }
2185 },
2186
2187 /**
2188 * Purages a cached user profile.
2189 *
2190 * @param integer userID
2191 */
2192 purge: function(userID) {
2193 delete this._userProfiles[userID];
2194
2195 // purge content cache
2196 this._data = { };
2197 }
2198 });
2199
2200 /**
2201 * Initializes WCF.User.Action namespace.
2202 */
2203 WCF.User.Action = {};
2204
2205 if (COMPILER_TARGET_DEFAULT) {
2206 /**
2207 * Handles user follow and unfollow links.
2208 */
2209 WCF.User.Action.Follow = Class.extend({
2210 /**
2211 * list with elements containing follow and unfollow buttons
2212 * @var array
2213 */
2214 _containerList: null,
2215
2216 /**
2217 * CSS selector for follow buttons
2218 * @var string
2219 */
2220 _followButtonSelector: '.jsFollowButton',
2221
2222 /**
2223 * id of the user that is currently being followed/unfollowed
2224 * @var integer
2225 */
2226 _userID: 0,
2227
2228 /**
2229 * Initializes new WCF.User.Action.Follow object.
2230 *
2231 * @param array containerList
2232 * @param string followButtonSelector
2233 */
2234 init: function (containerList, followButtonSelector) {
2235 if (!containerList.length) {
2236 return;
2237 }
2238 this._containerList = containerList;
2239
2240 if (followButtonSelector) {
2241 this._followButtonSelector = followButtonSelector;
2242 }
2243
2244 // initialize proxy
2245 this._proxy = new WCF.Action.Proxy({
2246 success: $.proxy(this._success, this)
2247 });
2248
2249 // bind event listeners
2250 this._containerList.each($.proxy(function (index, container) {
2251 $(container).find(this._followButtonSelector).click($.proxy(this._click, this));
2252 }, this));
2253 },
2254
2255 /**
2256 * Handles a click on a follow or unfollow button.
2257 *
2258 * @param object event
2259 */
2260 _click: function (event) {
2261 event.preventDefault();
2262 var link = $(event.target);
2263 if (!link.is('a')) {
2264 link = link.closest('a');
2265 }
2266 this._userID = link.data('objectID');
2267
2268 this._proxy.setOption('data', {
2269 'actionName': link.data('following') ? 'unfollow' : 'follow',
2270 'className': 'wcf\\data\\user\\follow\\UserFollowAction',
2271 'parameters': {
2272 data: {
2273 userID: this._userID
2274 }
2275 }
2276 });
2277 this._proxy.sendRequest();
2278 },
2279
2280 /**
2281 * Handles the successful (un)following of a user.
2282 *
2283 * @param object data
2284 * @param string textStatus
2285 * @param jQuery jqXHR
2286 */
2287 _success: function (data, textStatus, jqXHR) {
2288 this._containerList.each($.proxy(function (index, container) {
2289 var button = $(container).find(this._followButtonSelector).get(0);
2290
2291 if (button && $(button).data('objectID') == this._userID) {
2292 button = $(button);
2293
2294 // toogle icon title
2295 if (data.returnValues.following) {
2296 button.attr('data-tooltip', WCF.Language.get('wcf.user.button.unfollow')).children('.icon').removeClass('fa-plus').addClass('fa-minus');
2297 button.children('.invisible').text(WCF.Language.get('wcf.user.button.unfollow'));
2298 }
2299 else {
2300 button.attr('data-tooltip', WCF.Language.get('wcf.user.button.follow')).children('.icon').removeClass('fa-minus').addClass('fa-plus');
2301 button.children('.invisible').text(WCF.Language.get('wcf.user.button.follow'));
2302 }
2303
2304 button.data('following', data.returnValues.following);
2305
2306 return false;
2307 }
2308 }, this));
2309
2310 var $notification = new WCF.System.Notification();
2311 $notification.show();
2312 }
2313 });
2314
2315 /**
2316 * Handles user ignore and unignore links.
2317 */
2318 WCF.User.Action.Ignore = Class.extend({
2319 /**
2320 * list with elements containing ignore and unignore buttons
2321 * @var array
2322 */
2323 _containerList: null,
2324
2325 /**
2326 * CSS selector for ignore buttons
2327 * @var string
2328 */
2329 _ignoreButtonSelector: '.jsIgnoreButton',
2330
2331 /**
2332 * id of the user that is currently being ignored/unignored
2333 * @var integer
2334 */
2335 _userID: 0,
2336
2337 /**
2338 * Initializes new WCF.User.Action.Ignore object.
2339 *
2340 * @param array containerList
2341 * @param string ignoreButtonSelector
2342 */
2343 init: function (containerList, ignoreButtonSelector) {
2344 if (!containerList.length) {
2345 return;
2346 }
2347 this._containerList = containerList;
2348
2349 if (ignoreButtonSelector) {
2350 this._ignoreButtonSelector = ignoreButtonSelector;
2351 }
2352
2353 // initialize proxy
2354 this._proxy = new WCF.Action.Proxy({
2355 success: $.proxy(this._success, this)
2356 });
2357
2358 // bind event listeners
2359 this._containerList.each($.proxy(function (index, container) {
2360 $(container).find(this._ignoreButtonSelector).click($.proxy(this._click, this));
2361 }, this));
2362 },
2363
2364 /**
2365 * Handles a click on a ignore or unignore button.
2366 *
2367 * @param object event
2368 */
2369 _click: function (event) {
2370 event.preventDefault();
2371 var link = $(event.target);
2372 if (!link.is('a')) {
2373 link = link.closest('a');
2374 }
2375 this._userID = link.data('objectID');
2376
2377 this._proxy.setOption('data', {
2378 'actionName': link.data('ignored') ? 'unignore' : 'ignore',
2379 'className': 'wcf\\data\\user\\ignore\\UserIgnoreAction',
2380 'parameters': {
2381 data: {
2382 userID: this._userID
2383 }
2384 }
2385 });
2386 this._proxy.sendRequest();
2387 },
2388
2389 /**
2390 * Handles the successful (un)ignoring of a user.
2391 *
2392 * @param object data
2393 * @param string textStatus
2394 * @param jQuery jqXHR
2395 */
2396 _success: function (data, textStatus, jqXHR) {
2397 this._containerList.each($.proxy(function (index, container) {
2398 var button = $(container).find(this._ignoreButtonSelector).get(0);
2399
2400 if (button && $(button).data('objectID') == this._userID) {
2401 button = $(button);
2402
2403 // toogle icon title
2404 if (data.returnValues.isIgnoredUser) {
2405 button.attr('data-tooltip', WCF.Language.get('wcf.user.button.unignore')).children('.icon').removeClass('fa-ban').addClass('fa-circle-o');
2406 button.children('.invisible').text(WCF.Language.get('wcf.user.button.unignore'));
2407 }
2408 else {
2409 button.attr('data-tooltip', WCF.Language.get('wcf.user.button.ignore')).children('.icon').removeClass('fa-circle-o').addClass('fa-ban');
2410 button.children('.invisible').text(WCF.Language.get('wcf.user.button.ignore'));
2411 }
2412
2413 button.data('ignored', data.returnValues.isIgnoredUser);
2414
2415 return false;
2416 }
2417 }, this));
2418
2419 var $notification = new WCF.System.Notification();
2420 $notification.show();
2421
2422 // force rebuilding of popover cache
2423 var self = this;
2424 WCF.System.ObjectStore.invoke('WCF.User.ProfilePreview', function (profilePreview) {
2425 profilePreview.purge(self._userID);
2426 });
2427 }
2428 });
2429 }
2430 else {
2431 WCF.User.Action.Follow = Class.extend({
2432 _containerList: {},
2433 _followButtonSelector: "",
2434 _userID: 0,
2435 init: function() {},
2436 _click: function() {},
2437 _success: function() {}
2438 });
2439
2440 WCF.User.Action.Ignore = Class.extend({
2441 _containerList: {},
2442 _ignoreButtonSelector: "",
2443 _userID: 0,
2444 init: function() {},
2445 _click: function() {},
2446 _success: function() {}
2447 });
2448 }
2449
2450 /**
2451 * Namespace for avatar functions.
2452 */
2453 WCF.User.Avatar = {};
2454
2455 if (COMPILER_TARGET_DEFAULT) {
2456 /**
2457 * Avatar upload function
2458 *
2459 * @see WCF.Upload
2460 */
2461 WCF.User.Avatar.Upload = WCF.Upload.extend({
2462 /**
2463 * user id of avatar owner
2464 * @var integer
2465 */
2466 _userID: 0,
2467
2468 /**
2469 * Initializes a new WCF.User.Avatar.Upload object.
2470 *
2471 * @param integer userID
2472 */
2473 init: function (userID) {
2474 this._super($('#avatarUpload > dd > div'), undefined, 'wcf\\data\\user\\avatar\\UserAvatarAction');
2475 this._userID = userID || 0;
2476
2477 $('#avatarForm input[type=radio]').change(function () {
2478 if ($(this).val() == 'custom') {
2479 $('#avatarUpload > dd > div').show();
2480 }
2481 else {
2482 $('#avatarUpload > dd > div').hide();
2483 }
2484 });
2485 if (!$('#avatarForm input[type=radio][value=custom]:checked').length) {
2486 $('#avatarUpload > dd > div').hide();
2487 }
2488 },
2489
2490 /**
2491 * @see WCF.Upload._initFile()
2492 */
2493 _initFile: function (file) {
2494 return $('#avatarUpload > dt > img');
2495 },
2496
2497 /**
2498 * @see WCF.Upload._success()
2499 */
2500 _success: function (uploadID, data) {
2501 if (data.returnValues.url) {
2502 this._updateImage(data.returnValues.url);
2503
2504 // hide error
2505 $('#avatarUpload > dd > .innerError').remove();
2506
2507 // show success message
2508 var $notification = new WCF.System.Notification(WCF.Language.get('wcf.user.avatar.upload.success'));
2509 $notification.show();
2510 }
2511 else if (data.returnValues.errorType) {
2512 // show error
2513 this._getInnerErrorElement().text(WCF.Language.get('wcf.user.avatar.upload.error.' + data.returnValues.errorType));
2514 }
2515 },
2516
2517 /**
2518 * Updates the displayed avatar image.
2519 *
2520 * @param string url
2521 */
2522 _updateImage: function (url) {
2523 $('#avatarUpload > dt > img').remove();
2524 var $image = $('<img src="' + url + '" class="userAvatarImage" alt="" />').css({
2525 'height': 'auto',
2526 'max-height': '96px',
2527 'max-width': '96px',
2528 'width': 'auto'
2529 });
2530
2531 $('#avatarUpload > dt').prepend($image);
2532
2533 WCF.DOMNodeInsertedHandler.execute();
2534 },
2535
2536 /**
2537 * Returns the inner error element.
2538 *
2539 * @return jQuery
2540 */
2541 _getInnerErrorElement: function () {
2542 var $span = $('#avatarUpload > dd > .innerError');
2543 if (!$span.length) {
2544 $span = $('<small class="innerError"></span>');
2545 $('#avatarUpload > dd').append($span);
2546 }
2547
2548 return $span;
2549 },
2550
2551 /**
2552 * @see WCF.Upload._getParameters()
2553 */
2554 _getParameters: function () {
2555 return {
2556 userID: this._userID
2557 };
2558 }
2559 });
2560 }
2561 else {
2562 WCF.User.Avatar.Upload = WCF.Upload.extend({
2563 _userID: 0,
2564 init: function() {},
2565 _initFile: function() {},
2566 _success: function() {},
2567 _updateImage: function() {},
2568 _getInnerErrorElement: function() {},
2569 _getParameters: function() {},
2570 _name: "",
2571 _buttonSelector: {},
2572 _fileListSelector: {},
2573 _fileUpload: {},
2574 _className: "",
2575 _iframe: {},
2576 _internalFileID: 0,
2577 _options: {},
2578 _uploadMatrix: {},
2579 _supportsAJAXUpload: true,
2580 _overlay: {},
2581 _createButton: function() {},
2582 _insertButton: function() {},
2583 _removeButton: function() {},
2584 _upload: function() {},
2585 _createUploadMatrix: function() {},
2586 _error: function() {},
2587 _progress: function() {},
2588 _showOverlay: function() {},
2589 _evaluateResponse: function() {},
2590 _getFilename: function() {}
2591 });
2592 }
2593
2594 /**
2595 * Generic implementation for grouped user lists.
2596 *
2597 * @param string className
2598 * @param string dialogTitle
2599 * @param object additionalParameters
2600 */
2601 WCF.User.List = Class.extend({
2602 /**
2603 * list of additional parameters
2604 * @var object
2605 */
2606 _additionalParameters: { },
2607
2608 /**
2609 * list of cached pages
2610 * @var object
2611 */
2612 _cache: { },
2613
2614 /**
2615 * action class name
2616 * @var string
2617 */
2618 _className: '',
2619
2620 /**
2621 * dialog overlay
2622 * @var jQuery
2623 */
2624 _dialog: null,
2625
2626 /**
2627 * dialog title
2628 * @var string
2629 */
2630 _dialogTitle: '',
2631
2632 /**
2633 * page count
2634 * @var integer
2635 */
2636 _pageCount: 0,
2637
2638 /**
2639 * current page no
2640 * @var integer
2641 */
2642 _pageNo: 1,
2643
2644 /**
2645 * action proxy
2646 * @var WCF.Action.Proxy
2647 */
2648 _proxy: null,
2649
2650 /**
2651 * Initializes a new grouped user list.
2652 *
2653 * @param string className
2654 * @param string dialogTitle
2655 * @param object additionalParameters
2656 */
2657 init: function(className, dialogTitle, additionalParameters) {
2658 this._additionalParameters = additionalParameters || { };
2659 this._cache = { };
2660 this._className = className;
2661 this._dialog = null;
2662 this._dialogTitle = dialogTitle;
2663 this._pageCount = 0;
2664 this._pageNo = 1;
2665
2666 this._proxy = new WCF.Action.Proxy({
2667 success: $.proxy(this._success, this)
2668 });
2669 },
2670
2671 /**
2672 * Opens the dialog overlay.
2673 */
2674 open: function() {
2675 this._pageNo = 1;
2676 this._showPage();
2677 },
2678
2679 /**
2680 * Displays the specified page.
2681 *
2682 * @param object event
2683 * @param object data
2684 */
2685 _showPage: function(event, data) {
2686 if (data && data.activePage) {
2687 this._pageNo = data.activePage;
2688 }
2689
2690 if (this._pageCount != 0 && (this._pageNo < 1 || this._pageNo > this._pageCount)) {
2691 console.debug("[WCF.User.List] Cannot access page " + this._pageNo + " of " + this._pageCount);
2692 return;
2693 }
2694
2695 if (this._cache[this._pageNo]) {
2696 var $dialogCreated = false;
2697 if (this._dialog === null) {
2698 this._dialog = $('#userList' + this._className.hashCode());
2699 if (this._dialog.length === 0) {
2700 this._dialog = $('<div id="userList' + this._className.hashCode() + '" />').hide().appendTo(document.body);
2701 $dialogCreated = true;
2702 }
2703 }
2704
2705 // remove current view
2706 this._dialog.empty();
2707
2708 // insert HTML
2709 this._dialog.html(this._cache[this._pageNo]);
2710
2711 // add pagination
2712 if (this._pageCount > 1) {
2713 this._dialog.find('.jsPagination').wcfPages({
2714 activePage: this._pageNo,
2715 maxPage: this._pageCount
2716 }).on('wcfpagesswitched', $.proxy(this._showPage, this));
2717 }
2718 else {
2719 this._dialog.find('.jsPagination').hide();
2720 }
2721
2722 // show dialog
2723 if ($dialogCreated) {
2724 this._dialog.wcfDialog({
2725 title: this._dialogTitle
2726 });
2727 }
2728 else {
2729 this._dialog.wcfDialog('option', 'title', this._dialogTitle);
2730 this._dialog.wcfDialog('open').wcfDialog('render');
2731 }
2732
2733 WCF.DOMNodeInsertedHandler.execute();
2734 }
2735 else {
2736 this._additionalParameters.pageNo = this._pageNo;
2737
2738 // load template via AJAX
2739 this._proxy.setOption('data', {
2740 actionName: 'getGroupedUserList',
2741 className: this._className,
2742 interfaceName: 'wcf\\data\\IGroupedUserListAction',
2743 parameters: this._additionalParameters
2744 });
2745 this._proxy.sendRequest();
2746 }
2747 },
2748
2749 /**
2750 * Handles successful AJAX requests.
2751 *
2752 * @param object data
2753 * @param string textStatus
2754 * @param jQuery jqXHR
2755 */
2756 _success: function(data, textStatus, jqXHR) {
2757 if (data.returnValues.pageCount) {
2758 this._pageCount = data.returnValues.pageCount;
2759 }
2760
2761 this._cache[this._pageNo] = data.returnValues.template;
2762 this._showPage();
2763 }
2764 });
2765
2766 /**
2767 * Namespace for object watch functions.
2768 */
2769 WCF.User.ObjectWatch = {};
2770
2771 if (COMPILER_TARGET_DEFAULT) {
2772 /**
2773 * Handles subscribe/unsubscribe links.
2774 */
2775 WCF.User.ObjectWatch.Subscribe = Class.extend({
2776 /**
2777 * CSS selector for subscribe buttons
2778 * @var string
2779 */
2780 _buttonSelector: '.jsSubscribeButton',
2781
2782 /**
2783 * list of buttons
2784 * @var object
2785 */
2786 _buttons: {},
2787
2788 /**
2789 * dialog overlay
2790 * @var object
2791 */
2792 _dialog: null,
2793
2794 /**
2795 * system notification
2796 * @var WCF.System.Notification
2797 */
2798 _notification: null,
2799
2800 /**
2801 * reload page on unsubscribe
2802 * @var boolean
2803 */
2804 _reloadOnUnsubscribe: false,
2805
2806 /**
2807 * WCF.User.ObjectWatch.Subscribe object.
2808 *
2809 * @param boolean reloadOnUnsubscribe
2810 */
2811 init: function (reloadOnUnsubscribe) {
2812 this._buttons = {};
2813 this._notification = null;
2814 this._reloadOnUnsubscribe = (reloadOnUnsubscribe === true);
2815
2816 // initialize proxy
2817 this._proxy = new WCF.Action.Proxy({
2818 success: $.proxy(this._success, this)
2819 });
2820
2821 // bind event listeners
2822 $(this._buttonSelector).each($.proxy(function (index, button) {
2823 var $button = $(button);
2824 $button.addClass('pointer');
2825 var $objectType = $button.data('objectType');
2826 var $objectID = $button.data('objectID');
2827
2828 if (this._buttons[$objectType] === undefined) {
2829 this._buttons[$objectType] = {};
2830 }
2831
2832 this._buttons[$objectType][$objectID] = $button.click($.proxy(this._click, this));
2833 }, this));
2834
2835 WCF.System.Event.addListener('com.woltlab.wcf.objectWatch', 'update', $.proxy(this._updateSubscriptionStatus, this));
2836 },
2837
2838 /**
2839 * Handles a click on a subscribe button.
2840 *
2841 * @param object event
2842 */
2843 _click: function (event) {
2844 event.preventDefault();
2845 var $button = $(event.currentTarget);
2846
2847 this._proxy.setOption('data', {
2848 actionName: 'manageSubscription',
2849 className: 'wcf\\data\\user\\object\\watch\\UserObjectWatchAction',
2850 parameters: {
2851 objectID: $button.data('objectID'),
2852 objectType: $button.data('objectType')
2853 }
2854 });
2855 this._proxy.sendRequest();
2856 },
2857
2858 /**
2859 * Handles successful AJAX requests.
2860 *
2861 * @param object data
2862 * @param string textStatus
2863 * @param jQuery jqXHR
2864 */
2865 _success: function (data, textStatus, jqXHR) {
2866 if (data.actionName === 'manageSubscription') {
2867 if (this._dialog === null) {
2868 this._dialog = $('<div>' + data.returnValues.template + '</div>').hide().appendTo(document.body);
2869 this._dialog.wcfDialog({
2870 title: WCF.Language.get('wcf.user.objectWatch.manageSubscription')
2871 });
2872 }
2873 else {
2874 this._dialog.html(data.returnValues.template);
2875 this._dialog.wcfDialog('open');
2876 }
2877
2878 // bind event listener
2879 this._dialog.find('.formSubmit > .jsButtonSave').data('objectID', data.returnValues.objectID).data('objectType', data.returnValues.objectType).click($.proxy(this._save, this));
2880 var $enableNotification = this._dialog.find('input[name=enableNotification]').disable();
2881
2882 // toggle subscription
2883 this._dialog.find('input[name=subscribe]').change(function (event) {
2884 var $input = $(event.currentTarget);
2885 if ($input.val() == 1) {
2886 $enableNotification.enable();
2887 }
2888 else {
2889 $enableNotification.disable();
2890 }
2891 });
2892
2893 // setup
2894 var $selectedOption = this._dialog.find('input[name=subscribe]:checked');
2895 if ($selectedOption.length && $selectedOption.val() == 1) {
2896 $enableNotification.enable();
2897 }
2898 }
2899 else if (data.actionName === 'saveSubscription' && this._dialog.is(':visible')) {
2900 this._dialog.wcfDialog('close');
2901
2902 this._updateSubscriptionStatus({
2903 isSubscribed: data.returnValues.subscribe,
2904 objectID: data.returnValues.objectID
2905 });
2906
2907
2908 // show notification
2909 if (this._notification === null) {
2910 this._notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success.edit'));
2911 }
2912
2913 this._notification.show();
2914 }
2915 },
2916
2917 /**
2918 * Saves the subscription.
2919 *
2920 * @param object event
2921 */
2922 _save: function (event) {
2923 var $button = this._buttons[$(event.currentTarget).data('objectType')][$(event.currentTarget).data('objectID')];
2924 var $subscribe = this._dialog.find('input[name=subscribe]:checked').val();
2925 var $enableNotification = (this._dialog.find('input[name=enableNotification]').is(':checked')) ? 1 : 0;
2926
2927 this._proxy.setOption('data', {
2928 actionName: 'saveSubscription',
2929 className: 'wcf\\data\\user\\object\\watch\\UserObjectWatchAction',
2930 parameters: {
2931 enableNotification: $enableNotification,
2932 objectID: $button.data('objectID'),
2933 objectType: $button.data('objectType'),
2934 subscribe: $subscribe
2935 }
2936 });
2937 this._proxy.sendRequest();
2938 },
2939
2940 /**
2941 * Updates subscription status and icon.
2942 *
2943 * @param object data
2944 */
2945 _updateSubscriptionStatus: function (data) {
2946 var $button = $(this._buttonSelector + '[data-object-id=' + data.objectID + ']');
2947 var $icon = $button.children('.icon');
2948 if (data.isSubscribed) {
2949 $icon.removeClass('fa-bookmark-o').addClass('fa-bookmark');
2950 $button.data('isSubscribed', true);
2951 }
2952 else {
2953 if ($button.data('removeOnUnsubscribe')) {
2954 $button.parent().remove();
2955 }
2956 else {
2957 $icon.removeClass('fa-bookmark').addClass('fa-bookmark-o');
2958 $button.data('isSubscribed', false);
2959 }
2960
2961 if (this._reloadOnUnsubscribe) {
2962 window.location.reload();
2963 return;
2964 }
2965 }
2966
2967 WCF.System.Event.fireEvent('com.woltlab.wcf.objectWatch', 'updatedSubscription', data);
2968 }
2969 });
2970 }
2971 else {
2972 WCF.User.ObjectWatch.Subscribe = Class.extend({
2973 _buttonSelector: "",
2974 _buttons: {},
2975 _dialog: {},
2976 _notification: {},
2977 _reloadOnUnsubscribe: false,
2978 init: function() {},
2979 _click: function() {},
2980 _success: function() {},
2981 _save: function() {},
2982 _updateSubscriptionStatus: function() {}
2983 });
2984 }