Notification dropdown overhaul
authorAlexander Ebert <ebert@woltlab.com>
Wed, 10 Dec 2014 02:17:00 +0000 (03:17 +0100)
committerAlexander Ebert <ebert@woltlab.com>
Wed, 10 Dec 2014 02:17:00 +0000 (03:17 +0100)
14 files changed:
com.woltlab.wcf/templates/headIncludeJavaScript.tpl
com.woltlab.wcf/templates/notificationList.tpl
com.woltlab.wcf/templates/notificationListOustanding.tpl [deleted file]
com.woltlab.wcf/templates/notificationListUserPanel.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/userPanel.tpl
wcfsetup/install/files/js/WCF.Assets.js
wcfsetup/install/files/js/WCF.User.js
wcfsetup/install/files/js/WCF.js
wcfsetup/install/files/lib/data/user/notification/UserNotificationAction.class.php
wcfsetup/install/files/style/dropdown.less
wcfsetup/install/files/style/layout.less
wcfsetup/install/files/style/user.less
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

index ab7d4b250e6f7dfee81cbbe21af5c307e2a786dc..a5c5de62b53d87c0203237368fe2df5a97274cb9 100644 (file)
                '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}',
index 6522ea6a3381c20d5b9ee557c36f7d1b3f26f3e8..16b78e7ddeb9cad61c966eb91b4a06e0c4c1bbb7 100644 (file)
                        <div class="container marginTop">
                                <ul class="containerList userNotificationItemList">
                {/if}
-                               <li class="jsNotificationItem notificationItem{if $notification[authors] > 1} groupedNotificationItem{/if}{if !$notification[event]->isConfirmed()} notificationUnconfirmed{/if}" data-notification-id="{@$notification[notificationID]}" data-link="{$notification[event]->getLink()}" data-confirm-link="{link controller='NotificationConfirm' id=$notification[notificationID]}{/link}" data-is-grouped="{if $notification[authors] > 1}true{else}false{/if}" data-is-confirmed="{if $notification[event]->isConfirmed()}true{else}false{/if}">
-                                       <div class="box24">
+                               <li class="jsNotificationItem notificationItem{if $notification[authors] > 1} groupedNotificationItem{/if}{if !$notification[event]->isConfirmed()} notificationUnconfirmed{/if}"  data-link="{if $notification[event]->isConfirmed()}{$notification[event]->getLink()}{else}{link controller='NotificationConfirm' id=$notification[notificationID]}{/link}{/if}" data-link-replace-all="{if $notification[event]->isConfirmed()}false{else}true{/if}" data-object-id="{@$notification[notificationID]}" data-is-read="{if $notification[event]->isConfirmed()}true{else}false{/if}" data-is-grouped="{if $notification[authors] > 1}true{else}false{/if}">
+                                       <div class="box32">
                                                {if $notification[authors] < 2}
                                                        <div class="framed">
-                                                               {@$notification[event]->getAuthor()->getAvatar()->getImageTag(24)}
+                                                               {@$notification[event]->getAuthor()->getAvatar()->getImageTag(32)}
                                                        </div>
                                                        
                                                        <div class="details">
@@ -79,7 +79,7 @@
                                                        </div>
                                                {else}
                                                        <div class="framed">
-                                                               <span class="icon icon24 fa-users"></span>
+                                                               <span class="icon icon32 fa-users"></span>
                                                        </div>
                                                        
                                                        <div class="details">
diff --git a/com.woltlab.wcf/templates/notificationListOustanding.tpl b/com.woltlab.wcf/templates/notificationListOustanding.tpl
deleted file mode 100644 (file)
index e8bae59..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-{foreach from=$notifications[notifications] item=notification}
-       <li class="jsNotificationItem notificationItem{if $notification[event]->getAuthors()|count > 1} groupedNotificationItem{/if}{if !$notification[event]->isConfirmed()} notificationUnconfirmed{/if}" data-link="{$notification[event]->getLink()}" data-confirm-link="{link controller='NotificationConfirm' id=$notification[notificationID]}{/link}" data-notification-id="{@$notification[notificationID]}" data-is-confirmed="{if $notification[event]->isConfirmed()}true{else}false{/if}">
-               <span class="box24">
-                       <div class="framed">
-                               {if $notification[event]->getAuthors()|count < 2}
-                                       {@$notification[event]->getAuthor()->getAvatar()->getImageTag(24)}
-                               {else}
-                                       <span class="icon icon24 fa-users"></span>
-                               {/if}
-                       </div>
-                       
-                       <div>
-                               <h3>{if !$notification[event]->isConfirmed()}<span class="badge label newContentBadge">{lang}wcf.message.new{/lang}</span>{/if} {@$notification[event]->getMessage()}</h3>
-                               <small>{@$notification[time]|time}</small>
-                       </div>
-               </span>
-       </li>
-{/foreach}
\ No newline at end of file
diff --git a/com.woltlab.wcf/templates/notificationListUserPanel.tpl b/com.woltlab.wcf/templates/notificationListUserPanel.tpl
new file mode 100644 (file)
index 0000000..0e5a481
--- /dev/null
@@ -0,0 +1,18 @@
+{foreach from=$notifications[notifications] item=notification}
+       <li class="notificationItem{if $notification[event]->getAuthors()|count > 1} groupedNotificationItem{/if}{if !$notification[event]->isConfirmed()} interactiveDropdownItemOutstanding{/if}" data-link="{if $notification[event]->isConfirmed()}{$notification[event]->getLink()}{else}{link controller='NotificationConfirm' id=$notification[notificationID]}{/link}{/if}" data-link-replace-all="{if $notification[event]->isConfirmed()}false{else}true{/if}" data-object-id="{@$notification[notificationID]}" data-is-read="{if $notification[event]->isConfirmed()}true{else}false{/if}">
+               <div class="box32">
+                       <div class="framed">
+                               {if $notification[event]->getAuthors()|count < 2}
+                                       {@$notification[event]->getAuthor()->getAvatar()->getImageTag(32)}
+                               {else}
+                                       <span class="icon icon32 fa-users"></span>
+                               {/if}
+                       </div>
+                       
+                       <div>
+                               <h3>{@$notification[event]->getMessage()}</h3>
+                               <small>{@$notification[time]|time}</small>
+                       </div>
+               </div>
+       </li>
+{/foreach}
\ No newline at end of file
index a1adbde952e812892975675fd38bfced90af2983..b7f9d5a5edc4fbfa49ec1c714dda19683fe670b2 100644 (file)
                                <script data-relocate="true">
                                        //<![CDATA[
                                        $(function() {
-                                               WCF.Language.addObject({
-                                                       'wcf.user.notification.count': '{lang}wcf.user.notification.count{/lang}',
-                                                       'wcf.user.notification.markAsConfirmed': '{lang}wcf.user.notification.markAsConfirmed{/lang}',
-                                                       'wcf.user.notification.markAllAsConfirmed': '{lang}wcf.user.notification.markAllAsConfirmed{/lang}',
-                                                       'wcf.user.notification.markAllAsConfirmed.confirmMessage': '{lang}wcf.user.notification.markAllAsConfirmed.confirmMessage{/lang}',
-                                                       'wcf.user.notification.noMoreNotifications': '{lang}wcf.user.notification.noMoreNotifications{/lang}',
-                                                       'wcf.user.notification.showAll': '{lang}wcf.user.notification.showAll{/lang}'
+                                               new WCF.User.Panel.Notification({
+                                                       markAllAsReadConfirmMessage: '{lang}wcf.user.notification.markAllAsConfirmed.confirmMessage{/lang}',
+                                                       noItems: '{lang}wcf.user.notification.noMoreNotifications{/lang}',
+                                                       settingsLink: '{link controller='NotificationSettings' encode=false}{/link}',
+                                                       showAllLink: '{link controller='NotificationList' encode=false}{/link}',
+                                                       title: '{lang}wcf.user.notification.notifications{/lang}'
                                                });
-                                               
-                                               new WCF.Notification.UserPanel('{link controller='NotificationList' encode=false}{/link}');
                                        });
                                        //]]>
                                </script>
index e967c8394048bf788c3f8c00a8b3cf14d078278a..effa3d0938028f07ac54c023382b7f847cdd2817 100644 (file)
@@ -62,4 +62,9 @@ window.matchMedia||(window.matchMedia=function(){"use strict";var e=window.style
  * Released under the MIT license.
  * http://flaviusmatis.github.com/license.html
  */
-(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<t.length;e++){r.css(t[e],n.css(t[e]))}}function c(){var e=n.val().replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/&/g,"&").replace(/\n/g,"<br/>");r.html(e+"&nbsp;");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<f){i=f}if(n.height()!==i){n.css({overflow:t,height:i+"px"})}}if(this.type!=="textarea")return false;var n=e(this).css({resize:"none",overflow:"hidden"});var r=e("<div></div>").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<t.length;e++){r.css(t[e],n.css(t[e]))}}function c(){var e=n.val().replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/&/g,"&").replace(/\n/g,"<br/>");r.html(e+"&nbsp;");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<f){i=f}if(n.height()!==i){n.css({overflow:t,height:i+"px"})}}if(this.type!=="textarea")return false;var n=e(this).css({resize:"none",overflow:"hidden"});var r=e("<div></div>").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.x<a.left+3?(l.left=-5,H.addClass("in-scrolling")):t.x>a.right-3?(l.left=5,H.addClass("in-scrolling")):l.left=0,t.y<a.top+3?(l.top=5>a.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("<div class='ps-scrollbar-x-rail'>").appendTo(M),U=e("<div class='ps-scrollbar-x'>").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("<div class='ps-scrollbar-y-rail'>").appendTo(M),J=e("<div class='ps-scrollbar-y'>").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})}});
index 718fa274c7e0ad9601fc7b2be609ae68934c1415..09c590ea60ae172764fb34244bd7656927e5a73e 100644 (file)
@@ -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);
+               $('<li class="loading"><span class="icon icon24 fa-spinner" /> <span>' + WCF.Language.get('wcf.global.loading') + '</span></li>').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) {
+                               $('<li class="noItems">' + this._options.noItems + '</li>').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 = $('<li class="interactiveDropdownItemMarkAllAsRead"><a href="#" title="' + WCF.Language.get('wcf.user.panel.markAllAsRead') + '" class="jsTooltip"><span class="icon icon16 fa-check" /></a></li>').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 = $('<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);
+                                       $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');
+                                               $('<a href="' + $link + '" class="interactiveDropdownItemShadowLink" />').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 = $('<span class="badge badgeInverse" />').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();
+               
+               $('<li><a href="' + this._options.settingsLink + '" title="' + WCF.Language.get('wcf.user.panel.settings') + '" class="jsTooltip"><span class="icon icon16 fa-cog" /></a></li>').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 = $('<a href="#" class="icon icon24 fa-check green notificationItemMarkAsConfirmed jsTooltip" title="' + WCF.Language.get('wcf.user.notification.markAsConfirmed') + '" />').prependTo($item.find('> div.box24 > .framed'));
-                                       $markAsConfirmed.click($.proxy(this._markAsConfirmed, this));
-                               }
+                               var $markAsConfirmed = $('<a href="#" class="icon icon24 fa-check notificationItemMarkAsConfirmed jsTooltip" title="' + WCF.Language.get('wcf.user.notification.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');
        }
 });
index 3a885b2d0bfbb3c167b27094a2e25656c00d2d7c..2617e443d4c1a90e6646aecbe13bfd13deb1b597 100755 (executable)
@@ -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<WCF.Dropdown.Interactive.Instance>
+        */
+       _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 = $('<div class="dropdownMenuContainer" />').appendTo(document.body);
+                       WCF.CloseOverlayHandler.addCallback('WCF.Dropdown.Interactive.Handler', $.proxy(this.closeAll, this));
+               }
+               
+               var $instance = new WCF.Dropdown.Interactive.Instance(this._dropdownContainer, triggerElement, identifier, options);
+               this._dropdownMenus[identifier] = $instance;
+               
+               return $instance;
+       },
+       
+       /**
+        * Opens an interactive dropdown, returns false if identifier is unknown.
+        * 
+        * @param       string          identifier
+        * @return      boolean
+        */
+       open: function(identifier) {
+               if (this._dropdownMenus[identifier]) {
+                       this._dropdownMenus[identifier].open();
+                       
+                       return true;
+               }
+               
+               return false;
+       },
+       
+       /**
+        * Closes an interactive dropdown, returns false if identifier is unknown.
+        * 
+        * @param       string          identifier
+        * @return      boolean
+        */
+       close: function(identifier) {
+               if (this._dropdownMenus[identifier]) {
+                       this._dropdownMenus[identifier].close();
+                       
+                       return true;
+               }
+               
+               return false;
+       },
+       
+       /**
+        * Closes all interactive dropdowns.
+        */
+       closeAll: function() {
+               $.each(this._dropdownMenus, function(identifier, instance) {
+                       instance.close();
+               });
+       }
+};
+
+/**
+ * Represents and manages a single interactive dropdown instance.
+ * 
+ * @param      jQuery          dropdownContainer
+ * @param      jQuery          triggerElement
+ * @param      string          identifier
+ * @param      object          options
+ */
+WCF.Dropdown.Interactive.Instance = Class.extend({
+       /**
+        * dropdown container
+        * @var jQuery
+        */
+       _container: null,
+       
+       /**
+        * inner item list
+        * @var jQuery
+        */
+       _itemList: null,
+       
+       /**
+        * header link list
+        * @var jQuery
+        */
+       _linkList: null,
+       
+       /**
+        * arrow pointer
+        * @var jQuery
+        */
+       _pointer: null,
+       
+       /**
+        * trigger element
+        * @var jQuery
+        */
+       _triggerElement: null,
+       
+       /**
+        * Represents and manages a single interactive dropdown instance.
+        * 
+        * @param       jQuery          dropdownContainer
+        * @param       jQuery          triggerElement
+        * @param       string          identifier
+        * @param       object          options
+        */
+       init: function(dropdownContainer, triggerElement, identifier, options) {
+               this._triggerElement = triggerElement;
+               
+               this._container = $('<div class="interactiveDropdown" data-source="' + identifier + '" />').click(function(event) { event.stopPropagation(); });
+               
+               var $header = $('<div class="interactiveDropdownHeader" />').appendTo(this._container);
+               $('<span class="interactiveDropdownTitle">' + options.title + '</span>').appendTo($header);
+               this._linkList = $('<ul class="interactiveDropdownLinks"></ul>').appendTo($header);
+               
+               var $itemContainer = $('<div class="interactiveDropdownItemsContainer" />').appendTo(this._container);
+               this._itemList = $('<ul class="interactiveDropdownItems" />').appendTo($itemContainer);
+               
+               $('<a href="' + options.showAllLink + '" class="interactiveDropdownShowAll">' + WCF.Language.get('wcf.user.panel.showAll') + '</a>').appendTo(this._container);
+               
+               this._pointer = $('<span class="pointer"><span /></span>').appendTo(this._container);
+               
+               if (!$.browser.mobile) {
+                       // use jQuery scrollbar on desktop, mobile browsers have a similar display built-in
+                       $itemContainer.perfectScrollbar({
+                               suppressScrollX: true
+                       });
+               }
+               
+               this._container.appendTo(dropdownContainer);
+       },
+       
+       /**
+        * Returns the dropdown container.
+        * 
+        * @return      jQuery
+        */
+       getContainer: function() {
+               return this._container;
+       },
+       
+       /**
+        * Returns the inner item list.
+        * 
+        * @return      jQuery
+        */
+       getItemList: function() {
+               return this._itemList;
+       },
+       
+       /**
+        * Returns the header link list.
+        * 
+        * @return      jQuery
+        */
+       getLinkList: function() {
+               return this._linkList;
+       },
+       
+       /**
+        * Opens the dropdown.
+        */
+       open: function() {
+               WCF.Dropdown._closeAll();
+               
+               this._container.addClass('open');
+               
+               this.render();
+       },
+       
+       /**
+        * Closes the dropdown
+        */
+       close: function() {
+               this._container.removeClass('open');
+       },
+       
+       /**
+        * Toggles the dropdown state, returns true if dropdown is open afterwards, else false.
+        * 
+        * @return      boolean
+        */
+       toggle: function() {
+               if (this._container.hasClass('open')) {
+                       this.close();
+                       
+                       return false;
+               }
+               else {
+                       this.open();
+                       
+                       return true;
+               }
+       },
+       
+       /**
+        * Resets the inner item list and closes the dropdown.
+        */
+       resetItems: function() {
+               this._itemList.empty();
+               
+               this.close();
+       },
+       
+       /**
+        * Renders the dropdown.
+        */
+       render: function() {
+               var $pageDirection = WCF.Language.get('wcf.global.pageDirection');
+               
+               if ($('html').css('caption-side') === 'bottom') {
+                       this._renderMobile($pageDirection);
+               }
+               else {
+                       this._renderDesktop($pageDirection);
+               }
+       },
+       
+       /**
+        * Rebuilds the desktop scrollbar.
+        */
+       rebuildScrollbar: function() {
+               if (!$.browser.mobile) {
+                       var $itemContainer = this._itemList.parent();
+                       
+                       // do NOT use 'update', seems to be broken
+                       $itemContainer.perfectScrollbar('destroy');
+                       $itemContainer.perfectScrollbar({
+                               suppressScrollX: true
+                       });
+               }
+       },
+       
+       /**
+        * Renders the dropdown on mobile devices.
+        * 
+        * @param       string          pageDirection
+        */
+       _renderMobile: function(pageDirection) {
+               var $elementDimensions = this._triggerElement.getDimensions('outer');
+               var $elementHalfWidth = Math.floor($elementDimensions.width / 2);
+               var $elementOffsets = this._triggerElement.getOffsets('offset');
+               var $pointerHalfWidth = Math.floor(this._pointer.outerWidth() / 2);
+               
+               this._container.css({
+                       top: $elementOffsets.top + $elementDimensions.height + 'px'
+               });
+               
+               this._pointer.css({
+                       left: ($elementOffsets.left + $elementHalfWidth) - $pointerHalfWidth + 'px'
+               });
+       },
+       
+       /**
+        * Renders the dropdown on desktops.
+        * 
+        * @param       string          pageDirection
+        */
+       _renderDesktop: function(pageDirection) {
+               var $elementDimensions = this._triggerElement.getDimensions('outer');
+               var $elementOffsets = this._triggerElement.getOffsets('offset');
+               var $dropdownDimensions = this._container.getDimensions();
+               var $pageWidth = $(window).width();
+               
+               var $left = null;
+               var $right = null;
+               if (pageDirection === 'ltr') {
+                       $left = this._getPositionLeft($elementOffsets, $dropdownDimensions, $pageWidth);
+                       
+                       if (!$left.result) {
+                               $right = this._getPositionRight($elementOffsets, $dropdownDimensions, $elementDimensions);
+                               
+                               if ($right.result) {
+                                       $left = null;
+                               }
+                               else {
+                                       $right = null;
+                               }
+                       }
+               }
+               else {
+                       $right = this._getPositionRight($elementOffsets, $dropdownDimensions, $elementDimensions);
+                       
+                       if (!$right.result) {
+                               $left = this._getPositionLeft($elementOffsets, $dropdownDimensions, $pageWidth);
+                               if ($left.result) {
+                                       $right = null;
+                               }
+                               else {
+                                       $left = null;
+                               }
+                       }
+               }
+               
+               if ($right === null) {
+                       // align to the left
+                       this._container.css({
+                               left: $left.left + 'px',
+                               top: $elementOffsets.top + $elementDimensions.height + 'px'
+                       });
+                       
+                       this._pointer.css({
+                               left: '4px'
+                       });
+               }
+               else {
+                       // align to the right
+                       this._container.css({
+                               right: $right.right + 'px',
+                               top: $elementOffsets.top + $elementDimensions.height + 'px'
+                       });
+                       
+                       this._pointer.css({
+                               right: '4px'
+                       });
+               }
+       },
+       
+       /**
+        * Calculates the dropdown position aligned with its left side.
+        * 
+        * @param       object          elementOffsets
+        * @param       object          dropdownDimensions
+        * @param       integer         pageWidth
+        * @return      object
+        */
+       _getPositionLeft: function(elementOffsets, dropdownDimensions, pageWidth) {
+               var $left = elementOffsets.left;
+               var $right = elementOffsets.left + dropdownDimensions.width;
+               
+               return {
+                       left: $left,
+                       result: ($right < pageWidth)
+               };
+       },
+       
+       /**
+        * Calculates the dropdown position aligned with its right side.
+        * 
+        * @param       object          elementOffsets
+        * @param       object          dropdownDimensions
+        * @param       object          elementDimensions
+        * @return      object
+        */
+       _getPositionRight: function(elementOffsets, dropdownDimensions, elementDimensions) {
+               var $left = (elementOffsets.left + elementDimensions.width) - dropdownDimensions.width;
+               var $right = elementOffsets.right;
+               
+               return {
+                       result: ($left > 0),
+                       right: $right
+               };
+       }
+});
+
 /**
  * Clipboard API
  */
index bbb1232c23a6ff4d78c0f63a219805b3b81371bc..300b30bd1a1e2a45c45f31abd53ed4a4eeec800e 100644 (file)
@@ -167,7 +167,7 @@ class UserNotificationAction extends AbstractDatabaseObjectAction {
                ));
                
                return array(
-                       'template' => WCF::getTPL()->fetch('notificationListOustanding'),
+                       'template' => WCF::getTPL()->fetch('notificationListUserPanel'),
                        'totalCount' => $notifications['notificationCount']
                );
        }
index 4c68aa1e5f0fd563ca524402d4bafece64ed6996..f3288db7f030dfe74712df781149d90cf333a754 100644 (file)
                                font-size: @wcfSmallFontSize;
                        }
                }
-               
-               &.notificationItem {
-                       &.groupedNotificationItem > span {
-                               > .framed > span.fa-users:before {
-                                       position: relative;
-                                       top: 3px;
-                               }
-                               
-                               > div + div {
-                                       margin-left: 32px;
-                               }
-                       }
-                       
-                       &.notificationItemLink {
-                               position: relative;
-                               
-                               > span {
-                                       cursor: default;
-                                       pointer-events: none;
-                                       position: relative;
-                                       z-index: 10;
-                                       
-                                       a {
-                                               pointer-events: all;
-                                       }
-                               }
-                               
-                               > a {
-                                       bottom: 0;
-                                       left: 0;
-                                       position: absolute;
-                                       right: 0;
-                                       top: 0;
-                                       z-index: 1;
-                               }
-                       }
-                       
-                       &:hover > span > .framed {
-                               > .notificationItemMarkAsConfirmed {
-                                       height: 24px;   
-                                       display: block;
-                                       width: 24px;
-                                       
-                                       &:before {
-                                               position: relative;
-                                               top: 3px;
-                                       }
-                               }
-                               
-                               > .notificationItemMarkAsConfirmed ~ img,
-                               > .notificationItemMarkAsConfirmed ~ .fa-users {
-                                       display: none;
-                               }
-                       }
-                       
-                       > span {
-                               cursor: pointer;
-                               white-space: normal;
-                               
-                               > .framed > .notificationItemMarkAsConfirmed {
-                                       display: none;
-                               }
-                       }
-               }
        }
        
        .scrollableDropdownMenu {
                }
        }
 }
+
+.interactiveDropdown {
+       background-color: @wcfContainerAccentBackgroundColor;
+       border: 1px solid @wcfDropdownBorderColor;
+       color: @wcfDropdownColor;
+       display: none;
+       position: absolute;
+       z-index: 450;
+       
+       .boxShadow(0, 3px, rgba(0, 0, 0, .25), 8px);
+       
+       &.open {
+               display: block;
+       }
+       
+       > .interactiveDropdownHeader {
+               padding: @wcfGapSmall (@wcfGapSmall + @wcfGapTiny);
+               
+               > .interactiveDropdownTitle {
+                       font-weight: bold;
+               }
+               
+               > .interactiveDropdownLinks {
+                       float: right;
+                       
+                       > li {
+                               display: inline-block;
+                               margin-left: (@wcfGapSmall + @wcfGapTiny);
+                       }
+               }
+               
+               &:after {
+                       clear: both;
+               }
+       }
+       
+       > .interactiveDropdownItemsContainer {
+               border: 1px solid @wcfDropdownBorderColor;
+               border-width: 1px 0;
+               max-height: 300px;
+               
+               > .interactiveDropdownItems {
+                       > li {
+                               background-color: @wcfDropdownBackgroundColor;
+                               
+                               .transition(~"background-color, background-position", .3s, linear);
+                               
+                               &.loading,
+                               &.noItems {
+                                       font-size: 1.2rem;
+                                       padding: @wcfGapLarge @wcfGapMedium;
+                                       text-align: center;
+                               }
+                               
+                               &:not(.loading):not(.noItems) {
+                                       overflow: hidden;
+                                       padding: @wcfGapSmall (@wcfGapSmall + @wcfGapTiny);
+                                       position: relative;
+                                       
+                                       .textShadow(@wcfContainerAccentBackgroundColor);
+                                       
+                                       &:not(:last-child) {
+                                               border-bottom: 1px solid @wcfDropdownBorderColor;
+                                       }
+                                       
+                                       &:hover {
+                                               background-color: @wcfDropdownHoverBackgroundColor;
+                                       }
+                                       
+                                       &.interactiveDropdownItemOutstanding {
+                                               background-color: @wcfDropdownHoverBackgroundColor;
+                                               background-repeat: no-repeat;
+                                               background-size: 200%;
+                                               
+                                               .linearGradientNative(~"to left, @{wcfDropdownHoverBackgroundColor} 50%, @{wcfDropdownBackgroundColor} 100%");
+                                               
+                                               &:hover {
+                                                       background-position: 100%;
+                                               }
+                                       }
+                                       
+                                       &.interactiveDropdownItemOutstandingIcon {
+                                               > div.box32 {
+                                                       padding-right: 16px + (@wcfGapSmall + @wcfGapTiny);
+                                               }
+                                               
+                                               > div.interactiveDropdownItemMarkAsRead {
+                                                       opacity: .6;
+                                                       position: absolute;
+                                                       right: (@wcfGapSmall + @wcfGapTiny);
+                                                       top: 50%;
+                                                       
+                                                       -ms-transform: translateY(-50%);
+                                                       transform: translateY(-50%);
+                                                       
+                                                       .transition(opacity, .3s, linear);
+                                               }
+                                               
+                                               &:hover > div.interactiveDropdownItemMarkAsRead {
+                                                       opacity: 1;
+                                               }
+                                       }
+                                       
+                                       &.notificationItem {
+                                               .userLink {
+                                                       font-weight: bold;
+                                               }
+                                       }
+                                       
+                                       &.interactiveDropdownItemShadow {
+                                               > .box32 {
+                                                       position: relative;
+                                               }
+                                               
+                                               > .box32,
+                                               > .interactiveDropdownItemMarkAsRead {
+                                                       pointer-events: none;
+                                                       z-index: 20;
+                                                       
+                                                       a {
+                                                               pointer-events: all;
+                                                       }
+                                               }
+                                               
+                                               > .interactiveDropdownItemShadowLink {
+                                                       bottom: 0;
+                                                       left: 0;
+                                                       position: absolute;
+                                                       right: 0;
+                                                       top: 0;
+                                                       z-index: 10;
+                                               }
+                                       }
+                                       
+                                       &.groupedNotificationItem > .box32 > .framed > span.fa-users:before {
+                                               position: relative;
+                                               top: 3px;
+                                       }
+                               }
+                       }
+               }
+       }
+       
+       > .interactiveDropdownShowAll {
+               display: block;
+               padding: @wcfGapSmall (@wcfGapSmall + @wcfGapTiny);
+               text-align: center;
+       }
+       
+       > .pointer {
+               border: 10px solid transparent;
+               border-bottom-color: @wcfDropdownBorderColor;
+               border-top-width: 0;
+               content: "";
+               display: inline-block;
+               position: absolute;
+               top: -10px;
+               z-index: 100;
+               
+               > span {
+                       border: 8px solid transparent;
+                       border-bottom-color: @wcfContainerAccentBackgroundColor;
+                       border-top-width: 0;
+                       content: "";
+                       display: inline-block;
+                       left: -8px;
+                       position: absolute;
+                       top: 2px;
+                       z-index: 101;
+               }
+       }
+}
+
+@media only screen and (min-width: 801px) {
+       .interactiveDropdown {
+               min-width: 350px;
+               
+               > .interactiveDropdownItemsContainer {
+                       overflow: hidden;
+                       position: relative;
+                       
+                       > .interactiveDropdownItems > li:not(.loading) {
+                               max-width: 400px;
+                       }
+               }
+       }
+}
+
+@media only screen and (max-width: 800px) {
+       .interactiveDropdown {
+               left: 0 !important;
+               right: 0 !important;
+               width: 100%;
+               
+               > .interactiveDropdownItemsContainer {
+                       overflow-x: auto;
+               }
+       }
+}
index 84fc09af20bbf9441a8b7bd359be0d8941141d41..ac64012f3183e8e326dd8dddd15f990674fb9388 100644 (file)
@@ -2415,3 +2415,33 @@ see: http://beta.woltlab.com/index.php/Thread/3874-Kosmetik-Darstellungsfehler-I
 .ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; }
 .ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; }
 .ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}
+
+/* PerfectScrollbar plugin */
+.ps-container {
+       &:hover > .ps-scrollbar-y-rail > .ps-scrollbar-y {
+               background-color: rgba(102, 102, 102, .6);
+       }
+       
+       > .ps-scrollbar-y-rail {
+               background-color: rgba(102, 102, 102, 0);
+               border-radius: 4px;
+               bottom: 0;
+               position: absolute;
+               right: 2px;
+               width: 8px;
+               
+               .transition(background-color, .3s, linear);
+               
+               &:hover {
+                       background-color: rgba(102, 102, 102, .3);
+               }
+               
+               > .ps-scrollbar-y {
+                       background-color: rgba(102, 102, 102, 0);
+                       border-radius: 4px;
+                       position: relative;
+                       
+                       .transition(background-color, .3s, linear);
+               }
+       }
+}
index d57c58236f6ff7e4244fced584b5aa011494a8b8..996026d5b045acc4034b63f3e09d01253d17962b 100644 (file)
                top: 3px;
        }
        
-       &:hover > div > .framed {
+       &.notificationUnconfirmed {
+               position: relative;
+               
+               > div > .details {
+                       padding-right: 16px + @wcfGapLarge;
+               }
+               
                > .notificationItemMarkAsConfirmed {
-                       height: 24px;   
-                       display: block;
-                       width: 24px;
+                       opacity: .6;
+                       position: absolute;
+                       right: @wcfGapLarge;
+                       top: 50%;
                        
-                       &:before {
-                               position: relative;
-                               top: 3px;
-                       }
+                       -ms-transform: translateY(-50%);
+                       transform: translateY(-50%);
+                       
+                       .transition(opacity, .3s, linear);
                }
                
-               > .notificationItemMarkAsConfirmed ~ img,
-               > .notificationItemMarkAsConfirmed ~ .fa-users {
-                       display: none;
+               &:hover > .notificationItemMarkAsConfirmed {
+                       opacity: 1;
                }
        }
        
-       > div > .framed > .notificationItemMarkAsConfirmed {
-               display: none;
+       > div > .details {
+               padding-top: 3px;
+               
+               .userLink {
+                       font-weight: bold;
+               }
        }
 }
 
index 644a1d5874dbae0a8c952603db5aceb4343e9081..49385e5a9afaa828e60462416b45395743ac4f0a 100644 (file)
@@ -2855,6 +2855,10 @@ Wenn Sie Probleme mit der Aktivierung haben, wenden Sie sich bitte an den Admini
                <item name="wcf.user.birthdayToday"><![CDATA[Hat heute Geburtstag]]></item>
                <item name="wcf.user.login.blocked"><![CDATA[Aufgrund einer hohen Anzahl von fehlgeschlagenen Anmeldeversuchen durch Ihre IP-Adresse steht Ihnen die Anmeldung aus Sicherheitsgründen vorübergehend nicht zur Verfügung. Bitte versuchen Sie es später erneut!]]></item>
                <item name="wcf.user.banned"><![CDATA[Der Benutzer {$user->username} wurde{if $user->banExpires != 0} bis zum {@$user->banExpires|date}{/if} gesperrt.]]></item>
+               <item name="wcf.user.panel.markAllAsRead"><![CDATA[Alle als gelesen markieren]]></item>
+               <item name="wcf.user.panel.markAsRead"><![CDATA[Als gelesen markieren]]></item>
+               <item name="wcf.user.panel.settings"><![CDATA[Einstellungen]]></item>
+               <item name="wcf.user.panel.showAll"><![CDATA[Alle anzeigen]]></item>
        </category>
        
        <category name="wcf.user.menu">
@@ -3107,7 +3111,7 @@ Möchten Sie diese E-Mail-Benachrichtigung in Zukunft nicht mehr erhalten, könn
                <item name="wcf.user.notification.markAllAsConfirmed"><![CDATA[Alle als gelesen markieren]]></item>
                <item name="wcf.user.notification.markAllAsConfirmed.confirmMessage"><![CDATA[Wollen Sie wirklich alle Benachrichtigungen als gelesen markieren?]]></item>
                <item name="wcf.user.notification.markAsConfirmed"><![CDATA[Als gelesen markieren]]></item>
-               <item name="wcf.user.notification.noMoreNotifications"><![CDATA[Keine neuen Benachrichtigungen]]></item>
+               <item name="wcf.user.notification.noMoreNotifications"><![CDATA[Keine aktuellen Benachrichtigungen]]></item>
                <item name="wcf.user.notification.noNotifications"><![CDATA[Sie haben keine Benachrichtigungen.]]></item>
                <item name="wcf.user.notification.notifications"><![CDATA[Benachrichtigungen]]></item>
                <item name="wcf.user.notification.showAll"><![CDATA[Alle Benachrichtigungen anzeigen]]></item>
index 790195d9c39e51dfb75055082b1209a61d2e099b..916f55ac9b8b0759f83db4496455babd26014ee7 100644 (file)
@@ -2850,6 +2850,10 @@ If you cannot activate your email address or have any troubles following the ins
                <item name="wcf.user.birthdayToday"><![CDATA[It is their birthday]]></item>
                <item name="wcf.user.login.blocked"><![CDATA[There have been too many failed login attempts originating from your ip address, your login attempts will be temporarily rejected for security reasons. Please try again later!]]></item>
                <item name="wcf.user.banned"><![CDATA[The user {$user->username} has been banned{if $user->banExpires != 0} until {@$user->banExpires|date}{/if}.]]></item>
+               <item name="wcf.user.panel.markAllAsRead"><![CDATA[Mark All as Read]]></item>
+               <item name="wcf.user.panel.markAsRead"><![CDATA[Mark as Read]]></item>
+               <item name="wcf.user.panel.settings"><![CDATA[Settings]]></item>
+               <item name="wcf.user.panel.showAll"><![CDATA[Show All]]></item>
        </category>
        
        <category name="wcf.user.menu">
@@ -3101,7 +3105,7 @@ If you do not want to receive further email notifications for this event, you ca
                <item name="wcf.user.notification.markAllAsConfirmed"><![CDATA[Mark All as Read]]></item>
                <item name="wcf.user.notification.markAllAsConfirmed.confirmMessage"><![CDATA[Do you really want to mark all notifications as read?]]></item>
                <item name="wcf.user.notification.markAsConfirmed"><![CDATA[Mark as Read]]></item>
-               <item name="wcf.user.notification.noMoreNotifications"><![CDATA[No new notifications]]></item>
+               <item name="wcf.user.notification.noMoreNotifications"><![CDATA[No recent notifications]]></item>
                <item name="wcf.user.notification.noNotifications"><![CDATA[You have no notifications.]]></item>
                <item name="wcf.user.notification.notifications"><![CDATA[Notifications]]></item>
                <item name="wcf.user.notification.showAll"><![CDATA[Show All Notifications]]></item>