PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.1
16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 All 504 releases
← All changes | _inc/jetpack-modules.models.js +117 -33 13.7.216.3-a.1 View file →
@@ -1,7 +1,7 @@
1 1 window.jetpackModules = window.jetpackModules || {};
2 2
3 -window.jetpackModules.models = ( function( window, $, _, Backbone ) {
3 +window.jetpackModules.models = ( function ( window, $, Backbone ) {
4 4 'use strict';
5 5
6 6 var models = {};
7 7
@@ -8,36 +8,61 @@
8 8 models.Modules = Backbone.Model.extend( {
9 9 visibles: {},
10 10
11 11 /**
12 - * Updates modules.items dataset to be a reflection of both the current
13 - * modules.raw data, as well as any filters or sorting that may be in effect.
12 + * Applies the active availability, tag, state/purpose and search filters
13 + * to the raw module set and returns the matching items (unsorted).
14 + *
15 + * @param {Object} [options] Filtering options.
16 + * @param {boolean} [options.ignoreTag] When true, skip the "Show" tag
17 + * facet so counts can be computed independently of the current tag
18 + * selection (faceted counting).
19 + * @param {boolean} [options.ignorePurpose] When true, skip the "Purpose"
20 + * facet so its own faceted counts can be computed.
21 + * @return {Array} Filtered module items.
14 22 */
15 - filter_and_sort: function() {
23 + apply_filters: function ( options ) {
24 + options = options || {};
25 +
16 26 var subsubsub = $( '.subsubsub .current a' ),
17 - items = this.get( 'raw' ),
18 - m_filter = $( '.button-group.filter-active .active' ),
19 - m_sort = $( '.button-group.sort .active' ),
20 - m_search = $( '#srch-term-search-input' )
21 - .val()
22 - .toLowerCase(),
23 - groups;
27 + purpose = $( '.purpose-filter .current a' ),
28 + items = Object.values( this.get( 'raw' ) ),
29 + m_availability = $( '.button-group.availability-filter .active' ),
30 + m_search = $( '#srch-term-search-input' ).val().toLowerCase();
24 31
25 - // If a module filter has been selected, filter it!
26 - if ( ! subsubsub.closest( 'li' ).hasClass( 'all' ) ) {
27 - items = _.filter( items, function( item ) {
28 - return _.contains( item.module_tags, subsubsub.data( 'title' ) );
29 - } );
32 + // Offline-mode top-level filter: when "Hide unavailable" is selected,
33 + // drop every module that isn't available offline, regardless of the
34 + // other filters below it.
35 + if ( 'hide-unavailable' === m_availability.data( 'availability' ) ) {
36 + items = items.filter( item => item.available );
30 37 }
31 38
32 - if ( m_filter.data( 'filter-by' ) ) {
33 - items = _.filter( items, function( item ) {
34 - return item[ m_filter.data( 'filter-by' ) ] === m_filter.data( 'filter-value' );
35 - } );
39 + // Tag facet ("Show"). Skipped when computing that facet's own counts.
40 + if ( ! options.ignoreTag && ! subsubsub.closest( 'li' ).hasClass( 'all' ) ) {
41 + items = items.filter( item => item.module_tags.includes( subsubsub.data( 'title' ) ) );
36 42 }
37 43
44 + // Purpose facet. Skipped when computing that facet's own counts.
45 + if (
46 + ! options.ignorePurpose &&
47 + purpose.length &&
48 + ! purpose.closest( 'li' ).hasClass( 'all' )
49 + ) {
50 + items = items.filter( item => item.product_group === purpose.data( 'group' ) );
51 + }
52 +
53 + // State facet (segmented buttons). "All" carries no data-filter-by.
54 + $( '.button-group.module-filter .active' ).each( function () {
55 + var by = $( this ).data( 'filter-by' ),
56 + value = $( this ).data( 'filter-value' );
57 +
58 + if ( by ) {
59 + items = items.filter( item => item[ by ] === value );
60 + }
61 + } );
62 +
38 63 if ( m_search.length ) {
39 - items = _.filter( items, function( item ) {
64 + items = items.filter( function ( item ) {
40 65 var search_text =
41 66 item.name +
42 67 ' ' +
43 68 item.description +
@@ -50,21 +75,80 @@
50 75 return -1 !== search_text.toLowerCase().indexOf( m_search );
51 76 } );
52 77 }
53 78
54 - if ( m_sort.data( 'sort-by' ) ) {
55 - items = _.sortBy( items, m_sort.data( 'sort-by' ) );
56 - if ( 'reverse' === m_sort.data( 'sort-order' ) ) {
57 - items.reverse();
79 + return items;
80 + },
81 +
82 + /**
83 + * Computes, for the "Show" tag list, how many modules match every active
84 + * filter except the tag facet itself. This powers faceted counts that
85 + * stay meaningful as the user narrows the other filters.
86 + *
87 + * @return {Object} Map of translated tag title to count, plus an
88 + * `__all__` key holding the total across all tags.
89 + */
90 + tag_counts: function () {
91 + var base = this.apply_filters( { ignoreTag: true } ),
92 + counts = { __all__: base.length };
93 +
94 + base.forEach( function ( item ) {
95 + ( item.module_tags || [] ).forEach( function ( tag ) {
96 + counts[ tag ] = ( counts[ tag ] || 0 ) + 1;
97 + } );
98 + } );
99 +
100 + return counts;
101 + },
102 +
103 + /**
104 + * Computes, for the "Purpose" list, how many modules fall in each product
105 + * group under every active filter except the Purpose facet itself. Mirrors
106 + * tag_counts() so the two facet lists behave identically.
107 + *
108 + * @return {Object} Map of product-group slug to count, plus an `__all__`
109 + * key holding the total across all groups.
110 + */
111 + purpose_counts: function () {
112 + var base = this.apply_filters( { ignorePurpose: true } ),
113 + counts = { __all__: base.length };
114 +
115 + base.forEach( function ( item ) {
116 + if ( item.product_group ) {
117 + counts[ item.product_group ] = ( counts[ item.product_group ] || 0 ) + 1;
58 118 }
59 - }
119 + } );
60 120
61 - // Sort unavailable modules to the end if the user is running in local mode.
62 - groups = _.groupBy( items, 'available' );
63 - if ( _.has( groups, 'false' ) ) {
64 - items = [].concat( groups[ true ], groups[ false ] );
65 - }
121 + return counts;
122 + },
66 123
124 + /**
125 + * Updates modules.items dataset to be a reflection of both the current
126 + * modules.raw data, as well as any filters or sorting that may be in effect.
127 + */
128 + filter_and_sort: function () {
129 + var items = this.apply_filters();
130 +
131 + // Default alphabetical order by name. The product-group sort below is
132 + // stable, so this ordering is preserved within each group.
133 + items.sort( ( a, b ) => a.name.localeCompare( b.name ) );
134 +
135 + // Group by product group (primary), then push unavailable modules to
136 + // the end of their group. Any user-selected sort above is preserved
137 + // within a group because Array.prototype.sort is stable in the
138 + // browsers we support.
139 + var groupOrder = { growth: 0, performance: 1, security: 2, other: 3 };
140 + items.sort( function ( a, b ) {
141 + var ga = groupOrder[ a.product_group ] !== undefined ? groupOrder[ a.product_group ] : 99;
142 + var gb = groupOrder[ b.product_group ] !== undefined ? groupOrder[ b.product_group ] : 99;
143 +
144 + if ( ga !== gb ) {
145 + return ga - gb;
146 + }
147 +
148 + return ( b.available ? 1 : 0 ) - ( a.available ? 1 : 0 );
149 + } );
150 +
67 151 // Now shove it back in.
68 152 this.set( 'items', items );
69 153
70 154 return this;
@@ -69,9 +153,9 @@
69 153
70 154 return this;
71 155 },
72 156
73 - initialize: function() {
157 + initialize: function () {
74 158 var items = this.get( 'items' );
75 159 this.set( 'raw', items );
76 160 },
77 161 } );
@@ -76,5 +160,5 @@
76 160 },
77 161 } );
78 162
79 163 return models;
80 -} )( window, jQuery, _, Backbone );
164 +} )( window, jQuery, Backbone );