Merge "Fix highlight display for enhanced mode"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 30 Aug 2017 19:05:16 +0000 (19:05 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 30 Aug 2017 19:05:16 +0000 (19:05 +0000)
1  2 
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.mixins.less
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.ChangesListWrapperWidget.less
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.ChangesListWrapperWidget.js

@@@ -8,7 -8,8 +8,7 @@@
  }
  
  // This is a general mixin for a color circle
 -.mw-rcfilters-mixin-circle( @color: #fff, @diameter: 2em, @padding: 0.5em, @border: false ) {
 -      background-color: @color;
 +.mw-rcfilters-mixin-circle( @color: #fff, @diameter: 2em, @padding: 0.5em, @border: false, @borderColor: #54595d, @emptyBackground: false ) {
        .box-sizing( border-box );
        min-width: @diameter;
        width: @diameter;
        margin: @padding;
        border-radius: 50%;
  
 +      & when ( @emptyBackground = false ) {
 +              background-color: @color;
 +      }
 +      & when ( @emptyBackground = true ) {
 +              background-color: @highlight-none;
 +      }
 +
        & when ( @border = true ) {
 -              border: 1px solid #54595d;
 +              border: 1px solid @borderColor;
        }
  }
  
        }
  }
  
+ // A mixin just for changesListWrapperWidget page, to output the scope of the widget
+ // so it is before the rest of the rule; we need the li& to be in
+ // between the wrapper scope and the color-cX class, which doesn't
+ // work if the rules are inside the above widget LESS scope
+ .highlight-changesListWrapperWidget( @bgcolor ) {
+       .mw-rcfilters-ui-changesListWrapperWidget li&,
+               .mw-rcfilters-ui-changesListWrapperWidget & tr:first-child,
+               .mw-rcfilters-ui-changesListWrapperWidget tr&.mw-rcfilters-ui-changesListWrapperWidget-enhanced-toplevel:not(.mw-rcfilters-ui-changesListWrapperWidget-enhanced-grey) td:not( :nth-child( -n+2 ) ),
+               .mw-rcfilters-ui-changesListWrapperWidget tr&.mw-rcfilters-ui-changesListWrapperWidget-enhanced-nested:not(.mw-rcfilters-ui-changesListWrapperWidget-enhanced-grey) td:not( :nth-child( -n+3 ) ) {
+               background-color: @bgcolor;
+       }
+ }
  // This mixin produces color mixes for two, three and four colors
  .highlight-color-mix( @color1, @color2, @color3: false, @color4: false ) {
        @highlight-color-class-var: ~'.mw-rcfilters-highlight-color-@{color1}.mw-rcfilters-highlight-color-@{color2}';
  
        // Two colors
        @{highlight-color-class-var} when ( @color3 = false ) and ( @color4 = false ) and not ( @color1 = false ), ( @color2 = false ) {
-               background-color: tint( average( @@c1var, @@c2var ), 50% );
+               .highlight-changesListWrapperWidget( tint( average( @@c1var, @@c2var ), 50% ) );
        }
        // Three colors
        @{highlight-color-class-var}.mw-rcfilters-highlight-color-@{color3} when ( @color4 = false ) and not ( @color3 = false ) {
                @c3var: ~'highlight-@{color3}';
-               background-color: tint( mix( @@c1var, average( @@c2var, @@c3var ), 33% ), 30% );
+               .highlight-changesListWrapperWidget( tint( mix( @@c1var, average( @@c2var, @@c3var ), 33% ), 30% ) );
        }
  
        // Four colors
        @{highlight-color-class-var}.mw-rcfilters-highlight-color-@{color3}.mw-rcfilters-highlight-color-@{color4} when not ( @color4 = false ) {
                @c3var: ~'highlight-@{color3}';
                @c4var: ~'highlight-@{color4}';
-               background-color: tint( mix( @@c1var, mix( @@c2var, average( @@c3var, @@c4var ), 25% ), 25% ), 25% );
+               .highlight-changesListWrapperWidget( tint( mix( @@c1var, mix( @@c2var, average( @@c3var, @@c4var ), 25% ), 25% ), 25% ) );
        }
  }
                }
        }
  
+       // Rule needs to be specific
+       // We want the expand button to appear outside the color
+       // to match the way the general highlight background appears
+       &-enhanced-grey td:not( :nth-child( -n+2 ) ) {
+               background-color: #dee0e3;
+       }
        h4:first-of-type {
                margin-top: 0;
                padding-top: 0;
                // and then plus the general margin
                width: ~'calc( ( @{result-circle-diameter} + @{result-circle-margin} ) * 5 )';
                // And we want to shift the entire block to the left of the li
-               position: absolute;
-               left: 0;
+               position: relative;
+               // Negative left margin of width + padding
+               margin-left: ~'calc( ( @{result-circle-diameter} + @{result-circle-margin} ) * -5 - @{result-circle-general-margin} )';
  
                .mw-rcfilters-ui-changesListWrapperWidget-highlighted & {
                        display: inline-block;
                }
  
 -              div {
 +              // This needs to be very specific, since these are
 +              // position rules that should apply to all overrides
 +              .mw-rcfilters-ui-changesListWrapperWidget .mw-rcfilters-ui-changesListWrapperWidget-highlights > div&-circle {
                        .box-sizing( border-box );
                        margin-right: @result-circle-margin;
                        vertical-align: middle;
                        .result-circle( c5 );
                }
        }
+ }
  
      // One color
      .mw-rcfilters-highlight-color-c1 {
-               background-color: tint( @highlight-c1, 70% );
      }
+ // One color
+ .mw-rcfilters-highlight-color-c1 {
+       .highlight-changesListWrapperWidget( tint( @highlight-c1, 70% ); );
+ }
  
      .mw-rcfilters-highlight-color-c2 {
-               background-color: tint( @highlight-c2, 70% );
      }
+ .mw-rcfilters-highlight-color-c2 {
+       .highlight-changesListWrapperWidget( tint( @highlight-c2, 70% ); );
+ }
  
      .mw-rcfilters-highlight-color-c3 {
-               background-color: tint( @highlight-c3, 70% );
      }
+ .mw-rcfilters-highlight-color-c3 {
+       .highlight-changesListWrapperWidget( tint( @highlight-c3, 70% ); );
+ }
  
      .mw-rcfilters-highlight-color-c4 {
-               background-color: tint( @highlight-c4, 70% );
      }
+ .mw-rcfilters-highlight-color-c4 {
+       .highlight-changesListWrapperWidget( tint( @highlight-c4, 70% ); );
+ }
  
      .mw-rcfilters-highlight-color-c5 {
-               background-color: tint( @highlight-c5, 70% );
      }
+ .mw-rcfilters-highlight-color-c5 {
+       .highlight-changesListWrapperWidget( tint( @highlight-c5, 70% ); );
+ }
  
-       // Two colors
-       .highlight-color-mix( c1, c2 );
-       // Overriding .highlight-color-mix( c1, c3 ); to produce
-       // a custom color rather than the computed tint
-       // see https://phabricator.wikimedia.org/T161267
-       .mw-rcfilters-highlight-color-c1.mw-rcfilters-highlight-color-c3 {
-               background-color: #ccdecc;
-       }
-       .highlight-color-mix( c1, c4 );
-       .highlight-color-mix( c1, c5 );
-       .highlight-color-mix( c2, c3 );
-       .highlight-color-mix( c2, c4 );
-       .highlight-color-mix( c2, c5 );
-       .highlight-color-mix( c3, c4 );
-       .highlight-color-mix( c3, c5 );
-       .highlight-color-mix( c4, c5 );
-       // Three colors
-       .highlight-color-mix( c1, c2, c3 );
-       .highlight-color-mix( c1, c2, c5 );
-       .highlight-color-mix( c1, c2, c4 );
-       .highlight-color-mix( c1, c3, c4 );
-       .highlight-color-mix( c1, c3, c5 );
-       .highlight-color-mix( c1, c4, c5 );
-       .highlight-color-mix( c2, c3, c4 );
-       .highlight-color-mix( c2, c3, c5 );
-       .highlight-color-mix( c2, c4, c5 );
-       .highlight-color-mix( c3, c4, c5 );
-       // Four colors
-       .highlight-color-mix( c1, c2, c3, c4 );
-       .highlight-color-mix( c1, c2, c3, c5 );
-       .highlight-color-mix( c1, c2, c4, c5 );
-       .highlight-color-mix( c1, c3, c4, c5 );
-       .highlight-color-mix( c2, c3, c4, c5 );
-       // Five colors:
-       .mw-rcfilters-highlight-color-c1.mw-rcfilters-highlight-color-c2.mw-rcfilters-highlight-color-c3.mw-rcfilters-highlight-color-c4.mw-rcfilters-highlight-color-c5 {
-               background-color: tint( mix( @highlight-c1, mix( @highlight-c2, mix( @highlight-c3, average( @highlight-c4, @highlight-c5 ), 20% ), 20% ), 20% ), 15% );
-       }
+ // Two colors
+ .highlight-color-mix( c1, c2 );
+ // Overriding .highlight-color-mix( c1, c3 ); to produce
+ // a custom color rather than the computed tint
+ // see https://phabricator.wikimedia.org/T161267
+ .mw-rcfilters-highlight-color-c1.mw-rcfilters-highlight-color-c3 {
+       .highlight-changesListWrapperWidget( #ccdecc );
+ }
+ .highlight-color-mix( c1, c4 );
+ .highlight-color-mix( c1, c5 );
+ .highlight-color-mix( c2, c3 );
+ .highlight-color-mix( c2, c4 );
+ .highlight-color-mix( c2, c5 );
+ .highlight-color-mix( c3, c4 );
+ .highlight-color-mix( c3, c5 );
+ .highlight-color-mix( c4, c5 );
+ // Three colors
+ .highlight-color-mix( c1, c2, c3 );
+ .highlight-color-mix( c1, c2, c5 );
+ .highlight-color-mix( c1, c2, c4 );
+ .highlight-color-mix( c1, c3, c4 );
+ .highlight-color-mix( c1, c3, c5 );
+ .highlight-color-mix( c1, c4, c5 );
+ .highlight-color-mix( c2, c3, c4 );
+ .highlight-color-mix( c2, c3, c5 );
+ .highlight-color-mix( c2, c4, c5 );
+ .highlight-color-mix( c3, c4, c5 );
+ // Four colors
+ .highlight-color-mix( c1, c2, c3, c4 );
+ .highlight-color-mix( c1, c2, c3, c5 );
+ .highlight-color-mix( c1, c2, c4, c5 );
+ .highlight-color-mix( c1, c3, c4, c5 );
+ .highlight-color-mix( c2, c3, c4, c5 );
+ // Five colors:
+ .mw-rcfilters-highlight-color-c1.mw-rcfilters-highlight-color-c2.mw-rcfilters-highlight-color-c3.mw-rcfilters-highlight-color-c4.mw-rcfilters-highlight-color-c5 {
+       .highlight-changesListWrapperWidget( tint( mix( @highlight-c1, mix( @highlight-c2, mix( @highlight-c3, average( @highlight-c4, @highlight-c5 ), 20% ), 20% ), 20% ), 15% ) );
  }
                this.filtersViewModel = filtersViewModel;
                this.changesListViewModel = changesListViewModel;
                this.controller = controller;
+               this.highlightClasses = null;
+               this.filtersModelInitialized = false;
  
                // Events
                this.filtersViewModel.connect( this, {
                        itemUpdate: 'onItemUpdate',
-                       highlightChange: 'onHighlightChange'
+                       highlightChange: 'onHighlightChange',
+                       initialize: 'onFiltersModelInitialize'
                } );
                this.changesListViewModel.connect( this, {
                        invalidate: 'onModelInvalidate',
@@@ -45,9 -48,6 +48,6 @@@
                        // We handle our own display/hide of the empty results message
                        .removeClass( 'mw-changeslist-empty' );
  
-               // Set up highlight containers
-               this.setupHighlightContainers( this.$element );
                this.setupNewChangesButtonContainer( this.$element );
        };
  
  
        OO.inheritClass( mw.rcfilters.ui.ChangesListWrapperWidget, OO.ui.Widget );
  
+       /**
+        * Respond to filters model initialize event
+        */
+       mw.rcfilters.ui.ChangesListWrapperWidget.prototype.onFiltersModelInitialize = function () {
+               this.filtersModelInitialized = true;
+               // Set up highlight containers. We need to wait for the filters model
+               // to be initialized, so we can make sure we have all the css class definitions
+               // we get from the server with our filters
+               this.setupHighlightContainers( this.$element );
+       };
+       /**
+        * Get all available highlight classes
+        *
+        * @return {string[]} An array of available highlight class names
+        */
+       mw.rcfilters.ui.ChangesListWrapperWidget.prototype.getHighlightClasses = function () {
+               if ( !this.highlightClasses || !this.highlightClasses.length ) {
+                       this.highlightClasses = this.filtersViewModel.getItemsSupportingHighlights()
+                               .map( function ( filterItem ) {
+                                       return filterItem.getCssClass();
+                               } );
+               }
+               return this.highlightClasses;
+       };
        /**
         * Respond to the highlight feature being toggled on and off
         *
@@@ -72,7 -99,7 +99,7 @@@
         * Respond to a filter item model update
         */
        mw.rcfilters.ui.ChangesListWrapperWidget.prototype.onItemUpdate = function () {
-               if ( this.filtersViewModel.isHighlightEnabled() ) {
+               if ( this.filtersModelInitialized && this.filtersViewModel.isHighlightEnabled() ) {
                        this.clearHighlight();
                        this.applyHighlight();
                }
         * @param {jQuery|string} $content The content of the updated changes list
         */
        mw.rcfilters.ui.ChangesListWrapperWidget.prototype.setupHighlightContainers = function ( $content ) {
-               var highlightClass = 'mw-rcfilters-ui-changesListWrapperWidget-highlights',
+               var $enhancedTopPageCell, $enhancedNestedPagesCell,
+                       widget = this,
+                       highlightClass = 'mw-rcfilters-ui-changesListWrapperWidget-highlights',
                        $highlights = $( '<div>' )
                                .addClass( highlightClass )
                                .append(
                                        $( '<div>' )
 +                                              .addClass( 'mw-rcfilters-ui-changesListWrapperWidget-highlights-circle' )
                                                .addClass( 'mw-rcfilters-ui-changesListWrapperWidget-highlights-color-none' )
                                                .prop( 'data-color', 'none' )
                                );
                        $highlights.append(
                                $( '<div>' )
                                        .addClass( 'mw-rcfilters-ui-changesListWrapperWidget-highlights-color-' + color )
 +                                      .addClass( 'mw-rcfilters-ui-changesListWrapperWidget-highlights-circle' )
                                        .prop( 'data-color', color )
                        );
                } );
  
                if ( this.inEnhancedMode() ) {
-                       // Enhanced RC
-                       $content.find( 'td.mw-enhanced-rc' )
-                               .parent()
+                       $enhancedTopPageCell = $content.find( 'table.mw-enhanced-rc.mw-collapsible' );
+                       $enhancedNestedPagesCell = $content.find( 'td.mw-enhanced-rc-nested' );
+                       // Enhanced RC highlight containers
+                       $content.find( 'table.mw-enhanced-rc tr:first-child' )
+                               .addClass( 'mw-rcfilters-ui-changesListWrapperWidget-enhanced-toplevel' )
                                .prepend(
                                        $( '<td>' )
                                                .append( $highlights.clone() )
                                );
+                       // We are adding and changing cells in a table that, despite having nested rows,
+                       // is actually all one big table. To do that right, we want to remove the 'placeholder'
+                       // cell from the top row, because we're actually adding that placeholder in the children
+                       // with the highlights.
+                       $content.find( 'table.mw-enhanced-rc tr:first-child td.mw-changeslist-line-prefix' )
+                               .detach();
+                       $content.find( 'table.mw-enhanced-rc tr:first-child td.mw-enhanced-rc' )
+                               .prop( 'colspan', '2' );
+                       $enhancedNestedPagesCell
+                               .before(
+                                       $( '<td>' )
+                                               .append( $highlights.clone().addClass( 'mw-enhanced-rc-nested' ) )
+                               );
+                       // We need to target the nested rows differently than the top rows so that the
+                       // LESS rules applies correctly. In top rows, the rule should highlight all but
+                       // the first 2 cells td:not( :nth-child( -n+2 ) and the nested rows, the rule
+                       // should highlight all but the first 3 cells td:not( :nth-child( -n+3 )
+                       $enhancedNestedPagesCell
+                               .closest( 'tr' )
+                               .addClass( 'mw-rcfilters-ui-changesListWrapperWidget-enhanced-nested' );
+                       // Go over pages that have sub results
+                       // HACK: We really only can collect those by targetting the collapsible class
+                       $enhancedTopPageCell.each( function () {
+                               var collectedClasses,
+                                       $table = $( this );
+                               // Go over <tr>s and pick up all recognized classes
+                               collectedClasses = widget.getHighlightClasses().filter( function ( className ) {
+                                       return $table.find( 'tr' ).hasClass( className );
+                               } );
+                               $table.find( 'tr:first-child' )
+                                       .addClass( collectedClasses.join( ' ' ) );
+                       } );
+                       $content.addClass( 'mw-rcfilters-ui-changesListWrapperWidget-enhancedView' );
                } else {
                        // Regular RC
                        $content.find( 'ul.special li' )
                }
        };
  
+       /**
+        * In enhanced mode, we need to check whether the grouped results all have the
+        * same active highlights in order to see whether the "parent" of the group should
+        * be grey or highlighted normally.
+        *
+        * This is called every time highlights are applied.
+        */
+       mw.rcfilters.ui.ChangesListWrapperWidget.prototype.updateEnhancedParentHighlight = function () {
+               var activeHighlightClasses,
+                       $enhancedTopPageCell = this.$element.find( 'table.mw-enhanced-rc.mw-collapsible' );
+               activeHighlightClasses = this.filtersViewModel.getCurrentlyUsedHighlightColors().map( function ( color ) {
+                       return 'mw-rcfilters-highlight-color-' + color;
+               } );
+               // Go over top pages and their children, and figure out if all sub-pages have the
+               // same highlights between themselves. If they do, the parent should be highlighted
+               // with all colors. If classes are different, the parent should receive a grey
+               // background
+               $enhancedTopPageCell.each( function () {
+                       var firstChildClasses, $rowsWithDifferentHighlights,
+                               $table = $( this );
+                       // Collect the relevant classes from the first nested child
+                       firstChildClasses = activeHighlightClasses.filter( function ( className ) {
+                               return $table.find( 'tr:nth-child(2)' ).hasClass( className );
+                       } );
+                       // Filter the non-head rows and see if they all have the same classes
+                       // to the first row
+                       $rowsWithDifferentHighlights = $table.find( 'tr:not(:first-child)' ).filter( function () {
+                               var classesInThisRow,
+                                       $this = $( this );
+                               classesInThisRow = activeHighlightClasses.filter( function ( className ) {
+                                       return $this.hasClass( className );
+                               } );
+                               return !OO.compare( firstChildClasses, classesInThisRow );
+                       } );
+                       // If classes are different, tag the row for using grey color
+                       $table.find( 'tr:first-child' )
+                               .toggleClass( 'mw-rcfilters-ui-changesListWrapperWidget-enhanced-grey', $rowsWithDifferentHighlights.length > 0 );
+               } );
+       };
        /**
         * @return {boolean} Whether the changes are grouped by page
         */
                        }
                } );
  
+               if ( this.inEnhancedMode() ) {
+                       this.updateEnhancedParentHighlight();
+               }
                // Turn on highlights
                this.$element.addClass( 'mw-rcfilters-ui-changesListWrapperWidget-highlighted' );
        };
                this.$element.find( '[data-highlightedFilters]' )
                        .removeAttr( 'title' )
                        .removeAttr( 'data-highlightedFilters' );
+               // Remove grey from enhanced rows
+               this.$element.find( '.mw-rcfilters-ui-changesListWrapperWidget-enhanced-grey' )
+                       .removeClass( 'mw-rcfilters-ui-changesListWrapperWidget-enhanced-grey' );
                // Turn off highlights
                this.$element.removeClass( 'mw-rcfilters-ui-changesListWrapperWidget-highlighted' );
        };