<commit_before>/**
 * Map constructor
 *
 * @class Roguelike.Map
 * @classdesc The object that holds all the rooms, corridors and tiles
 *
 * @param {Roguelike.Game} game - Reference to the current game object
 */
Roguelike.Map = function(game) {

	/**
	 * @property {Roguelike.Game} game - Reference to the current game object
	 */
	this.game = game;

	/**
	 * @property {Roguelike.Group} entities - Holds all the entities of this game
	 */
	this.entities = null;

	/**
	 * @property {Object} settings - The empty settings object
	 */
	this.settings = {};

	/**
	 * @property {Array} rooms - An array that holds all room objects
	 */
	this.rooms = [];

	/**
	 * @property {Array} tiles - An array that holds all tile objects
	 */
	this.tiles = [];

	/**
	 * @property {Array} objects - An array that holds all objects that are on the map
	 */
	this.objects = [];

	/**
	 * @property {Roguelike.Vector2} entrance - An object that holds the position of the entrance of this map
	 */
	this.entrance = null;

	/**
	 * @property {Roguelike.Vector2} exit - An object that holds the position of the exit of this map
	 */
	this.exit = null;

	this.allRooms = [];

	this.mediumRooms = [];

	/**
	 * @property {Array} objects - An array that holds all objects that are on the map
	 */
	this.roomsToExit = [];

	//Initialize itself
	this.initialize();

};

Roguelike.Map.prototype = {

	/**
	 * Initialize the layout of the map, filling it with empty tiles
	 * @protected
	 */
	initialize: function() {

		//TODO: Make this dynamic on the depth of the dungeon, this will allow the generator to make more complicated maps the deeper you go
		//Define settings
		this.settings = {
			tilesX: 60, //The number of horizontal tiles on this map
			tilesY: 40, //The number of vertical tiles on this map
			tileSize: 16, //The width and height of a single tile
			maxRooms: 15, //The maximum number of rooms on this map
			minRoomWidth: 5, //The minimum width of a single room
			maxRoomWidth: 8, //The maximum width of a single room
			minRoomHeight: 5, //The minimum height of a single room
			maxRoomHeight: 8, //The maximum height of a single room
			roomSpacing: 1, //The length of a corridor, 0 for no corridors
			maxMainRooms: 15,
			maxMediumRooms: 5
		};

		//Loop through every horizontal row
		for(var x = 0; x < this.settings.tilesX; x++) {

			//Initialize this row
			this.tiles[x] = [];

			//Loop through every vertical row
			for(var y = 0; y < this.settings.tilesY; y++) {

				//Initialize this position by setting it to zero, and blocking light
				this.tiles[x][y] = new Roguelike.Tile(0, true, 0, 0);

			}

		}

	},

	/**
	 * Function that returns an array with only the tiletypes of every position
	 * Used for EasyStar Pathfinding
	 * @protected
	 *
	 * @return {Array} Array with only the tiletypes of every position on the map
	 */
	typeList: function() {

		//Define variables
		var mapTypeList = [];

		//Loop through every horizontal row
		for(var y = 0; y < this.settings.tilesY; y++) {

			//Initialize this row
			mapTypeList.push([]);

			//Loop through every vertical row
			for(var x = 0; x < this.settings.tilesX; x++) {

				//Initialize this position by setting it to zero, and blocking light
				mapTypeList[y][x] = (this.tiles[x][y].type);

			}

		}

		//Return the array with Y X coordinates of every tiletype
		return mapTypeList;

	},

	/**
	 * Check if a single room overlaps a room that is already on the map and if it's inside the maps boundaries
	 * @protected
	 *
	 * @param {Roguelike.Room} room - The room object that has to be checked
	 *
	 * @return {Boolean} True when the room intersects with an existing room, false when it's not intersecting
	 */
	roomFits: function(room) {

		//Check if the room fits inside the boundaries of this map. If not we can't place it.
		if(room.x1 < 1 || room.x2 > this.settings.tilesX - 1 || room.y1 < 1 || room.y2 > this.settings.tilesY - 1) {

			return false;

		}

		//Loop through every room in the list
		for(var i = 0; i < this.allRooms.length; i++) {

			//Check if the room intersects with the current room
			if(room.x1 <= this.allRooms[i].x2 && room.x2 >= this.allRooms[i].x1 && room.y1 <= this.allRooms[i].y2 && room.y2 >= this.allRooms[i].y1) {

				return false;

			}

		}
		//If the room doesn't intersect another room, return true
		return true;

	}

};
<commit_msg>Documented some properties of the Map object.<commit_after>/**
 * Map constructor
 *
 * @class Roguelike.Map
 * @classdesc The object that holds all the rooms, corridors and tiles
 *
 * @param {Roguelike.Game} game - Reference to the current game object
 */
Roguelike.Map = function(game) {

	/**
	 * @property {Roguelike.Game} game - Reference to the current game object
	 */
	this.game = game;

	/**
	 * @property {Roguelike.Group} entities - Holds all the entities of this game
	 */
	this.entities = null;

	/**
	 * @property {Object} settings - The empty settings object
	 */
	this.settings = {};

	/**
	 * @property {Array} rooms - An array that holds all room objects
	 */
	this.rooms = [];

	/**
	 * @property {Array} tiles - An array that holds all tile objects
	 */
	this.tiles = [];

	/**
	 * @property {Array} objects - An array that holds all objects that are on the map
	 */
	this.objects = [];

	/**
	 * @property {Roguelike.Vector2} entrance - An object that holds the position of the entrance of this map
	 */
	this.entrance = null;

	/**
	 * @property {Roguelike.Vector2} exit - An object that holds the position of the exit of this map
	 */
	this.exit = null;

	/**
	 * @property {Array} allRooms - An array that holds all the rooms on this map
	 */
	this.allRooms = [];

	/**
	 * @property {Array} mediumRooms - An array that holds all the medium rooms on this map
	 */
	this.mediumRooms = [];

	/**
	 * @property {Array} roomsToExit - An array that holds all the rooms that go from the entrance to the exit on this map
	 */
	this.roomsToExit = [];

	//Initialize itself
	this.initialize();

};

Roguelike.Map.prototype = {

	/**
	 * Initialize the layout of the map, filling it with empty tiles
	 * @protected
	 */
	initialize: function() {

		//TODO: Make this dynamic on the depth of the dungeon, this will allow the generator to make more complicated maps the deeper you go
		//Define settings
		this.settings = {
			tilesX: 60, //The number of horizontal tiles on this map
			tilesY: 40, //The number of vertical tiles on this map
			tileSize: 16, //The width and height of a single tile
			maxRooms: 15, //The maximum number of rooms on this map
			minRoomWidth: 5, //The minimum width of a single room
			maxRoomWidth: 8, //The maximum width of a single room
			minRoomHeight: 5, //The minimum height of a single room
			maxRoomHeight: 8, //The maximum height of a single room
			roomSpacing: 1, //The length of a corridor, 0 for no corridors
			maxMainRooms: 15,
			maxMediumRooms: 5
		};

		//Loop through every horizontal row
		for(var x = 0; x < this.settings.tilesX; x++) {

			//Initialize this row
			this.tiles[x] = [];

			//Loop through every vertical row
			for(var y = 0; y < this.settings.tilesY; y++) {

				//Initialize this position by setting it to zero, and blocking light
				this.tiles[x][y] = new Roguelike.Tile(0, true, 0, 0);

			}

		}

	},

	/**
	 * Function that returns an array with only the tiletypes of every position
	 * Used for EasyStar Pathfinding
	 * @protected
	 *
	 * @return {Array} Array with only the tiletypes of every position on the map
	 */
	typeList: function() {

		//Define variables
		var mapTypeList = [];

		//Loop through every horizontal row
		for(var y = 0; y < this.settings.tilesY; y++) {

			//Initialize this row
			mapTypeList.push([]);

			//Loop through every vertical row
			for(var x = 0; x < this.settings.tilesX; x++) {

				//Initialize this position by setting it to zero, and blocking light
				mapTypeList[y][x] = (this.tiles[x][y].type);

			}

		}

		//Return the array with Y X coordinates of every tiletype
		return mapTypeList;

	},

	/**
	 * Check if a single room overlaps a room that is already on the map and if it's inside the maps boundaries
	 * @protected
	 *
	 * @param {Roguelike.Room} room - The room object that has to be checked
	 *
	 * @return {Boolean} True when the room intersects with an existing room, false when it's not intersecting
	 */
	roomFits: function(room) {

		//Check if the room fits inside the boundaries of this map. If not we can't place it.
		if(room.x1 < 1 || room.x2 > this.settings.tilesX - 1 || room.y1 < 1 || room.y2 > this.settings.tilesY - 1) {

			return false;

		}

		//Loop through every room in the list
		for(var i = 0; i < this.allRooms.length; i++) {

			//Check if the room intersects with the current room
			if(room.x1 <= this.allRooms[i].x2 && room.x2 >= this.allRooms[i].x1 && room.y1 <= this.allRooms[i].y2 && room.y2 >= this.allRooms[i].y1) {

				return false;

			}

		}
		//If the room doesn't intersect another room, return true
		return true;

	}

};
<|endoftext|>


<commit_before>/**
 * Add-on for the Suggest widget which allows multiple values to be selected and displayed in a list. It requires an existing input and a Suggest instance already bound to the input.
 */
var XWiki = (function (XWiki) {
  var widgets = XWiki.widgets = XWiki.widgets || {};
  widgets.SuggestPicker = Class.create({

  options : {
    // Display the internal value?
    'showKey' : false,
    // If there's extra information for each item (the 'info' field), display it as a tooltip?
    'showTooltip' : false,
    // Show a delete icon next to each selected entry?
    'showDeleteTool' : true,
    // Should the list of selected values be sortable? Requires scriptaculous/dragdrop.js to be available.
    'enableSort' : true,
    // Show a "delete all" button after the list of suggestions?
    'showClearTool' : true,
    // Which type of input should be used for the selected values? Another good option is "checkbox"
    'inputType': 'hidden',
    // The element used as a reference insertion point. By default the enhanced input is used. Either a CSS selector or an actual element can be used.
    'listInsertionElement' : null,
    // Where should the list of selected items be placed? Valid options are 'before', 'top', 'bottom', 'after'.
    'listInsertionPosition' : 'after',
    // Accept free text, i.e. text that was entered by the user but didn't match anything on the server (no suggestions)?
    'acceptFreeText' : false,
    // Optional callback method called whenever a new item has been added
    'onItemAdded' : Prototype.emptyFunction,
    // The character used to separate the multiple values in the initial content of the target text input.
    'separator' : ','
  },
  /**
   * Constructor method.
   *
   * @param element the DOM <input> Element to bind to
   * @param suggest the instance of XWiki.widgets.Suggest already bound to the input
   * @param options additional configuration of the picker; see the {@link #options} field for possible options to specify
   */
  initialize: function(element, suggest, options) {
    // Pre-bind callback functions
    this.removeItem = this.removeItem.bindAsEventListener(this);
    this.checkboxChanged = this.checkboxChanged.bindAsEventListener(this);

    // Process options
    this.options = Object.extend(Object.clone(this.options), options || { });
    this.input = element;
    this.suggest = suggest;
    this.inputName = this.input.name;
    if (!this.options.acceptFreeText) {
      this.input.name = this.input.name + "__suggested";
    } else {
      this.input.addClassName("accept-value");
    }
    this.suggest.options.callback = this.acceptSuggestion.bind(this);

    // Create the list element which will hold the accepted elements
    this.list = new Element('ul', {'class' : 'accepted-suggestions'});
    var listInsertionElement;
    if (this.options.listInsertionElement) {
      if (typeof(this.options.listInsertionElement) === "string") {
        listInsertionElement = this.input.up().down(this.options.listInsertionElement);
      } else {
        listInsertionElement = this.options.listInsertionElement;
      }
    }
    if (!listInsertionElement) {
      listInsertionElement = this.input;
    }
    var insertion = {};
    insertion[this.options.listInsertionPosition] = this.list;
    listInsertionElement.insert(insertion);

    if (this.options.showClearTool) {
      this.clearTool = new Element('a', {
        href: '#clearSelection',
        'class' : 'clear-tool',
        title : "$msg.get('core.widgets.suggestPicker.deleteAll.tooltip')"
      }).update("$msg.get('core.widgets.suggestPicker.deleteAll')");
      this.clearTool.hide().observe('click', this.clearAcceptedList.bindAsEventListener(this));
      this.list.insert({'after': this.clearTool});
    }

    this.initializeSelection();
  },

  /**
   * Splits the initial content of the target text input using the configured separator and adds all the values to the
   * list of accepted suggestions.
   */
  initializeSelection: function() {
    this.input.readonly = true;
    this.loadSelectedValue(this.input.value.split(this.options.separator), 0);
  },

  /**
   * Loads suggestions for the selected value in order to display it properly.
   */
  loadSelectedValue: function(values, index) {
    if (index >= values.length) {
      this.input.clear();
      this.input.readonly = false;
      return;
    }
    // Look for the selected value in all the sources.
    var found = false;
    var sourceIndex = 0;
    this.input.value = values[index];
    this.suggest.doAjaxRequests(-1, {
      onSuccess: function(response) {
        if (found) return;
        var suggestions = this.suggest.parseResponse(response, this.suggest.sources[sourceIndex]) || [];
        for (var i = 0; i < suggestions.length; i++) {
          if (this.matchesSelectedValue(values[index], suggestions[i])) {
            found = true;
            this.addItem(suggestions[i]);
            return;
          }
        }
      }.bind(this),
      onComplete: function(response) {
        response.request.options.defaultValues.onComplete(response);
        if (++sourceIndex >= this.suggest.sources.length) {
          if (!found) this.addItem(this.getDefaultSuggestion(values[index]));
          // Load the next selected value.
          this.loadSelectedValue(values, index + 1);
        }
      }.bind(this)
    });
  },

  /**
   * @return {@code true} if the given suggestion matches perfectly the specified selected value, {@code false} otherwise
   */
  matchesSelectedValue: function(value, suggestion) {
    return value == suggestion.value;
  },

  /**
   * @return default suggestion data for when a selected value is not found in any of the configured sources
   */
  getDefaultSuggestion: function(value) {
    return {id: value, value: value};
  },

  /**
   * Callback called when the user accepted an item from the Suggest.
   *
   * @param suggestion the accepted suggestion, as an object with id, value and info values
   */
  acceptSuggestion : function(suggestion) {
    if (!this.acceptAlreadyAddedItem(suggestion.id || suggestion.value)) {
      this.addItem(suggestion);
    }
    this.input.value = "";
  },

  /**
   * Callback called when the user deleted an item from the list of accepted items.
   *
   * @param event the click event fired by the browser
   */
  removeItem : function(event) {
    var item = event.findElement('li');
    item.remove();
    this.notifySelectionChange(item);
    this.updateListTools();
    this.input.activate();
  },

  /**
   * Remove all the accepted items when the "remove all" button is clicked.
   */
  clearAcceptedList : function (event) {
    event && event.stop();
    this.list.update("");
    this.notifySelectionChange();
    this.updateListTools();
    this.input.activate();
  },

  /**
   * Callback called when the user checked or unchecked an item from the list of accepted items.
   *
   * @param event the click event fired by the browser
   */
  checkboxChanged : function(event) {
    var item = event.findElement('li');
    this.notifySelectionChange(item);
  },

  /**
   * Check if an item has been previously accepted or was displayed from the start as an unchecked checkbox, but might be unchecked, and re-enable it.
   *
   * @param key the key of the item to look for
   * @return true if the item existed and has been re-enabled, false otherwise
   */
  acceptAlreadyAddedItem : function (key) {
    var input = $(this.getInputId(key));
    if (input) {
      input.checked = true;
      this.notifySelectionChange(input.up('li') || input);
      return true;
    }
    return false;
  },

  /**
   * Add a new item in the list of accepted items.
   *
   * @param suggestion the suggestion object received from the Suggest widget
   */
  addItem : function(suggestion) {
    if (!suggestion) {
      return;
    }
    var listItem = this.displayItem(suggestion);
    this.list.insert(listItem);
    this.options.onItemAdded(listItem.down('input'));
    this.notifySelectionChange(listItem);
    this.updateListTools();
  },

  /**
   * Displays a selected item.
   */
  displayItem: function(suggestion) {
    var itemInput = this.createItemInput(suggestion);
    var listItem = new Element("li");
    var displayedValue = new Element('label', {'for' : itemInput.id}).insert({'bottom' : itemInput});
    // If the key should be displayed, insert it
    if (this.options.showKey) {
      displayedValue.insert({'bottom' : new Element("span", {"class": "key"}).update("[" + itemInput.value.escapeHTML() + "]")});
      displayedValue.insert({'bottom' : new Element("span", {"class": "sep"}).update(" ")});
    }
    // Insert the displayed value
    displayedValue.insert({'bottom' : new Element("span", {"class": "value"}).update(suggestion.value.escapeHTML())});
    listItem.insert(displayedValue);
    // Delete tool
    this.options.showDeleteTool && listItem.insert(this.createDeleteTool());
    // Tooltip, if information exists and the options state there should be a tooltip
    if (this.options.showTooltip && suggestion.info) {
      listItem.appendChild(new Element("div", {'class' : "tooltip"}).update(suggestion.info));
    }
    return listItem;
  },

  /**
   * Creates the input used to store the value of a selected item.
   */
  createItemInput: function(suggestion) {
    var inputOptions = {
      type : this.options.inputType,
      name : this.inputName,
      id : this.getInputId(suggestion.id || suggestion.value),
      value : suggestion.value || suggestion.id
    };
    if (this.options.inputType == 'checkbox') {
      inputOptions.checked = 'checked';
    }
    var input = new Element('input', inputOptions);
    input.observe('change', this.checkboxChanged);
    return input;
  },

  /**
   * Creates the tool used to delete a selected item.
   */
  createDeleteTool: function() {
    var deleteTool = new Element("span", {
      'class': "delete-tool",
      title : "$msg.get('core.widgets.suggestPicker.delete.tooltip')"
    }).update('×').observe('click', this.removeItem);
    deleteTool.insert({top: new Element('span', {'class': 'hidden'}).update('[')});
    deleteTool.insert({bottom: new Element('span', {'class': 'hidden'}).update(']')});
    return deleteTool;
  },

  /**
   * Meta-maintenance of the accepted items list: show/hide the "delete all" button, refresh the Sortable behavior.
   */
  updateListTools : function () {
    // Show/hide the "delete all" button
    if (this.clearTool) {
      if (this.list.childElements().length > 1) {
        this.clearTool.show();
      } else {
        this.clearTool.hide();
      }
    }
    // Refresh the Sortable behavior to take into account the new items
    if (this.options.enableSort && this.list.childElements().length > 1 && typeof(Sortable) != "undefined") {
      Sortable.create(this.list);
      this.list.addClassName('sortable');
    }
  },

  /**
   * Fire an event when the list of values changes.
   *
   * @param liElement an optional li element which changed (was removed or added or toggled)
   */
  notifySelectionChange : function(liElement) {
    Event.fire(document, "xwiki:multisuggestpicker:selectionchanged", {
       'trigger'    : this.input,
       'fieldName'  : this.inputName,
       'changedElement' : liElement
    });
  },

  /**
   * Compute a unique ID for a given item key.
   *
   * @param key the key of the item
   * @return an identifier obtained by concatenating the base input name and the key
   */
  getInputId : function(key) {
    return this.inputName + "_" + key;
  },

  /**
   * Remove suggest behavior from the target field (detach all listeners and remove the list of accepted suggestions)
   */
  detach : function() {
    this.clearTool && this.clearTool.stopObserving('click').remove();
    this.list && this.list.remove();
  }
});
  return XWiki;
}(XWiki || {}));
<commit_msg>Properly set the readOnly and loading flag on the suggest input. Also, skip empty values when initializing the suggest input.<commit_after>/**
 * Add-on for the Suggest widget which allows multiple values to be selected and displayed in a list. It requires an existing input and a Suggest instance already bound to the input.
 */
var XWiki = (function (XWiki) {
  var widgets = XWiki.widgets = XWiki.widgets || {};
  widgets.SuggestPicker = Class.create({

  options : {
    // Display the internal value?
    'showKey' : false,
    // If there's extra information for each item (the 'info' field), display it as a tooltip?
    'showTooltip' : false,
    // Show a delete icon next to each selected entry?
    'showDeleteTool' : true,
    // Should the list of selected values be sortable? Requires scriptaculous/dragdrop.js to be available.
    'enableSort' : true,
    // Show a "delete all" button after the list of suggestions?
    'showClearTool' : true,
    // Which type of input should be used for the selected values? Another good option is "checkbox"
    'inputType': 'hidden',
    // The element used as a reference insertion point. By default the enhanced input is used. Either a CSS selector or an actual element can be used.
    'listInsertionElement' : null,
    // Where should the list of selected items be placed? Valid options are 'before', 'top', 'bottom', 'after'.
    'listInsertionPosition' : 'after',
    // Accept free text, i.e. text that was entered by the user but didn't match anything on the server (no suggestions)?
    'acceptFreeText' : false,
    // Optional callback method called whenever a new item has been added
    'onItemAdded' : Prototype.emptyFunction,
    // The character used to separate the multiple values in the initial content of the target text input.
    'separator' : ','
  },
  /**
   * Constructor method.
   *
   * @param element the DOM <input> Element to bind to
   * @param suggest the instance of XWiki.widgets.Suggest already bound to the input
   * @param options additional configuration of the picker; see the {@link #options} field for possible options to specify
   */
  initialize: function(element, suggest, options) {
    // Pre-bind callback functions
    this.removeItem = this.removeItem.bindAsEventListener(this);
    this.checkboxChanged = this.checkboxChanged.bindAsEventListener(this);

    // Process options
    this.options = Object.extend(Object.clone(this.options), options || { });
    this.input = element;
    this.suggest = suggest;
    this.inputName = this.input.name;
    if (!this.options.acceptFreeText) {
      this.input.name = this.input.name + "__suggested";
    } else {
      this.input.addClassName("accept-value");
    }
    this.suggest.options.callback = this.acceptSuggestion.bind(this);

    // Create the list element which will hold the accepted elements
    this.list = new Element('ul', {'class' : 'accepted-suggestions'});
    var listInsertionElement;
    if (this.options.listInsertionElement) {
      if (typeof(this.options.listInsertionElement) === "string") {
        listInsertionElement = this.input.up().down(this.options.listInsertionElement);
      } else {
        listInsertionElement = this.options.listInsertionElement;
      }
    }
    if (!listInsertionElement) {
      listInsertionElement = this.input;
    }
    var insertion = {};
    insertion[this.options.listInsertionPosition] = this.list;
    listInsertionElement.insert(insertion);

    if (this.options.showClearTool) {
      this.clearTool = new Element('a', {
        href: '#clearSelection',
        'class' : 'clear-tool',
        title : "$msg.get('core.widgets.suggestPicker.deleteAll.tooltip')"
      }).update("$msg.get('core.widgets.suggestPicker.deleteAll')");
      this.clearTool.hide().observe('click', this.clearAcceptedList.bindAsEventListener(this));
      this.list.insert({'after': this.clearTool});
    }

    this.initializeSelection();
  },

  /**
   * Splits the initial content of the target text input using the configured separator and adds all the values to the
   * list of accepted suggestions.
   */
  initializeSelection: function() {
    this.input.readOnly = true;
    this.input.addClassName('loading');
    this.loadSelectedValue(this.input.value.split(this.options.separator), 0);
  },

  /**
   * Loads suggestions for the selected value in order to display it properly.
   */
  loadSelectedValue: function(values, index) {
    if (index >= values.length) {
      this.input.readOnly = false;
      this.input.clear().removeClassName('loading');
      return;
    } else if (values[index].trim() == '') {
      this.loadSelectedValue(values, index + 1);
      return;
    }
    // Look for the selected value in all the sources.
    var found = false;
    var sourceIndex = 0;
    this.input.value = values[index];
    this.suggest.doAjaxRequests(-1, {
      onSuccess: function(response) {
        if (found) return;
        var suggestions = this.suggest.parseResponse(response, this.suggest.sources[sourceIndex]) || [];
        for (var i = 0; i < suggestions.length; i++) {
          if (this.matchesSelectedValue(values[index], suggestions[i])) {
            found = true;
            this.addItem(suggestions[i]);
            return;
          }
        }
      }.bind(this),
      onComplete: function(response) {
        response.request.options.defaultValues.onComplete(response);
        if (++sourceIndex >= this.suggest.sources.length) {
          if (!found) this.addItem(this.getDefaultSuggestion(values[index]));
          // Load the next selected value.
          this.loadSelectedValue(values, index + 1);
        }
      }.bind(this)
    });
  },

  /**
   * @return {@code true} if the given suggestion matches perfectly the specified selected value, {@code false} otherwise
   */
  matchesSelectedValue: function(value, suggestion) {
    return value == suggestion.value;
  },

  /**
   * @return default suggestion data for when a selected value is not found in any of the configured sources
   */
  getDefaultSuggestion: function(value) {
    return {id: value, value: value};
  },

  /**
   * Callback called when the user accepted an item from the Suggest.
   *
   * @param suggestion the accepted suggestion, as an object with id, value and info values
   */
  acceptSuggestion : function(suggestion) {
    if (!this.acceptAlreadyAddedItem(suggestion.id || suggestion.value)) {
      this.addItem(suggestion);
    }
    this.input.value = "";
  },

  /**
   * Callback called when the user deleted an item from the list of accepted items.
   *
   * @param event the click event fired by the browser
   */
  removeItem : function(event) {
    var item = event.findElement('li');
    item.remove();
    this.notifySelectionChange(item);
    this.updateListTools();
    this.input.activate();
  },

  /**
   * Remove all the accepted items when the "remove all" button is clicked.
   */
  clearAcceptedList : function (event) {
    event && event.stop();
    this.list.update("");
    this.notifySelectionChange();
    this.updateListTools();
    this.input.activate();
  },

  /**
   * Callback called when the user checked or unchecked an item from the list of accepted items.
   *
   * @param event the click event fired by the browser
   */
  checkboxChanged : function(event) {
    var item = event.findElement('li');
    this.notifySelectionChange(item);
  },

  /**
   * Check if an item has been previously accepted or was displayed from the start as an unchecked checkbox, but might be unchecked, and re-enable it.
   *
   * @param key the key of the item to look for
   * @return true if the item existed and has been re-enabled, false otherwise
   */
  acceptAlreadyAddedItem : function (key) {
    var input = $(this.getInputId(key));
    if (input) {
      input.checked = true;
      this.notifySelectionChange(input.up('li') || input);
      return true;
    }
    return false;
  },

  /**
   * Add a new item in the list of accepted items.
   *
   * @param suggestion the suggestion object received from the Suggest widget
   */
  addItem : function(suggestion) {
    if (!suggestion) {
      return;
    }
    var listItem = this.displayItem(suggestion);
    this.list.insert(listItem);
    this.options.onItemAdded(listItem.down('input'));
    this.notifySelectionChange(listItem);
    this.updateListTools();
  },

  /**
   * Displays a selected item.
   */
  displayItem: function(suggestion) {
    var itemInput = this.createItemInput(suggestion);
    var listItem = new Element("li");
    var displayedValue = new Element('label', {'for' : itemInput.id}).insert({'bottom' : itemInput});
    // If the key should be displayed, insert it
    if (this.options.showKey) {
      displayedValue.insert({'bottom' : new Element("span", {"class": "key"}).update("[" + itemInput.value.escapeHTML() + "]")});
      displayedValue.insert({'bottom' : new Element("span", {"class": "sep"}).update(" ")});
    }
    // Insert the displayed value
    displayedValue.insert({'bottom' : new Element("span", {"class": "value"}).update(suggestion.value.escapeHTML())});
    listItem.insert(displayedValue);
    // Delete tool
    this.options.showDeleteTool && listItem.insert(this.createDeleteTool());
    // Tooltip, if information exists and the options state there should be a tooltip
    if (this.options.showTooltip && suggestion.info) {
      listItem.appendChild(new Element("div", {'class' : "tooltip"}).update(suggestion.info));
    }
    return listItem;
  },

  /**
   * Creates the input used to store the value of a selected item.
   */
  createItemInput: function(suggestion) {
    var inputOptions = {
      type : this.options.inputType,
      name : this.inputName,
      id : this.getInputId(suggestion.id || suggestion.value),
      value : suggestion.value || suggestion.id
    };
    if (this.options.inputType == 'checkbox') {
      inputOptions.checked = 'checked';
    }
    var input = new Element('input', inputOptions);
    input.observe('change', this.checkboxChanged);
    return input;
  },

  /**
   * Creates the tool used to delete a selected item.
   */
  createDeleteTool: function() {
    var deleteTool = new Element("span", {
      'class': "delete-tool",
      title : "$msg.get('core.widgets.suggestPicker.delete.tooltip')"
    }).update('×').observe('click', this.removeItem);
    deleteTool.insert({top: new Element('span', {'class': 'hidden'}).update('[')});
    deleteTool.insert({bottom: new Element('span', {'class': 'hidden'}).update(']')});
    return deleteTool;
  },

  /**
   * Meta-maintenance of the accepted items list: show/hide the "delete all" button, refresh the Sortable behavior.
   */
  updateListTools : function () {
    // Show/hide the "delete all" button
    if (this.clearTool) {
      if (this.list.childElements().length > 1) {
        this.clearTool.show();
      } else {
        this.clearTool.hide();
      }
    }
    // Refresh the Sortable behavior to take into account the new items
    if (this.options.enableSort && this.list.childElements().length > 1 && typeof(Sortable) != "undefined") {
      Sortable.create(this.list);
      this.list.addClassName('sortable');
    }
  },

  /**
   * Fire an event when the list of values changes.
   *
   * @param liElement an optional li element which changed (was removed or added or toggled)
   */
  notifySelectionChange : function(liElement) {
    Event.fire(document, "xwiki:multisuggestpicker:selectionchanged", {
       'trigger'    : this.input,
       'fieldName'  : this.inputName,
       'changedElement' : liElement
    });
  },

  /**
   * Compute a unique ID for a given item key.
   *
   * @param key the key of the item
   * @return an identifier obtained by concatenating the base input name and the key
   */
  getInputId : function(key) {
    return this.inputName + "_" + key;
  },

  /**
   * Remove suggest behavior from the target field (detach all listeners and remove the list of accepted suggestions)
   */
  detach : function() {
    this.clearTool && this.clearTool.stopObserving('click').remove();
    this.list && this.list.remove();
  }
});
  return XWiki;
}(XWiki || {}));
<|endoftext|>


<commit_before>function setupExamples() {
    var delay = 0;
    $('.example-group div.contents').each(function() {
        var contents = $(this);
        var header = $(this).siblings('h3');
        var wasOpen = readCookie(header.html());
        var visitedBefore = readCookie('visitedBefore');

        if (!visitedBefore) {
            createCookie('visitedBefore', true, 365);
        }

        if (!wasOpen || wasOpen === 'false') {
            if (!visitedBefore) {
                contents.delay(500 + delay).slideUp(500);
                delay += 100;
            }
            else {
                contents.hide();
            }
        }
        else {
            header.addClass('shown');
        }
    });

    $('.example-group').click(function(e) {
        var header = $(e.target);
        var contents = header.siblings('div.contents');

        contents.stop(false, true).slideToggle(500, function() {
            createCookie(header.html(), contents.is(':visible'), 365);
        });
        header.toggleClass('shown');
        header.siblings('i').toggleClass('shown');
    });
}

function setupSavedQueries() {
    $('div.col.recent a.remove').click(function(e) {
        var link = $(e.target);
        e.preventDefault();
        link.parent().slideUp(300);
        $.get(link.attr('href'));
    });

    $('#clear-all-recent').click(function() {
        $('div.col.recent a.remove').click();
    })
}

function setupMobileKeyboard() {
    var keyboard = $('
');

    keyboard.append([
        $('<button data-key="+">+</button>'),
        $('<button data-key="-">-</button>'),
        $('<button data-key="*">*</button>'),
        $('<button data-key="/">/</button>'),
        $('<button data-key="()">()</button>'),
        $('<button data-offset="-1"><</button>'),
        $('<button data-offset="1">></button>')
    ]);

    var h1height = $('.input h1').height();

    $('.input').prepend(keyboard);
    $('form input[type=text]').focus(function() {
        keyboard.find('button').height(h1height);
        keyboard.slideDown();
        $('.input h1').slideUp();
    });
    $('form input[type=text]').blur(function() {
        setTimeout(function() {
            if (!(document.activeElement.tagName.toLowerCase() == 'input' &&
                  document.activeElement.getAttribute('type') == 'text')) {
                keyboard.slideUp();
                $('.input h1').slideDown();
            }
        }, 100);
    });

    $('#mobile-keyboard button').click(function(e) {
        $('#mobile-keyboard').stop().show().height(h1height);
        $('.input h1').stop().hide();

        var input = $('.input input[type=text]')[0];
        var start = input.selectionStart;
        if ($(this).data('key')) {
            var text = input.value;

            input.value = (text.substring(0, start) +
                           $(this).data('key') + text.substring(start));
            input.setSelectionRange(start + 1, start + 1);
        }
        else if ($(this).data('offset')) {
            var offset = parseInt($(this).data('offset'), 10);
            input.setSelectionRange(start + offset, start + offset);
        }
    });
}

function evaluateCards() {
    var deferred = new $.Deferred();
    var requests = [];

    $('.result_card').each(function() {
        var card = Card.fromCardEl($(this));
        card.initSpecificFunctionality();

        // deferred if can evaluate, false otherwise
        var result = card.evaluate();
        if (!(result.state() == "rejected")) {
            requests.push(result);
        }
    });

    $.when.apply($, requests).then(function() {
        deferred.resolve();
    });

    return deferred;
}

function setupDidYouMean() {
    $('.did_you_mean var').each(function() {
        $(this).wrap($("").attr('href', '/input/?i=' + $(this).text()));
    });
}

function setupVariableChooser() {
    var defaultVariableClass = 'variable-' + $('.result_variables .active').html();
    var currentVariableClass = defaultVariableClass;
    $('.result_card').slice(1).addClass(defaultVariableClass);

    $('.result_variables button').click(function() {
        var button = $(this);
        if (!button.is('.active')) {
            var variable = button.html();
            // disable the buttons while loading
            $('.result_variables').addClass('active');

            var deferreds = [];

            // check if we've already loaded the cards
            if ($('.variable-' + variable).length) {
                $('.result_card.' + currentVariableClass).hide();
                $('.result_card.variable-' + variable).show();
            }
            else {
                $('.result_card.' + currentVariableClass).each(function() {
                    var card = $(this).data('card');
                    if (card.variable) {
                        var placeholder = $('
')
                            .addClass('result_card')
                            .append($('
').addClass('loader'));
                        card.element.after(placeholder);
                        deferreds.push(
                            Card.loadFullCard(card.card_name, variable, card.expr, {})
                                .done(function(result) {
                                    var newCardEl = $(result);
                                    newCardEl
                                        .addClass('variable-' + variable)
                                        .find('.loader').remove();
                                    placeholder.slideUp(300, function() {
                                        placeholder.replaceWith(newCardEl);
                                        newCardEl.hide().slideDown();
                                        var newCard = Card.fromCardEl(newCardEl);
                                        newCard.initSpecificFunctionality();
                                        newCard.evaluateFinished();

                                        MathJax.Hub.Queue(["Typeset", MathJax.Hub]);
                                    });
                                })
                        );
                    }
                    card.element.fadeOut();
                });
            }

            $.when.apply($, deferreds).then(function() {
                currentVariableClass = 'variable-' + variable;
                $('.result_variables button').removeClass('active');
                button.addClass('active');
            });
        }
    });
}

$(document).ready(function() {
    evaluateCards().done(function() {
        setupPlots();

        setupExamples();
        setupSavedQueries();

        setupFactorization();

        setupDidYouMean();

        setupVariableChooser();

        // TODO: finish integration with Sphinx
        // setupDocumentation();
    });

    if (screen.width <= 1024) {
        setupMobileKeyboard();
    }
});
<commit_msg>Fixed the bug which was preventing border to appear after clicking arrow in examples.<commit_after>function setupExamples() {
    var delay = 0;
    $('.example-group div.contents').each(function() {
        var contents = $(this);
        var header = $(this).siblings('h3');
        var wasOpen = readCookie(header.html());
        var visitedBefore = readCookie('visitedBefore');

        if (!visitedBefore) {
            createCookie('visitedBefore', true, 365);
        }

        if (!wasOpen || wasOpen === 'false') {
            if (!visitedBefore) {
                contents.delay(500 + delay).slideUp(500);
                delay += 100;
            }
            else {
                contents.hide();
            }
        }
        else {
            header.addClass('shown');
        }
    });

    $('.example-group').click(function(e) {
        var header = $(e.target);
        var contents = header.siblings('div.contents');

        contents.stop(false, true).slideToggle(500, function() {
            createCookie(header.html(), contents.is(':visible'), 365);
        });
        header.toggleClass('shown');
        header.siblings('i').toggleClass('shown');
	header.siblings('h3').toggleClass('shown');
    });
}

function setupSavedQueries() {
    $('div.col.recent a.remove').click(function(e) {
        var link = $(e.target);
        e.preventDefault();
        link.parent().slideUp(300);
        $.get(link.attr('href'));
    });

    $('#clear-all-recent').click(function() {
        $('div.col.recent a.remove').click();
    })
}

function setupMobileKeyboard() {
    var keyboard = $('
');

    keyboard.append([
        $('<button data-key="+">+</button>'),
        $('<button data-key="-">-</button>'),
        $('<button data-key="*">*</button>'),
        $('<button data-key="/">/</button>'),
        $('<button data-key="()">()</button>'),
        $('<button data-offset="-1"><</button>'),
        $('<button data-offset="1">></button>')
    ]);

    var h1height = $('.input h1').height();

    $('.input').prepend(keyboard);
    $('form input[type=text]').focus(function() {
        keyboard.find('button').height(h1height);
        keyboard.slideDown();
        $('.input h1').slideUp();
    });
    $('form input[type=text]').blur(function() {
        setTimeout(function() {
            if (!(document.activeElement.tagName.toLowerCase() == 'input' &&
                  document.activeElement.getAttribute('type') == 'text')) {
                keyboard.slideUp();
                $('.input h1').slideDown();
            }
        }, 100);
    });

    $('#mobile-keyboard button').click(function(e) {
        $('#mobile-keyboard').stop().show().height(h1height);
        $('.input h1').stop().hide();

        var input = $('.input input[type=text]')[0];
        var start = input.selectionStart;
        if ($(this).data('key')) {
            var text = input.value;

            input.value = (text.substring(0, start) +
                           $(this).data('key') + text.substring(start));
            input.setSelectionRange(start + 1, start + 1);
        }
        else if ($(this).data('offset')) {
            var offset = parseInt($(this).data('offset'), 10);
            input.setSelectionRange(start + offset, start + offset);
        }
    });
}

function evaluateCards() {
    var deferred = new $.Deferred();
    var requests = [];

    $('.result_card').each(function() {
        var card = Card.fromCardEl($(this));
        card.initSpecificFunctionality();

        // deferred if can evaluate, false otherwise
        var result = card.evaluate();
        if (!(result.state() == "rejected")) {
            requests.push(result);
        }
    });

    $.when.apply($, requests).then(function() {
        deferred.resolve();
    });

    return deferred;
}

function setupDidYouMean() {
    $('.did_you_mean var').each(function() {
        $(this).wrap($("").attr('href', '/input/?i=' + $(this).text()));
    });
}

function setupVariableChooser() {
    var defaultVariableClass = 'variable-' + $('.result_variables .active').html();
    var currentVariableClass = defaultVariableClass;
    $('.result_card').slice(1).addClass(defaultVariableClass);

    $('.result_variables button').click(function() {
        var button = $(this);
        if (!button.is('.active')) {
            var variable = button.html();
            // disable the buttons while loading
            $('.result_variables').addClass('active');

            var deferreds = [];

            // check if we've already loaded the cards
            if ($('.variable-' + variable).length) {
                $('.result_card.' + currentVariableClass).hide();
                $('.result_card.variable-' + variable).show();
            }
            else {
                $('.result_card.' + currentVariableClass).each(function() {
                    var card = $(this).data('card');
                    if (card.variable) {
                        var placeholder = $('
')
                            .addClass('result_card')
                            .append($('
').addClass('loader'));
                        card.element.after(placeholder);
                        deferreds.push(
                            Card.loadFullCard(card.card_name, variable, card.expr, {})
                                .done(function(result) {
                                    var newCardEl = $(result);
                                    newCardEl
                                        .addClass('variable-' + variable)
                                        .find('.loader').remove();
                                    placeholder.slideUp(300, function() {
                                        placeholder.replaceWith(newCardEl);
                                        newCardEl.hide().slideDown();
                                        var newCard = Card.fromCardEl(newCardEl);
                                        newCard.initSpecificFunctionality();
                                        newCard.evaluateFinished();

                                        MathJax.Hub.Queue(["Typeset", MathJax.Hub]);
                                    });
                                })
                        );
                    }
                    card.element.fadeOut();
                });
            }

            $.when.apply($, deferreds).then(function() {
                currentVariableClass = 'variable-' + variable;
                $('.result_variables button').removeClass('active');
                button.addClass('active');
            });
        }
    });
}

$(document).ready(function() {
    evaluateCards().done(function() {
        setupPlots();

        setupExamples();
        setupSavedQueries();

        setupFactorization();

        setupDidYouMean();

        setupVariableChooser();

        // TODO: finish integration with Sphinx
        // setupDocumentation();
    });

    if (screen.width <= 1024) {
        setupMobileKeyboard();
    }
});
<|endoftext|>


<commit_before>'use strict';

var $ = require('jquery');
var bootbox = require('bootbox');
var Raven = require('raven-js');
var ko = require('knockout');
var $osf = require('js/osfHelpers');
var oop = require('js/oop');
var ChangeMessageMixin = require('js/changeMessage');
var language = require('js/osfLanguage').projectSettings;

var ProjectSettings = oop.extend(
    ChangeMessageMixin,
    {
        constructor: function(params) {
            this.super.constructor.call(this);
            var self = this;

            self.title = ko.observable(params.currentTitle).extend({
                required: {
                    params: true,
                    message: 'Title cannot be blank.'
                }});
            self.description = ko.observable(params.currentDescription);
            self.titlePlaceholder = params.currentTitle;
            self.descriptionPlaceholder = params.currentDescription;

            self.disabled = params.disabled || false;

            if (!params.updateUrl) {
                throw new Error(language.instantiationErrorMessage);
            }

            self.categoryOptions = params.categoryOptions;
            self.categoryPlaceholder = params.category;
            self.selectedCategory = ko.observable(params.category);

            self.updateUrl = params.updateUrl;
            self.node_id = params.node_id;
        },
        /*error handler*/
        updateError: function(xhr, status, error) {
            var self = this;
            self.changeMessage(language.updateErrorMessage, 'text-danger');
            Raven.captureMessage(language.updateErrorMessage, {
                url: self.updateUrl,
                textStatus: status,
                err: error
            });
        },
        /*update handler*/
        updateAll: function() {
            var self = this;
            var requestPayload = self.serialize();
            var request = $osf.ajaxJSON('put',
                self.updateUrl,
                { data: requestPayload,
                isCors: true });
            request.done(function(response) {
                self.categoryPlaceholder = response.data.attributes.category;
                self.titlePlaceholder = response.data.attributes.title;
                self.descriptionPlaceholder = response.data.attributes.description;
                self.selectedCategory(self.categoryPlaceholder);
                self.title(self.titlePlaceholder);
                self.description(self.descriptionPlaceholder);
                self.changeMessage(language.updateSuccessMessage, 'text-success')
            });
            request.fail(self.updateError.bind(self));
            return request;
        },
        /*cancel handler*/
        cancelAll: function() {
            var self = this;
            self.selectedCategory(self.categoryPlaceholder);
            self.title(self.titlePlaceholder);
            self.description(self.descriptionPlaceholder);
            self.resetMessage();
        },
        serialize: function() {
            var self = this;
            return {
                data: {
                    type: 'nodes',
                    id:   self.node_id,
                    attributes: {
                        title: self.title(),
                        category: self.selectedCategory(),
                        description: self.description(),
                    }
                }
            }
        }
    });

// TODO: Pass this in as an argument rather than relying on global contextVars
var nodeApiUrl = window.contextVars.node.urls.api;

// Request the first 5 contributors, for display in the deletion modal
var contribs = [];
var moreContribs = 0;

var contribURL = nodeApiUrl + 'get_contributors/?limit=5';
var request = $.ajax({
    url: contribURL,
    type: 'get',
    dataType: 'json'
});
request.done(function(response) {
    // TODO: Remove reliance on contextVars
    var currentUserName = window.contextVars.currentUser.fullname;
    contribs = response.contributors.filter(function(contrib) {
        return contrib.shortname !== currentUserName;
    });
    moreContribs = response.more;
});
request.fail(function(xhr, textStatus, err) {
    Raven.captureMessage('Error requesting contributors', {
        url: contribURL,
        textStatus: textStatus,
        err: err,
    });
});

/**
 * Pulls a random name from the scientist list to use as confirmation string
 *  Ignores case and whitespace
 */
var getConfirmationCode = function(nodeType) {

    // It's possible that the XHR request for contributors has not finished before getting to this
    // point; only construct the HTML for the list of contributors if the contribs list is populated
    var message = '
It will no longer be available to other contributors on the project.';

    $osf.confirmDangerousAction({
        title: 'Are you sure you want to delete this ' + nodeType + '?',
        message: message,
        callback: function () {
            var request = $.ajax({
                type: 'DELETE',
                dataType: 'json',
                url: nodeApiUrl
            });
            request.done(function(response) {
                // Redirect to either the parent project or the dashboard
                window.location.href = response.url;
            });
            request.fail($osf.handleJSONError);
        },
        buttons: {
            success: {
                label: 'Delete'
            }
        }
    });
};

module.exports = {
    ProjectSettings: ProjectSettings,
    getConfirmationCode: getConfirmationCode
};
<commit_msg>move code for readability<commit_after>'use strict';

var $ = require('jquery');
var bootbox = require('bootbox');
var Raven = require('raven-js');
var ko = require('knockout');
var $osf = require('js/osfHelpers');
var oop = require('js/oop');
var ChangeMessageMixin = require('js/changeMessage');
var language = require('js/osfLanguage').projectSettings;

var ProjectSettings = oop.extend(
    ChangeMessageMixin,
    {
        constructor: function(params) {
            this.super.constructor.call(this);
            var self = this;

            self.title = ko.observable(params.currentTitle).extend({
                required: {
                    params: true,
                    message: 'Title cannot be blank.'
                }});
            self.description = ko.observable(params.currentDescription);
            self.titlePlaceholder = params.currentTitle;
            self.descriptionPlaceholder = params.currentDescription;

            self.categoryOptions = params.categoryOptions;
            self.categoryPlaceholder = params.category;
            self.selectedCategory = ko.observable(params.category);

            self.disabled = params.disabled || false;

            if (!params.updateUrl) {
                throw new Error(language.instantiationErrorMessage);
            }

            self.updateUrl = params.updateUrl;
            self.node_id = params.node_id;
        },
        /*error handler*/
        updateError: function(xhr, status, error) {
            var self = this;
            self.changeMessage(language.updateErrorMessage, 'text-danger');
            Raven.captureMessage(language.updateErrorMessage, {
                url: self.updateUrl,
                textStatus: status,
                err: error
            });
        },
        /*update handler*/
        updateAll: function() {
            var self = this;
            var requestPayload = self.serialize();
            var request = $osf.ajaxJSON('put',
                self.updateUrl,
                { data: requestPayload,
                isCors: true });
            request.done(function(response) {
                self.categoryPlaceholder = response.data.attributes.category;
                self.titlePlaceholder = response.data.attributes.title;
                self.descriptionPlaceholder = response.data.attributes.description;
                self.selectedCategory(self.categoryPlaceholder);
                self.title(self.titlePlaceholder);
                self.description(self.descriptionPlaceholder);
                self.changeMessage(language.updateSuccessMessage, 'text-success')
            });
            request.fail(self.updateError.bind(self));
            return request;
        },
        /*cancel handler*/
        cancelAll: function() {
            var self = this;
            self.selectedCategory(self.categoryPlaceholder);
            self.title(self.titlePlaceholder);
            self.description(self.descriptionPlaceholder);
            self.resetMessage();
        },
        serialize: function() {
            var self = this;
            return {
                data: {
                    type: 'nodes',
                    id:   self.node_id,
                    attributes: {
                        title: self.title(),
                        category: self.selectedCategory(),
                        description: self.description(),
                    }
                }
            }
        }
    });

// TODO: Pass this in as an argument rather than relying on global contextVars
var nodeApiUrl = window.contextVars.node.urls.api;

// Request the first 5 contributors, for display in the deletion modal
var contribs = [];
var moreContribs = 0;

var contribURL = nodeApiUrl + 'get_contributors/?limit=5';
var request = $.ajax({
    url: contribURL,
    type: 'get',
    dataType: 'json'
});
request.done(function(response) {
    // TODO: Remove reliance on contextVars
    var currentUserName = window.contextVars.currentUser.fullname;
    contribs = response.contributors.filter(function(contrib) {
        return contrib.shortname !== currentUserName;
    });
    moreContribs = response.more;
});
request.fail(function(xhr, textStatus, err) {
    Raven.captureMessage('Error requesting contributors', {
        url: contribURL,
        textStatus: textStatus,
        err: err,
    });
});

/**
 * Pulls a random name from the scientist list to use as confirmation string
 *  Ignores case and whitespace
 */
var getConfirmationCode = function(nodeType) {

    // It's possible that the XHR request for contributors has not finished before getting to this
    // point; only construct the HTML for the list of contributors if the contribs list is populated
    var message = '
It will no longer be available to other contributors on the project.';

    $osf.confirmDangerousAction({
        title: 'Are you sure you want to delete this ' + nodeType + '?',
        message: message,
        callback: function () {
            var request = $.ajax({
                type: 'DELETE',
                dataType: 'json',
                url: nodeApiUrl
            });
            request.done(function(response) {
                // Redirect to either the parent project or the dashboard
                window.location.href = response.url;
            });
            request.fail($osf.handleJSONError);
        },
        buttons: {
            success: {
                label: 'Delete'
            }
        }
    });
};

module.exports = {
    ProjectSettings: ProjectSettings,
    getConfirmationCode: getConfirmationCode
};
<|endoftext|>


<commit_before>
var async = require("async"),
	_ = require("underscore"),
	uuid = require("node-uuid"),
	utils = require("./utils"),
	EventEmitter = require("events").EventEmitter,
	ALL_CHANNEL = "__all__";

var TokenSocketServer = function(options){
	var self = this;
	self.app = options.app;
	self.prefix = options.prefix || "/sockets";
	self.tokenRoute = options.tokenRoute || "/socket/token";
	self.redisClient = options.redisClient;
	self.socketServer = options.socketServer;
	self.socketController = options.socketController || {};
	self.cleanup = options.cleanup;
	self.authentication = options.authentication;
	self._channels = {};
	self._sockets = {};
	self._inTransit = {};
	self._emitter = new EventEmitter();

	if(options.debug)
		self.debug = options.debug;

	// create the token route
	var requestToken = function(req, res){
		async.waterfall([
			function(callback){
				if(self.debug)
					console.log("Sockjs: Sockjs token request received ", req.query);
				if(typeof self.authentication === "function")
					self.authentication(req, callback); // use custom function with "req" as a parameter
				else if(typeof self.authentication === "string")
					callback(null, req.session[self.authentication]); // use custom session property
				else
					callback(null, req.session && req.session.auth); // default
			}
		], function(error, auth){
			var type = utils.requestType(req);
			if(error){
				if(self.debug) console.log("Sockjs: Error issuing token", error);
				res[type](500, { error: error.message || error });
			}else if(!auth){
				if(self.debug) console.log("Sockjs: Rejected token request");
				res[type](400, { error: "Unauthorized" });
			}else{
				// issue a token
				var save = req.session || {};
				if(typeof auth === "object")
					save = auth;
				utils.issueToken(self.redisClient, save, function(error, token){
					if(self.debug) console.log("Sockjs: Attaching token to data: ", token, save);
					if(error)
						res[type](500, { error: error.message || error });
					else
						res[type](200, { token: token });
				});
			}
		});
	};

	if(options.customMiddleware){
		if(self.debug)
			console.log("Sockjs: Creating token route and attaching custom middleware");
		self.app.get(self.tokenRoute, options.customMiddleware, requestToken);
	}else{
		if(self.debug)
			console.log("Sockjs: Creating token route");
		self.app.get(self.tokenRoute, requestToken);
	}

	if(options.pubsubClient){
		if(self.debug)
			console.log("Sockjs: Setting up publish-subscribe functionality");
		self.pubsubClient = options.pubsubClient;
		self.pubsubClient.subscribe(ALL_CHANNEL);
		self.pubsubClient.on("message", function(channel, message){
			if(channel === ALL_CHANNEL){
				if(self.debug)
					console.log("Sockjs: Broadcasting message to all sockets on all channels", message);
				async.each(Object.keys(self._channels), function(_channel, callback){
					utils.writeSockets(self._channels[_channel], _channel, message);
					callback();
				});
			}else{
				if(self.debug)
					console.log("Sockjs: Pub-sub message received: ", channel, message);
				if(self._channels[channel])
					utils.writeSockets(self._channels[channel], channel, message);
			}
		});
	}

	if(self.cleanup)
		self.enableCleanup(self.cleanup);

	self.socketServer.on("connection", function(socket){
		if(self.debug) console.log("Sockjs: Socket connection initiated");
		var sid = uuid.v4();
		socket.sid = sid;
		socket.created = new Date().getTime();
		socket.channels = {};
		self._sockets[sid] = socket;
		socket.on("close", function(){
			if(self.debug)
				console.log("Sockjs: Socket connection closed", sid);
			delete self._sockets[sid];
			if(self.pubsubClient && socket.channels){
				_.each(socket.channels, function(channel){
					if(self._channels[channel]){
						delete self._channels[channel][socket.sid];
						if(Object.keys(self._channels[channel]).length === 0){
							self.pubsubClient.unsubscribe(channel);
							delete self._channels[channel];
						}
					}
				});
			}
		});

		socket.on("data", function(message){
			try{
				message = JSON.parse(message);
			}catch(e){
				return socket.write(JSON.stringify({ error: "Invalid message" }));
			}

			if(self.debug)
				console.log("Sockjs: Socket message received", message);

			var sendMessage = function(error, resp){
				if(error)
					message.error = error.message || error;
				else
					message.resp = resp;
				if(self.debug)
					console.log("Sockjs: Writing message on socket", socket.sid, message);
				socket.write(JSON.stringify(message));
			};

			if(message.rpc === "auth" && message.token){
				if(self.debug)
					console.log("Sockjs: Socket auth request received", message.token);
				if(!message.token.match(/[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}/i)){
					delete self._sockets[socket.id];
					return socket.end();
				}
				utils.verifyToken(self.redisClient, message.token, function(error, data){
					if(error || !data){
						if(self.debug)
							console.log("Sockjs: Error verifying token ", message.token, error);
						sendMessage(error.message || error || "Invalid token");
						delete self._sockets[socket.sid];
						socket.end();
					}else{
						if(self.debug)
							console.log("Sockjs: Successfully verified token ", message.token, data);
						socket.auth = data;
						// only allow tokens to work once
						utils.revokeToken(self.redisClient, message.token, function(err){
							if(err)
								throw err;
						});
						utils.checkListeners(self._emitter, "authentication", socket, socket.auth, function(){
							sendMessage(null, "success");
						});
					}
				});
			}else if(!socket.auth){
				if(self.debug)
					console.log("Sockjs: Invalid socket message. Ending connection", sid, message);
				message.error = "Not authorized";
				socket.write(JSON.stringify(message));
				socket.end();
				delete self._sockets[socket.sid];
			}else{
				// TODO clean this up
				if(message.rpc && message.rpc === "_subscribe"){
					utils.checkListeners(self._emitter, "subscribe", socket, message, function(error, allowed){
						if(error)
							sendMessage(error);
						else if(allowed)
							self.subscribe(socket, message.req.channel);
						else
							sendMessage("Not authorized");
					});
				}else if(message.rpc && message.rpc === "_publish"){
					utils.checkListeners(self._emitter, "publish", socket, message, function(error, allowed){
						if(error)
							sendMessage(error);
						else if(allowed)
							self.publish(message.req.channel, message.req.data);
						else
							sendMessage("Not authorized");
					});
				}else if(message.rpc && message.rpc === "_unsubscribe"){
					utils.checkListeners(self._emitter, "unsubscribe", socket, message, function(error, allowed){
						if(error)
							sendMessage(error);
						else if(allowed)
							self.unsubscribe(socket, message.req.channel);
						else
							sendMessage("Not authorized");
					});
				}else if(message.rpc && message.rpc === "_broadcast"){
					utils.checkListeners(self._emitter, "broadcast", socket, message, function(error, allowed){
						if(error)
							sendMessage(error);
						else if(allowed)
							self.broadcast(message.req.data);
						else
							sendMessage("Not authorized");
					});
				}else if(message.rpc && message.rpc === "_rpc"){
					if(message.fid && self._inTransit[message.fid]){
						var fn = self._inTransit[message.fid];
						fn(message.resp.error, message.resp.data);
						delete self._inTransit[message.fid];
					}
				}else if(message.rpc){
					// proxy the socket controller
					var action = utils.checkController(self.socketController, message.rpc);
					if(action && typeof action === "function")
						action(socket.auth, message.req, sendMessage, socket);
					else
						sendMessage("Function not found");
				}else{
					sendMessage("Invalid request");
				}
			}
		});
	});

};

// expose a subset of the event emitter functions

TokenSocketServer.prototype.on = TokenSocketServer.prototype.addListener = function(event, callback){
	this._emitter.on(event, callback);
};

TokenSocketServer.prototype.removeListener = function(event, listener){
	this._emitter.removeListener(event, listener);
};

TokenSocketServer.prototype.removeAllListeners = function(event){
	this._emitter.removeAllListeners(event);
};

TokenSocketServer.prototype.rpc = function(socket, command, args, callback){
	var fid = uuid.v4();
	this._inTransit[fid] = callback;
	socket.write(JSON.stringify({
		internal: true,
		command: "rpc",
		data: {
			fid: fid,
			command: command,
			args: args
		}
	}));
};

TokenSocketServer.prototype.enableCleanup = function(interval){
	if(this.debug)
		console.log("Sockjs: Enabling cleanup process");
	this.cleanup = typeof interval === "number" ? interval : 5000;
	if(this.cleanupTimer)
		clearInterval(this.cleanupTimer);
	if(!(this.cleanupFn && typeof this.cleanupFn === "function")){
		this.cleanupFn = function(instance){
			if(instance.debug)
				console.log("Sockjs: Running cleanup process");
			var now = new Date().getTime();
			var diff = now - instance.cleanup;
			var deleted = [];
			// cleanup sockets
			async.each(instance.sockets() || [], function(socket, callback){
				if(!socket.auth && socket.created && socket.created < diff){
					if(instance.debug)
						console.log("Sockjs: Cleaning up socket", socket.sid);
					deleted.push(socket.sid);
					socket.end();
					delete instance._sockets[socket.sid];
				}
				callback();
			}, function(){
				// cleanup channels
				if(instance.pubsubClient){
					async.each(Object.keys(instance._channels), function(channel, callback){
						async.each(deleted, function(sid, cb){
							delete instance._channels[channel][sid];
							if(Object.keys(instance._channels[channel]).length === 0)
								instance.pubsubClient.unsubscribe(channel);
							cb();
						}, callback);
					});
				}
			});
		};
	}
	this.cleanupTimer = setInterval(this.cleanupFn, this.cleanup, this);
};

TokenSocketServer.prototype.disableCleanup = function(){
	if(this.debug)
		console.log("Sockjs: Disabling cleanup");
	this.cleanup = null;
	if(this.cleanupTimer)
		clearInterval(this.cleanupTimer);
};

TokenSocketServer.prototype.subscribe = function(socket, channel){
	if(this.pubsubClient){
		if(this.debug)
			console.log("Sockjs: Subscribing socket ", socket.sid, channel);
		if(!this._channels[channel]){
			this.pubsubClient.subscribe(channel);
			this._channels[channel] = {};
		}
		this._channels[channel][socket.sid] = socket;
		if(!socket.channels)
			socket.channels = {};
		socket.channels[channel] = true;
		utils.sync(socket, "subscribe", { channel: channel });
	}
};

TokenSocketServer.prototype.unsubscribe = function(socket, channel){
	if(this.pubsubClient && socket && channel){
		if(this.debug)
			console.log("Sockjs: Unsubscribing socket ", socket.sid, channel);
		if(socket.channels && socket.channels[channel])
			delete socket.channels[channel];
		if(this._channels[channel]){
			delete this._channels[channel][socket.sid];
			if(Object.keys(this._channels[channel]).length === 0){
				this.pubsubClient.unsubscribe(channel);
				delete this._channels[channel];
			}
		}
		utils.sync(socket, "unsubscribe", { channel: channel });
	}
};

TokenSocketServer.prototype.sockets = function(){
	var self = this;
	return _.map(Object.keys(self._sockets), function(sid){
		return self._sockets[sid];
	});
};

TokenSocketServer.prototype.channels = function(){
	return Object.keys(this._channels);
};

// publish a message on a channel
TokenSocketServer.prototype.publish = function(channel, data){
	if(this.pubsubClient){
		if(this.debug)
			console.log("Sockjs: Publishing message", channel, data);
		if(typeof data === "object")
			data = JSON.stringify(data);
		this.redisClient.publish(channel, data);
	}
};

// publish a message on all channels
TokenSocketServer.prototype.broadcast = function(data){
	if(this.pubsubClient){
		var self = this;
		if(self.debug)
			console.log("Sockjs: Broadcasting message", data);
		if(typeof data === "object")
			data = JSON.stringify(data);
		self.redisClient.publish(ALL_CHANNEL, data);
	}
};

// remove all sockets and channels
TokenSocketServer.prototype.shutdown = function(){
	if(this.debug)
		console.log("Sockjs: Shutting down server...");
	var self = this;
	self.app.routes.get = _.filter(self.app.routes.get, function(route){
		return route.path !== self.tokenRoute;
	});
	if(self.debug)
		console.log("Sockjs: Removed token route");
	_.each(self._sockets, function(socket, sid){
		socket.end();
	});
	if(self.debug)
		console.log("Sockjs: Closed all sockets");
	self._sockets = {};
	if(self.pubsubClient){
		_.each(self._channels, function(sockets, channel){
			delete self._channels[channel];
			self.pubsubClient.unsubscribe(channel);
		});
		if(self.debug)
			console.log("Sockjs: Unsubscribed from all channels");
	}
};

module.exports = TokenSocketServer;

<commit_msg>tiny fix<commit_after>
var async = require("async"),
	_ = require("underscore"),
	uuid = require("node-uuid"),
	utils = require("./utils"),
	EventEmitter = require("events").EventEmitter,
	ALL_CHANNEL = "__all__";

var TokenSocketServer = function(options){
	var self = this;
	self.app = options.app;
	self.prefix = options.prefix || "/sockets";
	self.tokenRoute = options.tokenRoute || "/socket/token";
	self.redisClient = options.redisClient;
	self.socketServer = options.socketServer;
	self.socketController = options.socketController || {};
	self.cleanup = options.cleanup;
	self.authentication = options.authentication;
	self._channels = {};
	self._sockets = {};
	self._inTransit = {};
	self._emitter = new EventEmitter();

	if(options.debug)
		self.debug = options.debug;

	// create the token route
	var requestToken = function(req, res){
		async.waterfall([
			function(callback){
				if(self.debug)
					console.log("Sockjs: Sockjs token request received ", req.query);
				if(typeof self.authentication === "function")
					self.authentication(req, callback); // use custom function with "req" as a parameter
				else if(typeof self.authentication === "string")
					callback(null, req.session[self.authentication]); // use custom session property
				else
					callback(null, req.session && req.session.auth); // default
			}
		], function(error, auth){
			var type = utils.requestType(req);
			if(error){
				if(self.debug) console.log("Sockjs: Error issuing token", error);
				res[type](500, { error: error.message || error });
			}else if(!auth){
				if(self.debug) console.log("Sockjs: Rejected token request");
				res[type](400, { error: "Unauthorized" });
			}else{
				// issue a token
				var save = req.session || {};
				if(typeof auth === "object")
					save = auth;
				utils.issueToken(self.redisClient, save, function(error, token){
					if(self.debug) console.log("Sockjs: Attaching token to data: ", token, save);
					if(error)
						res[type](500, { error: error.message || error });
					else
						res[type](200, { token: token });
				});
			}
		});
	};

	if(options.customMiddleware){
		if(self.debug)
			console.log("Sockjs: Creating token route and attaching custom middleware");
		self.app.get(self.tokenRoute, options.customMiddleware, requestToken);
	}else{
		if(self.debug)
			console.log("Sockjs: Creating token route");
		self.app.get(self.tokenRoute, requestToken);
	}

	if(options.pubsubClient){
		if(self.debug)
			console.log("Sockjs: Setting up publish-subscribe functionality");
		self.pubsubClient = options.pubsubClient;
		self.pubsubClient.subscribe(ALL_CHANNEL);
		self.pubsubClient.on("message", function(channel, message){
			if(channel === ALL_CHANNEL){
				if(self.debug)
					console.log("Sockjs: Broadcasting message to all sockets on all channels", message);
				async.each(Object.keys(self._channels), function(_channel, callback){
					utils.writeSockets(self._channels[_channel], _channel, message);
					callback();
				});
			}else{
				if(self.debug)
					console.log("Sockjs: Pub-sub message received: ", channel, message);
				if(self._channels[channel])
					utils.writeSockets(self._channels[channel], channel, message);
			}
		});
	}

	if(self.cleanup)
		self.enableCleanup(self.cleanup);

	self.socketServer.on("connection", function(socket){
		if(self.debug) console.log("Sockjs: Socket connection initiated");
		var sid = uuid.v4();
		socket.sid = sid;
		socket.created = new Date().getTime();
		socket.channels = {};
		self._sockets[sid] = socket;
		socket.on("close", function(){
			if(self.debug)
				console.log("Sockjs: Socket connection closed", sid);
			delete self._sockets[sid];
			if(self.pubsubClient && socket.channels){
				_.each(socket.channels, function(channel){
					if(self._channels[channel]){
						delete self._channels[channel][socket.sid];
						if(Object.keys(self._channels[channel]).length === 0){
							self.pubsubClient.unsubscribe(channel);
							delete self._channels[channel];
						}
					}
				});
			}
		});

		socket.on("data", function(message){
			try{
				message = JSON.parse(message);
			}catch(e){
				return socket.write(JSON.stringify({ error: "Invalid message" }));
			}

			if(self.debug)
				console.log("Sockjs: Socket message received", message);

			var sendMessage = function(error, resp){
				if(error)
					message.error = error.message || error;
				else
					message.resp = resp;
				if(self.debug)
					console.log("Sockjs: Writing message on socket", socket.sid, message);
				socket.write(JSON.stringify(message));
			};

			if(message.rpc === "auth" && message.token){
				if(self.debug)
					console.log("Sockjs: Socket auth request received", message.token);
				if(!message.token.match(/[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}/i)){
					delete self._sockets[socket.id];
					return socket.end();
				}
				utils.verifyToken(self.redisClient, message.token, function(error, data){
					if(error || !data){
						if(self.debug)
							console.log("Sockjs: Error verifying token ", message.token, error);
						sendMessage(error.message || error || "Invalid token");
						delete self._sockets[socket.sid];
						socket.end();
					}else{
						if(self.debug)
							console.log("Sockjs: Successfully verified token ", message.token, data);
						socket.auth = data;
						// only allow tokens to work once
						utils.revokeToken(self.redisClient, message.token, function(err){
							if(err)
								throw err;
						});
						utils.checkListeners(self._emitter, "authentication", socket, socket.auth, function(){
							sendMessage(null, "success");
						});
					}
				});
			}else if(!socket.auth){
				if(self.debug)
					console.log("Sockjs: Invalid socket message. Ending connection", sid, message);
				message.error = "Not authorized";
				socket.write(JSON.stringify(message));
				socket.end();
				delete self._sockets[socket.sid];
			}else{
				// TODO clean this up
				if(message.rpc && message.rpc === "_subscribe"){
					utils.checkListeners(self._emitter, "subscribe", socket, message, function(error, allowed){
						if(error)
							sendMessage(error);
						else if(allowed)
							self.subscribe(socket, message.req.channel);
						else
							sendMessage("Not authorized");
					});
				}else if(message.rpc && message.rpc === "_publish"){
					utils.checkListeners(self._emitter, "publish", socket, message, function(error, allowed){
						if(error)
							sendMessage(error);
						else if(allowed)
							self.publish(message.req.channel, message.req.data);
						else
							sendMessage("Not authorized");
					});
				}else if(message.rpc && message.rpc === "_unsubscribe"){
					utils.checkListeners(self._emitter, "unsubscribe", socket, message, function(error, allowed){
						if(error)
							sendMessage(error);
						else if(allowed)
							self.unsubscribe(socket, message.req.channel);
						else
							sendMessage("Not authorized");
					});
				}else if(message.rpc && message.rpc === "_broadcast"){
					utils.checkListeners(self._emitter, "broadcast", socket, message, function(error, allowed){
						if(error)
							sendMessage(error);
						else if(allowed)
							self.broadcast(message.req.data);
						else
							sendMessage("Not authorized");
					});
				}else if(message.rpc && message.rpc === "_rpc"){
					if(message.fid && self._inTransit[message.fid]){
						var fn = self._inTransit[message.fid];
						if(typeof fn === "function")
							fn(message.resp.error, message.resp.data);
						delete self._inTransit[message.fid];
					}
				}else if(message.rpc){
					// proxy the socket controller
					var action = utils.checkController(self.socketController, message.rpc);
					if(action && typeof action === "function")
						action(socket.auth, message.req, sendMessage, socket);
					else
						sendMessage("Function not found");
				}else{
					sendMessage("Invalid request");
				}
			}
		});
	});

};

// expose a subset of the event emitter functions

TokenSocketServer.prototype.on = TokenSocketServer.prototype.addListener = function(event, callback){
	this._emitter.on(event, callback);
};

TokenSocketServer.prototype.removeListener = function(event, listener){
	this._emitter.removeListener(event, listener);
};

TokenSocketServer.prototype.removeAllListeners = function(event){
	this._emitter.removeAllListeners(event);
};

TokenSocketServer.prototype.rpc = function(socket, command, args, callback){
	var fid = uuid.v4();
	this._inTransit[fid] = callback;
	socket.write(JSON.stringify({
		internal: true,
		command: "rpc",
		data: {
			fid: fid,
			command: command,
			args: args
		}
	}));
};

TokenSocketServer.prototype.enableCleanup = function(interval){
	if(this.debug)
		console.log("Sockjs: Enabling cleanup process");
	this.cleanup = typeof interval === "number" ? interval : 5000;
	if(this.cleanupTimer)
		clearInterval(this.cleanupTimer);
	if(!(this.cleanupFn && typeof this.cleanupFn === "function")){
		this.cleanupFn = function(instance){
			if(instance.debug)
				console.log("Sockjs: Running cleanup process");
			var now = new Date().getTime();
			var diff = now - instance.cleanup;
			var deleted = [];
			// cleanup sockets
			async.each(instance.sockets() || [], function(socket, callback){
				if(!socket.auth && socket.created && socket.created < diff){
					if(instance.debug)
						console.log("Sockjs: Cleaning up socket", socket.sid);
					deleted.push(socket.sid);
					socket.end();
					delete instance._sockets[socket.sid];
				}
				callback();
			}, function(){
				// cleanup channels
				if(instance.pubsubClient){
					async.each(Object.keys(instance._channels), function(channel, callback){
						async.each(deleted, function(sid, cb){
							delete instance._channels[channel][sid];
							if(Object.keys(instance._channels[channel]).length === 0)
								instance.pubsubClient.unsubscribe(channel);
							cb();
						}, callback);
					});
				}
			});
		};
	}
	this.cleanupTimer = setInterval(this.cleanupFn, this.cleanup, this);
};

TokenSocketServer.prototype.disableCleanup = function(){
	if(this.debug)
		console.log("Sockjs: Disabling cleanup");
	this.cleanup = null;
	if(this.cleanupTimer)
		clearInterval(this.cleanupTimer);
};

TokenSocketServer.prototype.subscribe = function(socket, channel){
	if(this.pubsubClient){
		if(this.debug)
			console.log("Sockjs: Subscribing socket ", socket.sid, channel);
		if(!this._channels[channel]){
			this.pubsubClient.subscribe(channel);
			this._channels[channel] = {};
		}
		this._channels[channel][socket.sid] = socket;
		if(!socket.channels)
			socket.channels = {};
		socket.channels[channel] = true;
		utils.sync(socket, "subscribe", { channel: channel });
	}
};

TokenSocketServer.prototype.unsubscribe = function(socket, channel){
	if(this.pubsubClient && socket && channel){
		if(this.debug)
			console.log("Sockjs: Unsubscribing socket ", socket.sid, channel);
		if(socket.channels && socket.channels[channel])
			delete socket.channels[channel];
		if(this._channels[channel]){
			delete this._channels[channel][socket.sid];
			if(Object.keys(this._channels[channel]).length === 0){
				this.pubsubClient.unsubscribe(channel);
				delete this._channels[channel];
			}
		}
		utils.sync(socket, "unsubscribe", { channel: channel });
	}
};

TokenSocketServer.prototype.sockets = function(){
	var self = this;
	return _.map(Object.keys(self._sockets), function(sid){
		return self._sockets[sid];
	});
};

TokenSocketServer.prototype.channels = function(){
	return Object.keys(this._channels);
};

// publish a message on a channel
TokenSocketServer.prototype.publish = function(channel, data){
	if(this.pubsubClient){
		if(this.debug)
			console.log("Sockjs: Publishing message", channel, data);
		if(typeof data === "object")
			data = JSON.stringify(data);
		this.redisClient.publish(channel, data);
	}
};

// publish a message on all channels
TokenSocketServer.prototype.broadcast = function(data){
	if(this.pubsubClient){
		var self = this;
		if(self.debug)
			console.log("Sockjs: Broadcasting message", data);
		if(typeof data === "object")
			data = JSON.stringify(data);
		self.redisClient.publish(ALL_CHANNEL, data);
	}
};

// remove all sockets and channels
TokenSocketServer.prototype.shutdown = function(){
	if(this.debug)
		console.log("Sockjs: Shutting down server...");
	var self = this;
	self.app.routes.get = _.filter(self.app.routes.get, function(route){
		return route.path !== self.tokenRoute;
	});
	if(self.debug)
		console.log("Sockjs: Removed token route");
	_.each(self._sockets, function(socket, sid){
		socket.end();
	});
	if(self.debug)
		console.log("Sockjs: Closed all sockets");
	self._sockets = {};
	if(self.pubsubClient){
		_.each(self._channels, function(sockets, channel){
			delete self._channels[channel];
			self.pubsubClient.unsubscribe(channel);
		});
		if(self.debug)
			console.log("Sockjs: Unsubscribed from all channels");
	}
};

module.exports = TokenSocketServer;

<|endoftext|>


<commit_before>// @flow
import { Component, createElement } from 'react'
import PropTypes from 'prop-types'

import type { Theme } from './ThemeProvider'

import isTag from '../utils/isTag'
import isStyledComponent from '../utils/isStyledComponent'
import getComponentName from '../utils/getComponentName'
import determineTheme from '../utils/determineTheme'
import type { RuleSet, Target } from '../types'

import { CHANNEL, CHANNEL_NEXT, CONTEXT_CHANNEL_SHAPE } from './ThemeProvider'

export default (constructWithOptions: Function, InlineStyle: Function) => {
  class BaseStyledNativeComponent extends Component {
    static target: Target
    static styledComponentId: string
    static attrs: Object
    static inlineStyle: Object
    root: ?Object

    attrs = {}
    state = {
      theme: null,
      generatedStyles: undefined,
    }

    unsubscribeId: number = -1

    unsubscribeFromContext() {
      if (this.unsubscribeId !== -1) {
        this.context[CHANNEL_NEXT].unsubscribe(this.unsubscribeId)
      }
    }

    buildExecutionContext(theme: any, props: any) {
      const { attrs } = this.constructor
      const context = { ...props, theme }
      if (attrs === undefined) {
        return context
      }

      this.attrs = Object.keys(attrs).reduce((acc, key) => {
        const attr = attrs[key]
        // eslint-disable-next-line no-param-reassign
        acc[key] = typeof attr === 'function' ? attr(context) : attr
        return acc
      }, {})

      return { ...context, ...this.attrs }
    }

    generateAndInjectStyles(theme: any, props: any) {
      const { inlineStyle } = this.constructor
      const executionContext = this.buildExecutionContext(theme, props)

      return inlineStyle.generateStyleObject(executionContext)
    }

    componentWillMount() {
      // If there is a theme in the context, subscribe to the event emitter. This
      // is necessary due to pure components blocking context updates, this circumvents
      // that by updating when an event is emitted
      const styledContext = this.context[CHANNEL_NEXT]
      if (styledContext !== undefined) {
        const { subscribe } = styledContext
        this.unsubscribeId = subscribe(nextTheme => {
          // This will be called once immediately
          const theme = determineTheme(
            this.props,
            nextTheme,
            this.constructor.defaultProps
          )
          const generatedStyles = this.generateAndInjectStyles(
            theme,
            this.props
          )

          this.setState({ theme, generatedStyles })
        })
      } else {
        // eslint-disable-next-line react/prop-types
        const theme = this.props.theme || {}
        const generatedStyles = this.generateAndInjectStyles(theme, this.props)
        this.setState({ theme, generatedStyles })
      }
    }

    componentWillReceiveProps(nextProps: {
      theme?: Theme,
      [key: string]: any,
    }) {
      this.setState(oldState => {
        const theme = determineTheme(
          nextProps,
          oldState.theme,
          this.constructor.defaultProps
        )
        const generatedStyles = this.generateAndInjectStyles(theme, nextProps)

        return { theme, generatedStyles }
      })
    }

    componentWillUnmount() {
      this.unsubscribeFromContext()
    }

    setNativeProps(nativeProps: Object) {
      if (this.root !== undefined) {
        // $FlowFixMe
        this.root.setNativeProps(nativeProps)
      } else if (process.env.NODE_ENV !== 'production') {
        const { displayName } = this.constructor

        // eslint-disable-next-line no-console
        console.warn(
          'setNativeProps was called on a Styled Component wrapping a stateless functional component. ' +
            'In this case no ref will be stored, and instead an innerRef prop will be passed on.\n' +
            `Check whether the stateless functional component is passing on innerRef as a ref in ${displayName}.`
        )
      }
    }

    onRef = (node: any) => {
      // eslint-disable-next-line react/prop-types
      const { innerRef } = this.props
      this.root = node

      if (typeof innerRef === 'function') {
        innerRef(node)
      } else if (typeof innerRef === 'object' && innerRef) {
        innerRef.current = node
      }
    }

    render() {
      // eslint-disable-next-line react/prop-types
      const { children, style } = this.props
      const { generatedStyles } = this.state
      const { target } = this.constructor

      const propsForElement = {
        ...this.attrs,
        ...this.props,
        style: [generatedStyles, style],
      }

      if (
        !isStyledComponent(target) &&
        // NOTE: We can't pass a ref to a stateless functional component
        (typeof target !== 'function' ||
          (target.prototype && 'isReactComponent' in target.prototype))
      ) {
        propsForElement.ref = this.onRef
        delete propsForElement.innerRef
      } else {
        propsForElement.innerRef = this.onRef
      }

      return createElement(target, propsForElement, children)
    }
  }

  const createStyledNativeComponent = (
    target: Target,
    options: Object,
    rules: RuleSet
  ) => {
    const {
      displayName = isTag(target)
        ? `styled.${target}`
        : `Styled(${getComponentName(target)})`,
      ParentComponent = BaseStyledNativeComponent,
      rules: extendingRules,
      attrs,
    } = options

    const inlineStyle = new InlineStyle(
      extendingRules === undefined ? rules : extendingRules.concat(rules)
    )

    class StyledNativeComponent extends ParentComponent {
      static displayName = displayName
      static target = target
      static attrs = attrs
      static inlineStyle = inlineStyle

      static contextTypes = {
        [CHANNEL]: PropTypes.func,
        [CHANNEL_NEXT]: CONTEXT_CHANNEL_SHAPE,
      }

      // NOTE: This is so that isStyledComponent passes for the innerRef unwrapping
      static styledComponentId = 'StyledNativeComponent'

      static withComponent(tag) {
        const { displayName: _, componentId: __, ...optionsToCopy } = options
        const newOptions = {
          ...optionsToCopy,
          ParentComponent: StyledNativeComponent,
        }
        return createStyledNativeComponent(tag, newOptions, rules)
      }

      static get extend() {
        const {
          displayName: _,
          componentId: __,
          rules: rulesFromOptions,
          ...optionsToCopy
        } = options

        const newRules =
          rulesFromOptions === undefined
            ? rules
            : rulesFromOptions.concat(rules)

        const newOptions = {
          ...optionsToCopy,
          rules: newRules,
          ParentComponent: StyledNativeComponent,
        }

        return constructWithOptions(
          createStyledNativeComponent,
          target,
          newOptions
        )
      }
    }

    return StyledNativeComponent
  }

  return createStyledNativeComponent
}
<commit_msg>Stronger type-check for object refs.<commit_after>// @flow
import { Component, createElement } from 'react'
import PropTypes from 'prop-types'

import type { Theme } from './ThemeProvider'

import isTag from '../utils/isTag'
import isStyledComponent from '../utils/isStyledComponent'
import getComponentName from '../utils/getComponentName'
import determineTheme from '../utils/determineTheme'
import type { RuleSet, Target } from '../types'

import { CHANNEL, CHANNEL_NEXT, CONTEXT_CHANNEL_SHAPE } from './ThemeProvider'

export default (constructWithOptions: Function, InlineStyle: Function) => {
  class BaseStyledNativeComponent extends Component {
    static target: Target
    static styledComponentId: string
    static attrs: Object
    static inlineStyle: Object
    root: ?Object

    attrs = {}
    state = {
      theme: null,
      generatedStyles: undefined,
    }

    unsubscribeId: number = -1

    unsubscribeFromContext() {
      if (this.unsubscribeId !== -1) {
        this.context[CHANNEL_NEXT].unsubscribe(this.unsubscribeId)
      }
    }

    buildExecutionContext(theme: any, props: any) {
      const { attrs } = this.constructor
      const context = { ...props, theme }
      if (attrs === undefined) {
        return context
      }

      this.attrs = Object.keys(attrs).reduce((acc, key) => {
        const attr = attrs[key]
        // eslint-disable-next-line no-param-reassign
        acc[key] = typeof attr === 'function' ? attr(context) : attr
        return acc
      }, {})

      return { ...context, ...this.attrs }
    }

    generateAndInjectStyles(theme: any, props: any) {
      const { inlineStyle } = this.constructor
      const executionContext = this.buildExecutionContext(theme, props)

      return inlineStyle.generateStyleObject(executionContext)
    }

    componentWillMount() {
      // If there is a theme in the context, subscribe to the event emitter. This
      // is necessary due to pure components blocking context updates, this circumvents
      // that by updating when an event is emitted
      const styledContext = this.context[CHANNEL_NEXT]
      if (styledContext !== undefined) {
        const { subscribe } = styledContext
        this.unsubscribeId = subscribe(nextTheme => {
          // This will be called once immediately
          const theme = determineTheme(
            this.props,
            nextTheme,
            this.constructor.defaultProps
          )
          const generatedStyles = this.generateAndInjectStyles(
            theme,
            this.props
          )

          this.setState({ theme, generatedStyles })
        })
      } else {
        // eslint-disable-next-line react/prop-types
        const theme = this.props.theme || {}
        const generatedStyles = this.generateAndInjectStyles(theme, this.props)
        this.setState({ theme, generatedStyles })
      }
    }

    componentWillReceiveProps(nextProps: {
      theme?: Theme,
      [key: string]: any,
    }) {
      this.setState(oldState => {
        const theme = determineTheme(
          nextProps,
          oldState.theme,
          this.constructor.defaultProps
        )
        const generatedStyles = this.generateAndInjectStyles(theme, nextProps)

        return { theme, generatedStyles }
      })
    }

    componentWillUnmount() {
      this.unsubscribeFromContext()
    }

    setNativeProps(nativeProps: Object) {
      if (this.root !== undefined) {
        // $FlowFixMe
        this.root.setNativeProps(nativeProps)
      } else if (process.env.NODE_ENV !== 'production') {
        const { displayName } = this.constructor

        // eslint-disable-next-line no-console
        console.warn(
          'setNativeProps was called on a Styled Component wrapping a stateless functional component. ' +
            'In this case no ref will be stored, and instead an innerRef prop will be passed on.\n' +
            `Check whether the stateless functional component is passing on innerRef as a ref in ${displayName}.`
        )
      }
    }

    onRef = (node: any) => {
      // eslint-disable-next-line react/prop-types
      const { innerRef } = this.props
      this.root = node

      if (typeof innerRef === 'function') {
        innerRef(node)
      } else if (
        typeof innerRef === 'object' &&
        innerRef &&
        innerRef.hasOwnProperty('current')
      ) {
        innerRef.current = node
      }
    }

    render() {
      // eslint-disable-next-line react/prop-types
      const { children, style } = this.props
      const { generatedStyles } = this.state
      const { target } = this.constructor

      const propsForElement = {
        ...this.attrs,
        ...this.props,
        style: [generatedStyles, style],
      }

      if (
        !isStyledComponent(target) &&
        // NOTE: We can't pass a ref to a stateless functional component
        (typeof target !== 'function' ||
          (target.prototype && 'isReactComponent' in target.prototype))
      ) {
        propsForElement.ref = this.onRef
        delete propsForElement.innerRef
      } else {
        propsForElement.innerRef = this.onRef
      }

      return createElement(target, propsForElement, children)
    }
  }

  const createStyledNativeComponent = (
    target: Target,
    options: Object,
    rules: RuleSet
  ) => {
    const {
      displayName = isTag(target)
        ? `styled.${target}`
        : `Styled(${getComponentName(target)})`,
      ParentComponent = BaseStyledNativeComponent,
      rules: extendingRules,
      attrs,
    } = options

    const inlineStyle = new InlineStyle(
      extendingRules === undefined ? rules : extendingRules.concat(rules)
    )

    class StyledNativeComponent extends ParentComponent {
      static displayName = displayName
      static target = target
      static attrs = attrs
      static inlineStyle = inlineStyle

      static contextTypes = {
        [CHANNEL]: PropTypes.func,
        [CHANNEL_NEXT]: CONTEXT_CHANNEL_SHAPE,
      }

      // NOTE: This is so that isStyledComponent passes for the innerRef unwrapping
      static styledComponentId = 'StyledNativeComponent'

      static withComponent(tag) {
        const { displayName: _, componentId: __, ...optionsToCopy } = options
        const newOptions = {
          ...optionsToCopy,
          ParentComponent: StyledNativeComponent,
        }
        return createStyledNativeComponent(tag, newOptions, rules)
      }

      static get extend() {
        const {
          displayName: _,
          componentId: __,
          rules: rulesFromOptions,
          ...optionsToCopy
        } = options

        const newRules =
          rulesFromOptions === undefined
            ? rules
            : rulesFromOptions.concat(rules)

        const newOptions = {
          ...optionsToCopy,
          rules: newRules,
          ParentComponent: StyledNativeComponent,
        }

        return constructWithOptions(
          createStyledNativeComponent,
          target,
          newOptions
        )
      }
    }

    return StyledNativeComponent
  }

  return createStyledNativeComponent
}
<|endoftext|>