From: Alexander Ebert Date: Wed, 10 Dec 2014 02:17:00 +0000 (+0100) Subject: Notification dropdown overhaul X-Git-Tag: 2.1.0_Beta_1~76^2~4 X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=0d42c1c1cf742f7f02a31231461501554f3d3102;p=GitHub%2FWoltLab%2FWCF.git Notification dropdown overhaul --- diff --git a/com.woltlab.wcf/templates/headIncludeJavaScript.tpl b/com.woltlab.wcf/templates/headIncludeJavaScript.tpl index ab7d4b250e..a5c5de62b5 100644 --- a/com.woltlab.wcf/templates/headIncludeJavaScript.tpl +++ b/com.woltlab.wcf/templates/headIncludeJavaScript.tpl @@ -111,7 +111,11 @@ 'wcf.global.thousandsSeparator': '{capture assign=thousandsSeparator}{lang}wcf.global.thousandsSeparator{/lang}{/capture}{@$thousandsSeparator|encodeJS}', 'wcf.page.pagePosition': '{lang __literal=true}wcf.page.pagePosition{/lang}', 'wcf.page.sitemap': '{lang}wcf.page.sitemap{/lang}', - 'wcf.style.changeStyle': '{lang}wcf.style.changeStyle{/lang}' + 'wcf.style.changeStyle': '{lang}wcf.style.changeStyle{/lang}', + 'wcf.user.panel.markAllAsRead': '{lang}wcf.user.panel.markAllAsRead{/lang}', + 'wcf.user.panel.markAsRead': '{lang}wcf.user.panel.markAsRead{/lang}', + 'wcf.user.panel.settings': '{lang}wcf.user.panel.settings{/lang}', + 'wcf.user.panel.showAll': '{lang}wcf.user.panel.showAll{/lang}' {if MODULE_LIKE} ,'wcf.like.button.like': '{lang}wcf.like.button.like{/lang}', 'wcf.like.button.dislike': '{lang}wcf.like.button.dislike{/lang}', diff --git a/com.woltlab.wcf/templates/notificationList.tpl b/com.woltlab.wcf/templates/notificationList.tpl index 6522ea6a33..16b78e7dde 100644 --- a/com.woltlab.wcf/templates/notificationList.tpl +++ b/com.woltlab.wcf/templates/notificationList.tpl @@ -63,11 +63,11 @@
").css({position:"absolute",display:"none","word-wrap":"break-word","white-space":"pre-wrap","border-style":"solid"}).appendTo(document.body);i();var s=n.css("box-sizing")=="border-box"||n.css("-moz-box-sizing")=="border-box"||n.css("-webkit-box-sizing")=="border-box";var o=parseInt(n.css("border-top-width"))+parseInt(n.css("padding-top"))+parseInt(n.css("padding-bottom"))+parseInt(n.css("border-bottom-width"));var u=parseInt(n.css("height"),10);var a=parseInt(n.css("line-height"),10)||parseInt(n.css("font-size"),10);var f=a*2>u?a*2:u;var l=parseInt(n.css("max-height"),10)>-1?parseInt(n.css("max-height"),10):Number.MAX_VALUE;n.bind("keyup change cut paste",function(){c()});e(window).bind("resize",function(){var e=parseInt(n.width(),10);if(r.width()!==e){r.css({width:e+"px"});c()}});n.bind("blur",function(){h()});n.bind("updateHeight",function(){i();c()});e(function(){c()})})}};e.fn.flexible=function(n){if(t[n]){return t[n].apply(this,Array.prototype.slice.call(arguments,1))}else if(typeof n==="object"||!n){return t.init.apply(this,arguments)}else{e.error("Method "+n+" does not exist on jQuery.flexible")}}})(jQuery) +(function(e){var t={init:function(){var t=["paddingTop","paddingRight","paddingBottom","paddingLeft","fontSize","lineHeight","fontFamily","width","fontWeight","border-top-width","border-right-width","border-bottom-width","border-left-width","-moz-box-sizing","-webkit-box-sizing","box-sizing"];return this.each(function(){function i(){for(var e=0;e/g,">").replace(/&/g,"&").replace(/\n/g,"
");r.html(e+" ");h()}function h(){var e=r.height();var t="hidden";var i=s?e+a+o:e+a;if(i>l){i=l;t="auto"}else if(i").css({position:"absolute",display:"none","word-wrap":"break-word","white-space":"pre-wrap","border-style":"solid"}).appendTo(document.body);i();var s=n.css("box-sizing")=="border-box"||n.css("-moz-box-sizing")=="border-box"||n.css("-webkit-box-sizing")=="border-box";var o=parseInt(n.css("border-top-width"))+parseInt(n.css("padding-top"))+parseInt(n.css("padding-bottom"))+parseInt(n.css("border-bottom-width"));var u=parseInt(n.css("height"),10);var a=parseInt(n.css("line-height"),10)||parseInt(n.css("font-size"),10);var f=a*2>u?a*2:u;var l=parseInt(n.css("max-height"),10)>-1?parseInt(n.css("max-height"),10):Number.MAX_VALUE;n.bind("keyup change cut paste",function(){c()});e(window).bind("resize",function(){var e=parseInt(n.width(),10);if(r.width()!==e){r.css({width:e+"px"});c()}});n.bind("blur",function(){h()});n.bind("updateHeight",function(){i();c()});e(function(){c()})})}};e.fn.flexible=function(n){if(t[n]){return t[n].apply(this,Array.prototype.slice.call(arguments,1))}else if(typeof n==="object"||!n){return t.init.apply(this,arguments)}else{e.error("Method "+n+" does not exist on jQuery.flexible")}}})(jQuery); + +/*! perfect-scrollbar - v0.5.8 +* http://noraesae.github.com/perfect-scrollbar/ +* Copyright (c) 2014 Hyunje Alex Jun; Licensed MIT */ +(function(e){"use strict";"function"==typeof define&&define.amd?define(["jquery"],e):"object"==typeof exports?e(require("jquery")):e(jQuery)})(function(e){"use strict";function t(e){return"string"==typeof e?parseInt(e,10):~~e}var o={wheelSpeed:1,wheelPropagation:!1,swipePropagation:!0,minScrollbarLength:null,maxScrollbarLength:null,useBothWheelAxes:!1,useKeyboard:!0,suppressScrollX:!1,suppressScrollY:!1,scrollXMarginOffset:0,scrollYMarginOffset:0,includePadding:!1},n=0,r=function(){var e=n++;return function(t){var o=".perfect-scrollbar-"+e;return t===void 0?o:t+o}},l="WebkitAppearance"in document.documentElement.style;e.fn.perfectScrollbar=function(n,i){return this.each(function(){function a(e,o){var n=e+o,r=D-R;j=0>n?0:n>r?r:n;var l=t(j*(Y-D)/(D-R));M.scrollTop(l)}function s(e,o){var n=e+o,r=E-k;W=0>n?0:n>r?r:n;var l=t(W*(C-E)/(E-k));M.scrollLeft(l)}function c(e){return P.minScrollbarLength&&(e=Math.max(e,P.minScrollbarLength)),P.maxScrollbarLength&&(e=Math.min(e,P.maxScrollbarLength)),e}function u(){var e={width:I};e.left=B?M.scrollLeft()+E-C:M.scrollLeft(),N?e.bottom=_-M.scrollTop():e.top=Q+M.scrollTop(),H.css(e);var t={top:M.scrollTop(),height:A};Z?t.right=B?C-M.scrollLeft()-V-J.outerWidth():V-M.scrollLeft():t.left=B?M.scrollLeft()+2*E-C-$-J.outerWidth():$+M.scrollLeft(),G.css(t),U.css({left:W,width:k-z}),J.css({top:j,height:R-et})}function d(){M.removeClass("ps-active-x"),M.removeClass("ps-active-y"),E=P.includePadding?M.innerWidth():M.width(),D=P.includePadding?M.innerHeight():M.height(),C=M.prop("scrollWidth"),Y=M.prop("scrollHeight"),!P.suppressScrollX&&C>E+P.scrollXMarginOffset?(X=!0,I=E-F,k=c(t(I*E/C)),W=t(M.scrollLeft()*(I-k)/(C-E))):(X=!1,k=0,W=0,M.scrollLeft(0)),!P.suppressScrollY&&Y>D+P.scrollYMarginOffset?(O=!0,A=D-tt,R=c(t(A*D/Y)),j=t(M.scrollTop()*(A-R)/(Y-D))):(O=!1,R=0,j=0,M.scrollTop(0)),W>=I-k&&(W=I-k),j>=A-R&&(j=A-R),u(),X&&M.addClass("ps-active-x"),O&&M.addClass("ps-active-y")}function p(){var t,o,n=function(e){s(t,e.pageX-o),d(),e.stopPropagation(),e.preventDefault()},r=function(){H.removeClass("in-scrolling"),e(q).unbind(K("mousemove"),n)};U.bind(K("mousedown"),function(l){o=l.pageX,t=U.position().left,H.addClass("in-scrolling"),e(q).bind(K("mousemove"),n),e(q).one(K("mouseup"),r),l.stopPropagation(),l.preventDefault()}),t=o=null}function f(){var t,o,n=function(e){a(t,e.pageY-o),d(),e.stopPropagation(),e.preventDefault()},r=function(){G.removeClass("in-scrolling"),e(q).unbind(K("mousemove"),n)};J.bind(K("mousedown"),function(l){o=l.pageY,t=J.position().top,G.addClass("in-scrolling"),e(q).bind(K("mousemove"),n),e(q).one(K("mouseup"),r),l.stopPropagation(),l.preventDefault()}),t=o=null}function v(e,t){var o=M.scrollTop();if(0===e){if(!O)return!1;if(0===o&&t>0||o>=Y-D&&0>t)return!P.wheelPropagation}var n=M.scrollLeft();if(0===t){if(!X)return!1;if(0===n&&0>e||n>=C-E&&e>0)return!P.wheelPropagation}return!0}function g(e,t){var o=M.scrollTop(),n=M.scrollLeft(),r=Math.abs(e),l=Math.abs(t);if(l>r){if(0>t&&o===Y-D||t>0&&0===o)return!P.swipePropagation}else if(r>l&&(0>e&&n===C-E||e>0&&0===n))return!P.swipePropagation;return!0}function b(){function e(e){var t=e.originalEvent.deltaX,o=-1*e.originalEvent.deltaY;return(t===void 0||o===void 0)&&(t=-1*e.originalEvent.wheelDeltaX/6,o=e.originalEvent.wheelDeltaY/6),e.originalEvent.deltaMode&&1===e.originalEvent.deltaMode&&(t*=10,o*=10),t!==t&&o!==o&&(t=0,o=e.originalEvent.wheelDelta),[t,o]}function t(t){if(l||!(M.find("select:focus").length>0)){var n=e(t),r=n[0],i=n[1];o=!1,P.useBothWheelAxes?O&&!X?(i?M.scrollTop(M.scrollTop()-i*P.wheelSpeed):M.scrollTop(M.scrollTop()+r*P.wheelSpeed),o=!0):X&&!O&&(r?M.scrollLeft(M.scrollLeft()+r*P.wheelSpeed):M.scrollLeft(M.scrollLeft()-i*P.wheelSpeed),o=!0):(M.scrollTop(M.scrollTop()-i*P.wheelSpeed),M.scrollLeft(M.scrollLeft()+r*P.wheelSpeed)),d(),o=o||v(r,i),o&&(t.stopPropagation(),t.preventDefault())}}var o=!1;window.onwheel!==void 0?M.bind(K("wheel"),t):window.onmousewheel!==void 0&&M.bind(K("mousewheel"),t)}function h(){var t=!1;M.bind(K("mouseenter"),function(){t=!0}),M.bind(K("mouseleave"),function(){t=!1});var o=!1;e(q).bind(K("keydown"),function(n){if((!n.isDefaultPrevented||!n.isDefaultPrevented())&&t){for(var r=document.activeElement?document.activeElement:q.activeElement;r.shadowRoot;)r=r.shadowRoot.activeElement;if(!e(r).is(":input,[contenteditable]")){var l=0,i=0;switch(n.which){case 37:l=-30;break;case 38:i=30;break;case 39:l=30;break;case 40:i=-30;break;case 33:i=90;break;case 32:case 34:i=-90;break;case 35:i=n.ctrlKey?-Y:-D;break;case 36:i=n.ctrlKey?M.scrollTop():D;break;default:return}M.scrollTop(M.scrollTop()-i),M.scrollLeft(M.scrollLeft()+l),o=v(l,i),o&&n.preventDefault()}}})}function w(){function e(e){e.stopPropagation()}J.bind(K("click"),e),G.bind(K("click"),function(e){var o=t(R/2),n=e.pageY-G.offset().top-o,r=D-R,l=n/r;0>l?l=0:l>1&&(l=1),M.scrollTop((Y-D)*l)}),U.bind(K("click"),e),H.bind(K("click"),function(e){var o=t(k/2),n=e.pageX-H.offset().left-o,r=E-k,l=n/r;0>l?l=0:l>1&&(l=1),M.scrollLeft((C-E)*l)})}function m(){function t(){var e=window.getSelection?window.getSelection():document.getSlection?document.getSlection():{rangeCount:0};return 0===e.rangeCount?null:e.getRangeAt(0).commonAncestorContainer}function o(){r||(r=setInterval(function(){return x()?(M.scrollTop(M.scrollTop()+l.top),M.scrollLeft(M.scrollLeft()+l.left),d(),void 0):(clearInterval(r),void 0)},50))}function n(){r&&(clearInterval(r),r=null),H.removeClass("in-scrolling"),G.removeClass("in-scrolling")}var r=null,l={top:0,left:0},i=!1;e(q).bind(K("selectionchange"),function(){e.contains(M[0],t())?i=!0:(i=!1,n())}),e(window).bind(K("mouseup"),function(){i&&(i=!1,n())}),e(window).bind(K("mousemove"),function(e){if(i){var t={x:e.pageX,y:e.pageY},r=M.offset(),a={left:r.left,right:r.left+M.outerWidth(),top:r.top,bottom:r.top+M.outerHeight()};t.xa.right-3?(l.left=5,H.addClass("in-scrolling")):l.left=0,t.ya.top+3-t.y?-5:-20,G.addClass("in-scrolling")):t.y>a.bottom-3?(l.top=5>t.y-a.bottom+3?5:20,G.addClass("in-scrolling")):l.top=0,0===l.top&&0===l.left?n():o()}})}function T(t,o){function n(e,t){M.scrollTop(M.scrollTop()-t),M.scrollLeft(M.scrollLeft()-e),d()}function r(){h=!0}function l(){h=!1}function i(e){return e.originalEvent.targetTouches?e.originalEvent.targetTouches[0]:e.originalEvent}function a(e){var t=e.originalEvent;return t.targetTouches&&1===t.targetTouches.length?!0:t.pointerType&&"mouse"!==t.pointerType&&t.pointerType!==t.MSPOINTER_TYPE_MOUSE?!0:!1}function s(e){if(a(e)){w=!0;var t=i(e);p.pageX=t.pageX,p.pageY=t.pageY,f=(new Date).getTime(),null!==b&&clearInterval(b),e.stopPropagation()}}function c(e){if(!h&&w&&a(e)){var t=i(e),o={pageX:t.pageX,pageY:t.pageY},r=o.pageX-p.pageX,l=o.pageY-p.pageY;n(r,l),p=o;var s=(new Date).getTime(),c=s-f;c>0&&(v.x=r/c,v.y=l/c,f=s),g(r,l)&&(e.stopPropagation(),e.preventDefault())}}function u(){!h&&w&&(w=!1,clearInterval(b),b=setInterval(function(){return x()?.01>Math.abs(v.x)&&.01>Math.abs(v.y)?(clearInterval(b),void 0):(n(30*v.x,30*v.y),v.x*=.8,v.y*=.8,void 0):(clearInterval(b),void 0)},10))}var p={},f=0,v={},b=null,h=!1,w=!1;t&&(e(window).bind(K("touchstart"),r),e(window).bind(K("touchend"),l),M.bind(K("touchstart"),s),M.bind(K("touchmove"),c),M.bind(K("touchend"),u)),o&&(window.PointerEvent?(e(window).bind(K("pointerdown"),r),e(window).bind(K("pointerup"),l),M.bind(K("pointerdown"),s),M.bind(K("pointermove"),c),M.bind(K("pointerup"),u)):window.MSPointerEvent&&(e(window).bind(K("MSPointerDown"),r),e(window).bind(K("MSPointerUp"),l),M.bind(K("MSPointerDown"),s),M.bind(K("MSPointerMove"),c),M.bind(K("MSPointerUp"),u)))}function y(){M.bind(K("scroll"),function(){d()})}function L(){M.unbind(K()),e(window).unbind(K()),e(q).unbind(K()),M.data("perfect-scrollbar",null),M.data("perfect-scrollbar-update",null),M.data("perfect-scrollbar-destroy",null),U.remove(),J.remove(),H.remove(),G.remove(),M=H=G=U=J=X=O=E=D=C=Y=k=W=_=N=Q=R=j=V=Z=$=B=K=null}function S(){d(),y(),p(),f(),w(),m(),b(),(ot||nt)&&T(ot,nt),P.useKeyboard&&h(),M.data("perfect-scrollbar",M),M.data("perfect-scrollbar-update",d),M.data("perfect-scrollbar-destroy",L)}var P=e.extend(!0,{},o),M=e(this),x=function(){return!!M};if("object"==typeof n?e.extend(!0,P,n):i=n,"update"===i)return M.data("perfect-scrollbar-update")&&M.data("perfect-scrollbar-update")(),M;if("destroy"===i)return M.data("perfect-scrollbar-destroy")&&M.data("perfect-scrollbar-destroy")(),M;if(M.data("perfect-scrollbar"))return M.data("perfect-scrollbar");M.addClass("ps-container");var E,D,C,Y,X,k,W,I,O,R,j,A,B="rtl"===M.css("direction"),K=r(),q=this.ownerDocument||document,H=e("
").appendTo(M),U=e("
").appendTo(H),_=t(H.css("bottom")),N=_===_,Q=N?null:t(H.css("top")),z=t(H.css("borderLeftWidth"))+t(H.css("borderRightWidth")),F=t(H.css("marginLeft"))+t(H.css("marginRight")),G=e("
").appendTo(M),J=e("
").appendTo(G),V=t(G.css("right")),Z=V===V,$=Z?null:t(G.css("left")),et=t(G.css("borderTopWidth"))+t(G.css("borderBottomWidth")),tt=t(G.css("marginTop"))+t(G.css("marginBottom")),ot="ontouchstart"in window||window.DocumentTouch&&document instanceof window.DocumentTouch,nt=null!==window.navigator.msMaxTouchPoints;return S(),M})}}); diff --git a/wcfsetup/install/files/js/WCF.User.js b/wcfsetup/install/files/js/WCF.User.js index 718fa274c7..09c590ea60 100644 --- a/wcfsetup/install/files/js/WCF.User.js +++ b/wcfsetup/install/files/js/WCF.User.js @@ -102,6 +102,408 @@ WCF.User.Login = Class.extend({ } }); +/** + * Namespace for User Panel classes. + */ +WCF.User.Panel = { }; + +/** + * Abstract implementation for user panel items providing an interactive dropdown. + * + * @param jQuery triggerElement + * @param string identifier + * @param object options + */ +WCF.User.Panel.Abstract = Class.extend({ + /** + * counter badge + * @var jQuery + */ + _badge: null, + + /** + * interactive dropdown instance + * @var WCF.Dropdown.Interactive.Instance + */ + _dropdown: null, + + /** + * dropdown identifier + * @var string + */ + _identifier: '', + + /** + * true if data should be loaded using an AJAX request + * @var boolean + */ + _loadData: true, + + /** + * header icon to mark all items belonging to this user panel item as read + * @var jQuery + */ + _markAllAsReadLink: null, + + /** + * list of options required to dropdown initialization and UI features + * @var object + */ + _options: { }, + + /** + * action proxy + * @var WCF.Action.Proxy + */ + _proxy: null, + + /** + * trigger element + * @var jQuery + */ + _triggerElement: null, + + /** + * Initializes the WCF.User.Panel.Abstract class. + * + * @param jQuery triggerElement + * @param string identifier + * @param object options + */ + init: function(triggerElement, identifier, options) { + this._dropdown = null; + this._identifier = identifier; + this._triggerElement = triggerElement; + this._options = options; + + this._proxy = new WCF.Action.Proxy({ + success: $.proxy(this._success, this) + }); + + this._triggerElement.click($.proxy(this.toggle, this)); + + var $badge = this._triggerElement.find('span.badge'); + if ($badge.length) { + this._badge = $badge; + } + }, + + /** + * Toggles the interactive dropdown. + * + * @param object event + * @return boolean + */ + toggle: function(event) { + event.preventDefault(); + + if (this._dropdown === null) { + this._dropdown = this._initDropdown(); + } + + if (this._dropdown.toggle() && this._loadData) { + this._load(); + } + + return false; + }, + + /** + * Initializes the dropdown on first usage. + * + * @return WCF.Dropdown.Interactive.Instance + */ + _initDropdown: function() { + var $dropdown = WCF.Dropdown.Interactive.Handler.create(this._triggerElement, this._identifier, this._options); + $('
  • ' + WCF.Language.get('wcf.global.loading') + '
  • ').appendTo($dropdown.getItemList()); + + return $dropdown; + }, + + /** + * Loads item list data via AJAX. + */ + _load: function() { + // override this in your own implementation to fetch display data + }, + + /** + * Handles successful AJAX requests. + * + * @param object data + */ + _success: function(data) { + if (data.returnValues.template !== undefined) { + var $itemList = this._dropdown.getItemList().empty(); + $(data.returnValues.template).appendTo($itemList); + + if (!$itemList.children().length) { + $('
  • ' + this._options.noItems + '
  • ').appendTo($itemList); + } + + if (this._options.enableMarkAsRead) { + var $outstandingItems = this._dropdown.getItemList().children('.interactiveDropdownItemOutstanding'); + if (this._markAllAsReadLink === null && $outstandingItems.length && this._options.markAllAsReadConfirmMessage) { + var $button = this._markAllAsReadLink = $('
  • ').appendTo(this._dropdown.getLinkList()); + $button.click((function(event) { + this._dropdown.close(); + + WCF.System.Confirmation.show(this._options.markAllAsReadConfirmMessage, (function(action) { + if (action === 'confirm') { + this._markAllAsRead(); + } + }).bind(this)); + + return false; + }).bind(this)); + } + + $outstandingItems.each((function(index, item) { + var $item = $(item).addClass('interactiveDropdownItemOutstandingIcon'); + var $objectID = $item.data('objectID'); + + var $button = $('
    ').appendTo($item); + $button.click((function(event) { + this._markAsRead(event, $objectID); + + return false; + }).bind(this)); + }).bind(this)); + } + + this._dropdown.getItemList().children().each(function(index, item) { + var $item = $(item); + var $link = $item.data('link'); + + if ($link) { + if ($.browser.msie) { + $item.click(function(event) { + if (event.target.tagName !== 'A') { + window.location = $link; + + return false; + } + }); + } + else { + $item.addClass('interactiveDropdownItemShadow'); + $('').appendTo($item); + } + + if ($item.data('linkReplaceAll')) { + $item.find('> .box32 a:not(.userLink)').prop('href', $link); + } + } + }); + + this._dropdown.rebuildScrollbar(); + } + + if (data.returnValues.totalCount !== undefined) { + this.updateBadge(data.returnValues.totalCount); + } + + if (this._options.enableMarkAsRead) { + if (data.returnValues.markAsRead) { + var $item = this._dropdown.getItemList().children('li[data-object-id=' + data.returnValues.markAsRead + ']'); + if ($item.length) { + $item.removeClass('interactiveDropdownItemOutstanding').data('isRead', true); + $item.children('.interactiveDropdownItemMarkAsRead').remove(); + } + } + else if (data.returnValues.markAllAsRead) { + this.resetItems(); + this.updateBadge(0); + } + } + }, + + /** + * Marks an item as read. + * + * @param object event + * @param integer objectID + */ + _markAsRead: function(event, objectID) { + // override this in your own implementation to mark an item as read + }, + + /** + * Marks all items as read. + */ + _markAllAsRead: function() { + // override this in your own implementation to mark all items as read + }, + + /** + * Updates the badge's count or removes it if count reaches zero. Passing a negative number is undefined. + * + * @param integer count + */ + updateBadge: function(count) { + count = parseInt(count) || 0; + + if (count) { + if (this._badge === null) { + this._badge = $('').appendTo(this._triggerElement); + } + + this._badge.text(count); + } + else if (this._badge !== null) { + this._badge.remove(); + } + + if (this._options.enableMarkAsRead) { + if (!count && this._markAllAsReadLink !== null) { + this._markAllAsReadLink.remove(); + this._markAllAsReadLink = null; + } + } + }, + + /** + * Resets the dropdown's inner item list. + */ + resetItems: function() { + this._dropdown.resetItems(); + } +}); + +/** + * User Panel implementation for user notifications. + * + * @see WCF.User.Panel.Abstract + */ +WCF.User.Panel.Notification = WCF.User.Panel.Abstract.extend({ + /** + * favico instance + * @var Favico + */ + _favico: null, + + /** + * @see WCF.User.Panel.Abstract.init() + */ + init: function(options) { + options.enableMarkAsRead = true; + + this._super($('#userNotifications'), 'userNotifications', options); + + try { + this._favico = new Favico({ + animation: 'none', + type: 'circle' + }); + + if (this._badge !== null) { + var $count = parseInt(this._badge.text()) || 0; + this._favico.badge($count); + } + } + catch (e) { + console.debug("[WCF.User.Panel.Notification] Failed to initialized Favico: " + e.message); + } + + WCF.System.PushNotification.addCallback('userNotificationCount', $.proxy(this.updateUserNotificationCount, this)); + }, + + /** + * @see WCF.User.Panel.Abstract._initDropdown() + */ + _initDropdown: function() { + var $dropdown = this._super(); + + $('
  • ').appendTo($dropdown.getLinkList()); + + return $dropdown; + }, + + /** + * @see WCF.User.Panel.Abstract._load() + */ + _load: function() { + this._proxy.setOption('data', { + actionName: 'getOutstandingNotifications', + className: 'wcf\\data\\user\\notification\\UserNotificationAction' + }); + this._proxy.sendRequest(); + }, + + /** + * @see WCF.User.Panel.Abstract._markAsRead() + */ + _markAsRead: function(event, objectID) { + this._proxy.setOption('data', { + actionName: 'markAsConfirmed', + className: 'wcf\\data\\user\\notification\\UserNotificationAction', + objectIDs: [ objectID ] + }); + this._proxy.sendRequest(); + }, + + /** + * @see WCF.User.Panel.Abstract._markAllAsRead() + */ + _markAllAsRead: function(event) { + this._proxy.setOption('data', { + actionName: 'markAllAsConfirmed', + className: 'wcf\\data\\user\\notification\\UserNotificationAction' + }); + this._proxy.sendRequest(); + }, + + /** + * @see WCF.User.Panel.Abstract._success() + */ + _success: function(data) { + this._super(data); + + if (data.actionName === 'markAllAsConfirmed') { + this.resetItems(); + this.updateBadge(0); + } + }, + + /** + * @see WCF.User.Panel.Abstract.resetItems() + */ + resetItems: function() { + this._super(); + + if (this._markAllAsReadLink) { + this._markAllAsReadLink.remove(); + this._markAllAsReadLink = null; + } + }, + + /** + * @see WCF.User.Panel.Abstract.updateBadge() + */ + updateBadge: function(count) { + count = parseInt(count) || 0; + + if (this._favico !== null) { + this._favico.badge(count); + } + + this._super(count); + }, + + /** + * Updates the badge counter and resets the dropdown's item list. + * + * @param integer count + */ + updateUserNotificationCount: function(count) { + if (this._dropdown !== null) { + this._dropdown.resetItems(); + } + + this.updateBadge(count); + } +}); + /** * Quick login box */ @@ -1295,15 +1697,12 @@ WCF.Notification.List = Class.extend({ _convertList: function() { $('.userNotificationItemList > .notificationItem').each((function(index, item) { var $item = $(item); - var $isConfirmed = $item.data('isConfirmed'); - if (!$isConfirmed) { - $item.find('a:not(.userLink)').prop('href', $item.data('confirmLink')); + if (!$item.data('isRead')) { + $item.find('a:not(.userLink)').prop('href', $item.data('link')); - if (!$.browser.mobile) { - var $markAsConfirmed = $('').prependTo($item.find('> div.box24 > .framed')); - $markAsConfirmed.click($.proxy(this._markAsConfirmed, this)); - } + var $markAsConfirmed = $('').appendTo($item); + $markAsConfirmed.click($.proxy(this._markAsConfirmed, this)); } }).bind(this)); @@ -1318,7 +1717,7 @@ WCF.Notification.List = Class.extend({ _markAsConfirmed: function(event) { event.preventDefault(); - var $notificationID = $(event.currentTarget).parents('.notificationItem:eq(0)').data('notificationID'); + var $notificationID = $(event.currentTarget).parents('.notificationItem:eq(0)').data('objectID'); this._proxy.setOption('data', { actionName: 'markAsConfirmed', @@ -1338,11 +1737,11 @@ WCF.Notification.List = Class.extend({ * @param jQuery jqXHR */ _success: function(data, textStatus, jqXHR) { - var $item = $('.userNotificationItemList > .notificationItem[data-notification-id=' + data.returnValues.notificationID + ']'); + var $item = $('.userNotificationItemList > .notificationItem[data-object-id=' + data.returnValues.markAsRead + ']'); - $item.data('isConfirmed', true); - $item.find('.notificationItemMarkAsConfirmed').remove(); + $item.data('isRead', true); $item.find('.newContentBadge').remove(); + $item.find('.notificationItemMarkAsConfirmed').remove(); $item.removeClass('notificationUnconfirmed'); } }); diff --git a/wcfsetup/install/files/js/WCF.js b/wcfsetup/install/files/js/WCF.js index 3a885b2d0b..2617e443d4 100755 --- a/wcfsetup/install/files/js/WCF.js +++ b/wcfsetup/install/files/js/WCF.js @@ -1029,6 +1029,8 @@ WCF.Dropdown = { } } + WCF.Dropdown.Interactive.Handler.closeAll(); + if (event !== null) { event.stopPropagation(); return false; @@ -1294,6 +1296,385 @@ WCF.Dropdown = { } }; +/** + * Namespace for interactive dropdowns. + */ +WCF.Dropdown.Interactive = { }; + +/** + * General interface to create and manage interactive dropdowns. + */ +WCF.Dropdown.Interactive.Handler = { + /** + * global container for interactive dropdowns + * @var jQuery + */ + _dropdownContainer: null, + + /** + * list of dropdown instances by identifier + * @var object + */ + _dropdownMenus: { }, + + /** + * Creates a new interactive dropdown instance. + * + * @param jQuery triggerElement + * @param string identifier + * @param object options + * @return WCF.Dropdown.Interactive.Instance + */ + create: function(triggerElement, identifier, options) { + if (this._dropdownContainer === null) { + this._dropdownContainer = $('