ThemeGrill Demo Importer - Version 1.1.0

Version Description

  • Feature - Backbone views interface for demos and previews
  • Feature - Added download button for the preview not installed
  • Feature - Robust quick search view to change in demo interface
  • Feature - Detailed information in popup to showcase plugins, events, etc
  • Refactor - Replace old way to import demo with new wp.updates methods
  • Fix - Menu classes to hide admin menu if no JS using css class hide-if-no-js
  • Fix - Remove the old demo pack if found and to update the new demo packs
Download this release

Release Info

Developer ThemeGrill
Plugin Icon 128x128 ThemeGrill Demo Importer
Version 1.1.0
Comparing to
See all releases

Code changes from version 1.0 to 1.1.0

.stylelintrc ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "rules": {
3
+ "indentation": "tab",
4
+ "color-hex-case": "lower",
5
+ "color-no-invalid-hex": true,
6
+
7
+ "function-calc-no-unspaced-operator": true,
8
+ "function-comma-space-after": "always-single-line",
9
+ "function-comma-space-before": "never",
10
+ "function-name-case": "lower",
11
+ "function-url-quotes": "always",
12
+ "function-whitespace-after": "always",
13
+
14
+ "number-leading-zero": "always",
15
+ "number-no-trailing-zeros": true,
16
+ "length-zero-no-unit": true,
17
+
18
+ "string-no-newline": true,
19
+ "string-quotes": "single",
20
+
21
+ "unit-case": "lower",
22
+ "unit-no-unknown": true,
23
+ "unit-whitelist": ["px", "%", "deg", "ms", "em", "vh", "vw", "rem", "s", "ex", "pt", "cm"],
24
+
25
+ "value-list-comma-space-after": "always-single-line",
26
+ "value-list-comma-space-before": "never",
27
+
28
+ "shorthand-property-no-redundant-values": true,
29
+
30
+ "property-case": "lower",
31
+
32
+ "declaration-block-no-duplicate-properties": [true, { "severity": "warning" } ],
33
+ "declaration-block-no-ignored-properties": [true, { "severity": "warning" } ],
34
+ "declaration-block-trailing-semicolon": "always",
35
+ "declaration-block-single-line-max-declarations": 0,
36
+ "declaration-block-semicolon-space-before": "never",
37
+ "declaration-block-semicolon-space-after": "always-single-line",
38
+ "declaration-block-semicolon-newline-before": "never-multi-line",
39
+ "declaration-block-semicolon-newline-after": "always-multi-line",
40
+
41
+ "block-closing-brace-newline-after": "always",
42
+ "block-closing-brace-newline-before": "always-multi-line",
43
+ "block-no-empty": true,
44
+ "block-opening-brace-newline-after": "always-multi-line",
45
+ "block-opening-brace-space-before": "always",
46
+
47
+ "selector-attribute-brackets-space-inside": "never",
48
+ "selector-attribute-operator-space-after": "never",
49
+ "selector-attribute-operator-space-before": "never",
50
+ "selector-combinator-space-after": "always",
51
+ "selector-combinator-space-before": "always",
52
+ "selector-pseudo-class-case": "lower",
53
+ "selector-pseudo-class-parentheses-space-inside": "always",
54
+ "selector-pseudo-element-case": "lower",
55
+ "selector-pseudo-element-colon-notation": "double",
56
+ "selector-pseudo-element-no-unknown": true,
57
+ "selector-type-case": "lower",
58
+ }
59
+ }
assets/css/demo-importer.css CHANGED
@@ -1 +1 @@
1
- .appearance_page_demo-importer .wp-filter{padding:0 20px}.appearance_page_demo-importer .wp-filter .filter-links li>a:focus{box-shadow:none}.theme-browser .theme .theme-actions span.spinner{display:none}.theme-browser .theme .theme-actions.imported,.theme-browser .theme .theme-actions.importing{opacity:1}.theme-browser .theme .theme-actions.imported .button,.theme-browser .theme .theme-actions.importing .button,.theme-browser .theme:not(.active) .theme-actions .preview{display:none;visibility:hidden}.theme-browser .theme:not(.active) .theme-actions.imported .preview{display:block;visibility:visible}.theme-browser .theme .theme-actions.importing span.spinner.is-active{width:auto;display:block;font-size:12px;padding-right:25px;background-position:right center}@media only screen and (max-width:780px){.theme-browser .theme .theme-actions.imported{opacity:0}}.theme .notice,.theme .notice.is-dismissible{left:0;margin:0;position:absolute;right:0;top:0}.theme .notice-success p::before{color:#79ba49;content:'\f147';margin-right:6px;vertical-align:top;display:inline-block;font:400 20px/1 dashicons;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.tips{cursor:help;text-decoration:none}img.tips{padding:5px 0 0}#tiptip_holder{display:none;position:absolute;top:0;left:0;z-index:9999999}#tiptip_holder.tip_top{padding-bottom:5px}#tiptip_holder.tip_top #tiptip_arrow_inner{margin-top:-7px;margin-left:-6px;border-top-color:#333}#tiptip_holder.tip_bottom{padding-top:5px}#tiptip_holder.tip_bottom #tiptip_arrow_inner{margin-top:-5px;margin-left:-6px;border-bottom-color:#333}#tiptip_holder.tip_right{padding-left:5px}#tiptip_holder.tip_right #tiptip_arrow_inner{margin-top:-6px;margin-left:-5px;border-right-color:#333}#tiptip_holder.tip_left{padding-right:5px}#tiptip_holder.tip_left #tiptip_arrow_inner{margin-top:-6px;margin-left:-7px;border-left-color:#333}#tiptip_content{color:#fff;font-size:.8em;max-width:150px;background:#333;text-align:center;border-radius:3px;padding:.618em 1em;box-shadow:0 1px 3px rgba(0,0,0,.2)}#tiptip_content code{padding:1px;background:#888}#tiptip_arrow,#tiptip_arrow_inner{position:absolute;border-color:transparent;border-style:solid;border-width:6px;height:0;width:0}
1
+ #tiptip_content,table.plugins-list-table .plugin-status{text-align:center}.appearance_page_demo-importer .demo-importer h1{margin-bottom:15px}.appearance_page_demo-importer .demo-importer .wp-filter-search{position:relative;top:-2px;left:20px;margin:0;width:280px;font-size:16px;font-weight:300;line-height:1.5}.appearance_page_demo-importer .demo-installer .wp-filter{padding:0 20px}.appearance_page_demo-importer .demo-installer .wp-filter .filter-links li>a:focus{box-shadow:none}.appearance_page_demo-importer .demo-installer .welcome-panel{margin:0}table.plugins-list-table{margin-bottom:1em}table.plugins-list-table tr:nth-child(2n) td{background:#fcfcfc}table.plugins-list-table th{padding:9px;font-weight:600}table.plugins-list-table td{padding:9px;font-size:1.1em}table.plugins-list-table td mark{background:0 0}table.plugins-list-table td mark.yes{color:#7ad03a}table.plugins-list-table td mark.no{color:#999}table.plugins-list-table td mark.error{color:#a00}.tips{cursor:help;text-decoration:none}img.tips{padding:5px 0 0}#tiptip_holder{display:none;position:absolute;top:0;left:0;z-index:9999999}#tiptip_holder.tip_top{padding-bottom:5px}#tiptip_holder.tip_top #tiptip_arrow_inner{margin-top:-7px;margin-left:-6px;border-top-color:#333}#tiptip_holder.tip_bottom{padding-top:5px}#tiptip_holder.tip_bottom #tiptip_arrow_inner{margin-top:-5px;margin-left:-6px;border-bottom-color:#333}#tiptip_holder.tip_right{padding-left:5px}#tiptip_holder.tip_right #tiptip_arrow_inner{margin-top:-6px;margin-left:-5px;border-right-color:#333}#tiptip_holder.tip_left{padding-right:5px}#tiptip_holder.tip_left #tiptip_arrow_inner{margin-top:-6px;margin-left:-7px;border-left-color:#333}#tiptip_content{color:#fff;font-size:.8em;max-width:150px;background:#333;border-radius:3px;padding:.618em 1em;box-shadow:0 1px 3px rgba(0,0,0,.2)}#tiptip_content code{padding:1px;background:#888}#tiptip_arrow,#tiptip_arrow_inner{position:absolute;border-color:transparent;border-style:solid;border-width:6px;height:0;width:0}
assets/css/demo-importer.scss CHANGED
@@ -1,68 +1,87 @@
1
/**
2
- * Styling begins.
3
*/
4
- .appearance_page_demo-importer .wp-filter {
5
- padding: 0 20px;
6
7
- .filter-links li > a:focus {
8
- box-shadow: none;
9
}
10
- }
11
12
- .theme-browser .theme .theme-actions span.spinner {
13
- display: none;
14
- }
15
16
- .theme-browser .theme .theme-actions.imported,
17
- .theme-browser .theme .theme-actions.importing {
18
- opacity: 1;
19
- }
20
21
- .theme-browser .theme .theme-actions.imported .button,
22
- .theme-browser .theme .theme-actions.importing .button,
23
- .theme-browser .theme:not( .active ) .theme-actions .preview {
24
- display: none;
25
- visibility: hidden;
26
}
27
28
- .theme-browser .theme:not( .active ) .theme-actions.imported .preview {
29
- display: block;
30
- visibility: visible;
31
- }
32
33
- .theme-browser .theme .theme-actions.importing span.spinner.is-active {
34
- width: auto;
35
- display: block;
36
- font-size: 12px;
37
- padding-right: 25px;
38
- background-position: right center;
39
- }
40
41
- @media only screen and (max-width: 780px) {
42
- .theme-browser .theme .theme-actions.imported {
43
- opacity: 0;
44
}
45
- }
46
47
- /* Position admin messages */
48
- .theme .notice,
49
- .theme .notice.is-dismissible {
50
- left: 0;
51
- margin: 0;
52
- position: absolute;
53
- right: 0;
54
- top: 0;
55
- }
56
57
- .theme .notice-success p::before {
58
- color: #79ba49;
59
- content: '\f147';
60
- margin-right: 6px;
61
- vertical-align: top;
62
- display: inline-block;
63
- font: normal 20px/1 'dashicons';
64
- -webkit-font-smoothing: antialiased;
65
- -moz-osx-font-smoothing: grayscale;
66
}
67
68
/**
1
/**
2
+ * demo-importer.scss
3
+ * General ThemeGrill Demo Importer admin styles.
4
*/
5
6
+ /**
7
+ * Imports
8
+ */
9
+ @import 'bourbon';
10
+
11
+ /**
12
+ * Styling begins
13
+ */
14
+ .appearance_page_demo-importer {
15
+ .demo-importer {
16
+ h1 {
17
+ margin-bottom: 15px;
18
+ }
19
+
20
+ .wp-filter-search {
21
+ position: relative;
22
+ top: -2px;
23
+ left: 20px;
24
+ margin: 0;
25
+ width: 280px;
26
+ font-size: 16px;
27
+ font-weight: 300;
28
+ line-height: 1.5;
29
+ }
30
}
31
32
+ .demo-installer {
33
+ .wp-filter {
34
+ padding: 0 20px;
35
36
+ .filter-links li > a:focus {
37
+ box-shadow: none;
38
+ }
39
+ }
40
41
+ .welcome-panel {
42
+ margin: 0;
43
+ }
44
+ }
45
}
46
47
+ table.plugins-list-table {
48
+ margin-bottom: 1em;
49
50
+ tr:nth-child( 2n ) {
51
+ td {
52
+ background: #fcfcfc;
53
+ }
54
+ }
55
56
+ th {
57
+ padding: 9px;
58
+ font-weight: 600;
59
}
60
61
+ td {
62
+ padding: 9px;
63
+ font-size: 1.1em;
64
+
65
+ mark {
66
+ background: transparent none;
67
+ }
68
69
+ mark.yes {
70
+ color: #7ad03a;
71
+ }
72
+
73
+ mark.no {
74
+ color: #999;
75
+ }
76
+
77
+ mark.error {
78
+ color: #a00;
79
+ }
80
+ }
81
+
82
+ .plugin-status {
83
+ text-align: center;
84
+ }
85
}
86
87
/**
assets/js/admin/demo-importer.js CHANGED
@@ -1,96 +1,1224 @@
1
- /* global demo_importer_params */
2
- jQuery( function ( $ ) {
3
-
4
- var tg_demo_importer = {
5
- init: function() {
6
- this.uploader();
7
- this.init_tiptip();
8
-
9
- // Trigger importer events.
10
- $( '.theme-actions' ).on( 'click', '.import', this.process_import );
11
- $( '.notice.is-dismissible' ).on( 'click', '.notice-dismiss', this.dismiss_notice );
12
- },
13
- uploader: function() {
14
- var uploadViewToggle = $( '.upload-view-toggle' ),
15
- $body = $( document.body );
16
-
17
- uploadViewToggle.on( 'click', function() {
18
- // Toggle the upload view.
19
- $body.toggleClass( 'show-upload-view' );
20
- // Toggle the `aria-expanded` button attribute.
21
- uploadViewToggle.attr( 'aria-expanded', $body.hasClass( 'show-upload-view' ) );
22
- });
23
- },
24
- init_tiptip: function() {
25
- $( '#tiptip_holder' ).removeAttr( 'style' );
26
- $( '#tiptip_arrow' ).removeAttr( 'style' );
27
- $( '.tips' ).tipTip({ 'attribute': 'data-tip', 'fadeIn': 50, 'fadeOut': 50, 'delay': 200 });
28
- },
29
- process_import: function( e ) {
30
- e.preventDefault();
31
-
32
- var $this_el = $( this );
33
-
34
- if ( ! $this_el.hasClass( 'disabled' ) ) {
35
- if ( window.confirm( demo_importer_params.i18n_import_dummy_data ) ) {
36
-
37
- var data = {
38
- action: 'tg_import_demo_data',
39
- demo_id: $this_el.data( 'demo_id' ),
40
- security: demo_importer_params.import_demo_data_nonce
41
- };
42
-
43
- $.ajax({
44
- url: demo_importer_params.ajax_url,
45
- data: data,
46
- type: 'POST',
47
- beforeSend: function() {
48
- $this_el.parent().addClass( 'importing' );
49
- $this_el.parent().find( '.spinner' ).addClass( 'is-active' );
50
- },
51
- success: function( response ) {
52
- $this_el.closest( '.theme' ).find( '.notice' ).remove();
53
- $this_el.parent().find( '.spinner' ).removeClass( 'is-active' );
54
- $this_el.parent().removeClass( 'importing' ).addClass( 'imported' );
55
-
56
- // Display import message.
57
- if ( true === response.success ) {
58
- $this_el.closest( '.theme' ).append( '<div class="notice notice-success notice-alt"><p>' + response.data.message + '</p></div>' );
59
- } else {
60
- $this_el.closest( '.theme' ).append( '<div class="update-message notice notice-error notice-alt"><p>' + demo_importer_params.i18n_import_data_error + '</p></div>' );
61
- }
62
- },
63
- error: function( jqXHR, textStatus, errorThrown ) {
64
- $this_el.closest( '.theme' ).find( '.notice' ).remove();
65
- $this_el.parent().find( '.spinner' ).removeClass( 'is-active' );
66
- $this_el.parent().removeClass( 'importing' ).addClass( 'imported' );
67
-
68
- // Display error message.
69
- $this_el.closest( '.theme' ).append( '<div class="update-message notice notice-error notice-alt"><p>' + errorThrown + '</p></div>' );
70
- }
71
- });
72
}
73
74
- return false;
75
}
76
- },
77
- dismiss_notice: function( e ) {
78
- e.preventDefault();
79
80
- var $this_el = $( this );
81
82
- if ( $this_el.parent().attr( 'id' ) === 'undefined' ) {
83
return;
84
}
85
86
- $.post( demo_importer_params.ajax_url, {
87
- action: 'tg_dismiss_notice',
88
- notice_id: $this_el.parent().data( 'notice_id' )
89
});
90
91
- return false;
92
}
93
- };
94
95
- tg_demo_importer.init();
96
});
1
+ /* global demoImporterLocalizeScript */
2
+ window.wp = window.wp || {};
3
+
4
+ ( function( $ ) {
5
+
6
+ // Set up our namespace...
7
+ var demos, l10n;
8
+ demos = wp.demos = wp.demos || {};
9
+
10
+ // Store the demo data and settings for organized and quick access
11
+ // demos.data.settings, demos.data.demos, demos.data.l10n
12
+ demos.data = demoImporterLocalizeScript;
13
+ l10n = demos.data.l10n;
14
+
15
+ // Shortcut for isPreview check
16
+ demos.isPreview = !! demos.data.settings.isPreview;
17
+
18
+ // Shortcut for isInstall check
19
+ demos.isInstall = !! demos.data.settings.isInstall;
20
+
21
+ // Setup app structure
22
+ _.extend( demos, { model: {}, view: {}, routes: {}, router: {}, template: wp.template });
23
+
24
+ demos.Model = Backbone.Model.extend({
25
+ // Adds attributes to the default data coming through the .org demos api
26
+ // Map `id` to `slug` for shared code
27
+ initialize: function() {
28
+ var description;
29
+
30
+ // If demo is already installed, set an attribute.
31
+ if ( _.indexOf( demos.data.installedDemos, this.get( 'slug' ) ) !== -1 ) {
32
+ this.set({ installed: true });
33
+ }
34
+
35
+ // Set the attributes
36
+ this.set({
37
+ // slug is for installation, id is for existing.
38
+ id: this.get( 'slug' ) || this.get( 'id' )
39
+ });
40
+
41
+ // Map `section.description` to `description`
42
+ // as the API sometimes returns it differently
43
+ if ( this.has( 'sections' ) ) {
44
+ description = this.get( 'sections' ).description;
45
+ this.set({ description: description });
46
+ }
47
+ }
48
+ });
49
+
50
+ // Main view controller for demo importer
51
+ // Unifies and renders all available views
52
+ demos.view.Appearance = wp.Backbone.View.extend({
53
+
54
+ el: '#wpbody-content .wrap .theme-browser',
55
+
56
+ window: $( window ),
57
+ // Pagination instance
58
+ page: 0,
59
+
60
+ // Sets up a throttler for binding to 'scroll'
61
+ initialize: function( options ) {
62
+ // Scroller checks how far the scroll position is
63
+ _.bindAll( this, 'scroller' );
64
+
65
+ this.SearchView = options.SearchView ? options.SearchView : demos.view.Search;
66
+ // Bind to the scroll event and throttle
67
+ // the results from this.scroller
68
+ this.window.bind( 'scroll', _.throttle( this.scroller, 300 ) );
69
+ },
70
+
71
+ // Main render control
72
+ render: function() {
73
+ // Setup the main demo view
74
+ // with the current demo collection
75
+ this.view = new demos.view.Demos({
76
+ collection: this.collection,
77
+ parent: this
78
+ });
79
+
80
+ // Render search form.
81
+ this.search();
82
+
83
+ // Render and append
84
+ this.view.render();
85
+ this.$el.empty().append( this.view.el ).addClass( 'rendered' );
86
+ },
87
+
88
+ // Defines search element container
89
+ searchContainer: $( '#wpbody h1:first' ),
90
+
91
+ // Search input and view
92
+ // for current demo collection
93
+ search: function() {
94
+ var view,
95
+ self = this;
96
+
97
+ // Don't render the search if there is only one demo
98
+ if ( demos.data.demos.length === 1 ) {
99
+ return;
100
+ }
101
+
102
+ view = new this.SearchView({
103
+ collection: self.collection,
104
+ parent: this
105
+ });
106
+
107
+ // Render and append after screen title
108
+ view.render();
109
+ this.searchContainer
110
+ .append( $.parseHTML( '<label class="screen-reader-text" for="wp-filter-search-input">' + l10n.search + '</label>' ) )
111
+ .append( view.el );
112
+ },
113
+
114
+ // Checks when the user gets close to the bottom
115
+ // of the mage and triggers a demo:scroll event
116
+ scroller: function() {
117
+ var self = this,
118
+ bottom, threshold;
119
+
120
+ bottom = this.window.scrollTop() + self.window.height();
121
+ threshold = self.$el.offset().top + self.$el.outerHeight( false ) - self.window.height();
122
+ threshold = Math.round( threshold * 0.9 );
123
+
124
+ if ( bottom > threshold ) {
125
+ this.trigger( 'demo:scroll' );
126
+ }
127
+ },
128
+
129
+ // Remove any lingering tooltips and initialize TipTip
130
+ initTipTip: function() {
131
+ $( '#tiptip_holder' ).removeAttr( 'style' );
132
+ $( '#tiptip_arrow' ).removeAttr( 'style' );
133
+ $( '.tips' ).tipTip({ 'attribute': 'data-tip', 'fadeIn': 50, 'fadeOut': 50, 'delay': 50 });
134
+ }
135
+ });
136
+
137
+ // Set up the Collection for our demo data
138
+ // @has 'id' 'name' 'screenshot' 'author' 'authorURI' 'version' 'active' ...
139
+ demos.Collection = Backbone.Collection.extend({
140
+
141
+ model: demos.Model,
142
+
143
+ // Search terms
144
+ terms: '',
145
+
146
+ // Controls searching on the current theme collection
147
+ // and triggers an update event
148
+ doSearch: function( value ) {
149
+
150
+ // Don't do anything if we've already done this search
151
+ // Useful because the Search handler fires multiple times per keystroke
152
+ if ( this.terms === value ) {
153
+ return;
154
+ }
155
+
156
+ // Updates terms with the value passed
157
+ this.terms = value;
158
+
159
+ // If we have terms, run a search...
160
+ if ( this.terms.length > 0 ) {
161
+ this.search( this.terms );
162
+ }
163
+
164
+ // If search is blank, show all demos
165
+ // Useful for resetting the views when you clean the input
166
+ if ( this.terms === '' ) {
167
+ this.reset( demos.data.demos );
168
+ $( 'body' ).removeClass( 'no-results' );
169
+ }
170
+
171
+ // Trigger a 'demos:update' event
172
+ this.trigger( 'demos:update' );
173
+ },
174
+
175
+ // Performs a search within the collection
176
+ // @uses RegExp
177
+ search: function( term ) {
178
+ var match, results, haystack, name, description, author;
179
+
180
+ // Start with a full collection
181
+ this.reset( demos.data.demos, { silent: true } );
182
+
183
+ // Escape the term string for RegExp meta characters
184
+ term = term.replace( /[-\/\\^$*+?.()|[\]{}]/g, '\\amp;' );
185
+
186
+ // Consider spaces as word delimiters and match the whole string
187
+ // so matching terms can be combined
188
+ term = term.replace( / /g, ')(?=.*' );
189
+ match = new RegExp( '^(?=.*' + term + ').+', 'i' );
190
+
191
+ // Find results
192
+ // _.filter and .test
193
+ results = this.filter( function( data ) {
194
+ name = data.get( 'name' ).replace( /(<([^>]+)>)/ig, '' );
195
+ description = data.get( 'description' ).replace( /(<([^>]+)>)/ig, '' );
196
+ author = data.get( 'author' ).replace( /(<([^>]+)>)/ig, '' );
197
+
198
+ haystack = _.union( [ name, data.get( 'id' ), description, author, data.get( 'tags' ) ] );
199
+
200
+ if ( match.test( data.get( 'author' ) ) && term.length > 2 ) {
201
+ data.set( 'displayAuthor', true );
202
+ }
203
+
204
+ return match.test( haystack );
205
+ });
206
+
207
+ if ( results.length === 0 ) {
208
+ this.trigger( 'query:empty' );
209
+ } else {
210
+ $( 'body' ).removeClass( 'no-results' );
211
+ }
212
+
213
+ this.reset( results );
214
+ },
215
+
216
+ // Paginates the collection with a helper method
217
+ // that slices the collection
218
+ paginate: function( instance ) {
219
+ var collection = this;
220
+ instance = instance || 0;
221
+
222
+ // Demos per instance are set at 20
223
+ collection = _( collection.rest( 20 * instance ) );
224
+ collection = _( collection.first( 20 ) );
225
+
226
+ return collection;
227
+ }
228
+ });
229
+
230
+ // This is the view that controls each demo item
231
+ // that will be displayed on the screen
232
+ demos.view.Demo = wp.Backbone.View.extend({
233
+
234
+ // Wrap demo data on a div.theme element
235
+ className: 'theme',
236
+
237
+ // Reflects which demo view we have
238
+ // 'grid' (default) or 'detail'
239
+ state: 'grid',
240
+
241
+ // The HTML template for each element to be rendered
242
+ html: demos.template( demos.isPreview ? 'demo-preview' : 'demo' ),
243
+
244
+ events: {
245
+ 'click': 'expand',
246
+ 'keydown': 'expand',
247
+ 'touchend': 'expand',
248
+ 'keyup': 'addFocus',
249
+ 'touchmove': 'preventExpand',
250
+ 'click .demo-import': 'importDemo'
251
+ },
252
+
253
+ touchDrag: false,
254
+
255
+ initialize: function() {
256
+ this.model.on( 'change', this.render, this );
257
+ },
258
+
259
+ render: function() {
260
+ var data = this.model.toJSON();
261
+
262
+ // Render demos using the html template
263
+ this.$el.html( this.html( data ) ).attr({
264
+ tabindex: 0,
265
+ 'aria-describedby' : data.id + '-action ' + data.id + '-name',
266
+ 'data-slug': data.id
267
+ });
268
+
269
+ // Renders active demo styles
270
+ this.activeDemo();
271
+
272
+ if ( this.model.get( 'displayAuthor' ) ) {
273
+ this.$el.addClass( 'display-author' );
274
+ }
275
+ },
276
+
277
+ // Adds a class to the currently active demo
278
+ // and to the overlay in detailed view mode
279
+ activeDemo: function() {
280
+ if ( this.model.get( 'active' ) ) {
281
+ this.$el.addClass( 'active' );
282
+ }
283
+ },
284
+
285
+ // Add class of focus to the demo we are focused on.
286
+ addFocus: function() {
287
+ var $demoToFocus = ( $( ':focus' ).hasClass( 'theme' ) ) ? $( ':focus' ) : $(':focus').parents('.theme');
288
+
289
+ $('.theme.focus').removeClass('focus');
290
+ $demoToFocus.addClass('focus');
291
+ },
292
+
293
+ // Single theme overlay screen
294
+ // It's shown when clicking a theme
295
+ expand: function( event ) {
296
+ var self = this;
297
+
298
+ // Prevent the modal.
299
+ if ( demos.isPreview ) {
300
+ return;
301
+ }
302
+
303
+ event = event || window.event;
304
+
305
+ // 'enter' and 'space' keys expand the details view when a theme is :focused
306
+ if ( event.type === 'keydown' && ( event.which !== 13 && event.which !== 32 ) ) {
307
+ return;
308
+ }
309
+
310
+ // Bail if the user scrolled on a touch device
311
+ if ( this.touchDrag === true ) {
312
+ return this.touchDrag = false;
313
+ }
314
+
315
+ // Prevent the modal from showing when the user clicks
316
+ // one of the direct action buttons
317
+ if ( $( event.target ).is( '.theme-actions a' ) ) {
318
+ return;
319
+ }
320
+
321
+ // Prevent the modal from showing when the user clicks one of the direct action buttons.
322
+ if ( $( event.target ).is( '.theme-actions a, .update-message, .button-link, .notice-dismiss' ) ) {
323
+ return;
324
+ }
325
+
326
+ // Set focused demo to current element
327
+ demos.focusedDemo = this.$el;
328
+
329
+ this.trigger( 'demo:expand', self.model.cid );
330
+ },
331
+
332
+ preventExpand: function() {
333
+ this.touchDrag = true;
334
+ },
335
+
336
+ importDemo: function() {
337
+ var _this = this,
338
+ $target = $( event.target );
339
+ event.preventDefault();
340
+
341
+ if ( $target.hasClass( 'disabled' ) ) {
342
+ return;
343
+ }
344
+
345
+ // Confirmation dialog for importing a demo.
346
+ if ( ! window.confirm( wp.demos.data.settings.confirmImport ) ) {
347
+ return;
348
+ }
349
+
350
+ $( document ).on( 'wp-demo-import-success', function( event, response ) {
351
+ if ( _this.model.get( 'id' ) === response.slug ) {
352
+ _this.model.set( { 'imported': true } );
353
+ }
354
+ } );
355
+
356
+ wp.updates.importDemo( {
357
+ slug: $target.data( 'slug' )
358
+ } );
359
+ }
360
+ });
361
+
362
+ // Demo Details view
363
+ // Set ups a modal overlay with the expanded demo data
364
+ demos.view.Details = wp.Backbone.View.extend({
365
+
366
+ // Wrap theme data on a div.theme element
367
+ className: 'theme-overlay',
368
+
369
+ events: {
370
+ 'click': 'collapse',
371
+ 'click .delete-demo': 'deleteDemo',
372
+ 'click .left': 'previousDemo',
373
+ 'click .right': 'nextDemo',
374
+ 'click .demo-import': 'importDemo'
375
+ },
376
+
377
+ // The HTML template for the theme overlay
378
+ html: demos.template( 'demo-single' ),
379
+
380
+ render: function() {
381
+ var data = this.model.toJSON();
382
+ this.$el.html( this.html( data ) );
383
+ // Renders active theme styles
384
+ this.activeDemo();
385
+ // Set up navigation events
386
+ this.navigation();
387
+ // Checks screenshot size
388
+ this.screenshotCheck( this.$el );
389
+ // Contain "tabbing" inside the overlay
390
+ this.containFocus( this.$el );
391
+ },
392
+
393
+ // Adds a class to the currently active theme
394
+ // and to the overlay in detailed view mode
395
+ activeDemo: function() {
396
+ // Check the model has the active property
397
+ this.$el.toggleClass( 'active', this.model.get( 'active' ) );
398
+ },
399
+
400
+ // Set initial focus and constrain tabbing within the theme browser modal.
401
+ containFocus: function( $el ) {
402
+
403
+ // Set initial focus on the primary action control.
404
+ _.delay( function() {
405
+ $( '.theme-wrap a.button-primary:visible' ).focus();
406
+ }, 100 );
407
+
408
+ // Constrain tabbing within the modal.
409
+ $el.on( 'keydown.wp-themes', function( event ) {
410
+ var $firstFocusable = $el.find( '.theme-header button:not(.disabled)' ).first(),
411
+ $lastFocusable = $el.find( '.theme-actions a:visible' ).last();
412
+
413
+ // Check for the Tab key.
414
+ if ( 9 === event.which ) {
415
+ if ( $firstFocusable[0] === event.target && event.shiftKey ) {
416
+ $lastFocusable.focus();
417
+ event.preventDefault();
418
+ } else if ( $lastFocusable[0] === event.target && ! event.shiftKey ) {
419
+ $firstFocusable.focus();
420
+ event.preventDefault();
421
+ }
422
+ }
423
+ });
424
+ },
425
+
426
+ // Single demo overlay screen
427
+ // It's shown when clicking a demo
428
+ collapse: function( event ) {
429
+ var self = this,
430
+ scroll;
431
+
432
+ event = event || window.event;
433
+
434
+ // Prevent collapsing detailed view when there is only one demo available
435
+ if ( demos.data.demos.length === 1 ) {
436
+ return;
437
+ }
438
+
439
+ // Detect if the click is inside the overlay
440
+ // and don't close it unless the target was
441
+ // the div.back button
442
+ if ( $( event.target ).is( '.theme-backdrop' ) || $( event.target ).is( '.close' ) || event.keyCode === 27 ) {
443
+
444
+ // Add a temporary closing class while overlay fades out
445
+ $( 'body' ).addClass( 'closing-overlay' );
446
+
447
+ // With a quick fade out animation
448
+ this.$el.fadeOut( 130, function() {
449
+ // Clicking outside the modal box closes the overlay
450
+ $( 'body' ).removeClass( 'closing-overlay' );
451
+ // Handle event cleanup
452
+ self.closeOverlay();
453
+
454
+ // Get scroll position to avoid jumping to the top
455
+ scroll = document.body.scrollTop;
456
+
457
+ // Clean the url structure
458
+ demos.router.navigate( demos.router.baseUrl( '' ) );
459
+
460
+ // Restore scroll position
461
+ document.body.scrollTop = scroll;
462
+
463
+ // Return focus to the demo div
464
+ if ( demos.focusedDemo ) {
465
+ demos.focusedDemo.focus();
466
}
467
+ });
468
+ }
469
+ },
470
+
471
+ // Handles .disabled classes for next/previous buttons
472
+ navigation: function() {
473
+
474
+ // Disable Left/Right when at the start or end of the collection
475
+ if ( this.model.cid === this.model.collection.at(0).cid ) {
476
+ this.$el.find( '.left' )
477
+ .addClass( 'disabled' )
478
+ .prop( 'disabled', true );
479
+ }
480
+ if ( this.model.cid === this.model.collection.at( this.model.collection.length - 1 ).cid ) {
481
+ this.$el.find( '.right' )
482
+ .addClass( 'disabled' )
483
+ .prop( 'disabled', true );
484
+ }
485
+ },
486
+
487
+ // Performs the actions to effectively close
488
+ // the demo details overlay
489
+ closeOverlay: function() {
490
+ $( 'body' ).removeClass( 'modal-open' );
491
+ this.remove();
492
+ this.unbind();
493
+ this.trigger( 'demo:collapse' );
494
+ },
495
+
496
+ importDemo: function() {
497
+ var _this = this,
498
+ $target = $( event.target );
499
+ event.preventDefault();
500
+
501
+ if ( $target.hasClass( 'disabled' ) ) {
502
+ return;
503
+ }
504
+
505
+ // Confirmation dialog for importing a demo.
506
+ if ( ! window.confirm( wp.demos.data.settings.confirmImport ) ) {
507
+ return;
508
+ }
509
510
+ $( document ).on( 'wp-demo-import-success', function( event, response ) {
511
+ if ( _this.model.get( 'id' ) === response.slug ) {
512
+ _this.model.set( { 'imported': true } );
513
}
514
+ } );
515
+
516
+ // Handle a demo queue job.
517
+ $( document ).on( 'wp-updates-queue-job', function( event, job ) {
518
+ if ( 'import-demo' === job.action ) {
519
+ wp.updates.importDemo( job.data );
520
+ }
521
+ } );
522
+
523
+ wp.updates.importDemo( {
524
+ slug: $target.data( 'slug' )
525
+ } );
526
+ },
527
+
528
+ deleteDemo: function( event ) {
529
+ var _this = this,
530
+ _collection = this.model.collection,
531
+ _demos = demos;
532
+ event.preventDefault();
533
+
534
+ // Confirmation dialog for deleting a demo.
535
+ if ( ! window.confirm( wp.demos.data.settings.confirmDelete ) ) {
536
+ return;
537
+ }
538
+
539
+ wp.updates.maybeRequestFilesystemCredentials( event );
540
+
541
+ $( document ).one( 'wp-demo-delete-success', function( event, response ) {
542
+ _this.$el.find( '.close' ).trigger( 'click' );
543
+ $( '[data-slug="' + response.slug + '"' ).css( { backgroundColor:'#faafaa' } ).fadeOut( 350, function() {
544
+ $( this ).remove();
545
+ _demos.data.demos = _.without( _demos.data.demos, _.findWhere( _demos.data.demos, { id: response.slug } ) );
546
+
547
+ $( '.wp-filter-search' ).val( '' );
548
+ _collection.doSearch( '' );
549
+ _collection.remove( _this.model );
550
+ _collection.trigger( 'demos:update' );
551
+ } );
552
+ } );
553
+
554
+ wp.updates.deleteDemo( {
555
+ slug: this.model.get( 'id' )
556
+ } );
557
+ },
558
+
559
+ nextDemo: function() {
560
+ var self = this;
561
+ self.trigger( 'demo:next', self.model.cid );
562
+ return false;
563
+ },
564
+
565
+ previousDemo: function() {
566
+ var self = this;
567
+ self.trigger( 'demo:previous', self.model.cid );
568
+ return false;
569
+ },
570
+
571
+ // Checks if the theme screenshot is the old 300px width version
572
+ // and adds a corresponding class if it's true
573
+ screenshotCheck: function( el ) {
574
+ var screenshot, image;
575
+
576
+ screenshot = el.find( '.screenshot img' );
577
+ image = new Image();
578
+ image.src = screenshot.attr( 'src' );
579
+
580
+ // Width check
581
+ if ( image.width && image.width <= 300 ) {
582
+ el.addClass( 'small-screenshot' );
583
+ }
584
+ }
585
+ });
586
+
587
+ // Controls the rendering of div.themes,
588
+ // a wrapper that will hold all the theme elements
589
+ demos.view.Demos = wp.Backbone.View.extend({
590
+
591
+ className: 'themes wp-clearfix',
592
+ $overlay: $( 'div.theme-overlay' ),
593
594
+ // Number to keep track of scroll position
595
+ // while in theme-overlay mode
596
+ index: 0,
597
598
+ // The demo count element
599
+ count: $( '.wrap .demo-count' ),
600
+
601
+ // The live demos count
602
+ liveDemoCount: 0,
603
+
604
+ initialize: function( options ) {
605
+ var self = this;
606
+
607
+ // Set up parent
608
+ this.parent = options.parent;
609
+
610
+ // Move the imported demo to the beginning of the collection
611
+ self.importedDemo();
612
+
613
+ // Set current view to [grid]
614
+ this.setView( 'grid' );
615
+
616
+ // When the collection is updated by user input...
617
+ this.listenTo( self.collection, 'demos:update', function() {
618
+ self.parent.page = 0;
619
+ self.importedDemo();
620
+ self.render( this );
621
+ self.parent.initTipTip();
622
+ } );
623
+
624
+ // Update demo count to full result set when available.
625
+ this.listenTo( self.collection, 'query:success', function( count ) {
626
+ if ( _.isNumber( count ) ) {
627
+ self.count.text( count );
628
+ self.announceSearchResults( count );
629
+ } else {
630
+ self.count.text( self.collection.length );
631
+ self.announceSearchResults( self.collection.length );
632
+ }
633
+ });
634
+
635
+ this.listenTo( self.collection, 'query:empty', function() {
636
+ $( 'body' ).addClass( 'no-results' );
637
+ });
638
+
639
+ this.listenTo( this.parent, 'demo:scroll', function() {
640
+ self.renderDemos( self.parent.page );
641
+ });
642
+
643
+ this.listenTo( this.parent, 'demo:close', function() {
644
+ if ( self.overlay ) {
645
+ self.overlay.closeOverlay();
646
+ }
647
+ } );
648
+
649
+ // Bind keyboard events.
650
+ $( 'body' ).on( 'keyup', function( event ) {
651
+ if ( ! self.overlay ) {
652
+ return;
653
+ }
654
+
655
+ // Bail if the filesystem credentials dialog is shown.
656
+ if ( $( '#request-filesystem-credentials-dialog' ).is( ':visible' ) ) {
657
return;
658
}
659
660
+ // Pressing the right arrow key fires a demo:next event
661
+ if ( event.keyCode === 39 ) {
662
+ self.overlay.nextDemo();
663
+ }
664
+
665
+ // Pressing the left arrow key fires a demo:previous event
666
+ if ( event.keyCode === 37 ) {
667
+ self.overlay.previousDemo();
668
+ }
669
+
670
+ // Pressing the escape key fires a demo:collapse event
671
+ if ( event.keyCode === 27 ) {
672
+ self.overlay.collapse( event );
673
+ }
674
+ });
675
+ },
676
+
677
+ // Manages rendering of demo pages
678
+ // and keeping demo count in sync
679
+ render: function() {
680
+ // Clear the DOM, please
681
+ this.$el.empty();
682
+
683
+ // If the user doesn't have switch capabilities
684
+ // or there is only one demo in the collection
685
+ // render the detailed view of the active demo
686
+ if ( ! demos.isPreview && demos.data.demos.length === 1 ) {
687
+
688
+ // Constructs the view
689
+ this.singleDemo = new demos.view.Details({
690
+ model: this.collection.models[0]
691
});
692
693
+ // Render and apply a 'single-theme' class to our container
694
+ this.singleDemo.render();
695
+ this.$el.addClass( 'single-theme' );
696
+ this.$el.append( this.singleDemo.el );
697
+ }
698
+
699
+ // Generate the demos
700
+ // Using page instance
701
+ // While checking the collection has items
702
+ if ( this.options.collection.size() > 0 ) {
703
+ this.renderDemos( this.parent.page );
704
+ }
705
+
706
+ // Display a live demo count for the collection
707
+ this.liveDemoCount = this.collection.count ? this.collection.count : this.collection.length;
708
+ this.count.text( this.liveDemoCount );
709
+ },
710
+
711
+ // Iterates through each instance of the collection
712
+ // and renders each theme module
713
+ renderDemos: function( page ) {
714
+ var self = this;
715
+
716
+ self.instance = self.collection.paginate( page );
717
+
718
+ // If we have no more demos bail
719
+ if ( self.instance.size() === 0 ) {
720
+ // Fire a no-more-demos event.
721
+ this.parent.trigger( 'demo:end' );
722
+ return;
723
+ }
724
+
725
+ // Loop through the demos and setup each demo view
726
+ self.instance.each( function( demo ) {
727
+ self.demo = new demos.view.Demo({
728
+ model: demo,
729
+ parent: self
730
+ });
731
+
732
+ // Render the views...
733
+ self.demo.render();
734
+ // and append them to div.themes
735
+ self.$el.append( self.demo.el );
736
+
737
+ // Binds to demo:expand to show the modal box
738
+ // with the demo details
739
+ self.listenTo( self.demo, 'demo:expand', self.expand, self );
740
+ });
741
+
742
+ // 'Add new demo' element shown at the end of the grid
743
+ if ( ! demos.isPreview && demos.isInstall && demos.data.settings.canInstall ) {
744
+ this.$el.append( '<div class="theme add-new-theme"><a href="' + demos.data.settings.installURI + '"><div class="theme-screenshot"><span></span></div><h2 class="theme-name">' + l10n.addNew + '</h2></a></div>' );
745
+ }
746
+
747
+ this.parent.page++;
748
+ },
749
+
750
+ // Grabs imported demo and puts it at the beginning of the collection
751
+ importedDemo: function() {
752
+ var self = this,
753
+ current;
754
+
755
+ current = self.collection.findWhere({ active: true });
756
+
757
+ // Move the imported demo to the beginning of the collection
758
+ if ( current ) {
759
+ self.collection.remove( current );
760
+ self.collection.add( current, { at:0 } );
761
+ }
762
+ },
763
+
764
+ // Sets current view
765
+ setView: function( view ) {
766
+ return view;
767
+ },
768
+
769
+ // Renders the overlay with the DemoDetails view
770
+ // Uses the current model data
771
+ expand: function( id ) {
772
+ var self = this, $card, $modal;
773
+
774
+ // Set the current demo model
775
+ this.model = self.collection.get( id );
776
+
777
+ // Trigger a route update for the current model
778
+ demos.router.navigate( demos.router.baseUrl( demos.router.demoPath + this.model.id ) );
779
+
780
+ // Sets this.view to 'detail'
781
+ this.setView( 'detail' );
782
+ $( 'body' ).addClass( 'modal-open' );
783
+
784
+ // Set up the demo details view
785
+ this.overlay = new demos.view.Details({
786
+ model: self.model
787
+ });
788
+
789
+ this.overlay.render();
790
+
791
+ if ( this.model.get( 'hasUpdate' ) ) {
792
+ $card = $( '[data-slug="' + this.model.id + '"]' );
793
+ $modal = $( this.overlay.el );
794
+
795
+ if ( $card.find( '.updating-message' ).length ) {
796
+ $modal.find( '.notice-warning h3' ).remove();
797
+ $modal.find( '.notice-warning' )
798
+ .removeClass( 'notice-large' )
799
+ .addClass( 'updating-message' )
800
+ .find( 'p' ).text( wp.updates.l10n.updating );
801
+ } else if ( $card.find( '.notice-error' ).length ) {
802
+ $modal.find( '.notice-warning' ).remove();
803
+ }
804
}
805
806
+ this.$overlay.html( this.overlay.el );
807
+
808
+ // Bind to demo:next and demo:previous
809
+ // triggered by the arrow keys
810
+ //
811
+ // Keep track of the current model so we
812
+ // can infer an index position
813
+ this.listenTo( this.overlay, 'demo:next', function() {
814
+ // Renders the next demo on the overlay
815
+ self.next( [ self.model.cid ] );
816
+
817
+ })
818
+ .listenTo( this.overlay, 'demo:previous', function() {
819
+ // Renders the previous demo on the overlay
820
+ self.previous( [ self.model.cid ] );
821
+ });
822
+ },
823
+
824
+ // This method renders the next demo on the overlay modal
825
+ // based on the current position in the collection
826
+ // @params [model cid]
827
+ next: function( args ) {
828
+ var self = this,
829
+ model, nextModel;
830
+
831
+ // Get the current demo
832
+ model = self.collection.get( args[0] );
833
+ // Find the next model within the collection
834
+ nextModel = self.collection.at( self.collection.indexOf( model ) + 1 );
835
+
836
+ // Sanity check which also serves as a boundary test
837
+ if ( nextModel !== undefined ) {
838
+
839
+ // We have a new demo...
840
+ // Close the overlay
841
+ this.overlay.closeOverlay();
842
+
843
+ // Trigger a route update for the current model
844
+ self.demo.trigger( 'demo:expand', nextModel.cid );
845
+ }
846
+ },
847
+
848
+ // This method renders the previous demo on the overlay modal
849
+ // based on the current position in the collection
850
+ // @params [model cid]
851
+ previous: function( args ) {
852
+ var self = this,
853
+ model, previousModel;
854
+
855
+ // Get the current demo
856
+ model = self.collection.get( args[0] );
857
+ // Find the previous model within the collection
858
+ previousModel = self.collection.at( self.collection.indexOf( model ) - 1 );
859
+
860
+ if ( previousModel !== undefined ) {
861
+
862
+ // We have a new demo...
863
+ // Close the overlay
864
+ this.overlay.closeOverlay();
865
+
866
+ // Trigger a route update for the current model
867
+ self.demo.trigger( 'demo:expand', previousModel.cid );
868
+ }
869
+ },
870
+
871
+ // Dispatch audible search results feedback message
872
+ announceSearchResults: function( count ) {
873
+ if ( 0 === count ) {
874
+ wp.a11y.speak( l10n.noDemosFound );
875
+ } else {
876
+ wp.a11y.speak( l10n.demosFound.replace( '%d', count ) );
877
+ }
878
+ }
879
});
880
+
881
+ // Search input view controller.
882
+ demos.view.Search = wp.Backbone.View.extend({
883
+
884
+ tagName: 'input',
885
+ className: 'wp-filter-search',
886
+ id: 'wp-filter-search-input',
887
+ searching: false,
888
+
889
+ attributes: {
890
+ placeholder: l10n.searchPlaceholder,
891
+ type: 'search',
892
+ 'aria-describedby': 'live-search-desc'
893
+ },
894
+
895
+ events: {
896
+ 'input': 'search',
897
+ 'keyup': 'search',
898
+ 'blur': 'pushState'
899
+ },
900
+
901
+ initialize: function( options ) {
902
+
903
+ this.parent = options.parent;
904
+
905
+ this.listenTo( this.parent, 'demo:close', function() {
906
+ this.searching = false;
907
+ } );
908
+ },
909
+
910
+ search: function( event ) {
911
+ // Clear on escape.
912
+ if ( event.type === 'keyup' && event.which === 27 ) {
913
+ event.target.value = '';
914
+ }
915
+
916
+ /**
917
+ * Since doSearch is debounced, it will only run when user input comes to a rest
918
+ */
919
+ this.doSearch( event );
920
+ },
921
+
922
+ // Runs a search on the demo collection.
923
+ doSearch: _.debounce( function( event ) {
924
+ var options = {};
925
+
926
+ this.collection.doSearch( event.target.value );
927
+
928
+ // if search is initiated and key is not return
929
+ if ( this.searching && event.which !== 13 ) {
930
+ options.replace = true;
931
+ } else {
932
+ this.searching = true;
933
+ }
934
+
935
+ // Update the URL hash
936
+ if ( event.target.value ) {
937
+ demos.router.navigate( demos.router.baseUrl( demos.router.searchPath + event.target.value ), options );
938
+ } else {
939
+ demos.router.navigate( demos.router.baseUrl( '' ) );
940
+ }
941
+ }, 500 ),
942
+
943
+ pushState: function( event ) {
944
+ var url = demos.router.baseUrl( '' );
945
+
946
+ if ( event.target.value ) {
947
+ url = demos.router.baseUrl( demos.router.searchPath + event.target.value );
948
+ }
949
+
950
+ this.searching = false;
951
+ demos.router.navigate( url );
952
+ }
953
+ });
954
+
955
+ // Sets up the routes events for relevant url queries
956
+ // Listens to [demo] and [search] params
957
+ demos.Router = Backbone.Router.extend({
958
+
959
+ routes: {
960
+ 'themes.php?page=demo-importer&demo=:slug': 'demo',
961
+ 'themes.php?page=demo-importer&search=:query': 'search',
962
+ 'themes.php?page=demo-importer&s=:query': 'search',
963
+ 'themes.php?page=demo-importer': 'demos',
964
+ '': 'demos'
965
+ },
966
+
967
+ baseUrl: function( url ) {
968
+ return 'themes.php?page=demo-importer' + url;
969
+ },
970
+
971
+ demoPath: '&demo=',
972
+ searchPath: '&search=',
973
+
974
+ search: function( query ) {
975
+ $( '.wp-filter-search' ).val( query );
976
+ },
977
+
978
+ demos: function() {
979
+ $( '.wp-filter-search' ).val( '' );
980
+ },
981
+
982
+ navigate: function() {
983
+ if ( Backbone.history._hasPushState ) {
984
+ Backbone.Router.prototype.navigate.apply( this, arguments );
985
+ }
986
+ }
987
+ });
988
+
989
+ // Execute and setup the application
990
+ demos.Run = {
991
+ init: function() {
992
+ // Initializes the blog's demo library view
993
+ // Create a new collection with data
994
+ this.demos = new demos.Collection( demos.data.demos );
995
+
996
+ // Set up the view
997
+ this.view = new demos.view.Appearance({
998
+ collection: this.demos
999
+ });
1000
+
1001
+ this.render();
1002
+ },
1003
+
1004
+ render: function() {
1005
+
1006
+ // Render results
1007
+ this.view.render();
1008
+ this.view.initTipTip();
1009
+ this.routes();
1010
+
1011
+ Backbone.history.start({
1012
+ root: demos.data.settings.adminUrl,
1013
+ pushState: true,
1014
+ hashChange: false
1015
+ });
1016
+ },
1017
+
1018
+ routes: function() {
1019
+ var self = this;
1020
+ // Bind to our global thx object
1021
+ // so that the object is available to sub-views
1022
+ demos.router = new demos.Router();
1023
+
1024
+ // Handles demo details route event
1025
+ demos.router.on( 'route:demo', function( slug ) {
1026
+ self.view.view.expand( slug );
1027
+ });
1028
+
1029
+ demos.router.on( 'route:demos', function() {
1030
+ self.demos.doSearch( '' );
1031
+ self.view.trigger( 'demo:close' );
1032
+ });
1033
+
1034
+ // Handles search route event
1035
+ demos.router.on( 'route:search', function() {
1036
+ $( '.wp-filter-search' ).trigger( 'keyup' );
1037
+ });
1038
+
1039
+ this.extraRoutes();
1040
+ },
1041
+
1042
+ extraRoutes: function() {
1043
+ return false;
1044
+ }
1045
+ };
1046
+
1047
+ demos.view.Installer = demos.view.Appearance.extend({
1048
+
1049
+ el: '#wpbody-content .wrap',
1050
+
1051
+ // Initial render method
1052
+ render: function() {
1053
+ this.search();
1054
+ this.uploader();
1055
+
1056
+ // Setup the main demo view
1057
+ // with the current demo collection
1058
+ this.view = new demos.view.Demos({
1059
+ collection: this.collection,
1060
+ parent: this
1061
+ });
1062
+
1063
+ // Render and append
1064
+ this.$el.find( '.themes' ).remove();
1065
+ this.view.render();
1066
+ this.$el.find( '.theme-browser' ).append( this.view.el ).addClass( 'rendered' );
1067
+ },
1068
+
1069
+ // Overwrite search container class to append search
1070
+ // in new location
1071
+ searchContainer: $( '.wp-filter .search-form' ),
1072
+
1073
+ /*
1074
+ * When users press the "Upload Theme" button, show the upload form in place.
1075
+ */
1076
+ uploader: function() {
1077
+ var uploadViewToggle = $( '.upload-view-toggle' ),
1078
+ $body = $( document.body );
1079
+
1080
+ uploadViewToggle.on( 'click', function() {
1081
+ // Toggle the upload view.
1082
+ $body.toggleClass( 'show-upload-view' );
1083
+ // Toggle the `aria-expanded` button attribute.
1084
+ uploadViewToggle.attr( 'aria-expanded', $body.hasClass( 'show-upload-view' ) );
1085
+ });
1086
+ },
1087
+
1088
+ clearSearch: function() {
1089
+ $( '#wp-filter-search-input').val( '' );
1090
+ }
1091
+ });
1092
+
1093
+ demos.InstallerRouter = Backbone.Router.extend({
1094
+
1095
+ routes: {
1096
+ 'themes.php?page=demo-importer&browse=uploads&demo=:slug': 'demo',
1097
+ 'themes.php?page=demo-importer&browse=:sort&search=:query': 'search',
1098
+ 'themes.php?page=demo-importer&browse=:sort&s=:query': 'search',
1099
+ 'themes.php?page=demo-importer&browse=welcome': 'sort',
1100
+ 'themes.php?page=demo-importer&browse=:sort': 'demos',
1101
+ 'themes.php?page=demo-importer': 'sort'
1102
+ },
1103
+
1104
+ browse: function() {
1105
+ return demos.isPreview ? 'preview' : 'uploads';
1106
+ },
1107
+
1108
+ baseUrl: function( url ) {
1109
+ return 'themes.php?page=demo-importer&browse=' + this.browse() + url;
1110
+ },
1111
+
1112
+ demoPath: '&demo=',
1113
+ searchPath: '&search=',
1114
+
1115
+ search: function( sort, query ) {
1116
+ $( '.wp-filter-search' ).val( query );
1117
+ },
1118
+
1119
+ demos: function() {
1120
+ $( '.wp-filter-search' ).val( '' );
1121
+ },
1122
+
1123
+ navigate: function() {
1124
+ if ( Backbone.history._hasPushState ) {
1125
+ Backbone.Router.prototype.navigate.apply( this, arguments );
1126
+ }
1127
+ }
1128
+ });
1129
+
1130
+ demos.RunInstaller = {
1131
+
1132
+ init: function() {
1133
+ // Initializes the blog's demo library view
1134
+ // Create a new collection with data
1135
+ this.demos = new demos.Collection( demos.data.demos );
1136
+
1137
+ // Set up the view
1138
+ this.view = new demos.view.Installer({
1139
+ collection: this.demos
1140
+ });
1141
+
1142
+ this.render();
1143
+ },
1144
+
1145
+ render: function() {
1146
+
1147
+ // Render results
1148
+ this.view.render();
1149
+ this.view.initTipTip();
1150
+ this.routes();
1151
+
1152
+ Backbone.history.start({
1153
+ root: demos.data.settings.adminUrl,
1154
+ pushState: true,
1155
+ hashChange: false
1156
+ });
1157
+ },
1158
+
1159
+ routes: function() {
1160
+ var self = this;
1161
+ // Bind to our global thx object
1162
+ // so that the object is available to sub-views
1163
+ demos.router = new demos.InstallerRouter();
1164
+
1165
+ // Handles demo details route event
1166
+ demos.router.on( 'route:demo', function( slug ) {
1167
+ self.view.view.expand( slug );
1168
+ });
1169
+
1170
+ demos.router.on( 'route:demos', function() {
1171
+ self.demos.doSearch( '' );
1172
+ self.view.trigger( 'demo:close' );
1173
+ });
1174
+
1175
+ // Handles sorting / browsing routes
1176
+ // Also handles the root URL triggering a sort request
1177
+ // for `welcome`, the default view
1178
+ demos.router.on( 'route:sort', function( sort ) {
1179
+ if ( ! sort || 'welcome' === sort ) {
1180
+ $( '.wp-filter-search' ).hide();
1181
+ }
1182
+ });
1183
+
1184
+ // The `search` route event. The router populates the input field.
1185
+ demos.router.on( 'route:search', function() {
1186
+ $( '.wp-filter-search' ).focus().trigger( 'keyup' );
1187
+ });
1188
+
1189
+ this.extraRoutes();
1190
+ },
1191
+
1192
+ extraRoutes: function() {
1193
+ return false;
1194
+ }
1195
+ };
1196
+
1197
+ // Ready...
1198
+ $( document ).ready( function() {
1199
+ if ( demos.isInstall ) {
1200
+ demos.RunInstaller.init();
1201
+ } else {
1202
+ demos.Run.init();
1203
+ }
1204
+
1205
+ // Dismissible notice.
1206
+ $( '.notice.is-dismissible' ).on( 'click', '.notice-dismiss', function( event ) {
1207
+ var $this_el = $( this );
1208
+
1209
+ event.preventDefault();
1210
+
1211
+ if ( $this_el.parent().attr( 'id' ) === 'undefined' ) {
1212
+ return;
1213
+ }
1214
+
1215
+ $.post( demos.data.settings.ajaxUrl, {
1216
+ action: 'dismiss-notice',
1217
+ notice_id: $this_el.parent().data( 'notice_id' )
1218
+ });
1219
+
1220
+ return false;
1221
+ } );
1222
+ });
1223
+
1224
+ })( jQuery );
assets/js/admin/demo-importer.min.js CHANGED
@@ -1 +1 @@
1
- jQuery(function(a){var b={init:function(){this.uploader(),this.init_tiptip(),a(".theme-actions").on("click",".import",this.process_import),a(".notice.is-dismissible").on("click",".notice-dismiss",this.dismiss_notice)},uploader:function(){var b=a(".upload-view-toggle"),c=a(document.body);b.on("click",function(){c.toggleClass("show-upload-view"),b.attr("aria-expanded",c.hasClass("show-upload-view"))})},init_tiptip:function(){a("#tiptip_holder").removeAttr("style"),a("#tiptip_arrow").removeAttr("style"),a(".tips").tipTip({attribute:"data-tip",fadeIn:50,fadeOut:50,delay:200})},process_import:function(b){b.preventDefault();var c=a(this);if(!c.hasClass("disabled")){if(window.confirm(demo_importer_params.i18n_import_dummy_data)){var d={action:"tg_import_demo_data",demo_id:c.data("demo_id"),security:demo_importer_params.import_demo_data_nonce};a.ajax({url:demo_importer_params.ajax_url,data:d,type:"POST",beforeSend:function(){c.parent().addClass("importing"),c.parent().find(".spinner").addClass("is-active")},success:function(a){c.closest(".theme").find(".notice").remove(),c.parent().find(".spinner").removeClass("is-active"),c.parent().removeClass("importing").addClass("imported"),!0===a.success?c.closest(".theme").append('<div class="notice notice-success notice-alt"><p>'+a.data.message+"</p></div>"):c.closest(".theme").append('<div class="update-message notice notice-error notice-alt"><p>'+demo_importer_params.i18n_import_data_error+"</p></div>")},error:function(a,b,d){c.closest(".theme").find(".notice").remove(),c.parent().find(".spinner").removeClass("is-active"),c.parent().removeClass("importing").addClass("imported"),c.closest(".theme").append('<div class="update-message notice notice-error notice-alt"><p>'+d+"</p></div>")}})}return!1}},dismiss_notice:function(b){b.preventDefault();var c=a(this);if("undefined"!==c.parent().attr("id"))return a.post(demo_importer_params.ajax_url,{action:"tg_dismiss_notice",notice_id:c.parent().data("notice_id")}),!1}};b.init()});
1
+ window.wp=window.wp||{},function(a){var b,c;b=wp.demos=wp.demos||{},b.data=demoImporterLocalizeScript,c=b.data.l10n,b.isPreview=!!b.data.settings.isPreview,b.isInstall=!!b.data.settings.isInstall,_.extend(b,{model:{},view:{},routes:{},router:{},template:wp.template}),b.Model=Backbone.Model.extend({initialize:function(){var a;_.indexOf(b.data.installedDemos,this.get("slug"))!==-1&&this.set({installed:!0}),this.set({id:this.get("slug")||this.get("id")}),this.has("sections")&&(a=this.get("sections").description,this.set({description:a}))}}),b.view.Appearance=wp.Backbone.View.extend({el:"#wpbody-content .wrap .theme-browser",window:a(window),page:0,initialize:function(a){_.bindAll(this,"scroller"),this.SearchView=a.SearchView?a.SearchView:b.view.Search,this.window.bind("scroll",_.throttle(this.scroller,300))},render:function(){this.view=new b.view.Demos({collection:this.collection,parent:this}),this.search(),this.view.render(),this.$el.empty().append(this.view.el).addClass("rendered")},searchContainer:a("#wpbody h1:first"),search:function(){var d,e=this;1!==b.data.demos.length&&(d=new this.SearchView({collection:e.collection,parent:this}),d.render(),this.searchContainer.append(a.parseHTML('<label class="screen-reader-text" for="wp-filter-search-input">'+c.search+"</label>")).append(d.el))},scroller:function(){var a,b,c=this;a=this.window.scrollTop()+c.window.height(),b=c.$el.offset().top+c.$el.outerHeight(!1)-c.window.height(),b=Math.round(.9*b),a>b&&this.trigger("demo:scroll")},initTipTip:function(){a("#tiptip_holder").removeAttr("style"),a("#tiptip_arrow").removeAttr("style"),a(".tips").tipTip({attribute:"data-tip",fadeIn:50,fadeOut:50,delay:50})}}),b.Collection=Backbone.Collection.extend({model:b.Model,terms:"",doSearch:function(c){this.terms!==c&&(this.terms=c,this.terms.length>0&&this.search(this.terms),""===this.terms&&(this.reset(b.data.demos),a("body").removeClass("no-results")),this.trigger("demos:update"))},search:function(c){var d,e,f,g,h,i;this.reset(b.data.demos,{silent:!0}),c=c.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\amp;"),c=c.replace(/ /g,")(?=.*"),d=new RegExp("^(?=.*"+c+").+","i"),e=this.filter(function(a){return g=a.get("name").replace(/(<([^>]+)>)/gi,""),h=a.get("description").replace(/(<([^>]+)>)/gi,""),i=a.get("author").replace(/(<([^>]+)>)/gi,""),f=_.union([g,a.get("id"),h,i,a.get("tags")]),d.test(a.get("author"))&&c.length>2&&a.set("displayAuthor",!0),d.test(f)}),0===e.length?this.trigger("query:empty"):a("body").removeClass("no-results"),this.reset(e)},paginate:function(a){var b=this;return a=a||0,b=_(b.rest(20*a)),b=_(b.first(20))}}),b.view.Demo=wp.Backbone.View.extend({className:"theme",state:"grid",html:b.template(b.isPreview?"demo-preview":"demo"),events:{click:"expand",keydown:"expand",touchend:"expand",keyup:"addFocus",touchmove:"preventExpand","click .demo-import":"importDemo"},touchDrag:!1,initialize:function(){this.model.on("change",this.render,this)},render:function(){var a=this.model.toJSON();this.$el.html(this.html(a)).attr({tabindex:0,"aria-describedby":a.id+"-action "+a.id+"-name","data-slug":a.id}),this.activeDemo(),this.model.get("displayAuthor")&&this.$el.addClass("display-author")},activeDemo:function(){this.model.get("active")&&this.$el.addClass("active")},addFocus:function(){var b=a(":focus").hasClass("theme")?a(":focus"):a(":focus").parents(".theme");a(".theme.focus").removeClass("focus"),b.addClass("focus")},expand:function(c){var d=this;if(!b.isPreview&&(c=c||window.event,"keydown"!==c.type||13===c.which||32===c.which))return this.touchDrag===!0?this.touchDrag=!1:void(a(c.target).is(".theme-actions a")||a(c.target).is(".theme-actions a, .update-message, .button-link, .notice-dismiss")||(b.focusedDemo=this.$el,this.trigger("demo:expand",d.model.cid)))},preventExpand:function(){this.touchDrag=!0},importDemo:function(){var b=this,c=a(event.target);event.preventDefault(),c.hasClass("disabled")||window.confirm(wp.demos.data.settings.confirmImport)&&(a(document).on("wp-demo-import-success",function(a,c){b.model.get("id")===c.slug&&b.model.set({imported:!0})}),wp.updates.importDemo({slug:c.data("slug")}))}}),b.view.Details=wp.Backbone.View.extend({className:"theme-overlay",events:{click:"collapse","click .delete-demo":"deleteDemo","click .left":"previousDemo","click .right":"nextDemo","click .demo-import":"importDemo"},html:b.template("demo-single"),render:function(){var a=this.model.toJSON();this.$el.html(this.html(a)),this.activeDemo(),this.navigation(),this.screenshotCheck(this.$el),this.containFocus(this.$el)},activeDemo:function(){this.$el.toggleClass("active",this.model.get("active"))},containFocus:function(b){_.delay(function(){a(".theme-wrap a.button-primary:visible").focus()},100),b.on("keydown.wp-themes",function(a){var c=b.find(".theme-header button:not(.disabled)").first(),d=b.find(".theme-actions a:visible").last();9===a.which&&(c[0]===a.target&&a.shiftKey?(d.focus(),a.preventDefault()):d[0]!==a.target||a.shiftKey||(c.focus(),a.preventDefault()))})},collapse:function(c){var d,e=this;c=c||window.event,1!==b.data.demos.length&&(a(c.target).is(".theme-backdrop")||a(c.target).is(".close")||27===c.keyCode)&&(a("body").addClass("closing-overlay"),this.$el.fadeOut(130,function(){a("body").removeClass("closing-overlay"),e.closeOverlay(),d=document.body.scrollTop,b.router.navigate(b.router.baseUrl("")),document.body.scrollTop=d,b.focusedDemo&&b.focusedDemo.focus()}))},navigation:function(){this.model.cid===this.model.collection.at(0).cid&&this.$el.find(".left").addClass("disabled").prop("disabled",!0),this.model.cid===this.model.collection.at(this.model.collection.length-1).cid&&this.$el.find(".right").addClass("disabled").prop("disabled",!0)},closeOverlay:function(){a("body").removeClass("modal-open"),this.remove(),this.unbind(),this.trigger("demo:collapse")},importDemo:function(){var b=this,c=a(event.target);event.preventDefault(),c.hasClass("disabled")||window.confirm(wp.demos.data.settings.confirmImport)&&(a(document).on("wp-demo-import-success",function(a,c){b.model.get("id")===c.slug&&b.model.set({imported:!0})}),a(document).on("wp-updates-queue-job",function(a,b){"import-demo"===b.action&&wp.updates.importDemo(b.data)}),wp.updates.importDemo({slug:c.data("slug")}))},deleteDemo:function(c){var d=this,e=this.model.collection,f=b;c.preventDefault(),window.confirm(wp.demos.data.settings.confirmDelete)&&(wp.updates.maybeRequestFilesystemCredentials(c),a(document).one("wp-demo-delete-success",function(b,c){d.$el.find(".close").trigger("click"),a('[data-slug="'+c.slug+'"').css({backgroundColor:"#faafaa"}).fadeOut(350,function(){a(this).remove(),f.data.demos=_.without(f.data.demos,_.findWhere(f.data.demos,{id:c.slug})),a(".wp-filter-search").val(""),e.doSearch(""),e.remove(d.model),e.trigger("demos:update")})}),wp.updates.deleteDemo({slug:this.model.get("id")}))},nextDemo:function(){var a=this;return a.trigger("demo:next",a.model.cid),!1},previousDemo:function(){var a=this;return a.trigger("demo:previous",a.model.cid),!1},screenshotCheck:function(a){var b,c;b=a.find(".screenshot img"),c=new Image,c.src=b.attr("src"),c.width&&c.width<=300&&a.addClass("small-screenshot")}}),b.view.Demos=wp.Backbone.View.extend({className:"themes wp-clearfix",$overlay:a("div.theme-overlay"),index:0,count:a(".wrap .demo-count"),liveDemoCount:0,initialize:function(b){var c=this;this.parent=b.parent,c.importedDemo(),this.setView("grid"),this.listenTo(c.collection,"demos:update",function(){c.parent.page=0,c.importedDemo(),c.render(this),c.parent.initTipTip()}),this.listenTo(c.collection,"query:success",function(a){_.isNumber(a)?(c.count.text(a),c.announceSearchResults(a)):(c.count.text(c.collection.length),c.announceSearchResults(c.collection.length))}),this.listenTo(c.collection,"query:empty",function(){a("body").addClass("no-results")}),this.listenTo(this.parent,"demo:scroll",function(){c.renderDemos(c.parent.page)}),this.listenTo(this.parent,"demo:close",function(){c.overlay&&c.overlay.closeOverlay()}),a("body").on("keyup",function(b){c.overlay&&(a("#request-filesystem-credentials-dialog").is(":visible")||(39===b.keyCode&&c.overlay.nextDemo(),37===b.keyCode&&c.overlay.previousDemo(),27===b.keyCode&&c.overlay.collapse(b)))})},render:function(){this.$el.empty(),b.isPreview||1!==b.data.demos.length||(this.singleDemo=new b.view.Details({model:this.collection.models[0]}),this.singleDemo.render(),this.$el.addClass("single-theme"),this.$el.append(this.singleDemo.el)),this.options.collection.size()>0&&this.renderDemos(this.parent.page),this.liveDemoCount=this.collection.count?this.collection.count:this.collection.length,this.count.text(this.liveDemoCount)},renderDemos:function(a){var d=this;return d.instance=d.collection.paginate(a),0===d.instance.size()?void this.parent.trigger("demo:end"):(d.instance.each(function(a){d.demo=new b.view.Demo({model:a,parent:d}),d.demo.render(),d.$el.append(d.demo.el),d.listenTo(d.demo,"demo:expand",d.expand,d)}),!b.isPreview&&b.isInstall&&b.data.settings.canInstall&&this.$el.append('<div class="theme add-new-theme"><a href="'+b.data.settings.installURI+'"><div class="theme-screenshot"><span></span></div><h2 class="theme-name">'+c.addNew+"</h2></a></div>"),void this.parent.page++)},importedDemo:function(){var a,b=this;a=b.collection.findWhere({active:!0}),a&&(b.collection.remove(a),b.collection.add(a,{at:0}))},setView:function(a){return a},expand:function(c){var d,e,f=this;this.model=f.collection.get(c),b.router.navigate(b.router.baseUrl(b.router.demoPath+this.model.id)),this.setView("detail"),a("body").addClass("modal-open"),this.overlay=new b.view.Details({model:f.model}),this.overlay.render(),this.model.get("hasUpdate")&&(d=a('[data-slug="'+this.model.id+'"]'),e=a(this.overlay.el),d.find(".updating-message").length?(e.find(".notice-warning h3").remove(),e.find(".notice-warning").removeClass("notice-large").addClass("updating-message").find("p").text(wp.updates.l10n.updating)):d.find(".notice-error").length&&e.find(".notice-warning").remove()),this.$overlay.html(this.overlay.el),this.listenTo(this.overlay,"demo:next",function(){f.next([f.model.cid])}).listenTo(this.overlay,"demo:previous",function(){f.previous([f.model.cid])})},next:function(a){var b,c,d=this;b=d.collection.get(a[0]),c=d.collection.at(d.collection.indexOf(b)+1),void 0!==c&&(this.overlay.closeOverlay(),d.demo.trigger("demo:expand",c.cid))},previous:function(a){var b,c,d=this;b=d.collection.get(a[0]),c=d.collection.at(d.collection.indexOf(b)-1),void 0!==c&&(this.overlay.closeOverlay(),d.demo.trigger("demo:expand",c.cid))},announceSearchResults:function(a){0===a?wp.a11y.speak(c.noDemosFound):wp.a11y.speak(c.demosFound.replace("%d",a))}}),b.view.Search=wp.Backbone.View.extend({tagName:"input",className:"wp-filter-search",id:"wp-filter-search-input",searching:!1,attributes:{placeholder:c.searchPlaceholder,type:"search","aria-describedby":"live-search-desc"},events:{input:"search",keyup:"search",blur:"pushState"},initialize:function(a){this.parent=a.parent,this.listenTo(this.parent,"demo:close",function(){this.searching=!1})},search:function(a){"keyup"===a.type&&27===a.which&&(a.target.value=""),this.doSearch(a)},doSearch:_.debounce(function(a){var c={};this.collection.doSearch(a.target.value),this.searching&&13!==a.which?c.replace=!0:this.searching=!0,a.target.value?b.router.navigate(b.router.baseUrl(b.router.searchPath+a.target.value),c):b.router.navigate(b.router.baseUrl(""))},500),pushState:function(a){var c=b.router.baseUrl("");a.target.value&&(c=b.router.baseUrl(b.router.searchPath+a.target.value)),this.searching=!1,b.router.navigate(c)}}),b.Router=Backbone.Router.extend({routes:{"themes.php?page=demo-importer&demo=:slug":"demo","themes.php?page=demo-importer&search=:query":"search","themes.php?page=demo-importer&s=:query":"search","themes.php?page=demo-importer":"demos","":"demos"},baseUrl:function(a){return"themes.php?page=demo-importer"+a},demoPath:"&demo=",searchPath:"&search=",search:function(b){a(".wp-filter-search").val(b)},demos:function(){a(".wp-filter-search").val("")},navigate:function(){Backbone.history._hasPushState&&Backbone.Router.prototype.navigate.apply(this,arguments)}}),b.Run={init:function(){this.demos=new b.Collection(b.data.demos),this.view=new b.view.Appearance({collection:this.demos}),this.render()},render:function(){this.view.render(),this.view.initTipTip(),this.routes(),Backbone.history.start({root:b.data.settings.adminUrl,pushState:!0,hashChange:!1})},routes:function(){var c=this;b.router=new b.Router,b.router.on("route:demo",function(a){c.view.view.expand(a)}),b.router.on("route:demos",function(){c.demos.doSearch(""),c.view.trigger("demo:close")}),b.router.on("route:search",function(){a(".wp-filter-search").trigger("keyup")}),this.extraRoutes()},extraRoutes:function(){return!1}},b.view.Installer=b.view.Appearance.extend({el:"#wpbody-content .wrap",render:function(){this.search(),this.uploader(),this.view=new b.view.Demos({collection:this.collection,parent:this}),this.$el.find(".themes").remove(),this.view.render(),this.$el.find(".theme-browser").append(this.view.el).addClass("rendered")},searchContainer:a(".wp-filter .search-form"),uploader:function(){var b=a(".upload-view-toggle"),c=a(document.body);b.on("click",function(){c.toggleClass("show-upload-view"),b.attr("aria-expanded",c.hasClass("show-upload-view"))})},clearSearch:function(){a("#wp-filter-search-input").val("")}}),b.InstallerRouter=Backbone.Router.extend({routes:{"themes.php?page=demo-importer&browse=uploads&demo=:slug":"demo","themes.php?page=demo-importer&browse=:sort&search=:query":"search","themes.php?page=demo-importer&browse=:sort&s=:query":"search","themes.php?page=demo-importer&browse=welcome":"sort","themes.php?page=demo-importer&browse=:sort":"demos","themes.php?page=demo-importer":"sort"},browse:function(){return b.isPreview?"preview":"uploads"},baseUrl:function(a){return"themes.php?page=demo-importer&browse="+this.browse()+a},demoPath:"&demo=",searchPath:"&search=",search:function(b,c){a(".wp-filter-search").val(c)},demos:function(){a(".wp-filter-search").val("")},navigate:function(){Backbone.history._hasPushState&&Backbone.Router.prototype.navigate.apply(this,arguments)}}),b.RunInstaller={init:function(){this.demos=new b.Collection(b.data.demos),this.view=new b.view.Installer({collection:this.demos}),this.render()},render:function(){this.view.render(),this.view.initTipTip(),this.routes(),Backbone.history.start({root:b.data.settings.adminUrl,pushState:!0,hashChange:!1})},routes:function(){var c=this;b.router=new b.InstallerRouter,b.router.on("route:demo",function(a){c.view.view.expand(a)}),b.router.on("route:demos",function(){c.demos.doSearch(""),c.view.trigger("demo:close")}),b.router.on("route:sort",function(b){b&&"welcome"!==b||a(".wp-filter-search").hide()}),b.router.on("route:search",function(){a(".wp-filter-search").focus().trigger("keyup")}),this.extraRoutes()},extraRoutes:function(){return!1}},a(document).ready(function(){b.isInstall?b.RunInstaller.init():b.Run.init(),a(".notice.is-dismissible").on("click",".notice-dismiss",function(c){var d=a(this);if(c.preventDefault(),"undefined"!==d.parent().attr("id"))return a.post(b.data.settings.ajaxUrl,{action:"dismiss-notice",notice_id:d.parent().data("notice_id")}),!1})})}(jQuery);
assets/js/admin/demo-updates.js ADDED
@@ -0,0 +1,305 @@
1
+ ( function( $, wp, settings ) {
2
+ var $document = $( document );
3
+
4
+ wp = wp || {};
5
+
6
+ /**
7
+ * The WP Updates object.
8
+ *
9
+ * @type {object}
10
+ */
11
+ wp.updates = wp.updates || {};
12
+
13
+ /**
14
+ * Localized strings.
15
+ *
16
+ * @type {object}
17
+ */
18
+ wp.updates.l10n = _.extend( wp.updates.l10n, settings.l10n || {} );
19
+
20
+ /**
21
+ * Sends an Ajax request to the server to import a demo.
22
+ *
23
+ * @param {object} args
24
+ * @param {string} args.slug Demo ID.
25
+ * @param {importDemoSuccess=} args.success Optional. Success callback. Default: wp.updates.importDemoSuccess
26
+ * @param {importDemoError=} args.error Optional. Error callback. Default: wp.updates.importDemoError
27
+ * @return {$.promise} A jQuery promise that represents the request,
28
+ * decorated with an abort() method.
29
+ */
30
+ wp.updates.importDemo = function( args ) {
31
+ var $message = $( '.demo-import[data-slug="' + args.slug + '"]' );
32
+
33
+ args = _.extend( {
34
+ success: wp.updates.importDemoSuccess,
35
+ error: wp.updates.importDemoError
36
+ }, args );
37
+
38
+ $message.addClass( 'updating-message' );
39
+ $message.parents( '.theme' ).addClass( 'focus' );
40
+ if ( $message.html() !== wp.updates.l10n.importing ) {
41
+ $message.data( 'originaltext', $message.html() );
42
+ }
43
+
44
+ $message
45
+ .text( wp.updates.l10n.importing )
46
+ .attr( 'aria-label', wp.updates.l10n.demoImportingLabel.replace( '%s', $message.data( 'name' ) ) );
47
+ wp.a11y.speak( wp.updates.l10n.importingMsg, 'polite' );
48
+
49
+ // Remove previous error messages, if any.
50
+ $( '.theme-info .theme-description, [data-slug="' + args.slug + '"]' ).removeClass( 'demo-import-failed' ).find( '.notice.notice-error' ).remove();
51
+
52
+ $document.trigger( 'wp-demo-importing', args );
53
+
54
+ return wp.updates.ajax( 'import-demo', args );
55
+ };
56
+
57
+ /**
58
+ * Updates the UI appropriately after a successful demo import.
59
+ *
60
+ * @typedef {object} importDemoSuccess
61
+ * @param {object} response Response from the server.
62
+ * @param {string} response.slug Slug of the demo that was imported.
63
+ * @param {string} response.previewUrl URL to preview the just imported demo.
64
+ */
65
+ wp.updates.importDemoSuccess = function( response ) {
66
+ var $card = $( '.theme-overlay, [data-slug=' + response.slug + ']' ),
67
+ $message;
68
+
69
+ $document.trigger( 'wp-demo-import-success', response );
70
+
71
+ $message = $card.find( '.button-primary' )
72
+ .removeClass( 'updating-message' )
73
+ .addClass( 'updated-message disabled' )
74
+ .attr( 'aria-label', wp.updates.l10n.demoImportedLabel.replace( '%s', response.demoName ) )
75
+ .text( wp.updates.l10n.imported );
76
+
77
+ wp.a11y.speak( wp.updates.l10n.importedMsg, 'polite' );
78
+
79
+ setTimeout( function() {
80
+
81
+ if ( response.previewUrl ) {
82
+
83
+ // Remove the 'Preview' button.
84
+ $message.siblings( '.demo-preview' ).remove();
85
+
86
+ // Transform the 'Import' button into an 'Live Preview' button.
87
+ $message
88
+ .attr( 'target', '_blank' )
89
+ .attr( 'href', response.previewUrl )
90
+ .removeClass( 'demo-import updated-message disabled' )
91
+ .addClass( 'live-preview' )
92
+ .attr( 'aria-label', wp.updates.l10n.livePreviewLabel.replace( '%s', response.demoName ) )
93
+ .text( wp.updates.l10n.livePreview );
94
+ }
95
+ }, 1000 );
96
+ };
97
+
98
+ /**
99
+ * Updates the UI appropriately after a failed demo import.
100
+ *
101
+ * @typedef {object} importDemoError
102
+ * @param {object} response Response from the server.
103
+ * @param {string} response.slug Slug of the demo to be imported.
104
+ * @param {string} response.errorCode Error code for the error that occurred.
105
+ * @param {string} response.errorMessage The error that occurred.
106
+ */
107
+ wp.updates.importDemoError = function( response ) {
108
+ var $card, $button,
109
+ errorMessage = wp.updates.l10n.importFailed.replace( '%s', response.errorMessage ),
110
+ $message = wp.updates.adminNotice( {
111
+ className: 'update-message notice-error notice-alt',
112
+ message: errorMessage
113
+ } );
114
+
115
+ if ( ! wp.updates.isValidResponse( response, 'import' ) ) {
116
+ return;
117
+ }
118
+
119
+ if ( $document.find( 'body' ).hasClass( 'modal-open' ) || $document.find( '.themes' ).hasClass( 'single-theme' ) ) {
120
+ $button = $( '.demo-import[data-slug="' + response.slug + '"]' );
121
+ $card = $( '.theme-info .theme-description' ).prepend( $message );
122
+ } else {
123
+ $card = $( '[data-slug="' + response.slug + '"]' ).removeClass( 'focus' ).addClass( 'demo-import-failed' ).append( $message );
124
+ $button = $card.find( '.demo-import' );
125
+ }
126
+
127
+ $button
128
+ .removeClass( 'updating-message' )
129
+ .attr( 'aria-label', wp.updates.l10n.demoImportFailedLabel.replace( '%s', $button.data( 'name' ) ) )
130
+ .text( wp.updates.l10n.importFailedShort );
131
+
132
+ wp.a11y.speak( errorMessage, 'assertive' );
133
+
134
+ $document.trigger( 'wp-demo-import-error', response );
135
+ };
136
+
137
+ /**
138
+ * Sends an Ajax request to the server to delete a demo.
139
+ *
140
+ * @param {object} args
141
+ * @param {string} args.slug Demo ID.
142
+ * @param {deleteDemoSuccess=} args.success Optional. Success callback. Default: wp.updates.deleteDemoSuccess
143
+ * @param {deleteDemoError=} args.error Optional. Error callback. Default: wp.updates.deleteDemoError
144
+ * @return {$.promise} A jQuery promise that represents the request,
145
+ * decorated with an abort() method.
146
+ */
147
+ wp.updates.deleteDemo = function( args ) {
148
+ var $button = $( '.theme-actions .delete-demo' );
149
+
150
+ args = _.extend( {
151
+ success: wp.updates.deleteDemoSuccess,
152
+ error: wp.updates.deleteDemoError
153
+ }, args );
154
+
155
+ if ( $button && $button.html() !== wp.updates.l10n.deleting ) {
156
+ $button
157
+ .data( 'originaltext', $button.html() )
158
+ .text( wp.updates.l10n.deleting );
159
+ }
160
+
161
+ wp.a11y.speak( wp.updates.l10n.deleting, 'polite' );
162
+
163
+ // Remove previous error messages, if any.
164
+ $( '.theme-info .update-message' ).remove();
165
+
166
+ $document.trigger( 'wp-demo-deleting', args );
167
+
168
+ return wp.updates.ajax( 'delete-demo', args );
169
+ };
170
+
171
+ /**
172
+ * Updates the UI appropriately after a successful demo deletion.
173
+ *
174
+ * @typedef {object} deleteDemoSuccess
175
+ * @param {object} response Response from the server.
176
+ * @param {string} response.slug Slug of the demo that was deleted.
177
+ */
178
+ wp.updates.deleteDemoSuccess = function( response ) {
179
+ wp.a11y.speak( wp.updates.l10n.deleted, 'polite' );
180
+
181
+ $document.trigger( 'wp-demo-delete-success', response );
182
+ };
183
+
184
+ /**
185
+ * Updates the UI appropriately after a failed demo deletion.
186
+ *
187
+ * @typedef {object} deleteDemoError
188
+ * @param {object} response Response from the server.
189
+ * @param {string} response.slug Slug of the demo to be deleted.
190
+ * @param {string} response.errorCode Error code for the error that occurred.
191
+ * @param {string} response.errorMessage The error that occurred.
192
+ */
193
+ wp.updates.deleteDemoError = function( response ) {
194
+ var $button = $( '.theme-actions .delete-demo' ),
195
+ errorMessage = wp.updates.l10n.deleteFailed.replace( '%s', response.errorMessage ),
196
+ $message = wp.updates.adminNotice( {
197
+ className: 'update-message notice-error notice-alt',
198
+ message: errorMessage
199
+ } );
200
+
201
+ if ( wp.updates.maybeHandleCredentialError( response, 'delete-demo' ) ) {
202
+ return;
203
+ }
204
+
205
+ $( '.theme-info .theme-description' ).before( $message );
206
+
207
+ $button.html( $button.data( 'originaltext' ) );
208
+
209
+ wp.a11y.speak( errorMessage, 'assertive' );
210
+
211
+ $document.trigger( 'wp-demo-delete-error', response );
212
+ };
213
+
214
+ /**
215
+ * Validates an AJAX response to ensure it's a proper object.
216
+ *
217
+ * If the response deems to be invalid, an admin notice is being displayed.
218
+ *
219
+ * @param {(object|string)} response Response from the server.
220
+ * @param {function=} response.always Optional. Callback for when the Deferred is resolved or rejected.
221
+ * @param {string=} response.statusText Optional. Status message corresponding to the status code.
222
+ * @param {string=} response.responseText Optional. Request response as text.
223
+ * @param {string} action Type of action the response is referring to. Can be 'delete',
224
+ * 'update' or 'install'.
225
+ */
226
+ wp.updates.isValidResponse = function( response, action ) {
227
+ var error = wp.updates.l10n.unknownError,
228
+ errorMessage;
229
+
230
+ // Make sure the response is a valid data object and not a Promise object.
231
+ if ( _.isObject( response ) && ! _.isFunction( response.always ) ) {
232
+ return true;
233
+ }
234
+
235
+ if ( _.isString( response ) && '-1' === response ) {
236
+ error = wp.updates.l10n.nonceError;
237
+ } else if ( _.isString( response ) ) {
238
+ error = response;
239
+ } else if ( 'undefined' !== typeof response.readyState && 0 === response.readyState ) {
240
+ error = wp.updates.l10n.connectionError;
241
+ } else if ( _.isString( response.statusText ) ) {
242
+ error = response.statusText + ' ' + wp.updates.l10n.statusTextLink;
243
+ }
244
+
245
+ switch ( action ) {
246
+ case 'import':
247
+ errorMessage = wp.updates.l10n.importFailed;
248
+ break;
249
+ }
250
+
251
+ errorMessage = errorMessage.replace( '%s', error );
252
+
253
+ // Add admin notice.
254
+ wp.updates.addAdminNotice( {
255
+ id: 'unknown_error',
256
+ className: 'notice-error is-dismissible',
257
+ message: _.unescape( errorMessage )
258
+ } );
259
+
260
+ // Change buttons of all running updates.
261
+ $( '.button.updating-message' )
262
+ .removeClass( 'updating-message' )
263
+ .removeAttr( 'aria-label' )
264
+ .text( wp.updates.l10n.updateFailedShort );
265
+
266
+ wp.a11y.speak( errorMessage, 'assertive' );
267
+
268
+ return false;
269
+ };
270
+
271
+ /**
272
+ * Pulls available jobs from the queue and runs them.
273
+ * @see https://core.trac.wordpress.org/ticket/39364
274
+ */
275
+ wp.updates.queueChecker = function() {
276
+ var job;
277
+
278
+ if ( wp.updates.ajaxLocked || ! wp.updates.queue.length ) {
279
+ return;
280
+ }
281
+
282
+ job = wp.updates.queue.shift();
283
+
284
+ job = wp.updates.queue.shift();
285
+
286
+ // Handle a queue job.
287
+ switch ( job.action ) {
288
+ case 'import-demo':
289
+ wp.updates.importDemo( job.data );
290
+ break;
291
+
292
+ case 'delete-demo':
293
+ wp.updates.deleteDemo( job.data );
294
+ break;
295
+
296
+ default:
297
+ break;
298
+
299
+ }
300
+
301
+ // Handle a queue job.
302
+ $document.trigger( 'wp-updates-queue-job', job );
303
+ };
304
+
305
+ })( jQuery, window.wp, window._demoUpdatesSettings );
assets/js/admin/demo-updates.min.js ADDED
@@ -0,0 +1 @@
1
+ !function(a,b,c){var d=a(document);b=b||{},b.updates=b.updates||{},b.updates.l10n=_.extend(b.updates.l10n,c.l10n||{}),b.updates.importDemo=function(c){var e=a('.demo-import[data-slug="'+c.slug+'"]');return c=_.extend({success:b.updates.importDemoSuccess,error:b.updates.importDemoError},c),e.addClass("updating-message"),e.parents(".theme").addClass("focus"),e.html()!==b.updates.l10n.importing&&e.data("originaltext",e.html()),e.text(b.updates.l10n.importing).attr("aria-label",b.updates.l10n.demoImportingLabel.replace("%s",e.data("name"))),b.a11y.speak(b.updates.l10n.importingMsg,"polite"),a('.theme-info .theme-description, [data-slug="'+c.slug+'"]').removeClass("demo-import-failed").find(".notice.notice-error").remove(),d.trigger("wp-demo-importing",c),b.updates.ajax("import-demo",c)},b.updates.importDemoSuccess=function(c){var e,f=a(".theme-overlay, [data-slug="+c.slug+"]");d.trigger("wp-demo-import-success",c),e=f.find(".button-primary").removeClass("updating-message").addClass("updated-message disabled").attr("aria-label",b.updates.l10n.demoImportedLabel.replace("%s",c.demoName)).text(b.updates.l10n.imported),b.a11y.speak(b.updates.l10n.importedMsg,"polite"),setTimeout(function(){c.previewUrl&&(e.siblings(".demo-preview").remove(),e.attr("target","_blank").attr("href",c.previewUrl).removeClass("demo-import updated-message disabled").addClass("live-preview").attr("aria-label",b.updates.l10n.livePreviewLabel.replace("%s",c.demoName)).text(b.updates.l10n.livePreview))},1e3)},b.updates.importDemoError=function(c){var e,f,g=b.updates.l10n.importFailed.replace("%s",c.errorMessage),h=b.updates.adminNotice({className:"update-message notice-error notice-alt",message:g});b.updates.isValidResponse(c,"import")&&(d.find("body").hasClass("modal-open")||d.find(".themes").hasClass("single-theme")?(f=a('.demo-import[data-slug="'+c.slug+'"]'),e=a(".theme-info .theme-description").prepend(h)):(e=a('[data-slug="'+c.slug+'"]').removeClass("focus").addClass("demo-import-failed").append(h),f=e.find(".demo-import")),f.removeClass("updating-message").attr("aria-label",b.updates.l10n.demoImportFailedLabel.replace("%s",f.data("name"))).text(b.updates.l10n.importFailedShort),b.a11y.speak(g,"assertive"),d.trigger("wp-demo-import-error",c))},b.updates.deleteDemo=function(c){var e=a(".theme-actions .delete-demo");return c=_.extend({success:b.updates.deleteDemoSuccess,error:b.updates.deleteDemoError},c),e&&e.html()!==b.updates.l10n.deleting&&e.data("originaltext",e.html()).text(b.updates.l10n.deleting),b.a11y.speak(b.updates.l10n.deleting,"polite"),a(".theme-info .update-message").remove(),d.trigger("wp-demo-deleting",c),b.updates.ajax("delete-demo",c)},b.updates.deleteDemoSuccess=function(a){b.a11y.speak(b.updates.l10n.deleted,"polite"),d.trigger("wp-demo-delete-success",a)},b.updates.deleteDemoError=function(c){var e=a(".theme-actions .delete-demo"),f=b.updates.l10n.deleteFailed.replace("%s",c.errorMessage),g=b.updates.adminNotice({className:"update-message notice-error notice-alt",message:f});b.updates.maybeHandleCredentialError(c,"delete-demo")||(a(".theme-info .theme-description").before(g),e.html(e.data("originaltext")),b.a11y.speak(f,"assertive"),d.trigger("wp-demo-delete-error",c))},b.updates.isValidResponse=function(c,d){var e,f=b.updates.l10n.unknownError;if(_.isObject(c)&&!_.isFunction(c.always))return!0;switch(_.isString(c)&&"-1"===c?f=b.updates.l10n.nonceError:_.isString(c)?f=c:"undefined"!=typeof c.readyState&&0===c.readyState?f=b.updates.l10n.connectionError:_.isString(c.statusText)&&(f=c.statusText+" "+b.updates.l10n.statusTextLink),d){case"import":e=b.updates.l10n.importFailed}return e=e.replace("%s",f),b.updates.addAdminNotice({id:"unknown_error",className:"notice-error is-dismissible",message:_.unescape(e)}),a(".button.updating-message").removeClass("updating-message").removeAttr("aria-label").text(b.updates.l10n.updateFailedShort),b.a11y.speak(e,"assertive"),!1},b.updates.queueChecker=function(){var a;if(!b.updates.ajaxLocked&&b.updates.queue.length){switch(a=b.updates.queue.shift(),a=b.updates.queue.shift(),a.action){case"import-demo":b.updates.importDemo(a.data);break;case"delete-demo":b.updates.deleteDemo(a.data)}d.trigger("wp-updates-queue-job",a)}}}(jQuery,window.wp,window._demoUpdatesSettings);
composer.lock ADDED
@@ -0,0 +1,295 @@
1
+ {
2
+ "_readme": [
3
+ "This file locks the dependencies of your project to a known state",
4
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
5
+ "This file is @generated automatically"
6
+ ],
7
+ "hash": "dc8868b0f068d03c4a1ea5221933732b",
8
+ "content-hash": "e902d62f93fd8f9f14910ae4a1e8a26f",
9
+ "packages": [
10
+ {
11
+ "name": "composer/installers",
12
+ "version": "v1.2.0",
13
+ "source": {
14
+ "type": "git",
15
+ "url": "https://github.com/composer/installers.git",
16
+ "reference": "d78064c68299743e0161004f2de3a0204e33b804"
17
+ },
18
+ "dist": {
19
+ "type": "zip",
20
+ "url": "https://api.github.com/repos/composer/installers/zipball/d78064c68299743e0161004f2de3a0204e33b804",
21
+ "reference": "d78064c68299743e0161004f2de3a0204e33b804",
22
+ "shasum": ""
23
+ },
24
+ "require": {
25
+ "composer-plugin-api": "^1.0"
26
+ },
27
+ "replace": {
28
+ "roundcube/plugin-installer": "*",
29
+ "shama/baton": "*"
30
+ },
31
+ "require-dev": {
32
+ "composer/composer": "1.0.*@dev",
33
+ "phpunit/phpunit": "4.1.*"
34
+ },
35
+ "type": "composer-plugin",
36
+ "extra": {
37
+ "class": "Composer\\Installers\\Plugin",
38
+ "branch-alias": {
39
+ "dev-master": "1.0-dev"
40
+ }
41
+ },
42
+ "autoload": {
43
+ "psr-4": {
44
+ "Composer\\Installers\\": "src/Composer/Installers"
45
+ }
46
+ },
47
+ "notification-url": "https://packagist.org/downloads/",
48
+ "license": [
49
+ "MIT"
50
+ ],
51
+ "authors": [
52
+ {
53
+ "name": "Kyle Robinson Young",
54
+ "email": "kyle@dontkry.com",
55
+ "homepage": "https://github.com/shama"
56
+ }
57
+ ],
58
+ "description": "A multi-framework Composer library installer",
59
+ "homepage": "https://composer.github.io/installers/",
60
+ "keywords": [
61
+ "Craft",
62
+ "Dolibarr",
63
+ "Hurad",
64
+ "ImageCMS",
65
+ "MODX Evo",
66
+ "Mautic",
67
+ "OXID",
68
+ "Plentymarkets",
69
+ "RadPHP",
70
+ "SMF",
71
+ "Thelia",
72
+ "WolfCMS",
73
+ "agl",
74
+ "aimeos",
75
+ "annotatecms",
76
+ "attogram",
77
+ "bitrix",
78
+ "cakephp",
79
+ "chef",
80
+ "cockpit",
81
+ "codeigniter",
82
+ "concrete5",
83
+ "croogo",
84
+ "dokuwiki",
85
+ "drupal",
86
+ "elgg",
87
+ "expressionengine",
88
+ "fuelphp",
89
+ "grav",
90
+ "installer",
91
+ "joomla",
92
+ "kohana",
93
+ "laravel",
94
+ "lithium",
95
+ "magento",
96
+ "mako",
97
+ "mediawiki",
98
+ "modulework",
99
+ "moodle",
100
+ "phpbb",
101
+ "piwik",
102
+ "ppi",
103
+ "puppet",
104
+ "reindex",
105
+ "roundcube",
106
+ "shopware",
107
+ "silverstripe",
108
+ "symfony",
109
+ "typo3",
110
+ "wordpress",
111
+ "yawik",
112
+ "zend",
113
+ "zikula"
114
+ ],
115
+ "time": "2016-08-13 20:53:52"
116
+ }
117
+ ],
118
+ "packages-dev": [
119
+ {
120
+ "name": "simplyadmire/composer-plugins",
121
+ "version": "dev-master",
122
+ "source": {
123
+ "type": "git",
124
+ "url": "https://github.com/SimplyAdmire/ComposerPlugins.git",
125
+ "reference": "d8380f670694c1c2330b22591ca74adc82cffe19"
126
+ },
127
+ "dist": {
128
+ "type": "zip",
129
+ "url": "https://api.github.com/repos/SimplyAdmire/ComposerPlugins/zipball/d8380f670694c1c2330b22591ca74adc82cffe19",
130
+ "reference": "d8380f670694c1c2330b22591ca74adc82cffe19",
131
+ "shasum": ""
132
+ },
133
+ "require": {
134
+ "composer-plugin-api": "^1.0",
135
+ "squizlabs/php_codesniffer": "*"
136
+ },
137