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