<commit_before>"use strict";

const msg_types = require("webrtc-conductor").enums,
	  WebSocket = require("ws"),
	  u = require("./UtilFunctions.js");

class BootstrapChannelClient {
	// Single use bootstrap connection class.

	constructor(wsAddr, chord){
		this._manager = null;
		this.ws = null;
		this.addr = wsAddr;
		this.chord = chord;

		this.finalID = null;
		this.initialID = chord.id.idString;
		this.renamed = false;

		this.serverPem = null;
	}

	get internalID(){
		return "Boot-Client";
	}

	onbind(){
		let t = this;
		this.ws = new WebSocket(this.addr);
		u.log(t.chord, "Opening WebSocket connection.");

		return new Promise((resolve, reject) => {
			this.ws.onopen = () => {
				u.log(t.chord, "WebSocket opened.");
				//Take over the message handler until registration is done.
				this.ws.onmessage = evt => {
					let obj = JSON.parse(evt.data);
					switch(obj.type){
						case "bstrap-wel":
							u.log(t.chord, "Server has replied, perform the exchange of IDs.");
							this.finalID = obj.id;
							this.serverPem = obj.data;
							this.ws.onmessage = evt => {t._manager.response(evt, t);};
							resolve(true);
							break;
						default:
							throw new Error("Illegal class "+type+" of message sent to "+this.internalID+" channel!");
					}
				};

				u.log(t.chord, "Asking server for its ID on the network.");
				safeSend(this.ws, {
					type: "bstrap-reg",
					id: this.initialID,
					data: this.chord.pubKeyPem
				});
			};

			this.ws.onerror = (e) => {reject(e);};
		});
	}

	send(id,type,data){
		u.log(t.chord, "BSTRAP: SENDING "+type);

		switch(type){
			case msg_types.MSG_SDP_OFFER:
				//In this case, id refers to the CLIENT'S ID.
				this.renamed = true;
				this._manager.renameConnection(this.initialID, this.finalID);

				safeSend(this.ws, {
					type: "bstrap-offer",
					id: id,
					data: data
				});
				break;
			case msg_types.MSG_ICE:
				safeSend(this.ws, {
					type: "bstrap-ice",
					id: id,
					data: data
				});
				break;
			default:
				throw new Error("Illegal class "+type+" of message sent to "+this.internalID+" channel!");
		}
	}

	onmessage(evt){
		let obj = JSON.parse(evt.data),
			out = {
			type: null,
			data: obj.data,
			id: null
		};

		switch(obj.type){
			case "bstrap-ice":
				out.type = msg_types.RESPONSE_ICE;
				break;
			case "bstrap-sdpReply":
				out.type = msg_types.RESPONSE_SDP_ANSWER;
				break;
			default:
				out.type = msg_types.RESPONSE_NONE;
				break;
		}

		out.id = this.renamed ? this.finalID : this.initialID;

		return out;
	}

	close(){
		this.ws.close();
	}
}

function safeSend(ws, obj){
	try{
		ws.send(JSON.stringify(obj));
	} catch (e) {
		console.log(e);
	}
}

module.exports = BootstrapChannelClient;
<commit_msg>Hurr I'm dumb.<commit_after>"use strict";

const msg_types = require("webrtc-conductor").enums,
	  WebSocket = require("ws"),
	  u = require("./UtilFunctions.js");

class BootstrapChannelClient {
	// Single use bootstrap connection class.

	constructor(wsAddr, chord){
		this._manager = null;
		this.ws = null;
		this.addr = wsAddr;
		this.chord = chord;

		this.finalID = null;
		this.initialID = chord.id.idString;
		this.renamed = false;

		this.serverPem = null;
	}

	get internalID(){
		return "Boot-Client";
	}

	onbind(){
		let t = this;
		this.ws = new WebSocket(this.addr);
		u.log(t.chord, "Opening WebSocket connection.");

		return new Promise((resolve, reject) => {
			this.ws.onopen = () => {
				u.log(t.chord, "WebSocket opened.");
				//Take over the message handler until registration is done.
				this.ws.onmessage = evt => {
					let obj = JSON.parse(evt.data);
					switch(obj.type){
						case "bstrap-wel":
							u.log(t.chord, "Server has replied, perform the exchange of IDs.");
							this.finalID = obj.id;
							this.serverPem = obj.data;
							this.ws.onmessage = evt => {t._manager.response(evt, t);};
							resolve(true);
							break;
						default:
							throw new Error("Illegal class "+type+" of message sent to "+this.internalID+" channel!");
					}
				};

				u.log(t.chord, "Asking server for its ID on the network.");
				safeSend(this.ws, {
					type: "bstrap-reg",
					id: this.initialID,
					data: this.chord.pubKeyPem
				});
			};

			this.ws.onerror = (e) => {reject(e);};
		});
	}

	send(id,type,data){
		u.log(this.chord, "BSTRAP: SENDING "+type);

		switch(type){
			case msg_types.MSG_SDP_OFFER:
				//In this case, id refers to the CLIENT'S ID.
				this.renamed = true;
				this._manager.renameConnection(this.initialID, this.finalID);

				safeSend(this.ws, {
					type: "bstrap-offer",
					id: id,
					data: data
				});
				break;
			case msg_types.MSG_ICE:
				safeSend(this.ws, {
					type: "bstrap-ice",
					id: id,
					data: data
				});
				break;
			default:
				throw new Error("Illegal class "+type+" of message sent to "+this.internalID+" channel!");
		}
	}

	onmessage(evt){
		let obj = JSON.parse(evt.data),
			out = {
			type: null,
			data: obj.data,
			id: null
		};

		switch(obj.type){
			case "bstrap-ice":
				out.type = msg_types.RESPONSE_ICE;
				break;
			case "bstrap-sdpReply":
				out.type = msg_types.RESPONSE_SDP_ANSWER;
				break;
			default:
				out.type = msg_types.RESPONSE_NONE;
				break;
		}

		out.id = this.renamed ? this.finalID : this.initialID;

		return out;
	}

	close(){
		this.ws.close();
	}
}

function safeSend(ws, obj){
	try{
		ws.send(JSON.stringify(obj));
	} catch (e) {
		console.log(e);
	}
}

module.exports = BootstrapChannelClient;
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import PropTypes from 'prop-types'
import TableRow from './TableRow/index.js'
import Button from '../../Components/Button/index.js'

export default class Table extends Component {

  static propTypes = {
    readOnly: PropTypes.bool,
    columns: PropTypes.arrayOf(PropTypes.shape({
      name: PropTypes.string.isRequired,
      type: PropTypes.string
    }).isRequired)
  }

  static defaultProps = {
    columns: {
      name: "",
      type: "text"
    }
  }

  state = {
    addedRows: []
  }

  addRow = () => {
    const defaultValues = this.props.columns.map(() => {return ""})
    const newRow = <TableRow key={this.state.addedRows.length + 1} editing cellTypes={this.props.columns.map((column) => {return column.type})} cellValues={defaultValues} />
    this.setState((prevState) => ({
          addedRows: prevState.addedRows.concat([newRow])
        }))
  }

  render(){
    if(this.props.readOnly){
      return(

          <TableRow
            readOnly
            cellValues={this.props.columns.map((column) => {return column.name})}
            cellTypes={this.props.columns.map((column) => {return column.type})}
          />
          {this.props.children}

      )
    }else{
      return(

          <TableRow
            readOnly
            cellValues={this.props.columns.map((column) => {return column.name})}
            cellTypes={this.props.columns.map((column) => {return column.type})}
          />
          {this.props.children}
          {this.state.addedRows}
          <Button
            className="round"
            buttonText="+"
            onClick={this.addRow}
          />

      )
    }
  }

}<commit_msg>make the columns array required<commit_after>import React, { Component } from 'react'
import PropTypes from 'prop-types'
import TableRow from './TableRow/index.js'
import Button from '../../Components/Button/index.js'

export default class Table extends Component {

  static propTypes = {
    readOnly: PropTypes.bool,
    columns: PropTypes.arrayOf(PropTypes.shape({
      name: PropTypes.string.isRequired,
      type: PropTypes.string
    }).isRequired).isRequired
  }

  static defaultProps = {
    columns: {
      name: "",
      type: "text"
    }
  }

  state = {
    addedRows: []
  }

  addRow = () => {
    const defaultValues = this.props.columns.map(() => {return ""})
    const newRow = <TableRow key={this.state.addedRows.length + 1} editing cellTypes={this.props.columns.map((column) => {return column.type})} cellValues={defaultValues} />
    this.setState((prevState) => ({
          addedRows: prevState.addedRows.concat([newRow])
        }))
  }

  render(){
    if(this.props.readOnly){
      return(

          <TableRow
            readOnly
            cellValues={this.props.columns.map((column) => {return column.name})}
            cellTypes={this.props.columns.map((column) => {return column.type})}
          />
          {this.props.children}

      )
    }else{
      return(

          <TableRow
            readOnly
            cellValues={this.props.columns.map((column) => {return column.name})}
            cellTypes={this.props.columns.map((column) => {return column.type})}
          />
          {this.props.children}
          {this.state.addedRows}
          <Button
            className="round"
            buttonText="+"
            onClick={this.addRow}
          />

      )
    }
  }

}<|endoftext|>


<commit_before>// @flow
import { takeEvery, put } from 'redux-saga/effects';
import { saveLastActions, SAVE_LAST_ACTIONS } from 'redux-modules/app';

export function* logLastActions(): Generator<*, *, *> {
  yield takeEvery('*', function* logger(action) {
    const log = action.type !== SAVE_LAST_ACTIONS && !action.type.startsWith('user/');
    if (log) {
      yield put(saveLastActions(action));
    }
  });
}
<commit_msg>fix: hide last actions middleware in dev mode<commit_after>// @flow
import { takeEvery, put } from 'redux-saga/effects';
import { saveLastActions, SAVE_LAST_ACTIONS } from 'redux-modules/app';

declare var __DEV__: boolean;

export function* logLastActions(): Generator<*, *, *> {
  yield takeEvery('*', function* logger(action) {
    const log = action.type !== SAVE_LAST_ACTIONS && !action.type.startsWith('user/');
    if (log && !__DEV__) {
      yield put(saveLastActions(action));
    }
  });
}
<|endoftext|>


<commit_before>/**
 * Route Mappings
 * (sails.config.routes)
 *
 * Your routes map URLs to views and controllers.
 *
 * If Sails receives a URL that doesn't match any of the routes below,
 * it will check for matching files (images, scripts, stylesheets, etc.)
 * in your assets directory.  e.g. `http://localhost:1337/images/foo.jpg`
 * might match an image file: `/assets/images/foo.jpg`
 *
 * Finally, if those don't match either, the default 404 handler is triggered.
 * See `api/responses/notFound.js` to adjust your app's 404 logic.
 *
 * Note: Sails doesn't ACTUALLY serve stuff from `assets`-- the default Gruntfile in Sails copies
 * flat files from `assets` to `.tmp/public`.  This allows you to do things like compile LESS or
 * CoffeeScript for the front-end.
 *
 * For more information on configuring custom routes, check out:
 * http://sailsjs.org/#/documentation/concepts/Routes/RouteTargetSyntax.html
 */

module.exports.routes = {

  /***************************************************************************
  *                                                                          *
  * Make the view located at `views/homepage.ejs` (or `views/homepage.jade`, *
  * etc. depending on your default view engine) your home page.              *
  *                                                                          *
  * (Alternatively, remove this and add an `index.html` file in your         *
  * `assets` directory)                                                      *
  *                                                                          *
  ***************************************************************************/

  '/': {
    view: 'homepage'
  }

  /***************************************************************************
  *                                                                          *
  * Custom routes here...                                                    *
  *                                                                          *
  *  If a request to a URL doesn't match any of the custom routes above, it  *
  * is matched against Sails route blueprints. See `config/blueprints.js`    *
  * for configuration options and examples.                                  *
  *                                                                          *
  ***************************************************************************/

};
<commit_msg>Update routes.js<commit_after>/**
 * Route Mappings
 * (sails.config.routes)
 *
 * Your routes map URLs to views and controllers.
 *
 * If Sails receives a URL that doesn't match any of the routes below,
 * it will check for matching files (images, scripts, stylesheets, etc.)
 * in your assets directory.  e.g. `http://localhost:1337/images/foo.jpg`
 * might match an image file: `/assets/images/foo.jpg`
 *
 * Finally, if those don't match either, the default 404 handler is triggered.
 * See `api/responses/notFound.js` to adjust your app's 404 logic.
 *
 * Note: Sails doesn't ACTUALLY serve stuff from `assets`-- the default Gruntfile in Sails copies
 * flat files from `assets` to `.tmp/public`.  This allows you to do things like compile LESS or
 * CoffeeScript for the front-end.
 *
 * For more information on configuring custom routes, check out:
 * http://sailsjs.org/#/documentation/concepts/Routes/RouteTargetSyntax.html
 */

module.exports.routes = {

  /***************************************************************************
  *                                                                          *
  * Make the view located at `views/homepage.ejs` (or `views/homepage.jade`, *
  * etc. depending on your default view engine) your home page.              *
  *                                                                          *
  * (Alternatively, remove this and add an `index.html` file in your         *
  * `assets` directory)                                                      *
  *                                                                          *
  ***************************************************************************/

  '/': {
    view: 'homepage'
  }

  /***************************************************************************
  *                                                                          *
  * Custom routes here...                                                    *
  *                                                                          *
  * If a request to a URL doesn't match any of the custom routes above, it   *
  * is matched against Sails route blueprints. See `config/blueprints.js`    *
  * for configuration options and examples.                                  *
  *                                                                          *
  ***************************************************************************/

};
<|endoftext|>


<commit_before>/*
 * Copyright 2014, Gregg Tavares.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Gregg Tavares. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

"use strict";

define(function() {
  /**
   * Shell class for offline use.
   *
   * You can use this class as a substitute for NetPlayer when
   * offline. It provides a no-op version of sendCmd so when
   * your game tries to send a message to the contoller nothing
   * happens.
   *
   * It also provides a sendEvent method you can use to trigger
   * events in your game as though they were from the controller.
   *
   * @constructor
   * @alias LocalNetPlayer
   * @example
   *   if (!globals.haveServer) {
   *     // We're testing locally so just manually create a
   *     // player.
   *
   *     var localNetPlayer1 = new LocalNetPlayer();
   *     var player1 = new MyPlayer(localNetPlayer1);
   *     addPlayer(player1);
   *
   *     // pretend player1 one got a message from the
   *     // controller when any key is pressed
   *
   *     window.addEventListner('keypress', function() {
   *       localNetPlayer1.sendEvent('jump', { power: 10 });
   *     });
   *
   *   } else {
   *
   *     // We're online so create players as they connect.
   *     var server = new GameServer(...);
   *     server.addEventListener(
   *         'playerconnect',
   *         function(netPlayer) {
   *           addPlayer(new MyPlayer(netPlayer));
   *         });
   */
  var LocalNetPlayer = (function() {
    var _count = 0;
    return function() {
      this.id = ++_count;
      this.eventHandlers = { };
    }
  }());

  /**
   * @see NetPlayer.addEventListener
   */
  LocalNetPlayer.prototype.addEventListener = function(eventType, handler) {
    this.eventHandlers[eventType] = handler;
  };

  /**
   * @see NetPlayer.removeEventListener
   */
  LocalNetPlayer.prototype.removeEventListener = function(eventType) {
    this.eventHandlers[eventType] = undefined;
  };

  /**
   * @see NetPlayer.removeAllListeners
   */
  LocalNetPlayer.prototype.removeAllListeners = function() {
    this.eventHanders = { };
  };

  LocalNetPlayer.prototype.sendCmd = function() {
    // No-op because this is a local player.
  };

  /**
   * Sends an event to this netplayer.
   *
   * Use this to emit synthetic events into the game. Effectively
   * whatever messages you're sending from your controller you can
   * send here based on events on the computer. In other words to
   * simulate having a phone connected you can emit events based
   * on mouse movement or keypressed that correspond to the same
   * events that would have been emitted if a phone was actually
   * connected and programmed to send similar events.
   */
  LocalNetPlayer.prototype.sendEvent = function(eventType, data) {
    var fn = this.eventHandlers[eventType];
    if (fn) {
      fn.call(this, data);
    } else {
      console.error("LocalNetPlayer: Unknown Event: " + eventType);
    }
  };

  LocalNetPlayer.prototype.switchGame = function(id, data) {
  };

  return LocalNetPlayer;
});

<commit_msg>add game msgs to localnetplayer<commit_after>/*
 * Copyright 2014, Gregg Tavares.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Gregg Tavares. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

"use strict";

define(function() {
  /**
   * Shell class for offline use.
   *
   * You can use this class as a substitute for NetPlayer when
   * offline. It provides a no-op version of sendCmd so when
   * your game tries to send a message to the contoller nothing
   * happens.
   *
   * It also provides a sendEvent method you can use to trigger
   * events in your game as though they were from the controller.
   *
   * @constructor
   * @alias LocalNetPlayer
   * @example
   *   if (!globals.haveServer) {
   *     // We're testing locally so just manually create a
   *     // player.
   *
   *     var localNetPlayer1 = new LocalNetPlayer();
   *     var player1 = new MyPlayer(localNetPlayer1);
   *     addPlayer(player1);
   *
   *     // pretend player1 one got a message from the
   *     // controller when any key is pressed
   *
   *     window.addEventListner('keypress', function() {
   *       localNetPlayer1.sendEvent('jump', { power: 10 });
   *     });
   *
   *   } else {
   *
   *     // We're online so create players as they connect.
   *     var server = new GameServer(...);
   *     server.addEventListener(
   *         'playerconnect',
   *         function(netPlayer) {
   *           addPlayer(new MyPlayer(netPlayer));
   *         });
   */
  var LocalNetPlayer = (function() {
    var _count = 0;
    return function() {
      this.id = ++_count;
      this.eventHandlers = { };
      this.gameEventHandlers = { };
    }
  }());

  /**
   * @see NetPlayer.addEventListener
   */
  LocalNetPlayer.prototype.addEventListener = function(eventType, handler) {
    this.eventHandlers[eventType] = handler;
  };

  /**
   * @see NetPlayer.removeEventListener
   */
  LocalNetPlayer.prototype.removeEventListener = function(eventType) {
    this.eventHandlers[eventType] = undefined;
  };

  /**
   * @see NetPlayer.removeAllListeners
   */
  LocalNetPlayer.prototype.removeAllListeners = function() {
    this.eventHanders = { };
  };

  /**
   */
  LocalNetPlayer.prototype.addGameEventListener = function(eventType, handler) {
    this.gameEventHandlers[eventType] = handler;
  };

  /**
   */
  LocalNetPlayer.prototype.removeGameEventListener = function(eventType) {
    this.gameEventHandlers[eventType] = undefined;
  };

  /**
   */
  LocalNetPlayer.prototype.removeGameAllListeners = function() {
    this.gameEventHanders = { };
  };

  LocalNetPlayer.prototype.sendCmd = function(cmd, data) {
    var fn = this.gameEventHandlers[cmd];
    if (fn) {
      fn.call(this, data);
    }
  };

  /**
   * Sends an event to this netplayer.
   *
   * Use this to emit synthetic events into the game. Effectively
   * whatever messages you're sending from your controller you can
   * send here based on events on the computer. In other words to
   * simulate having a phone connected you can emit events based
   * on mouse movement or keypressed that correspond to the same
   * events that would have been emitted if a phone was actually
   * connected and programmed to send similar events.
   */
  LocalNetPlayer.prototype.sendEvent = function(eventType, data) {
    var fn = this.eventHandlers[eventType];
    if (fn) {
      fn.call(this, data);
    } else {
      console.error("LocalNetPlayer: Unknown Event: " + eventType);
    }
  };

  LocalNetPlayer.prototype.switchGame = function(id, data) {
  };

  return LocalNetPlayer;
});

<|endoftext|>


<commit_before>(function() {
  var size = {
    width: window.innerWidth,
    height: window.innerHeight
  };
  // create an new instance of a pixi _stage - and set it to be interactive
  var _stage = new PIXI.Stage(0xd9e2cc);
  _stage.setInteractive(true);

  // create a _renderer instance
  var _renderer = PIXI.autoDetectRenderer(size.width, size.height);

  _renderer.view.style.width = size.width + "px";
  _renderer.view.style.height = size.height + "px";

  _renderer.view.style.display = "block";

  requestAnimFrame(animate);
  var renderTexture = new PIXI.RenderTexture(size.width, size.height);

  var _target = new PIXI.DisplayObjectContainer();
  _stage.addChild(_target);
  var count = 0;

  // add render view to DOM
  document.body.appendChild(_renderer.view);

  var boids = [];
  var groupTogether = false;
  var optiflowVector = new PIXI.Vector(0, 0);
  var borderFactor = 10;
  var path = [];

  // Create a path. This is for testing. Ideally we should detect an outline with an IR camera and using that data for the path.
  var steps = 90;
  for (var i = 0; i < steps; i++) {
    path.push(new PIXI.Vector(
      (size.width / 2) + 300 * Math.cos(2 * Math.PI * i / steps),
      (size.height / 2) + 300 * Math.sin(2 * Math.PI * i / steps)
     ));
  }

  var Boid = function(position, maxSpeed, maxForce) {
    var strength = Math.random() + 1.5;
    this.acceleration = new PIXI.Vector();
    this.vector = new PIXI.Vector(Math.random(), Math.random()).multiplyScalar(50).add(new PIXI.Vector(10,10));
    this.position = position.clone();
    this.radius = 30;
    this.maxSpeed = maxSpeed + strength;
    this.maxForce = maxForce + strength;
    this.amount = strength * 10 + 10;
    this.count = 0;
    this.flap = Math.random() * 0.5 + 0.5;
  };

  Boid.prototype.run = function(boids) {
    if (!groupTogether) {
      this.flock(boids);
    } else {
      this.align(boids);
    }
    this.update();
  };

  // We accumulate a new acceleration each time based on several rules
  Boid.prototype.flock = function(boids) {
    var separation = this.separate(boids).multiplyScalar(1);
    var alignment = this.align(boids).multiplyScalar(20);
    var cohesion = this.cohesion(boids).multiplyScalar(0.5);
    var borders = this.borders().multiplyScalar(2);
    this.acceleration.add(separation).add(alignment).add(cohesion).add(borders);
  };

  Boid.prototype.opticalFlow = function() {
    if (window.boidData && window.boidData.u) {
      var u = window.boidData.u;
      var v = window.boidData.v;
      optiflowVector.x -= u;
      optiflowVector.y += v;
    }
    return vector;
  };

  Boid.prototype.update = function() {
    // Update velocity
    this.vector.add(this.acceleration);
    // Limit speed (vector#limit?)
    this.vector.setLength(Math.min(this.maxSpeed, this.vector.length()));
    this.position.add(this.vector);
    // Reset acceleration to 0 each cycle
    // The closer to 0 this is, the more insect like the behavior
    // with sudden changes in direction
    this.acceleration.multiplyScalar(0.95);
  };

  Boid.prototype.seek = function(target) {
    this.acceleration.add(this.steer(target, false));
  };

  Boid.prototype.arrive = function(target) {
    this.acceleration.add(this.steer(target, true));
  };

  Boid.prototype.borders = function() {

    var vector = new PIXI.Vector(0, 0);
    var position = this.position;
    var radius = this.radius;

    if (position.x < -radius) {
      vector.x = borderFactor * (-radius - position.x);
    }
    if (position.y < -radius){
      vector.y = borderFactor * (-radius - position.y);
    }
    if (position.x > size.width + radius){
      vector.x = -borderFactor * (position.x - size.width - radius);
    }
    if (position.y > size.height + radius) {
      vector.y = -borderFactor * (position.y - size.height - radius);
    }

    return vector;
  };

  // A method that calculates a steering vector towards a target
  // Takes a second argument, if true, it slows down as it approaches
  // the target
  Boid.prototype.steer = function(target, slowdown) {
    var steer;
    var desired = target.clone().sub(this.position);
    var distance = desired.length();
    // Two options for desired vector magnitude
    // (1 -- based on distance, 2 -- maxSpeed)
    if (slowdown && distance < 20) {
      // This damping is somewhat arbitrary:
      desired.setLength(this.maxSpeed * (1 / distance));
    } else {
      // desired.setLength(this.maxSpeed);
    }
    steer = desired.clone().sub(this.vector);
    steer.setLength(Math.min(this.maxForce, steer.length()));

    return steer;
  };

  Boid.prototype.separate = function(boids) {
    var desiredSeperation = Math.pow(100, 2);
    var steer = new PIXI.Vector(0, 0);
    var count = 0;
    // For every boid in the system, check if it's too close
    for (var i = 0, l = boids.length; i < l; i++) {
      var other = boids[i];
      // since this. and other. poisition are PIXI.Points, need to create a new Vector out of that point
      var vector = new PIXI.Vector(this.position.clone().sub(other.position));
      var distanceSq = vector.lengthSq();
      if (distanceSq > 0 && distanceSq < desiredSeperation) {
        // Calculate vector pointing away from neighbor
        steer.add(vector.multiplyScalar(1 / (distanceSq * distanceSq)));
        count++;
      }
    }
    // Average -- divide by how many
    if (count > 0)
      steer.divideScalar(count);
    if (steer.length() !== 0) {
      // Implement Reynolds: Steering = Desired - Velocity
      steer.setLength(this.maxSpeed);
      steer.sub(this.vector);
      steer.setLength(Math.min(steer.length(), this.maxForce));
    }
    return steer;
  };

  // Alignment
  // For every nearby boid in the system, calculate the average velocity
  Boid.prototype.align = function(boids) {
    // Using square of distance to ease calculations
    var neighborDist = Math.pow(250, 2);
    var steer = new PIXI.Vector(0, 0);
    var count = 0;
    for (var i = 0, l = boids.length; i < l; i++) {
      var other = boids[i];
      // We just need to find the square of the distance — less calculation
      var distanceSq = this.position.distanceToSq(other.position);
      // debugger;
      if (distanceSq > 0 && distanceSq < neighborDist) {
        steer.add(other.vector);
        count++;
      }
    }

    if (count > 0)
      steer.divideScalar(count);
    if (! (steer.length() === 0)) {
      // Implement Reynolds: Steering = Desired - Velocity
      steer.setLength(this.maxSpeed);
      steer.sub(this.vector);
      steer.setLength(Math.min(steer.length(), this.maxForce));
    }
    return steer;
  };

  // Cohesion
  // For the average location (i.e. center) of all nearby boids,
  // calculate steering vector towards that location
  Boid.prototype.cohesion = function(boids) {
    // Using square of distance to ease calculations
    var neighborDist = Math.pow(600, 2);
    var sum = new PIXI.Vector(0, 0);
    var count = 0;
    for (var i = 0, l = boids.length; i < l; i++) {
      var other = boids[i];
      var distanceSq = this.position.distanceToSq(other.position);
      if (distanceSq > 0 && distanceSq < neighborDist) {
        sum.add(other.position); // Add location
        count++;
      }
    }
    if (count > 0) {
      sum.divideScalar(count);
      // Steer towards the location
      return this.steer(sum, false);
    }
    return sum;
  };

  //Main loop -
  init();

  function init() {
    var boidContainers = [];

    // Add the boids:
    for (var i = 0; i < 100; i++) {
      var position = new PIXI.Point(Math.random() * size.width, Math.random() * size.height);
      var boid = new Boid(position, 2, 1);
      var boidContainer = new PIXI.DisplayObjectContainer();
      var boidGraphic = new PIXI.Graphics();
      boidGraphic.beginFill(0x002244);
      boidGraphic.drawCircle(0, 0, 5);
      boidGraphic.lineTo(3.5,0);
      boidGraphic.endFill();

      boidContainer.addChild(boidGraphic);
      boidContainer.alpha = boid.vector.length() / boid.maxSpeed;

      boid.container = boidContainer;
      boidContainers.push(boidContainer);

      boids.push(boid);

      _target.addChild(boidContainer);
      _stage.addChild(boidContainer);
    }
  _stage.setInteractive(true);
  _stage.mousedown = _stage.touchstart = onMouseDown;
  }

  function onMouseDown() {
    groupTogether = !groupTogether;
  }

  function animate() {
    requestAnimFrame(animate);

    count += 1;
    _renderer.render(_stage);

    for (var i = 0, l = boids.length; i < l; i++) {
      var boid = boids[i];

      if (groupTogether) {
        var index = parseInt( ((i + boid.count / 6) % l) / l * path.length);
        var point = path[index];
        boid.seek(point);
      }
      boid.container.position = boid.position;
      boid.vector.normalize();

      boid.count += (1 + boid.flap);
      boid.container.alpha = 1.1 - (1 / (boid.vector.lengthSq() + .5)) ;
      boid.container.rotation = -boid.vector.rad();

      var flapFactor = boid.count * boid.vector.lengthSq() * 0.2;
      boid.container.scale = new PIXI.Point(Math.sin(flapFactor)*.25 + 0.85, 0.6);

      boid.run(boids);
    }
  }
})();
<commit_msg>Changed "optiflow" to "opticalFlow" for consisetency.<commit_after>(function() {
  var size = {
    width: window.innerWidth,
    height: window.innerHeight
  };
  // create an new instance of a pixi _stage - and set it to be interactive
  var _stage = new PIXI.Stage(0xd9e2cc);
  _stage.setInteractive(true);

  // create a _renderer instance
  var _renderer = PIXI.autoDetectRenderer(size.width, size.height);

  _renderer.view.style.width = size.width + "px";
  _renderer.view.style.height = size.height + "px";

  _renderer.view.style.display = "block";

  requestAnimFrame(animate);
  var renderTexture = new PIXI.RenderTexture(size.width, size.height);

  var _target = new PIXI.DisplayObjectContainer();
  _stage.addChild(_target);
  var count = 0;

  // add render view to DOM
  document.body.appendChild(_renderer.view);

  var boids = [];
  var groupTogether = false;
  var opticalFlowVector = new PIXI.Vector(0, 0);
  var borderFactor = 10;
  var path = [];

  // Create a path. This is for testing. Ideally we should detect an outline with an IR camera and using that data for the path.
  var steps = 90;
  for (var i = 0; i < steps; i++) {
    path.push(new PIXI.Vector(
      (size.width / 2) + 300 * Math.cos(2 * Math.PI * i / steps),
      (size.height / 2) + 300 * Math.sin(2 * Math.PI * i / steps)
     ));
  }

  var Boid = function(position, maxSpeed, maxForce) {
    var strength = Math.random() + 1.5;
    this.acceleration = new PIXI.Vector();
    this.vector = new PIXI.Vector(Math.random(), Math.random()).multiplyScalar(50).add(new PIXI.Vector(10,10));
    this.position = position.clone();
    this.radius = 30;
    this.maxSpeed = maxSpeed + strength;
    this.maxForce = maxForce + strength;
    this.amount = strength * 10 + 10;
    this.count = 0;
    this.flap = Math.random() * 0.5 + 0.5;
  };

  Boid.prototype.run = function(boids) {
    if (!groupTogether) {
      this.flock(boids);
    } else {
      this.align(boids);
    }
    this.update();
  };

  // We accumulate a new acceleration each time based on several rules
  Boid.prototype.flock = function(boids) {
    var separation = this.separate(boids).multiplyScalar(1);
    var alignment = this.align(boids).multiplyScalar(20);
    var cohesion = this.cohesion(boids).multiplyScalar(0.5);
    var borders = this.borders().multiplyScalar(2);
    this.acceleration.add(separation).add(alignment).add(cohesion).add(borders);
  };

  Boid.prototype.opticalFlow = function() {
    if (window.boidData && window.boidData.u) {
      var u = window.boidData.u;
      var v = window.boidData.v;
      opticalFlowVector.x -= u;
      opticalFlowVector.y += v;
    }
    return vector;
  };

  Boid.prototype.update = function() {
    // Update velocity
    this.vector.add(this.acceleration);
    // Limit speed (vector#limit?)
    this.vector.setLength(Math.min(this.maxSpeed, this.vector.length()));
    this.position.add(this.vector);
    // Reset acceleration to 0 each cycle
    // The closer to 0 this is, the more insect like the behavior
    // with sudden changes in direction
    this.acceleration.multiplyScalar(0.95);
  };

  Boid.prototype.seek = function(target) {
    this.acceleration.add(this.steer(target, false));
  };

  Boid.prototype.arrive = function(target) {
    this.acceleration.add(this.steer(target, true));
  };

  Boid.prototype.borders = function() {

    var vector = new PIXI.Vector(0, 0);
    var position = this.position;
    var radius = this.radius;

    if (position.x < -radius) {
      vector.x = borderFactor * (-radius - position.x);
    }
    if (position.y < -radius){
      vector.y = borderFactor * (-radius - position.y);
    }
    if (position.x > size.width + radius){
      vector.x = -borderFactor * (position.x - size.width - radius);
    }
    if (position.y > size.height + radius) {
      vector.y = -borderFactor * (position.y - size.height - radius);
    }

    return vector;
  };

  // A method that calculates a steering vector towards a target
  // Takes a second argument, if true, it slows down as it approaches
  // the target
  Boid.prototype.steer = function(target, slowdown) {
    var steer;
    var desired = target.clone().sub(this.position);
    var distance = desired.length();
    // Two options for desired vector magnitude
    // (1 -- based on distance, 2 -- maxSpeed)
    if (slowdown && distance < 20) {
      // This damping is somewhat arbitrary:
      desired.setLength(this.maxSpeed * (1 / distance));
    } else {
      // desired.setLength(this.maxSpeed);
    }
    steer = desired.clone().sub(this.vector);
    steer.setLength(Math.min(this.maxForce, steer.length()));

    return steer;
  };

  Boid.prototype.separate = function(boids) {
    var desiredSeperation = Math.pow(100, 2);
    var steer = new PIXI.Vector(0, 0);
    var count = 0;
    // For every boid in the system, check if it's too close
    for (var i = 0, l = boids.length; i < l; i++) {
      var other = boids[i];
      // since this. and other. poisition are PIXI.Points, need to create a new Vector out of that point
      var vector = new PIXI.Vector(this.position.clone().sub(other.position));
      var distanceSq = vector.lengthSq();
      if (distanceSq > 0 && distanceSq < desiredSeperation) {
        // Calculate vector pointing away from neighbor
        steer.add(vector.multiplyScalar(1 / (distanceSq * distanceSq)));
        count++;
      }
    }
    // Average -- divide by how many
    if (count > 0)
      steer.divideScalar(count);
    if (steer.length() !== 0) {
      // Implement Reynolds: Steering = Desired - Velocity
      steer.setLength(this.maxSpeed);
      steer.sub(this.vector);
      steer.setLength(Math.min(steer.length(), this.maxForce));
    }
    return steer;
  };

  // Alignment
  // For every nearby boid in the system, calculate the average velocity
  Boid.prototype.align = function(boids) {
    // Using square of distance to ease calculations
    var neighborDist = Math.pow(250, 2);
    var steer = new PIXI.Vector(0, 0);
    var count = 0;
    for (var i = 0, l = boids.length; i < l; i++) {
      var other = boids[i];
      // We just need to find the square of the distance — less calculation
      var distanceSq = this.position.distanceToSq(other.position);
      // debugger;
      if (distanceSq > 0 && distanceSq < neighborDist) {
        steer.add(other.vector);
        count++;
      }
    }

    if (count > 0)
      steer.divideScalar(count);
    if (! (steer.length() === 0)) {
      // Implement Reynolds: Steering = Desired - Velocity
      steer.setLength(this.maxSpeed);
      steer.sub(this.vector);
      steer.setLength(Math.min(steer.length(), this.maxForce));
    }
    return steer;
  };

  // Cohesion
  // For the average location (i.e. center) of all nearby boids,
  // calculate steering vector towards that location
  Boid.prototype.cohesion = function(boids) {
    // Using square of distance to ease calculations
    var neighborDist = Math.pow(600, 2);
    var sum = new PIXI.Vector(0, 0);
    var count = 0;
    for (var i = 0, l = boids.length; i < l; i++) {
      var other = boids[i];
      var distanceSq = this.position.distanceToSq(other.position);
      if (distanceSq > 0 && distanceSq < neighborDist) {
        sum.add(other.position); // Add location
        count++;
      }
    }
    if (count > 0) {
      sum.divideScalar(count);
      // Steer towards the location
      return this.steer(sum, false);
    }
    return sum;
  };

  //Main loop -
  init();

  function init() {
    var boidContainers = [];

    // Add the boids:
    for (var i = 0; i < 100; i++) {
      var position = new PIXI.Point(Math.random() * size.width, Math.random() * size.height);
      var boid = new Boid(position, 2, 1);
      var boidContainer = new PIXI.DisplayObjectContainer();
      var boidGraphic = new PIXI.Graphics();
      boidGraphic.beginFill(0x002244);
      boidGraphic.drawCircle(0, 0, 5);
      boidGraphic.lineTo(3.5,0);
      boidGraphic.endFill();

      boidContainer.addChild(boidGraphic);
      boidContainer.alpha = boid.vector.length() / boid.maxSpeed;

      boid.container = boidContainer;
      boidContainers.push(boidContainer);

      boids.push(boid);

      _target.addChild(boidContainer);
      _stage.addChild(boidContainer);
    }
  _stage.setInteractive(true);
  _stage.mousedown = _stage.touchstart = onMouseDown;
  }

  function onMouseDown() {
    groupTogether = !groupTogether;
  }

  function animate() {
    requestAnimFrame(animate);

    count += 1;
    _renderer.render(_stage);

    for (var i = 0, l = boids.length; i < l; i++) {
      var boid = boids[i];

      if (groupTogether) {
        var index = parseInt( ((i + boid.count / 6) % l) / l * path.length);
        var point = path[index];
        boid.seek(point);
      }
      boid.container.position = boid.position;
      boid.vector.normalize();

      boid.count += (1 + boid.flap);
      boid.container.alpha = 1.1 - (1 / (boid.vector.lengthSq() + .5)) ;
      boid.container.rotation = -boid.vector.rad();

      var flapFactor = boid.count * boid.vector.lengthSq() * 0.2;
      boid.container.scale = new PIXI.Point(Math.sin(flapFactor)*.25 + 0.85, 0.6);

      boid.run(boids);
    }
  }
})();
<|endoftext|>


<commit_before>/**
 * @(#)yobi.Markdown 2013.03.21
 *
 * Copyright NHN Corporation.
 * Released under the MIT license
 *
 * http://yobi.dev.naver.com/license
 */
yobi.Markdown = function(htOptions){

    var htVar = {};
    var htElement = {};

    /**
     * initialize
     * @param {Hash Table} htOptions
     */
    function _init(htOptions){
        _initVar(htOptions);
        _initElement(htOptions);

        _enableMarkdown();
    }

    /**
     * initialize variables
     * @param {Hash Table} htOptions
     */
    function _initVar(htOptions){
        htVar.sTplSwitch = htOptions.sTplSwitch;
        htVar.sIssuesUrl = htOptions.sIssuesUrl;
        htVar.sProjectUrl = htOptions.sProjectUrl;
        htVar.bWysiwyg = htOptions.bWysiwyg || false;
        htVar.sUserRules = '[a-z0-9_\\-\\.]';
        htVar.sProjecRules = '[a-z0-9_\\-]';
        htVar.sIssueRules = '\\d';
        htVar.sSha1Rules = '[a-f0-9]{7,40}';
    }

    /**
     * initialize element
     * @param {Hash Table} htOptions
     */
    function _initElement(htOptions){
        htElement.waTarget = $(htOptions.aTarget) || $("[markdown]");
    }

    /**
     * Render as Markdown document
     * @require showdown.js
     * @require hljs.js
     * @param {String} sText
     * @return {String}
     */
    function _renderMarkdown(sText) {

        var htMarkedOption = {
          gfm: true,
          wysiwyg: htVar.bWysiwyg,
          tables: true,
          breaks: false,
          pedantic: false,
          sanitize: false,
          smartLists: true,
          langPrefix: '',
          highlight: function(code, lang) {
            if (lang === 'js') {
              return highlighter.javascript(code);
            }
            return code;
          }
        };

        var hooks = function(sSrc,sType) {

            var sGfmLinkRules =  '(([user]+\\/[project]+)|([user]+))?(#([issue]+)|(@)?([shar1]))|@([user]+)',
                rIgnoreRules = /[^<]*<\/code>|<img[^<]+src=[^>]+\/?>|<a [^<]*href[^>]*>[^<]+<\/a>/igm,
                aIgnores,
                sIgnore,
                aChecker;

            if(sType=='code') return sSrc;

            sGfmLinkRules = sGfmLinkRules.replace(/\[user\]/g,htVar.sUserRules)
                .replace(/\[user\]/g,htVar.sUserRules)
                .replace(/\[project\]/g,htVar.sProjecRules)
                .replace(/\[shar1\]/g,htVar.sSha1Rules)
                .replace(/\[issue\]/g,htVar.sIssueRules);

            sSrc = sSrc.replace(new RegExp(sGfmLinkRules,'gm'), function(sMatch,sProjectGroup,sProjectPath,sUserName,sTargetGoup,sIssue,sAt ,sShar1,sMention,nMatchIndex) {
                if(aIgnores = sSrc.match(rIgnoreRules)) {
                    while(sIgnore = aIgnores.shift()) {
                        if(aChecker = new RegExp(sIgnore.replace(/\//g,'\/'),'ig').exec(sSrc)) {
                            if(nMatchIndex > aChecker.index && nMatchIndex < aChecker.index+aChecker[0].length) return sMatch;
                        }
                    }
                }

                if(/\w/.test(sSrc[nMatchIndex-1]) || /\w/.test(sSrc[nMatchIndex+sMatch.length])) return sMatch;

                return _makeLink(sMatch,sProjectGroup,sProjectPath,sUserName,sTargetGoup,sIssue, sAt, sShar1,sMention);
            });

            return  sSrc;
        };

        htMarkedOption.hook = hooks;

        return marked(sText,htMarkedOption);
    }

    function _makeLink(sMatch,sProjectGroup,sProjectPath,sUserName,sTargetGoup,sIssue,sAt,sShar1,sMention) {
        var sRef,
            sTitle,
            sOwner = htVar.sProjectUrl.split('/')[1],
            sProject = htVar.sProjectUrl.split('/')[2];

        if(sProjectGroup && sUserName && sIssue && !sProjectPath) {
            // User/#Num nforge#12345
            sRef = [sUserName, sProject, 'issue', sIssue].join("/");
            sTitle = sMatch;
        } else if(sProjectGroup && sProjectPath && sIssue && !sUserName) {
            // User/Project#Num nforge/yobi#12345
            sRef = [sProjectGroup, 'issue', sIssue].join("/");
            sTitle = sMatch;
        } else if(sIssue && !sProjectGroup && !sProjectPath && !sUserName) {
            // #Num #123
            sRef = [sOwner, sProject, 'issue', sIssue].join("/");
            sTitle = sMatch;
        } else if(sShar1 && !sAt) {
            // SHA1 be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2
            sRef = [sOwner, sProject, 'commit' , sMatch].join("/");
            sTitle = sMatch.slice(0,7);
        } else if(sProjectGroup && sUserName && sShar1 && sAt && !sProjectPath ) {
            // User@SHA1 nforge@be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2
            sRef = [sUserName, sProject, 'commit' , sShar1].join("/");
            sTitle = [sUserName, '@', sShar1.slice(0,7)].join("");
        } else if(sProjectGroup && sShar1 && sProjectPath && sAt && !sUserName) {
            // User/Project@SHA1 nforge/yobi@be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2
            sRef = [sProjectGroup, 'commit' , sShar1].join("/");
            sTitle = [sProjectGroup, '@', sShar1.slice(0,7)].join("");
        } else if (sMention) {
            sRef = sMention;
            sTitle = sMatch;
        } else {
            return sMatch;
        }

        return ''+sTitle+'';

    }

    /**
     * set Markdown Editor
     * @param {Wrapped Element} welTextarea
     */
    function _setEditor(welTextarea) {
        // create new preview area
        var welPreview = $('
');
        welPreview.css({
            "display"   : "none",
            "width"     : welTextarea.width()  + 'px',
            "min-height": welTextarea.height() + 'px',
            "padding"   : welTextarea.css("padding")
        });

        var welPreviewSwitch = $('
');
            welPreviewSwitch.html(htVar.sTplSwitch);

        var fOnChangeSwitch = function() {
            var bPreview = ($("input:radio[name=edit-mode]:checked").val() == "preview");
            welPreview.html(_renderMarkdown(welTextarea.val()));
            (bPreview ? welPreview: welTextarea).show();
            (bPreview ? welTextarea: welPreview).hide();
        };
        welPreviewSwitch.change(fOnChangeSwitch);

        welTextarea.before(welPreviewSwitch);
        welTextarea.before(welPreview);
    }

    /**
     * set Markdown Viewer
     * @param {Wrapped Element} welTarget is not <textarea> or <input>
     */
    function _setViewer(welTarget) {
        welTarget.html(_renderMarkdown(welTarget.text())).removeClass('markdown-before');
    }

    /**
     * enableMarkdown
     * same as nforge.markdown.enable
     */
    function _enableMarkdown(){
        var sTagName;

        htElement.waTarget.each(function(nIndex, elTarget){
            sTagName = elTarget.tagName.toUpperCase();

            if(sTagName == "TEXTAREA" || sTagName == "INPUT" || elTarget.contentEditable == "true"){
                _setEditor($(elTarget));
            } else {
                _setViewer($(elTarget));
            }
        });
    }

    // initialize
    _init(htOptions || {});
};
<commit_msg>string escape before check autolink ignore pattern<commit_after>/**
 * @(#)yobi.Markdown 2013.03.21
 *
 * Copyright NHN Corporation.
 * Released under the MIT license
 *
 * http://yobi.dev.naver.com/license
 */
yobi.Markdown = function(htOptions){

    var htVar = {};
    var htElement = {};

    /**
     * initialize
     * @param {Hash Table} htOptions
     */
    function _init(htOptions){
        _initVar(htOptions);
        _initElement(htOptions);

        _enableMarkdown();
    }

    /**
     * initialize variables
     * @param {Hash Table} htOptions
     */
    function _initVar(htOptions){
        htVar.sTplSwitch = htOptions.sTplSwitch;
        htVar.sIssuesUrl = htOptions.sIssuesUrl;
        htVar.sProjectUrl = htOptions.sProjectUrl;
        htVar.bWysiwyg = htOptions.bWysiwyg || false;
        htVar.sUserRules = '[a-z0-9_\\-\\.]';
        htVar.sProjecRules = '[a-z0-9_\\-]';
        htVar.sIssueRules = '\\d';
        htVar.sSha1Rules = '[a-f0-9]{7,40}';
    }

    /**
     * initialize element
     * @param {Hash Table} htOptions
     */
    function _initElement(htOptions){
        htElement.waTarget = $(htOptions.aTarget) || $("[markdown]");
    }

    /**
     * Render as Markdown document
     * @require showdown.js
     * @require hljs.js
     * @param {String} sText
     * @return {String}
     */
    function _renderMarkdown(sText) {

        var htMarkedOption = {
          gfm: true,
          wysiwyg: htVar.bWysiwyg,
          tables: true,
          breaks: false,
          pedantic: false,
          sanitize: false,
          smartLists: true,
          langPrefix: '',
          highlight: function(code, lang) {
            if (lang === 'js') {
              return highlighter.javascript(code);
            }
            return code;
          }
        };

        var hooks = function(sSrc,sType) {

            var sGfmLinkRules =  '(([user]+\\/[project]+)|([user]+))?(#([issue]+)|(@)?([shar1]))|@([user]+)',
                rIgnoreRules = /[^<]*<\/code>|<img[^<]+src=[^>]+\/?>|<a [^<]*href[^>]*>[^<]+<\/a>/igm,
                aIgnores,
                sIgnore,
                nIgnoreIndex;

            if(sType=='code') return sSrc;

            sGfmLinkRules = sGfmLinkRules.replace(/\[user\]/g,htVar.sUserRules)
                .replace(/\[user\]/g,htVar.sUserRules)
                .replace(/\[project\]/g,htVar.sProjecRules)
                .replace(/\[shar1\]/g,htVar.sSha1Rules)
                .replace(/\[issue\]/g,htVar.sIssueRules);

            sSrc = sSrc.replace(new RegExp(sGfmLinkRules,'gm'), function(sMatch,sProjectGroup,sProjectPath,sUserName,sTargetGoup,sIssue,sAt ,sShar1,sMention,nMatchIndex) {
                if(aIgnores = sSrc.match(rIgnoreRules)) {
                    while(sIgnore = aIgnores.shift()) {
                        nIgnoreIndex = sSrc.indexOf(sIgnore);

                        if(nMatchIndex > nIgnoreIndex && nMatchIndex < nIgnoreIndex + sIgnore.length) return sMatch;
                    }
                }

                if(/\w/.test(sSrc[nMatchIndex-1]) || /\w/.test(sSrc[nMatchIndex+sMatch.length])) return sMatch;

                return _makeLink(sMatch,sProjectGroup,sProjectPath,sUserName,sTargetGoup,sIssue, sAt, sShar1,sMention);
            });

            return  sSrc;
        };

        htMarkedOption.hook = hooks;

        return marked(sText,htMarkedOption);
    }

    function _makeLink(sMatch,sProjectGroup,sProjectPath,sUserName,sTargetGoup,sIssue,sAt,sShar1,sMention) {
        var sRef,
            sTitle,
            sOwner = htVar.sProjectUrl.split('/')[1],
            sProject = htVar.sProjectUrl.split('/')[2];

        if(sProjectGroup && sUserName && sIssue && !sProjectPath) {
            // User/#Num nforge#12345
            sRef = [sUserName, sProject, 'issue', sIssue].join("/");
            sTitle = sMatch;
        } else if(sProjectGroup && sProjectPath && sIssue && !sUserName) {
            // User/Project#Num nforge/yobi#12345
            sRef = [sProjectGroup, 'issue', sIssue].join("/");
            sTitle = sMatch;
        } else if(sIssue && !sProjectGroup && !sProjectPath && !sUserName) {
            // #Num #123
            sRef = [sOwner, sProject, 'issue', sIssue].join("/");
            sTitle = sMatch;
        } else if(sShar1 && !sAt) {
            // SHA1 be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2
            sRef = [sOwner, sProject, 'commit' , sMatch].join("/");
            sTitle = sMatch.slice(0,7);
        } else if(sProjectGroup && sUserName && sShar1 && sAt && !sProjectPath ) {
            // User@SHA1 nforge@be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2
            sRef = [sUserName, sProject, 'commit' , sShar1].join("/");
            sTitle = [sUserName, '@', sShar1.slice(0,7)].join("");
        } else if(sProjectGroup && sShar1 && sProjectPath && sAt && !sUserName) {
            // User/Project@SHA1 nforge/yobi@be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2
            sRef = [sProjectGroup, 'commit' , sShar1].join("/");
            sTitle = [sProjectGroup, '@', sShar1.slice(0,7)].join("");
        } else if (sMention) {
            sRef = sMention;
            sTitle = sMatch;
        } else {
            return sMatch;
        }

        return ''+sTitle+'';

    }

    /**
     * set Markdown Editor
     * @param {Wrapped Element} welTextarea
     */
    function _setEditor(welTextarea) {
        // create new preview area
        var welPreview = $('
');
        welPreview.css({
            "display"   : "none",
            "width"     : welTextarea.width()  + 'px',
            "min-height": welTextarea.height() + 'px',
            "padding"   : welTextarea.css("padding")
        });

        var welPreviewSwitch = $('
');
            welPreviewSwitch.html(htVar.sTplSwitch);

        var fOnChangeSwitch = function() {
            var bPreview = ($("input:radio[name=edit-mode]:checked").val() == "preview");
            welPreview.html(_renderMarkdown(welTextarea.val()));
            (bPreview ? welPreview: welTextarea).show();
            (bPreview ? welTextarea: welPreview).hide();
        };
        welPreviewSwitch.change(fOnChangeSwitch);

        welTextarea.before(welPreviewSwitch);
        welTextarea.before(welPreview);
    }

    /**
     * set Markdown Viewer
     * @param {Wrapped Element} welTarget is not <textarea> or <input>
     */
    function _setViewer(welTarget) {
        welTarget.html(_renderMarkdown(welTarget.text())).removeClass('markdown-before');
    }

    /**
     * enableMarkdown
     * same as nforge.markdown.enable
     */
    function _enableMarkdown(){
        var sTagName;

        htElement.waTarget.each(function(nIndex, elTarget){
            sTagName = elTarget.tagName.toUpperCase();

            if(sTagName == "TEXTAREA" || sTagName == "INPUT" || elTarget.contentEditable == "true"){
                _setEditor($(elTarget));
            } else {
                _setViewer($(elTarget));
            }
        });
    }

    // initialize
    _init(htOptions || {});
};
<|endoftext|>


<commit_before>(function($) {

  var defaultSettings = {
    beforeInsert: function(item, callback) { callback() },
    afterInsert: function(item) {},
    beforeRemove: function(item, callback) { callback() },
    afterRemove: function(item) {},
    itemTemplate: '.item.template',
    noneTemplate: '.none.template',
    container: '.container',
    item: '.item',
    none: '.none',
    addHandler: '.add',
    removeHandler: '.remove',
    newItemIndex: 'new_nested_item'
  };

  // PUBLIC API
  var methods = {
    init: function(options) {
      var $this = $(this);
      if($(this).data('nested-fields.options')) {
        console.log('Nested fields already defined for this element. If you want to redefine options, destroy it and init again.');
        return $this;
      } else if(getOptions($this)) {
        console.log('You cannot nest nested fields. Who would say that, uh?');
        return $this;
      }

      options = $.extend({}, defaultSettings, options);
      options.itemTemplate = $(options.itemTemplate, $this);
      options.noneTemplate = $(options.noneTemplate, $this);
      options.container = $(options.container, $this);
      options.addHandler = $(options.addHandler, $this);
      $this.data('nested-fields.options', options);

      options.addHandler.bind('click.nested-fields', function(e) {
        e.preventDefault();
        var newItem = prepareTemplate(options);
        insertItemWithCallbacks(newItem, null, options);
      });

      $(options.item, options.container).each(function(i, item) {
        bindRemoveEvent(item, options);
      });

      return $this;
    },

    insert: function(callback) {
      var options = getOptions(this);
      var newItem = prepareTemplate(options);
      insertItemWithCallbacks(newItem, callback, options);
    },

    remove: function(element) {
      return removeItem(element, getOptions(this));
    },

    items: function() {
      return findItems(getOptions(this));
    },

    destroy: function() {
      $(this).removeData('nested-fields.options');
      $('*', this).unbind('.nested-fields');
    }
  };

  $.fn.nestedFields = function(method) {
    if (methods[method]) {
      return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
    } else if (typeof method === 'object' || !method) {
      return methods.init.apply(this, arguments);
    } else {
      $.error( 'Method ' +  method + ' does not exist on jQuery.nestedFields' );
    }
  };

  function getOptions(element) {
    element = $(element);
    while(element.length > 0) {
      var data = element.data('nested-fields.options');
      if(data) {
        return data;
      } else {
        element = element.parent();
      }
    }
    return null;
  }

  function prepareTemplate(options) {
    var regexp = new RegExp(options.newItemIndex, 'g');
    var newId = new Date().getTime();

    var contents = options.itemTemplate.html();
    var newItem = $(contents.replace(regexp, newId));
    newItem.attr('data-new-record', true);
    newItem.attr('data-record-id', newId);

    bindRemoveEvent(newItem, options);

    return newItem;
  }

  function insertItem(newItem, options) {
    removeNone(options);
    options.container.append(newItem);
  }

  function insertItemWithCallbacks(newItem, onInsertCallback, options) {
    options.beforeInsert(newItem, function() {
      if(onInsertCallback) {
        onInsertCallback(item);
      }
      insertItem(newItem, options);
    });
    options.afterInsert(newItem);

    return newItem;
  }

  function removeItem(element, options) {
    var $element = $(element);
    options.beforeRemove($element, function() {
      if($element.attr('data-new-record')) { // record is new
        $element.remove();
      } else { // record should be marked and sent to server
        $element.find('INPUT[name*=_destroy]').val('true');
        $element.hide();
      }
      insertNone(options);
    });
    options.afterRemove($element);
    return $element;
  }

  function bindRemoveEvent(item, options) {
    $(item).find(options.removeHandler).bind('click.nested-fields', function(e) {
      e.preventDefault();
      removeItem(item, options);
    });
  }

  function insertNone(options) {
    if(findItems(options).length == 0) {
      options.container.append(options.noneTemplate.html());
    }
  }

  function removeNone(options) {
    findNone(options).remove();
  }

  function findItems(options) {
    return options.container.find(options.item + ':visible');
  }

  function findNone(options) {
    return options.container.find(options.none);
  }

})(jQuery);
<commit_msg>Added options to skip before and after callbacks when inserting via API.<commit_after>(function($) {

  var defaultSettings = {
    beforeInsert: function(item, callback) { callback() },
    afterInsert: function(item) {},
    beforeRemove: function(item, callback) { callback() },
    afterRemove: function(item) {},
    itemTemplate: '.item.template',
    noneTemplate: '.none.template',
    container: '.container',
    item: '.item',
    none: '.none',
    addHandler: '.add',
    removeHandler: '.remove',
    newItemIndex: 'new_nested_item'
  };

  // PUBLIC API
  var methods = {
    init: function(options) {
      var $this = $(this);
      if($(this).data('nested-fields.options')) {
        console.log('Nested fields already defined for this element. If you want to redefine options, destroy it and init again.');
        return $this;
      } else if(getOptions($this)) {
        console.log('You cannot nest nested fields. Who would say that, uh?');
        return $this;
      }

      options = $.extend({}, defaultSettings, options);
      options.itemTemplate = $(options.itemTemplate, $this);
      options.noneTemplate = $(options.noneTemplate, $this);
      options.container = $(options.container, $this);
      options.addHandler = $(options.addHandler, $this);
      $this.data('nested-fields.options', options);

      options.addHandler.bind('click.nested-fields', function(e) {
        e.preventDefault();
        var newItem = prepareTemplate(options);
        insertItemWithCallbacks(newItem, null, options);
      });

      $(options.item, options.container).each(function(i, item) {
        bindRemoveEvent(item, options);
      });

      return $this;
    },

    insert: function(callback, options) {
      options = $.extend({}, getOptions(this), options);

      var newItem = prepareTemplate(options);
      insertItemWithCallbacks(newItem, callback, options);
    },

    remove: function(element) {
      return removeItem(element, getOptions(this));
    },

    items: function() {
      return findItems(getOptions(this));
    },

    destroy: function() {
      $(this).removeData('nested-fields.options');
      $('*', this).unbind('.nested-fields');
    }
  };

  $.fn.nestedFields = function(method) {
    if (methods[method]) {
      return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
    } else if (typeof method === 'object' || !method) {
      return methods.init.apply(this, arguments);
    } else {
      $.error( 'Method ' +  method + ' does not exist on jQuery.nestedFields' );
    }
  };

  function getOptions(element) {
    element = $(element);
    while(element.length > 0) {
      var data = element.data('nested-fields.options');
      if(data) {
        return data;
      } else {
        element = element.parent();
      }
    }
    return null;
  }

  function prepareTemplate(options) {
    var regexp = new RegExp(options.newItemIndex, 'g');
    var newId = new Date().getTime();

    var contents = options.itemTemplate.html();
    var newItem = $(contents.replace(regexp, newId));
    newItem.attr('data-new-record', true);
    newItem.attr('data-record-id', newId);

    bindRemoveEvent(newItem, options);

    return newItem;
  }

  function insertItem(newItem, options) {
    removeNone(options);
    options.container.append(newItem);
  }

  function insertItemWithCallbacks(newItem, onInsertCallback, options) {
    function insert() {
      if(onInsertCallback) {
        onInsertCallback(newItem);
      }
      insertItem(newItem, options);
    }

    if(!options.skipBefore) {
      options.beforeInsert(newItem, insert);
    } else {
      insert();
    }

    if(!options.skipAfter) {
      options.afterInsert(newItem);
    }

    return newItem;
  }

  function removeItem(element, options) {
    var $element = $(element);
    options.beforeRemove($element, function() {
      if($element.attr('data-new-record')) { // record is new
        $element.remove();
      } else { // record should be marked and sent to server
        $element.find('INPUT[name*=_destroy]').val('true');
        $element.hide();
      }
      insertNone(options);
    });
    options.afterRemove($element);
    return $element;
  }

  function bindRemoveEvent(item, options) {
    $(item).find(options.removeHandler).bind('click.nested-fields', function(e) {
      e.preventDefault();
      removeItem(item, options);
    });
  }

  function insertNone(options) {
    if(findItems(options).length == 0) {
      options.container.append(options.noneTemplate.html());
    }
  }

  function removeNone(options) {
    findNone(options).remove();
  }

  function findItems(options) {
    return options.container.find(options.item + ':visible');
  }

  function findNone(options) {
    return options.container.find(options.none);
  }

})(jQuery);
<|endoftext|>


<commit_before>'use strict';

// TODO: getView should not be used because it is defined the other place
(function () {
    var onHashchange = (function () {
        var lastFragment = null;
        return function () {
            var view = getView();
            var session = view.session();
            if (session.id() === 0) {
                return;
            }
            var fragment = starChat.getFragment();
            if (fragment === lastFragment) {
                return;
            }
            lastFragment = fragment;
            view.channelName = '';
            if (fragment.match(/^channels\//)) {
                var params = {};
                var match;
                if (match = fragment.match(/^channels\/([^\/\?]+)(\?(.*))?$/)) {
                    var channelName = decodeURIComponent(match[1]);
                    if (match[3]) {
                        params = starChat.parseQuery(match[3]);
                    }
                } else {
                    return;
                }
                channelName = channelName.replace(/^\s*(.*?)\s*$/, '$1').replace(/(?![\n\r\t])[\x00-\x1f\x7f]/mg, '');
                channelName = channelName.substring(0, 32);
                var isAlreadyJoined = false;
                view.session().user().channels().forEach(function (channel) {
                    if (channel.name() === channelName) {
                        isAlreadyJoined = true;
                        return false;
                    }
                });
                if (isAlreadyJoined) {
                    var channel = starChat.Channel.find(channelName);
                    if (!channel.firstMessage()) {
                        channel.loadFirstMessage(session, function (sessionId) {
                            var view = getView();
                            if (view.session().id() !== sessionId) {
                                return;
                            }
                            if (!channel.firstMessage()) {
                                return;
                            }
                            view.update();
                        });
                    }
                    view.channelName = channelName;
                    view.update();
                    return;
                }
                // Confirming joining the new channel
                var msg = "Are you sure you want to join '" +
                    channelName + "'?"
                if (!confirm(msg)) {
                    return;
                }
                var subscribing = new starChat.Subscribing(channelName, session.user().name());
                if ('key' in params) {
                    subscribing.key(params.key);
                }
                subscribing.save(session, function (sessionId) {
                    var view = getView();
                    if (view.session().id() !== sessionId) {
                        return;
                    }
                    view.channelName = channelName;
                    view.update();
                    var channel = starChat.Channel.find(channelName);
                    channel.loadUsers(view.session(), function () {
                        var view = getView();
                        if (view.session().id() !== sessionId) {
                            return;
                        }
                        view.update();
                    });
                    if (!channel.firstMessage()) {
                        channel.loadFirstMessage(session, function (sessionId) {
                            var view = getView();
                            if (view.session().id() !== sessionId) {
                                return;
                            }
                            if (!channel.firstMessage()) {
                                return;
                            }
                            view.update();
                        });
                    }
                });
                return false;
            }
        }
    })();

    $(window).bind('hashchange', onHashchange);
})();
<commit_msg>Hiding the timeline when changing the channel<commit_after>'use strict';

// TODO: getView should not be used because it is defined the other place
(function () {
    var onHashchange = (function () {
        var lastFragment = null;
        return function () {
            var view = getView();
            var session = view.session();
            if (session.id() === 0) {
                return;
            }
            var fragment = starChat.getFragment();
            if (fragment === lastFragment) {
                return;
            }
            lastFragment = fragment;
            $('#timeline').hide();
            view.channelName = '';
            if (fragment.match(/^channels\//)) {
                var params = {};
                var match;
                if (match = fragment.match(/^channels\/([^\/\?]+)(\?(.*))?$/)) {
                    var channelName = decodeURIComponent(match[1]);
                    if (match[3]) {
                        params = starChat.parseQuery(match[3]);
                    }
                } else {
                    return;
                }
                channelName = channelName.replace(/^\s*(.*?)\s*$/, '$1').replace(/(?![\n\r\t])[\x00-\x1f\x7f]/mg, '');
                channelName = channelName.substring(0, 32);
                var isAlreadyJoined = false;
                view.session().user().channels().forEach(function (channel) {
                    if (channel.name() === channelName) {
                        isAlreadyJoined = true;
                        return false;
                    }
                });
                if (isAlreadyJoined) {
                    var channel = starChat.Channel.find(channelName);
                    if (!channel.firstMessage()) {
                        channel.loadFirstMessage(session, function (sessionId) {
                            var view = getView();
                            if (view.session().id() !== sessionId) {
                                return;
                            }
                            if (!channel.firstMessage()) {
                                return;
                            }
                            view.update();
                        });
                    }
                    view.channelName = channelName;
                    view.update();
                    return;
                }
                // Confirming joining the new channel
                var msg = "Are you sure you want to join '" +
                    channelName + "'?"
                if (!confirm(msg)) {
                    return;
                }
                var subscribing = new starChat.Subscribing(channelName, session.user().name());
                if ('key' in params) {
                    subscribing.key(params.key);
                }
                subscribing.save(session, function (sessionId) {
                    var view = getView();
                    if (view.session().id() !== sessionId) {
                        return;
                    }
                    view.channelName = channelName;
                    view.update();
                    var channel = starChat.Channel.find(channelName);
                    channel.loadUsers(view.session(), function () {
                        var view = getView();
                        if (view.session().id() !== sessionId) {
                            return;
                        }
                        view.update();
                    });
                    if (!channel.firstMessage()) {
                        channel.loadFirstMessage(session, function (sessionId) {
                            var view = getView();
                            if (view.session().id() !== sessionId) {
                                return;
                            }
                            if (!channel.firstMessage()) {
                                return;
                            }
                            view.update();
                        });
                    }
                });
                return false;
            }
        }
    })();

    $(window).bind('hashchange', onHashchange);
})();
<|endoftext|>


<commit_before>angularIO.directive('apiList', function () {
  return {
    restrict: 'E',
    template:
      '
' +
      '
' +
      '
Display:
' +
      '
{{apiType.title}}
' +
      '
' +
      '  <input placeholder="Filter" ng-model="$ctrl.apiFilter" class="api-filter">' +
      '
' +
      '
' +
      '
' +
      '
{{ section.title }}
' +
      '
' +
      '
' +
      '        {{ item.title }}' +
      '
' +
      '
' +
      '
' +
      '
',
    controllerAs: '$ctrl',
    controller: function($scope, $attrs, $http) {
      var $ctrl = this;

      $ctrl.apiType     = null;
      $ctrl.apiFilter   = '';
      $ctrl.filteredSections = [];

      $ctrl.setType = function (type) {
        if (type === $ctrl.apiType) $ctrl.apiType = null;
        else $ctrl.apiType = type;
      };

      $http.get($attrs.src).then(function(response) {
        $ctrl.sections = response.data;
      });

      $ctrl.apiTypes = [
        { cssClass: 'directive', title: 'Directive', matches: ['directive'] },
        { cssClass: 'class', title: 'Class', matches: ['class'] },
        { cssClass: 'interface', title: 'Interface', matches: ['interface'] },
        { cssClass: 'function', title: 'function', matches: ['function'] },
        { cssClass: 'const', title: 'Const or Enum', matches: ['const', 'enum'] },
        { cssClass: 'var', title: 'Variable', matches: ['var', 'let'] }
      ];

      $scope.$watchGroup(
        [function() { return $ctrl.apiFilter}, function() { return $ctrl.apiType; }, function() { return $ctrl.sections; }],
        function(filter, type, sections) {
          $ctrl.filteredSections.length = 0;
          angular.forEach($ctrl.sections, function(section, title) {
            var matchesModule = $ctrl.apiFilter == '' || title.toLowerCase().indexOf($ctrl.apiFilter.toLowerCase()) !== -1;
            var filteredItems = section.filter(function(item) {
              var matchesDocType = !$ctrl.apiType || $ctrl.apiType.matches.indexOf(item.docType) !== -1;
              var matchesTitle = $ctrl.apiFilter == '' || item.title.toLowerCase().indexOf($ctrl.apiFilter.toLowerCase()) !== -1;
              return matchesDocType && (matchesTitle || matchesModule);
            });
            if (filteredItems.length) {
              $ctrl.filteredSections.push({ title: title, items: filteredItems });
            }
          });
        }
      );
    }
  };
});<commit_msg>feat(api-list): sync filters with URL query<commit_after>angularIO.directive('apiList', function () {
  var API_FILTER_KEY = 'apiFilter';
  var API_TYPE_KEY = 'apiType';
  return {
    restrict: 'E',
    template:
      '
' +
      '
' +
      '
Display:
' +
      '
{{apiType.title}}
' +
      '
' +
      '  <input placeholder="Filter" ng-model="$ctrl.apiFilter" class="api-filter">' +
      '
' +
      '
' +
      '
' +
      '
{{ section.title }}
' +
      '
' +
      '
' +
      '        {{ item.title }}' +
      '
' +
      '
' +
      '
' +
      '
',
    controllerAs: '$ctrl',
    controller: function($scope, $attrs, $http, $location) {
      var $ctrl = this;

      $ctrl.apiTypes = [
        { cssClass: 'directive', title: 'Directive', matches: ['directive'] },
        { cssClass: 'class', title: 'Class', matches: ['class'] },
        { cssClass: 'interface', title: 'Interface', matches: ['interface'] },
        { cssClass: 'function', title: 'function', matches: ['function'] },
        { cssClass: 'const', title: 'Const or Enum', matches: ['const', 'enum'] },
        { cssClass: 'var', title: 'Variable', matches: ['var', 'let'] }
      ];

      $ctrl.apiFilter = getApiFilterFromLocation();
      $ctrl.apiType = getApiTypeFromLocation();
      $ctrl.filteredSections = [];

      $ctrl.setType = function (type) {
        if (type === $ctrl.apiType) $ctrl.apiType = null;
        else $ctrl.apiType = type;
      };

      $http.get($attrs.src).then(function(response) {
        $ctrl.sections = response.data;
      });

      $scope.$watchGroup(
        [function() { return $ctrl.apiFilter}, function() { return $ctrl.apiType; }, function() { return $ctrl.sections; }],
        function() {
          var apiFilter = ($ctrl.apiFilter || '').toLowerCase();

          $location.search(API_FILTER_KEY, apiFilter || null);
          $location.search(API_TYPE_KEY, $ctrl.apiType && $ctrl.apiType.title || null);

          $ctrl.filteredSections.length = 0;
          angular.forEach($ctrl.sections, function(section, title) {
            var matchesModule = $ctrl.apiFilter === '' || $ctrl.apiFilter === null || title.toLowerCase().indexOf($ctrl.apiFilter.toLowerCase()) !== -1;
            var filteredItems = section.filter(function(item) {
              var matchesDocType = !$ctrl.apiType || $ctrl.apiType.matches.indexOf(item.docType) !== -1;
              var matchesTitle = !apiFilter || item.title.toLowerCase().indexOf(apiFilter) !== -1;
              return matchesDocType && (matchesTitle || matchesModule);
            });
            if (filteredItems.length) {
              $ctrl.filteredSections.push({ title: title, items: filteredItems });
            }
          });
        }
      );

      function getApiFilterFromLocation() {
        return $location.search()[API_FILTER_KEY] || null;
      }

      function getApiTypeFromLocation() {
        var apiFilter = $location.search()[API_TYPE_KEY];
        if (!apiFilter) {
          return null;
        } else if (!$ctrl.apiFilter || $ctrl.apiFilter.title != apiFilter) {
          for(var i = 0, ii = $ctrl.apiTypes.length; i < ii; i++) {
            if ($ctrl.apiTypes[i].title == apiFilter) {
              return $ctrl.apiTypes[i];
            }
          }
        }
        // If we get here then the apiType query didn't match any apiTypes
        $location.search(API_TYPE_KEY, null);
      }
    }
  };
});<|endoftext|>