Improved the UX and the DOM markup
authorAlexander Ebert <ebert@woltlab.com>
Mon, 8 Jul 2019 10:44:49 +0000 (12:44 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Mon, 8 Jul 2019 10:44:49 +0000 (12:44 +0200)
com.woltlab.wcf/templates/articleListItems.tpl
com.woltlab.wcf/templates/reactionSummaryList.tpl
com.woltlab.wcf/templates/reactionTypeImage.tpl
wcfsetup/install/files/acp/templates/reactionTypeImage.tpl
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Reaction/CountButtons.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Reaction/Handler.js
wcfsetup/install/files/lib/data/reaction/ReactionAction.class.php
wcfsetup/install/files/style/ui/message.scss
wcfsetup/install/files/style/ui/reactions.scss

index 0ac8920fc13fd418745042c643859f8d3e1b25be..13f4a11144a870e3844a348cb464c6b6da6cf321 100644 (file)
                                </div>
                                
                                <div class="contentItemMetaIcons">
-                                       {if MODULE_LIKE && $__wcf->getSession()->getPermission('user.like.canViewLike') && ($article->likes || $article->dislikes || $article->neutralReactions)}
-                                               <div class="contentItemMetaIcon reputationCounter {if $article->cumulativeLikes > 0}positive{elseif $article->cumulativeLikes < 0}negative{else}neutral{/if}">
-                                                       <span aria-label="{lang cumulativeLikes=$article->cumulativeLikes}wcf.like.reputation.label{/lang}">
-                                                               {if $article->cumulativeLikes > 0}+{elseif $article->cumulativeLikes == 0}±{/if}{#$article->cumulativeLikes}
-                                                       </span>
-                                               </div>
-                                       {/if}
                                        <div class="contentItemMetaIcon">
                                                <span class="icon icon16 fa-comments"></span>
                                                <span aria-label="{$article->getDiscussionProvider()->getDiscussionCountPhrase()}">
index 8a70bae2fbb7934dd872da1534ba237a42347a92..50b880922a09eed60b66a536e8de6105ad66f5b3 100644 (file)
@@ -1,9 +1,16 @@
 {if $__wcf->session->getPermission('user.like.canViewLike')}
-       <ul class="reactionSummaryList{if $isTiny|isset && $isTiny} reactionSummaryListTiny{/if} jsOnly" data-object-type="{$objectType}" data-object-id="{$objectID}">
-               {if $reactionData[$objectID]|isset && $reactionData[$objectID]->getReactions()|is_array}
-                       {foreach from=$reactionData[$objectID]->getReactions() key=reactionTypeID item=reaction}
-                               <li class="reactCountButton jsTooltip" data-reaction-type-id="{$reactionTypeID}" title="{lang}wcf.reactions.summary.listReactions{/lang}"><span class="reactionCount">{$reaction[reactionCount]|shortUnit}</span> {@$reaction[renderedReactionIcon]}</li>
+       {assign var='_reactionSummaryListReactions' value=null}
+        {if $reactionData[$objectID]|isset}
+               {assign var='_reactionSummaryListReactions' value=$reactionData[$objectID]->getReactions()}
+       {/if}
+       <a href="#" class="reactionSummaryList{if $isTiny|isset && $isTiny} reactionSummaryListTiny{/if} jsOnly jsTooltip" data-object-type="{$objectType}" data-object-id="{$objectID}" title="{lang}wcf.reactions.summary.listReactions{/lang}"{if $_reactionSummaryListReactions|empty} style="display: none;"{/if}>
+               {if !$_reactionSummaryListReactions|empty}
+                       {foreach from=$_reactionSummaryListReactions key=reactionTypeID item=reaction}
+                               <span class="reactCountButton" data-reaction-type-id="{@$reactionTypeID}">
+                                       {@$reaction[renderedReactionIcon]}
+                                       <span class="reactionCount">{$reaction[reactionCount]|shortUnit}</span>
+                               </span>
                        {/foreach}
                {/if}
-       </ul>
-{/if}
\ No newline at end of file
+       </a>
+{/if}
index b8aa009259daa0e1f0463f9a7da56554b794a7ef..55cd51006011c8d5b046cbdfa7c697c6ffd047a5 100644 (file)
@@ -2,4 +2,4 @@
        src="{@$__wcf->getPath()}images/reaction/{$reactionType->iconFile}"
        class="reactionType"
        data-reaction-type-id="{$reactionType->reactionTypeID}"
-/>
+>
index a4577bbdef397d275c46eb6f8266e3856eb8ea1c..a44061561b2593801139c31e4262764fd58e6730 100644 (file)
@@ -3,4 +3,4 @@
        style="width:24px;height:24px"
        class="reactionType"
        data-reaction-type-id="{$reactionType->reactionTypeID}"
-/>
+>
index 4e5afd715250ac0830e490de651a7812779e6ed0..d9e4f4aab72a1a7a65340b7806563c0df76c3117 100644 (file)
@@ -69,27 +69,26 @@ define(
                                                continue;
                                        }
                                        
+                                       objectId = ~~elData(element, 'object-id');
                                        elementData = {
                                                reactButton: null,
                                                summary: null,
                                                
-                                               objectId: ~~elData(element, 'object-id')
+                                               objectId: objectId
                                                element: element
                                        };
                                        
                                        this._containers.set(DomUtil.identify(element), elementData);
                                        this._initReactionCountButtons(element, elementData);
-                                       
-                                       if (!this._objects.has(~~elData(element, 'object-id'))) {
-                                               var objects = [];
-                                       }
-                                       else {
-                                               var objects = this._objects.get(~~elData(element, 'object-id'));
+
+                                       var objects = [];
+                                       if (this._objects.has(objectId)) {
+                                               objects = this._objects.get(objectId);
                                        }
                                        
                                        objects.push(elementData);
                                        
-                                       this._objects.set(~~elData(element, 'object-id'), objects);
+                                       this._objects.set(objectId, objects);
                                        
                                        triggerChange = true;
                                }
@@ -113,13 +112,14 @@ define(
                                        // summary list for the object not found; abort
                                        if (summaryList === null) return; 
                                        
-                                       var sortedElements = {}, elements = elBySelAll('li', summaryList);
+                                       var sortedElements = {}, elements = elBySelAll('.reactCountButton', summaryList);
                                        for (var i = 0, length = elements.length; i < length; i++) {
-                                               if (data[elData(elements[i], 'reaction-type-id')] !== undefined) {
-                                                       sortedElements[elData(elements[i], 'reaction-type-id')] = elements[i];
+                                               var reactionTypeId = elData(elements[i], 'reaction-type-id');
+                                               if (data.hasOwnProperty(reactionTypeId)) {
+                                                       sortedElements[reactionTypeId] = elements[i];
                                                }
                                                else {
-                                                       // reaction has no longer reactions
+                                                       // The reaction no longer has any reactions.
                                                        elRemove(elements[i]);
                                                }
                                        }
@@ -130,25 +130,23 @@ define(
                                                        reactionCount.innerHTML = StringUtil.shortUnit(data[key]);
                                                }
                                                else if (REACTION_TYPES[key] !== undefined) {
-                                                       // create element 
-                                                       var createdElement = elCreate('li');
+                                                       var createdElement = elCreate('span');
                                                        createdElement.className = 'reactCountButton';
+                                                       createdElement.innerHTML = REACTION_TYPES[key].renderedIcon;
                                                        elData(createdElement, 'reaction-type-id', key);
-                                                       
+
                                                        var countSpan = elCreate('span');
                                                        countSpan.className = 'reactionCount';
                                                        countSpan.innerHTML = StringUtil.shortUnit(data[key]);
                                                        createdElement.appendChild(countSpan);
                                                        
-                                                       createdElement.innerHTML = createdElement.innerHTML + REACTION_TYPES[key].renderedIcon;
-                                                       
                                                        summaryList.appendChild(createdElement);
                                                        
-                                                       this._initReactionCountButton(createdElement, objectId);
-                                                       
                                                        triggerChange = true;
                                                }
                                        }, this);
+                                       
+                                       window[(summaryList.childElementCount > 0 ? 'elShow' : 'elHide')](summaryList);
                                }.bind(this));
                                
                                if (triggerChange) {
@@ -163,31 +161,12 @@ define(
                         * @param       {object}        elementData
                         */
                        _initReactionCountButtons: function(element, elementData) {
-                               if (this._options.isSingleItem) {
-                                       var summaryList = elBySel(this._options.summaryListSelector);
-                               }
-                               else {
-                                       var summaryList = elBySel(this._options.summaryListSelector, element);
-                               }
-                               
+                               var summaryList = elBySel(this._options.summaryListSelector, this._options.isSingleItem ? undefined : element);
                                if (summaryList !== null) {
-                                       var elements = elBySelAll('li', summaryList);
-                                       for (var i = 0, length = elements.length; i < length; i++) {
-                                               this._initReactionCountButton(elements[i], elementData.objectId);
-                                       }
+                                       summaryList.addEventListener(WCF_CLICK_EVENT, this._showReactionOverlay.bind(this, elementData.objectId));
                                }
                        },
                        
-                       /**
-                        * Initialized a specific reaction count button for an object.
-                        *
-                        * @param       {element}        element
-                        * @param       {int}            objectId
-                        */
-                       _initReactionCountButton: function(element, objectId) {
-                               element.addEventListener(WCF_CLICK_EVENT, this._showReactionOverlay.bind(this, objectId));
-                       },
-                       
                        /**
                         * Shows the reaction overly for a specific object. 
                         *
index eb6bdf2dbead710b0a3091e26168a6421371aa9d..5e6b18046ec52a698a915a32e296ffecac813c84 100644 (file)
@@ -40,7 +40,6 @@ define(
                                }
                                
                                this._containers = new Dictionary();
-                               this._details = new ObjectMap();
                                this._objectType = objectType;
                                this._cache = new Dictionary();
                                this._objects = new Dictionary();
@@ -81,25 +80,24 @@ define(
                                                continue;
                                        }
                                        
+                                       objectId = ~~elData(element, 'object-id');
                                        elementData = {
                                                reactButton: null,
-                                               objectId: ~~elData(element, 'object-id'),
+                                               objectId: objectId,
                                                element: element
                                        };
                                        
                                        this._containers.set(DomUtil.identify(element), elementData);
                                        this._initReactButton(element, elementData);
-                                       
-                                       if (!this._objects.has(~~elData(element, 'object-id'))) {
-                                               var objects = [];
-                                       }
-                                       else {
-                                               var objects = this._objects.get(~~elData(element, 'object-id'));
+
+                                       var objects = [];
+                                       if (this._objects.has(objectId)) {
+                                               objects = this._objects.get(objectId);
                                        }
                                        
                                        objects.push(elementData);
                                        
-                                       this._objects.set(~~elData(element, 'object-id'), objects);
+                                       this._objects.set(objectId, objects);
                                        
                                        triggerChange = true;
                                }
@@ -122,11 +120,13 @@ define(
                                }
                                
                                if (elementData.reactButton === null || elementData.reactButton.length === 0) {
-                                       // the element may have no react button 
+                                       // The element may have no react button. 
                                        return;
                                }
                                
+                               //noinspection JSUnresolvedVariable
                                if (Object.keys(REACTION_TYPES).length === 1) {
+                                       //noinspection JSUnresolvedVariable
                                        var reaction = REACTION_TYPES[Object.keys(REACTION_TYPES)[0]];
                                        elementData.reactButton.title = reaction.title;
                                        var textSpan = elBySel('.invisible', elementData.reactButton);
@@ -190,7 +190,8 @@ define(
                                                if (reactionTypeID) {
                                                        elementData.reactButton.classList.add('active');
                                                        elData(elementData.reactButton, 'reaction-type-id', reactionTypeID);
-                                               } else {
+                                               }
+                                               else {
                                                        elData(elementData.reactButton, 'reaction-type-id', 0);
                                                        elementData.reactButton.classList.remove('active');
                                                }
@@ -199,25 +200,24 @@ define(
                        },
                        
                        _markReactionAsActive: function() {
-                               var reactionTypeID;
+                               var reactionTypeID = null;
                                this._objects.get(this._popoverCurrentObjectId).forEach(function (element) {
                                        if (element.reactButton !== null) {
-                                               reactionTypeID = elData(element.reactButton, 'reaction-type-id');
+                                               reactionTypeID = ~~elData(element.reactButton, 'reaction-type-id');
                                        }
                                });
                                
-                               if (reactionTypeID === undefined) {
+                               if (reactionTypeID === null) {
                                        throw new Error("Unable to find react button for current popover.");
                                }
                                
-                               //  clear old active state
-                               var elements = elBySelAll('.reactionTypeButton.active', this._getPopover());
-                               for (var i = 0, length = elements.length; i < length; i++) {
-                                       elements[i].classList.remove('active');
-                               }
+                               //  Clear the old active state.
+                               elBySelAll('.reactionTypeButton.active', this._getPopover(), function(element) {
+                                       element.classList.remove('active');
+                               });
                                
-                               if (reactionTypeID != 0) {
-                                       elBySel('.reactionTypeButton[data-reaction-type-id="'+reactionTypeID+'"]', this._getPopover()).classList.add('active');
+                               if (reactionTypeID) {
+                                       elBySel('.reactionTypeButton[data-reaction-type-id="' + reactionTypeID + '"]', this._getPopover()).classList.add('active');
                                }
                        },
                        
@@ -226,14 +226,17 @@ define(
                         * 
                         * @param       {int}           objectId
                         * @param       {Element}       element
+                        * @param       {?Event}        event
                         */
                        _toggleReactPopover: function(objectId, element, event) {
                                if (event !== null) {
                                        event.preventDefault();
                                        event.stopPropagation();
                                }
-                               
+
+                               //noinspection JSUnresolvedVariable
                                if (Object.keys(REACTION_TYPES).length === 1) {
+                                       //noinspection JSUnresolvedVariable
                                        var reaction = REACTION_TYPES[Object.keys(REACTION_TYPES)[0]];
                                        this._popoverCurrentObjectId = objectId;
                                        
@@ -256,7 +259,6 @@ define(
                         * @param       {Element}       element                 container element
                         */
                        _openReactPopover: function(objectId, element) {
-                               // first close old popover, if exists 
                                if (this._popoverCurrentObjectId !== 0) {
                                        this._closePopover();
                                }
@@ -271,9 +273,7 @@ define(
                                });
                                
                                if (this._options.isButtonGroupNavigation) {
-                                       // find nav element
-                                       var nav = element.closest('nav');
-                                       nav.style.opacity = "1";
+                                       element.closest('nav').style.setProperty('opacity', '1', '');
                                }
                                
                                this._getPopover().classList.remove('forceHide');
@@ -311,7 +311,8 @@ define(
                                                var reactionTypeItemSpan = elCreate('span');
                                                reactionTypeItemSpan.classList = 'reactionTypeButtonTitle';
                                                reactionTypeItemSpan.innerHTML = reactionType.title;
-                                               
+
+                                               //noinspection JSUnresolvedVariable
                                                reactionTypeItem.innerHTML = reactionType.renderedIcon;
                                                
                                                reactionTypeItem.appendChild(reactionTypeItemSpan);
@@ -346,13 +347,18 @@ define(
                                var sortedReactionTypes = [];
                                
                                // convert our reaction type object to an array
+                               //noinspection JSUnresolvedVariable
                                for (var key in REACTION_TYPES) {
-                                       if (!REACTION_TYPES.hasOwnProperty(key)) continue;
-                                       sortedReactionTypes.push(REACTION_TYPES[key]);
+                                       //noinspection JSUnresolvedVariable
+                                       if (REACTION_TYPES.hasOwnProperty(key)) {
+                                               //noinspection JSUnresolvedVariable
+                                               sortedReactionTypes.push(REACTION_TYPES[key]);
+                                       }
                                }
                                
                                // sort the array
                                sortedReactionTypes.sort(function (a, b) {
+                                       //noinspection JSUnresolvedVariable
                                        return a.showOrder - b.showOrder;
                                });
                                
@@ -395,9 +401,9 @@ define(
                        },
                        
                        _ajaxSuccess: function(data) {
+                               //noinspection JSUnresolvedVariable
                                this.countButtons.updateCountButtons(data.returnValues.objectID, data.returnValues.reactions);
                                
-                               // update react button status
                                this._updateReactButton(data.returnValues.objectID, data.returnValues.reactionTypeID);
                        },
                        
index 98c91034cea42edf3a24a0a211e8d27c667ce437..50fe922e393f4e987ed4837431c195e4f2506d4c 100644 (file)
@@ -52,25 +52,25 @@ class ReactionAction extends AbstractDatabaseObjectAction {
         * likeable object
         * @var ILikeObject
         */
-       public $likeableObject = null;
+       public $likeableObject;
        
        /**
         * object type object
         * @var ObjectType
         */
-       public $objectType = null;
+       public $objectType;
        
        /**
         * like object type provider object
         * @var ILikeObjectTypeProvider
         */
-       public $objectTypeProvider = null;
+       public $objectTypeProvider;
        
        /**
         * reaction type for the reaction
         * @var ReactionType
         */
-       public $reactionType = null;
+       public $reactionType;
        
        /**
         * Validates parameters to fetch like details.
index fa6010cba8a4d2455722f4b3bbd0f26e0e8a0179..90ee43c24185fb8dca979af04f7af958660694c3 100644 (file)
        flex-wrap: wrap;
        
        &:not(:first-child) {
-               > .likesSummary,
+               > .reactionSummaryList,
                > .messageFooterButtons,
                > .messageFooterButtonsExtra {
                        margin-top: 20px;
                }
        }
        
-       > .likesSummary {
+       > .reactionSummaryList {
                flex: 0 1 auto;
                
                @include wcfFontSmall;
index 08337081c9278d9a079448c78f5cd5669e396039..c112d283fe871df302928a5415c9a797d3ea57fe 100644 (file)
        }
 }
 
-img.reactionType {
-       width: 24px;
-       height: 24px;
+.reactionType {
+       width: 20px;
+       height: 20px;
 }
 
 .reactionSummaryList {
-       span.reactionCount::after {
-               content: ' × ';
+       display: flex;
+       flex-wrap: wrap;
+       margin: -5px -5px 0 0;
+       
+       .reactionCount{
+               @include wcfFontSmall;
+               
+               &::before {
+                       content: ' × ';
+               }
+       }
+       
+       &.reactionSummaryListTiny .reactionType {
+               width: 16px;
+               height: 16px;
+       }
+}
+
+.reactCountButton {
+       color: $wcfContentDimmedText;
+       flex: 0 0 auto;
+       margin: 5px 5px 0 0;
+       white-space: nowrap;
+       
+       &:hover {
+               color: $wcfContentText;
        }
 }
 
@@ -59,10 +83,10 @@ img.reactionType {
        }
        
        @include screen-md-down {
-               padding: 5px 0px;
+               padding: 5px 0;
                
                > ul > li.reactionTypeButton {
-                       margin: 0px;
+                       margin: 0;
                        display: block;
                        padding: 5px 25px;
                }
@@ -106,23 +130,11 @@ img.reactionType {
                }
                
                > ul > li.reactionTypeButton:first-child {
-                       margin-left: 0px;
+                       margin-left: 0;
                }
        }
 }
 
-li.reactCountButton {
-       display: inline;
-       padding: 5px;
-       cursor: pointer;
-       color: $wcfContentDimmedText;
-       white-space: nowrap;
-}
-
-li.reactCountButton:hover {
-       color: $wcfContentText;
-}
-
 .reputationCounter {
        color: $wcfContentDimmedText;
        
@@ -153,24 +165,6 @@ li.reactCountButton:hover {
        }
 }
 
-.reactionSummaryListTiny {
-       display: inline;
-       
-       li.reactCountButton > img {
-               width: 16px;
-               height: 16px;
-       }
-       
-       span.reactionCount {
-               @include wcfFontSmall;
-       }
-       
-       li.reactCountButton {
-               background-color: transparent;
-               padding: 0px;
-       }
-}
-
 @include screen-sm-down {
        .reactionStatusContainer {
                display: none;
@@ -195,4 +189,4 @@ li.reactCountButton:hover {
                        align-items: center;
                }
        }
-}
\ No newline at end of file
+}