<commit_before>//Declare Variables
var firebaseURL = "https://battleshipit.firebaseio.com/",
    fb          = new Firebase(firebaseURL),
    gamesURL    = new Firebase(firebaseURL + 'games/'),
    createGame,
    board       = [['', '', '', '', '', '', '', '', '', ''],
                   ['', '', '', '', '', '', '', '', '', ''],
                   ['', '', '', '', '', '', '', '', '', ''],
                   ['', '', '', '', '', '', '', '', '', ''],
                   ['', '', '', '', '', '', '', '', '', ''],
                   ['', '', '', '', '', '', '', '', '', ''],
                   ['', '', '', '', '', '', '', '', '', ''],
                   ['', '', '', '', '', '', '', '', '', ''],
                   ['', '', '', '', '', '', '', '', '', ''],
                   ['', '', '', '', '', '', '', '', '', '']],
    player1,
    player2     = 'United States',
    playerTurn,
    index,
    myDingy     = 'D',
    battleShip  = 'BS',
    carrier     = 'AC',
    Destroyer   = 'DE',
    Sub         = 'SU',
    shipVertical,
    clickTr;

// var currentGameRef = fb.child(uuid);

$('#submit-player').on('click', function() {
  event.preventDefault();
  var thisplayersname = $('.playerName').val(),
      uuid  = $('table').data('uuid');
    gamesURL.push({"player1board": board, "player2board": board, "player1name" : thisplayersname, "player2name": "", "player1turn" : "", "player2turn" : ""});
          createBoard(board);
    console.log("uuid");

});

$('#flipShipVertically').on('click', function() {
  shipVertical = 'true';
  console.log(shipVertical);
  $(this).css("background-color", "lime");
  $('#flipShipHorizontally').css("background-color", '');
})

$('#flipShipHorizontally').on('click', function() {
  shipVertical = 'false';
  console.log(shipVertical);
  $(this).css("background-color", "lime");
  $('#flipShipVertically').css("background-color", '');
})

gamesURL.once("value", function(snapshot){
  var games = (snapshot.val());

  Object.keys(games).forEach(function (uuid) {
    $('.board').attr("data-uuid", uuid);

  });
});

function createBoard(board) {
  $('#board').html('');

  var $table = $('

')              //Created table to append too
  board.forEach(function (createRow) {                              //For Each item in the board array and create a row for it
    var $tr = $('

');                                       //created table rows in table
    createRow.forEach(function (createTd){                          //For Each table row create a TD
      $tr.append('' + createTd + '	')      //Nested loop going through every '' within the array
    });
  $table.append($tr);                                               //Append the created Td's to the table
  });
  $('.board').append($table);
  clickHandlerForGameBoard();                                       //Append the tbale to the the actual game board
};

function clickHandlerForGameBoard() {

  $('td').on('click', function() {

    var ericsDingy = ($('td').index(this));
    var clickTr = $(this).closest('tr').index();
    board[clickTr].splice($(this).index(), myDingy.length, myDingy);
    console.log($('td').index(this), $(this).closest('tr').index());
    var xCoordinates =  $('td').index(this);
    var yCoordinates = $(this).closest('tr').index();

    if (shipVertical === 'false') {
    //The for loop for the horizontal placement
      for (var i=0; i < 3; i++) {
        //var shipIndex = ($(this).index() + i);
        board[clickTr].splice($(this).index() + i, 1, myDingy);
      };

      createBoard(board);
    } else {

      //The for loop for the vertical placement
      for (var i=0; i < 3; i++) {
        board[clickTr].splice($(this).index(), 1, myDingy);
        clickTr = clickTr + 1;
      };
      createBoard(board);
    };
  });
}
<commit_msg>working on adding ships. still broken<commit_after>//Declare Variables
var firebaseURL = "https://battleshipit.firebaseio.com/",
    fb          = new Firebase(firebaseURL),
    gamesURL    = new Firebase(firebaseURL + 'games/'),
    createGame,
    board       = [['', '', '', '', '', '', '', '', '', ''],
                   ['', '', '', '', '', '', '', '', '', ''],
                   ['', '', '', '', '', '', '', '', '', ''],
                   ['', '', '', '', '', '', '', '', '', ''],
                   ['', '', '', '', '', '', '', '', '', ''],
                   ['', '', '', '', '', '', '', '', '', ''],
                   ['', '', '', '', '', '', '', '', '', ''],
                   ['', '', '', '', '', '', '', '', '', ''],
                   ['', '', '', '', '', '', '', '', '', ''],
                   ['', '', '', '', '', '', '', '', '', '']],
    player1,
    player2     = 'United States',
    playerTurn,
    index,
    myDingy     = 'D',
    battleShip  = 'BS',
    carrier     = 'AC',
    Destroyer   = 'DE',
    Sub         = 'SU',
    shipVertical,
    clickTr;

// var currentGameRef = fb.child(uuid);

$('#submit-player').on('click', function() {
  event.preventDefault();
  var thisplayersname = $('.playerName').val(),
      uuid  = $('table').data('uuid');
    gamesURL.push({"player1board": board, "player2board": board, "player1name" : thisplayersname, "player2name": "", "player1turn" : "", "player2turn" : ""});
          createBoard(board);
    console.log("uuid");

});

$('#flipShipVertically').on('click', function() {
  shipVertical = 'true';
  console.log(shipVertical);
  $(this).css("background-color", "lime");
  $('#flipShipHorizontally').css("background-color", '');
})

$('#flipShipHorizontally').on('click', function() {
  shipVertical = 'false';
  console.log(shipVertical);
  $(this).css("background-color", "lime");
  $('#flipShipVertically').css("background-color", '');
})

gamesURL.once("value", function(snapshot){
  var games = (snapshot.val());

  Object.keys(games).forEach(function (uuid) {
    $('.board').attr("data-uuid", uuid);

  });
});

function createBoard(board) {
  $('#board').html('');

  var $table = $('

')              //Created table to append too
  board.forEach(function (createRow) {                              //For Each item in the board array and create a row for it
    var $tr = $('

');                                       //created table rows in table
    createRow.forEach(function (createTd){                          //For Each table row create a TD
      $tr.append('' + createTd + '	')      //Nested loop going through every '' within the array
    });
  $table.append($tr);                                               //Append the created Td's to the table
  });
  $('.board').append($table);
  clickHandlerForGameBoard();                                       //Append the tbale to the the actual game board
};

function clickHandlerForGameBoard() {

  $('td').on('click', function() {

    var ericsDingy = ($('td').index(this));
    var clickTr = $(this).closest('tr').index();
    board[clickTr].splice($(this).index(), myDingy.length, myDingy);
    console.log($('td').index(this), $(this).closest('tr').index());
    var xCoordinates =  $('td').index(this);
    var yCoordinates = $(this).closest('tr').index();

    var lengthOfShips = [5,4,3,3,2];
    for (j=0; j < lengthOfShips.length; j++){
      var myShip = lengthOfShips[j];
      // console.log('myShip', myShip);
      if (shipVertical === 'false') {
      //The for loop for the horizontal placement
        for (var i=0; i < lengthOfShips[j]; i++) {
          console.log('myShip', lengthOfShips[j]);
          //var shipIndex = ($(this).index() + i);
          board[clickTr].splice($(this).index() + i, 1, myDingy);
        }

        createBoard(board);
      } else {

        //The for loop for the vertical placement
        for (var k=0; k < lengthOfShips[j]; k++) {
          console.log('myShip', lengthOfShips[j]);
          board[clickTr].splice($(this).index(), 1, myDingy);
          clickTr = clickTr + 1;
        }
        createBoard(board);
      }
    }
  });
}
<|endoftext|>


<commit_before>import Ember from 'ember';
import Space from 'hyperchannel/models/space';
import Channel from 'hyperchannel/models/channel';
import UserChannel from 'hyperchannel/models/user_channel';
import Message from 'hyperchannel/models/message';
import config from 'hyperchannel/config/environment';
import moment from 'moment';
import { storageFor as localStorageFor } from 'ember-local-storage';

const {
  Service,
  inject: {
    service
  },
  RSVP,
  isEmpty,
  isPresent,
  Logger,
  get
} = Ember;

/**
 * This service provides the central command interface for communicating with
 * chat servers/networks
 * @module hyperchannel/services/coms
 */
export default Service.extend({

  // Utils
  ajax: service(),
  logger: service(),
  // Data storage
  userSettings: localStorageFor('user-settings'),
  storage: service('remotestorage'),
  // Message transport
  irc:  service('sockethub-irc'),
  xmpp: service('sockethub-xmpp'),

  /**
   * A collection of all space model instances
   * @type {Space[]}
   */
  spaces: null,

  /**
   * This is called from the application route on app startup. Sets up all
   * listeners for incoming Sockethub messages.
   * @public
   */
  setupListeners() {
    this.sockethub.socket.on('completed', this.handleSockethubCompleted.bind(this));
    this.sockethub.socket.on('message'  , this.handleSockethubMessage.bind(this));
    this.sockethub.socket.on('failure'  , this.handleSockethubFailure.bind(this));
  },

  /**
   * This is called from the application route on app startup. Instantiates,
   * connects, and joins all either configured/saved or default spaces/channels
   * @public
   */
  instantiateSpacesAndChannels() {
    this.set('spaces', []);
    let rs = this.get('storage.rs');

    return new RSVP.Promise((resolve, reject) => {
      rs.kosmos.spaces.getAll().then(spaceData => {
        if (isEmpty(Object.keys(spaceData))) {
          Logger.debug('No space data found in RS. Adding default space...');
          this.get('storage').addDefaultSpace().then((data) => {
            this.connectAndAddSpace(data.space);
            this.instantiateChannels(data.space, data.channels);
            resolve();
          });
          // resolve();
        } else {
          Object.keys(spaceData).forEach((id) => {
            let space = Space.create({
              id: id,
              name: spaceData[id].name,
              protocol: spaceData[id].protocol,
              server: spaceData[id].server
            });
            this.connectAndAddSpace(space);
            this.instantiateChannels(space, spaceData[id].channels);
          });
          resolve();
        }
      }, e => {
        this.log('error', 'couldn\'d load spaces from RS', e);
        reject();
      });
    });
  },

  /**
   * Invokes the connect function on the appropriate transport service
   * @public
   */
  connectServer(space) {
    this.getSockethubPlatformFor(space.get('protocol')).connect(space);
  },

  connectAndAddSpace(space) {
    this.connectServer(space);
    this.get('spaces').pushObject(space);
  },

  /**
   * Invokes the channel-join function on the appropriate transport service
   * @param {Space} space
   * @param {Channel} channel
   * @param {string} type - Type of channel. Can be "room" or "person"
   * @public
   */
  joinChannel: function(space, channel, type) {
    this.getSockethubPlatformFor(space.get('protocol')).join(space, channel, type);
  },

  /**
   * Invokes the send-message function on the appropriate transport service
   * @param {Space} space
   * @param {Channel} channel
   * @param {string} content
   * @public
   */
  transferMessage(space, channel, content) {
    const target = {
      '@id': channel.get('sockethubChannelId'),
      '@type': channel.get('isUserChannel') ? 'person' : 'room',
      displayName: channel.get('name')
    };
    this.getSockethubPlatformFor(space.get('protocol'))
      .transferMessage(space, target, content);
  },

  /**
   * Invokes the send-action-message function on the appropriate transport service
   * @public
   */
  transferMeMessage(space, target, content) {
    switch (space.get('protocol')) {
      case 'IRC':
        this.get('irc').transferMeMessage(space, target, content);
        break;
    }
  },

  leaveChannel: function(space, channel) {
    switch (space.get('protocol')) {
      case 'IRC':
        this.get('irc').leave(space, channel);
        break;
    }
  },

  changeTopic: function(space, channel, topic) {
    switch (space.get('protocol')) {
      case 'IRC':
        this.get('irc').changeTopic(space, channel, topic);
        break;
    }
  },

  updateChannelUserList(message) {
    const channel = this.getChannelById(message.actor['@id']);
    if (channel) {
      if (Array.isArray(message.object.members)) {
        channel.set('userList', message.object.members.sort());
      }
    }
  },

  addUserToChannelUserList(message) {
    const channel = this.getChannelById(message.target['@id']);
    if (channel) {
      channel.addUser(message.actor.displayName);
    }
  },

  removeUserFromChannelUserList(message) {
    // TODO handle user quit leaves (multiple channels)
    // e.g. target is `{ @type: 'service', @id: 'irc://irc.freenode.net' }`
    const channel = this.getChannelById(message.target['@id']);
    if (channel) {
      channel.removeUser(message.actor.displayName);
    }
  },

  getChannelById(channelId) {
    // TODO handle multiple spaces with same hostname:
    // This method should return an array of channels for all spaces with the
    // same hostname

    const hostname = channelId.match(/(?:irc:\/\/)?(?:.+@)?(.+?)(?:\/|$)/)[1];

    const space = this.get('spaces').findBy('server.hostname', hostname);

    if (isPresent(space)) {
      const channel = space.get('channels').findBy('sockethubChannelId', channelId);
      if (isPresent(channel)) {
        return channel;
      } else {
        Ember.Logger.warn('Could not find channel by sockethubChannelId', channelId);
      }
    } else {
      Ember.Logger.warn('Could not find space by hostname', hostname);
    }
  },

  /**
   * @param {string} personId
   * @param {string} channelId
   */
  getChannel(personId, channelId) {
    const space = this.get('spaces').findBy('sockethubPersonId', personId);
    if (isPresent(space)) {
      const channel = space.get('channels').findBy('sockethubChannelId', channelId);
      if (isPresent(channel)) {
        return channel;
      } else {
        Ember.Logger.warn('Could not find channel by sockethubChannelId', channelId);
      }
    } else {
      Ember.Logger.warn('Could not find space by sockethubPersonId', personId);
    }
  },

  updateUsername(message) {
    if (typeof message.actor === 'object') {
      const actorId = message.actor['@id'];
      const space = this.get('spaces').findBy('sockethubPersonId', actorId);
      if (isPresent(space)) {
        space.updateUsername(message.target.displayName);
      }
    }
  },

  updateChannelTopic(message) {
    let hostname;
    if (typeof message.target === 'object') {
      hostname = message.target['@id'].match(/irc:\/\/(.+)\//)[1];
    } else if (typeof message.actor === 'string') {
      hostname = message.actor.match(/irc:\/\/.+\@(.+)/)[1];
    }

    let space = this.get('spaces').findBy('server.hostname', hostname);

    if (!isEmpty(space)) {
      let channel = space.get('channels').findBy('sockethubChannelId', message.target['@id']);

      if (isEmpty(channel)) {
        Ember.Logger.warn('No channel for update topic message found. Creating it.', message);
        channel = this.createChannel(space, message.target['displayName']);
      }

      let currentTopic = channel.get('topic');
      let newTopic = message.object.topic;

      channel.set('topic', newTopic);

      if (isPresent(currentTopic) && (newTopic !== currentTopic) && !channel.get('visible')) {
        Notification.requestPermission(function() {
          new Notification(channel.name, {
            body: `New Topic: ${newTopic}`
          });
        });
      }

      // let notification = Message.create({
      //   type: 'notification-topic-change',
      //   date: new Date(message.published),
      //   nickname: message.actor.displayName,
      //   content: message.object.topic
      // });

      // channel.get('messages').pushObject(notification);

    }
  },

  instantiateChannels: function(space, channels) {
    channels.forEach((channelName) => {
      this.createChannel(space, channelName);
    });
  },

  createChannel: function(space, channelName) {
    const platform = this.getSockethubPlatformFor(space.get('protocol'));

    const channel = Channel.create({
      space: space,
      name: channelName,
      sockethubChannelId: platform.generateChannelId(space, channelName)
    });

    this.joinChannel(space, channel, "room");
    space.get('channels').pushObject(channel);

    // TODO Do we need this on startup? Could overwrite updates from remote.
    this.get('storage').saveSpace(space);

    if (channel.get('isLogged')) {
      this.loadLastMessages(space, channel, moment.utc(), 2).catch(() => {});
    }

    return channel;
  },

  loadLastMessages(space, channel, date, maximumDays = 14) {
    let searchUntilDate;
    if (channel.get('searchedPreviousLogsUntilDate')) {
      searchUntilDate = moment(channel.get('searchedPreviousLogsUntilDate')).subtract(maximumDays, 'days');
    } else {
      searchUntilDate = moment.utc().subtract(maximumDays, 'days');
    }

    if (date.isBefore(searchUntilDate, 'day')) {
      channel.set('searchedPreviousLogsUntilDate', date);
      return;
    }

    return this.loadArchiveMessages(space, channel, date).catch(() => {
      // didn't find any archive for this day, restart searching for the previous day
      return this.loadLastMessages(space, channel, date.subtract(1, 'day'));
    });
  },

  loadArchiveMessages(space, channel, date) {
    let logsUrl = `${config.publicLogsUrl}/${space.get('name').toLowerCase()}/channels/${channel.get('slug')}/`;
        logsUrl += date.format('YYYY/MM/DD');

    return this.get('ajax').request(logsUrl, {
      type: 'GET',
      dataType: 'json'
    }).then(archive => {
      get(archive, 'today.messages').forEach((message) => {
        this.log('chat_message', message);

        let channelMessage = Message.create({
          type: 'message-chat',
          date: new Date(message.timestamp),
          nickname: message.from,
          content: message.text
        });

        channel.addMessage(channelMessage);
      });
      let previous = get(archive, 'today.previous');
      channel.set('searchedPreviousLogsUntilDate', moment.utc(previous.replace(/\//g, '-')));
    }).catch(error => {
      this.log('ajax-error', 'couldn\'t load archive document', error);
      throw(error);
    });
  },

  createUserChannel: function(space, userName) {
    const platform = this.getSockethubPlatformFor(space.get('protocol'));

    const channel = UserChannel.create({
      space: space,
      name: userName,
      sockethubChannelId: platform.generateChannelId(space, userName)
    });

    // TODO check if this is necesarry for XMPP,
    // because for IRC it is not
    this.joinChannel(space, channel, "person");
    space.get('channels').pushObject(channel);

    return channel;
  },

  removeChannel: function(space, channelName) {
    var channel = space.get('channels').findBy('name', channelName);
    this.leaveChannel(space, channel);

    space.get('channels').removeObject(channel);

    this.get('storage').saveSpace(space);

    return channel;
  },

  getSockethubPlatformFor(protocol) {
    return this.get(protocol.dasherize());
  },

  /*
   * @private
   *
   * Handles completed Sockethub actions:
   *
   *     - Successfully joined a channel
   */
  handleSockethubCompleted(message) {
    this.log(`${message.context}_completed`, message);

    switch(message['@type']) {
      case 'join':
        var space = this.get('spaces').findBy('sockethubPersonId', message.actor['@id']);

        // try to find space by older sockethubPersonId
        if (isEmpty(space)) {
          space = this.get('spaces').find((space) => {
            return space.get('previousSockethubPersonIds').includes(message.actor['@id']);
          });
        }

        if (!isEmpty(space)) {
          this.get(message.context).handleJoinCompleted(space, message);
        } else {
          Logger.warn('Could not find space for join message', message);
        }
        break;
    }
  },

  /**
   * Handles incoming Sockethub messages:
   * - Attendance list for channel
   * - Another user joined or left a channel
   * - Received a channel message (normal or me/action)
   * - A channel topic was updated
   * - The username/address changed
   * @private
   */
  handleSockethubMessage(message) {
    this.log(`${message.context}_message`, 'SH message', message);

    switch(message['@type']) {
      case 'observe':
        if (message.object['@type'] === 'attendance') {
          this.updateChannelUserList(message);
        }
        break;
      case 'join':
        this.handleChannelJoin(message);
        break;
      case 'leave':
        this.removeUserFromChannelUserList(message);
        break;
      case 'send':
        switch(message.object['@type']) {
          case 'message':
          case 'me':
            this.getSockethubPlatformFor(message.context).addMessageToChannel(message);
            break;
        }
        break;
      case 'update':
        switch(message.object['@type']) {
          case 'topic':
            this.updateChannelTopic(message);
            break;
          case 'address':
            this.updateUsername(message);
            break;
          case 'presence':
            this.get('xmpp').handlePresenceUpdate(message);
            break;
          case 'error':
            Logger.warn('Got error update message', message.actor['@id'], message.object.content);
            break;
        }
        break;
      case 'request-friend':
        var space = this.get('spaces').findBy('sockethubPersonId', message.target['@id']);
        if (isPresent(space)) {
          this.get('xmpp').handleFriendRequest(space, message);
        } else {
          Logger.warn('Could not find space for friend request', message);
        }
        break;
    }
  },

  /**
   * Handles the various checks assosciated with channel joins
   * @private
   */
  handleChannelJoin(message) {
    if (message.object['@type'] && (message.object['@type'] === 'error')) {
      // failed to join a channel
      let channel = this.getChannel(message.target['@id'], message.actor['@id']);
      if (isPresent(channel)) {
        channel.set('connected', false);
      } else {
        Logger.warn('Could not find channel for error message', message);
      }
    } else {
      this.addUserToChannelUserList(message);
    }
  },

  /**
   * Handles incoming Sockethub errors/failures
   * @private
   */
  handleSockethubFailure(message) {
    this.log('sh_failure', message);
  },

  /**
   * Utility function for easier logging
   * @private
   */
  log() {
    this.get('logger').log(...arguments);
  }
});
<commit_msg>Rename method<commit_after>import Ember from 'ember';
import Space from 'hyperchannel/models/space';
import Channel from 'hyperchannel/models/channel';
import UserChannel from 'hyperchannel/models/user_channel';
import Message from 'hyperchannel/models/message';
import config from 'hyperchannel/config/environment';
import moment from 'moment';
import { storageFor as localStorageFor } from 'ember-local-storage';

const {
  Service,
  inject: {
    service
  },
  RSVP,
  isEmpty,
  isPresent,
  Logger,
  get
} = Ember;

/**
 * This service provides the central command interface for communicating with
 * chat servers/networks
 * @module hyperchannel/services/coms
 */
export default Service.extend({

  // Utils
  ajax: service(),
  logger: service(),
  // Data storage
  userSettings: localStorageFor('user-settings'),
  storage: service('remotestorage'),
  // Message transport
  irc:  service('sockethub-irc'),
  xmpp: service('sockethub-xmpp'),

  /**
   * A collection of all space model instances
   * @type {Space[]}
   */
  spaces: null,

  /**
   * This is called from the application route on app startup. Sets up all
   * listeners for incoming Sockethub messages.
   * @public
   */
  setupListeners() {
    this.sockethub.socket.on('completed', this.handleSockethubCompleted.bind(this));
    this.sockethub.socket.on('message'  , this.handleSockethubMessage.bind(this));
    this.sockethub.socket.on('failure'  , this.handleSockethubFailure.bind(this));
  },

  /**
   * This is called from the application route on app startup. Instantiates,
   * connects, and joins all either configured/saved or default spaces/channels
   * @public
   */
  instantiateSpacesAndChannels() {
    this.set('spaces', []);
    let rs = this.get('storage.rs');

    return new RSVP.Promise((resolve, reject) => {
      rs.kosmos.spaces.getAll().then(spaceData => {
        if (isEmpty(Object.keys(spaceData))) {
          Logger.debug('No space data found in RS. Adding default space...');
          this.get('storage').addDefaultSpace().then((data) => {
            this.connectAndAddSpace(data.space);
            this.instantiateChannels(data.space, data.channels);
            resolve();
          });
          // resolve();
        } else {
          Object.keys(spaceData).forEach((id) => {
            let space = Space.create({
              id: id,
              name: spaceData[id].name,
              protocol: spaceData[id].protocol,
              server: spaceData[id].server
            });
            this.connectAndAddSpace(space);
            this.instantiateChannels(space, spaceData[id].channels);
          });
          resolve();
        }
      }, e => {
        this.log('error', 'couldn\'d load spaces from RS', e);
        reject();
      });
    });
  },

  /**
   * Invokes the connect function on the appropriate transport service
   * @public
   */
  connectServer(space) {
    this.getServiceForSockethubPlatform(space.get('protocol')).connect(space);
  },

  connectAndAddSpace(space) {
    this.connectServer(space);
    this.get('spaces').pushObject(space);
  },

  /**
   * Invokes the channel-join function on the appropriate transport service
   * @param {Space} space
   * @param {Channel} channel
   * @param {string} type - Type of channel. Can be "room" or "person"
   * @public
   */
  joinChannel: function(space, channel, type) {
    this.getServiceForSockethubPlatform(space.get('protocol')).join(space, channel, type);
  },

  /**
   * Invokes the send-message function on the appropriate transport service
   * @param {Space} space
   * @param {Channel} channel
   * @param {string} content
   * @public
   */
  transferMessage(space, channel, content) {
    const target = {
      '@id': channel.get('sockethubChannelId'),
      '@type': channel.get('isUserChannel') ? 'person' : 'room',
      displayName: channel.get('name')
    };
    this.getServiceForSockethubPlatform(space.get('protocol'))
      .transferMessage(space, target, content);
  },

  /**
   * Invokes the send-action-message function on the appropriate transport service
   * @public
   */
  transferMeMessage(space, target, content) {
    switch (space.get('protocol')) {
      case 'IRC':
        this.get('irc').transferMeMessage(space, target, content);
        break;
    }
  },

  leaveChannel: function(space, channel) {
    switch (space.get('protocol')) {
      case 'IRC':
        this.get('irc').leave(space, channel);
        break;
    }
  },

  changeTopic: function(space, channel, topic) {
    switch (space.get('protocol')) {
      case 'IRC':
        this.get('irc').changeTopic(space, channel, topic);
        break;
    }
  },

  updateChannelUserList(message) {
    const channel = this.getChannelById(message.actor['@id']);
    if (channel) {
      if (Array.isArray(message.object.members)) {
        channel.set('userList', message.object.members.sort());
      }
    }
  },

  addUserToChannelUserList(message) {
    const channel = this.getChannelById(message.target['@id']);
    if (channel) {
      channel.addUser(message.actor.displayName);
    }
  },

  removeUserFromChannelUserList(message) {
    // TODO handle user quit leaves (multiple channels)
    // e.g. target is `{ @type: 'service', @id: 'irc://irc.freenode.net' }`
    const channel = this.getChannelById(message.target['@id']);
    if (channel) {
      channel.removeUser(message.actor.displayName);
    }
  },

  getChannelById(channelId) {
    // TODO handle multiple spaces with same hostname:
    // This method should return an array of channels for all spaces with the
    // same hostname

    const hostname = channelId.match(/(?:irc:\/\/)?(?:.+@)?(.+?)(?:\/|$)/)[1];

    const space = this.get('spaces').findBy('server.hostname', hostname);

    if (isPresent(space)) {
      const channel = space.get('channels').findBy('sockethubChannelId', channelId);
      if (isPresent(channel)) {
        return channel;
      } else {
        Ember.Logger.warn('Could not find channel by sockethubChannelId', channelId);
      }
    } else {
      Ember.Logger.warn('Could not find space by hostname', hostname);
    }
  },

  /**
   * @param {string} personId
   * @param {string} channelId
   */
  getChannel(personId, channelId) {
    const space = this.get('spaces').findBy('sockethubPersonId', personId);
    if (isPresent(space)) {
      const channel = space.get('channels').findBy('sockethubChannelId', channelId);
      if (isPresent(channel)) {
        return channel;
      } else {
        Ember.Logger.warn('Could not find channel by sockethubChannelId', channelId);
      }
    } else {
      Ember.Logger.warn('Could not find space by sockethubPersonId', personId);
    }
  },

  updateUsername(message) {
    if (typeof message.actor === 'object') {
      const actorId = message.actor['@id'];
      const space = this.get('spaces').findBy('sockethubPersonId', actorId);
      if (isPresent(space)) {
        space.updateUsername(message.target.displayName);
      }
    }
  },

  updateChannelTopic(message) {
    let hostname;
    if (typeof message.target === 'object') {
      hostname = message.target['@id'].match(/irc:\/\/(.+)\//)[1];
    } else if (typeof message.actor === 'string') {
      hostname = message.actor.match(/irc:\/\/.+\@(.+)/)[1];
    }

    let space = this.get('spaces').findBy('server.hostname', hostname);

    if (!isEmpty(space)) {
      let channel = space.get('channels').findBy('sockethubChannelId', message.target['@id']);

      if (isEmpty(channel)) {
        Ember.Logger.warn('No channel for update topic message found. Creating it.', message);
        channel = this.createChannel(space, message.target['displayName']);
      }

      let currentTopic = channel.get('topic');
      let newTopic = message.object.topic;

      channel.set('topic', newTopic);

      if (isPresent(currentTopic) && (newTopic !== currentTopic) && !channel.get('visible')) {
        Notification.requestPermission(function() {
          new Notification(channel.name, {
            body: `New Topic: ${newTopic}`
          });
        });
      }

      // let notification = Message.create({
      //   type: 'notification-topic-change',
      //   date: new Date(message.published),
      //   nickname: message.actor.displayName,
      //   content: message.object.topic
      // });

      // channel.get('messages').pushObject(notification);

    }
  },

  instantiateChannels: function(space, channels) {
    channels.forEach((channelName) => {
      this.createChannel(space, channelName);
    });
  },

  createChannel: function(space, channelName) {
    const platform = this.getServiceForSockethubPlatform(space.get('protocol'));

    const channel = Channel.create({
      space: space,
      name: channelName,
      sockethubChannelId: platform.generateChannelId(space, channelName)
    });

    this.joinChannel(space, channel, "room");
    space.get('channels').pushObject(channel);

    // TODO Do we need this on startup? Could overwrite updates from remote.
    this.get('storage').saveSpace(space);

    if (channel.get('isLogged')) {
      this.loadLastMessages(space, channel, moment.utc(), 2).catch(() => {});
    }

    return channel;
  },

  loadLastMessages(space, channel, date, maximumDays = 14) {
    let searchUntilDate;
    if (channel.get('searchedPreviousLogsUntilDate')) {
      searchUntilDate = moment(channel.get('searchedPreviousLogsUntilDate')).subtract(maximumDays, 'days');
    } else {
      searchUntilDate = moment.utc().subtract(maximumDays, 'days');
    }

    if (date.isBefore(searchUntilDate, 'day')) {
      channel.set('searchedPreviousLogsUntilDate', date);
      return;
    }

    return this.loadArchiveMessages(space, channel, date).catch(() => {
      // didn't find any archive for this day, restart searching for the previous day
      return this.loadLastMessages(space, channel, date.subtract(1, 'day'));
    });
  },

  loadArchiveMessages(space, channel, date) {
    let logsUrl = `${config.publicLogsUrl}/${space.get('name').toLowerCase()}/channels/${channel.get('slug')}/`;
        logsUrl += date.format('YYYY/MM/DD');

    return this.get('ajax').request(logsUrl, {
      type: 'GET',
      dataType: 'json'
    }).then(archive => {
      get(archive, 'today.messages').forEach((message) => {
        this.log('chat_message', message);

        let channelMessage = Message.create({
          type: 'message-chat',
          date: new Date(message.timestamp),
          nickname: message.from,
          content: message.text
        });

        channel.addMessage(channelMessage);
      });
      let previous = get(archive, 'today.previous');
      channel.set('searchedPreviousLogsUntilDate', moment.utc(previous.replace(/\//g, '-')));
    }).catch(error => {
      this.log('ajax-error', 'couldn\'t load archive document', error);
      throw(error);
    });
  },

  createUserChannel: function(space, userName) {
    const platform = this.getServiceForSockethubPlatform(space.get('protocol'));

    const channel = UserChannel.create({
      space: space,
      name: userName,
      sockethubChannelId: platform.generateChannelId(space, userName)
    });

    // TODO check if this is necesarry for XMPP,
    // because for IRC it is not
    this.joinChannel(space, channel, "person");
    space.get('channels').pushObject(channel);

    return channel;
  },

  removeChannel: function(space, channelName) {
    var channel = space.get('channels').findBy('name', channelName);
    this.leaveChannel(space, channel);

    space.get('channels').removeObject(channel);

    this.get('storage').saveSpace(space);

    return channel;
  },

  getServiceForSockethubPlatform(protocol) {
    return this.get(protocol.dasherize());
  },

  /*
   * @private
   *
   * Handles completed Sockethub actions:
   *
   *     - Successfully joined a channel
   */
  handleSockethubCompleted(message) {
    this.log(`${message.context}_completed`, message);

    switch(message['@type']) {
      case 'join':
        var space = this.get('spaces').findBy('sockethubPersonId', message.actor['@id']);

        // try to find space by older sockethubPersonId
        if (isEmpty(space)) {
          space = this.get('spaces').find((space) => {
            return space.get('previousSockethubPersonIds').includes(message.actor['@id']);
          });
        }

        if (!isEmpty(space)) {
          this.get(message.context).handleJoinCompleted(space, message);
        } else {
          Logger.warn('Could not find space for join message', message);
        }
        break;
    }
  },

  /**
   * Handles incoming Sockethub messages:
   * - Attendance list for channel
   * - Another user joined or left a channel
   * - Received a channel message (normal or me/action)
   * - A channel topic was updated
   * - The username/address changed
   * @private
   */
  handleSockethubMessage(message) {
    this.log(`${message.context}_message`, 'SH message', message);

    switch(message['@type']) {
      case 'observe':
        if (message.object['@type'] === 'attendance') {
          this.updateChannelUserList(message);
        }
        break;
      case 'join':
        this.handleChannelJoin(message);
        break;
      case 'leave':
        this.removeUserFromChannelUserList(message);
        break;
      case 'send':
        switch(message.object['@type']) {
          case 'message':
          case 'me':
            this.getServiceForSockethubPlatform(message.context).addMessageToChannel(message);
            break;
        }
        break;
      case 'update':
        switch(message.object['@type']) {
          case 'topic':
            this.updateChannelTopic(message);
            break;
          case 'address':
            this.updateUsername(message);
            break;
          case 'presence':
            this.get('xmpp').handlePresenceUpdate(message);
            break;
          case 'error':
            Logger.warn('Got error update message', message.actor['@id'], message.object.content);
            break;
        }
        break;
      case 'request-friend':
        var space = this.get('spaces').findBy('sockethubPersonId', message.target['@id']);
        if (isPresent(space)) {
          this.get('xmpp').handleFriendRequest(space, message);
        } else {
          Logger.warn('Could not find space for friend request', message);
        }
        break;
    }
  },

  /**
   * Handles the various checks assosciated with channel joins
   * @private
   */
  handleChannelJoin(message) {
    if (message.object['@type'] && (message.object['@type'] === 'error')) {
      // failed to join a channel
      let channel = this.getChannel(message.target['@id'], message.actor['@id']);
      if (isPresent(channel)) {
        channel.set('connected', false);
      } else {
        Logger.warn('Could not find channel for error message', message);
      }
    } else {
      this.addUserToChannelUserList(message);
    }
  },

  /**
   * Handles incoming Sockethub errors/failures
   * @private
   */
  handleSockethubFailure(message) {
    this.log('sh_failure', message);
  },

  /**
   * Utility function for easier logging
   * @private
   */
  log() {
    this.get('logger').log(...arguments);
  }
});
<|endoftext|>


<commit_before>import Ember from 'ember';
import {
    raw
    }
    from 'ic-ajax';

var config = {
    defaultViewBoxSize: 24
};

var urlRegex = /[-a-zA-Z0-9@:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?/i;

function Icon(el, config) {
    if (el.tagName != 'svg') {
        el = Ember.$('<svg xmlns="http://www.w3.org/2000/svg">').append(el)[0];
    }

    el = $(el);

    // inject the namespace if not available...
    if (!el.attr('xmlns')) {
        el.attr('xmlns', "http://www.w3.org/2000/svg");
    }

    this.element = el;
    this.config = config;
    this.prepare();
}

Icon.prototype = {
    clone: cloneSVG,
    prepare: prepareAndStyle
};

/**
 * Clone the Icon DOM element
 */
function cloneSVG() {
    return Ember.$(this.element[0].cloneNode(true));
}

/**
 * Prepare the DOM element that will be cached in the
 * loaded iconCache store.
 */
function prepareAndStyle() {
    var viewBoxSize = this.config ? this.config.viewBoxSize : config.defaultViewBoxSize;
    var svg = this.element;

    svg[0].setAttribute('fit', '');
    svg[0].setAttribute('height', '100%');
    svg[0].setAttribute('width', '100%');
    svg[0].setAttribute('preserveAspectRatio', 'xMidYMid meet');

    svg[0].setAttribute('viewBox', svg[0].getAttribute('viewBox') || ('0 0 ' + viewBoxSize + ' ' + viewBoxSize));

    svg.css({
        'pointer-events': 'none',
        'display': 'block'
    });

    this.element = svg;
}

var IconService = Ember.Service.extend({

    iconCache: {},
    templateCache: {},

    preloadIcons: function () {

        var self = this;

        var svgRegistry = [
            {
                id: 'md-tabs-arrow',
                url: 'md-tabs-arrow.svg',
                svg: '<svg version="1.1" x="0px" y="0px" viewBox="0 0 24 24"><g><polygon points="15.4,7.4 14,6 8,12 14,18 15.4,16.6 10.8,12 "/></g></svg>'

            },
            {
                id: 'md-close',
                url: 'md-close.svg',
                svg: '<svg version="1.1" x="0px" y="0px" viewBox="0 0 24 24"><g><path d="M19 6.41l-1.41-1.41-5.59 5.59-5.59-5.59-1.41 1.41 5.59 5.59-5.59 5.59 1.41 1.41 5.59-5.59 5.59 5.59 1.41-1.41-5.59-5.59z"/></g></svg>'

            },
            {
                id: 'md-cancel',
                url: 'md-cancel.svg',
                svg: '<svg version="1.1" x="0px" y="0px" viewBox="0 0 24 24"><g><path d="M12 2c-5.53 0-10 4.47-10 10s4.47 10 10 10 10-4.47 10-10-4.47-10-10-10zm5 13.59l-1.41 1.41-3.59-3.59-3.59 3.59-1.41-1.41 3.59-3.59-3.59-3.59 1.41-1.41 3.59 3.59 3.59-3.59 1.41 1.41-3.59 3.59 3.59 3.59z"/></g></svg>'

            },
            {
                id: 'md-toggle-arrow',
                url: 'md-toggle-arrow-svg',
                svg: '<svg version="1.1" x="0px" y="0px" viewBox="0 0 48 48"><path d="M24 16l-12 12 2.83 2.83 9.17-9.17 9.17 9.17 2.83-2.83z"/><path d="M0 0h48v48h-48z" fill="none"/></svg>'

            }
        ];

        svgRegistry.forEach(function (asset) {
            //self.icon(asset.id, asset.url);
            self.templateCache[asset.url] = asset.svg;
        });

    },

    init: function () {
        this._super(...arguments);
        this.preloadIcons();
    },

    preloadSets: function(){
        var self = this;

        for(var id in config){
            if(!config[id].svg){
                self.loadFromIconSet(id);
            }
        }
    },

    getIcon: function (id) {
        id = id || '';

        var self = this;

        // if already loaded and cached, use a clone of the cached icon.
        if (this.iconCache[id]) {
            return Ember.RSVP.Promise.resolve(this.iconCache[id].clone());
        }

        if (urlRegex.test(id)) {
            return this.loadByURL(id)
                .then(function (icon) {
                    return self.cacheIcon(icon, id);
                });
        }

        if (id.indexOf(':') == -1) {
            id = '$default:' + id;
        }

        var iconConfig = config[id];

        if(iconConfig){
            return this.loadByID(id).then(function(icon) {
                return self.cacheIcon(icon, id);
            });
        }else{
            return this.loadFromIconSet(id).then(function(icon){
                return self.cacheIcon(icon, id);
            });

        }
    },

    icon: function (id, url, viewBoxSize) {
        if (id.indexOf(':') == -1) {
            id = '$default:' + id;
        }

        config[id] = {
            url: url,
            viewBoxSize: viewBoxSize || config.defaultViewBoxSize
        };
    },

    iconSet: function (id, url, viewBoxSize) {
        config[id] = {
            url: url,
            viewBoxSize: viewBoxSize || config.defaultViewBoxSize
        };
    },

    defaultIconSet: function (url, viewBoxSize) {
        var setName = '$default';

        if (!config[setName]) {
            config[setName] = {
                url: url,
                viewBoxSize: viewBoxSize || config.defaultViewBoxSize
            };
        }
    },

    defaultViewBoxSize: function (viewBoxSize) {
        config.defaultViewBoxSize = viewBoxSize;
    },

    loadByID: function (id) {
        var iconConfig = config[id];

        return !iconConfig ? Ember.RSVP.Promise.reject(id) : this.loadByURL(iconConfig.url).then(function (icon) {
            return new Icon(icon, iconConfig);
        });
    },

    loadFromIconSet: function (id) {

        var self = this;
        var setName = id.substring(0, id.lastIndexOf(':')) || '$default';
        var iconSetConfig = config[setName];

        function extractFromSet(svg) {

            if (!self.templateCache[iconSetConfig.url]) {
                self.templateCache[iconSetConfig.url] = svg;
            }

            var iconName = id.slice(id.lastIndexOf(':') + 1);
            var icon = svg.querySelector('#' + iconName);
            return !icon ? Ember.RSVP.Promise.reject(id) : new Icon(icon, iconSetConfig);
        }

        return !iconSetConfig ? Ember.RSVP.Promise.reject(id) : this.loadByURL(iconSetConfig.url).then(extractFromSet);
    },

    loadByURL: function (url) {

        // first check templateCache

        var req;

        if (this.templateCache[url]) {
            req = Ember.RSVP.Promise.resolve(this.templateCache[url]);
        } else {
            req = raw(url, {
                dataType: 'text'
            });
        }

        return req
            .then(function (response) {
                // if its an actual ajax request, just get the response text
                if (response.jqXHR) {
                    response = response.jqXHR.responseText;
                }
                var els = Ember.$(response);

                for (var i = 0; i < els.length; ++i) {
                    if (els[i].nodeName === 'svg') {
                        return els[i];
                    }
                }
            });
    },

    announceIdNotFound: function (id) {
        var msg;

        if (typeof id === 'string') {
            msg = 'icon ' + id + ' not found';
            Ember.Logger.log(msg);
        }

        return Ember.RSVP.Promise.reject(msg || id);
    },

    announceNotFound: function (err) {
        var msg = (typeof err === 'string') ? err : (err.message || err.data || err.statusText);

        return Ember.RSVP.Promise.reject(msg);
    },

    isIcon: function (target) {
        return (typeof target.element !== 'undefined') && (typeof target.config !== 'undefined');
    },

    cacheIcon: function (icon, id) {
        this.iconCache[id] = this.isIcon(icon) ? icon : new Icon(icon, config[id]);
        return this.iconCache[id].clone();
    }

});

export default
IconService;
<commit_msg>cache icons #28<commit_after>import Ember from 'ember';
import {
    raw
    }
    from 'ic-ajax';

var config = {
    defaultViewBoxSize: 24
};

var urlRegex = /[-a-zA-Z0-9@:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?/i;

function Icon(el, config) {
    if (el.tagName != 'svg') {
        el = Ember.$('<svg xmlns="http://www.w3.org/2000/svg">').append(el)[0];
    }

    el = $(el);

    // inject the namespace if not available...
    if (!el.attr('xmlns')) {
        el.attr('xmlns', "http://www.w3.org/2000/svg");
    }

    this.element = el;
    this.config = config;
    this.prepare();
}

Icon.prototype = {
    clone: cloneSVG,
    prepare: prepareAndStyle
};

/**
 * Clone the Icon DOM element
 */
function cloneSVG() {
    return Ember.$(this.element[0].cloneNode(true));
}

/**
 * Prepare the DOM element that will be cached in the
 * loaded iconCache store.
 */
function prepareAndStyle() {
    var viewBoxSize = this.config ? this.config.viewBoxSize : config.defaultViewBoxSize;
    var svg = this.element;

    svg[0].setAttribute('fit', '');
    svg[0].setAttribute('height', '100%');
    svg[0].setAttribute('width', '100%');
    svg[0].setAttribute('preserveAspectRatio', 'xMidYMid meet');

    svg[0].setAttribute('viewBox', svg[0].getAttribute('viewBox') || ('0 0 ' + viewBoxSize + ' ' + viewBoxSize));

    svg.css({
        'pointer-events': 'none',
        'display': 'block'
    });

    this.element = svg;
}

var IconService = Ember.Service.extend({

    iconCache: {},
    templateCache: {},

    preloadIcons: function () {

        var self = this;

        var svgRegistry = [
            {
                id: 'md-tabs-arrow',
                url: 'md-tabs-arrow.svg',
                svg: '<svg version="1.1" x="0px" y="0px" viewBox="0 0 24 24"><g><polygon points="15.4,7.4 14,6 8,12 14,18 15.4,16.6 10.8,12 "/></g></svg>'

            },
            {
                id: 'md-close',
                url: 'md-close.svg',
                svg: '<svg version="1.1" x="0px" y="0px" viewBox="0 0 24 24"><g><path d="M19 6.41l-1.41-1.41-5.59 5.59-5.59-5.59-1.41 1.41 5.59 5.59-5.59 5.59 1.41 1.41 5.59-5.59 5.59 5.59 1.41-1.41-5.59-5.59z"/></g></svg>'

            },
            {
                id: 'md-cancel',
                url: 'md-cancel.svg',
                svg: '<svg version="1.1" x="0px" y="0px" viewBox="0 0 24 24"><g><path d="M12 2c-5.53 0-10 4.47-10 10s4.47 10 10 10 10-4.47 10-10-4.47-10-10-10zm5 13.59l-1.41 1.41-3.59-3.59-3.59 3.59-1.41-1.41 3.59-3.59-3.59-3.59 1.41-1.41 3.59 3.59 3.59-3.59 1.41 1.41-3.59 3.59 3.59 3.59z"/></g></svg>'

            },
            {
                id: 'md-toggle-arrow',
                url: 'md-toggle-arrow-svg',
                svg: '<svg version="1.1" x="0px" y="0px" viewBox="0 0 48 48"><path d="M24 16l-12 12 2.83 2.83 9.17-9.17 9.17 9.17 2.83-2.83z"/><path d="M0 0h48v48h-48z" fill="none"/></svg>'

            }
        ];

        svgRegistry.forEach(function (asset) {
            //self.icon(asset.id, asset.url);
            self.templateCache[asset.url] = asset.svg;
        });

    },

    init: function () {
        this._super(...arguments);
        this.preloadIcons();
    },

    preloadSets: function(){
        var self = this;

        for(var id in config){
            if(!config[id].svg){
                self.loadFromIconSet(id);
            }
        }
    },

    getIcon: function (id) {
        id = id || '';

        var self = this;

        // if already loaded and cached, use a clone of the cached icon.
        if (this.iconCache[id]) {
            return Ember.RSVP.Promise.resolve(this.iconCache[id].clone());
        }

        if (urlRegex.test(id)) {
            return this.loadByURL(id)
                .then(function (icon) {
                    return self.cacheIcon(icon, id);
                });
        }

        if (id.indexOf(':') == -1) {
            id = '$default:' + id;
        }

        var iconConfig = config[id];

        if(iconConfig){
            return this.loadByID(id).then(function(icon) {
                return self.cacheIcon(icon, id);
            });
        }else{
            Ember.run.debounce(function(){
                return this.loadFromIconSet(id).then(function(icon){
                    return self.cacheIcon(icon, id);
                });
            }, 10);

        }
    },

    icon: function (id, url, viewBoxSize) {
        if (id.indexOf(':') == -1) {
            id = '$default:' + id;
        }

        config[id] = {
            url: url,
            viewBoxSize: viewBoxSize || config.defaultViewBoxSize
        };
    },

    iconSet: function (id, url, viewBoxSize) {
        config[id] = {
            url: url,
            viewBoxSize: viewBoxSize || config.defaultViewBoxSize
        };
    },

    defaultIconSet: function (url, viewBoxSize) {
        var setName = '$default';

        if (!config[setName]) {
            config[setName] = {
                url: url,
                viewBoxSize: viewBoxSize || config.defaultViewBoxSize
            };
        }
    },

    defaultViewBoxSize: function (viewBoxSize) {
        config.defaultViewBoxSize = viewBoxSize;
    },

    loadByID: function (id) {
        var iconConfig = config[id];

        return !iconConfig ? Ember.RSVP.Promise.reject(id) : this.loadByURL(iconConfig.url).then(function (icon) {
            return new Icon(icon, iconConfig);
        });
    },

    loadFromIconSet: function (id) {

        var self = this;
        var setName = id.substring(0, id.lastIndexOf(':')) || '$default';
        var iconSetConfig = config[setName];

        function extractFromSet(svg) {

            if (!self.templateCache[iconSetConfig.url]) {
                self.templateCache[iconSetConfig.url] = svg;
            }

            var iconName = id.slice(id.lastIndexOf(':') + 1);
            var icon = svg.querySelector('#' + iconName);
            return !icon ? Ember.RSVP.Promise.reject(id) : new Icon(icon, iconSetConfig);
        }

        return !iconSetConfig ? Ember.RSVP.Promise.reject(id) : this.loadByURL(iconSetConfig.url).then(extractFromSet);
    },

    loadByURL: function (url) {

        // first check templateCache

        var req;

        if (this.templateCache[url]) {
            req = Ember.RSVP.Promise.resolve(this.templateCache[url]);
        } else {
            req = raw(url, {
                dataType: 'text'
            });
        }

        return req
            .then(function (response) {
                // if its an actual ajax request, just get the response text
                if (response.jqXHR) {
                    response = response.jqXHR.responseText;
                }
                var els = Ember.$(response);

                for (var i = 0; i < els.length; ++i) {
                    if (els[i].nodeName === 'svg') {
                        return els[i];
                    }
                }
            });
    },

    announceIdNotFound: function (id) {
        var msg;

        if (typeof id === 'string') {
            msg = 'icon ' + id + ' not found';
            Ember.Logger.log(msg);
        }

        return Ember.RSVP.Promise.reject(msg || id);
    },

    announceNotFound: function (err) {
        var msg = (typeof err === 'string') ? err : (err.message || err.data || err.statusText);

        return Ember.RSVP.Promise.reject(msg);
    },

    isIcon: function (target) {
        return (typeof target.element !== 'undefined') && (typeof target.config !== 'undefined');
    },

    cacheIcon: function (icon, id) {
        this.iconCache[id] = this.isIcon(icon) ? icon : new Icon(icon, config[id]);
        return this.iconCache[id].clone();
    }

});

export default
IconService;
<|endoftext|>


<commit_before>var events = require('events');
var util = require('util');

var GENERIC_ACCESS_UUID                     = '1800';
var DEVICE_NAME_UUID                        = '2a00';

function NobleDevice(peripheral) {
  this._peripheral = peripheral;
  this._services = {};
  this._characteristics = {};

  this.uuid = peripheral.uuid;

  this._peripheral.on('disconnect', this.onDisconnect.bind(this));
}

util.inherits(NobleDevice, events.EventEmitter);

NobleDevice.prototype.onDisconnect = function() {
  this.emit('disconnect');
};

NobleDevice.prototype.toString = function() {
  return JSON.stringify({
    uuid: this.uuid
  });
};

NobleDevice.prototype.connect = function(callback) {
  this._peripheral.connect(callback);
};

NobleDevice.prototype.disconnect = function(callback) {
  this._peripheral.disconnect(callback);
};

NobleDevice.prototype.discoverServicesAndCharacteristics = function(callback) {
  this._peripheral.discoverAllServicesAndCharacteristics(function(error, services, characteristics) {
    if (error === null) {
      for (var i in services) {
        var service = services[i];
        var characteristics = service.characteristics;

        var serviceUuid = service.uuid;

        this._services[serviceUuid] = service;
        this._characteristics[serviceUuid] = {};

        for (var j in characteristics) {
          var characteristic = characteristics[j];

          this._characteristics[serviceUuid][characteristic.uuid] = characteristic;
        }
      }
    }

    callback();
  }.bind(this));
};

NobleDevice.prototype.connectAndSetup = function(callback) {
  this.connect(function() {
    this.discoverServicesAndCharacteristics(callback);
  }.bind(this));
};

NobleDevice.prototype.readDataCharacteristic = function(serviceUuid, characteristicUuid, callback) {
  this._characteristics[serviceUuid][characteristicUuid].read(function(error, data) {
    callback(data);
  });
};

NobleDevice.prototype.writeDataCharacteristic = function(serviceUuid, characteristicUuid, data, callback) {
  var c = this._characteristics[serviceUuid][characteristicUuid];
  var withoutResponse = c.properties.indexOf('writeWithoutResponse') !== -1;
  this._characteristics[serviceUuid][characteristicUuid].write(data, withoutResponse, function(error) {
    callback();
  });
};

NobleDevice.prototype.notifyCharacteristic = function(serviceUuid, characteristicUuid, notify, listener, callback) {
  var characteristic = this._characteristics[serviceUuid][characteristicUuid];

  characteristic.notify(notify, function(state) {
    if (notify) {
      characteristic.addListener('read', listener);
    } else {
      characteristic.removeListener('read', listener);
    }

    callback();
  });
};

NobleDevice.prototype.readStringCharacteristic = function(serviceUuid, characteristicUuid, callback) {
  this.readDataCharacteristic(serviceUuid, characteristicUuid, function(data) {
    callback(data.toString());
  });
};

NobleDevice.prototype.readUInt8Characteristic = function(serviceUuid, characteristicUuid, callback) {
  this.readDataCharacteristic(serviceUuid, characteristicUuid, function(data) {
    callback(data.readUInt8(0));
  });
};

NobleDevice.prototype.readUInt16LECharacteristic = function(serviceUuid, characteristicUuid, callback) {
  this.readDataCharacteristic(serviceUuid, characteristicUuid, function(data) {
    callback(data.readUInt16LE(0));
  });
};

NobleDevice.prototype.readFloatLECharacteristic = function(serviceUuid, characteristicUuid, callback) {
  this.readDataCharacteristic(serviceUuid, characteristicUuid, function(data) {
    callback(data.readFloatLE(0));
  });
};

NobleDevice.prototype.writeStringCharacteristic = function(serviceUuid, characteristicUuid, string, callback) {
  this.writeDataCharacteristic(serviceUuid, characteristicUuid, new Buffer(string), callback);
};

NobleDevice.prototype.readDeviceName = function(callback) {
  this.readStringCharacteristic(GENERIC_ACCESS_UUID, DEVICE_NAME_UUID, callback);
};

module.exports = NobleDevice;
<commit_msg>When both write and writeWithoutResponse are available, prefer write.<commit_after>var events = require('events');
var util = require('util');

var GENERIC_ACCESS_UUID                     = '1800';
var DEVICE_NAME_UUID                        = '2a00';

function NobleDevice(peripheral) {
  this._peripheral = peripheral;
  this._services = {};
  this._characteristics = {};

  this.uuid = peripheral.uuid;

  this._peripheral.on('disconnect', this.onDisconnect.bind(this));
}

util.inherits(NobleDevice, events.EventEmitter);

NobleDevice.prototype.onDisconnect = function() {
  this.emit('disconnect');
};

NobleDevice.prototype.toString = function() {
  return JSON.stringify({
    uuid: this.uuid
  });
};

NobleDevice.prototype.connect = function(callback) {
  this._peripheral.connect(callback);
};

NobleDevice.prototype.disconnect = function(callback) {
  this._peripheral.disconnect(callback);
};

NobleDevice.prototype.discoverServicesAndCharacteristics = function(callback) {
  this._peripheral.discoverAllServicesAndCharacteristics(function(error, services, characteristics) {
    if (error === null) {
      for (var i in services) {
        var service = services[i];
        var characteristics = service.characteristics;

        var serviceUuid = service.uuid;

        this._services[serviceUuid] = service;
        this._characteristics[serviceUuid] = {};

        for (var j in characteristics) {
          var characteristic = characteristics[j];

          this._characteristics[serviceUuid][characteristic.uuid] = characteristic;
        }
      }
    }

    callback();
  }.bind(this));
};

NobleDevice.prototype.connectAndSetup = function(callback) {
  this.connect(function() {
    this.discoverServicesAndCharacteristics(callback);
  }.bind(this));
};

NobleDevice.prototype.readDataCharacteristic = function(serviceUuid, characteristicUuid, callback) {
  this._characteristics[serviceUuid][characteristicUuid].read(function(error, data) {
    callback(data);
  });
};

NobleDevice.prototype.writeDataCharacteristic = function(serviceUuid, characteristicUuid, data, callback) {
  var c = this._characteristics[serviceUuid][characteristicUuid];
  var withoutResponse = (c.properties.indexOf('writeWithoutResponse') !== -1) &&
                        (c.properties.indexOf('write') === -1);
  this._characteristics[serviceUuid][characteristicUuid].write(data, withoutResponse, function(error) {
    callback();
  });
};

NobleDevice.prototype.notifyCharacteristic = function(serviceUuid, characteristicUuid, notify, listener, callback) {
  var characteristic = this._characteristics[serviceUuid][characteristicUuid];

  characteristic.notify(notify, function(state) {
    if (notify) {
      characteristic.addListener('read', listener);
    } else {
      characteristic.removeListener('read', listener);
    }

    callback();
  });
};

NobleDevice.prototype.readStringCharacteristic = function(serviceUuid, characteristicUuid, callback) {
  this.readDataCharacteristic(serviceUuid, characteristicUuid, function(data) {
    callback(data.toString());
  });
};

NobleDevice.prototype.readUInt8Characteristic = function(serviceUuid, characteristicUuid, callback) {
  this.readDataCharacteristic(serviceUuid, characteristicUuid, function(data) {
    callback(data.readUInt8(0));
  });
};

NobleDevice.prototype.readUInt16LECharacteristic = function(serviceUuid, characteristicUuid, callback) {
  this.readDataCharacteristic(serviceUuid, characteristicUuid, function(data) {
    callback(data.readUInt16LE(0));
  });
};

NobleDevice.prototype.readFloatLECharacteristic = function(serviceUuid, characteristicUuid, callback) {
  this.readDataCharacteristic(serviceUuid, characteristicUuid, function(data) {
    callback(data.readFloatLE(0));
  });
};

NobleDevice.prototype.writeStringCharacteristic = function(serviceUuid, characteristicUuid, string, callback) {
  this.writeDataCharacteristic(serviceUuid, characteristicUuid, new Buffer(string), callback);
};

NobleDevice.prototype.readDeviceName = function(callback) {
  this.readStringCharacteristic(GENERIC_ACCESS_UUID, DEVICE_NAME_UUID, callback);
};

module.exports = NobleDevice;
<|endoftext|>


<commit_before>const cm = require('sdk/context-menu');
const _ = require('sdk/l10n').get;
const self = require('sdk/self');

const launchVideo = require('./launch-video');
const sendMetricsData = require('./send-metrics-data');

const getVimeoUrl = require('./get-vimeo-url');
const getYouTubeUrl = require('./youtube-helpers').getVideo;
const getSoundcloudUrl = require('./get-soundcloud-url');
const getUrlFnMock = require('./get-url-mock');

const playNowLabel = _('play_now');
const addToQueueLabel = _('add_to_queue');

const URLS = {
  'vimeo': ['vimeo.com/'],
  'youtube': ['youtube.com/', 'youtu.be/']
}

const menus = [];

// Given a video service name from the URLS object, return an href *= selector
// for the corresponding urls.
// Arguments:
//   videoService: a key from URLS or '*' for all domains from URLS
//   shouldEncode: (optional) encode domains if true
function getSelectors(videoService, shouldEncode) {
  let domains = [];
  if (videoService in URLS) {
    domains = URLS[videoService]
  } else if (videoService === '*') {
    domains = Object.keys(URLS).map(name => URLS[name])
                    .reduce((prev, curr) => prev.concat(curr))
  } else {
    console.error(`Error: ${videoService} missing or not supported`) // eslint-disable-line no-console
  }
  const selectors = domains.map(url => `[href*="${shouldEncode ? encodeURIComponent(url) : url}"]`)
                           .reduce((prev, curr) => `${prev}, ${curr}`)

  return selectors;
}

/* Content Scripts */

const contextMenuContentScript = `
self.on('click', function (node, data) {
  self.postMessage({
    action: data,
    url: node.href || node['data-feed-url']
  });
});`;

const contentScriptVimeoCase = `self.on('click', function (node, data) {
  self.postMessage({
    action: data,
    url: 'https://vimeo.com/' + node.href
  });
});`

const contextMenuMediaContentScript = `
self.on('click', function (node, data) {
  let host = '';
  try {
    host = new URL(node.href || node.src).host;
  }
  catch (err) {
    console.error('error parsing domain from ', url, err);
    return;
  }

  self.postMessage({
    url: node.href || node.src,
    launchUrl: window.location.href,
    domain: host,
    action: data
  });
});`;

const contextMenuSoundCloudContentScript = `
self.on('click', function (node, data) {
  // if the target element isn't the link we
  // are looking for, recurse until we find it.
  function findLinkNode(node) {
    if (node && node.href) {
      self.postMessage({
        url: node.href,
        domain: 'soundcloud.com',
        action: data
      });
    }
    else findLinkNode(node.parentNode);
  }
  findLinkNode(node);
});`;

/* get the context menu with options to play and add to queue */
function getMenu(opts) {
  menus.push(cm.Menu(Object.assign(opts, {
    label: 'Min Vid',
    items: [
      cm.Item({label: playNowLabel, data: 'play', image: self.data.url('img/Play-gray.svg')}),
      cm.Item({label: addToQueueLabel, data: 'add-to-queue', image: self.data.url('img/Add-gray.svg'),})
    ]
  })));
}

function init() {
  const onMessageVimeoHandler = (opts) => {
    launch({
      url: opts.url,
      domain: 'vimeo.com',
      getUrlFn: getVimeoUrl,
      action: opts.action
    });
  };

  const onMessageYouTubeHandler = (opts) => {
    launch({
      url: opts.url,
      domain: 'youtube.com',
      getUrlFn: getYouTubeUrl,
      action: opts.action
    });
  };

  function audioPredicateContext(context) {
    return new RegExp('^(https?:)?//*.+(.mp3|.opus|.weba|.ogg|.wav|.flac)')
      .exec(context.srcURL || context.linkURL);
  }

  function videoPredicateContext(context) {
    return new RegExp('^(https?:)?//*.+(.mp4|.ogg|.3gp|.ogv|.spx|.oga|.mkv|.webm)')
      .exec(context.srcURL || context.linkURL);
  }

  function soundcloudPredicateContext(context) {
    return new RegExp(/https:\/\/soundcloud.com\/[a-zA-Z]*(.+)\/[a-zA-Z]*(.+)/)
      .exec(context.linkURL);
  }

  /*
   * <audio> handling
   */
  getMenu({
    context: cm.PredicateContext(audioPredicateContext),
    contentScript: contextMenuMediaContentScript,
    onMessage: launch
  });

  /*
   * <video> handling
   */
  getMenu({
    context: cm.PredicateContext(videoPredicateContext),
    contentScript: contextMenuMediaContentScript,
    onMessage: launch
  });

  /*
   * Soundcloud handling
   */

  /*
   * Track links
   */
  getMenu({
    context: cm.PredicateContext(soundcloudPredicateContext),
    contentScript: contextMenuSoundCloudContentScript,
    onMessage: launch
  });

  /*
   * YouTube handling
   */
  getMenu({
    context: cm.SelectorContext(getSelectors('youtube')),
    contentScript: contextMenuContentScript,
    onMessage: onMessageYouTubeHandler
  });

  // watch page playlist video link
  getMenu({
    context: [
      cm.URLContext(['*.youtube.com']),
      cm.SelectorContext('.spf-link.playlist-video')
    ],
    contentScript: contextMenuContentScript,
    onMessage: onMessageYouTubeHandler
  });

  // Playlist page video links
  getMenu({
    context: [
      cm.URLContext(['*.youtube.com']),
      cm.SelectorContext('.pl-video-title-link')
    ],
    contentScript: contextMenuContentScript,
    onMessage: onMessageYouTubeHandler
  });

  // Thumbnail
  getMenu({
    context: [
      cm.URLContext(['*.youtube.com']),
      cm.SelectorContext('.yt-lockup-thumbnail > .yt-uix.sessionlink')
    ],
    contentScript: contextMenuContentScript,
    onMessage: onMessageYouTubeHandler
  });

  // Home page thumbnails
  getMenu({
    context: [
      cm.URLContext(['*.youtube.com']),
      cm.SelectorContext('.yt-lockup-thumbnail')
    ],
    contentScript: `
    self.on('click', function (node, data) {
      node = node.querySelector('a');
      self.postMessage({
        action: data,
        url: node.href || node['data-feed-url']
      });
    });`,
    onMessage: onMessageYouTubeHandler
  });

  // Title Link
  getMenu({
    context: [
      cm.URLContext(['*.youtube.com']),
      cm.SelectorContext('.yt-lockup-content .yt-lockup-title .yt-uix-sessionlink')
    ],
    contentScript: contextMenuContentScript,
    onMessage: onMessageYouTubeHandler
  });

  // Related Title link
  getMenu({
    context: [
      cm.URLContext(['*.youtube.com']),
      cm.SelectorContext('.watch-sidebar-section .yt-uix-sessionlink')
    ],
    contentScript: contextMenuContentScript,
    onMessage: onMessageYouTubeHandler
  });

  /*
   * Vimeo handling
   */

  getMenu({
    context: [
      cm.URLContext(['*.vimeo.com']),
      cm.SelectorContext('[class*="faux_player"]')
    ],
    contentScript: `self.on('click', function (node, data) {
      self.postMessage({
        action: data,
        url: 'https://vimeo.com/' + node.getAttribute('data-clip-id')
      });
    });`,
    onMessage: onMessageVimeoHandler
  });

  getMenu({
    context: cm.SelectorContext(getSelectors('vimeo')),
    contentScript: contextMenuContentScript,
    onMessage: onMessageVimeoHandler
  });

  getMenu({
    context: [
      cm.URLContext(['*.vimeo.com']),
      cm.SelectorContext('.contextclip-header a')
    ],
    contentScript: contentScriptVimeoCase,
    onMessage: onMessageVimeoHandler
  });

  getMenu({
    context: [
      cm.URLContext(['*.vimeo.com']),
      cm.SelectorContext('.iris_video-vital__overlay.iris_link-box')
    ],
    contentScript: contentScriptVimeoCase,
    onMessage: onMessageVimeoHandler
  });

  getMenu({
    context: [
      cm.URLContext(['*.vimeo.com']),
      cm.SelectorContext('.contextclip-img-thumb')
    ],
    contentScript: contentScriptVimeoCase,
    onMessage: onMessageVimeoHandler
  });

  /*
   * google.com search results handling
   */
  getMenu({
    context: [
      cm.URLContext(['*.google.com']),
      cm.SelectorContext(getSelectors('*', true)),
    ],
    contentScript: contextMenuContentScript,
    onMessage: function(opts) {
      const regex = /url=(https?[^;]*)/.exec(opts.url)[1];
      const decoded = decodeURIComponent(regex).split('&usg')[0];
      let getUrlFn, domain;
      if (decoded.indexOf('youtube.com' || 'youtu.be') > -1) {
        getUrlFn = getYouTubeUrl;
        domain = 'youtube.com';
      } else if (decoded.indexOf('vimeo.com')  > -1) {
        getUrlFn = getVimeoUrl;
        domain = 'vimeo.com';
      } else if (domain && getUrlFn) {
        launch({
          url: decoded,
          domain: domain,
          getUrlFn: getUrlFn,
          action: opts.action
        });
      }
    }
  });
}

function launch(opts) {
  // getUrlFn cannot be in a self.postMessage call, so for
  // soundcloud we must put it here

  if (!opts.getUrlFn) {
    if (opts.domain === 'soundcloud.com') opts.getUrlFn = getSoundcloudUrl;
    else opts.getUrlFn = getUrlFnMock;
  }

  sendMetricsData({object: 'contextmenu', method: 'activate', domain: opts.domain});
  opts.method = opts.action;
  launchVideo(opts);
}

function destroy() {
  menus.forEach(m => m.destroy());
}

module.exports = {
  init: init,
  destroy: destroy
};
<commit_msg>Prevent context menu option on some unwanted yt links (#795)<commit_after>const cm = require('sdk/context-menu');
const _ = require('sdk/l10n').get;
const self = require('sdk/self');

const launchVideo = require('./launch-video');
const sendMetricsData = require('./send-metrics-data');

const getVimeoUrl = require('./get-vimeo-url');
const getYouTubeUrl = require('./youtube-helpers').getVideo;
const getSoundcloudUrl = require('./get-soundcloud-url');
const getUrlFnMock = require('./get-url-mock');

const playNowLabel = _('play_now');
const addToQueueLabel = _('add_to_queue');

const URLS = {
  'vimeo': ['vimeo.com/'],
  'youtube': ['youtube.com/watch?', 'youtu.be/watch?']
}

const menus = [];

// Given a video service name from the URLS object, return an href *= selector
// for the corresponding urls.
// Arguments:
//   videoService: a key from URLS or '*' for all domains from URLS
//   shouldEncode: (optional) encode domains if true
function getSelectors(videoService, shouldEncode) {
  let domains = [];
  if (videoService in URLS) {
    domains = URLS[videoService]
  } else if (videoService === '*') {
    domains = Object.keys(URLS).map(name => URLS[name])
                    .reduce((prev, curr) => prev.concat(curr))
  } else {
    console.error(`Error: ${videoService} missing or not supported`) // eslint-disable-line no-console
  }
  const selectors = domains.map(url => `[href*="${shouldEncode ? encodeURIComponent(url) : url}"]`)
                           .reduce((prev, curr) => `${prev}, ${curr}`)

  return selectors;
}

/* Content Scripts */

const contextMenuContentScript = `
self.on('click', function (node, data) {
  self.postMessage({
    action: data,
    url: node.href || node['data-feed-url']
  });
});`;

const contentScriptVimeoCase = `self.on('click', function (node, data) {
  self.postMessage({
    action: data,
    url: 'https://vimeo.com/' + node.href
  });
});`

const contextMenuMediaContentScript = `
self.on('click', function (node, data) {
  let host = '';
  try {
    host = new URL(node.href || node.src).host;
  }
  catch (err) {
    console.error('error parsing domain from ', url, err);
    return;
  }

  self.postMessage({
    url: node.href || node.src,
    launchUrl: window.location.href,
    domain: host,
    action: data
  });
});`;

const contextMenuSoundCloudContentScript = `
self.on('click', function (node, data) {
  // if the target element isn't the link we
  // are looking for, recurse until we find it.
  function findLinkNode(node) {
    if (node && node.href) {
      self.postMessage({
        url: node.href,
        domain: 'soundcloud.com',
        action: data
      });
    }
    else findLinkNode(node.parentNode);
  }
  findLinkNode(node);
});`;

/* get the context menu with options to play and add to queue */
function getMenu(opts) {
  menus.push(cm.Menu(Object.assign(opts, {
    label: 'Min Vid',
    items: [
      cm.Item({label: playNowLabel, data: 'play', image: self.data.url('img/Play-gray.svg')}),
      cm.Item({label: addToQueueLabel, data: 'add-to-queue', image: self.data.url('img/Add-gray.svg'),})
    ]
  })));
}

function init() {
  const onMessageVimeoHandler = (opts) => {
    launch({
      url: opts.url,
      domain: 'vimeo.com',
      getUrlFn: getVimeoUrl,
      action: opts.action
    });
  };

  const onMessageYouTubeHandler = (opts) => {
    launch({
      url: opts.url,
      domain: 'youtube.com',
      getUrlFn: getYouTubeUrl,
      action: opts.action
    });
  };

  function audioPredicateContext(context) {
    return new RegExp('^(https?:)?//*.+(.mp3|.opus|.weba|.ogg|.wav|.flac)')
      .exec(context.srcURL || context.linkURL);
  }

  function videoPredicateContext(context) {
    return new RegExp('^(https?:)?//*.+(.mp4|.ogg|.3gp|.ogv|.spx|.oga|.mkv|.webm)')
      .exec(context.srcURL || context.linkURL);
  }

  function soundcloudPredicateContext(context) {
    return new RegExp(/https:\/\/soundcloud.com\/[a-zA-Z]*(.+)\/[a-zA-Z]*(.+)/)
      .exec(context.linkURL);
  }

  /*
   * <audio> handling
   */
  getMenu({
    context: cm.PredicateContext(audioPredicateContext),
    contentScript: contextMenuMediaContentScript,
    onMessage: launch
  });

  /*
   * <video> handling
   */
  getMenu({
    context: cm.PredicateContext(videoPredicateContext),
    contentScript: contextMenuMediaContentScript,
    onMessage: launch
  });

  /*
   * Soundcloud handling
   */

  /*
   * Track links
   */
  getMenu({
    context: cm.PredicateContext(soundcloudPredicateContext),
    contentScript: contextMenuSoundCloudContentScript,
    onMessage: launch
  });

  /*
   * YouTube handling
   */
  getMenu({
    context: cm.SelectorContext(getSelectors('youtube')),
    contentScript: contextMenuContentScript,
    onMessage: onMessageYouTubeHandler
  });

  // watch page playlist video link
  getMenu({
    context: [
      cm.URLContext(['*.youtube.com']),
      cm.SelectorContext('.spf-link.playlist-video')
    ],
    contentScript: contextMenuContentScript,
    onMessage: onMessageYouTubeHandler
  });

  // Playlist page video links
  getMenu({
    context: [
      cm.URLContext(['*.youtube.com']),
      cm.SelectorContext('.pl-video-title-link')
    ],
    contentScript: contextMenuContentScript,
    onMessage: onMessageYouTubeHandler
  });

  // Thumbnail
  getMenu({
    context: [
      cm.URLContext(['*.youtube.com']),
      cm.SelectorContext('.yt-lockup-thumbnail > .yt-uix.sessionlink')
    ],
    contentScript: contextMenuContentScript,
    onMessage: onMessageYouTubeHandler
  });

  // Home page thumbnails

  // TODO: This allows channel links to be sent to min vid, which we
  // do not want.
  getMenu({
    context: [
      cm.URLContext(['*.youtube.com']),
      cm.SelectorContext('.yt-lockup-thumbnail')
    ],
    contentScript: `
    self.on('click', function (node, data) {
      node = node.querySelector('a');
      self.postMessage({
        action: data,
        url: node.href || node['data-feed-url']
      });
    });`,
    onMessage: onMessageYouTubeHandler
  });

  // Title Link
  getMenu({
    context: [
      cm.URLContext(['*.youtube.com']),
      cm.SelectorContext('.yt-lockup-content .yt-lockup-title .yt-uix-sessionlink')
    ],
    contentScript: contextMenuContentScript,
    onMessage: onMessageYouTubeHandler
  });

  // Related Title link
  getMenu({
    context: [
      cm.URLContext(['*.youtube.com']),
      cm.SelectorContext('.watch-sidebar-section .yt-uix-sessionlink')
    ],
    contentScript: contextMenuContentScript,
    onMessage: onMessageYouTubeHandler
  });

  /*
   * Vimeo handling
   */

  getMenu({
    context: [
      cm.URLContext(['*.vimeo.com']),
      cm.SelectorContext('[class*="faux_player"]')
    ],
    contentScript: `self.on('click', function (node, data) {
      self.postMessage({
        action: data,
        url: 'https://vimeo.com/' + node.getAttribute('data-clip-id')
      });
    });`,
    onMessage: onMessageVimeoHandler
  });

  getMenu({
    context: cm.SelectorContext(getSelectors('vimeo')),
    contentScript: contextMenuContentScript,
    onMessage: onMessageVimeoHandler
  });

  getMenu({
    context: [
      cm.URLContext(['*.vimeo.com']),
      cm.SelectorContext('.contextclip-header a')
    ],
    contentScript: contentScriptVimeoCase,
    onMessage: onMessageVimeoHandler
  });

  getMenu({
    context: [
      cm.URLContext(['*.vimeo.com']),
      cm.SelectorContext('.iris_video-vital__overlay.iris_link-box')
    ],
    contentScript: contentScriptVimeoCase,
    onMessage: onMessageVimeoHandler
  });

  getMenu({
    context: [
      cm.URLContext(['*.vimeo.com']),
      cm.SelectorContext('.contextclip-img-thumb')
    ],
    contentScript: contentScriptVimeoCase,
    onMessage: onMessageVimeoHandler
  });

  /*
   * google.com search results handling
   */
  getMenu({
    context: [
      cm.URLContext(['*.google.com']),
      cm.SelectorContext(getSelectors('*', true)),
    ],
    contentScript: contextMenuContentScript,
    onMessage: function(opts) {
      const regex = /url=(https?[^;]*)/.exec(opts.url)[1];
      const decoded = decodeURIComponent(regex).split('&usg')[0];
      let getUrlFn, domain;
      if (decoded.indexOf('youtube.com' || 'youtu.be') > -1) {
        getUrlFn = getYouTubeUrl;
        domain = 'youtube.com';
      } else if (decoded.indexOf('vimeo.com')  > -1) {
        getUrlFn = getVimeoUrl;
        domain = 'vimeo.com';
      } else if (domain && getUrlFn) {
        launch({
          url: decoded,
          domain: domain,
          getUrlFn: getUrlFn,
          action: opts.action
        });
      }
    }
  });
}

function launch(opts) {
  // getUrlFn cannot be in a self.postMessage call, so for
  // soundcloud we must put it here

  if (!opts.getUrlFn) {
    if (opts.domain === 'soundcloud.com') opts.getUrlFn = getSoundcloudUrl;
    else opts.getUrlFn = getUrlFnMock;
  }

  sendMetricsData({object: 'contextmenu', method: 'activate', domain: opts.domain});
  opts.method = opts.action;
  launchVideo(opts);
}

function destroy() {
  menus.forEach(m => m.destroy());
}

module.exports = {
  init: init,
  destroy: destroy
};
<|endoftext|>


<commit_before>/*globals require, module*/
var cp = require('child_process'),
  events = require('events'),
  fs = require('fs'),
  readline = require('readline'),
  spawn = cp.spawn
  validUrl = require('valid-url');

function Mplayer(path){
  this.childProc = null;
  this.file = "";
  this.rl = null;
  this.playing = false;
  this.volume = 100;
  if(typeof path !== 'undefined')
    this.setFile(path);

  events.EventEmitter.call(this);

  cp.exec('mplayer', function(err, stdout, stdin){
    if(err)
      throw new Error("Mplayer encountered an error or isn't installed.");
  });
}

Object.setPrototypeOf(Mplayer.prototype, events.EventEmitter.prototype);

Mplayer.prototype.play = function(opts) {
  if(this.file !== null){
    if(opts && opts.volume)
      this.volume = opts.volume;
    var args = ['-slave', '-quiet', '--volume='+this.volume, this.file],
    that = this;

    this.childProc = spawn('mplayer', args);
    this.playing = true;

    if(opts && opts.loop)
      this.setLoop(opts.loop);

    this.childProc.on('error', function(error){
      that.emit('error');
    });

    this.childProc.on('exit', function(code, sig){
      if(code === 0 && sig === null){
        that.playing = false;
        that.emit('end');
      }
    });

    this.rl = readline.createInterface({
      input: this.childProc.stdout,
      output: this.childProc.stdin
    });
  }
};

Mplayer.prototype.checkPlaying = function(){
  return this.playing;
};

Mplayer.prototype.quit = function() {
  if(this.childProc !== null){
    this.playing = false;
    this.childProc.stdin.write('quit\n');
  }
};

Mplayer.prototype.getPercentPosition = function(callback) {
  if(this.childProc !== null){
    this.rl.question("get_percent_pos\n", function(answer) {
      callback(answer.split('=')[1]);
    });
  }
};

Mplayer.prototype.stop = function() {
  if(this.childProc !== null){
    this.childProc.stdin.write('stop\n');
    this.playing = false;
  }
};

Mplayer.prototype.pause = function() {
  if(this.childProc !== null){
    this.childProc.stdin.write('pause\n');
  }
};

Mplayer.prototype.mute = function() {
  if(this.childProc !== null){
    this.childProc.stdin.write('mute\n');
  }
};

Mplayer.prototype.setVolume = function(volume) {
  if(this.childProc !== null){
    this.volume = volume;
    this.childProc.stdin.write('volume ' + volume + ' 1\n');
  }
};

Mplayer.prototype.seek = function(sec) {
  if(this.childProc !== null){
    this.childProc.stdin.write('seek ' + sec + ' 2\n');
  }
};

Mplayer.prototype.setLoop = function(times) {
  if(this.childProc !== null){
    this.childProc.stdin.write('loop ' + times + '\n');
  }
};

Mplayer.prototype.setSpeed = function(speed) {
  if(this.childProc !== null){
    this.childProc.stdin.write('speed_set ' + speed + '\n');
  }
};

Mplayer.prototype.setFile = function(path) {
  if(this.childProc){
    this.quit();
  }
  if(fs.existsSync(path) || validUrl.isUri(path))
    this.file = path;
  else
    throw new Error("File '" + path + "' not found!");
};

Mplayer.prototype.getTimeLength = function(callback) {
  if(this.childProc !== null){
    this.rl.question("get_time_length\n", function(answer) {
      callback(answer.split('=')[1]);
    });
  }
};

Mplayer.prototype.getTimePosition = function(callback) {
  if(this.childProc !== null){
    var that = this;
    this.rl.question("get_time_pos\n", function(answer) {
      var splittedAns = answer.split('=');

      if (splittedAns[0]=='ANS_TIME_POSITION'){
        callback(splittedAns[1]);
      }
      else{
        // Try again :(
        that.getTimePosition(callback);
      }
    });
  }
};

Mplayer.prototype.getVolume = function(callback) {
  return callback(this.volume);
}

module.exports = Mplayer;<commit_msg>Addtionnal Mplayer args passing added<commit_after>/*globals require, module*/
var cp = require('child_process'),
  events = require('events'),
  fs = require('fs'),
  readline = require('readline'),
  spawn = cp.spawn
  validUrl = require('valid-url');

function Mplayer(path, addArgs){
  this.childProc = null;
  this.file = "";
  this.rl = null;
  this.playing = false;
  this.volume = 100;
  if(typeof path !== 'undefined')
    this.setFile(path);

  events.EventEmitter.call(this);

  cp.exec('mplayer', function(err, stdout, stdin){
    if(err)
      throw new Error("Mplayer encountered an error or isn't installed.");
  });
}

Object.setPrototypeOf(Mplayer.prototype, events.EventEmitter.prototype);

Mplayer.prototype.play = function(opts) {
  if(this.file !== null){
    if(opts && opts.volume)
      this.volume = opts.volume;

    var args = ['-slave', '-quiet', '--volume='+this.volume, this.file],
      that = this;

    if(opts && opts.additionnalArgs)
      args = args.concat(opts.additionnalArgs);

    this.childProc = spawn('mplayer', args);
    this.playing = true;

    if(opts && opts.loop)
      this.setLoop(opts.loop);

    this.childProc.on('error', function(error){
      that.emit('error');
    });

    this.childProc.on('exit', function(code, sig){
      if(code === 0 && sig === null){
        that.playing = false;
        that.emit('end');
      }
    });

    this.rl = readline.createInterface({
      input: this.childProc.stdout,
      output: this.childProc.stdin
    });
  }
};

Mplayer.prototype.checkPlaying = function(){
  return this.playing;
};

Mplayer.prototype.quit = function() {
  if(this.childProc !== null){
    this.playing = false;
    this.childProc.stdin.write('quit\n');
  }
};

Mplayer.prototype.getPercentPosition = function(callback) {
  if(this.childProc !== null){
    this.rl.question("get_percent_pos\n", function(answer) {
      callback(answer.split('=')[1]);
    });
  }
};

Mplayer.prototype.stop = function() {
  if(this.childProc !== null){
    this.childProc.stdin.write('stop\n');
    this.playing = false;
  }
};

Mplayer.prototype.pause = function() {
  if(this.childProc !== null){
    this.childProc.stdin.write('pause\n');
  }
};

Mplayer.prototype.mute = function() {
  if(this.childProc !== null){
    this.childProc.stdin.write('mute\n');
  }
};

Mplayer.prototype.setVolume = function(volume) {
  if(this.childProc !== null){
    this.volume = volume;
    this.childProc.stdin.write('volume ' + volume + ' 1\n');
  }
};

Mplayer.prototype.seek = function(sec) {
  if(this.childProc !== null){
    this.childProc.stdin.write('seek ' + sec + ' 2\n');
  }
};

Mplayer.prototype.setLoop = function(times) {
  if(this.childProc !== null){
    this.childProc.stdin.write('loop ' + times + '\n');
  }
};

Mplayer.prototype.setSpeed = function(speed) {
  if(this.childProc !== null){
    this.childProc.stdin.write('speed_set ' + speed + '\n');
  }
};

Mplayer.prototype.setFile = function(path) {
  if(this.childProc){
    this.quit();
  }
  if(fs.existsSync(path) || validUrl.isUri(path))
    this.file = path;
  else
    throw new Error("File '" + path + "' not found!");
};

Mplayer.prototype.getTimeLength = function(callback) {
  if(this.childProc !== null){
    this.rl.question("get_time_length\n", function(answer) {
      callback(answer.split('=')[1]);
    });
  }
};

Mplayer.prototype.getTimePosition = function(callback) {
  if(this.childProc !== null){
    var that = this;
    this.rl.question("get_time_pos\n", function(answer) {
      var splittedAns = answer.split('=');

      if (splittedAns[0]=='ANS_TIME_POSITION'){
        callback(splittedAns[1]);
      }
      else{
        // Try again :(
        that.getTimePosition(callback);
      }
    });
  }
};

Mplayer.prototype.getVolume = function(callback) {
  return callback(this.volume);
}

module.exports = Mplayer;<|endoftext|>