<commit_before>/**
 * @license Copyright 2020 Google Inc. All Rights Reserved.
 * 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.
 */
'use strict';

/* globals window document getBoundingClientRect */

const FRGatherer = require('../../fraggle-rock/gather/base-gatherer.js');
const emulation = require('../../lib/emulation.js');
const pageFunctions = require('../../lib/page-functions.js');

// JPEG quality setting
// Exploration and examples of reports using different quality settings: https://docs.google.com/document/d/1ZSffucIca9XDW2eEwfoevrk-OTl7WQFeMf0CgeJAA8M/edit#
const FULL_PAGE_SCREENSHOT_QUALITY = 30;

/**
 * @param {string} str
 */
function snakeCaseToCamelCase(str) {
  return str.replace(/(-\w)/g, m => m[1].toUpperCase());
}

class FullPageScreenshot extends FRGatherer {
  /** @type {LH.Gatherer.GathererMeta} */
  meta = {
    supportedModes: ['snapshot', 'timespan', 'navigation'],
  }

  /**
   * @param {LH.Gatherer.FRTransitionalContext} context
   * @return {Promise<number>}
   * @see https://bugs.chromium.org/p/chromium/issues/detail?id=770769
   */
  async getMaxScreenshotHeight(context) {
    return await context.driver.executionContext.evaluate(pageFunctions.getMaxTextureSize, {
      args: [],
      useIsolation: true,
      deps: [],
    });
  }

  /**
   * @param {LH.Gatherer.FRTransitionalContext} context
   * @return {Promise<LH.Artifacts.FullPageScreenshot['screenshot']>}
   */
  async _takeScreenshot(context) {
    const session = context.driver.defaultSession;
    const maxScreenshotHeight = await this.getMaxScreenshotHeight(context);
    const metrics = await session.sendCommand('Page.getLayoutMetrics');

    // Width should match emulated width, without considering content overhang.
    // Both layoutViewport and visualViewport capture this. visualViewport accounts
    // for page zoom/scale, which we currently don't account for (or expect). So we use layoutViewport.width.
    // Note: If the page is zoomed, many assumptions fail.
    //
    // Height should be as tall as the content. So we use contentSize.height
    const width = Math.min(metrics.layoutViewport.clientWidth, maxScreenshotHeight);
    const height = Math.min(metrics.contentSize.height, maxScreenshotHeight);

    await session.sendCommand('Emulation.setDeviceMetricsOverride', {
      // If we're gathering with mobile screenEmulation on (overlay scrollbars, etc), continue to use that for this screenshot.
      mobile: context.settings.screenEmulation.mobile,
      height,
      width,
      deviceScaleFactor: 1,
      scale: 1,
      screenOrientation: {angle: 0, type: 'portraitPrimary'},
    });

    // TODO: elements collected earlier in gathering are likely to have been shifted by now.
    // The lower in the page, the more likely (footer elements especially).
    // https://github.com/GoogleChrome/lighthouse/issues/11118

    const result = await session.sendCommand('Page.captureScreenshot', {
      format: 'jpeg',
      quality: FULL_PAGE_SCREENSHOT_QUALITY,
    });
    const data = 'data:image/jpeg;base64,' + result.data;

    return {
      data,
      width,
      height,
    };
  }

  /**
   * Gatherers can collect details about DOM nodes, including their position on the page.
   * Layout shifts occuring after a gatherer runs can cause these positions to be incorrect,
   * resulting in a poor experience for element screenshots.
   * `getNodeDetails` maintains a collection of DOM objects in the page, which we can iterate
   * to re-collect the bounding client rectangle.
   * @see pageFunctions.getNodeDetails
   * @param {LH.Gatherer.FRTransitionalContext} context
   * @return {Promise<LH.Artifacts.FullPageScreenshot['nodes']>}
   */
  async _resolveNodes(context) {
    function resolveNodes() {
      /** @type {LH.Artifacts.FullPageScreenshot['nodes']} */
      const nodes = {};
      if (!window.__lighthouseNodesDontTouchOrAllVarianceGoesAway) return nodes;

      const lhIdToElements = window.__lighthouseNodesDontTouchOrAllVarianceGoesAway;
      for (const [node, id] of lhIdToElements.entries()) {
        // @ts-expect-error - getBoundingClientRect put into scope via stringification
        const rect = getBoundingClientRect(node);
        nodes[id] = rect;
      }

      return nodes;
    }

    /**
     * @param {{useIsolation: boolean}} _
     */
    function resolveNodesInPage({useIsolation}) {
      return context.driver.executionContext.evaluate(resolveNodes, {
        args: [],
        useIsolation,
        deps: [pageFunctions.getBoundingClientRectString],
      });
    }

    // Collect nodes with the page context (`useIsolation: false`) and with our own, reused
    // context (`useIsolation: true`). Gatherers use both modes when collecting node details,
    // so we must do the same here too.
    const pageContextResult = await resolveNodesInPage({useIsolation: false});
    const isolatedContextResult = await resolveNodesInPage({useIsolation: true});
    return {...pageContextResult, ...isolatedContextResult};
  }

  /**
   * @param {LH.Gatherer.FRTransitionalContext} context
   * @return {Promise<LH.Artifacts['FullPageScreenshot']>}
   */
  async getArtifact(context) {
    const session = context.driver.defaultSession;
    const executionContext = context.driver.executionContext;
    const settings = context.settings;

    // In case some other program is controlling emulation, try to remember what the device looks
    // like now and reset after gatherer is done.
    const lighthouseControlsEmulation = !settings.screenEmulation.disabled;

    try {
      return {
        screenshot: await this._takeScreenshot(context),
        nodes: await this._resolveNodes(context),
      };
    } finally {
      // Revert resized page.
      if (lighthouseControlsEmulation) {
        await emulation.emulate(session, settings);
      } else {
        // Best effort to reset emulation to what it was.
        // https://github.com/GoogleChrome/lighthouse/pull/10716#discussion_r428970681
        // TODO: seems like this would be brittle. Should at least work for devtools, but what
        // about scripted puppeteer usages? Better to introduce a "setEmulation" callback
        // in the LH runner api, which for ex. puppeteer consumers would setup puppeteer emulation,
        // and then just call that to reset?
        // https://github.com/GoogleChrome/lighthouse/issues/11122

        // eslint-disable-next-line no-inner-declarations
        function getObservedDeviceMetrics() {
          // Convert the Web API's snake case (landscape-primary) to camel case (landscapePrimary).
          const screenOrientationType = /** @type {LH.Crdp.Emulation.ScreenOrientationType} */ (
            snakeCaseToCamelCase(window.screen.orientation.type));
          return {
            width: document.documentElement.clientWidth,
            height: document.documentElement.clientHeight,
            screenOrientation: {
              type: screenOrientationType,
              angle: window.screen.orientation.angle,
            },
            deviceScaleFactor: window.devicePixelRatio,
          };
        }

        const observedDeviceMetrics = await executionContext.evaluate(getObservedDeviceMetrics, {
          args: [],
          useIsolation: true,
          deps: [snakeCaseToCamelCase],
        });
        await session.sendCommand('Emulation.setDeviceMetricsOverride', {
          mobile: settings.formFactor === 'mobile',
          ...observedDeviceMetrics,
        });
      }
    }
  }
}

module.exports = FullPageScreenshot;
<commit_msg>core(full-page-screenshot): fix emulation reset (#13175)<commit_after>/**
 * @license Copyright 2020 Google Inc. All Rights Reserved.
 * 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.
 */
'use strict';

/* globals window document getBoundingClientRect */

const FRGatherer = require('../../fraggle-rock/gather/base-gatherer.js');
const emulation = require('../../lib/emulation.js');
const pageFunctions = require('../../lib/page-functions.js');

// JPEG quality setting
// Exploration and examples of reports using different quality settings: https://docs.google.com/document/d/1ZSffucIca9XDW2eEwfoevrk-OTl7WQFeMf0CgeJAA8M/edit#
const FULL_PAGE_SCREENSHOT_QUALITY = 30;

/**
 * @param {string} str
 */
function snakeCaseToCamelCase(str) {
  return str.replace(/(-\w)/g, m => m[1].toUpperCase());
}

/* c8 ignore start */

// eslint-disable-next-line no-inner-declarations
function getObservedDeviceMetrics() {
  // Convert the Web API's snake case (landscape-primary) to camel case (landscapePrimary).
  const screenOrientationType = /** @type {LH.Crdp.Emulation.ScreenOrientationType} */ (
    snakeCaseToCamelCase(window.screen.orientation.type));
  return {
    width: document.documentElement.clientWidth,
    height: document.documentElement.clientHeight,
    screenOrientation: {
      type: screenOrientationType,
      angle: window.screen.orientation.angle,
    },
    deviceScaleFactor: window.devicePixelRatio,
  };
}

/* c8 ignore stop */

class FullPageScreenshot extends FRGatherer {
  /** @type {LH.Gatherer.GathererMeta} */
  meta = {
    supportedModes: ['snapshot', 'timespan', 'navigation'],
  }

  /**
   * @param {LH.Gatherer.FRTransitionalContext} context
   * @return {Promise<number>}
   * @see https://bugs.chromium.org/p/chromium/issues/detail?id=770769
   */
  async getMaxScreenshotHeight(context) {
    return await context.driver.executionContext.evaluate(pageFunctions.getMaxTextureSize, {
      args: [],
      useIsolation: true,
      deps: [],
    });
  }

  /**
   * @param {LH.Gatherer.FRTransitionalContext} context
   * @return {Promise<LH.Artifacts.FullPageScreenshot['screenshot']>}
   */
  async _takeScreenshot(context) {
    const session = context.driver.defaultSession;
    const maxScreenshotHeight = await this.getMaxScreenshotHeight(context);
    const metrics = await session.sendCommand('Page.getLayoutMetrics');

    // Width should match emulated width, without considering content overhang.
    // Both layoutViewport and visualViewport capture this. visualViewport accounts
    // for page zoom/scale, which we currently don't account for (or expect). So we use layoutViewport.width.
    // Note: If the page is zoomed, many assumptions fail.
    //
    // Height should be as tall as the content. So we use contentSize.height
    const width = Math.min(metrics.layoutViewport.clientWidth, maxScreenshotHeight);
    const height = Math.min(metrics.contentSize.height, maxScreenshotHeight);

    await session.sendCommand('Emulation.setDeviceMetricsOverride', {
      // If we're gathering with mobile screenEmulation on (overlay scrollbars, etc), continue to use that for this screenshot.
      mobile: context.settings.screenEmulation.mobile,
      height,
      width,
      deviceScaleFactor: 1,
      scale: 1,
      screenOrientation: {angle: 0, type: 'portraitPrimary'},
    });

    // TODO: elements collected earlier in gathering are likely to have been shifted by now.
    // The lower in the page, the more likely (footer elements especially).
    // https://github.com/GoogleChrome/lighthouse/issues/11118

    const result = await session.sendCommand('Page.captureScreenshot', {
      format: 'jpeg',
      quality: FULL_PAGE_SCREENSHOT_QUALITY,
    });
    const data = 'data:image/jpeg;base64,' + result.data;

    return {
      data,
      width,
      height,
    };
  }

  /**
   * Gatherers can collect details about DOM nodes, including their position on the page.
   * Layout shifts occuring after a gatherer runs can cause these positions to be incorrect,
   * resulting in a poor experience for element screenshots.
   * `getNodeDetails` maintains a collection of DOM objects in the page, which we can iterate
   * to re-collect the bounding client rectangle.
   * @see pageFunctions.getNodeDetails
   * @param {LH.Gatherer.FRTransitionalContext} context
   * @return {Promise<LH.Artifacts.FullPageScreenshot['nodes']>}
   */
  async _resolveNodes(context) {
    function resolveNodes() {
      /** @type {LH.Artifacts.FullPageScreenshot['nodes']} */
      const nodes = {};
      if (!window.__lighthouseNodesDontTouchOrAllVarianceGoesAway) return nodes;

      const lhIdToElements = window.__lighthouseNodesDontTouchOrAllVarianceGoesAway;
      for (const [node, id] of lhIdToElements.entries()) {
        // @ts-expect-error - getBoundingClientRect put into scope via stringification
        const rect = getBoundingClientRect(node);
        nodes[id] = rect;
      }

      return nodes;
    }

    /**
     * @param {{useIsolation: boolean}} _
     */
    function resolveNodesInPage({useIsolation}) {
      return context.driver.executionContext.evaluate(resolveNodes, {
        args: [],
        useIsolation,
        deps: [pageFunctions.getBoundingClientRectString],
      });
    }

    // Collect nodes with the page context (`useIsolation: false`) and with our own, reused
    // context (`useIsolation: true`). Gatherers use both modes when collecting node details,
    // so we must do the same here too.
    const pageContextResult = await resolveNodesInPage({useIsolation: false});
    const isolatedContextResult = await resolveNodesInPage({useIsolation: true});
    return {...pageContextResult, ...isolatedContextResult};
  }

  /**
   * @param {LH.Gatherer.FRTransitionalContext} context
   * @return {Promise<LH.Artifacts['FullPageScreenshot']>}
   */
  async getArtifact(context) {
    const session = context.driver.defaultSession;
    const executionContext = context.driver.executionContext;
    const settings = context.settings;

    // In case some other program is controlling emulation, remember what the device looks
    // like now and reset after gatherer is done.
    let observedDeviceMetrics;
    const lighthouseControlsEmulation = !settings.screenEmulation.disabled;
    if (!lighthouseControlsEmulation) {
      observedDeviceMetrics = await executionContext.evaluate(getObservedDeviceMetrics, {
        args: [],
        useIsolation: true,
        deps: [snakeCaseToCamelCase],
      });
    }

    try {
      return {
        screenshot: await this._takeScreenshot(context),
        nodes: await this._resolveNodes(context),
      };
    } finally {
      // Revert resized page.
      if (lighthouseControlsEmulation) {
        await emulation.emulate(session, settings);
      } else if (observedDeviceMetrics) {
        // Best effort to reset emulation to what it was.
        // https://github.com/GoogleChrome/lighthouse/pull/10716#discussion_r428970681
        // TODO: seems like this would be brittle. Should at least work for devtools, but what
        // about scripted puppeteer usages? Better to introduce a "setEmulation" callback
        // in the LH runner api, which for ex. puppeteer consumers would setup puppeteer emulation,
        // and then just call that to reset?
        // https://github.com/GoogleChrome/lighthouse/issues/11122
        await session.sendCommand('Emulation.setDeviceMetricsOverride', {
          mobile: settings.formFactor === 'mobile',
          ...observedDeviceMetrics,
        });
      }
    }
  }
}

module.exports = FullPageScreenshot;
<|endoftext|>


<commit_before>var P = require('bluebird');
var async = require('async');
var config = require('../config');
var GitHubApi = require('github');
var users = require('./db').get('users');
var request = require('superagent');
P.promisifyAll(users);

function newGithub(token) {
  var api = new GitHubApi({
    version: '3.0.0'
  });
  api.authenticate({
    type: 'oauth',
    token: token
  });
  return api;
};

/**
 * Constructor for our user class
 */
function User(login, github, model) {
  this.login = login;
  this.github = github;
  this.model = model;
}

/**
 * Checks if the user is following the given username.
 */
User.prototype.isFollowing = function(other) {
  var getFollowUser = P.promisify(this.github.user.getFollowUser, this.github);
  return new P(function(resolve) {
    getFollowUser({
      user: other
    }).then(function(res) {
      return resolve(true);
    }, function(res) {
      return resolve(false);
    });
  });
};

/**
 * Follow a user.
 *
 * @param other String login username
 */
User.prototype.follow = function(other) {
  return this.isFollowing(other).bind(this).then(function(following) {
    if (following) return false;
    var followUser = P.promisify(this.github.user.followUser, this.github.user);
    return followUser({
      user: other
    });
  }).then(function(res) {
    if (!res) return false;
    return users.updateAsync({
      login: other
    }, {
      $addToSet: {
        followedBy: this.login
      }
    });
  });
};

/**
 * Make a user unfollow someone. Both params must be GH API instances.
 *
 * @param user GH API instance
 * @param other String login username
 */
User.prototype.unfollow = function(other) {
  return this.isFollowing(other).bind(this).then(function(following) {
    if (!following) return false;
    var unfollowUser = P.promisify(this.github.user.unFollowUser, this.github.user);
    return unfollowUser({
      user: other
    });
  }).then(function(res) {
    if (!res) return false;
    return users.updateAsync({
      login: other
    }, {
      $pull: {
        followedBy: this.login
      }
    });
  });
};

/**
 * Stars a repo.
 */
User.prototype.star = function(user, repo) {
  var star = P.promisify(this.github.repos.star, this.github.repos);
  return star({
    user: user,
    repo: repo
  });
};

/**
 * Adds an amount of followers to a user.
 *
 * @param amount - Use '-1' to add as many as possible. It will add as many followers as possible.
 */
User.prototype.addFollowers = function(amount) {
  var thiz = this;
  return users.findAsync({
    login: {
      $ne: this.login
    }
  }).bind(this).then(function(docs) {

    return new P(function(resolve) {
      var sum = 0;
      async.eachSeries(docs, function(otherModel, done) {
        // Ignore if same as user
        if (amount !== -1 && sum >= amount) return done();

        // Otherwise follow
        var otherUser = fromModel(otherModel);
        return otherUser.follow(thiz.login).then(function(res) {
          if (res) sum++;
          done();
        }, function() {
          // OAuth token no longer valid
          thiz.invalidate().then(done, done);
        });

      }.bind(this), function() {
        resolve(sum);
      });

    }).bind(this);

  }).then(function(sum) {
    return {
      follows: sum,
      amount: amount
    };
  });

};

/**
 * Removes all of a user's followers.
 */
User.prototype.removeFollowers = function() {
  var login = this.login;
  return P.settle((this.model.followedBy || []).map(function(user) {

    return fromLogin(user).then(function(u) {
      return u.unfollow(login);
    });

  })).then(function(results) {

    var unfollowed = results.filter(function(r) {
      return r.isFulfilled();
    }).length;
    var invalid = results.filter(function(r) {
      return r.isRejected();
    }).length;

    return {
      unfollowed: unfollowed,
      invalid: invalid
    };

  });
};

/**
 * Checks the amount of followers this user is supposed to have.
 * @param login The login of the user (username)
 * @param cb(err, privilege)
 */
User.prototype.checkPrivilege = function() {
  var count = config.baseFollowers;
  return users.countAsync({
    ref: this.login
  }).bind(this).then(function(ct) {
    count += ct * config.referralBonus;
    if (this.model.god) {
      count = -1;
    }
    return {
      count: count,
      referrals: ct
    };
  });
};

/**
 * Gets all of the users that the user hasn't been followed by.
 * This is a pretty expensive operation as it makes a lot of GH api requests.
 */
User.prototype.findNotFollowedBy = function() {
  var thiz = this;

  // Get followers of user
  var followedBy = this.model.followedBy || [];

  // Find users where their login isn't one of those followedBy
  return users.findAsync({
    login: {
      $nin: followedBy,
      $ne: this.login
    }
  }).then(function(dbNotFollowing) {
    return new P(function(resolve, reject) {
      if (!dbNotFollowing) return;

      // These are the users that are not following in the DB.
      var notFollowing = [];
      async.each(dbNotFollowing, function(userObj, done) {
        if (this.login === userObj.login) return done(); // Skip if same user

        var other = fromModel(userObj);
        other.isFollowing(thiz.login).then(function(res) {
          if (!res) {
            notFollowing.push(other.login);
          }
          done();
        });

      }, function(err) {
        if (err) return reject(err);
        resolve(notFollowing);
      });
    });
  });
};

/**
 * Get users that this user is following
 */
User.prototype.getFollowing = function() {
  return users.findAsync({
    followedBy: this.login
  });
};

/**
 * Returns a summary of a user
 */
User.prototype.summary = function() {
  return P.props({
    privilege: this.checkPrivilege(),
    following: this.getFollowing(),
    notFollowedBy: this.findNotFollowedBy()
  }).bind(this).then(function(res) {

    var followerCt = this.model.followedBy ? this.model.followedBy.length : 0;
    var remaining = res.privilege.count - followerCt;
    if (res.privilege.count === -1) {
      remaining = 99999999;
    }
    var amount = Math.min(res.notFollowedBy.length, remaining);
    if (amount < 0) amount = 0;

    return {
      privilege: res.privilege,
      following: res.following,
      amount: amount,
      remaining: remaining,
      followerCt: followerCt,
      user: this.model
    };

  });
};

/**
 * Updates this user's followers in the database.
 */
User.prototype.updateFollowedBy = function() {
  var me = this.login;
  var remove = [];
  return P.map(this.model.followedBy, function(login) {
    return fromLogin(login).then(function(user) {
      if (!user) {
        remove.push(login);
        throw 'done';
      }
      return [user, user.isFollowing(me)];
    }).spread(function(user, res) {
      if (!res) {
        remove.push(user.login);
        throw 'done';
      }
      return user;
    }).then(function(user) {
      return [user, user.validate()];
    }).spread(function(user, valid) {
      if (!valid) {
        remove.push(user.login);
      }
    }).catch(function(err) {
      if (err === 'done') return;
      console.error(err.stack);
    });
  }).then(function() {
    return users.updateAsync({
      login: me
    }, {
      $pullAll: {
        followedBy: remove
      }
    });
  }).then(function() {
    return remove;
  });
};

/**
 * Checks if a user's oauth token is valid.
 */
User.prototype.validate = function() {
  var get = P.promisify(this.github.user.get, this.github.user);
  return new P(function(resolve) {
    get({}).then(function(user) {
      request.get('https://github.com/' + user.login).end(function(res) {
        return resolve(!res.notFound);
      });
    }, function() {
      return resolve(false);
    });
  });
};

/**
 * Invalidates this user by removing their followers and lowering their cap.
 */
User.prototype.invalidate = function() {
  return this.getFollowing().then(function(users) {
    return P.map(users, function(model) {
      return fromModel(model).updateFollowedBy();
    });
  }).then(function() {
    return this.removeFollowers();
    // TODO reduce cap
  }).bind(this);
};

/**
 * Makes a user object from their login. Assumes it's already in the db.
 */
function fromLogin(login) {
  return users.findOneAsync({
    login: login
  }).then(function(model) {
    return new P(function(resolve) {
      if (!model) return resolve(null);
      return resolve(fromModel(model));
    });
  });
}

/**
 * Makes a (possibly new) user object from their token.
 */
function fromToken(token, ref) {
  var github = newGithub(token);
  var get = P.promisify(github.user.get, github.user);
  return get({}).then(function(result) {
    return [result, users.findOneAsync({
      login: result.login
    })];
  }).spread(function(gh, model) {
    if (model) {
      return [model.login, users.updateByIdAsync(model._id, {
        $set: {
          token: token
        }
      })];
    }

    var insert = {
      login: gh.login,
      token: token,
      avatar: gh.avatar_url
    };
    if (ref) {
      insert.ref = ref;
    }
    return [gh.login, users.insertAsync(insert)];
  }).spread(function(login) {
    return users.findOneAsync({
      login: login
    });
  }).then(function(model) {
    return fromModel(model);
  });
}

/**
 * Makes a user object from their DB model. Does not return a promise.
 */
function fromModel(model) {
  return new User(model.login, newGithub(model.token), model);
}

module.exports = {
  fromLogin: fromLogin,
  fromToken: fromToken,
  fromModel: fromModel,
  User: User
};
<commit_msg>Use eachLimit rather than eachSeries<commit_after>var P = require('bluebird');
var async = require('async');
var config = require('../config');
var GitHubApi = require('github');
var users = require('./db').get('users');
var request = require('superagent');
P.promisifyAll(users);

function newGithub(token) {
  var api = new GitHubApi({
    version: '3.0.0'
  });
  api.authenticate({
    type: 'oauth',
    token: token
  });
  return api;
};

/**
 * Constructor for our user class
 */
function User(login, github, model) {
  this.login = login;
  this.github = github;
  this.model = model;
}

/**
 * Checks if the user is following the given username.
 */
User.prototype.isFollowing = function(other) {
  var getFollowUser = P.promisify(this.github.user.getFollowUser, this.github);
  return new P(function(resolve) {
    getFollowUser({
      user: other
    }).then(function(res) {
      return resolve(true);
    }, function(res) {
      return resolve(false);
    });
  });
};

/**
 * Follow a user.
 *
 * @param other String login username
 */
User.prototype.follow = function(other) {
  return this.isFollowing(other).bind(this).then(function(following) {
    if (following) return false;
    var followUser = P.promisify(this.github.user.followUser, this.github.user);
    return followUser({
      user: other
    });
  }).then(function(res) {
    if (!res) return false;
    return users.updateAsync({
      login: other
    }, {
      $addToSet: {
        followedBy: this.login
      }
    });
  });
};

/**
 * Make a user unfollow someone. Both params must be GH API instances.
 *
 * @param user GH API instance
 * @param other String login username
 */
User.prototype.unfollow = function(other) {
  return this.isFollowing(other).bind(this).then(function(following) {
    if (!following) return false;
    var unfollowUser = P.promisify(this.github.user.unFollowUser, this.github.user);
    return unfollowUser({
      user: other
    });
  }).then(function(res) {
    if (!res) return false;
    return users.updateAsync({
      login: other
    }, {
      $pull: {
        followedBy: this.login
      }
    });
  });
};

/**
 * Stars a repo.
 */
User.prototype.star = function(user, repo) {
  var star = P.promisify(this.github.repos.star, this.github.repos);
  return star({
    user: user,
    repo: repo
  });
};

/**
 * Adds an amount of followers to a user.
 *
 * @param amount - Use '-1' to add as many as possible. It will add as many followers as possible.
 */
User.prototype.addFollowers = function(amount) {
  var thiz = this;
  return users.findAsync({
    login: {
      $ne: this.login
    }
  }).bind(this).then(function(docs) {

    return new P(function(resolve) {
      var sum = 0;
      async.eachLimit(docs, 10, function(otherModel, done) {
        // Ignore if same as user
        if (amount !== -1 && sum >= amount) return done();

        // Otherwise follow
        var otherUser = fromModel(otherModel);
        return otherUser.follow(thiz.login).then(function(res) {
          if (res) sum++;
          done();
        }, function() {
          // OAuth token no longer valid
          thiz.invalidate().then(done, done);
        });

      }.bind(this), function() {
        resolve(sum);
      });

    }).bind(this);

  }).then(function(sum) {
    return {
      follows: sum,
      amount: amount
    };
  });

};

/**
 * Removes all of a user's followers.
 */
User.prototype.removeFollowers = function() {
  var login = this.login;
  return P.settle((this.model.followedBy || []).map(function(user) {

    return fromLogin(user).then(function(u) {
      return u.unfollow(login);
    });

  })).then(function(results) {

    var unfollowed = results.filter(function(r) {
      return r.isFulfilled();
    }).length;
    var invalid = results.filter(function(r) {
      return r.isRejected();
    }).length;

    return {
      unfollowed: unfollowed,
      invalid: invalid
    };

  });
};

/**
 * Checks the amount of followers this user is supposed to have.
 * @param login The login of the user (username)
 * @param cb(err, privilege)
 */
User.prototype.checkPrivilege = function() {
  var count = config.baseFollowers;
  return users.countAsync({
    ref: this.login
  }).bind(this).then(function(ct) {
    count += ct * config.referralBonus;
    if (this.model.god) {
      count = -1;
    }
    return {
      count: count,
      referrals: ct
    };
  });
};

/**
 * Gets all of the users that the user hasn't been followed by.
 * This is a pretty expensive operation as it makes a lot of GH api requests.
 */
User.prototype.findNotFollowedBy = function() {
  var thiz = this;

  // Get followers of user
  var followedBy = this.model.followedBy || [];

  // Find users where their login isn't one of those followedBy
  return users.findAsync({
    login: {
      $nin: followedBy,
      $ne: this.login
    }
  }).then(function(dbNotFollowing) {
    return new P(function(resolve, reject) {
      if (!dbNotFollowing) return;

      // These are the users that are not following in the DB.
      var notFollowing = [];
      async.each(dbNotFollowing, function(userObj, done) {
        if (this.login === userObj.login) return done(); // Skip if same user

        var other = fromModel(userObj);
        other.isFollowing(thiz.login).then(function(res) {
          if (!res) {
            notFollowing.push(other.login);
          }
          done();
        });

      }, function(err) {
        if (err) return reject(err);
        resolve(notFollowing);
      });
    });
  });
};

/**
 * Get users that this user is following
 */
User.prototype.getFollowing = function() {
  return users.findAsync({
    followedBy: this.login
  });
};

/**
 * Returns a summary of a user
 */
User.prototype.summary = function() {
  return P.props({
    privilege: this.checkPrivilege(),
    following: this.getFollowing(),
    notFollowedBy: this.findNotFollowedBy()
  }).bind(this).then(function(res) {

    var followerCt = this.model.followedBy ? this.model.followedBy.length : 0;
    var remaining = res.privilege.count - followerCt;
    if (res.privilege.count === -1) {
      remaining = 99999999;
    }
    var amount = Math.min(res.notFollowedBy.length, remaining);
    if (amount < 0) amount = 0;

    return {
      privilege: res.privilege,
      following: res.following,
      amount: amount,
      remaining: remaining,
      followerCt: followerCt,
      user: this.model
    };

  });
};

/**
 * Updates this user's followers in the database.
 */
User.prototype.updateFollowedBy = function() {
  var me = this.login;
  var remove = [];
  return P.map(this.model.followedBy, function(login) {
    return fromLogin(login).then(function(user) {
      if (!user) {
        remove.push(login);
        throw 'done';
      }
      return [user, user.isFollowing(me)];
    }).spread(function(user, res) {
      if (!res) {
        remove.push(user.login);
        throw 'done';
      }
      return user;
    }).then(function(user) {
      return [user, user.validate()];
    }).spread(function(user, valid) {
      if (!valid) {
        remove.push(user.login);
      }
    }).catch(function(err) {
      if (err === 'done') return;
      console.error(err.stack);
    });
  }).then(function() {
    return users.updateAsync({
      login: me
    }, {
      $pullAll: {
        followedBy: remove
      }
    });
  }).then(function() {
    return remove;
  });
};

/**
 * Checks if a user's oauth token is valid.
 */
User.prototype.validate = function() {
  var get = P.promisify(this.github.user.get, this.github.user);
  return new P(function(resolve) {
    get({}).then(function(user) {
      request.get('https://github.com/' + user.login).end(function(res) {
        return resolve(!res.notFound);
      });
    }, function() {
      return resolve(false);
    });
  });
};

/**
 * Invalidates this user by removing their followers and lowering their cap.
 */
User.prototype.invalidate = function() {
  return this.getFollowing().then(function(users) {
    return P.map(users, function(model) {
      return fromModel(model).updateFollowedBy();
    });
  }).then(function() {
    return this.removeFollowers();
    // TODO reduce cap
  }).bind(this);
};

/**
 * Makes a user object from their login. Assumes it's already in the db.
 */
function fromLogin(login) {
  return users.findOneAsync({
    login: login
  }).then(function(model) {
    return new P(function(resolve) {
      if (!model) return resolve(null);
      return resolve(fromModel(model));
    });
  });
}

/**
 * Makes a (possibly new) user object from their token.
 */
function fromToken(token, ref) {
  var github = newGithub(token);
  var get = P.promisify(github.user.get, github.user);
  return get({}).then(function(result) {
    return [result, users.findOneAsync({
      login: result.login
    })];
  }).spread(function(gh, model) {
    if (model) {
      return [model.login, users.updateByIdAsync(model._id, {
        $set: {
          token: token
        }
      })];
    }

    var insert = {
      login: gh.login,
      token: token,
      avatar: gh.avatar_url
    };
    if (ref) {
      insert.ref = ref;
    }
    return [gh.login, users.insertAsync(insert)];
  }).spread(function(login) {
    return users.findOneAsync({
      login: login
    });
  }).then(function(model) {
    return fromModel(model);
  });
}

/**
 * Makes a user object from their DB model. Does not return a promise.
 */
function fromModel(model) {
  return new User(model.login, newGithub(model.token), model);
}

module.exports = {
  fromLogin: fromLogin,
  fromToken: fromToken,
  fromModel: fromModel,
  User: User
};
<|endoftext|>


<commit_before>'use strict';
/**
 * Created by jsturtevant on 1/18/14.
 */
var options = {};

chrome.runtime.sendMessage({method: "getOptions"}, function(response) {
    options = response.options;

    Mousetrap.bind(options.shortcutKey, function(e) {
        var textboxes = SelectElements();
        ExpandAllOccurrances(textboxes);
        return false;
    });
});

function ExpandAllOccurrances(elements) {
    elements.forEach(function (textbox) {
        var string = textbox.value;

        if (string.match(options.shortcuts[0].key)) {
            textbox.value = string.replace(options.shortcuts[0].key, options.shortcuts[0].value);
        }
    });
}

function SelectElements() {
    var elementList = document.querySelectorAll('input[type=text], textarea');
    var textboxes = Array.prototype.slice.call(elementList, 0);
    return textboxes;
}

Mousetrap.stopCallback =function(e, element, combo) {
    // if the element has the class "mousetrap" then no need to stop
    if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
        return false;
    }

    // stop for input, select, and textarea
    return  element.tagName == 'SELECT' || (element.contentEditable && element.contentEditable == 'true');
}

<commit_msg>now expands all keywords<commit_after>'use strict';
/**
 * Created by jsturtevant on 1/18/14.
 */
var options = {};

chrome.runtime.sendMessage({method: "getOptions"}, function(response) {
    options = response.options;

    Mousetrap.bind(options.shortcutKey, function(e) {
        var textboxes = selectElements();
        expandAlShortCutOccurrences(textboxes);
        return false;
    });
});

function expandAlShortCutOccurrences(elements) {
    elements.forEach(function (element) {
        replaceAllShortcuts(element);
    });
};

function replaceAllShortcuts(textbox) {
    options.shortcuts.forEach(function (shortcut) {
        var string = textbox.value;
        textbox.value = string.replace(shortcut.key, shortcut.value);
    });
};

function selectElements() {
    var elementList = document.querySelectorAll('input[type=text], textarea');
    var textboxes = Array.prototype.slice.call(elementList, 0);
    return textboxes;
};

Mousetrap.stopCallback =function(e, element, combo) {
    // if the element has the class "mousetrap" then no need to stop
    if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
        return false;
    }

    // stop for input, select, and textarea
    return  element.tagName == 'SELECT' || (element.contentEditable && element.contentEditable == 'true');
};

<|endoftext|>


<commit_before>'use strict';

/* global -Promise */
var Promise = require('bluebird');

var _ = require('underscore');
var glob = Promise.promisify(require('glob'));

var getFileList = function getFileList (files) {
  if (_.isString(files)) {
    return glob(files);
  } else if (_.isArray(files)) {
    var fileSubLists = _.map(files, function (fileSubList) {
      return glob(fileSubList);
    });

    return Promise.reduce(fileSubLists, function(fileList, subList) {
      return fileList.concat(subList);
    }, []);
  }

  throw new Error('You can only provide a String or an Array of filenames');
};

var getSupportedBrowserList = function getUnsupportedBrowserList (browsers) {
  return {
    ie: ['6']
  };
};

module.exports = {
  getFileList: getFileList,
  getSupportedBrowserList: getSupportedBrowserList
};
<commit_msg>Correct typo in util function name and dd more test info to supportedBrowsers return data<commit_after>'use strict';

/* global -Promise */
var Promise = require('bluebird');

var _ = require('underscore');
var glob = Promise.promisify(require('glob'));

var getFileList = function getFileList (files) {
  if (_.isString(files)) {
    return glob(files);
  } else if (_.isArray(files)) {
    var fileSubLists = _.map(files, function (fileSubList) {
      return glob(fileSubList);
    });

    return Promise.reduce(fileSubLists, function(fileList, subList) {
      return fileList.concat(subList);
    }, []);
  }

  throw new Error('You can only provide a String or an Array of filenames');
};

var getSupportedBrowserList = function getSupportedBrowserList (browsers) {
  return {
    'ie'      : ['6', '7', '8', '9', '10', '11']
    'firefox' : ['4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18',
                 '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', '32',
                 '33', '34']
  };
};

module.exports = {
  getFileList: getFileList,
  getSupportedBrowserList: getSupportedBrowserList
};
<|endoftext|>


<commit_before>jQuery(function () {
	var SUGGESTIONS_TIMEOUT = 7000;

	//auto-completion module
	var langTools = ace.require("ace/ext/language_tools");

	//initial editor configuration
	editor = ace.edit("txt_sparql_query");
	editor.setTheme("ace/theme/monokai");
	editor.setShowPrintMargin(false);
	editor.getSession().setUseWrapMode(true);
	editor.$blockScrolling = Infinity;

	var SparqlMode = require("ace/mode/sparql").Mode;
	editor.getSession().setMode(new SparqlMode());

	ace.config.loadModule("ace/ext/language_tools", function() {
		editor.setOptions({
			enableSnippets: true,
			enableBasicAutocompletion: true
		});
	});

	//add autocomplete for prefixes
	var SparQLCompleter = {
		getCompletions: function(editor, session, pos, prefix, callback) {
			var token = editor.session.getTokenAt(pos.row, pos.column - prefix.length - 1);  //get previous token

			if (!token) {
				return;
			}

			var editor_selector = '.ace_content';
			$(editor_selector).css('cursor', 'wait');
			setTimeout(function() {$(editor_selector).css('cursor', 'inherit')}, SUGGESTIONS_TIMEOUT);
			var in_vocabulary = '';
			var vocab_prefix = '';
			if (token.type == "sparql.prefix.constant.language") { //search classes or properties inside a vocabulary
				vocab_prefix = token.value;
				in_vocabulary += '&prefix=' + token.value.split(":")[0];
				token = editor.session.getTokenAt(pos.row, token.start - 1); //get the previous token
			}

			var val = token.value.toLowerCase()
			if (val == "prefix") { //suggest vocabularies
				$.ajax({
					dataType: "json",
					url: "/coreapi/recommend/?vocabulary=" + prefix,
					timeout: SUGGESTIONS_TIMEOUT,
					success: function(vocabularyList) {
						callback(null, vocabularyList.map(function(v) {
							$(editor_selector).css('cursor', 'inherit');
							return {caption: v.vocabulary, name: v.vocabulary, value: v.prefix + ": <" + v.uri + ">\n", score: v.ranking, meta: "Vocabulary", snippet: "this is the snippet"}
						}))
					}
				});
			}
			else if (val == "service") { //suggest services
				$.ajax({
					dataType: "json",
					url: "/api/datasources/",
					timeout: SUGGESTIONS_TIMEOUT,
					success: function(datasourceList) {
						callback(null, datasourceList.map(function(d) {
							var tp = "Public endpoint";
							if (!d.public) {
								tp = "Private RDF";
							}

							$(editor_selector).css('cursor', 'inherit');
							return {caption: d.title, name: d.title, value: "<" + d.endpoint + "> { ", score: 0, meta: tp}
						}))
					}
				});
			}
			else if ((val == "a") || (val == "type")) { //suggest classes
		        if (in_vocabulary == "") { //look inside the data source
					$.ajax({
						dataType: "json",
						url: "/query-designer/api/active_classes/" + $("#datasource-select").val() + '/?q=' + editor.session.getTokenAt(pos.row, pos.column).value,
						timeout: SUGGESTIONS_TIMEOUT,
						success: function(classList) {
							callback(null, classList.results.bindings.map(function(c) {
								var label = uri_to_label(c.Concept.value);

								$(editor_selector).css('cursor', 'inherit');
								return {caption: label, name: label, value: '<' + c.Concept.value + '> ', score: 1000-label.length, meta: $("#datasource-select option:selected").text()}
							}))
						}
                    });
                } else {
                    $.ajax({ //ask the vocabulary repository
						dataType: "json",
                        url: "/coreapi/recommend/?class=" + prefix + in_vocabulary,
                        timeout: SUGGESTIONS_TIMEOUT,
                        success: function(classList) {
                            callback(null, classList.map(function(c) {
                            	$(editor_selector).css('cursor', 'inherit');
                                return {caption: c.label, name: c.label, value: c.uri + ' ', score: c.ranking, meta: c.vocabulary}
							}))
						}
					});
                }
			} else if ((token.type == "sparql.variable") || (token.type == "sparql.constant.uri")) { //suggest properties
			    if (in_vocabulary == "") { //look inside the data source
                    $.ajax({
						dataType: "json",
                        url: "/query-designer/api/active_properties/" + $("#datasource-select").val() + '/?q=' + editor.session.getTokenAt(pos.row, pos.column).value,
                    	timeout: SUGGESTIONS_TIMEOUT,
                    	success: function(propertyList) {
							callback(null, propertyList.results.bindings.map(function(c) {
								var label = uri_to_label(c.property.value);
								$(editor_selector).css('cursor', 'inherit');
								return {caption: label, name: label, value: '<' + c.property.value + '> ', score: 1000-label.length, meta: $("#datasource-select option:selected").text()}
							}))
						}
					});
			    } else { //ask the vocabulary repository
                    $.ajax({
						dataType: "json",
                        url: "/coreapi/recommend/?property=" + prefix + in_vocabulary,
                    	timeout: SUGGESTIONS_TIMEOUT,
                    	success: function(propertyList) {
                        	callback(null, propertyList.map(function(p) {
								$(editor_selector).css('cursor', 'inherit');
								return {caption: p.label, name: p.label, value: p.uri + ' ', score: p.ranking, meta: p.vocabulary}
							}))
						}
					});
				}
			} else if (in_vocabulary != "") {
            	$.ajax({
					dataType: "json",
                    url: "/coreapi/recommend/?class_property=" + prefix + in_vocabulary,
					timeout: SUGGESTIONS_TIMEOUT,
					success: function(propertyList) {
						callback(null, propertyList.map(function(p) {
							$(editor_selector).css('cursor', 'inherit');
							return {caption: p.label, name: p.label, value: p.uri + ' ', score: p.ranking, meta: p.vocabulary}
						}))
					}
				});
			}
		}
	}
	langTools.addCompleter(SparQLCompleter);

	/*F9 to run query*/
	$("body").on('keyup', function(e) {
    	if (e.keyCode == 120) { //F9 key pressed
        	execute_sparql_query();

			e.preventDefault();
			e.stopPropagation();
        }
    });

    /*Ctrl+S to save query*/
	document.addEventListener("keydown", function(e) {
    	if (e.keyCode == 83 && e.ctrlKey) { //Ctrl+S key pressed
        	if (typeof(builder_workbench) === "undefined") {
        		save_query();
        	} else {
        		save_design();
        	}

			e.preventDefault();
			e.stopPropagation();
        }
    }, false);

    /*Check if autocomplete selection has changed*/
    setInterval(function() {
    	var prev = editor.autocomplete_selection;
    	var res = $('.ace_autocomplete .ace_text-layer .ace_line.ace_selected');
    	editor.autocomplete_selection = undefined;
    	if (res.length > 0) {
    		if (editor.completer.completions) {
    			editor.autocomplete_selection_index = $(res[0]).index();
    			editor.autocomplete_selection = editor.completer.completions.filtered[editor.autocomplete_selection_index];
			}
		}

		if (prev != editor.autocomplete_selection) { //selection changed
			$('.autocomplete-tooltip').remove(); //remove prev tooltip
			if (editor.autocomplete_selection) {
				var a = editor.autocomplete_selection;

				$('body').append('

' + a.name + '

');
				$('.autocomplete-tooltip').css('left', $('.ace_autocomplete').offset().left + $('.ace_autocomplete').width() + 5);
				$('.autocomplete-tooltip').css('top', $('.ace_autocomplete').offset().top + editor.autocomplete_selection_index*16);

				var info = "No info found";
				var callback = function(data) {
					info = data;
					$('.autocomplete-tooltip span.loading').remove();
					console.log(info);
					$('.autocomplete-tooltip').append(info);

					//apply formatting
					var pre_list = $('.autocomplete-tooltip pre');
					for (var i=0; i<pre_list.length; i++) {
						var text = $(pre_list[i]).text();
						$(pre_list[i]).text(decodeURIComponent(text));
					}
				};

				if (a.meta == "SPARQL Core") { //core
					info = $.ajax({url: '/query-designer/docs/sparql/' + a.value + '/'}).then(callback);
				}
			}
		}
    }, 1000);
});

<commit_msg>Fixed snippets<commit_after>jQuery(function () {
	var SUGGESTIONS_TIMEOUT = 7000;

	//auto-completion module
	var langTools = ace.require("ace/ext/language_tools");

	//initial editor configuration
	editor = ace.edit("txt_sparql_query");
	editor.setTheme("ace/theme/monokai");
	editor.setShowPrintMargin(false);
	editor.getSession().setUseWrapMode(true);
	editor.$blockScrolling = Infinity;

	var SparqlMode = require("ace/mode/sparql").Mode;
	editor.getSession().setMode(new SparqlMode());

	ace.config.loadModule("ace/ext/language_tools", function() {
		editor.setOptions({
			enableSnippets: true,
			enableBasicAutocompletion: true
		});
	});

	//add autocomplete for prefixes
	var SparQLCompleter = {
		getCompletions: function(editor, session, pos, prefix, callback) {
			var token = editor.session.getTokenAt(pos.row, pos.column - prefix.length - 1);  //get previous token

			if (!token) {
				return;
			}

			var editor_selector = '.ace_content';
			$(editor_selector).css('cursor', 'wait');
			setTimeout(function() {$(editor_selector).css('cursor', 'inherit')}, SUGGESTIONS_TIMEOUT);
			var in_vocabulary = '';
			var vocab_prefix = '';
			if (token.type == "sparql.prefix.constant.language") { //search classes or properties inside a vocabulary
				vocab_prefix = token.value;
				in_vocabulary += '&prefix=' + token.value.split(":")[0];
				token = editor.session.getTokenAt(pos.row, token.start - 1); //get the previous token
			}

			var val = token.value.toLowerCase()
			if (val == "prefix") { //suggest vocabularies
				$.ajax({
					dataType: "json",
					url: "/coreapi/recommend/?vocabulary=" + prefix,
					timeout: SUGGESTIONS_TIMEOUT,
					success: function(vocabularyList) {
						callback(null, vocabularyList.map(function(v) {
							$(editor_selector).css('cursor', 'inherit');
							return {caption: v.vocabulary, name: v.vocabulary, value: v.prefix + ": <" + v.uri + ">\n", score: v.ranking, meta: "Vocabulary"}
						}))
					}
				});
			}
			else if (val == "service") { //suggest services
				$.ajax({
					dataType: "json",
					url: "/api/datasources/",
					timeout: SUGGESTIONS_TIMEOUT,
					success: function(datasourceList) {
						callback(null, datasourceList.map(function(d) {
							var tp = "Public endpoint";
							if (!d.public) {
								tp = "Private RDF";
							}

							$(editor_selector).css('cursor', 'inherit');
							return {caption: d.title, name: d.title, value: "<" + d.endpoint + "> { ", score: 0, meta: tp}
						}))
					}
				});
			}
			else if ((val == "a") || (val == "type")) { //suggest classes
		        if (in_vocabulary == "") { //look inside the data source
					$.ajax({
						dataType: "json",
						url: "/query-designer/api/active_classes/" + $("#datasource-select").val() + '/?q=' + editor.session.getTokenAt(pos.row, pos.column).value,
						timeout: SUGGESTIONS_TIMEOUT,
						success: function(classList) {
							callback(null, classList.results.bindings.map(function(c) {
								var label = uri_to_label(c.Concept.value);

								$(editor_selector).css('cursor', 'inherit');
								return {caption: label, name: label, value: '<' + c.Concept.value + '> ', score: 1000-label.length, meta: $("#datasource-select option:selected").text()}
							}))
						}
                    });
                } else {
                    $.ajax({ //ask the vocabulary repository
						dataType: "json",
                        url: "/coreapi/recommend/?class=" + prefix + in_vocabulary,
                        timeout: SUGGESTIONS_TIMEOUT,
                        success: function(classList) {
                            callback(null, classList.map(function(c) {
                            	$(editor_selector).css('cursor', 'inherit');
                                return {caption: c.label, name: c.label, value: c.uri + ' ', score: c.ranking, meta: c.vocabulary}
							}))
						}
					});
                }
			} else if ((token.type == "sparql.variable") || (token.type == "sparql.constant.uri")) { //suggest properties
			    if (in_vocabulary == "") { //look inside the data source
                    $.ajax({
						dataType: "json",
                        url: "/query-designer/api/active_properties/" + $("#datasource-select").val() + '/?q=' + editor.session.getTokenAt(pos.row, pos.column).value,
                    	timeout: SUGGESTIONS_TIMEOUT,
                    	success: function(propertyList) {
							callback(null, propertyList.results.bindings.map(function(c) {
								var label = uri_to_label(c.property.value);
								$(editor_selector).css('cursor', 'inherit');
								return {caption: label, name: label, value: '<' + c.property.value + '> ', score: 1000-label.length, meta: $("#datasource-select option:selected").text()}
							}))
						}
					});
			    } else { //ask the vocabulary repository
                    $.ajax({
						dataType: "json",
                        url: "/coreapi/recommend/?property=" + prefix + in_vocabulary,
                    	timeout: SUGGESTIONS_TIMEOUT,
                    	success: function(propertyList) {
                        	callback(null, propertyList.map(function(p) {
								$(editor_selector).css('cursor', 'inherit');
								return {caption: p.label, name: p.label, value: p.uri + ' ', score: p.ranking, meta: p.vocabulary}
							}))
						}
					});
				}
			} else if (in_vocabulary != "") {
            	$.ajax({
					dataType: "json",
                    url: "/coreapi/recommend/?class_property=" + prefix + in_vocabulary,
					timeout: SUGGESTIONS_TIMEOUT,
					success: function(propertyList) {
						callback(null, propertyList.map(function(p) {
							$(editor_selector).css('cursor', 'inherit');
							return {caption: p.label, name: p.label, value: p.uri + ' ', score: p.ranking, meta: p.vocabulary}
						}))
					}
				});
			}
		}
	}
	langTools.addCompleter(SparQLCompleter);

	/*F9 to run query*/
	$("body").on('keyup', function(e) {
    	if (e.keyCode == 120) { //F9 key pressed
        	execute_sparql_query();

			e.preventDefault();
			e.stopPropagation();
        }
    });

    /*Ctrl+S to save query*/
	document.addEventListener("keydown", function(e) {
    	if (e.keyCode == 83 && e.ctrlKey) { //Ctrl+S key pressed
        	if (typeof(builder_workbench) === "undefined") {
        		save_query();
        	} else {
        		save_design();
        	}

			e.preventDefault();
			e.stopPropagation();
        }
    }, false);

    /*Check if autocomplete selection has changed*/
    setInterval(function() {
    	var prev = editor.autocomplete_selection;
    	var res = $('.ace_autocomplete .ace_text-layer .ace_line.ace_selected');
    	editor.autocomplete_selection = undefined;
    	if (res.length > 0) {
    		if (editor.completer.completions) {
    			editor.autocomplete_selection_index = $(res[0]).index();
    			editor.autocomplete_selection = editor.completer.completions.filtered[editor.autocomplete_selection_index];
			}
		}

		if (prev != editor.autocomplete_selection) { //selection changed
			$('.autocomplete-tooltip').remove(); //remove prev tooltip
			if (editor.autocomplete_selection) {
				var a = editor.autocomplete_selection;

				$('body').append('

' + a.name + '

');
				$('.autocomplete-tooltip').css('left', $('.ace_autocomplete').offset().left + $('.ace_autocomplete').width() + 5);
				$('.autocomplete-tooltip').css('top', $('.ace_autocomplete').offset().top + editor.autocomplete_selection_index*16);

				var info = "No info found";
				var callback = function(data) {
					info = data;
					$('.autocomplete-tooltip span.loading').remove();
					$('.autocomplete-tooltip').append(info);

					//apply formatting
					var pre_list = $('.autocomplete-tooltip pre');
					for (var i=0; i<pre_list.length; i++) {
						var text = $(pre_list[i]).text();
						$(pre_list[i]).text(decodeURIComponent(text));
					}
				};

				if (a.meta == "SPARQL Core") { //core
					info = $.ajax({url: '/query-designer/docs/sparql/' + a.value + '/'}).then(callback);
				}
			}
		}
    }, 1000);
});

<|endoftext|>


<commit_before>/*
 * `Snake` group
 * =============
 *
 * Handles the logic and appearance of the snake in the maze.
 */

import {WIDTH, HEIGHT} from '../constants/grid';
import {UP, DOWN, LEFT, RIGHT} from '../constants/directions';

export default class Snake {
  constructor(state, x, y) {
    this.headPosition = new Phaser.Geom.Point(x, y);

    this.body = state.add.group();

    this.head = this.body.create(x * 16, y * 16, 'body');
    this.head.setOrigin(0);

    this.alive = true;
    this.moveTime = 0;
    this.moveDelay = 100;
    this.tailPosition = new Phaser.Geom.Point(x, y);

    this.heading = RIGHT;
    this.direction = RIGHT;
  }

  update(time) {
    if (time >= this.moveTime) {
      return this.move(time);
    }
  }

  faceLeft() {
    if (this.direction === UP || this.direction === DOWN) {
      this.heading = LEFT;
    }
  }

  faceRight() {
    if (this.direction === UP || this.direction === DOWN) {
      this.heading = RIGHT;
    }
  }

  faceUp() {
    if (this.direction === LEFT || this.direction === RIGHT) {
      this.heading = UP;
    }
  }

  faceDown() {
    if (this.direction === LEFT || this.direction === RIGHT) {
      this.heading = DOWN;
    }
  }

  move(time) {
    //  Based on the heading property (which is the arrow direction pressed) we
    //  update `headPosition` value accordingly.
    //
    //  The `Math.Wrap` call allow the snake to wrap around the screen, so when
    //  it goes off any of the sides it re-appears on the other.
    switch (this.heading) {
    case LEFT:
      this.headPosition.x = Phaser.Math.Wrap(this.headPosition.x - 1, 0, WIDTH);
      break;

    case RIGHT:
      this.headPosition.x = Phaser.Math.Wrap(this.headPosition.x + 1, 0, WIDTH);
      break;

    case UP:
      this.headPosition.y = Phaser.Math.Wrap(this.headPosition.y - 1, 0, HEIGHT);
      break;

    case DOWN:
      this.headPosition.y = Phaser.Math.Wrap(this.headPosition.y + 1, 0, HEIGHT);
      break;

    //  No default
    }

    this.direction = this.heading;

    //  Update the body segments and place the last coordinate into
    //  `this.tailPosition`.
    this.body.shiftPosition(
      this.headPosition.x * 16,
      this.headPosition.y * 16,
      1,
      this.tailPosition
    );

    //  Check to see if any of the body pieces have the same x/y as the head.
    //  If they do, the head ran into the body.
    let hitBody = this.body.getFirst({x: this.head.x, y: this.head.y}, 1);

    if (hitBody) {
      //  Game Over!
      this.alive = false;
      return false;
    }

    //  Update the timer ready for the next movement.
    this.moveTime = time + this.moveDelay;
    return true;
  }

  grow() {
    const newPart = this.body.create(
      this.tailPosition.x,
      this.tailPosition.y,
      'body'
    );
    newPart.setOrigin(0);
  }

  collideWithFood(food) {
    if (this.head.x === food.x && this.head.y === food.y) {
      this.grow();
      food.eat();

      //  For every 5 items of food eaten we'll increase the snake speed a
      //  little.
      if (this.moveDelay > 20 && food.total % 5 === 0) {
        this.moveDelay -= 5;
      }
      return true;
    }
    return false;
  }

  updateGrid(grid) {
    //  Remove all body pieces from valid positions list.
    this.body.children.each(segment => {
      let bx = segment.x / 16;
      let by = segment.y / 16;

      grid[by][bx] = false;
    });
    return grid;
  }
}
<commit_msg>Fixed Snake entity crashing due to API changes.<commit_after>/*
 * `Snake` group
 * =============
 *
 * Handles the logic and appearance of the snake in the maze.
 */

import {WIDTH, HEIGHT} from '../constants/grid';
import {UP, DOWN, LEFT, RIGHT} from '../constants/directions';

export default class Snake {
  constructor(state, x, y) {
    this.headPosition = new Phaser.Geom.Point(x, y);

    this.body = state.add.group();

    this.head = this.body.create(x * 16, y * 16, 'body');
    this.head.setOrigin(0);

    this.alive = true;
    this.moveTime = 0;
    this.moveDelay = 100;
    this.tailPosition = new Phaser.Geom.Point(x, y);

    this.heading = RIGHT;
    this.direction = RIGHT;
  }

  update(time) {
    if (time >= this.moveTime) {
      return this.move(time);
    }
  }

  faceLeft() {
    if (this.direction === UP || this.direction === DOWN) {
      this.heading = LEFT;
    }
  }

  faceRight() {
    if (this.direction === UP || this.direction === DOWN) {
      this.heading = RIGHT;
    }
  }

  faceUp() {
    if (this.direction === LEFT || this.direction === RIGHT) {
      this.heading = UP;
    }
  }

  faceDown() {
    if (this.direction === LEFT || this.direction === RIGHT) {
      this.heading = DOWN;
    }
  }

  move(time) {
    //  Based on the heading property (which is the arrow direction pressed) we
    //  update `headPosition` value accordingly.
    //
    //  The `Math.Wrap` call allow the snake to wrap around the screen, so when
    //  it goes off any of the sides it re-appears on the other.
    switch (this.heading) {
    case LEFT:
      this.headPosition.x = Phaser.Math.Wrap(this.headPosition.x - 1, 0, WIDTH);
      break;

    case RIGHT:
      this.headPosition.x = Phaser.Math.Wrap(this.headPosition.x + 1, 0, WIDTH);
      break;

    case UP:
      this.headPosition.y = Phaser.Math.Wrap(this.headPosition.y - 1, 0, HEIGHT);
      break;

    case DOWN:
      this.headPosition.y = Phaser.Math.Wrap(this.headPosition.y + 1, 0, HEIGHT);
      break;

    //  No default
    }

    this.direction = this.heading;

    //  Update the body segments and place the last coordinate into
    //  `this.tailPosition`.
    Phaser.Actions.ShiftPosition(
      this.body.children.entries,
      this.headPosition.x * 16,
      this.headPosition.y * 16,
      1,
      this.tailPosition
    );

    //  Check to see if any of the body pieces have the same x/y as the head.
    //  If they do, the head ran into the body.
    let hitBody = Phaser.Actions.GetFirst(
      this.body.children.entries,
      {x: this.head.x, y: this.head.y},
      1
    );

    if (hitBody) {
      //  Game Over!
      this.alive = false;
      return false;
    }

    //  Update the timer ready for the next movement.
    this.moveTime = time + this.moveDelay;
    return true;
  }

  grow() {
    const newPart = this.body.create(
      this.tailPosition.x,
      this.tailPosition.y,
      'body'
    );
    newPart.setOrigin(0);
  }

  collideWithFood(food) {
    if (this.head.x === food.x && this.head.y === food.y) {
      this.grow();
      food.eat();

      //  For every 5 items of food eaten we'll increase the snake speed a
      //  little.
      if (this.moveDelay > 20 && food.total % 5 === 0) {
        this.moveDelay -= 5;
      }
      return true;
    }
    return false;
  }

  updateGrid(grid) {
    //  Remove all body pieces from valid positions list.
    this.body.children.each(segment => {
      let bx = segment.x / 16;
      let by = segment.y / 16;

      grid[by][bx] = false;
    });
    return grid;
  }
}
<|endoftext|>


<commit_before>'use strict';

exports.findCommonBase = function(files) {
  if (!files || files.length === 1) return '';
  var first = files[0];
  var prefixlen = first.length;
  files.forEach(function(file){
    for (var i = prefixlen; i > 0; i--) {
      if (file.substr(0,i) === first.substr(0,i)) {
        prefixlen = i;
        break;
      }
    }
  });
  return first.substr(0,prefixlen);
};

exports.formatJSON = function (report) {
  return JSON.stringify(report, function(k,v){
    if (k === 'identifiers') return ['__stripped__'];
    return v;
  },2);
};

<commit_msg>Fixed missing letter when no common path prefix.<commit_after>'use strict';

exports.findCommonBase = function(files) {
  if (!files || files.length === 1) return '';
  var first = files[0];
  var prefixlen = first.length;
  files.forEach(function(file){
    for (var i = prefixlen; i > 0; i--) {
      if (file.substr(0,i) === first.substr(0,i)) {
        prefixlen = i;
        return;
      }
    }
    prefixlen = 0;
  });
  return first.substr(0,prefixlen);
};

exports.formatJSON = function (report) {
  return JSON.stringify(report, function(k,v){
    if (k === 'identifiers') return ['__stripped__'];
    return v;
  },2);
};

<|endoftext|>


<commit_before>define([
	'backbone',
	'leaflet',
	'communicator',
	'underscore',
	'vendor/leaflet-providers'
],
function( Backbone, Leaflet, Communicator, _ ){
    'use strict';

	return Backbone.View.extend({

		id: 'map',

		onShow: function() {
			this.setupMap();
			this.registerEvents();
			this.placeRequestedMarkers();
		},

		locate: function() {
			this.map.locate();
		},

		mark: function() {
			this.map.once('moveend', this.setMarkerAtCurPos, this);
			this.map.locate();
		},

		setMarkerAtCurPos: function(e) {
			var curPos = this.map.getCenter();
			new Leaflet.Marker(curPos).addTo(this.map);
		},

		onLocationFound: function(e) {
			// animate to that location at closest zoom level
			this.map.setView(e.latlng, Infinity, {animate: true});
		},

		onLocationError: function(e) {
			alert(e.message);
			console.log(e.message, e.code);
		},

		addSpots: function(spots) {
			this.geoJSON.addData(spots.toJSON());
		},

		// TODO make private
		setupMap: function() {
			var location = [50.13, 8.67]; // Frankfurt
			this.map = Leaflet.map('map');
			this.map.setView(location, 13);
			Leaflet.tileLayer.provider("Esri.WorldStreetMap").addTo(this.map);

			// add a GeoJSON layer
			this.geoJSON = Leaflet.geoJson().addTo(this.map);
		},

		registerEvents: function() {
			this.map.on('locationfound', this.onLocationFound, this);
			this.map.on('locationerror', this.onLocationError, this);
			Communicator.mediator.on("LOCATOR:CLICK", this.locate, this);
			Communicator.mediator.on("MARK_POSITION:CLICK", this.mark, this);
		},

		placeRequestedMarkers: function() {
			this.spotsRequest.done(_.bind(this.addSpots, this));
		}
	});
});
<commit_msg>Adding leaflet-providers to the argument list of the mapView module. Maybe it'll help loading leaflet-providers properly in the server env.<commit_after>define([
	'backbone',
	'leaflet',
	'communicator',
	'underscore',
	'vendor/leaflet-providers'
],
function( Backbone, Leaflet, Communicator, _, LeafletProviders ){
    'use strict';

	return Backbone.View.extend({

		id: 'map',

		onShow: function() {
			this.setupMap();
			this.registerEvents();
			this.placeRequestedMarkers();
		},

		locate: function() {
			this.map.locate();
		},

		mark: function() {
			this.map.once('moveend', this.setMarkerAtCurPos, this);
			this.map.locate();
		},

		setMarkerAtCurPos: function(e) {
			var curPos = this.map.getCenter();
			new Leaflet.Marker(curPos).addTo(this.map);
		},

		onLocationFound: function(e) {
			// animate to that location at closest zoom level
			this.map.setView(e.latlng, Infinity, {animate: true});
		},

		onLocationError: function(e) {
			alert(e.message);
			console.log(e.message, e.code);
		},

		addSpots: function(spots) {
			this.geoJSON.addData(spots.toJSON());
		},

		// TODO make private
		setupMap: function() {
			var location = [50.13, 8.67]; // Frankfurt
			this.map = Leaflet.map('map');
			this.map.setView(location, 13);
			Leaflet.tileLayer.provider("Esri.WorldStreetMap").addTo(this.map);

			// add a GeoJSON layer
			this.geoJSON = Leaflet.geoJson().addTo(this.map);
		},

		registerEvents: function() {
			this.map.on('locationfound', this.onLocationFound, this);
			this.map.on('locationerror', this.onLocationError, this);
			Communicator.mediator.on("LOCATOR:CLICK", this.locate, this);
			Communicator.mediator.on("MARK_POSITION:CLICK", this.mark, this);
		},

		placeRequestedMarkers: function() {
			this.spotsRequest.done(_.bind(this.addSpots, this));
		}
	});
});
<|endoftext|>


<commit_before>const ENTITIES = require('html-entities').AllHtmlEntities;
const URL = require('url');
const HTTPS = require('https');

const PLAYLIST_URL = 'https://www.youtube.com/playlist?list=';

// Parses the header information of a playlist
const AUTHOR_REFLINK_REGEXP = /

(.*?(?=<\/li>))<\/li>
(.*?)(?=<\/li>)<\/li>
(.*?(?=<\/li>))<\/li>
(.*?(?=<\/li>))<\/li>/; // eslint-disable-line max-len
const PLAYLIST_NAME_REGEXP = /
\n[\s]*(.*?(?=\n))\n[\s]+(<\/h1>|
 {
  const importantTxt = exports.between(body, 'branded-page-box clearfix', '
');
  const authorMatch = importantTxt.match(AUTHOR_REFLINK_REGEXP);
  const description = exports.removeHtml(exports.between(
    importantTxt,
    '',
    ''
  ).replace(/<button class="yt-uix-button[\s\S]+/, ''));

  return {
    id: plistId,
    url: PLAYLIST_URL + plistId,
    title: exports.removeHtml(importantTxt.match(PLAYLIST_NAME_REGEXP)[1]),
    visibility: importantTxt.includes('data-tooltip-text="') ? 'link only' : 'everyone',
    description: description ? exports.removeHtml(description) : null,
    total_items: parseInt(authorMatch[2]),
    views: Number(authorMatch[3].split(' ')[0].replace(/\.|,/g, '')),
    last_updated: authorMatch[4],
    author: {
      id: exports.between(importantTxt, 'data-all-playlists-url="/channel/', '/playlists"'),
      name: exports.removeHtml(exports.between(authorMatch[1], '>', '')),
      avatar: exports.between(body, '<img class="channel-header-profile-image" src="', '" title="'),
      user: importantTxt.includes('/user/') ? exports.between(authorMatch[1], 'href="/user/', '"') : null,
      channel_url: URL.resolve(
        PLAYLIST_URL,
        `/channel/${exports.between(importantTxt, 'data-all-playlists-url="/channel/', '/playlists"')}`
      ),
      user_url: importantTxt.includes('/user/') ?
        URL.resolve(PLAYLIST_URL, exports.between(authorMatch[1], 'href="', '"')) :
        null,
    },
    nextpage: null,
    items: [],
  };
};

// Splits out the video container
exports.getVideoContainers = body => body
  .substring(body.indexOf('<tr class="'), body.lastIndexOf('
'))
  .split('
 {
  const authorBox = exports.between(videoString, '
', '
');
  const authorMatch = authorBox.match(/]*>(.*)(?=<\/a>)/);
  return {
    id: exports.between(videoString, 'href="/watch?v=', '&'),
    url: URL.resolve(PLAYLIST_URL, exports.removeHtml(exports.between(videoString, 'href="', '"'))),
    url_simple: URL.resolve(PLAYLIST_URL, exports.between(videoString, 'href="', '&')),
    title: exports.removeHtml(exports.between(videoString, 'data-title="', '"')),
    thumbnail: URL.resolve(PLAYLIST_URL, exports.between(videoString, 'data-thumb="', '"').split('?')[0]),
    duration: videoString.includes('
') ?
      videoString.match(/(.*?(?=<\/span>))<\/span>/)[1] :
      null,
    author: {
      name: authorMatch ? exports.removeHtml(authorMatch[0]) : null,
      ref: authorMatch ? URL.resolve(PLAYLIST_URL, exports.between(authorBox, 'href="', '"')) : null,
    },
  };
};

// Parse the input to a id (or error)
const PLAYLIST_REGEX = /^(PL|UU)[a-zA-Z0-9-_]{22,32}$/;
const CHANNEL_REGEX = /^UC[a-zA-Z0-9-_]{22,32}$/;
exports.getPlaylistId = (link, callback) => {
  if (typeof link !== 'string') {
    return callback(new Error('The link has to be a string'));
  }
  if (PLAYLIST_REGEX.test(link)) {
    return callback(null, link);
  }
  const parsed = URL.parse(link, true);
  if (Object.hasOwnProperty.call(parsed.query, 'list')) {
    if (!PLAYLIST_REGEX.test(parsed.query.list)) {
      return callback(new Error('invalid list query in url'));
    }
    return callback(null, parsed.query.list);
  }
  const p = parsed.pathname.split('/');
  const maybeType = p[p.length - 2];
  const maybeId = p[p.length - 1];
  if (maybeType === 'channel') {
    if (CHANNEL_REGEX.test(maybeId)) {
      return callback(null, `UU${maybeId.substr(2)}`);
    } else {
      return callback(new Error(`Unable to find a id in ${link}`));
    }
  } else if (maybeType === 'user') {
    return exports.userToChannelUploadList(maybeId, callback);
  } else {
    return callback(new Error(`Unable to find a id in ${link}`));
  }
};

// Gets the channel uploads id of a user (needed for uploads playlist)
const CHANNEL_REGEXP = /channel_id=UC([\w-]{22,32})"/;
exports.userToChannelUploadList = (user, callback) => {
  let finished = false;
  const req = HTTPS.get(`https://www.youtube.com/user/${user}`, resp => {
    const responseString = [];
    resp.on('data', data => {
      if (finished) return;
      responseString.push(data);
      const channelMatch = data.toString().match(CHANNEL_REGEXP);
      if (!channelMatch) return;
      finished = true;
      resp.req.abort();
      callback(null, `UU${channelMatch[1]}`);
    });
    resp.on('end', () => {
      if (finished) return;
      const channelMatch = Buffer.concat(responseString).toString().match(CHANNEL_REGEXP);
      if (channelMatch) return callback(null, `UU${channelMatch[1]}`); // eslint-disable-line consistent-return
      callback(new Error(`unable to resolve the user: ${user}`)); // eslint-disable-line consistent-return
    });
  });
  req.on('error', err => {
    if (finished) return;
    finished = true;
    callback(new Error(`request failed with err: ${err.message}`));
  });
};

// Taken from https://github.com/fent/node-ytdl-core/
exports.between = (haystack, left, right) => {
  let pos;
  pos = haystack.indexOf(left);
  if (pos === -1) { return ''; }
  haystack = haystack.slice(pos + left.length);
  if (!right) { return haystack; }
  pos = haystack.indexOf(right);
  if (pos === -1) { return ''; }
  haystack = haystack.slice(0, pos);
  return haystack;
};

// Cleans up html text
exports.removeHtml = string => new ENTITIES().decode(
  string.replace(/\n/g, ' ')
    .replace(/\s*<\s*br\s*\/?\s*>\s*/gi, '\n')
    .replace(/<\s*\/\s*p\s*>\s*<\s*p[^>]*>/gi, '\n')
    .replace(/<.*?>/gi, '')
).trim();
<commit_msg>Allow playlist urls with 18 character long ids<commit_after>const ENTITIES = require('html-entities').AllHtmlEntities;
const URL = require('url');
const HTTPS = require('https');

const PLAYLIST_URL = 'https://www.youtube.com/playlist?list=';

// Parses the header information of a playlist
const AUTHOR_REFLINK_REGEXP = /

(.*?(?=<\/li>))<\/li>
(.*?)(?=<\/li>)<\/li>
(.*?(?=<\/li>))<\/li>
(.*?(?=<\/li>))<\/li>/; // eslint-disable-line max-len
const PLAYLIST_NAME_REGEXP = /
\n[\s]*(.*?(?=\n))\n[\s]+(<\/h1>|
 {
  const importantTxt = exports.between(body, 'branded-page-box clearfix', '
');
  const authorMatch = importantTxt.match(AUTHOR_REFLINK_REGEXP);
  const description = exports.removeHtml(exports.between(
    importantTxt,
    '',
    ''
  ).replace(/<button class="yt-uix-button[\s\S]+/, ''));

  return {
    id: plistId,
    url: PLAYLIST_URL + plistId,
    title: exports.removeHtml(importantTxt.match(PLAYLIST_NAME_REGEXP)[1]),
    visibility: importantTxt.includes('data-tooltip-text="') ? 'link only' : 'everyone',
    description: description ? exports.removeHtml(description) : null,
    total_items: parseInt(authorMatch[2]),
    views: Number(authorMatch[3].split(' ')[0].replace(/\.|,/g, '')),
    last_updated: authorMatch[4],
    author: {
      id: exports.between(importantTxt, 'data-all-playlists-url="/channel/', '/playlists"'),
      name: exports.removeHtml(exports.between(authorMatch[1], '>', '')),
      avatar: exports.between(body, '<img class="channel-header-profile-image" src="', '" title="'),
      user: importantTxt.includes('/user/') ? exports.between(authorMatch[1], 'href="/user/', '"') : null,
      channel_url: URL.resolve(
        PLAYLIST_URL,
        `/channel/${exports.between(importantTxt, 'data-all-playlists-url="/channel/', '/playlists"')}`
      ),
      user_url: importantTxt.includes('/user/') ?
        URL.resolve(PLAYLIST_URL, exports.between(authorMatch[1], 'href="', '"')) :
        null,
    },
    nextpage: null,
    items: [],
  };
};

// Splits out the video container
exports.getVideoContainers = body => body
  .substring(body.indexOf('<tr class="'), body.lastIndexOf('
'))
  .split('
 {
  const authorBox = exports.between(videoString, '
', '
');
  const authorMatch = authorBox.match(/]*>(.*)(?=<\/a>)/);
  return {
    id: exports.between(videoString, 'href="/watch?v=', '&'),
    url: URL.resolve(PLAYLIST_URL, exports.removeHtml(exports.between(videoString, 'href="', '"'))),
    url_simple: URL.resolve(PLAYLIST_URL, exports.between(videoString, 'href="', '&')),
    title: exports.removeHtml(exports.between(videoString, 'data-title="', '"')),
    thumbnail: URL.resolve(PLAYLIST_URL, exports.between(videoString, 'data-thumb="', '"').split('?')[0]),
    duration: videoString.includes('
') ?
      videoString.match(/(.*?(?=<\/span>))<\/span>/)[1] :
      null,
    author: {
      name: authorMatch ? exports.removeHtml(authorMatch[0]) : null,
      ref: authorMatch ? URL.resolve(PLAYLIST_URL, exports.between(authorBox, 'href="', '"')) : null,
    },
  };
};

// Parse the input to a id (or error)
const PLAYLIST_REGEX = /^(PL|UU)[a-zA-Z0-9-_]{16,32}$/;
const CHANNEL_REGEX = /^UC[a-zA-Z0-9-_]{22,32}$/;
exports.getPlaylistId = (link, callback) => {
  if (typeof link !== 'string') {
    return callback(new Error('The link has to be a string'));
  }
  if (PLAYLIST_REGEX.test(link)) {
    return callback(null, link);
  }
  const parsed = URL.parse(link, true);
  if (Object.hasOwnProperty.call(parsed.query, 'list')) {
    if (!PLAYLIST_REGEX.test(parsed.query.list)) {
      return callback(new Error('invalid list query in url'));
    }
    return callback(null, parsed.query.list);
  }
  const p = parsed.pathname.split('/');
  const maybeType = p[p.length - 2];
  const maybeId = p[p.length - 1];
  if (maybeType === 'channel') {
    if (CHANNEL_REGEX.test(maybeId)) {
      return callback(null, `UU${maybeId.substr(2)}`);
    } else {
      return callback(new Error(`Unable to find a id in ${link}`));
    }
  } else if (maybeType === 'user') {
    return exports.userToChannelUploadList(maybeId, callback);
  } else {
    return callback(new Error(`Unable to find a id in ${link}`));
  }
};

// Gets the channel uploads id of a user (needed for uploads playlist)
const CHANNEL_REGEXP = /channel_id=UC([\w-]{22,32})"/;
exports.userToChannelUploadList = (user, callback) => {
  let finished = false;
  const req = HTTPS.get(`https://www.youtube.com/user/${user}`, resp => {
    const responseString = [];
    resp.on('data', data => {
      if (finished) return;
      responseString.push(data);
      const channelMatch = data.toString().match(CHANNEL_REGEXP);
      if (!channelMatch) return;
      finished = true;
      resp.req.abort();
      callback(null, `UU${channelMatch[1]}`);
    });
    resp.on('end', () => {
      if (finished) return;
      const channelMatch = Buffer.concat(responseString).toString().match(CHANNEL_REGEXP);
      if (channelMatch) return callback(null, `UU${channelMatch[1]}`); // eslint-disable-line consistent-return
      callback(new Error(`unable to resolve the user: ${user}`)); // eslint-disable-line consistent-return
    });
  });
  req.on('error', err => {
    if (finished) return;
    finished = true;
    callback(new Error(`request failed with err: ${err.message}`));
  });
};

// Taken from https://github.com/fent/node-ytdl-core/
exports.between = (haystack, left, right) => {
  let pos;
  pos = haystack.indexOf(left);
  if (pos === -1) { return ''; }
  haystack = haystack.slice(pos + left.length);
  if (!right) { return haystack; }
  pos = haystack.indexOf(right);
  if (pos === -1) { return ''; }
  haystack = haystack.slice(0, pos);
  return haystack;
};

// Cleans up html text
exports.removeHtml = string => new ENTITIES().decode(
  string.replace(/\n/g, ' ')
    .replace(/\s*<\s*br\s*\/?\s*>\s*/gi, '\n')
    .replace(/<\s*\/\s*p\s*>\s*<\s*p[^>]*>/gi, '\n')
    .replace(/<.*?>/gi, '')
).trim();
<|endoftext|>


<commit_before>'use strict';

/* Controllers */

/* global angular */
/* global moment */
/* jshint strict : true */
/* jshint undef : true */
/* jshint unused : true */
/* jshint globalstrict : true */

angular.module('myApp.controllers.nav', []).

	controller('NavigationCtrl', ['$scope', '$window', 'metadataService', function ( $scope, $window, metadataService ) {

		$scope.data = metadataService.data;

		$scope.yearFilter = metadataService.current;

		$scope.viewSelection = metadataService.current;

		$scope.filter = metadataService.current;

		$scope.changeYear = function() {

			metadataService.setParamYear($scope.yearFilter.year);

			$scope.changeView();
		};

		$scope.changeCircuitFilter = function() {

			metadataService.setParamFilter('circuit', $scope.filter.filter.circuit);

			$scope.changeView();
		};

		$scope.changeLocationFilter = function() {

			metadataService.setParamFilter('location', $scope.filter.filter.location);

			$scope.changeView();
		};

		$scope.changeView = function() {

			var interval;

			if ($scope.yearFilter.year == 'ALL') {

				interval = 'years';
			}
			else if (metadataService.data.interval == 'years') {

				metadataService.setInterval('months');

				interval = metadataService.data.interval;
			}
			else {

				interval = metadataService.data.interval;
			}

			var location = '#/' + interval + '/' + $scope.viewSelection.view;

			if ( $scope.viewSelection.view == 'monthly/usage' ) {
				// need to take into account usage screen with drilldown
				location = location + '/' + metadataService.data.circuit;
			}

			location = location + '?house=' + metadataService.data.houseId + '&date=' + metadataService.data.chartDate;

			$window.location = location;
		};

	}]);
<commit_msg>separate circuit and location filters - take 2<commit_after>'use strict';

/* Controllers */

/* global angular */
/* global moment */
/* jshint strict : true */
/* jshint undef : true */
/* jshint unused : true */
/* jshint globalstrict : true */

angular.module('myApp.controllers.nav', []).

	controller('NavigationCtrl', ['$scope', '$window', 'metadataService', function ( $scope, $window, metadataService ) {

		$scope.data = metadataService.data;

		$scope.yearFilter = metadataService.current;

		$scope.viewSelection = metadataService.current;

		$scope.filter = metadataService.current;

		$scope.changeYear = function() {

			metadataService.setParamYear($scope.yearFilter.year);

			$scope.changeView();
		};

		$scope.changeCircuitFilter = function() {

			metadataService.setParamFilter('circuit', $scope.filter.filter.circuit);

			$scope.changeView();
		};

		$scope.changeLocationFilter = function() {

			metadataService.setParamFilter('location', $scope.filter.filter.location);

			$scope.changeView();
		};

		$scope.changeView = function() {

			var interval;

			if ($scope.yearFilter.year == 'ALL') {

				interval = 'years';
			}
			else if (metadataService.data.interval == 'years') {

				metadataService.setInterval('months');

				interval = metadataService.data.interval;
			}
			else {

				interval = metadataService.data.interval;
			}

			var location = '#/' + interval + '/' + $scope.viewSelection.view;

			switch ( $scope.viewSelection.view ) {

				case 'summary' :
				case 'generation' :
				case 'basetemp' :
				case 'water' :

					// ...
					break;

				case 'usage' :

					if (metadataService.data.filter.circuit !== undefined) {

						location = location + '/' + metadataService.data.filter.circuit;
					}
					else {

						location = location + '/all'; // default
					}
					break;

				case 'temperature' :

					if (metadataService.data.filter.location !== undefined) {

						location = location + '/' + metadataService.data.filter.location;
					}
					else {

						location = location + '/outdoor'; // default
					}
					break;

			}

			location = location + '?house=' + metadataService.data.houseId + '&date=' + metadataService.data.chartDate;

			$window.location = location;
		};

	}]);
<|endoftext|>


<commit_before>var FormData     = require('form-data');

require('sugar');

var url          = require('url'),
    util         = require('util');

var VcapClient = module.exports = function (info, callback) {
    /**
     * VCAP cloudfoundry client.
     *
     * Parameters
     *
     * - info: object containing the following
     *   - host (required): host of CF deployment
     *   - protocol: http: vs https:
     *   - one of either:
     *     - token (access token)
     *     - email,password (two separate fields)
     */

    // ~~~~~ PRIVATE
    var self = this;

    if (! info.host) {
        return new TypeError('host must be provided');
    }

    var request = require('./request')(info.protocol || 'http:', info.host,
            info.token, info.email, info.password);

    // ~~~~~ PUBLIC

    var Collections = require('./collections');
    var collections = new Collections(request);

    this.apps = collections.create('apps', {
        name: { type: 'string' },
        space_guid: { type: 'string' },
    }, [ 'routes', 'summary', 'service_bindings' ]);

    this.services = collections.create('services', {
        label: { type: 'string' },
        url: { type: 'string' },
        provider: { type: 'string' },
        version: { type: 'string' },
        description: { type: 'string' },
    });

    this.services.getByName = function (name, callback) {
        this.getBy(function (each) {
            return each.entity.label === name;
        }, callback);
    };

    this.services.getPlanForServiceByName = function (name, callback) {
        var self = this;

        this.getByName(name, function (err, service) {
            if (err) {
                return callback(err);
            }

            self.get(service.metadata.guid).get('service_plans', callback);
        });
    };

    this.servicePlans = collections.create('service_plans', {
        name: { type: 'string' },
        free: { type: 'bool' },
        description: { type: 'string' },
    });

    this.serviceInstances = collections.create('service_instances', {
        name: { type: 'string' },
        space_guid: { type: 'string' },
        service_plan_guid: { type: 'string' },
    });

    this.orgs = this.organizations = collections.create('organizations', {
        name: { type: 'string' }
    });

    this.spaces =     collections.create('spaces', {
        name: { type: 'string' },
        organization_guid: { type: 'string' }
    });

    this.routes = collections.create('routes');
    this.domains = collections.create('domains');
    this.runtimes = collections.create('runtimes');
    this.frameworks = collections.create('frameworks');
    this.events = collections.create('events');

    // ~ applications

    this.apps.upload = function (guid, zipFile, callback) {
        var fileStream = typeof zipFile === 'string' ?
            require('fs').createReadStream(zipFile) :
            zipFile;

        var form = new FormData();
        form.append('application', fileStream,
                { contentType: 'application/zip' });

        form.append('resources', '[]');

        form.getLength(function (err, length) {
            if (err) {
                return callback(err);
            }

            var request = request({
//                 endpoint: getPath('apps', guid, 'bits'),
                method: 'PUT'
            }, callback);

            request.setHeader('Content-Length', length);
            request._form = form;
        });
    };

    this.apps.setRoute = function (appId, routeId, callback) {
        request({
//             endpoint: getPath('apps', appId, 'routes', routeId),
            method: 'PUT'
        }, callback);
    };

    this.apps.restart = function (id, callback) {
        var self = this;
        this.stop(id, function (err) {
            if (err) {
                return callback(err);
            }

            self.start(id, callback);
        });
    };

    this.apps.stop = function (id, callback) {
        self.apps.update(id, { state: 'STOPPED' }, callback);
    };

    this.apps.start = function (id, callback) {
        self.apps.update(id, { state: 'STARTED' }, callback);
    };
};
<commit_msg>setRoute now accomplished via apps.get(:id).routes.put(:routeId,         callback);<commit_after>var FormData     = require('form-data');

require('sugar');

var url          = require('url'),
    util         = require('util');

var VcapClient = module.exports = function (info, callback) {
    /**
     * VCAP cloudfoundry client.
     *
     * Parameters
     *
     * - info: object containing the following
     *   - host (required): host of CF deployment
     *   - protocol: http: vs https:
     *   - one of either:
     *     - token (access token)
     *     - email,password (two separate fields)
     */

    // ~~~~~ PRIVATE
    var self = this;

    if (! info.host) {
        return new TypeError('host must be provided');
    }

    var request = require('./request')(info.protocol || 'http:', info.host,
            info.token, info.email, info.password);

    // ~~~~~ PUBLIC

    var Collections = require('./collections');
    var collections = new Collections(request);

    this.apps = collections.create('apps', {
        name: { type: 'string' },
        space_guid: { type: 'string' },
    }, [ 'routes', 'summary', 'service_bindings' ]);

    this.services = collections.create('services', {
        label: { type: 'string' },
        url: { type: 'string' },
        provider: { type: 'string' },
        version: { type: 'string' },
        description: { type: 'string' },
    });

    this.services.getByName = function (name, callback) {
        this.getBy(function (each) {
            return each.entity.label === name;
        }, callback);
    };

    this.services.getPlanForServiceByName = function (name, callback) {
        var self = this;

        this.getByName(name, function (err, service) {
            if (err) {
                return callback(err);
            }

            self.get(service.metadata.guid).get('service_plans', callback);
        });
    };

    this.servicePlans = collections.create('service_plans', {
        name: { type: 'string' },
        free: { type: 'bool' },
        description: { type: 'string' },
    });

    this.serviceInstances = collections.create('service_instances', {
        name: { type: 'string' },
        space_guid: { type: 'string' },
        service_plan_guid: { type: 'string' },
    });

    this.orgs = this.organizations = collections.create('organizations', {
        name: { type: 'string' }
    });

    this.spaces =     collections.create('spaces', {
        name: { type: 'string' },
        organization_guid: { type: 'string' }
    });

    this.routes = collections.create('routes');
    this.domains = collections.create('domains');
    this.runtimes = collections.create('runtimes');
    this.frameworks = collections.create('frameworks');
    this.events = collections.create('events');

    // ~ applications

    this.apps.upload = function (guid, zipFile, callback) {
        var fileStream = typeof zipFile === 'string' ?
            require('fs').createReadStream(zipFile) :
            zipFile;

        var form = new FormData();
        form.append('application', fileStream,
                { contentType: 'application/zip' });

        form.append('resources', '[]');

        form.getLength(function (err, length) {
            if (err) {
                return callback(err);
            }

            var request = request({
//                 endpoint: getPath('apps', guid, 'bits'),
                method: 'PUT'
            }, callback);

            request.setHeader('Content-Length', length);
            request._form = form;
        });
    };

    this.apps.restart = function (id, callback) {
        var self = this;
        this.stop(id, function (err) {
            if (err) {
                return callback(err);
            }

            self.start(id, callback);
        });
    };

    this.apps.stop = function (id, callback) {
        self.apps.update(id, { state: 'STOPPED' }, callback);
    };

    this.apps.start = function (id, callback) {
        self.apps.update(id, { state: 'STARTED' }, callback);
    };
};
<|endoftext|>