184 results = this.filter( function( data ) { |
189 results = this.filter( function( data ) { |
185 name = data.get( 'name' ).replace( /(<([^>]+)>)/ig, '' ); |
190 name = data.get( 'name' ).replace( /(<([^>]+)>)/ig, '' ); |
186 description = data.get( 'description' ).replace( /(<([^>]+)>)/ig, '' ); |
191 description = data.get( 'description' ).replace( /(<([^>]+)>)/ig, '' ); |
187 author = data.get( 'author' ).replace( /(<([^>]+)>)/ig, '' ); |
192 author = data.get( 'author' ).replace( /(<([^>]+)>)/ig, '' ); |
188 |
193 |
189 haystack = _.union( name, data.get( 'id' ), description, author, data.get( 'tags' ) ); |
194 haystack = _.union( [ name, data.get( 'id' ), description, author, data.get( 'tags' ) ] ); |
190 |
195 |
191 if ( match.test( data.get( 'author' ) ) && term.length > 2 ) { |
196 if ( match.test( data.get( 'author' ) ) && term.length > 2 ) { |
192 data.set( 'displayAuthor', true ); |
197 data.set( 'displayAuthor', true ); |
193 } |
198 } |
194 |
199 |
374 events: { |
379 events: { |
375 'click': themes.isInstall ? 'preview': 'expand', |
380 'click': themes.isInstall ? 'preview': 'expand', |
376 'keydown': themes.isInstall ? 'preview': 'expand', |
381 'keydown': themes.isInstall ? 'preview': 'expand', |
377 'touchend': themes.isInstall ? 'preview': 'expand', |
382 'touchend': themes.isInstall ? 'preview': 'expand', |
378 'keyup': 'addFocus', |
383 'keyup': 'addFocus', |
379 'touchmove': 'preventExpand' |
384 'touchmove': 'preventExpand', |
|
385 'click .theme-install': 'installTheme', |
|
386 'click .update-message': 'updateTheme' |
380 }, |
387 }, |
381 |
388 |
382 touchDrag: false, |
389 touchDrag: false, |
|
390 |
|
391 initialize: function() { |
|
392 this.model.on( 'change', this.render, this ); |
|
393 }, |
383 |
394 |
384 render: function() { |
395 render: function() { |
385 var data = this.model.toJSON(); |
396 var data = this.model.toJSON(); |
|
397 |
386 // Render themes using the html template |
398 // Render themes using the html template |
387 this.$el.html( this.html( data ) ).attr({ |
399 this.$el.html( this.html( data ) ).attr({ |
388 tabindex: 0, |
400 tabindex: 0, |
389 'aria-describedby' : data.id + '-action ' + data.id + '-name' |
401 'aria-describedby' : data.id + '-action ' + data.id + '-name', |
|
402 'data-slug': data.id |
390 }); |
403 }); |
391 |
404 |
392 // Renders active theme styles |
405 // Renders active theme styles |
393 this.activeTheme(); |
406 this.activeTheme(); |
394 |
407 |
395 if ( this.model.get( 'displayAuthor' ) ) { |
408 if ( this.model.get( 'displayAuthor' ) ) { |
396 this.$el.addClass( 'display-author' ); |
409 this.$el.addClass( 'display-author' ); |
397 } |
|
398 |
|
399 if ( this.model.get( 'installed' ) ) { |
|
400 this.$el.addClass( 'is-installed' ); |
|
401 } |
410 } |
402 }, |
411 }, |
403 |
412 |
404 // Adds a class to the currently active theme |
413 // Adds a class to the currently active theme |
405 // and to the overlay in detailed view mode |
414 // and to the overlay in detailed view mode |
576 |
593 |
577 // Disable next if the next model is undefined |
594 // Disable next if the next model is undefined |
578 if ( _.isUndefined( this.model.collection.at( this.model.collection.indexOf( current ) + 1 ) ) ) { |
595 if ( _.isUndefined( this.model.collection.at( this.model.collection.indexOf( current ) + 1 ) ) ) { |
579 $themeInstaller.find( '.next-theme' ).addClass( 'disabled' ); |
596 $themeInstaller.find( '.next-theme' ).addClass( 'disabled' ); |
580 } |
597 } |
|
598 }, |
|
599 |
|
600 installTheme: function( event ) { |
|
601 var _this = this; |
|
602 |
|
603 event.preventDefault(); |
|
604 |
|
605 wp.updates.maybeRequestFilesystemCredentials( event ); |
|
606 |
|
607 $( document ).on( 'wp-theme-install-success', function( event, response ) { |
|
608 if ( _this.model.get( 'id' ) === response.slug ) { |
|
609 _this.model.set( { 'installed': true } ); |
|
610 } |
|
611 } ); |
|
612 |
|
613 wp.updates.installTheme( { |
|
614 slug: $( event.target ).data( 'slug' ) |
|
615 } ); |
|
616 }, |
|
617 |
|
618 updateTheme: function( event ) { |
|
619 var _this = this; |
|
620 |
|
621 if ( ! this.model.get( 'hasPackage' ) ) { |
|
622 return; |
|
623 } |
|
624 |
|
625 event.preventDefault(); |
|
626 |
|
627 wp.updates.maybeRequestFilesystemCredentials( event ); |
|
628 |
|
629 $( document ).on( 'wp-theme-update-success', function( event, response ) { |
|
630 _this.model.off( 'change', _this.render, _this ); |
|
631 if ( _this.model.get( 'id' ) === response.slug ) { |
|
632 _this.model.set( { |
|
633 hasUpdate: false, |
|
634 version: response.newVersion |
|
635 } ); |
|
636 } |
|
637 _this.model.on( 'change', _this.render, _this ); |
|
638 } ); |
|
639 |
|
640 wp.updates.updateTheme( { |
|
641 slug: $( event.target ).parents( 'div.theme' ).first().data( 'slug' ) |
|
642 } ); |
581 } |
643 } |
582 }); |
644 }); |
583 |
645 |
584 // Theme Details view |
646 // Theme Details view |
585 // Set ups a modal overlay with the expanded theme data |
647 // Set ups a modal overlay with the expanded theme data |
616 activeTheme: function() { |
679 activeTheme: function() { |
617 // Check the model has the active property |
680 // Check the model has the active property |
618 this.$el.toggleClass( 'active', this.model.get( 'active' ) ); |
681 this.$el.toggleClass( 'active', this.model.get( 'active' ) ); |
619 }, |
682 }, |
620 |
683 |
621 // Keeps :focus within the theme details elements |
684 // Set initial focus and constrain tabbing within the theme browser modal. |
622 containFocus: function( $el ) { |
685 containFocus: function( $el ) { |
623 var $target; |
686 |
624 |
687 // Set initial focus on the primary action control. |
625 // Move focus to the primary action |
|
626 _.delay( function() { |
688 _.delay( function() { |
627 $( '.theme-wrap a.button-primary:visible' ).focus(); |
689 $( '.theme-overlay' ).focus(); |
628 }, 500 ); |
690 }, 100 ); |
629 |
691 |
|
692 // Constrain tabbing within the modal. |
630 $el.on( 'keydown.wp-themes', function( event ) { |
693 $el.on( 'keydown.wp-themes', function( event ) { |
631 |
694 var $firstFocusable = $el.find( '.theme-header button:not(.disabled)' ).first(), |
632 // Tab key |
695 $lastFocusable = $el.find( '.theme-actions a:visible' ).last(); |
633 if ( event.which === 9 ) { |
696 |
634 $target = $( event.target ); |
697 // Check for the Tab key. |
635 |
698 if ( 9 === event.which ) { |
636 // Keep focus within the overlay by making the last link on theme actions |
699 if ( $firstFocusable[0] === event.target && event.shiftKey ) { |
637 // switch focus to button.left on tabbing and vice versa |
700 $lastFocusable.focus(); |
638 if ( $target.is( 'button.left' ) && event.shiftKey ) { |
|
639 $el.find( '.theme-actions a:last-child' ).focus(); |
|
640 event.preventDefault(); |
701 event.preventDefault(); |
641 } else if ( $target.is( '.theme-actions a:last-child' ) ) { |
702 } else if ( $lastFocusable[0] === event.target && ! event.shiftKey ) { |
642 $el.find( 'button.left' ).focus(); |
703 $firstFocusable.focus(); |
643 event.preventDefault(); |
704 event.preventDefault(); |
644 } |
705 } |
645 } |
706 } |
646 }); |
707 }); |
647 }, |
708 }, |
694 // Handles .disabled classes for next/previous buttons |
755 // Handles .disabled classes for next/previous buttons |
695 navigation: function() { |
756 navigation: function() { |
696 |
757 |
697 // Disable Left/Right when at the start or end of the collection |
758 // Disable Left/Right when at the start or end of the collection |
698 if ( this.model.cid === this.model.collection.at(0).cid ) { |
759 if ( this.model.cid === this.model.collection.at(0).cid ) { |
699 this.$el.find( '.left' ).addClass( 'disabled' ); |
760 this.$el.find( '.left' ) |
|
761 .addClass( 'disabled' ) |
|
762 .prop( 'disabled', true ); |
700 } |
763 } |
701 if ( this.model.cid === this.model.collection.at( this.model.collection.length - 1 ).cid ) { |
764 if ( this.model.cid === this.model.collection.at( this.model.collection.length - 1 ).cid ) { |
702 this.$el.find( '.right' ).addClass( 'disabled' ); |
765 this.$el.find( '.right' ) |
|
766 .addClass( 'disabled' ) |
|
767 .prop( 'disabled', true ); |
703 } |
768 } |
704 }, |
769 }, |
705 |
770 |
706 // Performs the actions to effectively close |
771 // Performs the actions to effectively close |
707 // the theme details overlay |
772 // the theme details overlay |
710 this.remove(); |
775 this.remove(); |
711 this.unbind(); |
776 this.unbind(); |
712 this.trigger( 'theme:collapse' ); |
777 this.trigger( 'theme:collapse' ); |
713 }, |
778 }, |
714 |
779 |
715 // Confirmation dialog for deleting a theme |
780 updateTheme: function( event ) { |
716 deleteTheme: function() { |
781 var _this = this; |
717 return confirm( themes.data.settings.confirmDelete ); |
782 event.preventDefault(); |
|
783 |
|
784 wp.updates.maybeRequestFilesystemCredentials( event ); |
|
785 |
|
786 $( document ).on( 'wp-theme-update-success', function( event, response ) { |
|
787 if ( _this.model.get( 'id' ) === response.slug ) { |
|
788 _this.model.set( { |
|
789 hasUpdate: false, |
|
790 version: response.newVersion |
|
791 } ); |
|
792 } |
|
793 _this.render(); |
|
794 } ); |
|
795 |
|
796 wp.updates.updateTheme( { |
|
797 slug: $( event.target ).data( 'slug' ) |
|
798 } ); |
|
799 }, |
|
800 |
|
801 deleteTheme: function( event ) { |
|
802 var _this = this, |
|
803 _collection = _this.model.collection, |
|
804 _themes = themes; |
|
805 event.preventDefault(); |
|
806 |
|
807 // Confirmation dialog for deleting a theme. |
|
808 if ( ! window.confirm( wp.themes.data.settings.confirmDelete ) ) { |
|
809 return; |
|
810 } |
|
811 |
|
812 wp.updates.maybeRequestFilesystemCredentials( event ); |
|
813 |
|
814 $( document ).one( 'wp-theme-delete-success', function( event, response ) { |
|
815 _this.$el.find( '.close' ).trigger( 'click' ); |
|
816 $( '[data-slug="' + response.slug + '"]' ).css( { backgroundColor:'#faafaa' } ).fadeOut( 350, function() { |
|
817 $( this ).remove(); |
|
818 _themes.data.themes = _.without( _themes.data.themes, _.findWhere( _themes.data.themes, { id: response.slug } ) ); |
|
819 |
|
820 $( '.wp-filter-search' ).val( '' ); |
|
821 _collection.doSearch( '' ); |
|
822 _collection.remove( _this.model ); |
|
823 _collection.trigger( 'themes:update' ); |
|
824 } ); |
|
825 } ); |
|
826 |
|
827 wp.updates.deleteTheme( { |
|
828 slug: this.model.get( 'id' ) |
|
829 } ); |
718 }, |
830 }, |
719 |
831 |
720 nextTheme: function() { |
832 nextTheme: function() { |
721 var self = this; |
833 var self = this; |
722 self.trigger( 'theme:next', self.model.cid ); |
834 self.trigger( 'theme:next', self.model.cid ); |
753 el: '.theme-install-overlay', |
865 el: '.theme-install-overlay', |
754 |
866 |
755 events: { |
867 events: { |
756 'click .close-full-overlay': 'close', |
868 'click .close-full-overlay': 'close', |
757 'click .collapse-sidebar': 'collapse', |
869 'click .collapse-sidebar': 'collapse', |
|
870 'click .devices button': 'previewDevice', |
758 'click .previous-theme': 'previousTheme', |
871 'click .previous-theme': 'previousTheme', |
759 'click .next-theme': 'nextTheme', |
872 'click .next-theme': 'nextTheme', |
760 'keyup': 'keyEvent' |
873 'keyup': 'keyEvent', |
|
874 'click .theme-install': 'installTheme' |
761 }, |
875 }, |
762 |
876 |
763 // The HTML template for the theme preview |
877 // The HTML template for the theme preview |
764 html: themes.template( 'theme-preview' ), |
878 html: themes.template( 'theme-preview' ), |
765 |
879 |
766 render: function() { |
880 render: function() { |
767 var data = this.model.toJSON(); |
881 var self = this, |
768 |
882 currentPreviewDevice, |
769 this.$el.html( this.html( data ) ); |
883 data = this.model.toJSON(), |
770 |
884 $body = $( document.body ); |
771 themes.router.navigate( themes.router.baseUrl( themes.router.themePath + this.model.get( 'id' ) ), { replace: true } ); |
885 |
|
886 $body.attr( 'aria-busy', 'true' ); |
|
887 |
|
888 this.$el.removeClass( 'iframe-ready' ).html( this.html( data ) ); |
|
889 |
|
890 currentPreviewDevice = this.$el.data( 'current-preview-device' ); |
|
891 if ( currentPreviewDevice ) { |
|
892 self.tooglePreviewDeviceButtons( currentPreviewDevice ); |
|
893 } |
|
894 |
|
895 themes.router.navigate( themes.router.baseUrl( themes.router.themePath + this.model.get( 'id' ) ), { replace: false } ); |
772 |
896 |
773 this.$el.fadeIn( 200, function() { |
897 this.$el.fadeIn( 200, function() { |
774 $( 'body' ).addClass( 'theme-installer-active full-overlay-active' ); |
898 $body.addClass( 'theme-installer-active full-overlay-active' ); |
775 $( '.close-full-overlay' ).focus(); |
899 }); |
776 }); |
900 |
|
901 this.$el.find( 'iframe' ).one( 'load', function() { |
|
902 self.iframeLoaded(); |
|
903 }); |
|
904 }, |
|
905 |
|
906 iframeLoaded: function() { |
|
907 this.$el.addClass( 'iframe-ready' ); |
|
908 $( document.body ).attr( 'aria-busy', 'false' ); |
777 }, |
909 }, |
778 |
910 |
779 close: function() { |
911 close: function() { |
780 this.$el.fadeOut( 200, function() { |
912 this.$el.fadeOut( 200, function() { |
781 $( 'body' ).removeClass( 'theme-installer-active full-overlay-active' ); |
913 $( 'body' ).removeClass( 'theme-installer-active full-overlay-active' ); |
782 |
914 |
783 // Return focus to the theme div |
915 // Return focus to the theme div |
784 if ( themes.focusedTheme ) { |
916 if ( themes.focusedTheme ) { |
785 themes.focusedTheme.focus(); |
917 themes.focusedTheme.focus(); |
786 } |
918 } |
787 }); |
919 }).removeClass( 'iframe-ready' ); |
788 |
920 |
789 themes.router.navigate( themes.router.baseUrl( '' ) ); |
921 // Restore the previous browse tab if available. |
|
922 if ( themes.router.selectedTab ) { |
|
923 themes.router.navigate( themes.router.baseUrl( '?browse=' + themes.router.selectedTab ) ); |
|
924 themes.router.selectedTab = false; |
|
925 } else { |
|
926 themes.router.navigate( themes.router.baseUrl( '' ) ); |
|
927 } |
790 this.trigger( 'preview:close' ); |
928 this.trigger( 'preview:close' ); |
791 this.undelegateEvents(); |
929 this.undelegateEvents(); |
792 this.unbind(); |
930 this.unbind(); |
793 return false; |
931 return false; |
794 }, |
932 }, |
795 |
933 |
796 collapse: function() { |
934 collapse: function( event ) { |
|
935 var $button = $( event.currentTarget ); |
|
936 if ( 'true' === $button.attr( 'aria-expanded' ) ) { |
|
937 $button.attr({ 'aria-expanded': 'false', 'aria-label': l10n.expandSidebar }); |
|
938 } else { |
|
939 $button.attr({ 'aria-expanded': 'true', 'aria-label': l10n.collapseSidebar }); |
|
940 } |
797 |
941 |
798 this.$el.toggleClass( 'collapsed' ).toggleClass( 'expanded' ); |
942 this.$el.toggleClass( 'collapsed' ).toggleClass( 'expanded' ); |
799 return false; |
943 return false; |
|
944 }, |
|
945 |
|
946 previewDevice: function( event ) { |
|
947 var device = $( event.currentTarget ).data( 'device' ); |
|
948 |
|
949 this.$el |
|
950 .removeClass( 'preview-desktop preview-tablet preview-mobile' ) |
|
951 .addClass( 'preview-' + device ) |
|
952 .data( 'current-preview-device', device ); |
|
953 |
|
954 this.tooglePreviewDeviceButtons( device ); |
|
955 }, |
|
956 |
|
957 tooglePreviewDeviceButtons: function( newDevice ) { |
|
958 var $devices = $( '.wp-full-overlay-footer .devices' ); |
|
959 |
|
960 $devices.find( 'button' ) |
|
961 .removeClass( 'active' ) |
|
962 .attr( 'aria-pressed', false ); |
|
963 |
|
964 $devices.find( 'button.preview-' + newDevice ) |
|
965 .addClass( 'active' ) |
|
966 .attr( 'aria-pressed', true ); |
800 }, |
967 }, |
801 |
968 |
802 keyEvent: function( event ) { |
969 keyEvent: function( event ) { |
803 // The escape key closes the preview |
970 // The escape key closes the preview |
804 if ( event.keyCode === 27 ) { |
971 if ( event.keyCode === 27 ) { |
812 |
979 |
813 // The left arrow key, previous theme |
980 // The left arrow key, previous theme |
814 if ( event.keyCode === 37 ) { |
981 if ( event.keyCode === 37 ) { |
815 this.previousTheme(); |
982 this.previousTheme(); |
816 } |
983 } |
|
984 }, |
|
985 |
|
986 installTheme: function( event ) { |
|
987 var _this = this, |
|
988 $target = $( event.target ); |
|
989 event.preventDefault(); |
|
990 |
|
991 if ( $target.hasClass( 'disabled' ) ) { |
|
992 return; |
|
993 } |
|
994 |
|
995 wp.updates.maybeRequestFilesystemCredentials( event ); |
|
996 |
|
997 $( document ).on( 'wp-theme-install-success', function() { |
|
998 _this.model.set( { 'installed': true } ); |
|
999 } ); |
|
1000 |
|
1001 wp.updates.installTheme( { |
|
1002 slug: $target.data( 'slug' ) |
|
1003 } ); |
817 } |
1004 } |
818 }); |
1005 }); |
819 |
1006 |
820 // Controls the rendering of div.themes, |
1007 // Controls the rendering of div.themes, |
821 // a wrapper that will hold all the theme elements |
1008 // a wrapper that will hold all the theme elements |
822 themes.view.Themes = wp.Backbone.View.extend({ |
1009 themes.view.Themes = wp.Backbone.View.extend({ |
823 |
1010 |
824 className: 'themes', |
1011 className: 'themes wp-clearfix', |
825 $overlay: $( 'div.theme-overlay' ), |
1012 $overlay: $( 'div.theme-overlay' ), |
826 |
1013 |
827 // Number to keep track of scroll position |
1014 // Number to keep track of scroll position |
828 // while in theme-overlay mode |
1015 // while in theme-overlay mode |
829 index: 0, |
1016 index: 0, |
830 |
1017 |
831 // The theme count element |
1018 // The theme count element |
832 count: $( '.wp-core-ui .theme-count' ), |
1019 count: $( '.wrap .theme-count' ), |
833 |
1020 |
834 // The live themes count |
1021 // The live themes count |
835 liveThemeCount: 0, |
1022 liveThemeCount: 0, |
836 |
1023 |
837 initialize: function( options ) { |
1024 initialize: function( options ) { |
845 |
1032 |
846 // Move the active theme to the beginning of the collection |
1033 // Move the active theme to the beginning of the collection |
847 self.currentTheme(); |
1034 self.currentTheme(); |
848 |
1035 |
849 // When the collection is updated by user input... |
1036 // When the collection is updated by user input... |
850 this.listenTo( self.collection, 'update', function() { |
1037 this.listenTo( self.collection, 'themes:update', function() { |
851 self.parent.page = 0; |
1038 self.parent.page = 0; |
852 self.currentTheme(); |
1039 self.currentTheme(); |
853 self.render( this ); |
1040 self.render( this ); |
854 }); |
1041 } ); |
855 |
1042 |
856 // Update theme count to full result set when available. |
1043 // Update theme count to full result set when available. |
857 this.listenTo( self.collection, 'query:success', function( count ) { |
1044 this.listenTo( self.collection, 'query:success', function( count ) { |
858 if ( _.isNumber( count ) ) { |
1045 if ( _.isNumber( count ) ) { |
859 self.count.text( count ); |
1046 self.count.text( count ); |
1019 this.overlay = new themes.view.Details({ |
1217 this.overlay = new themes.view.Details({ |
1020 model: self.model |
1218 model: self.model |
1021 }); |
1219 }); |
1022 |
1220 |
1023 this.overlay.render(); |
1221 this.overlay.render(); |
|
1222 |
|
1223 if ( this.model.get( 'hasUpdate' ) ) { |
|
1224 $card = $( '[data-slug="' + this.model.id + '"]' ); |
|
1225 $modal = $( this.overlay.el ); |
|
1226 |
|
1227 if ( $card.find( '.updating-message' ).length ) { |
|
1228 $modal.find( '.notice-warning h3' ).remove(); |
|
1229 $modal.find( '.notice-warning' ) |
|
1230 .removeClass( 'notice-large' ) |
|
1231 .addClass( 'updating-message' ) |
|
1232 .find( 'p' ).text( wp.updates.l10n.updating ); |
|
1233 } else if ( $card.find( '.notice-error' ).length ) { |
|
1234 $modal.find( '.notice-warning' ).remove(); |
|
1235 } |
|
1236 } |
|
1237 |
1024 this.$overlay.html( this.overlay.el ); |
1238 this.$overlay.html( this.overlay.el ); |
1025 |
1239 |
1026 // Bind to theme:next and theme:previous |
1240 // Bind to theme:next and theme:previous |
1027 // triggered by the arrow keys |
1241 // triggered by the arrow keys |
1028 // |
1242 // |
1132 // Clear on escape. |
1346 // Clear on escape. |
1133 if ( event.type === 'keyup' && event.which === 27 ) { |
1347 if ( event.type === 'keyup' && event.which === 27 ) { |
1134 event.target.value = ''; |
1348 event.target.value = ''; |
1135 } |
1349 } |
1136 |
1350 |
1137 /** |
1351 // Since doSearch is debounced, it will only run when user input comes to a rest. |
1138 * Since doSearch is debounced, it will only run when user input comes to a rest |
|
1139 */ |
|
1140 this.doSearch( event ); |
1352 this.doSearch( event ); |
1141 }, |
1353 }, |
1142 |
1354 |
1143 // Runs a search on the theme collection. |
1355 // Runs a search on the theme collection. |
1144 doSearch: _.debounce( function( event ) { |
1356 doSearch: function( event ) { |
1145 var options = {}; |
1357 var options = {}; |
1146 |
1358 |
1147 this.collection.doSearch( event.target.value ); |
1359 this.collection.doSearch( event.target.value.replace( /\+/g, ' ' ) ); |
1148 |
1360 |
1149 // if search is initiated and key is not return |
1361 // if search is initiated and key is not return |
1150 if ( this.searching && event.which !== 13 ) { |
1362 if ( this.searching && event.which !== 13 ) { |
1151 options.replace = true; |
1363 options.replace = true; |
1152 } else { |
1364 } else { |
1157 if ( event.target.value ) { |
1369 if ( event.target.value ) { |
1158 themes.router.navigate( themes.router.baseUrl( themes.router.searchPath + event.target.value ), options ); |
1370 themes.router.navigate( themes.router.baseUrl( themes.router.searchPath + event.target.value ), options ); |
1159 } else { |
1371 } else { |
1160 themes.router.navigate( themes.router.baseUrl( '' ) ); |
1372 themes.router.navigate( themes.router.baseUrl( '' ) ); |
1161 } |
1373 } |
1162 }, 500 ), |
1374 }, |
1163 |
1375 |
1164 pushState: function( event ) { |
1376 pushState: function( event ) { |
1165 var url = themes.router.baseUrl( '' ); |
1377 var url = themes.router.baseUrl( '' ); |
1166 |
1378 |
1167 if ( event.target.value ) { |
1379 if ( event.target.value ) { |
1168 url = themes.router.baseUrl( themes.router.searchPath + event.target.value ); |
1380 url = themes.router.baseUrl( themes.router.searchPath + encodeURIComponent( event.target.value ) ); |
1169 } |
1381 } |
1170 |
1382 |
1171 this.searching = false; |
1383 this.searching = false; |
1172 themes.router.navigate( url ); |
1384 themes.router.navigate( url ); |
1173 |
1385 |
1174 } |
1386 } |
1175 }); |
1387 }); |
|
1388 |
|
1389 /** |
|
1390 * Navigate router. |
|
1391 * |
|
1392 * @since 4.9.0 |
|
1393 * |
|
1394 * @param {string} url - URL to navigate to. |
|
1395 * @param {object} state - State. |
|
1396 * @returns {void} |
|
1397 */ |
|
1398 function navigateRouter( url, state ) { |
|
1399 var router = this; |
|
1400 if ( Backbone.history._hasPushState ) { |
|
1401 Backbone.Router.prototype.navigate.call( router, url, state ); |
|
1402 } |
|
1403 } |
1176 |
1404 |
1177 // Sets up the routes events for relevant url queries |
1405 // Sets up the routes events for relevant url queries |
1178 // Listens to [theme] and [search] params |
1406 // Listens to [theme] and [search] params |
1179 themes.Router = Backbone.Router.extend({ |
1407 themes.Router = Backbone.Router.extend({ |
1180 |
1408 |
1313 if ( value.substring( 0, 4 ) === 'tag:' ) { |
1553 if ( value.substring( 0, 4 ) === 'tag:' ) { |
1314 request.search = ''; |
1554 request.search = ''; |
1315 request.tag = [ value.slice( 4 ) ]; |
1555 request.tag = [ value.slice( 4 ) ]; |
1316 } |
1556 } |
1317 |
1557 |
1318 $( '.filter-links li > a.current' ).removeClass( 'current' ); |
1558 $( '.filter-links li > a.current' ) |
1319 $( 'body' ).removeClass( 'show-filters filters-applied' ); |
1559 .removeClass( 'current' ) |
|
1560 .removeAttr( 'aria-current' ); |
|
1561 |
|
1562 $( 'body' ).removeClass( 'show-filters filters-applied show-favorites-form' ); |
|
1563 $( '.drawer-toggle' ).attr( 'aria-expanded', 'false' ); |
1320 |
1564 |
1321 // Get the themes by sending Ajax POST request to api.wordpress.org/themes |
1565 // Get the themes by sending Ajax POST request to api.wordpress.org/themes |
1322 // or searching the local cache |
1566 // or searching the local cache |
1323 this.collection.query( request ); |
1567 this.collection.query( request ); |
1324 |
1568 |
1325 // Set route |
1569 // Set route |
1326 themes.router.navigate( themes.router.baseUrl( themes.router.searchPath + value ), { replace: true } ); |
1570 themes.router.navigate( themes.router.baseUrl( themes.router.searchPath + encodeURIComponent( value ) ), { replace: true } ); |
1327 }, 500 ) |
1571 } |
1328 }); |
1572 }); |
1329 |
1573 |
1330 themes.view.Installer = themes.view.Appearance.extend({ |
1574 themes.view.Installer = themes.view.Appearance.extend({ |
1331 |
1575 |
1332 el: '#wpbody-content .wrap', |
1576 el: '#wpbody-content .wrap', |
1337 'click .theme-filter': 'onFilter', |
1581 'click .theme-filter': 'onFilter', |
1338 'click .drawer-toggle': 'moreFilters', |
1582 'click .drawer-toggle': 'moreFilters', |
1339 'click .filter-drawer .apply-filters': 'applyFilters', |
1583 'click .filter-drawer .apply-filters': 'applyFilters', |
1340 'click .filter-group [type="checkbox"]': 'addFilter', |
1584 'click .filter-group [type="checkbox"]': 'addFilter', |
1341 'click .filter-drawer .clear-filters': 'clearFilters', |
1585 'click .filter-drawer .clear-filters': 'clearFilters', |
1342 'click .filtered-by': 'backToFilters' |
1586 'click .edit-filters': 'backToFilters', |
|
1587 'click .favorites-form-submit' : 'saveUsername', |
|
1588 'keyup #wporg-username-input': 'saveUsername' |
1343 }, |
1589 }, |
1344 |
1590 |
1345 // Initial render method |
1591 // Initial render method |
1346 render: function() { |
1592 render: function() { |
1347 var self = this; |
1593 var self = this; |
1443 // Bail if this is already active |
1708 // Bail if this is already active |
1444 if ( $el.hasClass( this.activeClass ) ) { |
1709 if ( $el.hasClass( this.activeClass ) ) { |
1445 return; |
1710 return; |
1446 } |
1711 } |
1447 |
1712 |
1448 $( '.filter-links li > a, .theme-section' ).removeClass( this.activeClass ); |
1713 $( '.filter-links li > a, .theme-section' ) |
1449 $el.addClass( this.activeClass ); |
1714 .removeClass( this.activeClass ) |
|
1715 .removeAttr( 'aria-current' ); |
|
1716 $el |
|
1717 .addClass( this.activeClass ) |
|
1718 .attr( 'aria-current', 'page' ); |
1450 |
1719 |
1451 if ( ! filter ) { |
1720 if ( ! filter ) { |
1452 return; |
1721 return; |
1453 } |
1722 } |
1454 |
1723 |
1455 // Construct the filter request |
1724 // Construct the filter request |
1456 // using the default values |
1725 // using the default values |
1457 filter = _.union( filter, this.filtersChecked() ); |
1726 filter = _.union( [ filter, this.filtersChecked() ] ); |
1458 request = { tag: [ filter ] }; |
1727 request = { tag: [ filter ] }; |
1459 |
1728 |
1460 // Get the themes by sending Ajax POST request to api.wordpress.org/themes |
1729 // Get the themes by sending Ajax POST request to api.wordpress.org/themes |
1461 // or searching the local cache |
1730 // or searching the local cache |
1462 this.collection.query( request ); |
1731 this.collection.query( request ); |
1476 |
1745 |
1477 if ( event ) { |
1746 if ( event ) { |
1478 event.preventDefault(); |
1747 event.preventDefault(); |
1479 } |
1748 } |
1480 |
1749 |
|
1750 if ( ! tags ) { |
|
1751 wp.a11y.speak( l10n.selectFeatureFilter ); |
|
1752 return; |
|
1753 } |
|
1754 |
1481 $( 'body' ).addClass( 'filters-applied' ); |
1755 $( 'body' ).addClass( 'filters-applied' ); |
1482 $( '.filter-links li > a.current' ).removeClass( 'current' ); |
1756 $( '.filter-links li > a.current' ) |
|
1757 .removeClass( 'current' ) |
|
1758 .removeAttr( 'aria-current' ); |
|
1759 |
1483 filteringBy.empty(); |
1760 filteringBy.empty(); |
1484 |
1761 |
1485 _.each( tags, function( tag ) { |
1762 _.each( tags, function( tag ) { |
1486 name = $( 'label[for="filter-id-' + tag + '"]' ).text(); |
1763 name = $( 'label[for="filter-id-' + tag + '"]' ).text(); |
1487 filteringBy.append( '<span class="tag">' + name + '</span>' ); |
1764 filteringBy.append( '<span class="tag">' + name + '</span>' ); |
1488 }); |
1765 }); |
1489 |
1766 |
1490 // Get the themes by sending Ajax POST request to api.wordpress.org/themes |
1767 // Get the themes by sending Ajax POST request to api.wordpress.org/themes |
1491 // or searching the local cache |
1768 // or searching the local cache |
1492 this.collection.query( request ); |
1769 this.collection.query( request ); |
|
1770 }, |
|
1771 |
|
1772 // Save the user's WordPress.org username and get his favorite themes. |
|
1773 saveUsername: function ( event ) { |
|
1774 var username = $( '#wporg-username-input' ).val(), |
|
1775 nonce = $( '#wporg-username-nonce' ).val(), |
|
1776 request = { browse: 'favorites', user: username }, |
|
1777 that = this; |
|
1778 |
|
1779 if ( event ) { |
|
1780 event.preventDefault(); |
|
1781 } |
|
1782 |
|
1783 // save username on enter |
|
1784 if ( event.type === 'keyup' && event.which !== 13 ) { |
|
1785 return; |
|
1786 } |
|
1787 |
|
1788 return wp.ajax.send( 'save-wporg-username', { |
|
1789 data: { |
|
1790 _wpnonce: nonce, |
|
1791 username: username |
|
1792 }, |
|
1793 success: function () { |
|
1794 // Get the themes by sending Ajax POST request to api.wordpress.org/themes |
|
1795 // or searching the local cache |
|
1796 that.collection.query( request ); |
|
1797 } |
|
1798 } ); |
1493 }, |
1799 }, |
1494 |
1800 |
1495 // Get the checked filters |
1801 // Get the checked filters |
1496 // @return {array} of tags or false |
1802 // @return {array} of tags or false |
1497 filtersChecked: function() { |
1803 filtersChecked: function() { |
1516 return tags; |
1822 return tags; |
1517 }, |
1823 }, |
1518 |
1824 |
1519 activeClass: 'current', |
1825 activeClass: 'current', |
1520 |
1826 |
1521 // Overwrite search container class to append search |
1827 /* |
1522 // in new location |
1828 * When users press the "Upload Theme" button, show the upload form in place. |
1523 searchContainer: $( '.wp-filter .search-form' ), |
1829 */ |
1524 |
|
1525 uploader: function() { |
1830 uploader: function() { |
1526 $( 'a.upload' ).on( 'click', function( event ) { |
1831 var uploadViewToggle = $( '.upload-view-toggle' ), |
1527 event.preventDefault(); |
1832 $body = $( document.body ); |
1528 $( 'body' ).addClass( 'show-upload-theme' ); |
1833 |
1529 themes.router.navigate( themes.router.baseUrl( '?upload' ), { replace: true } ); |
1834 uploadViewToggle.on( 'click', function() { |
1530 }); |
1835 // Toggle the upload view. |
1531 $( 'a.browse-themes' ).on( 'click', function( event ) { |
1836 $body.toggleClass( 'show-upload-view' ); |
1532 event.preventDefault(); |
1837 // Toggle the `aria-expanded` button attribute. |
1533 $( 'body' ).removeClass( 'show-upload-theme' ); |
1838 uploadViewToggle.attr( 'aria-expanded', $body.hasClass( 'show-upload-view' ) ); |
1534 themes.router.navigate( themes.router.baseUrl( '' ), { replace: true } ); |
|
1535 }); |
1839 }); |
1536 }, |
1840 }, |
1537 |
1841 |
1538 // Toggle the full filters navigation |
1842 // Toggle the full filters navigation |
1539 moreFilters: function( event ) { |
1843 moreFilters: function( event ) { |
|
1844 var $body = $( 'body' ), |
|
1845 $toggleButton = $( '.drawer-toggle' ); |
|
1846 |
1540 event.preventDefault(); |
1847 event.preventDefault(); |
1541 |
1848 |
1542 if ( $( 'body' ).hasClass( 'filters-applied' ) ) { |
1849 if ( $body.hasClass( 'filters-applied' ) ) { |
1543 return this.backToFilters(); |
1850 return this.backToFilters(); |
1544 } |
1851 } |
1545 |
1852 |
1546 // If the filters section is opened and filters are checked |
|
1547 // run the relevant query collapsing to filtered-by state |
|
1548 if ( $( 'body' ).hasClass( 'show-filters' ) && this.filtersChecked() ) { |
|
1549 return this.addFilter(); |
|
1550 } |
|
1551 |
|
1552 this.clearSearch(); |
1853 this.clearSearch(); |
1553 |
1854 |
1554 themes.router.navigate( themes.router.baseUrl( '' ) ); |
1855 themes.router.navigate( themes.router.baseUrl( '' ) ); |
1555 $( 'body' ).toggleClass( 'show-filters' ); |
1856 // Toggle the feature filters view. |
|
1857 $body.toggleClass( 'show-filters' ); |
|
1858 // Toggle the `aria-expanded` button attribute. |
|
1859 $toggleButton.attr( 'aria-expanded', $body.hasClass( 'show-filters' ) ); |
1556 }, |
1860 }, |
1557 |
1861 |
1558 // Clears all the checked filters |
1862 // Clears all the checked filters |
1559 // @uses filtersChecked() |
1863 // @uses filtersChecked() |
1560 clearFilters: function( event ) { |
1864 clearFilters: function( event ) { |
1648 themes.router = new themes.InstallerRouter(); |
1952 themes.router = new themes.InstallerRouter(); |
1649 |
1953 |
1650 // Handles `theme` route event |
1954 // Handles `theme` route event |
1651 // Queries the API for the passed theme slug |
1955 // Queries the API for the passed theme slug |
1652 themes.router.on( 'route:preview', function( slug ) { |
1956 themes.router.on( 'route:preview', function( slug ) { |
1653 request.theme = slug; |
1957 |
1654 self.view.collection.query( request ); |
1958 // Remove existing handlers. |
|
1959 if ( themes.preview ) { |
|
1960 themes.preview.undelegateEvents(); |
|
1961 themes.preview.unbind(); |
|
1962 } |
|
1963 |
|
1964 // If the theme preview is active, set the current theme. |
|
1965 if ( self.view.view.theme && self.view.view.theme.preview ) { |
|
1966 self.view.view.theme.model = self.view.collection.findWhere( { 'slug': slug } ); |
|
1967 self.view.view.theme.preview(); |
|
1968 } else { |
|
1969 |
|
1970 // Select the theme by slug. |
|
1971 request.theme = slug; |
|
1972 self.view.collection.query( request ); |
|
1973 self.view.collection.trigger( 'update' ); |
|
1974 |
|
1975 // Open the theme preview. |
|
1976 self.view.collection.once( 'query:success', function() { |
|
1977 $( 'div[data-slug="' + slug + '"]' ).trigger( 'click' ); |
|
1978 }); |
|
1979 |
|
1980 } |
1655 }); |
1981 }); |
1656 |
1982 |
1657 // Handles sorting / browsing routes |
1983 // Handles sorting / browsing routes |
1658 // Also handles the root URL triggering a sort request |
1984 // Also handles the root URL triggering a sort request |
1659 // for `featured`, the default view |
1985 // for `featured`, the default view |
1660 themes.router.on( 'route:sort', function( sort ) { |
1986 themes.router.on( 'route:sort', function( sort ) { |
1661 if ( ! sort ) { |
1987 if ( ! sort ) { |
1662 sort = 'featured'; |
1988 sort = 'featured'; |
|
1989 themes.router.navigate( themes.router.baseUrl( '?browse=featured' ), { replace: true } ); |
1663 } |
1990 } |
1664 self.view.sort( sort ); |
1991 self.view.sort( sort ); |
1665 self.view.trigger( 'theme:close' ); |
1992 |
1666 }); |
1993 // Close the preview if open. |
1667 |
1994 if ( themes.preview ) { |
1668 // Support the `upload` route by going straight to upload section |
1995 themes.preview.close(); |
1669 themes.router.on( 'route:upload', function() { |
1996 } |
1670 $( 'a.upload' ).trigger( 'click' ); |
|
1671 }); |
1997 }); |
1672 |
1998 |
1673 // The `search` route event. The router populates the input field. |
1999 // The `search` route event. The router populates the input field. |
1674 themes.router.on( 'route:search', function() { |
2000 themes.router.on( 'route:search', function() { |
1675 $( '.wp-filter-search' ).focus().trigger( 'keyup' ); |
2001 $( '.wp-filter-search' ).focus().trigger( 'keyup' ); |