4 * User-related classes.
6 * @author Alexander Ebert
7 * @copyright 2001-2019 WoltLab GmbH
8 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
12 * UserProfile namespace
14 WCF
.User
.Profile
= {};
17 * Shows the activity point list for users.
19 WCF
.User
.Profile
.ActivityPointList
= {
21 * list of cached templates
33 * initialization state
40 * @var WCF.Action.Proxy
45 * Initializes the WCF.User.Profile.ActivityPointList class.
54 this._proxy
= new WCF
.Action
.Proxy({
55 success
: $.proxy(this._success
, this)
60 WCF
.DOMNodeInsertedHandler
.addCallback('WCF.User.Profile.ActivityPointList', $.proxy(this._init
, this));
66 * Initializes display for activity points.
69 $('.activityPointsDisplay').removeClass('activityPointsDisplay').click($.proxy(this._click
, this));
73 * Shows or loads the activity point for selected user.
77 _click: function(event
) {
78 event
.preventDefault();
79 var $userID
= $(event
.currentTarget
).data('userID');
81 if (this._cache
[$userID
] === undefined) {
82 this._proxy
.setOption('data', {
83 actionName
: 'getDetailedActivityPointList',
84 className
: 'wcf\\data\\user\\UserProfileAction',
85 objectIDs
: [ $userID
]
87 this._proxy
.sendRequest();
95 * Displays activity points for given user.
97 * @param integer userID
99 _show: function(userID
) {
100 if (this._dialog
=== null) {
101 this._dialog
= $('<div>' + this._cache
[userID
] + '</div>').hide().appendTo(document
.body
);
102 this._dialog
.wcfDialog({
103 title
: WCF
.Language
.get('wcf.user.activityPoint')
107 this._dialog
.html(this._cache
[userID
]);
108 this._dialog
.wcfDialog('open');
113 * Handles successful AJAX requests.
116 * @param string textStatus
117 * @param jQuery jqXHR
119 _success: function(data
, textStatus
, jqXHR
) {
120 this._cache
[data
.returnValues
.userID
] = data
.returnValues
.template
;
121 this._show(data
.returnValues
.userID
);
126 * Provides methods to load tab menu content upon request.
128 WCF
.User
.Profile
.TabMenu
= Class
.extend({
139 _profileContent
: null,
143 * @var WCF.Action.Proxy
154 * Initializes the tab menu loader.
156 * @param integer userID
158 init: function(userID
) {
159 this._profileContent
= $('#profileContent');
160 this._userID
= userID
;
162 var $activeMenuItem
= this._profileContent
.data('active');
163 var $enableProxy
= false;
165 // fetch content state
166 this._profileContent
.find('div.tabMenuContent').each($.proxy(function(index
, container
) {
167 var $containerID
= $(container
).wcfIdentify();
169 if ($activeMenuItem
=== $containerID
) {
170 this._hasContent
[$containerID
] = true;
173 this._hasContent
[$containerID
] = false;
178 // enable loader if at least one container is empty
180 this._proxy
= new WCF
.Action
.Proxy({
181 success
: $.proxy(this._success
, this)
184 this._profileContent
.on('wcftabsbeforeactivate', $.proxy(this._loadContent
, this));
186 // check which tab is selected
187 this._profileContent
.find('> nav.tabMenu > ul > li').each($.proxy(function(index
, listItem
) {
188 var $listItem
= $(listItem
);
189 if ($listItem
.hasClass('ui-state-active')) {
191 this._loadContent(null, {
192 newPanel
: $('#' + $listItem
.attr('aria-controls'))
201 $('.userProfileUser .contentDescription a[href$="#likes"]').click((function (event
) {
202 event
.preventDefault();
204 require(['Ui/TabMenu'], function (UiTabMenu
) {
205 UiTabMenu
.getTabMenu('profileContent').select('likes');
211 * Prepares to load content once tabs are being switched.
213 * @param object event
216 _loadContent: function(event
, ui
) {
217 var $panel
= $(ui
.newPanel
);
218 var $containerID
= $panel
.attr('id');
220 if (!this._hasContent
[$containerID
]) {
221 this._proxy
.setOption('data', {
222 actionName
: 'getContent',
223 className
: 'wcf\\data\\user\\profile\\menu\\item\\UserProfileMenuItemAction',
226 containerID
: $containerID
,
227 menuItem
: $panel
.data('menuItem'),
232 this._proxy
.sendRequest();
237 * Shows previously requested content.
240 * @param string textStatus
241 * @param jQuery jqXHR
243 _success: function(data
, textStatus
, jqXHR
) {
244 var $containerID
= data
.returnValues
.containerID
;
245 this._hasContent
[$containerID
] = true;
247 // insert content, uses non jQuery because DomUtil.insertHtml() moves <script> elements
248 // to the bottom of the element by default which is exactly what is required here
249 require(['Dom/ChangeListener', 'Dom/Util'], function(DomChangeListener
, DomUtil
) {
250 DomUtil
.insertHtml(data
.returnValues
.template
, elById($containerID
), 'append');
252 DomChangeListener
.trigger();
257 if (COMPILER_TARGET_DEFAULT
) {
259 * User profile inline editor.
261 * @param integer userID
262 * @param boolean editOnInit
264 WCF
.User
.Profile
.Editor
= Class
.extend({
274 * list of interface buttons
287 * @var WCF.Action.Proxy
304 * Initializes the WCF.User.Profile.Editor object.
306 * @param integer userID
307 * @param boolean editOnInit
309 init: function (userID
, editOnInit
) {
310 this._actionName
= '';
311 this._active
= false;
312 this._cachedTemplate
= '';
313 this._tab
= $('#about');
314 this._userID
= userID
;
315 this._proxy
= new WCF
.Action
.Proxy({
316 success
: $.proxy(this._success
, this)
321 // begin editing on page load
328 * Initializes interface buttons.
330 _initButtons: function () {
333 beginEdit
: $('.jsButtonEditProfile:eq(0)').click(this._beginEdit
.bind(this))
340 * @param {Event?} event event object
342 _beginEdit: function (event
) {
343 if (event
) event
.preventDefault();
345 if (this._active
) return;
348 this._actionName
= 'beginEdit';
349 this._buttons
.beginEdit
.parent().addClass('active');
350 $('#profileContent').wcfTabs('select', 'about');
353 this._proxy
.setOption('data', {
354 actionName
: 'beginEdit',
355 className
: 'wcf\\data\\user\\UserProfileAction',
356 objectIDs
: [this._userID
]
358 this._proxy
.sendRequest();
362 * Saves input values.
365 require(["WoltLabSuite/Core/Component/Ckeditor"], ({ getCkeditor
}) => {
366 const textareas
= Array
.from(this._tab
[0].querySelectorAll("textarea"));
367 const scrollToTextarea
= textareas
.find((textarea
) => {
368 const editor
= getCkeditor(textarea
);
369 if (editor
=== undefined) {
375 throwError
: elInnerError
379 WCF
.System
.Event
.fireEvent('com.woltlab.wcf.ckeditor5', `validate_${textarea.id}`, data
);
381 return data
.valid
=== false;
384 if (scrollToTextarea
) {
385 scrollToTextarea
.parentElement
.scrollIntoView({ behavior
: 'smooth' });
389 this._actionName
= 'save';
392 var $regExp
= /values\[([a-zA-Z0-9._-]+)\]/;
394 this._tab
.find('input, textarea, select').each(function (index
, element
) {
395 var $element
= $(element
);
398 switch ($element
.getTagName()) {
400 var $type
= $element
.attr('type');
402 if (($type
=== 'radio' || $type
=== 'checkbox') && !$element
.prop('checked')) {
408 let editor
= getCkeditor(element
);
409 if (editor
!== undefined) {
410 $value
= editor
.getHtml();
415 var $name
= $element
.attr('name');
416 if ($regExp
.test($name
)) {
417 var $fieldName
= RegExp
.$1;
418 if ($value
=== null) $value
= $element
.val();
420 // check for checkboxes
421 if ($element
.attr('type') === 'checkbox' && /\[\]$/.test($name
)) {
422 if (!Array
.isArray($values
[$fieldName
])) {
423 $values
[$fieldName
] = [];
426 $values
[$fieldName
].push($value
);
429 $values
[$fieldName
] = $value
;
434 this._proxy
.setOption('data', {
436 className
: 'wcf\\data\\user\\UserProfileAction',
437 objectIDs
: [this._userID
],
442 this._proxy
.sendRequest();
447 * Restores back to default view.
449 _restore: function () {
450 this._actionName
= 'restore';
451 this._active
= false;
452 this._buttons
.beginEdit
.parent().removeClass('active');
454 this._destroyEditor();
456 this._tab
.html(this._cachedTemplate
).children().css({height
: 'auto'});
460 * Handles successful AJAX requests.
463 * @param string textStatus
464 * @param jQuery jqXHR
466 _success: function (data
, textStatus
, jqXHR
) {
467 switch (this._actionName
) {
469 this._prepareEdit(data
);
473 // save was successful, show parsed template
474 if (data
.returnValues
.success
) {
475 this._cachedTemplate
= data
.returnValues
.template
;
479 this._prepareEdit(data
, true);
486 * Prepares editing mode.
489 * @param boolean disableCache
491 _prepareEdit: function (data
, disableCache
) {
492 this._destroyEditor();
496 this._tab
.html(function (index
, oldHTML
) {
497 if (disableCache
!== true) {
498 self
._cachedTemplate
= oldHTML
;
501 return data
.returnValues
.template
;
504 // block autocomplete
505 this._tab
.find('input[type=text]').attr('autocomplete', 'off');
507 // bind event listener
508 this._tab
.find('.formSubmit > button[data-type=save]').click($.proxy(this._save
, this));
509 this._tab
.find('.formSubmit > button[data-type=restore]').click($.proxy(this._restore
, this));
510 this._tab
.find('input').keyup(function (event
) {
511 if (event
.which
=== $.ui
.keyCode
.ENTER
) {
514 event
.preventDefault();
521 * Destroys all editor instances within current tab.
523 _destroyEditor: function () {
524 require(["WoltLabSuite/Core/Component/Ckeditor"], ({ getCkeditor
}) => {
525 this._tab
[0].querySelectorAll("textarea").forEach((textarea
) => {
526 const editor
= getCkeditor(textarea
);
534 WCF
.User
.Profile
.Editor
= Class
.extend({
543 _initButtons: function() {},
544 _beginEdit: function() {},
545 _save: function() {},
546 _restore: function() {},
547 _success: function() {},
548 _prepareEdit: function() {},
549 _destroyEditor: function() {}
554 * Namespace for registration functions.
556 WCF
.User
.Registration
= {};
559 * Validates the password.
561 * @param jQuery element
562 * @param jQuery confirmElement
563 * @param object options
564 * @deprecated 6.1 use `WoltLabSuite/Core/Controller/User/Registration` instead
566 WCF
.User
.Registration
.Validation
= Class
.extend({
580 * confirmation input element
583 _confirmElement
: null,
592 * list of error messages
598 * list of additional options
605 * @var WCF.Action.Proxy
610 * Initializes the validation.
612 * @param jQuery element
613 * @param jQuery confirmElement
614 * @param object options
616 init: function(element
, confirmElement
, options
) {
617 this._element
= element
;
618 this._element
.blur($.proxy(this._blur
, this));
619 this._confirmElement
= confirmElement
|| null;
621 if (this._confirmElement
!== null) {
622 this._confirmElement
.blur($.proxy(this._blurConfirm
, this));
625 options
= options
|| { };
626 this._setOptions(options
);
628 this._proxy
= new WCF
.Action
.Proxy({
629 success
: $.proxy(this._success
, this),
630 showLoadingOverlay
: false
633 this._setErrorMessages();
637 * Sets additional options
639 _setOptions: function(options
) { },
642 * Sets error messages.
644 _setErrorMessages: function() {
645 this._errorMessages
= {
652 * Validates once focus on input is lost.
654 * @param object event
656 _blur: function(event
) {
657 var $value
= this._element
.val();
659 return this._showError(this._element
, WCF
.Language
.get('wcf.global.form.error.empty'));
662 if (this._confirmElement
!== null) {
663 var $confirmValue
= this._confirmElement
.val();
664 if ($confirmValue
!= '' && $value
!= $confirmValue
) {
665 return this._showError(this._confirmElement
, this._errorMessages
.notEqual
);
669 if (!this._validateOptions()) {
673 this._proxy
.setOption('data', {
674 actionName
: this._actionName
,
675 className
: this._className
,
676 parameters
: this._getParameters()
678 this._proxy
.sendRequest();
682 * Returns a list of parameters.
686 _getParameters: function() {
691 * Validates input by options.
695 _validateOptions: function() {
700 * Validates value once confirmation input focus is lost.
702 * @param object event
704 _blurConfirm: function(event
) {
705 var $value
= this._confirmElement
.val();
707 return this._showError(this._confirmElement
, WCF
.Language
.get('wcf.global.form.error.empty'));
714 * Handles AJAX responses.
717 * @param string textStatus
718 * @param jQuery jqXHR
720 _success: function(data
, textStatus
, jqXHR
) {
721 if (data
.returnValues
.isValid
) {
722 this._showSuccess(this._element
);
723 if (this._confirmElement
!== null && this._confirmElement
.val()) {
724 this._showSuccess(this._confirmElement
);
728 this._showError(this._element
, WCF
.Language
.get(this._errorMessages
.ajaxError
+ data
.returnValues
.error
));
733 * Shows an error message.
735 * @param jQuery element
736 * @param string message
738 _showError: function(element
, message
) {
739 element
.parent().parent().addClass('formError').removeClass('formSuccess');
741 var $innerError
= element
.parent().find('small.innerError');
742 if (!$innerError
.length
) {
743 $innerError
= $('<small />').addClass('innerError').insertAfter(element
);
746 $innerError
.text(message
);
750 * Displays a success message.
752 * @param jQuery element
754 _showSuccess: function(element
) {
755 element
.parent().parent().addClass('formSuccess').removeClass('formError');
756 element
.next('small.innerError').remove();
761 * Username validation for registration.
763 * @see WCF.User.Registration.Validation
764 * @deprecated 6.1 use `WoltLabSuite/Core/Controller/User/Registration` instead
766 WCF
.User
.Registration
.Validation
.Username
= WCF
.User
.Registration
.Validation
.extend({
768 * @see WCF.User.Registration.Validation._actionName
770 _actionName
: 'validateUsername',
773 * @see WCF.User.Registration.Validation._className
775 _className
: 'wcf\\data\\user\\UserRegistrationAction',
778 * @see WCF.User.Registration.Validation._setOptions()
780 _setOptions: function(options
) {
781 this._options
= $.extend(true, {
788 * @see WCF.User.Registration.Validation._setErrorMessages()
790 _setErrorMessages: function() {
791 this._errorMessages
= {
792 ajaxError
: 'wcf.user.username.error.'
797 * @see WCF.User.Registration.Validation._validateOptions()
799 _validateOptions: function() {
800 var $value
= this._element
.val();
801 if ($value
.length
< this._options
.minlength
|| $value
.length
> this._options
.maxlength
) {
802 this._showError(this._element
, WCF
.Language
.get('wcf.user.username.error.invalid'));
810 * @see WCF.User.Registration.Validation._getParameters()
812 _getParameters: function() {
814 username
: this._element
.val()
820 * Email validation for registration.
822 * @see WCF.User.Registration.Validation
823 * @deprecated 6.1 use `WoltLabSuite/Core/Controller/User/Registration` instead
825 WCF
.User
.Registration
.Validation
.EmailAddress
= WCF
.User
.Registration
.Validation
.extend({
827 * @see WCF.User.Registration.Validation._actionName
829 _actionName
: 'validateEmailAddress',
832 * @see WCF.User.Registration.Validation._className
834 _className
: 'wcf\\data\\user\\UserRegistrationAction',
837 * @see WCF.User.Registration.Validation._getParameters()
839 _getParameters: function() {
841 email
: this._element
.val()
846 * @see WCF.User.Registration.Validation._setErrorMessages()
848 _setErrorMessages: function() {
849 this._errorMessages
= {
850 ajaxError
: 'wcf.user.email.error.',
851 notEqual
: WCF
.Language
.get('wcf.user.confirmEmail.error.notEqual')
857 * Notification system for WCF.
859 * @author Alexander Ebert
860 * @copyright 2001-2019 WoltLab GmbH
861 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
863 WCF
.Notification
= { };
865 if (COMPILER_TARGET_DEFAULT
) {
867 * Handles the notification list.
869 WCF
.Notification
.List
= Class
.extend({
872 * @var WCF.Action.Proxy
877 * Initializes the WCF.Notification.List object.
880 this._proxy
= new WCF
.Action
.Proxy({
881 success
: $.proxy(this._success
, this)
884 // handle 'mark all as confirmed' buttons
885 $('.jsMarkAllAsConfirmed').click(function () {
886 WCF
.System
.Confirmation
.show(WCF
.Language
.get('wcf.user.notification.markAllAsConfirmed.confirmMessage'), function (action
) {
887 if (action
=== 'confirm') {
888 new WCF
.Action
.Proxy({
891 actionName
: 'markAllAsConfirmed',
892 className
: 'wcf\\data\\user\\notification\\UserNotificationAction'
894 success: function () {
895 window
.location
.reload();
902 // handle regular items
907 * Converts the notification item list to be in sync with the notification dropdown.
909 _convertList: function () {
910 $('.userNotificationItemList > .notificationItem').each((function (index
, item
) {
913 if (!$item
.data('isRead')) {
914 $item
.find('a:not(.userLink)').prop('href', $item
.data('link'));
916 var $markAsConfirmed
= $(`<button type="button" class="notificationItemMarkAsConfirmed jsTooltip" title="${WCF.Language.get('wcf.global.button.markAsRead')}">
917 <fa-icon size="24" name="check"></fa-icon>
918 </button>`).appendTo($item
);
919 $markAsConfirmed
.click($.proxy(this._markAsConfirmed
, this));
923 WCF
.DOMNodeInsertedHandler
.execute();
927 * Marks a single notification as confirmed.
929 * @param object event
931 _markAsConfirmed: function (event
) {
932 event
.preventDefault();
934 var $notificationID
= $(event
.currentTarget
).parents('.notificationItem:eq(0)').data('objectID');
936 this._proxy
.setOption('data', {
937 actionName
: 'markAsConfirmed',
938 className
: 'wcf\\data\\user\\notification\\UserNotificationAction',
939 objectIDs
: [$notificationID
]
941 this._proxy
.sendRequest();
947 * Handles successful AJAX requests.
950 * @param string textStatus
951 * @param jQuery jqXHR
953 _success: function (data
, textStatus
, jqXHR
) {
954 var $item
= $('.userNotificationItemList > .notificationItem[data-object-id=' + data
.returnValues
.markAsRead
+ ']');
956 $item
.data('isRead', true);
957 $item
.find('.newContentBadge').remove();
958 $item
.find('.notificationItemMarkAsConfirmed').remove();
959 $item
.removeClass('notificationUnconfirmed');
966 * @see WCF.Message.Preview
968 WCF
.User
.SignaturePreview
= WCF
.Message
.Preview
.extend({
970 * @see WCF.Message.Preview._handleResponse()
972 _handleResponse: function (data
) {
973 // get preview container
974 var $preview
= $('#previewContainer');
975 if (!$preview
.length
) {
976 $preview
= $('<section class="section" id="previewContainer"><h2 class="sectionTitle">' + WCF
.Language
.get('wcf.global.preview') + '</h2><div class="htmlContent messageSignatureConstraints"></div></section>').insertBefore($('#signatureContainer')).wcfFadeIn();
979 $preview
.children('div').first().html(data
.returnValues
.message
);
984 WCF
.Notification
.List
= Class
.extend({
987 _convertList: function() {},
988 _markAsConfirmed: function() {},
989 _success: function() {}
992 WCF
.User
.SignaturePreview
= WCF
.Message
.Preview
.extend({
993 _handleResponse: function() {},
999 _previewButtonLabel
: "",
1000 init: function() {},
1001 _click: function() {},
1002 _getParameters: function() {},
1003 _getMessage: function() {},
1004 _success: function() {},
1005 _failure: function() {}
1010 * Loads recent activity events once the user scrolls to the very bottom.
1012 * @param integer userID
1013 * @deprecated 6.1 use `WoltLabSuite/Core/Components/User/RecentActivity/Loader` instead
1015 WCF
.User
.RecentActivityLoader
= Class
.extend({
1023 * true if list should be filtered by followed users
1026 _filteredByFollowedUsers
: false,
1029 * button to load next events
1036 * @var WCF.Action.Proxy
1047 * Initializes a new RecentActivityLoader object.
1049 * @param integer userID
1050 * @param boolean filteredByFollowedUsers
1052 init: function(userID
, filteredByFollowedUsers
) {
1053 this._container
= $('#recentActivities');
1054 this._filteredByFollowedUsers
= (filteredByFollowedUsers
=== true);
1055 this._userID
= userID
;
1057 if (this._userID
!== null && !this._userID
) {
1058 console
.debug("[WCF.User.RecentActivityLoader] Invalid parameter 'userID' given.");
1062 this._proxy
= new WCF
.Action
.Proxy({
1063 success
: $.proxy(this._success
, this)
1066 if (this._container
.children('li').length
) {
1067 this._loadButton
= $('<li class="showMore"><button type="button" class="button small">' + WCF
.Language
.get('wcf.user.recentActivity.more') + '</button></li>').appendTo(this._container
);
1068 this._loadButton
= this._loadButton
.children('button').click($.proxy(this._click
, this));
1071 $('<li class="showMore"><small>' + WCF
.Language
.get('wcf.user.recentActivity.noMoreEntries') + '</small></li>').appendTo(this._container
);
1074 if (WCF
.User
.userID
) {
1075 $('.jsRecentActivitySwitchContext .button').click($.proxy(this._switchContext
, this));
1080 * Loads next activity events.
1082 _click: function() {
1083 this._loadButton
.enable();
1086 lastEventID
: this._container
.data('lastEventID'),
1087 lastEventTime
: this._container
.data('lastEventTime')
1090 $parameters
.userID
= this._userID
;
1092 else if (this._filteredByFollowedUsers
) {
1093 $parameters
.filteredByFollowedUsers
= 1;
1096 this._proxy
.setOption('data', {
1098 className
: 'wcf\\data\\user\\activity\\event\\UserActivityEventAction',
1099 parameters
: $parameters
1101 this._proxy
.sendRequest();
1105 * Switches recent activity context.
1107 _switchContext: function(event
) {
1108 event
.preventDefault();
1110 if (!$(event
.currentTarget
).hasClass('active')) {
1111 new WCF
.Action
.Proxy({
1114 actionName
: 'switchContext',
1115 className
: 'wcf\\data\\user\\activity\\event\\UserActivityEventAction'
1117 success: function() {
1118 window
.location
.hash
= '#dashboardBoxRecentActivity';
1119 window
.location
.reload();
1126 * Handles successful AJAX requests.
1128 * @param object data
1129 * @param string textStatus
1130 * @param jQuery jqXHR
1132 _success: function(data
, textStatus
, jqXHR
) {
1133 if (data
.returnValues
.template
) {
1134 $(data
.returnValues
.template
).insertBefore(this._loadButton
.parent());
1136 this._container
.data('lastEventTime', data
.returnValues
.lastEventTime
);
1137 this._container
.data('lastEventID', data
.returnValues
.lastEventID
);
1138 this._loadButton
.enable();
1141 $('<small>' + WCF
.Language
.get('wcf.user.recentActivity.noMoreEntries') + '</small>').appendTo(this._loadButton
.parent());
1142 this._loadButton
.remove();
1148 * Initializes WCF.User.Action namespace.
1150 WCF
.User
.Action
= {};
1152 if (COMPILER_TARGET_DEFAULT
) {
1154 * Handles user follow and unfollow links.
1156 * @deprecated 6.1 use `WoltLabSuite/Core/Component/User/Follow` instead
1158 WCF
.User
.Action
.Follow
= Class
.extend({
1160 * list with elements containing follow and unfollow buttons
1163 _containerList
: null,
1166 * CSS selector for follow buttons
1169 _followButtonSelector
: '.jsFollowButton',
1172 * id of the user that is currently being followed/unfollowed
1178 * Initializes new WCF.User.Action.Follow object.
1180 * @param array containerList
1181 * @param string followButtonSelector
1183 init: function (containerList
, followButtonSelector
) {
1184 if (!containerList
.length
) {
1187 this._containerList
= containerList
;
1189 if (followButtonSelector
) {
1190 this._followButtonSelector
= followButtonSelector
;
1194 this._proxy
= new WCF
.Action
.Proxy({
1195 success
: $.proxy(this._success
, this)
1198 // bind event listeners
1199 this._containerList
.each($.proxy(function (index
, container
) {
1200 $(container
).find(this._followButtonSelector
).click($.proxy(this._click
, this));
1205 * Handles a click on a follow or unfollow button.
1207 * @param object event
1209 _click: function (event
) {
1210 event
.preventDefault();
1211 var link
= $(event
.target
);
1212 if (!link
.is('a')) {
1213 link
= link
.closest('a');
1215 this._userID
= link
.data('objectID');
1217 this._proxy
.setOption('data', {
1218 'actionName': link
.data('following') ? 'unfollow' : 'follow',
1219 'className': 'wcf\\data\\user\\follow\\UserFollowAction',
1222 userID
: this._userID
1226 this._proxy
.sendRequest();
1230 * Handles the successful (un)following of a user.
1232 * @param object data
1233 * @param string textStatus
1234 * @param jQuery jqXHR
1236 _success: function (data
, textStatus
, jqXHR
) {
1237 this._containerList
.each($.proxy(function (index
, container
) {
1238 var button
= $(container
).find(this._followButtonSelector
).get(0);
1240 if (button
&& $(button
).data('objectID') == this._userID
) {
1243 // toogle icon title
1244 if (data
.returnValues
.following
) {
1245 button
.attr('data-tooltip', WCF
.Language
.get('wcf.user.button.unfollow'));
1246 button
[0].querySelector("fa-icon").setIcon("circle-minus");
1247 button
.children('.invisible').text(WCF
.Language
.get('wcf.user.button.unfollow'));
1250 button
.attr('data-tooltip', WCF
.Language
.get('wcf.user.button.follow'));
1251 button
[0].querySelector("fa-icon").setIcon("circle-plus");
1252 button
.children('.invisible').text(WCF
.Language
.get('wcf.user.button.follow'));
1255 button
.data('following', data
.returnValues
.following
);
1261 var $notification
= new WCF
.System
.Notification();
1262 $notification
.show();
1267 * Handles user ignore and unignore links.
1269 * @deprecated 5.4 Use a FormBuilderDialog for wcf\data\user\ignore\UserIgnoreAction::getDialog()
1271 WCF
.User
.Action
.Ignore
= Class
.extend({
1273 * list with elements containing ignore and unignore buttons
1276 _containerList
: null,
1279 * CSS selector for ignore buttons
1282 _ignoreButtonSelector
: '.jsIgnoreButton',
1285 * id of the user that is currently being ignored/unignored
1291 * Initializes new WCF.User.Action.Ignore object.
1293 * @param array containerList
1294 * @param string ignoreButtonSelector
1296 init: function (containerList
, ignoreButtonSelector
) {
1297 if (!containerList
.length
) {
1300 this._containerList
= containerList
;
1302 if (ignoreButtonSelector
) {
1303 this._ignoreButtonSelector
= ignoreButtonSelector
;
1307 this._proxy
= new WCF
.Action
.Proxy({
1308 success
: $.proxy(this._success
, this)
1311 // bind event listeners
1312 this._containerList
.each($.proxy(function (index
, container
) {
1313 $(container
).find(this._ignoreButtonSelector
).click($.proxy(this._click
, this));
1318 * Handles a click on a ignore or unignore button.
1320 * @param object event
1322 _click: function (event
) {
1323 event
.preventDefault();
1324 var link
= $(event
.target
);
1325 if (!link
.is('a')) {
1326 link
= link
.closest('a');
1328 this._userID
= link
.data('objectID');
1330 this._proxy
.setOption('data', {
1331 'actionName': link
.data('ignored') ? 'unignore' : 'ignore',
1332 'className': 'wcf\\data\\user\\ignore\\UserIgnoreAction',
1335 userID
: this._userID
1339 this._proxy
.sendRequest();
1343 * Handles the successful (un)ignoring of a user.
1345 * @param object data
1346 * @param string textStatus
1347 * @param jQuery jqXHR
1349 _success: function (data
, textStatus
, jqXHR
) {
1350 this._containerList
.each($.proxy(function (index
, container
) {
1351 var button
= $(container
).find(this._ignoreButtonSelector
).get(0);
1353 if (button
&& $(button
).data('objectID') == this._userID
) {
1356 // toogle icon title
1357 if (data
.returnValues
.isIgnoredUser
) {
1358 button
.attr('data-tooltip', WCF
.Language
.get('wcf.user.button.unignore'));
1359 button
[0].querySelector("fa-icon").setIcon("circle");
1360 button
.children('.invisible').text(WCF
.Language
.get('wcf.user.button.unignore'));
1363 button
.attr('data-tooltip', WCF
.Language
.get('wcf.user.button.ignore'));
1364 button
[0].querySelector("fa-icon").setIcon("ban");
1365 button
.children('.invisible').text(WCF
.Language
.get('wcf.user.button.ignore'));
1368 button
.data('ignored', data
.returnValues
.isIgnoredUser
);
1374 var $notification
= new WCF
.System
.Notification();
1375 $notification
.show();
1380 WCF
.User
.Action
.Follow
= Class
.extend({
1382 _followButtonSelector
: "",
1384 init: function() {},
1385 _click: function() {},
1386 _success: function() {}
1392 WCF
.User
.Action
.Ignore
= Class
.extend({
1394 _ignoreButtonSelector
: "",
1396 init: function() {},
1397 _click: function() {},
1398 _success: function() {}
1403 * Namespace for avatar functions.
1405 WCF
.User
.Avatar
= {};
1407 if (COMPILER_TARGET_DEFAULT
) {
1409 * Avatar upload function
1413 WCF
.User
.Avatar
.Upload
= WCF
.Upload
.extend({
1415 * user id of avatar owner
1421 * Initializes a new WCF.User.Avatar.Upload object.
1423 * @param integer userID
1425 init: function (userID
) {
1426 this._super($('#avatarUpload > dd > div'), undefined, 'wcf\\data\\user\\avatar\\UserAvatarAction');
1427 this._userID
= userID
|| 0;
1429 $('#avatarForm input[type=radio]').change(function () {
1430 if ($(this).val() == 'custom') {
1431 $('#avatarUpload > dd > div').show();
1434 $('#avatarUpload > dd > div').hide();
1437 if (!$('#avatarForm input[type=radio][value=custom]:checked').length
) {
1438 $('#avatarUpload > dd > div').hide();
1443 * @see WCF.Upload._initFile()
1445 _initFile: function (file
) {
1446 return $('#avatarUpload > dt > img');
1450 * @see WCF.Upload._success()
1452 _success: function (uploadID
, data
) {
1453 if (data
.returnValues
.url
) {
1454 this._updateImage(data
.returnValues
.url
);
1457 $('#avatarUpload > dd > .innerError').remove();
1459 // show success message
1460 var $notification
= new WCF
.System
.Notification(WCF
.Language
.get('wcf.user.avatar.upload.success'));
1461 $notification
.show();
1463 else if (data
.returnValues
.errorType
) {
1465 this._getInnerErrorElement().text(WCF
.Language
.get('wcf.user.avatar.upload.error.' + data
.returnValues
.errorType
));
1470 * Updates the displayed avatar image.
1474 _updateImage: function (url
) {
1475 $('#avatarUpload > dt > img').remove();
1476 var $image
= $('<img src="' + url
+ '" class="userAvatarImage" alt="" />').css({
1478 'max-height': '96px',
1479 'max-width': '96px',
1483 $('#avatarUpload > dt').prepend($image
);
1485 WCF
.DOMNodeInsertedHandler
.execute();
1489 * Returns the inner error element.
1493 _getInnerErrorElement: function () {
1494 var $span
= $('#avatarUpload > dd > .innerError');
1495 if (!$span
.length
) {
1496 $span
= $('<small class="innerError"></span>');
1497 $('#avatarUpload > dd').append($span
);
1504 * @see WCF.Upload._getParameters()
1506 _getParameters: function () {
1508 userID
: this._userID
1514 WCF
.User
.Avatar
.Upload
= WCF
.Upload
.extend({
1516 init: function() {},
1517 _initFile: function() {},
1518 _success: function() {},
1519 _updateImage: function() {},
1520 _getInnerErrorElement: function() {},
1521 _getParameters: function() {},
1523 _buttonSelector
: {},
1524 _fileListSelector
: {},
1531 _supportsAJAXUpload
: true,
1533 _createButton: function() {},
1534 _insertButton: function() {},
1535 _removeButton: function() {},
1536 _upload: function() {},
1537 _createUploadMatrix: function() {},
1538 _error: function() {},
1539 _progress: function() {},
1540 _showOverlay: function() {},
1541 _evaluateResponse: function() {},
1542 _getFilename: function() {}
1547 * Generic implementation for grouped user lists.
1549 * @param string className
1550 * @param string dialogTitle
1551 * @param object additionalParameters
1552 * @deprecated 6.0 use `WoltLabSuite/Core/Component/User/List` instead
1554 WCF
.User
.List
= Class
.extend({
1556 * list of additional parameters
1559 _additionalParameters
: { },
1562 * list of cached pages
1599 * @var WCF.Action.Proxy
1604 * Initializes a new grouped user list.
1606 * @param string className
1607 * @param string dialogTitle
1608 * @param object additionalParameters
1610 init: function(className
, dialogTitle
, additionalParameters
) {
1611 this._additionalParameters
= additionalParameters
|| { };
1613 this._className
= className
;
1614 this._dialog
= null;
1615 this._dialogTitle
= dialogTitle
;
1616 this._pageCount
= 0;
1619 this._proxy
= new WCF
.Action
.Proxy({
1620 success
: $.proxy(this._success
, this)
1625 * Opens the dialog overlay.
1633 * Displays the specified page.
1635 * @param object event
1636 * @param object data
1638 _showPage: function(event
, data
) {
1639 if (data
&& data
.activePage
) {
1640 this._pageNo
= data
.activePage
;
1643 if (this._pageCount
!= 0 && (this._pageNo
< 1 || this._pageNo
> this._pageCount
)) {
1644 console
.debug("[WCF.User.List] Cannot access page " + this._pageNo
+ " of " + this._pageCount
);
1648 if (this._cache
[this._pageNo
]) {
1649 var $dialogCreated
= false;
1650 if (this._dialog
=== null) {
1651 this._dialog
= $('#userList' + this._className
.hashCode());
1652 if (this._dialog
.length
=== 0) {
1653 this._dialog
= $('<div id="userList' + this._className
.hashCode() + '" />').hide().appendTo(document
.body
);
1654 $dialogCreated
= true;
1658 // remove current view
1659 this._dialog
.empty();
1662 this._dialog
.html(this._cache
[this._pageNo
]);
1665 if (this._pageCount
> 1) {
1666 this._dialog
.find('.jsPagination').wcfPages({
1667 activePage
: this._pageNo
,
1668 maxPage
: this._pageCount
1669 }).on('wcfpagesswitched', $.proxy(this._showPage
, this));
1672 this._dialog
.find('.jsPagination').hide();
1676 if ($dialogCreated
) {
1677 this._dialog
.wcfDialog({
1678 title
: this._dialogTitle
1682 this._dialog
.wcfDialog('option', 'title', this._dialogTitle
);
1683 this._dialog
.wcfDialog('open').wcfDialog('render');
1686 WCF
.DOMNodeInsertedHandler
.execute();
1689 this._additionalParameters
.pageNo
= this._pageNo
;
1691 // load template via AJAX
1692 this._proxy
.setOption('data', {
1693 actionName
: 'getGroupedUserList',
1694 className
: this._className
,
1695 interfaceName
: 'wcf\\data\\IGroupedUserListAction',
1696 parameters
: this._additionalParameters
1698 this._proxy
.sendRequest();
1703 * Handles successful AJAX requests.
1705 * @param object data
1706 * @param string textStatus
1707 * @param jQuery jqXHR
1709 _success: function(data
, textStatus
, jqXHR
) {
1710 if (data
.returnValues
.pageCount
) {
1711 this._pageCount
= data
.returnValues
.pageCount
;
1714 this._cache
[this._pageNo
] = data
.returnValues
.template
;
1720 * Namespace for object watch functions.
1722 WCF
.User
.ObjectWatch
= {};
1724 if (COMPILER_TARGET_DEFAULT
) {
1726 * Handles subscribe/unsubscribe links.
1728 * @deprecated since 6.0, use `WoltLabSuite/Core/Ui/User/ObjectWatch` instead.
1730 WCF
.User
.ObjectWatch
.Subscribe
= Class
.extend({
1732 * CSS selector for subscribe buttons
1735 _buttonSelector
: '.jsSubscribeButton',
1750 * system notification
1751 * @var WCF.System.Notification
1753 _notification
: null,
1756 * reload page on unsubscribe
1759 _reloadOnUnsubscribe
: false,
1762 * WCF.User.ObjectWatch.Subscribe object.
1764 * @param boolean reloadOnUnsubscribe
1766 init: function (reloadOnUnsubscribe
) {
1768 this._notification
= null;
1769 this._reloadOnUnsubscribe
= (reloadOnUnsubscribe
=== true);
1772 this._proxy
= new WCF
.Action
.Proxy({
1773 success
: $.proxy(this._success
, this)
1776 // bind event listeners
1777 $(this._buttonSelector
).each($.proxy(function (index
, button
) {
1778 var $button
= $(button
);
1779 $button
.addClass('pointer');
1780 var $objectType
= $button
.data('objectType');
1781 var $objectID
= $button
.data('objectID');
1783 if (this._buttons
[$objectType
] === undefined) {
1784 this._buttons
[$objectType
] = {};
1787 this._buttons
[$objectType
][$objectID
] = $button
.click($.proxy(this._click
, this));
1790 WCF
.System
.Event
.addListener('com.woltlab.wcf.objectWatch', 'update', $.proxy(this._updateSubscriptionStatus
, this));
1794 * Handles a click on a subscribe button.
1796 * @param object event
1798 _click: function (event
) {
1799 event
.preventDefault();
1800 var $button
= $(event
.currentTarget
);
1802 this._proxy
.setOption('data', {
1803 actionName
: 'manageSubscription',
1804 className
: 'wcf\\data\\user\\object\\watch\\UserObjectWatchAction',
1806 objectID
: $button
.data('objectID'),
1807 objectType
: $button
.data('objectType')
1810 this._proxy
.sendRequest();
1814 * Handles successful AJAX requests.
1816 * @param object data
1817 * @param string textStatus
1818 * @param jQuery jqXHR
1820 _success: function (data
, textStatus
, jqXHR
) {
1821 if (data
.actionName
=== 'manageSubscription') {
1822 if (this._dialog
=== null) {
1823 this._dialog
= $('<div>' + data
.returnValues
.template
+ '</div>').hide().appendTo(document
.body
);
1824 this._dialog
.wcfDialog({
1825 title
: WCF
.Language
.get('wcf.user.objectWatch.manageSubscription')
1829 this._dialog
.html(data
.returnValues
.template
);
1830 this._dialog
.wcfDialog('open');
1833 // bind event listener
1834 this._dialog
.find('.formSubmit > .jsButtonSave').data('objectID', data
.returnValues
.objectID
).data('objectType', data
.returnValues
.objectType
).click($.proxy(this._save
, this));
1835 var $enableNotification
= this._dialog
.find('input[name=enableNotification]').disable();
1837 // toggle subscription
1838 this._dialog
.find('input[name=subscribe]').change(function (event
) {
1839 var $input
= $(event
.currentTarget
);
1840 if ($input
.val() == 1) {
1841 $enableNotification
.enable();
1844 $enableNotification
.disable();
1849 var $selectedOption
= this._dialog
.find('input[name=subscribe]:checked');
1850 if ($selectedOption
.length
&& $selectedOption
.val() == 1) {
1851 $enableNotification
.enable();
1854 else if (data
.actionName
=== 'saveSubscription' && this._dialog
.is(':visible')) {
1855 this._dialog
.wcfDialog('close');
1857 this._updateSubscriptionStatus({
1858 isSubscribed
: data
.returnValues
.subscribe
,
1859 objectID
: data
.returnValues
.objectID
1863 // show notification
1864 if (this._notification
=== null) {
1865 this._notification
= new WCF
.System
.Notification(WCF
.Language
.get('wcf.global.success.edit'));
1868 this._notification
.show();
1873 * Saves the subscription.
1875 * @param object event
1877 _save: function (event
) {
1878 var $button
= this._buttons
[$(event
.currentTarget
).data('objectType')][$(event
.currentTarget
).data('objectID')];
1879 var $subscribe
= this._dialog
.find('input[name=subscribe]:checked').val();
1880 var $enableNotification
= (this._dialog
.find('input[name=enableNotification]').is(':checked')) ? 1 : 0;
1882 this._proxy
.setOption('data', {
1883 actionName
: 'saveSubscription',
1884 className
: 'wcf\\data\\user\\object\\watch\\UserObjectWatchAction',
1886 enableNotification
: $enableNotification
,
1887 objectID
: $button
.data('objectID'),
1888 objectType
: $button
.data('objectType'),
1889 subscribe
: $subscribe
1892 this._proxy
.sendRequest();
1896 * Updates subscription status and icon.
1898 * @param object data
1900 _updateSubscriptionStatus: function (data
) {
1901 var $button
= $(this._buttonSelector
+ '[data-object-id=' + data
.objectID
+ ']');
1902 var $icon
= $button
.children('.icon');
1903 if (data
.isSubscribed
) {
1904 $icon
[0].querySelector("fa-icon").setIcon("bookmark", true);
1905 $button
.data('isSubscribed', true);
1906 $button
.addClass('active');
1909 if ($button
.data('removeOnUnsubscribe')) {
1910 $button
.parent().remove();
1913 $icon
[0].querySelector("fa-icon").setIcon("bookmark");
1914 $button
.data('isSubscribed', false);
1915 $button
.removeClass('active');
1918 if (this._reloadOnUnsubscribe
) {
1919 window
.location
.reload();
1924 WCF
.System
.Event
.fireEvent('com.woltlab.wcf.objectWatch', 'updatedSubscription', data
);
1929 WCF
.User
.ObjectWatch
.Subscribe
= Class
.extend({
1930 _buttonSelector
: "",
1934 _reloadOnUnsubscribe
: false,
1935 init: function() {},
1936 _click: function() {},
1937 _success: function() {},
1938 _save: function() {},
1939 _updateSubscriptionStatus: function() {}