<commit_before>const Connection = require('../connection');
const uuid = require('uuid');

class Memory extends Connection {
  constructor (options) {
    super(options);

    this.data = {};
  }

  async fetch (collection) {
    const data = this.data[collection.name] || [];
    return await data[collection._offset];
  }

  persist (model) {
    const data = this.data[model.name] = this.data[model.name] || [];

    if (model.id) {
      let row = data[model.id];
      if (row) {
        data[model.id] = Object.assign(data[model.id], model.row);
      }

      return;
    }

    let row = Object.assign({ id: uuid.v4() }, model.row);
    data.push(row);

    return row;
  }
}

module.exports = Memory;
<commit_msg>fixed memory adapter<commit_after>const Connection = require('../connection');
const uuid = require('uuid');

class Memory extends Connection {
  constructor (options) {
    super(options);

    this.data = {};
  }

  async fetch (collection) {
    const data = this.data[collection.name] || [];
    return await data[collection._offset];
  }

  persist (model) {
    const data = this.data[model.name] = this.data[model.name] || [];

    if (model.id) {
      let row = data[model.id];
      if (!row) {
        throw new Error(`Stale data with id: ${model.id}`);
      }

      data[model.id] = row = Object.assign(row, model.row);
      return row;
    }

    let row = Object.assign({ id: uuid.v4() }, model.row);
    data.push(row);

    return row;
  }
}

module.exports = Memory;
<|endoftext|>


<commit_before>var ctx = document.getElementById('drawn-canvas').getContext('2d');
draw({
		head: { color: "#000000",
			x: [44.8,  59.4,  97.4,  110.4, 115.1, 138.5, 160.4, 180.7, 200.5, 203.6, 217.7, 251.6, 260.9, 242.7, 226.0, 221.9, 229.2, 232.8, 230.2, 174.0, 137.5, 104.7, 88.0, 46.9, 50.5, 58.9, 70.8,  58.9,  44.8],
			y: [154.2, 176.0, 197.9, 200.0, 205.7, 216.1, 213.0, 210.4, 196.4, 188.0, 182.3, 149.0, 123.4, 114.6, 91.7,  85.9,  69.8,  54.2,  23.4,  41.1,  41.7,  50.5,  48.4, 50.0, 71.9, 92.2, 107.3, 141.2, 154.2],
		},
		outlineLeft: { color: "#000000",
			x: [151.6, 151.0, 144.4, 110.1, 82.4,  68.6,  81.9,  66.0],
			y: [214.6, 218.1, 220.1, 214.5, 232.9, 260.1, 269.8, 300.0],
		},
		outlineRight: { color: "#000000",
			x: [174.1, 174.1, 180.2, 213.5, 249.3, 263.1, 252.9, 271.3],
			y: [211.9, 217.1, 219.1, 213.0, 229.4, 260.6, 270.8, 300.0],
		},
		body: { color: "#000000",
			x: [88.6,  95.7,  100.9, 110.6, 130.0, 144.4, 180.2, 192.0, 209.4, 221.7, 232.9, 239.6],
			y: [300.0, 277.0, 278.0, 247.3, 226.3, 220.1, 219.1, 224.7, 242.7, 261.6, 283.6, 300.0],
		},
		armLeft: { color: "#000000",
			x: [81.9,  95.7],
			y: [269.8, 277.0],
		},
		armRight: { color: "#000000",
			x: [252.9, 232.9],
			y: [270.8, 283.6],
		},
		muzzle: { color: "#000000",
			x: [110.4, 102.9, 103.1, 111.6, 140.3, 148.0, 159.7, 173.0, 198.6, 208.4, 204.8, 203.6],
			y: [200.0, 185.3, 164.3, 151.0, 135.2, 130.5, 134.1, 135.2, 146.9, 165.9, 180.7, 188.0],
		},
	},
	ctx,
	0,
	1,
	false
);
draw({
		nose: { color: "#000000",
			x: [148.0, 140.3, 142.8, 155.6, 164.8, 159.7],
			y: [130.5, 135.2, 146.9, 158.7, 143.3, 134.1],
		},
	},
	ctx,
	0,
	1,
	'#000000'
);
draw({
		mouthMiddle: { color: "#000000",
			x: [155.6, 158.2],
			y: [158.7, 179.2],
		},
		mouth: { color: "#000000",
			x: [102.9, 114.7, 158.2, 194.5, 204.8],
			y: [185.3, 196.1, 179.2, 184.3, 180.7],
		},
		earLeft: { color: "#000000",
			x: [70.8,  78.3, 104.7],
			y: [107.3, 77.8, 50.5],
		},
		earRight: { color: "#000000",
			x: [174.0, 206.8, 221.9],
			y: [41.1,  59.9,  85.9],
		},
		earLeftInner: { color: "#000000",
			x: [73.9, 58.9, 55.3, 73.7, 95.2],
			y: [94.7, 74.2, 56.3, 54.3, 61.4],
		},
		earRightInner: { color: "#000000",
			x: [184.3, 203.2, 224.7, 227.3, 223.7, 215.0],
			y: [46.9,  37.4,  33.3,  49.7,  63.5,  74.7],
		},
		eyeLeft: { color: "#000000",
			x: [112.6, 100.9, 91.1,  88.1,  92.2, 107.0, 114.7, 126.5, 134.1, 138.2, 139.8, 140.3],
			y: [150.5, 141.3, 125.4, 108.0, 95.7, 87.5,  86.5,  93.2,  100.3, 109.2, 120.3, 135.2],
		},
		eyelidLeft: { color: "#000000",
			x: [91.1,  92.7,  97.8,  109.0, 126.5],
			y: [125.4, 113.5, 105.5, 97.3,  93.2],
		},
		pupilLeft: { color: "#000000",
			x: [110.6, 113.1, 119.3, 129.0, 132.1, 131.1, 123.9, 118.3, 113.7, 111.6, 120.3, 110.6],
			y: [120.3, 107.5, 102.4, 107.5, 115.2, 130.5, 139.2, 139.8, 135.2, 129.5, 122.9, 120.3],
		},
		eyebrowLeft: { color: "#000000",
			x: [86.5, 91.1, 106.0],
			y: [91.1, 80.4, 74.2],
		},
		eyeRight: { color: "#000000",
			x: [157.2, 152.6, 153.1, 158.3, 169.8, 179.2, 190.1, 200.0, 203.1, 203.6, 199.0, 193.8, 187.0],
			y: [132.3, 114.1, 99.5,  88.5,  78.1,  76.0,  78.1,  86.5,  100.0, 104.7, 123.4, 132.3, 139.6],
		},
		eyelidRight: { color: "#000000",
			x: [158.3, 172.4, 181.3, 191.7, 197.4, 203.6],
			y: [88.5,  88.0,  89.6,  93.8,  99.0,  104.7],
		},
		pupilRight: { color: "#000000",
			x: [165.6, 173.4, 163.0, 165.1, 168.8, 175.5, 181.8, 185.9, 188.0, 186.5, 182.3, 179.7, 172.4, 168.8, 165.6],
			y: [119.8, 115.1, 112.0, 101.0, 95.3,  94.8,  100.0, 108.3, 119.8, 129.2, 132.3, 131.8, 129.2, 125.5, 119.8],
		},
		eyebrowRight: { color: "#000000",
			x: [199.0, 190.6, 172.4],
			y: [74.0,  63.5,  62.5],
		},
		bodyLineZero: {color: "#000000",
			x: [132.8, 188.8],
			y: [224.5, 222.6],
		},
		bodyLineOne: { color: "#000000",
			x: [111.8, 136.8, 161.5, 208.1],
			y: [245.8, 247.1, 245.8, 241.7],
		},
		bodyLineTwo: { color: "#000000",
			x: [105.7, 128.3, 162.0, 188.7, 219.8],
			y: [263.0, 265.1, 266.7, 264.6, 258.9],
		},
		bodyLineThree: { color: "#000000",
			x: [93.3,  128.3, 161.2, 201.2, 231.1],
			y: [285.0, 288.5, 290.0, 286.5, 281.0],
		},
		armLeftLineZero: { color: "#000000",
			x: [98.8,  100.8, 103.3, 108.8, 117.1],
			y: [222.1, 229.1, 232.6, 237.1, 241.1],
		},
		armLeftLineOne: { color: "#000000",
			x: [77.4,  89.3,  96.2,  106.9],
			y: [242.6, 251.1, 254.5, 257.7],
		},
		armRightLineZero: { color: "#000000",
			x: [229.6, 227.3, 223.7, 218.5, 209.5],
			y: [219.9, 227.0, 232.8, 237.6, 241.8],
		},
		armRightLineOne: { color: "#000000",
			x: [254.0, 238.1, 224.3],
			y: [238.1, 254.5, 264.0],
		},
	},
	ctx,
	0,
	1,
	false
);<commit_msg>changed demo<commit_after>var ctx = document.getElementById('drawn-canvas').getContext('2d');
draw({
		head: { color: "#000000",
			x: [44.8,  59.4,  97.4,  110.4, 115.1, 138.5, 160.4, 180.7, 200.5, 203.6, 217.7, 251.6, 260.9, 242.7, 226.0, 221.9, 229.2, 232.8, 230.2, 174.0, 137.5, 104.7, 88.0, 46.9, 50.5, 58.9, 70.8,  58.9,  44.8],
			y: [154.2, 176.0, 197.9, 200.0, 205.7, 216.1, 213.0, 210.4, 196.4, 188.0, 182.3, 149.0, 123.4, 114.6, 91.7,  85.9,  69.8,  54.2,  23.4,  41.1,  41.7,  50.5,  48.4, 50.0, 71.9, 92.2, 107.3, 141.2, 154.2],
		},
		outlineLeft: { color: "#000000",
			x: [151.6, 151.0, 144.4, 110.1, 82.4,  68.6,  81.9,  66.0],
			y: [214.6, 218.1, 220.1, 214.5, 232.9, 260.1, 269.8, 300.0],
		},
		outlineRight: { color: "#000000",
			x: [174.1, 174.1, 180.2, 213.5, 249.3, 263.1, 252.9, 271.3],
			y: [211.9, 217.1, 219.1, 213.0, 229.4, 260.6, 270.8, 300.0],
		},
		body: { color: "#000000",
			x: [88.6,  95.7,  100.9, 110.6, 130.0, 144.4, 180.2, 192.0, 209.4, 221.7, 232.9, 239.6],
			y: [300.0, 277.0, 278.0, 247.3, 226.3, 220.1, 219.1, 224.7, 242.7, 261.6, 283.6, 300.0],
		},
		armLeft: { color: "#000000",
			x: [81.9,  95.7],
			y: [269.8, 277.0],
		},
		armRight: { color: "#000000",
			x: [252.9, 232.9],
			y: [270.8, 283.6],
		},
		muzzle: { color: "#000000",
			x: [110.4, 102.9, 103.1, 111.6, 140.3, 148.0, 159.7, 173.0, 198.6, 208.4, 204.8, 203.6],
			y: [200.0, 185.3, 164.3, 151.0, 135.2, 130.5, 134.1, 135.2, 146.9, 165.9, 180.7, 188.0],
		},
	},
	ctx,
	0,
	1,
	false
);
draw({
		nose: { color: "#000000",
			x: [148.0, 140.3, 142.8, 155.6, 164.8, 159.7],
			y: [130.5, 135.2, 146.9, 158.7, 143.3, 134.1],
		},
	},
	ctx,
	0,
	1,
	'#000000'
);
draw({
		mouthMiddle: { color: "#000000",
			x: [155.6, 158.2],
			y: [158.7, 179.2],
		},
		mouth: { color: "#000000",
			x: [102.9, 114.7, 158.2, 194.5, 204.8],
			y: [185.3, 196.1, 179.2, 184.3, 180.7],
		},
		earLeft: { color: "#000000",
			x: [70.8,  78.3, 104.7],
			y: [107.3, 77.8, 50.5],
		},
		earRight: { color: "#000000",
			x: [174.0, 206.8, 221.9],
			y: [41.1,  59.9,  85.9],
		},
		earLeftInner: { color: "#000000",
			x: [73.9, 58.9, 55.3, 73.7, 95.2],
			y: [94.7, 74.2, 56.3, 54.3, 61.4],
		},
		earRightInner: { color: "#000000",
			x: [184.3, 203.2, 224.7, 227.3, 223.7, 215.0],
			y: [46.9,  37.4,  33.3,  49.7,  63.5,  74.7],
		},
		eyeLeft: { color: "#000000",
			x: [112.6, 100.9, 91.1,  88.1,  92.2, 107.0, 114.7, 126.5, 134.1, 138.2, 139.8, 140.3],
			y: [150.5, 141.3, 125.4, 108.0, 95.7, 87.5,  86.5,  93.2,  100.3, 109.2, 120.3, 135.2],
		},
		eyelidLeft: { color: "#000000",
			x: [91.1,  92.7,  97.8,  109.0, 126.5],
			y: [125.4, 113.5, 105.5, 97.3,  93.2],
		},
		eyebrowLeft: { color: "#000000",
			x: [86.5, 91.1, 106.0],
			y: [91.1, 80.4, 74.2],
		},
		eyeRight: { color: "#000000",
			x: [157.2, 152.6, 153.1, 158.3, 169.8, 179.2, 190.1, 200.0, 203.1, 203.6, 199.0, 193.8, 187.0],
			y: [132.3, 114.1, 99.5,  88.5,  78.1,  76.0,  78.1,  86.5,  100.0, 104.7, 123.4, 132.3, 139.6],
		},
		eyelidRight: { color: "#000000",
			x: [158.3, 172.4, 181.3, 191.7, 197.4, 203.6],
			y: [88.5,  88.0,  89.6,  93.8,  99.0,  104.7],
		},
		eyebrowRight: { color: "#000000",
			x: [199.0, 190.6, 172.4],
			y: [74.0,  63.5,  62.5],
		},
	},
	ctx,
	0,
	1,
	false
);
draw({
		pupilLeft: { color: "#000000",
			x: [110.6, 113.1, 119.3, 129.0, 132.1, 131.1, 123.9, 118.3, 113.7, 111.6, 120.3, 110.6],
			y: [120.3, 107.5, 102.4, 107.5, 115.2, 130.5, 139.2, 139.8, 135.2, 129.5, 122.9, 120.3],
		},
		pupilRight: { color: "#000000",
			x: [165.6, 173.4, 163.0, 165.1, 168.8, 175.5, 181.8, 185.9, 188.0, 186.5, 182.3, 179.7, 172.4, 168.8, 165.6],
			y: [119.8, 115.1, 112.0, 101.0, 95.3,  94.8,  100.0, 108.3, 119.8, 129.2, 132.3, 131.8, 129.2, 125.5, 119.8],
		},
	},
	ctx,
	0,
	1,
	'#000000'
);
draw({
		bodyLineZero: {color: "#000000",
			x: [132.8, 188.8],
			y: [224.5, 222.6],
		},
		bodyLineOne: { color: "#000000",
			x: [111.8, 136.8, 161.5, 208.1],
			y: [245.8, 247.1, 245.8, 241.7],
		},
		bodyLineTwo: { color: "#000000",
			x: [105.7, 128.3, 162.0, 188.7, 219.8],
			y: [263.0, 265.1, 266.7, 264.6, 258.9],
		},
		bodyLineThree: { color: "#000000",
			x: [93.3,  128.3, 161.2, 201.2, 231.1],
			y: [285.0, 288.5, 290.0, 286.5, 281.0],
		},
		armLeftLineZero: { color: "#000000",
			x: [98.8,  100.8, 103.3, 108.8, 117.1],
			y: [222.1, 229.1, 232.6, 237.1, 241.1],
		},
		armLeftLineOne: { color: "#000000",
			x: [77.4,  89.3,  96.2,  106.9],
			y: [242.6, 251.1, 254.5, 257.7],
		},
		armRightLineZero: { color: "#000000",
			x: [229.6, 227.3, 223.7, 218.5, 209.5],
			y: [219.9, 227.0, 232.8, 237.6, 241.8],
		},
		armRightLineOne: { color: "#000000",
			x: [254.0, 238.1, 224.3],
			y: [238.1, 254.5, 264.0],
		},
	},
	ctx,
	0,
	1,
	false
);<|endoftext|>


<commit_before>'use strict';

module.exports = function renderReplyText() {
  return (req, res, next) => {
    if (req.reply.text) {
      return next();
    }

    if (req.campaign) {
      req.reply.text = req.campaign[req.reply.template];

      return next();
    }

    // TODO: Set this as a config variable.
    req.reply.template = 'noCampaignMessage';
    req.reply.text = 'Sorry, I\'m not sure how to respond to that.\n\nSay MENU to find a Campaign to join.';
    return next();
  };
};
<commit_msg>Gets noReply working again<commit_after>'use strict';

module.exports = function renderReplyText() {
  return (req, res, next) => {
    // If we haven't rendered text yet, this is a campaign message except for edge-case when our
    // template === noReply -- in that case our text is set to empty string.
    if (req.reply.text || req.reply.template === 'noReply') {
      return next();
    }

    if (req.campaign) {
      req.reply.text = req.campaign[req.reply.template];

      return next();
    }

    // TODO: Set this as a config variable.
    req.reply.template = 'noCampaignMessage';
    req.reply.text = 'Sorry, I\'m not sure how to respond to that.\n\nSay MENU to find a Campaign to join.';
    return next();
  };
};
<|endoftext|>


<commit_before>angular.module('Trestle')

/**
 @ngdoc service
 @name Trestle.trIssueHelpers

 @description
 Simple service to hold methods helpful to working with issues.
 */
.service('trIssueHelpers', function($q, gh, trRepoModel) {
   var me = this,
       config_header = '<!-- TRESTLE',
       config_footer = '-->';

   this.mergeBodyConfig = function(body, configObj) {
      // Store the updated configuration on the issue
      return [body, config_header, JSON.stringify(configObj), config_footer].join('\n');
   };

   /**
   * Spawns off queries to resolve all grafted on information for the issue.
   *  (all comments, counts, build status, etc)
   * We call this every time the issue is retrieved so that we can get
   * the full details.
   */
   this._resolveIssueFields = function(issue) {
      // Add a quick list of the issue label names
      issue.labelNames = _.map(issue.labels, function(labelObj) {
         return labelObj.name;
      });

      // Spawn off all the resolve methods (may go async internally)
      resolveIssueConf(issue);
      resolveIssueComments(issue);
      resolvePullStatus(issue);
   };

   // When we get issues parse the description and add a custom feed called
   // `config` to the issue.
   // note: operation is provided in lowercase
   gh.addResponseExtractor(function(response, operation, what, url, headers, params) {
      // endpoint: issue list
      if (operation === 'get' && /^repos\/.+\/.+\/issues$/.exec(what)) {
         response = _.map(response, function(issue) {
            me._resolveIssueFields(issue);
            return issue;
         });
      }
      // endpoint: specific issue
      else if (operation === 'get' && /^repos\/.+\/.+\/issues\/[0-9]+$/.exec(what)) {
         var issue = response;
         me._resolveIssueFields(issue);
         return issue;
      }

      // Add override on pull request handler
      if (operation === 'get' && /^repos\/.+\/.+\/pulls$/.exec(what)) {
         response = _.map(response, function(pull) {
            pull.tr_head = pull.head;
            return pull;
         });
      }
      // endpoint: specific issue
      else if (operation === 'get' && /^repos\/.+\/.+\/pulls\/[0-9]+$/.exec(what)) {
         var pull = response;
         pull.tr_head = pull.head;
         return pull;
      }

      return response;
   });

   // -- Private Helpers --- //

   /**
   * Request all comments for the issue and attach them.
   * as part of this we are going to calculate voting information.
   */
   function resolveIssueComments(issue) {
      issue.tr_comments = issue.tr_comments || [];
      issue.tr_comment_voting = {
         users: {
            //username: {
            //   avatar_url: "",
            //   count: <total int> // -1, 0, 1
            //}
         },
         total: 0   // <summed count>
      };

      // Go get all comments for the issue
      gh.getIssueComments(trRepoModel.owner, trRepoModel.repo, issue.number).then(
         function(commentResults) {
            // Store on the issue
            issue.tr_comments = commentResults;
            _calculateCommentVoting(issue);
         }
      );
   }

   /**
   * Helper to calculate the voting details for the current issue given
   * the comments.
   */
   function _calculateCommentVoting(issue) {
      var voting = {
         users: { },
         total: 0
      };

      // Calculate the counting
      _.each(issue.comments, function(comment) {
         var login = comment.user.login;

         if(undefined === voting.users[login]) {
            voting.users[login] = {
               avatar_url : comment.user.avatar_url,
               count      : 0
            };
         }

         // XXX: Update the voting
      });

      issue.tr_comment_voting = voting;
   }

   function resolveIssueConf(issue) {
      var config = {
         // XXX only do this if the config did not have it already
         weight: _.random(-1000, 1000)//Number.MIN_VALUE, Number.MAX_VALUE)
      };

      var body = issue.body;

      var lines = _.map(body.split('\n'), function(line) {return line.trim();}),
          conf_begin = _.findIndex(lines, function(line) {return line === config_header;}),
          conf_end   = _.findIndex(lines, function(line) {return line === config_footer;});

      if (conf_begin > -1 && conf_end > -1) {
         var config_str = lines.slice(conf_begin+1, conf_end).join('\n');
         try {
            config = _.defaults(JSON.parse(config_str), config);
            // Join non-config sections as the actual description
            // - Note: This strips the XML comment lines also
            body = [].concat(lines.slice(0, conf_begin),
                             lines.slice(conf_end + 1, lines.length-1)).join('\n');
         } catch (err) {
            console.error('Loading configuration:', body, err);
         }
      }

      // Store the extra configuration
      // XXX: change .config to .tr_config
      issue.config = config;
      // Store the cleaned up body
      issue.body = body;

      return issue;
   }

   /**
   * Spawn off queries to get build status and pull request details
   * added to the issue.
   */
   function resolvePullStatus(issue) {
      issue.tr_build_status = issue.tr_build_status || null;
      issue.tr_pull_details = issue.tr_pull_details || null;

      // If we have a valid pull for the issue, spawn off query for it's details
      if(issue.pull_request && issue.pull_request.html_url) {
         gh.getPull(trRepoModel.owner, trRepoModel.repo, issue.number).then(
            function(pullResult) {
               var head_ref = pullResult.tr_head.sha;
               issue.tr_pull_details = pullResult;

               gh.getStatus(trRepoModel.owner, trRepoModel.repo, head_ref).then(
                  function(statusResult) {
                     issue.tr_build_status     = statusResult;
                     issue.tr_top_build_status = statusResult[0];
                  }
               );
            }
         );
      }
   }

})

/**
 @ngdoc filter
 @name  Trestle.issuesWithLabel

 @description
 Takes an array of GitHub issues and filters it down to only issues with the
 specified labelName

 @param {String} labelName The textual name of a label all issues must have to
        be returned by this filter.
 @returns {Array} Set of issues with the label
 */
.filter('issuesWithLabel', function() {
   return function(issues, labelName) {
      if (!labelName) { throw new Error('labelName must be set'); }

      return _.filter(issues, function(issue) {
         return _.contains(issue.labelNames, labelName);
      });
   };
})

/**
 @ngdoc filter
 @name  Trestle.issuesInBacklog

 @description
 Takes an array of GitHub issues and filters it down to only issues that do not
 have a label used for a column.

 @returns {Array} Set of issues that are not part of a column
 */
.filter('issuesInBacklog', function(trRepoModel) {
   return function(issues) {
      var columns = trRepoModel.config.columns;
      return _.filter(issues, function(issue) {
         return _.intersection(issue.labelNames, columns).length === 0;
      });
   };
})

;
<commit_msg>Add plus one tracking for issues.<commit_after>angular.module('Trestle')

/**
 @ngdoc service
 @name Trestle.trIssueHelpers

 @description
 Simple service to hold methods helpful to working with issues.
 */
.service('trIssueHelpers', function($q, gh, trRepoModel) {
   var me = this,
       config_header = '<!-- TRESTLE',
       config_footer = '-->';

   this.mergeBodyConfig = function(body, configObj) {
      // Store the updated configuration on the issue
      return [body, config_header, JSON.stringify(configObj), config_footer].join('\n');
   };

   /**
   * Spawns off queries to resolve all grafted on information for the issue.
   *  (all comments, counts, build status, etc)
   * We call this every time the issue is retrieved so that we can get
   * the full details.
   */
   this._resolveIssueFields = function(issue) {
      // Add a quick list of the issue label names
      issue.labelNames = _.map(issue.labels, function(labelObj) {
         return labelObj.name;
      });

      // Spawn off all the resolve methods (may go async internally)
      resolveIssueConf(issue);
      resolveIssueComments(issue);
      resolvePullStatus(issue);
   };

   // When we get issues parse the description and add a custom feed called
   // `config` to the issue.
   // note: operation is provided in lowercase
   gh.addResponseExtractor(function(response, operation, what, url, headers, params) {
      // endpoint: issue list
      if (operation === 'get' && /^repos\/.+\/.+\/issues$/.exec(what)) {
         response = _.map(response, function(issue) {
            me._resolveIssueFields(issue);
            return issue;
         });
      }
      // endpoint: specific issue
      else if (operation === 'get' && /^repos\/.+\/.+\/issues\/[0-9]+$/.exec(what)) {
         var issue = response;
         me._resolveIssueFields(issue);
         return issue;
      }

      // Add override on pull request handler
      if (operation === 'get' && /^repos\/.+\/.+\/pulls$/.exec(what)) {
         response = _.map(response, function(pull) {
            pull.tr_head = pull.head;
            return pull;
         });
      }
      // endpoint: specific issue
      else if (operation === 'get' && /^repos\/.+\/.+\/pulls\/[0-9]+$/.exec(what)) {
         var pull = response;
         pull.tr_head = pull.head;
         return pull;
      }

      return response;
   });

   // -- Private Helpers --- //

   /**
   * Request all comments for the issue and attach them.
   * as part of this we are going to calculate voting information.
   */
   function resolveIssueComments(issue) {
      issue.tr_comments = issue.tr_comments || [];
      issue.tr_comment_voting = {
         users: {
            //username: {
            //   avatar_url: "",
            //   count: <total int> // -1, 0, 1
            //}
         },
         total: 0   // <summed count>
      };

      // Go get all comments for the issue
      // - Get the comments as HTML so to simplify the +1 counting
      gh.getIssueComments(trRepoModel.owner, trRepoModel.repo, issue.number, {asHtml: true})
         .then(function(commentResults) {
            // Store on the issue
            issue.tr_comments = commentResults;
            _calculateCommentVoting(issue);
         }
      );
   }

   /**
   * Helper to calculate the voting details for the current issue given
   * the comments.
   */
   function _getCleanComment(commentText) {
      return commentText
         // - strip code blocks so that things like `i = i+1` do not trigger
         //   the plus one counting.
         .replace(/<code.*<\/code>/, '')

         // - Attempt to strip links since they can contain text we don't care about
         .replace(/https?.*\s?/, '');
   }

   function _calculateCommentVoting(issue) {
      // Calculate the counting
      //console.log(issue);

      var votes = _.reduce(issue.tr_comments, function(votes, comment) {
         var login = comment.user.login;

         if(!votes[login]) {
            votes[login] = {
               avatar_url : comment.user.avatar_url,
               count      : 0
            };
         }

         // Extract any math out of the comment
         var counts = _getCleanComment(comment.body_html).match(/([-+][0-9]+)/g);
         // - convert the string counts into numbers
         counts = _.map(counts, function(numStr) {return parseInt(numStr, 10);});
         // - Run the math for the comment (this is silly but allows people to
         //   have various pluses in their comment).
         var total = _.reduce(counts, function(memo, cur) {
            return memo + cur;
         }, 0);

         // Update the users total count with the new information
         votes[login].count = votes[login].count + total;
         console.log(login, votes[login].count);

         return votes;
      }, {});

      issue.tr_comment_voting = {
         users: votes,
         total: _.reduce(votes, function(count, voteDetails) {
            return count + voteDetails.count;
         }, 0)
      };
   }

   function resolveIssueConf(issue) {
      var config = {
         // XXX only do this if the config did not have it already
         weight: _.random(-1000, 1000)//Number.MIN_VALUE, Number.MAX_VALUE)
      };

      var body = issue.body;

      var lines = _.map(body.split('\n'), function(line) {return line.trim();}),
          conf_begin = _.findIndex(lines, function(line) {return line === config_header;}),
          conf_end   = _.findIndex(lines, function(line) {return line === config_footer;});

      if (conf_begin > -1 && conf_end > -1) {
         var config_str = lines.slice(conf_begin+1, conf_end).join('\n');
         try {
            config = _.defaults(JSON.parse(config_str), config);
            // Join non-config sections as the actual description
            // - Note: This strips the XML comment lines also
            body = [].concat(lines.slice(0, conf_begin),
                             lines.slice(conf_end + 1, lines.length-1)).join('\n');
         } catch (err) {
            console.error('Loading configuration:', body, err);
         }
      }

      // Store the extra configuration
      // XXX: change .config to .tr_config
      issue.config = config;
      // Store the cleaned up body
      issue.body = body;

      return issue;
   }

   /**
   * Spawn off queries to get build status and pull request details
   * added to the issue.
   */
   function resolvePullStatus(issue) {
      issue.tr_build_status = issue.tr_build_status || null;
      issue.tr_pull_details = issue.tr_pull_details || null;

      // If we have a valid pull for the issue, spawn off query for it's details
      if(issue.pull_request && issue.pull_request.html_url) {
         gh.getPull(trRepoModel.owner, trRepoModel.repo, issue.number).then(
            function(pullResult) {
               var head_ref = pullResult.tr_head.sha;
               issue.tr_pull_details = pullResult;

               gh.getStatus(trRepoModel.owner, trRepoModel.repo, head_ref).then(
                  function(statusResult) {
                     issue.tr_build_status     = statusResult;
                     issue.tr_top_build_status = statusResult[0];
                  }
               );
            }
         );
      }
   }

})

/**
 @ngdoc filter
 @name  Trestle.issuesWithLabel

 @description
 Takes an array of GitHub issues and filters it down to only issues with the
 specified labelName

 @param {String} labelName The textual name of a label all issues must have to
        be returned by this filter.
 @returns {Array} Set of issues with the label
 */
.filter('issuesWithLabel', function() {
   return function(issues, labelName) {
      if (!labelName) { throw new Error('labelName must be set'); }

      return _.filter(issues, function(issue) {
         return _.contains(issue.labelNames, labelName);
      });
   };
})

/**
 @ngdoc filter
 @name  Trestle.issuesInBacklog

 @description
 Takes an array of GitHub issues and filters it down to only issues that do not
 have a label used for a column.

 @returns {Array} Set of issues that are not part of a column
 */
.filter('issuesInBacklog', function(trRepoModel) {
   return function(issues) {
      var columns = trRepoModel.config.columns;
      return _.filter(issues, function(issue) {
         return _.intersection(issue.labelNames, columns).length === 0;
      });
   };
})

;
<|endoftext|>


<commit_before>(function(exports) {

	var BackgroundWindow = new Class({
		Extends: Window,
		initialize: function() {
			this.parent();
			this._image = new Image();
			this._image.src = "WoodBackground.jpg";
			this.hasInput = false;
		},
		connect: function(server) {
			this.parent(server);
			this.configure(0, 0, server.width, server.height);
		},
		expose: function(wrapper) {
			wrapper.drawWithContext(function(ctx) {
				var pattern = ctx.createPattern(this._image, 'repeat');
				ctx.fillStyle = pattern;
				ctx.fillRect(0, 0, this.width, this.height);
			}.bind(this));
			wrapper.clearDamage();
		},
	});

	var FakeWindow = new Class({
		Extends: Window,
		initialize: function(imageSrc, delay) {
			this.parent();
			this._image = new Image();
			this._image.src = imageSrc;

			// Delay every expose by a bit.
			this.expose = new Task(this._draw.bind(this), delay);
		},
		_draw: function(wrapper) {
			wrapper.drawWithContext(function(ctx) {
				ctx.drawImage(this._image, 0, 0, this.width, this.height);
			}.bind(this));
			wrapper.clearDamage();
			return false;
		},
	});

	var server = new Server(1024, 768);
	document.querySelector(".server").appendChild(server.elem);

	var w = new BackgroundWindow();
	w.connect(server);

	function animWindow(window, freq) {
		var delay = 50;
		var stepsPerSec = 1000 / delay;

		var time = 0;
		var origX = window.x;

		var amplitude = 40;
		freq = freq || 0.5;

		var step = freq * (Math.PI * 2 / stepsPerSec);

		function animate() {
			var offs = Math.sin(time) * amplitude;
			var x = origX + offs;
			window.configure(x, undefined, undefined, undefined);
			time += step;
			return true;
		}
		var task = new Task(animate, delay);
		task();
	}

	for (var i = 0; i < 5; i++) {
		var cascade = 40;
		var windowNumber = i + 1;
		var delay = 40 - windowNumber * 10;
		var w = new FakeWindow("TerminalScreenshot.png", delay);
		w.connect(server);
		w.configure(windowNumber * cascade, windowNumber * cascade, 735, 461);
		var freq = i * 0.25 + 0.5;
		animWindow(w, freq);
	}

	window.addEventListener("keydown", function(evt) {
		var letter = String.fromCharCode(evt.keyCode);
		if (letter === 'D')
			server.toggleDebug();
		if (letter === 'R')
			server.queueFullRedraw();
	});

})(window);
<commit_msg>Optimize wallpaper<commit_after>(function(exports) {

	var BackgroundWindow = new Class({
		Extends: Window,
		initialize: function() {
			this.parent();
			this._image = new Image();
			this._image.src = "WoodBackground.jpg";
			this.hasInput = false;
		},
		connect: function(server) {
			this.parent(server);
			this.configure(0, 0, server.width, server.height);
		},
		expose: function(wrapper) {
			wrapper.drawWithContext(function(ctx) {
				ctx.drawImage(this._image, 0, 0, this.width, this.height);
			}.bind(this));
			wrapper.clearDamage();
		},
	});

	var FakeWindow = new Class({
		Extends: Window,
		initialize: function(imageSrc, delay) {
			this.parent();
			this._image = new Image();
			this._image.src = imageSrc;

			// Delay every expose by a bit.
			this.expose = new Task(this._draw.bind(this), delay);
		},
		_draw: function(wrapper) {
			wrapper.drawWithContext(function(ctx) {
				ctx.drawImage(this._image, 0, 0, this.width, this.height);
			}.bind(this));
			wrapper.clearDamage();
			return false;
		},
	});

	var server = new Server(1024, 768);
	document.querySelector(".server").appendChild(server.elem);

	var w = new BackgroundWindow();
	w.connect(server);

	function animWindow(window, freq) {
		var delay = 50;
		var stepsPerSec = 1000 / delay;

		var time = 0;
		var origX = window.x;

		var amplitude = 40;
		freq = freq || 0.5;

		var step = freq * (Math.PI * 2 / stepsPerSec);

		function animate() {
			var offs = Math.sin(time) * amplitude;
			var x = origX + offs;
			window.configure(x, undefined, undefined, undefined);
			time += step;
			return true;
		}
		var task = new Task(animate, delay);
		task();
	}

	for (var i = 0; i < 5; i++) {
		var cascade = 40;
		var windowNumber = i + 1;
		var delay = 40 - windowNumber * 10;
		var w = new FakeWindow("TerminalScreenshot.png", delay);
		w.connect(server);
		w.configure(windowNumber * cascade, windowNumber * cascade, 735, 461);
		var freq = i * 0.25 + 0.5;
		animWindow(w, freq);
	}

	window.addEventListener("keydown", function(evt) {
		var letter = String.fromCharCode(evt.keyCode);
		if (letter === 'D')
			server.toggleDebug();
		if (letter === 'R')
			server.queueFullRedraw();
	});

})(window);
<|endoftext|>


<commit_before>import { fromJS } from 'immutable';

const CHANGE_THREAD = 'CHANGE_THREAD';

export function changeThread(to) {
  // TODO: change route too
  return {
    type: CHANGE_THREAD,
    data: to,
  };
}

const initialState = fromJS({
  activeThread: '#freelance',
  threads: [
    { title: '#fulltime',
      messages: [
        { text: '😍' },
        { text: '😍', me: true },
        { text: 'So we’re hiring and we thought you’d be a great fit…' },
        { text: 'tell me more!', me: true },
        { text: 'well, we’re a diverse, well-funded team in London, Berlin, San Francisco or Portland working on something interesting. Our design team is full of weird hybrids like you, too!' },
        { text: 'our design team is full of weird hybrids like you, too!' },
        { text: 'sounds awesome. if you’re in the US, can you sponsor visas?', me: true },
        { text: 'yup! want to chat more?' },
        { text: 'I’d love to. Want to drop me an email? hire@jon.gold.', me: true },
      ],
    },
    { title: '#freelance',
      messages: [
        { text: 'Hey Jon, nice beard! We’d like to pay you money to make some internet.' },
        { text: 'Rad, I’m listening!', me: true },
        { text: 'Awesome! So you’re a designer?' },
        { text: 'Yup! I can do everything from UX to visual design; tending to be happiest in fast-moving product design roles.' },
        { text: 'And code? What technologies do you use?' },
        { text: 'As of 2016 I’m very, very fast with React. I can also work on projects in Meteor and Ember. I’m not taking on backend projects, or any WordPress / Angular at the moment. ', me: true },
        { text: 'Do you do design or development in isolation?' },
        { text: 'If the project is amazing; but you get the most @jongold for your money if I’m simulatenously involved with design and build.', me: true },
        { text: 'Wonderful. Want to #touchbase and hear more?' },
        { text: 'I’d love to. Drop me an email: hire@jon.gold.', me: true },
      ],
    },
    { title: '#speaking',
      messages: [
        { text: 'we’d like you to speak at our conference!' },
        { text: 'fancy it?' },
        { text: 'I’d love to — I’m currently giving talks about the integration of design & AI, and of the lessons we can take from Functional Programming into CSS.', me: true },
        { text: '💗' },
        { text: 'Pretty standard stuff though; I need flights paid for, and you absolutely need to have a Code of Conduct and a diverse speaker lineup.', me: true },
        { text: 'Drop me a line - speaking@jon.gold', me: true },
      ],
    },
    { title: '#mentoring',
      messages: [
        { text: 'I have questions about design / code / life; do you mind if I email you for advice? 🙏' },
        { img: '/fursure.gif', me: true },
        { text: 'hit me at mentoring@jon.gold', me: true },
      ],
    },
  ],
});

function contactReducer(state = initialState, action) {
  switch (action.type) {
    case CHANGE_THREAD: {
      return state.set('activeThread', action.data);
    }
    default:
      return state;
  }
}

export default contactReducer;
<commit_msg>contact typo<commit_after>import { fromJS } from 'immutable';

const CHANGE_THREAD = 'CHANGE_THREAD';

export function changeThread(to) {
  // TODO: change route too
  return {
    type: CHANGE_THREAD,
    data: to,
  };
}

const initialState = fromJS({
  activeThread: '#freelance',
  threads: [
    { title: '#fulltime',
      messages: [
        { text: '😍' },
        { text: '😍', me: true },
        { text: 'So we’re hiring and we thought you’d be a great fit…' },
        { text: 'tell me more!', me: true },
        { text: 'well, we’re a diverse, well-funded team in London, Berlin, San Francisco or Portland working on something interesting. Our design team is full of weird hybrids like you, too!' },
        { text: 'our design team is full of weird hybrids like you, too!' },
        { text: 'sounds awesome. if you’re in the US, can you sponsor visas?', me: true },
        { text: 'yup! want to chat more?' },
        { text: 'I’d love to. Want to drop me an email? hire@jon.gold.', me: true },
      ],
    },
    { title: '#freelance',
      messages: [
        { text: 'Hey Jon, nice beard! We’d like to pay you money to make some internet.' },
        { text: 'Rad, I’m listening!', me: true },
        { text: 'Awesome! So you’re a designer?' },
        { text: 'Yup! I can do everything from UX to visual design; tending to be happiest in fast-moving product design roles.', me: true },
        { text: 'And code? What technologies do you use?' },
        { text: 'As of 2016 I’m very, very fast with React. I can also work on projects in Meteor and Ember. I’m not taking on backend projects, or any WordPress / Angular at the moment. ', me: true },
        { text: 'Do you do design or development in isolation?' },
        { text: 'If the project is amazing; but you get the most @jongold for your money if I’m simulatenously involved with design and build.', me: true },
        { text: 'Wonderful. Want to #touchbase and hear more?' },
        { text: 'I’d love to. Drop me an email: hire@jon.gold.', me: true },
      ],
    },
    { title: '#speaking',
      messages: [
        { text: 'we’d like you to speak at our conference!' },
        { text: 'fancy it?' },
        { text: 'I’d love to — I’m currently giving talks about the integration of design & AI, and of the lessons we can take from Functional Programming into CSS.', me: true },
        { text: '💗' },
        { text: 'Pretty standard stuff though; I need flights paid for, and you absolutely need to have a Code of Conduct and a diverse speaker lineup.', me: true },
        { text: 'Drop me a line - speaking@jon.gold', me: true },
      ],
    },
    { title: '#mentoring',
      messages: [
        { text: 'I have questions about design / code / life; do you mind if I email you for advice? 🙏' },
        { img: '/fursure.gif', me: true },
        { text: 'hit me at mentoring@jon.gold', me: true },
      ],
    },
  ],
});

function contactReducer(state = initialState, action) {
  switch (action.type) {
    case CHANGE_THREAD: {
      return state.set('activeThread', action.data);
    }
    default:
      return state;
  }
}

export default contactReducer;
<|endoftext|>


<commit_before>import React from 'react';
import Reflux from 'reflux';
import _ from 'lodash';

import { DialogMixin, FormMixin } from '../../mixins';

import Actions from './HostingActions';
import Store from './HostingStore';

import { TextField } from 'material-ui';
import { Dialog, Show, Notification, SelectWrapper } from '../../common';

const CreateHostingDialog = React.createClass({
  mixins: [
    Reflux.connect(Store),
    DialogMixin,
    FormMixin
  ],

  validatorConstraints: {
    label: {
      presence: true,
      length: {
        maximum: 64
      }
    },
    description: {
      length: {
        maximum: 256
      }
    }
  },

  getHostingParams() {
    const { label, description, domains, id } = this.state;
    let domainsArray = domains;

    if (domains && domains.length && _.isObject(domains[0])) {
      domainsArray = _.map(domains, 'value');
    }

    return { label, description, id, domains: domainsArray };
  },

  handleChangeDomain(newValues, domains) {
    this.setState({
      domains: _.uniqBy(domains, 'value')
    });
  },

  handleAddSubmit() {
    const params = this.getHostingParams();
    const { items } = this.state;

    if (!items.length) {
      params.domains.push('default');
    }
    Actions.createHosting(params);
  },

  handleEditSubmit() {
    const params = this.getHostingParams();

    Actions.updateHosting(params.id, params);
  },

  handleChangeLabel(event, value) {
    this.setState({ label: value });
  },

  handleChangeDescription(event, value) {
    this.setState({ description: value });
  },

  render() {
    const { isLoading, open, label, description, canSubmit, domains } = this.state;
    const title = this.hasEditMode() ? 'Edit Hosting' : 'Add Hosting';

    return (
      <Dialog.FullPage
        key="dialog"
        ref="dialog"
        contentSize="medium"
        title={title}
        onRequestClose={this.handleCancel}
        open={open}
        isLoading={isLoading}
        actions={
          <Dialog.StandardButtons
            disabled={!canSubmit}
            handleCancel={this.handleCancel}
            handleConfirm={this.handleFormValidation}
            data-e2e-submit="hosting-dialog-submit-button"
          />
        }
        sidebar={
          <Dialog.SidebarBox>
            <Dialog.SidebarSection>
              Hosting allows you to manage, deploy and publish websites using Syncano platform.
            </Dialog.SidebarSection>
            <Dialog.SidebarSection last={true}>
              <Dialog.SidebarLink to="http://docs.syncano.io/v1.1/docs/">
                Learn more
              </Dialog.SidebarLink>
            </Dialog.SidebarSection>
          </Dialog.SidebarBox>
        }
      >

          <TextField
            autoFocus={true}
            fullWidth={true}
            value={label}
            name="label"
            onChange={this.handleChangeLabel}
            errorText={this.getValidationMessages('label').join(' ')}
            hintText="Hosting's label"
            floatingLabelText="Label"
            data-e2e="hosting-dialog-label-input"
          />
          <TextField
            fullWidth={true}
            value={description}
            name="description"
            onChange={this.handleChangeDescription}
            errorText={this.getValidationMessages('description').join(' ')}
            hintText="Hosting's description"
            floatingLabelText="Description"
            data-e2e="hosting-dialog-description-input"
          />
          <SelectWrapper
            errorText={this.getValidationMessages('domains').join(' ')}
            className="vm-5-t"
            multi={true}
            value={domains}
            allowCreate={true}
            placeholder="Domains"
            onChange={this.handleChangeDomain}
            data-e2e="hosting-dialog-domains-input"
          />

          {this.renderFormNotifications()}

          <Show if={this.getValidationMessages('detail').length}>
            <Notification type="error">
              {this.getValidationMessages('detail').join(' ')}
            </Notification>
          </Show>

      </Dialog.FullPage>
    );
  }
});

export default CreateHostingDialog;
<commit_msg>[DASH-2155] Comments, 'addsubmit 'refactor<commit_after>import React from 'react';
import Reflux from 'reflux';
import _ from 'lodash';

import { DialogMixin, FormMixin } from '../../mixins';

import Actions from './HostingActions';
import Store from './HostingStore';

import { TextField } from 'material-ui';
import { Dialog, Show, Notification, SelectWrapper } from '../../common';

const CreateHostingDialog = React.createClass({
  mixins: [
    Reflux.connect(Store),
    DialogMixin,
    FormMixin
  ],

  validatorConstraints: {
    label: {
      presence: true,
      length: {
        maximum: 64
      }
    },
    description: {
      length: {
        maximum: 256
      }
    }
  },

  getHostingParams() {
    const { label, description, domains, id } = this.state;
    let domainsArray = domains;

    if (domains && domains.length && _.isObject(domains[0])) {
      domainsArray = _.map(domains, 'value');
    }

    return { label, description, id, domains: domainsArray };
  },

  handleChangeDomain(newValues, domains) {
    this.setState({
      domains: _.uniqBy(domains, 'value')
    });
  },

  handleAddSubmit() {
    const { items } = this.state;
    const params = this.getHostingParams();

    if (_.isEmpty(items)) {
      params.domains.push('default');
    }

    Actions.createHosting(params);
  },

  handleEditSubmit() {
    const params = this.getHostingParams();

    Actions.updateHosting(params.id, params);
  },

  handleChangeLabel(event, value) {
    this.setState({ label: value });
  },

  handleChangeDescription(event, value) {
    this.setState({ description: value });
  },

  render() {
    const { isLoading, open, label, description, canSubmit, domains } = this.state;
    const title = this.hasEditMode() ? 'Edit Hosting' : 'Add Hosting';

    return (
      <Dialog.FullPage
        key="dialog"
        ref="dialog"
        contentSize="medium"
        title={title}
        onRequestClose={this.handleCancel}
        open={open}
        isLoading={isLoading}
        actions={
          <Dialog.StandardButtons
            disabled={!canSubmit}
            handleCancel={this.handleCancel}
            handleConfirm={this.handleFormValidation}
            data-e2e-submit="hosting-dialog-submit-button"
          />
        }
        sidebar={
          <Dialog.SidebarBox>
            <Dialog.SidebarSection>
              Hosting allows you to manage, deploy and publish websites using Syncano platform.
            </Dialog.SidebarSection>
            <Dialog.SidebarSection last={true}>
              <Dialog.SidebarLink to="http://docs.syncano.io/v1.1/docs/">
                Learn more
              </Dialog.SidebarLink>
            </Dialog.SidebarSection>
          </Dialog.SidebarBox>
        }
      >

          <TextField
            autoFocus={true}
            fullWidth={true}
            value={label}
            name="label"
            onChange={this.handleChangeLabel}
            errorText={this.getValidationMessages('label').join(' ')}
            hintText="Hosting's label"
            floatingLabelText="Label"
            data-e2e="hosting-dialog-label-input"
          />
          <TextField
            fullWidth={true}
            value={description}
            name="description"
            onChange={this.handleChangeDescription}
            errorText={this.getValidationMessages('description').join(' ')}
            hintText="Hosting's description"
            floatingLabelText="Description"
            data-e2e="hosting-dialog-description-input"
          />
          <SelectWrapper
            errorText={this.getValidationMessages('domains').join(' ')}
            className="vm-5-t"
            multi={true}
            value={domains}
            allowCreate={true}
            placeholder="Domains"
            onChange={this.handleChangeDomain}
            data-e2e="hosting-dialog-domains-input"
          />

          {this.renderFormNotifications()}

          <Show if={this.getValidationMessages('detail').length}>
            <Notification type="error">
              {this.getValidationMessages('detail').join(' ')}
            </Notification>
          </Show>

      </Dialog.FullPage>
    );
  }
});

export default CreateHostingDialog;
<|endoftext|>


<commit_before>/*
 * responsive-carousel autoplay extension
 * https://github.com/filamentgroup/responsive-carousel
 *
 * Copyright (c) 2012 Filament Group, Inc.
 * Licensed under the MIT, GPL licenses.
 */

(function( $, undefined ) {
	var pluginName = "carousel",
		interval = 4000,
		autoPlayMethods = {
			play: function(){
				var $self = $( this ),
					intAttr = $self.attr( "data-interval" ),
					thisInt = parseFloat( intAttr ) || interval;
				return $self.data(
					"timer",
					setInterval( function(){
						$self[ pluginName ]( "next" );
					},
					thisInt )
				);
			},

			stop: function(){
				clearTimeout( $( this ).data( "timer" ) );
			},

			_bindStopListener: function(){
				return $(this).bind( "mousedown", function(){
					$( this )[ pluginName ]( "stop" );
				} );
			},

			_initAutoPlay: function(){
				var autoplay = $( this ).attr( "data-autoplay") || null;
				if( autoplay === true || ( autoplay !== null && autoplay !== false ) ){
					$( this )
						[ pluginName ]( "_bindStopListener" )
						[ pluginName ]( "play" );
				}
			}
		};

	// add methods
	$.extend( $.fn[ pluginName ].prototype, autoPlayMethods );

	// DOM-ready auto-init
	$( document ).bind(  "create." + pluginName, function( e ){
		$( e.target )[ pluginName ]( "_initAutoPlay" );
	});

}(jQuery));<commit_msg>Fixing an bug in the autoplay plugin where autoplay functionality will fire in the case where the  attribute is not on the element, the  function will return  and the conditional will evalute  because it only tests for not null and not false. The fix will will run autoplay in cases where the attribute is empty '', or set to anything except for 'false' (case-insensitive).<commit_after>/*
 * responsive-carousel autoplay extension
 * https://github.com/filamentgroup/responsive-carousel
 *
 * Copyright (c) 2012 Filament Group, Inc.
 * Licensed under the MIT, GPL licenses.
 */

(function( $, undefined ) {
	var pluginName = "carousel",
		interval = 4000,
		autoPlayMethods = {
			play: function(){
				var $self = $( this ),
					intAttr = $self.attr( "data-interval" ),
					thisInt = parseFloat( intAttr ) || interval;
				return $self.data(
					"timer",
					setInterval( function(){
						$self[ pluginName ]( "next" );
					},
					thisInt )
				);
			},

			stop: function(){
				clearTimeout( $( this ).data( "timer" ) );
			},

			_bindStopListener: function(){
				return $(this).bind( "mousedown", function(){
					$( this )[ pluginName ]( "stop" );
				} );
			},

			_initAutoPlay: function(){
				var autoplayAttr = $( this ).attr( "data-autoplay" ),
					autoplay = ( autoplayAttr !== undefined ) ?
						( autoplayAttr.toLowerCase() !== "false" ) :
						false;
				if( autoplay === true ){
					$( this )
						[ pluginName ]( "_bindStopListener" )
						[ pluginName ]( "play" );
				}
			}
		};

	// add methods
	$.extend( $.fn[ pluginName ].prototype, autoPlayMethods );

	// DOM-ready auto-init
	$( document ).bind(  "create." + pluginName, function( e ){
		$( e.target )[ pluginName ]( "_initAutoPlay" );
	});

}(jQuery));<|endoftext|>


<commit_before>document.getElementById("ayuda").setAttribute("aria-current", "page");

var aside = document.getElementById("complementario");

var form = document.createElement("FORM");
var p = document.createElement("P");
var label = document.createElement("LABEL");
label.setAttribute("for", "repo");
t = document.createTextNode("cuenta/repositorio de GitHub");
label.appendChild(t);
var input = document.createElement("INPUT");
input.setAttribute("type", "text");
input.setAttribute("id", "repo");
input.setAttribute("name", "repo");
var submit = document.createElement("INPUT");
submit.setAttribute("type", "submit");
submit.setAttribute("id", "submit");
p.appendChild(label);
form.appendChild(p);
form.appendChild(submit);
aside.appendChild(form);
/*
$(document).ready(function () {

	$"#complementario").append(form);
	$("#submit").click(function () {
		$.getJSON("https://api.github.com/repos/" + $("#repo").val() + "/releases/latest", function(json) {
			var name = json.name);
			var p = document.createElement("P");
			var t = document.createTextNode(name);
			p.appendChild(t);
			$("#complementario").append(p);
		});
	});
});
*/<commit_msg>Append input to form<commit_after>document.getElementById("ayuda").setAttribute("aria-current", "page");

var aside = document.getElementById("complementario");

var form = document.createElement("FORM");
var p = document.createElement("P");
var label = document.createElement("LABEL");
label.setAttribute("for", "repo");
t = document.createTextNode("cuenta/repositorio de GitHub");
label.appendChild(t);
var input = document.createElement("INPUT");
input.setAttribute("type", "text");
input.setAttribute("id", "repo");
input.setAttribute("name", "repo");
form.appendChild(input);
var submit = document.createElement("INPUT");
submit.setAttribute("type", "submit");
submit.setAttribute("id", "submit");
p.appendChild(label);
form.appendChild(p);
form.appendChild(submit);
aside.appendChild(form);
/*
$(document).ready(function () {

	$"#complementario").append(form);
	$("#submit").click(function () {
		$.getJSON("https://api.github.com/repos/" + $("#repo").val() + "/releases/latest", function(json) {
			var name = json.name);
			var p = document.createElement("P");
			var t = document.createTextNode(name);
			p.appendChild(t);
			$("#complementario").append(p);
		});
	});
});
*/<|endoftext|>


<commit_before>/**
 * Module dependencies.
 */
var util = require('util')
  , OAuth2Strategy = require('passport-oauth').OAuth2Strategy
  , InternalOAuthError = require('passport-oauth').InternalOAuthError;

/**
 * `Strategy` constructor.
 *
 * Youtube authentication strategy authenticates requests using the OAuth 2.0 protocol.
 *
 * @param {Object} options
 * @param {Function} verify
 * @api public
 */
function Strategy(options, verify) {
  options = options || {};
  options.authorizationURL = options.authorizationURL || 'https://accounts.google.com/o/oauth2/auth';
  options.tokenURL = options.tokenURL || 'https://accounts.google.com/o/oauth2/token';
  options.scope = options.scope || ['https://www.googleapis.com/auth/youtube.readonly'];

  OAuth2Strategy.call(this, options, verify);
  this.name = 'youtube';
  this._profileURL = options.profileURL || 'https://www.googleapis.com/youtube/v3/channels?part=snippet&mine=true';
}

/**
 * Inherit from `OAuth2Strategy`.
 */
util.inherits(Strategy, OAuth2Strategy);

/**
 * Retrieve user profile from Youtube.
 *
 * This function constructs a normalized profile, with the following properties:
 *
 *   - `provider`         always set to `youtube`
 *   - `id`               the user's Google Plus user ID
 *   - `username`         the user's Youtube username
 *   - `displayName`      the user's full name
 *   - `name.familyName`  the user's last name
 *   - `name.givenName`   the user's first name
 *
 * @param {String} accessToken
 * @param {Function} done
 * @api protected
 */
Strategy.prototype.userProfile = function(accessToken, done) {
  var url = this._profileURL;

  this._oauth2.getProtectedResource(url, accessToken, function (err, body, res) {

    if (err) { return done(new InternalOAuthError('failed to fetch user profile', err)); }

    try {
      var json = JSON.parse(body);

      var youtubeProfile = json.items && json.items.length && json.items[0];

      var profile = { provider: 'youtube' };

      if (youtubeProfile) {
        profile.id = youtubeProfile.id;
        profile.displayName = youtubeProfile.snippet.title;
      }

      profile._raw = body;
      profile._json = json;

      done(null, profile);
    } catch(e) {
      done(e);
    }
  });
}

Strategy.prototype._convertProfileFields = function(profileFields) {
  var map = {
    'id':          'id',
    'username':    'username',
    'displayName': 'name',
    'name':       ['last_name', 'first_name']
  };

  var fields = [];

  profileFields.forEach(function(f) {
    if (typeof map[f] === 'undefined') return;

    if (Array.isArray(map[f])) {
      Array.prototype.push.apply(fields, map[f]);
    } else {
      fields.push(map[f]);
    }
  });

  return fields.join(',');
}

Strategy.prototype.authorizationParams = function(options) {
  return options && options.authorizationParams || {
    access_type : 'offline',
    approval_prompt: 'force'
  }
}

/**
 * Expose `Strategy`.
 */
module.exports = Strategy;
<commit_msg>Try to fix #3 because options wasnt coming from the right place<commit_after>/**
 * Module dependencies.
 */
var util = require('util')
  , OAuth2Strategy = require('passport-oauth').OAuth2Strategy
  , InternalOAuthError = require('passport-oauth').InternalOAuthError;

/**
 * `Strategy` constructor.
 *
 * Youtube authentication strategy authenticates requests using the OAuth 2.0 protocol.
 *
 * @param {Object} options
 * @param {Function} verify
 * @api public
 */
function Strategy(options, verify) {
  options = options || {};
  options.authorizationURL = options.authorizationURL || 'https://accounts.google.com/o/oauth2/auth';
  options.tokenURL = options.tokenURL || 'https://accounts.google.com/o/oauth2/token';
  options.scope = options.scope || ['https://www.googleapis.com/auth/youtube.readonly'];

  OAuth2Strategy.call(this, options, verify);
  this.name = 'youtube';
  this._profileURL = options.profileURL || 'https://www.googleapis.com/youtube/v3/channels?part=snippet&mine=true';
  this._authorizationParams = options.authorizationParams;
}

/**
 * Inherit from `OAuth2Strategy`.
 */
util.inherits(Strategy, OAuth2Strategy);

/**
 * Retrieve user profile from Youtube.
 *
 * This function constructs a normalized profile, with the following properties:
 *
 *   - `provider`         always set to `youtube`
 *   - `id`               the user's Google Plus user ID
 *   - `username`         the user's Youtube username
 *   - `displayName`      the user's full name
 *   - `name.familyName`  the user's last name
 *   - `name.givenName`   the user's first name
 *
 * @param {String} accessToken
 * @param {Function} done
 * @api protected
 */
Strategy.prototype.userProfile = function(accessToken, done) {
  var url = this._profileURL;

  this._oauth2.getProtectedResource(url, accessToken, function (err, body, res) {

    if (err) { return done(new InternalOAuthError('failed to fetch user profile', err)); }

    try {
      var json = JSON.parse(body);

      var youtubeProfile = json.items && json.items.length && json.items[0];

      var profile = { provider: 'youtube' };

      if (youtubeProfile) {
        profile.id = youtubeProfile.id;
        profile.displayName = youtubeProfile.snippet.title;
      }

      profile._raw = body;
      profile._json = json;

      done(null, profile);
    } catch(e) {
      done(e);
    }
  });
}

Strategy.prototype._convertProfileFields = function(profileFields) {
  var map = {
    'id':          'id',
    'username':    'username',
    'displayName': 'name',
    'name':       ['last_name', 'first_name']
  };

  var fields = [];

  profileFields.forEach(function(f) {
    if (typeof map[f] === 'undefined') return;

    if (Array.isArray(map[f])) {
      Array.prototype.push.apply(fields, map[f]);
    } else {
      fields.push(map[f]);
    }
  });

  return fields.join(',');
}

Strategy.prototype.authorizationParams = function(options) {
  return options && options.authorizationParams || this._authorizationParams || {
    access_type : 'offline',
    approval_prompt: 'force'
  }
}

/**
 * Expose `Strategy`.
 */
module.exports = Strategy;
<|endoftext|>


<commit_before>document.getElementById("ayuda").setAttribute("aria-current", "page");

var aside = document.getElementById("complementario");
var button = document.createElement("BUTTON");
button.setAttribute("id", "addonsButton");
var buttonText = document.createTextNode("Información sobre complementos de nvdaes");
button.appendChild(buttonText);
aside.appendChild(button);

addons = [
	"clipContentsDesigner",
	"emoticons",
	"eMule",
	"placeMarkers",
	"readFeeds",
	"reportSymbols",
]

var addonsLength = addons.length;

$(document).ready(function () {
	$("#addonsButton").click(function () {
		for (var i = 0; i < addonsLength; i++) {
			var details = document.createElement("DETAILS");
			var summary = document.createElement("SUMMARY");
			var t = document.createTextNode(addons[i]);
			summary.appendChild(t);
			details.appendChild(summary);
			$.getJSON("https://api.github.com/repos/nvdaes/" + addons[i] + "/releases/latest", function(json) {
				var name = json.name;
			});
			var ul = document.createElement("UL");
			var li = document.createElement("LI");
			var t = document.createTextNode("name");
			li.appendChild(t);
			ul.appendChild(li);")
			details.appendChild(ul);
			aside.appendChild(details);
		}
	});
});
<commit_msg>Update script<commit_after>document.getElementById("ayuda").setAttribute("aria-current", "page");

var aside = document.getElementById("complementario");
var button = document.createElement("BUTTON");
button.setAttribute("id", "addonsButton");
var buttonText = document.createTextNode("Información sobre complementos de nvdaes");
button.appendChild(buttonText);
aside.appendChild(button);

addons = [
	"clipContentsDesigner",
	"emoticons",
	"eMule",
	"placeMarkers",
	"readFeeds",
	"reportSymbols",
]

var addonsLength = addons.length;

$(document).ready(function () {
	$("#addonsButton").click(function () {
		for (var i = 0; i < addonsLength; i++) {
			var details = document.createElement("DETAILS");
			var summary = document.createElement("SUMMARY");
			var t = document.createTextNode(addons[i]);
			summary.appendChild(t);
			details.appendChild(summary);
			/*$.getJSON("https://api.github.com/repos/nvdaes/" + addons[i] + "/releases/latest", function(json) {
				var name = json.name;
			});*/
			var ul = document.createElement("UL");
			var li = document.createElement("LI");
			var t = document.createTextNode("name");
			li.appendChild(t);
			ul.appendChild(li);")
			details.appendChild(ul);
			aside.appendChild(details);
		}
	});
});
<|endoftext|>


<commit_before>import {defineModel, createModel, transaction, REFUSE_UPDATE} from 'afflatus'
import {createEaser, createTargets} from 'animachine-connect'
import {recurseKeys} from './recursers'

import uniq from 'lodash/uniq'
import flatten from 'lodash/flatten'
import pick from 'lodash/pick'
import {getValueOfParamAtTime} from './getters'

function mapSources(sources = [], ItemClass, parent) {
  return sources.map(source => {
    const item = new ItemClass(source)
    item.parent = parent
    return item
  })
}

function sortNum(a, b) {
  return a - b
}

function load(target, source, names) {
  names.forEach(name => {
    if (source.hasOwnProperty(name)) {
      target[name] = source[name]
    }
  })
}

const min = _min => value => Math.max(_min, value)
const minmax = (min, max) => value => Math.min(max, Math.max(min, value))
const notEmptyString = () => value => value ? value : REFUSE_UPDATE

function findParent(item, ParentClass) {
  let parent = item.parent
  while (parent) {
    if (parent instanceof ParentClass) {
      return parent
    }
    parent = parent.parent
  }
}

function getHistory(item) {
  return item.history || item.parent('Timeline').history
}

function removeItem(arr, item) {
  const index = arr.indexOf(item)
  if (index >= 0) {
    arr.splice(index, 1)
  }
}

function createAdder(type, storeName) {
  let history

  return function (source) {
    if (!history) {
      history = getHistory(this)
    }

    let item

    return history.save(
      () => {
        item = createModel(type, source, this)
        this[storeName].push(item)
        return item
      },
      () => removeItem(this[storeName], item),
    )
  }
}

export function createRemover(storeName) {
  let history

  return function (item) {
    if (!history) {
      history = getHistory(this)
    }

    return history.save(
      () => removeItem(this[storeName], item),
      () => this[storeName].push(item),
    )
  }
}

function createSetter(paramName) {
  let history

  return function (value) {
    if (!history) {
      history = getHistory(this)
    }

    const oldValue = this[paramName]

    if (oldValue === value) {
      return
    }

    return history.save(
      () => this[paramName] = value,
      () => this[paramName] = oldValue,
    )
  }
}

export class HistoryFlag {
  constructor(pair) {
    this.pair = pair || new HistoryFlag(this)
  }
  isPair(pair) {
    return this.pair === pair
  }
}

export class HistoryReg {
  constructor(redo, undo) {
    this.undo = undo
    this.redo = redo
  }
}

defineModel({
  type: 'History',
  simpleValues: {
    position: {defaultValue: -1}
  },
  arrayValues: {
    stack: {}
  },
  untrackedValues: {
    save(redo, undo) {
      this.push(new HistoryReg(redo, undo))
      return redo()
    },
    push(item) {
      this.stack.splice(this.position + 1)
      this.stack.push(item)
      this.position += 1
    },
    startFlag() {
      const flag = new HistoryFlag()
      this.push(flag)
      return () => this.push(flag.pair)
    },
    wrap(fn) {
      const endFlag = this.startFlag()
      const result = transaction(() => fn())
      endFlag()
      return result
    },
    undo() {
      if (this.position < 0) {
        return
      }
      const stepBack = () => {
        const item = this.stack[this.position--]
        if (item.undo) {
          item.undo()
        }
        return item
      }

      transaction(() => {
        const item = stepBack()
        if (item instanceof HistoryFlag) {
          const pairIndex = this.stack.indexOf(item.pair)
          if (pairIndex <= this.position && pairIndex !== -1) {
            while (this.position >= pairIndex) {
              stepBack()
            }
          }
        }
      })
    },
    redo() {
      if (this.position >= this.stack.length - 1) {
        return
      }
      const stepForward = () => {
        const item = this.stack[++this.position]
        if (item.redo) {
          item.redo()
        }
        return item
      }

      transaction(() => {
        const item = stepForward()
        if (item instanceof HistoryFlag) {
          const pairIndex = this.stack.indexOf(item.pair)
          if (pairIndex >= this.position && pairIndex !== -1) {
            while (this.position < pairIndex) {
              stepForward()
            }
          }
        }
      })
    }
  }
})

defineModel({
  type: 'Project',
  simpleValues: {
    name: {type: 'string', defaultValue: 'unnamed project', transform: notEmptyString()},
    currentTimeline: {type: 'Timeline', canBeNull: true},
    isRenaming: {type: 'boolean', defaultValue: false, dontSerialise: true},
  },
  arrayValues: {
    timelines: {type: 'Timeline'}
  },
  untrackedValues: {
    addTimeline(source) {
      const timeline = createModel('Timeline', source, this)
      this.timelines.push(timeline)
      return timeline
    }
  }
})

defineModel({
  type: 'Timeline',
  simpleValues: {
    history: {type: 'History', dontSerialise: true},
    name: {type: 'string', defaultValue: 'unnamed timeline', transform: notEmptyString()},
    currentTrack: {type: 'Track', canBeNull: true},
    isPlaying: {type: 'boolean', defaultValue: false},
    isSeeking: {type: 'boolean', defaultValue: false},
    currentTime: {type: 'number', defaultValue: 0, transform: min(0)},
    // length: {type: 'number', defaultValue: 60000},
    pxpms: {type: 'number', defaultValue: 1, transform: minmax(0.0001, 5)},
    width: {type: 'number', defaultValue: 2000},
    start: {type: 'number', defaultValue: 0},
    startMargin: {type: 'number', defaultValue: 6},
    inlineEaseEditor: {type: 'object', defaultValue: null, dontSerialise: true},
    isRenaming: {type: 'boolean', defaultValue: false, dontSerialise: true},
  },
  arrayValues: {
    tracks: {type: 'Track'},
    previews: {dontSerialise: true},
  },
  computedValues: {
    animationSource() {
      return {
        name: this.name,
        tracks: this.tracks.map(track => track.animationSource)
      }
    },
    lastKeyTime() {
      let result = 0
      recurseKeys(this, key => {
        if (key.time > result) {
          result = key.time
        }
      })
      return result
    },
    length() {
      return this.lastKeyTime * 1.5
    },
    visibleTime() {
      return (this.width / this.pxpms) || 0
    }
  },
  untrackedValues: {
    registerPreview(rootTarget, gsapAnimation) {
      this.previews.push({rootTarget, gsapAnimation})
    },
    addTrack: createAdder('Track', 'tracks'),
    removeTrack: createAdder('tracks'),
    deselectAllKeys() {
      getHistory(this).wrap(() => {
        recurseKeys(this, key => key.setSelected(false))
      })
    },
    translateSelectedKeys(offset) {
      getHistory(this).wrap(() => {
        recurseKeys(this, key => {
          if (key.selected) {
            key.setTime(key.time + offset)
          }
        })
      })
    },
    removeSelectedKeys() {
      getHistory(this).wrap(() => {
        this.inlineEaseEditor = null
        recurseKeys(this, key => {
          if (key.selected) {
            key.parent('Param').removeKey(key)
          }
        })
      })
    },
  }
})

defineModel({
  type: 'Track',
  simpleValues: {
    name: {type: 'string', defaultValue: 'unnamed track', transform: notEmptyString()},
    openInTimeline: {type: 'boolean', defaultValue: true},
    isRenaming: {type: 'boolean', defaultValue: false, dontSerialise: true},
  },
  arrayValues: {
    params: {type: 'Param'},
    selectors: {type: 'Selector'},
  },
  computedValues: {
    keyTimes() {
      return uniq(flatten(this.params.map(param => param.keyTimes))).sort(sortNum)
    },
    animationSource() {
      return {
        params: this.params.map(param => param.animationSource),
        selectors: this.selectors.map(selector => selector.animationSource),
      }
    },
    targets() {
      const track = this
      const {previews} = track.parent('Timeline')
      const result = []

      for (let i = 0; i < previews.length; ++i) {
        for (let j = 0; j < track.selectors.getLength(); ++j) {
          const targets = createTargets(
            previews[i].rootTarget,
            track.selectors[j]
          )
          result.push(...targets)
        }
      }
      return result
    }
  },
  untrackedValues: {
    addParam: createAdder('Param', 'params'),
    removeParam: createRemover('params'),
    addSelector: createAdder('Selector', 'selectors'),
    removeSelector: createRemover('selectors'),
    setValueAtTime(paramName, value, time) {
      getHistory(this).wrap(() => {
        let param = this.params.find(param => param.name === paramName)
        if (!param) {
          param = this.addParam({name: paramName})
        }
        param.setValueAtTime(value, time)
      })
    },
    selectKeysAtTime(time) {
      getHistory(this).wrap(() => {
        this.params.forEach(param => {
          param.selectKeysAtTime(time)
        })
      })
    },
    toggleKeysAtTime(time) {
      getHistory(this).wrap(() => {
        const keysAtTime = this.params
          .map(param => param.keys.find(key => key.time === time))
          .filter(key => Boolean(key))
        const thereIsKeyless = keysAtTime.length < this.params.length
        keysAtTime.forEach(key => {
          key.setSelected(thereIsUnselected ? true : false)
          if (thereIsKeyless) {
            if (!key) {
              param.addKey({
                time,
                value: param.getValueAtTime(time)
              })
            }
          }
          else {
            if (key) {
              param.removeKey(key)
            }
          }
        })
      })
    },
    toggleKeysSelectionAtTime(time) {
      getHistory(this).wrap(() => {
        const keysAtTime = this.params
          .map(param => param.keys.find(key => key.time === time))
          .filter(key => Boolean(key))
        const thereIsUnselected = keysAtTime.find(key => key.selected === false)
        keysAtTime.forEach(key => {
          key.setSelected(thereIsUnselected ? true : false)
        })
      })
    },
  },
})

defineModel({
  type: 'Selector',
  simpleValues: {
    selectorType: {type: 'string', defaultValue: 'css'},
    query: {type: 'string', defaultValue: ''},
  },
  computedValues: {
    animationSource() {
      return pick(this, ['selectorType', 'query'])
    }
  },
  untrackedValues: {
    setQuery: createSetter('query'),
    addSelector: createAdder('Selector', 'selectors'),
    removeSelector: createRemover('selectors'),
  }
})

defineModel({
  type: 'Param',
  simpleValues: {
    name: {type: 'string', defaultValue: 'unnamed param', transform: notEmptyString()},
    openInTimeline: {type: 'boolean', defaultValue: true},
    isRenaming: {type: 'boolean', defaultValue: false, dontSerialise: true},
  },
  arrayValues: {
    keys: {type: 'Key'},
  },
  computedValues: {
    keyTimes() {
      return this.keys.map(key => key.time).sort(sortNum)
    },
    currentValue() {
      const timeline = this.parent('Timeline')
      if (timeline) {
        return this.getValueAtTime(timeline.currentTime)
      }
    },
    animationSource() {
      return {
        keys: this.keys.map(key => key.animationSource),
        name: this.name,
      }
    },
  },
  untrackedValues: {
    addKey: createAdder('Key', 'keys'),
    removeKey: createRemover('keys'),
    getValueAtTime(time) {
      return getValueOfParamAtTime(this, time)
    },
    setValueAtTime(value, time) {
      getHistory(this).wrap(() => {
        this.parent('Timeline').isPlaying = false
        let key = this.keys.find(key => key.time === time)
        if (!key) {
          key = this.addKey({time})
        }
        key.setValue(value)
      })
    },
    toggleKeysAtTime(time) {
      getHistory(this).wrap(() => {
        const key = this.keys.find(key => key.time === time)
        if (key) {
          this.removeKey(key)
        }
        else {
          this.addKey({
            time,
            value: this.getValueAtTime(time)
          })
        }
      })
    },
    toggleKeysSelectionAtTime(time) {
      getHistory(this).wrap(() => {
        const key = this.keys.find(key => key.time === time)
        if (key) {
          key.setSelected(!key.selected)
        }
      })
    },
    selectKeysAtTime(time) {
      getHistory(this).wrap(() => {
        const key = this.keys.find(key => key.time === time)
        if (key) {
          key.setSelected(true)
        }
      })
    },
  }
})

defineModel({
  type: 'Key',
  simpleValues: {
    time: {type: 'int',  defaultValue: 0, transform: min(0)},
    value: {type: 'string',  defaultValue: '0'},
    ease: {type: 'Ease',  defaultValue: {}},
    selected: {type: 'boolean',  defaultValue: false},
  },
  computedValues: {
    animationSource() {
      return {
        time: this.time,
        value: this.value,
        ease: this.ease.animationSource,
      }
    }
  },
  untrackedValues: {
    setTime: createSetter('time'),
    setValue: createSetter('value'),
    setSelected: createSetter('selected'),
  }
})

defineModel({
  type: 'Ease',
  simpleValues: {
    easeType: {type: 'string', defaultValue: 'bezier'},
    pointAX: {type: 'number', defaultValue: 0.3, transform: minmax(0, 1)},
    pointAY: {type: 'number', defaultValue: 0.3},
    pointBX: {type: 'number', defaultValue: 0.7, transform: minmax(0, 1)},
    pointBY: {type: 'number', defaultValue: 0.7},
    roughEase: {type: 'number', defaultValue: false},
    roughStrength: {type: 'number', defaultValue: 1},
    roughPoints: {type: 'number', defaultValue: 20},
    roughClamp: {type: 'boolean', defaultValue: false},
    roughRandomise: {type: 'boolean', defaultValue: true},
    roughTaper: {type: 'string', defaultValue: 'none'},
  },
  computedValues: {
    easer() {return createEaser(this)},
    animationSource() {
      return pick(this, [
        'easeType', 'pointAX', 'pointAY', 'pointBX', 'pointBY',
        'roughEase','roughStrength', 'roughPoints', 'roughClamp',
        'roughRandomise', 'roughTaper',
      ])
    }
  },
  untrackedValues: {
    setPointAX: createSetter('pointAX'),
    setPointAY: createSetter('pointAY'),
    setPointBX: createSetter('pointBX'),
    setPointBY: createSetter('pointBY'),
  }
})
<commit_msg>fix bugs on removing params/tracks<commit_after>import {defineModel, createModel, transaction, REFUSE_UPDATE} from 'afflatus'
import {createEaser, createTargets} from 'animachine-connect'
import {recurseKeys} from './recursers'

import uniq from 'lodash/uniq'
import flatten from 'lodash/flatten'
import pick from 'lodash/pick'
import {getValueOfParamAtTime} from './getters'

function mapSources(sources = [], ItemClass, parent) {
  return sources.map(source => {
    const item = new ItemClass(source)
    item.parent = parent
    return item
  })
}

function sortNum(a, b) {
  return a - b
}

function load(target, source, names) {
  names.forEach(name => {
    if (source.hasOwnProperty(name)) {
      target[name] = source[name]
    }
  })
}

const min = _min => value => Math.max(_min, value)
const minmax = (min, max) => value => Math.min(max, Math.max(min, value))
const notEmptyString = () => value => value ? value : REFUSE_UPDATE

function findParent(item, ParentClass) {
  let parent = item.parent
  while (parent) {
    if (parent instanceof ParentClass) {
      return parent
    }
    parent = parent.parent
  }
}

function getHistory(item) {
  return item.history || item.parent('Timeline').history
}

function removeItem(arr, item) {
  const index = arr.indexOf(item)
  if (index >= 0) {
    arr.splice(index, 1)
  }
}

function createAdder(type, storeName) {
  let history

  return function (source) {
    if (!history) {
      history = getHistory(this)
    }

    let item

    return history.save(
      () => {
        item = createModel(type, source, this)
        this[storeName].push(item)
        return item
      },
      () => removeItem(this[storeName], item),
    )
  }
}

export function createRemover(storeName) {
  let history

  return function (item) {
    if (!history) {
      history = getHistory(this)
    }

    return history.save(
      () => removeItem(this[storeName], item),
      () => this[storeName].push(item),
    )
  }
}

function createSetter(paramName) {
  let history

  return function (value) {
    if (!history) {
      history = getHistory(this)
    }

    const oldValue = this[paramName]

    if (oldValue === value) {
      return
    }

    return history.save(
      () => this[paramName] = value,
      () => this[paramName] = oldValue,
    )
  }
}

export class HistoryFlag {
  constructor(pair) {
    this.pair = pair || new HistoryFlag(this)
  }
  isPair(pair) {
    return this.pair === pair
  }
}

export class HistoryReg {
  constructor(redo, undo) {
    this.undo = undo
    this.redo = redo
  }
}

defineModel({
  type: 'History',
  simpleValues: {
    position: {defaultValue: -1}
  },
  arrayValues: {
    stack: {}
  },
  untrackedValues: {
    save(redo, undo) {
      this.push(new HistoryReg(redo, undo))
      return redo()
    },
    push(item) {
      this.stack.splice(this.position + 1)
      this.stack.push(item)
      this.position += 1
    },
    startFlag() {
      const flag = new HistoryFlag()
      this.push(flag)
      return () => this.push(flag.pair)
    },
    wrap(fn) {
      const endFlag = this.startFlag()
      const result = transaction(() => fn())
      endFlag()
      return result
    },
    undo() {
      if (this.position < 0) {
        return
      }
      const stepBack = () => {
        const item = this.stack[this.position--]
        if (item.undo) {
          item.undo()
        }
        return item
      }

      transaction(() => {
        const item = stepBack()
        if (item instanceof HistoryFlag) {
          const pairIndex = this.stack.indexOf(item.pair)
          if (pairIndex <= this.position && pairIndex !== -1) {
            while (this.position >= pairIndex) {
              stepBack()
            }
          }
        }
      })
    },
    redo() {
      if (this.position >= this.stack.length - 1) {
        return
      }
      const stepForward = () => {
        const item = this.stack[++this.position]
        if (item.redo) {
          item.redo()
        }
        return item
      }

      transaction(() => {
        const item = stepForward()
        if (item instanceof HistoryFlag) {
          const pairIndex = this.stack.indexOf(item.pair)
          if (pairIndex >= this.position && pairIndex !== -1) {
            while (this.position < pairIndex) {
              stepForward()
            }
          }
        }
      })
    }
  }
})

defineModel({
  type: 'Project',
  simpleValues: {
    name: {type: 'string', defaultValue: 'unnamed project', transform: notEmptyString()},
    currentTimeline: {type: 'Timeline', canBeNull: true},
    isRenaming: {type: 'boolean', defaultValue: false, dontSerialise: true},
  },
  arrayValues: {
    timelines: {type: 'Timeline'}
  },
  untrackedValues: {
    addTimeline(source) {
      const timeline = createModel('Timeline', source, this)
      timeline.registerPreview(document.body, new TimelineMax())
      this.timelines.push(timeline)
      return timeline
    }
  }
})

defineModel({
  type: 'Timeline',
  simpleValues: {
    history: {type: 'History', dontSerialise: true},
    name: {type: 'string', defaultValue: 'unnamed timeline', transform: notEmptyString()},
    currentTrack: {type: 'Track', canBeNull: true},
    isPlaying: {type: 'boolean', defaultValue: false},
    isSeeking: {type: 'boolean', defaultValue: false},
    currentTime: {type: 'number', defaultValue: 0, transform: min(0)},
    // length: {type: 'number', defaultValue: 60000},
    pxpms: {type: 'number', defaultValue: 1, transform: minmax(0.0001, 5)},
    width: {type: 'number', defaultValue: 2000},
    start: {type: 'number', defaultValue: 0},
    startMargin: {type: 'number', defaultValue: 6},
    inlineEaseEditor: {type: 'object', defaultValue: null, dontSerialise: true},
    isRenaming: {type: 'boolean', defaultValue: false, dontSerialise: true},
  },
  arrayValues: {
    tracks: {type: 'Track'},
    previews: {dontSerialise: true},
  },
  computedValues: {
    animationSource() {
      return {
        name: this.name,
        tracks: this.tracks.map(track => track.animationSource)
      }
    },
    lastKeyTime() {
      let result = 0
      recurseKeys(this, key => {
        if (key.time > result) {
          result = key.time
        }
      })
      return result
    },
    length() {
      return this.lastKeyTime * 1.5
    },
    visibleTime() {
      return (this.width / this.pxpms) || 0
    }
  },
  untrackedValues: {
    registerPreview(rootTarget, gsapAnimation) {
      this.previews.push({rootTarget, gsapAnimation})
    },
    addTrack: createAdder('Track', 'tracks'),
    removeTrack: createRemover('tracks'),
    deselectAllKeys() {
      getHistory(this).wrap(() => {
        recurseKeys(this, key => key.setSelected(false))
      })
    },
    translateSelectedKeys(offset) {
      getHistory(this).wrap(() => {
        recurseKeys(this, key => {
          if (key.selected) {
            key.setTime(key.time + offset)
          }
        })
      })
    },
    removeSelectedKeys() {
      getHistory(this).wrap(() => {
        this.inlineEaseEditor = null
        recurseKeys(this, key => {
          if (key.selected) {
            key.parent('Param').removeKey(key)
          }
        })
      })
    },
  }
})

defineModel({
  type: 'Track',
  simpleValues: {
    name: {type: 'string', defaultValue: 'unnamed track', transform: notEmptyString()},
    openInTimeline: {type: 'boolean', defaultValue: true},
    isRenaming: {type: 'boolean', defaultValue: false, dontSerialise: true},
  },
  arrayValues: {
    params: {type: 'Param'},
    selectors: {type: 'Selector'},
  },
  computedValues: {
    keyTimes() {
      return uniq(flatten(this.params.map(param => param.keyTimes))).sort(sortNum)
    },
    animationSource() {
      return {
        params: this.params.map(param => param.animationSource),
        selectors: this.selectors.map(selector => selector.animationSource),
      }
    },
    targets() {
      if (!this.firstParent) return []// HACK
      const track = this
      const {previews} = track.parent('Timeline')
      const result = []

      for (let i = 0; i < previews.length; ++i) {
        for (let j = 0; j < track.selectors.getLength(); ++j) {
          const targets = createTargets(
            previews[i].rootTarget,
            track.selectors[j]
          )
          result.push(...targets)
        }
      }
      return result
    }
  },
  untrackedValues: {
    addParam: createAdder('Param', 'params'),
    removeParam: createRemover('params'),
    addSelector: createAdder('Selector', 'selectors'),
    removeSelector: createRemover('selectors'),
    setValueAtTime(paramName, value, time) {
      getHistory(this).wrap(() => {
        let param = this.params.find(param => param.name === paramName)
        if (!param) {
          param = this.addParam({name: paramName})
        }
        param.setValueAtTime(value, time)
      })
    },
    selectKeysAtTime(time) {
      getHistory(this).wrap(() => {
        this.params.forEach(param => {
          param.selectKeysAtTime(time)
        })
      })
    },
    toggleKeysAtTime(time) {
      getHistory(this).wrap(() => {
        const keysAtTime = this.params
          .map(param => param.keys.find(key => key.time === time))
          .filter(key => Boolean(key))
        const thereIsKeyless = keysAtTime.length < this.params.length
        keysAtTime.forEach(key => {
          key.setSelected(thereIsUnselected ? true : false)
          if (thereIsKeyless) {
            if (!key) {
              param.addKey({
                time,
                value: param.getValueAtTime(time)
              })
            }
          }
          else {
            if (key) {
              param.removeKey(key)
            }
          }
        })
      })
    },
    toggleKeysSelectionAtTime(time) {
      getHistory(this).wrap(() => {
        const keysAtTime = this.params
          .map(param => param.keys.find(key => key.time === time))
          .filter(key => Boolean(key))
        const thereIsUnselected = keysAtTime.find(key => key.selected === false)
        keysAtTime.forEach(key => {
          key.setSelected(thereIsUnselected ? true : false)
        })
      })
    },
  },
})

defineModel({
  type: 'Selector',
  simpleValues: {
    selectorType: {type: 'string', defaultValue: 'css'},
    query: {type: 'string', defaultValue: ''},
  },
  computedValues: {
    animationSource() {
      return pick(this, ['selectorType', 'query'])
    }
  },
  untrackedValues: {
    setQuery: createSetter('query'),
    addSelector: createAdder('Selector', 'selectors'),
    removeSelector: createRemover('selectors'),
  }
})

defineModel({
  type: 'Param',
  simpleValues: {
    name: {type: 'string', defaultValue: 'unnamed param', transform: notEmptyString()},
    openInTimeline: {type: 'boolean', defaultValue: true},
    isRenaming: {type: 'boolean', defaultValue: false, dontSerialise: true},
  },
  arrayValues: {
    keys: {type: 'Key'},
  },
  computedValues: {
    keyTimes() {
      return this.keys.map(key => key.time).sort(sortNum)
    },
    currentValue() {
      if (!this.firstParent || !this.firstParent.firstParent) return //HACK
      const timeline = this.parent('Timeline')
      if (timeline) {
        return this.getValueAtTime(timeline.currentTime)
      }
    },
    animationSource() {
      return {
        keys: this.keys.map(key => key.animationSource),
        name: this.name,
      }
    },
  },
  untrackedValues: {
    addKey: createAdder('Key', 'keys'),
    removeKey: createRemover('keys'),
    getValueAtTime(time) {
      return getValueOfParamAtTime(this, time)
    },
    setValueAtTime(value, time) {
      getHistory(this).wrap(() => {
        this.parent('Timeline').isPlaying = false
        let key = this.keys.find(key => key.time === time)
        if (!key) {
          key = this.addKey({time})
        }
        key.setValue(value)
      })
    },
    toggleKeysAtTime(time) {
      getHistory(this).wrap(() => {
        const key = this.keys.find(key => key.time === time)
        if (key) {
          this.removeKey(key)
        }
        else {
          this.addKey({
            time,
            value: this.getValueAtTime(time)
          })
        }
      })
    },
    toggleKeysSelectionAtTime(time) {
      getHistory(this).wrap(() => {
        const key = this.keys.find(key => key.time === time)
        if (key) {
          key.setSelected(!key.selected)
        }
      })
    },
    selectKeysAtTime(time) {
      getHistory(this).wrap(() => {
        const key = this.keys.find(key => key.time === time)
        if (key) {
          key.setSelected(true)
        }
      })
    },
  }
})

defineModel({
  type: 'Key',
  simpleValues: {
    time: {type: 'int',  defaultValue: 0, transform: min(0)},
    value: {type: 'string',  defaultValue: '0'},
    ease: {type: 'Ease',  defaultValue: {}},
    selected: {type: 'boolean',  defaultValue: false},
  },
  computedValues: {
    animationSource() {
      return {
        time: this.time,
        value: this.value,
        ease: this.ease.animationSource,
      }
    }
  },
  untrackedValues: {
    setTime: createSetter('time'),
    setValue: createSetter('value'),
    setSelected: createSetter('selected'),
  }
})

defineModel({
  type: 'Ease',
  simpleValues: {
    easeType: {type: 'string', defaultValue: 'bezier'},
    pointAX: {type: 'number', defaultValue: 0.3, transform: minmax(0, 1)},
    pointAY: {type: 'number', defaultValue: 0.3},
    pointBX: {type: 'number', defaultValue: 0.7, transform: minmax(0, 1)},
    pointBY: {type: 'number', defaultValue: 0.7},
    roughEase: {type: 'number', defaultValue: false},
    roughStrength: {type: 'number', defaultValue: 1},
    roughPoints: {type: 'number', defaultValue: 20},
    roughClamp: {type: 'boolean', defaultValue: false},
    roughRandomise: {type: 'boolean', defaultValue: true},
    roughTaper: {type: 'string', defaultValue: 'none'},
  },
  computedValues: {
    easer() {return createEaser(this)},
    animationSource() {
      return pick(this, [
        'easeType', 'pointAX', 'pointAY', 'pointBX', 'pointBY',
        'roughEase','roughStrength', 'roughPoints', 'roughClamp',
        'roughRandomise', 'roughTaper',
      ])
    }
  },
  untrackedValues: {
    setPointAX: createSetter('pointAX'),
    setPointAY: createSetter('pointAY'),
    setPointBX: createSetter('pointBX'),
    setPointBY: createSetter('pointBY'),
  }
})
<|endoftext|>