<commit_before>import React, { Component, PropTypes } from 'react';
import _ from 'lodash';
import moment from 'moment';
import spahql from 'spahql';
import MasonryCtl from 'react-masonry-component';
import Radium from 'radium';
import { connect } from 'react-redux';
import { ActionCreators } from 'redux-undo';
import DashboardComponentSmall from '../components/DashboardComponentSmall';
import DashboardMediumGraph from '../components/DashboardMediumGraph';
import ReactGridLayout from 'react-grid-layout';
import Paper from 'material-ui/lib/paper';
import Styles from 'material-ui/lib/styles';
import Card from 'material-ui/lib/card/card';
import CardHeader from 'material-ui/lib/card/card-header';
import CardTitle from 'material-ui/lib/card/card-title';
import CardMedia from 'material-ui/lib/card/card-media';
import CardText from 'material-ui/lib/card/card-text';
import CardActions from 'material-ui/lib/card/card-actions';
import TextField from 'material-ui/lib/text-field';
import Avatar from 'material-ui/lib/avatar';
import Dialog from 'material-ui/lib/dialog';
import Toolbar from 'material-ui/lib/toolbar/toolbar';
import ToolbarGroup from 'material-ui/lib/toolbar/toolbar-group';
import ToolbarSeparator from 'material-ui/lib/toolbar/toolbar-separator';
import ToolbarTitle from 'material-ui/lib/toolbar/toolbar-title';
import FlatButton from 'material-ui/lib/flat-button';
import RaisedButton from 'material-ui/lib/raised-button';
import IconButton from 'material-ui/lib/icon-button';
import CircularProgress from 'material-ui/lib/circular-progress';
import Editor from 'react-medium-editor';
import { Grid, Row, Col } from 'react-bootstrap';
//import 'react-medium-editor/node_modules/medium-editor/dist/css/medium-editor.css';
//import 'react-medium-editor/node_modules/medium-editor/dist/css/themes/default.css';
import { updateClassAttendance, updateNote, addNote, updateClassAttendanceLocal } from '../actions';
let Masonry = MasonryCtl(React);

@Radium
class Dashboard extends Component {
  constructor(props) {
    super(props);
  }

  resize() {
    _.throttle(() => {
      const { masonryOptions } = this.state;
      const graphDiv = this.refs.graphDiv;
      //console.log(graphDiv.clientWidth);
      masonryOptions.updated = true;
      this.setState({
          sparklineWidth: graphDiv.offsetWidth - 40
      });
    }, 30)
  }

  componentDidMount() {
    window.addEventListener('resize', this.resize);
  }

  componentWillReceiveProps(nextProps) {
    //console.log("componentWillReceiveProps", nextProps);
  }

  componentWillMount() {
    const { dispatch, configs } = this.props;
    let db = spahql.db(configs.data),
        today = moment().weekday(),
        attendanceDay = db.select("/*/classMeetingDays/*[/day == "+today+"]").value();

    this.delayedAttendanceUpdate = _.debounce(function (divClass, count, event) {
      //console.log(divClass, event.target.value, attendanceDay);
      dispatch(updateClassAttendance(divClass.id, today, moment().format('YYYY-MM-DD 00:00:00'), count));
    }, 500);

    this.delayedNoteUpdate = _.debounce(function (note, changes) {
      dispatch(updateNote(note, changes));
    }, 500);

    this.setState({
      sparklineWidth: 100,
      attendanceDay: attendanceDay,
      displayAttendance: this.displayAttendance(),
      showDialog: false,
      currentNote: {
        id: null,
        title: null,
        text: null
      },
      masonryOptions: {}
    });
  }

  handleAttendanceUpdate() {
    let currentValue = select(store.getState());
    //console.log("handleAttendance", currentValue);
  }

  displayAttendance() {
    const { configs } = this.props;
    let db = spahql.db(configs.data),
        today = moment().weekday(),
        classDay = db.select("/*/classMeetingDays/*[/day == "+today+"]").value();
    if (!Array.isArray(classDay) && classDay !== null) {
      classDay = [classDay];
    } else if (classDay === null) {
      classDay = [];
    }
    let results = classDay.map(function(day, index){
      let config = db.select("/*[/id == "+day.divisionConfigId+"]"),
          classes = db.select("/*[/id == "+day.divisionConfigId+"]/divisionYears/0/divisions/0/divisionClasses");
      day.classes = classes.value();
      day.config = config.value();
    });
    return classDay;
  }

  attendanceUpdate(divClass, e) {
    const { dispatch } = this.props,
          { attendanceDay } = this.state,
          db = spahql.db(divClass),
          attendance = db.select("//divisionClassAttendances/*[/attendanceDate =~ '^"+moment().format("YYYY-MM-DD")+"']"),
          exists = attendance.values.length,
          count = parseInt(e.target.value, 10);
    let attend = {},
        today = moment().format("YYYY-MM-DD")+"T00:00:00.000Z";
    e.persist();
    if (!exists) {
      attend = {
        attendanceDate: today,
        count: count,
        createdAt: today,
        day: moment().weekday(),
        deletedAt: null,
        divisionClassId: divClass.id,
        id: null,
        revision: null,
        updatedAt: today,
        updating: true
      }
      divClass.divisionClassAttendances.unshift(attend);
    } else {
      attendance.values[0].count = count;
      attendance.values[0].updating = true;
    }
    dispatch(updateClassAttendanceLocal(divClass));
    this.delayedAttendanceUpdate(divClass, count, e);
  }

  getClassAttendance(divClass) {
    let db = spahql.db(divClass),
        attendance = db.select("//divisionClassAttendances/*[/attendanceDate =~ '^"+moment().format("YYYY-MM-DD")+"']").values(),
        isToday = false;
    //console.log("getClassAttendance", divClass, attendance);
    if (attendance) {
      isToday = moment(divClass.divisionClassAttendances[0].attendanceDate, "YYYY-MM-DDTHH:mm:ss.SSSZ").isSame(moment(), 'day');
      if (isToday) {
        return attendance.toString();
      } else {
        return "0";
      }
    } else {
      return "0";
    }
  }

  getGraphAttendance() {
    const { attendance } = this.props;
    let labels = attendance.data.latest.map(function(day, index){
          return moment(day.attendanceDate, "YYYY-MM-DD HH:mm:ss").format("MM/DD");
        }).reverse(),
        series = attendance.data.latest.map(function(day, index){
          return parseInt(day.attendance,10);
        }).reverse();
    //console.log("graphAttendance", labels, series);
    return {
      labels: labels,
      series: [series]
    };
  }

  isUpdating(divClass) {
    if (divClass.divisionClassAttendances.length && "updating" in divClass.divisionClassAttendances[0]) {
      console.log("isUpdating", true);
      return true;
    } else {
      console.log("isUpdating", false);
      return false;
    }
  }

  handleCardTouchTap(note, e) {
    this.setState({
      currentNote: note,
      showDialog: true
    });
  }

  handleDialogClose(e) {
    const { dispatch } = this.props;
    if (!this.state.currentNote.id) {
      let title = (this.refs.title.getValue().length) ? this.refs.title.getValue() : null;
      this.setState({
        currentNote: {
          id: this.state.currentNote.id,
          text: this.state.currentNote.text,
          title: title
        }
      });
      dispatch(
        addNote(
          this.state.currentNote
        )
      );
    }
    this.setState({
      showDialog: false
    });
  }

  handleEditorChange(text) {
    if (this.state.currentNote.id) {
      this.delayedNoteUpdate(this.state.currentNote, {text: text});
    } else {
      this.setState({
        currentNote: {
          id: this.state.currentNote.id,
          text: text,
          title: this.state.currentNote.title
        }
      });
    }
  }

  handleTitleChange(e) {
    let title = (this.refs.title.getValue().length) ? this.refs.title.getValue() : null;
    if (this.state.currentNote.id) {
      this.delayedNoteUpdate(this.state.currentNote, {title: title});
    } else {
      this.setState({
        currentNote: {
          id: this.state.currentNote.id,
          text: this.state.currentNote.text,
          title: title
        }
      });
    }
  }

  handleNewNote(e) {
    this.setState({
      currentNote: {
        id: null,
        title: null,
        text: null
      },
      showDialog: true
    });
  }

  handleNoteDelete(e) {
    e.stopPropagation();
    console.log("Delete", e);
  }

  highlightText(e) {
    if (e) {
      e.target.select();
    }
  }

  render() {
    const { dispatch, configs, attendance, notes, ...props } = this.props;
    const { masonryOptions, displayAttendance } = this.state;
    let paperStyle = {
          display: 'flex',
          flexDirection: 'row',
          alignItems: 'center',
          justifyContent: 'center',
          height: '75px',
          backgroundColor: Styles.Colors.cyan300,
          color: 'white'
        },
        grid = {
          className: "layout",
          isDraggable: false,
          isResizable: false,
          cols: 12,
          rowHeight: 50
        },
        lineChartOptions = {
          low: 0,
          showArea: true
        },
        customActions = [
          <FlatButton
            key={0}
            label="Done"
            primary={true}
            onTouchTap={::this.handleDialogClose} />
        ],
        halogenContainer = {
          height: "25px",
          width: "25px"
        },
        halogen = {
          color: '#4DAF7C',
          size: '8px'
        };
    return (

        <Grid fluid={true}>
          {displayAttendance.map((attendance, index) =>
            <Row key={attendance.id}>
              <Col xs={12} sm={12} md={12} lg={12}>
                <Card>
                  <CardHeader
                    title={attendance.config.title+" Attendance"}
                    subtitle={moment().format("dddd MM/DD/YYYY")}
                    avatar={<Avatar>{moment().format("dd")}</Avatar>}>
                  </CardHeader>
                  <CardMedia>
                    <Grid fluid={true}>
                      {attendance.classes.map((divClass, index) =>
                        <Col style={{display: "flex", alignItems: "center", justifyContent: "center"}} key={divClass.id} xs={12} sm={6} md={4} lg={3}>

                            <TextField
                              type="number"
                              hintText="Enter attendance"
                              value={::this.getClassAttendance(divClass)}
                              min="0"
                              max="500"
                              ref={"inputAttendance"+divClass.id}
                              onFocus={((...args)=>this.highlightText(...args))}
                              onChange={((...args)=>this.attendanceUpdate(divClass, ...args))}
                              floatingLabelText={divClass.class.title} />

<CircularProgress style={{display: (this.isUpdating(divClass)) ? "block" : "none"}} size={0.35} mode="indeterminate" />

                        </Col>
                      )}
                    </Grid>
                  </CardMedia>
                </Card>
              </Col>
            </Row>
          )}
          <Masonry className={"row"} options={masonryOptions}>
            <Col xs={12} sm={12} md={6} lg={6}>
              <DashboardMediumGraph
                title={"Attendance"}
                subtitle={"Past 8 weeks"}
                avatar={<Avatar>A</Avatar>}
                lineChartData={::this.getGraphAttendance()}
                lineChartOptions={lineChartOptions}
              />
            </Col>
            <Col xs={12} sm={12} md={6} lg={6}>
              <Toolbar>
                <ToolbarGroup key={0} float="left">
                  <ToolbarTitle text="Notes" />
                </ToolbarGroup>
                <ToolbarGroup key={1} float="right">
                  <RaisedButton
                    label="Add Note"
                    primary={true}
                    onTouchTap={::this.handleNewNote}
                  />
                </ToolbarGroup>
              </Toolbar>
              <Masonry>
                {notes.data.map((note, index) =>
                  <Col key={note.id} xs={12} sm={6} md={6} lg={6} key={index}>
                    <Card>
                      <CardTitle title={note.title} style={(note.title) ? null : {display: 'none'}} />
                      <CardText onClick={((...args)=>this.handleCardTouchTap(note, ...args))}>

                      </CardText>
                      <CardActions style={{float: "right"}}>
                        <IconButton
                          iconClassName="material-icons"
                          tooltipPosition="top-center"
                          tooltip="Delete"
                          onTouchTap={::this.handleNoteDelete}>
                            delete
                        </IconButton>
                      </CardActions>
                    </Card>
                  </Col>
                )}
              </Masonry>
            </Col>
            <Col xs={6} sm={4} md={3} lg={3}>
              <DashboardComponentSmall
                zDepth={1}
                sparkLineData={[5, 10, 5, 20, 5, 30, 25, 10, 18, 32, 22, 28, 30, 21, 45, 29, 33, 18, 10, 15]}
                title={"Avg. Attendance"}
                body={attendance.data.average[0].attendance.toFixed(2)}
                style={paperStyle}
              />
            </Col>
          </Masonry>
        </Grid>
        <Dialog
          title={
            <TextField
              style={{margin: "25px"}}
              ref="title"
              hintText="Title"
              onChange={::this.handleTitleChange}
              defaultValue={this.state.currentNote.title} />
          }
          actions={customActions}
          autoDetectWindowHeight={true}
          autoScrollBodyContent={true}
          open={this.state.showDialog}
          onRequestClose={::this.handleDialogClose}>
          <Editor
            className="editor"
            ref="editor"
            text={this.state.currentNote.text}
            onChange={::this.handleEditorChange}
            options={{toolbar: {buttons: ['bold', 'italic', 'underline', 'unorderedlist']}}}
          />
        </Dialog>

    );
  }
}

Dashboard.propTypes = {
  dispatch: PropTypes.func.isRequired
};

const styles = {
  noteSmall: {
    width: '48%',
    paddingRight: '2%',
    paddingBottom: '1.5em',
    '@media (max-width: 839px)': {
      paddingRight: '0',
      width: '100%'
    }
  },
  smallCell: {
    width: '31%',
    padding:'1% 2% 1% 0',
    '@media (min-width: 480px)': {
      padding: '0',
      width: '%'
    }
  }
};

function select(state) {
  return {
    configs: state.divisionConfigs.present,
    attendance: state.attendance.present,
    notes: state.notes.present
  };
}

export default connect(select)(Dashboard);
<commit_msg>use utc<commit_after>import React, { Component, PropTypes } from 'react';
import _ from 'lodash';
import moment from 'moment';
import spahql from 'spahql';
import MasonryCtl from 'react-masonry-component';
import Radium from 'radium';
import { connect } from 'react-redux';
import { ActionCreators } from 'redux-undo';
import DashboardComponentSmall from '../components/DashboardComponentSmall';
import DashboardMediumGraph from '../components/DashboardMediumGraph';
import ReactGridLayout from 'react-grid-layout';
import Paper from 'material-ui/lib/paper';
import Styles from 'material-ui/lib/styles';
import Card from 'material-ui/lib/card/card';
import CardHeader from 'material-ui/lib/card/card-header';
import CardTitle from 'material-ui/lib/card/card-title';
import CardMedia from 'material-ui/lib/card/card-media';
import CardText from 'material-ui/lib/card/card-text';
import CardActions from 'material-ui/lib/card/card-actions';
import TextField from 'material-ui/lib/text-field';
import Avatar from 'material-ui/lib/avatar';
import Dialog from 'material-ui/lib/dialog';
import Toolbar from 'material-ui/lib/toolbar/toolbar';
import ToolbarGroup from 'material-ui/lib/toolbar/toolbar-group';
import ToolbarSeparator from 'material-ui/lib/toolbar/toolbar-separator';
import ToolbarTitle from 'material-ui/lib/toolbar/toolbar-title';
import FlatButton from 'material-ui/lib/flat-button';
import RaisedButton from 'material-ui/lib/raised-button';
import IconButton from 'material-ui/lib/icon-button';
import CircularProgress from 'material-ui/lib/circular-progress';
import Editor from 'react-medium-editor';
import { Grid, Row, Col } from 'react-bootstrap';
//import 'react-medium-editor/node_modules/medium-editor/dist/css/medium-editor.css';
//import 'react-medium-editor/node_modules/medium-editor/dist/css/themes/default.css';
import { updateClassAttendance, updateNote, addNote, updateClassAttendanceLocal } from '../actions';
let Masonry = MasonryCtl(React);

@Radium
class Dashboard extends Component {
  constructor(props) {
    super(props);
  }

  resize() {
    _.throttle(() => {
      const { masonryOptions } = this.state;
      const graphDiv = this.refs.graphDiv;
      //console.log(graphDiv.clientWidth);
      masonryOptions.updated = true;
      this.setState({
          sparklineWidth: graphDiv.offsetWidth - 40
      });
    }, 30)
  }

  componentDidMount() {
    window.addEventListener('resize', this.resize);
  }

  componentWillReceiveProps(nextProps) {
    //console.log("componentWillReceiveProps", nextProps);
  }

  componentWillMount() {
    const { dispatch, configs } = this.props;
    let db = spahql.db(configs.data),
        today = moment().weekday(),
        attendanceDay = db.select("/*/classMeetingDays/*[/day == "+today+"]").value();

    this.delayedAttendanceUpdate = _.debounce(function (divClass, count, event) {
      //console.log(divClass, event.target.value, attendanceDay);
      dispatch(updateClassAttendance(divClass.id, today, moment().format('YYYY-MM-DD 00:00:00'), count));
    }, 500);

    this.delayedNoteUpdate = _.debounce(function (note, changes) {
      dispatch(updateNote(note, changes));
    }, 500);

    this.setState({
      sparklineWidth: 100,
      attendanceDay: attendanceDay,
      displayAttendance: this.displayAttendance(),
      showDialog: false,
      currentNote: {
        id: null,
        title: null,
        text: null
      },
      masonryOptions: {}
    });
  }

  handleAttendanceUpdate() {
    let currentValue = select(store.getState());
    //console.log("handleAttendance", currentValue);
  }

  displayAttendance() {
    const { configs } = this.props;
    let db = spahql.db(configs.data),
        today = moment().weekday(),
        classDay = db.select("/*/classMeetingDays/*[/day == "+today+"]").value();
    if (!Array.isArray(classDay) && classDay !== null) {
      classDay = [classDay];
    } else if (classDay === null) {
      classDay = [];
    }
    let results = classDay.map(function(day, index){
      let config = db.select("/*[/id == "+day.divisionConfigId+"]"),
          classes = db.select("/*[/id == "+day.divisionConfigId+"]/divisionYears/0/divisions/0/divisionClasses");
      day.classes = classes.value();
      day.config = config.value();
    });
    return classDay;
  }

  attendanceUpdate(divClass, e) {
    const { dispatch } = this.props,
          { attendanceDay } = this.state,
          db = spahql.db(divClass),
          attendance = db.select("//divisionClassAttendances/*[/attendanceDate =~ '^"+moment().format("YYYY-MM-DD")+"']"),
          exists = attendance.values.length,
          count = parseInt(e.target.value, 10);
    let attend = {},
        today = moment().format("YYYY-MM-DD")+"T00:00:00.000Z";
    e.persist();
    if (!exists) {
      attend = {
        attendanceDate: today,
        count: count,
        createdAt: today,
        day: moment().weekday(),
        deletedAt: null,
        divisionClassId: divClass.id,
        id: null,
        revision: null,
        updatedAt: today,
        updating: true
      }
      divClass.divisionClassAttendances.unshift(attend);
    } else {
      attendance.values[0].count = count;
      attendance.values[0].updating = true;
    }
    dispatch(updateClassAttendanceLocal(divClass));
    this.delayedAttendanceUpdate(divClass, count, e);
  }

  getClassAttendance(divClass) {
    let db = spahql.db(divClass),
        attendance = db.select("//divisionClassAttendances/*[/attendanceDate =~ '^"+moment().format("YYYY-MM-DD")+"']").values(),
        isToday = false;
    //console.log("getClassAttendance", divClass, attendance);
    if (attendance) {
      isToday = moment.utc(attendance[0].attendanceDate, "YYYY-MM-DDTHH:mm:ss.SSSZ").isSame(moment(), 'day');
      if (isToday) {
        return attendance.toString();
      } else {
        return "0";
      }
    } else {
      return "0";
    }
  }

  getGraphAttendance() {
    const { attendance } = this.props;
    let labels = attendance.data.latest.map(function(day, index){
          return moment(day.attendanceDate, "YYYY-MM-DD HH:mm:ss").format("MM/DD");
        }).reverse(),
        series = attendance.data.latest.map(function(day, index){
          return parseInt(day.attendance,10);
        }).reverse();
    //console.log("graphAttendance", labels, series);
    return {
      labels: labels,
      series: [series]
    };
  }

  isUpdating(divClass) {
    if (divClass.divisionClassAttendances.length && "updating" in divClass.divisionClassAttendances[0]) {
      console.log("isUpdating", true);
      return true;
    } else {
      console.log("isUpdating", false);
      return false;
    }
  }

  handleCardTouchTap(note, e) {
    this.setState({
      currentNote: note,
      showDialog: true
    });
  }

  handleDialogClose(e) {
    const { dispatch } = this.props;
    if (!this.state.currentNote.id) {
      let title = (this.refs.title.getValue().length) ? this.refs.title.getValue() : null;
      this.setState({
        currentNote: {
          id: this.state.currentNote.id,
          text: this.state.currentNote.text,
          title: title
        }
      });
      dispatch(
        addNote(
          this.state.currentNote
        )
      );
    }
    this.setState({
      showDialog: false
    });
  }

  handleEditorChange(text) {
    if (this.state.currentNote.id) {
      this.delayedNoteUpdate(this.state.currentNote, {text: text});
    } else {
      this.setState({
        currentNote: {
          id: this.state.currentNote.id,
          text: text,
          title: this.state.currentNote.title
        }
      });
    }
  }

  handleTitleChange(e) {
    let title = (this.refs.title.getValue().length) ? this.refs.title.getValue() : null;
    if (this.state.currentNote.id) {
      this.delayedNoteUpdate(this.state.currentNote, {title: title});
    } else {
      this.setState({
        currentNote: {
          id: this.state.currentNote.id,
          text: this.state.currentNote.text,
          title: title
        }
      });
    }
  }

  handleNewNote(e) {
    this.setState({
      currentNote: {
        id: null,
        title: null,
        text: null
      },
      showDialog: true
    });
  }

  handleNoteDelete(e) {
    e.stopPropagation();
    console.log("Delete", e);
  }

  highlightText(e) {
    if (e) {
      e.target.select();
    }
  }

  render() {
    const { dispatch, configs, attendance, notes, ...props } = this.props;
    const { masonryOptions, displayAttendance } = this.state;
    let paperStyle = {
          display: 'flex',
          flexDirection: 'row',
          alignItems: 'center',
          justifyContent: 'center',
          height: '75px',
          backgroundColor: Styles.Colors.cyan300,
          color: 'white'
        },
        grid = {
          className: "layout",
          isDraggable: false,
          isResizable: false,
          cols: 12,
          rowHeight: 50
        },
        lineChartOptions = {
          low: 0,
          showArea: true
        },
        customActions = [
          <FlatButton
            key={0}
            label="Done"
            primary={true}
            onTouchTap={::this.handleDialogClose} />
        ],
        halogenContainer = {
          height: "25px",
          width: "25px"
        },
        halogen = {
          color: '#4DAF7C',
          size: '8px'
        };
    return (

        <Grid fluid={true}>
          {displayAttendance.map((attendance, index) =>
            <Row key={attendance.id}>
              <Col xs={12} sm={12} md={12} lg={12}>
                <Card>
                  <CardHeader
                    title={attendance.config.title+" Attendance"}
                    subtitle={moment().format("dddd MM/DD/YYYY")}
                    avatar={<Avatar>{moment().format("dd")}</Avatar>}>
                  </CardHeader>
                  <CardMedia>
                    <Grid fluid={true}>
                      {attendance.classes.map((divClass, index) =>
                        <Col style={{display: "flex", alignItems: "center", justifyContent: "center"}} key={divClass.id} xs={12} sm={6} md={4} lg={3}>

                            <TextField
                              type="number"
                              hintText="Enter attendance"
                              value={::this.getClassAttendance(divClass)}
                              min="0"
                              max="500"
                              ref={"inputAttendance"+divClass.id}
                              onFocus={((...args)=>this.highlightText(...args))}
                              onChange={((...args)=>this.attendanceUpdate(divClass, ...args))}
                              floatingLabelText={divClass.class.title} />

<CircularProgress style={{display: (this.isUpdating(divClass)) ? "block" : "none"}} size={0.35} mode="indeterminate" />

                        </Col>
                      )}
                    </Grid>
                  </CardMedia>
                </Card>
              </Col>
            </Row>
          )}
          <Masonry className={"row"} options={masonryOptions}>
            <Col xs={12} sm={12} md={6} lg={6}>
              <DashboardMediumGraph
                title={"Attendance"}
                subtitle={"Past 8 weeks"}
                avatar={<Avatar>A</Avatar>}
                lineChartData={::this.getGraphAttendance()}
                lineChartOptions={lineChartOptions}
              />
            </Col>
            <Col xs={12} sm={12} md={6} lg={6}>
              <Toolbar>
                <ToolbarGroup key={0} float="left">
                  <ToolbarTitle text="Notes" />
                </ToolbarGroup>
                <ToolbarGroup key={1} float="right">
                  <RaisedButton
                    label="Add Note"
                    primary={true}
                    onTouchTap={::this.handleNewNote}
                  />
                </ToolbarGroup>
              </Toolbar>
              <Masonry>
                {notes.data.map((note, index) =>
                  <Col key={note.id} xs={12} sm={6} md={6} lg={6} key={index}>
                    <Card>
                      <CardTitle title={note.title} style={(note.title) ? null : {display: 'none'}} />
                      <CardText onClick={((...args)=>this.handleCardTouchTap(note, ...args))}>

                      </CardText>
                      <CardActions style={{float: "right"}}>
                        <IconButton
                          iconClassName="material-icons"
                          tooltipPosition="top-center"
                          tooltip="Delete"
                          onTouchTap={::this.handleNoteDelete}>
                            delete
                        </IconButton>
                      </CardActions>
                    </Card>
                  </Col>
                )}
              </Masonry>
            </Col>
            <Col xs={6} sm={4} md={3} lg={3}>
              <DashboardComponentSmall
                zDepth={1}
                sparkLineData={[5, 10, 5, 20, 5, 30, 25, 10, 18, 32, 22, 28, 30, 21, 45, 29, 33, 18, 10, 15]}
                title={"Avg. Attendance"}
                body={attendance.data.average[0].attendance.toFixed(2)}
                style={paperStyle}
              />
            </Col>
          </Masonry>
        </Grid>
        <Dialog
          title={
            <TextField
              style={{margin: "25px"}}
              ref="title"
              hintText="Title"
              onChange={::this.handleTitleChange}
              defaultValue={this.state.currentNote.title} />
          }
          actions={customActions}
          autoDetectWindowHeight={true}
          autoScrollBodyContent={true}
          open={this.state.showDialog}
          onRequestClose={::this.handleDialogClose}>
          <Editor
            className="editor"
            ref="editor"
            text={this.state.currentNote.text}
            onChange={::this.handleEditorChange}
            options={{toolbar: {buttons: ['bold', 'italic', 'underline', 'unorderedlist']}}}
          />
        </Dialog>

    );
  }
}

Dashboard.propTypes = {
  dispatch: PropTypes.func.isRequired
};

const styles = {
  noteSmall: {
    width: '48%',
    paddingRight: '2%',
    paddingBottom: '1.5em',
    '@media (max-width: 839px)': {
      paddingRight: '0',
      width: '100%'
    }
  },
  smallCell: {
    width: '31%',
    padding:'1% 2% 1% 0',
    '@media (min-width: 480px)': {
      padding: '0',
      width: '%'
    }
  }
};

function select(state) {
  return {
    configs: state.divisionConfigs.present,
    attendance: state.attendance.present,
    notes: state.notes.present
  };
}

export default connect(select)(Dashboard);
<|endoftext|>


<commit_before>var _ = require('lodash');
var levenshtein = require('levenshtein-dist');

/**
* Remove reference 'noise' from a string
* @param string a The string to remove the noise from
* @return string The input string with all noise removed
*/
function stripNoise(a) {
	return a
		.replace(/[^a-z0-9]+/i, ' ')
		.replace(/ (the|a) /, ' ');
}

/**
* Fuzzily compare strings a and b
* @param string a The first string to compare
* @param string b The second string to compare
* @return boolean True if a ≈ b
*/
function fuzzyStringCompare(a, b) {
	if (a == b) return true;

	var as = stripNoise(a);
	if (as.length > 255) as = as.substr(0, 255);

	var bs = stripNoise(b);
	if (bs.length > 255) bs = bs.substr(0, 255);

	if (levenshtein(as, bs) < 10) return true;
}

/**
* Returns true if the string 'looks' numeric
* e.g. '1st', '23rd'
* @param string string The string to examine
* @return boolean True if the string appears to be numeric
*/
function isDecendentNumeric(a) {
	return /^[0-9]+(st|nd|rd|th)$/.test(a);
}

/**
* Splits an author string into its component parts
* @param string author The raw author string to split
* @param boolean firstLast Whether to allow firstname-lastname hyman style names (e.g. 'John Smith'). If false all names are treated as if they are specified in last-first order
* @return array An array composed of lastname, initial/name
*/
function splitAuthor(author, firstLast) {
	var out = [];

	var re = new RegExp('/^(.*?)' + (firstLast ? ',' : ',?') + '\s*(.*)\s*$/');
	var matches = re.exec(author);
	if (matches) {
		if (matches[1]) out.push(matches[1]);
		matches[2].split(/\s*(\.| )\s*/).forEach(function(rawInitial) {
			var initial = rawInitial
				.replace(/^\s+/, '')
				.replace(/\s+$/, '');
			if (initial) out.push(initial);
		});
	} else if (firstLast && (matches = /^(.*)\s+(.*)/.exec(author))) {
		out.push(matches[2]);
		matches[1].split(/\s*(\.| )\s*/).forEach(function(rawInitial) {
			var initial = rawInitial
				.replace(/^\s+/, '')
				.replace(/\s+$/, '');
		});
	} else { // No idea - return the whole string as an entry
		out.push(author);
	}
	return out;
}

/**
* Splits a single string of multiple authors into an array
* @param string str The string to split
* @return array The array of extracted authors
*/
function splitAuthorString(str) {
	return str.split(/\s*;\s*/);
}

/**
* Compare an array of authors against a second array
* @param array a The first array of authors
* @param array b The second array of authors
* @return bolean True if a ≈ b
*/
function compareNames(a, b) {
	if (!_.isArray(a)) a = splitAuthorString(a);
	if (!_.isArray(b)) b = splitAuthorString(b);

	var aPos = 0, bPos = 0;
	var authorLimit = Math.min(a.length, b.length);
	var failed = false;

	while (aPos < authorLimit && bPos < authorLimit) {
		if (isDecendentNumeric(a[aPos])) {
			aPos++;
		} else if (isDecendentNumeric(b[bPos])) {
			bPos++;
		} else if (a[aPos] == b[bPos] || fuzzyStringCompare(a[aPos], b[bPos])) {
			aPos++;
			bPos++;
		} else {
			var aAuth = splitAuthor(a[aPos]);
			var bAuth = splitAuthor(b[aPos]);
			var nameLimit = Math.min(aAuth.length, bAuth.length);
			var nameMatches = 1;
			for (var n = 0; n < nameLimit; n++) {
				if (
					aAuth[n] == bAuth[n] || // Direct match
					aAuth[n].length == 1 && bAuth[n].substr(0, 1) || // A is initial and B is full name
					bAuth[n].length == 1 && aAuth[n].substr(0, 1) ||
					fuzzyStringCompare(aAuth[n], bAuth[n])
				) {
					nameMatches++;
				}
			}

			if (nameMatches >= nameLimit) {
				aPos++;
				bPos++;
			} else {
				failed = true;
			}
			break;
		}
		aPos++;
	}
	return !failed;
}

module.exports = compareNames;
<commit_msg>Added tolerence parameter to fuzzy string matcher<commit_after>var _ = require('lodash');
var levenshtein = require('levenshtein-dist');

/**
* Remove reference 'noise' from a string
* @param string a The string to remove the noise from
* @return string The input string with all noise removed
*/
function stripNoise(a) {
	return a
		.replace(/[^a-z0-9]+/i, ' ')
		.replace(/ (the|a) /, ' ');
}

/**
* Fuzzily compare strings a and b
* @param string a The first string to compare
* @param string b The second string to compare
* @param number tolerence The tolerence when comparing using levenshtein, defaults to 10
* @return boolean True if a ≈ b
*/
function fuzzyStringCompare(a, b, tolerence) {
	if (a == b) return true;

	var as = stripNoise(a);
	if (as.length > 255) as = as.substr(0, 255);

	var bs = stripNoise(b);
	if (bs.length > 255) bs = bs.substr(0, 255);

	if (tolerence == undefined && levenshtein(as, bs) < 10) return true;
	if (tolerence && levenshtein(as, bs) <= tolerence) return true;
}

/**
* Splits an author string into its component parts
* @param string author The raw author string to split
* @param boolean firstLast Whether to allow firstname-lastname hyman style names (e.g. 'John Smith'). If false all names are treated as if they are specified in last-first order
* @return array An array composed of lastname, initial/name
*/
function splitAuthor(author, firstLast) {
	var out = [];

	var re = new RegExp('/^(.*?)' + (firstLast ? ',' : ',?') + '\s*(.*)\s*$/');
	var matches = re.exec(author);
	if (matches) {
		if (matches[1]) out.push(matches[1]);
		matches[2].split(/\s*(\.| )\s*/).forEach(function(rawInitial) {
			var initial = rawInitial
				.replace(/^\s+/, '')
				.replace(/\s+$/, '');
			if (initial) out.push(initial);
		});
	} else if (firstLast && (matches = /^(.*)\s+(.*)/.exec(author))) {
		out.push(matches[2]);
		matches[1].split(/\s*(\.| )\s*/).forEach(function(rawInitial) {
			var initial = rawInitial
				.replace(/^\s+/, '')
				.replace(/\s+$/, '');
		});
	} else { // No idea - return the whole string as an entry
		out.push(author);
	}
	return out;
}

/**
* Splits a single string of multiple authors into an array
* @param string str The string to split
* @return array The array of extracted authors
*/
function splitAuthorString(str) {
	return str.split(/\s*;\s*/);
}

/**
* Compare an array of authors against a second array
* @param array a The first array of authors
* @param array b The second array of authors
* @return bolean True if a ≈ b
*/
function compareNames(a, b) {
	if (!_.isArray(a)) a = splitAuthorString(a);
	if (!_.isArray(b)) b = splitAuthorString(b);

	var aPos = 0, bPos = 0;
	var authorLimit = Math.min(a.length, b.length);
	var failed = false;

	while (aPos < authorLimit && bPos < authorLimit) {
		if (isDecendentNumeric(a[aPos])) {
			aPos++;
		} else if (isDecendentNumeric(b[bPos])) {
			bPos++;
		} else if (a[aPos] == b[bPos] || fuzzyStringCompare(a[aPos], b[bPos])) {
			aPos++;
			bPos++;
		} else {
			var aAuth = splitAuthor(a[aPos]);
			var bAuth = splitAuthor(b[aPos]);
			var nameLimit = Math.min(aAuth.length, bAuth.length);
			var nameMatches = 1;
			for (var n = 0; n < nameLimit; n++) {
				if (
					aAuth[n] == bAuth[n] || // Direct match
					aAuth[n].length == 1 && bAuth[n].substr(0, 1) || // A is initial and B is full name
					bAuth[n].length == 1 && aAuth[n].substr(0, 1) ||
					fuzzyStringCompare(aAuth[n], bAuth[n])
				) {
					nameMatches++;
				}
			}

			if (nameMatches >= nameLimit) {
				aPos++;
				bPos++;
			} else {
				failed = true;
			}
			break;
		}
		aPos++;
	}
	return !failed;
}

module.exports = compareNames;
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { Link } from 'react-router';
import rocket from './rocket.svg';
import './Home.css';

class Home extends Component {
  render() {
    return (

Tristan Read - Projects

The purpose of this site is for me to learn and showcase a few tools. You can find the results as projects in the menu above. Below I have documented the tools used.

For my CV, please check out <Link href="https://phazor.github.io/about-me/" target="_blank">phazor.github.io/about-me</Link>.

For my blog on IoT, check out <Link href="https://phazor.github.io/blog/" target="_blank">phazor.github.io/blog</Link>.

Tools

______________

style: Milligram

This app was styled using Milligram as a base. The responsive menu behavior is added by me, as pure CSS.

Milligram is a CSS framework designed to be a minimal starting point.
              It includes a bunch of css resets as well as default styles for various components.

Most importantly, it is only 2kB when gzipped.

______________

routing: react-router

Routing is performed by react-router. The browserHistory routing scheme is used.

Github Pages needs to be tricked into accepting the routes, this is achieved by a script in the 404.html. See https://github.com/rafrex/spa-github-pages for more detail.

______________

state: redux

State is stored with redux in the <Link to="/user-list">UserList</Link> page. There is no ajax yet - so no middleware is used.

______________

build: create-react-app

This app is built with create-react-app.

Everything you see has been done without using eject.

______________

deploy: gh-pages script

This app is curently hosted on Github Pages. Deployment is done using the gh-pages node module.

______________

embedding: iframe-resizer

iframe-resizer is used to make the hosting of 3rd party iframes more secure and prettier.

See the <Link to="/about">About Me</Link> page for an example.

    )
  }
}

export default Home;
<commit_msg>update links to use Link pattern<commit_after>import React, { Component } from 'react';
import { Link } from 'react-router';
import rocket from './rocket.svg';
import './Home.css';

class Home extends Component {
  render() {
    return (

Tristan Read - Projects

The purpose of this site is for me to learn and showcase a few tools. You can find the results as projects in the menu above. Below I have documented the tools used and why.

For my CV, please check out <Link href="https://phazor.github.io/about-me/" target="_blank">phazor.github.io/about-me</Link>.

For my blog on IoT, check out <Link href="https://phazor.github.io/blog/" target="_blank">phazor.github.io/blog</Link>.

Tools

______________

style: Milligram

This app was styled using <Link href="https://milligram.github.io/" target="_blank">Milligram</Link> as a base. The responsive menu behavior is added by me, as pure CSS.

Milligram is a CSS framework designed to be a minimal starting point.
              It includes a bunch of css resets as well as default styles for various components.

Most importantly, it is only 2kB when gzipped.

______________

routing: react-router

Routing is performed by <Link href="https://github.com/ReactTraining/react-router" target="_blank">react-router</Link>. The browserHistory routing scheme is used.

Github Pages needs to be tricked into accepting the routes, this is achieved by a script in the 404.html. See <Link href="https://github.com/rafrex/spa-github-pages" target="_blank">https://github.com/rafrex/spa-github-pages</Link> for more detail.

______________

state: redux

State is stored with <Link href="https://redux.js.org" target="_blank">redux</Link> in the <Link to="/user-list">UserList</Link> page. There is no ajax yet - so no middleware is used.

______________

build: create-react-app

This app is built with <Link href="https://github.com/facebookincubator/create-react-app/" target="_blank">create-react-app</Link>.

Everything you see has been done without using eject.

______________

deploy: gh-pages script

This app is curently hosted on Github Pages. Deployment is done using the <Link href="https://github.com/tschaub/gh-pages" target="_blank">gh-pages</Link> node module.

______________

embedding: iframe-resizer

<Link href="https://github.com/davidjbradshaw/iframe-resizer" target="_blank">iframe-resizer</Link> is used to make the hosting of 3rd party iframes more secure and prettier.

See the <Link to="/about">About Me</Link> page for an example.

    )
  }
}

export default Home;
<|endoftext|>


<commit_before>var through = require('through2'),
    gutil = require('gulp-util'),
    _ = require('lodash'),
    fs = require('fs'),
    jsYaml = require('js-yaml'),
    templateFilePath = __dirname + '/template.html',
    PluginError = gutil.PluginError;

const PLUGIN_NAME = 'gulp-ng-config',
      WRAP_TEMPLATE = '(function () { \n return <%= module %>\n})();\n';

function gulpNgConfig (moduleName, configuration) {
  var templateFile, stream, defaults;
  defaults = {
    createModule: true,
    wrap: false,
    environment: null,
    parser: null,
    pretty: false
  };

  if (!moduleName) {
    throw new PluginError(PLUGIN_NAME, 'Missing required moduleName option for gulp-ng-config');
  }

  templateFile = fs.readFileSync(templateFilePath, 'utf8');
  configuration = configuration || {};
  configuration = _.merge({}, defaults, configuration);

  stream = through.obj(function (file, encoding, callback) {
    var constants = [],
        templateOutput,
        jsonObj,
        wrapTemplate;

    if (!configuration.parser && (_.endsWith(file.path, 'yml') || _.endsWith(file.path, 'yaml'))) {
      configuration.parser = 'yml';
    }

    if (!configuration.parser) {
      configuration.parser = 'json';
    }

    if (configuration.parser === 'json') {
      try {
        jsonObj = file.isNull() ? {} : JSON.parse(file.contents.toString('utf8'));
      } catch (e) {
        this.emit('error', new PluginError(PLUGIN_NAME, 'invaild JSON file provided'));
      }
    } else if (configuration.parser === 'yml' || configuration.parser === 'yaml') {
      try {
        jsonObj = jsYaml.safeLoad(file.contents);
      } catch (e) {
        this.emit('error', new PluginError(PLUGIN_NAME, 'invaild YML file provided'));
      }
    } else {
      this.emit('error', new PluginError(PLUGIN_NAME, configuration.parser + ' is not supported as a valid parser'));
    }

    if (!_.isPlainObject(jsonObj)) {
      this.emit('error', new PluginError(PLUGIN_NAME, 'invalid JSON object provided'));
    }

    // select the environment in the configuration
    if (configuration.environment && jsonObj.hasOwnProperty(configuration.environment)) {
      jsonObj = jsonObj[configuration.environment];
    }

    jsonObj = _.merge({}, jsonObj, configuration.constants || {});

    var spaces = 0;
    if (configuration.pretty) {
      if (configuration.pretty === true) {
        spaces = 2;
      } else if (configuration.pretty === +configuration.pretty) {
        //if number passed
        spaces = configuration.pretty;
      }
    }

    _.each(jsonObj, function (value, key) {
      constants.push({
        name: key,
        value: JSON.stringify(value, null, spaces)
      });
    });

    templateOutput = _.template(templateFile)({
      createModule: configuration.createModule,
      moduleName: moduleName,
      constants: constants
    });

    if (configuration.wrap) {
      if (typeof configuration.wrap === 'string') {
        wrapTemplate = configuration.wrap;
      } else {
        wrapTemplate = WRAP_TEMPLATE;
      }
      templateOutput = _.template(wrapTemplate)({
        module: templateOutput
      });
    }

    file.path = gutil.replaceExtension(file.path, '.js');
    file.contents = new Buffer(templateOutput);
    this.push(file);
    callback();
  });

  return stream;
}

module.exports = gulpNgConfig;
<commit_msg>Added errors if passed float or infinity value as 'pretty' param<commit_after>var through = require('through2'),
    gutil = require('gulp-util'),
    _ = require('lodash'),
    fs = require('fs'),
    jsYaml = require('js-yaml'),
    templateFilePath = __dirname + '/template.html',
    PluginError = gutil.PluginError;

const PLUGIN_NAME = 'gulp-ng-config',
      WRAP_TEMPLATE = '(function () { \n return <%= module %>\n})();\n';

function gulpNgConfig (moduleName, configuration) {
  var templateFile, stream, defaults;
  defaults = {
    createModule: true,
    wrap: false,
    environment: null,
    parser: null,
    pretty: false
  };

  if (!moduleName) {
    throw new PluginError(PLUGIN_NAME, 'Missing required moduleName option for gulp-ng-config');
  }

  templateFile = fs.readFileSync(templateFilePath, 'utf8');
  configuration = configuration || {};
  configuration = _.merge({}, defaults, configuration);

  stream = through.obj(function (file, encoding, callback) {
    var constants = [],
        templateOutput,
        jsonObj,
        wrapTemplate;

    if (!configuration.parser && (_.endsWith(file.path, 'yml') || _.endsWith(file.path, 'yaml'))) {
      configuration.parser = 'yml';
    }

    if (!configuration.parser) {
      configuration.parser = 'json';
    }

    if (configuration.parser === 'json') {
      try {
        jsonObj = file.isNull() ? {} : JSON.parse(file.contents.toString('utf8'));
      } catch (e) {
        this.emit('error', new PluginError(PLUGIN_NAME, 'invaild JSON file provided'));
      }
    } else if (configuration.parser === 'yml' || configuration.parser === 'yaml') {
      try {
        jsonObj = jsYaml.safeLoad(file.contents);
      } catch (e) {
        this.emit('error', new PluginError(PLUGIN_NAME, 'invaild YML file provided'));
      }
    } else {
      this.emit('error', new PluginError(PLUGIN_NAME, configuration.parser + ' is not supported as a valid parser'));
    }

    if (!_.isPlainObject(jsonObj)) {
      this.emit('error', new PluginError(PLUGIN_NAME, 'invalid JSON object provided'));
    }

    // select the environment in the configuration
    if (configuration.environment && jsonObj.hasOwnProperty(configuration.environment)) {
      jsonObj = jsonObj[configuration.environment];
    }

    jsonObj = _.merge({}, jsonObj, configuration.constants || {});

    var spaces = 0;
    if (configuration.pretty) {
      if (configuration.pretty === true) {
        spaces = 2;
      } else if (configuration.pretty === +configuration.pretty) {

        if (!Number.isInteger(configuration.pretty) || !Number.isFinite(configuration.pretty))  {
          return this.emit('error', new PluginError(PLUGIN_NAME, 'invalid \'pretty\' value. Should be boolean or integer number'));
        }

        spaces = configuration.pretty;
      }
    }

    _.each(jsonObj, function (value, key) {
      constants.push({
        name: key,
        value: JSON.stringify(value, null, spaces)
      });
    });

    templateOutput = _.template(templateFile)({
      createModule: configuration.createModule,
      moduleName: moduleName,
      constants: constants
    });

    if (configuration.wrap) {
      if (typeof configuration.wrap === 'string') {
        wrapTemplate = configuration.wrap;
      } else {
        wrapTemplate = WRAP_TEMPLATE;
      }
      templateOutput = _.template(wrapTemplate)({
        module: templateOutput
      });
    }

    file.path = gutil.replaceExtension(file.path, '.js');
    file.contents = new Buffer(templateOutput);
    this.push(file);
    callback();
  });

  return stream;
}

module.exports = gulpNgConfig;
<|endoftext|>


<commit_before>var userModel = require('../mongo').userModel;

// Custom symbol and hiding

Users.User.prototype.getIdentity = function(roomid) {
    if (this.locked) {
        return '‽' + this.name;
    }
    if (roomid) {
        if (this.mutedRooms[roomid]) {
            return '!' + this.name;
        }
        var room = Rooms.rooms[roomid];
        if (room && room.auth) {
            if (room.auth[this.userid]) {
                return room.auth[this.userid] + this.name;
            }
            if (room.isPrivate === true) return ' ' + this.name;
        }
    }
    if (this.hiding) {
      return ' ' + this.name;
    }
    if (this.customSymbol) {
        return this.customSymbol + this.name;
    }
    return this.group + this.name;
};

// Limit the number of concurrent connections a user can have.

var Connection = Users.Connection;
var User = Users.User;
var connections = Users.connections;
var connectedIps = Users.connectedIps = Object.create(null);
var bannedIps;
var lockedRanges;
var rangelockedUsers = Object.create(null);

if (Users.bannedIps) {
    bannedIps = Users.bannedIps;
} else {
    bannedIps = Users.bannedIps = Object.create(null);
}

if (Users.lockedRanges) {
    lockedRanges = Users.lockedRanges;
} else {
    lockedRanges = Users.lockedRanges = Object.create(null);
}

Users.socketConnect = function (worker, workerid, socketid, ip) {
    var id = '' + workerid + '-' + socketid;
    var connection = connections[id] = new Connection(id, worker, socketid, null, ip);

    if (!connectedIps[ip]) {
        connectedIps[ip] = 1;
    } else {
        connectedIps[ip]++;
    }

    if (!Config.connectionWhitelist) Config.connectionWhitelist = {};
    if (connectedIps[ip] > (Config.maxConnections || Infinity) && !Config.connectionWhitelist[ip] || connectedIps[ip] > Config.connectionWhitelist[ip]) {
        connection.send("|popup|You may not have more than " + (Config.connectionWhitelist[ip] || Config.maxConnections) + " concurrent connections.");
        return connection.destroy();
    }

    if (ResourceMonitor.countConnection(ip)) {
        connection.destroy();
        bannedIps[ip] = '#cflood';
        return;
    }
    var checkResult = Users.checkBanned(ip);
    if (!checkResult && Users.checkRangeBanned(ip)) {
        checkResult = '#ipban';
    }
    if (checkResult) {
        console.log('CONNECT BLOCKED - IP BANNED: ' + ip + ' (' + checkResult + ')');
        if (checkResult === '#ipban') {
            connection.send("|popup|Your IP (" + ip + ") is not allowed to connect to PS, because it has been used to spam, hack, or otherwise attack our server.||Make sure you are not using any proxies to connect to PS.");
        } else if (checkResult === '#cflood') {
            connection.send("|popup|PS is under heavy load and cannot accommodate your connection right now.");
        } else {
            connection.send("|popup|Your IP (" + ip + ") used is banned under the username '" + checkResult + "'. Your ban will expire in a few days.||" + (Config.appealurl ? " Or you can appeal at:\n" + Config.appealurl : ""));
        }
        return connection.destroy();
    }
    // Emergency mode connections logging
    if (Config.emergency) {
        fs.appendFile('logs/cons.emergency.log', '[' + ip + ']\n', function (err) {
            if (err) {
                console.log('!! Error in emergency conns log !!');
                throw err;
            }
        });
    }

    var user = new User(connection);
    connection.user = user;
    // Generate 1024-bit challenge string.
    require('crypto').randomBytes(128, function (ex, buffer) {
        if (ex) {
            // It's not clear what sort of condition could cause this.
            // For now, we'll basically assume it can't happen.
            console.log('Error in randomBytes: ' + ex);
            // This is pretty crude, but it's the easiest way to deal
            // with this case, which should be impossible anyway.
            user.disconnectAll();
        } else if (connection.user) {   // if user is still connected
            connection.challenge = buffer.toString('hex');
            // console.log('JOIN: ' + connection.user.name + ' [' + connection.challenge.substr(0, 15) + '] [' + socket.id + ']');
            var keyid = Config.loginserverpublickeyid || 0;
            connection.sendTo(null, '|challstr|' + keyid + '|' + connection.challenge);
        }
    });

    Dnsbl.reverse(ip, function (err, hosts) {
        if (hosts && hosts[0]) {
            user.latestHost = hosts[0];
            if (Config.hostfilter) Config.hostfilter(hosts[0], user, connection);
            if (user.named && !user.locked && user.group === Config.groupsranking[0]) {
                var shortHost = Users.shortenHost(hosts[0]);
                if (lockedRanges[shortHost]) {
                    user.send("|popup|You are locked because someone on your ISP has spammed, and your ISP does not give us any way to tell you apart from them.");
                    rangelockedUsers[shortHost][user.userid] = 1;
                    user.locked = '#range';
                    user.updateIdentity();
                }
            }
        } else {
            if (Config.hostfilter) Config.hostfilter('', user, connection);
        }
    });

    Dnsbl.query(connection.ip, function (isBlocked) {
        if (isBlocked) {
            connection.popup("You are locked because someone using your IP (" + connection.ip + ") has spammed/hacked other websites. This usually means you're using a proxy, in a country where other people commonly hack, or have a virus on your computer that's spamming websites.");
            if (connection.user && !connection.user.locked) {
                connection.user.locked = '#dnsbl';
                connection.user.updateIdentity();
            }
        }
    });

    user.joinRoom('global', connection);
};

Users.Connection.prototype.onDisconnect = function () {
    delete connections[this.id];
    connectedIps[this.ip]--;
    if (connectedIps[this.ip] === 0) delete connectedIps[this.ip];
    if (this.user) this.user.onDisconnect(this);
    this.user = null;
};

// System Operators

Users.User.prototype.hasSysopAccess = function() {
    if (['creaturephil', 'dabicboi'].indexOf(this.userid) >= 0) {
        return true;
    }
    return false;
};

// Last seen a user

Users.User.prototype.originalOnDisconnect = Users.User.prototype.onDisconnect;

Users.User.prototype.onDisconnect = function(connection) {
    if (this.userid.indexOf('guest') >= 0) return this.originalOnDisconnect(connection);
    var self = this;
    userModel.findOne({name: this.userid}, function(err, user) {
        if (err) throw err;
        if (!user) {
            user = new userModel({
                name: self.userid,
                seen: Date.now()
            });
            return user.save();
        }
        user.seen = Date.now();
        user.save();
    });
    this.originalOnDisconnect(connection);
};

// Tells

Users.User.prototype.originalRename = Users.User.prototype.rename;

var users;
if (Users.users) {
    users = Users.users
} else {
    users = Users.users = Object.create(null);
}

Users.User.prototype.rename = function(name, token, auth, connection) {
    for (var i in this.roomCount) {
        var room = Rooms.get(i);
        if (room && room.rated && (this.userid === room.rated.p1 || this.userid === room.rated.p2)) {
            this.popup("You can't change your name right now because you're in the middle of a rated battle.");
            return false;
        }
    }

    var challenge = '';
    if (connection) {
        challenge = connection.challenge;
    }

    if (!name) name = '';
    name = this.filterName(name);
    var userid = toId(name);
    if (this.registered) auth = false;

    if (!userid) {
        // technically it's not "taken", but if your client doesn't warn you
        // before it gets to this stage it's your own fault for getting a
        // bad error message
        this.send('|nametaken|' + "|You did not specify a name or your name was invalid.");
        return false;
    } else {
        if (userid === this.userid && !auth) {
            return this.forceRename(name, this.registered);
        }
    }
    if (users[userid] && !users[userid].registered && users[userid].connected && !auth) {
        this.send('|nametaken|' + name + "|Someone is already using the name \"" + users[userid].name + "\".");
        return false;
    }

    if (token && token.charAt(0) !== ';') {
        var tokenSemicolonPos = token.indexOf(';');
        var tokenData = token.substr(0, tokenSemicolonPos);
        var tokenSig = token.substr(tokenSemicolonPos + 1);

        this.renamePending = name;
        var self = this;
        Verifier.verify(tokenData, tokenSig, function (success, tokenData) {
            self.finishRename(success, tokenData, token, auth, challenge);
        });
    } else {
        this.send('|nametaken|' + name + "|Your authentication token was invalid.");
    }

    if (Tells.inbox[userid]) Tells.sendTell(userid, this);
    return false;
};
<commit_msg>Fix lint error<commit_after>var userModel = require('../mongo').userModel;

// Custom symbol and hiding

Users.User.prototype.getIdentity = function(roomid) {
    if (this.locked) {
        return '‽' + this.name;
    }
    if (roomid) {
        if (this.mutedRooms[roomid]) {
            return '!' + this.name;
        }
        var room = Rooms.rooms[roomid];
        if (room && room.auth) {
            if (room.auth[this.userid]) {
                return room.auth[this.userid] + this.name;
            }
            if (room.isPrivate === true) return ' ' + this.name;
        }
    }
    if (this.hiding) {
      return ' ' + this.name;
    }
    if (this.customSymbol) {
        return this.customSymbol + this.name;
    }
    return this.group + this.name;
};

// Limit the number of concurrent connections a user can have.

var Connection = Users.Connection;
var User = Users.User;
var connections = Users.connections;
var connectedIps = Users.connectedIps = Object.create(null);
var bannedIps;
var lockedRanges;
var rangelockedUsers = Object.create(null);

if (Users.bannedIps) {
    bannedIps = Users.bannedIps;
} else {
    bannedIps = Users.bannedIps = Object.create(null);
}

if (Users.lockedRanges) {
    lockedRanges = Users.lockedRanges;
} else {
    lockedRanges = Users.lockedRanges = Object.create(null);
}

Users.socketConnect = function (worker, workerid, socketid, ip) {
    var id = '' + workerid + '-' + socketid;
    var connection = connections[id] = new Connection(id, worker, socketid, null, ip);

    if (!connectedIps[ip]) {
        connectedIps[ip] = 1;
    } else {
        connectedIps[ip]++;
    }

    if (!Config.connectionWhitelist) Config.connectionWhitelist = {};
    if (connectedIps[ip] > (Config.maxConnections || Infinity) && !Config.connectionWhitelist[ip] || connectedIps[ip] > Config.connectionWhitelist[ip]) {
        connection.send("|popup|You may not have more than " + (Config.connectionWhitelist[ip] || Config.maxConnections) + " concurrent connections.");
        return connection.destroy();
    }

    if (ResourceMonitor.countConnection(ip)) {
        connection.destroy();
        bannedIps[ip] = '#cflood';
        return;
    }
    var checkResult = Users.checkBanned(ip);
    if (!checkResult && Users.checkRangeBanned(ip)) {
        checkResult = '#ipban';
    }
    if (checkResult) {
        console.log('CONNECT BLOCKED - IP BANNED: ' + ip + ' (' + checkResult + ')');
        if (checkResult === '#ipban') {
            connection.send("|popup|Your IP (" + ip + ") is not allowed to connect to PS, because it has been used to spam, hack, or otherwise attack our server.||Make sure you are not using any proxies to connect to PS.");
        } else if (checkResult === '#cflood') {
            connection.send("|popup|PS is under heavy load and cannot accommodate your connection right now.");
        } else {
            connection.send("|popup|Your IP (" + ip + ") used is banned under the username '" + checkResult + "'. Your ban will expire in a few days.||" + (Config.appealurl ? " Or you can appeal at:\n" + Config.appealurl : ""));
        }
        return connection.destroy();
    }
    // Emergency mode connections logging
    if (Config.emergency) {
        fs.appendFile('logs/cons.emergency.log', '[' + ip + ']\n', function (err) {
            if (err) {
                console.log('!! Error in emergency conns log !!');
                throw err;
            }
        });
    }

    var user = new User(connection);
    connection.user = user;
    // Generate 1024-bit challenge string.
    require('crypto').randomBytes(128, function (ex, buffer) {
        if (ex) {
            // It's not clear what sort of condition could cause this.
            // For now, we'll basically assume it can't happen.
            console.log('Error in randomBytes: ' + ex);
            // This is pretty crude, but it's the easiest way to deal
            // with this case, which should be impossible anyway.
            user.disconnectAll();
        } else if (connection.user) {   // if user is still connected
            connection.challenge = buffer.toString('hex');
            // console.log('JOIN: ' + connection.user.name + ' [' + connection.challenge.substr(0, 15) + '] [' + socket.id + ']');
            var keyid = Config.loginserverpublickeyid || 0;
            connection.sendTo(null, '|challstr|' + keyid + '|' + connection.challenge);
        }
    });

    Dnsbl.reverse(ip, function (err, hosts) {
        if (hosts && hosts[0]) {
            user.latestHost = hosts[0];
            if (Config.hostfilter) Config.hostfilter(hosts[0], user, connection);
            if (user.named && !user.locked && user.group === Config.groupsranking[0]) {
                var shortHost = Users.shortenHost(hosts[0]);
                if (lockedRanges[shortHost]) {
                    user.send("|popup|You are locked because someone on your ISP has spammed, and your ISP does not give us any way to tell you apart from them.");
                    rangelockedUsers[shortHost][user.userid] = 1;
                    user.locked = '#range';
                    user.updateIdentity();
                }
            }
        } else {
            if (Config.hostfilter) Config.hostfilter('', user, connection);
        }
    });

    Dnsbl.query(connection.ip, function (isBlocked) {
        if (isBlocked) {
            connection.popup("You are locked because someone using your IP (" + connection.ip + ") has spammed/hacked other websites. This usually means you're using a proxy, in a country where other people commonly hack, or have a virus on your computer that's spamming websites.");
            if (connection.user && !connection.user.locked) {
                connection.user.locked = '#dnsbl';
                connection.user.updateIdentity();
            }
        }
    });

    user.joinRoom('global', connection);
};

Users.Connection.prototype.onDisconnect = function () {
    delete connections[this.id];
    connectedIps[this.ip]--;
    if (connectedIps[this.ip] === 0) delete connectedIps[this.ip];
    if (this.user) this.user.onDisconnect(this);
    this.user = null;
};

// System Operators

Users.User.prototype.hasSysopAccess = function() {
    if (['creaturephil', 'dabicboi'].indexOf(this.userid) >= 0) {
        return true;
    }
    return false;
};

// Last seen a user

Users.User.prototype.originalOnDisconnect = Users.User.prototype.onDisconnect;

Users.User.prototype.onDisconnect = function(connection) {
    if (this.userid.indexOf('guest') >= 0) return this.originalOnDisconnect(connection);
    var self = this;
    userModel.findOne({name: this.userid}, function(err, user) {
        if (err) throw err;
        if (!user) {
            user = new userModel({
                name: self.userid,
                seen: Date.now()
            });
            return user.save();
        }
        user.seen = Date.now();
        user.save();
    });
    this.originalOnDisconnect(connection);
};

// Tells

Users.User.prototype.originalRename = Users.User.prototype.rename;

var users;
if (Users.users) {
    users = Users.users;
} else {
    users = Users.users = Object.create(null);
}

Users.User.prototype.rename = function(name, token, auth, connection) {
    for (var i in this.roomCount) {
        var room = Rooms.get(i);
        if (room && room.rated && (this.userid === room.rated.p1 || this.userid === room.rated.p2)) {
            this.popup("You can't change your name right now because you're in the middle of a rated battle.");
            return false;
        }
    }

    var challenge = '';
    if (connection) {
        challenge = connection.challenge;
    }

    if (!name) name = '';
    name = this.filterName(name);
    var userid = toId(name);
    if (this.registered) auth = false;

    if (!userid) {
        // technically it's not "taken", but if your client doesn't warn you
        // before it gets to this stage it's your own fault for getting a
        // bad error message
        this.send('|nametaken|' + "|You did not specify a name or your name was invalid.");
        return false;
    } else {
        if (userid === this.userid && !auth) {
            return this.forceRename(name, this.registered);
        }
    }
    if (users[userid] && !users[userid].registered && users[userid].connected && !auth) {
        this.send('|nametaken|' + name + "|Someone is already using the name \"" + users[userid].name + "\".");
        return false;
    }

    if (token && token.charAt(0) !== ';') {
        var tokenSemicolonPos = token.indexOf(';');
        var tokenData = token.substr(0, tokenSemicolonPos);
        var tokenSig = token.substr(tokenSemicolonPos + 1);

        this.renamePending = name;
        var self = this;
        Verifier.verify(tokenData, tokenSig, function (success, tokenData) {
            self.finishRename(success, tokenData, token, auth, challenge);
        });
    } else {
        this.send('|nametaken|' + name + "|Your authentication token was invalid.");
    }

    if (Tells.inbox[userid]) Tells.sendTell(userid, this);
    return false;
};
<|endoftext|>


<commit_before>import gulp from 'gulp';
import babel from 'gulp-babel';
import del from 'del';

gulp.task('clean-lib', () => {
  return del(['lib/']);
});

gulp.task('babel', ['clean-lib'], () => {
  return gulp.src('src/**/*.js')
    .pipe(babel({
      optional: ['runtime'],
      stage: 0,
    }))
    // just put them all here why don't you
    // it's actually a genius idea
    // or a horrible one
    // TIME WILL TELL
    //
    //                           or will it
    .pipe(gulp.dest('lib/'));
});
<commit_msg>[build] Remove insanity<commit_after>import gulp from 'gulp';
import babel from 'gulp-babel';
import del from 'del';

gulp.task('clean-lib', () => {
  return del(['lib/']);
});

gulp.task('babel', ['clean-lib'], () => {
  return gulp.src('src/**/*.js')
    .pipe(babel({
      optional: ['runtime'],
      stage: 0,
    }))
    .pipe(gulp.dest('lib/'));
});
<|endoftext|>


<commit_before>var fs = require('fs')

fs.readdir('.', function(err, files) {
    if (err) {
        console.log(err)
    } else {
        files.forEach(function(file) {
            if (file.endsWith('.json')) {
                console.log(file);
                console.log(getDockerCommandLine(file))
                console.log()
            }
        });
    }
});

function getDockerCommandLine(fileName) {
    var dockerConfig = require('./' + fileName)[0];
    var command = 'docker run -d';
    command += ' --name=' + dockerConfig['Name'].substring(1);

    function getEnvs(dockerConfig) {
        var ret = '';

        var envs = dockerConfig['Config']['Env'];
        if (envs != null) {
            for (var i = 0; i < envs.length; i++) {
                var env = envs[i];
                if (!env.startsWith('PATH') && !env.startsWith('LANG') && !env.startsWith('LC_ALL')) {
                    ret += ' -e ';
                    if (env.indexOf(' ') !== -1 || env.indexOf('&') !== -1) {
                        ret += env.substring(0, env.indexOf('=')) + '="' + env.substring(env.indexOf('=')+1) + '"';
                    } else {
                        ret += env;
                    }
                }
            }
        }

        return ret;
    }

    function getHostConfig(dockerConfig) {
        var ret = '';

        if (Object.keys(dockerConfig['HostConfig']['PortBindings']).length > 0) {
            for (var port in dockerConfig['HostConfig']['PortBindings']) {
                var bindings = dockerConfig['HostConfig']['PortBindings'][port];
                for (var i = 0; i < bindings.length; i++) {
                    ret += ' -p ' + bindings[i]['HostPort'] + ':' + port.split('/')[0];
                }
            }
        }

        if (dockerConfig['HostConfig']['Links'] != null) {
            var links = dockerConfig['HostConfig']['Links'];
            for (var i = 0; i < links.length; i++) {
                var link = links[i];
                var splitted = link.split('/');
                ret += ' --link ' + splitted[1] + splitted[3];
            }
        }

        return ret;
    }

    command += getEnvs(dockerConfig);
    command += getHostConfig(dockerConfig);

    command += ' ' + dockerConfig['Config']['Image'];

    return command;
}
<commit_msg>Isolated the way of check if a environment variable value need to be wrapped into double quotation<commit_after>var fs = require('fs')

fs.readdir('.', function(err, files) {
    if (err) {
        console.log(err)
    } else {
        files.forEach(function(file) {
            if (file.endsWith('.json')) {
                console.log(file);
                console.log(getDockerCommandLine(file))
                console.log()
            }
        });
    }
});

function hasWrapChar(str) {
	var wrapChars = [' ', '&', ';'];
	for (var c in wrapChars)
		if (str.indexOf(wrapChars[c]) !== -1)
			return true;
	return false;
}

function getDockerCommandLine(fileName) {
    var dockerConfig = require('./' + fileName)[0];
    var command = 'docker run -d';
    command += ' --name=' + dockerConfig['Name'].substring(1);

    function getEnvs(dockerConfig) {
        var ret = '';

        var envs = dockerConfig['Config']['Env'];
        if (envs != null) {
            for (var i = 0; i < envs.length; i++) {
                var env = envs[i];
                if (!env.startsWith('PATH') && !env.startsWith('LANG') && !env.startsWith('LC_ALL')) {
                    ret += ' -e ';
                    if (hasWrapChar(env)) {
                        ret += env.substring(0, env.indexOf('=')) + '="' + env.substring(env.indexOf('=')+1) + '"';
                    } else {
                        ret += env;
                    }
                }
            }
        }

        return ret;
    }

    function getHostConfig(dockerConfig) {
        var ret = '';

        if (Object.keys(dockerConfig['HostConfig']['PortBindings']).length > 0) {
            for (var port in dockerConfig['HostConfig']['PortBindings']) {
                var bindings = dockerConfig['HostConfig']['PortBindings'][port];
                for (var i = 0; i < bindings.length; i++) {
                    ret += ' -p ' + bindings[i]['HostPort'] + ':' + port.split('/')[0];
                }
            }
        }

        if (dockerConfig['HostConfig']['Links'] != null) {
            var links = dockerConfig['HostConfig']['Links'];
            for (var i = 0; i < links.length; i++) {
                var link = links[i];
                var splitted = link.split('/');
                ret += ' --link ' + splitted[1] + splitted[3];
            }
        }

        return ret;
    }

    command += getEnvs(dockerConfig);
    command += getHostConfig(dockerConfig);

    command += ' ' + dockerConfig['Config']['Image'];

    return command;
}
<|endoftext|>


<commit_before>import gulp from 'gulp'

// Clean
gulp.task('clean', callback => {
  const config = require('./_gulpfile/config').default
  require('del')(config.paths.dist.base).then(() => callback())
})

// Scripts
import { prod as scriptsProd, dev as scriptsDev } from './_gulpfile/scripts'
gulp.task('scripts:prod', scriptsProd)
gulp.task('scripts:dev', scriptsDev)

// Styles
import { prod as stylesProd, dev as stylesDev, criticalCSS as stylesCritical } from './_gulpfile/styles'
gulp.task('styles:prod', stylesProd)
gulp.task('styles:dev', stylesDev)
gulp.task('styles:critical', stylesCritical)

// HTML
import { prod as htmlProd, dev as htmlDev, sitemap as htmlSitemap } from './_gulpfile/html'
gulp.task('html:prod', htmlProd)
gulp.task('html:dev', htmlDev)
gulp.task('html:sitemap', htmlSitemap)

// Linting
import { lint as lintStyles } from './_gulpfile/styles'
import { lint as lintHTML } from './_gulpfile/html'
gulp.task('lint:styles', lintStyles)
gulp.task('lint:html', lintHTML)
gulp.task('lint', gulp.parallel('lint:styles', 'lint:html'))

// Images
import { normal as imagesNormal, icons as imagesIcons } from './_gulpfile/images'
gulp.task('images:default', imagesNormal)
gulp.task('images:icons', imagesIcons)
gulp.task('images', gulp.parallel('images:default', 'images:icons'))

// Copy
import { base as copyBase, libraries as copyLibraries, fonts as copyFonts} from './_gulpfile/copy'
gulp.task('copy:base', copyBase)
gulp.task('copy:libraries', copyLibraries)
gulp.task('copy:fonts', copyFonts)
gulp.task('copy', gulp.parallel('copy:base', 'copy:libraries', 'copy:fonts'))

// Default
gulp.task('default', gulp.series('clean', gulp.parallel('images', 'scripts:dev', gulp.series(gulp.parallel('styles:dev', 'html:dev'), 'html:sitemap'), 'copy')))

// Production
gulp.task('production', gulp.series('clean', gulp.parallel('images', 'scripts:prod', gulp.series(gulp.parallel('styles:prod', 'html:prod'), gulp.parallel('styles:critical', 'html:sitemap')), 'copy')))

// Watch
gulp.task('watch', gulp.series('default', () => {
  const config = require('./_gulpfile/config').default
  const browserSync = require('browser-sync')
  const modRewrite = require('connect-modrewrite')
  const compression = require('compression')

  gulp.watch(config.paths.src.images + '/**/*.{jpg,jpeg,ico,png,gif,svg}', gulp.series('images', done => { // https://github.com/BrowserSync/browser-sync/issues/1065#issuecomment-215516517
    browserSync.reload()
    done()
  }))

  gulp.watch(config.paths.src.scripts + '/**/*.ts', gulp.series('scripts:dev', done => {
    browserSync.reload()
    done()
  }))

  gulp.watch(config.paths.src.styles + '/**/*.scss', gulp.series('styles:dev'))

  gulp.watch([
    config.paths.src.base + '/**/*.pug',
    config.paths.src.data + '/**/*.cson',
  ], gulp.series('html:dev', 'html:sitemap', done => {
    browserSync.reload()
    done()
  }))

  gulp.watch([
    config.paths.src.base + '/robots.txt',
    config.paths.src.base + '/.htaccess',
    config.paths.src.base + '/humans.txt',
  ], gulp.series('copy:base', done => {
    browserSync.reload()
    done()
  }))

  gulp.watch(config.paths.src.base + '/assets/libraries/**/*', gulp.series('copy:libraries', done => {
    browserSync.reload()
    done()
  }))

  gulp.watch(config.paths.src.fonts + '/**/*', gulp.series('copy:fonts', done => {
    browserSync.reload()
    done()
  }))

  // Mirror this part to valimate.js
  browserSync({
    ghostMode: {
      clicks: false,
      scroll: false,
      forms: false,
    },
    server: {
      baseDir: config.paths.dist.base,
      middleware: [
        modRewrite([
          '^.([^\\.]+)$ /$1.html [L]', // Remove .html from URL (as in the .htaccess)
        ]),
        compression(), // Enable gzip compression
      ],
    },
    https: true,
    online: false,
  })
}))
<commit_msg>Fix yml watch task<commit_after>import gulp from 'gulp'

// Clean
gulp.task('clean', callback => {
  const config = require('./_gulpfile/config').default
  require('del')(config.paths.dist.base).then(() => callback())
})

// Scripts
import { prod as scriptsProd, dev as scriptsDev } from './_gulpfile/scripts'
gulp.task('scripts:prod', scriptsProd)
gulp.task('scripts:dev', scriptsDev)

// Styles
import { prod as stylesProd, dev as stylesDev, criticalCSS as stylesCritical } from './_gulpfile/styles'
gulp.task('styles:prod', stylesProd)
gulp.task('styles:dev', stylesDev)
gulp.task('styles:critical', stylesCritical)

// HTML
import { prod as htmlProd, dev as htmlDev, sitemap as htmlSitemap } from './_gulpfile/html'
gulp.task('html:prod', htmlProd)
gulp.task('html:dev', htmlDev)
gulp.task('html:sitemap', htmlSitemap)

// Linting
import { lint as lintStyles } from './_gulpfile/styles'
import { lint as lintHTML } from './_gulpfile/html'
gulp.task('lint:styles', lintStyles)
gulp.task('lint:html', lintHTML)
gulp.task('lint', gulp.parallel('lint:styles', 'lint:html'))

// Images
import { normal as imagesNormal, icons as imagesIcons } from './_gulpfile/images'
gulp.task('images:default', imagesNormal)
gulp.task('images:icons', imagesIcons)
gulp.task('images', gulp.parallel('images:default', 'images:icons'))

// Copy
import { base as copyBase, libraries as copyLibraries, fonts as copyFonts} from './_gulpfile/copy'
gulp.task('copy:base', copyBase)
gulp.task('copy:libraries', copyLibraries)
gulp.task('copy:fonts', copyFonts)
gulp.task('copy', gulp.parallel('copy:base', 'copy:libraries', 'copy:fonts'))

// Default
gulp.task('default', gulp.series('clean', gulp.parallel('images', 'scripts:dev', gulp.series(gulp.parallel('styles:dev', 'html:dev'), 'html:sitemap'), 'copy')))

// Production
gulp.task('production', gulp.series('clean', gulp.parallel('images', 'scripts:prod', gulp.series(gulp.parallel('styles:prod', 'html:prod'), gulp.parallel('styles:critical', 'html:sitemap')), 'copy')))

// Watch
gulp.task('watch', gulp.series('default', () => {
  const config = require('./_gulpfile/config').default
  const browserSync = require('browser-sync')
  const modRewrite = require('connect-modrewrite')
  const compression = require('compression')

  gulp.watch(config.paths.src.images + '/**/*.{jpg,jpeg,ico,png,gif,svg}', gulp.series('images', done => { // https://github.com/BrowserSync/browser-sync/issues/1065#issuecomment-215516517
    browserSync.reload()
    done()
  }))

  gulp.watch(config.paths.src.scripts + '/**/*.ts', gulp.series('scripts:dev', done => {
    browserSync.reload()
    done()
  }))

  gulp.watch(config.paths.src.styles + '/**/*.scss', gulp.series('styles:dev'))

  gulp.watch([
    config.paths.src.base + '/**/*.pug',
    config.paths.src.base + '/**/*.yml',
  ], gulp.series('html:dev', 'html:sitemap', done => {
    browserSync.reload()
    done()
  }))

  gulp.watch([
    config.paths.src.base + '/robots.txt',
    config.paths.src.base + '/.htaccess',
    config.paths.src.base + '/humans.txt',
  ], gulp.series('copy:base', done => {
    browserSync.reload()
    done()
  }))

  gulp.watch(config.paths.src.base + '/assets/libraries/**/*', gulp.series('copy:libraries', done => {
    browserSync.reload()
    done()
  }))

  gulp.watch(config.paths.src.fonts + '/**/*', gulp.series('copy:fonts', done => {
    browserSync.reload()
    done()
  }))

  // Mirror this part to valimate.js
  browserSync({
    ghostMode: {
      clicks: false,
      scroll: false,
      forms: false,
    },
    server: {
      baseDir: config.paths.dist.base,
      middleware: [
        modRewrite([
          '^.([^\\.]+)$ /$1.html [L]', // Remove .html from URL (as in the .htaccess)
        ]),
        compression(), // Enable gzip compression
      ],
    },
    https: true,
    online: false,
  })
}))
<|endoftext|>


<commit_before>/* jshint node: true */
'use strict';

var existsSync = require('exists-sync');
var fs = require('fs');
var path = require('path');
var VersionChecker = require('ember-cli-version-checker');
var walkSync = require('walk-sync');

function getWelcomePageHTML(baseURL, version) {
  var lodash = require('lodash');
  var fileName = path.join(__dirname, 'vendor', 'welcome.html');
  var contents = fs.readFileSync(fileName, { encoding: 'utf8' });
  var compiled = lodash.template(contents);

  return compiled({ baseURL: baseURL, version: version });
}

module.exports = {
  name: 'ember-welcome-page',

  included: function(app) {
    this._super.included(app);

    this.app = app;

    var checker = new VersionChecker(this);
    this.guidesVersion = this.massageVersionNumber(checker.for('ember', 'bower').version);

    this.jsExt = this.app.registry.extensionsForType('js');
    this.templateExt = this.app.registry.extensionsForType('template');
  },

  serverMiddleware: function(config) {
    var app = config.app;
    var options = config.options;

    this.ui.writeInfoLine("\nJust getting started with Ember? Please visit http://localhost:" + options.port + "/ember-getting-started to get going\n");
    var _this = this;

    var welcomePageHTML = getWelcomePageHTML(options.baseURL, this.guidesVersion);

    app.get('/ember-getting-started-image.png', function (req, res, next) {
      res.sendFile(__dirname + '/vendor/construction.png', options, function (err) {
        if (err) {
          res.status(err.status).end();
        }
      });
    });

    app.get('/ember-getting-started', function (req, res, next) {
      res.send(welcomePageHTML);
    });

    // we'll assume we're running '/' but then bail if we aren't supposed to be overriding
    app.get(options.baseURL, function(req, res, next) {
      if (_this.shouldOverride(_this.jsExt, _this.templateExt, _this.app.project.root)) {
        res.send(welcomePageHTML);
      } else {
        next();
      }
    });
  },

  /* requested tests (need to be watching in real-time preferably ...)
   - Has an application.hbs been created?
   - Has an index.hbs been created?
   - Have any routes been created?
   */
  shouldOverride: function(jsExtensions, templateExtensions, folderPath) {

    var appFiles = walkSync(path.join(folderPath, 'app'));

    var jsFiles = appFiles
      .filter(function(fileName) {
        var extension = path.extname(fileName).slice(1); // remove leading `.`

        return jsExtensions.indexOf(extension) > -1;
      });

    var templateFiles = appFiles
      .filter(function(fileName) {
        var extension = path.extname(fileName).slice(1); // remove leading `.`

        return templateExtensions.indexOf(extension) > -1;
      });

    var routeFiles = jsFiles
      .filter(function(fileName) {
        var baseName = path.basename(fileName);

        return baseName === 'route' || fileName.match(/^routes/);
      });

    var routerPath = path.join(folderPath, 'app', 'router.js');
    var hasRouterMapContents = false;

    if (existsSync(routerPath)) {
      var router = fs.readFileSync(routerPath, { encoding: 'utf8' });
      hasRouterMapContents = router.match(/this\.route|this\.resource/);
    }

    var hasTemplates = templateFiles.length > 0;
    var hasRoutes = routeFiles.length > 0;

    return !hasTemplates && !hasRoutes && !hasRouterMapContents;
  },

  massageVersionNumber: function(version) {
    return version.replace(/(\d).(\d).\d/, '$1.$2.0');
  }
};
<commit_msg>add support for ember-core once it lands<commit_after>/* jshint node: true */
'use strict';

var existsSync = require('exists-sync');
var fs = require('fs');
var path = require('path');
var VersionChecker = require('ember-cli-version-checker');
var walkSync = require('walk-sync');

function getWelcomePageHTML(baseURL, version) {
  var lodash = require('lodash');
  var fileName = path.join(__dirname, 'vendor', 'welcome.html');
  var contents = fs.readFileSync(fileName, { encoding: 'utf8' });
  var compiled = lodash.template(contents);

  return compiled({ baseURL: baseURL, version: version });
}

module.exports = {
  name: 'ember-welcome-page',

  included: function(app) {
    this._super.included(app);

    this.app = app;

    var checker = new VersionChecker(this);
    this.guidesVersion = this.massageVersionNumber(checker.for('ember', 'bower').version || checker.for('ember-core', 'npm').version);

    this.jsExt = this.app.registry.extensionsForType('js');
    this.templateExt = this.app.registry.extensionsForType('template');
  },

  serverMiddleware: function(config) {
    var app = config.app;
    var options = config.options;

    this.ui.writeInfoLine("\nJust getting started with Ember? Please visit http://localhost:" + options.port + "/ember-getting-started to get going\n");
    var _this = this;

    var welcomePageHTML = getWelcomePageHTML(options.baseURL, this.guidesVersion);

    app.get('/ember-getting-started-image.png', function (req, res, next) {
      res.sendFile(__dirname + '/vendor/construction.png', options, function (err) {
        if (err) {
          res.status(err.status).end();
        }
      });
    });

    app.get('/ember-getting-started', function (req, res, next) {
      res.send(welcomePageHTML);
    });

    // we'll assume we're running '/' but then bail if we aren't supposed to be overriding
    app.get(options.baseURL, function(req, res, next) {
      if (_this.shouldOverride(_this.jsExt, _this.templateExt, _this.app.project.root)) {
        res.send(welcomePageHTML);
      } else {
        next();
      }
    });
  },

  /* requested tests (need to be watching in real-time preferably ...)
   - Has an application.hbs been created?
   - Has an index.hbs been created?
   - Have any routes been created?
   */
  shouldOverride: function(jsExtensions, templateExtensions, folderPath) {

    var appFiles = walkSync(path.join(folderPath, 'app'));

    var jsFiles = appFiles
      .filter(function(fileName) {
        var extension = path.extname(fileName).slice(1); // remove leading `.`

        return jsExtensions.indexOf(extension) > -1;
      });

    var templateFiles = appFiles
      .filter(function(fileName) {
        var extension = path.extname(fileName).slice(1); // remove leading `.`

        return templateExtensions.indexOf(extension) > -1;
      });

    var routeFiles = jsFiles
      .filter(function(fileName) {
        var baseName = path.basename(fileName);

        return baseName === 'route' || fileName.match(/^routes/);
      });

    var routerPath = path.join(folderPath, 'app', 'router.js');
    var hasRouterMapContents = false;

    if (existsSync(routerPath)) {
      var router = fs.readFileSync(routerPath, { encoding: 'utf8' });
      hasRouterMapContents = router.match(/this\.route|this\.resource/);
    }

    var hasTemplates = templateFiles.length > 0;
    var hasRoutes = routeFiles.length > 0;

    return !hasTemplates && !hasRoutes && !hasRouterMapContents;
  },

  massageVersionNumber: function(version) {
    return version.replace(/(\d).(\d).\d/, '$1.$2.0');
  }
};
<|endoftext|>