<commit_before>/* eslint-disable */
import React from 'react';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import { StyleSheet, css } from 'aphrodite';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import { red700, white, yellow400, grey900, grey300 } from 'material-ui/styles/colors';
// deepOrange600
import PeopleIcon from 'material-ui/svg-icons/social/people';
import RaisedButton from 'material-ui/RaisedButton';
import { Toolbar } from 'material-ui/Toolbar';
import IconButton from 'material-ui/IconButton';
import TextField from 'material-ui/TextField';
import { connect } from 'react-redux';
import BugReport from 'material-ui/svg-icons/action/bug-report';
import Fuse from 'fuse.js';
// import SettingsDialog from './SettingsDialog';
// import NotificationsDialog from './NotificationsDialog';
import config from '../../server/config';
import dfsTraversal from '../routes/Discussion/produceCommentGraph';

/* eslint-enable */

function getTotalAuthorNumber(annotations, groupId = '0') {
  if (!annotations) {
    return 0;
  }

  let filtered = annotations;

  if (groupId !== '0') {
    filtered = annotations.filter(a => a.groups.includes(groupId));
  }

  const authorArray = [];

  for (let i = 0; i < filtered.length; i += 1) {
    if (!filtered[i].author) {
      /* eslint-disable */
      continue;
      /* eslint-enable */
    }
    authorArray.push(filtered[i].author._id);
  }

  const uniqueArray = authorArray.filter((authorID, i) => { // Filter out so we don't count the same person more than once
    return authorArray.indexOf(authorID) === i;
  });

  return uniqueArray.length;
}

const muiTheme = getMuiTheme({
  fontFamily: 'Roboto, sans-serif',
  palette: {
    accent1Color: red700,
    accent2Color: '#F98C25',
    textColor: white,
  },
  userAgent: (typeof navigator !== 'undefined' && navigator.userAgent) || 'all',
});

const styles = StyleSheet.create({
  toolbarContainer: {
    display: 'flex',
    width: '100%',
    alignItems: 'center',
    justifyContent: 'space-between',
  },
  feedDetails: {
    display: 'flex',
    flexDirection: 'column',
    flex: 1,
    // alignItems: 'center',
  },
  feedTopRow: {
    display: 'flex',
    alignItems: 'center',
    // justifyContent: 'space-between',
  },
  notificationsAndSettings: {
    display: 'flex',
    alignItems: 'center',
    flex: 1,
    '@media (max-width: 900px)': {
      display: 'none',
    },
  },
  searchBar: {
    '@media (max-width: 900px)': {
      display: 'none',
    },
    // flex: 1,
  },
  link: {
    maxWidth: 700,
    color: '#999',
    textDecoration: 'none',
    fontWeight: 'bold',
    transition: '.2s opacity ease',
    ':hover': {
      opacity: 0.6,
    },
  },
  topLink: {
    color: '#fff',
    fontSize: '20px',
  },
  activeLink: {
    color: '#000',
  },
  feedDescription: {
    left: 25,
    fontSize: 20,
  },
  numMembers: {
    fontSize: 14,
    textDecoration: 'underline',
  },
});

function search(list, query) { // Will return the array in sorted order
  const options = {
    shouldSort: true,
    threshold: 0.5,
    location: 0,
    distance: 100,
    maxPatternLength: 32,
    minMatchCharLength: 1,
    keys: [
      'info.title',
      'info.domain',
      'annotations[0]',
    ],
  };

  const fuse = new Fuse(list, options);
  const result = fuse.search(query);

  return result;
}

class TopNav extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: 2,
      subscribed: false,
    };
    this.handleSubscribeClick = this.handleSubscribeClick.bind(this);
  }

  handleChange = (event, index, value) => this.setState({ value });

  handleSubscribeClick() {
    this.setState({ subscribed: !this.state.subscribed });
  }

  handleChange = (event, index, value) => this.setState({ value });

  executeSearch = (props) => {
    const textfield = document.getElementById('Search');
    const searchResults = search(this.props.data, textfield.value);
    const searchIsEmpty = textfield.value.length === 0;
    this.props.dispatch({ type: 'EXECUTE_SEARCH', search: searchResults, searchIsEmpty });
  }

  render() {
    const subButton = null;

    /* if (window.location.href.includes('feed')) {
      if (this.state.subscribed) { // Check if subscribed to group
        subButton = <RaisedButton label="unsubscribe" onClick={this.handleSubscribeClick} backgroundColor={red700} />;
      } else {
        subButton = <RaisedButton label="subscribe" onClick={this.handleSubscribeClick} backgroundColor={yellow400} labelColor={grey900} />;
      }
    }*/

    /*
      when not yet response from button click, use
      import RefreshIndicator from 'material-ui/lib/refresh-indicator';
    <RefreshIndicator
     size={40}
     left={10}
     top={0}
     status="loading"
     style={style.refresh}
   />
      */

    /* eslint-disable */

    let annotations = [];
    let groupId = '0';
    if (window.location.href.includes("feed")) {
      groupId = window.location.href.substr(window.location.href.lastIndexOf('/') + 1);
    }
    const isFeedView = window.location.href.includes('feed');
    const isFeedOrDiscussionView = isFeedView || window.location.href.includes('discussion');
    let allComments = [];
    if (isFeedOrDiscussionView) {
      annotations = isFeedView  ? this.props.annotationsF : this.props.annotationsD;
      for (let i = 0; i < annotations.length; i += 1) {
        const order = dfsTraversal(annotations[i], () => {

        });
        allComments = allComments.concat(order);
      }
    }

    const totalAuthorNumber = getTotalAuthorNumber(allComments, groupId);
    const totalNumberOfAnnotations = allComments.length; // This could go on the articleCard
    const shouldLoadMembers = (totalAuthorNumber !== 0 && isFeedOrDiscussionView);

    let feedName;

    if (this.props.feedDescription) {
      feedName = {
        fontSize: 33,
        paddingRight: 15,
      };
    } else {
      feedName = {
        fontSize: 41,
        paddingRight: 15,
        color: white,
      };
    }

    return (
      <MuiThemeProvider muiTheme={muiTheme}>
        <Toolbar style={{ position: 'fixed', zIndex: 200, height: 90, top: 0, left: 0, width: '100%', color: white, fontFamily: 'Roboto, sans-serif', boxShadow: '0 2px 4px rgba(0,0,0,0.15), 0 2px 4px rgba(0,0,0,0.22)' }}>

{this.props.currentFeedName || Notist}

{subButton}

                { shouldLoadMembers ? <PeopleIcon /> : '' }

{shouldLoadMembers ? (totalAuthorNumber.toString().concat(totalAuthorNumber === 1 ? ' member' : ' members')) : ''}

{this.props.feedDescription}

              Found any bugs?

                  <IconButton tooltip="Submit a bug report">
                    <BugReport color={white} hoverColor={grey300} />
                  </IconButton>

            {isFeedView ?

                <TextField id="Search" floatingLabelText="Search" onChange={this.executeSearch} />

 : ''}

              <a className={css(styles.link, styles.topLink)}
                href={`${config.apiHost}/logout`}
              >Logout

        </Toolbar>
      </MuiThemeProvider>
    );
  }
}

/* eslint-enable */

const mapStateToProps = state => ({
  data: state.articles ? state.articles.data : [],
  annotationsD: state.Discussion ? state.Discussion.annotations : [],
  annotationsF: state.articles ? state.articles.annotations : [],
});

export default connect(mapStateToProps)(TopNav);

//
<PeopleIcon />

//

//   <Search />
//

//
{this.props.numFeedMembers || 0} members

//
{subButton}

//

//   <SettingsDialog />
//

//

//   <NotificationsDialog numNotifications={this.props.numNotifications || 0} />
//

<commit_msg>oops made a typo in the url<commit_after>/* eslint-disable */
import React from 'react';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import { StyleSheet, css } from 'aphrodite';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import { red700, white, yellow400, grey900, grey300 } from 'material-ui/styles/colors';
// deepOrange600
import PeopleIcon from 'material-ui/svg-icons/social/people';
import RaisedButton from 'material-ui/RaisedButton';
import { Toolbar } from 'material-ui/Toolbar';
import IconButton from 'material-ui/IconButton';
import TextField from 'material-ui/TextField';
import { connect } from 'react-redux';
import BugReport from 'material-ui/svg-icons/action/bug-report';
import Fuse from 'fuse.js';
// import SettingsDialog from './SettingsDialog';
// import NotificationsDialog from './NotificationsDialog';
import config from '../../server/config';
import dfsTraversal from '../routes/Discussion/produceCommentGraph';

/* eslint-enable */

function getTotalAuthorNumber(annotations, groupId = '0') {
  if (!annotations) {
    return 0;
  }

  let filtered = annotations;

  if (groupId !== '0') {
    filtered = annotations.filter(a => a.groups.includes(groupId));
  }

  const authorArray = [];

  for (let i = 0; i < filtered.length; i += 1) {
    if (!filtered[i].author) {
      /* eslint-disable */
      continue;
      /* eslint-enable */
    }
    authorArray.push(filtered[i].author._id);
  }

  const uniqueArray = authorArray.filter((authorID, i) => { // Filter out so we don't count the same person more than once
    return authorArray.indexOf(authorID) === i;
  });

  return uniqueArray.length;
}

const muiTheme = getMuiTheme({
  fontFamily: 'Roboto, sans-serif',
  palette: {
    accent1Color: red700,
    accent2Color: '#F98C25',
    textColor: white,
  },
  userAgent: (typeof navigator !== 'undefined' && navigator.userAgent) || 'all',
});

const styles = StyleSheet.create({
  toolbarContainer: {
    display: 'flex',
    width: '100%',
    alignItems: 'center',
    justifyContent: 'space-between',
  },
  feedDetails: {
    display: 'flex',
    flexDirection: 'column',
    flex: 1,
    // alignItems: 'center',
  },
  feedTopRow: {
    display: 'flex',
    alignItems: 'center',
    // justifyContent: 'space-between',
  },
  notificationsAndSettings: {
    display: 'flex',
    alignItems: 'center',
    flex: 1,
    '@media (max-width: 900px)': {
      display: 'none',
    },
  },
  searchBar: {
    '@media (max-width: 900px)': {
      display: 'none',
    },
    // flex: 1,
  },
  link: {
    maxWidth: 700,
    color: '#999',
    textDecoration: 'none',
    fontWeight: 'bold',
    transition: '.2s opacity ease',
    ':hover': {
      opacity: 0.6,
    },
  },
  topLink: {
    color: '#fff',
    fontSize: '20px',
  },
  activeLink: {
    color: '#000',
  },
  feedDescription: {
    left: 25,
    fontSize: 20,
  },
  numMembers: {
    fontSize: 14,
    textDecoration: 'underline',
  },
});

function search(list, query) { // Will return the array in sorted order
  const options = {
    shouldSort: true,
    threshold: 0.5,
    location: 0,
    distance: 100,
    maxPatternLength: 32,
    minMatchCharLength: 1,
    keys: [
      'info.title',
      'info.domain',
      'annotations[0]',
    ],
  };

  const fuse = new Fuse(list, options);
  const result = fuse.search(query);

  return result;
}

class TopNav extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: 2,
      subscribed: false,
    };
    this.handleSubscribeClick = this.handleSubscribeClick.bind(this);
  }

  handleChange = (event, index, value) => this.setState({ value });

  handleSubscribeClick() {
    this.setState({ subscribed: !this.state.subscribed });
  }

  handleChange = (event, index, value) => this.setState({ value });

  executeSearch = (props) => {
    const textfield = document.getElementById('Search');
    const searchResults = search(this.props.data, textfield.value);
    const searchIsEmpty = textfield.value.length === 0;
    this.props.dispatch({ type: 'EXECUTE_SEARCH', search: searchResults, searchIsEmpty });
  }

  render() {
    const subButton = null;

    /* if (window.location.href.includes('feed')) {
      if (this.state.subscribed) { // Check if subscribed to group
        subButton = <RaisedButton label="unsubscribe" onClick={this.handleSubscribeClick} backgroundColor={red700} />;
      } else {
        subButton = <RaisedButton label="subscribe" onClick={this.handleSubscribeClick} backgroundColor={yellow400} labelColor={grey900} />;
      }
    }*/

    /*
      when not yet response from button click, use
      import RefreshIndicator from 'material-ui/lib/refresh-indicator';
    <RefreshIndicator
     size={40}
     left={10}
     top={0}
     status="loading"
     style={style.refresh}
   />
      */

    /* eslint-disable */

    let annotations = [];
    let groupId = '0';
    if (window.location.href.includes("feed")) {
      groupId = window.location.href.substr(window.location.href.lastIndexOf('/') + 1);
    }
    const isFeedView = window.location.href.includes('feed');
    const isFeedOrDiscussionView = isFeedView || window.location.href.includes('discussion');
    let allComments = [];
    if (isFeedOrDiscussionView) {
      annotations = isFeedView  ? this.props.annotationsF : this.props.annotationsD;
      for (let i = 0; i < annotations.length; i += 1) {
        const order = dfsTraversal(annotations[i], () => {

        });
        allComments = allComments.concat(order);
      }
    }

    const totalAuthorNumber = getTotalAuthorNumber(allComments, groupId);
    const totalNumberOfAnnotations = allComments.length; // This could go on the articleCard
    const shouldLoadMembers = (totalAuthorNumber !== 0 && isFeedOrDiscussionView);

    let feedName;

    if (this.props.feedDescription) {
      feedName = {
        fontSize: 33,
        paddingRight: 15,
      };
    } else {
      feedName = {
        fontSize: 41,
        paddingRight: 15,
      };
    }

    return (
      <MuiThemeProvider muiTheme={muiTheme}>
        <Toolbar style={{ position: 'fixed', zIndex: 200, height: 90, top: 0, left: 0, width: '100%', color: white, fontFamily: 'Roboto, sans-serif', boxShadow: '0 2px 4px rgba(0,0,0,0.15), 0 2px 4px rgba(0,0,0,0.22)' }}>

{this.props.currentFeedName || Notist}

{subButton}

                { shouldLoadMembers ? <PeopleIcon /> : '' }

{shouldLoadMembers ? (totalAuthorNumber.toString().concat(totalAuthorNumber === 1 ? ' member' : ' members')) : ''}

{this.props.feedDescription}

              Found any bugs?

                  <IconButton tooltip="Submit a bug report">
                    <BugReport color={white} hoverColor={grey300} />
                  </IconButton>

            {isFeedView ?

                <TextField id="Search" floatingLabelText="Search" onChange={this.executeSearch} />

 : ''}

              <a className={css(styles.link, styles.topLink)}
                href={`${config.apiHost}/logout`}
              >Logout

        </Toolbar>
      </MuiThemeProvider>
    );
  }
}

/* eslint-enable */

const mapStateToProps = state => ({
  data: state.articles ? state.articles.data : [],
  annotationsD: state.Discussion ? state.Discussion.annotations : [],
  annotationsF: state.articles ? state.articles.annotations : [],
});

export default connect(mapStateToProps)(TopNav);

//
<PeopleIcon />

//

//   <Search />
//

//
{this.props.numFeedMembers || 0} members

//
{subButton}

//

//   <SettingsDialog />
//

//

//   <NotificationsDialog numNotifications={this.props.numNotifications || 0} />
//

<|endoftext|>


<commit_before>/**
 * AccountCreate component.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * WordPress dependencies
 */
import { __ } from '@wordpress/i18n';
import { useState, Fragment, useCallback } from '@wordpress/element';

/**
 * Internal dependencies
 */
import Button from '../../../components/button';
import ProgressBar from '../../../components/progress-bar';
import { trackEvent } from '../../../util';
import TimezoneSelect from './timzezone-select';
import AccountField from './account-field';
import PropertyField from './property-field';
import ProfileField from './profile-field';
import { STORE_NAME } from '../datastore/constants';

import Data from 'googlesitekit-data';
const { useDispatch, useSelect } = Data;

const AccountCreate = () => {
	const isDoingCreateAccount = useSelect(
		( select ) => {
			return select( STORE_NAME ).isDoingCreateAccount();
		},
		[]
	);
	const accountTicketTermsOfServiceURL = useSelect(
		( select ) => {
			return select( STORE_NAME ).getAccountTicketTermsOfServiceURL();
		},
		[]
	);

	const { createAccount } = useDispatch( STORE_NAME );

	// Redirect if the accountTicketTermsOfServiceURL is set.
	if ( accountTicketTermsOfServiceURL ) {
		location = accountTicketTermsOfServiceURL;
	}

	const { siteName, siteURL, timezone: tz } = global.googlesitekit.admin;

	const handleSubmit = useCallback( ( accountName, propertyName, profileName, timezone ) => {
		trackEvent( 'analytics_setup', 'new_account_setup_clicked' );
		createAccount( {
			accountName,
			propertyName,
			profileName,
			timezone,
		} ).then( () => {
			// Log error message?
		} );
	} );

	const [ accountName, setAccountName ] = useState( siteName );
	const [ propertyName, setPropertyName ] = useState( siteURL );
	const [ profileName, setProfileName ] = useState( __( 'All website traffic', 'google-site-kit' ) );
	const [ timezone, setTimezone ] = useState( tz );
	const [ validationIssues, setValidationIssues ] = useState( {
		accountName: accountName === '',
		propertyName: propertyName === '',
		profileName: profileName === '',
		timezone: timezone === '',
	} );

	// Disable the submit button if there are validation errors, and while submission is in progress.
	const buttonDisabled = validationIssues.accountName || validationIssues.propertyName || validationIssues.profileName || validationIssues.timezone || isDoingCreateAccount;

	return (
		<Fragment>

							{ __( 'Create new Analytics account', 'google-site-kit' ) }

							{
								isDoingCreateAccount
									? <ProgressBar />
									:

											{ __( 'Confirm your account details:', 'google-site-kit' ) }

												<AccountField
													validationIssues={ validationIssues }
													setValidationIssues={ setValidationIssues }
													accountName={ accountName }
													setAccountName={ setAccountName }
												/>

												<PropertyField
													validationIssues={ validationIssues }
													setValidationIssues={ setValidationIssues }
													propertyName={ propertyName }
													setPropertyName={ setPropertyName }
												/>

												<ProfileField
													validationIssues={ validationIssues }
													setValidationIssues={ setValidationIssues }
													profileName={ profileName }
													setProfileName={ setProfileName }
												/>

												<TimezoneSelect
													validationIssues={ validationIssues }
													setValidationIssues={ setValidationIssues }
													timezone={ timezone }
													setTimezone={ setTimezone }
												/>

							}

							<Button
								disabled={ buttonDisabled }
								onClick={ () => {
									handleSubmit( accountName, propertyName, profileName, timezone );
								} }
							>
								{ __( 'Create Account', 'google-site-kit' ) }
							</Button>

		</Fragment>
	);
};

export default AccountCreate;
<commit_msg>Track isSubmitting in state.<commit_after>/**
 * AccountCreate component.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * WordPress dependencies
 */
import { __ } from '@wordpress/i18n';
import { useState, Fragment, useCallback } from '@wordpress/element';

/**
 * Internal dependencies
 */
import Button from '../../../components/button';
import ProgressBar from '../../../components/progress-bar';
import { trackEvent } from '../../../util';
import TimezoneSelect from './timzezone-select';
import AccountField from './account-field';
import PropertyField from './property-field';
import ProfileField from './profile-field';
import { STORE_NAME } from '../datastore/constants';

import Data from 'googlesitekit-data';
const { useDispatch, useSelect } = Data;

const AccountCreate = () => {
	const isDoingCreateAccount = useSelect(
		( select ) => {
			return select( STORE_NAME ).isDoingCreateAccount();
		},
		[]
	);
	const accountTicketTermsOfServiceURL = useSelect(
		( select ) => {
			return select( STORE_NAME ).getAccountTicketTermsOfServiceURL();
		},
		[]
	);

	const { createAccount } = useDispatch( STORE_NAME );

	// Redirect if the accountTicketTermsOfServiceURL is set.
	if ( accountTicketTermsOfServiceURL ) {
		location = accountTicketTermsOfServiceURL;
	}

	const { siteName, siteURL, timezone: tz } = global.googlesitekit.admin;

	const handleSubmit = useCallback( ( accountName, propertyName, profileName, timezone ) => {
		trackEvent( 'analytics_setup', 'new_account_setup_clicked' );
		setIsSubmitting( true );
		createAccount( {
			accountName,
			propertyName,
			profileName,
			timezone,
		} ).then( () => {
			// Log error message?
			setIsSubmitting( false );
		} );
	} );

	const [ isSubmitting, setIsSubmitting ] = useState( false );
	const [ accountName, setAccountName ] = useState( siteName );
	const [ propertyName, setPropertyName ] = useState( siteURL );
	const [ profileName, setProfileName ] = useState( __( 'All website traffic', 'google-site-kit' ) );
	const [ timezone, setTimezone ] = useState( tz );
	const [ validationIssues, setValidationIssues ] = useState( {
		accountName: accountName === '',
		propertyName: propertyName === '',
		profileName: profileName === '',
		timezone: timezone === '',
	} );

	// Disable the submit button if there are validation errors, and while submission is in progress.
	const buttonDisabled = validationIssues.accountName || validationIssues.propertyName || validationIssues.profileName || validationIssues.timezone || isDoingCreateAccount || isSubmitting;

	return (
		<Fragment>

							{ __( 'Create new Analytics account', 'google-site-kit' ) }

							{
								isDoingCreateAccount
									? <ProgressBar />
									:

											{ __( 'Confirm your account details:', 'google-site-kit' ) }

												<AccountField
													validationIssues={ validationIssues }
													setValidationIssues={ setValidationIssues }
													accountName={ accountName }
													setAccountName={ setAccountName }
												/>

												<PropertyField
													validationIssues={ validationIssues }
													setValidationIssues={ setValidationIssues }
													propertyName={ propertyName }
													setPropertyName={ setPropertyName }
												/>

												<ProfileField
													validationIssues={ validationIssues }
													setValidationIssues={ setValidationIssues }
													profileName={ profileName }
													setProfileName={ setProfileName }
												/>

												<TimezoneSelect
													validationIssues={ validationIssues }
													setValidationIssues={ setValidationIssues }
													timezone={ timezone }
													setTimezone={ setTimezone }
												/>

							}

							<Button
								disabled={ buttonDisabled }
								onClick={ () => {
									handleSubmit( accountName, propertyName, profileName, timezone );
								} }
							>
								{ __( 'Create Account', 'google-site-kit' ) }
							</Button>

		</Fragment>
	);
};

export default AccountCreate;
<|endoftext|>


<commit_before>/*global module:false*/
module.exports = function(grunt) {

  grunt.initConfig({
    shell: {
      options: {
        stdout: true
      },
      jekyll: {
        command: 'jekyll build'
      },
      inuit: {
        command: 'compass compile .'
      }
    },
    watch: {
      jekyll: {
        files: [
          '_posts/**/*.md',
          '_layouts/**/*.html'
        ],
        tasks: [
          'shell:jekyll'
        ]
      },
      inuit: {
        files: [
          'css/**/*.scss'
        ],
        tasks: [
          'shell:inuit'
        ]
      }
    }
  });

  grunt.loadNpmTasks('grunt-shell');
  grunt.loadNpmTasks('grunt-contrib-watch');

  grunt.registerTask('default', [
    'watch'
  ]);

};
<commit_msg>Run Inuit before Jekyll so that CSS is generated before Jekyll builds.<commit_after>/*global module:false*/
module.exports = function(grunt) {

  grunt.initConfig({
    shell: {
      options: {
        stdout: true
      },
      inuit: {
        command: 'compass compile .'
      },
      jekyll: {
        command: 'jekyll build'
      }
    },
    watch: {
      inuit: {
        files: [
          'css/**/*.scss'
        ],
        tasks: [
          'shell:inuit'
        ]
      },
      jekyll: {
        files: [
          '_posts/**/*.md',
          '_layouts/**/*.html'
        ],
        tasks: [
          'shell:jekyll'
        ]
      }
    }
  });

  grunt.loadNpmTasks('grunt-shell');
  grunt.loadNpmTasks('grunt-contrib-watch');

  grunt.registerTask('default', [
    'watch'
  ]);

};
<|endoftext|>


<commit_before>'use strict'

const async = require('async');

module.exports = function (mongoose, config, db) {
    const utils = require('../lib/utils')();

    function articleResponse(article, owner, list) {
        const user = article.user;
        return {
            id: article.id,
            created_at: article.created_at,
            title: article.title,
            user_id: article.user.id,
            user: {
                id: user.id,
                name: user.name,
                email: user.email
            },
            keywords: article.keywords,
            preview: article.preview,
            content: list ? undefined : article.content,
            draft: article.draft,
            category: article.category
        }
    }

    function commentResponse(comment, owner) {
        const user = comment.user;
        return {
            id: comment.id,
            user: {
                id: user.id,
                name: user.name,
                email: user.email
            },
            created_at: comment.created_at,
            content: comment.content,
            ref_id: comment.ref_id,
            hide: comment.hide
        }
    }

    function keywords(req, res, is_public) {
        let query = {};
        if (is_public || !req.user.isSuperUser()) {
            query = { draft: false };
        }
        db.Article.find(query).select('keywords').exec((err, dbResponse) => {
            if (err) return utils.error(res, 422, err.message);
            let map = {};
            async.each(dbResponse, (article, cb_master) => {
                async.each(article.keywords, (value, cb) => {
                    map[value] = (map[value] ? map[value] : 0) + 1;
                    cb();
                }, cb_master);
            }, err => err ? utils.error(res, 422, err.message) : utils.responseData(res, map.count, map));
        })
    }

    return {
        _checkBlogPermission() {
            return (req, res, next) => {
                if (req.user.isSuperUser()) return next();
                utils.error(res, 403);
            }
        },

        _getArticle(req, res, next) {
            db.Article.findOne({ id: req.params.articleId })
                .populate('user')
                .populate('comments.user').exec((err, article) => {
                    if (err) return utils.error(res, 422, err);
                    if (!article) return utils.error(res, 404);
                    req.article = article;
                    next();
                });
        },

        create(req, res) {
            db.Sequence.getNextSequence("articles", (err, nextArticleId) => {
                if (err) return utils.error(res, 422, err);
                let keywords = req.body.keywords || req.body.title;
                if (!keywords || !keywords.length) keywords = undefined;
                console.log(keywords);
                const article = db.Article({
                    id: nextArticleId,
                    title: req.body.title,
                    user: req.user,
                    preview: req.body.preview,
                    content: req.body.content,
                    keywords: keywords,
                    draft: !!req.body.draft
                });
                db.Category.setCategory(req.body.category, article, () => article.save(err => err ?
                    db.Sequence.SequenceRollback("articles", utils.error(res, 422, err.message)) :
                    utils.success(res, "create success!")));
            })
        },

        delete(req, res) {
            if (!req.user.isSuperUser() && req.article.user.id != req.user.id) return utils.error(res, 403);
            req.article.remove(err => {
                if (err) return utils.error(res, 422);
                db.Category.tryToRemoveCategory(req.article.category);
                utils.success(res, "delete successful!");
            })
        },

        update(req, res) {
            if (!req.user.isSuperUser() && req.article.user.id != req.user.id) return utils.error(res, 403);

            let old_category = req.article.category;

            if (typeof req.body.title == 'string') req.article.title = req.body.title;
            if (typeof req.body.preview == 'string') req.article.preview = req.body.preview;
            if (typeof req.body.content == 'string') req.article.content = req.body.content;
            if (req.body.keywords instanceof Array) req.article.keywords = req.body.keywords;
            if (typeof req.body.draft == 'boolean') req.article.draft = !!req.body.draft;
            if (typeof req.body.category == 'string' && req.article.category != req.body.category) {
                req.article.category = req.body.category;
            }

            req.article.save((err, article) => {
                if (err) return utils.error(res, 422, err.message);
                if (req.body.category && req.article.category != old_category) {
                    db.Category.tryToRemoveCategory(old_category);
                    db.Category.setCategory(req.body.category, article, () =>
                        utils.responseData(res, "update success!", articleResponse(article)));
                } else {
                    utils.responseData(res, 'update successful!', articleResponse(article));
                }
            });
        },

        get(req, res) {
            const id = req.params.articleId;
            db.Article.findOne({ id }).populate("user").exec((err, article) => {
                if (err) return utils.error(res, 422, err.message);
                if (!article) return utils.error(res, 404);
                if (article.draft && !(req.user && (req.user.isSuperUser() || req.user.id == article.user.id)))
                    return utils.error(res, 422, err.message);
                utils.responseData(res, article.id, articleResponse(article,
                    req.user && (req.user.isSuperUser() || req.user.id == article.user.id)));
            })
        },

        find(req, res) {
            let query = {};
            let sortBy = null;

            sortBy = {};
            sortBy["order_by"] = "created_at";
            sortBy["order_type"] = "desc";
            if (typeof req.query.title == 'string') query["title"] = new RegExp(req.query.title, "i");
            if (typeof req.query.content == 'string') query["content"] = new RegExp(req.query.content, "i");
            if (typeof req.query.user_name == 'string') query["user.name"] = req.query.user_name;
            if (req.query.keywords instanceof Array) query["keywords"] = { $in: req.query.keywords };
            if (typeof req.query.category == 'string') query["category"] = req.query.category;
            if (req.query.order_type === "asc") sortBy["order_type"] = req.query.order_type;
            if (typeof req.query.draft == 'boolean') query["draft"] = req.query.draft;

            const queryRequest = db.Article.find(query);
            if (sortBy) {
                var sort = {};
                sort[sortBy["order_by"]] = sortBy["order_type"] === "asc" ? "ascending" : "descending";
                queryRequest.sort(sort);
            }

            queryRequest.populate("user").exec((err, dbResponse) => {
                if (err) return utils.error(res, 422, err.message);
                if (!dbResponse) return utils.error(res, 404);
                let ret = dbResponse
                    .filter(article => !article.draft || (req.user && (req.user.isSuperUser() || req.user.id == article.user.id)))
                    .map(article =>
                        articleResponse(article, req.user && (req.user.isSuperUser() || req.user.id == article.user.id), true));
                utils.responseData(res, ret.count, ret);
            })
        },

        keywordsPublic(req, res) {
            keywords(req, res, true);
        },

        keywords,

        setupPublic(Router) {
            const articleRouter = new Router();

            articleRouter.param('articleId', utils.requiredParams('articleId'));
            articleRouter.param('commentId', utils.requiredParams('commentId'));
            articleRouter.use('/article/:articleId', this._getArticle);
            articleRouter.use('/article/:articleId/comment/:commentId', this.Comment._getComment);

            articleRouter.get('/article', this.find);
            articleRouter.get('/article/:articleId', this.get);
            articleRouter.get('/article/:articleId/comment', this.Comment.getAll);
            articleRouter.get('/article/:articleId/comment/:commentId', this.Comment.getSingle);

            articleRouter.get('/keywords', this.keywordsPublic);

            return articleRouter;
        },

        setup(Router) {
            const articleRouter = new Router();

            articleRouter.param('articleId', utils.requiredParams('articleId'));
            articleRouter.param('commentId', utils.requiredParams('commentId'));
            articleRouter.use('/article/:articleId', this._getArticle);
            articleRouter.use('/article/:articleId/comment/:commentId', this.Comment._getComment);

            articleRouter.route('/article')
                .get(this.find)
                .post(utils.requiredFields('title category content'), utils.checkSuperUser, this.create);
            articleRouter.route('/article/:articleId')
                .get(this.get)
                .put(this.update)
                .delete(utils.checkSuperUser, this.delete);
            articleRouter.route('/article/:articleId/comment')
                .get(this.Comment.getAll)
                .post(this.Comment.create);
            articleRouter.route('/article/:articleId/comment/:commentId')
                .get(this.Comment.getSingle)
                .post(this.Comment.create)
                .delete(this.Comment.delete)
                .put(utils.checkSuperUser, this.Comment.changeHideState);

            articleRouter.get('/keywords', this.keywords);

            return articleRouter;
        },
        Comment: {
            _getComment(req, res, next) {
                let comment = req.article.comments.id(req.params.commentId);
                if (!comment) return utils.error(res, 404);
                req.comment = comment;
                next();
            },

            getAll(req, res) {
                // Only bloggers and commentators can see the hidden comments
                let ret = req.user && (req.user.isSuperUser() || req.article.user.id == req.user.id) ? req.article.comments
                    : req.article.comments.filter(comment => !comment.hide || (req.user && comment.user.id == req.user.id))
                utils.responseData(res, ret.count, ret.map(comment => commentResponse(comment)));
            },

            getSingle(req, res) {
                if (!req.user.isSuperUser()
                    && req.comment.hide
                    && (!req.user || req.article.user.id != req.user.id) // Logged, but not blogger
                    && req.comment.user.id != req.user.id) // Nor is commentator
                    return utils.error(res, 404); // Pretend it does not exist
                utils.responseData(res, "", commentResponse(req.comment));
            },

            create(req, res) {
                if (!req.article.comments) req.article.comments = [];
                if (req.params.commentId
                    && !req.user.isSuperUser()
                    && req.comment.hide
                    && (!req.user || req.article.user.id != req.user.id)
                    && req.comment.user.id != req.user.id)
                    return utils.error(res, 404);
                req.article.comments.push({
                    user: req.user,
                    content: req.body.content,
                    ref_id: req.params.commentId ? req.params.commentId : undefined,
                    hide: !req.user.isSuperUser()
                });
                req.article.save(err => err ? utils.error(res, 422, err.message)
                    : utils.responseData(res, "post successful"));
            },

            changeHideState(req, res) {
                if (!req.user.isSuperUser() && (!req.user || req.article.user.id != req.user.id))
                    return utils.error(res, 404); // Pretend it does not exist
                db.Article.update({_id: req.article._id, 'comments._id': req.comment.id}, {'$set': {
                    'comments.$.hide': !!req.body.hide
                }}, err => err ? utils.error(res, 422, err.message)
                    : utils.success(res, "put successful"));
            },

            delete(req, res) {
                if (!req.user.isSuperUser()
                    && req.comment.hide
                    && (!req.user || req.article.user.id != req.user.id) // Logged, but not blogger
                    && req.comment.user.id != req.user.id) // Nor is commentator
                    return utils.error(res, 404);
                req.comment.remove();
                req.article.save(err => err ? utils.error(res, 422, err.message)
                    : utils.responseData(res, "delete successful"));
            }
        }
    }
}<commit_msg>解决异常退出的问题<commit_after>'use strict'

const async = require('async');

module.exports = function (mongoose, config, db) {
    const utils = require('../lib/utils')();

    function articleResponse(article, owner, list) {
        const user = article.user;
        return {
            id: article.id,
            created_at: article.created_at,
            title: article.title,
            user_id: article.user.id,
            user: {
                id: user.id,
                name: user.name,
                email: user.email
            },
            keywords: article.keywords,
            preview: article.preview,
            content: list ? undefined : article.content,
            draft: article.draft,
            category: article.category
        }
    }

    function commentResponse(comment, owner) {
        const user = comment.user;
        return {
            id: comment.id,
            user: {
                id: user.id,
                name: user.name,
                email: user.email
            },
            created_at: comment.created_at,
            content: comment.content,
            ref_id: comment.ref_id,
            hide: comment.hide
        }
    }

    function keywords(req, res, is_public) {
        let query = {};
        if (is_public || !req.user.isSuperUser()) {
            query = { draft: false };
        }
        db.Article.find(query).select('keywords').exec((err, dbResponse) => {
            if (err) return utils.error(res, 422, err.message);
            let map = {};
            async.each(dbResponse, (article, cb_master) => {
                async.each(article.keywords, (value, cb) => {
                    map[value] = (map[value] ? map[value] : 0) + 1;
                    cb();
                }, cb_master);
            }, err => err ? utils.error(res, 422, err.message) : utils.responseData(res, map.count, map));
        })
    }

    return {
        _checkBlogPermission() {
            return (req, res, next) => {
                if (req.user.isSuperUser()) return next();
                utils.error(res, 403);
            }
        },

        _getArticle(req, res, next) {
            db.Article.findOne({ id: req.params.articleId })
                .populate('user')
                .populate('comments.user').exec((err, article) => {
                    if (err) return utils.error(res, 422, err);
                    if (!article) return utils.error(res, 404);
                    req.article = article;
                    next();
                });
        },

        create(req, res) {
            db.Sequence.getNextSequence("articles", (err, nextArticleId) => {
                if (err) return utils.error(res, 422, err);
                let keywords = req.body.keywords || req.body.title;
                if (!keywords || !keywords.length) keywords = undefined;
                console.log(keywords);
                const article = db.Article({
                    id: nextArticleId,
                    title: req.body.title,
                    user: req.user,
                    preview: req.body.preview,
                    content: req.body.content,
                    keywords: keywords,
                    draft: !!req.body.draft
                });
                db.Category.setCategory(req.body.category, article, () => article.save(err => err ?
                    db.Sequence.SequenceRollback("articles", utils.error(res, 422, err.message)) :
                    utils.success(res, "create success!")));
            })
        },

        delete(req, res) {
            if (!req.user.isSuperUser() && req.article.user.id != req.user.id) return utils.error(res, 403);
            req.article.remove(err => {
                if (err) return utils.error(res, 422);
                db.Category.tryToRemoveCategory(req.article.category);
                utils.success(res, "delete successful!");
            })
        },

        update(req, res) {
            if (!req.user.isSuperUser() && req.article.user.id != req.user.id) return utils.error(res, 403);

            let old_category = req.article.category;

            if (typeof req.body.title == 'string') req.article.title = req.body.title;
            if (typeof req.body.preview == 'string') req.article.preview = req.body.preview;
            if (typeof req.body.content == 'string') req.article.content = req.body.content;
            if (req.body.keywords instanceof Array) req.article.keywords = req.body.keywords;
            if (typeof req.body.draft == 'boolean') req.article.draft = !!req.body.draft;
            if (typeof req.body.category == 'string' && req.article.category != req.body.category) {
                req.article.category = req.body.category;
            }

            req.article.save((err, article) => {
                if (err) return utils.error(res, 422, err);
                if (req.body.category && req.article.category != old_category) {
                    db.Category.tryToRemoveCategory(old_category);
                    db.Category.setCategory(req.body.category, article, () =>
                        utils.responseData(res, "update success!", articleResponse(article)));
                } else {
                    utils.responseData(res, 'update successful!', articleResponse(article));
                }
            });
        },

        get(req, res) {
            const id = req.params.articleId;
            db.Article.findOne({ id }).populate("user").exec((err, article) => {
                if (err) return utils.error(res, 422, err.message);
                if (!article) return utils.error(res, 404);
                if (article.draft && !(req.user && (req.user.isSuperUser() || req.user.id == article.user.id)))
                    return utils.error(res, 422, err.message);
                utils.responseData(res, article.id, articleResponse(article,
                    req.user && (req.user.isSuperUser() || req.user.id == article.user.id)));
            })
        },

        find(req, res) {
            let query = {};
            let sortBy = null;

            sortBy = {};
            sortBy["order_by"] = "created_at";
            sortBy["order_type"] = "desc";
            if (typeof req.query.title == 'string') query["title"] = new RegExp(req.query.title, "i");
            if (typeof req.query.content == 'string') query["content"] = new RegExp(req.query.content, "i");
            if (typeof req.query.user_name == 'string') query["user.name"] = req.query.user_name;
            if (req.query.keywords instanceof Array) query["keywords"] = { $in: req.query.keywords };
            if (typeof req.query.category == 'string') query["category"] = req.query.category;
            if (req.query.order_type === "asc") sortBy["order_type"] = req.query.order_type;
            if (typeof req.query.draft == 'boolean') query["draft"] = req.query.draft;

            const queryRequest = db.Article.find(query);
            if (sortBy) {
                var sort = {};
                sort[sortBy["order_by"]] = sortBy["order_type"] === "asc" ? "ascending" : "descending";
                queryRequest.sort(sort);
            }

            queryRequest.populate("user").exec((err, dbResponse) => {
                if (err) return utils.error(res, 422, err.message);
                if (!dbResponse) return utils.error(res, 404);
                let ret = dbResponse
                    .filter(article => !article.draft || (req.user && (req.user.isSuperUser() || req.user.id == article.user.id)))
                    .map(article =>
                        articleResponse(article, req.user && (req.user.isSuperUser() || req.user.id == article.user.id), true));
                utils.responseData(res, ret.count, ret);
            })
        },

        keywordsPublic(req, res) {
            keywords(req, res, true);
        },

        keywords,

        setupPublic(Router) {
            const articleRouter = new Router();

            articleRouter.param('articleId', utils.requiredParams('articleId'));
            articleRouter.param('commentId', utils.requiredParams('commentId'));
            articleRouter.use('/article/:articleId', this._getArticle);
            articleRouter.use('/article/:articleId/comment/:commentId', this.Comment._getComment);

            articleRouter.get('/article', this.find);
            articleRouter.get('/article/:articleId', this.get);
            articleRouter.get('/article/:articleId/comment', this.Comment.getAll);
            articleRouter.get('/article/:articleId/comment/:commentId', this.Comment.getSingle);

            articleRouter.get('/keywords', this.keywordsPublic);

            return articleRouter;
        },

        setup(Router) {
            const articleRouter = new Router();

            articleRouter.param('articleId', utils.requiredParams('articleId'));
            articleRouter.param('commentId', utils.requiredParams('commentId'));
            articleRouter.use('/article/:articleId', this._getArticle);
            articleRouter.use('/article/:articleId/comment/:commentId', this.Comment._getComment);

            articleRouter.route('/article')
                .get(this.find)
                .post(utils.requiredFields('title category content'), utils.checkSuperUser, this.create);
            articleRouter.route('/article/:articleId')
                .get(this.get)
                .put(this.update)
                .delete(utils.checkSuperUser, this.delete);
            articleRouter.route('/article/:articleId/comment')
                .get(this.Comment.getAll)
                .post(this.Comment.create);
            articleRouter.route('/article/:articleId/comment/:commentId')
                .get(this.Comment.getSingle)
                .post(this.Comment.create)
                .delete(this.Comment.delete)
                .put(utils.checkSuperUser, this.Comment.changeHideState);

            articleRouter.get('/keywords', this.keywords);

            return articleRouter;
        },
        Comment: {
            _getComment(req, res, next) {
                let comment = req.article.comments.id(req.params.commentId);
                if (!comment) return utils.error(res, 404);
                req.comment = comment;
                next();
            },

            getAll(req, res) {
                // Only bloggers and commentators can see the hidden comments
                let ret = req.user && (req.user.isSuperUser() || req.article.user.id == req.user.id) ? req.article.comments
                    : req.article.comments.filter(comment => !comment.hide || (req.user && comment.user.id == req.user.id))
                utils.responseData(res, ret.count, ret.map(comment => commentResponse(comment)));
            },

            getSingle(req, res) {
                if (!req.user.isSuperUser()
                    && req.comment.hide
                    && (!req.user || req.article.user.id != req.user.id) // Logged, but not blogger
                    && req.comment.user.id != req.user.id) // Nor is commentator
                    return utils.error(res, 404); // Pretend it does not exist
                utils.responseData(res, "", commentResponse(req.comment));
            },

            create(req, res) {
                if (!req.article.comments) req.article.comments = [];
                if (req.params.commentId
                    && !req.user.isSuperUser()
                    && req.comment.hide
                    && (!req.user || req.article.user.id != req.user.id)
                    && req.comment.user.id != req.user.id)
                    return utils.error(res, 404);
                req.article.comments.push({
                    user: req.user,
                    content: req.body.content,
                    ref_id: req.params.commentId ? req.params.commentId : undefined,
                    hide: !req.user.isSuperUser()
                });
                req.article.save(err => err ? utils.error(res, 422, err.message)
                    : utils.responseData(res, "post successful"));
            },

            changeHideState(req, res) {
                if (!req.user.isSuperUser() && (!req.user || req.article.user.id != req.user.id))
                    return utils.error(res, 404); // Pretend it does not exist
                db.Article.update({_id: req.article._id, 'comments._id': req.comment.id}, {'$set': {
                    'comments.$.hide': !!req.body.hide
                }}, err => err ? utils.error(res, 422, err.message)
                    : utils.success(res, "put successful"));
            },

            delete(req, res) {
                if (!req.user.isSuperUser()
                    && req.comment.hide
                    && (!req.user || req.article.user.id != req.user.id) // Logged, but not blogger
                    && req.comment.user.id != req.user.id) // Nor is commentator
                    return utils.error(res, 404);
                req.comment.remove();
                req.article.save(err => err ? utils.error(res, 422, err.message)
                    : utils.responseData(res, "delete successful"));
            }
        }
    }
}
<|endoftext|>


<commit_before>var d3Chart = {};

/**
 * Create the D3 chart object
 */
d3Chart.create = function (el, props, state) {
  var svg = d3.select(el).append('svg')
      .attr('class', 'd3')
      .attr('width', props.width)
      .attr('height', props.height);

  // Add a group for the map
  svg.append('g')
      .attr('class', 'd3-map');

  // Add a group for the points
  svg.append('g')
      .attr('class', 'd3-points');

  this._drawMap(el, state);
};

/**
 * Update the D3 chart when the React props change
 */
d3Chart.update = function (el, state) {
  const mapPath = d3.select('path.states');
  mapPath.remove();
  this._drawMap(el, state);
};

d3Chart.destroy = function (el) {
  // Clean up chart
};

d3Chart._drawMap = function (el, state) {
  var places = state.places;
  var images = state.images;
  var settings = state.settings;

  // Set up map projection
  var projection = d3.geo.mercator()
      .scale(settings.mapScale)
      .center([settings.mapX, settings.mapY])
      .precision(0.1);

  // Draw county boundaries in dev mode
  if (Meteor.settings.public.dev == 'true') {
    drawCounties(el, projection);
  }

  _.each(places, function (place) {
    // Find the images for this location
    const placeImages = _.filter(images, function (o) {
      if (o.place == place.name) {
        return true;
      }
    });

    // Select a random image
    const placeImage = _.sample(placeImages);

    // Draw map dots, for each location
    drawDot(el, projection, place);

    // Draw the location labels and images
    drawPlace(el, projection, place, placeImage);
  });

};

/**
 * Draw individual location dot
 *
 * We draw this seperately since we don't want any other transformations to
 * move the lat/long based map dot.
 *
 * @param  {object} el Top level react DOM parent for our SVG elements
 * @param  {object} projection D3 map projection object
 * @param  {object} place Place object data from Meteor
 */
function drawDot(el, projection, place) {
  var placeGroup = d3.select(el).selectAll('.d3-points')
    .append('g')
    .classed('place-dot', true);
  placeGroup.append('circle')
    .attr('r', '10px')
    .classed('map-dot', true);
  translateX = projection([place.long, place.lat])[0];
  translateY = projection([place.long, place.lat])[1];
  placeGroup.attr('transform', 'translate(' + translateX + ',' + translateY + ')');
}

/**
 * Draw individual location label group
 *
 * Draw the text label, picture, and clickable box near each location dot
 *
 * @param  {object} el Top level react DOM parent for our SVG elements
 * @param  {object} projection D3 map projection object
 * @param  {object} place Place object data from Meteor
 */
function drawPlace(el, projection, place) {
  groupWidth = 300;
  groupHeight = 100;
  var placeGroup = d3.select(el).selectAll('.d3-points')
    .append('g')
    .classed('place-group', true);

  // Drop shadow
  placeGroup.append('defs')
    .append('filter')
    .attr('id', 'blur')
    .append('feGaussianBlur')
    .attr('stdDeviation', 5);
  placeGroup.append('rect')
    .attr('width', groupWidth)
    .attr('height', groupHeight)
    .attr('opacity', '1')
    .attr('x', 3)
    .attr('y', 3)
    .style('fill', '#000')
    .attr('filter', 'url(#blur)');

  // White box
  placeGroup.append('rect')
    .attr('width', groupWidth + 'px')
    .attr('height', groupHeight + 'px')
    .classed('map-box', true);

  // Label
  placeGroup.append('text')
    .text(place.name)
    .attr('x', '20px')
    .attr('y', '40px')
    .attr('font-family', 'national-medium')
    .attr('font-size', '40px')
    .attr('fill', 'black')
    .attr('stroke-width', 1.5)
    .attr('stroke', '#000');

  // Move group into position
  let translateX = projection([place.long, place.lat])[0] + place.offsetX;
  let translateY = projection([place.long, place.lat])[1] + place.offsetY;
  if (place.alignH == 'right') {
    translateX = translateX - groupWidth;
  }

  placeGroup.attr('transform', 'translate(' + translateX + ',' + translateY + ')');
}

function oldPlace() {

  var point = pointG.selectAll('dot')
  .data(places)
  .enter().append('circle')
  .attr('cx', function (d) { return projection([d.long, d.lat])[0]; })
  .attr('cy', function (d) { return projection([d.long, d.lat])[1]; })
  .attr('r', '8px')
  .attr('fill', 'red');

  var squares = pointG.selectAll('squares')
  .data(places)
  .enter().append('rect')
  .attr('x', function (d) {
    var x = projection([d.long, d.lat])[0];
    return x + d.offsetX;
  })
  .attr('y', function (d) {
    return 0;
  })
  .attr('width', '40px')
  .attr('height', '40px')
  .attr('fill', function (d) {
    if (d.color) {
      return d.color;
    } else {
      return 'orange';
    }
  });

}

/**
 * Draw county boundaries on map
 *
 * We use this in development to align the raster and vector layers.
 *
 * @param  {object} el Top level react DOM parent for our SVG elements
 * @param  {object} projection D3 map projection object
 */
function drawCounties(el, projection) {
  var path = d3.geo.path().projection(projection);
  var mapG = d3.select(el).selectAll('.d3-map');
  d3.json('/data/counties.json', function (error, counties) {
    mapG.append('path')
    .datum(topojson.feature(counties, counties.objects.counties))
    .attr('d', path)
    .attr('class', 'states');
  });
}

export default d3Chart;
<commit_msg>Apply images as background of the rectangles<commit_after>var d3Chart = {};

/**
 * Create the D3 chart object
 */
d3Chart.create = function (el, props, state) {
  var svg = d3.select(el).append('svg')
      .attr('class', 'd3')
      .attr('width', props.width)
      .attr('height', props.height);

  // Add a group for the map
  svg.append('g')
      .attr('class', 'd3-map');

  // Add a group for the points
  svg.append('g')
      .attr('class', 'd3-points');

  this._drawMap(el, state);
};

/**
 * Update the D3 chart when the React props change
 */
d3Chart.update = function (el, state) {
  const mapPath = d3.select('path.states');
  mapPath.remove();
  this._drawMap(el, state);
};

d3Chart.destroy = function (el) {
  // Clean up chart
};

d3Chart._drawMap = function (el, state) {
  var places = state.places;
  var images = state.images;
  var settings = state.settings;

  // Set up map projection
  var projection = d3.geo.mercator()
      .scale(settings.mapScale)
      .center([settings.mapX, settings.mapY])
      .precision(0.1);

  // Draw county boundaries in dev mode
  if (Meteor.settings.public.dev == 'true') {
    drawCounties(el, projection);
  }

  _.each(places, function (place) {
    // Find the images for this location
    const placeImages = _.filter(images, function (o) {
      if (o.place == place.name) {
        return true;
      }
    });

    // Select a random image
    const placeImage = _.sample(placeImages);

    // Draw map dots, for each location
    drawDot(el, projection, place);

    // Draw the location labels and images
    drawPlace(el, projection, place, placeImage);
  });

};

/**
 * Draw individual location dot
 *
 * We draw this seperately since we don't want any other transformations to
 * move the lat/long based map dot.
 *
 * @param  {object} el Top level react DOM parent for our SVG elements
 * @param  {object} projection D3 map projection object
 * @param  {object} place Place object data from Meteor
 */
function drawDot(el, projection, place) {
  var placeGroup = d3.select(el).selectAll('.d3-points')
    .append('g')
    .classed('place-dot', true);
  placeGroup.append('circle')
    .attr('r', '10px')
    .classed('map-dot', true);
  translateX = projection([place.long, place.lat])[0];
  translateY = projection([place.long, place.lat])[1];
  placeGroup.attr('transform', 'translate(' + translateX + ',' + translateY + ')');
}

/**
 * Draw individual location label group
 *
 * Draw the text label, picture, and clickable box near each location dot
 *
 * @param  {object} el Top level react DOM parent for our SVG elements
 * @param  {object} projection D3 map projection object
 * @param  {object} place Place object data from Meteor
 */
function drawPlace(el, projection, place, placeImage) {
  groupWidth = 300;
  groupHeight = 100;
  var placeGroup = d3.select(el).selectAll('.d3-points')
    .append('g')
    .classed('place-group', true);

  // Drop shadow
  placeGroup.append('defs')
    .append('filter')
    .attr('id', 'blur')
    .append('feGaussianBlur')
    .attr('stdDeviation', 5);
  placeGroup.append('rect')
    .attr('width', groupWidth)
    .attr('height', groupHeight)
    .attr('opacity', '1')
    .attr('x', 3)
    .attr('y', 3)
    .style('fill', '#000')
    .attr('filter', 'url(#blur)');

  var defs = placeGroup.append('svg:defs');

  defs.append('svg:pattern')
    .attr('id', placeImage.slug)
    .attr('width', 400)
    .attr('height', 200)
    .attr('patternUnits', 'userSpaceOnUse')
    .append('svg:image')
    .attr('xlink:href', '/images/collection/' + placeImage.filename)
    .attr('width', 400)
    .attr('height', 200)
    .attr('x', 0)
    .attr('y', 0);

  // White box
  var placeGroupRect = placeGroup.append('rect')
    .attr('width', groupWidth + 'px')
    .attr('height', groupHeight + 'px')
    // .classed('map-box', true)
    .attr('fill', 'url(#' + placeImage.slug + ')');

  // Apply the background image as a pattern for the rectangle

  // Label
  placeGroup.append('text')
    .text(place.name)
    .attr('x', '20px')
    .attr('y', '40px')
    .attr('font-family', 'national-medium')
    .attr('font-size', '40px')
    .attr('fill', 'black')
    .attr('stroke-width', 1.5)
    .attr('stroke', '#000');

  // Move group into position
  let translateX = projection([place.long, place.lat])[0] + place.offsetX;
  let translateY = projection([place.long, place.lat])[1] + place.offsetY;
  if (place.alignH == 'right') {
    translateX = translateX - groupWidth;
  }

  placeGroup.attr('transform', 'translate(' + translateX + ',' + translateY + ')');
}

function oldPlace() {

  var point = pointG.selectAll('dot')
  .data(places)
  .enter().append('circle')
  .attr('cx', function (d) { return projection([d.long, d.lat])[0]; })
  .attr('cy', function (d) { return projection([d.long, d.lat])[1]; })
  .attr('r', '8px')
  .attr('fill', 'red');

  var squares = pointG.selectAll('squares')
  .data(places)
  .enter().append('rect')
  .attr('x', function (d) {
    var x = projection([d.long, d.lat])[0];
    return x + d.offsetX;
  })
  .attr('y', function (d) {
    return 0;
  })
  .attr('width', '40px')
  .attr('height', '40px')
  .attr('fill', function (d) {
    if (d.color) {
      return d.color;
    } else {
      return 'orange';
    }
  });

}

/**
 * Draw county boundaries on map
 *
 * We use this in development to align the raster and vector layers.
 *
 * @param  {object} el Top level react DOM parent for our SVG elements
 * @param  {object} projection D3 map projection object
 */
function drawCounties(el, projection) {
  var path = d3.geo.path().projection(projection);
  var mapG = d3.select(el).selectAll('.d3-map');
  d3.json('/data/counties.json', function (error, counties) {
    mapG.append('path')
    .datum(topojson.feature(counties, counties.objects.counties))
    .attr('d', path)
    .attr('class', 'states');
  });
}

export default d3Chart;
<|endoftext|>


<commit_before>'use strict';

var gulp     = require('gulp'),
	stylish    = require('jshint-stylish'),
	path       = require('path'),
	connect    = require('connect'),
	http       = require('http'),
	open       = require('open'),
	tinylr     = require('tiny-lr'),
	livereload = tinylr(),
	fs         = require('fs'),
	chalk      = require('chalk'),
	args       = require('yargs').argv,
	browserSync = require('browser-sync'),
	runSequence = require('run-sequence'),
	map         = require('map-stream');

var gulpPlugins = require('gulp-load-plugins')();

var	SETTINGS = {
	livereloadPort: 35729,
	serverPort: 9000,
	src: {
		app: 'app/',
		css: 'app/css/',
		js: 'app/js/',
		templates: 'app/templates/',
		images: 'app/images/',
		fonts: 'app/fonts/',
		bower: 'bower_components/'
	},
	build: {
		app: 'build/',
		css: 'build/css/',
		js: 'build/js/',
		templates: 'build/templates/',
		images: 'build/images/',
		fonts: 'build/fonts/',
		bower: 'build/js/'
	},
	scss: 'scss/'
};

// jsHint Options.
var hintOptions = JSON.parse(fs.readFileSync('.jshintrc', 'utf8'));

// Flag for generating production code.
var isProduction = args.type === 'production';

/*============================================================
=                          Server (Live - Reload)            =
============================================================*/

gulp.task('local:server', function () {

	var middleware = [
		require('connect-livereload')({ port: SETTINGS.livereloadPort }),
		connect.static(SETTINGS.build.app),
		connect.directory(SETTINGS.build.app)
	],

	app = connect.apply(null, middleware),
	server = http.createServer(app);
	server
		.listen(SETTINGS.serverPort)
		.on('listening', function () {
			console.log('Started connect web server on http://localhost:' + SETTINGS.serverPort + '.');
			open('http://localhost:' + SETTINGS.serverPort);
		});
});

gulp.task('tinylr', function () {
	livereload.listen(SETTINGS.livereloadPort, function (err) {
		if (err) {
			return console.log(err);
		}
	});
});

gulp.task('server', ['local:server', 'tinylr'], function () {
	console.log('------------------>>>> firing server  <<<<-----------------------');
});

/*============================================================
=                          JS-HINT                          =
============================================================*/

gulp.task('js:hint', function () {

	console.log('-------------------------------------------------- JS - HINT');
	var stream = gulp.src([SETTINGS.src.js + 'app.js', '!' + SETTINGS.src.js + 'plugins/*.js', SETTINGS.src.js + '**/*.js', 'gulpfile.js'])
		.pipe(gulpPlugins.jshint(hintOptions))
		.pipe(gulpPlugins.jshint.reporter(stylish));
	return stream;
});

/*============================================================
=                          Concat                           =
============================================================*/

gulp.task('concat', ['concat:bower', 'concat:js', 'concat:css']);

gulp.task('concat:bower', function () {
	console.log('-------------------------------------------------- CONCAT :bower');

	var jsFilter = gulpPlugins.filter('**/*.js'),
		cssFilter = gulpPlugins.filter('**/*.css'),
		assetsFilter = gulpPlugins.filter(['!**/*.js', '!**/*.css', '!**/*.scss']);

	gulpPlugins.bowerFiles()
		.pipe(jsFilter)
		.pipe(gulpPlugins.concat('_bower.min.js'))
		.pipe(gulpPlugins.if(isProduction, gulpPlugins.uglify()))
		.pipe(gulp.dest(SETTINGS.build.bower))
		.pipe(jsFilter.restore())
		.pipe(cssFilter)
		.pipe(gulpPlugins.sass())
		.pipe(map(function(file, callback) {
			var relativePath = path.dirname(path.relative(path.resolve(SETTINGS.src.bower), file.path));

			// CSS path resolving
			// Taken from https://github.com/enyojs/enyo/blob/master/tools/minifier/minify.js
			var contents = file.contents.toString().replace(/url\([^)]*\)/g, function(match) {
				// find the url path, ignore quotes in url string
				var matches = /url\s*\(\s*(('([^']*)')|("([^"]*)")|([^'"]*))\s*\)/.exec(match),
					url = matches[3] || matches[5] || matches[6];

				// Don't modify data and http(s) urls
				if (/^data:/.test(url) || /^http(:?s)?:/.test(url)) {
					return "url(" + url + ")";
				}
				return "url(" + path.join(path.relative(SETTINGS.build.bower, SETTINGS.build.app), SETTINGS.build.bower, relativePath, url) + ")";
			});
			file.contents = new Buffer(contents);

			callback(null, file)
		}))
		.pipe(gulpPlugins.concat('_bower.css'))
		.pipe(gulp.dest(SETTINGS.build.bower))
		.pipe(cssFilter.restore())
		.pipe(assetsFilter)
		.pipe(gulp.dest(SETTINGS.build.bower))
		.pipe(assetsFilter.restore())
		.pipe(gulpPlugins.livereload(livereload));
});

gulp.task('concat:js', ['js:hint'], function () {

	console.log('-------------------------------------------------- CONCAT :js');
	gulp.src([SETTINGS.src.js + 'plugins/*.js', SETTINGS.src.js + 'app.js', SETTINGS.src.js + '*.js', SETTINGS.src.js + '**/*.js'])
	    .pipe(gulpPlugins.concat('all.js'))
	    .pipe(gulpPlugins.if(isProduction, gulpPlugins.uglify()))
	    .pipe(gulp.dest(SETTINGS.build.js))
	    .pipe(gulpPlugins.livereload(livereload));
});

gulp.task('convert:scss', function () {
	console.log('-------------------------------------------------- COVERT - scss');

    var stream = gulp.src(SETTINGS.src.css + 'application.scss')
       .pipe(gulpPlugins.sass({includePaths: [SETTINGS.src.css]}))
       .pipe(gulp.dest(SETTINGS.scss))
       .pipe(gulpPlugins.livereload(livereload));
    return stream;
});

gulp.task('concat:css', ['convert:scss'], function () {

	console.log('-------------------------------------------------- CONCAT :css ');
	gulp.src([SETTINGS.src.css + 'reset-api.css', SETTINGS.src.css + 'fonts.css', SETTINGS.scss + 'application.css', SETTINGS.src.css + '*.css'])
	    .pipe(gulpPlugins.concat('styles.css'))
	    .pipe(gulpPlugins.if(isProduction, gulpPlugins.minifyCss({keepSpecialComments: '*'})))
	    .pipe(gulp.dest(SETTINGS.build.css))
	    .pipe(gulpPlugins.livereload(livereload));
});

/*============================================================
=                          Minify				            =
============================================================*/

gulp.task('image:min', function () {
	gulp.src(SETTINGS.src.images + '**')
        .pipe(gulpPlugins.imagemin())
        .pipe(gulp.dest(SETTINGS.build.images))
        .pipe(gulpPlugins.livereload(livereload));
});

/*============================================================
=                           Copy                            =
============================================================*/

gulp.task('copy', ['copy:html', 'copy:images', 'copy:fonts', 'copy:html:root']);

gulp.task('copy:html', function () {

	console.log('-------------------------------------------------- COPY :html');
	gulp.src([SETTINGS.src.templates + '*.html', SETTINGS.src.templates + '**/*.html'])
		.pipe(gulpPlugins.if(isProduction, gulpPlugins.minifyHtml({comments: false, quotes: true, spare: true, empty: true, cdata: true})))
		.pipe(gulp.dest(SETTINGS.build.templates))
		.pipe(gulpPlugins.livereload(livereload));
});

gulp.task('copy:html:root', function () {

	console.log('-------------------------------------------------- COPY :html:root');
	gulp.src(SETTINGS.src.app + '*.html')
		.pipe(gulpPlugins.if(isProduction, gulpPlugins.minifyHtml({comments: false, quotes: true, spare: true, empty: true, cdata: true})))
		.pipe(gulp.dest(SETTINGS.build.app))
		.pipe(gulpPlugins.livereload(livereload));
});

gulp.task('copy:images', function () {

	console.log('-------------------------------------------------- COPY :images');
	gulp.src([SETTINGS.src.images + '*.*', SETTINGS.src.images + '**/*.*'])
		.pipe(gulp.dest(SETTINGS.build.images));
});

gulp.task('copy:fonts', function () {

	console.log('-------------------------------------------------- COPY :fonts');
	gulp.src([SETTINGS.src.fonts + '*', SETTINGS.src.fonts + '**/*'])
		.pipe(gulp.dest(SETTINGS.build.fonts));
});

/*=========================================================================================================
=												Watch

	Incase the watch fails due to limited number of watches available on your sysmtem, the execute this
	command on terminal

	$ echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
=========================================================================================================*/

gulp.task('watch', function () {

	console.log('watching all the files.....');

	var watchedFiles = [];

	watchedFiles.push(gulp.watch([SETTINGS.src.css + '*.css',  SETTINGS.src.css + '**/*.css'],  ['concat:css']));

	watchedFiles.push(gulp.watch([SETTINGS.src.css + '*.scss', SETTINGS.src.css + '**/*.scss'], ['concat:css']));

    watchedFiles.push(gulp.watch([SETTINGS.src.js + '*.js',    SETTINGS.src.js + '**/*.js'],    ['concat:js']));

    watchedFiles.push(gulp.watch([SETTINGS.src.app + '*.html'], ['copy:html:root']));

	watchedFiles.push(gulp.watch([SETTINGS.src.images + '*.*', SETTINGS.src.images + '**/*.*'], ['copy:images']));

	watchedFiles.push(gulp.watch([SETTINGS.src.fonts + '*.*',  SETTINGS.src.fonts + '**/*.*'],  ['copy:fonts']));

	watchedFiles.push(gulp.watch([SETTINGS.src.bower + '*.js', SETTINGS.src.bower + '**/*.js'], ['concat:bower']));

	watchedFiles.push(gulp.watch([SETTINGS.src.templates + '*.html', SETTINGS.src.templates + '**/*.html'], ['copy:html']));

	// Just to add log messages on Terminal, in case any file is changed
	var onChange = function (event) {
		if (event.type == 'deleted') {
			runSequence('clean');
			setTimeout(function () {
				runSequence('copy', 'concat', 'watch');
			}, 500);
		}
		console.log(chalk.red('-------------------------------------------------->>>> File ' + event.path + ' was ------->>>> ' + event.type));
	};

	watchedFiles.forEach(function (watchedFile) {
		watchedFile.on('change', onChange);
	});

});

/*============================================================
=                             Clean                          =
============================================================*/

var cleanFiles = function (files, logMessage) {
	console.log('-------------------------------------------------- CLEAN :' + logMessage);
	gulp.src(files, {read: false})
		.pipe(gulpPlugins.clean({force: true}));
};

gulp.task('clean', function () {
	cleanFiles([SETTINGS.build.app], 'all files');
});

gulp.task('clean:css', function () {
	cleanFiles([SETTINGS.build.css], 'css');
});

gulp.task('clean:js', function () {
	cleanFiles([SETTINGS.build.js], 'js');
});

gulp.task('clean:html', function () {
	cleanFiles([SETTINGS.build.templates], 'html');
});

gulp.task('clean:images', function () {
	cleanFiles([SETTINGS.build.images], 'images');
});

gulp.task('clean:fonts', function () {
	cleanFiles([SETTINGS.build.fonts + '*.*', SETTINGS.build.fonts + '**/*.*'], 'fonts');
});

gulp.task('clean:zip', function () {
	cleanFiles(['zip/**/*', '!zip/build-*.zip'], 'zip');
});

/*============================================================
=                             Zip                          =
============================================================*/

gulp.task('zip', function () {
    gulp.src([SETTINGS.build.app + '*', SETTINGS.build.app + '**/*'])
        .pipe(gulpPlugins.zip('build-' + new Date() + '.zip'))
        .pipe(gulp.dest('./zip/'));

    setTimeout(function () {
		runSequence('clean:zip');
    }, 500); // wait for file creation

});

/*============================================================
=                             Start                          =
============================================================*/

gulp.task('build', function () {
	console.log(chalk.blue('-------------------------------------------------- BUILD - Development Mode'));
	runSequence('copy', 'concat', 'watch');
});

gulp.task('build:prod', function () {
	console.log(chalk.blue('-------------------------------------------------- BUILD - Production Mode'));
	isProduction = true;
	runSequence('copy', 'concat', 'watch');
});

gulp.task('default', ['build', 'server']);

// Just in case you are too lazy to type: $ gulp --type production
gulp.task('prod', ['build:prod', 'server']);

/*============================================================
=                       Browser Sync                         =
============================================================*/

gulp.task('bs', function () {
    browserSync.init([SETTINGS.build.app + 'index.html', SETTINGS.build + 'templates/*.html', SETTINGS.build.css + '*css', SETTINGS.build.js + '*.js'], {
		proxy: {
            host: '127.0.0.1',
            port: SETTINGS.serverPort
        }
    });
});<commit_msg>fix jshint issues<commit_after>'use strict';

var gulp     = require('gulp'),
	stylish    = require('jshint-stylish'),
	path       = require('path'),
	connect    = require('connect'),
	http       = require('http'),
	open       = require('open'),
	tinylr     = require('tiny-lr'),
	livereload = tinylr(),
	fs         = require('fs'),
	chalk      = require('chalk'),
	args       = require('yargs').argv,
	browserSync = require('browser-sync'),
	runSequence = require('run-sequence'),
	map         = require('map-stream');

var gulpPlugins = require('gulp-load-plugins')();

var	SETTINGS = {
	livereloadPort: 35729,
	serverPort: 9000,
	src: {
		app: 'app/',
		css: 'app/css/',
		js: 'app/js/',
		templates: 'app/templates/',
		images: 'app/images/',
		fonts: 'app/fonts/',
		bower: 'bower_components/'
	},
	build: {
		app: 'build/',
		css: 'build/css/',
		js: 'build/js/',
		templates: 'build/templates/',
		images: 'build/images/',
		fonts: 'build/fonts/',
		bower: 'build/js/'
	},
	scss: 'scss/'
};

// jsHint Options.
var hintOptions = JSON.parse(fs.readFileSync('.jshintrc', 'utf8'));

// Flag for generating production code.
var isProduction = args.type === 'production';

/*============================================================
=                          Server (Live - Reload)            =
============================================================*/

gulp.task('local:server', function () {

	var middleware = [
		require('connect-livereload')({ port: SETTINGS.livereloadPort }),
		connect.static(SETTINGS.build.app),
		connect.directory(SETTINGS.build.app)
	],

	app = connect.apply(null, middleware),
	server = http.createServer(app);
	server
		.listen(SETTINGS.serverPort)
		.on('listening', function () {
			console.log('Started connect web server on http://localhost:' + SETTINGS.serverPort + '.');
			open('http://localhost:' + SETTINGS.serverPort);
		});
});

gulp.task('tinylr', function () {
	livereload.listen(SETTINGS.livereloadPort, function (err) {
		if (err) {
			return console.log(err);
		}
	});
});

gulp.task('server', ['local:server', 'tinylr'], function () {
	console.log('------------------>>>> firing server  <<<<-----------------------');
});

/*============================================================
=                          JS-HINT                          =
============================================================*/

gulp.task('js:hint', function () {

	console.log('-------------------------------------------------- JS - HINT');
	var stream = gulp.src([SETTINGS.src.js + 'app.js', '!' + SETTINGS.src.js + 'plugins/*.js', SETTINGS.src.js + '**/*.js', 'gulpfile.js'])
		.pipe(gulpPlugins.jshint(hintOptions))
		.pipe(gulpPlugins.jshint.reporter(stylish));
	return stream;
});

/*============================================================
=                          Concat                           =
============================================================*/

gulp.task('concat', ['concat:bower', 'concat:js', 'concat:css']);

gulp.task('concat:bower', function () {
	console.log('-------------------------------------------------- CONCAT :bower');

	var jsFilter = gulpPlugins.filter('**/*.js'),
		cssFilter = gulpPlugins.filter('**/*.css'),
		assetsFilter = gulpPlugins.filter(['!**/*.js', '!**/*.css', '!**/*.scss']);

	gulpPlugins.bowerFiles()
		.pipe(jsFilter)
		.pipe(gulpPlugins.concat('_bower.min.js'))
		.pipe(gulpPlugins.if(isProduction, gulpPlugins.uglify()))
		.pipe(gulp.dest(SETTINGS.build.bower))
		.pipe(jsFilter.restore())
		.pipe(cssFilter)
		.pipe(gulpPlugins.sass())
		.pipe(map(function (file, callback) {
			var relativePath = path.dirname(path.relative(path.resolve(SETTINGS.src.bower), file.path));

			// CSS path resolving
			// Taken from https://github.com/enyojs/enyo/blob/master/tools/minifier/minify.js
			var contents = file.contents.toString().replace(/url\([^)]*\)/g, function (match) {
				// find the url path, ignore quotes in url string
				var matches = /url\s*\(\s*(('([^']*)')|("([^"]*)")|([^'"]*))\s*\)/.exec(match),
					url = matches[3] || matches[5] || matches[6];

				// Don't modify data and http(s) urls
				if (/^data:/.test(url) || /^http(:?s)?:/.test(url)) {
					return 'url(' + url + ')';
				}
				return 'url(' + path.join(path.relative(SETTINGS.build.bower, SETTINGS.build.app), SETTINGS.build.bower, relativePath, url) + ')';
			});
			file.contents = new Buffer(contents);

			callback(null, file);
		}))
		.pipe(gulpPlugins.concat('_bower.css'))
		.pipe(gulp.dest(SETTINGS.build.bower))
		.pipe(cssFilter.restore())
		.pipe(assetsFilter)
		.pipe(gulp.dest(SETTINGS.build.bower))
		.pipe(assetsFilter.restore())
		.pipe(gulpPlugins.livereload(livereload));
});

gulp.task('concat:js', ['js:hint'], function () {

	console.log('-------------------------------------------------- CONCAT :js');
	gulp.src([SETTINGS.src.js + 'plugins/*.js', SETTINGS.src.js + 'app.js', SETTINGS.src.js + '*.js', SETTINGS.src.js + '**/*.js'])
	    .pipe(gulpPlugins.concat('all.js'))
	    .pipe(gulpPlugins.if(isProduction, gulpPlugins.uglify()))
	    .pipe(gulp.dest(SETTINGS.build.js))
	    .pipe(gulpPlugins.livereload(livereload));
});

gulp.task('convert:scss', function () {
	console.log('-------------------------------------------------- COVERT - scss');

    var stream = gulp.src(SETTINGS.src.css + 'application.scss')
       .pipe(gulpPlugins.sass({includePaths: [SETTINGS.src.css]}))
       .pipe(gulp.dest(SETTINGS.scss))
       .pipe(gulpPlugins.livereload(livereload));
    return stream;
});

gulp.task('concat:css', ['convert:scss'], function () {

	console.log('-------------------------------------------------- CONCAT :css ');
	gulp.src([SETTINGS.src.css + 'reset-api.css', SETTINGS.src.css + 'fonts.css', SETTINGS.scss + 'application.css', SETTINGS.src.css + '*.css'])
	    .pipe(gulpPlugins.concat('styles.css'))
	    .pipe(gulpPlugins.if(isProduction, gulpPlugins.minifyCss({keepSpecialComments: '*'})))
	    .pipe(gulp.dest(SETTINGS.build.css))
	    .pipe(gulpPlugins.livereload(livereload));
});

/*============================================================
=                          Minify				            =
============================================================*/

gulp.task('image:min', function () {
	gulp.src(SETTINGS.src.images + '**')
        .pipe(gulpPlugins.imagemin())
        .pipe(gulp.dest(SETTINGS.build.images))
        .pipe(gulpPlugins.livereload(livereload));
});

/*============================================================
=                           Copy                            =
============================================================*/

gulp.task('copy', ['copy:html', 'copy:images', 'copy:fonts', 'copy:html:root']);

gulp.task('copy:html', function () {

	console.log('-------------------------------------------------- COPY :html');
	gulp.src([SETTINGS.src.templates + '*.html', SETTINGS.src.templates + '**/*.html'])
		.pipe(gulpPlugins.if(isProduction, gulpPlugins.minifyHtml({comments: false, quotes: true, spare: true, empty: true, cdata: true})))
		.pipe(gulp.dest(SETTINGS.build.templates))
		.pipe(gulpPlugins.livereload(livereload));
});

gulp.task('copy:html:root', function () {

	console.log('-------------------------------------------------- COPY :html:root');
	gulp.src(SETTINGS.src.app + '*.html')
		.pipe(gulpPlugins.if(isProduction, gulpPlugins.minifyHtml({comments: false, quotes: true, spare: true, empty: true, cdata: true})))
		.pipe(gulp.dest(SETTINGS.build.app))
		.pipe(gulpPlugins.livereload(livereload));
});

gulp.task('copy:images', function () {

	console.log('-------------------------------------------------- COPY :images');
	gulp.src([SETTINGS.src.images + '*.*', SETTINGS.src.images + '**/*.*'])
		.pipe(gulp.dest(SETTINGS.build.images));
});

gulp.task('copy:fonts', function () {

	console.log('-------------------------------------------------- COPY :fonts');
	gulp.src([SETTINGS.src.fonts + '*', SETTINGS.src.fonts + '**/*'])
		.pipe(gulp.dest(SETTINGS.build.fonts));
});

/*=========================================================================================================
=												Watch

	Incase the watch fails due to limited number of watches available on your sysmtem, the execute this
	command on terminal

	$ echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
=========================================================================================================*/

gulp.task('watch', function () {

	console.log('watching all the files.....');

	var watchedFiles = [];

	watchedFiles.push(gulp.watch([SETTINGS.src.css + '*.css',  SETTINGS.src.css + '**/*.css'],  ['concat:css']));

	watchedFiles.push(gulp.watch([SETTINGS.src.css + '*.scss', SETTINGS.src.css + '**/*.scss'], ['concat:css']));

    watchedFiles.push(gulp.watch([SETTINGS.src.js + '*.js',    SETTINGS.src.js + '**/*.js'],    ['concat:js']));

    watchedFiles.push(gulp.watch([SETTINGS.src.app + '*.html'], ['copy:html:root']));

	watchedFiles.push(gulp.watch([SETTINGS.src.images + '*.*', SETTINGS.src.images + '**/*.*'], ['copy:images']));

	watchedFiles.push(gulp.watch([SETTINGS.src.fonts + '*.*',  SETTINGS.src.fonts + '**/*.*'],  ['copy:fonts']));

	watchedFiles.push(gulp.watch([SETTINGS.src.bower + '*.js', SETTINGS.src.bower + '**/*.js'], ['concat:bower']));

	watchedFiles.push(gulp.watch([SETTINGS.src.templates + '*.html', SETTINGS.src.templates + '**/*.html'], ['copy:html']));

	// Just to add log messages on Terminal, in case any file is changed
	var onChange = function (event) {
		if (event.type == 'deleted') {
			runSequence('clean');
			setTimeout(function () {
				runSequence('copy', 'concat', 'watch');
			}, 500);
		}
		console.log(chalk.red('-------------------------------------------------->>>> File ' + event.path + ' was ------->>>> ' + event.type));
	};

	watchedFiles.forEach(function (watchedFile) {
		watchedFile.on('change', onChange);
	});

});

/*============================================================
=                             Clean                          =
============================================================*/

var cleanFiles = function (files, logMessage) {
	console.log('-------------------------------------------------- CLEAN :' + logMessage);
	gulp.src(files, {read: false})
		.pipe(gulpPlugins.clean({force: true}));
};

gulp.task('clean', function () {
	cleanFiles([SETTINGS.build.app], 'all files');
});

gulp.task('clean:css', function () {
	cleanFiles([SETTINGS.build.css], 'css');
});

gulp.task('clean:js', function () {
	cleanFiles([SETTINGS.build.js], 'js');
});

gulp.task('clean:html', function () {
	cleanFiles([SETTINGS.build.templates], 'html');
});

gulp.task('clean:images', function () {
	cleanFiles([SETTINGS.build.images], 'images');
});

gulp.task('clean:fonts', function () {
	cleanFiles([SETTINGS.build.fonts + '*.*', SETTINGS.build.fonts + '**/*.*'], 'fonts');
});

gulp.task('clean:zip', function () {
	cleanFiles(['zip/**/*', '!zip/build-*.zip'], 'zip');
});

/*============================================================
=                             Zip                          =
============================================================*/

gulp.task('zip', function () {
    gulp.src([SETTINGS.build.app + '*', SETTINGS.build.app + '**/*'])
        .pipe(gulpPlugins.zip('build-' + new Date() + '.zip'))
        .pipe(gulp.dest('./zip/'));

    setTimeout(function () {
		runSequence('clean:zip');
    }, 500); // wait for file creation

});

/*============================================================
=                             Start                          =
============================================================*/

gulp.task('build', function () {
	console.log(chalk.blue('-------------------------------------------------- BUILD - Development Mode'));
	runSequence('copy', 'concat', 'watch');
});

gulp.task('build:prod', function () {
	console.log(chalk.blue('-------------------------------------------------- BUILD - Production Mode'));
	isProduction = true;
	runSequence('copy', 'concat', 'watch');
});

gulp.task('default', ['build', 'server']);

// Just in case you are too lazy to type: $ gulp --type production
gulp.task('prod', ['build:prod', 'server']);

/*============================================================
=                       Browser Sync                         =
============================================================*/

gulp.task('bs', function () {
    browserSync.init([SETTINGS.build.app + 'index.html', SETTINGS.build + 'templates/*.html', SETTINGS.build.css + '*css', SETTINGS.build.js + '*.js'], {
		proxy: {
            host: '127.0.0.1',
            port: SETTINGS.serverPort
        }
    });
});<|endoftext|>


<commit_before>/**
 * Add and delete 'rows' to any container element.
 *
 * @author		Stephen Lewis (http://github.com/experience/)
 * @copyright   Experience Internet
 * @version     1.0.0
 */

(function($) {

    $.fn.roland = function(options) {
        var opts = $.extend({}, $.fn.roland.defaults, options);

        return this.each(function() {
            $container = $(this);
            updateIndexes($container, opts);
            updateNav($container, opts);

            // Adds a row.
            $container.find('.' + opts.addRowClass).bind('click', function(e) {
                e.preventDefault();

                $link       = $(this);
                $parentRow  = $link.closest('.' + opts.rowClass);
                $lastRow    = $container.find('.' + opts.rowClass + ':last');
                $cloneRow   = $lastRow.clone(true);

                // Reset the field values.
                $cloneRow.find('input').each(function() {
                    type = $(this).attr('type');

                    switch (type.toLowerCase()) {
                        case 'checkbox':
                        case 'radio':
                            $(this).attr('checked', false);
                            break;

                        case 'email':
                        case 'password':
                        case 'search':
                        case 'text':
                            $(this).val('');
                            break;
                    }
                });

                // Pre-add event. Only checks return value from last listener.
                if ($link.data('events').preAddRow !== undefined) {
                    eventData = {container : $container, options : opts, newRow : $cloneRow};
                    $cloneRow = $link.triggerHandler('preAddRow', [eventData]);

                    // Returning FALSE prevents the row being added.
                    if ($cloneRow === false) {
                        return;
                    }
                }

                // If the 'add row' link lives inside a row, insert the new row after it.
                // Otherwise, just tag it on the end.
                typeof $parentRow === 'object' ? $parentRow.after($cloneRow) : $lastRow.append($cloneRow);

                // Post-add event.
                if ($link.data('events').postAddRow !== undefined) {
                    eventData = {container : $container, options : opts};
                    $link.triggerHandler('postAddRow', [eventData]);
                }

                // Update everything.
                updateIndexes($container, opts);
                updateNav($container, opts);
            });

            // Removes a row.
            $container.find('.' + opts.removeRowClass).bind('click', function(e) {
                e.preventDefault();
                $row = $(this).closest('.' + opts.rowClass);

                // Can't remove the last row.
                if ($row.siblings().length == 0) {
                    return false;
                }

                $row.remove();

                // Update everything.
                updateIndexes($container, opts);
                updateNav($container, opts);
            });
        });
    };

    // Defaults.
    $.fn.roland.defaults = {
        rowClass        : 'row',
        addRowClass     : 'add_row',
        removeRowClass  : 'remove_row'
    };

    /* ------------------------------------------
     * PRIVATE METHODS
     * -----------------------------------------*/

    // Initialises the 'available options' list for the specified select field.
    function initAvailableOptionsList($select)
    {
        options = {};
        $select.find('option').each(function() {
            options[$(this).attr('value')] = $(this).text();
        });
        return options;
    }

    // Updates the indexes of any form elements.
	function updateIndexes($container, opts) {
		$container.find('.' + opts.rowClass).each(function(rowCount) {
			regex = /^([a-z_]+)\[(?:[0-9]+)\]\[([a-z_]+)\]$/;

			$(this).find('input, select, textarea').each(function(fieldCount) {
				fieldName	= $(this).attr('name');
				fieldName	= fieldName.replace(regex, '$1[' + rowCount + '][$2]');
				$(this).attr('name', fieldName);
			});

		});
	};

    // Updates the navigation buttons.
	function updateNav($container, opts) {
		$remove = $container.find('.' + opts.removeRowClass);
		$rows	= $container.find('.' + opts.rowClass);
        $rows.size() == 1 ? $remove.hide() : $remove.show();
	};

})(jQuery);

/* End of file      : jquery.roland.js */
<commit_msg>Bump version number.<commit_after>/**
 * Add and delete 'rows' to any container element.
 *
 * @author		Stephen Lewis (http://github.com/experience/)
 * @copyright   Experience Internet
 * @version     1.0.1
 */

(function($) {

    $.fn.roland = function(options) {
        var opts = $.extend({}, $.fn.roland.defaults, options);

        return this.each(function() {
            $container = $(this);
            updateIndexes($container, opts);
            updateNav($container, opts);

            // Adds a row.
            $container.find('.' + opts.addRowClass).bind('click', function(e) {
                e.preventDefault();

                $link       = $(this);
                $parentRow  = $link.closest('.' + opts.rowClass);
                $lastRow    = $container.find('.' + opts.rowClass + ':last');
                $cloneRow   = $lastRow.clone(true);

                // Reset the field values.
                $cloneRow.find('input').each(function() {
                    type = $(this).attr('type');

                    switch (type.toLowerCase()) {
                        case 'checkbox':
                        case 'radio':
                            $(this).attr('checked', false);
                            break;

                        case 'email':
                        case 'password':
                        case 'search':
                        case 'text':
                            $(this).val('');
                            break;
                    }
                });

                // Pre-add event. Only checks return value from last listener.
                if ($link.data('events').preAddRow !== undefined) {
                    eventData = {container : $container, options : opts, newRow : $cloneRow};
                    $cloneRow = $link.triggerHandler('preAddRow', [eventData]);

                    // Returning FALSE prevents the row being added.
                    if ($cloneRow === false) {
                        return;
                    }
                }

                // If the 'add row' link lives inside a row, insert the new row after it.
                // Otherwise, just tag it on the end.
                typeof $parentRow === 'object' ? $parentRow.after($cloneRow) : $lastRow.append($cloneRow);

                // Post-add event.
                if ($link.data('events').postAddRow !== undefined) {
                    eventData = {container : $container, options : opts};
                    $link.triggerHandler('postAddRow', [eventData]);
                }

                // Update everything.
                updateIndexes($container, opts);
                updateNav($container, opts);
            });

            // Removes a row.
            $container.find('.' + opts.removeRowClass).bind('click', function(e) {
                e.preventDefault();
                $row = $(this).closest('.' + opts.rowClass);

                // Can't remove the last row.
                if ($row.siblings().length == 0) {
                    return false;
                }

                $row.remove();

                // Update everything.
                updateIndexes($container, opts);
                updateNav($container, opts);
            });
        });
    };

    // Defaults.
    $.fn.roland.defaults = {
        rowClass        : 'row',
        addRowClass     : 'add_row',
        removeRowClass  : 'remove_row'
    };

    /* ------------------------------------------
     * PRIVATE METHODS
     * -----------------------------------------*/

    // Initialises the 'available options' list for the specified select field.
    function initAvailableOptionsList($select)
    {
        options = {};
        $select.find('option').each(function() {
            options[$(this).attr('value')] = $(this).text();
        });
        return options;
    }

    // Updates the indexes of any form elements.
	function updateIndexes($container, opts) {
		$container.find('.' + opts.rowClass).each(function(rowCount) {
			regex = /^([a-z_]+)\[(?:[0-9]+)\]\[([a-z_]+)\]$/;

			$(this).find('input, select, textarea').each(function(fieldCount) {
				fieldName	= $(this).attr('name');
				fieldName	= fieldName.replace(regex, '$1[' + rowCount + '][$2]');
				$(this).attr('name', fieldName);
			});

		});
	};

    // Updates the navigation buttons.
	function updateNav($container, opts) {
		$remove = $container.find('.' + opts.removeRowClass);
		$rows	= $container.find('.' + opts.rowClass);
        $rows.size() == 1 ? $remove.hide() : $remove.show();
	};

})(jQuery);

/* End of file      : jquery.roland.js */
<|endoftext|>