<commit_before>/*
 Copyright 2020 First People's Cultural Council

 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

 http://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.
 */
import Grade from '@material-ui/icons/Grade'

import ProviderHelpers from 'common/ProviderHelpers'
import DialectTile from 'components/DialectTile'
import { List } from 'immutable'
import PropTypes from 'prop-types'
import React, { Component } from 'react'

// REDUX
import { connect } from 'react-redux'
import { pushWindowPath } from 'reducers/windowPath'
import selectn from 'selectn'
import NavigationHelpers from '../common/NavigationHelpers'

const { oneOfType, instanceOf, array, func, object, string, bool } = PropTypes

export class PortalListDialects extends Component {
  static propTypes = {
    items: oneOfType([array, instanceOf(List)]),
    filteredItems: oneOfType([array, instanceOf(List)]),
    fieldMapping: object,
    siteTheme: string.isRequired,
    pushWindowPath: func.isRequired,
    isWorkspaces: bool.isRequired,
    languages: array.isRequired,
    intl: object.isRequired,
  }

  static defaultProps = {
    fieldMapping: {
      title: 'properties.dc:title',
      logo: 'properties.file:content',
    },
  }

  componentDidUpdate() {
    const link = decodeURI(window.location.hash.substring(1))
    if (link) {
      // We want to be able to grab any of the SENĆOŦEN / Malchosen / Lekwungen / Semiahmoo / T’Sou-ke.
      // This allows us to have a URL with # + one of any of the above names
      const anchor = document.querySelector(`[id*="${link}"]`)
      if (anchor) {
        anchor.scrollIntoView({ behavior: 'smooth', block: 'center' })
      }
    }
  }

  _createTile = (tile) => {
    // Switch roles
    const dialectRoles = selectn('roles', tile)
    const actionIcon = ProviderHelpers.isActiveRole(dialectRoles) ? (

        <Grade />

    ) : null

    // Dialect title
    const title = selectn('title', tile)
    let dialectCoverImage = 'assets/images/cover.png'
    const logoID = selectn('logoId', tile) || null

    if (logoID !== null) {
      dialectCoverImage = `${NavigationHelpers.getBaseURL()}nxpicsfile/default/${tile.logoPath}/Thumbnail:content/`
    }

    dialectCoverImage = encodeURI(dialectCoverImage)

    const href = encodeURI(`/${this.props.siteTheme}${tile.path.replace('/Portal', '')}`)
    const dialectTitle = this.props.intl.searchAndReplace(title)
    const dialectDescription = tile.description ? (
      {this.props.intl.searchAndReplace(tile.description)}
    ) : null
    return (
      <DialectTile
        key={tile.uid}
        dialectCoverImage={dialectCoverImage}
        href={href}
        dialectTitle={dialectTitle}
        dialectDescription={dialectDescription}
        actionIcon={actionIcon}
        pushWindowPath={this.props.pushWindowPath}
        data-cy="dialect-tile"
      />
    )
  }

  _oldView = () => {
    const items = this.props.filteredItems || this.props.items
    return (

        {items.map((tile) => this._createTile(tile))}

    )
  }

  _getContent = () => {
    if (this.props.isWorkspaces) {
      return this._oldView()
    }

    const items = this.props.filteredItems || this.props.items

    // Maps each language name to an array of the associated archives
    // For example {'Secwepemctsín' : [Splatsin Tsm7aksaltn's Archive, Secwepecm's Archive, etc. ] }
    const languages = {}

    // Maps language name to the language color.
    const languageColors = {}

    // If the language doesn't have a parent language set, it will be put in the 'Other FirstVoices Archives'.
    const defaultArchiveName = this.props.intl.searchAndReplace('Other FirstVoices Language Sites')

    // Perform mapping of above objects
    this.props.languages.forEach((lang) => {
      languages[lang.label] = []
      languageColors[lang.label] = lang.color
    })

    items.forEach((archive) => {
      const archiveName = selectn('parentLanguageTitle', archive) || defaultArchiveName
      if (!languages[archiveName]) {
        languages[archiveName] = []
      }
      languages[archiveName].push(archive)
    })

    const toReturn = Object.keys(languages)
      .sort()
      .map((key) => {
        if (key !== defaultArchiveName) {
          return this._tile(key, languageColors[key], languages[key])
        }
        return null
      })

    // We want the 'Other FirstVoices Archives' to appear last.
    if (languages[defaultArchiveName] && languages[defaultArchiveName].length > 0) {
      toReturn.push(this._tile(defaultArchiveName, 'rgb(180, 0, 0)', languages[defaultArchiveName]))
    }
    return toReturn
  }

  _tile = (key, color, archives) => {
    return (
      <div
        className="languageGroup fontBCSans"
        data-cy="language_group_view"
        key={key}
        style={{ borderLeft: `4px ${color} solid` }}
      >

          {key}

          {archives.length > 0
            ? archives.map((tile) => this._createTile(tile))
            : this.props.intl.searchAndReplace('No public language site.')}

    )
  }

  render() {
    return
{this._getContent()}

  }
}

// REDUX: reducers/state
const mapStateToProps = (state) => {
  const { locale } = state
  const { intlService } = locale

  return {
    intl: intlService,
  }
}

// REDUX: actions/dispatch/func
const mapDispatchToProps = {
  pushWindowPath,
}

export default connect(mapStateToProps, mapDispatchToProps)(PortalListDialects)
<commit_msg>corrected logo path in JS (#603)<commit_after>/*
 Copyright 2020 First People's Cultural Council

 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

 http://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.
 */
import Grade from '@material-ui/icons/Grade'

import ProviderHelpers from 'common/ProviderHelpers'
import DialectTile from 'components/DialectTile'
import { List } from 'immutable'
import PropTypes from 'prop-types'
import React, { Component } from 'react'

// REDUX
import { connect } from 'react-redux'
import { pushWindowPath } from 'reducers/windowPath'
import selectn from 'selectn'
import NavigationHelpers from '../common/NavigationHelpers'

const { oneOfType, instanceOf, array, func, object, string, bool } = PropTypes

export class PortalListDialects extends Component {
  static propTypes = {
    items: oneOfType([array, instanceOf(List)]),
    filteredItems: oneOfType([array, instanceOf(List)]),
    fieldMapping: object,
    siteTheme: string.isRequired,
    pushWindowPath: func.isRequired,
    isWorkspaces: bool.isRequired,
    languages: array.isRequired,
    intl: object.isRequired,
  }

  static defaultProps = {
    fieldMapping: {
      title: 'properties.dc:title',
      logo: 'properties.file:content',
    },
  }

  componentDidUpdate() {
    const link = decodeURI(window.location.hash.substring(1))
    if (link) {
      // We want to be able to grab any of the SENĆOŦEN / Malchosen / Lekwungen / Semiahmoo / T’Sou-ke.
      // This allows us to have a URL with # + one of any of the above names
      const anchor = document.querySelector(`[id*="${link}"]`)
      if (anchor) {
        anchor.scrollIntoView({ behavior: 'smooth', block: 'center' })
      }
    }
  }

  _createTile = (tile) => {
    // Switch roles
    const dialectRoles = selectn('roles', tile)
    const actionIcon = ProviderHelpers.isActiveRole(dialectRoles) ? (

        <Grade />

    ) : null

    // Dialect title
    const title = selectn('title', tile)
    let dialectCoverImage = 'assets/images/cover.png'
    const logoID = selectn('logoId', tile) || null

    if (logoID !== null) {
      dialectCoverImage = `${NavigationHelpers.getBaseURL()}nxpicsfile/default/${tile.logoId}/Thumbnail:content/`
    }

    dialectCoverImage = encodeURI(dialectCoverImage)

    const href = encodeURI(`/${this.props.siteTheme}${tile.path.replace('/Portal', '')}`)
    const dialectTitle = this.props.intl.searchAndReplace(title)
    const dialectDescription = tile.description ? (
      {this.props.intl.searchAndReplace(tile.description)}
    ) : null
    return (
      <DialectTile
        key={tile.uid}
        dialectCoverImage={dialectCoverImage}
        href={href}
        dialectTitle={dialectTitle}
        dialectDescription={dialectDescription}
        actionIcon={actionIcon}
        pushWindowPath={this.props.pushWindowPath}
        data-cy="dialect-tile"
      />
    )
  }

  _oldView = () => {
    const items = this.props.filteredItems || this.props.items
    return (

        {items.map((tile) => this._createTile(tile))}

    )
  }

  _getContent = () => {
    if (this.props.isWorkspaces) {
      return this._oldView()
    }

    const items = this.props.filteredItems || this.props.items

    // Maps each language name to an array of the associated archives
    // For example {'Secwepemctsín' : [Splatsin Tsm7aksaltn's Archive, Secwepecm's Archive, etc. ] }
    const languages = {}

    // Maps language name to the language color.
    const languageColors = {}

    // If the language doesn't have a parent language set, it will be put in the 'Other FirstVoices Archives'.
    const defaultArchiveName = this.props.intl.searchAndReplace('Other FirstVoices Language Sites')

    // Perform mapping of above objects
    this.props.languages.forEach((lang) => {
      languages[lang.label] = []
      languageColors[lang.label] = lang.color
    })

    items.forEach((archive) => {
      const archiveName = selectn('parentLanguageTitle', archive) || defaultArchiveName
      if (!languages[archiveName]) {
        languages[archiveName] = []
      }
      languages[archiveName].push(archive)
    })

    const toReturn = Object.keys(languages)
      .sort()
      .map((key) => {
        if (key !== defaultArchiveName) {
          return this._tile(key, languageColors[key], languages[key])
        }
        return null
      })

    // We want the 'Other FirstVoices Archives' to appear last.
    if (languages[defaultArchiveName] && languages[defaultArchiveName].length > 0) {
      toReturn.push(this._tile(defaultArchiveName, 'rgb(180, 0, 0)', languages[defaultArchiveName]))
    }
    return toReturn
  }

  _tile = (key, color, archives) => {
    return (
      <div
        className="languageGroup fontBCSans"
        data-cy="language_group_view"
        key={key}
        style={{ borderLeft: `4px ${color} solid` }}
      >

          {key}

          {archives.length > 0
            ? archives.map((tile) => this._createTile(tile))
            : this.props.intl.searchAndReplace('No public language site.')}

    )
  }

  render() {
    return
{this._getContent()}

  }
}

// REDUX: reducers/state
const mapStateToProps = (state) => {
  const { locale } = state
  const { intlService } = locale

  return {
    intl: intlService,
  }
}

// REDUX: actions/dispatch/func
const mapDispatchToProps = {
  pushWindowPath,
}

export default connect(mapStateToProps, mapDispatchToProps)(PortalListDialects)
<|endoftext|>


<commit_before>import React, {PropTypes} from "react";
import {connect} from "react-redux";
import Moment from "moment";
import R from "ramda";
import Utils from "./ComponentUtils.es6";
import "moment-duration-format";
import {findParentOfFailedSubstep} from "steps/FailureStepFinder.es6";
import {isStepInParallel} from "steps/InParallelChecker.es6";
import {StateIcon} from "StateIcon.es6";
import Tools from "steps/Tools.es6";
import {toggleParallelStep} from "actions/BuildStepActions.es6";

export const duration = ({startTime, endTime}) => {
    const start = Moment(startTime);
    const end = Moment(endTime);

    const duration = Moment.duration(end.diff(start), "milliseconds");
    const durationString = duration.format("hh:mm:ss");
    return durationString.length < 5 ? "00:" + durationString : durationString;
};

export const getStepDuration = (step) => {
    if (step.endTime || !step.startTime) {
        return step;
    }
    const endTime = Moment();
    return ({startTime: step.startTime, endTime: endTime});
};

export const StepInfos = ({step}) => {
    return

        <StateIcon state={step.state}/>

{step.name}

{duration(getStepDuration(step))}

;
};

StepInfos.propTypes = {
    step: PropTypes.object.isRequired
};

const HideLine = ({isParallel}) => {
    if(isParallel){
        return

;
    }
    return

;
};

HideLine.propTypes = {
    isParallel: PropTypes.bool.isRequired
};

export class BuildStep extends React.Component {

    constructor(props) {
        super(props);
    }

    showParallelSteps() {
        const {buildId, step, toggleParallelStep} = this.props;

        const steps = R.map(step => <BuildStepCon key={step.stepId} buildId={buildId} step={step}/>)(step.steps);

        return

{steps}

;
    }

    render() {
        const {step, buildId, isParallel, showDirectlyInParallel, failureStep} = this.props;

        if (!isParallel && showDirectlyInParallel) {
            return this.showParallelSteps();
        }

        const buildStepClasses = Utils.classes("buildStep", step.state, isParallel ? "inParallel" : "");

        return

            <HideLine isParallel={isParallel}/>
            <HideLine isParallel={isParallel}/>
            <StepInfos step={step}/>
            <Tools buildId={buildId} step={step} failureStep={failureStep}/>

;
    }
}

BuildStep.propTypes = {
    step: PropTypes.object.isRequired,
    buildId: PropTypes.number.isRequired,
    isParallel: PropTypes.bool.isRequired,
    showDirectlyInParallel: PropTypes.bool.isRequired,
    failureStep: PropTypes.string,
    toggleParallelStep: PropTypes.func.isRequired
};

export const mapStateToProps = (state, ownProps) => {

    return R.merge(ownProps, {
        isParallel: isStepInParallel(state, ownProps.buildId, ownProps.step.stepId),
        showDirectlyInParallel: R.pathOr(false, [ownProps.buildId, ownProps.step.stepId])(state.showInParallel),
        buildId: ownProps.buildId,
        failureStep: findParentOfFailedSubstep(state, ownProps.buildId, ownProps.step.stepId)
    });
};

const mapDispatchToProps = (dispatch, ownProps) => {
    return {
        toggleParallelStep: () => dispatch(toggleParallelStep(ownProps.buildId, ownProps.step.stepId))
    };
};

const BuildStepCon = connect(mapStateToProps, mapDispatchToProps)(BuildStep);
export default BuildStepCon;<commit_msg>refactoring buildStep<commit_after>import React, {PropTypes} from "react";
import {connect} from "react-redux";
import Moment from "moment";
import R from "ramda";
import Utils from "./ComponentUtils.es6";
import "moment-duration-format";
import {findParentOfFailedSubstep} from "steps/FailureStepFinder.es6";
import {isStepInParallel} from "steps/InParallelChecker.es6";
import {StateIcon} from "StateIcon.es6";
import Tools from "steps/Tools.es6";
import {toggleParallelStep} from "actions/BuildStepActions.es6";

export const duration = ({startTime, endTime}) => {
    const start = Moment(startTime);
    const end = Moment(endTime);

    const duration = Moment.duration(end.diff(start), "milliseconds");
    const durationString = duration.format("hh:mm:ss");
    return durationString.length < 5 ? "00:" + durationString : durationString;
};

export const getStepDuration = (step) => {
    if (step.endTime || !step.startTime) {
        return step;
    }
    const endTime = Moment();
    return ({startTime: step.startTime, endTime: endTime});
};

export const StepInfos = ({step}) => {
    return

        <StateIcon state={step.state}/>

{step.name}

{duration(getStepDuration(step))}

;
};

StepInfos.propTypes = {
    step: PropTypes.object.isRequired
};

const HideLine = ({isParallel}) => {
    if(isParallel){
        return

;
    }
    return

;
};

HideLine.propTypes = {
    isParallel: PropTypes.bool.isRequired
};

export class BuildStep extends React.Component {

    constructor(props) {
        super(props);
    }

    showParallelSteps() {
        const {buildId, step, toggleParallelStep} = this.props;

        const steps = R.map(step => <BuildStepCon key={step.stepId} buildId={buildId} step={step}/>)(step.steps);

        return

{steps}

;
    }

    render() {
        const {step, buildId, isParallel, showDirectlyInParallel, failureStep} = this.props;

        if (!isParallel && showDirectlyInParallel) {
            return this.showParallelSteps();
        }

        const buildStepClasses = Utils.classes("buildStep", step.state, isParallel ? "inParallel" : "");

        return

            <HideLine isParallel={isParallel}/>
            <StepInfos step={step}/>
            <Tools buildId={buildId} step={step} failureStep={failureStep}/>

;
    }
}

BuildStep.propTypes = {
    step: PropTypes.object.isRequired,
    buildId: PropTypes.number.isRequired,
    isParallel: PropTypes.bool.isRequired,
    showDirectlyInParallel: PropTypes.bool.isRequired,
    failureStep: PropTypes.string,
    toggleParallelStep: PropTypes.func.isRequired
};

export const mapStateToProps = (state, ownProps) => {

    return R.merge(ownProps, {
        isParallel: isStepInParallel(state, ownProps.buildId, ownProps.step.stepId),
        showDirectlyInParallel: R.pathOr(false, [ownProps.buildId, ownProps.step.stepId])(state.showInParallel),
        buildId: ownProps.buildId,
        failureStep: findParentOfFailedSubstep(state, ownProps.buildId, ownProps.step.stepId)
    });
};

const mapDispatchToProps = (dispatch, ownProps) => {
    return {
        toggleParallelStep: () => dispatch(toggleParallelStep(ownProps.buildId, ownProps.step.stepId))
    };
};

const BuildStepCon = connect(mapStateToProps, mapDispatchToProps)(BuildStep);
export default BuildStepCon;<|endoftext|>


<commit_before>angular.module("hackGMS", [])

.controller("hackGMSController", ['$http', '$scope', function($http, $scope) {

    $scope.reloadMessages = function() {
        $http.get('api/messages').then(function(response) {
            $scope.messages = response.data;
            _.each($scope.messages, function(message) {
                message.date = new Date(message.date);
            })
        });
    }

    $scope.addMessage = function() {
        var newMessageData = {
            text: $scope.messageText,
            date: new Date()
        };
        $http.post('api/messages/create', newMessageData)
            .then($scope.reloadMessages)
            .then(function() {
                $scope.messageText = "";
            });
    }

    $scope.deleteMessage = function(message) {
        $http.get('api/messages/delete/' + message.id)
            .then($scope.reloadMessages)
    }

    $scope.reloadMessages()

}])
.directive("gmsMessage", function() {
    return {
        templateUrl: "static/templates/message.html"
    }
})
<commit_msg>Main page automatically checks for updates<commit_after>angular.module("hackGMS", [])

.controller("hackGMSController", ['$http', '$interval', '$scope', function($http, $interval, $scope) {

    $scope.reloadMessages = function() {
        $http.get('api/messages').then(function(response) {
            $scope.messages = response.data;
            _.each($scope.messages, function(message) {
                message.date = new Date(message.date);
            })
        });
    }

    $scope.addMessage = function() {
        var newMessageData = {
            text: $scope.messageText,
            date: new Date()
        };
        $http.post('api/messages/create', newMessageData)
            .then($scope.reloadMessages)
            .then(function() {
                $scope.messageText = "";
            });
    }

    $scope.deleteMessage = function(message) {
        $http.get('api/messages/delete/' + message.id)
            .then($scope.reloadMessages)
    }

    $interval($scope.reloadMessages, 5000)

}])
.directive("gmsMessage", function() {
    return {
        templateUrl: "static/templates/message.html"
    }
})
<|endoftext|>


<commit_before>var instructionsState = {

  create: function () {
    _setBackgroundImage('parchment');
    battleStateButton = new MenuButton(175, 500, "mainMenuButtons", "mapSelectState", "map", "army", "battleButton", 0, 0, 1, 0);
    campaignStateButton = new MenuButton(450, 500, "mainMenuButtons", "campaignState", "map", "army", "battleButton", 2, 2, 3, 2);
    game.add.text(300, 50, "Instructions", {font: "24px Herculanum"});
    game.add.text(30, 100, "This is FANTASY WARS, a turn-based strategy game chronicling the ", {font: "16px Herculanum", wordWrap: true, wordWrapWidth: 600})
  }

};

function _setBackgroundImage(imgKey) {
  var logo = game.add.image(0, 0, imgKey);
  logo.width = game.width;
  logo.height = game.height;
};
<commit_msg>Adjust instructions<commit_after>var instructionsState = {

  create: function () {
    _setBackgroundImage('parchment');
    battleStateButton = new MenuButton(175, 500, "mainMenuButtons", "mapSelectState", "map", "army", "battleButton", 0, 0, 1, 0);
    campaignStateButton = new MenuButton(450, 500, "mainMenuButtons", "campaignState", "map", "army", "battleButton", 2, 2, 3, 2);
    game.add.text(300, 50, "Instructions", {font: "24px Herculanum"});
    game.add.text(30, 100, "This is FANTASY WARS, a turn-based strategy game in which two armies do battle on a", {font: "16px Herculanum", wordWrap: true, wordWrapWidth: 600})
  }

};

function _setBackgroundImage(imgKey) {
  var logo = game.add.image(0, 0, imgKey);
  logo.width = game.width;
  logo.height = game.height;
};
<|endoftext|>


<commit_before>function Pic ( div, url ) {
    //save the detail
    var div = div;
    var url = url;

    //zoom level
    var scale = 1;

    //default zoom level
    var defaultZoomScale = 0.2;

    //default max and min scale of the picture
    var defaultMaxScale = 3;
    var defaultMinScale = 0.4;

    //position offsetX and offsetY
    var offsetX = 0;
    var offsetY = 0;

    //record the position when touchdown
    var initX = 0;
    var initY = 0;

    //use to caculate the new position
    var startX = 0;
    var startY = 0;

    //whether it is dragging
    var isDragging = false;

    //use this to record when pinch action the distance between two fingures
    var originDistance = undefined;

    //use this to record pinch result
    var zoomAction = null;

    //zones are clickable
    var zones = getSomeZone(100);

    //Zones visiblity
    var isZonesVisible = true;

    //build the innerDiv
    var innerDiv = document.createElement("div");
    innerDiv.style.position = "absolute";
    innerDiv.id = "innerDiv";
    innerDiv.style.left = "0";
    innerDiv.style.top = "0";

    //build the innerCanvas
    var innerCanvas = document.createElement("canvas");
    innerCanvas.style.position = "absolute";
    innerCanvas.style.left = "0";
    innerCanvas.style.top = "0";

    //get the 2D canvas context
    var context = innerCanvas.getContext("2d");

    //add the two element to the dom tree
    div.appendChild(innerDiv);
    innerDiv.appendChild(innerCanvas);

    //using Image as the src of canvacs
    var img = new Image();
    img.onload = init;
    img.src = url;

    //save the width and height of img
    var imgWidth = 0;
    var imgHeight = 0;

    //save the infoDiv's dom
    var info = null;

    //init the picture
    function init() {
        imgWidth = parseFloat(img.width);
        imgHeight = parseFloat(img.height);
        draw();
    }

    //draw the picture and the zones
    function draw() {
        drawImage();
        if ( true === isZonesVisible ) {
            drawZones();
        }
    }

    //draw the picture
    function drawImage() {
        context.clearRect(0,0,innerCanvas.width,innerCanvas.height);
        var newW = imgWidth*scale;
        var newH = imgHeight*scale;
        innerCanvas.width = newW;
        innerCanvas.height = newH;
        context.drawImage(img,0,0,newW,newH);
    }

    //draw the zones
    function drawZones() {
        var i;
        for ( i in zones ) {
            context.lineWidth = 1;
            context.strokeStyle = "red";
            context.strokeRect(zones[i].x*scale,zones[i].y*scale,zones[i].width*scale,zones[i].width*scale);
        }
    }

    //use click replace the tap function it's faster than tap
    function click(event) {
        var x = parseFloat(event.pageX)-offsetX-div.offsetLeft;
        var y = parseFloat(event.pageY)-offsetY-div.offsetTop;
        var point = {x:x/scale,y:y/scale};
        var num = isInZones(point);
        if ( false !== num ) {
            if ( info ) {
                innerDiv.removeChild(info);
                info = null;
            }
            point.x *= scale;
            point.y *= scale;
            point.text="this is zone "+num;
            info = new Info(point);
            innerDiv.appendChild(info);
        }
    }

    //move the left top to new position
    function move() {
        innerDiv.style.left = offsetX+"px";
        innerDiv.style.top = offsetY+"px";
    }

    //zoom the pic with deltaScale
    function zoom( deltaScale ) {
        scale += deltaScale;
        draw();
    }

    //zoomIn
    function zoomIn( deltaScale ) {
        if ( scale >= defaultMaxScale ) {
            return;
        }
        else {
            if ( deltaScale ) {
                zoom(deltaScale);
            } else {
                zoom(defaultZoomScale);
            }
        }
        return false;
    }

    //zoomOut
    function zoomOut( deltaScale ) {
        if ( scale <= defaultMinScale ) {
            return;
        }
        else {
            if ( deltaScale ) {
                zoom(deltaScale);
            } else {
                zoom(-defaultZoomScale);
            }
        }
        return false;
    }

    //zoom  and keep the pinch center stay where it was
    function zoomToPoint( point ) {
        if ( point == null ) {
            return;
        }
        var left = parseFloat(innerDiv.style.left);
        var top =  parseFloat(innerDiv.style.top);
        var midLeft = point.x - left;
        var midTop = point.y - top;
        midLeft /= scale;
        midTop /= scale;
        if ( point.action == -1 && scale <= defaultMinScale ) {
            return;
        }
        if ( point.action == 1 && scale >= defaultMaxScale ) {
            return;
        }
        if ( point.action == 1 ) {
            scale += 0.05;
        } else {
            scale -= 0.05;
        }
        // scale += point.action*defaultZoomScale;
        midLeft *= scale;
        midTop *= scale;
        offsetX = point.x - midLeft;
        offsetY =  point.y - midTop;
        move ();
        draw();
    }

    // touchdown and start to move
    function moveStart( event ) {
        isDragging = true;
        startX = event.gesture.deltaX;
        startY = event.gesture.deltaY;
        initX = startX;
        initY = startY;
        return false;
    }

    // touchmove
    function moving( event ) {
        if ( isDragging ) {
            var dx = event.gesture.deltaX - startX;
            var dy = event.gesture.deltaY - startY;
            startX = event.gesture.deltaX;
            startY = event.gesture.deltaY;
            offsetX += dx;
            offsetY += dy;
            move();
        }
        return false;
    }

    //touchend and end move
    function moveEnd(event) {
        isDragging = false;
        return false;
    }

    //pinch start
    function pinchStart( event ) {
        if ( info )   {
            innerDiv.removeChild(info);
            info = null;
        }
        var gesture = event.gesture;
        if ( originDistance === undefined ) {
            originDistance = getDistance( gesture.touches );
        }
        var currentDistance = getDistance( gesture.touches );
        if ( currentDistance+3 < originDistance ) {
            innerDiv.className = "zoomOut";
            zoomAction = { x : event.gesture.center.pageX - div.offsetLeft,
                           y : event.gesture.center.pageY - div.offsetTop,
                           action : -1
                         };
            originDistance = currentDistance;
            zoomToPoint(zoomAction);
        } else if ( currentDistance > originDistance+3 ) {
            innerDiv.className = "zoomIn";
            zoomAction = { x : event.gesture.center.pageX - div.offsetLeft,
                           y : event.gesture.center.pageY - div.offsetTop,
                           action : 1
                         };
            originDistance = currentDistance;
            zoomToPoint(zoomAction);

        }
        return false;
    }

    //pinch end and make zoomIn or zoomOut
    function pinchEnd( event ) {
        innerDiv.className = "";
        // zoomToPoint(zoomAction);
        originDistance = undefined;
        zoomAction = null;
    }

    //get distance from hammer gestures
    function getDistance( touches ) {
        var p1 = touches[0];
        var p2 = touches[1];
        x = p1.pageX - p2.pageX;
        y = p2.pageY - p2.pageY;
        var result = Math.sqrt( x*x + y*y );
        return result;
    }

    // bind the event
    function bindEvent() {
        var hammer = new Hammer(div);
        hammer.on("pinch",pinchStart);
        hammer.on("release",pinchEnd);
        hammer.on("dragstart",moveStart);
        hammer.on("drag",moving);
        hammer.on("dragend",moveEnd);
        hammer.on("click",click);
    }

    //make sure that the position clicked is in one of the zones
    function isInZones( point ) {
        var i;
        var zone = {x:point.x,y:point.y,width:0};
        for ( i in zones ) {
            if ( !checkOne(zone,zones[i]) ) {
                return i;
            }
        }
        return false;
    }

    //open some API
    //set zones
    this.setZones = function( _zones ) {
        zones = _zones;
        draw();
    }
    //get zones
    this.getZones = function () {
        return zones;
    }
    //set zones' visibility
    this.setZonesVisiable = function ( visiable ) {
        isZonesVisible = visiable;
        draw();
    }
    //get zones' visibility
    this.getZonesVisiable = function() {
        return isZonesVisible;
    }
    //clear info div
    this.clear = function() {
        if ( info ) {
            innerDiv.removeChild(info);
            info = null;
        }
    }

    bindEvent();
    return this;
}

<commit_msg>default<commit_after>function Pic ( div, url ) {
    //save the detail
    var div = div;
    var url = url;

    //zoom level
    var scale = 1;

    //default zoom level
    var defaultZoomScale = 0.2;

    //default max and min scale of the picture
    var defaultMaxScale = 3;
    var defaultMinScale = 0.4;

    //position offsetX and offsetY
    var offsetX = 0;
    var offsetY = 0;

    //record the position when touchdown
    var initX = 0;
    var initY = 0;

    //use to caculate the new position
    var startX = 0;
    var startY = 0;

    //whether it is dragging
    var isDragging = false;

    //use this to record when pinch action the distance between two fingures
    var originDistance = undefined;

    //use this to record pinch result
    var zoomAction = null;

    //zones are clickable
    var zones = getSomeZone(100);

    //Zones visiblity
    var isZonesVisible = true;

    //build the innerDiv
    var innerDiv = document.createElement("div");
    innerDiv.style.position = "absolute";
    innerDiv.id = "innerDiv";
    innerDiv.style.left = "0";
    innerDiv.style.top = "0";

    //build the innerCanvas
    var innerCanvas = document.createElement("canvas");
    innerCanvas.style.position = "absolute";
    innerCanvas.style.left = "0";
    innerCanvas.style.top = "0";

    //get the 2D canvas context
    var context = innerCanvas.getContext("2d");

    //add the two element to the dom tree
    div.appendChild(innerDiv);
    innerDiv.appendChild(innerCanvas);

    //using Image as the src of canvacs
    var img = new Image();
    img.onload = init;
    img.src = url;

    //save the width and height of img
    var imgWidth = 0;
    var imgHeight = 0;

    //save the infoDiv's dom
    var info = null;

    //init the picture
    function init() {
        imgWidth = parseFloat(img.width);
        imgHeight = parseFloat(img.height);
        draw();
    }

    //draw the picture and the zones
    function draw() {
        drawImage();
        if ( true === isZonesVisible ) {
            drawZones();
        }
    }

    //draw the picture
    function drawImage() {
        context.clearRect(0,0,innerCanvas.width,innerCanvas.height);
        var newW = imgWidth*scale;
        var newH = imgHeight*scale;
        innerCanvas.width = newW;
        innerCanvas.height = newH;
        context.drawImage(img,0,0,newW,newH);
    }

    //draw the zones
    function drawZones() {
        var i;
        for ( i in zones ) {
            context.lineWidth = 1;
            context.strokeStyle = "red";
            context.strokeRect(zones[i].x*scale,zones[i].y*scale,zones[i].width*scale,zones[i].width*scale);
        }
    }

    //use click replace the tap function it's faster than tap
    function click(event) {
        var x = parseFloat(event.pageX)-offsetX-div.offsetLeft;
        var y = parseFloat(event.pageY)-offsetY-div.offsetTop;
        var point = {x:x/scale,y:y/scale};
        var num = isInZones(point);
        if ( false !== num ) {
            if ( info ) {
                innerDiv.removeChild(info);
                info = null;
            }
            point.x *= scale;
            point.y *= scale;
            point.text="this is zone "+num;
            info = new Info(point);
            innerDiv.appendChild(info);
        }
    }

    //move the left top to new position
    function move() {
        innerDiv.style.left = offsetX+"px";
        innerDiv.style.top = offsetY+"px";
    }

    //zoom the pic with deltaScale
    function zoom( deltaScale ) {
        scale += deltaScale;
        draw();
    }

    //zoomIn
    function zoomIn( deltaScale ) {
        if ( scale >= defaultMaxScale ) {
            return;
        }
        else {
            if ( deltaScale ) {
                zoom(deltaScale);
            } else {
                zoom(defaultZoomScale);
            }
        }
        return false;
    }

    //zoomOut
    function zoomOut( deltaScale ) {
        if ( scale <= defaultMinScale ) {
            return;
        }
        else {
            if ( deltaScale ) {
                zoom(deltaScale);
            } else {
                zoom(-defaultZoomScale);
            }
        }
        return false;
    }

    //zoom  and keep the pinch center stay where it was
    function zoomToPoint( point ) {
        if ( point == null ) {
            return;
        }
        var left = parseFloat(innerDiv.style.left);
        var top =  parseFloat(innerDiv.style.top);
        var midLeft = point.x - left;
        var midTop = point.y - top;
        midLeft /= scale;
        midTop /= scale;
        if ( point.action == -1 && scale <= defaultMinScale ) {
            return;
        }
        if ( point.action == 1 && scale >= defaultMaxScale ) {
            return;
        }
        if ( point.action == 1 ) {
            scale += 0.05;
        } else {
            scale -= 0.05;
        }
        // scale += point.action*defaultZoomScale;
        midLeft *= scale;
        midTop *= scale;
        offsetX = point.x - midLeft;
        offsetY =  point.y - midTop;
        move();
        innerDiv.style.transform = "scale("+scale+")";
		innerDiv.style.mozTransform = "scale("+scale+")";
		innerDiv.style.webkitTransform = "scale("+scale+")";
		innerDiv.style.oTransform = "scale("+scale+")";
        // draw();
    }

    // touchdown and start to move
    function moveStart( event ) {
        isDragging = true;
        startX = event.gesture.deltaX;
        startY = event.gesture.deltaY;
        initX = startX;
        initY = startY;
        return false;
    }

    // touchmove
    function moving( event ) {
        if ( isDragging ) {
            var dx = event.gesture.deltaX - startX;
            var dy = event.gesture.deltaY - startY;
            startX = event.gesture.deltaX;
            startY = event.gesture.deltaY;
            offsetX += dx;
            offsetY += dy;
            move();
        }
        return false;
    }

    //touchend and end move
    function moveEnd(event) {
        isDragging = false;
        return false;
    }

    //pinch start
    function pinchStart( event ) {
        if ( info )   {
            innerDiv.removeChild(info);
            info = null;
        }
        var gesture = event.gesture;
        if ( originDistance === undefined ) {
            originDistance = getDistance( gesture.touches );
        }
        var currentDistance = getDistance( gesture.touches );
        if ( currentDistance+3 < originDistance ) {
            innerDiv.className = "zoomOut";
            zoomAction = { x : event.gesture.center.pageX - div.offsetLeft,
                           y : event.gesture.center.pageY - div.offsetTop,
                           action : -1
                         };
            originDistance = currentDistance;
            zoomToPoint(zoomAction);
        } else if ( currentDistance > originDistance+3 ) {
            innerDiv.className = "zoomIn";
            zoomAction = { x : event.gesture.center.pageX - div.offsetLeft,
                           y : event.gesture.center.pageY - div.offsetTop,
                           action : 1
                         };
            originDistance = currentDistance;
            zoomToPoint(zoomAction);

        }
        return false;
    }

    //pinch end and make zoomIn or zoomOut
    function pinchEnd( event ) {
        innerDiv.className = "";
        // zoomToPoint(zoomAction);
        originDistance = undefined;
        zoomAction = null;
    }

    //get distance from hammer gestures
    function getDistance( touches ) {
        var p1 = touches[0];
        var p2 = touches[1];
        x = p1.pageX - p2.pageX;
        y = p2.pageY - p2.pageY;
        var result = Math.sqrt( x*x + y*y );
        return result;
    }

    // bind the event
    function bindEvent() {
        var hammer = new Hammer(div);
        hammer.on("pinch",pinchStart);
        hammer.on("release",pinchEnd);
        hammer.on("dragstart",moveStart);
        hammer.on("drag",moving);
        hammer.on("dragend",moveEnd);
        hammer.on("click",click);
    }

    //make sure that the position clicked is in one of the zones
    function isInZones( point ) {
        var i;
        var zone = {x:point.x,y:point.y,width:0};
        for ( i in zones ) {
            if ( !checkOne(zone,zones[i]) ) {
                return i;
            }
        }
        return false;
    }

    //open some API
    //set zones
    this.setZones = function( _zones ) {
        zones = _zones;
        draw();
    }
    //get zones
    this.getZones = function () {
        return zones;
    }
    //set zones' visibility
    this.setZonesVisiable = function ( visiable ) {
        isZonesVisible = visiable;
        draw();
    }
    //get zones' visibility
    this.getZonesVisiable = function() {
        return isZonesVisible;
    }
    //clear info div
    this.clear = function() {
        if ( info ) {
            innerDiv.removeChild(info);
            info = null;
        }
    }

    bindEvent();
    return this;
}

<|endoftext|>


<commit_before>'use strict';

const Credentials = require('../app/credentials');
const request     = require('request');

module.exports = class YT {
  /**
   * Convenient place to store the different URLs that are
   * given by the YouTube Data API or just related to YouTube.
   *
   * @return {Object}
   */
  static get url() {
    return {
      token:  'https://accounts.google.com/o/oauth2/token',
      auth:   'https://accounts.google.com/o/oauth2/auth',
      data:   'https://www.googleapis.com/youtube/v3/',
      revoke: 'https://accounts.google.com/b/0/IssuedAuthSubTokens',
      info:   'http://www.youtube.com/get_video_info',
      mp3:    'http://youtube-mp3.org/',
      watch:  'https://www.youtube.com/watch?v='
    }
  }

  /**
   * Facility to fetch data from the YouTube API.
   *
   * @param  {String}  resource - The YouTube data API endpoint.
   * @param  {Object=} options  - Some options.
   * @return {null}
   */
  static fetch(resource, options) {
    // Refresh the token whenever we try to hit the API but
    // the current access token is no longer valid.
    return Service.for('youtube').connect().then(() => {
      // Mapping between resource and options
      var mapping = {
        channels:  { part: 'snippet,contentDetails', mine: true },
        items:     { part: 'snippet' },
        search:    { part: 'snippet' },
        playlists: { part: 'snippet,contentDetails' },
        videos:    { part: 'snippet,contentDetails'}
      };

      if (!options)
        options = mapping[resource];
      else
        Object.assign(options, mapping[resource])

      // Shorthand for playlistItems
      if (resource == 'items')
        resource = 'playlistItems';

      options.access_token = Credentials.user.youtube.access_token;

      var url  = this.url.data + resource;
      var body = ''

      return new Promise((resolve, reject) => {
        var req = request.get(url, { qs: options })

        req.on('data', chunck => body += chunck)
        req.on('end', () => resolve(JSON.parse(body)))
        req.on('error', (e) => reject(e))
      })
    });
  }

  /**
   * Fetches a given resource recursively stopping once there
   * is enough data to fill the screen.
   *
   * @param  {String} resource   - The resource passed to `fetch`.
   * @param  {Object} options    - Options delegated to `fetch`.
   * @param  {Array=} collection - Already fetched collection.
   * @return {Promise}
   */
  static fetchWithLimit(resource, options, collection) {
    if (!collection)
      collection = []

    return this.fetch(resource, options).then((data) => {
      collection = collection.concat(data.items)
      options.pageToken = data.nextPageToken

      if (data.nextPageToken && collection.length < Ui.pageSize('video'))
        return this.fetchWithLimit(resource, options, collection)
      else
        return {
          nextPageToken: data.nextPageToken,
          items: collection
        }
    })
  }

  /**
   * Fetches a given playlist until all its items have been
   * fetched.
   *
   * @param  {String}  id         - The playlist's id.
   * @param  {String=} token      - The current page token.
   * @param  {Array=}  collection - Already fetched items.
   * @return {Promise}
   */
  static fetchFull(id, token, collection) {
    var options = { playlistId: id, pageToken: token }

    if (!collection)
      collection = []

    return this.fetch('items', options).then((data) => {
      collection = collection.concat(data.items)

      if (data.nextPageToken)
        return this.fetchFull(id, data.nextPageToken, collection)
      else
        return {
          id: id,
          items: collection,
          nextPageToken: data.nextPageToken
        }
    })
  }

  /**
   * Facility to insert an element inside a playlist given its
   * id.
   *
   * @param  {String} playlist_id - The playlist's id.
   * @param  {String} record_id   - The element to add's id.
   * @return {Promise}
   */
  static insert(playlist_id, record_id) {
    return new Promise((resolve, reject) => {
      var options  = {
        url: this.url.data + 'playlistItems',
        qs: {
          part: 'snippet',
          key:  Credentials.youtube.client_id
        },
        json: {
          snippet: {
            playlistId: playlist_id,
            resourceId: {
              videoId: record_id,
              kind: 'youtube#video'
            }
          }
        },
        headers: {
          Authorization: 'Bearer ' + Credentials.user.youtube.access_token
        }
      };

      request.post(options, (err, res, body) => {
        if (err)
          reject(err);

        if (body.error)
          reject(body.error);
        else
          resolve(true);
      });
    });
  }

  /**
   * Facility to create a playlist on YouTube given
   * a title.
   *
   * @param  {String} title - The playlist's title.
   * @return {Promise}
   */
  static create(title) {
    return new Promise((resolve, reject) => {
      var options = {
        url: this.url.data + 'playlists',
        qs: {
          part: 'snippet'
        },
        json: {
          snippet: {
            title: title
          }
        },
        headers: {
          Authorization: 'Bearer ' + Credentials.user.youtube.access_token
        }
      };

      request.post(options, (err, res, body) => {
        if (err)
          reject(err);

        if (body.error)
          reject(body.error);
        else
          resolve(body);
      });
    });
  }

  /**
   * Returns the list of playlists that the user owns.
   *
   * @param  {String=} token - The next page token.
   * @return {Promise}
   */
  static playlists(token) {
    var options = { mine: true, pageToken: token }

    return this.fetchWithLimit('playlists', options)
  }

  /**
   * Returns the liked videos playlist items.
   *
   * @param  {String=} token - The page token.
   * @return {Promise}
   */
  static likes(token) {
    var id = Config.youtube.related_playlists.likes

    return this.items(id, false, token)
  }

  /**
   * Fetches the items of a given playlist.
   *
   * @param  {String}  id    - The playlist's id.
   * @param  {Bool=}   full  - Whether all items should be fetched.
   * @param  {String=} token - The next page token.
   * @return {Promise}
   */
  static items(id, full, token) {
    var options = { pageToken: token, playlistId: id }

    if (full)
      var promise = this.fetchFull(id, token)
    else
      var promise = this.fetchWithLimit('items', options)

    return promise.then((items) => {
      var ids = items.items.map(item => item.snippet.resourceId.videoId)

      return this.fetch('videos', { id: ids.join(",") }).then((data) => {
        data.nextPageToken = items.nextPageToken
        data.id            = id

        return data
      })
    })
  }

  /**
   * Searches for records given a query.
   *
   * @param  {String} query - The value to look for.
   * @return {Promise}
   */
  static search(query) {
    return this.fetch('search', { q: query, part: 'snippet' })
  }

  /**
   * Fetches the related playlists attached to the current
   * account and stores them inside the configuration file
   * and the cache.
   *
   * It saves us from a request, everytime we want to access
   * to the user's related playlists and they aren't going to
   * change anytime.
   *
   * @return {null}
   */
  static fetchRelatedPlaylists() {
    this.fetch('channels', (data) => {
      let playlists = data.items[0].contentDetails.relatedPlaylists;

      Config.store('youtube', 'related_playlists', playlists);
    });
  }
}
<commit_msg>Fix a regression introduced by 8c2be3c4<commit_after>'use strict';

const Credentials = require('../app/credentials');
const request     = require('request');

module.exports = class YT {
  /**
   * Convenient place to store the different URLs that are
   * given by the YouTube Data API or just related to YouTube.
   *
   * @return {Object}
   */
  static get url() {
    return {
      token:  'https://accounts.google.com/o/oauth2/token',
      auth:   'https://accounts.google.com/o/oauth2/auth',
      data:   'https://www.googleapis.com/youtube/v3/',
      revoke: 'https://accounts.google.com/b/0/IssuedAuthSubTokens',
      info:   'http://www.youtube.com/get_video_info',
      mp3:    'http://youtube-mp3.org/',
      watch:  'https://www.youtube.com/watch?v='
    }
  }

  /**
   * Facility to fetch data from the YouTube API.
   *
   * @param  {String}  resource - The YouTube data API endpoint.
   * @param  {Object=} options  - Some options.
   * @return {Promise}
   */
  static fetch(resource, options) {
    // Refresh the token whenever we try to hit the API but
    // the current access token is no longer valid.
    return Service.for('youtube').connect().then(() => {
      // Mapping between resource and options
      var mapping = {
        channels:  { part: 'snippet,contentDetails', mine: true },
        items:     { part: 'snippet' },
        search:    { part: 'snippet' },
        playlists: { part: 'snippet,contentDetails' },
        videos:    { part: 'snippet,contentDetails'}
      };

      if (!options)
        options = mapping[resource];
      else
        Object.assign(options, mapping[resource])

      // Shorthand for playlistItems
      if (resource == 'items')
        resource = 'playlistItems';

      options.access_token = Credentials.user.youtube.access_token;

      var url  = this.url.data + resource;
      var body = ''

      return new Promise((resolve, reject) => {
        var req = request.get(url, { qs: options })

        req.on('data', chunck => body += chunck)
        req.on('end', () => resolve(JSON.parse(body)))
        req.on('error', (e) => reject(e))
      })
    });
  }

  /**
   * Fetches a given resource recursively stopping once there
   * is enough data to fill the screen.
   *
   * @param  {String} resource   - The resource passed to `fetch`.
   * @param  {Object} options    - Options delegated to `fetch`.
   * @param  {Array=} collection - Already fetched collection.
   * @return {Promise}
   */
  static fetchWithLimit(resource, options, collection) {
    if (!collection)
      collection = []

    return this.fetch(resource, options).then((data) => {
      collection = collection.concat(data.items)
      options.pageToken = data.nextPageToken

      if (data.nextPageToken && collection.length < Ui.pageSize('video'))
        return this.fetchWithLimit(resource, options, collection)
      else
        return {
          nextPageToken: data.nextPageToken,
          items: collection
        }
    })
  }

  /**
   * Fetches a given playlist until all its items have been
   * fetched.
   *
   * @param  {String}  id         - The playlist's id.
   * @param  {String=} token      - The current page token.
   * @param  {Array=}  collection - Already fetched items.
   * @return {Promise}
   */
  static fetchFull(id, token, collection) {
    var options = { playlistId: id, pageToken: token }

    if (!collection)
      collection = []

    return this.fetch('items', options).then((data) => {
      collection = collection.concat(data.items)

      if (data.nextPageToken)
        return this.fetchFull(id, data.nextPageToken, collection)
      else
        return {
          id: id,
          items: collection,
          nextPageToken: data.nextPageToken
        }
    })
  }

  /**
   * Facility to insert an element inside a playlist given its
   * id.
   *
   * @param  {String} playlist_id - The playlist's id.
   * @param  {String} record_id   - The element to add's id.
   * @return {Promise}
   */
  static insert(playlist_id, record_id) {
    return new Promise((resolve, reject) => {
      var options  = {
        url: this.url.data + 'playlistItems',
        qs: {
          part: 'snippet',
          key:  Credentials.youtube.client_id
        },
        json: {
          snippet: {
            playlistId: playlist_id,
            resourceId: {
              videoId: record_id,
              kind: 'youtube#video'
            }
          }
        },
        headers: {
          Authorization: 'Bearer ' + Credentials.user.youtube.access_token
        }
      };

      request.post(options, (err, res, body) => {
        if (err)
          reject(err);

        if (body.error)
          reject(body.error);
        else
          resolve(true);
      });
    });
  }

  /**
   * Facility to create a playlist on YouTube given
   * a title.
   *
   * @param  {String} title - The playlist's title.
   * @return {Promise}
   */
  static create(title) {
    return new Promise((resolve, reject) => {
      var options = {
        url: this.url.data + 'playlists',
        qs: {
          part: 'snippet'
        },
        json: {
          snippet: {
            title: title
          }
        },
        headers: {
          Authorization: 'Bearer ' + Credentials.user.youtube.access_token
        }
      };

      request.post(options, (err, res, body) => {
        if (err)
          reject(err);

        if (body.error)
          reject(body.error);
        else
          resolve(body);
      });
    });
  }

  /**
   * Returns the list of playlists that the user owns.
   *
   * @param  {String=} token - The next page token.
   * @return {Promise}
   */
  static playlists(token) {
    var options = { mine: true, pageToken: token }

    return this.fetchWithLimit('playlists', options)
  }

  /**
   * Returns the liked videos playlist items.
   *
   * @param  {String=} token - The page token.
   * @return {Promise}
   */
  static likes(token) {
    var id = Config.youtube.related_playlists.likes

    return this.items(id, false, token)
  }

  /**
   * Fetches the items of a given playlist.
   *
   * @param  {String}  id    - The playlist's id.
   * @param  {Bool=}   full  - Whether all items should be fetched.
   * @param  {String=} token - The next page token.
   * @return {Promise}
   */
  static items(id, full, token) {
    var options = { pageToken: token, playlistId: id }

    if (full)
      var promise = this.fetchFull(id, token)
    else
      var promise = this.fetchWithLimit('items', options)

    return promise.then((items) => {
      var ids = items.items.map(item => item.snippet.resourceId.videoId)

      return this.fetch('videos', { id: ids.join(",") }).then((data) => {
        data.nextPageToken = items.nextPageToken
        data.id            = id

        return data
      })
    })
  }

  /**
   * Searches for records given a query.
   *
   * @param  {String} query - The value to look for.
   * @return {Promise}
   */
  static search(query) {
    return this.fetch('search', { q: query, part: 'snippet' })
  }

  /**
   * Fetches the related playlists attached to the current
   * account and stores them inside the configuration file
   * and the cache.
   *
   * It saves us from a request, everytime we want to access
   * to the user's related playlists and they aren't going to
   * change anytime.
   *
   * @return {null}
   */
  static fetchRelatedPlaylists() {
    this.fetch('channels').then((data) => {
      let playlists = data.items[0].contentDetails.relatedPlaylists;

      Config.store('youtube', 'related_playlists', playlists);
    });
  }
}
<|endoftext|>


<commit_before>$(document).ready(function() {
    "use strict";

    var websocket = "ws://" + window.location.host + "/realtime/";
    var ws;
    if (window.WebSocket) {
        ws = new WebSocket(websocket);
    }
    else if (window.MozWebSocket) {
        ws = new MozWebSocket(websocket);
    }
    else {
        alert("WebSocket Not Supported");
        return;
    }

    window.onbeforeunload = function(e) {
        console.log("Bye bye...");
        ws.close(1000, "%(username)s left the room");

        if (!e) e = window.event;
        e.stopPropagation();
        e.preventDefault();
    };
    ws.onmessage = function (evt) {
        console.log("received message [", evt.data + "]");

        // Execute a void command:
        if (evt.data.indexOf("$") === 0) {
            var command = evt.data.substr(1);
            //console.log('exec of [ ' + command + ' ]');
            eval(command);
        }

        // Execute a command and return the synchronously:
        else if (evt.data.indexOf("?") === 0) {
            var command = evt.data.substr(1).split("=", 2);
            console.log("raw command = [ " + command + " ]");
            var callback_id = command[0];
            var command = command[1];
            console.log("callback_id = [ " + callback_id + " ]");
            console.log("processed command = [ " + command + " ]");
            try {
                var result = eval(command);
                console.log("result = [" + result + "]");
                ws.send("?" + callback_id + "=" + typeof(result) + ":" + result);
            }
            catch(err) {
                console.log("error : " + err.name + err.message);
                ws.send("!" + callback_id + "=" + err.name + ":" + err.message);
            }
        }
        console.log("log" + evt.data);
    };
    ws.onopen = function() {
        ws.send("%(username)s entered the room");
    };
    ws.onclose = function(evt) {
        $("#stderr").val("Connection closed by server: " + evt.code + " \"" + evt.reason + "\"\n");
    };
});<commit_msg>Added reference to Skink on window object<commit_after>$(document).ready(function() {
    "use strict";

    var websocket = "ws://" + window.location.host + "/realtime/";
    var ws;
    if (window.WebSocket) {
        ws = new WebSocket(websocket);
    }
    else if (window.MozWebSocket) {
        ws = new MozWebSocket(websocket);
    }
    else {
        alert("WebSocket Not Supported");
        return;
    }

    window.onbeforeunload = function(e) {
        console.log("Bye bye...");
        ws.close(1000, "%(username)s left the room");

        if (!e) e = window.event;
        e.stopPropagation();
        e.preventDefault();
    };
    ws.onmessage = function (evt) {
        console.log("received message [", evt.data + "]");

        // Execute a void command:
        if (evt.data.indexOf("$") === 0) {
            var command = evt.data.substr(1);
            //console.log('exec of [ ' + command + ' ]');
            eval(command);
        }

        // Execute a command and return the synchronously:
        else if (evt.data.indexOf("?") === 0) {
            var command = evt.data.substr(1).split("=", 2);
            console.log("raw command = [ " + command + " ]");
            var callback_id = command[0];
            var command = command[1];
            console.log("callback_id = [ " + callback_id + " ]");
            console.log("processed command = [ " + command + " ]");
            try {
                var result = eval(command);
                console.log("result = [" + result + "]");
                ws.send("?" + callback_id + "=" + typeof(result) + ":" + result);
            }
            catch(err) {
                console.log("error : " + err.name + err.message);
                ws.send("!" + callback_id + "=" + err.name + ":" + err.message);
            }
        }
        console.log("log" + evt.data);
    };
    ws.onopen = function() {
        ws.send("%(username)s entered the room");
    };
    ws.onclose = function(evt) {
        $("#stderr").val("Connection closed by server: " + evt.code + " \"" + evt.reason + "\"\n");
    };

    window.skink = ws;
});<|endoftext|>


<commit_before>var NUMBER_OF_SQUARE = 17;
var PLAYGROUND_WIDTH = 600;
var SQUARE_WIDTH = PLAYGROUND_WIDTH / NUMBER_OF_SQUARE;
var MAX_CHARACTER = 5;

var CHARACTER_PARAMETER = [
  {
    "id": 0,
    "name": "daisyo",
    "cost": 1,
    "hitPoint": 3,
    "attack": 1,
    "attackableRange": 2,
    "move": 1
  },
  {
    "id": 1,
    "name": "pupepon",
    "cost": 3,
    "hitPoint": 7,
    "attack": 3,
    "attackableRange": 2,
    "move": 3
  },
  {
    "id": 2,
    "name": "haseaki",
    "cost": 2,
    "hitPoint": 3,
    "attack": 2,
    "attackableRange": 2,
    "move": 2
  },
  {
    "id": 3,
    "name": "miho",
    "cost": 5,
    "hitPoint": 8,
    "attack": 3,
    "attackableRange": 2,
    "move": 5
  }
]

character = function(id, posx, posy, charaId) {
  this.posx = posx;
  this.posy = posy;
  this.charaId = charaId;
  this.id = id;
  this.name = CHARACTER_PARAMETER[charaId].name;
  this.cost = CHARACTER_PARAMETER[charaId].cost;
  this.hitPoint = CHARACTER_PARAMETER[charaId].hitPoint;
  this.attack = CHARACTER_PARAMETER[charaId].attack;
  this.attackableRange = CHARACTER_PARAMETER[charaId].attackableRange;
  this.move = CHARACTER_PARAMETER[charaId].move;
}

test_Friend = [];
test_Friend[0] = new character(0,  9,  9, 0);
test_Friend[1] = new character(1,  8,  9, 3);
test_Friend[2] = new character(2, 10,  9, 3);
test_Friend[3] = new character(3,  9,  8, 3);
test_Friend[4] = new character(4,  9, 10, 3);

test_Enemy = [];
test_Enemy[0] = new character(100, 3, 3, 0);
test_Enemy[1] = new character(101, 2, 3, 2);
test_Enemy[2] = new character(102, 4, 3, 3);
test_Enemy[3] = new character(103, 3, 2, 1);
test_Enemy[4] = new character(104, 3, 4, 1);

// Declarations
var selectX = -1;
var selectY = -1;
var selectedCharacter = -1;
var overX = -1;
var overY = -1;
var onFiled = false;
var moveFlg = -1;
var turn = 'friend';
var selectCharacterState = false;

// =======================================
//=======================================================
$(function(){
  /* canvas */
  playGround = $("#play-ground").get(0);
  ctxCanvas  = playGround.getContext("2d");
  canvasPosition = $('#play-ground').position();

  redraw(false, onFiled);

  /* resize */
  var timer = false;
  $(window).resize(function() {
    if (timer !== false) {
      clearTimeout(timer);
    }
    timer = setTimeout(function() {
    console.log("resize Event");
    // 処理
    canvasPosition = $('#play-ground').position();
    }, 200);
  });

  /* Event */
  $('#play-ground').bind({
    "mousemove": function(e){
    	onFiled = true;
      redraw(e, onFiled);
    },
    "mouseout": function(e){
    	onFiled = false;
    	redraw(e, onFiled);
    },
    "click": function(e){
      selectCharacter(e);
      redraw(e, onFiled);
    }
  });

  /* For Debug */
  console.log(CHARACTER_PARAMETER[3].name);

});

// ==============================================================================================
function redraw(e, flag){
  ctxCanvas.clearRect(0, 0, PLAYGROUND_WIDTH, PLAYGROUND_WIDTH);
  if (e && flag === true) {
    drawHoverMarker(e);
  }

  for (var i = 0; i < 5; i++){
      ctxCanvas.fillStyle = "blue";
      drawCharacter(test_Friend[i].posx, test_Friend[i].posy, test_Friend[i].charaId);
      ctxCanvas.fillStyle = "red";
      drawCharacter(test_Enemy[i].posx, test_Enemy[i].posy, test_Enemy[i].charaId);
  }
  if (e.type === "click"){
    selectX = Math.floor((e.pageX - canvasPosition.left) / SQUARE_WIDTH);
    selectY = Math.floor((e.pageY - canvasPosition.top) / SQUARE_WIDTH);
  }
  drawRange(selectCharacterState.move,selectX,selectY,0,144,255);
  drawRange(selectCharacterState.attackableRange,selectX,selectY,200,20,0);
  drawField();
}

//マスを塗りつぶす
function drawSquare(x,y,r,g,b){
  //convertpositionで取った座標17ずつずれてる。
  obj = convertPosition(x,y,false);
  ctxCanvas.lineWidth = 0.0;
  ctxCanvas.fillStyle = "rgb("+r+","+g+","+b+")";
  ctxCanvas.globalAlpha = 0.5;
  ctxCanvas.beginPath();
  ctxCanvas.fillRect(obj.x-17, obj.y-17, SQUARE_WIDTH, SQUARE_WIDTH);
  ctxCanvas.stroke();
}

function drawHoverMarker(e){
    if (!e) {
      console.log("drawHoverMarker !e");
    } else {
      overX = (e.pageX - canvasPosition.left) / SQUARE_WIDTH;
      overY = (e.pageY - canvasPosition.top) / SQUARE_WIDTH;

      var obj = convertPosition(overX, overY, false);
      obj.x -= SQUARE_WIDTH / 2;
      obj.y -= SQUARE_WIDTH / 2;
    };

    ctxCanvas.lineWidth = 0.0;
    ctxCanvas.fillStyle = 'rgb(120, 200, 77)';
    ctxCanvas.globalAlpha = 0.7;
    ctxCanvas.beginPath();
    ctxCanvas.fillRect(obj.x, obj.y, SQUARE_WIDTH, SQUARE_WIDTH);
    ctxCanvas.stroke();
}

function drawCharacter(posX ,posY, charId){
	/* Draw Set */
	var typeArray = ['D','P','H','M'];
  var obj = convertPosition(posX, posY, false);
  ctxCanvas.globalAlpha = 1.0;
  ctxCanvas.font = "18px 'MS Pゴシック'";
  ctxCanvas.fillText(typeArray[charId], obj.x, obj.y);
  ctxCanvas.stroke();
}

function drawField(){

  //setting
  ctxCanvas.globalAlpha = 1.0;

  for (var i = 0; i <= 17; i++) {
    if (i === 0 || i === NUMBER_OF_SQUARE) {
      ctxCanvas.beginPath();
      ctxCanvas.lineWidth = 10.0;
    } else {
      ctxCanvas.beginPath();
      ctxCanvas.lineWidth = 1.0;
    }

    //draw
    ctxCanvas.moveTo(i * SQUARE_WIDTH, 0);
    ctxCanvas.lineTo(i * SQUARE_WIDTH, PLAYGROUND_WIDTH);
    /* ここない方がきれいに描画できてると思う */
    //ctxCanvas.stroke();
    ctxCanvas.moveTo(0, i * SQUARE_WIDTH);
    ctxCanvas.lineTo(PLAYGROUND_WIDTH, i * SQUARE_WIDTH);
    ctxCanvas.stroke();
  }
}

function drawRange(range,x,y,r,g,b){
  var i,j;
  var state = getCharacterState(selectedCharacter);
  //console.log(state);
  //範囲内マスの座標は取れたけど、それを塗りつぶすのが出来てない(drawSquare)

  if(selectedCharacter !== -1){
    for (i = -1*range; i < range+1;i++){
        //console.log(i,move*2+1-Math.abs(i)*2);
      for(j = 0;j < range*2+1-Math.abs(i)*2; j++){
        if(j%2 == 0){
          //console.log(j/2+x,i+y);
          drawSquare(j/2+x,i+y,r,g,b);
        }
        else{
          //console.log(Math.ceil(j/2)*-1+x,i+y);
          drawSquare(Math.ceil(j/2)*-1+x,i+y,r,g,b);
        }
      }
    }
  }
}

function selectCharacter(e){
  /*
  (selectY, selectX)
  */
  if (e) {
    selectX = Math.floor((e.pageX - canvasPosition.left) / SQUARE_WIDTH);
    selectY = Math.floor((e.pageY - canvasPosition.top) / SQUARE_WIDTH);
  };

  // クリックしたとこのキャラを検索。
  // 同じキャラが選択されたとき or 見つからなかったとき、-1を代入。
  var temp = searchCharacter(selectX, selectY, 0, true);
  if (selectedCharacter === temp[1] || temp[0] === false) {
    selectedCharacter = -1;
  } else {
    selectedCharacter = temp[1];
  }

  moveCharacter(selectX, selectY);
  selectCharacterState = getCharacterState(selectedCharacter);
  console.log('selected', selectedCharacter);

}

function moveCharacter(x, y){

  var characterId = selectedCharacter;
  var characterState = getCharacterState(moveFlg);
  var lx = characterState.posx;
  var ly = characterState.posy;

  // console.log(moveFlg, selectedCharacter);
  // console.log(characterState);

  if (moveFlg === -1) {
    moveFlg = characterId;
  } else {
    if (characterId != -1 || Math.abs(x - lx) + Math.abs(y - ly) > characterState.move) {
      //範囲外
      console.log("This postion protruding from moverenge");
      moveFlg = -1;
    } else {
      //移動
      if (turn === 'friend') {
        if (moveFlg < 100) {
          characterState.posx = x;
          characterState.posy = y;
        }
      };
      if (turn === 'enemy') {
        if (moveFlg >= 100) {
          characterState.posx = x;
          characterState.posy = y;
        }
      };

      moveFlg = -1;
    };
  };

}

function searchCharacter(x, y, range, flag){
  /*
    x, y はマスの座標を取る。
    flag:
     true: クリックしたところを含む
     false: クリックしたところを含まない
  */
  var foundCharacter = [];
  foundCharacter.push(false);

    var tx = x - range;   //検索範囲の一番左のマス
    var ty = y - range;   //検索範囲の一番上のマス
    var mp = range;       // _range の真ん中
    var sp = mp;          // startPoint
    var ep = mp;          // endPoint
    var _range = 2 * range + 1;

    for (var i = 0; i <= mp; i++) {

      // 真ん中の列まで
      for (var j = sp; j <= ep; j++) {
        for (var l = 0; l < MAX_CHARACTER; l++){
          if(test_Friend[l].posx === tx + j && test_Friend[l].posy === ty + i){
            foundCharacter.push(test_Friend[l].id);
          };
          if(test_Enemy[l].posx === tx + j && test_Enemy[l].posy === ty + i){
            foundCharacter.push(test_Enemy[l].id);
          };
        };
      };

      // 真ん中より下
      for (var k = sp; k > 0; k--) {
        if (i == mp) {
          break;
        };
        for (var l = 0; l < MAX_CHARACTER; l++){
          if(test_Friend[l].posx === tx + k && test_Friend[l].posy === ty + _range - (i + 1)){
            foundCharacter.push(test_Friend[l].id);
          }
          if(test_Enemy[l].posx === tx + k && test_Enemy[l].posy === ty + _range - (i + 1)){
            foundCharacter.push(test_Enemy[l].id);
          }
        }
      };

      sp--;
      ep++;

    };

  if (flag === false) {
    var temp = searchCharacter(x, y, 0, true);
    for(var i = 1; i <= foundCharacter.length; i++){
      if (foundCharacter[i] === temp[1]){
        foundCharacter.splice(i, 1);
      }
    }
  }

  if (typeof foundCharacter[1] === 'undefined') {
    return foundCharacter;
  } else {
    foundCharacter[0] = true;
    return foundCharacter;
  }

}

function getCharacterState(keyId){
  /*
    引数にidを取る。（配列不可）
    マッチしたキャラを返す。
  */
  for (var i = 0; i < MAX_CHARACTER; i++) {
    if (test_Enemy[i].id === keyId) {
      var result = test_Enemy[i];
    };
    if (test_Friend[i].id === keyId) {
      var result = test_Friend[i];
    };
  }

  if (!result) {
    return false;
  } else {
    return result;
  }
}
// ==============================================================================================
function attack(attackerId){
  var attacker = getCharacterState(attackerId);
  var defender = searchCharacter(attacker.posx, attacker.posy, attacker.attackableRange, false);

  // 味方のIDをdefenderリストから削除
  if (attackerId < 100) {
    for(var i = defender.length; i > 0; i--){
      if (defender[i] < 100){
        defender.splice(i, 1);
      }
    }
  } else {
    for(var i = defender.length; i > 0; i--){
      if (defender[i] >= 100){
        defender.splice(i, 1);
      }
    }
  }
  // 攻撃
  for (var i = 1; i <= defender.length; i++) {
    getCharacterState(defender[i]).hitPoint -= attacker.attack;
    if (getCharacterState(defender[i]).hitPoint <= 0) {
      console.log('dead');
    };
  };
}

// ==============================================================================================
/*
  posX: x座標,
  posY: y座標,
  flag:
    true : 座標 -> マス
    false: マス   -> 座標
*/
function convertPosition(posX, posY, flag) {
  var canvasPosition = $('#play-ground').position();
  var tx;
  var ty;

  posX = Math.floor(posX);
  posY = Math.floor(posY);

  if (flag === true) {
    tx = posX / SQUARE_WIDTH;
    ty = posY / SQUARE_WIDTH;

    return {x: Math.floor(tx), y: Math.floor(ty)}
  }
  else if (flag === false) {
    tx = (posX + 1) * (SQUARE_WIDTH) - (SQUARE_WIDTH / 2);
    ty = (posY + 1) * (SQUARE_WIDTH) - (SQUARE_WIDTH / 2);

    return {x: Math.floor(tx), y: Math.floor(ty)}
  }

  // 失敗したときのために
  return {x: -1, y: -1}
};
<commit_msg>enable changing turn<commit_after>var NUMBER_OF_SQUARE = 17;
var PLAYGROUND_WIDTH = 600;
var SQUARE_WIDTH = PLAYGROUND_WIDTH / NUMBER_OF_SQUARE;
var MAX_CHARACTER = 5;

var CHARACTER_PARAMETER = [
  {
    "id": 0,
    "name": "daisyo",
    "cost": 1,
    "hitPoint": 3,
    "attack": 1,
    "attackableRange": 2,
    "move": 1
  },
  {
    "id": 1,
    "name": "pupepon",
    "cost": 3,
    "hitPoint": 7,
    "attack": 3,
    "attackableRange": 2,
    "move": 3
  },
  {
    "id": 2,
    "name": "haseaki",
    "cost": 2,
    "hitPoint": 3,
    "attack": 2,
    "attackableRange": 2,
    "move": 2
  },
  {
    "id": 3,
    "name": "miho",
    "cost": 5,
    "hitPoint": 8,
    "attack": 3,
    "attackableRange": 2,
    "move": 5
  }
]

character = function(id, posx, posy, charaId) {
  this.posx = posx;
  this.posy = posy;
  this.charaId = charaId;
  this.id = id;
  this.movable = true;
  this.name = CHARACTER_PARAMETER[charaId].name;
  this.cost = CHARACTER_PARAMETER[charaId].cost;
  this.hitPoint = CHARACTER_PARAMETER[charaId].hitPoint;
  this.attack = CHARACTER_PARAMETER[charaId].attack;
  this.attackableRange = CHARACTER_PARAMETER[charaId].attackableRange;
  this.move = CHARACTER_PARAMETER[charaId].move;
}

test_Friend = [];
test_Friend[0] = new character(0,  9,  9, 0);
test_Friend[1] = new character(1,  8,  9, 3);
test_Friend[2] = new character(2, 10,  9, 3);
test_Friend[3] = new character(3,  9,  8, 3);
test_Friend[4] = new character(4,  9, 10, 3);

test_Enemy = [];
test_Enemy[0] = new character(100, 3, 3, 0);
test_Enemy[1] = new character(101, 2, 3, 2);
test_Enemy[2] = new character(102, 4, 3, 3);
test_Enemy[3] = new character(103, 3, 2, 1);
test_Enemy[4] = new character(104, 3, 4, 1);

// Declarations
var selectX = -1;
var selectY = -1;
var selectedCharacter = -1;
var overX = -1;
var overY = -1;
var onFiled = false;
var moveFlg = -1;
var turn = 'friend';
var selectCharacterState = false;

// =======================================
//=======================================================
$(function(){
  /* canvas */
  playGround = $("#play-ground").get(0);
  ctxCanvas  = playGround.getContext("2d");
  canvasPosition = $('#play-ground').position();

  redraw(false, onFiled);

  /* resize */
  var timer = false;
  $(window).resize(function() {
    if (timer !== false) {
      clearTimeout(timer);
    }
    timer = setTimeout(function() {
    console.log("resize Event");
    // 処理
    canvasPosition = $('#play-ground').position();
    }, 200);
  });

  /* Event */
  $('#play-ground').bind({
    "mousemove": function(e){
    	onFiled = true;
      redraw(e, onFiled);
    },
    "mouseout": function(e){
    	onFiled = false;
    	redraw(e, onFiled);
    },
    "click": function(e){
      selectCharacter(e);
      redraw(e, onFiled);
    }
  });

  $(window).bind({
    "keydown" : function(e) {
      if(e.keyCode === 32){
        if(turn === "friend"){
          turn = "enemy";
          for(var i=0; i<test_Enemy.length; i++){
            test_Enemy[i].movable = true;
          }
        }else if (turn === "enemy"){
          turn = "friend"
          for(var i=0; i<test_Friend.length; i++){
            test_Friend[i].movable = true;
          }
        }
        console.log("turn " + turn);
      }
    }
  });
  /* For Debug */
  console.log(CHARACTER_PARAMETER[3].name);

});

// ==============================================================================================
function redraw(e, flag){
  ctxCanvas.clearRect(0, 0, PLAYGROUND_WIDTH, PLAYGROUND_WIDTH);
  if (e && flag === true) {
    drawHoverMarker(e);
  }

  for (var i = 0; i < 5; i++){
      ctxCanvas.fillStyle = "blue";
      drawCharacter(test_Friend[i].posx, test_Friend[i].posy, test_Friend[i].charaId);
      ctxCanvas.fillStyle = "red";
      drawCharacter(test_Enemy[i].posx, test_Enemy[i].posy, test_Enemy[i].charaId);
  }
  if (e.type === "click"){
    selectX = Math.floor((e.pageX - canvasPosition.left) / SQUARE_WIDTH);
    selectY = Math.floor((e.pageY - canvasPosition.top) / SQUARE_WIDTH);
  }
  drawRange(selectCharacterState.move,selectX,selectY,0,144,255);
  drawRange(selectCharacterState.attackableRange,selectX,selectY,200,20,0);
  drawField();
}

//マスを塗りつぶす
function drawSquare(x,y,r,g,b){
  //convertpositionで取った座標17ずつずれてる。
  obj = convertPosition(x,y,false);
  ctxCanvas.lineWidth = 0.0;
  ctxCanvas.fillStyle = "rgb("+r+","+g+","+b+")";
  ctxCanvas.globalAlpha = 0.5;
  ctxCanvas.beginPath();
  ctxCanvas.fillRect(obj.x-17, obj.y-17, SQUARE_WIDTH, SQUARE_WIDTH);
  ctxCanvas.stroke();
}

function drawHoverMarker(e){
    if (!e) {
      console.log("drawHoverMarker !e");
    } else {
      overX = (e.pageX - canvasPosition.left) / SQUARE_WIDTH;
      overY = (e.pageY - canvasPosition.top) / SQUARE_WIDTH;

      var obj = convertPosition(overX, overY, false);
      obj.x -= SQUARE_WIDTH / 2;
      obj.y -= SQUARE_WIDTH / 2;
    };

    ctxCanvas.lineWidth = 0.0;
    ctxCanvas.fillStyle = 'rgb(120, 200, 77)';
    ctxCanvas.globalAlpha = 0.7;
    ctxCanvas.beginPath();
    ctxCanvas.fillRect(obj.x, obj.y, SQUARE_WIDTH, SQUARE_WIDTH);
    ctxCanvas.stroke();
}

function drawCharacter(posX ,posY, charId){
	/* Draw Set */
	var typeArray = ['D','P','H','M'];
  var obj = convertPosition(posX, posY, false);
  ctxCanvas.globalAlpha = 1.0;
  ctxCanvas.font = "18px 'MS Pゴシック'";
  ctxCanvas.fillText(typeArray[charId], obj.x, obj.y);
  ctxCanvas.stroke();
}

function drawField(){

  //setting
  ctxCanvas.globalAlpha = 1.0;

  for (var i = 0; i <= 17; i++) {
    if (i === 0 || i === NUMBER_OF_SQUARE) {
      ctxCanvas.beginPath();
      ctxCanvas.lineWidth = 10.0;
    } else {
      ctxCanvas.beginPath();
      ctxCanvas.lineWidth = 1.0;
    }

    //draw
    ctxCanvas.moveTo(i * SQUARE_WIDTH, 0);
    ctxCanvas.lineTo(i * SQUARE_WIDTH, PLAYGROUND_WIDTH);
    /* ここない方がきれいに描画できてると思う */
    //ctxCanvas.stroke();
    ctxCanvas.moveTo(0, i * SQUARE_WIDTH);
    ctxCanvas.lineTo(PLAYGROUND_WIDTH, i * SQUARE_WIDTH);
    ctxCanvas.stroke();
  }
}

function drawRange(range,x,y,r,g,b){
  var i,j;
  var state = getCharacterState(selectedCharacter);
  //console.log(state);
  //範囲内マスの座標は取れたけど、それを塗りつぶすのが出来てない(drawSquare)

  if(selectedCharacter !== -1){
    for (i = -1*range; i < range+1;i++){
        //console.log(i,move*2+1-Math.abs(i)*2);
      for(j = 0;j < range*2+1-Math.abs(i)*2; j++){
        if(j%2 == 0){
          //console.log(j/2+x,i+y);
          drawSquare(j/2+x,i+y,r,g,b);
        }
        else{
          //console.log(Math.ceil(j/2)*-1+x,i+y);
          drawSquare(Math.ceil(j/2)*-1+x,i+y,r,g,b);
        }
      }
    }
  }
}

function selectCharacter(e){
  /*
  (selectY, selectX)
  */
  if (e) {
    selectX = Math.floor((e.pageX - canvasPosition.left) / SQUARE_WIDTH);
    selectY = Math.floor((e.pageY - canvasPosition.top) / SQUARE_WIDTH);
  };

  // クリックしたとこのキャラを検索。
  // 同じキャラが選択されたとき or 見つからなかったとき、-1を代入。
  var temp = searchCharacter(selectX, selectY, 0, true);
  if (selectedCharacter === temp[1] || temp[0] === false) {
    selectedCharacter = -1;
  } else {
    selectedCharacter = temp[1];
  }

  moveCharacter(selectX, selectY);
  selectCharacterState = getCharacterState(selectedCharacter);
  console.log('selected', selectedCharacter);

}

function moveCharacter(x, y){

  var characterId = selectedCharacter;
  var characterState = getCharacterState(moveFlg);
  var lx = characterState.posx;
  var ly = characterState.posy;

  // console.log(moveFlg, selectedCharacter);
  // console.log(characterState);

  if (moveFlg === -1) {
    moveFlg = characterId;
  } else {
    if (characterId != -1 || Math.abs(x - lx) + Math.abs(y - ly) > characterState.move) {
      //範囲外
      console.log("This postion protruding from moverenge");
      moveFlg = -1;
    } else if(characterState.movable === true){
      //移動
      console.log(characterState.movable);
      if (turn === 'friend') {
        if (moveFlg < 100) {
          characterState.posx = x;
          characterState.posy = y;
        }
      };
      if (turn === 'enemy') {
        if (moveFlg >= 100) {
          characterState.posx = x;
          characterState.posy = y;
        }
      };
      moveFlg = -1;
      characterState.movable = false;
      attack(characterState.id);
    };
  };
}

function searchCharacter(x, y, range, flag){
  /*
    x, y はマスの座標を取る。
    flag:
     true: クリックしたところを含む
     false: クリックしたところを含まない
  */
  var foundCharacter = [];
  foundCharacter.push(false);

    var tx = x - range;   //検索範囲の一番左のマス
    var ty = y - range;   //検索範囲の一番上のマス
    var mp = range;       // _range の真ん中
    var sp = mp;          // startPoint
    var ep = mp;          // endPoint
    var _range = 2 * range + 1;

    for (var i = 0; i <= mp; i++) {

      // 真ん中の列まで
      for (var j = sp; j <= ep; j++) {
        for (var l = 0; l < MAX_CHARACTER; l++){
          if(test_Friend[l].posx === tx + j && test_Friend[l].posy === ty + i){
            foundCharacter.push(test_Friend[l].id);
          };
          if(test_Enemy[l].posx === tx + j && test_Enemy[l].posy === ty + i){
            foundCharacter.push(test_Enemy[l].id);
          };
        };
      };

      // 真ん中より下
      for (var k = sp; k > 0; k--) {
        if (i == mp) {
          break;
        };
        for (var l = 0; l < MAX_CHARACTER; l++){
          if(test_Friend[l].posx === tx + k && test_Friend[l].posy === ty + _range - (i + 1)){
            foundCharacter.push(test_Friend[l].id);
          }
          if(test_Enemy[l].posx === tx + k && test_Enemy[l].posy === ty + _range - (i + 1)){
            foundCharacter.push(test_Enemy[l].id);
          }
        }
      };

      sp--;
      ep++;

    };

  if (flag === false) {
    var temp = searchCharacter(x, y, 0, true);
    for(var i = 1; i <= foundCharacter.length; i++){
      if (foundCharacter[i] === temp[1]){
        foundCharacter.splice(i, 1);
      }
    }
  }

  if (typeof foundCharacter[1] === 'undefined') {
    return foundCharacter;
  } else {
    foundCharacter[0] = true;
    return foundCharacter;
  }

}

function getCharacterState(keyId){
  /*
    引数にidを取る。（配列不可）
    マッチしたキャラを返す。
  */
  for (var i = 0; i < MAX_CHARACTER; i++) {
    if (test_Enemy[i].id === keyId) {
      var result = test_Enemy[i];
    };
    if (test_Friend[i].id === keyId) {
      var result = test_Friend[i];
    };
  }

  if (!result) {
    return false;
  } else {
    return result;
  }
}
// ==============================================================================================
function attack(attackerId){
  var attacker = getCharacterState(attackerId);
  var defender = searchCharacter(attacker.posx, attacker.posy, attacker.attackableRange, false);

  // 味方のIDをdefenderリストから削除
  if (attackerId < 100) {
    for(var i = defender.length; i > 0; i--){
      if (defender[i] < 100){
        defender.splice(i, 1);
      }
    }
  } else {
    for(var i = defender.length; i > 0; i--){
      if (defender[i] >= 100){
        defender.splice(i, 1);
      }
    }
  }
  // 攻撃
  for (var i = 1; i <= defender.length; i++) {
    getCharacterState(defender[i]).hitPoint -= attacker.attack;
    if (getCharacterState(defender[i]).hitPoint <= 0) {
      console.log('dead');
      getCharacterState(defender[i]).posx = -1;
      getCharacterState(defender[i]).posy = -1;

    };
  };
}

// ==============================================================================================
/*
  posX: x座標,
  posY: y座標,
  flag:
    true : 座標 -> マス
    false: マス   -> 座標
*/
function convertPosition(posX, posY, flag) {
  var canvasPosition = $('#play-ground').position();
  var tx;
  var ty;

  posX = Math.floor(posX);
  posY = Math.floor(posY);

  if (flag === true) {
    tx = posX / SQUARE_WIDTH;
    ty = posY / SQUARE_WIDTH;

    return {x: Math.floor(tx), y: Math.floor(ty)}
  }
  else if (flag === false) {
    tx = (posX + 1) * (SQUARE_WIDTH) - (SQUARE_WIDTH / 2);
    ty = (posY + 1) * (SQUARE_WIDTH) - (SQUARE_WIDTH / 2);

    return {x: Math.floor(tx), y: Math.floor(ty)}
  }

  // 失敗したときのために
  return {x: -1, y: -1}
};
<|endoftext|>


<commit_before>import PropTypes from 'prop-types'
import React from 'react'
import { connect as connectFela } from 'react-fela'
import { Field, FieldArray, reduxForm as connectForm } from 'redux-form'
import { flow } from 'lodash'
import { TextField } from 'redux-form-material-ui'

import styles from '../styles/MemberInvites'
import Button from '../../app/components/Button'

class MemberInvites extends React.Component {
  renderMembers ({ fields, meta: { error, submitFailed } }) {
    return (

        <Button type='button' onClick={() => fields.push({})}>Add Member</Button>
        {submitFailed && error && {error}}
        {fields.map((member, index) => (

            <Field
              name={`${member}.name`}
              floatingLabelText='Name'
              component={TextField}
            />
            <Field
              name={`${member}.email`}
              floatingLabelText='Email'
              component={TextField}
            />
            <Field
              name={`${member}.role`}
              floatingLabelText='Role'
              component={TextField}
            />
            <Button type='button' onClick={() => fields.remove(index)}>Remove Member</Button>

        ))}

    )
  }

  render () {
    return (
      <form className={styles.container}>
        <Field
          name='groupName'
          type='text'
          component={TextField}
          floatingLabelText='Group Name'
        />
        <FieldArray name="members" component={this.renderMembers} />
      </form>
    )
  }
}

export default flow(
  connectFela(styles),
  connectForm({
    form: 'memberInvites'
  })
)(MemberInvites)
<commit_msg>refactored to use functions instead of react component class<commit_after>import PropTypes from 'prop-types'
import React from 'react'
import { connect as connectFela } from 'react-fela'
import { Field, FieldArray, reduxForm as connectForm } from 'redux-form'
import { flow } from 'lodash'
import { TextField } from 'redux-form-material-ui'

import styles from '../styles/MemberInvites'
import Button from '../../app/components/Button'

function renderMembers ({ fields, meta: { error, submitFailed } }) {
  return (

      <Button type='button' onClick={() => fields.push({})}>Add Member</Button>
      {submitFailed && error && {error}}
      {fields.map((member, index) => (

          <Field
            name={`${member}.name`}
            floatingLabelText='Name'
            component={TextField}
          />
          <Field
            name={`${member}.email`}
            floatingLabelText='Email'
            component={TextField}
          />
          <Field
            name={`${member}.role`}
            floatingLabelText='Role'
            component={TextField}
          />
          <Button type='button' onClick={() => fields.remove(index)}>Remove Member</Button>

      ))}

  )
}

function MemberInvites (props) {
  return (
    <form className={styles.container}>
      <Field
        name='groupName'
        type='text'
        component={TextField}
        floatingLabelText='Group Name'
      />
      <FieldArray name='members' component={renderMembers} />
    </form>
  )
}

export default flow(
  connectFela(styles),
  connectForm({
    form: 'memberInvites'
  })
)(MemberInvites)
<|endoftext|>


<commit_before>class AdminCommonPlugins {
  constructor() {
    this.DOM = {
      searchFieldId: '#searchbar',
    };

    this.config = {
      autocompleteURL: '/admin/autocomplete/',
      minChars: 3,
      currentPageType: document.location.pathname.split('/').slice(-2, -1)[0],
      pagesType: { categorypage: 'category', productpage: 'product' },
    };

    this.init();
  }

  init() {
    this.setupXHR();
    this.autoCompleteInit();
  }

  setupXHR() {
    const csrfUnsafeMethod = method => !(/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));

    $.ajaxSetup({
      beforeSend: (xhr, settings) => {
        if (csrfUnsafeMethod(settings.type)) {
          xhr.setRequestHeader('X-CSRFToken', Cookies.get('csrftoken'));
        }
      },
    });
  }

  /**
   * https://goo.gl/2UDLKC
   */
  autoCompleteInit() {
    const pageType = this.config.pagesType[this.config.currentPageType];
    if (!pageType) return;

    return new autoComplete({
      selector: this.DOM.searchFieldId,
      minChars: this.config.minChars,
      source: (term, response) => {
        $.getJSON(
          this.config.autocompleteURL,
          { term, pageType },
          namesArray => response(namesArray),
        );
      },
    });
  }
}
<commit_msg>Implemented shortcut support for save and continue button (#99)<commit_after>class AdminCommonPlugins {
  constructor() {
    this.DOM = {
      searchFieldId: '#searchbar',
      saveAndContinueButton: 'input[name="_continue"]',
    };

    this.config = {
      autocompleteURL: '/admin/autocomplete/',
      minChars: 3,
      currentPageType: document.location.pathname.split('/').slice(-2, -1)[0],
      pagesType: { categorypage: 'category', productpage: 'product' },
    };

    this.init();
  }

  init() {
    this.setupXHR();
    this.autoCompleteInit();
    this.saveAndContinueShortcutInit();
  }

  setupXHR() {
    const csrfUnsafeMethod = method => !(/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));

    $.ajaxSetup({
      beforeSend: (xhr, settings) => {
        if (csrfUnsafeMethod(settings.type)) {
          xhr.setRequestHeader('X-CSRFToken', Cookies.get('csrftoken'));
        }
      },
    });
  }

  /**
   * https://goo.gl/2UDLKC
   */
  autoCompleteInit() {
    const pageType = this.config.pagesType[this.config.currentPageType];
    if (!pageType) return;

    return new autoComplete({
      selector: this.DOM.searchFieldId,
      minChars: this.config.minChars,
      source: (term, response) => {
        $.getJSON(
          this.config.autocompleteURL,
          { term, pageType },
          namesArray => response(namesArray),
        );
      },
    });
  }

  /*
    Setup shortcut for "Save and continue" button (Ctrl + Enter)
  */
  saveAndContinueShortcutInit() {
    $(window).keydown((event) => {
      if(event.ctrlKey && event.keyCode == 13) {
        event.preventDefault();
        // Django Admin have two "Save and continue" buttons, at top and bottom of page
        const element = $('input[name="_continue"]')[0];
        if (element) {
          element.click();
        );
      };
    });
  }

}
<|endoftext|>


<commit_before>// Navelgazer
// Gaurav Manek
//
// expr.js prepares the various expression types

// The uid field is used to rapidly identify identical expressions. Since each Expressions object
// is immutable, every time we encounter a pair of identical expressions, we assign both their uids
// to the lower of the two uids. This still requires us to spend O(n^2) time to deduplicate n expressions,
// but it makes it substantially faster.
// The requirement that the lower uid be chosen is so that, if we are trying to deduplicate a novel set w.r.t.
// an existing set we avoid rewriting any of the existing set.
// There is a method with lower big-O complexity that involves indirection, but that's rather complicated and
// I do not expect it to provide a significant speed boost, so I have not implemented it.
var UID_COUNTER = 0;
var Expressions = {
	TYPE_UNKNOWN: 0,
	TYPE_SENTENCE_LETTER: 1,
	TYPE_PREDICATE: 2,
	TYPE_JUNCTION: 3,
	TYPE_CONDITIONAL: 4,
	TYPE_BICONDITIONAL: 5,
	TYPE_NEGATION: 6,
	TYPE_QUANTIFIER: 7,
	TYPE_IDENTITY: 8,
	COUNT_TYPE: 9,

	SentenceLetter: function (l) {
		return {
			uid: UID_COUNTER++,
			type: Expressions.TYPE_SENTENCE_LETTER,
			letter: l,
			toString: () => l
		}
	},

	Predicate: function (pred, lttr) {
		return {
			uid: UID_COUNTER++,
			type: Expressions.TYPE_PREDICATE,
			name: pred,
			args: lttr,
			argnum: lttr.length,
			toString: () => pred + lttr.map(k => k.toString()).join("")
		}
	},

	Junction: function (conj, exprs) {
		return {
			uid: UID_COUNTER++,
			type: Expressions.TYPE_JUNCTION,
			conjunction: conj,
			juncts: exprs,
			toString: () => "(" + exprs.map(k => k.toString()).join(conj?" ∧ ":" ∨ ") + ")"
		}
	},

	Conditional: function (cond, impl) {
		return {
			uid: UID_COUNTER++,
			type: Expressions.TYPE_CONDITIONAL,
			lhs: cond,
			rhs: impl,
			toString: () => "(" + cond.toString() + " → " + impl.toString() + ")"
		}
	},

	Biconditional: function (lhs, rhs) {
		return {
			uid: UID_COUNTER++,
			type: Expressions.TYPE_BICONDITIONAL,
			lhs: lhs,
			rhs: rhs,
			toString: () => "(" + lhs.toString() + " ≡ " + rhs.toString() + ")"
		}
	},

	Negation: function (e) {
		return {
			uid: UID_COUNTER++,
			type: Expressions.TYPE_NEGATION,
			expr: e,
			toString: () => "¬" + e.toString()
		}
	},

	Quantifier: function (binds, expr) {
		var b = binds.shift();
		if(binds.length > 0) {
			expr = Expressions.Quantifier(binds, expr);
		}
		return {
			uid: UID_COUNTER++,
			type: Expressions.TYPE_QUANTIFIER,
			universal: b.universal,
			letter: b.letter,
			expr: expr,
			toString: () => "(" + (b.universal?"∀":"∃") + b.letter.toString() + ")" + expr.toString()
		}
	},

	Identity: function (lhs, rhs, isequal) {
		return {
			uid: UID_COUNTER++,
			type: Expressions.TYPE_IDENTITY,
			equal: isequal,
			lhs: lhs,
			rhs: rhs,
			toString: () => lhs.toString() + (isequal?"=":"≠") + rhs.toString()
		}
	}
};

// To pretty-print flags.
var Print = {
	letter: k => "" + k + "",
	letters: k => k.map(Print.letter).join(", "),
	linenum: k => "(" + k + ")",
	premise: k => "[" + k + "]",
	mapping: (j, k) => "" + k.map((l,i) => Print.letter(j[i]) + " → " + Print.letter(l)).join(", ") + "",
	rule: k => "" + k + ""
}
<commit_msg>Bugfix, closes #1.<commit_after>// Navelgazer
// Gaurav Manek
//
// expr.js prepares the various expression types

// The uid field is used to rapidly identify identical expressions. Since each Expressions object
// is immutable, every time we encounter a pair of identical expressions, we assign both their uids
// to the lower of the two uids. This still requires us to spend O(n^2) time to deduplicate n expressions,
// but it makes it substantially faster.
// The requirement that the lower uid be chosen is so that, if we are trying to deduplicate a novel set w.r.t.
// an existing set we avoid rewriting any of the existing set.
// There is a method with lower big-O complexity that involves indirection, but that's rather complicated and
// I do not expect it to provide a significant speed boost, so I have not implemented it.
var UID_COUNTER = 0;
var Expressions = {
	TYPE_UNKNOWN: 0,
	TYPE_SENTENCE_LETTER: 1,
	TYPE_PREDICATE: 2,
	TYPE_JUNCTION: 3,
	TYPE_CONDITIONAL: 4,
	TYPE_BICONDITIONAL: 5,
	TYPE_NEGATION: 6,
	TYPE_QUANTIFIER: 7,
	TYPE_IDENTITY: 8,
	COUNT_TYPE: 9,

	SentenceLetter: function (l) {
		return {
			uid: UID_COUNTER++,
			type: Expressions.TYPE_SENTENCE_LETTER,
			letter: l,
			toString: () => l
		}
	},

	Predicate: function (pred, lttr) {
		return {
			uid: UID_COUNTER++,
			type: Expressions.TYPE_PREDICATE,
			name: pred,
			args: lttr,
			argnum: lttr.length,
			toString: () => pred + lttr.map(k => k.toString()).join("")
		}
	},

	Junction: function (conj, exprs) {
		// Merge *juncts if they are the same type.
		// i.e. you want to collapse ((A and B) and C) to (A and B and C)
		// so that the metalogic functions work as intended.

		var juncts = [];
		exprs.forEach(function(e) {
			if(e.type == Expressions.TYPE_JUNCTION && e.conjunction == conj) {
				juncts = juncts.concat(e.juncts);
			} else {
				juncts.push(e);
			}

		})

		return {
			uid: UID_COUNTER++,
			type: Expressions.TYPE_JUNCTION,
			conjunction: conj,
			juncts: juncts,
			toString: () => "(" + juncts.map(k => k.toString()).join(conj?" ∧ ":" ∨ ") + ")"
		}
	},

	Conditional: function (cond, impl) {
		return {
			uid: UID_COUNTER++,
			type: Expressions.TYPE_CONDITIONAL,
			lhs: cond,
			rhs: impl,
			toString: () => "(" + cond.toString() + " → " + impl.toString() + ")"
		}
	},

	Biconditional: function (lhs, rhs) {
		return {
			uid: UID_COUNTER++,
			type: Expressions.TYPE_BICONDITIONAL,
			lhs: lhs,
			rhs: rhs,
			toString: () => "(" + lhs.toString() + " ≡ " + rhs.toString() + ")"
		}
	},

	Negation: function (e) {
		return {
			uid: UID_COUNTER++,
			type: Expressions.TYPE_NEGATION,
			expr: e,
			toString: () => "¬" + e.toString()
		}
	},

	Quantifier: function (binds, expr) {
		var b = binds.shift();
		if(binds.length > 0) {
			expr = Expressions.Quantifier(binds, expr);
		}
		return {
			uid: UID_COUNTER++,
			type: Expressions.TYPE_QUANTIFIER,
			universal: b.universal,
			letter: b.letter,
			expr: expr,
			toString: () => "(" + (b.universal?"∀":"∃") + b.letter.toString() + ")" + expr.toString()
		}
	},

	Identity: function (lhs, rhs, isequal) {
		return {
			uid: UID_COUNTER++,
			type: Expressions.TYPE_IDENTITY,
			equal: isequal,
			lhs: lhs,
			rhs: rhs,
			toString: () => lhs.toString() + (isequal?"=":"≠") + rhs.toString()
		}
	}
};

// To pretty-print flags.
var Print = {
	letter: k => "" + k + "",
	letters: k => k.map(Print.letter).join(", "),
	linenum: k => "(" + k + ")",
	premise: k => "[" + k + "]",
	mapping: (j, k) => "" + k.map((l,i) => Print.letter(j[i]) + " → " + Print.letter(l)).join(", ") + "",
	rule: k => "" + k + ""
}
<|endoftext|>


<commit_before>var ejs = require('ejs'),
  merge = require('utils-merge'),
  path = require('path'),
  file = hexo.util.file2;

ejs.filters.cdata = function(str){
  return '<![CDATA[' + (str || '') + ']]>';
};

var atomTmplSrc = path.join(__dirname, 'atom.ejs'),
  atomTmpl = ejs.compile(file.readFileSync(atomTmplSrc));

var rss2TmplSrc = path.join(__dirname, 'rss2.ejs'),
  rss2Tmpl = ejs.compile(file.readFileSync(rss2TmplSrc));

module.exports = function(locals, render, callback){
  var config = hexo.config;

  var feedConfig = merge({
    type: 'atom',
    limit: 20
  }, config.feed);

  // Restrict feed type
  if (feedConfig.type !== 'atom' && feedConfig.type !== 'rss2'){
    feedConfig.type = 'atom';
  }

  // Set default feed path
  if (!feedConfig.path){
    feedConfig.path = feedConfig.type + '.xml';
  }

  // Add extension name if don't have
  if (!path.extname(feedConfig.path)){
    feedConfig.path += '.xml';
  }

  // Determine which template to use
  if (feedConfig.type === 'rss2'){
    var template = rss2Tmpl;
  } else {
    var template = atomTmpl;
  }

  var xml = template({
    config: config,
    posts: locals.posts.sort('date', -1).limit(feedConfig.limit),
    feed_url: config.root + feedConfig.path
  });

  hexo.route.set(feedConfig.path, xml);
  callback();
};<commit_msg>Allow option to generate feed of all posts<commit_after>var ejs = require('ejs'),
  merge = require('utils-merge'),
  path = require('path'),
  file = hexo.util.file2;

ejs.filters.cdata = function(str){
  return '<![CDATA[' + (str || '') + ']]>';
};

var atomTmplSrc = path.join(__dirname, 'atom.ejs'),
  atomTmpl = ejs.compile(file.readFileSync(atomTmplSrc));

var rss2TmplSrc = path.join(__dirname, 'rss2.ejs'),
  rss2Tmpl = ejs.compile(file.readFileSync(rss2TmplSrc));

module.exports = function(locals, render, callback){
  var config = hexo.config;

  var feedConfig = merge({
    type: 'atom',
    limit: 20
  }, config.feed);

  var limit = (feedConfig.limit === false) ? undefined : feedConfig.limit;

  // Restrict feed type
  if (feedConfig.type !== 'atom' && feedConfig.type !== 'rss2'){
    feedConfig.type = 'atom';
  }

  // Set default feed path
  if (!feedConfig.path){
    feedConfig.path = feedConfig.type + '.xml';
  }

  // Add extension name if don't have
  if (!path.extname(feedConfig.path)){
    feedConfig.path += '.xml';
  }

  // Determine which template to use
  if (feedConfig.type === 'rss2'){
    var template = rss2Tmpl;
  } else {
    var template = atomTmpl;
  }

  var xml = template({
    config: config,
    posts: locals.posts.sort('date', -1).limit(limit),
    feed_url: config.root + feedConfig.path
  });

  hexo.route.set(feedConfig.path, xml);
  callback();
};
<|endoftext|>