4 * User-related classes.
6 * @author Alexander Ebert
7 * @copyright 2001-2018 WoltLab GmbH
8 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
14 * @param boolean isQuickLogin
16 WCF
.User
.Login
= Class
.extend({
21 _loginSubmitButton
: null,
30 * password input container
33 _passwordContainer
: null,
42 * cookie input container
45 _useCookiesContainer
: null,
48 * Initializes the user login
50 * @param boolean isQuickLogin
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');
59 var $loginForm
= $('#loginForm');
60 $loginForm
.find('input[name=action]').change($.proxy(this._change
, this));
63 WCF
.User
.QuickLogin
.init();
68 * Handle toggle between login and register.
72 _change: function(event
) {
73 if ($(event
.currentTarget
).val() === 'register') {
74 this._setState(false, WCF
.Language
.get('wcf.user.button.register'));
77 this._setState(true, WCF
.Language
.get('wcf.user.button.login'));
84 * @param boolean enable
85 * @param string buttonTitle
87 _setState: function(enable
, buttonTitle
) {
89 this._password
.enable();
90 this._passwordContainer
.removeClass('disabled');
91 this._useCookies
.enable();
92 this._useCookiesContainer
.removeClass('disabled');
95 this._password
.disable();
96 this._passwordContainer
.addClass('disabled');
97 this._useCookies
.disable();
98 this._useCookiesContainer
.addClass('disabled');
101 this._loginSubmitButton
.val(buttonTitle
);
106 * Namespace for User Panel classes.
108 WCF
.User
.Panel
= { };
110 if (COMPILER_TARGET_DEFAULT
) {
112 * Abstract implementation for user panel items providing an interactive dropdown.
114 * @param jQuery triggerElement
115 * @param string identifier
116 * @param object options
118 WCF
.User
.Panel
.Abstract
= Class
.extend({
126 * interactive dropdown instance
127 * @var WCF.Dropdown.Interactive.Instance
132 * dropdown identifier
138 * true if data should be loaded using an AJAX request
144 * header icon to mark all items belonging to this user panel item as read
147 _markAllAsReadLink
: null,
150 * list of options required to dropdown initialization and UI features
157 * @var WCF.Action.Proxy
165 _triggerElement
: null,
168 * Initializes the WCF.User.Panel.Abstract class.
170 * @param jQuery triggerElement
171 * @param string identifier
172 * @param object options
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
;
181 this._proxy
= new WCF
.Action
.Proxy({
182 showLoadingOverlay
: false,
183 success
: $.proxy(this._success
, this)
186 this._triggerElement
.click($.proxy(this.toggle
, this));
188 if (this._options
.showAllLink
) {
189 this._triggerElement
.dblclick($.proxy(this._dblClick
, this));
192 if (this._options
.staticDropdown
=== true) {
193 this._loadData
= false;
196 var $badge
= this._triggerElement
.find('span.badge');
198 this._badge
= $badge
;
204 * Toggles the interactive dropdown.
206 * @param {Event?} event
209 toggle: function (event
) {
210 if (event
instanceof Event
) {
211 event
.preventDefault();
214 if (this._dropdown
=== null) {
215 this._dropdown
= this._initDropdown();
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;
229 if (this._loadData
) {
230 this._loadData
= false;
239 * Forward to original URL by double clicking the trigger element.
241 * @param object event
244 _dblClick: function (event
) {
245 event
.preventDefault();
247 window
.location
= this._options
.showAllLink
;
253 * Initializes the dropdown on first usage.
255 * @return WCF.Dropdown.Interactive.Instance
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());
265 * Loads item list data via AJAX.
268 // override this in your own implementation to fetch display data
272 * Handles successful AJAX requests.
276 _success: function (data
) {
277 if (data
.returnValues
.template
!== undefined) {
278 var $itemList
= this._dropdown
.getItemList().empty();
279 $(data
.returnValues
.template
).appendTo($itemList
);
281 if (!$itemList
.children().length
) {
282 $('<li class="noItems">' + this._options
.noItems
+ '</li>').appendTo($itemList
);
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();
292 WCF
.System
.Confirmation
.show(this._options
.markAllAsReadConfirmMessage
, (function (action
) {
293 if (action
=== 'confirm') {
294 this._markAllAsRead();
302 $outstandingItems
.each((function (index
, item
) {
303 var $item
= $(item
).addClass('interactiveDropdownItemOutstandingIcon');
304 var $objectID
= $item
.data('objectID');
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
);
315 this._dropdown
.getItemList().children().each(function (index
, item
) {
317 var $link
= $item
.data('link');
320 if ($.browser
.msie
) {
321 $item
.click(function (event
) {
322 if (event
.target
.tagName
!== 'A') {
323 window
.location
= $link
;
330 $item
.addClass('interactiveDropdownItemShadow');
331 $('<a href="' + $link
+ '" class="interactiveDropdownItemShadowLink" />').appendTo($item
);
334 if ($item
.data('linkReplaceAll')) {
335 $item
.find('> .box48 a:not(.userLink)').prop('href', $link
);
340 this._dropdown
.rebuildScrollbar();
343 if (data
.returnValues
.totalCount
!== undefined) {
344 this.updateBadge(data
.returnValues
.totalCount
);
347 if (this._options
.enableMarkAsRead
) {
348 if (data
.returnValues
.markAsRead
) {
349 var $item
= this._dropdown
.getItemList().children('li[data-object-id=' + data
.returnValues
.markAsRead
+ ']');
351 $item
.removeClass('interactiveDropdownItemOutstanding').data('isRead', true);
352 $item
.children('.interactiveDropdownItemMarkAsRead').remove();
355 else if (data
.returnValues
.markAllAsRead
) {
363 * Marks an item as read.
365 * @param object event
366 * @param integer objectID
368 _markAsRead: function (event
, objectID
) {
369 // override this in your own implementation to mark an item as read
373 * Marks all items as read.
375 _markAllAsRead: function () {
376 // override this in your own implementation to mark all items as read
380 * Updates the badge's count or removes it if count reaches zero. Passing a negative number is undefined.
382 * @param integer count
384 updateBadge: function (count
) {
385 count
= parseInt(count
) || 0;
388 if (this._badge
=== null) {
389 this._badge
= $('<span class="badge badgeUpdate" />').appendTo(this._triggerElement
.children('a'));
390 this._badge
.before(' ');
393 this._badge
.text(count
);
395 else if (this._badge
!== null) {
396 this._badge
.remove();
400 if (this._options
.enableMarkAsRead
) {
401 if (!count
&& this._markAllAsReadLink
!== null) {
402 this._markAllAsReadLink
.remove();
403 this._markAllAsReadLink
= null;
407 WCF
.System
.Event
.fireEvent('com.woltlab.wcf.userMenu', 'updateBadge', {
409 identifier
: this._identifier
414 * Resets the dropdown's inner item list.
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;
427 * User Panel implementation for user notifications.
429 * @see WCF.User.Panel.Abstract
431 WCF
.User
.Panel
.Notification
= WCF
.User
.Panel
.Abstract
.extend({
439 * @see WCF.User.Panel.Abstract.init()
441 init: function (options
) {
442 options
.enableMarkAsRead
= true;
444 this._super($('#userNotifications'), 'userNotifications', options
);
447 this._favico
= new Favico({
452 if (this._badge
!== null) {
453 var $count
= parseInt(this._badge
.text()) || 0;
454 this._favico
.badge($count
);
458 console
.debug("[WCF.User.Panel.Notification] Failed to initialized Favico: " + e
.message
);
461 WCF
.System
.PushNotification
.addCallback('userNotificationCount', $.proxy(this.updateUserNotificationCount
, this));
463 require(['EventHandler'], (function (EventHandler
) {
464 EventHandler
.add('com.woltlab.wcf.UserMenuMobile', 'more', (function (data
) {
465 if (data
.identifier
=== 'com.woltlab.wcf.notifications') {
473 * @see WCF.User.Panel.Abstract._initDropdown()
475 _initDropdown: function () {
476 var $dropdown
= this._super();
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());
484 * @see WCF.User.Panel.Abstract._load()
487 this._proxy
.setOption('data', {
488 actionName
: 'getOutstandingNotifications',
489 className
: 'wcf\\data\\user\\notification\\UserNotificationAction'
491 this._proxy
.sendRequest();
495 * @see WCF.User.Panel.Abstract._markAsRead()
497 _markAsRead: function (event
, objectID
) {
498 this._proxy
.setOption('data', {
499 actionName
: 'markAsConfirmed',
500 className
: 'wcf\\data\\user\\notification\\UserNotificationAction',
501 objectIDs
: [objectID
]
503 this._proxy
.sendRequest();
507 * @see WCF.User.Panel.Abstract._markAllAsRead()
509 _markAllAsRead: function (event
) {
510 this._proxy
.setOption('data', {
511 actionName
: 'markAllAsConfirmed',
512 className
: 'wcf\\data\\user\\notification\\UserNotificationAction'
514 this._proxy
.sendRequest();
518 * @see WCF.User.Panel.Abstract.resetItems()
520 resetItems: function () {
523 if (this._markAllAsReadLink
) {
524 this._markAllAsReadLink
.remove();
525 this._markAllAsReadLink
= null;
530 * @see WCF.User.Panel.Abstract.updateBadge()
532 updateBadge: function (count
) {
533 count
= parseInt(count
) || 0;
535 // update data attribute
536 $('#userNotifications').attr('data-count', count
);
538 if (this._favico
!== null) {
539 this._favico
.badge(count
);
546 * Updates the badge counter and resets the dropdown's item list.
548 * @param integer count
550 updateUserNotificationCount: function (count
) {
551 if (this._dropdown
!== null) {
552 this._dropdown
.resetItems();
555 this.updateBadge(count
);
560 * User Panel implementation for user menu dropdown.
562 * @see WCF.User.Panel.Abstract
564 WCF
.User
.Panel
.UserMenu
= WCF
.User
.Panel
.Abstract
.extend({
566 * @see WCF.User.Panel.Abstract.init()
569 this._super($('#userMenu'), 'userMenu', {
570 pointerOffset
: '13px',
577 WCF
.User
.Panel
.Abstract
= Class
.extend({
582 _markAllAsReadLink
: {},
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() {}
598 WCF
.User
.Panel
.Notification
= WCF
.User
.Panel
.Abstract
.extend({
601 _initDropdown: function() {},
602 _load: function() {},
603 _markAsRead: function() {},
604 _markAllAsRead: function() {},
605 resetItems: function() {},
606 updateBadge: function() {},
607 updateUserNotificationCount: function() {},
612 _markAllAsReadLink
: {},
616 toggle: function() {},
617 _dblClick: function() {},
618 _success: function() {}
621 WCF
.User
.Panel
.UserMenu
= WCF
.User
.Panel
.Abstract
.extend({
627 _markAllAsReadLink
: {},
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() {}
646 WCF
.User
.QuickLogin
= {
648 * Initializes the quick login box
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');
658 var registrationBlock
= elBySel('.loginFormRegister', loginForm
);
660 var callbackOpen = function(event
) {
661 if (event
instanceof Event
) {
662 event
.preventDefault();
663 event
.stopPropagation();
666 loginForm
.style
.removeProperty('display');
668 UiDialog
.openStatic('loginForm', null, {
669 title
: WCF
.Language
.get('wcf.user.login')
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
;
679 if (loginForm
.clientWidth
> loginSection
.clientWidth
* 2) {
680 while (loginOffset
< (registrationBlock
.offsetTop
- 50)) {
681 // push the registration down by 100 pixel each time
683 loginSection
.style
.setProperty('margin-bottom', margin
+ 'px', '');
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
);
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'));
699 EventHandler
.add('com.woltlab.wcf.UserMenuMobile', 'more', function(data
) {
700 if (data
.identifier
=== 'com.woltlab.wcf.login') {
701 data
.handler
.close(true);
711 * UserProfile namespace
713 WCF
.User
.Profile
= {};
716 * Shows the activity point list for users.
718 WCF
.User
.Profile
.ActivityPointList
= {
720 * list of cached templates
732 * initialization state
739 * @var WCF.Action.Proxy
744 * Initializes the WCF.User.Profile.ActivityPointList class.
753 this._proxy
= new WCF
.Action
.Proxy({
754 success
: $.proxy(this._success
, this)
759 WCF
.DOMNodeInsertedHandler
.addCallback('WCF.User.Profile.ActivityPointList', $.proxy(this._init
, this));
761 this._didInit
= true;
765 * Initializes display for activity points.
768 $('.activityPointsDisplay').removeClass('activityPointsDisplay').click($.proxy(this._click
, this));
772 * Shows or loads the activity point for selected user.
774 * @param object event
776 _click: function(event
) {
777 event
.preventDefault();
778 var $userID
= $(event
.currentTarget
).data('userID');
780 if (this._cache
[$userID
] === undefined) {
781 this._proxy
.setOption('data', {
782 actionName
: 'getDetailedActivityPointList',
783 className
: 'wcf\\data\\user\\UserProfileAction',
784 objectIDs
: [ $userID
]
786 this._proxy
.sendRequest();
794 * Displays activity points for given user.
796 * @param integer userID
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')
806 this._dialog
.html(this._cache
[userID
]);
807 this._dialog
.wcfDialog('open');
812 * Handles successful AJAX requests.
815 * @param string textStatus
816 * @param jQuery jqXHR
818 _success: function(data
, textStatus
, jqXHR
) {
819 this._cache
[data
.returnValues
.userID
] = data
.returnValues
.template
;
820 this._show(data
.returnValues
.userID
);
825 * Provides methods to load tab menu content upon request.
827 WCF
.User
.Profile
.TabMenu
= Class
.extend({
838 _profileContent
: null,
842 * @var WCF.Action.Proxy
853 * Initializes the tab menu loader.
855 * @param integer userID
857 init: function(userID
) {
858 this._profileContent
= $('#profileContent');
859 this._userID
= userID
;
861 var $activeMenuItem
= this._profileContent
.data('active');
862 var $enableProxy
= false;
864 // fetch content state
865 this._profileContent
.find('div.tabMenuContent').each($.proxy(function(index
, container
) {
866 var $containerID
= $(container
).wcfIdentify();
868 if ($activeMenuItem
=== $containerID
) {
869 this._hasContent
[$containerID
] = true;
872 this._hasContent
[$containerID
] = false;
877 // enable loader if at least one container is empty
879 this._proxy
= new WCF
.Action
.Proxy({
880 success
: $.proxy(this._success
, this)
883 this._profileContent
.on('wcftabsbeforeactivate', $.proxy(this._loadContent
, this));
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')) {
890 this._loadContent(null, {
891 newPanel
: $('#' + $listItem
.attr('aria-controls'))
900 $('.userProfileUser .contentDescription a[href$="#likes"]').click((function (event
) {
901 event
.preventDefault();
903 require(['Ui/TabMenu'], function (UiTabMenu
) {
904 UiTabMenu
.getTabMenu('profileContent').select('likes');
910 * Prepares to load content once tabs are being switched.
912 * @param object event
915 _loadContent: function(event
, ui
) {
916 var $panel
= $(ui
.newPanel
);
917 var $containerID
= $panel
.attr('id');
919 if (!this._hasContent
[$containerID
]) {
920 this._proxy
.setOption('data', {
921 actionName
: 'getContent',
922 className
: 'wcf\\data\\user\\profile\\menu\\item\\UserProfileMenuItemAction',
925 containerID
: $containerID
,
926 menuItem
: $panel
.data('menuItem'),
931 this._proxy
.sendRequest();
936 * Shows previously requested content.
939 * @param string textStatus
940 * @param jQuery jqXHR
942 _success: function(data
, textStatus
, jqXHR
) {
943 var $containerID
= data
.returnValues
.containerID
;
944 this._hasContent
[$containerID
] = true;
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');
951 DomChangeListener
.trigger();
956 if (COMPILER_TARGET_DEFAULT
) {
958 * User profile inline editor.
960 * @param integer userID
961 * @param boolean editOnInit
963 WCF
.User
.Profile
.Editor
= Class
.extend({
973 * list of interface buttons
986 * @var WCF.Action.Proxy
1003 * Initializes the WCF.User.Profile.Editor object.
1005 * @param integer userID
1006 * @param boolean editOnInit
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)
1018 this._initButtons();
1020 // begin editing on page load
1027 * Initializes interface buttons.
1029 _initButtons: function () {
1032 beginEdit
: $('.jsButtonEditProfile:eq(0)').click(this._beginEdit
.bind(this))
1039 * @param {Event?} event event object
1041 _beginEdit: function (event
) {
1042 if (event
) event
.preventDefault();
1044 if (this._active
) return;
1045 this._active
= true;
1047 this._actionName
= 'beginEdit';
1048 this._buttons
.beginEdit
.parent().addClass('active');
1049 $('#profileContent').wcfTabs('select', 'about');
1052 this._proxy
.setOption('data', {
1053 actionName
: 'beginEdit',
1054 className
: 'wcf\\data\\user\\UserProfileAction',
1055 objectIDs
: [this._userID
]
1057 this._proxy
.sendRequest();
1061 * Saves input values.
1063 _save: function () {
1064 this._actionName
= 'save';
1067 var $regExp
= /values\[([a-zA-Z0-9._-]+)\]/;
1069 this._tab
.find('input, textarea, select').each(function (index
, element
) {
1070 var $element
= $(element
);
1073 switch ($element
.getTagName()) {
1075 var $type
= $element
.attr('type');
1077 if (($type
=== 'radio' || $type
=== 'checkbox') && !$element
.prop('checked')) {
1083 if ($element
.data('redactor')) {
1084 $value
= $element
.redactor('code.get');
1089 var $name
= $element
.attr('name');
1090 if ($regExp
.test($name
)) {
1091 var $fieldName
= RegExp
.$1;
1092 if ($value
=== null) $value
= $element
.val();
1094 // check for checkboxes
1095 if ($element
.attr('type') === 'checkbox' && /\[\]$/.test($name
)) {
1096 if (!Array
.isArray($values
[$fieldName
])) {
1097 $values
[$fieldName
] = [];
1100 $values
[$fieldName
].push($value
);
1103 $values
[$fieldName
] = $value
;
1108 this._proxy
.setOption('data', {
1110 className
: 'wcf\\data\\user\\UserProfileAction',
1111 objectIDs
: [this._userID
],
1116 this._proxy
.sendRequest();
1120 * Restores back to default view.
1122 _restore: function () {
1123 this._actionName
= 'restore';
1124 this._active
= false;
1125 this._buttons
.beginEdit
.parent().removeClass('active');
1127 this._destroyEditor();
1129 this._tab
.html(this._cachedTemplate
).children().css({height
: 'auto'});
1133 * Handles successful AJAX requests.
1135 * @param object data
1136 * @param string textStatus
1137 * @param jQuery jqXHR
1139 _success: function (data
, textStatus
, jqXHR
) {
1140 switch (this._actionName
) {
1142 this._prepareEdit(data
);
1146 // save was successful, show parsed template
1147 if (data
.returnValues
.success
) {
1148 this._cachedTemplate
= data
.returnValues
.template
;
1152 this._prepareEdit(data
, true);
1159 * Prepares editing mode.
1161 * @param object data
1162 * @param boolean disableCache
1164 _prepareEdit: function (data
, disableCache
) {
1165 this._destroyEditor();
1169 this._tab
.html(function (index
, oldHTML
) {
1170 if (disableCache
!== true) {
1171 self
._cachedTemplate
= oldHTML
;
1174 return data
.returnValues
.template
;
1177 // block autocomplete
1178 this._tab
.find('input[type=text]').attr('autocomplete', 'off');
1180 // bind event listener
1181 this._tab
.find('.formSubmit > button[data-type=save]').click($.proxy(this._save
, this));
1182 this._tab
.find('.formSubmit > button[data-type=restore]').click($.proxy(this._restore
, this));
1183 this._tab
.find('input').keyup(function (event
) {
1184 if (event
.which
=== $.ui
.keyCode
.ENTER
) {
1187 event
.preventDefault();
1194 * Destroys all editor instances within current tab.
1196 _destroyEditor: function () {
1197 // destroy all editor instances
1198 this._tab
.find('textarea').each(function (index
, container
) {
1199 var $container
= $(container
);
1200 if ($container
.data('redactor')) {
1201 $container
.redactor('core.destroy');
1208 WCF
.User
.Profile
.Editor
= Class
.extend({
1212 _cachedTemplate
: "",
1216 init: function() {},
1217 _initButtons: function() {},
1218 _beginEdit: function() {},
1219 _save: function() {},
1220 _restore: function() {},
1221 _success: function() {},
1222 _prepareEdit: function() {},
1223 _destroyEditor: function() {}
1228 * Namespace for registration functions.
1230 WCF
.User
.Registration
= {};
1233 * Validates the password.
1235 * @param jQuery element
1236 * @param jQuery confirmElement
1237 * @param object options
1239 WCF
.User
.Registration
.Validation
= Class
.extend({
1253 * confirmation input element
1256 _confirmElement
: null,
1265 * list of error messages
1268 _errorMessages
: { },
1271 * list of additional options
1278 * @var WCF.Action.Proxy
1283 * Initializes the validation.
1285 * @param jQuery element
1286 * @param jQuery confirmElement
1287 * @param object options
1289 init: function(element
, confirmElement
, options
) {
1290 this._element
= element
;
1291 this._element
.blur($.proxy(this._blur
, this));
1292 this._confirmElement
= confirmElement
|| null;
1294 if (this._confirmElement
!== null) {
1295 this._confirmElement
.blur($.proxy(this._blurConfirm
, this));
1298 options
= options
|| { };
1299 this._setOptions(options
);
1301 this._proxy
= new WCF
.Action
.Proxy({
1302 success
: $.proxy(this._success
, this),
1303 showLoadingOverlay
: false
1306 this._setErrorMessages();
1310 * Sets additional options
1312 _setOptions: function(options
) { },
1315 * Sets error messages.
1317 _setErrorMessages: function() {
1318 this._errorMessages
= {
1325 * Validates once focus on input is lost.
1327 * @param object event
1329 _blur: function(event
) {
1330 var $value
= this._element
.val();
1332 return this._showError(this._element
, WCF
.Language
.get('wcf.global.form.error.empty'));
1335 if (this._confirmElement
!== null) {
1336 var $confirmValue
= this._confirmElement
.val();
1337 if ($confirmValue
!= '' && $value
!= $confirmValue
) {
1338 return this._showError(this._confirmElement
, this._errorMessages
.notEqual
);
1342 if (!this._validateOptions()) {
1346 this._proxy
.setOption('data', {
1347 actionName
: this._actionName
,
1348 className
: this._className
,
1349 parameters
: this._getParameters()
1351 this._proxy
.sendRequest();
1355 * Returns a list of parameters.
1359 _getParameters: function() {
1364 * Validates input by options.
1368 _validateOptions: function() {
1373 * Validates value once confirmation input focus is lost.
1375 * @param object event
1377 _blurConfirm: function(event
) {
1378 var $value
= this._confirmElement
.val();
1380 return this._showError(this._confirmElement
, WCF
.Language
.get('wcf.global.form.error.empty'));
1387 * Handles AJAX responses.
1389 * @param object data
1390 * @param string textStatus
1391 * @param jQuery jqXHR
1393 _success: function(data
, textStatus
, jqXHR
) {
1394 if (data
.returnValues
.isValid
) {
1395 this._showSuccess(this._element
);
1396 if (this._confirmElement
!== null && this._confirmElement
.val()) {
1397 this._showSuccess(this._confirmElement
);
1401 this._showError(this._element
, WCF
.Language
.get(this._errorMessages
.ajaxError
+ data
.returnValues
.error
));
1406 * Shows an error message.
1408 * @param jQuery element
1409 * @param string message
1411 _showError: function(element
, message
) {
1412 element
.parent().parent().addClass('formError').removeClass('formSuccess');
1414 var $innerError
= element
.parent().find('small.innerError');
1415 if (!$innerError
.length
) {
1416 $innerError
= $('<small />').addClass('innerError').insertAfter(element
);
1419 $innerError
.text(message
);
1423 * Displays a success message.
1425 * @param jQuery element
1427 _showSuccess: function(element
) {
1428 element
.parent().parent().addClass('formSuccess').removeClass('formError');
1429 element
.next('small.innerError').remove();
1434 * Username validation for registration.
1436 * @see WCF.User.Registration.Validation
1438 WCF
.User
.Registration
.Validation
.Username
= WCF
.User
.Registration
.Validation
.extend({
1440 * @see WCF.User.Registration.Validation._actionName
1442 _actionName
: 'validateUsername',
1445 * @see WCF.User.Registration.Validation._className
1447 _className
: 'wcf\\data\\user\\UserRegistrationAction',
1450 * @see WCF.User.Registration.Validation._setOptions()
1452 _setOptions: function(options
) {
1453 this._options
= $.extend(true, {
1460 * @see WCF.User.Registration.Validation._setErrorMessages()
1462 _setErrorMessages: function() {
1463 this._errorMessages
= {
1464 ajaxError
: 'wcf.user.username.error.'
1469 * @see WCF.User.Registration.Validation._validateOptions()
1471 _validateOptions: function() {
1472 var $value
= this._element
.val();
1473 if ($value
.length
< this._options
.minlength
|| $value
.length
> this._options
.maxlength
) {
1474 this._showError(this._element
, WCF
.Language
.get('wcf.user.username.error.invalid'));
1482 * @see WCF.User.Registration.Validation._getParameters()
1484 _getParameters: function() {
1486 username
: this._element
.val()
1492 * Email validation for registration.
1494 * @see WCF.User.Registration.Validation
1496 WCF
.User
.Registration
.Validation
.EmailAddress
= WCF
.User
.Registration
.Validation
.extend({
1498 * @see WCF.User.Registration.Validation._actionName
1500 _actionName
: 'validateEmailAddress',
1503 * @see WCF.User.Registration.Validation._className
1505 _className
: 'wcf\\data\\user\\UserRegistrationAction',
1508 * @see WCF.User.Registration.Validation._getParameters()
1510 _getParameters: function() {
1512 email
: this._element
.val()
1517 * @see WCF.User.Registration.Validation._setErrorMessages()
1519 _setErrorMessages: function() {
1520 this._errorMessages
= {
1521 ajaxError
: 'wcf.user.email.error.',
1522 notEqual
: WCF
.Language
.get('wcf.user.confirmEmail.error.notEqual')
1528 * Password validation for registration.
1530 * @see WCF.User.Registration.Validation
1532 WCF
.User
.Registration
.Validation
.Password
= WCF
.User
.Registration
.Validation
.extend({
1534 * @see WCF.User.Registration.Validation._actionName
1536 _actionName
: 'validatePassword',
1539 * @see WCF.User.Registration.Validation._className
1541 _className
: 'wcf\\data\\user\\UserRegistrationAction',
1544 * @see WCF.User.Registration.Validation._getParameters()
1546 _getParameters: function() {
1548 password
: this._element
.val()
1553 * @see WCF.User.Registration.Validation._setErrorMessages()
1555 _setErrorMessages: function() {
1556 this._errorMessages
= {
1557 ajaxError
: 'wcf.user.password.error.',
1558 notEqual
: WCF
.Language
.get('wcf.user.confirmPassword.error.notEqual')
1564 * Toggles input fields for lost password form.
1566 WCF
.User
.Registration
.LostPassword
= Class
.extend({
1580 * Initializes LostPassword-form class.
1583 // bind input fields
1584 this._email
= $('#emailInput');
1585 this._username
= $('#usernameInput');
1587 // bind event listener
1588 this._email
.keyup($.proxy(this._checkEmail
, this));
1589 this._username
.keyup($.proxy(this._checkUsername
, this));
1591 if ($.browser
.mozilla
&& $.browser
.touch
) {
1592 this._email
.on('input', $.proxy(this._checkEmail
, this));
1593 this._username
.on('input', $.proxy(this._checkUsername
, this));
1596 // toggle fields on init
1598 this._checkUsername();
1602 * Checks for content in email field and toggles username.
1604 _checkEmail: function() {
1605 if (this._email
.val() == '') {
1606 this._username
.enable();
1607 this._username
.parents('dl:eq(0)').removeClass('disabled');
1610 this._username
.disable();
1611 this._username
.parents('dl:eq(0)').addClass('disabled');
1612 this._username
.val('');
1617 * Checks for content in username field and toggles email.
1619 _checkUsername: function() {
1620 if (this._username
.val() == '') {
1621 this._email
.enable();
1622 this._email
.parents('dl:eq(0)').removeClass('disabled');
1625 this._email
.disable();
1626 this._email
.parents('dl:eq(0)').addClass('disabled');
1627 this._email
.val('');
1633 * Notification system for WCF.
1635 * @author Alexander Ebert
1636 * @copyright 2001-2018 WoltLab GmbH
1637 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
1639 WCF
.Notification
= { };
1641 if (COMPILER_TARGET_DEFAULT
) {
1643 * Handles the notification list.
1645 WCF
.Notification
.List
= Class
.extend({
1648 * @var WCF.Action.Proxy
1653 * Initializes the WCF.Notification.List object.
1656 this._proxy
= new WCF
.Action
.Proxy({
1657 success
: $.proxy(this._success
, this)
1660 // handle 'mark all as confirmed' buttons
1661 $('.contentHeaderNavigation .jsMarkAllAsConfirmed').click(function () {
1662 WCF
.System
.Confirmation
.show(WCF
.Language
.get('wcf.user.notification.markAllAsConfirmed.confirmMessage'), function (action
) {
1663 if (action
=== 'confirm') {
1664 new WCF
.Action
.Proxy({
1667 actionName
: 'markAllAsConfirmed',
1668 className
: 'wcf\\data\\user\\notification\\UserNotificationAction'
1670 success: function () {
1671 window
.location
.reload();
1678 // handle regular items
1679 this._convertList();
1683 * Converts the notification item list to be in sync with the notification dropdown.
1685 _convertList: function () {
1686 $('.userNotificationItemList > .notificationItem').each((function (index
, item
) {
1687 var $item
= $(item
);
1689 if (!$item
.data('isRead')) {
1690 $item
.find('a:not(.userLink)').prop('href', $item
.data('link'));
1692 var $markAsConfirmed
= $('<a href="#" class="icon icon24 fa-check notificationItemMarkAsConfirmed jsTooltip" title="' + WCF
.Language
.get('wcf.user.notification.markAsConfirmed') + '" />').appendTo($item
);
1693 $markAsConfirmed
.click($.proxy(this._markAsConfirmed
, this));
1696 // work-around for legacy notifications
1697 if (!$item
.find('a:not(.notificationItemMarkAsConfirmed)').length
) {
1698 $item
.find('.details > p:eq(0)').html(function (index
, oldHTML
) {
1699 return '<a href="' + $item
.data('link') + '">' + oldHTML
+ '</a>';
1704 WCF
.DOMNodeInsertedHandler
.execute();
1708 * Marks a single notification as confirmed.
1710 * @param object event
1712 _markAsConfirmed: function (event
) {
1713 event
.preventDefault();
1715 var $notificationID
= $(event
.currentTarget
).parents('.notificationItem:eq(0)').data('objectID');
1717 this._proxy
.setOption('data', {
1718 actionName
: 'markAsConfirmed',
1719 className
: 'wcf\\data\\user\\notification\\UserNotificationAction',
1720 objectIDs
: [$notificationID
]
1722 this._proxy
.sendRequest();
1728 * Handles successful AJAX requests.
1730 * @param object data
1731 * @param string textStatus
1732 * @param jQuery jqXHR
1734 _success: function (data
, textStatus
, jqXHR
) {
1735 var $item
= $('.userNotificationItemList > .notificationItem[data-object-id=' + data
.returnValues
.markAsRead
+ ']');
1737 $item
.data('isRead', true);
1738 $item
.find('.newContentBadge').remove();
1739 $item
.find('.notificationItemMarkAsConfirmed').remove();
1740 $item
.removeClass('notificationUnconfirmed');
1745 * Signature preview.
1747 * @see WCF.Message.Preview
1749 WCF
.User
.SignaturePreview
= WCF
.Message
.Preview
.extend({
1751 * @see WCF.Message.Preview._handleResponse()
1753 _handleResponse: function (data
) {
1754 // get preview container
1755 var $preview
= $('#previewContainer');
1756 if (!$preview
.length
) {
1757 $preview
= $('<section class="section" id="previewContainer"><h2 class="sectionTitle">' + WCF
.Language
.get('wcf.global.preview') + '</h2><div class="htmlContent"></div></section>').insertBefore($('#signatureContainer')).wcfFadeIn();
1760 $preview
.children('div').first().html(data
.returnValues
.message
);
1765 WCF
.Notification
.List
= Class
.extend({
1767 init: function() {},
1768 _convertList: function() {},
1769 _markAsConfirmed: function() {},
1770 _success: function() {}
1773 WCF
.User
.SignaturePreview
= WCF
.Message
.Preview
.extend({
1774 _handleResponse: function() {},
1776 _messageFieldID
: "",
1780 _previewButtonLabel
: "",
1781 init: function() {},
1782 _click: function() {},
1783 _getParameters: function() {},
1784 _getMessage: function() {},
1785 _success: function() {},
1786 _failure: function() {}
1791 * Loads recent activity events once the user scrolls to the very bottom.
1793 * @param integer userID
1795 WCF
.User
.RecentActivityLoader
= Class
.extend({
1803 * true if list should be filtered by followed users
1806 _filteredByFollowedUsers
: false,
1809 * button to load next events
1816 * @var WCF.Action.Proxy
1827 * Initializes a new RecentActivityLoader object.
1829 * @param integer userID
1830 * @param boolean filteredByFollowedUsers
1832 init: function(userID
, filteredByFollowedUsers
) {
1833 this._container
= $('#recentActivities');
1834 this._filteredByFollowedUsers
= (filteredByFollowedUsers
=== true);
1835 this._userID
= userID
;
1837 if (this._userID
!== null && !this._userID
) {
1838 console
.debug("[WCF.User.RecentActivityLoader] Invalid parameter 'userID' given.");
1842 this._proxy
= new WCF
.Action
.Proxy({
1843 success
: $.proxy(this._success
, this)
1846 if (this._container
.children('li').length
) {
1847 this._loadButton
= $('<li class="showMore"><button class="small">' + WCF
.Language
.get('wcf.user.recentActivity.more') + '</button></li>').appendTo(this._container
);
1848 this._loadButton
= this._loadButton
.children('button').click($.proxy(this._click
, this));
1851 $('<li class="showMore"><small>' + WCF
.Language
.get('wcf.user.recentActivity.noMoreEntries') + '</small></li>').appendTo(this._container
);
1854 if (WCF
.User
.userID
) {
1855 $('.jsRecentActivitySwitchContext .button').click($.proxy(this._switchContext
, this));
1860 * Loads next activity events.
1862 _click: function() {
1863 this._loadButton
.enable();
1866 lastEventID
: this._container
.data('lastEventID'),
1867 lastEventTime
: this._container
.data('lastEventTime')
1870 $parameters
.userID
= this._userID
;
1872 else if (this._filteredByFollowedUsers
) {
1873 $parameters
.filteredByFollowedUsers
= 1;
1876 this._proxy
.setOption('data', {
1878 className
: 'wcf\\data\\user\\activity\\event\\UserActivityEventAction',
1879 parameters
: $parameters
1881 this._proxy
.sendRequest();
1885 * Switches recent activity context.
1887 _switchContext: function(event
) {
1888 event
.preventDefault();
1890 if (!$(event
.currentTarget
).hasClass('active')) {
1891 new WCF
.Action
.Proxy({
1894 actionName
: 'switchContext',
1895 className
: 'wcf\\data\\user\\activity\\event\\UserActivityEventAction'
1897 success: function() {
1898 window
.location
.hash
= '#dashboardBoxRecentActivity';
1899 window
.location
.reload();
1906 * Handles successful AJAX requests.
1908 * @param object data
1909 * @param string textStatus
1910 * @param jQuery jqXHR
1912 _success: function(data
, textStatus
, jqXHR
) {
1913 if (data
.returnValues
.template
) {
1914 $(data
.returnValues
.template
).insertBefore(this._loadButton
.parent());
1916 this._container
.data('lastEventTime', data
.returnValues
.lastEventTime
);
1917 this._container
.data('lastEventID', data
.returnValues
.lastEventID
);
1918 this._loadButton
.enable();
1921 $('<small>' + WCF
.Language
.get('wcf.user.recentActivity.noMoreEntries') + '</small>').appendTo(this._loadButton
.parent());
1922 this._loadButton
.remove();
1928 * Loads likes once the user scrolls to the very bottom.
1930 * @param integer userID
1932 WCF
.User
.LikeLoader
= Class
.extend({
1943 _likeType
: 'received',
1952 * button to load next events
1958 * 'no more entries' element
1961 _noMoreEntries
: null,
1965 * @var WCF.Action.Proxy
1976 * Initializes a new RecentActivityLoader object.
1978 * @param integer userID
1979 * @param boolean filteredByFollowedUsers
1981 init: function(userID
) {
1982 this._container
= $('#likeList');
1983 this._userID
= userID
;
1985 if (!this._userID
) {
1986 console
.debug("[WCF.User.RecentActivityLoader] Invalid parameter 'userID' given.");
1990 this._proxy
= new WCF
.Action
.Proxy({
1991 success
: $.proxy(this._success
, this)
1994 var $container
= $('<li class="likeListMore showMore"><button class="small">' + WCF
.Language
.get('wcf.like.likes.more') + '</button><small>' + WCF
.Language
.get('wcf.like.likes.noMoreEntries') + '</small></li>').appendTo(this._container
);
1995 this._loadButton
= $container
.children('button').click($.proxy(this._click
, this));
1996 this._noMoreEntries
= $container
.children('small').hide();
1998 if (this._container
.find('> li').length
== 2) {
1999 this._loadButton
.hide();
2000 this._noMoreEntries
.show();
2003 $('#likeType .button').click($.proxy(this._clickLikeType
, this));
2004 $('#likeValue .button').click($.proxy(this._clickLikeValue
, this));
2008 * Handles like type change.
2010 _clickLikeType: function(event
) {
2011 var $button
= $(event
.currentTarget
);
2012 if (this._likeType
!= $button
.data('likeType')) {
2013 this._likeType
= $button
.data('likeType');
2014 $('#likeType .button').removeClass('active');
2015 $button
.addClass('active');
2021 * Handles like value change.
2023 _clickLikeValue: function(event
) {
2024 var $button
= $(event
.currentTarget
);
2025 if (this._likeValue
!= $button
.data('likeValue')) {
2026 this._likeValue
= $button
.data('likeValue');
2027 $('#likeValue .button').removeClass('active');
2028 $button
.addClass('active');
2030 // change button labels
2031 $('#likeType > li:first-child > .button').text(WCF
.Language
.get('wcf.like.' + (this._likeValue
== -1 ? 'dis' : '') + 'likesReceived'));
2032 $('#likeType > li:last-child > .button').text(WCF
.Language
.get('wcf.like.' + (this._likeValue
== -1 ? 'dis' : '') + 'likesGiven'));
2034 this._container
.find('> li.likeListMore button').text(WCF
.Language
.get('wcf.like.' + (this._likeValue
== -1 ? 'dis' : '') + 'likes.more'));
2035 this._container
.find('> li.likeListMore small').text(WCF
.Language
.get('wcf.like.' + (this._likeValue
== -1 ? 'dis' : '') + 'likes.noMoreEntries'));
2044 _reload: function() {
2045 this._container
.find('> li:not(:first-child):not(:last-child)').remove();
2046 this._container
.data('lastLikeTime', 0);
2053 _click: function() {
2054 this._loadButton
.enable();
2057 lastLikeTime
: this._container
.data('lastLikeTime'),
2058 userID
: this._userID
,
2059 likeType
: this._likeType
,
2060 likeValue
: this._likeValue
2063 this._proxy
.setOption('data', {
2065 className
: 'wcf\\data\\like\\LikeAction',
2066 parameters
: $parameters
2068 this._proxy
.sendRequest();
2072 * Handles successful AJAX requests.
2074 * @param object data
2075 * @param string textStatus
2076 * @param jQuery jqXHR
2078 _success: function(data
, textStatus
, jqXHR
) {
2079 if (data
.returnValues
.template
) {
2080 $(data
.returnValues
.template
).insertBefore(this._loadButton
.parent());
2082 this._container
.data('lastLikeTime', data
.returnValues
.lastLikeTime
);
2083 this._noMoreEntries
.hide();
2084 this._loadButton
.show().enable();
2087 this._noMoreEntries
.show();
2088 this._loadButton
.hide();
2094 * Loads user profile previews.
2098 WCF
.User
.ProfilePreview
= WCF
.Popover
.extend({
2101 * @var WCF.Action.Proxy
2106 * list of user profiles
2112 * @see WCF.Popover.init()
2115 this._super('.userLink');
2117 this._proxy
= new WCF
.Action
.Proxy({
2118 showLoadingOverlay
: false
2121 // register instance
2122 WCF
.System
.ObjectStore
.add('WCF.User.ProfilePreview', this);
2126 * @see WCF.Popover._loadContent()
2128 _loadContent: function() {
2129 var $element
= $('#' + this._activeElementID
);
2130 var $userID
= $element
.data('userID');
2132 if (this._userProfiles
[$userID
]) {
2133 // use cached user profile
2134 this._insertContent(this._activeElementID
, this._userProfiles
[$userID
], true);
2137 this._proxy
.setOption('data', {
2138 actionName
: 'getUserProfile',
2139 className
: 'wcf\\data\\user\\UserProfileAction',
2140 objectIDs
: [ $userID
]
2143 var $elementID
= this._activeElementID
;
2145 this._proxy
.setOption('success', function(data
, textStatus
, jqXHR
) {
2146 // cache user profile
2147 self
._userProfiles
[$userID
] = data
.returnValues
.template
;
2149 // show user profile
2150 self
._insertContent($elementID
, data
.returnValues
.template
, true);
2152 this._proxy
.setOption('failure', function(data
, jqXHR
, textStatus
, errorThrown
) {
2153 // cache user profile
2154 self
._userProfiles
[$userID
] = data
.message
;
2156 // show user profile
2157 self
._insertContent($elementID
, data
.message
, true);
2161 this._proxy
.sendRequest();
2166 * Purages a cached user profile.
2168 * @param integer userID
2170 purge: function(userID
) {
2171 delete this._userProfiles
[userID
];
2173 // purge content cache
2179 * Initializes WCF.User.Action namespace.
2181 WCF
.User
.Action
= {};
2183 if (COMPILER_TARGET_DEFAULT
) {
2185 * Handles user follow and unfollow links.
2187 WCF
.User
.Action
.Follow
= Class
.extend({
2189 * list with elements containing follow and unfollow buttons
2192 _containerList
: null,
2195 * CSS selector for follow buttons
2198 _followButtonSelector
: '.jsFollowButton',
2201 * id of the user that is currently being followed/unfollowed
2207 * Initializes new WCF.User.Action.Follow object.
2209 * @param array containerList
2210 * @param string followButtonSelector
2212 init: function (containerList
, followButtonSelector
) {
2213 if (!containerList
.length
) {
2216 this._containerList
= containerList
;
2218 if (followButtonSelector
) {
2219 this._followButtonSelector
= followButtonSelector
;
2223 this._proxy
= new WCF
.Action
.Proxy({
2224 success
: $.proxy(this._success
, this)
2227 // bind event listeners
2228 this._containerList
.each($.proxy(function (index
, container
) {
2229 $(container
).find(this._followButtonSelector
).click($.proxy(this._click
, this));
2234 * Handles a click on a follow or unfollow button.
2236 * @param object event
2238 _click: function (event
) {
2239 event
.preventDefault();
2240 var link
= $(event
.target
);
2241 if (!link
.is('a')) {
2242 link
= link
.closest('a');
2244 this._userID
= link
.data('objectID');
2246 this._proxy
.setOption('data', {
2247 'actionName': link
.data('following') ? 'unfollow' : 'follow',
2248 'className': 'wcf\\data\\user\\follow\\UserFollowAction',
2251 userID
: this._userID
2255 this._proxy
.sendRequest();
2259 * Handles the successful (un)following of a user.
2261 * @param object data
2262 * @param string textStatus
2263 * @param jQuery jqXHR
2265 _success: function (data
, textStatus
, jqXHR
) {
2266 this._containerList
.each($.proxy(function (index
, container
) {
2267 var button
= $(container
).find(this._followButtonSelector
).get(0);
2269 if (button
&& $(button
).data('objectID') == this._userID
) {
2272 // toogle icon title
2273 if (data
.returnValues
.following
) {
2274 button
.attr('data-tooltip', WCF
.Language
.get('wcf.user.button.unfollow')).children('.icon').removeClass('fa-plus').addClass('fa-minus');
2275 button
.children('.invisible').text(WCF
.Language
.get('wcf.user.button.unfollow'));
2278 button
.attr('data-tooltip', WCF
.Language
.get('wcf.user.button.follow')).children('.icon').removeClass('fa-minus').addClass('fa-plus');
2279 button
.children('.invisible').text(WCF
.Language
.get('wcf.user.button.follow'));
2282 button
.data('following', data
.returnValues
.following
);
2288 var $notification
= new WCF
.System
.Notification();
2289 $notification
.show();
2294 * Handles user ignore and unignore links.
2296 WCF
.User
.Action
.Ignore
= Class
.extend({
2298 * list with elements containing ignore and unignore buttons
2301 _containerList
: null,
2304 * CSS selector for ignore buttons
2307 _ignoreButtonSelector
: '.jsIgnoreButton',
2310 * id of the user that is currently being ignored/unignored
2316 * Initializes new WCF.User.Action.Ignore object.
2318 * @param array containerList
2319 * @param string ignoreButtonSelector
2321 init: function (containerList
, ignoreButtonSelector
) {
2322 if (!containerList
.length
) {
2325 this._containerList
= containerList
;
2327 if (ignoreButtonSelector
) {
2328 this._ignoreButtonSelector
= ignoreButtonSelector
;
2332 this._proxy
= new WCF
.Action
.Proxy({
2333 success
: $.proxy(this._success
, this)
2336 // bind event listeners
2337 this._containerList
.each($.proxy(function (index
, container
) {
2338 $(container
).find(this._ignoreButtonSelector
).click($.proxy(this._click
, this));
2343 * Handles a click on a ignore or unignore button.
2345 * @param object event
2347 _click: function (event
) {
2348 event
.preventDefault();
2349 var link
= $(event
.target
);
2350 if (!link
.is('a')) {
2351 link
= link
.closest('a');
2353 this._userID
= link
.data('objectID');
2355 this._proxy
.setOption('data', {
2356 'actionName': link
.data('ignored') ? 'unignore' : 'ignore',
2357 'className': 'wcf\\data\\user\\ignore\\UserIgnoreAction',
2360 userID
: this._userID
2364 this._proxy
.sendRequest();
2368 * Handles the successful (un)ignoring of a user.
2370 * @param object data
2371 * @param string textStatus
2372 * @param jQuery jqXHR
2374 _success: function (data
, textStatus
, jqXHR
) {
2375 this._containerList
.each($.proxy(function (index
, container
) {
2376 var button
= $(container
).find(this._ignoreButtonSelector
).get(0);
2378 if (button
&& $(button
).data('objectID') == this._userID
) {
2381 // toogle icon title
2382 if (data
.returnValues
.isIgnoredUser
) {
2383 button
.attr('data-tooltip', WCF
.Language
.get('wcf.user.button.unignore')).children('.icon').removeClass('fa-ban').addClass('fa-circle-o');
2384 button
.children('.invisible').text(WCF
.Language
.get('wcf.user.button.unignore'));
2387 button
.attr('data-tooltip', WCF
.Language
.get('wcf.user.button.ignore')).children('.icon').removeClass('fa-circle-o').addClass('fa-ban');
2388 button
.children('.invisible').text(WCF
.Language
.get('wcf.user.button.ignore'));
2391 button
.data('ignored', data
.returnValues
.isIgnoredUser
);
2397 var $notification
= new WCF
.System
.Notification();
2398 $notification
.show();
2400 // force rebuilding of popover cache
2402 WCF
.System
.ObjectStore
.invoke('WCF.User.ProfilePreview', function (profilePreview
) {
2403 profilePreview
.purge(self
._userID
);
2409 WCF
.User
.Action
.Follow
= Class
.extend({
2411 _followButtonSelector
: "",
2413 init: function() {},
2414 _click: function() {},
2415 _success: function() {}
2418 WCF
.User
.Action
.Ignore
= Class
.extend({
2420 _ignoreButtonSelector
: "",
2422 init: function() {},
2423 _click: function() {},
2424 _success: function() {}
2429 * Namespace for avatar functions.
2431 WCF
.User
.Avatar
= {};
2433 if (COMPILER_TARGET_DEFAULT
) {
2435 * Avatar upload function
2439 WCF
.User
.Avatar
.Upload
= WCF
.Upload
.extend({
2441 * user id of avatar owner
2447 * Initializes a new WCF.User.Avatar.Upload object.
2449 * @param integer userID
2451 init: function (userID
) {
2452 this._super($('#avatarUpload > dd > div'), undefined, 'wcf\\data\\user\\avatar\\UserAvatarAction');
2453 this._userID
= userID
|| 0;
2455 $('#avatarForm input[type=radio]').change(function () {
2456 if ($(this).val() == 'custom') {
2457 $('#avatarUpload > dd > div').show();
2460 $('#avatarUpload > dd > div').hide();
2463 if (!$('#avatarForm input[type=radio][value=custom]:checked').length
) {
2464 $('#avatarUpload > dd > div').hide();
2469 * @see WCF.Upload._initFile()
2471 _initFile: function (file
) {
2472 return $('#avatarUpload > dt > img');
2476 * @see WCF.Upload._success()
2478 _success: function (uploadID
, data
) {
2479 if (data
.returnValues
.url
) {
2480 this._updateImage(data
.returnValues
.url
);
2483 $('#avatarUpload > dd > .innerError').remove();
2485 // show success message
2486 var $notification
= new WCF
.System
.Notification(WCF
.Language
.get('wcf.user.avatar.upload.success'));
2487 $notification
.show();
2489 else if (data
.returnValues
.errorType
) {
2491 this._getInnerErrorElement().text(WCF
.Language
.get('wcf.user.avatar.upload.error.' + data
.returnValues
.errorType
));
2496 * Updates the displayed avatar image.
2500 _updateImage: function (url
) {
2501 $('#avatarUpload > dt > img').remove();
2502 var $image
= $('<img src="' + url
+ '" class="userAvatarImage" alt="" />').css({
2504 'max-height': '96px',
2505 'max-width': '96px',
2509 $('#avatarUpload > dt').prepend($image
);
2511 WCF
.DOMNodeInsertedHandler
.execute();
2515 * Returns the inner error element.
2519 _getInnerErrorElement: function () {
2520 var $span
= $('#avatarUpload > dd > .innerError');
2521 if (!$span
.length
) {
2522 $span
= $('<small class="innerError"></span>');
2523 $('#avatarUpload > dd').append($span
);
2530 * @see WCF.Upload._getParameters()
2532 _getParameters: function () {
2534 userID
: this._userID
2540 WCF
.User
.Avatar
.Upload
= WCF
.Upload
.extend({
2542 init: function() {},
2543 _initFile: function() {},
2544 _success: function() {},
2545 _updateImage: function() {},
2546 _getInnerErrorElement: function() {},
2547 _getParameters: function() {},
2549 _buttonSelector
: {},
2550 _fileListSelector
: {},
2557 _supportsAJAXUpload
: true,
2559 _createButton: function() {},
2560 _insertButton: function() {},
2561 _removeButton: function() {},
2562 _upload: function() {},
2563 _createUploadMatrix: function() {},
2564 _error: function() {},
2565 _progress: function() {},
2566 _showOverlay: function() {},
2567 _evaluateResponse: function() {},
2568 _getFilename: function() {}
2573 * Generic implementation for grouped user lists.
2575 * @param string className
2576 * @param string dialogTitle
2577 * @param object additionalParameters
2579 WCF
.User
.List
= Class
.extend({
2581 * list of additional parameters
2584 _additionalParameters
: { },
2587 * list of cached pages
2624 * @var WCF.Action.Proxy
2629 * Initializes a new grouped user list.
2631 * @param string className
2632 * @param string dialogTitle
2633 * @param object additionalParameters
2635 init: function(className
, dialogTitle
, additionalParameters
) {
2636 this._additionalParameters
= additionalParameters
|| { };
2638 this._className
= className
;
2639 this._dialog
= null;
2640 this._dialogTitle
= dialogTitle
;
2641 this._pageCount
= 0;
2644 this._proxy
= new WCF
.Action
.Proxy({
2645 success
: $.proxy(this._success
, this)
2650 * Opens the dialog overlay.
2658 * Displays the specified page.
2660 * @param object event
2661 * @param object data
2663 _showPage: function(event
, data
) {
2664 if (data
&& data
.activePage
) {
2665 this._pageNo
= data
.activePage
;
2668 if (this._pageCount
!= 0 && (this._pageNo
< 1 || this._pageNo
> this._pageCount
)) {
2669 console
.debug("[WCF.User.List] Cannot access page " + this._pageNo
+ " of " + this._pageCount
);
2673 if (this._cache
[this._pageNo
]) {
2674 var $dialogCreated
= false;
2675 if (this._dialog
=== null) {
2676 this._dialog
= $('#userList' + this._className
.hashCode());
2677 if (this._dialog
.length
=== 0) {
2678 this._dialog
= $('<div id="userList' + this._className
.hashCode() + '" />').hide().appendTo(document
.body
);
2679 $dialogCreated
= true;
2683 // remove current view
2684 this._dialog
.empty();
2687 this._dialog
.html(this._cache
[this._pageNo
]);
2690 if (this._pageCount
> 1) {
2691 this._dialog
.find('.jsPagination').wcfPages({
2692 activePage
: this._pageNo
,
2693 maxPage
: this._pageCount
2694 }).on('wcfpagesswitched', $.proxy(this._showPage
, this));
2697 this._dialog
.find('.jsPagination').hide();
2701 if ($dialogCreated
) {
2702 this._dialog
.wcfDialog({
2703 title
: this._dialogTitle
2707 this._dialog
.wcfDialog('option', 'title', this._dialogTitle
);
2708 this._dialog
.wcfDialog('open').wcfDialog('render');
2711 WCF
.DOMNodeInsertedHandler
.execute();
2714 this._additionalParameters
.pageNo
= this._pageNo
;
2716 // load template via AJAX
2717 this._proxy
.setOption('data', {
2718 actionName
: 'getGroupedUserList',
2719 className
: this._className
,
2720 interfaceName
: 'wcf\\data\\IGroupedUserListAction',
2721 parameters
: this._additionalParameters
2723 this._proxy
.sendRequest();
2728 * Handles successful AJAX requests.
2730 * @param object data
2731 * @param string textStatus
2732 * @param jQuery jqXHR
2734 _success: function(data
, textStatus
, jqXHR
) {
2735 if (data
.returnValues
.pageCount
) {
2736 this._pageCount
= data
.returnValues
.pageCount
;
2739 this._cache
[this._pageNo
] = data
.returnValues
.template
;
2745 * Namespace for object watch functions.
2747 WCF
.User
.ObjectWatch
= {};
2749 if (COMPILER_TARGET_DEFAULT
) {
2751 * Handles subscribe/unsubscribe links.
2753 WCF
.User
.ObjectWatch
.Subscribe
= Class
.extend({
2755 * CSS selector for subscribe buttons
2758 _buttonSelector
: '.jsSubscribeButton',
2773 * system notification
2774 * @var WCF.System.Notification
2776 _notification
: null,
2779 * reload page on unsubscribe
2782 _reloadOnUnsubscribe
: false,
2785 * WCF.User.ObjectWatch.Subscribe object.
2787 * @param boolean reloadOnUnsubscribe
2789 init: function (reloadOnUnsubscribe
) {
2791 this._notification
= null;
2792 this._reloadOnUnsubscribe
= (reloadOnUnsubscribe
=== true);
2795 this._proxy
= new WCF
.Action
.Proxy({
2796 success
: $.proxy(this._success
, this)
2799 // bind event listeners
2800 $(this._buttonSelector
).each($.proxy(function (index
, button
) {
2801 var $button
= $(button
);
2802 $button
.addClass('pointer');
2803 var $objectType
= $button
.data('objectType');
2804 var $objectID
= $button
.data('objectID');
2806 if (this._buttons
[$objectType
] === undefined) {
2807 this._buttons
[$objectType
] = {};
2810 this._buttons
[$objectType
][$objectID
] = $button
.click($.proxy(this._click
, this));
2813 WCF
.System
.Event
.addListener('com.woltlab.wcf.objectWatch', 'update', $.proxy(this._updateSubscriptionStatus
, this));
2817 * Handles a click on a subscribe button.
2819 * @param object event
2821 _click: function (event
) {
2822 event
.preventDefault();
2823 var $button
= $(event
.currentTarget
);
2825 this._proxy
.setOption('data', {
2826 actionName
: 'manageSubscription',
2827 className
: 'wcf\\data\\user\\object\\watch\\UserObjectWatchAction',
2829 objectID
: $button
.data('objectID'),
2830 objectType
: $button
.data('objectType')
2833 this._proxy
.sendRequest();
2837 * Handles successful AJAX requests.
2839 * @param object data
2840 * @param string textStatus
2841 * @param jQuery jqXHR
2843 _success: function (data
, textStatus
, jqXHR
) {
2844 if (data
.actionName
=== 'manageSubscription') {
2845 if (this._dialog
=== null) {
2846 this._dialog
= $('<div>' + data
.returnValues
.template
+ '</div>').hide().appendTo(document
.body
);
2847 this._dialog
.wcfDialog({
2848 title
: WCF
.Language
.get('wcf.user.objectWatch.manageSubscription')
2852 this._dialog
.html(data
.returnValues
.template
);
2853 this._dialog
.wcfDialog('open');
2856 // bind event listener
2857 this._dialog
.find('.formSubmit > .jsButtonSave').data('objectID', data
.returnValues
.objectID
).data('objectType', data
.returnValues
.objectType
).click($.proxy(this._save
, this));
2858 var $enableNotification
= this._dialog
.find('input[name=enableNotification]').disable();
2860 // toggle subscription
2861 this._dialog
.find('input[name=subscribe]').change(function (event
) {
2862 var $input
= $(event
.currentTarget
);
2863 if ($input
.val() == 1) {
2864 $enableNotification
.enable();
2867 $enableNotification
.disable();
2872 var $selectedOption
= this._dialog
.find('input[name=subscribe]:checked');
2873 if ($selectedOption
.length
&& $selectedOption
.val() == 1) {
2874 $enableNotification
.enable();
2877 else if (data
.actionName
=== 'saveSubscription' && this._dialog
.is(':visible')) {
2878 this._dialog
.wcfDialog('close');
2880 this._updateSubscriptionStatus({
2881 isSubscribed
: data
.returnValues
.subscribe
,
2882 objectID
: data
.returnValues
.objectID
2886 // show notification
2887 if (this._notification
=== null) {
2888 this._notification
= new WCF
.System
.Notification(WCF
.Language
.get('wcf.global.success.edit'));
2891 this._notification
.show();
2896 * Saves the subscription.
2898 * @param object event
2900 _save: function (event
) {
2901 var $button
= this._buttons
[$(event
.currentTarget
).data('objectType')][$(event
.currentTarget
).data('objectID')];
2902 var $subscribe
= this._dialog
.find('input[name=subscribe]:checked').val();
2903 var $enableNotification
= (this._dialog
.find('input[name=enableNotification]').is(':checked')) ? 1 : 0;
2905 this._proxy
.setOption('data', {
2906 actionName
: 'saveSubscription',
2907 className
: 'wcf\\data\\user\\object\\watch\\UserObjectWatchAction',
2909 enableNotification
: $enableNotification
,
2910 objectID
: $button
.data('objectID'),
2911 objectType
: $button
.data('objectType'),
2912 subscribe
: $subscribe
2915 this._proxy
.sendRequest();
2919 * Updates subscription status and icon.
2921 * @param object data
2923 _updateSubscriptionStatus: function (data
) {
2924 var $button
= $(this._buttonSelector
+ '[data-object-id=' + data
.objectID
+ ']');
2925 var $icon
= $button
.children('.icon');
2926 if (data
.isSubscribed
) {
2927 $icon
.removeClass('fa-bookmark-o').addClass('fa-bookmark');
2928 $button
.data('isSubscribed', true);
2931 if ($button
.data('removeOnUnsubscribe')) {
2932 $button
.parent().remove();
2935 $icon
.removeClass('fa-bookmark').addClass('fa-bookmark-o');
2936 $button
.data('isSubscribed', false);
2939 if (this._reloadOnUnsubscribe
) {
2940 window
.location
.reload();
2945 WCF
.System
.Event
.fireEvent('com.woltlab.wcf.objectWatch', 'updatedSubscription', data
);
2950 WCF
.User
.ObjectWatch
.Subscribe
= Class
.extend({
2951 _buttonSelector
: "",
2955 _reloadOnUnsubscribe
: false,
2956 init: function() {},
2957 _click: function() {},
2958 _success: function() {},
2959 _save: function() {},
2960 _updateSubscriptionStatus: function() {}