Making WordPress.org

Changeset 5024


Ignore:
Timestamp:
03/01/2017 06:08:54 PM (8 years ago)
Author:
obenland
Message:

Plugin Directory: Update React client with latest changes.

This is largely a cleanup commit with some WIP around switching to node-wpapi.

Location:
sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins
Files:
39 added
17 deleted
63 edited
8 copied
2 moved

Legend:

Unmodified
Added
Removed
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/.babelrc

    r4223 r5024  
    11{
    2   "presets": ["es2015", "react"]
     2  "plugins": [
     3    [ "transform-imports", {
     4      "lodash": {
     5        "transform": "lodash/${member}",
     6        "preventFullImport": true
     7      },
     8      "state/selectors": {
     9        "transform": "state/selectors/${member}",
     10        "kebabCase": true
     11      }
     12    }]
     13  ],
     14  "presets": ["es2015", "react", "stage-2"]
    315}
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/Gruntfile.js

    r4465 r5024  
    5858            }
    5959        },
    60         jshint: {
     60        eslint: {
    6161            files: [
    62                 'Gruntfile.js',
    63                 'js/**/*.js',
    6462                'client/**/*.js',
    65                 '!js/theme.js'
    66             ],
    67             options: grunt.file.readJSON('.jshintrc')
     63                'client/**/*.jsx',
     64
     65                // External library. For now.
     66                '!client/**/**/image-gallery/index.jsx'
     67            ]
    6868        },
    6969        sass: {
     
    8585            },
    8686            options: { signature: false }
     87        },
     88        shell: {
     89            build: {
     90                command: './node_modules/wpapi/lib/data/update-default-routes-json.js --endpoint=https://wordpress.org/plugins-wp/wp-json --output=./client'
     91            }
    8792        },
    8893        rtlcss: {
     
    148153        },
    149154        watch: {
    150             jshint: {
    151                 files: ['<%= jshint.files %>'],
    152                 tasks: ['jshint']
     155            eslint: {
     156                files: ['<%= eslint.files %>'],
     157                tasks: ['eslint']
    153158            },
    154159            css: {
     
    162167    grunt.loadNpmTasks('grunt-rtlcss');
    163168    grunt.loadNpmTasks('grunt-webpack');
    164     grunt.loadNpmTasks('grunt-contrib-jshint');
     169    grunt.loadNpmTasks('grunt-eslint');
    165170    grunt.loadNpmTasks('grunt-contrib-watch');
    166171    grunt.loadNpmTasks('grunt-sass-globbing');
     172    grunt.loadNpmTasks('grunt-shell');
    167173
    168     grunt.registerTask('default', ['jshint', 'sass_globbing', 'sass', 'rtlcss:dynamic']);
     174    grunt.registerTask('default', ['eslint', 'sass_globbing', 'sass', 'rtlcss:dynamic']);
    169175    grunt.registerTask('css', ['sass_globbing', 'sass', 'postcss', 'rtlcss:dynamic']);
    170     grunt.registerTask('build', ['webpack:build', 'css']);
     176    grunt.registerTask('build', ['webpack:build', 'css', 'shell:build']);
    171177};
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/404/index.jsx

    r4223 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { Component, PropTypes } from 'react';
     5import { identity } from 'lodash';
    26import { IndexLink } from 'react-router';
     7import { localize } from 'i18n-calypso';
    38
    4 export default React.createClass( {
    5     displayName: 'NotFound',
     9export class NotFound extends Component {
     10    static propTypes = {
     11        translate: PropTypes.func,
     12    };
     13
     14    static defaultProps = {
     15        translate: identity,
     16    };
    617
    718    componentDidMount() {
    8         setTimeout( function() {
    9             jQuery( '.hinge' ).hide();
    10         }, 1800 );
    11     },
     19        setTimeout( () => jQuery( '.hinge' ).hide(), 1800 );
     20    }
    1221
    1322    render() {
     
    1524            <section className="error-404 not-found">
    1625                <header className="page-header">
    17                     <h1 className="page-title">Oops! That page can&rsquo;t be found.</h1>
     26                    <h1 className="page-title">{ this.props.translate( 'Oops! That page can&rsquo;t be found.' ) }</h1>
    1827                </header>
    1928                <div className="page-content">
    20                     <p>Try searching from the field above, or go to the <IndexLink to="/">home page</IndexLink>.</p>
     29                    <p>
     30                        { this.props.translate(
     31                            'Try searching from the field above, or go to the {{link}}home page{{/link}}.', {
     32                                component: { link: <IndexLink to="/" /> },
     33                            }
     34                        ) }
     35                    </p>
    2136
    2237                    <div className="logo-swing">
     
    2641                </div>
    2742            </section>
    28         )
     43        );
    2944    }
    30 } );
     45}
    3146
    32 
     47export default localize( NotFound );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/archive/browse/browse.jsx

    r4223 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { PropTypes } from 'react';
     5import { connect } from 'react-redux';
     6import { identity } from 'lodash';
     7import { localize } from 'i18n-calypso';
    28
     9/**
     10 * Internal dependencies.
     11 */
    312import ContentNone from 'components/content-none';
     13import { getSection, getSectionPlugins } from 'state/selectors';
     14import Pagination from 'components/pagination';
    415import PluginCard from 'components/plugin-card';
    516
    6 export default React.createClass( {
    7     displayName: 'ArchiveBrowse',
     17export const ArchiveBrowse = ( { plugins, section, translate } ) => {
     18    if ( plugins && plugins.length ) {
     19        return (
     20            <div>
     21                <header className="page-header">
     22                    <h1 className="page-title">
     23                        { translate( 'Browse: {{strong}}%(name)s{{/strong}}', {
     24                            args: { name: section.name },
     25                            components: { strong: <strong /> },
     26                        } ) }
     27                    </h1>
     28                    { section.description &&
     29                        <div className="taxonomy-description">{ section.description }</div>
     30                    }
     31                </header>
     32                { plugins.map( ( plugin ) => <PluginCard key={ plugin.id } plugin={ plugin } /> ) }
     33                <Pagination current={ 12 } total={ 30 } />
     34            </div>
     35        );
     36    }
    837
    9     render() {
    10         if ( this.props.plugins && this.props.plugins.length ) {
    11             return (
    12                 <div>
    13                     <header className="page-header">
    14                         <h1 className="page-title">Browse: <strong>{ this.props.params.type }</strong></h1>
    15                         <div className="taxonomy-description"></div>
    16                     </header>
    17                     { this.props.plugins.map( slug =>
    18                         <PluginCard key={ slug } slug={ slug } />
    19                     ) }
    20                 </div>
    21             )
    22         }
     38    return <ContentNone />;
     39};
    2340
    24         return <ContentNone { ...this.props } />;
    25     }
    26 } );
     41ArchiveBrowse.propTypes = {
     42    plugins: PropTypes.arrayOf( PropTypes.object ),
     43    section: PropTypes.object,
     44    translate: PropTypes.func,
     45    type: PropTypes.string.isRequired,
     46};
     47
     48ArchiveBrowse.defaultProps = {
     49    plugins: [],
     50    section: {},
     51    translate: identity,
     52};
     53
     54export default connect(
     55    ( state, { type } ) => ( {
     56        plugins: getSectionPlugins( state, type ),
     57        section: getSection( state, type ),
     58    } ),
     59)( localize( ArchiveBrowse ) );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/archive/browse/index.jsx

    r4223 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { Component, PropTypes } from 'react';
    25import { connect } from 'react-redux';
    36
    4 import { getBrowse } from 'actions';
     7/**
     8 * Internal dependencies.
     9 */
     10import { fetchSection, fetchSections } from 'state/sections/actions';
     11import { getSection } from 'state/selectors';
    512import Browse from './browse';
    613
    7 const BrowseContainer = React.createClass( {
     14export class BrowseContainer extends Component {
     15    static propTypes = {
     16        fetchSection: PropTypes.func,
     17        params: PropTypes.object,
     18    };
     19
     20    static defaultProps = {
     21        fetchSection: () => {},
     22        params: {},
     23    };
     24
    825    componentDidMount() {
    9         this.getBrowse();
    10     },
     26        this.fetchSection();
     27    }
    1128
    12     componentDidUpdate( previousProps ) {
    13         if ( this.props.params.type !== previousProps.params.type ) {
    14             this.getBrowse();
     29    componentDidUpdate( { params } ) {
     30        if ( this.props.params.type !== params.type ) {
     31            this.fetchSection();
    1532        }
    16     },
     33    }
    1734
    18     getBrowse() {
    19         this.props.dispatch( getBrowse( this.props.params.type ) );
    20     },
     35    fetchSection() {
     36        if ( ! this.props.section ) {
     37            this.props.fetchSections();
     38        }
     39        this.props.fetchSection( this.props.params.type );
     40    }
    2141
    2242    render() {
    23         return <Browse { ...this.props } />;
     43        return <Browse type={ this.props.params.type } />;
    2444    }
    25 } );
     45}
    2646
    27 const mapStateToProps = ( state, ownProps ) => ( {
    28     plugins: state.browse[ ownProps.params.type ]
    29 } );
    30 
    31 export default connect( mapStateToProps )( BrowseContainer );
    32 
    33 
     47export default connect(
     48    ( state, { params } ) => ( {
     49        section: getSection( state, params.type ),
     50    } ),
     51    {
     52        fetchSection,
     53        fetchSections,
     54    },
     55)( BrowseContainer );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/content-none/index.jsx

    r4223 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { PropTypes } from 'react';
     5import { connect } from 'react-redux';
     6import { identity } from 'lodash';
     7import { localize } from 'i18n-calypso';
    28
    39/**
    410 * Internal dependencies.
    511 */
     12import { getParams, getPath } from 'state/selectors';
    613import SearchForm from 'components/search-form';
    714
    8 export default  React.createClass( {
    9     displayName: 'ContentNone',
     15export const ContentNone = ( { params, path, translate } ) => {
     16    let helpText, userHelp;
    1017
    11     render() {
    12         let helpText, userHelp;
     18    if ( -1 !== path.indexOf( 'search' ) ) {
     19        helpText = (
     20            <div className="page-content">
     21                <p>{ translate( 'Sorry, but nothing matched your search terms.' ) }</p>
     22                <p>{ translate( 'Please try again with some different keywords.' ) }</p>
     23                <SearchForm />
     24            </div>
     25        );
     26    } else if ( -1 !== path.indexOf( 'browse/favorites' ) ) {
     27        if ( pluginDirectory.userId > 0 ) {
     28            helpText = <p>{ translate( 'No favorites have been added, yet.' ) }</p>;
    1329
    14         if ( -1 !== this.props.location.pathname.indexOf( 'search' ) ) {
    15             helpText = (
    16                 <div className="page-content">
    17                     <p>Sorry, but nothing matched your search terms.</p>
    18                     <p>Please try again with some different keywords.</p>
    19                     <SearchForm searchTerm={ this.props.params.searchTerm } />
    20                 </div>
    21             );
    22 
    23         } else if ( -1 !== this.props.location.pathname.indexOf( 'browse/favorites' ) ) {
    24             if ( true /*user_logged_in*/ ) {
    25                 helpText = <p>No favorites have been added, yet.</p>;
    26 
    27                 if ( -1 !== this.props.location.pathname.indexOf( 'browse/favorites/' + this.props.params.username ) ) {
    28                     userHelp = (
    29                         <div>
    30                             <p>Find a plugin and mark it as a favorite to see it here.</p>
    31                             <p>Your favorite plugins are also shared on <a href={ 'https://profile.wordpress.org/' + this.props.params.username }>your profile</a></p>
    32                         </div>
    33                     );
    34                 }
    35 
    36                 helpText = <div className="page-content">{ helpText }{ userHelp }</div>;
    37             } else {
    38                 helpText = (
    39                     <div className="page-content">
    40                         <p><a href="https://login.wordpress.org/">Loginto WordPress.org</a> to mark plugins as favorites.</p>
     30            if ( -1 !== path.indexOf( 'browse/favorites/' + params.username ) ) {
     31                userHelp = (
     32                    <div>
     33                        <p>{ translate( 'Find a plugin and mark it as a favorite to see it here.' ) }</p>
     34                        <p>
     35                            { translate( 'Your favorite plugins are also shared on {{a}}your profile{{/a}}', {
     36                                components: { a: <a href={ 'https://profile.wordpress.org/' + params.username } /> },
     37                            } ) }
     38                        </p>
    4139                    </div>
    4240                );
    4341            }
     42
     43            helpText = <div className="page-content">{ helpText }{ userHelp }</div>;
    4444        } else {
    4545            helpText = (
    4646                <div className="page-content">
    47                     <p>It seems we can&#8217;t find what you&#8217;re looking for. Perhaps searching can help.</p>
     47                    <p>
     48                        { translate( '{{a}}Log into WordPress.org{{/a}} to mark plugins as favorites.', {
     49                            components: { a: <a href="https://login.wordpress.org/" /> },
     50                        } ) }
     51                    </p>
    4852                </div>
    4953            );
     54        }
     55    } else {
     56        helpText = (
     57            <div className="page-content">
     58                <div className="page-content">
     59                    <p>
     60                        { translate(
     61                            'It seems we can&#8217;t find what you&#8217;re looking for. Perhaps searching can help.'
     62                        ) }
     63                    </p>
     64                </div>
     65                <SearchForm />
     66            </div>
     67        );
     68    }
    5069
    51             helpText = (
    52                 <div className="page-content">
    53                     { helpText }
    54                     <SearchForm searchTerm={ this.props.params.searchTerm } />
    55                 </div>
    56             );
     70    return (
     71        <section className="no-results not-found">
     72            <header className="page-header">
     73                <h1 className="page-title">{ translate( 'Nothing Found' ) }</h1>
     74            </header>
     75            { helpText }
     76        </section>
     77    );
     78};
    5779
    58         }
     80ContentNone.propTypes = {
     81    params: PropTypes.object,
     82    path: PropTypes.string,
     83    translate: PropTypes.func,
     84};
    5985
    60         return (
    61             <section className="no-results not-found">
    62                 <header className="page-header">
    63                     <h1 className="page-title">Nothing Found</h1>
    64                 </header>
    65                 { helpText }
    66             </section>
    67         )
    68     }
    69 } );
     86ContentNone.defaultProps = {
     87    params: {},
     88    path: '',
     89    translate: identity,
     90};
     91
     92export default connect(
     93    ( state ) => ( {
     94        params: getParams( state ),
     95        path: getPath( state ),
     96    } ),
     97)( localize( ContentNone ) );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/front-page/index.js

    r4223 r5024  
    1 import { connect } from 'react-redux';
     1/**
     2 * External dependencies.
     3 */
     4import React, { PropTypes } from 'react';
     5import { identity } from 'lodash';
     6import { localize } from 'i18n-calypso';
    27
    38/**
    49 * Internal dependencies.
    510 */
    6 import FrontPage from './front-page';
     11import PluginSection from './plugin-section';
    712
    8 const mapStateToProps = () => ( {
    9     sections: [
    10         {
    11             path: 'browse/featured/',
    12             title: 'Featured Plugins',
    13             type: 'featured'
    14         },
    15         {
    16             path: 'browse/popular/',
    17             title: 'Popular Plugins',
    18             type: 'popular'
    19         },
    20         {
    21             path: 'browse/beta/',
    22             title: 'Beta Plugins',
    23             type: 'beta'
    24         }
    25     ]
    26 } );
     13const FrontPage = ( { sections } ) => (
     14    <div>
     15        { sections.map( ( type ) => <PluginSection key={ type } type={ type } /> ) }
     16    </div>
     17);
    2718
    28 export default connect( mapStateToProps )( FrontPage );
     19FrontPage.propTypes = {
     20    sections: PropTypes.arrayOf( PropTypes.string ),
     21    translate: PropTypes.func,
     22};
     23
     24FrontPage.defaultProps = {
     25    sections: [ 'featured', 'popular', 'beta' ],
     26    translate: identity,
     27};
     28
     29export default localize( FrontPage );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/front-page/plugin-section/index.jsx

    r4223 r5024  
    1 import React, { Component } from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { Component, PropTypes } from 'react';
    25import { connect } from 'react-redux';
    36
     
    58 * Internal dependencies.
    69 */
    7 import { getBrowse } from 'actions';
     10import { fetchSection, fetchSections } from 'state/sections/actions';
    811import PluginSection from './plugin-section';
    912
    1013class PluginSectionContainer extends Component {
     14    static propTypes = {
     15        fetchSection: PropTypes.func,
     16        type: PropTypes.string.isRequired,
     17    };
     18
     19    static defaultProps = {
     20        fetchBrowse: () => {},
     21    };
     22
    1123    componentDidMount() {
    12         this.getBrowse();
     24        this.props.fetchSections();
     25        this.fetchSection();
    1326    }
    1427
    15     componentDidUpdate( previousProps ) {
    16         if ( this.props.section.type !== previousProps.section.type ) {
    17             this.getBrowse();
     28    componentDidUpdate( { type } ) {
     29        if ( this.props.type !== type ) {
     30            this.fetchSection();
    1831        }
    1932    }
    2033
    21     getBrowse() {
    22         this.props.dispatch( getBrowse( this.props.section.type ) );
     34    fetchSection() {
     35        this.props.fetchSection( this.props.type );
    2336    }
    2437
    2538    render() {
    26         return <PluginSection { ...this.props } />;
     39        return <PluginSection type={ this.props.type } />;
    2740    }
    2841}
    2942
    30 const mapStateToProps = ( state, ownProps ) => ( {
    31     plugins: state.browse[ ownProps.section.type ].slice( 0, 4 )
    32 } );
    33 
    34 export default connect( mapStateToProps )( PluginSectionContainer );
     43export default connect(
     44    null,
     45    {
     46        fetchSection,
     47        fetchSections,
     48    },
     49)( PluginSectionContainer );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/front-page/plugin-section/plugin-section.jsx

    r4223 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { PropTypes } from 'react';
     5import { connect } from 'react-redux';
     6import { identity } from 'lodash';
    27import { Link } from 'react-router';
     8import { localize } from 'i18n-calypso';
    39
    410/**
    511 * Internal dependencies.
    612 */
     13import { getSection, getSectionPlugins } from 'state/selectors';
    714import PluginCard from 'components/plugin-card';
    815
    9 export default React.createClass( {
    10     displayName: 'PluginSection',
    11 
    12     render() {
    13         if ( ! this.props.plugins ) {
    14             return <div />;
    15         }
    16 
     16/**
     17 *
     18 * @param {Array}       plugins   Plugins
     19 * @param {Object}      section   Section
     20 * @param {Function}    translate Translation function
     21 * @return {(XML|null)}           Component or null.
     22 * @constructor
     23 */
     24export const PluginSection = ( { plugins, section, translate } ) => {
     25    if ( plugins ) {
    1726        return (
    1827            <section className="plugin-section">
    1928                <header className="section-header">
    20                     <h2 className="section-title">{ this.props.section.title }</h2>
    21                     <Link className="section-link" to={ this.props.section.path }>See all</Link>
     29                    <h2 className="section-title">{ section.name }</h2>
     30                    <Link className="section-link" to={ `/browse/${ section.slug }/` }>{ translate( 'See all' ) }</Link>
    2231                </header>
    23                 { this.props.plugins.map( slug =>
    24                     <PluginCard key={ slug } slug={ slug } />
    25                 ) }
     32                { plugins.map( ( plugin ) => <PluginCard key={ plugin.id } plugin={ plugin } /> ) }
    2633            </section>
    27         )
     34        );
    2835    }
    29 } );
     36
     37    return null;
     38};
     39
     40PluginSection.propTypes = {
     41    plugins: PropTypes.array,
     42    section: PropTypes.object,
     43    translate: PropTypes.func,
     44};
     45
     46PluginSection.defaultProps = {
     47    plugins: [],
     48    section: {},
     49    translate: identity,
     50};
     51
     52export default connect(
     53    ( state, { type } ) => ( {
     54        plugins: getSectionPlugins( state, type ).slice( 0, 4 ),
     55        section: getSection( state, type ),
     56    } ),
     57)( localize( PluginSection ) );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/index.js

    r4223 r5024  
     1/**
     2 * External dependencies.
     3 */
    14import { connect } from 'react-redux';
    25import { withRouter } from 'react-router';
     6import { localize } from 'i18n-calypso';
    37
     8/**
     9 * Internal dependencies.
     10 */
    411import PluginDirectory from './plugin-directory';
    512
    6 const mapStateToProps = () => ( {
    7     widgets: [
    8         {
    9             title: 'Add Your Plugin',
    10             text: 'The WordPress Plugin Directory is the largest directory of free and open source WordPress plugins. Find out how to host your plugin on WordPress.org.'
    11         },
    12         {
    13             title: 'Create a Plugin',
    14             text: 'Building a plugin has never been easier. Read through the Plugin Developer Handbook to learn all about WordPress plugin development.'
    15         },
    16         {
    17             title: 'Stay Up-to-Date',
    18             text: 'Plugin development is constantly changing with each new WordPress release. Keep up with the latest changes by following the Plugin Review Team’s blog.'
    19         },
    20     ]
    21 } );
    22 
    23 export default withRouter( connect( mapStateToProps )( PluginDirectory ) );
     13export default withRouter( localize( connect(
     14    ( state, { translate } ) => ( {
     15        widgets: [
     16            /* eslint-disable max-len */
     17            {
     18                title: translate( 'Add Your Plugin' ),
     19                text: translate( 'The WordPress Plugin Directory is the largest directory of free and open source WordPress plugins. Find out how to host your plugin on WordPress.org.' ),
     20            },
     21            {
     22                title: translate( 'Create a Plugin' ),
     23                text: translate( 'Building a plugin has never been easier. Read through the Plugin Developer Handbook to learn all about WordPress plugin development.' ),
     24            },
     25            {
     26                title: translate( 'Stay Up-to-Date' ),
     27                text: translate( 'Plugin development is constantly changing with each new WordPress release. Keep up with the latest changes by following the Plugin Review Team’s blog.' ),
     28            },
     29            /* eslint-enable max-len */
     30        ],
     31    } ),
     32)( PluginDirectory ) ) );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/page/index.jsx

    r4223 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { Component, PropTypes } from 'react';
    25import { connect } from 'react-redux';
    3 import find from 'lodash/find';
    46
    57/**
     
    79 */
    810import Page from './page';
    9 import { getPage } from 'actions';
     11import { fetchPage } from 'state/pages/actions';
    1012
    11 const PageContainer = React.createClass( {
     13class PageContainer extends Component {
     14    static propTypes = {
     15        fetchPage: PropTypes.func,
     16        route: PropTypes.object.isRequired,
     17    };
     18
     19    static defaultProps = {
     20        fetchPage: () => {},
     21    };
     22
    1223    componentDidMount() {
    13         this.getPage();
    14     },
     24        this.fetchPage();
     25    }
    1526
    16     componentDidUpdate( previousProps ) {
    17         if ( this.props.route.path !== previousProps.route.path ) {
    18             this.getPage();
     27    componentDidUpdate( { route } ) {
     28        if ( this.props.route.path !== route.path ) {
     29            this.fetchPage();
    1930        }
    20     },
     31    }
    2132
    22     getPage() {
    23         this.props.dispatch( getPage( this.props.route.path ) );
    24     },
     33    fetchPage() {
     34        this.props.fetchPage( this.props.route.path );
     35    }
    2536
    2637    render() {
    27         return <Page { ...this.props } />;
     38        return <Page />;
    2839    }
    29 } );
     40}
    3041
    31 const mapStateToProps = ( state, ownProps ) => ( {
    32     page: find( state.pages, { slug: ownProps.route.path } )
    33 } );
    34 
    35 export default connect( mapStateToProps )( PageContainer );
     42export default connect(
     43    null,
     44    {
     45        fetchPage,
     46    }
     47)( PageContainer );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/page/page.jsx

    r4223 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { PropTypes } from 'react';
     5import { connect } from 'react-redux';
    26
    3 export default React.createClass( {
    4     displayName: 'Page',
     7/**
     8 * Internal dependencies.
     9 */
     10import { getPage } from 'state/selectors';
    511
    6     render() {
    7         if ( ! this.props.page ) {
    8             return (
    9                 <article className="page type-page">
    10                     <header className="entry-header">
    11                         <h1 className="entry-title"> </h1>
    12                     </header>
    13                     <div className="entry-content">
    14                         <section>
    15                             <div className="container"> LOADING </div>
    16                         </section>
    17                     </div>
    18                 </article>
    19             )
    20         }
    21 
     12const Page = ( { page } ) => {
     13    if ( page && page.title ) {
    2214        return (
    2315            <article className="page type-page">
    2416                <header className="entry-header">
    25                     <h1 className="entry-title">{ this.props.page.title.rendered }</h1>
     17                    <h1 className="entry-title">{ page.title.rendered }</h1>
    2618                </header>
    2719                <div className="entry-content">
    2820                    <section>
    29                         <div className="container" dangerouslySetInnerHTML={ { __html: this.props.page.content.rendered } } />
     21                        <div className="container" dangerouslySetInnerHTML={ { __html: page.content.rendered } } />
    3022                    </section>
    3123                </div>
    3224            </article>
    33         )
     25        );
    3426    }
    35 } );
     27
     28    return (
     29        <article className="page type-page">
     30            <header className="entry-header">
     31                <h1 className="entry-title"> </h1>
     32            </header>
     33            <div className="entry-content">
     34                <section>
     35                    <div className="container"> LOADING </div>
     36                </section>
     37            </div>
     38        </article>
     39    );
     40};
     41
     42Page.propTypes = {
     43    page: PropTypes.object,
     44};
     45
     46Page.defaultProps = {
     47    page: {},
     48};
     49
     50export default connect(
     51    ( state ) => ( {
     52        page: getPage( state ),
     53    } ),
     54)( Page );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/plugin-card/index.jsx

    r4223 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { Component, PropTypes } from 'react';
    25import { connect } from 'react-redux';
    3 import find from 'lodash/find';
    46
    57/**
     
    79 */
    810import PluginCard from './plugin-card';
    9 import { getPlugin } from 'actions';
     11import { fetchPlugin } from 'state/plugins/actions';
     12import { getPlugin } from 'state/selectors';
    1013
    11 const PluginCardContainer = React.createClass( {
     14export class PluginCardContainer extends Component {
     15    static propTypes = {
     16        fetchPlugin: PropTypes.func,
     17        plugin: PropTypes.object,
     18        slug: PropTypes.string,
     19    };
     20
     21    static defaultProps = {
     22        fetchPlugin: () => {},
     23        plugin: {},
     24        slug: '',
     25    };
     26
    1227    componentDidMount() {
    13         this.getPlugin();
    14     },
     28        this.fetchPlugin();
     29    }
    1530
    16     componentDidUpdate( previousProps ) {
    17         if ( this.props.slug !== previousProps.slug ) {
    18             this.getPlugin();
     31    componentDidUpdate( { plugin, slug } ) {
     32        if ( this.props.slug !== slug || this.props.plugin !== plugin ) {
     33            this.fetchPlugin();
    1934        }
    20     },
     35    }
    2136
    22     getPlugin() {
    23         this.props.dispatch( getPlugin( this.props.slug ) );
    24     },
     37    fetchPlugin() {
     38    //  this.props.fetchPlugin( this.props.slug );
     39    }
    2540
    2641    render() {
    27         return <PluginCard { ...this.props } />;
     42        return <PluginCard plugin={ this.props.plugin } />;
    2843    }
    29 } );
     44}
    3045
    31 const mapStateToProps = ( state, ownProps ) => ( {
    32     plugin: find( state.plugins, { slug: ownProps.slug } )
    33 } );
    34 
    35 export default connect( mapStateToProps )( PluginCardContainer );
    36 
    37 
     46export default connect(
     47    ( state, { plugin, slug } ) => ( {
     48        plugin: plugin || getPlugin( state, slug ),
     49    } ),
     50    {
     51        fetchPlugin,
     52    },
     53)( PluginCardContainer );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/plugin-card/plugin-card.jsx

    r4223 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { PropTypes } from 'react';
    25import { Link } from 'react-router';
    36
     
    811import PluginRatings from 'components/plugin-ratings';
    912
    10 export default React.createClass( {
    11     displayName: 'PluginCard',
     13export const PluginCard = ( { plugin } ) => (
     14    <article className="plugin type-plugin plugin-card">
     15        <PluginIcon slug={ plugin.slug } />
     16        <div className="entry">
     17            <header className="entry-header">
     18                <h2 className="entry-title">
     19                    <Link
     20                        to={ `${ plugin.slug }/` }
     21                        rel="bookmark"
     22                        dangerouslySetInnerHTML={ { __html: plugin.title.rendered } }
     23                    />
     24                </h2>
     25            </header>
    1226
    13     render() {
    14         if ( ! this.props.plugin ) {
    15             return (
    16                 <div />
    17             );
    18         }
     27            { plugin.ratings && <PluginRatings rating={ plugin.meta.rating } ratingCount={ plugin.ratings.length } /> }
    1928
    20         return (
    21             <article className="plugin type-plugin plugin-card">
    22                 <PluginIcon plugin={ this.props.plugin } />
    23                 <div className="entry">
    24                     <header className="entry-header">
    25                         <h2 className="entry-title">
    26                             <Link to={ `${ this.props.plugin.slug }/` } rel="bookmark">{ this.props.plugin.name }</Link>
    27                         </h2>
    28                     </header>
     29            <div className="entry-excerpt" dangerouslySetInnerHTML={ { __html: plugin.excerpt.rendered } } />
     30        </div>
     31    </article>
     32);
    2933
    30                     <PluginRatings rating={ this.props.plugin.rating } ratingCount={ this.props.plugin.num_ratings } />
     34PluginCard.propTypes = {
     35    plugin: PropTypes.object,
     36};
    3137
    32                     <div className="entry-excerpt">{ this.props.plugin.short_description }</div>
    33                 </div>
    34             </article>
    35         )
    36     }
    37 } );
     38export default PluginCard;
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/plugin-directory.jsx

    r4223 r5024  
    1 import React from 'react';
    2 import { IndexLink } from 'react-router';
     1/**
     2 * External dependencies.
     3 */
     4import React, { PropTypes } from 'react';
    35
     6/**
     7 * Internal dependencies.
     8 */
    49import TextWidget from 'components/widget-area/widgets/text';
    510import WidgetArea from 'components/widget-area';
    611
    7 export default React.createClass( {
    8     displayName: 'PluginDirectory',
    9 
    10     widgetArea() {
    11         return (
    12             <WidgetArea { ...this.props }>
    13                 { this.props.widgets.map( widget =>
     12export const PluginDirectory = ( { header, main, router, widgets } ) => (
     13    <div>
     14        { header }
     15        { main }
     16        { router.isActive( '/', true ) &&
     17            <WidgetArea>
     18                { widgets.map( ( widget ) =>
    1419                    <TextWidget key={ widget.title } widget={ widget } />
    1520                ) }
    1621            </WidgetArea>
    17         );
    18     },
     22        }
     23    </div>
     24);
    1925
    20     render() {
    21         return (
    22             <div>
    23                 { this.props.header }
    24                 { this.props.main }
    25                 { this.props.router.isActive( '/', true ) ? this.widgetArea() : <div /> }
    26             </div>
    27         )
    28     }
    29 } );
     26PluginDirectory.propTypes = {
     27    header: PropTypes.object,
     28    main: PropTypes.object,
     29    router: PropTypes.object,
     30    widgets: PropTypes.arrayOf( PropTypes.object ),
     31};
    3032
     33PluginDirectory.defaultProps = {
     34    header: {},
     35    main: {},
     36    router: {},
     37    widgets: [],
     38};
     39
     40export default PluginDirectory;
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/plugin-icon/index.jsx

    r4223 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { PropTypes } from 'react';
     5import { connect } from 'react-redux';
    26
    3 export default React.createClass( {
    4     displayName: 'PluginIcon',
     7/**
     8 * Internal dependencies.
     9 */
     10import { getPlugin } from 'state/selectors';
    511
    6     render() {
    7         const { icons, slug } = this.props.plugin;
    8         let icon;
     12/**
     13 *
     14 * @param {Object} plugin Plugin object.
     15 * @return {*}            React element or null.
     16 * @constructor
     17 */
     18export const PluginIcon = ( { plugin } ) => {
     19    const { icons, slug } = plugin;
     20    let icon;
    921
    10         if ( ! icons ) {
    11             return <div />;
    12         }
     22    if ( ! icons ) {
     23        return null;
     24    }
    1325
    14         if ( icons[ '1x' ] ) {
    15             icon = icons[ '1x' ]
    16         } else if ( icons.svg ) {
    17             icon = icons.svg;
    18         } else {
    19             icon = icons.default;
    20         }
     26    if ( icons.svg ) {
     27        icon = icons.svg;
     28    } else if ( icons[ '1x' ] ) {
     29        icon = icons[ '1x' ];
     30    } else {
     31        icon = icons.default;
     32    }
    2133
    22         return (
    23             <div className="entry-thumbnail">
    24                 <div className="plugin-icon" id={ `plugin-icon-${ slug }` }></div>
    25                 <style type='text/css'>
    26                     { `#plugin-icon-${ slug } { background-image: url('${ icon }'); } .plugin-icon { background-size: contain; height: 128px; width: 128px; }` }
    27                     { icons[ '2x' ] && icon !== icons.default ?
    28                         `@media only screen and (-webkit-min-device-pixel-ratio: 1.5) { #plugin-icon-${ slug } { background-image: url('${ icons[ '2x' ] }'); } }` : ''
    29                     } }
    30                 </style>
    31             </div>
    32         )
    33     }
    34 } );
     34    return (
     35        <div className="entry-thumbnail">
     36            <div className="plugin-icon" id={ `plugin-icon-${ slug }` } />
     37            <style type="text/css">
     38                { `#plugin-icon-${ slug } { background-image: url('${ icon }'); }` }
     39                { icons[ '2x' ] && icon !== icons.default
     40                    // eslint-disable-next-line max-len
     41                    ? `@media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 144dpi) { #plugin-icon-${ slug } { background-image: url('${ icons[ '2x' ] }'); } }`
     42                    : ''
     43                }
     44            </style>
     45        </div>
     46    );
     47};
     48
     49PluginIcon.propTypes = {
     50    plugin: PropTypes.object,
     51    slug: PropTypes.string,
     52};
     53
     54PluginIcon.defaultProps = {
     55    plugin: {},
     56    slug: '',
     57};
     58
     59export default connect(
     60    ( state, { slug } ) => ( {
     61        plugin: getPlugin( state, slug ),
     62    } ),
     63)( PluginIcon );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/plugin-icon/style.scss

    r4223 r5024  
    22    display: none;
    33    max-width: 128px;
     4
     5    .plugin-icon {
     6        background-size: cover;
     7        height: 128px;
     8        width: 128px;
     9    }
    410
    511    @media screen and ( min-width: 21em ) {
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/plugin-ratings/index.jsx

    r4223 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { PropTypes } from 'react';
     5import { localize } from 'i18n-calypso';
     6import { identity } from 'lodash';
    27
    38/**
     
    611import Stars from './stars';
    712
    8 export default React.createClass( {
    9     displayName: 'PluginRatings',
     13export const PluginRatings = ( { numberFormat, rating, ratingCount, translate } ) => (
     14    <div className="plugin-rating" itemProp="aggregateRating" itemScope="" itemType="http://schema.org/AggregateRating">
     15        <meta itemProp="ratingCount" content={ ratingCount } />
     16        <meta itemProp="ratingValue" content={ rating } />
    1017
    11     render() {
    12         return (
    13             <div className="plugin-rating" itemProp="aggregateRating" itemScope="" itemType="http://schema.org/AggregateRating">
    14                 <meta itemProp="ratingCount" content={ this.props.ratingCount } />
    15                 <meta itemProp="ratingValue" content={ this.props.rating } />
     18        <Stars rating={ rating } />
     19        <span className="rating-count">
     20            { translate( '(%(count)s{{span}} total ratings{{/span}})', {
     21                args: { count: numberFormat( ratingCount ) },
     22                components: { span: <span className="screen-reader-text" /> },
     23            } ) }
     24        </span>
     25    </div>
     26);
    1627
    17                 <Stars rating={ this.props.rating } />
    18                 <span className="rating-count">({ this.props.ratingCount }<span className="screen-reader-text"> total ratings</span>)</span>
    19             </div>
    20         )
    21     }
    22 } );
     28PluginRatings.propTypes = {
     29    numberFormat: PropTypes.func,
     30    rating: PropTypes.number,
     31    ratingCount: PropTypes.number,
     32    translate: PropTypes.func,
     33};
     34
     35PluginRatings.defaultProps = {
     36    numberFormat: identity,
     37    rating: 0,
     38    ratingCount: 0,
     39    translate: identity,
     40};
     41
     42export default localize( PluginRatings );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/plugin-ratings/stars/index.jsx

    r4223 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { Component, PropTypes } from 'react';
     5import { identity } from 'lodash';
     6import { localize } from 'i18n-calypso';
    27
    3 export default React.createClass( {
    4     displayName: 'Stars',
     8export class Stars extends Component {
     9    static propTypes = {
     10        rating: PropTypes.number,
     11        translate: PropTypes.func,
     12    };
    513
    6     fillStars( rating ) {
    7         const template = '<span class="%1$s"></span>';
     14    static defaultProps = {
     15        rating: 0,
     16        translate: identity,
     17    };
     18
     19    /**
     20     * Returns filled stars representative of rating.
     21     *
     22     * @param {Number} rating Plugin rating.
     23     * @return {String} Rating stars.
     24     */
     25    fillStars = ( rating ) => {
    826        let counter = rating * 2,
    927            output = '',
     
    1331            switch ( counter ) {
    1432                case 0:
    15                     output += template.replace( '%1$s', 'dashicons dashicons-star-empty' );
     33                    output += '<span class="dashicons dashicons-star-empty"></span>';
    1634                    break;
    1735
    1836                case 1:
    19                     output += template.replace( '%1$s', 'dashicons dashicons-star-half' );
     37                    output += '<span class="dashicons dashicons-star-half"></span>';
    2038                    counter--;
    2139                    break;
    2240
    2341                default:
    24                     output += template.replace( '%1$s', 'dashicons dashicons-star-filled' );
     42                    output += '<span class="dashicons dashicons-star-filled"></span>';
    2543                    counter -= 2;
    2644            }
     
    2846
    2947        return output;
    30     },
     48    };
    3149
    3250    render() {
    33         const titleTemplate = '%s out of 5 stars',
    34             title = titleTemplate.replace( '%s', this.props.rating / 20 );
     51        const { rating, translate } = this.props;
     52        const stars =  Math.round( rating / 0.5 ) * 0.5;
    3553
    3654        return (
    3755            <div
    3856                className="wporg-ratings"
    39                 aria-label={ title }
    40                 data-title-template={ titleTemplate }
    41                 data-rating={ this.props.rating / 20 }
    42                 dangerouslySetInnerHTML={ { __html: this.fillStars( Math.round( this.props.rating / 10 ) / 2 ) } }
    43             ></div>
    44         )
     57                aria-label={ translate( '%(stars)s out of 5 stars', { args: { stars } } ) }
     58                dangerouslySetInnerHTML={ { __html: this.fillStars( stars ) } }
     59            />
     60        );
    4561    }
    46 } );
     62}
     63
     64export default localize( Stars );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/plugin/download-button/index.jsx

    r4223 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { PropTypes } from 'react';
     5import { connect } from 'react-redux';
     6import { identity } from 'lodash';
     7import { localize } from 'i18n-calypso';
    28
    3 export default React.createClass( {
    4     displayName: 'DownloadButton',
     9/**
     10 * Internal dependencies.
     11 */
     12import { getPlugin } from 'state/selectors';
    513
    6     render() {
    7         return (
    8             <span>
    9                 <a className="plugin-download button download-button button-large" href={ this.props.plugin.download_link } itemProp="downloadUrl">
    10                     Download
    11                 </a>
    12                 <meta itemProp="softwareVersion" content={ this.props.plugin.version } />
    13                 <meta itemProp="fileFormat" content="application/zip" />
    14             </span>
    15         )
    16     }
    17 } );
     14export const DownloadButton = ( { plugin, translate } ) => (
     15    <span>
     16        <a
     17            className="plugin-download button download-button button-large"
     18            href={ plugin.download_link }
     19            itemProp="downloadUrl"
     20        >
     21            { translate( 'Download' ) }
     22        </a>
     23        <meta itemProp="softwareVersion" content={ plugin.version } />
     24        <meta itemProp="fileFormat" content="application/zip" />
     25    </span>
     26);
     27
     28DownloadButton.propTypes = {
     29    plugin: PropTypes.object,
     30    translate: PropTypes.func,
     31};
     32
     33DownloadButton.defaultProps = {
     34    plugin: {},
     35    translate: identity,
     36};
     37
     38export default connect(
     39    ( state ) => ( {
     40        plugin: getPlugin( state ),
     41    } ),
     42)( localize( DownloadButton ) );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/plugin/favorite-button/index.jsx

    r4467 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { Component, PropTypes } from 'react';
     5import { connect } from 'react-redux';
     6import { identity } from 'lodash';
     7import { localize } from 'i18n-calypso';
    28
    3 import FavoriteButton from './button';
    4 import {
    5     getFavorites,
    6     favoritePlugin,
    7     unfavoritePlugin
    8 } from 'actions/index';
     9/**
     10 * Internal dependencies.
     11 */
     12import { favoritePlugin } from 'state/favorites/actions';
     13import { isFavorite } from 'state/selectors';
    914
    10 export default React.createClass( {
    11     componentDidMount() {
    12         this.getFavorites();
    13     },
     15export class FavoriteButton extends Component {
     16    static propTypes = {
     17        favorite: PropTypes.bool,
     18        favoritePlugin: PropTypes.func,
     19        plugin: PropTypes.object,
     20        translate: PropTypes.func,
     21    };
    1422
    15     componentDidUpdate( previousProps ) {
    16         if ( this.props.plugin.slug !== previousProps.plugin.slug ) {
    17             this.getFavorites();
    18         }
    19     },
     23    static defaultProps = {
     24        favorite: false,
     25        favoritePlugin: () => {},
     26        plugin: {},
     27        translate: identity,
     28    };
    2029
    21     getFavorites() {
    22         this.props.dispatch( getFavorites( this.props.plugin.slug ) );
    23     },
     30    toggleFavorite = ( event ) => {
     31        const $button = jQuery( event.target );
    2432
    25     toggleFavorite( event ) {
    26         if ( event.target.classList.contains( 'favorited' ) ) {
    27             this.props.dispatch( unfavoritePlugin( this.props.plugin.slug ) );
    28         } else {
    29             this.props.dispatch( favoritePlugin( this.props.plugin.slug ) );
    30         }
    31     },
     33        this.props.favoritePlugin( this.props.plugin );
     34
     35        $button.addClass( 'is-animating' ).one( 'animationend', () => {
     36            $button.toggleClass( 'is-animating favorited' );
     37        } );
     38    };
    3239
    3340    render() {
    34         return <FavoriteButton toggleFavorite={ this.toggleFavorite } />;
     41        if ( 0 === pluginDirectory.userId ) {
     42            return null;
     43        }
     44
     45        const { favorite, plugin, translate } = this.props;
     46        const classNames = [ 'plugin-favorite-heart' ];
     47
     48        if ( favorite ) {
     49            classNames.push( 'favorited' );
     50        }
     51
     52        return (
     53            <div className="plugin-favorite">
     54                <button type="button" className={ classNames.join( ' ' ) } onClick={ this.toggleFavorite } >
     55                    <span className="screen-reader-text">
     56                        { favorite
     57                            ? translate( 'Unfavorite %(name)s', { components: { name: plugin.name } } )
     58                            : translate( 'Favorite  %(name)s', { components: { name: plugin.name } } )
     59                        }
     60                    </span>
     61                </button>
     62            </div>
     63        );
    3564    }
    36 } );
     65}
     66
     67export default connect(
     68    ( state ) => ( {
     69        favorite: isFavorite( state ),
     70    } ),
     71    {
     72        favoritePlugin,
     73    },
     74)( localize( FavoriteButton ) );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/plugin/index.jsx

    r4223 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { Component, PropTypes } from 'react';
    25import { connect } from 'react-redux';
    3 import find from 'lodash/find';
    46
     7/**
     8 * Internal dependencies.
     9 */
    510import Plugin from './plugin';
    6 import { getPlugin } from 'actions';
     11import { fetchPlugin } from 'state/plugins/actions';
    712
    8 const PluginContainer = React.createClass( {
     13class PluginContainer extends Component {
     14    static PropTypes = {
     15        fetchPlugin: PropTypes.func,
     16        params: PropTypes.object,
     17    };
     18
     19    static defaultProps = {
     20        fetchPlugin: () => {},
     21        params: {},
     22    };
     23
    924    componentDidMount() {
    10         this.getPlugin();
    11     },
     25        this.fetchPlugin();
     26    }
    1227
    13     componentDidUpdate( previousProps ) {
    14         if ( this.props.route.path !== previousProps.route.path ) {
    15             this.getPlugin();
     28    componentDidUpdate( { params } ) {
     29        if ( this.props.params.slug !== params.slug ) {
     30            this.fetchPlugin();
    1631        }
    17     },
     32    }
    1833
    19     getPlugin() {
    20         this.props.dispatch( getPlugin( this.props.params.slug ) );
    21     },
     34    fetchPlugin() {
     35        this.props.fetchPlugin( this.props.params.slug );
     36    }
    2237
    2338    render() {
    24         return <Plugin { ...this.props } />;
     39        return <Plugin />;
    2540    }
    26 } );
     41}
    2742
    28 const mapStateToProps = ( state, ownProps ) => ( {
    29     plugin: find( state.plugins, { slug: ownProps.params.slug } )
    30 } );
    31 
    32 export default connect( mapStateToProps )( PluginContainer );
     43export default connect(
     44    null,
     45    {
     46        fetchPlugin,
     47    },
     48)( PluginContainer );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/plugin/plugin-banner/index.jsx

    r4223 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { PropTypes } from 'react';
     5import { connect } from 'react-redux';
    26
    3 export default React.createClass( {
    4     displayName: 'PluginBanner',
     7/**
     8 * Internal dependencies.
     9 */
     10import { getPlugin } from 'state/selectors';
    511
    6     render() {
    7         const { banners, slug } = this.props.plugin;
    8         let banner;
     12/**
     13 *
     14 * @param {Object} plugin Plugin object.
     15 * @return {*} React element.
     16 * @constructor
     17 */
     18export const PluginBanner = ( { plugin } ) => {
     19    const { banners, slug } = plugin;
    920
    10         if ( ! banners ) {
    11             return <div />;
    12         }
     21    if ( ! banners ) {
     22        return null;
     23    }
    1324
    14         banner = banners[ 'low' ] ? banners[ 'low' ] : banners[ 'high' ];
     25    const banner = banners.low || banners.high;
    1526
    16         if ( ! banner ) {
    17             return <div />;
    18         }
     27    if ( ! banner ) {
     28        return null;
     29    }
    1930
    20         return (
    21             <div className="entry-banner">
    22                 <div className="plugin-banner" id={ `plugin-banner-${ slug }` }></div>
    23                 <style type='text/css'>
    24                     { `#plugin-banner-${ slug } { background-image: url('${ banner }'); }` }
    25                     { banners[ 'high' ] ?
    26                         `@media only screen and (-webkit-min-device-pixel-ratio: 1.5) { #plugin-banner-${ slug } { background-image: url('${ banners[ 'high' ] }'); } }` : ''
    27                     } }
    28                 </style>
    29             </div>
    30         )
    31     }
    32 } );
     31    return (
     32        <div className="entry-banner">
     33            <div className="plugin-banner" id={ `plugin-banner-${ slug }` } />
     34            <style type="text/css">
     35                { `#plugin-banner-${ slug } { background-image: url('${ banner }'); }` }
     36                { banners.high && '@media ' +
     37                    'only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 144dpi) ' +
     38                    `{ #plugin-banner-${ slug } { background-image: url('${ banners.high }'); } }`
     39                } }
     40            </style>
     41        </div>
     42    );
     43};
     44
     45PluginBanner.propTypes = {
     46    plugin: PropTypes.object,
     47};
     48
     49PluginBanner.defaultProps = {
     50    plugin: {},
     51};
     52
     53export default connect(
     54    ( state ) => ( {
     55        plugin: getPlugin( state ),
     56    } ),
     57)( PluginBanner );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/plugin/plugin.jsx

    r4223 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { PropTypes } from 'react';
     5import { connect } from 'react-redux';
     6import { identity } from 'lodash';
     7import { localize } from 'i18n-calypso';
     8import { values } from 'lodash';
    29
     10/**
     11 * Internal dependencies.
     12 */
    313import Developers from './sections/developers';
    414import DonateWidget from 'components/widget-area/widgets/donate';
     
    616import FAQ from './sections/faq';
    717import FavoriteButton from './favorite-button';
     18import { getPlugin } from 'state/selectors';
    819import MetaWidget from 'components/widget-area/widgets/meta/index';
    920import PluginBanner from './plugin-banner';
     
    1526import SupportWidget from 'components/widget-area/widgets/support/index';
    1627
    17 export default React.createClass( {
    18     displayName: 'Plugin',
    19 
    20     render() {
    21         if ( ! this.props.plugin ) {
    22             return (
    23                 <article className="plugin type-plugin">
    24                     <header className="entry-header">
    25                         <h1 className="entry-title"> </h1>
    26                     </header>
    27                     <div className="entry-content">
    28                         <section>
    29                             <div className="container"> LOADING </div>
    30                         </section>
    31                     </div>
    32                 </article>
    33             )
    34         }
    35 
     28export const Plugin = ( { plugin, translate } ) => {
     29    if ( ! plugin ) {
    3630        return (
    3731            <article className="plugin type-plugin">
    38                 <PluginBanner plugin={ this.props.plugin } />
    39                 <header className="plugin-header">
    40                     <PluginIcon plugin={ this.props.plugin } />
    41                     <div className="plugin-actions">
    42                         <FavoriteButton plugin={ this.props.plugin } />
    43                         <DownloadButton plugin={ this.props.plugin } />
    44                     </div>
    45                     <h1 className="plugin-title">{ this.props.plugin.name }</h1>
    46                     <span className="byline">By <span className="author vcard" dangerouslySetInnerHTML={ { __html: this.props.plugin.author } } /></span>
     32                <header className="entry-header">
     33                    <h1 className="entry-title"> </h1>
    4734                </header>
    4835                <div className="entry-content">
    49                     <Section slug="description" title="Description" content={ this.props.plugin.sections.description } />
    50                     <Screenshots screenshots={ this.props.plugin.screenshots } />
    51                     <FAQ content={ this.props.plugin.sections.faq } />
    52                     <Reviews slug={ this.props.plugin.slug } content={ this.props.plugin.sections.reviews } numRatings={ this.props.plugin.num_ratings } />
    53                     <Section slug="changelog" title="Changelog" content={ this.props.plugin.sections.changelog } />
    54                     <Developers slug={ this.props.plugin.slug } contributors={ this.props.plugin.contributors } />
    55                 </div>
    56                 <div className="entry-meta">
    57                     <MetaWidget plugin={ this.props.plugin } />
    58                     <RatingsWidget plugin={ this.props.plugin } />
    59                     <SupportWidget plugin={ this.props.plugin } />
    60                     <DonateWidget plugin={ this.props.plugin } />
     36                    <section>
     37                        <div className="container"> LOADING </div>
     38                    </section>
    6139                </div>
    6240            </article>
    63         )
     41        );
    6442    }
    65 } );
     43
     44    return (
     45        <article className="plugin type-plugin">
     46            <PluginBanner />
     47            <header className="plugin-header">
     48                <PluginIcon />
     49                <div className="plugin-actions">
     50                    <FavoriteButton plugin={ plugin } />
     51                    <DownloadButton />
     52                </div>
     53                <h1 className="plugin-title" dangerouslySetInnerHTML={ { __html: plugin.title.rendered } } />
     54                <span className="byline">
     55                    { translate( 'By {{span/}}', { components: {
     56                        span: <span className="author vcard" dangerouslySetInnerHTML={ { __html: plugin.author } } />,
     57                    } } ) }
     58                </span>
     59            </header>
     60            <div className="entry-content">
     61                <Section slug="description" title="Description" content={ plugin.sections.description } />
     62                <Screenshots screenshots={ values( plugin.screenshots ) } />
     63                <FAQ content={ plugin.sections.faq } />
     64                <Reviews
     65                    slug={ plugin.slug }
     66                    content={ plugin.sections.reviews }
     67                    numRatings={ plugin.ratings.length } />
     68                <Section slug="changelog" title="Changelog" content={ plugin.sections.changelog } />
     69                <Developers />
     70            </div>
     71            <div className="entry-meta">
     72                <MetaWidget />
     73                <RatingsWidget />
     74                <SupportWidget />
     75                <DonateWidget />
     76            </div>
     77        </article>
     78    );
     79};
     80
     81Plugin.propTypes = {
     82    plugin: PropTypes.object,
     83    translate: PropTypes.func,
     84};
     85
     86Plugin.defaultProps = {
     87    plugin: {},
     88    translate: identity,
     89};
     90
     91export default connect(
     92    ( state ) => ( {
     93        plugin: getPlugin( state ),
     94    } ),
     95)( localize( Plugin ) );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/plugin/sections/changelog/style.scss

    r4322 r5024  
    1 #changelog {
    2     font-size: ms(-2);
     1.plugin-changelog {
     2    font-size: ms( -2 );
    33
    44    code {
    5         font-size: ms(-2);
     5        font-size: ms( -2 );
    66    }
    77
     
    1010    }
    1111}
    12 
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/plugin/sections/developers/index.jsx

    r4464 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { PropTypes } from 'react';
     5import { connect } from 'react-redux';
     6import { identity } from 'lodash';
     7import { localize } from 'i18n-calypso';
    28
     9/**
     10 * Internal dependencies.
     11 */
    312import DeveloperList from './list';
     13import { getPlugin } from 'state/selectors';
    414
    5 export default React.createClass( {
    6     displayName: 'Developers',
     15export const Developers = ( { plugin, translate } ) => (
     16    <div>
     17        <div id="developers" className="section read-more plugin-developers">
     18            <h2>{ translate( 'Contributors & Developers' ) }</h2>
     19            <p>
     20                { translate( 'This is open source software. The following people have contributed to this plugin.' ) }
     21            </p>
     22            <DeveloperList contributors={ plugin.contributors } />
    723
    8     render() {
    9         return (
    10             <div>
    11                 <div id="developers" className="read-more">
    12                     <h2>Contributors & Developers</h2>
    13                     <p>This is open source software. The following people have contributed to this plugin.</p>
    14                     <DeveloperList contributors={ this.props.contributors } />
     24            <h5>{ translate( 'Interested in development?' ) }</h5>
     25            <p>
     26                {
     27                    /* eslint-disable max-len */
     28                    translate( '{{code}}Browse the code{{/code}} or subscribe to the {{log}}development log{{/log}} by {{rss}}RSS{{/rss}}.', {
     29                        components: {
     30                            code: <a href={ `https://plugins.svn.wordpress.org/${ plugin.slug }/` } rel="nofollow" />,
     31                            log: <a href={ `https://plugins.trac.wordpress.org/log/${ plugin.slug }/` } rel="nofollow" />,
     32                            rss: <a href={ `https://plugins.trac.wordpress.org/log/${ plugin.slug }/?limit=100&mode=stop_on_copy&format=rss` } rel="nofollow" />,
     33                        },
     34                    } )
     35                    /* eslint-enable max-len */
     36                }
     37            </p>
     38        </div>
     39        <button type="button" className="button-link section-toggle" aria-controls="developers" aria-expanded="false">
     40            { translate( 'Read more' ) }
     41        </button>
     42    </div>
     43);
    1544
    16                     <h5>Interested in development?</h5>
    17                     <p><a href={ `https://plugins.svn.wordpress.org/${ this.props.slug }/` } rel="nofollow">Browse the code</a> or subscribe to the <a href={ `https://plugins.trac.wordpress.org/log/${ this.props.slug }/` } rel="nofollow">development log</a> by <a href={ `https://plugins.trac.wordpress.org/log/${ this.props.slug }/?limit=100&mode=stop_on_copy&format=rss` } rel="nofollow">RSS</a>.</p>
    18                 </div>
    19                 <button type="button" className="button-link section-toggle" aria-controls="developers" aria-expanded="false">Read more</button>
    20             </div>
    21         )
    22     }
    23 } );
     45Developers.propTypes = {
     46    plugin: PropTypes.object,
     47    translate: PropTypes.func,
     48};
     49
     50Developers.defaultProps = {
     51    plugin: {},
     52    translate: identity,
     53};
     54
     55export default connect(
     56    ( state ) => ( {
     57        plugin: getPlugin( state ),
     58    } ),
     59)( localize( Developers ) );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/plugin/sections/developers/list.jsx

    r4223 r5024  
    1 import React from 'react';
    2 import values from 'lodash/values';
     1/**
     2 * External dependencies.
     3 */
     4import React, { PropTypes } from 'react';
     5import { map } from 'lodash';
    36
    4 export default React.createClass( {
    5     displayName: 'DeveloperList',
    6 
    7     render() {
    8         if ( ! this.props.contributors ) {
    9             return <div />;
    10         }
    11 
     7/**
     8 *
     9 * @param {Object} contributors Plugin contributors.
     10 * @return {*} React Element
     11 * @constructor
     12 */
     13export const DeveloperList = ( { contributors } ) => {
     14    if ( contributors ) {
    1215        return (
    1316            <ul className="plugin-developers">
    14                 { values( this.props.contributors ).map( ( contributor, index ) =>
     17                { map( contributors, ( contributor, index ) =>
    1518                    <li key={ index }>
    16                         <img className="avatar avatar-32 photo" height="32" width="32" src={ contributor.avatar } />
    17                         <a href={ contributor.profile }>{ contributor.display_name }</a>
     19                        <a href={ contributor.profile }>
     20                            <img className="avatar avatar-32 photo" height="32" width="32" src={ contributor.avatar } />
     21                            { contributor.display_name }
     22                        </a>
    1823                    </li>
    1924                ) }
    2025            </ul>
    21         )
     26        );
    2227    }
    23 } );
     28
     29    return null;
     30};
     31
     32DeveloperList.propTypes = {
     33    contributors: PropTypes.object,
     34};
     35
     36DeveloperList.defaultProps = {
     37    contributors: {},
     38};
     39
     40export default DeveloperList;
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/plugin/sections/faq/index.jsx

    r4410 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { Component, PropTypes } from 'react';
     5import { identity } from 'lodash';
     6import { localize } from 'i18n-calypso';
    27
    3 export default React.createClass( {
    4     displayName: 'FAQ',
     8export class FAQ extends Component {
     9    static propTypes = {
     10        content: PropTypes.string,
     11        translate: PropTypes.func,
     12    };
    513
    6     toggleAnswer( event ) {
    7         var $question = jQuery( event.target );
     14    static defaultProps = {
     15        content: null,
     16        translate: identity,
     17    };
     18
     19    toggleAnswer = ( event ) => {
     20        const $question = jQuery( event.target );
    821
    922        if ( ! $question.is( '.open' ) ) {
    10             $question.siblings( '.open' ).toggleClass( 'open' ).attr( 'aria-expanded', false ).next( 'dd' ).slideToggle( 200 );
     23            $question.siblings( '.open' ).toggleClass( 'open' ).attr( 'aria-expanded', false )
     24                .next( 'dd' ).slideToggle( 200 );
    1125        }
    1226
    13         $question.toggleClass( 'open' ).attr( 'aria-expanded', function( index, attribute ) {
    14             return 'true' !== attribute;
    15         } ).next( 'dd' ).slideToggle( 200 );
    16     },
     27        $question.toggleClass( 'open' ).attr( 'aria-expanded', ( index, attribute ) => ( 'true' !== attribute ) )
     28            .next( 'dd' ).slideToggle( 200 );
     29    };
    1730
    1831    render() {
    19         if ( ! this.props.content ) {
    20             return <div />;
     32        const { content, translate } = this.props;
     33
     34        if ( content ) {
     35            return (
     36                <div id="faq" className="section plugin-faq">
     37                    <h2>{ translate( 'FAQ' ) }</h2>
     38                    <div onClick={ this.toggleAnswer } dangerouslySetInnerHTML={ { __html: content } } />
     39                </div>
     40            );
    2141        }
    2242
    23         return (
    24             <div id="faq">
    25                 <h2>FAQ</h2>
    26                 <div onClick={ this.toggleAnswer } dangerouslySetInnerHTML={ { __html: this.props.content } } />
    27             </div>
    28         )
     43        return null;
    2944    }
    30 } );
     45}
     46
     47export default localize( FAQ );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/plugin/sections/faq/style.scss

    r4410 r5024  
    1 #faq {
     1.plugin-faq {
    22    h2:first-of-type {
    33        font-size: ms( 2 );
     
    4040        margin: 0 0 1rem;
    4141
     42        .no-js & {
     43            display: block;
     44        }
     45
    4246        & p {
    4347            margin: 0;
     
    4953    }
    5054}
    51 
    52 .no-js #faq dd {
    53     display: block;
    54 }
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/plugin/sections/index.jsx

    r4464 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { PropTypes } from 'react';
     5import { identity } from 'lodash';
     6import { localize } from 'i18n-calypso';
    27
    3 export default React.createClass( {
    4     displayName: 'Section',
     8export const Section = ( { content, slug, title, translate } ) => (
     9    <div>
     10        <div id={ slug } className={ `section read-more plugin-${ slug }` }>
     11            <h2>{ title }</h2>
     12            <div dangerouslySetInnerHTML={ { __html: content } } />
     13        </div>
     14        <button
     15            type="button"
     16            className="button-link section-toggle"
     17            aria-controls={ slug }
     18            aria-expanded="false"
     19            data-show-less={ translate( 'Show less' ) }
     20            data-read-more={ translate( 'Read more' ) }
     21        >
     22            { translate( 'Read more' ) }
     23        </button>
     24    </div>
     25);
    526
    6     render() {
    7         return (
    8             <div>
    9                 <div id={ this.props.slug } className="section read-more">
    10                     <h2>{ this.props.title }</h2>
    11                     <div dangerouslySetInnerHTML={ { __html: this.props.content } } />
    12                 </div>
    13                 <button type="button" className="button-link section-toggle" aria-controls={ this.props.slug } aria-expanded="false">Read more</button>
    14             </div>
    15         )
    16     }
    17 } );
     27Section.propTypes = {
     28    content: PropTypes.string.isRequired,
     29    slug: PropTypes.string.isRequired,
     30    title: PropTypes.string.isRequired,
     31    translate: PropTypes.func,
     32};
     33
     34Section.defaultProps = {
     35    translate: identity,
     36};
     37
     38export default localize( Section );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/plugin/sections/reviews/index.jsx

    r4464 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { PropTypes } from 'react';
     5import { identity } from 'lodash';
     6import { localize } from 'i18n-calypso';
    27
    3 export default React.createClass( {
    4     displayName: 'Reviews',
     8export const Reviews = ( { content, numberFormat, numRatings, slug, translate } ) => {
     9    if ( ! numRatings ) {
     10        return null;
     11    }
    512
    6     render() {
    7         return (
    8             <div>
    9                 <div id="reviews" className="read-more">
    10                     <div className="plugin-reviews">
    11                         <h2>Reviews</h2>
    12                         <div dangerouslySetInnerHTML={ { __html: this.props.content } } />
    13                     </div>
     13    return (
     14        <div>
     15            <div id="reviews" className="section">
     16                <div className="plugin-reviews">
     17                    <h2>{ translate( 'Reviews' ) }</h2>
     18                    <div dangerouslySetInnerHTML={ { __html: content } }/>
    1419                </div>
    15                 <a className="reviews-link" href={ `https://wordpress.org/support/plugin/${ this.props.slug }/reviews/` } aria-expanded="false">Read all { this.props.numRatings } reviews</a>
    1620            </div>
    17         )
    18     }
    19 } );
     21            <a
     22                className="reviews-link"
     23                href={ `https://wordpress.org/support/plugin/${ slug }/reviews/` }
     24                aria-expanded="false"
     25            >
     26                { translate( 'Read all %(numRatings)s reviews', {
     27                    args: { numRatings: numberFormat( numRatings ) },
     28                } ) }
     29            </a>
     30        </div>
     31    );
     32};
     33
     34Reviews.propTypes = {
     35    content: PropTypes.string,
     36    numberFormat: PropTypes.func,
     37    numRatings: PropTypes.number.isRequired,
     38    slug: PropTypes.string.isRequired,
     39    translate: PropTypes.func,
     40};
     41
     42Reviews.defaultProps = {
     43    content: null,
     44    numberFormat: identity,
     45    translate: identity,
     46};
     47
     48export default localize( Reviews );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/plugin/sections/screenshots/image-gallery/index.jsx

    r4506 r5024  
    551551                                </span>,
    552552
    553                                 <div className="image-gallery-slides">{ slides }</div>
     553                                <div key={ this.state.currentIndex } className="image-gallery-slides">{ slides }</div>
    554554                            ]
    555555                            :
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/plugin/sections/screenshots/index.jsx

    r4469 r5024  
    1 import React from 'react';
    2 import values from 'lodash/values';
     1/**
     2 * External dependencies.
     3 */
     4import React, { PropTypes } from 'react';
     5//import { localize } from 'i18n-calypso';
     6import { identity, map } from 'lodash';
    37
     8/**
     9 * Internal dependencies.
     10 */
    411import ImageGallery from './image-gallery';
    512
    6 export default React.createClass( {
    7     displayName: 'Screenshots',
     13export const Screenshots = ( { screenshots, translate } ) => {
     14    const items = map( screenshots, ( { caption, src } ) => ( {
     15        original: src,
     16        originalAlt: '',
     17        thumbnail: src + '&width=100',
     18        thumbnailAlt: caption || '',
     19        description: caption || false,
     20    } ) );
    821
    9     render() {
    10         const items = values( this.props.screenshots ).map( ( { caption, src } ) => ( {
    11             original: src,
    12             originalAlt: '',
    13             thumbnail: src + '&width=100',
    14             thumbnailAlt: caption || '',
    15             description: caption || false,
    16         } ) );
    17 
    18         if ( ! items ) {
    19             return;
    20         }
    21 
     22    if ( items ) {
    2223        return (
    2324            <div id="screenshots" className="plugin-screenshots">
    24                 <h2>Screenshots</h2>
     25                <h2>{ translate( 'Screenshots' ) }</h2>
    2526                <ImageGallery items={ items } />
    2627            </div>
    27         )
     28        );
    2829    }
    29 } );
     30
     31    return null;
     32};
     33
     34Screenshots.propTypes = {
     35    screenshots: PropTypes.arrayOf( PropTypes.object ),
     36    translate: PropTypes.func,
     37};
     38
     39Screenshots.defaultProps = {
     40    screenshots: [],
     41    translate: identity,
     42};
     43
     44//export default localize( Screenshots );
     45export default Screenshots;
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/plugin/sections/style.scss

    r4425 r5024  
    77        padding-bottom: 1px;
    88
    9         &#description {
     9        &.plugin-description {
    1010            max-height: 400px;
    1111
     
    1919        }
    2020
     21        .no-js & {
     22            max-height: none;
     23            overflow: auto;
     24        }
    2125    }
    2226
     
    5963}
    6064
    61 .no-js .read-more {
    62     overflow: auto;
    63     max-height: none;
    64 }
    65 
    6665.section-toggle {
    6766    color: $color__link;
     
    7069    margin-top: 0.5rem;
    7170    position: relative;
     71
     72    .no-js & {
     73        display: none;
     74    }
     75
    7276
    7377    &:after {
     
    8993    }
    9094}
    91 
    92 .no-js .section-toggle {
    93     display: none;
    94 }
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/search-form/index.jsx

    r4223 r5024  
    1 import React from 'react';
     1/**
     2 * Internal dependencies.
     3 */
     4import React, { Component, PropTypes } from 'react';
     5import { connect } from 'react-redux';
    26import { withRouter } from 'react-router';
    37
     
    59 * Internal dependencies.
    610 */
     11import { getSearchTerm } from 'state/selectors';
    712import SearchForm from './search-form';
    813
    9 const SearchFormContainer = React.createClass( {
    10     getInitialState() {
    11         return {
    12             searchTerm: this.props.searchTerm
    13         };
    14     },
     14export class SearchFormContainer extends Component {
     15    static propTypes = {
     16        router: PropTypes.object,
     17        search: PropTypes.string,
     18    };
    1519
    16     onChange( searchTerm ) {
    17         this.setState( {
    18             searchTerm: searchTerm
    19         } );
    20     },
     20    static defaultProps = {
     21        router: {},
     22        search: '',
     23    };
    2124
    22     componentWillReceiveProps( nextProps ) {
    23         this.setState( {
    24             searchTerm: nextProps.searchTerm
    25         } );
    26     },
     25    onChange = ( search ) => {
     26        this.setState( { search } );
     27    };
    2728
    28     onSubmit( event ) {
    29         var searchTerm = encodeURIComponent( this.state.searchTerm );
     29    onSubmit = ( event ) => {
     30        const search = encodeURIComponent( this.state.search );
    3031        event.preventDefault();
    3132
    32         if ( searchTerm ) {
    33             this.props.router.push( `/search/${ searchTerm }/` );
     33        if ( search ) {
     34            this.props.router.push( `/search/${ search }/` );
    3435        } else {
    3536            this.props.router.push( '/' );
    3637        }
    37     },
     38    };
     39
     40    constructor() {
     41        super( ...arguments );
     42
     43        this.state = {
     44            search: this.props.search,
     45        };
     46    }
     47
     48    componentWillReceiveProps( { search } ) {
     49        this.setState( { search } );
     50    }
    3851
    3952    render() {
    40         return <SearchForm searchTerm={ this.state.searchTerm } onSubmit={ this.onSubmit } onChange={ this.onChange } />;
     53        return <SearchForm onSubmit={ this.onSubmit } onChange={ this.onChange } />;
    4154    }
    42 } );
     55}
    4356
    44 export default withRouter( SearchFormContainer );
     57export default withRouter( connect(
     58    ( state ) => ( {
     59        search: getSearchTerm( state ),
     60    } ),
     61)( SearchFormContainer ) );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/search-form/search-form.jsx

    r4223 r5024  
    1 import React from 'react';
     1/**
     2 * Internal dependencies.
     3 */
     4import React, { Component, PropTypes } from 'react';
     5import { identity } from 'lodash';
     6import { localize } from 'i18n-calypso';
    27
    3 export default React.createClass( {
    4     displayName: 'SearchForm',
     8export class SearchForm extends Component {
     9    static propTypes = {
     10        onChange: PropTypes.func,
     11        onSubmit: PropTypes.func,
     12        search: PropTypes.string,
     13        translate: PropTypes.func,
     14    };
    515
    6     onChange() {
    7         this.props.onChange( this.refs.search.value );
    8     },
     16    static defaultProps = {
     17        onChange: () => {},
     18        onSubmit: () => {},
     19        search: '',
     20        translate: identity,
     21    };
     22
     23    onChange = () => this.props.onChange( this.refs.search.value );
    924
    1025    render() {
     26        const { onSubmit, search, translate } = this.props;
     27
    1128        return (
    12             <form onSubmit={ this.props.onSubmit } role="search" method="get" className="search-form">
    13                 <label htmlFor="s" className="screen-reader-text">Search for:</label>
     29            <form onSubmit={ onSubmit } role="search" method="get" className="search-form">
     30                <label htmlFor="s" className="screen-reader-text">{ translate( 'Search for:' ) }</label>
    1431                <input
    1532                    className="search-field"
     
    2037                    ref="search"
    2138                    type="search"
    22                     value={ this.props.searchTerm }
     39                    defaultValue={ search }
    2340                />
    2441                <button className="button button-primary button-search">
    25                     <i className="dashicons dashicons-search"></i>
    26                     <span className="screen-reader-text">Search plugins</span>
     42                    <i className="dashicons dashicons-search" />
     43                    <span className="screen-reader-text">{ translate( 'Search plugins' ) }</span>
    2744                </button>
    2845            </form>
    29         )
     46        );
    3047    }
    31 } );
     48}
     49
     50export default localize( SearchForm );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/search/index.jsx

    r4223 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { Component, PropTypes } from 'react';
    25import { connect } from 'react-redux';
    36
     7/**
     8 * Internal dependencies.
     9 */
    410import Search from './search';
    5 import { searchPlugins } from 'actions';
     11import { fetchSearch } from 'state/search/actions';
    612
    7 const SearchContainer = React.createClass( {
     13export class SearchContainer extends Component {
     14    static propTypes = {
     15        fetchSearch: PropTypes.func,
     16        params: PropTypes.object,
     17    };
     18
     19    static defaultProps = {
     20        fetchSearch: () => {},
     21        params: {},
     22    };
     23
    824    componentDidMount() {
    9         this.searchPlugins();
    10     },
     25        this.fetchSearch();
     26    }
    1127
    12     componentDidUpdate( previousProps ) {
    13         if ( this.props.params.searchTerm !== previousProps.params.searchTerm ) {
    14             this.searchPlugins();
     28    componentDidUpdate( { params } ) {
     29        if ( this.props.params.search !== params.search ) {
     30            this.fetchSearch();
    1531        }
    16     },
     32    }
    1733
    18     searchPlugins() {
    19         this.props.dispatch( searchPlugins( this.props.params.searchTerm ) );
    20     },
     34    fetchSearch() {
     35        this.props.fetchSearch( this.props.params.search );
     36    }
    2137
    2238    render() {
    2339        return <Search { ...this.props } />;
    2440    }
    25 } );
     41}
    2642
    27 const mapStateToProps = ( state, ownProps ) => ( {
    28     plugins: state.search[ ownProps.params.searchTerm ] || null
    29 } );
    30 
    31 export default connect( mapStateToProps )( SearchContainer );
     43export default connect(
     44    null,
     45    {
     46        fetchSearch,
     47    },
     48)( SearchContainer );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/search/search.jsx

    r4223 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { PropTypes } from 'react';
     5import { connect } from 'react-redux';
     6import { identity } from 'lodash';
     7import { localize } from 'i18n-calypso';
    28
     9/**
     10 * Internal dependencies.
     11 */
    312import ContentNone from 'components/content-none';
    413import PluginCard from 'components/plugin-card';
     14import { getSearchResults } from 'state/selectors';
    515
    6 export default React.createClass( {
    7     displayName: 'Search',
     16export const Search = ( { params, plugins, translate } ) => {
     17    if ( ! plugins ) {
     18        return <div>{ translate( 'Loading&hellip;' ) }</div>;
     19    }
    820
    9     render() {
    10         if ( ! this.props.plugins ) {
    11             return <div>{ 'Loading...' }</div>;
    12         }
     21    if ( 0 === plugins.length ) {
     22        return <ContentNone />;
     23    }
    1324
    14         if ( 0 === this.props.plugins.length ) {
    15             return <ContentNone { ...this.props } />;
    16         }
     25    return (
     26        <div>
     27            <header className="page-header">
     28                <h1 className="page-title">
     29                    { translate( 'Search results for: {{strong}}%(search)s{{/strong}}', {
     30                        args: { search: params.search },
     31                        components: { strong: <strong /> },
     32                    } ) }
     33                </h1>
     34                <div className="taxonomy-description" />
     35            </header>
     36            { plugins.map( ( slug ) => <PluginCard key={ slug } slug={ slug } /> ) }
     37        </div>
     38    );
     39};
    1740
    18         return (
    19             <div>
    20                 <header className="page-header">
    21                     <h1 className="page-title">Search results for: <strong>{ this.props.params.searchTerm }</strong></h1>
    22                     <div className="taxonomy-description"></div>
    23                 </header>
    24                 { this.props.plugins.map( slug =>
    25                     <PluginCard key={ slug } slug={ slug } />
    26                 ) }
    27             </div>
    28         );
    29     }
    30 } );
     41Search.propTypes = {
     42    params: PropTypes.object,
     43    plugins: PropTypes.arrayOf( PropTypes.string ),
     44    translate: PropTypes.func,
     45};
     46
     47Search.defaultProps = {
     48    params: {},
     49    plugins: [],
     50    translate: identity,
     51};
     52
     53export default connect(
     54    ( state ) => ( {
     55        plugins: getSearchResults( state ),
     56    } ),
     57)( localize( Search ) );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/site-header/index.jsx

    r5023 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { PropTypes } from 'react';
     5import { withRouter } from 'react-router';
    26
     7/**
     8 * Internal dependencies.
     9 */
    310import SearchForm from 'components/search-form';
    411import SiteDescription from './site-description';
     
    613import MainNavigation from './main-navigation';
    714
    8 export default React.createClass( {
    9     displayName: 'SiteHeader',
     15export const SiteHeader = ( { router } ) => {
     16    const classes = [ 'site-header' ];
     17    const isHome = router.isActive( '/', true );
    1018
    11     render() {
    12         const classes = ['site-header'];
    13         classes.push( this.props.isHome ? 'home' : '' );
     19    if ( isHome ) {
     20        classes.push( 'home' );
     21    }
    1422
    15         return (
    16             <header id="masthead" className={ classes.join( ' ' ) } role="banner">
    17                 <div className="site-branding">
    18                     <SiteTitle isHome={ this.props.isHome } />
    19                     <SiteDescription isHome={ this.props.isHome } />
    20                     { this.props.isHome ? <SearchForm searchTerm={ this.props.searchTerm } /> : <MainNavigation searchTerm={ this.props.searchTerm } /> }
    21                 </div>
    22             </header>
    23         )
    24     }
    25 } );
     23    return (
     24        <header id="masthead" className={ classes.join( ' ' ) } role="banner">
     25            <div className="site-branding">
     26                <SiteTitle />
     27                <SiteDescription />
     28                { isHome ? <SearchForm /> : <MainNavigation /> }
     29            </div>
     30        </header>
     31    );
     32};
     33
     34SiteHeader.propTypes = {
     35    router: PropTypes.object,
     36};
     37
     38SiteHeader.defaultProps = {
     39    router: {},
     40};
     41
     42export default withRouter( SiteHeader );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/site-header/main-navigation/index.jsx

    r4223 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { PropTypes } from 'react';
     5import { connect } from 'react-redux';
     6import { identity } from 'lodash';
     7import { localize } from 'i18n-calypso';
    28
     9/**
     10 * Internal dependencies.
     11 */
    312import MenuItem from './menu-item';
    413import SearchForm from 'components/search-form';
    514
    6 export default React.createClass( {
    7     displayName: 'MainNavigation',
     15export const MainNavigation = ( { menuItems, translate } ) => (
     16    <nav id="site-navigation" className="main-navigation" role="navigation">
     17        <button
     18            className="menu-toggle dashicons dashicons-arrow-down-alt2"
     19            aria-controls="primary-menu"
     20            aria-expanded="false"
     21            aria-label={ translate( 'Primary Menu' ) }
     22        />
     23        <div id="primary-menu" className="menu">
     24            <ul>
     25                { menuItems.map( ( menuItem, key ) => <MenuItem key={ key } item={ menuItem } /> ) }
     26                <li><SearchForm /></li>
     27            </ul>
     28        </div>
     29    </nav>
     30);
    831
    9     getDefaultProps() {
    10         return {
    11             menuItems: [
    12                 {
    13                     path: 'browse/favorites/',
    14                     label: 'My Favorites'
    15                 },
    16                 {
    17                     path: 'browse/beta/',
    18                     label: 'Beta Testing'
    19                 },
    20                 {
    21                     path: 'developers/',
    22                     label: 'Developers'
    23                 }
    24             ]
    25         }
    26     },
     32MainNavigation.propTypes = {
     33    menuItems: PropTypes.arrayOf( PropTypes.object ),
     34    translate: PropTypes.func,
     35};
    2736
    28     render() {
    29         var menuItems = this.props.menuItems.map( ( menuItem, key ) => <MenuItem key={ key } item={ menuItem } /> );
     37MainNavigation.defaultProps = {
     38    menuItems: [],
     39    translate: identity,
     40};
    3041
    31         return (
    32             <nav id="site-navigation" className="main-navigation" role="navigation">
    33                 <button className="menu-toggle dashicons dashicons-arrow-down-alt2" aria-controls="primary-menu" aria-expanded="false" aria-label="Primary Menu"></button>
    34                 <div id="primary-menu" className="menu">
    35                     <ul>
    36                         { menuItems }
    37                         <li>
    38                             <SearchForm searchTerm={ this.props.searchTerm } />
    39                         </li>
    40                     </ul>
    41                 </div>
    42             </nav>
    43         )
    44     }
    45 } );
     42export default localize( connect(
     43    ( state, { translate } ) => ( {
     44        menuItems: [
     45            {
     46                path: 'browse/favorites/',
     47                label: translate( 'My Favorites' ),
     48            },
     49            {
     50                path: 'browse/beta/',
     51                label: translate( 'Beta Testing' ),
     52            },
     53            {
     54                path: 'developers/',
     55                label: translate( 'Developers' ),
     56            },
     57        ],
     58    } ),
     59)( MainNavigation ) );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/site-header/main-navigation/menu-item/index.jsx

    r4223 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { PropTypes } from 'react';
    25import { Link } from 'react-router';
    36
    4 export default React.createClass( {
    5     displayName: 'MenuItem',
     7export const MenuItem = ( { item } ) => (
     8    <li className="page_item">
     9        <Link to={ item.path } activeClassName="active">{ item.label }</Link>
     10    </li>
     11);
    612
    7     render() {
    8         return (
    9             <li className="page_item">
    10                 <Link to={ this.props.item.path } activeClassName="active">{ this.props.item.label }</Link>
    11             </li>
    12         )
    13     }
    14 } );
     13MenuItem.propTypes = {
     14    params: PropTypes.shape( {
     15        label: PropTypes.string,
     16        path: PropTypes.string,
     17    } ),
     18};
     19
     20export default MenuItem;
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/site-header/site-description/index.jsx

    r4223 r5024  
    1 import React from 'react';
    2 import { IndexLink } from 'react-router';
     1/**
     2 * External dependencies.
     3 */
     4import React, { PropTypes } from 'react';
     5import { identity } from 'lodash';
     6import { localize } from 'i18n-calypso';
     7import { withRouter } from 'react-router';
    38
    4 export default React.createClass( {
    5     displayName: 'SiteDescription',
     9/**
     10 *
     11 * @param {Boolean}  router    Router object.
     12 * @param {Function} translate i18n translation function.
     13 * @return {*}                 Component or null.
     14 * @constructor
     15 */
     16export const SiteDescription = ( { router, translate } ) => {
     17    if ( router.isActive( '/', true ) ) {
     18        return (
     19            <p className="site-description">
     20                { translate( 'Extend your WordPress experience with 40,000 plugins.' ) }
     21            </p>
     22        );
     23    }
    624
    7     render() {
    8         if ( this.props.isHome ) {
    9             return <p className="site-description">Extend your WordPress experience with 40,000 plugins.</p>;
    10         } else {
    11             return <span />;
    12         }
    13     }
    14 } );
     25    return null;
     26};
     27
     28SiteDescription.propTypes = {
     29    router: PropTypes.object,
     30    translate: PropTypes.func,
     31};
     32
     33SiteDescription.defaultProps = {
     34    router: {},
     35    translate: identity,
     36};
     37
     38export default withRouter( localize( SiteDescription ) );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/site-header/site-title/index.jsx

    r4223 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { PropTypes } from 'react';
     5import { identity } from 'lodash';
    26import { IndexLink } from 'react-router';
     7import { localize } from 'i18n-calypso';
     8import { withRouter } from 'react-router';
    39
    4 export default React.createClass( {
    5     displayName: 'SiteTitle',
     10export const SiteTitle = ( { router, translate } ) => (
     11    router.isActive( '/', true )
     12        ? <h1 className="site-title"><IndexLink to="/" rel="home">{ translate( 'Plugins' ) }</IndexLink></h1>
     13        : <p className="site-title"><IndexLink to="/" rel="home">{ translate( 'Plugins' ) }</IndexLink></p>
     14);
    615
    7     render() {
    8         if ( this.props.isHome ) {
    9             return <h1 className="site-title"><IndexLink to="/" rel="home">Plugins</IndexLink></h1>;
    10         } else {
    11             return <p className="site-title"><IndexLink to="/" rel="home">Plugins</IndexLink></p>;
    12         }
    13     }
    14 } );
     16SiteTitle.propTypes = {
     17    router: PropTypes.object,
     18    translate: PropTypes.func,
     19};
     20
     21SiteTitle.defaultProps = {
     22    router: {},
     23    translate: identity,
     24};
     25
     26export default withRouter( localize( SiteTitle ) );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/site-main/index.jsx

    r4223 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { PropTypes } from 'react';
    25
    3 export default React.createClass( {
    4     displayName: 'SiteMain',
     6export const SiteMain = ( { children, params } ) => {
     7    const classNames = [ 'site-main' ];
    58
    6     render() {
    7         let classNames = [ 'site-main' ];
     9    if ( params.slug ) {
     10        classNames.push( 'single' );
     11    }
    812
    9         if ( this.props.params.slug ) {
    10             classNames.push( 'single' );
    11         }
     13    return (
     14        <main id="main" className={ classNames.join( ' ' ) } role="main">
     15            { children }
     16        </main>
     17    );
     18};
    1219
    13         return (
    14             <main id="main" className={ classNames.join( ' ' ) } role="main">
    15                 { this.props.children }
    16             </main>
    17         )
    18     }
    19 } );
     20SiteMain.propTypes = {
     21    params: PropTypes.object,
     22};
     23
     24SiteMain.defaultProps = {
     25    params: {},
     26};
     27
     28export default SiteMain;
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/widget-area/index.jsx

    r4223 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { PropTypes } from 'react';
     5import { withRouter } from 'react-router';
    26
    3 export default React.createClass( {
    4     displayName: 'WidgetArea',
     7export const WidgetArea = ( { children, router } ) => {
     8    const classNames = [ 'widget-area' ];
    59
    6     render() {
    7         let classNames = [ 'widget-area' ];
     10    if ( router.isActive( '/', true ) ) {
     11        classNames.push( 'home' );
     12    }
    813
    9         if ( this.props.router.isActive( '/', true ) ) {
    10             classNames.push( 'home' );
    11         }
     14    return (
     15        <aside id="secondary" className={ classNames.join( ' ' ) } role="complementary">
     16            { children }
     17        </aside>
     18    );
     19};
    1220
    13         return (
    14             <aside id="secondary" className={ classNames.join( ' ' ) } role="complementary">
    15                 { this.props.children }
    16             </aside>
    17         )
    18     }
    19 } );
     21WidgetArea.propTypes = {
     22    router: PropTypes.object,
     23};
     24
     25WidgetArea.defaultProps = {
     26    router: {},
     27};
     28
     29export default withRouter( WidgetArea );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/widget-area/widgets/donate.jsx

    r4223 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { PropTypes } from 'react';
     5import { connect } from 'react-redux';
     6import { identity } from 'lodash';
     7import { localize } from 'i18n-calypso';
    28
    3 export default React.createClass( {
    4     displayName: 'DonateWidget',
     9/**
     10 * Internal dependencies.
     11 */
     12import { getPlugin } from 'state/selectors';
    513
    6     render() {
    7         if ( ! this.props.plugin.donate_link ) {
    8             return <div />;
    9         }
    10 
     14/**
     15 * Donate Widget component.
     16 *
     17 * @param {Object}   plugin    Plugin object.
     18 * @param {Function} translate Translation function.
     19 * @return {*}                 Component or null.
     20 * @constructor
     21 */
     22export const DonateWidget = ( { plugin, translate } ) => {
     23    if ( plugin.donate_link ) {
    1124        return (
    1225            <div className="widget plugin-donate">
    13                 <h4 className="widget-title">Donate</h4>
    14                 <p className="aside">Would you like to support the advancement of this plugin?</p>
     26                <h4 className="widget-title">{ translate( 'Donate' ) }</h4>
     27                <p className="aside">{ translate( 'Would you like to support the advancement of this plugin?' ) }</p>
    1528                <p>
    16                     <a className="button button-secondary" href={ this.props.plugin.donate_link } rel="nofollow">
    17                         Donate to this plugin
     29                    <a className="button button-secondary" href={ plugin.donate_link } rel="nofollow">
     30                        { translate( 'Donate to this plugin' ) }
    1831                    </a>
    1932                </p>
    2033            </div>
    21         )
     34        );
    2235    }
    23 } );
     36
     37    return null;
     38};
     39
     40DonateWidget.propTypes = {
     41    plugin: PropTypes.object,
     42    translate: PropTypes.func,
     43};
     44
     45DonateWidget.defaultProps = {
     46    plugin: {},
     47    translate: identity,
     48};
     49
     50export default connect(
     51    ( state ) => ( {
     52        plugin: getPlugin( state ),
     53    } ),
     54)( localize( DonateWidget ) );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/widget-area/widgets/meta/index.jsx

    r4223 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { Component, PropTypes } from 'react';
     5import { connect } from 'react-redux';
     6import { Link } from 'react-router';
     7import { localize } from 'i18n-calypso';
     8import { identity } from 'lodash';
    29
    3 export default React.createClass( {
    4     displayName: 'MetaWidget',
     10/**
     11 * Internal dependencies.
     12 */
     13import { getPlugin } from 'state/selectors';
    514
     15export class MetaWidget extends Component {
     16    static propTypes = {
     17        moment: PropTypes.func,
     18        numberFormat: PropTypes.func,
     19        plugin: PropTypes.object,
     20        translate: PropTypes.func,
     21    };
     22
     23    static defaultProps = {
     24        moment: identity,
     25        numberFormat: identity,
     26        plugin: {},
     27        translate: identity,
     28    };
     29
     30    /**
     31     * Returns a string representing the number of active installs for a plugin.
     32     *
     33     * @return {String} Active installs.
     34     */
     35    activeInstalls() {
     36        const { numberFormat, translate, plugin } = this.props;
     37        let text;
     38
     39        if ( plugin.meta.active_installs <= 10 ) {
     40            text = translate( 'Less than 10' );
     41        } else if ( plugin.meta.active_installs >= 1000000 ) {
     42            text = translate( '1+ million' );
     43        } else {
     44            text = numberFormat( plugin.meta.active_installs ) + '+';
     45        }
     46
     47        return text;
     48    }
     49
     50    /**
     51     * Returns markup to display tags, if there are any.
     52     *
     53     * @return {XML} Tags markup.
     54     */
    655    renderTags() {
    7         if ( this.props.plugin.tags.length ) {
    8             return ( <li>Categories: <div className="tags">{ this.props.plugin.tags.map( tag =>
    9                 <a key={ tag } href={ `https://wordpress.org/plugins-wp/category/${ tag }/` } rel="tag">{ tag }</a>
    10             ) }</div></li> );
     56        const { plugin_tags: tags } = this.props.plugin;
     57
     58        if ( tags && tags.length ) {
     59            const tagsList = (
     60                <div className="tags">
     61                    { tags.map( ( tag ) => <Link key={ tag } to={ `tags/${ tag }/` } rel="tag">{ tag }</Link> ) }
     62                </div>
     63            );
     64
     65            return (
     66                <li>
     67                    { this.props.translate( 'Tag: {{tagsList/}}', 'Tags: {{tagsList/}}', {
     68                        count: tags.length,
     69                        components: { tagsList },
     70                    } ) }
     71                </li>
     72            );
    1173        }
    12     },
     74    }
    1375
    1476    render() {
     77        const { moment, plugin, translate } = this.props;
    1578
    1679        return (
    1780            <div className="widget plugin-meta">
    18                 <h3 className="screen-reader-text">Meta</h3>
     81                <h3 className="screen-reader-text">{ translate( 'Meta' ) }</h3>
    1982                <link itemProp="applicationCategory" href="http://schema.org/OtherApplication" />
    2083                <span itemProp="offers" itemScope itemType="http://schema.org/Offer">
     
    2790
    2891                <ul>
    29                     <li>Version: <strong>{ this.props.plugin.version }</strong></li>
    30                     <li>Last updated: <strong><span itemProp="dateModified" content={ this.props.plugin.last_updated }>{ this.props.plugin.last_updated }</span> ago</strong></li>
    31                     <li>Active installs: <strong>{ this.props.plugin.active_installs }</strong></li>
    32                     <li>Tested up to: <strong>{ this.props.plugin.tested }</strong></li>
     92                    <li>
     93                        { translate( 'Version: {{strong}}%(version)s{{/strong}}', {
     94                            args: { version: plugin.meta.version },
     95                            components: { strong: <strong /> },
     96                        } ) }
     97                    </li>
     98                    <li>
     99                        { translate( 'Last updated: {{strong}}%(updated)s{{/strong}}', {
     100                            args: { updated: moment( plugin.modified_gmt ).fromNow() },
     101                            components: { strong: <strong itemProp="dateModified" content={ plugin.modified_gmt } /> },
     102                        } ) }
     103                    </li>
     104                    <li>
     105                        { translate( 'Active installs: {{strong}}%(installs)s{{/strong}}', {
     106                            args: { installs: this.activeInstalls() },
     107                            components: { strong: <strong /> },
     108                        } ) }
     109                    </li>
     110                    <li>
     111                        { translate( 'Tested up to: {{strong}}%(tested)s{{/strong}}', {
     112                            args: { tested: plugin.meta.tested },
     113                            components: { strong: <strong /> },
     114                        } ) }
     115                    </li>
    33116                    { this.renderTags() }
    34117                </ul>
    35118            </div>
    36         )
     119        );
    37120    }
    38 } );
     121}
     122
     123export default connect(
     124    ( state ) => ( {
     125        plugin: getPlugin( state ),
     126    } ),
     127)( localize( MetaWidget ) );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/widget-area/widgets/ratings/index.jsx

    r4223 r5024  
    1 import React from 'react';
    2 import rangeRight from 'lodash/rangeRight';
     1/**
     2 * External dependencies.
     3 */
     4import React, { Component, PropTypes } from 'react';
     5import { connect } from 'react-redux';
     6import { identity } from 'lodash';
     7import { localize } from 'i18n-calypso';
     8import { rangeRight } from 'lodash';
    39
     10/**
     11 * Internal dependencies.
     12 */
     13import { getPlugin } from 'state/selectors';
    414import PluginRatings from 'components/plugin-ratings';
    515
    6 export default React.createClass( {
    7     displayName: 'RatingsWidget',
     16export class RatingsWidget extends Component {
     17    static propTypes = {
     18        plugin: PropTypes.object,
     19        translate: PropTypes.func,
     20    };
     21
     22    static defaultProps = {
     23        plugin: {},
     24        translate: identity,
     25    };
    826
    927    ratingsBreakdown() {
     28        const { plugin, translate } = this.props;
     29
     30        if ( ! plugin.ratings.length ) {
     31            return (
     32                <div className="rating"><p>{ translate( 'This plugin has not been rated yet.' ) }</p></div>
     33            );
     34        }
     35
    1036        return (
    1137            <div>
    12                 <a className="reviews-link" href={ `https://wordpress.org/support/plugin/${ this.props.plugin.slug }/reviews/` }>See all</a>
     38                <a className="reviews-link" href={ `https://wordpress.org/support/plugin/${ plugin.slug }/reviews/` }>
     39                    { translate( 'See all' ) }
     40                </a>
    1341
    14                 <PluginRatings rating={ this.props.plugin.rating } ratingCount={ this.props.plugin.num_ratings } />
     42                <PluginRatings rating={ plugin.meta.rating } ratingCount={ plugin.ratings.length } />
    1543
    1644                <ul className="ratings-list">
    17                     { rangeRight( 1, 6 ).map( stars => {
    18                         const barWidth = this.props.plugin.num_ratings ? 100 * this.props.plugin.ratings[ stars ] / this.props.plugin.num_ratings : 0;
     45                    { rangeRight( 1, 6 ).map( ( stars ) => {
     46                        const barWidth = plugin.ratings.length ? 100 * plugin.ratings[ stars ] / plugin.ratings.length : 0;
     47                        const link = `https://wordpress.org/support/plugin/${ plugin.slug }/reviews/?filter=${ stars }`;
    1948
    2049                        return (
    21                             <li className="counter-container">
    22                                 <a href={ `https://wordpress.org/support/plugin/${ this.props.plugin.slug }/reviews/?filter=${ stars }` }>
    23                                     <span className="counter-label">{ stars > 1 ? `${ stars } stars` : `${ stars } star` }</span>
     50                            <li key={ stars } className="counter-container">
     51                                <a href={ link }>
     52                                    <span className="counter-label">
     53                                        { translate( '1 star', '%(stars)s stars', { count: stars, args: { stars } } ) }
     54                                    </span>
    2455                                    <span className="counter-back">
    2556                                        <span className="counter-bar" style={ { width: `${ barWidth }%` } } />
    2657                                    </span>
    27                                     <span className="counter-count">{ this.props.plugin.ratings[ stars ] }</span>
     58                                    <span className="counter-count">{ plugin.ratings[ stars ] }</span>
    2859                                </a>
    2960                            </li>
     
    3364            </div>
    3465        );
    35     },
     66    }
    3667
    3768    render() {
     69        const { plugin, translate } = this.props;
     70
    3871        return (
    3972            <div className="widget plugin-ratings">
    40                 <h4 className="widget-title">Ratings</h4>
    41                 <meta itemProp="ratingCount" content={ this.props.plugin.num_ratings } />
    42                 { this.props.plugin.num_ratings ?
    43                     this.ratingsBreakdown() :
    44                     <div className="rating"><p>This plugin has not been rated yet.</p></div>
    45                 }
     73                <h4 className="widget-title">{ translate( 'Ratings' ) }</h4>
     74                <meta itemProp="ratingCount" content={ plugin.ratings.length } />
     75
     76                { this.ratingsBreakdown() }
     77
    4678                <div className="user-rating">
    47                     <a className="button button-secondary" href={ `https://wordpress.org/support/plugin/${ this.props.plugin.slug }/reviews/#new-post` }>Add my review</a>
     79                    <a
     80                        className="button button-secondary"
     81                        href={ `https://wordpress.org/support/plugin/${ plugin.slug }/reviews/#new-post` }
     82                    >
     83                        { translate( 'Add my review' ) }
     84                    </a>
    4885                </div>
    4986            </div>
    50         )
     87        );
    5188    }
    52 } );
     89}
     90
     91export default connect(
     92    ( state ) => ( {
     93        plugin: getPlugin( state ),
     94    } ),
     95)( localize( RatingsWidget ) );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/widget-area/widgets/support/index.jsx

    r4223 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { Component, PropTypes } from 'react';
     5import { connect } from 'react-redux';
     6import { identity } from 'lodash';
     7import { localize } from 'i18n-calypso';
    28
    3 export default React.createClass( {
    4     displayName: 'SupportWidget',
    5     resolutions: false,
     9/**
     10 * Internal dependencies.
     11 */
     12import { getPlugin } from 'state/selectors';
     13
     14export class SupportWidget extends Component {
     15    resolutions: false;
     16
     17    static propTypes = {
     18        plugin: PropTypes.object,
     19        translate: PropTypes.func,
     20    };
     21
     22    static defaultProps = {
     23        plugin: {},
     24        translate: identity,
     25    };
    626
    727    componentWillMount() {
    8         this.resolutions = ( this.props.plugin.support_threads || 'buddypress' === this.props.plugin.slug || 'bbpress' === this.props.plugin.slug );
    9     },
     28        const { meta, slug } = this.props.plugin;
     29        this.resolutions = ( meta.support_threads || 'buddypress' === slug || 'bbpress' === slug );
     30    }
    1031
    1132    componentDidUpdate() {
    12         this.resolutions = this.resolutions || this.props.plugin.support_threads;
    13     },
     33        this.resolutions = this.resolutions || this.props.plugin.meta.support_threads;
     34    }
    1435
    1536    supportBar() {
    16         const { support_threads: threads, support_threads_resolved: resolved } = this.props.plugin;
     37        const { support_threads: threads, support_threads_resolved: resolved } = this.props.plugin.meta;
     38
     39        if ( ! this.resolutions ) {
     40            return (
     41                <p>{ this.props.translate( 'Got something to say? Need help?' ) }</p>
     42            );
     43        }
    1744
    1845        return (
    1946            <div>
    20                 <p className="aside">Issues resolved in last two months:</p>
     47                <p className="aside">{ this.props.translate( 'Issues resolved in last two months:' ) }</p>
    2148                <p className="counter-container">
    2249                    <span className="counter-back">
     
    2451                    </span>
    2552                    <span className="counter-count">
    26                         { resolved } out of { threads }
     53                        { this.props.translate( '%(resolved)s out of %(threads)s', { args: { resolved, threads } } ) }
    2754                    </span>
    2855                </p>
    2956            </div>
    30         )
    31     },
     57        );
     58    }
    3259
    3360    getSupportUrl() {
    34         let supportURL = `https://wordpress.org/support/plugin/${ this.props.plugin.slug }/`;
     61        const { slug } = this.props.plugin;
     62        let supportURL = `https://wordpress.org/support/plugin/${ slug }/`;
    3563
    3664        /*
     
    3866         * In the future we could open this up to all plugins that define a custom support URL.
    3967         */
    40         if ( 'buddypress' === this.props.plugin.slug ) {
     68        if ( 'buddypress' === slug ) {
    4169            supportURL = 'https://buddypress.org/support/';
    42         } else if ( 'bbpress' === this.props.plugin.slug ) {
     70        } else if ( 'bbpress' === slug ) {
    4371            supportURL = 'https://bbpress.org/forums/';
    4472        }
    4573
    4674        return supportURL;
    47     },
     75    }
    4876
    4977    render() {
    5078        return (
    5179            <div className="widget plugin-support">
    52                 <h4 className="widget-title">Support</h4>
    53                 { this.resolutions ?
    54                     this.supportBar() :
    55                     <p>Got something to say? Need help?</p>
    56                 }
     80                <h4 className="widget-title">{ this.props.translate( 'Support' ) }</h4>
     81
     82                { this.supportBar() }
     83
    5784                <p>
    58                     <a className="button" href={ this.getSupportUrl() }>View support forum</a>
     85                    <a className="button" href={ this.getSupportUrl() }>
     86                        { this.props.translate( 'View support forum' ) }
     87                    </a>
    5988                </p>
    6089            </div>
    61         )
     90        );
    6291    }
    63 } );
     92}
     93
     94export default connect(
     95    ( state ) => ( {
     96        plugin: getPlugin( state ),
     97    } ),
     98)( localize( SupportWidget ) );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/components/widget-area/widgets/text.jsx

    r4223 r5024  
    1 import React from 'react';
     1/**
     2 * External dependencies.
     3 */
     4import React, { PropTypes } from 'react';
    25
    3 export default React.createClass( {
    4     displayName: 'TextWidget',
     6export const TextWidget = ( { widget } ) => (
     7    <div className="widget widget_text">
     8        <h3 className="widget-title">{ widget.title }</h3>
     9        <div className="textwidget">{ widget.text }</div>
     10    </div>
     11);
    512
    6     render() {
    7         return (
    8             <div className="widget widget_text">
    9                 <h3 className="widget-title">{ this.props.widget.title }</h3>
    10                 <div className="textwidget">{ this.props.widget.text }</div>
    11             </div>
    12         )
    13     }
    14 } );
     13TextWidget.propTypes = {
     14    widget: PropTypes.object,
     15};
     16
     17TextWidget.defaultProps = {
     18    widget: {},
     19};
     20
     21export default TextWidget;
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/index.jsx

    r4409 r5024  
     1/**
     2 * External dependencies.
     3 */
    14import React from 'react';
    25import { render } from 'react-dom';
     6import { Provider } from 'react-redux';
     7import { setLocale } from 'i18n-calypso';
    38
    4 import Screenshots from 'components/plugin/sections/screenshots';
     9/**
     10 * Internal dependencies.
     11 */
     12import Router from 'modules/router';
     13import getStore from 'modules/store';
    514
    6 // Temporary hack to use the srceenshot viewer without the full React client
    7 var elements = document.querySelectorAll( '#screenshots figure' );
    8 var images = [];
    9 for ( var i=0; i < elements.length; i++ ) {
    10     var caption = elements[i].querySelector('figcaption');
    11     var item = {
    12         src: elements[i].querySelector('img.screenshot').src,
    13         caption: caption ? caption.textContent : '',
    14     }
    15     images.push( item );
    16 }
     15//setLocale( localeData );
    1716
    18 if ( images.length > 0 ) {
    19     render(
    20         <Screenshots screenshots={images}>
    21         </Screenshots>,
    22         document.getElementById( 'screenshots' )
    23     );
    24 }
     17render(
     18    <Provider store={ getStore() }>
     19        { Router }
     20    </Provider>,
     21    document.getElementById( 'content' )
     22);
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/modules/api.js

    r4223 r5024  
    1 /**
    2  * The API module incorporates code from Feelingrestful WordPress React JS theme, Copyright Human Made
    3  * Feelingrestful WordPress React JS theme is distributed under the terms of the GNU GPL v3
    4  */
    5 /* global app_data:object */
     1import WPAPI from 'wpapi';
    62
    7 import $ajax from 'jquery/src/ajax';
    8 import $xhr from 'jquery/src/ajax/xhr';
     3import routes from 'default-routes.json';
    94
    10 const $ = Object.assign( {}, $ajax,$xhr );
     5/** @type {Object} pluginDirectory Config variable */
     6const pluginDirectory = window.pluginDirectory || {};
     7pluginDirectory.routes = routes;
    118
    12 const API = {
     9const api = new WPAPI( pluginDirectory );
     10api.sections = api.registerRoute( 'wp/v2', '/plugin_section/(?P<id>)' );
    1311
    14     api_url: app_data.api_url,
    15 
    16     lastRequest: null,
    17 
    18     get: function( url, data, callback ) {
    19         return this.request( 'GET', url, data, callback );
    20     },
    21 
    22     post: function( url, data, callback ) {
    23         return this.request( 'POST', url, data, callback );
    24     },
    25 
    26     request: function( method, url, data, callback ) {
    27 
    28         this.lastRequest = {
    29             method: method,
    30             url: url,
    31             args: data,
    32             isLoading: true,
    33             data: null
    34         };
    35 
    36         var xhr = $.ajax( this.api_url + url, {
    37             data: data,
    38             global: false,
    39 
    40             success: ( data ) => {
    41                 this.lastRequest.isLoading = false;
    42                 this.lastRequest.data = data;
    43                 if ( ! callback ) {
    44                     return;
    45                 }
    46                 callback( data, null, xhr.getAllResponseHeaders() );
    47             },
    48             method: method,
    49 
    50             beforeSend: ( jqxhr ) => {
    51                 jqxhr.setRequestHeader( 'X-WP-Nonce', app_data.nonce );
    52             }
    53         } );
    54 
    55         xhr.fail( err => {
    56             this.lastRequest.isLoading = false;
    57 
    58             if ( 0 === xhr.status ) {
    59                 if ( 'abort' === xhr.statusText ) {
    60                     // Has been aborted
    61                     return;
    62                 } else {
    63                     // Offline mode
    64                 }
    65             }
    66 
    67             if ( err.responseJSON && err.responseJSON[0] ) {
    68                 this.lastRequest.data = err.responseJSON[0];
    69 
    70                 if ( callback ) {
    71                     callback( null, err.responseJSON[0] );
    72                 }
    73             } else {
    74                 window.console.error( err.statusText );
    75             }
    76         } );
    77 
    78         return xhr;
    79     }
    80 };
    81 
    82 export default API;
     12export default api;
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/modules/local-storage.js

    r4223 r5024  
    1919        const serializedState = JSON.stringify( state );
    2020        localStorage.setItem( storageKey, serializedState );
    21     } catch( error ) {}
     21    } catch ( error ) {}
    2222};
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/modules/router.jsx

    r4223 r5024  
    1 /* global add_data:object */
     1/**
     2 * External dependencies.
     3 */
    24import React from 'react';
    3 import { connect } from 'react-redux';
    4 import { Router, Route, IndexRoute, useRouterHistory } from 'react-router';
    5 import createBrowserHistory from 'history/lib/createBrowserHistory';
     5import { Route, IndexRoute } from 'react-router';
     6import { ReduxRouter } from 'redux-router';
    67
     8/**
     9 * Internal dependencies.
     10 */
    711import ArchiveBrowse from 'components/archive/browse';
    812import FrontPage from 'components/front-page';
     
    1519import SiteMain from 'components/site-main';
    1620
    17 const history = useRouterHistory( createBrowserHistory )( {
    18     /** @type {object} app_data Description */
    19     basename: app_data.base
    20 } );
     21const onUpdate = () => window.scrollTo( 0, 0 );
     22
     23export const routes = (
     24    <Route name="root" component={ PluginDirectory }>
     25        <Route path="/" components={ { header: SiteHeader, main: SiteMain } }>
     26            <IndexRoute component={ FrontPage } />
     27            <Route path="browse/favorites/:username" component={ ArchiveBrowse } />
     28            <Route path="browse/:type/page/:page" component={ ArchiveBrowse } />
     29            <Route path="browse/:type" component={ ArchiveBrowse } />
     30            <Route path="developers" component={ Page } />
     31            <Route path="search/:search" component={ Search } />
     32            <Route path=":slug" component={ Plugin } />
     33            <Route path="*" component={ NotFound } />
     34        </Route>
     35    </Route>
     36);
    2137
    2238export default (
    23     <Router history={ history } onUpdate={ () => window.scrollTo( 0, 0 ) }>
    24         <Route name="root" component={ PluginDirectory }>
    25             <Route path="/" components={ { header: SiteHeader, main: SiteMain } }>
    26                 <IndexRoute component={ FrontPage } />
    27                 <Route path="browse/favorites/:username" component={ ArchiveBrowse } />
    28                 <Route path="browse/:type" component={ ArchiveBrowse } />
    29                 <Route path="developers" component={ Page } />
    30                 <Route path="search/:searchTerm" component={ Search } />
    31                 <Route path=":slug" component={ Plugin } />
    32                 <Route path="*" component={ NotFound } />
    33             </Route>
    34         </Route>
    35     </Router>
     39    <ReduxRouter onUpdate={ onUpdate }>
     40        { routes }
     41    </ReduxRouter>
    3642);
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/modules/store.js

    r4223 r5024  
     1/**
     2 * External dependencies.
     3 */
    14import { compose, createStore, applyMiddleware } from 'redux';
     5import createBrowserHistory from 'history/lib/createBrowserHistory';
     6import { reduxReactRouter } from 'redux-router';
    27import thunk from 'redux-thunk';
    3 import throttle from 'lodash/throttle';
     8import { throttle } from 'lodash';
     9import { useRouterHistory } from 'react-router';
     10import { values } from 'lodash';
    411
    5 import reducers from 'reducers';
     12/**
     13 * Internal dependencies.
     14 */
     15import { fetchUser } from 'state/user/actions';
    616import { loadState, saveState } from 'modules/local-storage';
     17import * as middlewares from './middlewares';
     18import reducers from 'state/reducers';
     19import { routes } from './router';
     20
     21const history = useRouterHistory( createBrowserHistory )( {
     22    /** @type {object} pluginDirectory Description */
     23    basename: pluginDirectory.base,
     24} );
    725
    826const getStore = () => {
    9     const store = compose(
    10         applyMiddleware( thunk )
    11     )( createStore )( reducers, loadState() );
     27    const composeDev = ( 'production' !== process.env.NODE_ENV && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ );
     28    const composeEnhancers = composeDev || compose;
     29    const store = createStore( reducers, loadState(), composeEnhancers(
     30        reduxReactRouter( { routes, history } ),
     31        applyMiddleware( thunk, ...values( middlewares ) ),
     32    ) );
    1233
    1334    // Save state to local storage when the store gets updated.
     
    1637    }, 1000 ) );
    1738
     39    // Set up user object.
     40    if ( '0' !== pluginDirectory.userId ) {
     41        store.dispatch( fetchUser( pluginDirectory.userId ) );
     42    }
     43
    1844    return store;
    1945};
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/state/action-types.js

    r5023 r5024  
     1export const SECTION_RECEIVE = 'SECTION_RECEIVE';
     2export const SECTION_REQUEST = 'SECTION_REQUEST';
     3export const SECTION_REQUEST_SUCCESS = 'SECTION_REQUEST_SUCCESS';
     4export const SECTION_REQUEST_FAILURE = 'SECTION_REQUEST_FAILURE';
     5export const SECTIONS_RECEIVE = 'SECTIONS_RECEIVE';
     6export const SECTIONS_REQUEST = 'SECTIONS_REQUEST';
     7export const SECTIONS_REQUEST_SUCCESS = 'SECTIONS_REQUEST_SUCCESS';
     8export const SECTIONS_REQUEST_FAILURE = 'SECTIONS_REQUEST_FAILURE';
    19
    2 export const GET_BROWSE        = 'GET_BROWSE';
    3 export const GET_PAGE          = 'GET_PAGE';
    4 export const GET_PLUGIN        = 'GET_PLUGIN';
    5 export const SEARCH_PLUGINS    = 'SEARCH_PLUGINS';
     10export const PAGE_RECEIVE = 'PAGE_RECEIVE';
     11export const PAGE_REQUEST = 'PAGE_REQUEST';
     12export const PAGE_REQUEST_SUCCESS = 'PAGE_REQUEST_SUCCESS';
     13export const PAGE_REQUEST_FAILURE = 'PAGE_REQUEST_FAILURE';
     14
     15export const PLUGIN_RECEIVE = 'PLUGIN_RECEIVE';
     16export const PLUGIN_REQUEST = 'PLUGIN_REQUEST';
     17export const PLUGIN_REQUEST_SUCCESS = 'PLUGIN_REQUEST_SUCCESS';
     18export const PLUGIN_REQUEST_FAILURE = 'PLUGIN_REQUEST_FAILURE';
     19export const PLUGINS_RECEIVE = 'PLUGINS_RECEIVE';
     20export const PLUGINS_REQUEST = 'PLUGINS_REQUEST';
     21export const PLUGINS_REQUEST_SUCCESS = 'PLUGINS_REQUEST_SUCCESS';
     22export const PLUGINS_REQUEST_FAILURE = 'PLUGINS_REQUEST_FAILURE';
     23
     24export const SEARCH_RECEIVE = 'SEARCH_RECEIVE';
     25export const SEARCH_REQUEST = 'SEARCH_REQUEST';
     26export const SEARCH_REQUEST_SUCCESS = 'SEARCH_REQUEST_SUCCESS';
     27export const SEARCH_REQUEST_FAILURE = 'SEARCH_REQUEST_FAILURE';
     28
     29export const USER_RECEIVE = 'USER_RECEIVE';
     30export const USER_REQUEST = 'USER_REQUEST';
     31export const USER_REQUEST_SUCCESS = 'USER_REQUEST_SUCCESS';
     32export const USER_REQUEST_FAILURE = 'USER_REQUEST_FAILURE';
    633
    734export const GET_FAVORITES     = 'GET_FAVORITES';
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/state/browse/reducer.js

    r5023 r5024  
     1/**
     2 * External dependencies.
     3 */
    14import { combineReducers } from 'redux';
     5import union from 'lodash/union';
    26
    37/**
    48 * Internal dependencies.
    59 */
    6 import beta from './beta';
    7 import favorites from './favorites';
    8 import featured from './featured';
    9 import popular from './popular';
     10import { BROWSE_RECEIVE } from 'state/action-types';
     11
     12const beta = ( state = [], action ) => { // jshint ignore:line
     13    switch ( action.type ) {
     14        case BROWSE_RECEIVE:
     15            if ( 'beta' === action.term ) {
     16                state = union( state, action.plugins );
     17            }
     18            break;
     19    }
     20
     21    return state;
     22};
     23
     24const favorites = ( state = [], action ) => { // jshint ignore:line
     25    switch ( action.type ) {
     26        case BROWSE_RECEIVE:
     27            if ( 'favorites' === action.term ) {
     28                state = union( state, action.plugins );
     29            }
     30            break;
     31    }
     32
     33    return state;
     34};
     35
     36const featured = ( state = [], action ) => { // jshint ignore:line
     37    switch ( action.type ) {
     38        case BROWSE_RECEIVE:
     39            if ( 'featured' === action.term ) {
     40                state = union( state, action.plugins );
     41            }
     42            break;
     43    }
     44
     45    return state;
     46};
     47
     48const popular = ( state = [], action ) => { // jshint ignore:line
     49    switch ( action.type ) {
     50        case BROWSE_RECEIVE:
     51            if ( 'popular' === action.term ) {
     52                state = union( state, action.plugins );
     53            }
     54            break;
     55    }
     56
     57    return state;
     58};
    1059
    1160export default combineReducers( {
     
    1362    favorites,
    1463    featured,
    15     popular
     64    popular,
    1665} );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/state/favorites/reducer.js

    r5023 r5024  
    1 import union from 'lodash/union';
    2 import without from 'lodash/without';
     1/**
     2 * External dependencies.
     3 */
     4import { uniq, without } from 'lodash';
    35
     6/**
     7 * Internal dependencies.
     8 */
    49import {
    510    FAVORITE_PLUGIN,
    611    GET_FAVORITES,
    7     UNFAVORITE_PLUGIN
    8 } from 'actions/action-types';
     12    UNFAVORITE_PLUGIN,
     13} from 'state/action-types';
    914
    10 const favorites = ( state = [], action ) => { // jshint ignore:line
    11 
     15const favorites = ( state = [], action ) => {
    1216    switch ( action.type ) {
    1317        case FAVORITE_PLUGIN:
    1418        case GET_FAVORITES:
    15             state = union( state, state.concat( [ action.plugin ] ) );
     19            state = uniq( [ ...state, action.plugin ] );
    1620            break;
    1721
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/state/pages/reducer.js

    r5023 r5024  
    1 import find from 'lodash/find';
     1/**
     2 * External dependencies.
     3 */
     4import { find } from 'lodash';
    25
    36/**
    47 * Internal dependencies.
    58 */
    6 import { GET_PAGE } from 'actions/action-types';
     9import { PAGE_RECEIVE } from 'state/action-types';
    710
    8 const pages = ( state = [], action ) => { // jshint ignore:line
    9 
     11const pages = ( state = {}, action ) => {
    1012    switch ( action.type ) {
    11         case GET_PAGE:
    12             if ( ! find( state, { id: action.page.id } ) ) {
    13                 state = state.concat( [ action.page ] );
     13        case PAGE_RECEIVE:
     14            const page = find( action.pages, { slug: action.slug } );
     15            if ( page ) {
     16                state = { ...state, [ page.slug ]: page };
    1417            }
    1518            break;
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/state/plugins/reducer.js

    r5023 r5024  
    1 import union from 'lodash/union';
     1/**
     2 * External dependencies.
     3 */
     4import { keyBy } from 'lodash';
    25
    36/**
    47 * Internal dependencies.
    58 */
    6 import { GET_PLUGIN } from 'actions/action-types';
     9import {
     10    PLUGIN_RECEIVE,
     11    PLUGINS_RECEIVE,
     12} from 'state/action-types';
    713
    8 const plugins = ( state = [], action ) => { // jshint ignore:line
     14const plugins = ( state = {}, action ) => {
     15    switch ( action.type ) {
     16        case PLUGIN_RECEIVE:
     17            state = { ...state, [ action.plugin.slug ]: action.plugin };
     18            break;
    919
    10     switch ( action.type ) {
    11         case GET_PLUGIN:
    12             state = union( state, state.concat( [ action.plugin ] ) );
     20        case PLUGINS_RECEIVE:
     21            state = { ...state, ...keyBy( action.plugins, 'slug' ) };
    1322            break;
    1423    }
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/state/reducers.js

    r5023 r5024  
     1/**
     2 * External dependencies.
     3 */
    14import { combineReducers } from 'redux';
    2 import { routerReducer } from 'react-router-redux';
     5import { routerStateReducer } from 'redux-router';
    36
    4 import browse from './browse/index';
    5 import favorites from './favorites';
    6 import pages from './pages';
    7 import plugins from './plugins';
    8 import search from './search';
     7/**
     8 * Internal dependencies.
     9 */
     10import browse from './browse/reducer';
     11import favorites from './favorites/reducer';
     12import pages from './pages/reducer';
     13import plugins from './plugins/reducer';
     14import search from './search/reducer';
     15import sections from './sections/reducer';
     16import user from './user/reducer';
    917
    1018export default combineReducers( {
     
    1422    plugins,
    1523    search,
    16     routing: routerReducer
     24    sections,
     25    router: routerStateReducer,
     26    user,
    1727} );
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/state/search/actions.js

    r5023 r5024  
     1/**
     2 * Internal dependencies.
     3 */
    14import Api from 'modules/api';
    25import {
    3     FAVORITE_PLUGIN,
    4     GET_BROWSE,
    5     GET_FAVORITES,
    6     GET_PAGE,
    7     GET_PLUGIN,
    8     SEARCH_PLUGINS,
    9     UNFAVORITE_PLUGIN
    10 } from './action-types';
     6    SEARCH_RECEIVE,
     7    SEARCH_REQUEST,
     8    SEARCH_REQUEST_SUCCESS,
     9    SEARCH_REQUEST_FAILURE,
     10} from 'state/action-types';
    1111
    12 export const getBrowse = ( type ) => ( dispatch ) => {
    13     Api.get( '/plugins/v1/query-plugins', { browse: type }, ( data, error ) => {
    14         if ( ! data.plugins.length || error ) {
    15             return;
    16         }
     12export const fetchSearch = ( search ) => ( dispatch ) => {
     13    dispatch( {
     14        type: SEARCH_REQUEST,
     15        search,
     16    } );
    1717
    18         dispatch( {
    19             type: GET_BROWSE,
    20             plugins: data.plugins,
    21             term: type
    22         } );
    23     } );
     18    Api.plugin()
     19        .then( ( plugins ) => {
     20            dispatch( {
     21                type: SEARCH_REQUEST_SUCCESS,
     22                search,
     23            } );
     24            dispatch( {
     25                type: SEARCH_RECEIVE,
     26                plugins,
     27                search,
     28            } );
     29        } )
     30        .catch( () => dispatch( {
     31            type: SEARCH_REQUEST_FAILURE,
     32            search,
     33        } ) );
    2434};
    25 
    26 export const getFavorites = ( slug ) => ( dispatch ) => {
    27     Api.get( '/plugins/v1/plugin/' + slug + '/favorite', {}, ( data, error ) => {
    28         if ( ! data.favorite || error ) {
    29             return;
    30         }
    31 
    32         dispatch( {
    33             type: GET_FAVORITES,
    34             plugin: slug
    35         } );
    36     } );
    37 };
    38 
    39 export const favoritePlugin = ( slug ) => ( dispatch ) => {
    40     Api.get( '/plugins/v1/plugin/' + slug + '/favorite', { favorite: 1 }, ( data, error ) => {
    41         if ( ! data.favorite || error ) {
    42             return;
    43         }
    44 
    45         dispatch( {
    46             type: FAVORITE_PLUGIN,
    47             plugin: slug
    48         } );
    49     } );
    50 };
    51 
    52 export const unfavoritePlugin = ( slug ) => ( dispatch ) => {
    53     Api.get( '/plugins/v1/plugin/' + slug + '/favorite', { unfavorite: 1 }, ( data, error ) => {
    54         if ( ! data.favorite || error ) {
    55             return;
    56         }
    57 
    58         dispatch( {
    59             type: UNFAVORITE_PLUGIN,
    60             plugin: slug
    61         } );
    62     } );
    63 };
    64 
    65 export const getPage = ( slug ) => ( dispatch ) => {
    66     Api.get( '/wp/v2/pages', { filter: { name: slug } }, ( data, error ) => {
    67         if ( ! data.length || error ) {
    68             return;
    69         }
    70 
    71         dispatch( {
    72             type: GET_PAGE,
    73             page: data[0]
    74         } );
    75     } );
    76 };
    77 
    78 export const getPlugin = ( slug ) => ( dispatch ) => {
    79     Api.get( '/plugins/v1/plugin/' + slug, {}, ( data, error ) => {
    80         if ( ! data || error ) {
    81             return;
    82         }
    83 
    84         dispatch( {
    85             type: GET_PLUGIN,
    86             plugin: data
    87         } );
    88     } );
    89 };
    90 
    91 export const searchPlugins = ( searchTerm ) => ( dispatch ) => {
    92     Api.get( '/wp/v2/plugin', { search: searchTerm }, ( data, error ) => {
    93         if ( ! data || error ) {
    94             return;
    95         }
    96 
    97         dispatch( {
    98             type: SEARCH_PLUGINS,
    99             searchTerm: searchTerm,
    100             plugins: data
    101         } );
    102     } );
    103 };
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/state/search/reducer.js

    r5023 r5024  
    33 * Internal dependencies.
    44 */
    5 import { SEARCH_PLUGINS } from 'actions/action-types';
     5import { SEARCH_RECEIVE } from 'state/action-types';
    66
    7 const plugins = ( state = {}, action ) => { // jshint ignore:line
    8 
     7const search = ( state = {}, action ) => { // jshint ignore:line
    98    switch ( action.type ) {
    10         case SEARCH_PLUGINS:
    11             state = Object.assign( {}, state, {
    12                 [ action.searchTerm.toLowerCase() ] : action.plugins.map( plugin => ( plugin.slug ) )
    13             } );
    14 
     9        case SEARCH_RECEIVE:
     10            state = { ...state,
     11                [ action.search.toLowerCase() ]: action.plugins.map( ( plugin ) => plugin.slug ),
     12            };
    1513            break;
    1614    }
     
    1917};
    2018
    21 export default plugins;
     19export default search;
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/styles/_components.scss

    r4466 r5024  
    1010@import "../components/plugin/plugin-banner/style";
    1111@import "../components/plugin/sections/changelog/style";
    12 @import "../components/plugin/sections/committers/style";
    1312@import "../components/plugin/sections/developers/style";
    1413@import "../components/plugin/sections/faq/style";
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/client/styles/modules/_accessibility.scss

    r4278 r5024  
    2828
    2929/* Do not show the outline on the skip link target. */
    30 #content[tabindex="-1"]:focus {
     30.site-content[tabindex="-1"]:focus {
    3131    outline: 0;
    3232}
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/css/style-rtl.css

    r4795 r5024  
    14081408
    14091409/* Do not show the outline on the skip link target. */
    1410 #content[tabindex="-1"]:focus {
     1410.site-content[tabindex="-1"]:focus {
    14111411  outline: 0;
    14121412}
     
    19851985  display: none;
    19861986  max-width: 128px;
     1987}
     1988
     1989.entry-thumbnail .plugin-icon {
     1990  -webkit-background-size: cover;
     1991  background-size: cover;
     1992  height: 128px;
     1993  width: 128px;
    19871994}
    19881995
     
    22192226}
    22202227
    2221 #changelog {
     2228.plugin-changelog {
    22222229  font-size: 12.8px;
    22232230  font-size: 0.8rem;
    22242231}
    22252232
    2226 #changelog code {
     2233.plugin-changelog code {
    22272234  font-size: 12.8px;
    22282235  font-size: 0.8rem;
    22292236}
    22302237
    2231 #changelog h4 {
     2238.plugin-changelog h4 {
    22322239  margin-top: 0;
    2233 }
    2234 
    2235 .contributors-list {
    2236   list-style: none;
    2237   margin: 0;
    2238   font-size: 0.9em;
    2239 }
    2240 
    2241 .contributors-list li {
    2242   padding-bottom: 0.5em;
    22432240}
    22442241
     
    22682265}
    22692266
    2270 #faq h2:first-of-type {
     2267.plugin-faq h2:first-of-type {
    22712268  font-size: 20px;
    22722269  font-size: 1.25rem;
     
    22812278}
    22822279
    2283 #faq dl {
     2280.plugin-faq dl {
    22842281  border-bottom: 1px solid #eee;
    22852282}
    22862283
    2287 #faq dt {
     2284.plugin-faq dt {
    22882285  border-top: 1px solid #eee;
    22892286  cursor: pointer;
     
    22952292}
    22962293
    2297 #faq dt:before {
     2294.plugin-faq dt:before {
    22982295  content: "\f347";
    22992296  float: left;
     
    23032300}
    23042301
    2305 #faq dt.open:before {
     2302.plugin-faq dt.open:before {
    23062303  content: "\f343";
    23072304}
    23082305
    2309 #faq dd {
     2306.plugin-faq dd {
    23102307  display: none;
    23112308  margin: 0 0 16px;
     
    23132310}
    23142311
    2315 #faq dd p {
     2312.no-js .plugin-faq dd {
     2313  display: block;
     2314}
     2315
     2316.plugin-faq dd p {
    23162317  margin: 0;
    23172318}
    23182319
    2319 #faq dd p + p {
     2320.plugin-faq dd p + p {
    23202321  margin-top: 16px;
    23212322  margin-top: 1rem;
    2322 }
    2323 
    2324 .no-js #faq dd {
    2325   display: block;
    23262323}
    23272324
     
    26332630}
    26342631
    2635 .section.read-more#description {
     2632.section.read-more.plugin-description {
    26362633  max-height: 400px;
    26372634}
    26382635
    2639 .section.read-more#description.toggled {
     2636.section.read-more.plugin-description.toggled {
    26402637  max-height: none;
    26412638}
     
    26432640.section.read-more.toggled {
    26442641  max-height: none;
     2642}
     2643
     2644.no-js .section.read-more {
     2645  max-height: none;
     2646  overflow: auto;
    26452647}
    26462648
     
    26852687}
    26862688
    2687 .no-js .read-more {
    2688   overflow: auto;
    2689   max-height: none;
    2690 }
    2691 
    26922689.section-toggle {
    26932690  color: #0073aa;
     
    27002697}
    27012698
     2699.no-js .section-toggle {
     2700  display: none;
     2701}
     2702
    27022703.section-toggle:after {
    27032704  content: "\f347";
     
    27162717.section-toggle:hover {
    27172718  text-decoration: underline;
    2718 }
    2719 
    2720 .no-js .section-toggle {
    2721   display: none;
    27222719}
    27232720
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/css/style.css

    r4777 r5024  
    14081408
    14091409/* Do not show the outline on the skip link target. */
    1410 #content[tabindex="-1"]:focus {
     1410.site-content[tabindex="-1"]:focus {
    14111411  outline: 0;
    14121412}
     
    19851985  display: none;
    19861986  max-width: 128px;
     1987}
     1988
     1989.entry-thumbnail .plugin-icon {
     1990  -webkit-background-size: cover;
     1991  background-size: cover;
     1992  height: 128px;
     1993  width: 128px;
    19871994}
    19881995
     
    22192226}
    22202227
    2221 #changelog {
     2228.plugin-changelog {
    22222229  font-size: 12.8px;
    22232230  font-size: 0.8rem;
    22242231}
    22252232
    2226 #changelog code {
     2233.plugin-changelog code {
    22272234  font-size: 12.8px;
    22282235  font-size: 0.8rem;
    22292236}
    22302237
    2231 #changelog h4 {
     2238.plugin-changelog h4 {
    22322239  margin-top: 0;
    2233 }
    2234 
    2235 .contributors-list {
    2236   list-style: none;
    2237   margin: 0;
    2238   font-size: 0.9em;
    2239 }
    2240 
    2241 .contributors-list li {
    2242   padding-bottom: 0.5em;
    22432240}
    22442241
     
    22682265}
    22692266
    2270 #faq h2:first-of-type {
     2267.plugin-faq h2:first-of-type {
    22712268  font-size: 20px;
    22722269  font-size: 1.25rem;
     
    22812278}
    22822279
    2283 #faq dl {
     2280.plugin-faq dl {
    22842281  border-bottom: 1px solid #eee;
    22852282}
    22862283
    2287 #faq dt {
     2284.plugin-faq dt {
    22882285  border-top: 1px solid #eee;
    22892286  cursor: pointer;
     
    22952292}
    22962293
    2297 #faq dt:before {
     2294.plugin-faq dt:before {
    22982295  content: "\f347";
    22992296  float: right;
     
    23032300}
    23042301
    2305 #faq dt.open:before {
     2302.plugin-faq dt.open:before {
    23062303  content: "\f343";
    23072304}
    23082305
    2309 #faq dd {
     2306.plugin-faq dd {
    23102307  display: none;
    23112308  margin: 0 0 16px;
     
    23132310}
    23142311
    2315 #faq dd p {
     2312.no-js .plugin-faq dd {
     2313  display: block;
     2314}
     2315
     2316.plugin-faq dd p {
    23162317  margin: 0;
    23172318}
    23182319
    2319 #faq dd p + p {
     2320.plugin-faq dd p + p {
    23202321  margin-top: 16px;
    23212322  margin-top: 1rem;
    2322 }
    2323 
    2324 .no-js #faq dd {
    2325   display: block;
    23262323}
    23272324
     
    26332630}
    26342631
    2635 .section.read-more#description {
     2632.section.read-more.plugin-description {
    26362633  max-height: 400px;
    26372634}
    26382635
    2639 .section.read-more#description.toggled {
     2636.section.read-more.plugin-description.toggled {
    26402637  max-height: none;
    26412638}
     
    26432640.section.read-more.toggled {
    26442641  max-height: none;
     2642}
     2643
     2644.no-js .section.read-more {
     2645  max-height: none;
     2646  overflow: auto;
    26452647}
    26462648
     
    26852687}
    26862688
    2687 .no-js .read-more {
    2688   overflow: auto;
    2689   max-height: none;
    2690 }
    2691 
    26922689.section-toggle {
    26932690  color: #0073aa;
     
    27002697}
    27012698
     2699.no-js .section-toggle {
     2700  display: none;
     2701}
     2702
    27022703.section-toggle:after {
    27032704  content: "\f347";
     
    27162717.section-toggle:hover {
    27172718  text-decoration: underline;
    2718 }
    2719 
    2720 .no-js .section-toggle {
    2721   display: none;
    27222719}
    27232720
     
    34153412  }
    34163413}
    3417 /*# sourceMappingURL=style.css.map */
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/functions.php

    r4505 r5024  
    7777        wp_enqueue_script( 'google-jsapi', 'https://www.google.com/jsapi', array(), false, true );
    7878        wp_enqueue_script( 'wporg-plugins-stats', get_template_directory_uri() . '/js/stats.js', array( 'jquery', 'google-jsapi' ), '20161019', true );
    79 
    8079
    8180        wp_localize_script( 'wporg-plugins-stats', 'pluginStats', array(
     
    9190            ),
    9291        ) );
    93 
    9492    }
    9593
     
    9795    if ( is_single() ) {
    9896        wp_enqueue_script( 'wporg-plugins-client', get_template_directory_uri() . '/js/theme.js', array(), false, true );
    99         wp_localize_script( 'wporg-plugins-client', 'app_data', array(
    100             'api_url' => untrailingslashit( rest_url() ),
    101             'nonce'   => wp_create_nonce( 'wp_rest' ),
    102             'base'    => get_blog_details()->path,
     97        wp_localize_script( 'wporg-plugins-client', 'pluginDirectory', array(
     98            'endpoint' => untrailingslashit( rest_url() ), // 'https://wordpress.org/plugins-wp/wp-json',
     99            'nonce'    => wp_create_nonce( 'wp_rest' ),
     100            'base'     => get_blog_details()->path,
     101            'userId'   => get_current_user_id(),
     102        ) );
     103        wp_localize_script( 'wporg-plugins-client', 'localeData', array(
     104            '' => array(
     105                'Plural-Forms' => _x( 'nplurals=2; plural=n != 1;', 'plural forms', 'wporg-plugins' ),
     106                'Language'     => _x( 'en', 'language (fr, fr_CA)', 'wporg-plugins' ),
     107                'localeSlug'   => _x( 'en', 'locale slug', 'wporg-plugins' ) ,
     108            ),
    103109        ) );
    104110    }
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/js/section-accordion.js

    r4465 r5024  
    3535                } else {
    3636                    // If the description starts with an embed/video, set the min-height to include it.
    37                     if ( 'description' === element.id && $section.children().next( 'p, div' ).first().find( 'video, iframe' ) ) {
     37                    if ( 'description' === element.id && $section.children().next( 'p, div' ).first().find( 'video, iframe' ).length ) {
    3838                        var height = $section.children().next( 'p,div' ).first().outerHeight( true ) /* embed */ + $section.children().first().outerHeight( true ) /* h2 */;
    3939
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/js/theme.js

    r4506 r5024  
    1 !function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return e[r].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){e.exports=n(1)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}for(var o=n(2),a=r(o),i=n(30),s=n(162),u=r(s),l=document.querySelectorAll("#screenshots figure"),c=[],p=0;p<l.length;p++){var d=l[p].querySelector("figcaption"),f={src:l[p].querySelector("img.screenshot").src,caption:d?d.textContent:""};c.push(f)}c.length>0&&(0,i.render)(a["default"].createElement(u["default"],{screenshots:c}),document.getElementById("screenshots"))},function(e,t,n){"use strict";e.exports=n(3)},function(e,t,n){"use strict";var r=n(4),o=n(5),a=n(17),i=n(20),s=n(25),u=n(9),l=n(27),c=n(28),p=n(29),d=(n(11),u.createElement),f=u.createFactory,h=u.cloneElement,m=r,v={Children:{map:o.map,forEach:o.forEach,count:o.count,toArray:o.toArray,only:p},Component:a,createElement:d,cloneElement:h,isValidElement:u.isValidElement,PropTypes:l,createClass:i.createClass,createFactory:f,createMixin:function(e){return e},DOM:s,version:c,__spread:m};e.exports=v},function(e,t){"use strict";function n(e){if(null===e||void 0===e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}function r(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;var r=Object.getOwnPropertyNames(t).map(function(e){return t[e]});if("0123456789"!==r.join(""))return!1;var o={};return"abcdefghijklmnopqrst".split("").forEach(function(e){o[e]=e}),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},o)).join("")}catch(a){return!1}}var o=Object.prototype.hasOwnProperty,a=Object.prototype.propertyIsEnumerable;e.exports=r()?Object.assign:function(e,t){for(var r,i,s=n(e),u=1;u<arguments.length;u++){r=Object(arguments[u]);for(var l in r)o.call(r,l)&&(s[l]=r[l]);if(Object.getOwnPropertySymbols){i=Object.getOwnPropertySymbols(r);for(var c=0;c<i.length;c++)a.call(r,i[c])&&(s[i[c]]=r[i[c]])}}return s}},function(e,t,n){"use strict";function r(e){return(""+e).replace(_,"$&/")}function o(e,t){this.func=e,this.context=t,this.count=0}function a(e,t,n){var r=e.func,o=e.context;r.call(o,t,e.count++)}function i(e,t,n){if(null==e)return e;var r=o.getPooled(t,n);g(e,a,r),o.release(r)}function s(e,t,n,r){this.result=e,this.keyPrefix=t,this.func=n,this.context=r,this.count=0}function u(e,t,n){var o=e.result,a=e.keyPrefix,i=e.func,s=e.context,u=i.call(s,t,e.count++);Array.isArray(u)?l(u,o,n,v.thatReturnsArgument):null!=u&&(m.isValidElement(u)&&(u=m.cloneAndReplaceKey(u,a+(!u.key||t&&t.key===u.key?"":r(u.key)+"/")+n)),o.push(u))}function l(e,t,n,o,a){var i="";null!=n&&(i=r(n)+"/");var l=s.getPooled(t,i,o,a);g(e,u,l),s.release(l)}function c(e,t,n){if(null==e)return e;var r=[];return l(e,r,null,t,n),r}function p(e,t,n){return null}function d(e,t){return g(e,p,null)}function f(e){var t=[];return l(e,t,null,v.thatReturnsArgument),t}var h=n(6),m=n(9),v=n(12),g=n(14),y=h.twoArgumentPooler,b=h.fourArgumentPooler,_=/\/+/g;o.prototype.destructor=function(){this.func=null,this.context=null,this.count=0},h.addPoolingTo(o,y),s.prototype.destructor=function(){this.result=null,this.keyPrefix=null,this.func=null,this.context=null,this.count=0},h.addPoolingTo(s,b);var C={forEach:i,map:c,mapIntoWithKeyPrefixInternal:l,count:d,toArray:f};e.exports=C},function(e,t,n){"use strict";var r=n(7),o=(n(8),function(e){var t=this;if(t.instancePool.length){var n=t.instancePool.pop();return t.call(n,e),n}return new t(e)}),a=function(e,t){var n=this;if(n.instancePool.length){var r=n.instancePool.pop();return n.call(r,e,t),r}return new n(e,t)},i=function(e,t,n){var r=this;if(r.instancePool.length){var o=r.instancePool.pop();return r.call(o,e,t,n),o}return new r(e,t,n)},s=function(e,t,n,r){var o=this;if(o.instancePool.length){var a=o.instancePool.pop();return o.call(a,e,t,n,r),a}return new o(e,t,n,r)},u=function(e,t,n,r,o){var a=this;if(a.instancePool.length){var i=a.instancePool.pop();return a.call(i,e,t,n,r,o),i}return new a(e,t,n,r,o)},l=function(e){var t=this;e instanceof t?void 0:r("25"),e.destructor(),t.instancePool.length<t.poolSize&&t.instancePool.push(e)},c=10,p=o,d=function(e,t){var n=e;return n.instancePool=[],n.getPooled=t||p,n.poolSize||(n.poolSize=c),n.release=l,n},f={addPoolingTo:d,oneArgumentPooler:o,twoArgumentPooler:a,threeArgumentPooler:i,fourArgumentPooler:s,fiveArgumentPooler:u};e.exports=f},function(e,t){"use strict";function n(e){for(var t=arguments.length-1,n="Minified React error #"+e+"; visit http://facebook.github.io/react/docs/error-decoder.html?invariant="+e,r=0;r<t;r++)n+="&args[]="+encodeURIComponent(arguments[r+1]);n+=" for the full message or use the non-minified dev environment for full errors and additional helpful warnings.";var o=new Error(n);throw o.name="Invariant Violation",o.framesToPop=1,o}e.exports=n},function(e,t,n){"use strict";function r(e,t,n,r,o,a,i,s){if(!e){var u;if(void 0===t)u=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var l=[n,r,o,a,i,s],c=0;u=new Error(t.replace(/%s/g,function(){return l[c++]})),u.name="Invariant Violation"}throw u.framesToPop=1,u}}e.exports=r},function(e,t,n){"use strict";function r(e){return void 0!==e.ref}function o(e){return void 0!==e.key}var a=n(4),i=n(10),s=(n(11),n(13),Object.prototype.hasOwnProperty),u="function"==typeof Symbol&&Symbol["for"]&&Symbol["for"]("react.element")||60103,l={key:!0,ref:!0,__self:!0,__source:!0},c=function(e,t,n,r,o,a,i){var s={$$typeof:u,type:e,key:t,ref:n,props:i,_owner:a};return s};c.createElement=function(e,t,n){var a,u={},p=null,d=null,f=null,h=null;if(null!=t){r(t)&&(d=t.ref),o(t)&&(p=""+t.key),f=void 0===t.__self?null:t.__self,h=void 0===t.__source?null:t.__source;for(a in t)s.call(t,a)&&!l.hasOwnProperty(a)&&(u[a]=t[a])}var m=arguments.length-2;if(1===m)u.children=n;else if(m>1){for(var v=Array(m),g=0;g<m;g++)v[g]=arguments[g+2];u.children=v}if(e&&e.defaultProps){var y=e.defaultProps;for(a in y)void 0===u[a]&&(u[a]=y[a])}return c(e,p,d,f,h,i.current,u)},c.createFactory=function(e){var t=c.createElement.bind(null,e);return t.type=e,t},c.cloneAndReplaceKey=function(e,t){var n=c(e.type,t,e.ref,e._self,e._source,e._owner,e.props);return n},c.cloneElement=function(e,t,n){var u,p=a({},e.props),d=e.key,f=e.ref,h=e._self,m=e._source,v=e._owner;if(null!=t){r(t)&&(f=t.ref,v=i.current),o(t)&&(d=""+t.key);var g;e.type&&e.type.defaultProps&&(g=e.type.defaultProps);for(u in t)s.call(t,u)&&!l.hasOwnProperty(u)&&(void 0===t[u]&&void 0!==g?p[u]=g[u]:p[u]=t[u])}var y=arguments.length-2;if(1===y)p.children=n;else if(y>1){for(var b=Array(y),_=0;_<y;_++)b[_]=arguments[_+2];p.children=b}return c(e.type,d,f,h,m,v,p)},c.isValidElement=function(e){return"object"==typeof e&&null!==e&&e.$$typeof===u},c.REACT_ELEMENT_TYPE=u,e.exports=c},function(e,t){"use strict";var n={current:null};e.exports=n},function(e,t,n){"use strict";var r=n(12),o=r;e.exports=o},function(e,t){"use strict";function n(e){return function(){return e}}var r=function(){};r.thatReturns=n,r.thatReturnsFalse=n(!1),r.thatReturnsTrue=n(!0),r.thatReturnsNull=n(null),r.thatReturnsThis=function(){return this},r.thatReturnsArgument=function(e){return e},e.exports=r},function(e,t,n){"use strict";var r=!1;e.exports=r},function(e,t,n){"use strict";function r(e,t){return e&&"object"==typeof e&&null!=e.key?l.escape(e.key):t.toString(36)}function o(e,t,n,a){var d=typeof e;if("undefined"!==d&&"boolean"!==d||(e=null),null===e||"string"===d||"number"===d||s.isValidElement(e))return n(a,e,""===t?c+r(e,0):t),1;var f,h,m=0,v=""===t?c:t+p;if(Array.isArray(e))for(var g=0;g<e.length;g++)f=e[g],h=v+r(f,g),m+=o(f,h,n,a);else{var y=u(e);if(y){var b,_=y.call(e);if(y!==e.entries)for(var C=0;!(b=_.next()).done;)f=b.value,h=v+r(f,C++),m+=o(f,h,n,a);else for(;!(b=_.next()).done;){var E=b.value;E&&(f=E[1],h=v+l.escape(E[0])+p+r(f,0),m+=o(f,h,n,a))}}else if("object"===d){var x="",T=String(e);i("31","[object Object]"===T?"object with keys {"+Object.keys(e).join(", ")+"}":T,x)}}return m}function a(e,t,n){return null==e?0:o(e,"",t,n)}var i=n(7),s=(n(10),n(9)),u=n(15),l=(n(8),n(16)),c=(n(11),"."),p=":";e.exports=a},function(e,t){"use strict";function n(e){var t=e&&(r&&e[r]||e[o]);if("function"==typeof t)return t}var r="function"==typeof Symbol&&Symbol.iterator,o="@@iterator";e.exports=n},function(e,t){"use strict";function n(e){var t=/[=:]/g,n={"=":"=0",":":"=2"},r=(""+e).replace(t,function(e){return n[e]});return"$"+r}function r(e){var t=/(=0|=2)/g,n={"=0":"=","=2":":"},r="."===e[0]&&"$"===e[1]?e.substring(2):e.substring(1);return(""+r).replace(t,function(e){return n[e]})}var o={escape:n,unescape:r};e.exports=o},function(e,t,n){"use strict";function r(e,t,n){this.props=e,this.context=t,this.refs=i,this.updater=n||a}var o=n(7),a=n(18),i=(n(13),n(19));n(8),n(11);r.prototype.isReactComponent={},r.prototype.setState=function(e,t){"object"!=typeof e&&"function"!=typeof e&&null!=e?o("85"):void 0,this.updater.enqueueSetState(this,e),t&&this.updater.enqueueCallback(this,t,"setState")},r.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this),e&&this.updater.enqueueCallback(this,e,"forceUpdate")};e.exports=r},function(e,t,n){"use strict";function r(e,t){}var o=(n(11),{isMounted:function(e){return!1},enqueueCallback:function(e,t){},enqueueForceUpdate:function(e){r(e,"forceUpdate")},enqueueReplaceState:function(e,t){r(e,"replaceState")},enqueueSetState:function(e,t){r(e,"setState")}});e.exports=o},function(e,t,n){"use strict";var r={};e.exports=r},function(e,t,n){"use strict";function r(e,t){var n=E.hasOwnProperty(t)?E[t]:null;T.hasOwnProperty(t)&&(n!==_.OVERRIDE_BASE?p("73",t):void 0),e&&(n!==_.DEFINE_MANY&&n!==_.DEFINE_MANY_MERGED?p("74",t):void 0)}function o(e,t){if(t){"function"==typeof t?p("75"):void 0,h.isValidElement(t)?p("76"):void 0;var n=e.prototype,o=n.__reactAutoBindPairs;t.hasOwnProperty(b)&&x.mixins(e,t.mixins);for(var a in t)if(t.hasOwnProperty(a)&&a!==b){var i=t[a],l=n.hasOwnProperty(a);if(r(l,a),x.hasOwnProperty(a))x[a](e,i);else{var c=E.hasOwnProperty(a),d="function"==typeof i,f=d&&!c&&!l&&t.autobind!==!1;if(f)o.push(a,i),n[a]=i;else if(l){var m=E[a];!c||m!==_.DEFINE_MANY_MERGED&&m!==_.DEFINE_MANY?p("77",m,a):void 0,m===_.DEFINE_MANY_MERGED?n[a]=s(n[a],i):m===_.DEFINE_MANY&&(n[a]=u(n[a],i))}else n[a]=i}}}}function a(e,t){if(t)for(var n in t){var r=t[n];if(t.hasOwnProperty(n)){var o=n in x;o?p("78",n):void 0;var a=n in e;a?p("79",n):void 0,e[n]=r}}}function i(e,t){e&&t&&"object"==typeof e&&"object"==typeof t?void 0:p("80");for(var n in t)t.hasOwnProperty(n)&&(void 0!==e[n]?p("81",n):void 0,e[n]=t[n]);return e}function s(e,t){return function(){var n=e.apply(this,arguments),r=t.apply(this,arguments);if(null==n)return r;if(null==r)return n;var o={};return i(o,n),i(o,r),o}}function u(e,t){return function(){e.apply(this,arguments),t.apply(this,arguments)}}function l(e,t){var n=t.bind(e);return n}function c(e){for(var t=e.__reactAutoBindPairs,n=0;n<t.length;n+=2){var r=t[n],o=t[n+1];e[r]=l(e,o)}}var p=n(7),d=n(4),f=n(17),h=n(9),m=(n(21),n(23),n(18)),v=n(19),g=(n(8),n(22)),y=n(24),b=(n(11),y({mixins:null})),_=g({DEFINE_ONCE:null,DEFINE_MANY:null,OVERRIDE_BASE:null,DEFINE_MANY_MERGED:null}),C=[],E={mixins:_.DEFINE_MANY,statics:_.DEFINE_MANY,propTypes:_.DEFINE_MANY,contextTypes:_.DEFINE_MANY,childContextTypes:_.DEFINE_MANY,getDefaultProps:_.DEFINE_MANY_MERGED,getInitialState:_.DEFINE_MANY_MERGED,getChildContext:_.DEFINE_MANY_MERGED,render:_.DEFINE_ONCE,componentWillMount:_.DEFINE_MANY,componentDidMount:_.DEFINE_MANY,componentWillReceiveProps:_.DEFINE_MANY,shouldComponentUpdate:_.DEFINE_ONCE,componentWillUpdate:_.DEFINE_MANY,componentDidUpdate:_.DEFINE_MANY,componentWillUnmount:_.DEFINE_MANY,updateComponent:_.OVERRIDE_BASE},x={displayName:function(e,t){e.displayName=t},mixins:function(e,t){if(t)for(var n=0;n<t.length;n++)o(e,t[n])},childContextTypes:function(e,t){e.childContextTypes=d({},e.childContextTypes,t)},contextTypes:function(e,t){e.contextTypes=d({},e.contextTypes,t)},getDefaultProps:function(e,t){e.getDefaultProps?e.getDefaultProps=s(e.getDefaultProps,t):e.getDefaultProps=t},propTypes:function(e,t){e.propTypes=d({},e.propTypes,t)},statics:function(e,t){a(e,t)},autobind:function(){}},T={replaceState:function(e,t){this.updater.enqueueReplaceState(this,e),t&&this.updater.enqueueCallback(this,t,"replaceState")},isMounted:function(){return this.updater.isMounted(this)}},w=function(){};d(w.prototype,f.prototype,T);var N={createClass:function(e){var t=function(e,n,r){this.__reactAutoBindPairs.length&&c(this),this.props=e,this.context=n,this.refs=v,this.updater=r||m,this.state=null;var o=this.getInitialState?this.getInitialState():null;"object"!=typeof o||Array.isArray(o)?p("82",t.displayName||"ReactCompositeComponent"):void 0,this.state=o};t.prototype=new w,t.prototype.constructor=t,t.prototype.__reactAutoBindPairs=[],C.forEach(o.bind(null,t)),o(t,e),t.getDefaultProps&&(t.defaultProps=t.getDefaultProps()),t.prototype.render?void 0:p("83");for(var n in E)t.prototype[n]||(t.prototype[n]=null);return t},injection:{injectMixin:function(e){C.push(e)}}};e.exports=N},function(e,t,n){"use strict";var r=n(22),o=r({prop:null,context:null,childContext:null});e.exports=o},function(e,t,n){"use strict";var r=n(8),o=function(e){var t,n={};e instanceof Object&&!Array.isArray(e)?void 0:r(!1);for(t in e)e.hasOwnProperty(t)&&(n[t]=t);return n};e.exports=o},function(e,t,n){"use strict";var r={};e.exports=r},function(e,t){"use strict";var n=function(e){var t;for(t in e)if(e.hasOwnProperty(t))return t;return null};e.exports=n},function(e,t,n){"use strict";function r(e){return o.createFactory(e)}var o=n(9),a=n(26),i=a({a:"a",abbr:"abbr",address:"address",area:"area",article:"article",aside:"aside",audio:"audio",b:"b",base:"base",bdi:"bdi",bdo:"bdo",big:"big",blockquote:"blockquote",body:"body",br:"br",button:"button",canvas:"canvas",caption:"caption",cite:"cite",code:"code",col:"col",colgroup:"colgroup",data:"data",datalist:"datalist",dd:"dd",del:"del",details:"details",dfn:"dfn",dialog:"dialog",div:"div",dl:"dl",dt:"dt",em:"em",embed:"embed",fieldset:"fieldset",figcaption:"figcaption",figure:"figure",footer:"footer",form:"form",h1:"h1",h2:"h2",h3:"h3",h4:"h4",h5:"h5",h6:"h6",head:"head",header:"header",hgroup:"hgroup",hr:"hr",html:"html",i:"i",iframe:"iframe",img:"img",input:"input",ins:"ins",kbd:"kbd",keygen:"keygen",label:"label",legend:"legend",li:"li",link:"link",main:"main",map:"map",mark:"mark",menu:"menu",menuitem:"menuitem",meta:"meta",meter:"meter",nav:"nav",noscript:"noscript",object:"object",ol:"ol",optgroup:"optgroup",option:"option",output:"output",p:"p",param:"param",picture:"picture",pre:"pre",progress:"progress",q:"q",rp:"rp",rt:"rt",ruby:"ruby",s:"s",samp:"samp",script:"script",section:"section",select:"select",small:"small",source:"source",span:"span",strong:"strong",style:"style",sub:"sub",summary:"summary",sup:"sup",table:"table",tbody:"tbody",td:"td",textarea:"textarea",tfoot:"tfoot",th:"th",thead:"thead",time:"time",title:"title",tr:"tr",track:"track",u:"u",ul:"ul","var":"var",video:"video",wbr:"wbr",circle:"circle",clipPath:"clipPath",defs:"defs",ellipse:"ellipse",g:"g",image:"image",line:"line",linearGradient:"linearGradient",mask:"mask",path:"path",pattern:"pattern",polygon:"polygon",polyline:"polyline",radialGradient:"radialGradient",rect:"rect",stop:"stop",svg:"svg",text:"text",tspan:"tspan"},r);e.exports=i},function(e,t){"use strict";function n(e,t,n){if(!e)return null;var o={};for(var a in e)r.call(e,a)&&(o[a]=t.call(n,e[a],a,e));return o}var r=Object.prototype.hasOwnProperty;e.exports=n},function(e,t,n){"use strict";function r(e,t){return e===t?0!==e||1/e===1/t:e!==e&&t!==t}function o(e){function t(t,n,r,o,a,i){if(o=o||T,i=i||r,null==n[r]){var s=C[a];return t?new Error("Required "+s+" `"+i+"` was not specified in "+("`"+o+"`.")):null}return e(n,r,o,a,i)}var n=t.bind(null,!1);return n.isRequired=t.bind(null,!0),n}function a(e){function t(t,n,r,o,a){var i=t[n],s=g(i);if(s!==e){var u=C[o],l=y(i);return new Error("Invalid "+u+" `"+a+"` of type "+("`"+l+"` supplied to `"+r+"`, expected ")+("`"+e+"`."))}return null}return o(t)}function i(){return o(E.thatReturns(null))}function s(e){function t(t,n,r,o,a){if("function"!=typeof e)return new Error("Property `"+a+"` of component `"+r+"` has invalid PropType notation inside arrayOf.");var i=t[n];if(!Array.isArray(i)){var s=C[o],u=g(i);return new Error("Invalid "+s+" `"+a+"` of type "+("`"+u+"` supplied to `"+r+"`, expected an array."))}for(var l=0;l<i.length;l++){var c=e(i,l,r,o,a+"["+l+"]");if(c instanceof Error)return c}return null}return o(t)}function u(){function e(e,t,n,r,o){if(!_.isValidElement(e[t])){var a=C[r];return new Error("Invalid "+a+" `"+o+"` supplied to "+("`"+n+"`, expected a single ReactElement."))}return null}return o(e)}function l(e){function t(t,n,r,o,a){if(!(t[n]instanceof e)){var i=C[o],s=e.name||T,u=b(t[n]);return new Error("Invalid "+i+" `"+a+"` of type "+("`"+u+"` supplied to `"+r+"`, expected ")+("instance of `"+s+"`."))}return null}return o(t)}function c(e){function t(t,n,o,a,i){for(var s=t[n],u=0;u<e.length;u++)if(r(s,e[u]))return null;var l=C[a],c=JSON.stringify(e);return new Error("Invalid "+l+" `"+i+"` of value `"+s+"` "+("supplied to `"+o+"`, expected one of "+c+"."))}return o(Array.isArray(e)?t:function(){return new Error("Invalid argument supplied to oneOf, expected an instance of array.")})}function p(e){function t(t,n,r,o,a){if("function"!=typeof e)return new Error("Property `"+a+"` of component `"+r+"` has invalid PropType notation inside objectOf.");var i=t[n],s=g(i);if("object"!==s){var u=C[o];return new Error("Invalid "+u+" `"+a+"` of type "+("`"+s+"` supplied to `"+r+"`, expected an object."))}for(var l in i)if(i.hasOwnProperty(l)){var c=e(i,l,r,o,a+"."+l);if(c instanceof Error)return c}return null}return o(t)}function d(e){function t(t,n,r,o,a){for(var i=0;i<e.length;i++){var s=e[i];if(null==s(t,n,r,o,a))return null}var u=C[o];return new Error("Invalid "+u+" `"+a+"` supplied to "+("`"+r+"`."))}return o(Array.isArray(e)?t:function(){return new Error("Invalid argument supplied to oneOfType, expected an instance of array.")})}function f(){function e(e,t,n,r,o){if(!m(e[t])){var a=C[r];return new Error("Invalid "+a+" `"+o+"` supplied to "+("`"+n+"`, expected a ReactNode."))}return null}return o(e)}function h(e){function t(t,n,r,o,a){var i=t[n],s=g(i);if("object"!==s){var u=C[o];return new Error("Invalid "+u+" `"+a+"` of type `"+s+"` "+("supplied to `"+r+"`, expected `object`."))}for(var l in e){var c=e[l];if(c){var p=c(i,l,r,o,a+"."+l);if(p)return p}}return null}return o(t)}function m(e){switch(typeof e){case"number":case"string":case"undefined":return!0;case"boolean":return!e;case"object":if(Array.isArray(e))return e.every(m);if(null===e||_.isValidElement(e))return!0;var t=x(e);if(!t)return!1;var n,r=t.call(e);if(t!==e.entries){for(;!(n=r.next()).done;)if(!m(n.value))return!1}else for(;!(n=r.next()).done;){var o=n.value;if(o&&!m(o[1]))return!1}return!0;default:return!1}}function v(e,t){return"symbol"===e||("Symbol"===t["@@toStringTag"]||"function"==typeof Symbol&&t instanceof Symbol)}function g(e){var t=typeof e;return Array.isArray(e)?"array":e instanceof RegExp?"object":v(t,e)?"symbol":t}function y(e){var t=g(e);if("object"===t){if(e instanceof Date)return"date";if(e instanceof RegExp)return"regexp"}return t}function b(e){return e.constructor&&e.constructor.name?e.constructor.name:T}var _=n(9),C=n(23),E=n(12),x=n(15),T="<<anonymous>>",w={array:a("array"),bool:a("boolean"),func:a("function"),number:a("number"),object:a("object"),string:a("string"),symbol:a("symbol"),any:i(),arrayOf:s,element:u(),instanceOf:l,node:f(),objectOf:p,oneOf:c,oneOfType:d,shape:h};e.exports=w},function(e,t){"use strict";e.exports="15.2.1"},function(e,t,n){"use strict";function r(e){return a.isValidElement(e)?void 0:o("23"),e}var o=n(7),a=n(9);n(8);e.exports=r},function(e,t,n){"use strict";e.exports=n(31)},function(e,t,n){"use strict";var r=n(32),o=n(35),a=n(154),i=n(55),s=n(52),u=n(28),l=n(159),c=n(160),p=n(161);n(11);o.inject();var d={findDOMNode:l,render:a.render,unmountComponentAtNode:a.unmountComponentAtNode,version:u,unstable_batchedUpdates:s.batchedUpdates,unstable_renderSubtreeIntoContainer:p};"undefined"!=typeof __REACT_DEVTOOLS_GLOBAL_HOOK__&&"function"==typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.inject&&__REACT_DEVTOOLS_GLOBAL_HOOK__.inject({ComponentTree:{getClosestInstanceFromNode:r.getClosestInstanceFromNode,getNodeFromInstance:function(e){return e._renderedComponent&&(e=c(e)),e?r.getNodeFromInstance(e):null}},Mount:a,Reconciler:i});e.exports=d},function(e,t,n){"use strict";function r(e){for(var t;t=e._renderedComponent;)e=t;return e}function o(e,t){var n=r(e);n._hostNode=t,t[m]=n}function a(e){var t=e._hostNode;t&&(delete t[m],e._hostNode=null)}function i(e,t){if(!(e._flags&h.hasCachedChildNodes)){var n=e._renderedChildren,a=t.firstChild;e:for(var i in n)if(n.hasOwnProperty(i)){var s=n[i],u=r(s)._domID;if(null!=u){for(;null!==a;a=a.nextSibling)if(1===a.nodeType&&a.getAttribute(f)===String(u)||8===a.nodeType&&a.nodeValue===" react-text: "+u+" "||8===a.nodeType&&a.nodeValue===" react-empty: "+u+" "){o(s,a);continue e}c("32",u)}}e._flags|=h.hasCachedChildNodes}}function s(e){if(e[m])return e[m];for(var t=[];!e[m];){if(t.push(e),!e.parentNode)return null;e=e.parentNode}for(var n,r;e&&(r=e[m]);e=t.pop())n=r,t.length&&i(r,e);return n}function u(e){var t=s(e);return null!=t&&t._hostNode===e?t:null}function l(e){if(void 0===e._hostNode?c("33"):void 0,e._hostNode)return e._hostNode;for(var t=[];!e._hostNode;)t.push(e),e._hostParent?void 0:c("34"),e=e._hostParent;for(;t.length;e=t.pop())i(e,e._hostNode);return e._hostNode}var c=n(7),p=n(33),d=n(34),f=(n(8),p.ID_ATTRIBUTE_NAME),h=d,m="__reactInternalInstance$"+Math.random().toString(36).slice(2),v={getClosestInstanceFromNode:s,getInstanceFromNode:u,getNodeFromInstance:l,precacheChildNodes:i,precacheNode:o,uncacheNode:a};e.exports=v},function(e,t,n){"use strict";function r(e,t){return(e&t)===t}var o=n(7),a=(n(8),{MUST_USE_PROPERTY:1,HAS_BOOLEAN_VALUE:4,HAS_NUMERIC_VALUE:8,HAS_POSITIVE_NUMERIC_VALUE:24,HAS_OVERLOADED_BOOLEAN_VALUE:32,injectDOMPropertyConfig:function(e){var t=a,n=e.Properties||{},i=e.DOMAttributeNamespaces||{},u=e.DOMAttributeNames||{},l=e.DOMPropertyNames||{},c=e.DOMMutationMethods||{};e.isCustomAttribute&&s._isCustomAttributeFunctions.push(e.isCustomAttribute);for(var p in n){s.properties.hasOwnProperty(p)?o("48",p):void 0;var d=p.toLowerCase(),f=n[p],h={attributeName:d,attributeNamespace:null,propertyName:p,mutationMethod:null,mustUseProperty:r(f,t.MUST_USE_PROPERTY),hasBooleanValue:r(f,t.HAS_BOOLEAN_VALUE),hasNumericValue:r(f,t.HAS_NUMERIC_VALUE),hasPositiveNumericValue:r(f,t.HAS_POSITIVE_NUMERIC_VALUE),hasOverloadedBooleanValue:r(f,t.HAS_OVERLOADED_BOOLEAN_VALUE)};if(h.hasBooleanValue+h.hasNumericValue+h.hasOverloadedBooleanValue<=1?void 0:o("50",p),u.hasOwnProperty(p)){var m=u[p];h.attributeName=m}i.hasOwnProperty(p)&&(h.attributeNamespace=i[p]),l.hasOwnProperty(p)&&(h.propertyName=l[p]),c.hasOwnProperty(p)&&(h.mutationMethod=c[p]),s.properties[p]=h}}}),i=":A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD",s={ID_ATTRIBUTE_NAME:"data-reactid",ROOT_ATTRIBUTE_NAME:"data-reactroot",ATTRIBUTE_NAME_START_CHAR:i,ATTRIBUTE_NAME_CHAR:i+"\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040",properties:{},getPossibleStandardName:null,_isCustomAttributeFunctions:[],isCustomAttribute:function(e){for(var t=0;t<s._isCustomAttributeFunctions.length;t++){var n=s._isCustomAttributeFunctions[t];if(n(e))return!0}return!1},injection:a};e.exports=s},function(e,t){"use strict";var n={hasCachedChildNodes:1};e.exports=n},function(e,t,n){"use strict";function r(){E||(E=!0,g.EventEmitter.injectReactEventListener(v),g.EventPluginHub.injectEventPluginOrder(i),g.EventPluginUtils.injectComponentTree(p),g.EventPluginUtils.injectTreeTraversal(f),g.EventPluginHub.injectEventPluginsByName({SimpleEventPlugin:C,EnterLeaveEventPlugin:s,ChangeEventPlugin:a,SelectEventPlugin:_,BeforeInputEventPlugin:o}),g.HostComponent.injectGenericComponentClass(c),g.HostComponent.injectTextComponentClass(h),g.DOMProperty.injectDOMPropertyConfig(u),g.DOMProperty.injectDOMPropertyConfig(b),g.EmptyComponent.injectEmptyComponentFactory(function(e){return new d(e)}),g.Updates.injectReconcileTransaction(y),g.Updates.injectBatchingStrategy(m),g.Component.injectEnvironment(l))}var o=n(36),a=n(51),i=n(63),s=n(64),u=n(69),l=n(70),c=n(84),p=n(32),d=n(125),f=n(126),h=n(127),m=n(128),v=n(129),g=n(132),y=n(133),b=n(141),_=n(142),C=n(143),E=!1;e.exports={inject:r}},function(e,t,n){"use strict";function r(){var e=window.opera;return"object"==typeof e&&"function"==typeof e.version&&parseInt(e.version(),10)<=12}function o(e){return(e.ctrlKey||e.altKey||e.metaKey)&&!(e.ctrlKey&&e.altKey)}function a(e){switch(e){case P.topCompositionStart:return S.compositionStart;case P.topCompositionEnd:return S.compositionEnd;case P.topCompositionUpdate:return S.compositionUpdate}}function i(e,t){return e===P.topKeyDown&&t.keyCode===C}function s(e,t){switch(e){case P.topKeyUp:return _.indexOf(t.keyCode)!==-1;case P.topKeyDown:return t.keyCode!==C;case P.topKeyPress:case P.topMouseDown:case P.topBlur:return!0;default:return!1}}function u(e){var t=e.detail;return"object"==typeof t&&"data"in t?t.data:null}function l(e,t,n,r){var o,l;if(E?o=a(e):I?s(e,n)&&(o=S.compositionEnd):i(e,n)&&(o=S.compositionStart),!o)return null;w&&(I||o!==S.compositionStart?o===S.compositionEnd&&I&&(l=I.getData()):I=v.getPooled(r));var c=g.getPooled(o,t,n,r);if(l)c.data=l;else{var p=u(n);null!==p&&(c.data=p)}return h.accumulateTwoPhaseDispatches(c),c}function c(e,t){switch(e){case P.topCompositionEnd:return u(t);case P.topKeyPress:var n=t.which;return n!==N?null:(M=!0,k);case P.topTextInput:var r=t.data;return r===k&&M?null:r;default:return null}}function p(e,t){if(I){if(e===P.topCompositionEnd||s(e,t)){var n=I.getData();return v.release(I),I=null,n}return null}switch(e){case P.topPaste:return null;case P.topKeyPress:return t.which&&!o(t)?String.fromCharCode(t.which):null;case P.topCompositionEnd:return w?null:t.data;default:return null}}function d(e,t,n,r){var o;if(o=T?c(e,n):p(e,n),!o)return null;var a=y.getPooled(S.beforeInput,t,n,r);return a.data=o,h.accumulateTwoPhaseDispatches(a),a}var f=n(37),h=n(38),m=n(45),v=n(46),g=n(48),y=n(50),b=n(24),_=[9,13,27,32],C=229,E=m.canUseDOM&&"CompositionEvent"in window,x=null;m.canUseDOM&&"documentMode"in document&&(x=document.documentMode);var T=m.canUseDOM&&"TextEvent"in window&&!x&&!r(),w=m.canUseDOM&&(!E||x&&x>8&&x<=11),N=32,k=String.fromCharCode(N),P=f.topLevelTypes,S={beforeInput:{phasedRegistrationNames:{bubbled:b({onBeforeInput:null}),captured:b({onBeforeInputCapture:null})},dependencies:[P.topCompositionEnd,P.topKeyPress,P.topTextInput,P.topPaste]},compositionEnd:{phasedRegistrationNames:{bubbled:b({onCompositionEnd:null}),captured:b({onCompositionEndCapture:null})},dependencies:[P.topBlur,P.topCompositionEnd,P.topKeyDown,P.topKeyPress,P.topKeyUp,P.topMouseDown]},compositionStart:{phasedRegistrationNames:{bubbled:b({onCompositionStart:null}),captured:b({onCompositionStartCapture:null})},dependencies:[P.topBlur,P.topCompositionStart,P.topKeyDown,P.topKeyPress,P.topKeyUp,P.topMouseDown]},compositionUpdate:{phasedRegistrationNames:{bubbled:b({onCompositionUpdate:null}),captured:b({onCompositionUpdateCapture:null})},dependencies:[P.topBlur,P.topCompositionUpdate,P.topKeyDown,P.topKeyPress,P.topKeyUp,P.topMouseDown]}},M=!1,I=null,R={eventTypes:S,extractEvents:function(e,t,n,r){return[l(e,t,n,r),d(e,t,n,r)]}};e.exports=R},function(e,t,n){"use strict";var r=n(22),o=r({bubbled:null,captured:null}),a=r({topAbort:null,topAnimationEnd:null,topAnimationIteration:null,topAnimationStart:null,topBlur:null,topCanPlay:null,topCanPlayThrough:null,topChange:null,topClick:null,topCompositionEnd:null,topCompositionStart:null,topCompositionUpdate:null,topContextMenu:null,topCopy:null,topCut:null,topDoubleClick:null,topDrag:null,topDragEnd:null,topDragEnter:null,topDragExit:null,topDragLeave:null,topDragOver:null,topDragStart:null,topDrop:null,topDurationChange:null,topEmptied:null,topEncrypted:null,topEnded:null,topError:null,topFocus:null,topInput:null,topInvalid:null,topKeyDown:null,topKeyPress:null,topKeyUp:null,topLoad:null,topLoadedData:null,topLoadedMetadata:null,topLoadStart:null,topMouseDown:null,topMouseMove:null,topMouseOut:null,topMouseOver:null,topMouseUp:null,topPaste:null,topPause:null,topPlay:null,topPlaying:null,topProgress:null,topRateChange:null,topReset:null,topScroll:null,topSeeked:null,topSeeking:null,topSelectionChange:null,topStalled:null,topSubmit:null,topSuspend:null,topTextInput:null,topTimeUpdate:null,topTouchCancel:null,topTouchEnd:null,topTouchMove:null,topTouchStart:null,topTransitionEnd:null,topVolumeChange:null,topWaiting:null,topWheel:null}),i={topLevelTypes:a,PropagationPhases:o};e.exports=i},function(e,t,n){"use strict";function r(e,t,n){var r=t.dispatchConfig.phasedRegistrationNames[n];return b(e,r)}function o(e,t,n){var o=t?y.bubbled:y.captured,a=r(e,n,o);a&&(n._dispatchListeners=v(n._dispatchListeners,a),n._dispatchInstances=v(n._dispatchInstances,e))}function a(e){e&&e.dispatchConfig.phasedRegistrationNames&&m.traverseTwoPhase(e._targetInst,o,e)}function i(e){if(e&&e.dispatchConfig.phasedRegistrationNames){var t=e._targetInst,n=t?m.getParentInstance(t):null;m.traverseTwoPhase(n,o,e)}}function s(e,t,n){if(n&&n.dispatchConfig.registrationName){var r=n.dispatchConfig.registrationName,o=b(e,r);o&&(n._dispatchListeners=v(n._dispatchListeners,o),n._dispatchInstances=v(n._dispatchInstances,e))}}function u(e){e&&e.dispatchConfig.registrationName&&s(e._targetInst,null,e)}function l(e){g(e,a)}function c(e){g(e,i)}function p(e,t,n,r){m.traverseEnterLeave(n,r,s,e,t)}function d(e){g(e,u)}var f=n(37),h=n(39),m=n(41),v=n(43),g=n(44),y=(n(11),f.PropagationPhases),b=h.getListener,_={accumulateTwoPhaseDispatches:l,accumulateTwoPhaseDispatchesSkipTarget:c,accumulateDirectDispatches:d,accumulateEnterLeaveDispatches:p};e.exports=_},function(e,t,n){"use strict";var r=n(7),o=n(40),a=n(41),i=n(42),s=n(43),u=n(44),l=(n(8),{}),c=null,p=function(e,t){e&&(a.executeDispatchesInOrder(e,t),e.isPersistent()||e.constructor.release(e))},d=function(e){return p(e,!0)},f=function(e){return p(e,!1)},h={injection:{injectEventPluginOrder:o.injectEventPluginOrder,injectEventPluginsByName:o.injectEventPluginsByName},putListener:function(e,t,n){"function"!=typeof n?r("94",t,typeof n):void 0;var a=l[t]||(l[t]={});a[e._rootNodeID]=n;var i=o.registrationNameModules[t];i&&i.didPutListener&&i.didPutListener(e,t,n)},getListener:function(e,t){var n=l[t];return n&&n[e._rootNodeID]},deleteListener:function(e,t){var n=o.registrationNameModules[t];n&&n.willDeleteListener&&n.willDeleteListener(e,t);var r=l[t];r&&delete r[e._rootNodeID]},deleteAllListeners:function(e){for(var t in l)if(l.hasOwnProperty(t)&&l[t][e._rootNodeID]){var n=o.registrationNameModules[t];n&&n.willDeleteListener&&n.willDeleteListener(e,t),delete l[t][e._rootNodeID]}},extractEvents:function(e,t,n,r){for(var a,i=o.plugins,u=0;u<i.length;u++){var l=i[u];if(l){var c=l.extractEvents(e,t,n,r);c&&(a=s(a,c))}}return a},enqueueEvents:function(e){e&&(c=s(c,e))},processEventQueue:function(e){var t=c;c=null,e?u(t